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.
- checksums.yaml +7 -0
- data/lib/card/filter_query.rb +74 -0
- data/set/abstract/00_filter_helper.rb +46 -0
- data/set/abstract/02_search_params.rb +52 -0
- data/set/abstract/03_filter.rb +29 -0
- data/set/abstract/03_filter/_filter_input.haml +8 -0
- data/set/abstract/03_filter/filter_form.haml +56 -0
- data/set/abstract/03_filter/filter_form.rb +101 -0
- data/set/abstract/03_filter/filtered_content.haml +5 -0
- data/set/abstract/03_filter/form_helper.rb +105 -0
- data/set/abstract/03_filter/query_construction.rb +38 -0
- data/set/abstract/03_filter/quick_filters.haml +11 -0
- data/set/abstract/03_filter/selectable_filtered_content.haml +2 -0
- data/set/abstract/04_right_filter_form.rb +26 -0
- data/set/abstract/05_search.rb +80 -0
- data/set/abstract/05_search/checkbox_item.haml +5 -0
- data/set/abstract/05_search/select_item.haml +15 -0
- data/set/abstract/05_search/views.rb +167 -0
- data/set/abstract/06_cql_search.rb +91 -0
- data/set/abstract/filterable.rb +13 -0
- data/set/abstract/filterable_bar.rb +11 -0
- data/set/right/children.rb +4 -0
- data/set/right/created.rb +3 -0
- data/set/right/edited.rb +3 -0
- data/set/right/editors.rb +3 -0
- data/set/right/follow.rb +3 -0
- data/set/right/linked_to_by.rb +3 -0
- data/set/right/links_to.rb +5 -0
- data/set/right/mates.rb +3 -0
- data/set/right/nested_by.rb +3 -0
- data/set/right/nests.rb +3 -0
- data/set/right/referred_to_by.rb +3 -0
- data/set/right/refers_to.rb +3 -0
- data/set/self/recent.rb +35 -0
- data/set/self/search.rb +93 -0
- data/set/type/cardtype.rb +5 -0
- data/set/type/search_type.rb +80 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -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,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
|