redmine_extensions 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64e8df03758217430f1facaee877c27255442a0eef8c3f1f5889fab7f77509d1
4
- data.tar.gz: cd47a973209703791cb1a31b36e8b3de53a5ea2ea158f884cd5e2dcc8f26964e
3
+ metadata.gz: f1164cd52c5ad1f8091d8024064e2d14387ca07a9147254e51f60e5d3c2bcedc
4
+ data.tar.gz: 401da80865686976d559309770b5312bff0b1720fe2af74566902256c6915fcc
5
5
  SHA512:
6
- metadata.gz: ed937f82195898831bac284e8774cc806b3066d15a6b0ed9d8d8cd36bfa7cd79e1d6cc5401ffee08def1b42c8fcd34e751f230511dfb6ed2c605a63fedea0942
7
- data.tar.gz: c0542bed001dfa6c178be2792cb463d1ff1c6b4a6b1bc1a41414dcb5c9809c369e1669131fa7b444d4385867438aa48ed7d82b18a0e56a2429e6428b09f42cd9
6
+ metadata.gz: 35cb5a0137cf1235e9370fc12ad686d95ff0dc45915321228b785052801e174a96fe07678fa179c821d2767da78bd5125b8d05960214f67910b7d9fcc16c52ab
7
+ data.tar.gz: 7d811fc9b1a4fa69880fcf87708220dbe7644491dfbfefc1e10923932399713eea809e428222fc5ee1b759845bf4a5e0858a4c74188c30173b3dd352992ac66b
data/config/routes.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # Redmine routes
2
2
  Rails.application.routes.draw do
3
- resources :easy_settings, except: :destroy
3
+ resources :easy_settings, only: [:edit, :update]
4
4
  end
@@ -42,10 +42,6 @@ module RedmineExtensions
42
42
 
43
43
  # include helpers
44
44
  initializer 'redmine_extensions.rails_patching', before: :load_config_initializers do |_app|
45
- ActiveSupport.on_load(:action_controller_base) do
46
- helper RedmineExtensions::ApplicationHelper
47
- # helper RedmineExtensions::EasyQueryHelper
48
- end
49
45
  ActiveSupport.on_load(:active_record) do
50
46
  include RedmineExtensions::RailsPatches::ActiveRecord
51
47
  end
@@ -1,5 +1,5 @@
1
1
  module RedmineExtensions
2
2
 
3
- VERSION = '1.2.0'
3
+ VERSION = "2.0.0"
4
4
 
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redmine_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Easy Software Ltd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-16 00:00:00.000000000 Z
11
+ date: 2025-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -47,7 +47,6 @@ files:
47
47
  - app/assets/javascripts/redmine_extensions/redmine_extensions.js
48
48
  - app/assets/javascripts/redmine_extensions/render_polyfill.js
49
49
  - app/controllers/easy_settings_controller.rb
50
- - app/helpers/redmine_extensions/application_helper.rb
51
50
  - app/helpers/redmine_extensions/rendering_helper.rb
52
51
  - app/models/easy_entity_assignment.rb
53
52
  - app/models/easy_setting.rb
@@ -117,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
116
  - !ruby/object:Gem::Version
118
117
  version: '0'
119
118
  requirements: []
120
- rubygems_version: 3.3.26
119
+ rubygems_version: 3.5.11
121
120
  signing_key:
122
121
  specification_version: 4
123
122
  summary: Redmine Extensions is set of useful features for Redmine. Main focus is on
