card-mod-search 0.11.0

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/lib/card/filter_query.rb +74 -0
  3. data/set/abstract/00_filter_helper.rb +46 -0
  4. data/set/abstract/02_search_params.rb +52 -0
  5. data/set/abstract/03_filter.rb +29 -0
  6. data/set/abstract/03_filter/_filter_input.haml +8 -0
  7. data/set/abstract/03_filter/filter_form.haml +56 -0
  8. data/set/abstract/03_filter/filter_form.rb +101 -0
  9. data/set/abstract/03_filter/filtered_content.haml +5 -0
  10. data/set/abstract/03_filter/form_helper.rb +105 -0
  11. data/set/abstract/03_filter/query_construction.rb +38 -0
  12. data/set/abstract/03_filter/quick_filters.haml +11 -0
  13. data/set/abstract/03_filter/selectable_filtered_content.haml +2 -0
  14. data/set/abstract/04_right_filter_form.rb +26 -0
  15. data/set/abstract/05_search.rb +80 -0
  16. data/set/abstract/05_search/checkbox_item.haml +5 -0
  17. data/set/abstract/05_search/select_item.haml +15 -0
  18. data/set/abstract/05_search/views.rb +167 -0
  19. data/set/abstract/06_cql_search.rb +91 -0
  20. data/set/abstract/filterable.rb +13 -0
  21. data/set/abstract/filterable_bar.rb +11 -0
  22. data/set/right/children.rb +4 -0
  23. data/set/right/created.rb +3 -0
  24. data/set/right/edited.rb +3 -0
  25. data/set/right/editors.rb +3 -0
  26. data/set/right/follow.rb +3 -0
  27. data/set/right/linked_to_by.rb +3 -0
  28. data/set/right/links_to.rb +5 -0
  29. data/set/right/mates.rb +3 -0
  30. data/set/right/nested_by.rb +3 -0
  31. data/set/right/nests.rb +3 -0
  32. data/set/right/referred_to_by.rb +3 -0
  33. data/set/right/refers_to.rb +3 -0
  34. data/set/self/recent.rb +35 -0
  35. data/set/self/search.rb +93 -0
  36. data/set/type/cardtype.rb +5 -0
  37. data/set/type/search_type.rb +80 -0
  38. metadata +110 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1b91e3f82c4631b82c56aad295dc20b128f9fffcbf8d038e927b5b5bf4ebdc5c
