card 1.93.5 → 1.93.6
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/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
|