radiant-taggable-extension 2.0.0 → 2.0.1
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/app/models/library_page.rb +1 -57
- data/lib/radiant-taggable-extension.rb +1 -1
- data/lib/radius/asset_tags.rb +14 -0
- data/lib/radius/library_tags.rb +57 -26
- data/lib/taggable/faceted_page.rb +98 -0
- data/lib/taggable/model.rb +1 -1
- data/lib/taggable/page.rb +1 -1
- data/lib/taggable/site_controller.rb +20 -9
- data/spec/controllers/site_controller_spec.rb +9 -3
- data/spec/models/library_page_spec.rb +8 -4
- data/taggable_extension.rb +1 -1
- metadata +7 -8
data/app/models/library_page.rb
CHANGED
@@ -1,60 +1,4 @@
|
|
1
1
|
class LibraryPage < Page
|
2
|
-
include
|
3
|
-
|
4
|
-
class RedirectRequired < StandardError
|
5
|
-
def initialize(message = nil); super end
|
6
|
-
end
|
7
|
-
|
2
|
+
include Taggable::FacetedPage
|
8
3
|
description %{ Takes tag names in child position or as paramaters so that tagged items can be listed. }
|
9
|
-
|
10
|
-
attr_accessor :requested_tags, :strict_match
|
11
|
-
|
12
|
-
def cache?
|
13
|
-
true
|
14
|
-
end
|
15
|
-
|
16
|
-
def find_by_url(url, live = true, clean = false)
|
17
|
-
url = clean_url(url) if clean
|
18
|
-
my_url = self.url
|
19
|
-
return false unless url =~ /^#{Regexp.quote(my_url)}(.*)/
|
20
|
-
tags = $1.split('/')
|
21
|
-
if slug_child = children.find_by_slug(tags[0])
|
22
|
-
found = slug_child.find_by_url(url, live, clean)
|
23
|
-
return found if found
|
24
|
-
end
|
25
|
-
remove_tags, add_tags = tags.partition{|t| t.first == '-'}
|
26
|
-
add_request_tags(add_tags)
|
27
|
-
remove_request_tags(remove_tags)
|
28
|
-
self
|
29
|
-
end
|
30
|
-
|
31
|
-
def add_request_tags(tags=[])
|
32
|
-
if tags.any?
|
33
|
-
tags.collect! { |tag| Tag.find_by_title(Rack::Utils::unescape(tag)) }
|
34
|
-
self.requested_tags = (self.requested_tags + tags.select{|t| !t.nil?}).uniq
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def remove_request_tags(tags=[])
|
39
|
-
if tags.any?
|
40
|
-
tags.collect! { |tag|
|
41
|
-
tag.slice!(0) if tag.first == '-'
|
42
|
-
Tag.find_by_title(Rack::Utils::unescape(tag))
|
43
|
-
}
|
44
|
-
self.requested_tags = (self.requested_tags - tags.select{|t| !t.nil?}).uniq
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def requested_tags
|
49
|
-
@requested_tags ||= []
|
50
|
-
end
|
51
|
-
|
52
|
-
# this isn't very pleasing but it's the best way to let the controller know
|
53
|
-
# of our real address once tags have been added and removed.
|
54
|
-
|
55
|
-
def url_with_tags(tags = requested_tags)
|
56
|
-
clean_url( url_without_tags + '/' + tags.uniq.map(&:clean_title).to_param )
|
57
|
-
end
|
58
|
-
alias_method_chain :url, :tags
|
59
|
-
|
60
4
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module RadiantTaggableExtension
|
2
|
-
VERSION = '2.0.
|
2
|
+
VERSION = '2.0.1'
|
3
3
|
SUMMARY = %q{Tagging, clouding and faceting extension for Radiant CMS}
|
4
4
|
DESCRIPTION = %q{General purpose tagging and retrieval extension: more versatile but less focused than the tags extension. A good way to support faceted search.}
|
5
5
|
URL = "http://spanner.org/radiant/taggable"
|
data/lib/radius/asset_tags.rb
CHANGED
@@ -26,6 +26,20 @@ module Radius
|
|
26
26
|
tag 'asset:tags:each' do |tag|
|
27
27
|
tag.render('tags:each', tag.attr.dup, &tag.block)
|
28
28
|
end
|
29
|
+
|
30
|
+
desc %{
|
31
|
+
Expands if the present asset has any tags.
|
32
|
+
}
|
33
|
+
tag 'asset:if_tags' do |tag|
|
34
|
+
tag.expand if tag.locals.asset.attached_tags.any?
|
35
|
+
end
|
36
|
+
|
37
|
+
desc %{
|
38
|
+
Expands if the present asset has no tags.
|
39
|
+
}
|
40
|
+
tag 'asset:unless_tags' do |tag|
|
41
|
+
tag.expand unless tag.locals.asset.attached_tags.any?
|
42
|
+
end
|
29
43
|
|
30
44
|
desc %{
|
31
45
|
Lists all the assets similar to this asset (based on its tagging), in descending order of relatedness.
|
data/lib/radius/library_tags.rb
CHANGED
@@ -227,47 +227,78 @@ module Radius
|
|
227
227
|
end
|
228
228
|
end
|
229
229
|
|
230
|
-
############### extra tags:* tags that only make sense on library pages
|
230
|
+
############### extra tags:* tags that only make sense on library or other faceting pages
|
231
231
|
|
232
232
|
desc %{
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
233
|
+
Makes a link that adds the current tag to the active set. Other options are passed through as usual.
|
234
|
+
If the present page is not a library page, or similar, the link will be directed to the first available
|
235
|
+
library page. You can override this behavior by specifying a 'base' parameter, which will force that
|
236
|
+
path prefix regardless of what page it designates, if any. Remote urls are also possible.
|
237
|
+
|
238
|
+
*Usage:*
|
239
|
+
<pre><code><r:tag:link base='/library' /></code></pre>
|
240
|
+
}
|
241
|
+
tag 'tag:link' do |tag|
|
242
|
+
options = tag.attr.dup
|
243
|
+
options['class'] ||= 'facet'
|
244
|
+
anchor = options['anchor'] ? "##{options.delete('anchor')}" : ''
|
245
|
+
attributes = options.inject(' ') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
|
246
|
+
text = tag.double? ? tag.expand : tag.render('tag:name')
|
247
|
+
if tag.locals.page.respond_to?(:requested_tags)
|
248
|
+
href = tag.locals.page.path(tag.locals.page.requested_tags + tag.locals.tag)
|
249
|
+
elsif base_path = options.delete('base')
|
250
|
+
href = clean_path(base_path + '/-' + tag.locals.tag.clean_title)
|
251
|
+
elsif library = LibraryPage.first
|
252
|
+
href = library.path([tag.locals.tag])
|
253
|
+
else
|
254
|
+
raise TagError "cannot find a LibraryPage to link to."
|
244
255
|
end
|
256
|
+
|
257
|
+
%{<a href="#{href}#{anchor}"#{attributes}>#{text}</a>}
|
258
|
+
end
|
259
|
+
|
260
|
+
desc %{
|
261
|
+
Summarises in a sentence the list of attached, with each one presented as a faceting link.
|
262
|
+
}
|
263
|
+
tag 'tags:link_list' do |tag|
|
264
|
+
_get_requested_tags(tag).map { |t|
|
265
|
+
tag.locals.tag = t
|
266
|
+
tag.render('tag:link', tag.attr.dup)
|
267
|
+
}.join(' | ')
|
245
268
|
end
|
246
269
|
|
247
270
|
desc %{
|
248
|
-
Makes a link that removes the current tag from the active set. Other options as for tag:link.
|
271
|
+
Makes a link that removes the current tag from the active set. Other options as for tag:link.
|
272
|
+
This only really makes sense in the context of a requested_tags list, since no other tag can be unlinked.
|
249
273
|
|
250
274
|
*Usage:*
|
251
|
-
<pre><code><r:tag:unlink
|
275
|
+
<pre><code><r:requested_tags:each><r:tag:unlink base='/library' /></r:requested_tags:each></code></pre>
|
252
276
|
}
|
253
277
|
tag 'tag:unlink' do |tag|
|
254
|
-
raise TagError
|
278
|
+
raise TagError "unlinking a tag requires a library page or similar" unless tag.locals.page.respond_to?(:requested_tags)
|
279
|
+
|
255
280
|
options = tag.attr.dup
|
256
|
-
options['class'] ||= '
|
281
|
+
options['class'] ||= 'defacet'
|
257
282
|
anchor = options['anchor'] ? "##{options.delete('anchor')}" : ''
|
258
|
-
attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
|
259
|
-
attributes = " #{attributes}" unless attributes.empty?
|
283
|
+
attributes = options.inject(' ') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
|
260
284
|
text = tag.double? ? tag.expand : tag.render('tag:name')
|
285
|
+
href = tag.locals.page.path(tag.locals.page.requested_tags - [tag.locals.tag])
|
286
|
+
%{<a href="#{href}#{anchor}"#{attributes}>#{text}</a>}
|
287
|
+
end
|
261
288
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
289
|
+
desc %{
|
290
|
+
Summarises in a sentence the list of tags currently active, with each one presented as a defaceting link.
|
291
|
+
}
|
292
|
+
tag 'tags:unlink_list' do |tag|
|
293
|
+
requested = _get_requested_tags(tag)
|
294
|
+
if requested.any?
|
295
|
+
requested.map { |t|
|
296
|
+
tag.locals.tag = t
|
297
|
+
tag.render('tag:unlink', tag.attr.dup)
|
298
|
+
}.to_sentence
|
299
|
+
else
|
300
|
+
""
|
268
301
|
end
|
269
|
-
|
270
|
-
%{<a href="#{href}#{anchor}"#{attributes}>#{text}</a>}
|
271
302
|
end
|
272
303
|
|
273
304
|
private
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Taggable
|
2
|
+
class RedirectRequired < StandardError
|
3
|
+
def initialize(message = nil); super end
|
4
|
+
end
|
5
|
+
|
6
|
+
module FacetedPage
|
7
|
+
|
8
|
+
# This module can be mixed into any Page subclass to give it mystical web 2.0 faceting powers.
|
9
|
+
# Any nonexistent child path is assumed to be a tag set, and methods are provided for reading
|
10
|
+
# that set, and adding and removing tags.
|
11
|
+
#
|
12
|
+
def self.included(base)
|
13
|
+
base.extend ClassMethods
|
14
|
+
base.class_eval {
|
15
|
+
include InstanceMethods
|
16
|
+
alias_method_chain :path, :tags
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
module InstanceMethods
|
24
|
+
|
25
|
+
# Faceted pages map nicely onto urls and are cacheable.
|
26
|
+
# There can be redundancy if tags are specified in varying order,
|
27
|
+
# but the site controller tries to normalize everything.
|
28
|
+
#
|
29
|
+
def cache?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
# We override the normal Page#find_by_path mechanism and treat nonexistent child paths
|
34
|
+
# are understood as tag sets. Note that the extended site_controller will add to this set
|
35
|
+
# any tags that have been supplied in query string parameters. This may trigger a redirect
|
36
|
+
# to bring the request path into line with the consolidated tag set.
|
37
|
+
#
|
38
|
+
def find_by_path(path, live = true, clean = false)
|
39
|
+
path = clean_path(path) if clean
|
40
|
+
return false unless path =~ /^#{Regexp.quote(self.path)}(.*)/
|
41
|
+
tags = $1.split('/')
|
42
|
+
if slug_child = children.find_by_slug(tags[0])
|
43
|
+
found = slug_child.find_by_url(path, live, clean)
|
44
|
+
return found if found
|
45
|
+
end
|
46
|
+
remove_tags, add_tags = tags.partition{|t| t.first == '-'}
|
47
|
+
add_request_tags(add_tags)
|
48
|
+
remove_request_tags(remove_tags)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
alias_method :find_by_url, :find_by_path
|
52
|
+
|
53
|
+
# The set of tags attached to the page request.
|
54
|
+
#
|
55
|
+
def requested_tags
|
56
|
+
@requested_tags ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
# The normal `path` method is extended to append the (sorted and deduped) tags attached to the page request
|
60
|
+
#
|
61
|
+
def path_with_tags(tags = requested_tags)
|
62
|
+
clean_path( path_without_tags + '/' + tags.uniq.sort.map(&:clean_title).to_param )
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# @requested_tags is the set of Tag objects attached to the page request.
|
68
|
+
#
|
69
|
+
def requested_tags=(tags)
|
70
|
+
@requested_tags = tags
|
71
|
+
end
|
72
|
+
|
73
|
+
# We hold in memory a list of the tags that were appended to the path when this page was selected.
|
74
|
+
# This method adds tags to that list. It is normally called only once, to populate the list.
|
75
|
+
#
|
76
|
+
def add_request_tags(tags=[])
|
77
|
+
if tags.any?
|
78
|
+
tags.collect! { |tag| Tag.find_by_title(Rack::Utils::unescape(tag)) }
|
79
|
+
self.requested_tags = (self.requested_tags + tags.select{|t| !t.nil?}).uniq
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# This method removes tags from the appended list. Normally only used to handle defaceting links
|
84
|
+
# (in the form /path/to/page/tag1/tag2/tag3/-tag2).
|
85
|
+
#
|
86
|
+
def remove_request_tags(tags=[])
|
87
|
+
if tags.any?
|
88
|
+
tags.collect! { |tag|
|
89
|
+
tag.slice!(0) if tag.first == '-'
|
90
|
+
Tag.find_by_title(Rack::Utils::unescape(tag))
|
91
|
+
}
|
92
|
+
self.requested_tags = (self.requested_tags - tags.compact).uniq
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/lib/taggable/model.rb
CHANGED
data/lib/taggable/page.rb
CHANGED
@@ -5,26 +5,37 @@ module Taggable
|
|
5
5
|
|
6
6
|
base.class_eval {
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
# We override find_page to handle the combination of url tags and parameter tags.
|
9
|
+
# If this results in a change to the compound path for this page/tag combination,
|
10
|
+
# we raise a RedirectRequired error.
|
11
|
+
#
|
12
|
+
def find_page_with_tags(path)
|
13
|
+
path = clean_path(path)
|
14
|
+
page = find_page_without_tags(path)
|
15
|
+
if page.respond_to?(:requested_tags)
|
16
|
+
page.add_request_tags(Tag.in_this_list(params[:tag])) if params[:tag]
|
17
|
+
raise Taggable::RedirectRequired, page.path unless page.path == path
|
18
|
+
end
|
14
19
|
page
|
15
20
|
end
|
16
21
|
alias_method_chain :find_page, :tags
|
17
22
|
|
23
|
+
# Extends show_page to catch any redirect error raised by find_page, and redirect to the specified path.
|
24
|
+
# The effect of this is to standardise all tag-appended requests, which improves cache performance and
|
25
|
+
# allows us to defacet by adding -tag parameters.
|
26
|
+
#
|
27
|
+
# (I can't remember why that was necessary: it may no longer be, so don't rely on it!).
|
28
|
+
#
|
18
29
|
def show_page_with_tags
|
19
30
|
show_page_without_tags
|
20
|
-
rescue
|
31
|
+
rescue Taggable::RedirectRequired => e
|
21
32
|
redirect_to e.message
|
22
33
|
end
|
23
34
|
alias_method_chain :show_page, :tags
|
24
35
|
|
25
36
|
protected
|
26
|
-
def
|
27
|
-
"/#{
|
37
|
+
def clean_path(path)
|
38
|
+
"/#{ path.strip }/".gsub(%r{//+}, '/')
|
28
39
|
end
|
29
40
|
|
30
41
|
}
|
@@ -70,21 +70,27 @@ describe SiteController do
|
|
70
70
|
end
|
71
71
|
|
72
72
|
describe "with tags requested" do
|
73
|
-
it "should
|
73
|
+
it "should redirect to a normalised form of the requested address" do
|
74
74
|
get :show_page, :url => '/library/green/furiously'
|
75
|
+
response.should be_redirect
|
76
|
+
response.should redirect_to 'http://test.host/library/furiously/green'
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should add a default Cache-Control header with public and max-age of 5 minutes" do
|
80
|
+
get :show_page, :url => '/library/furiously/green'
|
75
81
|
response.headers['Cache-Control'].should =~ /public/
|
76
82
|
response.headers['Cache-Control'].should =~ /max-age=300/
|
77
83
|
end
|
78
84
|
|
79
85
|
it "should pass along the etag set by the page" do
|
80
|
-
get :show_page, :url => '/library/green
|
86
|
+
get :show_page, :url => '/library/furiously/green'
|
81
87
|
response.headers['ETag'].should be
|
82
88
|
end
|
83
89
|
|
84
90
|
it "should return a not-modified response when the sent etag matches" do
|
85
91
|
response.stub!(:etag).and_return("foobar")
|
86
92
|
request.if_none_match = 'foobar'
|
87
|
-
get :show_page, :url => '/library/green
|
93
|
+
get :show_page, :url => '/library/furiously/green'
|
88
94
|
response.response_code.should == 304
|
89
95
|
response.body.should be_blank
|
90
96
|
end
|
@@ -11,12 +11,14 @@ describe LibraryPage do
|
|
11
11
|
describe "on request" do
|
12
12
|
describe "with one tag" do
|
13
13
|
before do
|
14
|
-
@page = Page.
|
14
|
+
@page = Page.find_by_path('/library/colourless')
|
15
15
|
end
|
16
16
|
|
17
|
-
it "should interrupt
|
17
|
+
it "should interrupt find_by_path" do
|
18
|
+
@page.should_not be_nil
|
18
19
|
@page.should == pages(:library)
|
19
20
|
@page.is_a?(LibraryPage).should be_true
|
21
|
+
@page.respond_to?(:requested_tags).should be_true
|
20
22
|
end
|
21
23
|
|
22
24
|
it "should set tag context correctly" do
|
@@ -26,18 +28,20 @@ describe LibraryPage do
|
|
26
28
|
|
27
29
|
describe "with several tags" do
|
28
30
|
before do
|
29
|
-
@page = Page.
|
31
|
+
@page = Page.find_by_path('/library/colourless/green/ideas')
|
30
32
|
end
|
31
33
|
it "should set tag context correctly" do
|
34
|
+
@page.should_not be_nil
|
32
35
|
@page.requested_tags.should == [tags(:colourless), tags(:green), tags(:ideas)]
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
36
39
|
describe "with several tags and one tag negation" do
|
37
40
|
before do
|
38
|
-
@page = Page.
|
41
|
+
@page = Page.find_by_path('/library/colourless/green/ideas/-green')
|
39
42
|
end
|
40
43
|
it "should set tag context correctly" do
|
44
|
+
@page.should_not be_nil
|
41
45
|
@page.requested_tags.should == [tags(:colourless), tags(:ideas)]
|
42
46
|
end
|
43
47
|
end
|
data/taggable_extension.rb
CHANGED
@@ -13,7 +13,7 @@ class TaggableExtension < Radiant::Extension
|
|
13
13
|
Asset.send :include, Taggable::Asset # assets are taggable (and a fake keywords column is provided)
|
14
14
|
Page.send :include, Radius::TaggableTags # adds the basic radius tags for showing page tags and tag pages
|
15
15
|
Page.send :include, Radius::AssetTags # adds some asset:* tags
|
16
|
-
|
16
|
+
Page.send :include, Radius::LibraryTags
|
17
17
|
SiteController.send :include, Taggable::SiteController # some path and parameter handling in support of library pages
|
18
18
|
Admin::PagesController.send :include, Taggable::AdminPagesController # tweaks the admin interface to make page tags more prominent
|
19
19
|
UserActionObserver.instance.send :add_observer!, Tag # tags get creator-stamped
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: radiant-taggable-extension
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 2.0.
|
9
|
+
- 1
|
10
|
+
version: 2.0.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- William Ross
|
@@ -15,8 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
19
|
-
default_executable:
|
18
|
+
date: 2011-10-05 00:00:00 Z
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
22
21
|
name: sanitize
|
@@ -76,6 +75,7 @@ files:
|
|
76
75
|
- lib/taggable/admin_pages_controller.rb
|
77
76
|
- lib/taggable/admin_ui.rb
|
78
77
|
- lib/taggable/asset.rb
|
78
|
+
- lib/taggable/faceted_page.rb
|
79
79
|
- lib/taggable/model.rb
|
80
80
|
- lib/taggable/page.rb
|
81
81
|
- lib/taggable/site_controller.rb
|
@@ -100,11 +100,10 @@ files:
|
|
100
100
|
- spec/spec.opts
|
101
101
|
- spec/spec_helper.rb
|
102
102
|
- taggable_extension.rb
|
103
|
-
has_rdoc: true
|
104
103
|
homepage: http://spanner.org/radiant/taggable
|
105
104
|
licenses: []
|
106
105
|
|
107
|
-
post_install_message: "\n Add this to your Gemfile with:\n\n gem 'radiant-taggable-extension', '~> 2.0.
|
106
|
+
post_install_message: "\n Add this to your Gemfile with:\n\n gem 'radiant-taggable-extension', '~> 2.0.1'\n\n "
|
108
107
|
rdoc_options: []
|
109
108
|
|
110
109
|
require_paths:
|
@@ -130,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
129
|
requirements: []
|
131
130
|
|
132
131
|
rubyforge_project:
|
133
|
-
rubygems_version: 1.
|
132
|
+
rubygems_version: 1.8.10
|
134
133
|
signing_key:
|
135
134
|
specification_version: 3
|
136
135
|
summary: Tagging, clouding and faceting extension for Radiant CMS
|