redmineup 1.1.3 → 1.1.7
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 +4 -4
- data/.gitignore +1 -0
- data/README.md +74 -153
- data/app/assets/stylesheets/redmineup.css +476 -0
- data/app/views/redmineup/_additional_assets.html.erb +1 -0
- data/config/locales/en.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/config/routes.rb +2 -0
- data/doc/CHANGELOG +53 -25
- data/doc/active-record-mixins.md +156 -0
- data/doc/assets-money-and-utilities.md +71 -0
- data/doc/tagging-and-select2.md +108 -0
- data/lib/redmineup/acts_as_taggable/tag.rb +13 -3
- data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +9 -6
- data/lib/redmineup/engine.rb +12 -2
- data/lib/redmineup/helpers/datetime_helper.rb +23 -0
- data/lib/redmineup/helpers/external_assets_helper.rb +11 -5
- data/lib/redmineup/helpers/form_tag_helper.rb +42 -0
- data/lib/redmineup/helpers/tags_helper.rb +41 -0
- data/lib/redmineup/hooks/views_layouts_hook.rb +1 -5
- data/lib/redmineup/patches/auto_completes_controller_patch.rb +19 -0
- data/lib/redmineup/patches/compatibility/user_patch.rb +3 -3
- data/lib/redmineup/version.rb +1 -1
- data/lib/redmineup.rb +2 -0
- data/test/acts_as_taggable/tag_test.rb +12 -9
- data/test/tags_helper_view_test.rb +41 -0
- data/test/test_helper.rb +2 -1
- metadata +11 -2
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# ActiveRecord mixins
|
|
2
|
+
|
|
3
|
+
This page documents the model mixins provided by `redmineup`.
|
|
4
|
+
|
|
5
|
+
## `up_acts_as_taggable`
|
|
6
|
+
|
|
7
|
+
Adds polymorphic tagging support.
|
|
8
|
+
|
|
9
|
+
### Migration
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class CreateTags < ActiveRecord::Migration[6.1]
|
|
13
|
+
def change
|
|
14
|
+
ActiveRecord::Base.create_taggable_table
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Useful migration helpers:
|
|
20
|
+
|
|
21
|
+
- `ActiveRecord::Base.create_taggable_table`
|
|
22
|
+
- `ActiveRecord::Base.drop_taggable_table`
|
|
23
|
+
- `ActiveRecord::Base.add_tags_column(:tags, name: :color, type: :integer, default: nil)`
|
|
24
|
+
- `ActiveRecord::Base.remove_tags_columns(:tags, :color)`
|
|
25
|
+
|
|
26
|
+
### Model
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
class Product < ApplicationRecord
|
|
30
|
+
up_acts_as_taggable
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Runtime API
|
|
35
|
+
|
|
36
|
+
- `record.tag_list` / `record.tag_list = 'a, b'`
|
|
37
|
+
- `record.save_tags` (called via callbacks)
|
|
38
|
+
- `Model.available_tags(project: @project, limit: 30)`
|
|
39
|
+
- `Model.tagged_with(['tag1', 'tag2'])`
|
|
40
|
+
- `Model.find_related_tags('tag1')`
|
|
41
|
+
|
|
42
|
+
## `up_acts_as_draftable`
|
|
43
|
+
|
|
44
|
+
Adds draft save/restore workflow for unsaved or changed records.
|
|
45
|
+
|
|
46
|
+
### Migration
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
class CreateDrafts < ActiveRecord::Migration[6.1]
|
|
50
|
+
def change
|
|
51
|
+
ActiveRecord::Base.create_drafts_table
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Helpers:
|
|
57
|
+
|
|
58
|
+
- `ActiveRecord::Base.create_drafts_table`
|
|
59
|
+
- `ActiveRecord::Base.drop_drafts_table`
|
|
60
|
+
|
|
61
|
+
### Model
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
class WikiPage < ApplicationRecord
|
|
65
|
+
up_acts_as_draftable parent: :wiki
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Runtime API
|
|
70
|
+
|
|
71
|
+
- `record.save_draft`
|
|
72
|
+
- `record.update_draft(attrs)`
|
|
73
|
+
- `Model.drafts(User.current)`
|
|
74
|
+
- `Model.from_draft(draft_or_id)`
|
|
75
|
+
|
|
76
|
+
## `up_acts_as_viewed`
|
|
77
|
+
|
|
78
|
+
Tracks unique views by user (or IP) and total views.
|
|
79
|
+
|
|
80
|
+
### Migration
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class CreateViewings < ActiveRecord::Migration[6.1]
|
|
84
|
+
def change
|
|
85
|
+
ActiveRecord::Base.create_viewings_table
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Optional helpers for models with embedded counters:
|
|
91
|
+
|
|
92
|
+
- `ActiveRecord::Base.generate_viewings_columns(table)`
|
|
93
|
+
- `Model.add_viewings_columns`
|
|
94
|
+
- `Model.remove_viewings_columns`
|
|
95
|
+
- `ActiveRecord::Base.drop_viewings_table`
|
|
96
|
+
|
|
97
|
+
### Model
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class Question < ApplicationRecord
|
|
101
|
+
up_acts_as_viewed
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Runtime API
|
|
106
|
+
|
|
107
|
+
- `record.view(request.remote_ip, User.current)`
|
|
108
|
+
- `record.viewed?`
|
|
109
|
+
- `record.view_count`
|
|
110
|
+
- `record.viewed_by?(request.remote_ip, User.current)`
|
|
111
|
+
|
|
112
|
+
## `up_acts_as_votable` and `up_acts_as_voter`
|
|
113
|
+
|
|
114
|
+
Adds voting capabilities for content and users.
|
|
115
|
+
|
|
116
|
+
### Migration
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class CreateVotes < ActiveRecord::Migration[6.1]
|
|
120
|
+
def change
|
|
121
|
+
ActiveRecord::Base.create_votable_table
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Helpers:
|
|
127
|
+
|
|
128
|
+
- `ActiveRecord::Base.create_votable_table`
|
|
129
|
+
- `ActiveRecord::Base.drop_votable_table`
|
|
130
|
+
|
|
131
|
+
### Models
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
class Question < ApplicationRecord
|
|
135
|
+
up_acts_as_votable
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class User < ApplicationRecord
|
|
139
|
+
up_acts_as_voter
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## `up_acts_as_priceable`
|
|
144
|
+
|
|
145
|
+
Generates currency-aware `*_to_s` methods for price fields.
|
|
146
|
+
|
|
147
|
+
### Model
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
class Deal < ApplicationRecord
|
|
151
|
+
up_acts_as_priceable :amount, :tax_amount, :subtotal, :total
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This creates methods like `amount_to_s`, `tax_amount_to_s`, etc., backed by
|
|
156
|
+
`Redmineup::MoneyHelper#object_price`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Assets, money, and shared utilities
|
|
2
|
+
|
|
3
|
+
## Asset helpers
|
|
4
|
+
|
|
5
|
+
Provided by `Redmineup::ExternalAssetsHelper`:
|
|
6
|
+
|
|
7
|
+
- `redmineup_assets`
|
|
8
|
+
- `select2_assets` (alias for `redmineup_assets`)
|
|
9
|
+
- `chartjs_assets`
|
|
10
|
+
|
|
11
|
+
`redmineup_assets` includes:
|
|
12
|
+
|
|
13
|
+
- `consumer.js`
|
|
14
|
+
- `select2.js`
|
|
15
|
+
- `select2_helpers.js`
|
|
16
|
+
- `redmineup.css`
|
|
17
|
+
|
|
18
|
+
The gem also hooks `view_layouts_base_html_head` and renders
|
|
19
|
+
`redmineup/_additional_assets`, so shared assets can be included globally.
|
|
20
|
+
Helpers are idempotent and safe to call from plugin views.
|
|
21
|
+
|
|
22
|
+
## Money and currency
|
|
23
|
+
|
|
24
|
+
### Settings API
|
|
25
|
+
|
|
26
|
+
`Redmineup::Settings::Money` provides access to plugin-level money settings:
|
|
27
|
+
|
|
28
|
+
- `default_currency`
|
|
29
|
+
- `major_currencies`
|
|
30
|
+
- `default_tax`
|
|
31
|
+
- `tax_type`, `tax_exclusive?`
|
|
32
|
+
- `thousands_delimiter`
|
|
33
|
+
- `decimal_separator`
|
|
34
|
+
- `disable_taxes?`
|
|
35
|
+
|
|
36
|
+
### Formatting helpers
|
|
37
|
+
|
|
38
|
+
`Redmineup::MoneyHelper` provides:
|
|
39
|
+
|
|
40
|
+
- `price_to_currency(price, currency, options = {})`
|
|
41
|
+
- `object_price(record, :price)`
|
|
42
|
+
- `prices_collection_by_currency(hash, hide_zeros: true)`
|
|
43
|
+
- `collection_for_currencies_select`
|
|
44
|
+
- `all_currencies`
|
|
45
|
+
|
|
46
|
+
`up_acts_as_priceable` uses these helpers to generate `*_to_s` methods on
|
|
47
|
+
models.
|
|
48
|
+
|
|
49
|
+
### Admin UI
|
|
50
|
+
|
|
51
|
+
`Redmineup::Currency.add_admin_money_menu` registers a Redmine admin menu item
|
|
52
|
+
that points to:
|
|
53
|
+
|
|
54
|
+
- `redmineup_settings_path(:money)`
|
|
55
|
+
|
|
56
|
+
## Misc shared helpers and patches
|
|
57
|
+
|
|
58
|
+
- `Redmineup::CalendarsHelper#calendar_day_css_classes`
|
|
59
|
+
- Liquid patch for numeric conversion in `Liquid::StandardFilters`
|
|
60
|
+
- Liquid filters/drops under `lib/redmineup/liquid/**`
|
|
61
|
+
- Compatibility and ActionCable patches loaded at gem boot
|
|
62
|
+
|
|
63
|
+
## Version requirements in plugins
|
|
64
|
+
|
|
65
|
+
Plugins typically guard compatibility in `init.rb`:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
requires_redmineup version_or_higher: '1.1.5'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use this to fail fast when a plugin depends on newer helper or API behavior.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Tagging and Select2 helpers
|
|
2
|
+
|
|
3
|
+
This page covers view helpers used by RedmineUP plugins for tag rendering,
|
|
4
|
+
tag editing, and select2-backed filters.
|
|
5
|
+
|
|
6
|
+
## Tag rendering helpers
|
|
7
|
+
|
|
8
|
+
Provided by `Redmineup::TagsHelper`:
|
|
9
|
+
|
|
10
|
+
- `tag_link(url, tag_name, options = {})`
|
|
11
|
+
- `tag_links(tag_list, options = {}) { |tag| url }`
|
|
12
|
+
- `tag_cloud(tags, classes) { |tag, css_class| ... }`
|
|
13
|
+
- `tag_cloud_links(tags, key, options = {})`
|
|
14
|
+
- `tag_separator`
|
|
15
|
+
|
|
16
|
+
### Recommended pattern
|
|
17
|
+
|
|
18
|
+
Define `*_tag_url` per plugin context:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
def contact_tag_url(tag_name, options = {})
|
|
22
|
+
{
|
|
23
|
+
controller: 'contacts',
|
|
24
|
+
action: 'index',
|
|
25
|
+
set_filter: 1,
|
|
26
|
+
fields: [:tags],
|
|
27
|
+
values: { tags: [tag_name] },
|
|
28
|
+
operators: { tags: '=' }
|
|
29
|
+
}.merge(options)
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then render the cloud via convention:
|
|
34
|
+
|
|
35
|
+
```erb
|
|
36
|
+
<%= tag_cloud_links(Contact.available_tags(project: @project), :contact) %>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`tag_cloud_links(tags, :contact)` expects `contact_tag_url(name)` to be defined
|
|
40
|
+
in the current view context.
|
|
41
|
+
|
|
42
|
+
## Tag editors with Select2
|
|
43
|
+
|
|
44
|
+
Provided by `Redmineup::FormTagHelper`:
|
|
45
|
+
|
|
46
|
+
- `select2_tag(name, option_tags = nil, options = {})`
|
|
47
|
+
- `select2(...)` (alias)
|
|
48
|
+
- `tag_list_field_tag(name, value = [], options = {})`
|
|
49
|
+
- `transform_to_select2(type, options = {})`
|
|
50
|
+
|
|
51
|
+
`ActionView::Helpers::FormBuilder` is extended with:
|
|
52
|
+
|
|
53
|
+
- `f.tag_list_field(:tag_list, **options)`
|
|
54
|
+
|
|
55
|
+
### Example: form object API
|
|
56
|
+
|
|
57
|
+
```erb
|
|
58
|
+
<%= f.tag_list_field :tag_list, width: '95%' %>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Example: standalone field API
|
|
62
|
+
|
|
63
|
+
```erb
|
|
64
|
+
<%= tag_list_field_tag 'product[tag_list]', @product.tag_list, taggable_type: 'Product' %>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Example: query/filter select2 transform
|
|
68
|
+
|
|
69
|
+
```erb
|
|
70
|
+
<%= transform_to_select2 'product_tags',
|
|
71
|
+
url: auto_complete_taggable_tags_path(project_id: @project, taggable_type: 'Product') %>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Tag autocomplete endpoint
|
|
75
|
+
|
|
76
|
+
`redmineup` adds a shared route:
|
|
77
|
+
|
|
78
|
+
- `GET /auto_completes/taggable_tags`
|
|
79
|
+
|
|
80
|
+
Named route:
|
|
81
|
+
|
|
82
|
+
- `auto_complete_taggable_tags_path`
|
|
83
|
+
|
|
84
|
+
Typical params used by plugins:
|
|
85
|
+
|
|
86
|
+
- `taggable_type`: model class name (`Contact`, `Person`, `Product`, `Issue`, `DriveEntry`)
|
|
87
|
+
- `project_id`: optional project scope
|
|
88
|
+
- `limit`: optional result size limit
|
|
89
|
+
|
|
90
|
+
Response format:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
[
|
|
94
|
+
{ "id": "important", "text": "important" },
|
|
95
|
+
{ "id": "vip", "text": "vip" }
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Real plugin usage
|
|
100
|
+
|
|
101
|
+
Patterns above are used by:
|
|
102
|
+
|
|
103
|
+
- `redmine_contacts` (`contact_tag_url`, `contact_tag_links`, `f.tag_list_field`)
|
|
104
|
+
- `redmine_people` (`people_tag_url`, `tag_cloud_links`, bulk edit tag forms)
|
|
105
|
+
- `redmine_products` (`product_tag_url`, `tag_list_field_tag`, select2 filters)
|
|
106
|
+
- `redmine_questions` (`question_tag_url`, `tag_cloud_links`)
|
|
107
|
+
- `redmine_drive` (`drive_entry_tag_url`, select2 tag filters)
|
|
108
|
+
- `redmineup_tags` (issue tag forms with `tag_list_field_tag`)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
|
|
1
3
|
module Redmineup
|
|
2
4
|
module ActsAsTaggable #:nodoc:
|
|
3
5
|
class Tag < ActiveRecord::Base #:nodoc:
|
|
@@ -17,6 +19,10 @@ module Redmineup
|
|
|
17
19
|
where("LOWER(name) LIKE LOWER(?)", name).first || create(:name => name)
|
|
18
20
|
end
|
|
19
21
|
|
|
22
|
+
def self.color_from_name(str)
|
|
23
|
+
"##{Digest::MD5.hexdigest(str.to_s)[0..5]}"
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
def ==(object)
|
|
21
27
|
super || (object.is_a?(Tag) && name == object.name)
|
|
22
28
|
end
|
|
@@ -30,11 +36,15 @@ module Redmineup
|
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
def color
|
|
33
|
-
|
|
39
|
+
if self.class.column_names.include?('color')
|
|
40
|
+
db_color = read_attribute(:color)
|
|
41
|
+
return ('#' + "%06x" % db_color) unless db_color.nil?
|
|
42
|
+
end
|
|
43
|
+
self.class.color_from_name(name)
|
|
34
44
|
end
|
|
35
45
|
|
|
36
46
|
def color=(color)
|
|
37
|
-
write_attribute(:color, color.
|
|
47
|
+
write_attribute(:color, color.sub(/\A#/, '').hex) unless color.blank?
|
|
38
48
|
end
|
|
39
49
|
|
|
40
50
|
class << self
|
|
@@ -74,7 +84,7 @@ module Redmineup
|
|
|
74
84
|
having = 'COUNT(*) > 0'
|
|
75
85
|
having = [having, at_least, at_most].compact.join(' AND ')
|
|
76
86
|
group_by = "#{Tag.table_name}.id, #{Tag.table_name}.name"
|
|
77
|
-
|
|
87
|
+
group_by << ", #{Tag.table_name}.color" if column_names.include?('color')
|
|
78
88
|
|
|
79
89
|
select_condition = if self.column_names.include?('color')
|
|
80
90
|
"#{Tag.table_name}.id, #{Tag.table_name}.name, #{Tag.table_name}.color, COUNT(*) AS count"
|
|
@@ -52,6 +52,7 @@ module Redmineup
|
|
|
52
52
|
if !self.connection.table_exists?(tag_name_table)
|
|
53
53
|
self.connection.create_table(tag_name_table) do |t|
|
|
54
54
|
t.column :name, :string
|
|
55
|
+
t.column :color, :integer
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
58
|
|
|
@@ -64,6 +65,7 @@ module Redmineup
|
|
|
64
65
|
# You should make sure that the column created is
|
|
65
66
|
# long enough to store the required class names.
|
|
66
67
|
t.column :taggable_type, :string
|
|
68
|
+
t.column :color, :integer, default: nil
|
|
67
69
|
|
|
68
70
|
t.column :created_at, :datetime
|
|
69
71
|
end
|
|
@@ -82,16 +84,16 @@ module Redmineup
|
|
|
82
84
|
#
|
|
83
85
|
# E.g.: ActiveRecord::Base.add_tags_column(:tags, name: :color, type: :string, default: nil)
|
|
84
86
|
def add_tags_column(table_name = :tags, **options)
|
|
85
|
-
tag_column = options
|
|
87
|
+
tag_column = options
|
|
86
88
|
tag_column.assert_valid_keys(:name, :type, :default)
|
|
87
89
|
|
|
88
90
|
raise ArgumentError, "Table `#{table_name}' not found" unless self.connection.table_exists?(table_name)
|
|
89
91
|
|
|
90
92
|
self.table_name = table_name
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
return if connection.column_exists?(table_name, tag_column[:name])
|
|
94
|
+
|
|
95
|
+
connection.add_column table_name, tag_column[:name], tag_column[:type], default: tag_column[:default]
|
|
96
|
+
reset_column_information
|
|
95
97
|
end
|
|
96
98
|
|
|
97
99
|
# Remove the up_acts_as_taggable specific columns
|
|
@@ -140,13 +142,14 @@ module Redmineup
|
|
|
140
142
|
scope = scope.where("#{table_name}.project_id IN (%s)", projects.map(&:id).join(','))
|
|
141
143
|
end
|
|
142
144
|
|
|
143
|
-
if options[:name_like]
|
|
145
|
+
if options[:name_like].present?
|
|
144
146
|
scope = scope.where("LOWER(#{Tag.table_name}.name) LIKE LOWER(?)", "%#{options[:name_like]}%")
|
|
145
147
|
end
|
|
146
148
|
|
|
147
149
|
group_fields = ''
|
|
148
150
|
group_fields << ", #{Tag.table_name}.created_at" if Tag.respond_to?(:created_at)
|
|
149
151
|
group_fields << ", #{Tag.table_name}.updated_at" if Tag.respond_to?(:updated_at)
|
|
152
|
+
group_fields << ", #{Tag.table_name}.color" if Tag.column_names.include?('color')
|
|
150
153
|
|
|
151
154
|
if base_class.respond_to?(:visible_condition)
|
|
152
155
|
visible_condition = base_class.visible_condition(User.current)
|
data/lib/redmineup/engine.rb
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
module Redmineup
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
class Engine < Rails::Engine
|
|
3
|
+
config.to_prepare do
|
|
4
|
+
next unless defined?(::Redmine::Plugin)
|
|
5
|
+
|
|
6
|
+
require 'auto_completes_controller'
|
|
7
|
+
|
|
8
|
+
require File.expand_path('../redmineup/patches/compatibility_patch', __dir__)
|
|
9
|
+
require File.expand_path('../redmineup/patches/auto_completes_controller_patch', __dir__)
|
|
10
|
+
|
|
11
|
+
require File.expand_path('../redmineup/hooks/views_layouts_hook', __dir__)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
4
14
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Redmineup
|
|
2
|
+
module DatetimeHelper
|
|
3
|
+
def convert_time_to_user_timezone(time)
|
|
4
|
+
if User.current.time_zone
|
|
5
|
+
time.in_time_zone(User.current.time_zone)
|
|
6
|
+
else
|
|
7
|
+
time.utc? ? time.localtime : time
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def convert_time_to_utc(time = Time.now)
|
|
12
|
+
return Time.now.utc if time.blank?
|
|
13
|
+
return time.utc if time.respond_to?(:utc)
|
|
14
|
+
|
|
15
|
+
time_zone = User.current.time_zone
|
|
16
|
+
return Time.parse(time).utc unless time_zone
|
|
17
|
+
|
|
18
|
+
Time.use_zone(time_zone) do
|
|
19
|
+
Time.zone.parse(time).utc
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -2,12 +2,18 @@ module Redmineup
|
|
|
2
2
|
module ExternalAssetsHelper
|
|
3
3
|
include ActionView::Helpers::JavaScriptHelper
|
|
4
4
|
|
|
5
|
+
def redmineup_assets
|
|
6
|
+
return if @redmineup_assets_included
|
|
7
|
+
|
|
8
|
+
@redmineup_assets_included = true
|
|
9
|
+
javascript_include_tag('consumer', plugin: GEM_NAME) +
|
|
10
|
+
javascript_include_tag('select2', plugin: GEM_NAME) +
|
|
11
|
+
javascript_include_tag('select2_helpers', plugin: GEM_NAME) +
|
|
12
|
+
stylesheet_link_tag('redmineup', plugin: GEM_NAME)
|
|
13
|
+
end
|
|
14
|
+
|
|
5
15
|
def select2_assets
|
|
6
|
-
|
|
7
|
-
@select2_tag_included = true
|
|
8
|
-
javascript_include_tag('select2', plugin: GEM_NAME) +
|
|
9
|
-
stylesheet_link_tag('select2', plugin: GEM_NAME) +
|
|
10
|
-
javascript_include_tag('select2_helpers', plugin: GEM_NAME)
|
|
16
|
+
redmineup_assets
|
|
11
17
|
end
|
|
12
18
|
|
|
13
19
|
def chartjs_assets
|
|
@@ -49,6 +49,31 @@ module Redmineup
|
|
|
49
49
|
|
|
50
50
|
alias select2 select2_tag
|
|
51
51
|
|
|
52
|
+
# Renders a select2 tag field for a tag list (standalone, no form object).
|
|
53
|
+
# Analogous to text_field_tag.
|
|
54
|
+
#
|
|
55
|
+
# ==== Example
|
|
56
|
+
# tag_list_field_tag 'entry[tag_list]', entry.tag_list,
|
|
57
|
+
# url: auto_complete_taggable_tags_path(taggable_type: 'DriveEntry')
|
|
58
|
+
def tag_list_field_tag(name, value = [], options = {})
|
|
59
|
+
url = options.delete(:url)
|
|
60
|
+
taggable_type = options.delete(:taggable_type)
|
|
61
|
+
width = options.delete(:width)
|
|
62
|
+
project_id = options.delete(:project_id)
|
|
63
|
+
placeholder = options.delete(:placeholder)
|
|
64
|
+
select2_tag(
|
|
65
|
+
name,
|
|
66
|
+
options_for_select(value, value),
|
|
67
|
+
{
|
|
68
|
+
multiple: true,
|
|
69
|
+
width: width || '95%',
|
|
70
|
+
url: url || auto_complete_taggable_tags_path(taggable_type: taggable_type, project_id: project_id),
|
|
71
|
+
placeholder: placeholder || l(:lable_redmineup_add_tag),
|
|
72
|
+
tags: true
|
|
73
|
+
}.merge(options)
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
52
77
|
# Transforms select filters of +type+ fields into select2
|
|
53
78
|
#
|
|
54
79
|
# ==== Examples
|
|
@@ -86,3 +111,20 @@ module Redmineup
|
|
|
86
111
|
|
|
87
112
|
end
|
|
88
113
|
end
|
|
114
|
+
|
|
115
|
+
# Extends Rails FormBuilder with tag_list_field, analogous to f.text_field.
|
|
116
|
+
#
|
|
117
|
+
# ==== Example
|
|
118
|
+
# <%= f.tag_list_field :tag_list %>
|
|
119
|
+
ActionView::Helpers::FormBuilder.class_eval do
|
|
120
|
+
def tag_list_field(method, **options)
|
|
121
|
+
url = options.fetch(:url) {
|
|
122
|
+
@template.auto_complete_taggable_tags_path(taggable_type: @object.class.name)
|
|
123
|
+
}
|
|
124
|
+
@template.tag_list_field_tag(
|
|
125
|
+
"#{@object_name}[#{method}]",
|
|
126
|
+
@object.public_send(method),
|
|
127
|
+
{url: url}.merge(options.except(:url))
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -9,5 +9,46 @@ module Redmineup
|
|
|
9
9
|
yield tag, classes[index]
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
def tag_link(url, tag_name, options = {})
|
|
14
|
+
count = options.delete(:count)
|
|
15
|
+
inner = link_to(tag_name, url, options)
|
|
16
|
+
inner << content_tag('span', "(#{count})", class: 'tag-count') if count
|
|
17
|
+
css = monochrome? ? 'tag-label' : 'tag-label-color'
|
|
18
|
+
style = monochrome? ? {} : { style: "background-color: #{Redmineup::ActsAsTaggable::Tag.color_from_name(tag_name)}" }
|
|
19
|
+
content_tag(:span, inner, { class: css }.merge(style))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def tag_separator
|
|
23
|
+
monochrome? ? ', ' : ' '
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def monochrome?
|
|
27
|
+
@monochrome ||= defined?(RedmineupTags) && !RedmineupTags.use_colors?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def tag_links(tag_list, options = {}, &url_block)
|
|
31
|
+
return if tag_list.blank?
|
|
32
|
+
content_tag(:span, class: 'tag_list') do
|
|
33
|
+
safe_join(tag_list.map { |tag| tag_link(url_block.call(tag), tag, options.dup) }, tag_separator)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Renders span.tag_list from an array of tag objects (respond to .name and .count).
|
|
38
|
+
# Resolves URL via `{key}_tag_url(name)` in the current view context.
|
|
39
|
+
#
|
|
40
|
+
# tag_cloud_links(contact.available_tags, :contact) # → contact_tag_url(name)
|
|
41
|
+
# tag_cloud_links(tags_cloud, :people) # → people_tag_url(name)
|
|
42
|
+
def tag_cloud_links(tags, key, options = {})
|
|
43
|
+
return if tags.blank?
|
|
44
|
+
url_method = :"#{key}_tag_url"
|
|
45
|
+
raise NoMethodError, "#{url_method} is not defined in view context" unless respond_to?(url_method, true)
|
|
46
|
+
content_tag(:span, class: 'tag_list') do
|
|
47
|
+
safe_join(tags.map { |tag|
|
|
48
|
+
tag_link(send(url_method, tag.name), tag.name, options.merge(count: tag.count).dup)
|
|
49
|
+
}, tag_separator)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
12
53
|
end
|
|
13
54
|
end
|
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
module Redmineup
|
|
4
4
|
module Hooks
|
|
5
5
|
class ViewsLayoutsHook < Redmine::Hook::ViewListener
|
|
6
|
-
|
|
7
|
-
stylesheet_link_tag(:calendars, plugin: 'redmineup') +
|
|
8
|
-
stylesheet_link_tag(:money, plugin: 'redmineup') +
|
|
9
|
-
javascript_include_tag(:consumer, plugin: 'redmineup')
|
|
10
|
-
end
|
|
6
|
+
render_on :view_layouts_base_html_head, partial: 'redmineup/additional_assets'
|
|
11
7
|
end
|
|
12
8
|
end
|
|
13
9
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Redmineup
|
|
2
|
+
module Patches
|
|
3
|
+
module AutoCompletesControllerPatch
|
|
4
|
+
DEFAULT_TAGS_LIMIT = 10
|
|
5
|
+
|
|
6
|
+
def taggable_tags
|
|
7
|
+
limit = params.delete(:limit) || DEFAULT_TAGS_LIMIT
|
|
8
|
+
klass = Object.const_get(params[:taggable_type].camelcase) if params[:taggable_type].present?
|
|
9
|
+
|
|
10
|
+
tags = klass && klass.available_tags(params.merge(limit: limit, name_like: params[:q])) || Redmineup::ActsAsTaggable::Tag.limit(limit)
|
|
11
|
+
render json: tags.map { |tag| { id: tag.name, text: tag.name } }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
unless AutoCompletesController.included_modules.include?(Redmineup::Patches::AutoCompletesControllerPatch)
|
|
18
|
+
AutoCompletesController.include(Redmineup::Patches::AutoCompletesControllerPatch)
|
|
19
|
+
end
|
|
@@ -16,6 +16,6 @@ module Redmineup
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
unless
|
|
20
|
-
|
|
21
|
-
end
|
|
19
|
+
unless User.included_modules.include?(Redmineup::Patches::Compatibility::UserPatch)
|
|
20
|
+
User.send(:include, Redmineup::Patches::Compatibility::UserPatch)
|
|
21
|
+
end
|
data/lib/redmineup/version.rb
CHANGED
data/lib/redmineup.rb
CHANGED
|
@@ -44,6 +44,7 @@ require 'redmineup/liquid/drops/custom_field_enumeration_drop'
|
|
|
44
44
|
require 'redmineup/helpers/external_assets_helper'
|
|
45
45
|
require 'redmineup/helpers/form_tag_helper'
|
|
46
46
|
require 'redmineup/helpers/calendars_helper'
|
|
47
|
+
require 'redmineup/helpers/datetime_helper'
|
|
47
48
|
require 'redmineup/assets_manager'
|
|
48
49
|
|
|
49
50
|
require 'redmineup/patches/liquid_patch'
|
|
@@ -106,6 +107,7 @@ if defined?(ActionView::Base)
|
|
|
106
107
|
ActionView::Base.send :include, Redmineup::CalendarsHelper
|
|
107
108
|
ActionView::Base.send :include, Redmineup::ExternalAssetsHelper
|
|
108
109
|
ActionView::Base.send :include, Redmineup::FormTagHelper
|
|
110
|
+
ActionView::Base.send :include, Redmineup::TagsHelper
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
def requires_redmineup(arg)
|