card 1.93.5 → 1.93.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/db/migrate_core_cards/data/1.12_stylesheets/classic_cards.scss +1 -1
  4. data/db/migrate_core_cards/data/1.12_stylesheets/traditional.scss +2 -1
  5. data/lib/card/format/nest.rb +4 -1
  6. data/lib/card/format/nest/mode.rb +11 -3
  7. data/lib/card/query.rb +6 -2
  8. data/lib/card/query/sorting.rb +21 -12
  9. data/lib/card/query/sql_statement.rb +1 -2
  10. data/lib/card/view/options.rb +17 -12
  11. data/mod/Modfile +1 -0
  12. data/mod/core/layout/simple_modal.html +3 -0
  13. data/mod/core/set/all/fetch_helper.rb +42 -30
  14. data/mod/core/spec/format/html_format_spec.rb +10 -17
  15. data/mod/core/spec/set/all/fetch_helper_spec.rb +48 -0
  16. data/mod/machines/file/all_script_machine_output/file.js +132 -18
  17. data/mod/machines/lib/javascript/decko.js.coffee +11 -2
  18. data/mod/machines/lib/javascript/decko_filter.js.coffee +116 -9
  19. data/mod/machines/lib/stylesheets/style_cards.scss +24 -3
  20. data/mod/pointer/lib/javascript/script_pointer_config.js.coffee +16 -6
  21. data/mod/pointer/set/abstract/00_paging_params.rb +17 -4
  22. data/mod/pointer/set/abstract/02_pointer/filtered.rb +48 -0
  23. data/mod/pointer/set/self/input_options.rb +1 -0
  24. data/mod/pointer/spec/set/self/input_options_spec.rb +1 -1
  25. data/mod/pointer/template/abstract/02_pointer/filtered/filter_items.haml +34 -0
  26. data/mod/pointer/template/abstract/02_pointer/filtered/filtered_list_input.haml +30 -0
  27. data/mod/search/lib/card/filter_query.rb +81 -0
  28. data/mod/search/set/abstract/00_filter_helper.rb +51 -0
  29. data/mod/search/set/abstract/01_filter_form_helper.rb +77 -0
  30. data/mod/search/set/abstract/01_search_params.rb +3 -11
  31. data/mod/search/set/abstract/02_filter_formgroups.rb +134 -0
  32. data/mod/search/set/abstract/03_filter.rb +117 -0
  33. data/mod/search/set/abstract/04_right_filter_form.rb +23 -0
  34. data/mod/search/set/abstract/search.rb +44 -10
  35. data/mod/search/set/abstract/wql_search.rb +22 -18
  36. data/mod/search/spec/set/{all → abstract}/filter_spec.rb +6 -5
  37. data/mod/search/template/{all/filter → abstract/03_filter}/_filter_input.haml +0 -0
  38. data/mod/search/template/{all/filter → abstract/03_filter}/filter_form.haml +9 -7
  39. data/mod/search/template/abstract/search/checkbox_item.haml +7 -0
  40. data/mod/search/template/abstract/search/select_item.haml +14 -0
  41. data/mod/standard/set/all/rich_html/editing.rb +4 -4
  42. data/mod/standard/set/all/rich_html/form_elements.rb +11 -2
  43. data/mod/standard/set/all/rich_html/modal.rb +26 -19
  44. data/mod/standard/set/all/rich_html/new.rb +8 -2
  45. data/mod/standard/set/all/rich_html/wrapper.rb +22 -18
  46. data/mod/standard/set/type/cardtype.rb +2 -2
  47. data/mod/standard/spec/set/all/rich_html/editing_spec.rb +0 -1
  48. data/mod/utility/set/abstract/utility.rb +13 -0
  49. data/mod/utility/spec/set/abstract/utility_spec.rb +16 -0
  50. metadata +21 -7
  51. 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
- '.pointer-mixed': ->
15
- element = '.pointer-checkbox-sublist input:checked,\
16
- .pointer-sublist-ul input'
17
- pointerContent @find(element).map( -> $(this).val() )
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
- list = $.map $.makeArray(vals), (v) -> if v then '[[' + v + ']]'
37
- $.makeArray(list).join "\n"
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
- Env.params[:offset].present? ? Env.params.delete(:offset).to_i : 0
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
@@ -6,6 +6,7 @@ add_to_basket :options, "checkbox"
6
6
  add_to_basket :options, "select"
7
7
  add_to_basket :options, "multiselect"
8
8
  add_to_basket :options, "list"
9
+ add_to_basket :options, "filtered list"
9
10
 
10
11
  def content
11
12
  options.to_pointer_content
@@ -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 ||= begin
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: default_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