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.
@@ -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