radiant-taggable-extension 1.2.5 → 2.0.0.rc1
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.
- data/README.md +82 -18
- data/Rakefile +0 -15
- data/app/helpers/taggable_helper.rb +18 -0
- data/app/models/library_page.rb +60 -0
- data/app/models/tag.rb +19 -2
- data/app/views/admin/assets/_edit_metadata.html.haml +20 -0
- data/app/views/admin/assets/_edit_title.html.haml +14 -0
- data/app/views/admin/tags/_show_assets.html.haml +22 -0
- data/lib/link_renderer.rb +22 -0
- data/lib/radiant-taggable-extension.rb +8 -0
- data/lib/radius/asset_tags.rb +151 -0
- data/lib/radius/library_tags.rb +387 -0
- data/lib/radius/taggable_tags.rb +655 -0
- data/lib/taggable/admin_pages_controller.rb +16 -0
- data/lib/taggable/admin_ui.rb +43 -0
- data/lib/taggable/asset.rb +37 -0
- data/lib/taggable/model.rb +144 -0
- data/lib/taggable/page.rb +54 -0
- data/lib/taggable/site_controller.rb +33 -0
- data/radiant-taggable-extension.gemspec +23 -85
- data/spec/controllers/site_controller_spec.rb +96 -0
- data/spec/datasets/tags_dataset.rb +2 -0
- data/spec/lib/taggable_page_spec.rb +1 -1
- data/spec/models/library_page_spec.rb +47 -0
- data/taggable_extension.rb +17 -11
- metadata +49 -49
- data/.gitignore +0 -1
- data/VERSION +0 -1
- data/lib/taggable_admin_page_controller.rb +0 -18
- data/lib/taggable_admin_ui.rb +0 -41
- data/lib/taggable_model.rb +0 -139
- data/lib/taggable_page.rb +0 -51
- data/lib/taggable_tags.rb +0 -529
@@ -0,0 +1,387 @@
|
|
1
|
+
module Radius
|
2
|
+
module LibraryTags
|
3
|
+
include Radiant::Taggable
|
4
|
+
include TaggableHelper
|
5
|
+
|
6
|
+
class TagError < StandardError; end
|
7
|
+
|
8
|
+
############### tags for use on library pages
|
9
|
+
# usually to build a faceted browser
|
10
|
+
|
11
|
+
tag "library" do |tag|
|
12
|
+
raise TagError, "library:* tags can only be used on a LibraryPage" unless tag.locals.page && tag.locals.page.is_a?(LibraryPage)
|
13
|
+
tag.expand
|
14
|
+
end
|
15
|
+
|
16
|
+
desc %{
|
17
|
+
Displays a list of the tags that can be used to narrow the set of results displayed. This begins as a list of
|
18
|
+
all available tags, and as they are chosen it shrinks to show only the coincident tags that will further reduce the set.
|
19
|
+
|
20
|
+
This is normally used to display a list or cloud of facets that can be added to a search.
|
21
|
+
|
22
|
+
<pre><code>
|
23
|
+
<r:library:tags:each><li><r:tag:link /></li></r:library:tags:each>
|
24
|
+
<r:library:tags:list />
|
25
|
+
<r:library:tags:cloud />
|
26
|
+
</code></pre>
|
27
|
+
|
28
|
+
To show only those tags attached to a particular kind of object, supply a 'for' parameter.
|
29
|
+
The parameter can be 'pages', 'assets' or the plural of any asset type. If you're displaying an image gallery,
|
30
|
+
you may want to start with a cloud of all the tags that have been applied to images:
|
31
|
+
|
32
|
+
<pre><code>
|
33
|
+
<r:library:tags for="images" />
|
34
|
+
</code></pre>
|
35
|
+
|
36
|
+
You can still display pages associated with those tags, but the list will not include tags that only have pages.
|
37
|
+
}
|
38
|
+
tag "library:tags" do |tag|
|
39
|
+
tag.locals.tags = _get_coincident_tags(tag).sort
|
40
|
+
tag.expand
|
41
|
+
end
|
42
|
+
tag "library:tags:each" do |tag|
|
43
|
+
tag.render('each_tag', tag.attr.dup, &tag.block)
|
44
|
+
end
|
45
|
+
tag "library:tags:list" do |tag|
|
46
|
+
tag.render('tag_list', tag.attr.dup)
|
47
|
+
end
|
48
|
+
tag "library:tags:cloud" do |tag|
|
49
|
+
tag.render('tag_cloud', tag.attr.dup)
|
50
|
+
end
|
51
|
+
|
52
|
+
desc %{
|
53
|
+
Expands if there are is more than one tag to show.
|
54
|
+
|
55
|
+
*Usage:*
|
56
|
+
<pre><code>
|
57
|
+
<r:library:if_tags>
|
58
|
+
Displaying items tagged with all of <r:requested_tags />
|
59
|
+
<r:library:if_tags>
|
60
|
+
</code></pre>
|
61
|
+
}
|
62
|
+
tag "library:if_tags" do |tag|
|
63
|
+
tag.locals.tags = _get_coincident_tags(tag).sort
|
64
|
+
tag.expand if tag.locals.tags.length > 1
|
65
|
+
end
|
66
|
+
desc %{
|
67
|
+
Expands if are is one or no tag to show.
|
68
|
+
|
69
|
+
*Usage:*
|
70
|
+
<pre><code>
|
71
|
+
<r:library:unless_tags>That's your lot.</r:library:unless_tags>
|
72
|
+
</code></pre>
|
73
|
+
}
|
74
|
+
tag "library:unless_tags" do |tag|
|
75
|
+
tag.locals.tags = _get_coincident_tags(tag).sort
|
76
|
+
tag.expand if tag.locals.tags.length > 1
|
77
|
+
end
|
78
|
+
|
79
|
+
desc %{
|
80
|
+
Displays a list of the tags requested by the user.
|
81
|
+
To offer links that remove the tag from the current set, these will both work:
|
82
|
+
|
83
|
+
*Usage:*
|
84
|
+
<pre><code>
|
85
|
+
<r:library:requested_tags:each><li><r:tag:unlink /></li></r:library:requested_tags:each>
|
86
|
+
<r:library:requested_tags />
|
87
|
+
</code></pre>
|
88
|
+
}
|
89
|
+
tag "library:requested_tags" do |tag|
|
90
|
+
tag.locals.tags = _get_requested_tags(tag).sort
|
91
|
+
if tag.double?
|
92
|
+
tag.expand
|
93
|
+
else
|
94
|
+
tag.render('tags:unlink_list', tag.attr.dup)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
tag "library:requested_tags:each" do |tag|
|
98
|
+
tag.render('each_tag', tag.attr.dup, &tag.block)
|
99
|
+
end
|
100
|
+
|
101
|
+
desc %{
|
102
|
+
Expands if any tags have been specified:
|
103
|
+
|
104
|
+
*Usage:*
|
105
|
+
<pre><code>
|
106
|
+
<r:library:if_requested_tags>
|
107
|
+
Displaying items tagged with all of <r:requested_tags />
|
108
|
+
<r:library:if_requested_tags>
|
109
|
+
</code></pre>
|
110
|
+
}
|
111
|
+
tag "library:if_requested_tags" do |tag|
|
112
|
+
tag.locals.tags = _get_requested_tags(tag).sort
|
113
|
+
tag.expand if tag.locals.tags.any?
|
114
|
+
end
|
115
|
+
|
116
|
+
desc %{
|
117
|
+
Expands if no tags have been specified:
|
118
|
+
|
119
|
+
*Usage:*
|
120
|
+
<pre><code>
|
121
|
+
<r:library:unless_requested_tags>
|
122
|
+
Showing everything. Choose a tag to start narrowing down the list.
|
123
|
+
<r:library:unless_requested_tags>
|
124
|
+
</code></pre>
|
125
|
+
}
|
126
|
+
tag "library:unless_requested_tags" do |tag|
|
127
|
+
tag.expand unless _get_requested_tags(tag).any?
|
128
|
+
end
|
129
|
+
|
130
|
+
desc %{
|
131
|
+
Displays a list of the pages associated with the current tag set. If no tags are specified, this will show all pages.
|
132
|
+
You can use all the usual r:page tags within the list.
|
133
|
+
|
134
|
+
:by, :order, :limit, :offset, :status and all the usual list-control attributes are obeyed.
|
135
|
+
|
136
|
+
To paginate the list, set paginated="true" and, optionally, per_page="xx".
|
137
|
+
|
138
|
+
*Usage:*
|
139
|
+
<pre><code>
|
140
|
+
<r:library:pages:each><li><r:link /><br /><r:content part="description" /></li></r:library:pages:each>
|
141
|
+
</code></pre>
|
142
|
+
}
|
143
|
+
tag "library:pages" do |tag|
|
144
|
+
tag.locals.pages = _get_pages(tag)
|
145
|
+
tag.expand
|
146
|
+
end
|
147
|
+
tag "library:pages:each" do |tag|
|
148
|
+
tag.render('page_list', tag.attr.dup, &tag.block) # r:page_list is defined in taggable
|
149
|
+
end
|
150
|
+
|
151
|
+
desc %{
|
152
|
+
Expands if there are any pages associated with all of the current tag set.
|
153
|
+
|
154
|
+
*Usage:*
|
155
|
+
<pre><code>
|
156
|
+
<r:library:if_pages><h2>Pages</h2>...</r:library:if_pages>
|
157
|
+
</code></pre>
|
158
|
+
}
|
159
|
+
tag "library:if_pages" do |tag|
|
160
|
+
tag.expand if _get_pages(tag).any?
|
161
|
+
end
|
162
|
+
|
163
|
+
desc %{
|
164
|
+
Displays a list of the assets associated with the current tag set. If no tags are specified, this will show all assets.
|
165
|
+
You can use all the usual r:assets tags within the list.
|
166
|
+
|
167
|
+
By, order, limit and offset attributes are obeyed. The default is to sort by creation date, descending.
|
168
|
+
|
169
|
+
To paginate the list, set paginated="true" and, optionally, per_page="xx".
|
170
|
+
|
171
|
+
*Usage:*
|
172
|
+
<pre><code>
|
173
|
+
<r:library:assets:each><li><r:assets:thumbnail /></li></r:library:assets:each>
|
174
|
+
</code></pre>
|
175
|
+
}
|
176
|
+
tag "library:assets" do |tag|
|
177
|
+
tag.locals.assets = _get_assets(tag)
|
178
|
+
tag.expand
|
179
|
+
end
|
180
|
+
tag "library:assets:each" do |tag|
|
181
|
+
tag.render('asset_list', tag.attr.dup, &tag.block) # r:page_list is defined in spanner's paperclipped
|
182
|
+
end
|
183
|
+
|
184
|
+
desc %{
|
185
|
+
Expands if there are any assets associated with all of the current tag set.
|
186
|
+
|
187
|
+
*Usage:*
|
188
|
+
<pre><code>
|
189
|
+
<r:library:if_assets><h2>Assets</h2>...</r:library:if_assets>
|
190
|
+
</code></pre>
|
191
|
+
}
|
192
|
+
tag "library:if_assets" do |tag|
|
193
|
+
tag.expand if _get_assets(tag).any?
|
194
|
+
end
|
195
|
+
|
196
|
+
Asset.known_types.each do |type|
|
197
|
+
these = type.to_s.pluralize
|
198
|
+
|
199
|
+
desc %{
|
200
|
+
Displays a list of the all the #{these} associated with the current tag set. If no tags are specified, this will show all such assets.
|
201
|
+
You can use all the usual r:assets tags within the list.
|
202
|
+
|
203
|
+
*Usage:*
|
204
|
+
<pre><code>
|
205
|
+
<r:library:#{these}:each><li><r:assets:link /></li></r:library:#{these}:each>
|
206
|
+
</code></pre>
|
207
|
+
}
|
208
|
+
tag "library:#{these}" do |tag|
|
209
|
+
tag.locals.assets = _get_assets(tag).send(these.intern)
|
210
|
+
tag.expand
|
211
|
+
end
|
212
|
+
tag "library:#{these}:each" do |tag|
|
213
|
+
tag.render('asset_list', tag.attr.dup, &tag.block)
|
214
|
+
end
|
215
|
+
|
216
|
+
desc %{
|
217
|
+
Expands if there are any #{these} associated with all of the current tag set.
|
218
|
+
|
219
|
+
*Usage:*
|
220
|
+
<pre><code>
|
221
|
+
<r:library:if_#{these}><h2>#{these.titlecase}</h2>...</r:library:if_#{these}>
|
222
|
+
</code></pre>
|
223
|
+
}
|
224
|
+
tag "library:if_#{these}" do |tag|
|
225
|
+
tag.locals.assets = _get_assets(tag).send(these.intern)
|
226
|
+
tag.expand if tag.locals.assets.any?
|
227
|
+
end
|
228
|
+
|
229
|
+
############### tags:* tags that only make sense on library pages
|
230
|
+
|
231
|
+
desc %{
|
232
|
+
Summarises in a sentence the list of tags currently active, with each one presented as a defaceting link.
|
233
|
+
}
|
234
|
+
tag 'tags:unlink_list' do |tag|
|
235
|
+
requested = _get_requested_tags(tag)
|
236
|
+
if requested.any?
|
237
|
+
requested.map { |t|
|
238
|
+
tag.locals.tag = t
|
239
|
+
tag.render('tag:unlink', tag.attr.dup)
|
240
|
+
}.to_sentence
|
241
|
+
else
|
242
|
+
""
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
desc %{
|
247
|
+
Makes a link that removes the current tag from the active set. Other options as for tag:link.
|
248
|
+
|
249
|
+
*Usage:*
|
250
|
+
<pre><code><r:tag:unlink linkto='/library' /></code></pre>
|
251
|
+
}
|
252
|
+
tag 'tag:unlink' do |tag|
|
253
|
+
raise TagError, "tag must be defined for tag:unlink tag" unless tag.locals.tag
|
254
|
+
options = tag.attr.dup
|
255
|
+
options['class'] ||= 'detag'
|
256
|
+
anchor = options['anchor'] ? "##{options.delete('anchor')}" : ''
|
257
|
+
attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
|
258
|
+
attributes = " #{attributes}" unless attributes.empty?
|
259
|
+
text = tag.double? ? tag.expand : tag.render('tag:name')
|
260
|
+
|
261
|
+
if tag.locals.page.is_a?(LibraryPage)
|
262
|
+
href = tag.locals.page.url(tag.locals.page.requested_tags - [tag.locals.tag])
|
263
|
+
elsif page_url = (options.delete('tagpage') || Radiant::Config['tags.page'])
|
264
|
+
href = clean_url(page_url + '/-' + tag.locals.tag.clean_title)
|
265
|
+
else
|
266
|
+
href ||= Rack::Utils.escape("-#{tag.locals.tag.title}") + '/'
|
267
|
+
end
|
268
|
+
|
269
|
+
%{<a href="#{href}#{anchor}"#{attributes}>#{text}</a>}
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
############### libraryish utility tags that don't really belong here
|
274
|
+
|
275
|
+
desc %{
|
276
|
+
Truncates the contained text to the specified number of words. Attributes:
|
277
|
+
* `limit` sets the number of words shown. Default is 64.
|
278
|
+
* `ellipsis` is the suffix used to indicate truncation. Default is '…'
|
279
|
+
* `strip="true"` will cause all html tags to be stripped from the contained text before it is truncated. Default is false.
|
280
|
+
}
|
281
|
+
tag "truncate" do |tag|
|
282
|
+
# truncate_words is in LibraryHelper
|
283
|
+
truncate_words tag.expand, :limit => tag.attr['limit'], :ellipsis => tag.attr['ellipsis'], :strip => tag.attr['strip'] == 'true'
|
284
|
+
end
|
285
|
+
|
286
|
+
desc %{
|
287
|
+
Strips all html tags from the contained text, leaving the text itself unchanged.
|
288
|
+
Useful when, for example, using a page part to populate a meta tag.
|
289
|
+
}
|
290
|
+
tag "strip" do |tag|
|
291
|
+
# strip_html is in LibraryHelper
|
292
|
+
strip_html tag.expand
|
293
|
+
end
|
294
|
+
|
295
|
+
desc %{
|
296
|
+
Removes all unsafe html tags and attributes from the enclosed text, protecting from cross-site scripting attacks while leaving the text intact.
|
297
|
+
}
|
298
|
+
tag "clean" do |tag|
|
299
|
+
# clean_html is in LibraryHelper
|
300
|
+
clean_html tag.expand
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
def _get_requested_tags(tag)
|
308
|
+
tag.locals.page.requested_tags
|
309
|
+
end
|
310
|
+
|
311
|
+
def _get_coincident_tags(tag)
|
312
|
+
requested = _get_requested_tags(tag)
|
313
|
+
limit = tag.attr['limit'] || 50
|
314
|
+
if requested.any?
|
315
|
+
Tag.coincident_with(requested)
|
316
|
+
else
|
317
|
+
Tag.most_popular(limit)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# a bit of extra logic so that in the absence of any requested tags we default to all, not none
|
323
|
+
|
324
|
+
def _default_library_find_options
|
325
|
+
{
|
326
|
+
:by => 'created_at',
|
327
|
+
:order => 'desc'
|
328
|
+
}
|
329
|
+
end
|
330
|
+
|
331
|
+
def _get_pages(tag)
|
332
|
+
options = children_find_options(tag)
|
333
|
+
requested = _get_requested_tags(tag)
|
334
|
+
pages = Page.scoped(options)
|
335
|
+
pages = pages.tagged_with(requested) if requested.any?
|
336
|
+
pages
|
337
|
+
end
|
338
|
+
|
339
|
+
def _get_assets(tag)
|
340
|
+
options = asset_find_options(tag)
|
341
|
+
requested = _get_requested_tags(tag)
|
342
|
+
assets = Asset.scoped(options)
|
343
|
+
assets = assets.tagged_with(requested) if requested.any?
|
344
|
+
assets
|
345
|
+
end
|
346
|
+
|
347
|
+
# duplicate of children_find_options except:
|
348
|
+
# no virtual or status options
|
349
|
+
# defaults to chronological descending
|
350
|
+
|
351
|
+
def asset_find_options(tag)
|
352
|
+
attr = tag.attr.symbolize_keys
|
353
|
+
|
354
|
+
options = {}
|
355
|
+
|
356
|
+
[:limit, :offset].each do |symbol|
|
357
|
+
if number = attr[symbol]
|
358
|
+
if number =~ /^\d{1,4}$/
|
359
|
+
options[symbol] = number.to_i
|
360
|
+
else
|
361
|
+
raise TagError.new("`#{symbol}' attribute of `each' tag must be a positive number between 1 and 4 digits")
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
by = (attr[:by] || 'created_at').strip
|
367
|
+
order = (attr[:order] || 'desc').strip
|
368
|
+
order_string = ''
|
369
|
+
if self.attributes.keys.include?(by)
|
370
|
+
order_string << by
|
371
|
+
else
|
372
|
+
raise TagError.new("`by' attribute of `each' tag must be set to a valid field name")
|
373
|
+
end
|
374
|
+
if order =~ /^(asc|desc)$/i
|
375
|
+
order_string << " #{$1.upcase}"
|
376
|
+
else
|
377
|
+
raise TagError.new(%{`order' attribute of `each' tag must be set to either "asc" or "desc"})
|
378
|
+
end
|
379
|
+
options[:order] = order_string
|
380
|
+
options
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
|