Use redmine_plugin_kit for plugin loading
This commit is contained in:
parent
ec0379492c
commit
cdf18cdbb4
@ -5,8 +5,9 @@ Changelog
|
||||
+++++
|
||||
|
||||
- Mermaid 8.13.4 support
|
||||
- D3 7.1.1 support
|
||||
- D3 7.2.0 support
|
||||
- Ruby 2.6 is required
|
||||
- Use redmine_plugin_kit gem as loader
|
||||
|
||||
3.0.3
|
||||
+++++
|
||||
|
1
Gemfile
1
Gemfile
@ -2,6 +2,7 @@
|
||||
|
||||
# Specify your gem's dependencies in additionals.gemspec
|
||||
gemspec
|
||||
gem 'redmine_plugin_kit', path: '~/dev/redmine_plugin_kit'
|
||||
|
||||
group :development do
|
||||
# this is only used for development.
|
||||
|
13
Rakefile
13
Rakefile
@ -1,13 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << 'test'
|
||||
files = FileList['test/**/*test.rb']
|
||||
t.test_files = files
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
task default: :test
|
@ -20,8 +20,8 @@ Gem::Specification.new do |spec|
|
||||
spec.require_paths = ['lib']
|
||||
spec.required_ruby_version = '>= 2.6'
|
||||
|
||||
spec.add_runtime_dependency 'deface', '1.8.1'
|
||||
spec.add_runtime_dependency 'gemoji', '~> 3.0.0'
|
||||
spec.add_runtime_dependency 'redmine_plugin_kit'
|
||||
spec.add_runtime_dependency 'render_async'
|
||||
spec.add_runtime_dependency 'rss'
|
||||
spec.add_runtime_dependency 'slim-rails'
|
||||
|
@ -2,13 +2,12 @@
|
||||
|
||||
module AdditionalsChartjsHelper
|
||||
def chartjs_colorschemes_info_url
|
||||
link_to(l(:label_chartjs_colorscheme_info),
|
||||
'https://nagix.github.io/chartjs-plugin-colorschemes/colorchart.html',
|
||||
class: 'external')
|
||||
link_to_external l(:label_chartjs_colorscheme_info),
|
||||
'https://nagix.github.io/chartjs-plugin-colorschemes/colorchart.html'
|
||||
end
|
||||
|
||||
def select_options_for_chartjs_colorscheme(selected)
|
||||
data = AdditionalsLoader.yaml_config_load 'colorschemes.yml'
|
||||
data = RedminePluginKit::Loader.new(plugin_id: 'additionals').yaml_config_load 'colorschemes.yml'
|
||||
grouped_options_for_select data, selected
|
||||
end
|
||||
end
|
||||
|
@ -31,7 +31,7 @@ module AdditionalsSettingsHelper
|
||||
checked = if custom_value && !value_is_bool
|
||||
active_value
|
||||
else
|
||||
Additionals.true? active_value
|
||||
RedminePluginKit.true? active_value
|
||||
end
|
||||
|
||||
s = [label_tag(tag_name, label_title)]
|
||||
|
@ -16,7 +16,7 @@ module DashboardsHelper
|
||||
dashboard.enable_sidebar?
|
||||
end
|
||||
else
|
||||
Additionals.true? params['enable_sidebar']
|
||||
RedminePluginKit.true? params['enable_sidebar']
|
||||
end
|
||||
end
|
||||
|
||||
@ -211,7 +211,7 @@ module DashboardsHelper
|
||||
return
|
||||
end
|
||||
|
||||
content = render_dashboard_block_content block, block_definition, dashboard, overwritten_settings
|
||||
content = render_dashboard_block_content block, block_definition, dashboard, **overwritten_settings
|
||||
return if content.blank?
|
||||
|
||||
if dashboard.editable?
|
||||
@ -260,7 +260,7 @@ module DashboardsHelper
|
||||
|
||||
def render_async_options(settings, async)
|
||||
options = {}
|
||||
if Additionals.true? settings[:auto_refresh]
|
||||
if RedminePluginKit.true? settings[:auto_refresh]
|
||||
options[:interval] = (async[:cache_expires_in] || DashboardContent::RENDER_ASYNC_CACHE_EXPIRES_IN) * 1000
|
||||
end
|
||||
|
||||
@ -381,7 +381,7 @@ module DashboardsHelper
|
||||
max_entries = (settings[:max_entries] || DashboardContent::DEFAULT_MAX_ENTRIES).to_i
|
||||
user = User.current
|
||||
options = {}
|
||||
options[:author] = user if Additionals.true? settings[:me_only]
|
||||
options[:author] = user if RedminePluginKit.true? settings[:me_only]
|
||||
options[:project] = dashboard.content_project if dashboard.content_project.present?
|
||||
|
||||
Redmine::Activity::Fetcher.new(user, options)
|
||||
@ -435,7 +435,7 @@ module DashboardsHelper
|
||||
private
|
||||
|
||||
# Renders a single block content
|
||||
def render_dashboard_block_content(block, block_definition, dashboard, overwritten_settings = {})
|
||||
def render_dashboard_block_content(block, block_definition, dashboard, **overwritten_settings)
|
||||
settings = dashboard.layout_settings block
|
||||
settings = settings.merge overwritten_settings if overwritten_settings.present?
|
||||
|
||||
|
@ -8,7 +8,7 @@ class AdditionalsFontAwesome
|
||||
|
||||
class << self
|
||||
def load_icons(type)
|
||||
data = AdditionalsLoader.yaml_config_load 'fontawesome_icons.yml'
|
||||
data = RedminePluginKit::Loader.new(plugin_id: 'additionals').yaml_config_load 'fontawesome_icons.yml'
|
||||
icons = {}
|
||||
data.each do |key, values|
|
||||
icons[key] = { unicode: values['unicode'], label: values['label'] } if values['styles'].include? convert_type2style(type)
|
||||
|
@ -8,7 +8,9 @@ class AdditionalsInfo
|
||||
value: system_info },
|
||||
system_uptime: { label: l(:label_uptime),
|
||||
value: system_uptime,
|
||||
api_value: system_uptime(format: :datetime) } }
|
||||
api_value: system_uptime(format: :datetime) },
|
||||
redmine_plugin_kit: { label: 'Redmine Plugin Kit',
|
||||
value: RedminePluginKit::VERSION } }
|
||||
end
|
||||
|
||||
def system_info
|
||||
|
@ -1,263 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AdditionalsLoader
|
||||
class ExistingControllerPatchForHelper < StandardError; end
|
||||
|
||||
attr_accessor :plugin_id, :debug
|
||||
|
||||
DEFAULT_PLUGIN_ID = 'additionals'
|
||||
|
||||
class << self
|
||||
def default_settings(plugin_id = DEFAULT_PLUGIN_ID)
|
||||
cached_settings_name = "@default_settings_#{plugin_id}"
|
||||
cached_settings = instance_variable_get cached_settings_name
|
||||
if cached_settings.nil?
|
||||
data = yaml_config_load 'settings.yml', with_erb: true, plugin_id: plugin_id
|
||||
instance_variable_set cached_settings_name, data.symbolize_keys
|
||||
else
|
||||
cached_settings
|
||||
end
|
||||
end
|
||||
|
||||
def yaml_config_load(yaml_file, plugin_id: DEFAULT_PLUGIN_ID, with_erb: false)
|
||||
file_to_load = File.read File.join(plugin_dir(plugin_id), 'config', yaml_file)
|
||||
file_to_load = ERB.new(file_to_load).result if with_erb
|
||||
|
||||
YAML.safe_load(file_to_load) || {}
|
||||
end
|
||||
|
||||
def plugin_dir(plugin_id = DEFAULT_PLUGIN_ID)
|
||||
if Gem.loaded_specs[plugin_id].nil?
|
||||
File.join Redmine::Plugin.directory, plugin_id
|
||||
else
|
||||
Gem.loaded_specs[plugin_id].full_gem_path
|
||||
end
|
||||
end
|
||||
|
||||
def to_prepare(*args, &block)
|
||||
if Rails.version > '6.0'
|
||||
# INFO: https://www.redmine.org/issues/36245
|
||||
Rails.logger.info 'after_plugins_loaded hook should be used instead'
|
||||
else
|
||||
# ActiveSupport::Reloader.to_prepare(*args, &block)
|
||||
Rails.configuration.to_prepare(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def persisting
|
||||
Additionals.debug 'Loading persisting...'
|
||||
yield
|
||||
end
|
||||
|
||||
def after_initialize(&block)
|
||||
Additionals.debug 'After initialize...'
|
||||
Rails.application.config.after_initialize(&block)
|
||||
end
|
||||
|
||||
def load_hooks!(plugin_id = DEFAULT_PLUGIN_ID)
|
||||
target = plugin_id.camelize.constantize
|
||||
target::Hooks
|
||||
end
|
||||
|
||||
def deface_setup!
|
||||
Rails.application.paths['app/overrides'] ||= []
|
||||
Dir.glob(Rails.root.join('plugins/*/app/overrides')).each do |dir|
|
||||
Rails.application.paths['app/overrides'] << dir unless Rails.application.paths['app/overrides'].include? dir
|
||||
end
|
||||
end
|
||||
|
||||
# required multiple times because of this bug: https://www.redmine.org/issues/33290
|
||||
def redmine_database_ready?(with_table = nil)
|
||||
ActiveRecord::Base.connection
|
||||
rescue ActiveRecord::NoDatabaseError
|
||||
false
|
||||
else
|
||||
with_table.nil? || ActiveRecord::Base.connection.table_exists?(with_table)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(plugin_id: DEFAULT_PLUGIN_ID, debug: false)
|
||||
self.plugin_id = plugin_id
|
||||
self.debug = debug
|
||||
|
||||
apply_reset
|
||||
end
|
||||
|
||||
def apply_reset
|
||||
@patches = []
|
||||
@helpers = []
|
||||
@global_helpers = []
|
||||
end
|
||||
|
||||
def plugin_dir
|
||||
@plugin_dir ||= self.class.plugin_dir plugin_id
|
||||
end
|
||||
|
||||
# use_app: false => :plugin_dir/lib/:plugin_id directory
|
||||
def require_files(spec, use_app: false, reverse: false, ignore_autoload: false)
|
||||
dir = if use_app
|
||||
File.join plugin_dir, 'app', spec
|
||||
else
|
||||
File.join plugin_dir, 'lib', plugin_id, spec
|
||||
end
|
||||
|
||||
files = Dir[dir].sort
|
||||
|
||||
files.reverse! if reverse
|
||||
files.each do |f|
|
||||
Rails.autoloaders.main.ignore << f if Rails.version > '6.0' && ignore_autoload
|
||||
require f
|
||||
end
|
||||
end
|
||||
|
||||
def incompatible?(plugins = [])
|
||||
plugins.each do |plugin|
|
||||
raise "\n\033[31m#{plugin_id} plugin cannot be used with #{plugin} plugin.\033[0m" if Redmine::Plugin.installed? plugin
|
||||
end
|
||||
end
|
||||
|
||||
def load_macros!
|
||||
require_files File.join('wiki_macros', '**/*_macro.rb')
|
||||
end
|
||||
|
||||
def load_custom_field_format!(reverse: false)
|
||||
require_files File.join('custom_field_formats', '**/*_format.rb'),
|
||||
reverse: reverse,
|
||||
ignore_autoload: true
|
||||
end
|
||||
|
||||
def add_patch(patch)
|
||||
if patch.is_a? Array
|
||||
@patches += patch
|
||||
else
|
||||
@patches << patch
|
||||
end
|
||||
end
|
||||
|
||||
def add_helper(helper)
|
||||
if helper.is_a? Array
|
||||
@helpers += helper
|
||||
else
|
||||
@helpers << helper
|
||||
end
|
||||
end
|
||||
|
||||
def add_global_helper(helper)
|
||||
if helper.is_a? Array
|
||||
@global_helpers += helper
|
||||
else
|
||||
@global_helpers << helper
|
||||
end
|
||||
end
|
||||
|
||||
def apply!
|
||||
validate_apply
|
||||
|
||||
apply_patches!
|
||||
apply_helpers!
|
||||
apply_global_helpers!
|
||||
|
||||
# reset patches and helpers
|
||||
apply_reset
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_apply
|
||||
return if @helpers.none? || @patches.none?
|
||||
|
||||
controller_patches = @patches.select do |p|
|
||||
if p.is_a? String
|
||||
true unless p == p.chomp('Controller')
|
||||
else
|
||||
c = p[:target].to_s
|
||||
true unless c == c.chomp('Controller')
|
||||
end
|
||||
end
|
||||
|
||||
@helpers.each do |h|
|
||||
helper_controller = if h.is_a? String
|
||||
"#{h}Controller"
|
||||
else
|
||||
c = h[:controller]
|
||||
if c.is_a? String
|
||||
"#{c}Controller"
|
||||
else
|
||||
c.to_s
|
||||
end
|
||||
end
|
||||
|
||||
if controller_patches.include? helper_controller
|
||||
raise ExistingControllerPatchForHelper, "Do not add helper to #{helper_controller} if patch exists (#{plugin_id})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def apply_patches!
|
||||
patches = @patches.map do |p|
|
||||
if p.is_a? String
|
||||
{ target: p.constantize, patch: p }
|
||||
else
|
||||
patch = p[:patch] || p[:target].to_s
|
||||
{ target: p[:target], patch: patch }
|
||||
end
|
||||
end
|
||||
|
||||
patches.uniq!
|
||||
Additionals.debug "patches for #{plugin_id}: #{patches.inspect}" if debug
|
||||
|
||||
patches.each do |patch|
|
||||
patch_module = if patch[:patch].is_a? String
|
||||
patch_dir = "#{plugin_dir}/lib/#{plugin_id}/patches"
|
||||
require "#{patch_dir}/#{patch[:patch].underscore}_patch"
|
||||
"#{plugin_id.camelize}::Patches::#{patch[:patch]}Patch".constantize
|
||||
else
|
||||
# if module specified (if not string), use it
|
||||
patch[:patch]
|
||||
end
|
||||
|
||||
target = patch[:target]
|
||||
target.include patch_module unless target.included_modules.include? patch_module
|
||||
end
|
||||
end
|
||||
|
||||
def apply_helpers!
|
||||
helpers = @helpers.map do |h|
|
||||
if h.is_a? String
|
||||
{ controller: "#{h}Controller".constantize, helper: "#{plugin_id.camelize}#{h}Helper".constantize }
|
||||
else
|
||||
c = h[:controller].is_a?(String) ? "#{h[:controller]}Controller".constantize : h[:controller]
|
||||
helper = if h[:helper]
|
||||
h[:helper]
|
||||
else
|
||||
helper_name = if h[:controller].is_a? String
|
||||
h[:controller]
|
||||
else
|
||||
h[:controller].to_s.chomp 'Controller'
|
||||
end
|
||||
"#{plugin_id.camelize}#{helper_name}Helper".constantize
|
||||
end
|
||||
{ controller: c, helper: helper }
|
||||
end
|
||||
end
|
||||
|
||||
helpers.uniq!
|
||||
Additionals.debug "helpers for #{plugin_id}: #{helpers.inspect}" if debug
|
||||
|
||||
helpers.each do |h|
|
||||
target = h[:controller]
|
||||
target.send :helper, h[:helper]
|
||||
end
|
||||
end
|
||||
|
||||
def apply_global_helpers!
|
||||
global_helpers = @global_helpers.uniq
|
||||
Additionals.debug "global helpers for #{plugin_id}: #{global_helpers.inspect}" if debug
|
||||
|
||||
global_helpers.each do |h|
|
||||
ActionView::Base.include h
|
||||
end
|
||||
end
|
||||
end
|
@ -307,7 +307,7 @@ class Dashboard < ActiveRecord::Base
|
||||
config = { dashboard_id: id,
|
||||
block: block }
|
||||
|
||||
if Additionals.false? options[:skip_user_id]
|
||||
if RedminePluginKit.false? options[:skip_user_id]
|
||||
settings[:user_id] = User.current.id
|
||||
settings[:user_is_admin] = User.current.admin?
|
||||
end
|
||||
|
@ -12,6 +12,5 @@
|
||||
- if Additionals.setting? :open_external_urls
|
||||
javascript:
|
||||
$(function() {
|
||||
$('a.external').attr({ 'target': '_blank',
|
||||
'rel': 'noopener noreferrer'});
|
||||
openExternalUrlsInTab();
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
- dashboard_async_cache dashboard, block, async, settings do
|
||||
|
||||
- events_by_day = activity_dashboard_data settings, dashboard
|
||||
- title = Additionals.true?(settings[:me_only]) ? l(:label_my_activity) : l(:label_activity)
|
||||
- title = RedminePluginKit.true?(settings[:me_only]) ? l(:label_my_activity) : l(:label_activity)
|
||||
h3 = link_to title, activity_path(user_id: User.current,
|
||||
from: events_by_day.keys.first)
|
||||
|
||||
|
@ -15,3 +15,9 @@
|
||||
p.nodata = l :label_no_data
|
||||
- else
|
||||
p.nodata = l :label_invalid_feed_data
|
||||
|
||||
- if Additionals.setting? :open_external_urls
|
||||
javascript:
|
||||
$(function() {
|
||||
openExternalUrlsInTab();
|
||||
});
|
||||
|
@ -10,7 +10,11 @@ h3 = block_definition[:label]
|
||||
span.label
|
||||
= l :field_homepage
|
||||
' :
|
||||
= link_to_if uri_with_safe_scheme?(@project.homepage), @project.homepage, @project.homepage, class: 'external'
|
||||
- if uri_with_safe_scheme? @project.homepage
|
||||
= link_to_url @project.homepage
|
||||
- else
|
||||
= @project.homepage
|
||||
|
||||
- render_custom_field_values @project do |custom_field, formatted|
|
||||
li class="#{custom_field.css_classes}"
|
||||
span.label
|
||||
|
@ -31,3 +31,9 @@
|
||||
p.nodata = l :label_no_data
|
||||
- else
|
||||
p.nodata = l :label_no_data
|
||||
|
||||
- if Additionals.setting? :open_external_urls
|
||||
javascript:
|
||||
$(function() {
|
||||
openExternalUrlsInTab();
|
||||
});
|
||||
|
@ -15,6 +15,13 @@ function setClipboardJS(element){
|
||||
});
|
||||
}
|
||||
|
||||
/* exported openExternalUrlsInTab */
|
||||
function openExternalUrlsInTab() {
|
||||
$('a.external').attr({
|
||||
'target': '_blank',
|
||||
'rel': 'noopener noreferrer'});
|
||||
}
|
||||
|
||||
/* exported formatNameWithIcon */
|
||||
function formatNameWithIcon(opt) {
|
||||
if (opt.loading) return opt.name;
|
||||
|
4
assets/javascripts/d3.min.js
vendored
4
assets/javascripts/d3.min.js
vendored
File diff suppressed because one or more lines are too long
@ -178,7 +178,7 @@ It provides :
|
||||
* `Chart.js Plugin colorschemes 0.4.0 <https://github.com/nagix/chartjs-plugin-colorschemes>`_
|
||||
* `Chart.js Plugin datalabels 0.7.0 <https://github.com/chartjs/chartjs-plugin-datalabels>`_
|
||||
* `clipboardJS 2.0.8 <https://clipboardjs.com/>`_
|
||||
* `d3 7.1.1 <https://d3js.org/>`_
|
||||
* `d3 7.2.0 <https://d3js.org/>`_
|
||||
* `d3plus v2.0.0-alpha.30 <https://d3plus.org/>`_
|
||||
* `FontAwesome 5.15.4 <https://fontawesome.com/>`_
|
||||
* `mermaid 8.13.4 <https://github.com/mermaid-js/mermaid>`_
|
||||
|
14
init.rb
14
init.rb
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'additionals/plugin_version'
|
||||
loader = RedminePluginKit::Loader.new plugin_id: 'additionals'
|
||||
|
||||
Redmine::Plugin.register :additionals do
|
||||
name 'Additionals'
|
||||
@ -11,7 +12,7 @@ Redmine::Plugin.register :additionals do
|
||||
url 'https://github.com/alphanodes/additionals'
|
||||
directory __dir__
|
||||
|
||||
default_settings = AdditionalsLoader.default_settings
|
||||
default_settings = loader.default_settings
|
||||
5.times do |i|
|
||||
default_settings["custom_menu#{i}_name"] = ''
|
||||
default_settings["custom_menu#{i}_url"] = ''
|
||||
@ -51,22 +52,19 @@ Redmine::Plugin.register :additionals do
|
||||
menu :admin_menu, :additionals, { controller: 'settings', action: 'plugin', id: 'additionals' }, caption: :label_additionals
|
||||
end
|
||||
|
||||
AdditionalsLoader.persisting do
|
||||
RedminePluginKit::Loader.persisting do
|
||||
Redmine::AccessControl.include Additionals::Patches::AccessControlPatch
|
||||
Redmine::AccessControl.singleton_class.prepend Additionals::Patches::AccessControlClassPatch
|
||||
|
||||
# Deface support
|
||||
AdditionalsLoader.deface_setup!
|
||||
|
||||
# Hooks
|
||||
AdditionalsLoader.load_hooks!
|
||||
loader.load_model_hooks!
|
||||
end
|
||||
|
||||
AdditionalsLoader.after_initialize do
|
||||
RedminePluginKit::Loader.after_initialize do
|
||||
# @TODO: this should be moved to AdditionalsFontAwesome and use an instance of it
|
||||
FONTAWESOME_ICONS = { fab: AdditionalsFontAwesome.load_icons(:fab), # rubocop: disable Lint/ConstantDefinitionInBlock
|
||||
far: AdditionalsFontAwesome.load_icons(:far),
|
||||
fas: AdditionalsFontAwesome.load_icons(:fas) }.freeze
|
||||
end
|
||||
|
||||
AdditionalsLoader.to_prepare { Additionals.setup } if Rails.version < '6.0'
|
||||
RedminePluginKit::Loader.to_prepare { Additionals.setup!(loader) } if Rails.version < '6.0'
|
||||
|
@ -7,93 +7,9 @@ module Additionals
|
||||
GOTO_LIST = " \xc2\xbb"
|
||||
LIST_SEPARATOR = "#{GOTO_LIST} "
|
||||
|
||||
include RedminePluginKit::PluginBase
|
||||
|
||||
class << self
|
||||
def setup
|
||||
RenderAsync.configuration.jquery = true
|
||||
|
||||
loader = AdditionalsLoader.new
|
||||
|
||||
loader.incompatible? %w[redmine_editauthor
|
||||
redmine_changeauthor
|
||||
redmine_auto_watch]
|
||||
|
||||
loader.add_patch %w[ApplicationController
|
||||
AutoCompletesController
|
||||
Issue
|
||||
IssuePriority
|
||||
TimeEntry
|
||||
Project
|
||||
Wiki
|
||||
ProjectsController
|
||||
WelcomeController
|
||||
ReportsController
|
||||
Principal
|
||||
Query
|
||||
QueryFilter
|
||||
Role
|
||||
User
|
||||
UserPreference]
|
||||
|
||||
loader.add_helper %w[Issues
|
||||
Settings
|
||||
Wiki
|
||||
CustomFields]
|
||||
|
||||
loader.add_global_helper [Additionals::Helpers,
|
||||
AdditionalsFontawesomeHelper,
|
||||
AdditionalsMenuHelper,
|
||||
AdditionalsSelect2Helper]
|
||||
|
||||
Redmine::WikiFormatting.format_names.each do |format|
|
||||
case format
|
||||
when 'markdown'
|
||||
loader.add_patch [{ target: Redmine::WikiFormatting::Markdown::HTML, patch: 'FormatterMarkdown' },
|
||||
{ target: Redmine::WikiFormatting::Markdown::Helper, patch: 'FormattingHelper' }]
|
||||
when 'textile'
|
||||
loader.add_patch [{ target: Redmine::WikiFormatting::Textile::Formatter, patch: 'FormatterTextile' },
|
||||
{ target: Redmine::WikiFormatting::Textile::Helper, patch: 'FormattingHelper' }]
|
||||
end
|
||||
end
|
||||
|
||||
# Apply patches and helper
|
||||
loader.apply!
|
||||
|
||||
# Macros
|
||||
loader.load_macros!
|
||||
end
|
||||
|
||||
# support with default setting as fall back
|
||||
def setting(value)
|
||||
if settings.key? value
|
||||
settings[value]
|
||||
else
|
||||
AdditionalsLoader.default_settings[value]
|
||||
end
|
||||
end
|
||||
|
||||
def setting?(value)
|
||||
true? setting(value)
|
||||
end
|
||||
|
||||
def true?(value)
|
||||
return false if value.is_a? FalseClass
|
||||
return true if value.is_a?(TrueClass) || value.to_i == 1 || value.to_s.casecmp('true').zero?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# false if false or nil
|
||||
def false?(value)
|
||||
!true?(value)
|
||||
end
|
||||
|
||||
def debug(message = 'running')
|
||||
return if Rails.env.production?
|
||||
|
||||
msg = message.is_a?(String) ? message : message.inspect
|
||||
Rails.logger.debug { "#{Time.current.strftime '%H:%M:%S'} DEBUG [#{caller_locations(1..1).first.label}]: #{msg}" }
|
||||
end
|
||||
|
||||
def class_prefix(klass)
|
||||
klass_name = klass.is_a?(String) ? klass : klass.name
|
||||
klass_name.underscore.tr '/', '_'
|
||||
@ -154,16 +70,70 @@ module Additionals
|
||||
ids.take limit
|
||||
end
|
||||
|
||||
def debug(message = 'running')
|
||||
RedminePluginKit::Debug.log message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def settings
|
||||
Setting[:plugin_additionals]
|
||||
def setup
|
||||
RenderAsync.configuration.jquery = true
|
||||
|
||||
loader.incompatible? %w[redmine_editauthor
|
||||
redmine_changeauthor
|
||||
redmine_auto_watch]
|
||||
|
||||
loader.add_patch %w[ApplicationController
|
||||
AutoCompletesController
|
||||
Issue
|
||||
IssuePriority
|
||||
TimeEntry
|
||||
Project
|
||||
Wiki
|
||||
ProjectsController
|
||||
WelcomeController
|
||||
ReportsController
|
||||
Principal
|
||||
Query
|
||||
QueryFilter
|
||||
Role
|
||||
User
|
||||
UserPreference]
|
||||
|
||||
loader.add_helper %w[Issues
|
||||
Settings
|
||||
Wiki
|
||||
CustomFields]
|
||||
|
||||
loader.add_global_helper [Additionals::Helpers,
|
||||
AdditionalsFontawesomeHelper,
|
||||
AdditionalsMenuHelper,
|
||||
AdditionalsSelect2Helper]
|
||||
|
||||
Redmine::WikiFormatting.format_names.each do |format|
|
||||
case format
|
||||
when 'markdown'
|
||||
loader.add_patch [{ target: Redmine::WikiFormatting::Markdown::HTML, patch: 'FormatterMarkdown' },
|
||||
{ target: Redmine::WikiFormatting::Markdown::Helper, patch: 'FormattingHelper' }]
|
||||
when 'textile'
|
||||
loader.add_patch [{ target: Redmine::WikiFormatting::Textile::Formatter, patch: 'FormatterTextile' },
|
||||
{ target: Redmine::WikiFormatting::Textile::Helper, patch: 'FormattingHelper' }]
|
||||
end
|
||||
end
|
||||
|
||||
# Apply patches and helper
|
||||
loader.apply!
|
||||
|
||||
# Macros
|
||||
loader.load_macros!
|
||||
|
||||
# Load view hooks
|
||||
loader.load_view_hooks!
|
||||
end
|
||||
end
|
||||
|
||||
# Run the classic redmine plugin initializer after rails boot
|
||||
class Plugin < ::Rails::Engine
|
||||
require 'deface'
|
||||
require 'emoji'
|
||||
require 'render_async'
|
||||
require 'rss'
|
||||
@ -179,7 +149,7 @@ module Additionals
|
||||
|
||||
# gem is used as redmine plugin
|
||||
require File.expand_path '../init', __dir__
|
||||
Additionals.setup if Rails.version < '6.0'
|
||||
Additionals.setup! if Rails.version < '6.0'
|
||||
Additionals::Gemify.install_assets plugin_id
|
||||
Additionals::Gemify.create_plugin_hint plugin_id
|
||||
end
|
||||
|
@ -1,5 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'emoji'
|
||||
module Additionals
|
||||
module Formatter
|
||||
SMILEYS = { 'smiley' => ':-?\)', # :)
|
||||
|
@ -42,30 +42,6 @@ module Additionals
|
||||
l :label_live_search_hints, value: all_fields
|
||||
end
|
||||
|
||||
def link_to_external(name, link, **options)
|
||||
options[:class] ||= 'external'
|
||||
options[:class] = "#{options[:class]} external" if options[:class].exclude? 'external'
|
||||
|
||||
options[:rel] ||= 'noopener'
|
||||
options[:target] ||= '_blank'
|
||||
|
||||
link_to name, link, options
|
||||
end
|
||||
|
||||
def link_to_url(url, **options)
|
||||
return if url.blank?
|
||||
|
||||
parts = url.split '://'
|
||||
name = if parts.count.positive?
|
||||
parts.shift
|
||||
parts.join
|
||||
else
|
||||
url
|
||||
end
|
||||
|
||||
link_to_external name, url, **options
|
||||
end
|
||||
|
||||
def additionals_list_title(name:, obj: nil, obj_link: nil, query: nil)
|
||||
title = []
|
||||
case obj
|
||||
@ -194,7 +170,7 @@ module Additionals
|
||||
end
|
||||
|
||||
def format_yes(value, lowercase: false)
|
||||
if Additionals.true? value
|
||||
if RedminePluginKit.true? value
|
||||
lowercase ? l(:general_text_yes) : l(:general_text_Yes)
|
||||
else
|
||||
lowercase ? l(:general_text_no) : l(:general_text_No)
|
||||
|
13
lib/additionals/hooks/model_hook.rb
Normal file
13
lib/additionals/hooks/model_hook.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Additionals
|
||||
module Hooks
|
||||
class ModelHook < Redmine::Hook::Listener
|
||||
def after_plugins_loaded(_context = {})
|
||||
return if Rails.version < '6.0'
|
||||
|
||||
Additionals.setup!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -2,7 +2,7 @@
|
||||
|
||||
module Additionals
|
||||
module Hooks
|
||||
class AdditionalsHookListener < Redmine::Hook::ViewListener
|
||||
class ViewHook < Redmine::Hook::ViewListener
|
||||
include IssuesHelper
|
||||
include AdditionalsIssuesHelper
|
||||
|
||||
@ -24,10 +24,6 @@ module Additionals
|
||||
|
||||
render_on :view_projects_issue_settings, partial: 'projects/additionals_settings_issues'
|
||||
|
||||
def after_plugins_loaded(_context = {})
|
||||
Additionals.setup if Rails.version > '6.0'
|
||||
end
|
||||
|
||||
def helper_issues_show_detail_after_setting(context = {})
|
||||
detail = context[:detail]
|
||||
return unless detail.prop_key == 'author_id'
|
@ -11,7 +11,7 @@ module Additionals
|
||||
end
|
||||
|
||||
def disabled_project_modules
|
||||
@database_ready = (AdditionalsLoader.redmine_database_ready? Setting.table_name) unless defined? @database_ready
|
||||
@database_ready = (RedminePluginKit::Loader.redmine_database_ready? Setting.table_name) unless defined? @database_ready
|
||||
return [] unless @database_ready
|
||||
|
||||
mods = Additionals.setting(:disabled_modules).to_a.reject(&:blank?)
|
||||
|
@ -44,9 +44,8 @@ module Additionals
|
||||
raise '<edit_link> is not a Google document.' unless options[:edit_link].start_with? 'https://docs.google.com/'
|
||||
|
||||
s << tag.br
|
||||
s << link_to(font_awesome_icon('fab_google-drive', post_text: :label_open_in_google_docs),
|
||||
options[:edit_link],
|
||||
class: 'external')
|
||||
s << link_to_external(font_awesome_icon('fab_google-drive', post_text: :label_open_in_google_docs),
|
||||
options[:edit_link])
|
||||
end
|
||||
|
||||
safe_join s
|
||||
|
@ -35,7 +35,7 @@ module Additionals
|
||||
src: src,
|
||||
frameborder: 0,
|
||||
allowfullscreen: 'true')]
|
||||
s << link_to(l(:label_open_in_new_windows), src, class: 'external') if Additionals.true? options[:with_link]
|
||||
s << link_to(l(:label_open_in_new_windows), src, class: 'external') if RedminePluginKit.true? options[:with_link]
|
||||
|
||||
safe_join s
|
||||
elsif Setting.protocol == 'https'
|
||||
|
@ -39,7 +39,7 @@ module Additionals
|
||||
raise 'The correct usage is {{meteoblue(<location>[, days=x, color=BOOL])}}' if args.empty?
|
||||
|
||||
options[:days] = 4 if options[:days].blank?
|
||||
options[:coloured] = if Additionals.false? options[:color]
|
||||
options[:coloured] = if RedminePluginKit.false? options[:color]
|
||||
'monochrome'
|
||||
else
|
||||
'coloured'
|
||||
@ -79,7 +79,7 @@ module Additionals
|
||||
|
||||
def self.meteoblue_flag(options, name, default = tue)
|
||||
flag = +"#{name}="
|
||||
flag << if Additionals.true?(options[name]) || default
|
||||
flag << if RedminePluginKit.true?(options[name]) || default
|
||||
'1'
|
||||
else
|
||||
'0'
|
||||
|
@ -16,20 +16,20 @@ module Additionals
|
||||
|
||||
case name[0..1]
|
||||
when 'r/'
|
||||
link_to font_awesome_icon('fab_reddit', post_text: name),
|
||||
link_to_external font_awesome_icon('fab_reddit', post_text: name),
|
||||
"https://www.reddit.com/#{name}",
|
||||
class: 'external reddit',
|
||||
class: 'reddit',
|
||||
title: l(:label_reddit_subject)
|
||||
when 'u/'
|
||||
link_to font_awesome_icon('fab_reddit-square', post_text: name),
|
||||
link_to_external font_awesome_icon('fab_reddit-square', post_text: name),
|
||||
"https://www.reddit.com/username/#{name[2..]}",
|
||||
class: 'external reddit',
|
||||
class: 'reddit',
|
||||
title: l(:label_reddit_user_account)
|
||||
else
|
||||
name = "r/#{name}"
|
||||
link_to font_awesome_icon('fab_reddit', post_text: name),
|
||||
link_to_external font_awesome_icon('fab_reddit', post_text: name),
|
||||
"https://www.reddit.com/#{name}",
|
||||
class: 'external reddit',
|
||||
class: 'reddit',
|
||||
title: l(:label_reddit_subject)
|
||||
end
|
||||
end
|
||||
|
@ -34,10 +34,10 @@ module Additionals
|
||||
link = "https://www.redmine.org/issues/#{raw_link}"
|
||||
end
|
||||
|
||||
link_options = { class: 'external redmine-link' }
|
||||
link_options = { class: 'redmine-link' }
|
||||
link_options[:title] = options[:title].presence || l(:label_redmine_org_issue)
|
||||
|
||||
link_to "##{link_name}", link, link_options
|
||||
link_to_external "##{link_name}", link, **link_options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -15,20 +15,20 @@ module Additionals
|
||||
name = args[0].strip
|
||||
case name[0]
|
||||
when '@'
|
||||
link_to(font_awesome_icon('fab_twitter', post_text: name),
|
||||
link_to_external font_awesome_icon('fab_twitter', post_text: name),
|
||||
"https://twitter.com/#{name[1..]}",
|
||||
class: 'external twitter',
|
||||
title: l(:label_twitter_account))
|
||||
class: 'twitter',
|
||||
title: l(:label_twitter_account)
|
||||
when '#'
|
||||
link_to(font_awesome_icon('fab_twitter-square', post_text: name),
|
||||
link_to_external font_awesome_icon('fab_twitter-square', post_text: name),
|
||||
"https://twitter.com/hashtag/#{name[1..]}",
|
||||
class: 'external twitter',
|
||||
title: l(:label_twitter_hashtag))
|
||||
class: 'twitter',
|
||||
title: l(:label_twitter_hashtag)
|
||||
else
|
||||
link_to(font_awesome_icon('fab_twitter', post_text: " @#{name}"),
|
||||
link_to_external font_awesome_icon('fab_twitter', post_text: " @#{name}"),
|
||||
"https://twitter.com/#{name}",
|
||||
class: 'external twitter',
|
||||
title: l(:label_twitter_account))
|
||||
class: 'twitter',
|
||||
title: l(:label_twitter_account)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -27,7 +27,7 @@ module Additionals
|
||||
raise 'The correct usage is {{vimeo(<video key>[, width=x, height=y])}}' if args.empty?
|
||||
|
||||
v = args[0]
|
||||
src = if Additionals.true? options[:autoplay]
|
||||
src = if RedminePluginKit.true? options[:autoplay]
|
||||
"//player.vimeo.com/video/#{v}?autoplay=1"
|
||||
else
|
||||
"//player.vimeo.com/video/#{v}"
|
||||
|
@ -27,7 +27,7 @@ module Additionals
|
||||
raise 'The correct usage is {{youtube(<video key>[, width=x, height=y])}}' if args.empty?
|
||||
|
||||
v = args[0]
|
||||
src = if Additionals.true? options[:autoplay]
|
||||
src = if RedminePluginKit.true? options[:autoplay]
|
||||
"//www.youtube.com/embed/#{v}?autoplay=1"
|
||||
else
|
||||
"//www.youtube-nocookie.com/embed/#{v}"
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"stylelint": "^14.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -14,36 +14,6 @@ class AdditionalsTest < Additionals::TestCase
|
||||
prepare_tests
|
||||
end
|
||||
|
||||
def test_true
|
||||
assert Additionals.true? 1
|
||||
assert Additionals.true? true
|
||||
assert Additionals.true? 'true'
|
||||
assert Additionals.true? 'True'
|
||||
|
||||
assert_not Additionals.true?(-1)
|
||||
assert_not Additionals.true? 0
|
||||
assert_not Additionals.true? '0'
|
||||
assert_not Additionals.true? 1000
|
||||
assert_not Additionals.true? false
|
||||
assert_not Additionals.true? 'false'
|
||||
assert_not Additionals.true? 'False'
|
||||
assert_not Additionals.true? 'yes'
|
||||
assert_not Additionals.true? ''
|
||||
assert_not Additionals.true? nil
|
||||
assert_not Additionals.true? 'unknown'
|
||||
end
|
||||
|
||||
def test_false
|
||||
assert Additionals.false? false
|
||||
assert Additionals.false? nil
|
||||
assert Additionals.false? 'false'
|
||||
assert Additionals.false? 0
|
||||
|
||||
assert_not Additionals.false? 1
|
||||
assert_not Additionals.false? 'true'
|
||||
assert_not Additionals.false? 'True'
|
||||
end
|
||||
|
||||
def test_settings
|
||||
assert_raises NoMethodError do
|
||||
Additionals.settings[:open_external_urls]
|
||||
|
@ -4,6 +4,7 @@ require File.expand_path '../../../test_helper', __FILE__
|
||||
|
||||
class GlobalHelperTest < ActionView::TestCase
|
||||
include Additionals::Helpers
|
||||
include RedminePluginKit::Helpers::GlobalHelper
|
||||
include AdditionalsFontawesomeHelper
|
||||
include AdditionalsMenuHelper
|
||||
include CustomFieldsHelper
|
||||
|
@ -2,37 +2,41 @@
|
||||
|
||||
require File.expand_path '../../test_helper', __FILE__
|
||||
|
||||
class AdditionalsLoaderTest < Additionals::TestCase
|
||||
class RedminePluginKitLoaderTest < Additionals::TestCase
|
||||
def setup
|
||||
@plugin_id = 'additionals'
|
||||
end
|
||||
|
||||
def test_add_patch
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_patch 'Issue'
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_patch_as_hash
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_patch({ target: Issue, patch: 'Issue' })
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_patch_as_hash_without_patch
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_patch({ target: Issue })
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_multiple_patches
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_patch %w[Issue User]
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_invalid_patch
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_patch 'Issue2'
|
||||
|
||||
assert_raises NameError do
|
||||
@ -41,55 +45,42 @@ class AdditionalsLoaderTest < Additionals::TestCase
|
||||
end
|
||||
|
||||
def test_add_helper
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_helper 'Settings'
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_helper_as_hash
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_helper({ controller: SettingsController, helper: SettingsHelper })
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_helper_as_hash_as_string
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_helper({ controller: 'Settings', helper: 'Settings' })
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_helper_as_hash_controller_only
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_helper({ controller: SettingsController })
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_add_helper_as_hash_controller_only_string
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_helper({ controller: 'Settings' })
|
||||
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_load_macros
|
||||
loader = AdditionalsLoader.new
|
||||
macros = loader.load_macros!
|
||||
|
||||
assert macros.count.positive?
|
||||
assert(macros.detect { |macro| macro.include? 'fa_macro' })
|
||||
end
|
||||
|
||||
def test_load_hooks
|
||||
hooks = AdditionalsLoader.load_hooks!
|
||||
assert hooks.is_a? Module
|
||||
end
|
||||
|
||||
def test_require_files_for_lib
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
|
||||
spec = File.join 'wiki_macros', '**/*_macro.rb'
|
||||
files = loader.require_files spec
|
||||
@ -99,7 +90,7 @@ class AdditionalsLoaderTest < Additionals::TestCase
|
||||
end
|
||||
|
||||
def test_require_files_for_app
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
|
||||
spec = File.join 'helpers', '**/additionals_*.rb'
|
||||
files = loader.require_files spec, use_app: true
|
||||
@ -109,12 +100,12 @@ class AdditionalsLoaderTest < Additionals::TestCase
|
||||
end
|
||||
|
||||
def test_apply_without_data
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
assert loader.apply!
|
||||
end
|
||||
|
||||
def test_apply
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_helper 'Settings'
|
||||
loader.add_patch 'Issue'
|
||||
loader.add_global_helper Additionals::Helpers
|
||||
@ -122,22 +113,40 @@ class AdditionalsLoaderTest < Additionals::TestCase
|
||||
end
|
||||
|
||||
def test_do_not_allow_helper_if_controller_patch_exists
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_patch 'ProjectsController'
|
||||
loader.add_helper 'Projects'
|
||||
|
||||
assert_raises AdditionalsLoader::ExistingControllerPatchForHelper do
|
||||
assert_raises RedminePluginKit::Loader::ExistingControllerPatchForHelper do
|
||||
assert loader.apply!
|
||||
end
|
||||
end
|
||||
|
||||
def test_do_not_allow_helper_if_controller_patch_exists_as_hash
|
||||
loader = AdditionalsLoader.new
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
loader.add_patch 'ProjectsController'
|
||||
loader.add_helper({ controller: ProjectsController, helper: 'Settings' })
|
||||
|
||||
assert_raises AdditionalsLoader::ExistingControllerPatchForHelper do
|
||||
assert_raises RedminePluginKit::Loader::ExistingControllerPatchForHelper do
|
||||
assert loader.apply!
|
||||
end
|
||||
end
|
||||
|
||||
def test_load_model_hooks
|
||||
hooks = RedminePluginKit::Loader.new(plugin_id: @plugin_id).load_model_hooks!
|
||||
assert hooks.is_a? Module
|
||||
end
|
||||
|
||||
def test_load_hooks
|
||||
hooks = RedminePluginKit::Loader.new(plugin_id: @plugin_id).load_view_hooks!
|
||||
assert hooks.is_a? Module
|
||||
end
|
||||
|
||||
def test_load_macros
|
||||
loader = RedminePluginKit::Loader.new plugin_id: @plugin_id
|
||||
macros = loader.load_macros!
|
||||
|
||||
assert macros.count.positive?
|
||||
assert(macros.detect { |macro| macro.include? 'fa_macro' })
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user