4
+ data.tar.gz: 88b0423d13d1eba7d6a1e9e8f1f9b859d13b85491613ae05efe7970e15156d17
5
+ SHA512:
6
+ metadata.gz: fbe34fa188b58c055449bd6789106ec32fa2652d80e8fa9ccb7147d0eab6d6a7a0905fbba84128f7c43185d9fe1609227ad07aefbdb59f8476e4d817c89f50a2
7
+ data.tar.gz: bab5d5d2e0af84aa1e5e0a35dd1aff04103dcaf186c10b2f8590f78ddb6a676cea3005a4e83a5c2c18bdcde8bf075721357fd49c9800895d88f3271801527a69
@@ -0,0 +1,74 @@
1
+ class Card
2
+ # Class for generating CQL based on filter params
3
+ class FilterQuery
4
+ def initialize filter_keys_with_values, extra_cql={}
5
+ @filter_cql = 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_cql = extra_cql
10
+ prepare_filter_cql
11
+ end
12
+
13
+ def add_to_cql key, value
14
+ @filter_cql[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 |cql_key, val|
24
+ @filter_cql[cql_key] << val
25
+ end
26
+ else
27
+ send("#{key}_cql", value)
28
+ end
29
+ end
30
+
31
+ def to_cql
32
+ @cql = {}
33
+ @filter_cql.each do |cql_key, values|
34
+ next if values.empty?
35
+ case cql_key
36
+ when :right_plus, :left_plus, :type
37
+ merge_using_and cql_key, values
38
+ else
39
+ merge_using_array cql_key, values
40
+ end
41
+ end
42
+ @cql.merge @extra_cql
43
+ end
44
+
45
+ private
46
+
47
+ def prepare_filter_cql
48
+ @filter_keys_with_values.each do |key, values|
49
+ add_rule key, values
50
+ end
51
+ end
52
+
53
+ def merge_using_array cql_key, values
54
+ @cql[cql_key] = values.one? ? values.first : values
55
+ end
56
+
57
+ def merge_using_and cql_key, values
58
+ hash = build_nested_hash cql_key, values
59
+ @cql.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, and: build_nested_hash(key, values) }
67
+ end
68
+
69
+ def name_cql name
70
+ return unless name.present?
71
+ @filter_cql[:name] = ["match", name]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,46 @@
1
+ format do
2
+ def filter_param field
3
+ filter_hash[field.to_sym]
4
+ end
5
+
6
+ def filter_hash
7
+ @filter_hash ||= filter_hash_from_params || default_filter_hash
8
+ end
9
+
10
+ def filter_hash_from_params
11
+ return unless Env.params[:filter].present?
12
+
13
+ Env.hash(Env.params[:filter]).deep_symbolize_keys
14
+ end
15
+
16
+ def sort_param
17
+ @sort_param ||= safe_sql_param :sort
18
+ end
19
+
20
+ def safe_sql_param key
21
+ param = Env.params[key]
22
+ param.blank? ? nil : Card::Query.safe_sql(param)
23
+ end
24
+
25
+ def filter_keys_with_values
26
+ filter_keys.map do |key|
27
+ values = filter_param(key)
28
+ values.present? ? [key, values] : next
29
+ end.compact
30
+ end
31
+
32
+ # initial values for filtered search
33
+ def default_filter_hash
34
+ {}
35
+ end
36
+
37
+ def extra_paging_path_args
38
+ super.merge filter_and_sort_hash
39
+ end
40
+
41
+ def filter_and_sort_hash
42
+ { filter: filter_hash }.tap do |hash|
43
+ hash[:sort] = sort_param if sort_param
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,52 @@
1
+ format do
2
+ def search_params
3
+ @search_params ||= default_search_params
4
+ end
5
+
6
+ # used for override
7
+ def default_search_params
8
+ if (qparams = query_params)
9
+ paging_params.merge vars: qparams
10
+ else
11
+ paging_params
12
+ end
13
+ end
14
+
15
+ def paging_params
16
+ { limit: limit, offset: offset }
17
+ end
18
+
19
+ def query_params
20
+ return nil unless (vars = params[:query])
21
+
22
+ Env.hash vars
23
+ end
24
+
25
+ def default_limit
26
+ 100
27
+ end
28
+
29
+ def extra_paging_path_args
30
+ return {} unless (vars = query_params)
31
+
32
+ { query: vars }
33
+ end
34
+ end
35
+
36
+ format :html do
37
+ def default_limit
38
+ Cardio.config.paging_limit || 20
39
+ end
40
+ end
41
+
42
+ format :json do
43
+ def default_limit
44
+ 20
45
+ end
46
+ end
47
+
48
+ format :rss do
49
+ def default_limit
50
+ 25
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ # include_set Abstract::Utility
2
+
3
+ format do
4
+ def filter_class
5
+ Card::FilterQuery
6
+ end
7
+
8
+ def filter_keys
9
+ [:name]
10
+ end
11
+
12
+ def filter_keys_from_params
13
+ filter_hash.keys.map(&:to_sym) - [:not_ids]
14
+ end
15
+
16
+ def sort_options
17
+ { "Alphabetical": :name, "Recently Added": :create }
18
+ end
19
+ end
20
+
21
+ format :html do
22
+ view :filtered_content, template: :haml, wrap: :slot
23
+
24
+ view :filtered_results do
25
+ wrap { render_core }
26
+ end
27
+
28
+ view :selectable_filtered_content, template: :haml, cache: :never
29
+ end
@@ -0,0 +1,8 @@
1
+ ._filter-input.filter-input-group.input-group.input-group-sm{"data-category": key, "class": "_filter-input-#{key}" }
2
+ .input-group-prepend.filter-input-prepend
3
+ %button.btn.btn-secondary.input-group-text.p-1._delete-filter-input{:type => "button"}
4
+ %small.text-muted
5
+ = fa_icon :times
6
+ %span.input-group-text._selected-category.text-muted
7
+ = category[:label]
8
+ = category[:input_field]
@@ -0,0 +1,56 @@
1
+
2
+ .card.w-100.nodblclick
3
+ .card-body
4
+ ._filter-widget
5
+ // FILTERING PROTOTYPES
6
+ ._filter-input-field-prototypes.d-none
7
+ - categories.each do |key, category|
8
+ = haml_partial :filter_input, key: key, category: category
9
+
10
+
11
+ // FORM
12
+ %form._filter-form{form_args, "accept-charset": "UTF-8",
13
+ "data-remote": "true",
14
+ method: "get"}
15
+ - not_ids = params.dig :filter, :not_ids
16
+ %input{ type: :hidden,
17
+ name: "filter[not_ids]",
18
+ class: "_not-ids",
19
+ value: not_ids }
20
+ = render_quick_filters
21
+ .filter-and-sort.d-flex.flex-wrap
22
+ - if sort_input_field.present?
23
+ // SORTING
24
+ .sort-in-filter-form
25
+ %i.fa.fa-sort.filter-section-icon
26
+ .input-group.input-group-sm.flex-nowrap.sort-input-group.mb-2.mr-2
27
+ .input-group-prepend
28
+ %span.input-group-text.text-muted Sort
29
+ = sort_input_field
30
+
31
+ // FILTERING
32
+ .filter-in-filter-form
33
+ %i.fa.fa-filter.filter-section-icon
34
+ ._filter-container.d-flex.flew-row.flex-wrap
35
+ // FILTERS inserted here
36
+
37
+ // ADD FILTER DROPDOWN
38
+ .dropdown._add-filter-dropdown.mr-2
39
+ %button.btn.btn-sm.btn-primary.dropdown-toggle{"aria-expanded": "false",
40
+ "aria-haspopup": "true",
41
+ "data-toggle": "dropdown",
42
+ type: "button" }
43
+ More Filters
44
+ .dropdown-menu
45
+ - categories.each do |key, category|
46
+ %a{ class: "dropdown-item _filter-category-select",
47
+ href: "#",
48
+ "data-category": key,
49
+ "data-label": category[:label],
50
+ "data-active": ("true" if category[:active])}
51
+ = category[:label]
52
+
53
+ // RESET BUTTON
54
+ ._reset-filter{ "data-reset": reset_filter_data }
55
+ %button.btn.btn-sm.btn-secondary{ type: "button" }
56
+ %i.fa.fa-sync
@@ -0,0 +1,101 @@
1
+ format :html do
2
+ # sort and filter ui
3
+ view :filter_form, cache: :never do
4
+ filter_fields slot_selector: "._filter-result-slot",
5
+ sort_field: _render(:sort_formgroup)
6
+ end
7
+
8
+ view :quick_filters, cache: :never do
9
+ return "" unless quick_filter_list.present?
10
+
11
+ haml :quick_filters, filter_list: normalized_quick_filter_list
12
+ end
13
+
14
+ def normalized_quick_filter_list
15
+ quick_filter_list.map do |hash|
16
+ quick_filter_item hash.clone, hash.keys.first
17
+ end
18
+ end
19
+
20
+ def reset_filter_data
21
+ JSON default_filter_hash
22
+ end
23
+
24
+ def quick_filter_item hash, filter_key
25
+ {
26
+ text: (hash.delete(:text) || hash[filter_key]),
27
+ class: css_classes(hash.delete(:class),
28
+ "_filter-link quick-filter-by-#{filter_key}"),
29
+ filter: JSON(hash[:filter] || hash)
30
+ }
31
+ end
32
+
33
+ # for override
34
+ def quick_filter_list
35
+ []
36
+ end
37
+
38
+ # for override
39
+ def custom_quick_filters
40
+ ""
41
+ end
42
+
43
+ # @param data [Hash] the filter categories. The hash needs for every category
44
+ # a hash with a label and a input_field entry.
45
+ def filter_form data={}, sort_input_field=nil, form_args={}
46
+ haml :filter_form, categories: data,
47
+ sort_input_field: sort_input_field,
48
+ form_args: form_args
49
+ end
50
+
51
+ def filter_fields slot_selector: nil, sort_field: nil
52
+ form_args = { action: filter_action_path, class: "slotter" }
53
+ form_args["data-slot-selector"] = slot_selector if slot_selector
54
+ filter_form filter_form_data, sort_field, form_args
55
+ end
56
+
57
+ def filter_form_data
58
+ all_filter_keys.each_with_object({}) do |cat, h|
59
+ h[cat] = { label: filter_label(cat),
60
+ input_field: _render("filter_#{cat}_formgroup"),
61
+ active: active_filter?(cat) }
62
+ end
63
+ end
64
+
65
+ def active_filter? field
66
+ if filter_keys_from_params.present?
67
+ filter_hash.key? field
68
+ else
69
+ default_filter? field
70
+ end
71
+ end
72
+
73
+ def default_filter? field
74
+ default_filter_hash.key? field
75
+ end
76
+
77
+ def filter_label field
78
+ # return "Keyword" if field.to_sym == :name
79
+ #
80
+ filter_label_from_method(field) || filter_label_from_name(field)
81
+ end
82
+
83
+ def filter_label_from_method field
84
+ try "#{field}_filter_label"
85
+ end
86
+
87
+ def filter_label_from_name field
88
+ Card.fetch_name(field) { field.to_s.titleize }
89
+ end
90
+
91
+ def filter_action_path
92
+ path
93
+ end
94
+
95
+ view :sort_formgroup, cache: :never do
96
+ select_tag "sort",
97
+ options_for_select(sort_options, current_sort),
98
+ class: "pointer-select _filter-sort form-control",
99
+ "data-minimum-results-for-search": "Infinity"
100
+ end
101
+ end
@@ -0,0 +1,5 @@
1
+ ._filtered-content.nodblclick
2
+ .filter-form
3
+ = render_filter_form
4
+ .filtered-results
5
+ = render_filtered_results home_view: :filtered_results
@@ -0,0 +1,105 @@
1
+ include_set Abstract::FilterHelper
2
+
3
+ format :html do
4
+ view :filter_name_formgroup, cache: :never do
5
+ text_filter :name
6
+ end
7
+
8
+ def select_filter field, default=nil, options=nil
9
+ options ||= filter_options field
10
+ options.unshift(["--", ""]) unless default
11
+ select_filter_tag field, default, options
12
+ end
13
+
14
+ def multiselect_filter field, default=nil, options=nil
15
+ options ||= filter_options field
16
+ multiselect_filter_tag field, default, options
17
+ end
18
+
19
+ def text_filter field, default=nil, opts={}
20
+ value = filter_param(field) || default
21
+ text_filter_with_name_and_value filter_name(field), value, opts
22
+ end
23
+
24
+ def text_filter_with_name_and_value name, value, opts
25
+ opts[:class] ||= "simple-text"
26
+ add_class opts, "form-control"
27
+ text_field_tag name, value, opts
28
+ end
29
+
30
+ def range_filter field, default={}, opts={}
31
+ add_class opts, "simple-text range-filter-subfield"
32
+ default ||= {}
33
+ output [range_sign(:from),
34
+ sub_text_filter(field, :from, default, opts),
35
+ range_sign(:to),
36
+ sub_text_filter(field, :to, default, opts)]
37
+ end
38
+
39
+ def range_sign side
40
+ dir = side == :from ? "right" : "left"
41
+ wrap_with :span, class: "input-group-prepend" do
42
+ fa_icon("chevron-circle-#{dir}", class: "input-group-text")
43
+ end
44
+ end
45
+
46
+ def sub_text_filter field, subfield, default={}, opts={}
47
+ name = "filter[#{field}][#{subfield}]"
48
+ value = filter_hash.dig(field, subfield) || default[subfield]
49
+ text_filter_with_name_and_value name, value, opts
50
+ end
51
+
52
+ def autocomplete_filter type_code, options_card=nil
53
+ options_card ||= Card::Name[type_code, :type, :by_name]
54
+ text_filter type_code, "", class: "#{type_code}_autocomplete",
55
+ "data-options-card": options_card
56
+ end
57
+
58
+ def multiselect_filter_tag field, default, options, html_options={}
59
+ html_options[:multiple] = true
60
+ select_filter_tag field, default, options, html_options
61
+ end
62
+
63
+ def select_filter_tag field, default, options, html_options={}
64
+ name = filter_name field, html_options[:multiple]
65
+ options = options_for_select options, (filter_param(field) || default)
66
+ normalize_select_filter_tag_html_options field, html_options
67
+ select_tag name, options, html_options
68
+ end
69
+
70
+ # alters html_options hash
71
+ def normalize_select_filter_tag_html_options field, html_options
72
+ pointer_suffix = html_options[:multiple] ? "multiselect" : "select"
73
+ add_class html_options, "pointer-#{pointer_suffix} filter-input #{field} " \
74
+ "_filter_input_field _no-select2 form-control"
75
+ # _no-select2 because select is initiated after filter is opened.
76
+ html_options[:id] = "filter-input-#{unique_id}"
77
+ end
78
+
79
+ def filter_name field, multi=false
80
+ "filter[#{field}]#{'[]' if multi}"
81
+ end
82
+
83
+ def filter_options field
84
+ raw = send("#{field}_options")
85
+ raw.is_a?(Array) ? raw : option_hash_to_array(raw)
86
+ end
87
+
88
+ def option_hash_to_array hash
89
+ hash.each_with_object([]) do |(key, value), array|
90
+ array << [key, value.to_s]
91
+ array
92
+ end
93
+ end
94
+
95
+ def type_options type_codename, order="asc", max_length=nil
96
+ Card.cache.fetch "#{type_codename}-TYPE-OPTIONS" do
97
+ res = Card.search type: type_codename, return: :name, sort: "name", dir: order
98
+ max_length ? (res.map { |i| [trim_option(i, max_length), i] }) : res
99
+ end
100
+ end
101
+
102
+ def trim_option option, max_length
103
+ option.size > max_length ? "#{option[0..max_length]}..." : option
104
+ end
105
+ end