redmineup 1.0.2

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.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/Gemfile +4 -0
  4. data/README.md +204 -0
  5. data/Rakefile +11 -0
  6. data/app/controllers/redmineup_controller.rb +26 -0
  7. data/app/views/redmine_crm/redmineup_calendar/_calendar.html.erb +34 -0
  8. data/app/views/redmineup/_money.html.erb +44 -0
  9. data/app/views/redmineup/settings.html.erb +10 -0
  10. data/bitbucket-pipelines.yml +50 -0
  11. data/config/currency_iso.json +2544 -0
  12. data/config/locales/cs.yml +13 -0
  13. data/config/locales/de.yml +13 -0
  14. data/config/locales/en.yml +13 -0
  15. data/config/locales/es.yml +13 -0
  16. data/config/locales/ru.yml +13 -0
  17. data/config/routes.rb +5 -0
  18. data/doc/CHANGELOG +14 -0
  19. data/doc/LICENSE.txt +339 -0
  20. data/lib/redmineup/acts_as_draftable/draft.rb +40 -0
  21. data/lib/redmineup/acts_as_draftable/up_acts_as_draftable.rb +172 -0
  22. data/lib/redmineup/acts_as_list/list.rb +282 -0
  23. data/lib/redmineup/acts_as_priceable/up_acts_as_priceable.rb +33 -0
  24. data/lib/redmineup/acts_as_taggable/tag.rb +81 -0
  25. data/lib/redmineup/acts_as_taggable/tag_list.rb +111 -0
  26. data/lib/redmineup/acts_as_taggable/tagging.rb +16 -0
  27. data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +357 -0
  28. data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +274 -0
  29. data/lib/redmineup/acts_as_votable/up_acts_as_votable.rb +80 -0
  30. data/lib/redmineup/acts_as_votable/up_acts_as_voter.rb +20 -0
  31. data/lib/redmineup/acts_as_votable/votable.rb +323 -0
  32. data/lib/redmineup/acts_as_votable/vote.rb +28 -0
  33. data/lib/redmineup/acts_as_votable/voter.rb +131 -0
  34. data/lib/redmineup/assets_manager.rb +43 -0
  35. data/lib/redmineup/colors_helper.rb +192 -0
  36. data/lib/redmineup/compatibility/application_controller_patch.rb +33 -0
  37. data/lib/redmineup/compatibility/routing_mapper_patch.rb +25 -0
  38. data/lib/redmineup/currency/formatting.rb +224 -0
  39. data/lib/redmineup/currency/heuristics.rb +151 -0
  40. data/lib/redmineup/currency/loader.rb +23 -0
  41. data/lib/redmineup/currency.rb +450 -0
  42. data/lib/redmineup/engine.rb +4 -0
  43. data/lib/redmineup/helpers/external_assets_helper.rb +20 -0
  44. data/lib/redmineup/helpers/form_tag_helper.rb +88 -0
  45. data/lib/redmineup/helpers/rup_calendar_helper.rb +146 -0
  46. data/lib/redmineup/helpers/tags_helper.rb +13 -0
  47. data/lib/redmineup/helpers/vote_helper.rb +35 -0
  48. data/lib/redmineup/hooks/views_layouts_hook.rb +11 -0
  49. data/lib/redmineup/liquid/drops/attachment_drop.rb +47 -0
  50. data/lib/redmineup/liquid/drops/issue_relations_drop.rb +41 -0
  51. data/lib/redmineup/liquid/drops/issues_drop.rb +217 -0
  52. data/lib/redmineup/liquid/drops/news_drop.rb +54 -0
  53. data/lib/redmineup/liquid/drops/projects_drop.rb +86 -0
  54. data/lib/redmineup/liquid/drops/time_entries_drop.rb +65 -0
  55. data/lib/redmineup/liquid/drops/users_drop.rb +68 -0
  56. data/lib/redmineup/liquid/filters/arrays.rb +254 -0
  57. data/lib/redmineup/liquid/filters/base.rb +249 -0
  58. data/lib/redmineup/liquid/filters/colors.rb +31 -0
  59. data/lib/redmineup/money_helper.rb +66 -0
  60. data/lib/redmineup/patches/liquid_patch.rb +33 -0
  61. data/lib/redmineup/settings/money.rb +46 -0
  62. data/lib/redmineup/settings.rb +53 -0
  63. data/lib/redmineup/version.rb +3 -0
  64. data/lib/redmineup.rb +108 -0
  65. data/redmineup.gemspec +29 -0
  66. data/test/acts_as_draftable/draft_test.rb +29 -0
  67. data/test/acts_as_draftable/rup_acts_as_draftable_test.rb +178 -0
  68. data/test/acts_as_taggable/rup_acts_as_taggable_test.rb +350 -0
  69. data/test/acts_as_taggable/tag_list_test.rb +34 -0
  70. data/test/acts_as_taggable/tag_test.rb +72 -0
  71. data/test/acts_as_taggable/tagging_test.rb +15 -0
  72. data/test/acts_as_viewed/rup_acts_as_viewed_test.rb +47 -0
  73. data/test/acts_as_votable/rup_acts_as_votable_test.rb +19 -0
  74. data/test/acts_as_votable/rup_acts_as_voter_test.rb +14 -0
  75. data/test/acts_as_votable/votable_test.rb +507 -0
  76. data/test/acts_as_votable/voter_test.rb +296 -0
  77. data/test/currency_test.rb +292 -0
  78. data/test/database.yml +17 -0
  79. data/test/fixtures/attachments.yml +14 -0
  80. data/test/fixtures/issues.yml +24 -0
  81. data/test/fixtures/news.yml +8 -0
  82. data/test/fixtures/projects.yml +10 -0
  83. data/test/fixtures/taggings.yml +32 -0
  84. data/test/fixtures/tags.yml +11 -0
  85. data/test/fixtures/users.yml +9 -0
  86. data/test/fixtures/votable_caches.yml +2 -0
  87. data/test/fixtures/votables.yml +4 -0
  88. data/test/fixtures/voters.yml +6 -0
  89. data/test/liquid/drops/attachment_drop_test.rb +15 -0
  90. data/test/liquid/drops/issue_relations_drop_test.rb +24 -0
  91. data/test/liquid/drops/issues_drop_test.rb +38 -0
  92. data/test/liquid/drops/news_drop_test.rb +38 -0
  93. data/test/liquid/drops/projects_drop_test.rb +44 -0
  94. data/test/liquid/drops/uses_drop_test.rb +36 -0
  95. data/test/liquid/filters/arrays_filter_test.rb +31 -0
  96. data/test/liquid/filters/base_filter_test.rb +67 -0
  97. data/test/liquid/filters/colors_filter_test.rb +33 -0
  98. data/test/liquid/liquid_helper.rb +34 -0
  99. data/test/models/attachment.rb +3 -0
  100. data/test/models/issue.rb +21 -0
  101. data/test/models/issue_relation.rb +10 -0
  102. data/test/models/news.rb +3 -0
  103. data/test/models/project.rb +8 -0
  104. data/test/models/user.rb +11 -0
  105. data/test/models/vote_classes.rb +33 -0
  106. data/test/money_helper_test.rb +12 -0
  107. data/test/schema.rb +144 -0
  108. data/test/tags_helper_test.rb +29 -0
  109. data/test/test_helper.rb +66 -0
  110. data/test/vote_helper_test.rb +28 -0
  111. data/vendor/assets/images/money.png +0 -0
  112. data/vendor/assets/images/vcard.png +0 -0
  113. data/vendor/assets/javascripts/Chart.bundle.min.js +16 -0
  114. data/vendor/assets/javascripts/select2.js +2 -0
  115. data/vendor/assets/javascripts/select2_helpers.js +192 -0
  116. data/vendor/assets/stylesheets/money.css +96 -0
  117. data/vendor/assets/stylesheets/select2.css +424 -0
  118. metadata +295 -0
