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