@@ -1,323 +0,0 @@
1
- module RedmineExtensions
2
- module ApplicationHelper
3
- include RenderingHelper
4
-
5
- # -------= Hack methods =------
6
-
7
- def plugin_settings_path(plugin, *attrs)
8
- if plugin.is_a?(Redmine::Plugin) && (plugin.settings[:only_easy] || plugin.settings[:easy_settings])
9
- edit_easy_setting_path(plugin, *attrs)
10
- else
11
- super
12
- end
13
- end
14
-
15
- # -------= Rendering and presenting methods =-------
16
-
17
- def present(model, options = {}, &block)
18
- if model.is_a?(RedmineExtensions::BasePresenter)
19
- presenter = model.update_options(options.merge(view_context: self))
20
- else
21
- presenter = RedmineExtensions::BasePresenter.present(model, self, options)
22
- end
23
- if block
24
- yield presenter
25
- else
26
- presenter
27
- end
28
- end
29
-
30
- # --- COMMON RENDERING ----
31
-
32
- # hide elements for issues and users
33
- def detect_hide_elements(uniq_id, user = nil, default = true)
34
- return if uniq_id.blank?
35
-
36
- 'style="display:none"'.html_safe if !toggle_button_expanded?(uniq_id, user, default)
37
- end
38
-
39
- def url_to_entity(entity, options = {})
40
- m = "url_to_#{entity.class.name.underscore}".to_sym
41
- if respond_to?(m)
42
- send(m, entity, options)
43
- else
44
- nil
45
- end
46
- end
47
-
48
- def query_for_entity(entity_class)
49
- entity_class_name = entity_class.name
50
- query_class = "Easy#{entity_class_name}Query".constantize rescue nil
51
- return query_class if query_class && query_class < EasyQuery
52
- query_class ||= "#{entity_class_name}Query".constantize rescue nil
53
- end
54
-
55
- def render_entity_assignments(entity, target_entity, options = {}, &block)
56
- options ||= {}
57
- collection_name = options.delete(:collection_name) || target_entity.name.pluralize.underscore
58
- query_class = query_for_entity(target_entity)
59
-
60
- return '' if !query_class || !entity.respond_to?(collection_name)
61
-
62
- project = options.delete(:project)
63
-
64
- query = query_class.new(:name => 'c_query')
65
- query.project = project
66
- query.set_entity_scope(entity, collection_name)
67
- query.column_names = options[:query_column_names] unless options[:query_column_names].blank?
68
-
69
- entities = query.entities
70
-
71
- entities_count = entities.size
72
- options[:entities_count] = entities_count
73
- options[:module_name] ||= "entity_#{entity.class.name.underscore}_#{entity.id}_#{collection_name}"
74
- options[:heading] ||= l("label_#{query.entity.name.underscore}_plural", :default => 'Heading')
75
-
76
- if options[:context_menus_path].nil?
77
- options[:context_menus_path] = [
78
- "context_menu_#{collection_name}_path".to_sym,
79
- "context_menus_#{collection_name}_path".to_sym,
80
- "#{collection_name}_context_menu_path".to_sym
81
- ].detect do |m|
82
- m if respond_to?(m)
83
- end
84
- end
85
-
86
- query.output = options[:display_style] || (entities_count > 3 ? 'list' : 'tiles')
87
-
88
- render(:partial => 'easy_entity_assignments/assignments_container', :locals => {
89
- :entity => entity,
90
- :query => query, :project => project,
91
- :entities => entities, :entities_count => entities_count, :options => options })
92
- end
93
-
94
- def entity_css_icon(entity_or_entity_class)
95
- return '' if entity_or_entity_class.nil?
96
-
97
- if entity_or_entity_class.is_a?(Class) && entity_or_entity_class.respond_to?(:css_icon)
98
- entity_or_entity_class.css_icon
99
- elsif entity_or_entity_class.is_a?(ActiveRecord::Base)
100
- if entity_or_entity_class.respond_to?(:css_icon)
101
- entity_or_entity_class.css_icon
102
- elsif entity_or_entity_class.class.respond_to?(:css_icon)
103
- entity_or_entity_class.class.css_icon
104
- else
105
- "icon icon-#{entity_or_entity_class.class.name.dasherize}"
106
- end
107
- else
108
- "icon icon-#{entity_or_entity_class.class.name.dasherize}"
109
- end
110
- end
111
-
112
- def late_javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
113
- content =
114
- if block
115
- html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
116
- capture(&block)
117
- else
118
- content_or_options_with_block
119
- end
120
- html_options.reverse_merge!({ type: 'application/javascript' })
121
- priority = html_options.delete(:priority) || 0
122
- content = " EasyGem.schedule.late(function(){#{content} }, #{priority});"
123
-
124
- content_tag(:script, javascript_cdata_section(content), html_options)
125
- end
126
-
127
- def require_javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
128
- content =
129
- if block_given?
130
- html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
131
- capture(&block)
132
- else
133
- content_or_options_with_block
134
- end
135
- html_options.reverse_merge!(type: 'application/javascript')
136
- html_options[:data] ||= { container: '' }
137
- html_options[:data][:container].slice!("#module_inside_")
138
- content = "EasyGem.schedule.require(function(){#{content}}, function(){ return EASY.asyncModules.states['#{html_options[:data][:container]}']});"
139
-
140
- javascript_tag content.html_safe, html_options
141
- end
142
-
143
- def easy_avatar_url(user = nil)
144
- return avatar_url(user) if respond_to?(:avatar_url)
145
-
146
- user ||= User.current
147
- if Setting.gravatar_enabled?
148
- options = { ssl: (request&.ssl?), default: Setting.gravatar_default }
149
- email = if user.respond_to?(:mail)
150
- user.mail
151
- elsif user.to_s =~ %r{<(.+?)>}
152
- $1
153
- end
154
- email ? gravatar_url(email, options) : ''
155
- elsif user.easy_avatar_url.present?
156
- user.easy_avatar_url
157
- elsif user.respond_to?(:easy_avatar) && (av = user.easy_avatar).present? && (img_url = av.image.url(:small))
158
- get_easy_absolute_uri_for(img_url).to_s
159
- end
160
- end
161
-
162
- # ==== Options
163
- # * <tt>class: Hash or String</tt> - This option can be used to add custom CSS classes. It can be *String* or *Hash*.
164
- # class: {heading: 'heading-additional-css', container: 'container-additional-css'}
165
- # * <tt>heading_tag: name of HTML element of module heading</tt> - By default its its *h3*
166
- # ** Aliases for this options are: wrapping_heading_element, header_tag
167
- # * <tt>toggle: false</tt> - This disable toggle function (collapsible and remember)
168
- # ** Aliases for this options are: collapsible, no_expander
169
- # * <tt>remember: false</tt> - This disable remember function of toggle container
170
- # ** Aliases for this options are: ajax_call
171
- #
172
- def render_module_easy_box(id, heading, options = {}, &block)
173
- # with fallback to old
174
- options[:toggle] = true unless options.key?(:toggle)
175
- options[:remember] = options.delete(:ajax_call) if options.key?(:ajax_call)
176
- options[:collapsible] = !options.delete(:no_expander) if options.key?(:no_expander)
177
-
178
- renderer = EasyBoxRenderer.new(self, id, heading, options)
179
- renderer.content = capture { yield renderer }
180
-
181
- renderer.render
182
- end
183
-
184
- EasyBoxRenderer = Struct.new(:view, :id, :heading, :options) do
185
-
186
- attr_writer :container_class, :heading_class, :content_class
187
- attr_writer :heading_links, :footer, :icon
188
- attr_accessor :content
189
-
190
- def container_class
191
- s = (@container_class.presence || css_classes[:container]).to_s
192
- s += ' collapsible' if collapsible?
193
- s += ' collapsed' if collapsed?
194
-
195
- s
196
- end
197
-
198
- def saving_state_enabled?
199
- collapsible? && (options[:remember].nil? || !!options[:remember])
200
- end
201
-
202
- def heading_tag
203
- (options[:wrapping_heading_element] || (options[:header_tag] || options[:heading_tag])).presence || 'h3'
204
- end
205
-
206
- def heading_class
207
- (@heading_class || css_classes[:heading]).to_s
208
- end
209
-
210
- def icon
211
- @icon ||= options[:icon] && " icon #{options[:icon]}"
212
- end
213
-
214
- def heading_links
215
- if block_given?
216
- @heading_links = view.capture { yield }
217
- else
218
- @heading_links.to_s.html_safe
219
- end
220
- end
221
-
222
- def collapsible?
223
- return @collapsible unless @collapsible.nil?
224
- @collapsible ||= !!options[:toggle] && (options[:collapsible].nil? || !!options[:collapsible])
225
- end
226
-
227
- def collapsed?
228
- !!options[:default] || !!options[:collapsed] || !!options[:default_button_state]
229
- end
230
-
231
- def footer
232
- if block_given?
233
- @footer = view.capture { yield }
234
- else
235
- @footer.to_s.html_safe
236
- end
237
- end
238
-
239
- def render
240
- view.render({ partial: 'common/collapsible_module_layout', locals: { renderer: self, content: content } })
241
- end
242
-
243
- private
244
-
245
- def css_classes
246
- return @css_classes if @css_classes
247
- if (css_class = options.delete(:class)).is_a?(Hash)
248
- @css_classes = css_class
249
- else
250
- @css_classes = {
251
- container: css_class,
252
- heading: css_class,
253
- content: css_class
254
- }
255
- end
256
- end
257
-
258
- end
259
-
260
- # Returns a multiselect autocomplete input tag tailored for selecting 1..N values from defined source by +jsonpath_or_array+.
261
- # Preselected will be values in +selected_values+ parameter, or if those are empty, and +select_first_value+ option is set,
262
- # it will select first value from source. See warning from this parameter!
263
- # Additional options on the input tag can be passed as a hash with +options+.
264
- # These options will be passed to the handling javascript.
265
- # Available format for +selected_values+:
266
- # * Array of values. It will search for the values in available_values for values names assigned.
267
- # * Array of objects in format: {id: <value send in form>, value: <label - user shown value>}
268
- # Available options are:
269
- # * +multiple+ - tells if more than one value can be selected.
270
- # * +preload+ - tells if values should be preloaded all at once - in one request - if jsonpathh is a source, this parameter expect it to return all available values.
271
- # * +load_immediately+ - tells if values should be loaded immediatelly after page loaded, or wait for first use of the field.
272
- # Warning! if this option is false, selected values passed in first format will be ignored till it is loaded.
273
- # Please use second format for proper functionality.
274
- # * +select_first_value+ - if selectd_values are empty, with this option first available value will be selected.
275
- # Available only with <tt>preload: true</tt> option.
276
- # With <tt>load_immediately: false</tt> it will appear kinda weird for user because it will select the value after user starts to interact with input.
277
- # please consider if this is what you want.
278
- # * +rootElement+ - Has sence only if jsonpath is used for available values. It tells if the json response has values wrapped under root element.
279
- # For response like <tt>{projects: [[<name>, <id>], [<name2>, <id2>]]}</tt> user option <tt>rootElement: 'projects'</tt>
280
- def autocomplete_field_tag(name, jsonpath_or_array, selected_values, options = {})
281
- options.reverse_merge!(select_first_value: false, load_immediately: false, preload: true, multiple: true, combo: false)
282
- options[:id] ||= sanitize_to_id(name)
283
-
284
- selected_values ||= []
285
-
286
- if jsonpath_or_array.is_a?(Array)
287
- source = jsonpath_or_array.to_json
288
- else
289
- source = "'#{jsonpath_or_array}'"
290
- end
291
-
292
- html_options = options[:html_options] || {}
293
- content_tag(:span, class: 'easy-multiselect-tag-container', data: { cy: "container_old_autocomplete--#{name}" }) do
294
- search_field_tag('', '', html_options.merge(id: options[:id], data: { cy: "search_old_autocomplete--#{name}" }.merge(html_options[:data] || {}))) +
295
- late_javascript_tag("$('##{options[:id]}').easymultiselect({multiple: #{options[:multiple]}, rootElement: #{options[:rootElement].to_json}, inputName: '#{name}', preload: #{options[:preload]}, combo: #{options[:combo]}, source: #{source}, selected: #{selected_values.to_json}, select_first_value: #{options[:select_first_value]}, load_immediately: #{options[:load_immediately]}, autocomplete_options: #{(options[:jquery_auto_complete_options] || {}).to_json} });")
296
- end
297
- end
298
-
299
- # Returns a multiselect autocomplete input tag tailored for accessing a specified attribute (identified by +method+) on an object
300
- # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
301
- # hash with +options+. These options will be passed to the handling javascript as in the example shown.
302
- # Available values for select are passed as +choices+ attribute. It can be Array of values, or json path for later loading or autocomplete.
303
- # Please see autocomplete_field_tag documentation for more information about options and available values format combinations.
304
- # HTML options can be passed as a hash with +html_options+. These options will be passed to the input element.
305
- #
306
- # ==== Examples
307
- # autocomplete_field(:issue, :tag_ids, Tag.all.pluck(:name, :id), multiple: true)
308
- # # => <span class="easy-multiselect-tag-container"> \
309
- # <input type="text" id="issue_tags" /> \
310
- # <button type="button" tabindex="-1" class="..." role="button" ...>
311
- # <span class="ui-button-icon-primary ui-icon ui-icon-triangle-1-s"></span><span class="ui-button-text">&nbsp;</span>
312
- # </button>
313
- # ...(wraping service tags)
314
- # <input type="hidden" name="issue[tag_ids][]" value="#{@issue.tag_ids.first}" />
315
- # <input type="hidden" name="issue[tag_ids][]" value="#{@issue.tag_ids.second}" />
316
- # ...(wraping service tags end)
317
- # </span>
318
- def autocomplete_field(object_name, method, choices, options = {}, html_options = {})
319
- Tags::AutocompleteField.new(object_name, method, self, choices, options, html_options).render
320
- end
321
-
322
- end
323
- end