card-mod-search 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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