radiant-library-extension 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 @@
1
+ require 'digest/sha1'
2
+
3
+ class LibraryPagesDataset < Dataset::Base
4
+ uses :library_tags
5
+
6
+ def load
7
+ create_page "library", :slug => "library", :class_name => 'LibraryPage', :body => 'Shhhhh.'
8
+ end
9
+
10
+ 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