card-mod-collection 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cabe6e7e22075e4414381fa6929b7c012c03049d1eb477eca3bda54eb0cf4508
4
+ data.tar.gz: 815252c283d17851de677f1692b98733524387b2f8fc07e3fa3decc24480aaa7
5
+ SHA512:
6
+ metadata.gz: dbf957e00b6dd53cef2cd622497a84fad0ad24f1bde19f77cb7c1dfc79df8a3c890d401e59ce422ff8c5dd50e926177314f74f3441d27c13182a33a8104f70c4
7
+ data.tar.gz: 8978ebdad15565e224b64de457b6c5dafb5bc7172bcaec56412bbe64cb4adf7d4593e97eff60b49b131a301cf53232210dccbf0749dbb9c16956bebce393e82a
@@ -0,0 +1,202 @@
1
+ # ~~~~~~~~~~~~ READING ITEMS ~~~~~~~~~~~~
2
+
3
+ # While each of the three main methods for returning lists of items can handle arguments,
4
+ # they are most commonly used without them.
5
+
6
+ # @return [Array] list of Card::Name objects
7
+ # @param args [Hash]
8
+ # @option args [String] :content override card content
9
+ # @option args [String, Card::Name, Symbol] :context name in whose context relative items
10
+ # will be interpreted. For example. +A in context of B is interpreted as B+A
11
+ # context defaults to pointer card's name. If value is `:raw`, then name is not
12
+ # contextualized
13
+ # @option args [String, Integer] :limit max number of cards to return
14
+ # @option args [String, Integer] :offset begin after the offset-th item
15
+ def item_names args={}
16
+ context = args[:context]
17
+ item_strings(args).map do |item|
18
+ clean_item_name item, context
19
+ end.compact
20
+ end
21
+
22
+ def first_name args={}
23
+ item_names(args).first
24
+ end
25
+
26
+ def first_card args={}
27
+ return unless (name = first_name)
28
+ fetch_item_card name, args
29
+ end
30
+
31
+ def first_code
32
+ first_card&.codename
33
+ end
34
+
35
+ def first_id
36
+ first_card&.id
37
+ end
38
+
39
+ # @return [Array] list of integers (card ids of items)
40
+ # @param args [Hash] see #item_names
41
+ def item_ids args={}
42
+ item_names(args).map { |name| Card.fetch_id name }.compact
43
+ end
44
+
45
+ # @return [Array] list of Card objects
46
+ # @param args [Hash] see #item_names for additional options
47
+ # @option args [String] :complete keyword to use in searching among items
48
+ # @option args [True/False] :known_only if true, return only known cards
49
+ # @option args [String] :type name of type to be used for unknown cards
50
+ def item_cards args={}
51
+ return item_cards_search(args) if args[:complete]
52
+ return known_item_cards(args) if args[:known_only]
53
+ all_item_cards args
54
+ end
55
+
56
+ # #item_name, #item_id, and #item_card each return a single item, rather than an array.
57
+ %i[name id card].each do |obj|
58
+ define_method "item_#{obj}" do |args={}|
59
+ send("item_#{obj}s", args.merge(limit: 1)).first
60
+ end
61
+ end
62
+
63
+ # for override, eg by json
64
+ def item_value item_name
65
+ item_name
66
+ end
67
+
68
+ # ~~~~~~~~~~~~ ALTERING ITEMS ~~~~~~~~~~~~
69
+
70
+ # set card content based on array and save card
71
+ # @param array [Array] list of strings/names (Cardish)
72
+ def items= array
73
+ items_to_content array
74
+ save!
75
+ end
76
+
77
+ # append item to list (does not save)
78
+ # @param cardish [Cardish]
79
+ def << cardish
80
+ add_item cardish
81
+ end
82
+
83
+ # append item to list (does not save)
84
+ # @param cardish [String, Card::Name] item name
85
+ # @param allow_duplicates [True/False] permit duplicate items (default is False)
86
+ def add_item cardish, allow_duplicates=false
87
+ return if !allow_duplicates && include_item?(cardish)
88
+
89
+ items = item_strings << cardish
90
+ items_to_content items
91
+ end
92
+
93
+ # append item to list and save card
94
+ # @param name [String, Card::Name] item name
95
+ def add_item! name
96
+ add_item(name) && save!
97
+ end
98
+
99
+ # remove item from list
100
+ # @param cardish [String, Card::Name] item to drop
101
+ def drop_item cardish
102
+ drop_item_name = Card::Name[cardish]
103
+ items_to_content(item_names.reject { |item_name| item_name == drop_item_name })
104
+ end
105
+
106
+ # remove item from list and save card
107
+ # @param cardish [String, Card::Name] item to drop
108
+ def drop_item! cardish
109
+ drop_item cardish
110
+ save!
111
+ end
112
+
113
+ # insert item into list at specified location
114
+ # @param index [Integer] Array index in which to insert item (0 is first)
115
+ # @param name [String, Card::Name] item name
116
+ def insert_item index, name
117
+ new_names = item_names
118
+ new_names.delete name
119
+ new_names.insert index, name
120
+ items_to_content new_names
121
+ end
122
+
123
+ # insert item into list at specified location and save
124
+ # @param index [Integer] Array index in which to insert item (0 is first)
125
+ # @param name [String, Card::Name] item name
126
+ def insert_item! index, name
127
+ insert_item index, name
128
+ save!
129
+ end
130
+
131
+ # ~~~~~~~~~~~~ READING ITEM HELPERS ~~~~~~~~~~~~
132
+
133
+ # Warning: the following methods, while available for use, may be subject to change
134
+
135
+ # #item_cards helpers
136
+
137
+ def item_cards_search query
138
+ Card::Query.run query.reverse_merge(referred_to_by: name, limit: 0)
139
+ end
140
+
141
+ def known_item_cards args={}
142
+ item_names(args).map { |name| Card.fetch name }.compact
143
+ end
144
+
145
+ def all_item_cards args={}
146
+ names = args[:item_names] || item_names(args)
147
+ names.map { |name| fetch_item_card name, args }
148
+ end
149
+
150
+ # TODO: support type_code and type_id. (currently type)
151
+ # uses name, because its most common use is from CQL
152
+ def item_type
153
+ opt = options_rule_card
154
+ # FIXME: need better recursion prevention
155
+ return if !opt || opt == self
156
+ opt.item_type
157
+ end
158
+
159
+ def item_strings args={}
160
+ items = raw_item_strings(args[:content] || content)
161
+ return items unless args.present?
162
+
163
+ filtered_items items, args.slice(:limit, :offset)
164
+ end
165
+
166
+ def raw_item_strings content
167
+ content.to_s.split(/\n+/).map { |i| strip_item i }
168
+ end
169
+
170
+ private
171
+
172
+ def filtered_items items, limit: 0, offset: 0
173
+ limit = limit.to_i
174
+ offset = offset.to_i
175
+ return items unless limit.positive? || offset.positive?
176
+
177
+ items[offset, (limit.zero? ? items.size : limit)] || []
178
+ end
179
+
180
+ def fetch_item_card name, args={}
181
+ Card.fetch name, new: new_unknown_item_args(args)
182
+ end
183
+
184
+ def new_unknown_item_args args
185
+ itype = args[:type] || item_type
186
+ itype ? { type: itype } : {}
187
+ end
188
+
189
+ def clean_item_name item, context
190
+ item = item.to_name
191
+ return item if context == :raw
192
+ context ||= context_card.name
193
+ item.absolute_name context
194
+ rescue Card::Error::NotFound
195
+ # eg for invalid ids or codenames
196
+ # "Invalid Item: #{item}".to_name
197
+ nil
198
+ end
199
+
200
+ def strip_item item
201
+ item.gsub(/\[\[|\]\]/, "").strip
202
+ end
@@ -0,0 +1,64 @@
1
+ MAX_ANONYMOUS_SEARCH_PARAM = 1000
2
+
3
+ format do
4
+ def limit
5
+ @limit ||= contextual_param(:limit) || default_limit
6
+ end
7
+
8
+ def offset
9
+ @offset ||= contextual_param(:offset) || 0
10
+ end
11
+
12
+ def search_with_params
13
+ @search_with_params ||= card.item_names
14
+ end
15
+
16
+ def count_with_params
17
+ @count_with_params ||= card.item_names.count
18
+ end
19
+
20
+ def total_pages
21
+ return 1 if limit.zero?
22
+ ((count_with_params - 1) / limit).to_i
23
+ end
24
+
25
+ def current_page
26
+ (offset / limit).to_i
27
+ end
28
+
29
+ # for override
30
+ def extra_paging_path_args
31
+ {}
32
+ end
33
+
34
+ private
35
+
36
+ def contextual_param param
37
+ env_search_param(param) || voo_search_param(param)
38
+ end
39
+
40
+ def env_search_param param
41
+ enforcing_legal_limit param do
42
+ val = Env.params[param]
43
+ val.to_i if focal? && val.present?
44
+ end
45
+ end
46
+
47
+ def enforcing_legal_limit param
48
+ yield.tap do |val|
49
+ enforce_legal_limit! val if param == :limit
50
+ end
51
+ end
52
+
53
+ def enforce_legal_limit! val
54
+ return if Card::Auth.signed_in? || !val || val <= MAX_ANONYMOUS_SEARCH_PARAM
55
+
56
+ raise Card::Error::PermissionDenied,
57
+ "limit parameter exceeds maximum for anonymous users " \
58
+ "(#{MAX_ANONYMOUS_SEARCH_PARAM})"
59
+ end
60
+
61
+ def voo_search_param param
62
+ voo&.cql&.dig(param)&.to_i
63
+ end
64
+ end
@@ -0,0 +1,90 @@
1
+ #! no set module
2
+
3
+ # render paging links
4
+ class PagingLinks
5
+ def initialize total_pages, current_page
6
+ @total = total_pages
7
+ @current = current_page
8
+ end
9
+
10
+ # @param window [integer] number of page links shown left and right
11
+ # of the current page
12
+ # @example: current page = 5, window = 2
13
+ # |<<|1|...|3|4|[5]|6|7|...|10|>>|
14
+ # @yield [text, page, status, options] block to build single paging link
15
+ # @yieldparam status [Symbol] :active (for current page) or :disabled
16
+ # @yieldparam page [Integer] page number, first page is 0
17
+ # @return [Array<String>]
18
+ def build window=2, &block
19
+ @render_item = block
20
+ links window
21
+ end
22
+
23
+ private
24
+
25
+ def links window
26
+ @window_start = [@current - window, 0].max
27
+ @window_end = [@current + window, @total].min
28
+ left_part + window_part + right_part
29
+ end
30
+
31
+ # the links around the current page
32
+ def window_part
33
+ (@window_start..@window_end).map do |page|
34
+ direct_page_link page
35
+ end.compact
36
+ end
37
+
38
+ def left_part
39
+ [
40
+ previous_page_link,
41
+ (direct_page_link 0 if @window_start.positive?),
42
+ (ellipse if @window_start > 1)
43
+ ].compact
44
+ end
45
+
46
+ def right_part
47
+ [
48
+ (ellipse if @total > @window_end + 1),
49
+ (direct_page_link @total if @total > @window_end),
50
+ next_page_link
51
+ ].compact
52
+ end
53
+
54
+ def previous_page_link
55
+ paging_item '<span aria-hidden="true">&laquo;</span>', previous_page,
56
+ "aria-label" => "Previous", status: :previous
57
+ end
58
+
59
+ def next_page_link
60
+ paging_item '<span aria-hidden="true">&raquo;</span>', next_page,
61
+ "aria-label" => "Next", status: :next
62
+ end
63
+
64
+ def direct_page_link page
65
+ return unless page >= 0 && page <= @total
66
+ paging_item page + 1, page
67
+ end
68
+
69
+ def ellipse
70
+ paging_item "<span>...</span>", nil, status: :ellipses
71
+ end
72
+
73
+ def paging_item text, page, options={}
74
+ status =
75
+ if page == @current
76
+ :current
77
+ else
78
+ options.delete :status
79
+ end
80
+ @render_item.call text, page, status, options
81
+ end
82
+
83
+ def previous_page
84
+ @current.positive? ? @current - 1 : false
85
+ end
86
+
87
+ def next_page
88
+ @current < @total ? @current + 1 : false
89
+ end
90
+ end
@@ -0,0 +1,111 @@
1
+ format :html do
2
+ PAGE_LI_CLASS = { ellipses: "disabled", current: "active" }.freeze
3
+
4
+ def with_paging path_args={}
5
+ with_paging_path_args path_args do
6
+ output [yield(@paging_path_args), _render_paging]
7
+ end
8
+ end
9
+
10
+ view :paging, cache: :never do
11
+ return "" unless paging_needed?
12
+ <<-HTML
13
+ <nav>
14
+ <ul class="pagination paging">
15
+ #{paging_links.join}
16
+ </ul>
17
+ </nav>
18
+ HTML
19
+ end
20
+
21
+ def paging_links
22
+ PagingLinks.new(total_pages, current_page).build do |text, page, status, options|
23
+ page_link_li text, page, status, options
24
+ end
25
+ end
26
+
27
+ # First page is 0 (not 1)
28
+ def page_link_li text, page, status, options={}
29
+ wrap_with :li, class: page_link_li_class(status) do
30
+ page_link text, page, options
31
+ end
32
+ end
33
+
34
+ def page_link_li_class status
35
+ ["page-item", PAGE_LI_CLASS[status]].compact.join " "
36
+ end
37
+
38
+ def page_link text, page, options
39
+ return content_tag(:div, text.html_safe, class: "page-link") unless page
40
+
41
+ options.merge! class: "card-paging-link slotter page-link",
42
+ remote: true,
43
+ path: page_link_path_args(page)
44
+ link_to raw(text), options
45
+ end
46
+
47
+ def with_paging_path_args args
48
+ tmp = @paging_path_args
49
+ @paging_path_args = paging_path_args args
50
+ yield
51
+ ensure
52
+ @paging_path_args = tmp
53
+ end
54
+
55
+ def paging_path_args local_args={}
56
+ @paging_path_args ||= {}
57
+ @paging_path_args.reverse_merge!(limit: limit, offset: offset)
58
+ @paging_path_args.merge! extra_paging_path_args
59
+ @paging_path_args.merge local_args
60
+ end
61
+
62
+ def page_link_path_args page
63
+ paging_path_args.merge offset: page * limit
64
+ end
65
+
66
+ def paging_needed?
67
+ return false if limit < 1
68
+ return false if fewer_results_than_limit? # avoid extra count search
69
+
70
+ # count search result instead
71
+ limit < count_with_params
72
+ end
73
+
74
+ # clear we don't need paging even before running count query
75
+ def fewer_results_than_limit?
76
+ return false unless offset.zero?
77
+
78
+ limit > offset + search_with_params.length
79
+ end
80
+ end
81
+
82
+ format :json do
83
+ def page_link_path_args page
84
+ {
85
+ limit: limit,
86
+ offset: page * limit,
87
+ item: default_item_view, # hack. need standard voo handling
88
+ format: :json
89
+ }.merge extra_paging_path_args
90
+ end
91
+
92
+ view :paging_urls, cache: :never do
93
+ return {} unless total_pages > 1
94
+
95
+ { paging: paging_urls_hash }
96
+ end
97
+
98
+ def paging_urls_hash
99
+ hash = {}
100
+ PagingLinks.new(total_pages, current_page).build do |_text, page, status, _options|
101
+ add_paging_url hash, page, status
102
+ end
103
+ hash
104
+ end
105
+
106
+ def add_paging_url hash, page, status
107
+ return unless page && status.in?(%i[next previous])
108
+
109
+ hash[status] = path page_link_path_args(page)
110
+ end
111
+ end
@@ -0,0 +1,49 @@
1
+
2
+ def extended_item_cards context=nil
3
+ items = item_cards limit: "", context: (context || self).name
4
+ list = []
5
+ book = ::Set.new # avoid loops
6
+ extend_item_list items, list, book until items.empty?
7
+ list
8
+ end
9
+
10
+ def extended_item_contents context=nil
11
+ extended_item_cards(context).map(&:item_names).flatten
12
+ end
13
+
14
+ format do
15
+ delegate :extended_item_contents, to: :card
16
+ end
17
+
18
+ private
19
+
20
+ def extend_item_list items, list, book
21
+ item = items.shift
22
+ return if already_extended? item, book
23
+ if item.collection?
24
+ # keep items in order
25
+ items.unshift(*item.item_cards)
26
+ else # no further level of items
27
+ list << item
28
+ end
29
+ end
30
+
31
+ def already_extended? item, book
32
+ return true if book.include? item
33
+ book << item
34
+ false
35
+ end
36
+
37
+ # def extended_list context=nil
38
+ # context = (context ? context.name : name)
39
+ # args = { limit: "" }
40
+ # item_cards(args.merge(context: context)).map do |x|
41
+ # x.item_cards(args)
42
+ # end.flatten.map do |x|
43
+ # x.item_cards(args)
44
+ # end.flatten.map do |y|
45
+ # y.item_names(args)
46
+ # end.flatten
47
+ # # this could go on and on. more elegant to recurse until you don't have
48
+ # # a collection
49
+ # end
@@ -0,0 +1,130 @@
1
+ def item_names _args={}
2
+ format._render_raw.split(/[,\n]/)
3
+ end
4
+
5
+ def item_cards _args={} # FIXME: this is inconsistent with item_names
6
+ [self]
7
+ end
8
+
9
+ def item_type
10
+ nil
11
+ end
12
+
13
+ def item_keys args={}
14
+ item_names(args).map do |item|
15
+ item.to_name.key
16
+ end
17
+ end
18
+
19
+ def item_count args={}
20
+ item_names(args).size
21
+ end
22
+
23
+ def items_to_content array
24
+ items = array.map { |i| standardize_item i }.reject(&:blank?)
25
+ self.content = items.to_pointer_content
26
+ end
27
+
28
+ def standardize_item item
29
+ Card::Name[item]
30
+ end
31
+
32
+ def include_item? item
33
+ item_names.include? Card::Name[item]
34
+ end
35
+
36
+ def add_item item
37
+ return if include_item? item
38
+ items_to_content(items_strings << item)
39
+ end
40
+
41
+ def drop_item item
42
+ item = Card::Name[item]
43
+ return unless include_item? item
44
+ items_to_content(item_names.reject { |i| i == item })
45
+ end
46
+
47
+ def insert_item index, name
48
+ new_names = item_names
49
+ new_names.delete name
50
+ new_names.insert index, name
51
+ items_to_content new_names
52
+ end
53
+
54
+ def replace_item old, new
55
+ return unless include_item? old
56
+ drop_item old
57
+ add_item new
58
+ end
59
+
60
+ # I think the following should work as add_item...
61
+ #
62
+ def add_id id
63
+ add_item "~#{id}"
64
+ end
65
+
66
+ def drop_id id
67
+ drop_item "~#{id}"
68
+ end
69
+
70
+ def insert_id index, id
71
+ insert_item index, "~#{id}"
72
+ end
73
+
74
+ format do
75
+ def item_links _args={}
76
+ raw(render_core).split(/[,\n]/)
77
+ end
78
+
79
+ def nest_item cardish, options={}, &block
80
+ options = item_view_options options
81
+ options[:nest_name] = Card::Name[cardish].s
82
+ nest cardish, options, &block
83
+ end
84
+
85
+ def implicit_item_view
86
+ view = voo_items_view || default_item_view
87
+ Card::View.normalize view
88
+ end
89
+
90
+ def voo_items_view
91
+ return unless voo && (items = voo.items)
92
+ items[:view]
93
+ end
94
+
95
+ def default_item_view
96
+ :name
97
+ end
98
+
99
+ def item_view_options new_options={}
100
+ options = (voo.items || {}).clone
101
+ options = options.merge new_options
102
+ options[:view] ||= implicit_item_view
103
+ determine_item_view_options_type options
104
+ options
105
+ end
106
+
107
+ def determine_item_view_options_type options
108
+ return if options[:type]
109
+ type_from_rule = card.item_type
110
+ options[:type] = type_from_rule if type_from_rule
111
+ end
112
+
113
+ def listing listing_cards, item_args={}
114
+ listing_cards.map do |item_card|
115
+ nest_item item_card, item_args do |rendered, item_view|
116
+ wrap_item rendered, item_view
117
+ end
118
+ end
119
+ end
120
+
121
+ def wrap_item item, _args={}
122
+ item # no wrap in base
123
+ end
124
+ end
125
+
126
+ format :html do
127
+ def wrap_item rendered, item_view
128
+ %(<div class="item-#{item_view}">#{rendered}</div>)
129
+ end
130
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: card-mod-collection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.11.0
5
+ platform: ruby
6
+ authors:
7
+ - Ethan McCutchen
8
+ - Philipp Kühl
9
+ - Gerry Gleason
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2020-12-24 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: card
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.101.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - '='
27
+ - !ruby/object:Gem::Version
28
+ version: 1.101.0
29
+ description: ''
30
+ email:
31
+ - info@decko.org
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - set/abstract/items.rb
37
+ - set/abstract/paging.rb
38
+ - set/abstract/paging/paging_links.rb
39
+ - set/abstract/paging/paging_views.rb
40
+ - set/all/extended.rb
41
+ - set/all/item.rb
42
+ homepage: http://decko.org
43
+ licenses:
44
+ - GPL-3.0
45
+ metadata:
46
+ card-mod: collection
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '2.5'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.0.3
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: collection (list and search)
66
+ test_files: []