323 lines
10 KiB
Ruby
Executable file
323 lines
10 KiB
Ruby
Executable file
module Additionals
|
|
module Helpers
|
|
def link_to_external(name, link, options = {})
|
|
options[:class] ||= 'external'
|
|
options[:class] << ' external' if options[:class].exclude? 'external'
|
|
|
|
options[:rel] ||= 'noopener'
|
|
options[:target] ||= '_blank'
|
|
|
|
link_to name, link, options
|
|
end
|
|
|
|
def additionals_list_title(options)
|
|
title = []
|
|
if options[:issue]
|
|
title << link_to(h("#{options[:issue].subject} ##{options[:issue].id}"),
|
|
issue_path(options[:issue]),
|
|
class: options[:issue].css_classes)
|
|
elsif options[:user]
|
|
title << safe_join([avatar(options[:user], size: 50), options[:user].name], ' ')
|
|
end
|
|
title << options[:name] if options[:name]
|
|
title << h(options[:query].name) if options[:query] && !options[:query].new_record?
|
|
safe_join title, Additionals::LIST_SEPARATOR
|
|
end
|
|
|
|
def additionals_title_for_locale(title, lang)
|
|
"#{title}_#{lang}"
|
|
end
|
|
|
|
def additionals_titles_for_locale(title)
|
|
languages = [title.to_sym]
|
|
valid_languages.each do |lang|
|
|
languages << additionals_title_for_locale(title, lang).to_sym if lang.to_s.exclude? '-'
|
|
end
|
|
languages
|
|
end
|
|
|
|
def additionals_i18n_title(options, title)
|
|
i18n_title = "#{title}_#{::I18n.locale}".to_sym
|
|
if options.key? i18n_title
|
|
options[i18n_title]
|
|
elsif options.key? title
|
|
options[title]
|
|
end
|
|
end
|
|
|
|
def render_issue_macro_link(issue, text, comment_id = nil)
|
|
only_path = controller_path.split('_').last != 'mailer'
|
|
content = link_to(text, issue_url(issue, only_path: only_path), class: issue.css_classes)
|
|
if comment_id.nil?
|
|
content
|
|
else
|
|
render_issue_with_comment issue, content, comment_id, only_path: only_path
|
|
end
|
|
end
|
|
|
|
def render_issue_with_comment(issue, content, comment_id, only_path: false)
|
|
journal = issue.journals.select(:notes, :private_notes, :user_id).offset(comment_id - 1).limit(1).first
|
|
comment = if journal
|
|
user = User.current
|
|
if user.allowed_to?(:view_private_notes, issue.project) ||
|
|
!journal.private_notes? ||
|
|
journal.user == user
|
|
journal.notes
|
|
end
|
|
end
|
|
|
|
if comment.blank?
|
|
comment = 'N/A'
|
|
comment_link = comment_id
|
|
else
|
|
comment_link = link_to(comment_id, issue_url(issue, only_path: only_path, anchor: "note-#{comment_id}"))
|
|
end
|
|
|
|
tag.div class: 'issue-macro box' do
|
|
tag.div(safe_join([content, '-', l(:label_comment), comment_link], ' '), class: 'issue-macro-subject') +
|
|
tag.div(textilizable(comment), class: 'issue-macro-comment journal has-notes')
|
|
end
|
|
end
|
|
|
|
def memberships_new_issue_project_url(user, memberships, permission = :edit_issues)
|
|
return if memberships.blank?
|
|
|
|
project_count = 0
|
|
project_id = nil
|
|
memberships.each do |m|
|
|
project = m.is_a?(Project) ? m : Project.find_by(id: m.project_id)
|
|
next unless User.current.allowed_to?(permission, project) && user.issues_assignable?(project)
|
|
|
|
project_count += 1
|
|
break if project_count > 1
|
|
|
|
project_id = project.identifier
|
|
end
|
|
|
|
return if project_id.nil?
|
|
|
|
# if more than one projects available, we do not use project url for a new issue
|
|
if project_count > 1
|
|
if permission == :edit_issues
|
|
new_issue_path('issue[assigned_to_id]' => user.id, 'issue[project_id]' => project_id)
|
|
else
|
|
new_issue_path('issue[project_id]' => project_id)
|
|
end
|
|
elsif permission == :edit_issues
|
|
new_project_issue_path(project_id, 'issue[assigned_to_id]' => user.id)
|
|
else
|
|
new_project_issue_path(project_id)
|
|
end
|
|
end
|
|
|
|
def parse_issue_url(url, comment_id = nil)
|
|
rc = { issue_id: nil, comment_id: nil }
|
|
return rc if url == '' || url.is_a?(Integer) && url.zero?
|
|
|
|
unless url.to_i.zero?
|
|
rc[:issue_id] = url
|
|
return rc
|
|
end
|
|
|
|
uri = URI.parse(url)
|
|
# support issue_id plugin
|
|
# see https://www.redmine.org/plugins/issue_id
|
|
issue_id_parts = url.split('-')
|
|
if uri.scheme.nil? && uri.path[0] != '/' && issue_id_parts.count == 2
|
|
rc[:issue_id] = url
|
|
else
|
|
s_pos = uri.path.rindex '/issues/'
|
|
return rc unless s_pos
|
|
|
|
id_string = uri.path[s_pos + 8..-1]
|
|
e_pos = id_string.index '/'
|
|
rc[:issue_id] = e_pos.nil? ? id_string : id_string[0..e_pos - 1]
|
|
# check for comment_id
|
|
rc[:comment_id] = uri.fragment[5..-1].to_i if comment_id.nil? && uri.fragment.present? && uri.fragment[0..4] == 'note-'
|
|
end
|
|
|
|
rc
|
|
end
|
|
|
|
def additionals_library_load(module_names)
|
|
s = []
|
|
Array(module_names).each do |module_name|
|
|
s << send("additionals_load_#{module_name}")
|
|
end
|
|
safe_join s
|
|
end
|
|
|
|
def autocomplete_select_entries(name, type, option_tags, options = {})
|
|
unless option_tags.is_a?(String) || option_tags.blank?
|
|
# if option_tags is not an array, it should be an object
|
|
option_tags = options_for_select([[option_tags.try(:name), option_tags.try(:id)]], option_tags.try(:id))
|
|
end
|
|
options[:project] = @project if @project && options[:project].blank?
|
|
|
|
s = []
|
|
s << hidden_field_tag("#{name}[]", '') if options[:multiple]
|
|
s << select_tag(name,
|
|
option_tags,
|
|
include_blank: options[:include_blank],
|
|
multiple: options[:multiple],
|
|
disabled: options[:disabled])
|
|
s << render(layout: false,
|
|
partial: 'additionals/select2_ajax_call.js',
|
|
formats: [:js],
|
|
locals: { field_id: sanitize_to_id(name),
|
|
ajax_url: send("#{type}_path", project_id: options[:project], user_id: options[:user_id]),
|
|
options: options })
|
|
safe_join s
|
|
end
|
|
|
|
def project_list_css_classes(project, level)
|
|
classes = [cycle('odd', 'even')]
|
|
classes += project.css_classes.split
|
|
if level.positive?
|
|
classes << 'idnt'
|
|
classes << "idnt-#{level}"
|
|
end
|
|
classes.join ' '
|
|
end
|
|
|
|
def addtionals_textarea_cols(text, options = {})
|
|
[[(options[:min].presence || 8), text.to_s.length / 50].max, (options[:max].presence || 20)].min
|
|
end
|
|
|
|
def title_with_fontawesome(title, symbole, wrapper = 'span')
|
|
tag.send(wrapper) do
|
|
concat tag.i class: "#{symbole} for-fa-title", 'aria-hidden': 'true'
|
|
concat title
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def additionals_already_loaded(scope, js_name)
|
|
locked = "#{js_name}.#{scope}"
|
|
@alreaded_loaded = [] if @alreaded_loaded.nil?
|
|
return true if @alreaded_loaded.include?(locked)
|
|
|
|
@alreaded_loaded << locked
|
|
false
|
|
end
|
|
|
|
def additionals_include_js(js_name)
|
|
if additionals_already_loaded 'js', js_name
|
|
''
|
|
else
|
|
javascript_include_tag js_name, plugin: 'additionals'
|
|
end
|
|
end
|
|
|
|
def additionals_include_css(css)
|
|
if additionals_already_loaded 'css', css
|
|
''
|
|
else
|
|
stylesheet_link_tag css, plugin: 'additionals'
|
|
end
|
|
end
|
|
|
|
def additionals_load_select2
|
|
additionals_include_css('select2') +
|
|
additionals_include_js('select2.min') +
|
|
additionals_include_js('select2_helper')
|
|
end
|
|
|
|
def additionals_load_clipboardjs
|
|
additionals_include_js 'clipboard.min'
|
|
end
|
|
|
|
def additionals_load_font_awesome
|
|
additionals_include_css 'fontawesome-all.min'
|
|
end
|
|
|
|
def additionals_load_chartjs
|
|
additionals_include_css('Chart.min') +
|
|
additionals_include_js('Chart.bundle.min')
|
|
end
|
|
|
|
def additionals_load_chartjs_datalabels
|
|
additionals_include_js 'chartjs-plugin-datalabels.min'
|
|
end
|
|
|
|
def additionals_load_chartjs_colorschemes
|
|
additionals_include_js 'chartjs-plugin-colorschemes.min'
|
|
end
|
|
|
|
def additionals_load_mermaid
|
|
additionals_include_js('mermaid.min') +
|
|
additionals_include_js('mermaid_load')
|
|
end
|
|
|
|
def additionals_load_d3
|
|
additionals_include_js 'd3.min'
|
|
end
|
|
|
|
def additionals_load_d3plus
|
|
additionals_include_js 'd3plus.full.min'
|
|
end
|
|
|
|
def additionals_load_d3plus_old
|
|
additionals_include_js 'd3plus-old.full.min'
|
|
end
|
|
|
|
def additionals_load_d3plus_hierarchy
|
|
additionals_include_js 'd3plus-hierarchy.full'
|
|
end
|
|
|
|
def additionals_load_d3plus_network
|
|
additionals_include_js 'd3plus-network.full.min'
|
|
end
|
|
|
|
def user_with_avatar(user, options = {})
|
|
return if user.nil?
|
|
|
|
if user.type == 'Group'
|
|
if options[:no_link] || !Redmine::Plugin.installed?('redmine_hrm')
|
|
user.name
|
|
else
|
|
link_to_hrm_group user
|
|
end
|
|
else
|
|
options[:size] = 14 if options[:size].nil?
|
|
options[:class] = 'additionals-avatar' if options[:class].nil?
|
|
s = []
|
|
s << avatar(user, options)
|
|
s << if options[:no_link]
|
|
user.name
|
|
else
|
|
link_to_user user
|
|
end
|
|
safe_join s
|
|
end
|
|
end
|
|
|
|
def options_for_menu_select(active)
|
|
options_for_select({ l(:button_hide) => '',
|
|
l(:label_top_menu) => 'top',
|
|
l(:label_app_menu) => 'app' }, active)
|
|
end
|
|
|
|
def human_float_number(value, options = {})
|
|
ActionController::Base.helpers.number_with_precision(value,
|
|
precision: options[:precision].presence || 2,
|
|
separator: options[:separator].presence || '.',
|
|
strip_insignificant_zeros: true)
|
|
end
|
|
|
|
def query_list_back_url_tag(project = nil, params = nil)
|
|
url = if controller_name == 'dashboard_async_blocks' && request.query_parameters.key?('dashboard_id')
|
|
dashboard_link_path project,
|
|
Dashboard.find_by(id: request.query_parameters['dashboard_id']),
|
|
refresh: 1
|
|
elsif params.nil?
|
|
url_for params: request.query_parameters
|
|
else
|
|
url_for params: params
|
|
end
|
|
|
|
hidden_field_tag 'back_url', url, id: nil
|
|
end
|
|
end
|
|
end
|