@@ -0,0 +1,65 @@
1
+ module Redmineup
2
+ module Liquid
3
+ class TimeEntriesDrop < ::Liquid::Drop
4
+ def initialize(time_entries)
5
+ @time_entries = time_entries
6
+ end
7
+
8
+ def all
9
+ @all ||= @time_entries.map do |time_entry|
10
+ TimeEntryDrop.new time_entry
11
+ end
12
+ end
13
+
14
+ def visible
15
+ @visible ||= @all.select(&:visible?)
16
+ end
17
+
18
+ def each(&block)
19
+ all.each(&block)
20
+ end
21
+
22
+ def size
23
+ @time_entries.size
24
+ end
25
+ end
26
+
27
+ class TimeEntryDrop < ::Liquid::Drop
28
+ include ActionView::Helpers::UrlHelper
29
+
30
+ delegate :id,
31
+ :hours,
32
+ :comments,
33
+ :spent_on,
34
+ :tyear,
35
+ :tmonth,
36
+ :tweek,
37
+ :visible?,
38
+ :updated_on,
39
+ :created_on,
40
+ :to => :@time_entry,
41
+ allow_nil: true
42
+
43
+ def initialize(time_entry)
44
+ @time_entry = time_entry
45
+ end
46
+
47
+ def user
48
+ @user ||= UserDrop.new(@time_entry.user)
49
+ end
50
+
51
+ def issue
52
+ @issue ||= IssueDrop.new(@time_entry.issue) unless @time_entry.issue.blank?
53
+ end
54
+
55
+ def activity
56
+ @activity ||= @time_entry.activity && @time_entry.activity.name
57
+ end
58
+
59
+ def custom_field_values
60
+ @time_entry.custom_field_values
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,68 @@
1
+ module Redmineup
2
+ module Liquid
3
+ class UsersDrop < ::Liquid::Drop
4
+
5
+ def self.default_drop
6
+ self.new User.sorted
7
+ end
8
+
9
+ def initialize(users)
10
+ @users = users
11
+ end
12
+
13
+ def before_method(login)
14
+ user = @users.where(:login => login).first || User.new
15
+ UserDrop.new user
16
+ end
17
+
18
+ def current
19
+ UserDrop.new User.current
20
+ end
21
+
22
+ def all
23
+ @all ||= @users.map do |user|
24
+ UserDrop.new user
25
+ end
26
+ end
27
+
28
+ def each(&block)
29
+ all.each(&block)
30
+ end
31
+
32
+ def size
33
+ @users.size
34
+ end
35
+ end
36
+
37
+ class UserDrop < ::Liquid::Drop
38
+ delegate :id, :name, :firstname, :lastname, :mail, :active?, :admin?, :logged?, :language, :to => :@user, allow_nil: true
39
+
40
+ def initialize(user)
41
+ @user = user
42
+ end
43
+
44
+ def avatar
45
+ ApplicationController.helpers.avatar(@user)
46
+ end
47
+
48
+ def permissions
49
+ roles = @user.memberships.collect { |m| m.roles }.flatten.uniq
50
+ roles << (@user.logged? ? Role.non_member : Role.anonymous)
51
+ roles.map(&:permissions).flatten.uniq.map(&:to_s)
52
+ end
53
+
54
+ def groups
55
+ @user.groups.map(&:name)
56
+ end
57
+
58
+ def projects
59
+ ProjectsDrop.new @user.memberships.map(&:project).flatten.select(&:visible?).uniq
60
+ end
61
+
62
+ def custom_field_values
63
+ @user.custom_field_values
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,254 @@
1
+ module Redmineup
2
+ module Liquid
3
+ module Filters
4
+ module Arrays
5
+
6
+ # Get the first element of the passed in array
7
+ #
8
+ # Example:
9
+ # {{ product.images | first | to_img }}
10
+ #
11
+ # {{ product.images | first: 3 }}
12
+ #
13
+ def first(array, count=1)
14
+ (count > 1 ? array.first(count) : array.first) if array.respond_to?(:first)
15
+ end
16
+
17
+ # Convert the input into json string
18
+ #
19
+ # input - The Array or Hash to be converted
20
+ #
21
+ # Returns the converted json string
22
+ def jsonify(input)
23
+ as_liquid(input).to_json
24
+ end
25
+
26
+ # Group an array of items by a property
27
+ #
28
+ # input - the inputted Enumerable
29
+ # property - the property
30
+ #
31
+ # Returns an array of Hashes, each looking something like this:
32
+ # {"name" => "larry"
33
+ # "items" => [...] } # all the items where `property` == "larry"
34
+ def group_by(input, property)
35
+ if groupable?(input)
36
+ input.group_by { |item| item_property(item, property).to_s }.each_with_object([]) do |item, array|
37
+ array << {
38
+ "name" => item.first,
39
+ "items" => item.last,
40
+ "size" => item.last.size
41
+ }
42
+ end
43
+ else
44
+ input
45
+ end
46
+ end
47
+
48
+ # Filter an array of objects
49
+ #
50
+ # input - the object array
51
+ # property - property within each object to filter by
52
+ # value - desired value
53
+ #
54
+ # Returns the filtered array of objects
55
+ def where(input, property, value, operator='==')
56
+ return input unless input.respond_to?(:select)
57
+ input = input.values if input.is_a?(Hash)
58
+ if operator == '=='
59
+ input.select do |object|
60
+ Array(item_property(object, property)).map(&:to_s).include?(value.to_s)
61
+ end || []
62
+ elsif operator == '<>'
63
+ input.select do |object|
64
+ !Array(item_property(object, property)).map(&:to_s).include?(value.to_s)
65
+ end || []
66
+ elsif operator == '>'
67
+ input.select do |object|
68
+ item_property_value = item_property(object, property)
69
+ item_property_value && item_property_value > value
70
+ end || []
71
+ elsif operator == '<'
72
+ input.select do |object|
73
+ item_property_value = item_property(object, property)
74
+ item_property_value && item_property_value < value
75
+ end || []
76
+ elsif operator == 'match'
77
+ input.select do |object|
78
+ Array(item_property(object, property)).map(&:to_s).any?{|i| i.match(value.to_s)}
79
+ end || []
80
+ elsif operator == 'any'
81
+ input.select do |object|
82
+ item_property(object, property).present?
83
+ end || []
84
+ elsif operator == 'none'
85
+ input.select do |object|
86
+ item_property(object, property).blank?
87
+ end || []
88
+ else
89
+ []
90
+ end
91
+ end
92
+
93
+ # Filters an array of objects against an expression
94
+ #
95
+ # input - the object array
96
+ # variable - the variable to assign each item to in the expression
97
+ # expression - a Liquid comparison expression passed in as a string
98
+ #
99
+ # Returns the filtered array of objects
100
+ def where_exp(input, variable, expression)
101
+ return input unless input.respond_to?(:select)
102
+
103
+ input = input.values if input.is_a?(Hash) # FIXME
104
+
105
+ condition = parse_condition(expression)
106
+ @context.stack do
107
+ input.select do |object|
108
+ @context[variable] = object
109
+ condition.evaluate(@context)
110
+ end
111
+ end || []
112
+ end
113
+
114
+ # Filter an array of objects by tags
115
+ #
116
+ # input - the object array
117
+ # tags - quoted tags list divided by comma
118
+ # match - (all- defaut, any, exclude)
119
+ #
120
+ # Returns the filtered array of objects
121
+ def tagged_with(input, tags, match='all')
122
+ return input unless input.respond_to?(:select)
123
+ input = input.values if input.is_a?(Hash)
124
+ tag_list = tags.is_a?(Array) ? tags.sort : tags.split(',').map(&:strip).sort
125
+ case match
126
+ when "all"
127
+ input.select do |object|
128
+ object.respond_to?(:tag_list) &&
129
+ (tag_list - Array(item_property(object, 'tag_list')).map(&:to_s).sort).empty?
130
+ end || []
131
+ when "any"
132
+ input.select do |object|
133
+ object.respond_to?(:tag_list) &&
134
+ (tag_list & Array(item_property(object, 'tag_list')).map(&:to_s).sort).any?
135
+ end || []
136
+ when "exclude"
137
+ input.select do |object|
138
+ object.respond_to?(:tag_list) &&
139
+ (tag_list & Array(item_property(object, 'tag_list')).map(&:to_s).sort).empty?
140
+ end || []
141
+ else
142
+ []
143
+ end
144
+ end
145
+
146
+ # Sort an array of objects
147
+ #
148
+ # input - the object array
149
+ # property - property within each object to filter by
150
+ # nils ('first' | 'last') - nils appear before or after non-nil values
151
+ #
152
+ # Returns the filtered array of objects
153
+ def sort(input, property = nil, nils = "first")
154
+ if input.nil?
155
+ raise ArgumentError, "Cannot sort a null object."
156
+ end
157
+ if property.nil?
158
+ input.sort
159
+ else
160
+ if nils == "first"
161
+ order = - 1
162
+ elsif nils == "last"
163
+ order = + 1
164
+ else
165
+ raise ArgumentError, "Invalid nils order: " \
166
+ "'#{nils}' is not a valid nils order. It must be 'first' or 'last'."
167
+ end
168
+
169
+ sort_input(input, property, order)
170
+ end
171
+ end
172
+
173
+ def pop(array, input = 1)
174
+ return array unless array.is_a?(Array)
175
+ new_ary = array.dup
176
+ new_ary.pop(input.to_i || 1)
177
+ new_ary
178
+ end
179
+
180
+ def push(array, input)
181
+ return array unless array.is_a?(Array)
182
+ new_ary = array.dup
183
+ new_ary.push(input)
184
+ new_ary
185
+ end
186
+
187
+ def shift(array, input = 1)
188
+ return array unless array.is_a?(Array)
189
+ new_ary = array.dup
190
+ new_ary.shift(input.to_i || 1)
191
+ new_ary
192
+ end
193
+
194
+ def unshift(array, input)
195
+ return array unless array.is_a?(Array)
196
+ new_ary = array.dup
197
+ new_ary.unshift(input)
198
+ new_ary
199
+ end
200
+
201
+ private
202
+
203
+ # Parse a string to a Liquid Condition
204
+ def parse_condition(exp)
205
+ parser = ::Liquid::Parser.new(exp)
206
+ condition = parse_binary_comparison(parser)
207
+
208
+ parser.consume(:end_of_string)
209
+ condition
210
+ end
211
+
212
+ # Generate a Liquid::Condition object from a Liquid::Parser object additionally processing
213
+ # the parsed expression based on whether the expression consists of binary operations with
214
+ # Liquid operators `and` or `or`
215
+ #
216
+ # - parser: an instance of Liquid::Parser
217
+ #
218
+ # Returns an instance of Liquid::Condition
219
+ def parse_binary_comparison(parser)
220
+ condition = parse_comparison(parser)
221
+ first_condition = condition
222
+ while (binary_operator = parser.id?("and") || parser.id?("or"))
223
+ child_condition = parse_comparison(parser)
224
+ condition.send(binary_operator, child_condition)
225
+ condition =where_exp child_condition
226
+ end
227
+ first_condition
228
+ end
229
+
230
+ # Generates a Liquid::Condition object from a Liquid::Parser object based on whether the parsed
231
+ # expression involves a "comparison" operator (e.g. <, ==, >, !=, etc)
232
+ #
233
+ # - parser: an instance of Liquid::Parser
234
+ #
235
+ # Returns an instance of Liquid::Condition
236
+ def parse_comparison(parser)
237
+ left_operand = ::Liquid::Expression.parse(parser.expression)
238
+ operator = parser.consume?(:comparison)
239
+
240
+ # No comparison-operator detected. Initialize a Liquid::Condition using only left operand
241
+ return ::Liquid::Condition.new(left_operand) unless operator
242
+
243
+ # Parse what remained after extracting the left operand and the `:comparison` operator
244
+ # and initialize a Liquid::Condition object using the operands and the comparison-operator
245
+ ::Liquid::Condition.new(left_operand, operator, ::Liquid::Expression.parse(parser.expression))
246
+ end
247
+
248
+
249
+ end # module ArrayFilters
250
+ end
251
+ end
252
+
253
+ ::Liquid::Template.register_filter(Redmineup::Liquid::Filters::Arrays)
254
+ end
@@ -0,0 +1,249 @@
1
+ require 'uri'
2
+ require 'rack'
3
+ require 'json'
4
+ require 'date'
5
+ require 'liquid'
6
+
7
+ module Redmineup
8
+ module Liquid
9
+ module Filters
10
+ module Base
11
+ include Redmineup::MoneyHelper
12
+
13
+ def textilize(input)
14
+ RedCloth3.new(input).to_html
15
+ end
16
+
17
+ def default(input, value)
18
+ input.blank? ? value : input
19
+ end
20
+
21
+ def underscore(input)
22
+ input.to_s.gsub(' ', '_').gsub('/', '_').underscore
23
+ end
24
+
25
+ def dasherize(input)
26
+ input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
27
+ end
28
+
29
+ def shuffle(array)
30
+ array.to_a.shuffle
31
+ end
32
+
33
+ def random(input)
34
+ rand(input.to_i)
35
+ end
36
+
37
+ def md5(input)
38
+ Digest::MD5.hexdigest(input) unless input.blank?
39
+ end
40
+
41
+ # example:
42
+ # {{ "http:://www.example.com?key=hello world" | encode }}
43
+ #
44
+ # => http%3A%3A%2F%2Fwww.example.com%3Fkey%3Dhello+world
45
+ def encode(input)
46
+ ::Rack::Utils.escape(input)
47
+ end
48
+
49
+ # example:
50
+ # {{ today | plus_days: 2 }}
51
+ def plus_days(input, distanse)
52
+ return '' if input.nil?
53
+ days = distanse.to_i
54
+ input.to_date + days.days rescue 'Invalid date'
55
+ end
56
+
57
+ # example:
58
+ # {{ today | date_range: '2015-12-12' }}
59
+ def date_range(input, distanse)
60
+ return '' if input.nil?
61
+ (input.to_date - distanse.to_date).to_i rescue 'Invalid date'
62
+ end
63
+
64
+ # example:
65
+ # {{ now | utc }}
66
+ def utc(input)
67
+ return '' if input.nil?
68
+ input.to_time.utc rescue 'Invalid date'
69
+ end
70
+
71
+ def modulo(input, operand)
72
+ apply_operation(input, operand, :%)
73
+ end
74
+
75
+ def round(input, n = 0)
76
+ result = to_number(input).round(to_number(n))
77
+ result = result.to_f if result.is_a?(BigDecimal)
78
+ result = result.to_i if n == 0
79
+ result
80
+ end
81
+
82
+ def ceil(input)
83
+ to_number(input).ceil.to_i
84
+ end
85
+
86
+ def floor(input)
87
+ to_number(input).floor.to_i
88
+ end
89
+
90
+ def currency(input, currency_code = nil)
91
+ price_to_currency(input, currency_code || container_currency, :converted => false)
92
+ end
93
+
94
+ def call_method(input, method_name)
95
+ if input.respond_to?(method_name)
96
+ input.method(method_name).call
97
+ end
98
+ end
99
+
100
+ def custom_field(input, field_name)
101
+ if input.respond_to?(:custom_field_values)
102
+ custom_value = input.custom_field_values.detect { |cfv| cfv.custom_field.name == field_name }
103
+ custom_value.custom_field.format.formatted_custom_value(nil, custom_value) if custom_value
104
+ end
105
+ end
106
+
107
+ def custom_fields(input)
108
+ if input.respond_to?(:custom_field_values)
109
+ input.custom_field_values.map { |cfv| cfv.custom_field.name }
110
+ end
111
+ end
112
+
113
+ def attachment(input, file_name)
114
+ if input.respond_to?(:attachments)
115
+ if input.attachments.is_a?(Hash)
116
+ attachment = input.attachments[file_name]
117
+ else
118
+ attachment = input.attachments.detect{|a| a.file_name == file_name}
119
+ end
120
+ AttachmentDrop.new attachment if attachment
121
+ end
122
+ end
123
+
124
+ def multi_line(input)
125
+ input.to_s.gsub("\n", '<br/>').html_safe
126
+ end
127
+
128
+ def concat(input, *args)
129
+ result = input.to_s
130
+ args.flatten.each { |a| result << a.to_s }
131
+ result
132
+ end
133
+
134
+ # right justify and padd a string
135
+ def rjust(input, integer, padstr = '')
136
+ input.to_s.rjust(integer, padstr)
137
+ end
138
+
139
+ # left justify and padd a string
140
+ def ljust(input, integer, padstr = '')
141
+ input.to_s.ljust(integer, padstr)
142
+ end
143
+
144
+ def textile(input)
145
+ ::RedCloth3.new(input).to_html
146
+ end
147
+
148
+ protected
149
+
150
+ # Convert an array of properties ('key:value') into a hash
151
+ # Ex: ['width:50', 'height:100'] => { :width => '50', :height => '100' }
152
+ def args_to_options(*args)
153
+ options = {}
154
+ args.flatten.each do |a|
155
+ if (a =~ /^(.*):(.*)$/)
156
+ options[$1.to_sym] = $2
157
+ end
158
+ end
159
+ options
160
+ end
161
+
162
+ # Write options (Hash) into a string according to the following pattern:
163
+ # <key1>="<value1>", <key2>="<value2", ...etc
164
+ def inline_options(options = {})
165
+ return '' if options.empty?
166
+ (options.stringify_keys.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
167
+ end
168
+
169
+ def sort_input(input, property, order)
170
+ input.sort do |apple, orange|
171
+ apple_property = item_property(apple, property)
172
+ orange_property = item_property(orange, property)
173
+
174
+ if !apple_property.nil? && orange_property.nil?
175
+ - order
176
+ elsif apple_property.nil? && !orange_property.nil?
177
+ + order
178
+ else
179
+ apple_property <=> orange_property
180
+ end
181
+ end
182
+ end
183
+
184
+ def time(input)
185
+ case input
186
+ when Time
187
+ input.clone
188
+ when Date
189
+ input.to_time
190
+ when String
191
+ Time.parse(input) rescue Time.at(input.to_i)
192
+ when Numeric
193
+ Time.at(input)
194
+ else
195
+ raise Errors::InvalidDateError,
196
+ "Invalid Date: '#{input.inspect}' is not a valid datetime."
197
+ end.localtime
198
+ end
199
+
200
+ def groupable?(element)
201
+ element.respond_to?(:group_by)
202
+ end
203
+
204
+ def item_property(item, property)
205
+ if item.respond_to?(:to_liquid)
206
+ property.to_s.split(".").reduce(item.to_liquid) do |subvalue, attribute|
207
+ subvalue[attribute]
208
+ end
209
+ elsif item.respond_to?(:data)
210
+ item.data[property.to_s]
211
+ else
212
+ item[property.to_s]
213
+ end
214
+ end
215
+
216
+ def as_liquid(item)
217
+ case item
218
+ when Hash
219
+ pairs = item.map { |k, v| as_liquid([k, v]) }
220
+ Hash[pairs]
221
+ when Array
222
+ item.map { |i| as_liquid(i) }
223
+ else
224
+ if item.respond_to?(:to_liquid)
225
+ liquidated = item.to_liquid
226
+ # prevent infinite recursion for simple types (which return `self`)
227
+ if liquidated == item
228
+ item
229
+ else
230
+ as_liquid(liquidated)
231
+ end
232
+ else
233
+ item
234
+ end
235
+ end
236
+ end
237
+
238
+ def container
239
+ @container ||= @context.registers[:container]
240
+ end
241
+
242
+ def container_currency
243
+ container.currency if container.respond_to?(:currency)
244
+ end
245
+ end
246
+ ::Liquid::Template.register_filter(Redmineup::Liquid::Filters::Base)
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,31 @@
1
+ require 'liquid'
2
+
3
+ module Redmineup
4
+ module Liquid
5
+ module Filters
6
+ module Colors
7
+ def darken_color(input, value=0.4)
8
+ Redmineup::ColorsHelper.darken_color(input, value.to_f)
9
+ end
10
+
11
+ def lighten_color(input, value=0.6)
12
+ Redmineup::ColorsHelper.lighten_color(input, value.to_f)
13
+ end
14
+
15
+ def contrasting_text_color(input)
16
+ Redmineup::ColorsHelper.contrasting_text_color(input)
17
+ end
18
+
19
+ def hex_color(input)
20
+ Redmineup::ColorsHelper.hex_color(input)
21
+ end
22
+
23
+ def convert_to_brightness_value(input)
24
+ Redmineup::ColorsHelper.convert_to_brightness_value(input)
25
+ end
26
+
27
+ end
28
+ ::Liquid::Template.register_filter(Redmineup::Liquid::Filters::Colors)
29
+ end
30
+ end
31
+ end