redmineup 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/README.md +204 -0
- data/Rakefile +11 -0
- data/app/controllers/redmineup_controller.rb +26 -0
- data/app/views/redmine_crm/redmineup_calendar/_calendar.html.erb +34 -0
- data/app/views/redmineup/_money.html.erb +44 -0
- data/app/views/redmineup/settings.html.erb +10 -0
- data/bitbucket-pipelines.yml +50 -0
- data/config/currency_iso.json +2544 -0
- data/config/locales/cs.yml +13 -0
- data/config/locales/de.yml +13 -0
- data/config/locales/en.yml +13 -0
- data/config/locales/es.yml +13 -0
- data/config/locales/ru.yml +13 -0
- data/config/routes.rb +5 -0
- data/doc/CHANGELOG +14 -0
- data/doc/LICENSE.txt +339 -0
- data/lib/redmineup/acts_as_draftable/draft.rb +40 -0
- data/lib/redmineup/acts_as_draftable/up_acts_as_draftable.rb +172 -0
- data/lib/redmineup/acts_as_list/list.rb +282 -0
- data/lib/redmineup/acts_as_priceable/up_acts_as_priceable.rb +33 -0
- data/lib/redmineup/acts_as_taggable/tag.rb +81 -0
- data/lib/redmineup/acts_as_taggable/tag_list.rb +111 -0
- data/lib/redmineup/acts_as_taggable/tagging.rb +16 -0
- data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +357 -0
- data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +274 -0
- data/lib/redmineup/acts_as_votable/up_acts_as_votable.rb +80 -0
- data/lib/redmineup/acts_as_votable/up_acts_as_voter.rb +20 -0
- data/lib/redmineup/acts_as_votable/votable.rb +323 -0
- data/lib/redmineup/acts_as_votable/vote.rb +28 -0
- data/lib/redmineup/acts_as_votable/voter.rb +131 -0
- data/lib/redmineup/assets_manager.rb +43 -0
- data/lib/redmineup/colors_helper.rb +192 -0
- data/lib/redmineup/compatibility/application_controller_patch.rb +33 -0
- data/lib/redmineup/compatibility/routing_mapper_patch.rb +25 -0
- data/lib/redmineup/currency/formatting.rb +224 -0
- data/lib/redmineup/currency/heuristics.rb +151 -0
- data/lib/redmineup/currency/loader.rb +23 -0
- data/lib/redmineup/currency.rb +450 -0
- data/lib/redmineup/engine.rb +4 -0
- data/lib/redmineup/helpers/external_assets_helper.rb +20 -0
- data/lib/redmineup/helpers/form_tag_helper.rb +88 -0
- data/lib/redmineup/helpers/rup_calendar_helper.rb +146 -0
- data/lib/redmineup/helpers/tags_helper.rb +13 -0
- data/lib/redmineup/helpers/vote_helper.rb +35 -0
- data/lib/redmineup/hooks/views_layouts_hook.rb +11 -0
- data/lib/redmineup/liquid/drops/attachment_drop.rb +47 -0
- data/lib/redmineup/liquid/drops/issue_relations_drop.rb +41 -0
- data/lib/redmineup/liquid/drops/issues_drop.rb +217 -0
- data/lib/redmineup/liquid/drops/news_drop.rb +54 -0
- data/lib/redmineup/liquid/drops/projects_drop.rb +86 -0
- data/lib/redmineup/liquid/drops/time_entries_drop.rb +65 -0
- data/lib/redmineup/liquid/drops/users_drop.rb +68 -0
- data/lib/redmineup/liquid/filters/arrays.rb +254 -0
- data/lib/redmineup/liquid/filters/base.rb +249 -0
- data/lib/redmineup/liquid/filters/colors.rb +31 -0
- data/lib/redmineup/money_helper.rb +66 -0
- data/lib/redmineup/patches/liquid_patch.rb +33 -0
- data/lib/redmineup/settings/money.rb +46 -0
- data/lib/redmineup/settings.rb +53 -0
- data/lib/redmineup/version.rb +3 -0
- data/lib/redmineup.rb +108 -0
- data/redmineup.gemspec +29 -0
- data/test/acts_as_draftable/draft_test.rb +29 -0
- data/test/acts_as_draftable/rup_acts_as_draftable_test.rb +178 -0
- data/test/acts_as_taggable/rup_acts_as_taggable_test.rb +350 -0
- data/test/acts_as_taggable/tag_list_test.rb +34 -0
- data/test/acts_as_taggable/tag_test.rb +72 -0
- data/test/acts_as_taggable/tagging_test.rb +15 -0
- data/test/acts_as_viewed/rup_acts_as_viewed_test.rb +47 -0
- data/test/acts_as_votable/rup_acts_as_votable_test.rb +19 -0
- data/test/acts_as_votable/rup_acts_as_voter_test.rb +14 -0
- data/test/acts_as_votable/votable_test.rb +507 -0
- data/test/acts_as_votable/voter_test.rb +296 -0
- data/test/currency_test.rb +292 -0
- data/test/database.yml +17 -0
- data/test/fixtures/attachments.yml +14 -0
- data/test/fixtures/issues.yml +24 -0
- data/test/fixtures/news.yml +8 -0
- data/test/fixtures/projects.yml +10 -0
- data/test/fixtures/taggings.yml +32 -0
- data/test/fixtures/tags.yml +11 -0
- data/test/fixtures/users.yml +9 -0
- data/test/fixtures/votable_caches.yml +2 -0
- data/test/fixtures/votables.yml +4 -0
- data/test/fixtures/voters.yml +6 -0
- data/test/liquid/drops/attachment_drop_test.rb +15 -0
- data/test/liquid/drops/issue_relations_drop_test.rb +24 -0
- data/test/liquid/drops/issues_drop_test.rb +38 -0
- data/test/liquid/drops/news_drop_test.rb +38 -0
- data/test/liquid/drops/projects_drop_test.rb +44 -0
- data/test/liquid/drops/uses_drop_test.rb +36 -0
- data/test/liquid/filters/arrays_filter_test.rb +31 -0
- data/test/liquid/filters/base_filter_test.rb +67 -0
- data/test/liquid/filters/colors_filter_test.rb +33 -0
- data/test/liquid/liquid_helper.rb +34 -0
- data/test/models/attachment.rb +3 -0
- data/test/models/issue.rb +21 -0
- data/test/models/issue_relation.rb +10 -0
- data/test/models/news.rb +3 -0
- data/test/models/project.rb +8 -0
- data/test/models/user.rb +11 -0
- data/test/models/vote_classes.rb +33 -0
- data/test/money_helper_test.rb +12 -0
- data/test/schema.rb +144 -0
- data/test/tags_helper_test.rb +29 -0
- data/test/test_helper.rb +66 -0
- data/test/vote_helper_test.rb +28 -0
- data/vendor/assets/images/money.png +0 -0
- data/vendor/assets/images/vcard.png +0 -0
- data/vendor/assets/javascripts/Chart.bundle.min.js +16 -0
- data/vendor/assets/javascripts/select2.js +2 -0
- data/vendor/assets/javascripts/select2_helpers.js +192 -0
- data/vendor/assets/stylesheets/money.css +96 -0
- data/vendor/assets/stylesheets/select2.css +424 -0
- 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
|