card 1.93.5 → 1.93.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/db/migrate_core_cards/data/1.12_stylesheets/classic_cards.scss +1 -1
- data/db/migrate_core_cards/data/1.12_stylesheets/traditional.scss +2 -1
- data/lib/card/format/nest.rb +4 -1
- data/lib/card/format/nest/mode.rb +11 -3
- data/lib/card/query.rb +6 -2
- data/lib/card/query/sorting.rb +21 -12
- data/lib/card/query/sql_statement.rb +1 -2
- data/lib/card/view/options.rb +17 -12
- data/mod/Modfile +1 -0
- data/mod/core/layout/simple_modal.html +3 -0
- data/mod/core/set/all/fetch_helper.rb +42 -30
- data/mod/core/spec/format/html_format_spec.rb +10 -17
- data/mod/core/spec/set/all/fetch_helper_spec.rb +48 -0
- data/mod/machines/file/all_script_machine_output/file.js +132 -18
- data/mod/machines/lib/javascript/decko.js.coffee +11 -2
- data/mod/machines/lib/javascript/decko_filter.js.coffee +116 -9
- data/mod/machines/lib/stylesheets/style_cards.scss +24 -3
- data/mod/pointer/lib/javascript/script_pointer_config.js.coffee +16 -6
- data/mod/pointer/set/abstract/00_paging_params.rb +17 -4
- data/mod/pointer/set/abstract/02_pointer/filtered.rb +48 -0
- data/mod/pointer/set/self/input_options.rb +1 -0
- data/mod/pointer/spec/set/self/input_options_spec.rb +1 -1
- data/mod/pointer/template/abstract/02_pointer/filtered/filter_items.haml +34 -0
- data/mod/pointer/template/abstract/02_pointer/filtered/filtered_list_input.haml +30 -0
- data/mod/search/lib/card/filter_query.rb +81 -0
- data/mod/search/set/abstract/00_filter_helper.rb +51 -0
- data/mod/search/set/abstract/01_filter_form_helper.rb +77 -0
- data/mod/search/set/abstract/01_search_params.rb +3 -11
- data/mod/search/set/abstract/02_filter_formgroups.rb +134 -0
- data/mod/search/set/abstract/03_filter.rb +117 -0
- data/mod/search/set/abstract/04_right_filter_form.rb +23 -0
- data/mod/search/set/abstract/search.rb +44 -10
- data/mod/search/set/abstract/wql_search.rb +22 -18
- data/mod/search/spec/set/{all → abstract}/filter_spec.rb +6 -5
- data/mod/search/template/{all/filter → abstract/03_filter}/_filter_input.haml +0 -0
- data/mod/search/template/{all/filter → abstract/03_filter}/filter_form.haml +9 -7
- data/mod/search/template/abstract/search/checkbox_item.haml +7 -0
- data/mod/search/template/abstract/search/select_item.haml +14 -0
- data/mod/standard/set/all/rich_html/editing.rb +4 -4
- data/mod/standard/set/all/rich_html/form_elements.rb +11 -2
- data/mod/standard/set/all/rich_html/modal.rb +26 -19
- data/mod/standard/set/all/rich_html/new.rb +8 -2
- data/mod/standard/set/all/rich_html/wrapper.rb +22 -18
- data/mod/standard/set/type/cardtype.rb +2 -2
- data/mod/standard/spec/set/all/rich_html/editing_spec.rb +0 -1
- data/mod/utility/set/abstract/utility.rb +13 -0
- data/mod/utility/spec/set/abstract/utility_spec.rb +16 -0
- metadata +21 -7
- data/mod/search/set/all/filter.rb +0 -9
@@ -11,10 +11,13 @@ $.extend decko.editorContentFunctionMap,
|
|
11
11
|
pointerContent @find('input:checked').map( -> $(this).val() )
|
12
12
|
'.pointer-select-list': ->
|
13
13
|
pointerContent @find('.pointer-select select').map( -> $(this).val() )
|
14
|
-
'.
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
'._pointer-filtered-list': ->
|
15
|
+
pointerContent @find('._filtered-list-item').map( -> $(this).data('cardName') )
|
16
|
+
# can't find evidence that the following is in use: #efm
|
17
|
+
# '.pointer-mixed': ->
|
18
|
+
# element = '.pointer-checkbox-sublist input:checked,\
|
19
|
+
# .pointer-sublist-ul input'
|
20
|
+
# pointerContent @find(element).map( -> $(this).val() )
|
18
21
|
# must happen after pointer-list-ul, I think
|
19
22
|
'.perm-editor': -> permissionsContent this
|
20
23
|
|
@@ -22,6 +25,9 @@ decko.editorInitFunctionMap['.pointer-list-editor'] = ->
|
|
22
25
|
@sortable({handle: '.handle', cancel: ''})
|
23
26
|
decko.initPointerList @find('input')
|
24
27
|
|
28
|
+
decko.editorInitFunctionMap['._pointer-filtered-list'] = ->
|
29
|
+
@sortable({handle: '._handle', cancel: ''})
|
30
|
+
|
25
31
|
$.extend decko,
|
26
32
|
initPointerList: (input) ->
|
27
33
|
decko.initAutoCardPlete input
|
@@ -32,9 +38,13 @@ $.extend decko,
|
|
32
38
|
url = decko.rootPath + '/' + optionsCard + '.json?view=name_complete'
|
33
39
|
input.autocomplete { source: decko.prepUrl(url) }
|
34
40
|
|
41
|
+
pointerContent: (vals) ->
|
42
|
+
list = $.map $.makeArray(vals), (v) -> if v then '[[' + v + ']]'
|
43
|
+
$.makeArray(list).join "\n"
|
44
|
+
|
35
45
|
pointerContent = (vals) ->
|
36
|
-
|
37
|
-
|
46
|
+
decko.pointerContent vals
|
47
|
+
# deprecated. backwards compatibility
|
38
48
|
|
39
49
|
permissionsContent = (ed) ->
|
40
50
|
return '_left' if ed.find('#inherit').is(':checked')
|
@@ -1,11 +1,24 @@
|
|
1
1
|
format do
|
2
2
|
def limit_param
|
3
|
-
@limit ||=
|
4
|
-
Env.params[:limit].present? ? Env.params.delete(:limit).to_i : default_limit
|
3
|
+
@limit ||= contextual_param(:limit) || default_limit
|
5
4
|
end
|
6
5
|
|
7
6
|
def offset_param
|
8
|
-
@offset ||=
|
9
|
-
|
7
|
+
@offset ||= contextual_param(:offset) || 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def contextual_param param
|
11
|
+
env_search_param(param) || voo_search_param(param)
|
12
|
+
end
|
13
|
+
|
14
|
+
def env_search_param param
|
15
|
+
return unless focal?
|
16
|
+
val = Env.params[param]
|
17
|
+
val.present? && val.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def voo_search_param param
|
21
|
+
return unless voo&.wql
|
22
|
+
voo.wql[param]
|
10
23
|
end
|
11
24
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
format :html do
|
3
|
+
view :filtered_list, tags: :unknown_ok do
|
4
|
+
with_nest_mode :normal do
|
5
|
+
class_up "card-slot", editor_id
|
6
|
+
wrap do
|
7
|
+
haml :filtered_list_input
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def filtered_list_input
|
13
|
+
_render_filtered_list
|
14
|
+
end
|
15
|
+
|
16
|
+
def filtered_list_item item_card
|
17
|
+
nest_item item_card do |rendered, item_view|
|
18
|
+
wrap_item rendered, item_view
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# for override
|
23
|
+
# @return [Card] search card on which filtering is based
|
24
|
+
def filter_card
|
25
|
+
filter_card_from_params || default_filter_card
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_filter_card
|
29
|
+
fcard = card.options_rule_card || Card[:all]
|
30
|
+
return fcard if fcard.respond_to? :wql_hash
|
31
|
+
fcard.fetch trait: :referred_to_by, new: {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def filter_card_from_params
|
35
|
+
return unless params[:filter_card]
|
36
|
+
Card.fetch params[:filter_card], new: {}
|
37
|
+
end
|
38
|
+
|
39
|
+
view :filter_items, tags: :unknown_ok do
|
40
|
+
haml :filter_items
|
41
|
+
end
|
42
|
+
|
43
|
+
# currently actually used as a class
|
44
|
+
# (because we don't have api to override slot's id)
|
45
|
+
def editor_id
|
46
|
+
@editor_id ||= "editor#{unique_id}"
|
47
|
+
end
|
48
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
describe Card::Set::Self::InputOptions do
|
2
2
|
it "loads the self set" do
|
3
3
|
expect(Card[:input, :right, :options].item_names).to contain_exactly(
|
4
|
-
"radio", "checkbox", "select", "multiselect", "list", "ace editor",
|
4
|
+
"radio", "checkbox", "select", "multiselect", "list", "ace editor", "filtered list",
|
5
5
|
"prosemirror editor", "tinymce editor", "text area", "text field", "calendar"
|
6
6
|
)
|
7
7
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
._filter-items.container-fluid
|
2
|
+
.row
|
3
|
+
= nest filter_card, view: :filter_form
|
4
|
+
._unselected.col-6.border.mt-2.nopadding
|
5
|
+
= nest filter_card, view: :select_item,
|
6
|
+
items: { view: implicit_item_view },
|
7
|
+
wql: { limit: 10 }
|
8
|
+
._selected.col-6.border.mt-2.nopadding
|
9
|
+
.selected-box
|
10
|
+
.card-header
|
11
|
+
%h5
|
12
|
+
Selected
|
13
|
+
.badge.badge-secondary
|
14
|
+
%span._selected-items
|
15
|
+
0
|
16
|
+
._selected-item-list{ style: "display:none" }
|
17
|
+
.p-3.deselector
|
18
|
+
%input#deselect-all._deselect-all{ type: "checkbox", checked: true, disabled: true }
|
19
|
+
%label{ for: "deselect-all" }
|
20
|
+
deselect
|
21
|
+
%span._selected-items
|
22
|
+
0
|
23
|
+
following
|
24
|
+
._selected-bin
|
25
|
+
._filter-help.alert.alert-secondary
|
26
|
+
Filter and select items to add them here.
|
27
|
+
.form-group
|
28
|
+
.selected-item-buttons
|
29
|
+
= button_tag "Cancel", class: "cancel-modal", data: { dismiss: :modal }
|
30
|
+
- select_href = path item: params[:item], filter_card: params[:filter_card]
|
31
|
+
= button_tag "Add Selected", class: "_add-selected slotter close-modal",
|
32
|
+
disabled: true,
|
33
|
+
href: select_href,
|
34
|
+
data: { "slot-selector": ".#{params[:editor_id]}" }
|
@@ -0,0 +1,30 @@
|
|
1
|
+
%div.filtered-list-editor
|
2
|
+
%ul.filtered-list-review._pointer-filtered-list.list-group.vertical.nopadding
|
3
|
+
- card.item_cards(context: :raw).each do |item_card|
|
4
|
+
%li._filtered-list-item.clearfix{ data: item_card.format.wrap_data(false) }
|
5
|
+
%span._handle.float-left.m-2
|
6
|
+
= icon_tag :reorder
|
7
|
+
- nest_item item_card do |rendered, view|
|
8
|
+
%span{ class: "item-#{view} float-left w-75"}
|
9
|
+
= rendered
|
10
|
+
%span.filtered-list-item-button
|
11
|
+
%button._filtered-list-item-delete.btn.btn-secondary.btn-sm.m-2{:type => "button"}
|
12
|
+
= icon_tag :remove
|
13
|
+
%br
|
14
|
+
.clearfix
|
15
|
+
- path_opts = { view: :filter_items,
|
16
|
+
item: implicit_item_view,
|
17
|
+
filter_card: filter_card.name,
|
18
|
+
layout: :simple_modal,
|
19
|
+
editor_id: editor_id,
|
20
|
+
slot: { hide: :modal_footer },
|
21
|
+
filter: { not_ids: card.item_ids.map(&:to_s).join(",") } }
|
22
|
+
= render_modal_link title: "Add Item",
|
23
|
+
link_opts: { class: "btn btn-sm btn-primary",
|
24
|
+
"data-target": "#modal-filtered-list",
|
25
|
+
path: path_opts }
|
26
|
+
= render_modal_slot modal_id: "filtered-list", dialog_class: "modal-lg"
|
27
|
+
|
28
|
+
// note: passing item and filter card because in some cases (eg Project+Metric on wikirate)
|
29
|
+
// the link was losing set-identifying information (type of left)
|
30
|
+
// would be preferable to have a more general solution to retain set-identifying info.
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class Card
|
2
|
+
# Class for generating WQL based on filter params
|
3
|
+
class FilterQuery
|
4
|
+
def initialize filter_keys_with_values, extra_wql={}
|
5
|
+
@filter_wql = Hash.new { |h, k| h[k] = [] }
|
6
|
+
@rules = yield if block_given?
|
7
|
+
@rules ||= {}
|
8
|
+
@filter_keys_with_values = filter_keys_with_values
|
9
|
+
@extra_wql = extra_wql
|
10
|
+
prepare_filter_wql
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_to_wql key, value
|
14
|
+
@filter_wql[key] << value
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_rule key, value
|
18
|
+
return unless value.present?
|
19
|
+
case @rules[key]
|
20
|
+
when Symbol
|
21
|
+
send("#{@rules[key]}_rule", key, value)
|
22
|
+
when Proc
|
23
|
+
@rules[key].call(key, value).each do |wql_key, val|
|
24
|
+
@filter_wql[wql_key] << val
|
25
|
+
end
|
26
|
+
else
|
27
|
+
send("#{key}_wql", value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_wql
|
32
|
+
@wql = {}
|
33
|
+
@filter_wql.each do |wql_key, values|
|
34
|
+
next if values.empty?
|
35
|
+
case wql_key
|
36
|
+
when :right_plus, :left_plus, :type
|
37
|
+
merge_using_and wql_key, values
|
38
|
+
else
|
39
|
+
merge_using_array wql_key, values
|
40
|
+
end
|
41
|
+
end
|
42
|
+
@wql.merge @extra_wql
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def prepare_filter_wql
|
48
|
+
@filter_keys_with_values.each do |key, values|
|
49
|
+
add_rule key, values
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def merge_using_array wql_key, values
|
54
|
+
@wql[wql_key] = values.one? ? values.first : values
|
55
|
+
end
|
56
|
+
|
57
|
+
def merge_using_and wql_key, values
|
58
|
+
hash = build_nested_hash wql_key, values
|
59
|
+
@wql.deep_merge! hash
|
60
|
+
end
|
61
|
+
|
62
|
+
# nest values with the same key using :and
|
63
|
+
def build_nested_hash key, values
|
64
|
+
return { key => values[0] } if values.one?
|
65
|
+
val = values.pop
|
66
|
+
{ key => val,
|
67
|
+
and: build_nested_hash(key, values) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def name_wql name
|
71
|
+
return unless name.present?
|
72
|
+
@filter_wql[:name] = ["match", name]
|
73
|
+
end
|
74
|
+
|
75
|
+
# FIXME: move to wikirate
|
76
|
+
def project_wql project
|
77
|
+
return unless project.present?
|
78
|
+
@filter_wql[:referred_to_by] << { left: { name: project } }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
def sort_hash
|
3
|
+
{ sort: sort_param }
|
4
|
+
end
|
5
|
+
|
6
|
+
def filter_param field
|
7
|
+
filter_hash[field.to_sym]
|
8
|
+
end
|
9
|
+
|
10
|
+
def filter_hash
|
11
|
+
@filter_hash ||= begin
|
12
|
+
filter = Env.params[:filter]
|
13
|
+
filter = filter.to_unsafe_h if filter&.respond_to?(:to_unsafe_h)
|
14
|
+
filter.is_a?(Hash) ? filter : {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def sort_param
|
19
|
+
Env.params[:sort] if Env.params[:sort].present?
|
20
|
+
end
|
21
|
+
|
22
|
+
def filter_keys_with_values
|
23
|
+
(filter_keys + advanced_filter_keys).map do |key|
|
24
|
+
values = filter_param(key)
|
25
|
+
next unless values.present?
|
26
|
+
[key, values]
|
27
|
+
end.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_filter_option
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
|
34
|
+
def offset
|
35
|
+
param_to_i :offset, 0
|
36
|
+
end
|
37
|
+
|
38
|
+
format do
|
39
|
+
delegate :filter_hash, :sort_hash, :filter_param, :sort_param,
|
40
|
+
:all_filter_keys, to: :card
|
41
|
+
end
|
42
|
+
|
43
|
+
format :html do
|
44
|
+
def extra_paging_path_args
|
45
|
+
{ filter: filter_hash }.merge sort_hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def filter_active?
|
49
|
+
filter_hash.present?
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
include_set Abstract::FilterHelper
|
2
|
+
|
3
|
+
format :html do
|
4
|
+
def select_filter field, _label=nil, default=nil, options=nil
|
5
|
+
options ||= filter_options field
|
6
|
+
options.unshift(["--", ""]) unless default
|
7
|
+
select_filter_tag field, default, options
|
8
|
+
end
|
9
|
+
|
10
|
+
def multiselect_filter field, _label=nil, default=nil, options=nil
|
11
|
+
options ||= filter_options field
|
12
|
+
multiselect_filter_tag field, default, options
|
13
|
+
end
|
14
|
+
|
15
|
+
def text_filter field, opts={}
|
16
|
+
name = filter_name field
|
17
|
+
add_class opts, "form-control"
|
18
|
+
# formgroup filter_label(field), class: "filter-input" do
|
19
|
+
text_field_tag name, filter_param(field), opts
|
20
|
+
# end
|
21
|
+
end
|
22
|
+
|
23
|
+
def select_filter_type_based type_codename, order="asc"
|
24
|
+
# take the card name as default label
|
25
|
+
options = type_options type_codename, order
|
26
|
+
select_filter type_codename, nil, nil, options
|
27
|
+
end
|
28
|
+
|
29
|
+
def autocomplete_filter type_code, options_card=nil
|
30
|
+
options_card ||= Card::Name[type_code, :type, :by_name]
|
31
|
+
text_filter type_code, class: "#{type_code}_autocomplete",
|
32
|
+
"data-options-card": options_card
|
33
|
+
end
|
34
|
+
|
35
|
+
def multiselect_filter_type_based type_codename
|
36
|
+
options = type_options type_codename
|
37
|
+
multiselect_filter type_codename, nil, nil, options
|
38
|
+
end
|
39
|
+
|
40
|
+
def multiselect_filter_tag field, default, options, html_options={}
|
41
|
+
html_options[:multiple] = true
|
42
|
+
select_filter_tag field, default, options, html_options
|
43
|
+
end
|
44
|
+
|
45
|
+
def select_filter_tag field, default, options, html_options={}
|
46
|
+
name = filter_name field, html_options[:multiple]
|
47
|
+
default = filter_param(field) || default
|
48
|
+
options = options_for_select(options, default)
|
49
|
+
|
50
|
+
css_class =
|
51
|
+
html_options[:multiple] ? "pointer-multiselect" : "pointer-select"
|
52
|
+
add_class(html_options, css_class + " filter-input #{field} _filter_input_field")
|
53
|
+
|
54
|
+
select_tag name, options, html_options
|
55
|
+
end
|
56
|
+
|
57
|
+
def filter_name field, multi=false
|
58
|
+
"filter[#{field}]#{'[]' if multi}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def filter_options field
|
62
|
+
raw = send("#{field}_options")
|
63
|
+
raw.is_a?(Array) ? raw : option_hash_to_array(raw)
|
64
|
+
end
|
65
|
+
|
66
|
+
def option_hash_to_array hash
|
67
|
+
hash.each_with_object([]) do |(key, value), array|
|
68
|
+
array << [key, value.to_s.downcase]
|
69
|
+
array
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def type_options type_codename, order="asc"
|
74
|
+
type_card = Card[type_codename]
|
75
|
+
Card.search type_id: type_card.id, return: :name, sort: "name", dir: order
|
76
|
+
end
|
77
|
+
end
|
@@ -6,25 +6,17 @@ format do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def search_params
|
9
|
-
@search_params ||=
|
10
|
-
p = default_search_params.clone
|
11
|
-
offset_and_limit_search_params p if focal?
|
12
|
-
p
|
13
|
-
end
|
9
|
+
@search_params ||= default_search_params
|
14
10
|
end
|
15
11
|
|
12
|
+
# used for override
|
16
13
|
def default_search_params
|
17
|
-
{ limit:
|
14
|
+
{ limit: limit_param, offset: offset_param }
|
18
15
|
end
|
19
16
|
|
20
17
|
def default_limit
|
21
18
|
100
|
22
19
|
end
|
23
|
-
|
24
|
-
def offset_and_limit_search_params hash
|
25
|
-
hash[:offset] = offset_param
|
26
|
-
hash[:limit] = limit_param
|
27
|
-
end
|
28
20
|
end
|
29
21
|
|
30
22
|
format :html do
|