radiant-library-extension 2.0.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.
- data/README.md +112 -0
- data/Rakefile +137 -0
- data/app/models/library_page.rb +61 -0
- data/app/views/admin/assets/_edit_metadata.html.haml +20 -0
- data/app/views/admin/assets/_edit_title.html.haml +31 -0
- data/app/views/admin/tags/_show_assets.html.haml +22 -0
- data/cucumber.yml +1 -0
- data/db/migrate/20091030115120_furniture.rb +9 -0
- data/features/support/env.rb +16 -0
- data/features/support/paths.rb +14 -0
- data/lib/library/library_tag.rb +15 -0
- data/lib/library/library_tags.rb +276 -0
- data/lib/library/link_renderer.rb +22 -0
- data/lib/library/more_asset_tags.rb +151 -0
- data/lib/library/more_tag_tags.rb +179 -0
- data/lib/library/site_controller.rb +33 -0
- data/lib/library/tagged_asset.rb +42 -0
- data/lib/tasks/library_extension_tasks.rake +28 -0
- data/library_extension.rb +24 -0
- data/spec/controllers/library_site_controller_spec.rb +97 -0
- data/spec/datasets/library_pages_dataset.rb +10 -0
- data/spec/datasets/library_sites_dataset.rb +9 -0
- data/spec/datasets/library_tags_dataset.rb +43 -0
- data/spec/models/library_page_spec.rb +47 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +36 -0
- metadata +128 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
module Library
|
2
|
+
module MoreTagTags
|
3
|
+
include Radiant::Taggable
|
4
|
+
|
5
|
+
class TagError < StandardError; end
|
6
|
+
|
7
|
+
############### overriding standard taggable links
|
8
|
+
|
9
|
+
desc %{
|
10
|
+
Summarises in a sentence the list of tags currently active, with each one presented as a defaceting link.
|
11
|
+
}
|
12
|
+
tag 'tags:unlink_list' do |tag|
|
13
|
+
requested = _get_requested_tags(tag)
|
14
|
+
if requested.any?
|
15
|
+
requested.map { |t|
|
16
|
+
tag.locals.tag = t
|
17
|
+
tag.render('tag:unlink', tag.attr.dup)
|
18
|
+
}.to_sentence
|
19
|
+
else
|
20
|
+
""
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc %{
|
25
|
+
Makes a link that removes the current tag from the active set. Other options as for tag:link.
|
26
|
+
|
27
|
+
*Usage:*
|
28
|
+
<pre><code><r:tag:unlink linkto='/library' /></code></pre>
|
29
|
+
}
|
30
|
+
tag 'tag:unlink' do |tag|
|
31
|
+
raise TagError, "tag must be defined for tag:unlink tag" unless tag.locals.tag
|
32
|
+
options = tag.attr.dup
|
33
|
+
options['class'] ||= 'detag'
|
34
|
+
anchor = options['anchor'] ? "##{options.delete('anchor')}" : ''
|
35
|
+
attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
|
36
|
+
attributes = " #{attributes}" unless attributes.empty?
|
37
|
+
text = tag.double? ? tag.expand : tag.render('tag:name')
|
38
|
+
|
39
|
+
if tag.locals.page.is_a?(LibraryPage)
|
40
|
+
href = tag.locals.page.url(tag.locals.page.requested_tags - [tag.locals.tag])
|
41
|
+
elsif page_url = (options.delete('tagpage') || Radiant::Config['tags.page'])
|
42
|
+
href = clean_url(page_url + '/-' + tag.locals.tag.clean_title)
|
43
|
+
else
|
44
|
+
href ||= Rack::Utils.escape("-#{tag.locals.tag.title}") + '/'
|
45
|
+
end
|
46
|
+
|
47
|
+
%{<a href="#{href}#{anchor}"#{attributes}>#{text}</a>}
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
############### asset-listing equivalents of existing page tags
|
52
|
+
# the main use for these tags is to pull related images and documents into pages
|
53
|
+
# in the same way as you would pull in related pages
|
54
|
+
|
55
|
+
desc %{
|
56
|
+
Lists all the assets associated with a set of tags, in descending order of relatedness.
|
57
|
+
|
58
|
+
*Usage:*
|
59
|
+
<pre><code><r:tags:assets:each>...</r:tags:assets:each></code></pre>
|
60
|
+
}
|
61
|
+
tag 'tags:assets' do |tag|
|
62
|
+
tag.expand
|
63
|
+
end
|
64
|
+
tag 'tags:assets:each' do |tag|
|
65
|
+
tag.locals.assets ||= _asset_finder(tag)
|
66
|
+
tag.render('asset_list', tag.attr.dup, &tag.block)
|
67
|
+
end
|
68
|
+
|
69
|
+
desc %{
|
70
|
+
Renders the contained elements only if there are any assets associated with the current set of tags.
|
71
|
+
|
72
|
+
*Usage:*
|
73
|
+
<pre><code><r:tags:if_assets>...</r:tags:if_assets></code></pre>
|
74
|
+
}
|
75
|
+
tag "tags:if_assets" do |tag|
|
76
|
+
tag.locals.assets = _assets_for_tags(tag.locals.tags)
|
77
|
+
tag.expand if tag.locals.assets.any?
|
78
|
+
end
|
79
|
+
|
80
|
+
desc %{
|
81
|
+
Renders the contained elements only if there are no assets associated with the current set of tags.
|
82
|
+
|
83
|
+
*Usage:*
|
84
|
+
<pre><code><r:tags:unless_assets>...</r:tags:unless_assets></code></pre>
|
85
|
+
}
|
86
|
+
tag "tags:unless_assets" do |tag|
|
87
|
+
tag.locals.assets = _assets_for_tags(tag.locals.tags)
|
88
|
+
tag.expand unless tag.locals.assets.any?
|
89
|
+
end
|
90
|
+
|
91
|
+
desc %{
|
92
|
+
Lists all the assets similar to this page (based on its tagging), in descending order of relatedness.
|
93
|
+
|
94
|
+
*Usage:*
|
95
|
+
<pre><code><r:related_assets:each>...</r:related_assets:each></code></pre>
|
96
|
+
}
|
97
|
+
tag 'related_assets' do |tag|
|
98
|
+
raise TagError, "page must be defined for related_assets tag" unless tag.locals.page
|
99
|
+
tag.locals.assets = Asset.not_furniture.from_tags(tag.locals.page.attached_tags)
|
100
|
+
tag.expand
|
101
|
+
end
|
102
|
+
tag 'related_assets:each' do |tag|
|
103
|
+
tag.render('asset_list', tag.attr.dup, &tag.block)
|
104
|
+
end
|
105
|
+
|
106
|
+
Asset.known_types.each do |type|
|
107
|
+
desc %{
|
108
|
+
Lists all the #{type} assets similar to this page (based on its tagging), in descending order of relatedness.
|
109
|
+
|
110
|
+
*Usage:*
|
111
|
+
<pre><code><r:related_#{type.to_s.pluralize}:each>...</r:related_#{type.to_s.pluralize}:each></code></pre>
|
112
|
+
}
|
113
|
+
tag "related_#{type.to_s.pluralize}" do |tag|
|
114
|
+
raise TagError, "page must be defined for related_#{type.to_s.pluralize} tag" unless tag.locals.page
|
115
|
+
tag.locals.assets = Asset.not_furniture.from_tags(tag.locals.page.attached_tags).send("#{type.to_s.pluralize}".intern)
|
116
|
+
tag.expand
|
117
|
+
end
|
118
|
+
tag "related_#{type.to_s.pluralize}:each" do |tag|
|
119
|
+
tag.render('asset_list', tag.attr.dup, &tag.block)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
############### tags: tags for displaying assets when we have a tag
|
124
|
+
# similar tags already exist for pages
|
125
|
+
|
126
|
+
desc %{
|
127
|
+
Loops through the assets to which the present tag has been applied
|
128
|
+
|
129
|
+
*Usage:*
|
130
|
+
<pre><code><r:tag:assets:each>...</r:tag:assets:each></code></pre>
|
131
|
+
}
|
132
|
+
tag 'tag:assets' do |tag|
|
133
|
+
raise TagError, "tag must be defined for tag:assets tag" unless tag.locals.tag
|
134
|
+
tag.locals.assets = tag.locals.tag.assets
|
135
|
+
tag.expand
|
136
|
+
end
|
137
|
+
tag 'tag:assets:each' do |tag|
|
138
|
+
tag.render('assets:each', tag.attr.dup, &tag.block)
|
139
|
+
end
|
140
|
+
|
141
|
+
desc %{
|
142
|
+
Renders the contained elements only if there are any assets associated with the current tag.
|
143
|
+
|
144
|
+
*Usage:*
|
145
|
+
<pre><code><r:tag:if_assets>...</r:tag:if_assets></code></pre>
|
146
|
+
}
|
147
|
+
tag "tag:if_assets" do |tag|
|
148
|
+
raise TagError, "tag must be defined for tag:if_assets tag" unless tag.locals.tag
|
149
|
+
tag.locals.assets = tag.locals.tag.assets
|
150
|
+
tag.expand if tag.locals.assets.any?
|
151
|
+
end
|
152
|
+
|
153
|
+
desc %{
|
154
|
+
Renders the contained elements only if there are no assets associated with the current tag.
|
155
|
+
|
156
|
+
*Usage:*
|
157
|
+
<pre><code><r:tag:unless_assets>...</r:tag:unless_assets></code></pre>
|
158
|
+
}
|
159
|
+
tag "tag:unless_assets" do |tag|
|
160
|
+
raise TagError, "tag must be defined for tag:unless_assets tag" unless tag.locals.tag
|
161
|
+
tag.locals.assets = tag.locals.tag.assets
|
162
|
+
tag.expand unless tag.locals.assets.any?
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def _asset_finder(tag)
|
170
|
+
if (tag.locals.tags)
|
171
|
+
Asset.from_all_tags(tag.locals.tags).not_furniture
|
172
|
+
else
|
173
|
+
Asset.not_furniture
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Library
|
2
|
+
module SiteController
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
|
6
|
+
base.class_eval {
|
7
|
+
|
8
|
+
def find_page_with_tags(url)
|
9
|
+
url = clean_url(url)
|
10
|
+
page = find_page_without_tags(url)
|
11
|
+
return page unless page.is_a?(LibraryPage)
|
12
|
+
page.add_request_tags(Tag.in_this_list(params[:tag])) if params[:tag]
|
13
|
+
raise LibraryPage::RedirectRequired, page.url unless page.url == url # to handle removal of tags and ensure consistent addressing. should also allow cache hit.
|
14
|
+
page
|
15
|
+
end
|
16
|
+
alias_method_chain :find_page, :tags
|
17
|
+
|
18
|
+
def show_page_with_tags
|
19
|
+
show_page_without_tags
|
20
|
+
rescue LibraryPage::RedirectRequired => e
|
21
|
+
redirect_to e.message
|
22
|
+
end
|
23
|
+
alias_method_chain :show_page, :tags
|
24
|
+
|
25
|
+
protected
|
26
|
+
def clean_url(url)
|
27
|
+
"/#{ url.strip }/".gsub(%r{//+}, '/')
|
28
|
+
end
|
29
|
+
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Library
|
2
|
+
module TaggedAsset
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
|
6
|
+
base.class_eval {
|
7
|
+
named_scope :furniture, {:conditions => 'assets.furniture = 1'}
|
8
|
+
named_scope :not_furniture, {:conditions => 'assets.furniture = 0 or assets.furniture is null'}
|
9
|
+
named_scope :newest_first, { :order => 'created_at DESC'} do
|
10
|
+
def paged (options={})
|
11
|
+
paginate({:per_page => 20, :page => 1}.merge(options))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
extend TaggablePage::ClassMethods
|
16
|
+
include TaggablePage::InstanceMethods
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
module InstanceMethods
|
24
|
+
|
25
|
+
# just keeping compatibility with page tags
|
26
|
+
# so as to present the same interface
|
27
|
+
|
28
|
+
def keywords
|
29
|
+
self.attached_tags.map {|t| t.title}.join(', ')
|
30
|
+
end
|
31
|
+
|
32
|
+
def keywords=(somewords="")
|
33
|
+
self.attached_tags = Tag.from_list(somewords)
|
34
|
+
end
|
35
|
+
|
36
|
+
def keywords_before_type_cast # called by form_helper
|
37
|
+
keywords
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
namespace :radiant do
|
2
|
+
namespace :extensions do
|
3
|
+
namespace :library do
|
4
|
+
|
5
|
+
desc "Runs the migration of the Library extension"
|
6
|
+
task :migrate => :environment do
|
7
|
+
require 'radiant/extension_migrator'
|
8
|
+
if ENV["VERSION"]
|
9
|
+
LibraryExtension.migrator.migrate(ENV["VERSION"].to_i)
|
10
|
+
else
|
11
|
+
LibraryExtension.migrator.migrate
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Copies public assets of the Library to the instance public/ directory."
|
16
|
+
task :update => :environment do
|
17
|
+
is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
|
18
|
+
puts "Copying assets from LibraryExtension"
|
19
|
+
Dir[LibraryExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
|
20
|
+
path = file.sub(LibraryExtension.root, '')
|
21
|
+
directory = File.dirname(path)
|
22
|
+
mkdir_p RAILS_ROOT + directory, :verbose => false
|
23
|
+
cp file, RAILS_ROOT + path, :verbose => false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_dependency 'application_controller'
|
2
|
+
|
3
|
+
class LibraryExtension < Radiant::Extension
|
4
|
+
version "2.0.0"
|
5
|
+
description "Combines paperclipped and taggable to create a general purpose faceted library"
|
6
|
+
url "http://github.com/spanner/radiant-library-extension"
|
7
|
+
|
8
|
+
def activate
|
9
|
+
Asset.send :is_taggable # make assets taggable
|
10
|
+
Asset.send :include, Library::TaggedAsset # add a keywords method for likeness with pages
|
11
|
+
Tag.send :include, Library::LibraryTag # adds assets and asset-type methods to Tag
|
12
|
+
LibraryPage # page type that reads tags/from/url and prepares paginated lists of matching pages and assets
|
13
|
+
SiteController.send :include, Library::SiteController # intervene to catch tag[]= parameters too
|
14
|
+
Page.send :include, Library::MoreAssetTags # defines a few more r:assets:* radius tags
|
15
|
+
Page.send :include, Library::MoreTagTags # defines a few more r:tag:* radius tags
|
16
|
+
|
17
|
+
admin.tag.show.add :main, "/admin/tags/show_assets", :after => "show_pages"
|
18
|
+
end
|
19
|
+
|
20
|
+
def deactivate
|
21
|
+
admin.tabs.remove "Library" unless respond_to?(:tab)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
Radiant::Config['reader.layout'] = 'Main'
|
3
|
+
|
4
|
+
describe SiteController do
|
5
|
+
dataset :library_pages
|
6
|
+
|
7
|
+
describe "on get to a library page" do
|
8
|
+
before do
|
9
|
+
get :show_page, :url => '/library/'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should render the tag page" do
|
13
|
+
response.should be_success
|
14
|
+
response.body.should == 'Shhhhh. body.'
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "with tags in child position" do
|
18
|
+
before do
|
19
|
+
get :show_page, :url => '/library/colourless/green/'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should still render the tag page" do
|
23
|
+
response.should be_success
|
24
|
+
response.body.should == 'Shhhhh. body.'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "with tags in child position and missing final /" do
|
29
|
+
before do
|
30
|
+
get :show_page, :url => '/library/colourless/green'
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should still render the tag page" do
|
34
|
+
response.should be_success
|
35
|
+
response.body.should == 'Shhhhh. body.'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "with tag negation" do
|
40
|
+
before do
|
41
|
+
get :show_page, :url => '/library/colourless/green/-colourless'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should redirect to the reduced address" do
|
45
|
+
response.should be_redirect
|
46
|
+
response.should redirect_to('http://test.host/library/green/')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "caching" do
|
52
|
+
describe "without tags requested" do
|
53
|
+
it "should add a default Cache-Control header with public and max-age of 5 minutes" do
|
54
|
+
get :show_page, :url => '/library/'
|
55
|
+
response.headers['Cache-Control'].should =~ /public/
|
56
|
+
response.headers['Cache-Control'].should =~ /max-age=300/
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should pass along the etag set by the page" do
|
60
|
+
get :show_page, :url => '/library/'
|
61
|
+
response.headers['ETag'].should be
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return a not-modified response when the sent etag matches" do
|
65
|
+
response.stub!(:etag).and_return("foobar")
|
66
|
+
request.if_none_match = 'foobar'
|
67
|
+
get :show_page, :url => '/library/'
|
68
|
+
response.response_code.should == 304
|
69
|
+
response.body.should be_blank
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "with tags requested" do
|
74
|
+
it "should add a default Cache-Control header with public and max-age of 5 minutes" do
|
75
|
+
get :show_page, :url => '/library/green/furiously'
|
76
|
+
response.headers['Cache-Control'].should =~ /public/
|
77
|
+
response.headers['Cache-Control'].should =~ /max-age=300/
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should pass along the etag set by the page" do
|
81
|
+
get :show_page, :url => '/library/green/furiously'
|
82
|
+
response.headers['ETag'].should be
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return a not-modified response when the sent etag matches" do
|
86
|
+
response.stub!(:etag).and_return("foobar")
|
87
|
+
request.if_none_match = 'foobar'
|
88
|
+
get :show_page, :url => '/library/green/furiously'
|
89
|
+
response.response_code.should == 304
|
90
|
+
response.body.should be_blank
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class LibrarySitesDataset < Dataset::Base
|
2
|
+
|
3
|
+
def load
|
4
|
+
create_record Site, :mysite, :name => 'My Site', :domain => 'mysite.domain.com', :base_domain => 'mysite.domain.com', :position => 1
|
5
|
+
create_record Site, :yoursite, :name => 'Your Site', :domain => '^yoursite', :base_domain => 'yoursite.test.com', :position => 2
|
6
|
+
create_record Site, :test, :name => 'Test host', :domain => '^test\.', :base_domain => 'test.host', :position => 3
|
7
|
+
Page.current_site = sites(:test) if defined? Site
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
class LibraryTagsDataset < Dataset::Base
|
4
|
+
datasets = [:pages]
|
5
|
+
datasets << :library_sites if defined? Site
|
6
|
+
uses *datasets
|
7
|
+
|
8
|
+
def load
|
9
|
+
create_tag "colourless"
|
10
|
+
create_tag "green"
|
11
|
+
create_tag "ideas"
|
12
|
+
create_tag "sleep"
|
13
|
+
create_tag "furiously"
|
14
|
+
|
15
|
+
apply_tag :colourless, pages(:first)
|
16
|
+
apply_tag :ideas, pages(:first), pages(:another), pages(:grandchild)
|
17
|
+
apply_tag :sleep, pages(:first)
|
18
|
+
apply_tag :furiously, pages(:first)
|
19
|
+
end
|
20
|
+
|
21
|
+
helpers do
|
22
|
+
def create_tag(title, attributes={})
|
23
|
+
attributes = tag_attributes(attributes.update(:title => title))
|
24
|
+
tag = create_model Tag, title.symbolize, attributes
|
25
|
+
end
|
26
|
+
|
27
|
+
def tag_attributes(attributes={})
|
28
|
+
title = attributes[:name] || "Tag"
|
29
|
+
attributes = {
|
30
|
+
:title => title
|
31
|
+
}.merge(attributes)
|
32
|
+
attributes[:site] = sites(:test) if defined? Site
|
33
|
+
attributes
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply_tag(tag, *items)
|
37
|
+
tag = tag.is_a?(Tag) ? tag : tags(tag)
|
38
|
+
items.each { |i| i.attached_tags << tag }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|