Nuevo plugin Additionals 2.0.20
This commit is contained in:
parent
a2a901b71b
commit
93e1e28683
354 changed files with 40514 additions and 0 deletions
131
plugins/additionals/lib/additionals.rb
Executable file
131
plugins/additionals/lib/additionals.rb
Executable file
|
@ -0,0 +1,131 @@
|
|||
module Additionals
|
||||
MAX_CUSTOM_MENU_ITEMS = 5
|
||||
SELECT2_INIT_ENTRIES = 20
|
||||
|
||||
GOTO_LIST = " \xc2\xbb".freeze
|
||||
LIST_SEPARATOR = GOTO_LIST + ' '
|
||||
|
||||
class << self
|
||||
def setup
|
||||
incompatible_plugins(%w[redmine_tweaks
|
||||
redmine_issue_control_panel
|
||||
redmine_editauthor
|
||||
redmine_changeauthor
|
||||
redmine_auto_watch])
|
||||
patch(%w[AccountController
|
||||
Issue
|
||||
IssuePriority
|
||||
TimeEntry
|
||||
Project
|
||||
Wiki
|
||||
WikiController
|
||||
Principal
|
||||
QueryFilter
|
||||
Role
|
||||
User
|
||||
UserPreference])
|
||||
|
||||
Rails.configuration.assets.paths << Emoji.images_path
|
||||
Redmine::WikiFormatting.format_names.each do |format|
|
||||
case format
|
||||
when 'markdown'
|
||||
Redmine::WikiFormatting::Markdown::HTML.send(:include, Patches::FormatterMarkdownPatch)
|
||||
Redmine::WikiFormatting::Markdown::Helper.send(:include, Patches::FormattingHelperPatch)
|
||||
when 'textile'
|
||||
Redmine::WikiFormatting::Textile::Formatter.send(:include, Patches::FormatterTextilePatch)
|
||||
Redmine::WikiFormatting::Textile::Helper.send(:include, Patches::FormattingHelperPatch)
|
||||
end
|
||||
end
|
||||
|
||||
# Static class patches
|
||||
IssuesController.send(:helper, AdditionalsIssuesHelper)
|
||||
WikiController.send(:helper, AdditionalsWikiPdfHelper)
|
||||
Redmine::AccessControl.send(:include, Additionals::Patches::AccessControlPatch)
|
||||
|
||||
# Global helpers
|
||||
ActionView::Base.send :include, Additionals::Helpers
|
||||
ActionView::Base.send :include, AdditionalsFontawesomeHelper
|
||||
ActionView::Base.send :include, AdditionalsMenuHelper
|
||||
|
||||
# Hooks
|
||||
require_dependency 'additionals/hooks'
|
||||
|
||||
# Macros
|
||||
load_macros(%w[calendar cryptocompare date fa gist gmap group_users iframe
|
||||
issue redmine_issue redmine_wiki
|
||||
last_updated_at last_updated_by meteoblue member new_issue project
|
||||
recently_updated reddit slideshare tradingview twitter user vimeo youtube])
|
||||
end
|
||||
|
||||
def settings
|
||||
settings_compatible(:plugin_additionals)
|
||||
end
|
||||
|
||||
def settings_compatible(plugin_name)
|
||||
if Setting[plugin_name].class == Hash
|
||||
if Rails.version >= '5.2'
|
||||
# convert Rails 4 data (this runs only once)
|
||||
new_settings = ActiveSupport::HashWithIndifferentAccess.new(Setting[plugin_name])
|
||||
Setting.send("#{plugin_name}=", new_settings)
|
||||
new_settings
|
||||
else
|
||||
ActionController::Parameters.new(Setting[plugin_name])
|
||||
end
|
||||
else
|
||||
# Rails 5 uses ActiveSupport::HashWithIndifferentAccess
|
||||
Setting[plugin_name]
|
||||
end
|
||||
end
|
||||
|
||||
def setting?(value)
|
||||
true?(settings[value])
|
||||
end
|
||||
|
||||
def true?(value)
|
||||
return true if value.to_i == 1 || value.to_s.casecmp('true').zero?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def now_with_user_time_zone(user = User.current)
|
||||
if user.time_zone.nil?
|
||||
Time.zone.now
|
||||
else
|
||||
user.time_zone.now
|
||||
end
|
||||
end
|
||||
|
||||
def incompatible_plugins(plugins = [], title = 'additionals')
|
||||
plugins.each do |plugin|
|
||||
raise "\n\033[31m#{title} plugin cannot be used with #{plugin} plugin'.\033[0m" if Redmine::Plugin.installed?(plugin)
|
||||
end
|
||||
end
|
||||
|
||||
def patch(patches = [], plugin_id = 'additionals')
|
||||
patches.each do |name|
|
||||
patch_dir = Rails.root.join('plugins', plugin_id, 'lib', plugin_id, 'patches')
|
||||
require "#{patch_dir}/#{name.underscore}_patch"
|
||||
|
||||
target = name.constantize
|
||||
patch = "#{plugin_id.camelize}::Patches::#{name}Patch".constantize
|
||||
|
||||
target.send(:include, patch) unless target.included_modules.include?(patch)
|
||||
end
|
||||
end
|
||||
|
||||
def load_macros(macros = [], plugin_id = 'additionals')
|
||||
macro_dir = Rails.root.join('plugins', plugin_id, 'lib', plugin_id, 'wiki_macros')
|
||||
macros.each do |macro|
|
||||
require_dependency "#{macro_dir}/#{macro.underscore}_macro"
|
||||
end
|
||||
end
|
||||
|
||||
def load_settings(plugin_id = 'additionals')
|
||||
data = YAML.safe_load(ERB.new(IO.read(Rails.root.join('plugins',
|
||||
plugin_id,
|
||||
'config',
|
||||
'settings.yml'))).result) || {}
|
||||
data.symbolize_keys
|
||||
end
|
||||
end
|
||||
end
|
78
plugins/additionals/lib/additionals/formatter.rb
Executable file
78
plugins/additionals/lib/additionals/formatter.rb
Executable file
|
@ -0,0 +1,78 @@
|
|||
# Formater
|
||||
module Additionals
|
||||
module Formatter
|
||||
SMILEYS = { 'smiley' => ':-?\)', # :)
|
||||
'smiley2' => '=-?\)', # =)
|
||||
'laughing' => ':-?D', # :D
|
||||
'laughing2' => '[=]-?D', # =D
|
||||
'crying' => '[=:][\'*]\(', # :'(
|
||||
'sad' => '[=:]-?\(', # :(
|
||||
'wink' => ';-?[)D]', # ;)
|
||||
'cheeky' => '[=:]-?[Ppb]', # :P
|
||||
'shock' => '[=:]-?[Oo0]', # :O
|
||||
'annoyed' => '[=:]-?[\\/]', # :/
|
||||
'confuse' => '[=:]-?S', # :S
|
||||
'straight' => '[=:]-?[\|\]]', # :|
|
||||
'embarrassed' => '[=:]-?[Xx]', # :X
|
||||
'kiss' => '[=:]-?\*', # :*
|
||||
'angel' => '[Oo][=:]-?\)', # O:)
|
||||
'evil' => '>[=:;]-?[)(]', # >:)
|
||||
'rock' => 'B-?\)', # B)
|
||||
'rose' => '@[)\}][-\\/\',;()>\}]*', # @}->-
|
||||
'exclamation' => '[\[(]![\])]', # (!)
|
||||
'question' => '[\[(]\?[\])]', # (?)
|
||||
'check' => '[\[(]\\/[\])]', # (/)
|
||||
'success' => '[\[(]v[\])]', # (v)
|
||||
'failure' => '[\[(]x[\])]' }.freeze # (x)
|
||||
|
||||
def render_inline_smileys(text)
|
||||
return text if text.blank?
|
||||
|
||||
inline_smileys(text)
|
||||
text
|
||||
end
|
||||
|
||||
def inline_smileys(text)
|
||||
SMILEYS.each do |name, regexp|
|
||||
text.gsub!(/(\s|^|>|\))(!)?(#{regexp})(?=\W|$|<)/m) do
|
||||
leading = Regexp.last_match(1)
|
||||
esc = Regexp.last_match(2)
|
||||
smiley = Regexp.last_match(3)
|
||||
if esc.nil?
|
||||
leading + content_tag(:span,
|
||||
'',
|
||||
class: "additionals smiley smiley-#{name}",
|
||||
title: smiley)
|
||||
else
|
||||
leading + smiley
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def inline_emojify(text)
|
||||
text.gsub!(/:([\w+-]+):/) do |match|
|
||||
emoji_code = Regexp.last_match(1)
|
||||
emoji = Emoji.find_by_alias(emoji_code) # rubocop:disable Rails/DynamicFindBy
|
||||
if emoji.present?
|
||||
tag(:img,
|
||||
src: inline_emojify_image_path(emoji.image_filename),
|
||||
title: ":#{emoji_code}:",
|
||||
style: 'vertical-align: middle',
|
||||
width: '20',
|
||||
height: '20')
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
text
|
||||
end
|
||||
|
||||
def inline_emojify_image_path(image_filename)
|
||||
path = Setting.protocol + '://' + Setting.host_name
|
||||
# TODO: use relative path, if not for mailer
|
||||
# path = '/' + Rails.public_path.relative_path_from Rails.root.join('public')
|
||||
"#{path}/images/emoji/" + image_filename
|
||||
end
|
||||
end
|
||||
end
|
326
plugins/additionals/lib/additionals/helpers.rb
Executable file
326
plugins/additionals/lib/additionals/helpers.rb
Executable file
|
@ -0,0 +1,326 @@
|
|||
# Global helper functions
|
||||
module Additionals
|
||||
module Helpers
|
||||
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 << 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 additionals_settings_tabs
|
||||
tabs = [{ name: 'general', partial: 'additionals/settings/general', label: :label_general },
|
||||
{ name: 'content', partial: 'additionals/settings/overview', label: :label_overview_page },
|
||||
{ name: 'wiki', partial: 'additionals/settings/wiki', label: :label_wiki },
|
||||
{ name: 'macros', partial: 'additionals/settings/macros', label: :label_macro_plural },
|
||||
{ name: 'rules', partial: 'additionals/settings/issues', label: :label_issue_plural },
|
||||
{ name: 'projects', partial: 'additionals/settings/projects', label: :label_project_plural },
|
||||
{ name: 'users', partial: 'additionals/settings/users', label: :label_user_plural },
|
||||
{ name: 'web', partial: 'additionals/settings/web_apis', label: :label_web_apis }]
|
||||
|
||||
if User.current.try(:hrm_user_type_id).nil?
|
||||
tabs << { name: 'menu', partial: 'additionals/settings/menu', label: :label_settings_menu }
|
||||
end
|
||||
|
||||
tabs
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
def render_issue_with_comment(issue, content, comment_id, only_path = false)
|
||||
comment = issue.journals
|
||||
.where(private_notes: false)
|
||||
.offset(comment_id - 1).limit(1).first.try(:notes)
|
||||
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
|
||||
|
||||
content_tag :div, class: 'issue-macro box' do
|
||||
content_tag(:div, safe_join([content, '-', l(:label_comment), comment_link], ' '), class: 'issue-macro-subject') +
|
||||
content_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
|
||||
if request.nil?
|
||||
# this is used by mailer
|
||||
return rc if url.exclude?(Setting.host_name)
|
||||
elsif uri.host != URI.parse(request.original_url).host
|
||||
return rc
|
||||
end
|
||||
|
||||
s_pos = uri.path.rindex('/issues/')
|
||||
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_name)
|
||||
method = "additionals_load_#{module_name}"
|
||||
send(method)
|
||||
end
|
||||
|
||||
def system_uptime
|
||||
if windows_platform?
|
||||
`net stats srv | find "Statist"`
|
||||
elsif File.exist?('/proc/uptime')
|
||||
secs = `cat /proc/uptime`.to_i
|
||||
min = 0
|
||||
hours = 0
|
||||
days = 0
|
||||
if secs > 0
|
||||
min = (secs / 60).round
|
||||
hours = (secs / 3_600).round
|
||||
days = (secs / 86_400).round
|
||||
end
|
||||
if days >= 1
|
||||
"#{days} #{l(:days, count: days)}"
|
||||
elsif hours >= 1
|
||||
"#{hours} #{l(:hours, count: hours)}"
|
||||
else
|
||||
"#{min} #{l(:minutes, count: min)}"
|
||||
end
|
||||
else
|
||||
days = `uptime | awk '{print $3}'`.to_i.round
|
||||
"#{days} #{l(:days, count: days)}"
|
||||
end
|
||||
end
|
||||
|
||||
def system_info
|
||||
if windows_platform?
|
||||
'unknown'
|
||||
else
|
||||
`uname -a`
|
||||
end
|
||||
end
|
||||
|
||||
def windows_platform?
|
||||
true if /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
|
||||
end
|
||||
|
||||
def bootstrap_datepicker_locale
|
||||
s = ''
|
||||
locale = User.current.language.presence || ::I18n.locale
|
||||
locale = 'es' if locale == 'es-PA'
|
||||
locale = 'sr-latin' if locale == 'sr-YU'
|
||||
s = javascript_include_tag("locales/bootstrap-datepicker.#{locale.downcase}.min", plugin: 'additionals') unless locale == 'en'
|
||||
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.present? && 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
|
||||
|
||||
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') + "\n"
|
||||
end
|
||||
end
|
||||
|
||||
def additionals_include_css(css)
|
||||
if additionals_already_loaded('css', css)
|
||||
''
|
||||
else
|
||||
stylesheet_link_tag(css, plugin: 'additionals') + "\n"
|
||||
end
|
||||
end
|
||||
|
||||
def additionals_load_select2
|
||||
additionals_include_js('additionals_to_select2')
|
||||
end
|
||||
|
||||
def additionals_load_observe_field
|
||||
additionals_include_js('additionals_observe_field')
|
||||
end
|
||||
|
||||
def additionals_load_font_awesome
|
||||
additionals_include_css('fontawesome-all.min')
|
||||
end
|
||||
|
||||
def additionals_load_nvd3
|
||||
additionals_include_css('nv.d3.min') +
|
||||
additionals_include_js('d3.min') +
|
||||
additionals_include_js('nv.d3.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_zeroclipboard
|
||||
additionals_include_js('zeroclipboard_min')
|
||||
end
|
||||
|
||||
def user_with_avatar(user, options = {})
|
||||
return if user.nil?
|
||||
|
||||
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
|
||||
|
||||
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 options_for_overview_select(active)
|
||||
options_for_select({ l(:button_hide) => '',
|
||||
l(:show_on_redmine_home) => 'home',
|
||||
l(:show_on_project_overview) => 'project',
|
||||
l(:show_always) => 'always' }, active)
|
||||
end
|
||||
|
||||
def options_for_welcome_select(active)
|
||||
options_for_select({ l(:button_hide) => '',
|
||||
l(:show_welcome_left) => 'left',
|
||||
l(:show_welcome_right) => 'right' }, 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
|
||||
end
|
||||
end
|
33
plugins/additionals/lib/additionals/hooks.rb
Executable file
33
plugins/additionals/lib/additionals/hooks.rb
Executable file
|
@ -0,0 +1,33 @@
|
|||
# Redmine hooks
|
||||
module Additionals
|
||||
class AdditionalsHookListener < Redmine::Hook::ViewListener
|
||||
include IssuesHelper
|
||||
include AdditionalsIssuesHelper
|
||||
|
||||
render_on(:view_layouts_base_html_head, partial: 'additionals/html_head')
|
||||
render_on(:view_layouts_base_content, partial: 'additionals/content')
|
||||
render_on(:view_layouts_base_body_bottom, partial: 'additionals/body_bottom')
|
||||
|
||||
render_on(:view_account_login_bottom, partial: 'login_text')
|
||||
render_on(:view_issues_context_menu_start, partial: 'additionals_closed_issues')
|
||||
render_on(:view_issues_bulk_edit_details_bottom, partial: 'change_author_bulk')
|
||||
render_on(:view_issues_form_details_bottom, partial: 'change_author')
|
||||
render_on(:view_issues_new_top, partial: 'new_ticket_message')
|
||||
render_on(:view_issues_sidebar_issues_bottom, partial: 'issues/additionals_sidebar')
|
||||
render_on(:view_issues_sidebar_queries_bottom, partial: 'additionals/global_sidebar')
|
||||
render_on(:view_projects_show_right, partial: 'project_overview')
|
||||
render_on(:view_projects_show_sidebar_bottom, partial: 'additionals/global_sidebar')
|
||||
render_on(:view_welcome_index_right, partial: 'overview_right')
|
||||
render_on(:view_my_account_preferences, partial: 'users/autowatch_involved_issue')
|
||||
render_on(:view_users_form_preferences, partial: 'users/autowatch_involved_issue')
|
||||
render_on(:view_users_show_contextual, partial: 'users/additionals_contextual')
|
||||
|
||||
def helper_issues_show_detail_after_setting(context = {})
|
||||
d = context[:detail]
|
||||
return unless d.prop_key == 'author_id'
|
||||
|
||||
d[:value] = find_name_by_reflection('author', d.value)
|
||||
d[:old_value] = find_name_by_reflection('author', d.old_value)
|
||||
end
|
||||
end
|
||||
end
|
18
plugins/additionals/lib/additionals/patches/access_control_patch.rb
Executable file
18
plugins/additionals/lib/additionals/patches/access_control_patch.rb
Executable file
|
@ -0,0 +1,18 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module AccessControlPatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
def self.available_project_modules
|
||||
@available_project_modules = available_project_modules_all
|
||||
.reject { |m| Additionals.settings[:disabled_modules].to_a.include?(m.to_s) }
|
||||
end
|
||||
|
||||
def self.available_project_modules_all
|
||||
@permissions.collect(&:project_module).uniq.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
plugins/additionals/lib/additionals/patches/account_controller_patch.rb
Executable file
11
plugins/additionals/lib/additionals/patches/account_controller_patch.rb
Executable file
|
@ -0,0 +1,11 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module AccountControllerPatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
invisible_captcha only: [:register] if Additionals.setting?(:invisible_captcha)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
plugins/additionals/lib/additionals/patches/formatter_markdown_patch.rb
Executable file
20
plugins/additionals/lib/additionals/patches/formatter_markdown_patch.rb
Executable file
|
@ -0,0 +1,20 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module FormatterMarkdownPatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
base.send(:include, Additionals::Formatter)
|
||||
|
||||
# Add a postprocess hook to redcarpet's html formatter
|
||||
def postprocess(text)
|
||||
if Additionals.setting?(:legacy_smiley_support)
|
||||
render_inline_smileys(inline_emojify(text))
|
||||
else
|
||||
text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
plugins/additionals/lib/additionals/patches/formatter_textile_patch.rb
Executable file
16
plugins/additionals/lib/additionals/patches/formatter_textile_patch.rb
Executable file
|
@ -0,0 +1,16 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module FormatterTextilePatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
base.send(:include, Additionals::Formatter)
|
||||
# Add :inline_emojify to list of textile functions
|
||||
if Additionals.setting?(:legacy_smiley_support)
|
||||
Redmine::WikiFormatting::Textile::Formatter::RULES << :inline_emojify
|
||||
Redmine::WikiFormatting::Textile::Formatter::RULES << :inline_smileys
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
32
plugins/additionals/lib/additionals/patches/formatting_helper_patch.rb
Executable file
32
plugins/additionals/lib/additionals/patches/formatting_helper_patch.rb
Executable file
|
@ -0,0 +1,32 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module FormattingHelperPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
alias_method :heads_for_wiki_formatter_without_additionals, :heads_for_wiki_formatter
|
||||
alias_method :heads_for_wiki_formatter, :heads_for_wiki_formatter_with_additionals
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def heads_for_wiki_formatter_with_additionals
|
||||
heads_for_wiki_formatter_without_additionals
|
||||
|
||||
return if @additionals_macro_list
|
||||
|
||||
@additionals_macro_list = AdditionalsMacro.all(filtered: Additionals.settings[:hidden_macros_in_toolbar].to_a,
|
||||
only_names: true,
|
||||
controller_only: controller_name)
|
||||
|
||||
return if @additionals_macro_list.count.zero?
|
||||
|
||||
content_for :header_tags do
|
||||
javascript_include_tag('additionals_macro_button', plugin: 'additionals') +
|
||||
javascript_tag("jsToolBar.prototype.macroList = #{@additionals_macro_list.to_json};")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
189
plugins/additionals/lib/additionals/patches/issue_patch.rb
Executable file
189
plugins/additionals/lib/additionals/patches/issue_patch.rb
Executable file
|
@ -0,0 +1,189 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module IssuePatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
alias_method :editable_without_additionals?, :editable?
|
||||
alias_method :editable?, :editable_with_additionals?
|
||||
validate :validate_change_on_closed
|
||||
validate :validate_timelog_required
|
||||
validate :validate_open_sub_issues
|
||||
validate :validate_current_user_status
|
||||
before_validation :auto_assigned_to
|
||||
before_save :change_status_with_assigned_to_change,
|
||||
:autowatch_involved
|
||||
|
||||
safe_attributes 'author_id',
|
||||
if: proc { |issue, user|
|
||||
issue.new_record? && user.allowed_to?(:change_new_issue_author, issue.project) ||
|
||||
issue.persisted? && user.allowed_to?(:edit_issue_author, issue.project)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def sidbar_change_status_allowed_to(user, new_status_id = nil)
|
||||
statuses = new_statuses_allowed_to(user)
|
||||
if new_status_id.present?
|
||||
statuses.detect { |s| new_status_id == s.id && !timelog_required?(s.id) }
|
||||
else
|
||||
statuses.reject { |s| timelog_required?(s.id) }
|
||||
end
|
||||
end
|
||||
|
||||
def add_autowatcher(watcher)
|
||||
return if watcher.nil? ||
|
||||
!watcher.is_a?(User) ||
|
||||
watcher.anonymous? ||
|
||||
!watcher.active? ||
|
||||
watched_by?(watcher)
|
||||
|
||||
add_watcher(watcher)
|
||||
end
|
||||
|
||||
def autowatch_involved
|
||||
return unless Additionals.setting?(:issue_autowatch_involved) &&
|
||||
User.current.pref.autowatch_involved_issue
|
||||
|
||||
add_autowatcher(User.current)
|
||||
add_autowatcher(author) if (new_record? || author_id != author_id_was) && author != User.current
|
||||
unless assigned_to_id.nil? || assigned_to_id == User.current.id
|
||||
add_autowatcher(assigned_to) if new_record? || assigned_to_id != assigned_to_id_was
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def log_time_allowed?(user = User.current)
|
||||
!closed? || user.allowed_to?(:log_time_on_closed_issues, project)
|
||||
end
|
||||
|
||||
def editable_with_additionals?(user = User.current)
|
||||
return false unless editable_without_additionals?(user)
|
||||
return true unless closed?
|
||||
return true unless Additionals.setting?(:issue_freezed_with_close)
|
||||
|
||||
user.allowed_to?(:edit_closed_issues, project)
|
||||
end
|
||||
end
|
||||
|
||||
def autoassign_get_group_list
|
||||
return unless Setting.issue_group_assignment?
|
||||
|
||||
project.memberships
|
||||
.active
|
||||
.where("#{Principal.table_name}.type='Group'")
|
||||
.includes(:user, :roles)
|
||||
.each_with_object({}) do |m, h|
|
||||
m.roles.each do |r|
|
||||
h[r] ||= []
|
||||
h[r] << m.principal
|
||||
end
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
def new_ticket_message
|
||||
@new_ticket_message = ''
|
||||
message = Additionals.settings[:new_ticket_message]
|
||||
@new_ticket_message << message if message.present?
|
||||
end
|
||||
|
||||
def status_x_affected?(new_status_id)
|
||||
return false unless Additionals.setting?(:issue_current_user_status)
|
||||
return false if Additionals.settings[:issue_assign_to_x].blank?
|
||||
|
||||
if Additionals.settings[:issue_assign_to_x].include?(new_status_id.to_s)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def auto_assigned_to
|
||||
return if !Additionals.setting?(:issue_auto_assign) ||
|
||||
Additionals.settings[:issue_auto_assign_status].blank? ||
|
||||
Additionals.settings[:issue_auto_assign_role].blank? ||
|
||||
assigned_to_id.present?
|
||||
|
||||
return unless Additionals.settings[:issue_auto_assign_status].include?(status_id.to_s)
|
||||
|
||||
self.assigned_to_id = auto_assigned_to_user
|
||||
true
|
||||
end
|
||||
|
||||
def auto_assigned_to_user
|
||||
manager_role = Role.builtin.find_by(id: Additionals.settings[:issue_auto_assign_role])
|
||||
groups = autoassign_get_group_list
|
||||
return groups[manager_role].first.id unless groups.nil? || groups[manager_role].blank?
|
||||
|
||||
users_list = project.users_by_role
|
||||
return users_list[manager_role].first.id if users_list[manager_role].present?
|
||||
end
|
||||
|
||||
def timelog_required?(check_status_id)
|
||||
usr = User.current
|
||||
return false if !Additionals.setting?(:issue_timelog_required) ||
|
||||
Additionals.settings[:issue_timelog_required_tracker].blank? ||
|
||||
Additionals.settings[:issue_timelog_required_tracker].exclude?(tracker_id.to_s) ||
|
||||
Additionals.settings[:issue_timelog_required_status].blank? ||
|
||||
Additionals.settings[:issue_timelog_required_status].exclude?(check_status_id.to_s) ||
|
||||
!usr.allowed_to?(:log_time, project) ||
|
||||
usr.allowed_to?(:issue_timelog_never_required, project) ||
|
||||
time_entries.present?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def validate_timelog_required
|
||||
return true unless timelog_required?(status_id)
|
||||
|
||||
errors.add :base, :issue_requires_timelog
|
||||
end
|
||||
|
||||
def validate_change_on_closed
|
||||
return true if !closed? ||
|
||||
new_record? ||
|
||||
!Additionals.setting?(:issue_freezed_with_close) ||
|
||||
!status_was.is_closed ||
|
||||
User.current.allowed_to?(:edit_closed_issues, project)
|
||||
|
||||
errors.add :base, :issue_changes_not_allowed
|
||||
end
|
||||
|
||||
def validate_open_sub_issues
|
||||
return true unless Additionals.setting?(:issue_close_with_open_children)
|
||||
|
||||
errors.add :base, :issue_cannot_close_with_open_children if subject.present? &&
|
||||
closing? &&
|
||||
descendants.find { |d| !d.closed? }
|
||||
end
|
||||
|
||||
def validate_current_user_status
|
||||
if (assigned_to_id_changed? || status_id_changed?) &&
|
||||
status_x_affected?(status_id) &&
|
||||
(assigned_to_id.blank? || assigned_to_id != User.current.id)
|
||||
errors.add :base, :issue_current_user_status
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def change_status_with_assigned_to_change
|
||||
return true if !Additionals.setting?(:issue_status_change) ||
|
||||
Additionals.settings[:issue_status_x].blank? ||
|
||||
Additionals.settings[:issue_status_y].blank?
|
||||
|
||||
if !assigned_to_id_changed? &&
|
||||
status_id_changed? &&
|
||||
(Additionals.settings[:issue_status_x].include? status_id_was.to_s) &&
|
||||
Additionals.settings[:issue_status_y].to_i == status_id
|
||||
self.assigned_to = author
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
41
plugins/additionals/lib/additionals/patches/issue_priority_patch.rb
Executable file
41
plugins/additionals/lib/additionals/patches/issue_priority_patch.rb
Executable file
|
@ -0,0 +1,41 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module IssuePriorityPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
alias_method :css_classes_without_additionals, :css_classes
|
||||
alias_method :css_classes, :css_classes_with_additionals
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def css_classes_with_additionals
|
||||
classes = [css_classes_without_additionals, css_name_based_class]
|
||||
classes.join(' ')
|
||||
end
|
||||
|
||||
# css class based on priority name
|
||||
def css_name_based_class
|
||||
css_name_based_classes.each do |name_class|
|
||||
return name_class[:name] if name_class[:words].any? { |s| s.casecmp(name).zero? }
|
||||
end
|
||||
'prio-name-other'
|
||||
end
|
||||
|
||||
def css_name_based_classes
|
||||
@css_name_based_classes ||= [{ name: 'prio-name-low',
|
||||
words: [l(:default_priority_low), 'Low', 'Trivial', 'Niedrig', 'Gering'] },
|
||||
{ name: 'prio-name-normal',
|
||||
words: [l(:default_priority_normal), 'Normal', 'Minor', 'Unwesentlich', 'Default'] },
|
||||
{ name: 'prio-name-high',
|
||||
words: [l(:default_priority_high), 'High', 'Major', 'Important', 'Schwer', 'Hoch', 'Wichtig'] },
|
||||
{ name: 'prio-name-urgent',
|
||||
words: [l(:default_priority_urgent), 'Urgent', 'Critical', 'Kritisch', 'Dringend'] },
|
||||
{ name: 'prio-name-immediate',
|
||||
words: [l(:default_priority_immediate), 'Immediate', 'Blocker', 'Very high', 'Jetzt'] }]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
46
plugins/additionals/lib/additionals/patches/principal_patch.rb
Executable file
46
plugins/additionals/lib/additionals/patches/principal_patch.rb
Executable file
|
@ -0,0 +1,46 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module PrincipalPatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
# TODO: find better solution, which not requires overwrite visible
|
||||
# to filter out hide role members
|
||||
scope :visible, lambda { |*args|
|
||||
user = args.first || User.current
|
||||
|
||||
if user.admin?
|
||||
all
|
||||
else
|
||||
view_all_active = if user.memberships.to_a.any?
|
||||
user.memberships.any? { |m| m.roles.any? { |r| r.users_visibility == 'all' } }
|
||||
else
|
||||
user.builtin_role.users_visibility == 'all'
|
||||
end
|
||||
|
||||
if view_all_active
|
||||
active
|
||||
else
|
||||
# self and members of visible projects
|
||||
scope = if user.allowed_to?(:show_hidden_roles_in_memberbox, nil, global: true)
|
||||
active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id " \
|
||||
"FROM #{Member.table_name} WHERE project_id IN (?))",
|
||||
user.id, user.visible_project_ids)
|
||||
else
|
||||
active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id " \
|
||||
"FROM #{Member.table_name} JOIN #{MemberRole.table_name} " \
|
||||
" ON #{Member.table_name}.id = #{MemberRole.table_name}.member_id" \
|
||||
" JOIN #{Role.table_name} " \
|
||||
" ON #{Role.table_name}.id = #{MemberRole.table_name}.role_id" \
|
||||
" WHERE project_id IN (?) AND #{Role.table_name}.hide = ?)",
|
||||
user.id, user.visible_project_ids, false)
|
||||
end
|
||||
|
||||
scope
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
plugins/additionals/lib/additionals/patches/project_patch.rb
Executable file
23
plugins/additionals/lib/additionals/patches/project_patch.rb
Executable file
|
@ -0,0 +1,23 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module ProjectPatch
|
||||
def self.included(base)
|
||||
base.send(:prepend, InstancOverwriteMethods)
|
||||
end
|
||||
|
||||
module InstancOverwriteMethods
|
||||
def users_by_role
|
||||
roles_with_users = super
|
||||
roles_with_users.each do |role_with_users|
|
||||
role = role_with_users.first
|
||||
next unless role.hide
|
||||
|
||||
roles_with_users.delete(role) unless User.current.allowed_to?(:show_hidden_roles_in_memberbox, project)
|
||||
end
|
||||
|
||||
roles_with_users
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
plugins/additionals/lib/additionals/patches/query_filter_patch.rb
Executable file
21
plugins/additionals/lib/additionals/patches/query_filter_patch.rb
Executable file
|
@ -0,0 +1,21 @@
|
|||
require_dependency 'query'
|
||||
|
||||
module Additionals
|
||||
module Patches
|
||||
module QueryFilterPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
unless method_defined? :[]=
|
||||
def []=(key, value)
|
||||
return unless key == :values
|
||||
|
||||
@value = @options[:values] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
plugins/additionals/lib/additionals/patches/role_patch.rb
Executable file
11
plugins/additionals/lib/additionals/patches/role_patch.rb
Executable file
|
@ -0,0 +1,11 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module RolePatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
safe_attributes 'hide'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
30
plugins/additionals/lib/additionals/patches/time_entry_patch.rb
Executable file
30
plugins/additionals/lib/additionals/patches/time_entry_patch.rb
Executable file
|
@ -0,0 +1,30 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module TimeEntryPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
alias_method :editable_by_without_additionals?, :editable_by?
|
||||
alias_method :editable_by?, :editable_by_with_additionals?
|
||||
validate :validate_issue_allowed
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def validate_issue_allowed
|
||||
return unless issue_id && issue
|
||||
return if Setting.commit_logtime_enabled? && (issue.updated_on + 3.seconds) > Additionals.now_with_user_time_zone
|
||||
|
||||
errors.add(:issue_id, :issue_log_time_not_allowed) unless issue.log_time_allowed?
|
||||
end
|
||||
|
||||
def editable_by_with_additionals?(usr)
|
||||
return false unless editable_by_without_additionals?(usr)
|
||||
return true unless issue_id && issue
|
||||
|
||||
issue.log_time_allowed?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
plugins/additionals/lib/additionals/patches/user_patch.rb
Executable file
18
plugins/additionals/lib/additionals/patches/user_patch.rb
Executable file
|
@ -0,0 +1,18 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module UserPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def issues_assignable?(project = nil)
|
||||
scope = Principal.joins(members: :roles)
|
||||
.where(users: { id: id }, roles: { assignable: true })
|
||||
scope = scope.where(members: { project_id: project.id }) if project
|
||||
scope.exists?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
plugins/additionals/lib/additionals/patches/user_preference_patch.rb
Executable file
11
plugins/additionals/lib/additionals/patches/user_preference_patch.rb
Executable file
|
@ -0,0 +1,11 @@
|
|||
module Additionals
|
||||
module Patches
|
||||
module UserPreferencePatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
safe_attributes 'autowatch_involved_issue'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
65
plugins/additionals/lib/additionals/patches/wiki_controller_patch.rb
Executable file
65
plugins/additionals/lib/additionals/patches/wiki_controller_patch.rb
Executable file
|
@ -0,0 +1,65 @@
|
|||
require_dependency 'wiki_controller'
|
||||
|
||||
module Additionals
|
||||
module Patches
|
||||
module WikiControllerPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
alias_method :respond_to_without_additionals, :respond_to
|
||||
alias_method :respond_to, :respond_to_with_additionals
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def respond_to_with_additionals(&block)
|
||||
if @project && @content
|
||||
if @_action_name == 'show'
|
||||
additionals_include_header
|
||||
additionals_include_footer
|
||||
end
|
||||
end
|
||||
respond_to_without_additionals(&block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def additionals_include_header
|
||||
wiki_header = '' + Additionals.settings[:global_wiki_header].to_s
|
||||
return if wiki_header.empty?
|
||||
|
||||
if Object.const_defined?('WikiExtensionsUtil') && WikiExtensionsUtil.is_enabled?(@project)
|
||||
header = @wiki.find_page('Header')
|
||||
return if header
|
||||
end
|
||||
|
||||
text = "\n"
|
||||
text << '<div id="wiki_extentions_header">'
|
||||
text << "\n\n"
|
||||
text << wiki_header
|
||||
text << "\n\n</div>"
|
||||
text << "\n\n"
|
||||
text << @content.text
|
||||
@content.text = text
|
||||
end
|
||||
|
||||
def additionals_include_footer
|
||||
wiki_footer = '' + Additionals.settings[:global_wiki_footer].to_s
|
||||
return if wiki_footer.empty?
|
||||
|
||||
if Object.const_defined?('WikiExtensionsUtil') && WikiExtensionsUtil.is_enabled?(@project)
|
||||
footer = @wiki.find_page('Footer')
|
||||
return if footer
|
||||
end
|
||||
|
||||
text = @content.text
|
||||
text << "\n\n"
|
||||
text << '<div id="wiki_extentions_footer">'
|
||||
text << "\n\n"
|
||||
text << wiki_footer
|
||||
text << "\n\n</div>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
30
plugins/additionals/lib/additionals/patches/wiki_patch.rb
Executable file
30
plugins/additionals/lib/additionals/patches/wiki_patch.rb
Executable file
|
@ -0,0 +1,30 @@
|
|||
require_dependency 'wiki'
|
||||
|
||||
module Additionals
|
||||
module Patches
|
||||
# Patch wiki to include sidebar
|
||||
module WikiPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethodsForAdditionalsWiki)
|
||||
base.class_eval do
|
||||
alias_method :sidebar_without_additionals, :sidebar
|
||||
alias_method :sidebar, :sidebar_with_additionals
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Instance methodes for Wiki
|
||||
module InstanceMethodsForAdditionalsWiki
|
||||
def sidebar_with_additionals
|
||||
@sidebar ||= find_page('Sidebar', with_redirect: false)
|
||||
if @sidebar && @sidebar.content
|
||||
sidebar_without_additionals
|
||||
else
|
||||
wiki_sidebar = Additionals.settings[:global_wiki_sidebar].to_s
|
||||
@sidebar ||= find_page(project.wiki.start_page, with_redirect: false)
|
||||
@sidebar.content.text = wiki_sidebar if wiki_sidebar != '' && @sidebar.try(:content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
70
plugins/additionals/lib/additionals/wiki_macros/calendar_macro.rb
Executable file
70
plugins/additionals/lib/additionals/wiki_macros/calendar_macro.rb
Executable file
|
@ -0,0 +1,70 @@
|
|||
# Calendar wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Display calendar (only works on wiki pages)
|
||||
|
||||
Examples:
|
||||
|
||||
{{calendar}} show calendar for current date
|
||||
{{calendar(year=2014,month=6)}} show calendar for Juni in year 2014
|
||||
{{calendar(show_weeks=true)}} show calendar with week numbers
|
||||
{{calendar(select=2015-07-12 2015-07-31, show_weeks=true)}} preselect dates and show week numbers
|
||||
{{calendar(select=2016-03-13:2016-03-27)}} preselect dates between 2016/3/13 and 2016/3/27
|
||||
DESCRIPTION
|
||||
|
||||
macro :calendar do |_obj, args|
|
||||
raise 'Only works on wiki page' unless controller_name == 'wiki' && %w[show preview].include?(action_name)
|
||||
|
||||
_args, options = extract_macro_options(args, :show_weeks, :year, :month, :select)
|
||||
options[:show_weeks] = 'false' if options[:show_weeks].blank?
|
||||
options[:year] = Additionals.now_with_user_time_zone.year.to_s if options[:year].blank?
|
||||
options[:month] = Additionals.now_with_user_time_zone.month.to_s if options[:month].blank?
|
||||
options[:month] = options[:month].to_i - 1
|
||||
|
||||
selected = ''
|
||||
selected = Additionals.convert_string2date(options[:select]) if options[:select].present?
|
||||
|
||||
locale = User.current.language.presence || ::I18n.locale
|
||||
# not more then 30 calendars per page are expected
|
||||
id = (0..30).to_a.sort { rand - 0.5 } [1]
|
||||
render partial: 'wiki/calendar_macros',
|
||||
formats: [:html],
|
||||
locals: { options: options, locale: locale, id: id, selected: selected }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.convert_string2date(string)
|
||||
selected = if string.include? ':'
|
||||
convert_string2period(string)
|
||||
else
|
||||
convert_string2dates(string)
|
||||
end
|
||||
selected.join(', ')
|
||||
end
|
||||
|
||||
def self.convert_string2period(string)
|
||||
s = string.split ':'
|
||||
raise 'missing date' if s[0].blank? || s[1].blank?
|
||||
|
||||
tstart = Date.strptime(s[0], '%Y-%m-%d')
|
||||
raise 'invalid start date' if tstart.nil?
|
||||
|
||||
tend = Date.strptime(s[1], '%Y-%m-%d')
|
||||
raise 'invalid start date' if tend.nil?
|
||||
|
||||
(tstart..tend).map { |date| "new Date(#{date.year},#{date.month - 1},#{date.mday})" }
|
||||
end
|
||||
|
||||
def self.convert_string2dates(string)
|
||||
selected = []
|
||||
s = string.split
|
||||
s.each do |d|
|
||||
con = Date.strptime(d, '%Y-%m-%d')
|
||||
selected << "new Date(#{con.year},#{con.month - 1},#{con.mday})" unless con.nil?
|
||||
end
|
||||
selected
|
||||
end
|
||||
end
|
97
plugins/additionals/lib/additionals/wiki_macros/cryptocompare_macro.rb
Executable file
97
plugins/additionals/lib/additionals/wiki_macros/cryptocompare_macro.rb
Executable file
|
@ -0,0 +1,97 @@
|
|||
# CryptoCompare wiki macros
|
||||
# see https://www.cryptocompare.com/dev/widget/wizard/
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Create CryptoCompare information.
|
||||
{{cryptocompare(options)}}
|
||||
see https://additionals.readthedocs.io/en/latest/macros/#cryptocompare
|
||||
DESCRIPTION
|
||||
|
||||
macro :cryptocompare do |_obj, args|
|
||||
raise 'The correct usage is {{cryptocompare(options)}}' if args.empty?
|
||||
|
||||
_args, options = extract_macro_options(args, :fsym, :fsyms, :tsym, :tsyms, :period, :type)
|
||||
|
||||
options[:fsym] = 'BTC' if options[:fsym].blank?
|
||||
options[:tsym] = 'EUR' if options[:tsym].blank?
|
||||
|
||||
if options[:type].blank?
|
||||
widget_type = 'chart'
|
||||
else
|
||||
widget_type = options[:type]
|
||||
options.delete(:type)
|
||||
end
|
||||
|
||||
base_url = 'https://widgets.cryptocompare.com/'
|
||||
|
||||
case widget_type
|
||||
when 'chart'
|
||||
url = base_url + 'serve/v2/coin/chart'
|
||||
when 'news'
|
||||
options[:feedType] = 'CoinTelegraph' if options[:feedType].blank?
|
||||
url = base_url + 'serve/v1/coin/feed'
|
||||
when 'list'
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v1/coin/list'
|
||||
when 'titles'
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v1/coin/tiles'
|
||||
when 'tabbed'
|
||||
options[:fsyms] = Additionals.crypto_default(options, :fsyms, 'BTC,ETH,LTC')
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:fsym)
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v1/coin/multi'
|
||||
when 'header', 'header_v1'
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v1/coin/header'
|
||||
when 'header_v2'
|
||||
options[:fsyms] = Additionals.crypto_default(options, :fsyms, 'BTC,ETH,LTC')
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:fsym)
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v2/coin/header'
|
||||
when 'header_v3'
|
||||
options[:fsyms] = Additionals.crypto_default(options, :fsyms, 'BTC,ETH,LTC')
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR')
|
||||
options.delete(:fsym)
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v3/coin/header'
|
||||
when 'summary'
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v1/coin/summary'
|
||||
when 'historical'
|
||||
url = base_url + 'serve/v1/coin/histo_week'
|
||||
when 'converter'
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v1/coin/converter'
|
||||
when 'advanced'
|
||||
options[:tsyms] = Additionals.crypto_default(options, :tsyms, 'EUR,USD')
|
||||
options.delete(:tsym)
|
||||
url = base_url + 'serve/v3/coin/chart'
|
||||
else
|
||||
raise 'type is not supported'
|
||||
end
|
||||
|
||||
render partial: 'wiki/cryptocompare',
|
||||
formats: [:html],
|
||||
locals: { url: url + '?' + options.map { |k, v| "#{k}=#{v}" }.join('&') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.crypto_default(options, name, defaults)
|
||||
if options[name].blank?
|
||||
defaults
|
||||
else
|
||||
options[name].tr(';', ',')
|
||||
end
|
||||
end
|
||||
end
|
70
plugins/additionals/lib/additionals/wiki_macros/date_macro.rb
Executable file
70
plugins/additionals/lib/additionals/wiki_macros/date_macro.rb
Executable file
|
@ -0,0 +1,70 @@
|
|||
# Date wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Show date.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{date([TYPE])}}
|
||||
TYPE
|
||||
- current_date current date (default)
|
||||
- current_date_with_time current date with time
|
||||
- current_year current year
|
||||
- current_month current month
|
||||
- current_day current day
|
||||
- current_hour current hour
|
||||
- current_minute current minute
|
||||
- current_weekday current weekday
|
||||
- current_weeknumber current week number (1 - 52) The week starts with Monday
|
||||
- YYYY-MM-DD e.g. 2018-12-24, which will formated with Redmine date format
|
||||
|
||||
Examples:
|
||||
|
||||
{{date}}
|
||||
...show current date
|
||||
{{date(current_year)}}
|
||||
...show current year
|
||||
{{date(current_month)}}
|
||||
...show current month
|
||||
{{date(current_weeknumber)}}
|
||||
...show current week number
|
||||
DESCRIPTION
|
||||
|
||||
macro :date do |_obj, args|
|
||||
type = if args.present?
|
||||
args[0]
|
||||
else
|
||||
'current_date'
|
||||
end
|
||||
|
||||
d = Additionals.now_with_user_time_zone
|
||||
date_result = case type
|
||||
when 'current_date'
|
||||
format_date(User.current.today)
|
||||
when 'current_date_with_time'
|
||||
format_time(d, true)
|
||||
when 'current_year'
|
||||
d.year
|
||||
when 'current_month'
|
||||
d.month
|
||||
when 'current_day'
|
||||
d.day
|
||||
when 'current_hour'
|
||||
d.hour
|
||||
when 'current_minute'
|
||||
d.min
|
||||
when 'current_weekday'
|
||||
day_name(d.wday)
|
||||
when 'current_weeknumber'
|
||||
User.current.today.cweek
|
||||
else
|
||||
format_date(type.to_date)
|
||||
end
|
||||
|
||||
content_tag(:span, date_result, class: 'current-date')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
70
plugins/additionals/lib/additionals/wiki_macros/fa_macro.rb
Executable file
70
plugins/additionals/lib/additionals/wiki_macros/fa_macro.rb
Executable file
|
@ -0,0 +1,70 @@
|
|||
# Font Awesome wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Show Font Awesome icon.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{fa(ICON [, class=CLASS, title=TITLE, text=TEXT size=SIZE, color=COLOR)}}
|
||||
ICON of fontawesome icon, eg. fa-adjust
|
||||
CLASS = additional css classes
|
||||
TITLE = mouseover title
|
||||
TEXT = Text to show
|
||||
LINK = Link icon and text (if specified) to this URL
|
||||
COLOR = css color code
|
||||
|
||||
Examples:
|
||||
|
||||
{{fa(adjust)}}
|
||||
...show fontawesome icon "fas fa-adjust"
|
||||
{{fa(adjust, class=fa-inverse)}}
|
||||
...show fontawesome icon "fas fa-stack" and inverse
|
||||
{{fa(adjust, size=4x)}}
|
||||
...show fontawesome icon "fas fa-adjust" with size 4x
|
||||
{{fa(fas_adjust, title=Show icon)}}
|
||||
...show fontawesome icon "fas fa-adjust" with title "Show icon"
|
||||
{{fa(fab_angellist)}}
|
||||
...Show fontawesome icon "fab fa-angellist"
|
||||
{{fa(adjust, link=https=//www.redmine.org))}}
|
||||
...Show fontawesome icon "fas fa-adjust" and link it to https://www.redmine.org
|
||||
{{fa(adjust, link=https=//www.redmine.de, name=Go to Redmine.org))}}
|
||||
...Show fontawesome icon "fas fa-adjust" with name "Go to Redmine.org" and link it to https://www.redmine.org
|
||||
DESCRIPTION
|
||||
|
||||
macro :fa do |_obj, args|
|
||||
args, options = extract_macro_options(args, :class, :title, :text, :size, :color, :link)
|
||||
raise 'The correct usage is {{fa(<ICON>, class=CLASS, title=TITLE, text=TEXT, size=SIZE, color=COLOR)}}' if args.empty?
|
||||
|
||||
values = args[0].split('_')
|
||||
|
||||
classes = []
|
||||
if values.count == 2
|
||||
classes << values[0]
|
||||
classes << "fa-#{values[1]}"
|
||||
else
|
||||
classes << 'fas'
|
||||
classes << "fa-#{values[0]}"
|
||||
end
|
||||
|
||||
classes += options[:class].split(' ') if options[:class].present?
|
||||
classes << "fa-#{options[:size]}" if options[:size].present?
|
||||
|
||||
content_options = { class: classes.uniq.join(' ') }
|
||||
content_options[:title] = options[:title] if options[:title].present?
|
||||
content_options[:style] = "color: #{options[:color]}" if options[:color].present?
|
||||
|
||||
text = options[:text].present? ? ' ' + options[:text] : ''
|
||||
|
||||
if options[:link].present?
|
||||
content_tag(:a, href: options[:link]) do
|
||||
content_tag(:i, text, content_options)
|
||||
end
|
||||
else
|
||||
content_tag(:i, text, content_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
plugins/additionals/lib/additionals/wiki_macros/gist_macro.rb
Executable file
14
plugins/additionals/lib/additionals/wiki_macros/gist_macro.rb
Executable file
|
@ -0,0 +1,14 @@
|
|||
# Gist wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc 'gist embed'
|
||||
|
||||
macro :gist do |_obj, args|
|
||||
raise 'The correct usage is {{gist(<gist_id>)}}' if args.empty?
|
||||
|
||||
javascript_tag(nil, src: "https://gist.github.com/#{args[0]}.js")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
97
plugins/additionals/lib/additionals/wiki_macros/gmap_macro.rb
Executable file
97
plugins/additionals/lib/additionals/wiki_macros/gmap_macro.rb
Executable file
|
@ -0,0 +1,97 @@
|
|||
# Gist wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Display a google map. Examples:
|
||||
|
||||
Syntax:
|
||||
|
||||
{{gmap([q=QUERY, mode=MODE, width=216, height=368])}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{gmap(Munich)}} Google maps with Munich
|
||||
|
||||
{{gmap(mode=directions, origin=Munich+Rosenheimerstr, destination=Arco)}} Direction from Munich to Arco
|
||||
DESCRIPTION
|
||||
|
||||
macro :gmap do |_obj, args|
|
||||
src_options = %i[attribution_ios_deep_link_id
|
||||
attribution_source
|
||||
attribution_web_url
|
||||
avoid
|
||||
center
|
||||
destination
|
||||
fov
|
||||
heading
|
||||
language
|
||||
location
|
||||
maptype
|
||||
origin
|
||||
pano
|
||||
pitch
|
||||
region
|
||||
units
|
||||
waypoints
|
||||
zoom]
|
||||
|
||||
args, options = extract_macro_options(args,
|
||||
:mode,
|
||||
:width,
|
||||
:height,
|
||||
:attribution_ios_deep_link_id,
|
||||
:attribution_source,
|
||||
:attribution_web_url,
|
||||
:avoid,
|
||||
:center,
|
||||
:destination,
|
||||
:fov,
|
||||
:heading,
|
||||
:language,
|
||||
:location,
|
||||
:maptype,
|
||||
:origin,
|
||||
:pano,
|
||||
:pitch,
|
||||
:region,
|
||||
:units,
|
||||
:way_mode,
|
||||
:waypoints,
|
||||
:zoom)
|
||||
|
||||
raise 'Missing Google Maps Embed API Key. See documentation for more info.' if Additionals.settings[:google_maps_api_key].blank?
|
||||
|
||||
width = options[:width].presence || 620
|
||||
height = options[:height].presence || 350
|
||||
mode = options[:mode].presence || 'search'
|
||||
|
||||
if mode == 'search' && options[:q].blank? && args.empty?
|
||||
raise 'The correct usage is {{gmap([q=QUERY, mode=MODE, widths=x, height=y])}}'
|
||||
end
|
||||
|
||||
src = "https://www.google.com/maps/embed/v1/#{mode}?key=" + Additionals.settings[:google_maps_api_key]
|
||||
if options[:q].present?
|
||||
src << '&q=' + ERB::Util.url_encode(options[:q])
|
||||
elsif mode == 'search'
|
||||
src << '&q=' + ERB::Util.url_encode(args[0])
|
||||
end
|
||||
|
||||
src_options.each do |key|
|
||||
src << Additionals.gmap_flags(options, key)
|
||||
end
|
||||
src << "&#{mode}=" + ERB::Util.url_encode(options[:way_mode]) if options[:way_mode].present?
|
||||
|
||||
content_tag(:iframe, '', width: width, height: height, src: src, frameborder: 0, allowfullscreen: 'true')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.gmap_flags(options, key)
|
||||
if options[key].present?
|
||||
"&#{key}=" + ERB::Util.url_encode(options[key])
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
34
plugins/additionals/lib/additionals/wiki_macros/group_users_macro.rb
Executable file
34
plugins/additionals/lib/additionals/wiki_macros/group_users_macro.rb
Executable file
|
@ -0,0 +1,34 @@
|
|||
# Group wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Display users of group.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{group_users(GROUP_NAME}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{group_users(Team)}}
|
||||
...List all users in user group "Team" (with the current user permission)
|
||||
DESCRIPTION
|
||||
|
||||
macro :group_users do |_obj, args|
|
||||
raise 'The correct usage is {{group_users(<group_name>)}}' if args.empty?
|
||||
|
||||
group_name = args[0].strip
|
||||
group = Group.named(group_name).first
|
||||
raise unless group
|
||||
|
||||
users = Principal.visible.where(id: group.users).order(User.name_formatter[:order])
|
||||
render partial: 'wiki/user_macros',
|
||||
formats: [:html],
|
||||
locals: { users: users,
|
||||
user_roles: nil,
|
||||
list_title: group_name }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
61
plugins/additionals/lib/additionals/wiki_macros/iframe_macro.rb
Executable file
61
plugins/additionals/lib/additionals/wiki_macros/iframe_macro.rb
Executable file
|
@ -0,0 +1,61 @@
|
|||
# Slideshare wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Include iframe
|
||||
|
||||
Syntax:
|
||||
|
||||
{{iframe(<url> [, width=100%, height=485)}}
|
||||
|
||||
Examples:
|
||||
|
||||
show iframe of URL https://www.google.com/
|
||||
{{iframe(https://www.google.com/)}}
|
||||
|
||||
show iframe of URL https://www.google.com/ and show link to it
|
||||
{{iframe(https://www.google.com/, with_link: true)}}
|
||||
DESCRIPTION
|
||||
|
||||
macro :iframe do |_obj, args|
|
||||
args, options = extract_macro_options(args, :width, :height, :slide, :with_link)
|
||||
|
||||
width = options[:width].presence || '100%'
|
||||
height = options[:height].presence || 485
|
||||
|
||||
raise 'The correct usage is {{iframe(<url>[, width=x, height=y, with_link=bool])}}' if args.empty?
|
||||
|
||||
src = args[0]
|
||||
if Additionals.valid_iframe_url?(src)
|
||||
s = [content_tag(:iframe,
|
||||
'',
|
||||
width: width,
|
||||
height: height,
|
||||
src: src,
|
||||
frameborder: 0,
|
||||
allowfullscreen: 'true')]
|
||||
if !options[:with_link].nil? && Additionals.true?(options[:with_link])
|
||||
s << link_to(l(:label_open_in_new_windows), src, class: 'external')
|
||||
end
|
||||
safe_join(s)
|
||||
elsif Setting.protocol == 'https'
|
||||
raise 'Invalid url provided to iframe (only full URLs with protocol HTTPS are accepted)'
|
||||
else
|
||||
raise 'Invalid url provided to iframe (only full URLs are accepted)'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.valid_iframe_url?(url)
|
||||
uri = URI.parse(url)
|
||||
if Setting.protocol == 'https'
|
||||
uri.is_a?(URI::HTTPS) && !uri.host.nil?
|
||||
else
|
||||
!uri.host.nil?
|
||||
end
|
||||
rescue URI::InvalidURIError
|
||||
false
|
||||
end
|
||||
end
|
66
plugins/additionals/lib/additionals/wiki_macros/issue_macro.rb
Executable file
66
plugins/additionals/lib/additionals/wiki_macros/issue_macro.rb
Executable file
|
@ -0,0 +1,66 @@
|
|||
# Issue wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Create a link to issue with the subject of this issue.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{issue(URL [, format=USER_FORMAT, id=ID, note_id=NOTE_ID)}}
|
||||
URL is URL to issue
|
||||
USER_FORMATS
|
||||
- text
|
||||
- short
|
||||
- link (default)
|
||||
- full
|
||||
ID is issue
|
||||
NOTE_ID is note id, if you want to display it
|
||||
|
||||
Examples:
|
||||
|
||||
{{issue(1)}}
|
||||
...Link to issue with id and subject
|
||||
{{issue(http://myredmine.url/issues/1)}}
|
||||
...Link to issue with id and subject
|
||||
{{issue(http://myredmine.url/issues/1#note-3)}}
|
||||
...Link to issue with id and subject and display comment 3
|
||||
{{issue(1, format=short)}}
|
||||
...Link to issue with subject (without id)
|
||||
{{issue(1, format=text)}}
|
||||
...Display subject name
|
||||
{{issue(1, format=full)}}
|
||||
...Link to issue with track, issue id and subject
|
||||
DESCRIPTION
|
||||
|
||||
macro :issue do |_obj, args|
|
||||
args, options = extract_macro_options(args, :id, :note_id, :format)
|
||||
raise 'The correct usage is {{issue(<url>, format=FORMAT, id=INT, note_id=INT)}}' if args.empty? && options[:id].blank?
|
||||
|
||||
comment_id = options[:note_id].to_i if options[:note_id].present?
|
||||
issue_id = options[:id].presence ||
|
||||
(info = parse_issue_url(args[0], comment_id)
|
||||
comment_id = info[:comment_id] if comment_id.nil?
|
||||
info[:issue_id])
|
||||
|
||||
issue = Issue.find_by(id: issue_id)
|
||||
return if issue.nil? || !issue.visible?
|
||||
|
||||
text = case options[:format]
|
||||
when 'full'
|
||||
"#{issue.tracker.name} ##{issue_id} #{issue.subject}"
|
||||
when 'text', 'short'
|
||||
issue.subject
|
||||
else
|
||||
"##{issue_id} #{issue.subject}"
|
||||
end
|
||||
|
||||
if options[:format].blank? || options[:format] != 'text'
|
||||
render_issue_macro_link(issue, text, comment_id)
|
||||
else
|
||||
text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
40
plugins/additionals/lib/additionals/wiki_macros/last_updated_at_macro.rb
Executable file
40
plugins/additionals/lib/additionals/wiki_macros/last_updated_at_macro.rb
Executable file
|
@ -0,0 +1,40 @@
|
|||
# Last_updated_at wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Displays a date that updated the page.
|
||||
{{last_updated_at}}
|
||||
{{last_updated_at(project_name, wiki_page)}}
|
||||
{{last_updated_at(project_identifier, wiki_page)}}
|
||||
DESCRIPTION
|
||||
|
||||
macro :last_updated_at do |obj, args|
|
||||
return '' unless @project
|
||||
|
||||
if args.empty?
|
||||
page = obj
|
||||
else
|
||||
raise '{{last_updated_at(project_identifier, wiki_page)}}' if args.length < 2
|
||||
|
||||
project_name = args[0].strip
|
||||
page_name = args[1].strip
|
||||
project = Project.find_by(name: project_name)
|
||||
project ||= Project.find_by(identifier: project_name)
|
||||
return unless project
|
||||
|
||||
wiki = Wiki.find_by(project_id: project.id)
|
||||
return unless wiki
|
||||
|
||||
page = wiki.find_page(page_name)
|
||||
end
|
||||
|
||||
return unless page
|
||||
|
||||
content_tag(:span,
|
||||
l(:label_updated_time, time_tag(page.updated_on)).html_safe,
|
||||
class: 'last-updated-at')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
plugins/additionals/lib/additionals/wiki_macros/last_updated_by_macro.rb
Executable file
19
plugins/additionals/lib/additionals/wiki_macros/last_updated_by_macro.rb
Executable file
|
@ -0,0 +1,19 @@
|
|||
# Last_updated_by wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Displays a user who updated the page.
|
||||
{{last_updated_by}}
|
||||
DESCRIPTION
|
||||
|
||||
macro :last_updated_by do |obj, args|
|
||||
raise 'The correct usage is {{last_updated_by}}' unless args.empty?
|
||||
|
||||
content_tag(:span,
|
||||
safe_join([avatar(obj.author, size: 14), ' ', link_to_user(obj.author)]),
|
||||
class: 'last-updated-by')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
77
plugins/additionals/lib/additionals/wiki_macros/member_macro.rb
Executable file
77
plugins/additionals/lib/additionals/wiki_macros/member_macro.rb
Executable file
|
@ -0,0 +1,77 @@
|
|||
# Member wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Display members.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{members([PROJECT_NAME, title=My members list, role=ROLE)]}}
|
||||
|
||||
PROJECT_NAME can be project identifier, project name or project id
|
||||
|
||||
Examples:
|
||||
|
||||
{{members}}
|
||||
...List all members for all projects (with the current user permission)
|
||||
|
||||
{{members(the-identifier)}}
|
||||
...A box showing all members for the project with the identifier of 'the-identifier'
|
||||
|
||||
{{members(the-identifier, role=Manager)}}
|
||||
...A box showing all members for the project with the identifier of 'the-identifier', which
|
||||
have the role "Manager"
|
||||
|
||||
{{members(the-identifier, title=My user list)}}
|
||||
...A box showing all members for the project with the identifier of 'the-identifier' and with
|
||||
box title "My user list"
|
||||
DESCRIPTION
|
||||
|
||||
macro :members do |_obj, args|
|
||||
args, options = extract_macro_options(args, :role, :title)
|
||||
|
||||
project_id = args[0]
|
||||
user_roles = []
|
||||
|
||||
if project_id.present?
|
||||
project_id.strip!
|
||||
|
||||
project = Project.visible.find_by(id: project_id)
|
||||
project ||= Project.visible.find_by(identifier: project_id)
|
||||
project ||= Project.visible.find_by(name: project_id)
|
||||
return if project.nil?
|
||||
|
||||
raw_users = User.active
|
||||
.where(["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id=(?))", project.id])
|
||||
.sorted
|
||||
return if raw_users.nil?
|
||||
|
||||
users = []
|
||||
raw_users.each do |user|
|
||||
user_roles[user.id] = user.roles_for_project(project)
|
||||
users << user if options[:role].blank? || Additionals.check_role_matches(user_roles[user.id], options[:role])
|
||||
end
|
||||
else
|
||||
project_ids = Project.visible.collect(&:id)
|
||||
return unless project_ids.any?
|
||||
|
||||
# members of the user's projects
|
||||
users = User.active
|
||||
.where(["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids])
|
||||
.sorted
|
||||
end
|
||||
render partial: 'wiki/user_macros', locals: { users: users,
|
||||
user_roles: user_roles,
|
||||
list_title: options[:title] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.check_role_matches(roles, filters)
|
||||
filters.tr('|', ',').split(',').each do |filter|
|
||||
roles.each { |role| return true if filter.to_s == role.to_s }
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
85
plugins/additionals/lib/additionals/wiki_macros/meteoblue_macro.rb
Executable file
85
plugins/additionals/lib/additionals/wiki_macros/meteoblue_macro.rb
Executable file
|
@ -0,0 +1,85 @@
|
|||
# meteoblue wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Display current weather from meteoblue service. Examples:
|
||||
|
||||
Syntax:
|
||||
|
||||
{{meteoblue(<location> [, days=INT, width=216, height=368, color=BOOL])}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{meteoblue(münchen_deutschland_2867714)}} weather for Munich
|
||||
|
||||
{{meteoblue(münchen_deutschland_2867714, days=6, color=false)}} weather for Munich of the next 6 days without color
|
||||
DESCRIPTION
|
||||
|
||||
macro :meteoblue do |_obj, args|
|
||||
args, options = extract_macro_options(args,
|
||||
:days,
|
||||
:width,
|
||||
:height,
|
||||
:color,
|
||||
:pictoicon,
|
||||
:maxtemperature,
|
||||
:mintemperature,
|
||||
:windspeed,
|
||||
:windgust,
|
||||
:winddirection,
|
||||
:uv,
|
||||
:humidity,
|
||||
:precipitation,
|
||||
:precipitationprobability,
|
||||
:spot)
|
||||
|
||||
raise 'The correct usage is {{meteoblue(<location>[, days=x, color=BOOL])}}' if args.empty?
|
||||
|
||||
options[:days] = 4 if options[:days].blank?
|
||||
options[:coloured] = if options[:color].present? && !Additionals.true?(options[:color])
|
||||
'monochrome'
|
||||
else
|
||||
'coloured'
|
||||
end
|
||||
|
||||
width = options[:width].presence || 216
|
||||
height = options[:height].presence || 368
|
||||
|
||||
src = if User.current.language.blank? ? ::I18n.locale : User.current.language == 'de'
|
||||
'https://www.meteoblue.com/de/wetter/widget/daily/'
|
||||
else
|
||||
'https://www.meteoblue.com/en/weather/widget/daily/'
|
||||
end
|
||||
|
||||
src << ERB::Util.url_encode(args[0])
|
||||
src << "?geoloc=fixed&days=#{options[:days]}&tempunit=CELSIUS&windunit=KILOMETER_PER_HOUR"
|
||||
src << "&precipunit=MILLIMETER&coloured=#{options[:coloured]}"
|
||||
|
||||
src << Additionals.meteoblue_flag(options, :pictoicon, true)
|
||||
src << Additionals.meteoblue_flag(options, :maxtemperature, true)
|
||||
src << Additionals.meteoblue_flag(options, :mintemperature, true)
|
||||
src << Additionals.meteoblue_flag(options, :windspeed, false)
|
||||
src << Additionals.meteoblue_flag(options, :windgust, false)
|
||||
src << Additionals.meteoblue_flag(options, :winddirection, false)
|
||||
src << Additionals.meteoblue_flag(options, :uv, false)
|
||||
src << Additionals.meteoblue_flag(options, :humidity, false)
|
||||
src << Additionals.meteoblue_flag(options, :precipitation, true)
|
||||
src << Additionals.meteoblue_flag(options, :precipitationprobability, true)
|
||||
src << Additionals.meteoblue_flag(options, :spot, true)
|
||||
src << Additionals.meteoblue_flag(options, :pressure, false)
|
||||
|
||||
content_tag(:iframe, '', width: width, height: height, src: src, frameborder: 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.meteoblue_flag(options, name, default = tue)
|
||||
flag = "&#{name}="
|
||||
flag << if Additionals.true?(options[name]) || default
|
||||
'1'
|
||||
else
|
||||
'0'
|
||||
end
|
||||
end
|
||||
end
|
61
plugins/additionals/lib/additionals/wiki_macros/new_issue_macro.rb
Executable file
61
plugins/additionals/lib/additionals/wiki_macros/new_issue_macro.rb
Executable file
|
@ -0,0 +1,61 @@
|
|||
# Issue wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Create a link for "New issue" for the current user.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{new_issue([PROJECT_NAME, name=Custom name]}}
|
||||
|
||||
PROJECT_NAME can be project identifier, project name or project id.
|
||||
|
||||
If no PROJECT_NAME is specified, first project is used, which the current user
|
||||
has permission to create an issue.
|
||||
|
||||
Examples:
|
||||
|
||||
{{new_issue}}
|
||||
...Link to create new issue in first available project
|
||||
{{new_issue(the-identifier)}}
|
||||
...Link to create new issue in project with the identifier of 'the-identifier'
|
||||
{{new_issue(the-identifier, title=New issue for broken displays)}}
|
||||
...Link to create new issue in project with the identifier of 'the-identifier'
|
||||
and the name 'New issue for broken displays'
|
||||
DESCRIPTION
|
||||
|
||||
macro :new_issue do |_obj, args|
|
||||
if args.any?
|
||||
args, options = extract_macro_options(args, *additionals_titles_for_locale(:name))
|
||||
i18n_name = additionals_i18n_title(options, :name)
|
||||
project_id = args[0]
|
||||
end
|
||||
i18n_name = l(:label_issue_new) if i18n_name.blank?
|
||||
|
||||
if project_id.present?
|
||||
project_id.strip!
|
||||
|
||||
project = Project.visible.find_by(id: project_id)
|
||||
project ||= Project.visible.find_by(identifier: project_id)
|
||||
project ||= Project.visible.find_by(name: project_id)
|
||||
return '' if project.nil? || !User.current.allowed_to?(:add_issues, project)
|
||||
|
||||
return link_to(i18n_name, new_project_issue_path(project), class: 'macro-new-issue icon icon-add')
|
||||
else
|
||||
@memberships = User.current
|
||||
.memberships
|
||||
.preload(:roles, :project)
|
||||
.where(Project.visible_condition(User.current))
|
||||
.to_a
|
||||
if @memberships.present?
|
||||
project_url = memberships_new_issue_project_url(User.current, @memberships, :add_issues)
|
||||
return link_to(i18n_name, project_url, class: 'macro-new-issue icon icon-add') if project_url.present?
|
||||
end
|
||||
end
|
||||
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
plugins/additionals/lib/additionals/wiki_macros/project_macro.rb
Executable file
47
plugins/additionals/lib/additionals/wiki_macros/project_macro.rb
Executable file
|
@ -0,0 +1,47 @@
|
|||
# Project wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Display projects.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{projects([title=My project list, with_create_issue=BOOL])}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{projects}}
|
||||
...List all project, which I am member of
|
||||
|
||||
{{projects(title=My project list)}}
|
||||
...List all project with title "My project list", which I am member of
|
||||
|
||||
{{projects(with_create_issue=true)}}
|
||||
...List all project with link to create new issue, which I am member of
|
||||
DESCRIPTION
|
||||
|
||||
macro :projects do |_obj, args|
|
||||
_args, options = extract_macro_options(args, :title, :with_create_issue)
|
||||
@projects = Additionals.load_projects
|
||||
return if @projects.nil?
|
||||
|
||||
@html_options = { class: 'external' }
|
||||
render partial: 'wiki/project_macros',
|
||||
formats: [:html],
|
||||
locals: { projects: @projects,
|
||||
list_title: options[:title],
|
||||
with_create_issue: options[:with_create_issue] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.load_projects
|
||||
all_projects = Project.active.visible.sorted
|
||||
my_projects = []
|
||||
all_projects.each do |p|
|
||||
my_projects << p if User.current.member_of?(p)
|
||||
end
|
||||
my_projects
|
||||
end
|
||||
end
|
53
plugins/additionals/lib/additionals/wiki_macros/recently_updated_macro.rb
Executable file
53
plugins/additionals/lib/additionals/wiki_macros/recently_updated_macro.rb
Executable file
|
@ -0,0 +1,53 @@
|
|||
# Recently updated wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Displays a list of pages that were updated recently.
|
||||
{{recently_updated}}
|
||||
{{recently_updated([days])}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{recently_updated}}
|
||||
...List last updated pages (of the last 5 days)
|
||||
|
||||
{{recently_updated(15)}}
|
||||
...List last updated pages of the last 15 days
|
||||
DESCRIPTION
|
||||
|
||||
macro :recently_updated do |obj, args|
|
||||
page = obj.page
|
||||
return unless page
|
||||
|
||||
project = page.project
|
||||
return unless project
|
||||
|
||||
days = 5
|
||||
days = args[0].strip.to_i unless args.empty?
|
||||
|
||||
return if days < 1
|
||||
|
||||
pages = WikiPage
|
||||
.includes(:content)
|
||||
.where(["#{WikiPage.table_name}.wiki_id = ? AND #{WikiContent.table_name}.updated_on > ?",
|
||||
page.wiki_id, User.current.today - days])
|
||||
.order("#{WikiContent.table_name}.updated_on desc")
|
||||
o = ''
|
||||
date = nil
|
||||
pages.each do |page_raw|
|
||||
content = page_raw.content
|
||||
updated_on = Date.new(content.updated_on.year, content.updated_on.month, content.updated_on.day)
|
||||
if date != updated_on
|
||||
date = updated_on
|
||||
o << '<b>' + format_date(date) + '</b><br/>'
|
||||
end
|
||||
o << link_to(content.page.pretty_title,
|
||||
controller: 'wiki', action: 'show', project_id: content.page.project, id: content.page.title)
|
||||
o << '<br/>'
|
||||
end
|
||||
content_tag('div', o.html_safe, class: 'recently-updated')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
36
plugins/additionals/lib/additionals/wiki_macros/reddit_macro.rb
Executable file
36
plugins/additionals/lib/additionals/wiki_macros/reddit_macro.rb
Executable file
|
@ -0,0 +1,36 @@
|
|||
# Reddit wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Creates link to reddit.
|
||||
{{reddit(name)}}
|
||||
DESCRIPTION
|
||||
|
||||
macro :reddit do |_obj, args|
|
||||
raise 'The correct usage is {{reddit(<name>)}}' if args.empty?
|
||||
|
||||
name = args[0].strip
|
||||
|
||||
case name[0..1]
|
||||
when 'r/'
|
||||
link_to(font_awesome_icon('fab_reddit', post_text: name),
|
||||
"https://www.reddit.com/#{name}",
|
||||
class: 'external reddit',
|
||||
title: l(:label_reddit_subject))
|
||||
when 'u/'
|
||||
link_to(font_awesome_icon('fab_reddit-square', post_text: name),
|
||||
"https://www.reddit.com/username/#{name[2..-1]}",
|
||||
class: 'external reddit',
|
||||
title: l(:label_reddit_user_account))
|
||||
else
|
||||
name = 'r/' + name
|
||||
link_to(font_awesome_icon('fab_reddit', post_text: name),
|
||||
"https://www.reddit.com/#{name}",
|
||||
class: 'external reddit',
|
||||
title: l(:label_reddit_subject))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
42
plugins/additionals/lib/additionals/wiki_macros/redmine_issue_macro.rb
Executable file
42
plugins/additionals/lib/additionals/wiki_macros/redmine_issue_macro.rb
Executable file
|
@ -0,0 +1,42 @@
|
|||
# Redmine.org issue wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Creates link to redmine.org issue.
|
||||
{{redmine_issue(1448)}}
|
||||
DESCRIPTION
|
||||
|
||||
macro :redmine_issue do |_obj, args|
|
||||
raise 'The correct usage is {{redmine_issue(<id>)}}' if args.empty?
|
||||
|
||||
args, options = extract_macro_options(args, :title)
|
||||
raw_link = args[0].to_s.strip
|
||||
|
||||
if !/\A\d+\z/.match(raw_link[0])
|
||||
# https://www.redmine.org/issues/12066#note-7
|
||||
if raw_link =~ %r{redmine.org/issues/([0-9].+?)#(.*)} ||
|
||||
raw_link =~ %r{redmine.org/issues/([0-9].+)}
|
||||
link_name = Regexp.last_match(1)
|
||||
link = raw_link.gsub('http://', 'https://')
|
||||
else
|
||||
raise 'The correct usage is {{redmine_issue(<id>)}}'
|
||||
end
|
||||
elsif raw_link =~ /([0-9].+?)\D/
|
||||
# ID with parameters
|
||||
link_name = Regexp.last_match(1)
|
||||
link = "https://www.redmine.org/issues/#{raw_link}"
|
||||
else
|
||||
# just ID
|
||||
link_name = raw_link
|
||||
link = "https://www.redmine.org/issues/#{raw_link}"
|
||||
end
|
||||
|
||||
link_options = { class: 'external redmine-link' }
|
||||
link_options[:title] = options[:title].presence || l(:label_redmine_org_issue)
|
||||
|
||||
link_to("##{link_name}", link, link_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
37
plugins/additionals/lib/additionals/wiki_macros/redmine_wiki_macro.rb
Executable file
37
plugins/additionals/lib/additionals/wiki_macros/redmine_wiki_macro.rb
Executable file
|
@ -0,0 +1,37 @@
|
|||
# Redmine.org issue wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Creates link to redmine.org wiki page.
|
||||
{{redmine_wiki(Installing Redmine)}}
|
||||
DESCRIPTION
|
||||
|
||||
macro :redmine_wiki do |_obj, args|
|
||||
raise 'The correct usage is {{redmine_wiki(<page>)}}' if args.empty?
|
||||
|
||||
args, options = extract_macro_options(args, :title, :name)
|
||||
|
||||
raw_link = args[0].to_s.strip
|
||||
|
||||
if raw_link[0..3] == 'http'
|
||||
start_pos = raw_link.index('redmine.org/projects/redmine/wiki/')
|
||||
raise 'The correct usage is {{redmine_wiki(<page>)}}' if start_pos.nil? || start_pos.zero?
|
||||
|
||||
options[:name] = raw_link[(start_pos + 34)..-1] if options[:name].blank?
|
||||
link = raw_link.gsub('http://', 'https://')
|
||||
elsif raw_link[0] =~ /\w/
|
||||
options[:name] = raw_link if options[:name].blank?
|
||||
link = "https://www.redmine.org/projects/redmine/wiki/#{Wiki.titleize(raw_link)}"
|
||||
else
|
||||
raise 'The correct usage is {{redmine_wiki(<page>)}}'
|
||||
end
|
||||
|
||||
link_options = { class: 'external redmine-link' }
|
||||
link_options[:title] = options[:title].presence || l(:label_redmine_org_wiki)
|
||||
|
||||
link_to(options[:name], link, link_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
38
plugins/additionals/lib/additionals/wiki_macros/slideshare_macro.rb
Executable file
38
plugins/additionals/lib/additionals/wiki_macros/slideshare_macro.rb
Executable file
|
@ -0,0 +1,38 @@
|
|||
# Slideshare wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Slideshare macro to include Slideshare slide.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{slideshare(<key> [, width=595, height=485, slide=SLIDE])}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{slideshare(57941706)}} show slideshare slide with default size 595x485
|
||||
{{slideshare(57941706, width=514, height=422)}} show slide with user defined size
|
||||
{{slideshare(57941706, slide=5)}} start with slide (page) 5
|
||||
DESCRIPTION
|
||||
|
||||
macro :slideshare do |_obj, args|
|
||||
args, options = extract_macro_options(args, :width, :height, :slide)
|
||||
|
||||
width = options[:width].presence || 595
|
||||
height = options[:height].presence || 485
|
||||
slide = options[:slide].present? ? options[:slide].to_i : 0
|
||||
|
||||
raise 'The correct usage is {{slideshare(<key>[, width=x, height=y, slide=number])}}' if args.empty?
|
||||
|
||||
v = args[0]
|
||||
src = if slide > 0
|
||||
'//www.slideshare.net/slideshow/embed_code/' + v + '?startSlide=' + slide.to_s
|
||||
else
|
||||
'//www.slideshare.net/slideshow/embed_code/' + v
|
||||
end
|
||||
content_tag(:iframe, '', width: width, height: height, src: src, frameborder: 0, allowfullscreen: 'true')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
38
plugins/additionals/lib/additionals/wiki_macros/tradingview_macro.rb
Executable file
38
plugins/additionals/lib/additionals/wiki_macros/tradingview_macro.rb
Executable file
|
@ -0,0 +1,38 @@
|
|||
# Tradingview wiki macros
|
||||
# see https://www.tradingview.com/widget/
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Creates Tradingview chart
|
||||
{{tradingview(options)}}
|
||||
see https://additionals.readthedocs.io/en/latest/macros/#tradingview
|
||||
DESCRIPTION
|
||||
|
||||
macro :tradingview do |_obj, args|
|
||||
raise 'The correct usage is {{tradingview(options)}}' if args.empty?
|
||||
|
||||
_args, options = extract_macro_options(args, :width, :height, :symbol, :interval, :timezone,
|
||||
:theme, :style, :locale, :toolbar_bg, :enable_publishing,
|
||||
:allow_symbol_change, :hideideasbutton)
|
||||
|
||||
options[:width] = 640 if options[:width].blank?
|
||||
options[:height] = 480 if options[:height].blank?
|
||||
options[:symbol] = 'NASDAQ:AAPL' if options[:symbol].blank?
|
||||
options[:interval] = 'W' if options[:interval].blank?
|
||||
options[:timezone] = 'Europe/Berlin' if options[:timezone].blank?
|
||||
options[:theme] = 'White' if options[:theme].blank?
|
||||
options[:style] = 2 if options[:style].blank?
|
||||
options[:locale] = 'de' if options[:locale].blank?
|
||||
options[:toolbar_bg] = '#f1f3f6' if options[:toolbar_bg].blank?
|
||||
options[:enable_publishing] = false if options[:enable_publishing].blank?
|
||||
options[:allow_symbol_change] = true if options[:allow_symbol_change].blank?
|
||||
options[:hideideasbutton] = true if options[:hideideasbutton].blank?
|
||||
|
||||
render partial: 'wiki/tradingview',
|
||||
formats: [:html],
|
||||
locals: { options: options }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
plugins/additionals/lib/additionals/wiki_macros/twitter_macro.rb
Executable file
34
plugins/additionals/lib/additionals/wiki_macros/twitter_macro.rb
Executable file
|
@ -0,0 +1,34 @@
|
|||
# Twitter wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Creates link to twitter account page or topic.
|
||||
{{twitter(name)}}
|
||||
DESCRIPTION
|
||||
|
||||
macro :twitter do |_obj, args|
|
||||
raise 'The correct usage is {{twitter(<name>)}}' if args.empty?
|
||||
|
||||
name = args[0].strip
|
||||
case name[0]
|
||||
when '@'
|
||||
link_to(font_awesome_icon('fab_twitter', post_text: name),
|
||||
"https://twitter.com/#{name[1..-1]}",
|
||||
class: 'external twitter',
|
||||
title: l(:label_twitter_account))
|
||||
when '#'
|
||||
link_to(font_awesome_icon('fab_twitter-square', post_text: name),
|
||||
"https://twitter.com/hashtag/#{name[1..-1]}",
|
||||
class: 'external twitter',
|
||||
title: l(:label_twitter_hashtag))
|
||||
else
|
||||
link_to(font_awesome_icon('fab_twitter', post_text: " @#{name}"),
|
||||
"https://twitter.com/#{name}",
|
||||
class: 'external twitter',
|
||||
title: l(:label_twitter_account))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
50
plugins/additionals/lib/additionals/wiki_macros/user_macro.rb
Executable file
50
plugins/additionals/lib/additionals/wiki_macros/user_macro.rb
Executable file
|
@ -0,0 +1,50 @@
|
|||
# User wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc "Display link to user profile\n\n" \
|
||||
"Syntax:\n\n" \
|
||||
"{{user(USER_NAME [, format=USER_FORMAT, avatar=BOOL])}}\n\n" \
|
||||
"USER_NAME can be user id or user name (login name)\n" \
|
||||
"USER_FORMATS\n" \
|
||||
"- system (use system settings) (default)\n- " +
|
||||
User::USER_FORMATS.keys.join("\n- ") + "\n\n" \
|
||||
"Examples:\n\n" \
|
||||
"{{user(1)}}\n" \
|
||||
"...Link to user with user id 1\n\n" \
|
||||
"{{user(1, avatar=true)}}\n" \
|
||||
"...Link to user with user id 1 with avatar\n\n" \
|
||||
"{{user(admin)}}\n" \
|
||||
"...Link to user with username 'admin'\n\n" \
|
||||
"{{user(admin, format=firstname)}}\n" \
|
||||
"...Link to user with username 'admin' and show firstname as link text"
|
||||
|
||||
macro :user do |_obj, args|
|
||||
args, options = extract_macro_options(args, :format, :avatar)
|
||||
raise 'The correct usage is {{user(<user_id or username>, format=USER_FORMAT)}}' if args.empty?
|
||||
|
||||
user_id = args[0]
|
||||
|
||||
user = User.find_by(id: user_id)
|
||||
user ||= User.find_by(login: user_id)
|
||||
return if user.nil?
|
||||
|
||||
name = if options[:format].blank?
|
||||
user.name
|
||||
else
|
||||
user.name(options[:format].to_sym)
|
||||
end
|
||||
|
||||
s = []
|
||||
s << avatar(user, size: 14) + ' ' if options[:avatar].present? && options[:avatar]
|
||||
|
||||
s << if user.active?
|
||||
link_to(h(name), user_url(user, only_path: controller_path != 'mailer'), class: user.css_classes)
|
||||
else
|
||||
h(name)
|
||||
end
|
||||
safe_join(s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
43
plugins/additionals/lib/additionals/wiki_macros/vimeo_macro.rb
Executable file
43
plugins/additionals/lib/additionals/wiki_macros/vimeo_macro.rb
Executable file
|
@ -0,0 +1,43 @@
|
|||
# Vimeo wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Vimeo macro to include vimeo video.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{vimeo(<video key> [, width=640, height=360, autoplay=BOOL])}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{vimeo(142849533)}} show video with default size 640x360
|
||||
{{vimeo(142849533, width=853, height=480)}} show video with user defined size
|
||||
{{vimeo(142849533, autoplay=true)}} autoplay video
|
||||
DESCRIPTION
|
||||
|
||||
macro :vimeo do |_obj, args|
|
||||
args, options = extract_macro_options(args, :width, :height, :autoplay)
|
||||
|
||||
width = options[:width].presence || 640
|
||||
height = options[:height].presence || 360
|
||||
|
||||
autoplay = if !options[:autoplay].nil? && Additionals.true?(options[:autoplay])
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
raise 'The correct usage is {{vimeo(<video key>[, width=x, height=y])}}' if args.empty?
|
||||
|
||||
v = args[0]
|
||||
src = if autoplay
|
||||
'//player.vimeo.com/video/' + v + '?autoplay=1'
|
||||
else
|
||||
'//player.vimeo.com/video/' + v
|
||||
end
|
||||
content_tag(:iframe, '', width: width, height: height, src: src, frameborder: 0, allowfullscreen: 'true')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
43
plugins/additionals/lib/additionals/wiki_macros/youtube_macro.rb
Executable file
43
plugins/additionals/lib/additionals/wiki_macros/youtube_macro.rb
Executable file
|
@ -0,0 +1,43 @@
|
|||
# Youtube wiki macros
|
||||
module Additionals
|
||||
module WikiMacros
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc <<-DESCRIPTION
|
||||
Youtube macro to include youtube video.
|
||||
|
||||
Syntax:
|
||||
|
||||
{{youtube(<video key> [, width=640, height=360, autoplay=BOOL])}}
|
||||
|
||||
Examples:
|
||||
|
||||
{{youtube(KMU0tzLwhbE)}} show video with default size 640x360
|
||||
{{youtube(KMU0tzLwhbE, width=853, height=480)}} show video with user defined size
|
||||
{{youtube(KMU0tzLwhbE, autoplay=true)}} autoplay video
|
||||
DESCRIPTION
|
||||
|
||||
macro :youtube do |_obj, args|
|
||||
args, options = extract_macro_options(args, :width, :height, :autoplay)
|
||||
|
||||
width = options[:width].presence || 640
|
||||
height = options[:height].presence || 360
|
||||
|
||||
autoplay = if !options[:autoplay].nil? && Additionals.true?(options[:autoplay])
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
raise 'The correct usage is {{youtube(<video key>[, width=x, height=y])}}' if args.empty?
|
||||
|
||||
v = args[0]
|
||||
src = if autoplay
|
||||
'//www.youtube.com/embed/' + v + '?autoplay=1'
|
||||
else
|
||||
'//www.youtube-nocookie.com/embed/' + v
|
||||
end
|
||||
content_tag(:iframe, '', width: width, height: height, src: src, frameborder: 0, allowfullscreen: 'true')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
82
plugins/additionals/lib/tasks/additionals.rake
Executable file
82
plugins/additionals/lib/tasks/additionals.rake
Executable file
|
@ -0,0 +1,82 @@
|
|||
namespace :redmine do
|
||||
namespace :additionals do
|
||||
desc <<-DESCRIPTION
|
||||
Drop plugin settings.
|
||||
|
||||
Example:
|
||||
bundle exec rake redmine:additionals:drop_settings RAILS_ENV=production plugin="redmine_plugin_example"
|
||||
DESCRIPTION
|
||||
task drop_settings: :environment do
|
||||
plugin = ENV['plugin']
|
||||
|
||||
if plugin.blank?
|
||||
puts 'Parameter plugin is required.'
|
||||
exit 2
|
||||
end
|
||||
|
||||
Setting.where(name: "plugin_#{plugin}".to_sym).destroy_all
|
||||
Setting.clear_cache
|
||||
puts "Setting for plugin #{plugin} has been dropped."
|
||||
end
|
||||
|
||||
desc <<-DESCRIPTION
|
||||
Set settings.
|
||||
|
||||
Example for value:
|
||||
bundle exec rake redmine:additionals:setting_set RAILS_ENV=production name="additionals" setting="external_urls" value="2"
|
||||
Example for list of value:
|
||||
bundle exec rake redmine:additionals:setting_set RAILS_ENV=production setting="default_projects_modules" value="issue_tracking,time_tracking,wiki"
|
||||
DESCRIPTION
|
||||
task setting_set: :environment do
|
||||
name = ENV['name'] ||= 'redmine'
|
||||
setting = ENV['setting']
|
||||
value = if ENV['values'].present?
|
||||
ENV['values'].split(',')
|
||||
else
|
||||
ENV['value']
|
||||
end
|
||||
|
||||
if name.blank? || setting.blank? || value.blank?
|
||||
puts 'Parameters setting and value are required.'
|
||||
exit 2
|
||||
end
|
||||
|
||||
if name == 'redmine'
|
||||
Setting[setting.to_sym] = value
|
||||
else
|
||||
plugin_name = "plugin_#{name}".to_sym
|
||||
plugin_settings = Setting[plugin_name]
|
||||
plugin_settings[setting] = value
|
||||
Setting[plugin_name] = plugin_settings
|
||||
end
|
||||
end
|
||||
|
||||
desc <<-DESCRIPTION
|
||||
Get settings.
|
||||
|
||||
Example for plugin setting:
|
||||
bundle exec rake redmine:additionals:setting_get RAILS_ENV=production name="additionals" setting="external_urls"
|
||||
Example for redmine setting:
|
||||
bundle exec rake redmine:additionals:setting_get RAILS_ENV=production name="redmine" setting="app_title"
|
||||
Example for redmine setting:
|
||||
bundle exec rake redmine:additionals:setting_get RAILS_ENV=production setting="app_title"
|
||||
DESCRIPTION
|
||||
task setting_get: :environment do
|
||||
name = ENV['name'] ||= 'redmine'
|
||||
setting = ENV['setting']
|
||||
|
||||
if setting.blank?
|
||||
puts 'Parameters setting is required'
|
||||
exit 2
|
||||
end
|
||||
|
||||
if name == 'redmine'
|
||||
puts Setting.send(setting)
|
||||
else
|
||||
plugin_name = "plugin_#{name}".to_sym
|
||||
plugin_settings = Setting[plugin_name]
|
||||
puts plugin_settings[setting]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue