radiant-taggable-extension 1.2.5 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,34 +1,35 @@
1
1
  # Taggable
2
2
 
3
- This is another way to apply tags to objects in your radiant site and retrieve objects by tag. If you're looking at this you will also want to look at the [tags](http://github.com/jomz/radiant-tags-extension/tree) extension, which does a good job of tag-clouding and may be all you need, and at our [library](https://github.com/spanner/radiant-library-extension) which uses this functionality to make an image gallery and document library and may be a useful starting point for other extensions.
3
+ This is another way to apply tags to objects in your radiant site and retrieve objects by tag. If you're looking at this you will also want to look at the [tags](http://github.com/jomz/radiant-tags-extension/tree) extension, which does a good job of tag-clouding and may be all you need.
4
+
5
+ Taggable now includes what was previously the `library` extension so it handles radiant assets as well as pages and provides radius tags to support a faceted search.
4
6
 
5
7
  ## Why?
6
8
 
7
9
  This extension differs from `tags` in a few ways that matter to me but may not to you:
8
10
 
9
11
  * We're not so focused on tag clouds - though you can still make them - but more on archival and linking functions.
10
- * We subvert the keywords mechanism on pages rather than adding another one.
12
+ * It provides faceted search of any tagged class.
13
+ * We subvert the keywords mechanism on pages rather than adding another one. I may change this soon to play more nicely with page fields.
11
14
  * The tag-choosing and tag-removal interface is (about to be) quite nice.
12
15
  * It's editorially versatile: tags can be used as page pointers and their visibility is controllable
13
- * Anything can be tagged. By default we only do pages but other extensions can participate with a single line in a model class. See the [taggable_events](https://github.com/spanner/radiant-taggable_events-extension) extension for a minimal example or just put `is_taggable` at the top of a model class and see what happens.
16
+ * Anything can be tagged. By default we only do pages but other extensions can participate with a single line in a model class. See the [taggable_events](https://github.com/spanner/radiant-taggable_events-extension) extension for a minimal example or just put `has_tags` at the top of a model class.
14
17
  * We don't use `has_many_polymorphs` (it burns!)
15
18
  * Or any of the tagging libraries: it only takes a few scopes
16
- * it's multi-site compatible: if our fork is installed then you get site-scoped tags and taggings.
17
19
 
18
20
  When you first install the extension you shouldn't see much difference: all we do out of the box is to take over (and make more prominent) the keywords field in the page-edit view.
19
21
 
20
22
  ## New
21
23
 
22
- The long-promised tag-suggester is there in a useable though slightly basic form. I would prefer it to display a list rather than populating
23
- the text box, but we're getting there. Migration is required for the metaphone support.
24
+ * API change: `has_tags` instead of `is_taggable`.
24
25
 
25
- I'm refactoring, or at least tidying up, the great clutter of tag tags. There's a lot more reuse in there now but the documentation may be out of date here and there. Bug reports very welcome.
26
+ * The library extension has been reincorporated into taggable, since radiant 1 has assets. Out of the box you get tags, clouds and faceting of both pages and assets. It should work with 0.9.1 too but you will need paperclipped.
26
27
 
27
- I've stripped out quite a lot of display clutter in order to focus on the basic tagging mechanism here. Retrieval and display is now handled by the [library](http://example.com/) extension. The core radius tags remain here. Anything that used to refer to a tag page is probably now handled by the library page.
28
+ * The long-promised tag-suggester is there in a useable though slightly basic form.
28
29
 
29
30
  ## Status
30
31
 
31
- The underlying code is fairly well broken-in and has been in production for a couple of years, but I've rearranged it quite drastically and the interface is all new. There are tests now: not detailed but with reasonable coverage. Silly mistakes are getting less likely.
32
+ Apart from any crumpling caused by the recent reincorporation of Library this is all mature code that has been in use for years.
32
33
 
33
34
  ## Efficiency
34
35
 
@@ -44,23 +45,32 @@ is handled in a single pass.
44
45
 
45
46
  The exception is the `r:tag_cloud` tag: there we have to gather a list of descendant pages first. It's done in a fairly frugal way (by generation rather than individual) but still likely to involve several preparatory queries as well as the cloud computation.
46
47
 
48
+ ## Library pages
49
+
50
+ The **LibraryPage** page type is a handy cache-friendly way of catching tag parameters and displaying lists of related items: any path following the address of the page is taken as a slash-separated list of tags, so with a tag page at /archive you can call addresses like:
51
+
52
+ /archive/lasagne/chips/pudding
53
+
54
+ and the right tags will be retrieved, if they exist.
55
+
47
56
  ## Radius tags
48
57
 
49
- This extension creates several radius tags. There are two kinds:
58
+ This extension creates a great many radius tags. There are several kinds:
50
59
 
51
- ### presenting tag information
60
+ ### Tag information
52
61
 
53
62
  are used in the usual to display the properties and associations of a given tag (which can be supplied to a library as a query parameter or just specified in the radius tag)
54
63
 
55
64
  <r:tag:title />
56
65
  <r:tag:description />
57
66
  <r:tag:pages:each>...</r:tag:pages:each>
67
+ <r:tags:each >...</r:tags:each>
58
68
 
59
69
  currently only available in a tag cloud (or a `top_tags` list):
60
70
 
61
71
  <r:tag:use_count />
62
72
 
63
- ### presenting page information
73
+ ### Page tags and tag pages
64
74
 
65
75
  These display the tag-associations of a given page.
66
76
 
@@ -71,7 +81,46 @@ These display the tag-associations of a given page.
71
81
  <r:tag_cloud [url=""] />
72
82
 
73
83
  The library extension adds a lot more ways to retrieve lists of tags and tagged objects, and to work with assets in the same way as we do here with pages.
84
+
85
+ ### Tag assets and asset tags
86
+
87
+ All the page tags have asset equivalents:
88
+
89
+ <r:tags:assets:each tags="foo, bar">...</r:tags:assets:each>
90
+ <r:related_assets:each>...</r:related_assets:each>
91
+
92
+ and for any `*asset*` tag you can substitute an asset type, so this also works:
93
+
94
+ <r:related_images:each>...</r:related_images:each>
95
+
96
+ Within the each loops you can use all the usual page and asset tags.
97
+
98
+ ### Library page tags
99
+
100
+ The library tags focus on two tasks: choosing a set of tags and displaying a set of matching objects.
101
+
102
+ <r:library:tags />
103
+ <r:library:tags:each>...</r:library:tags:each>
104
+
105
+ Displays a list of the tags available. If any tags have been requested, this will show the list of coincident tags (that can be used to limit the result set further). If not it shows all the available tags. If a `for` attribute is set:
106
+
107
+ <r:library:tags for="images" />
108
+ <r:library:tags for="pages" />
109
+
110
+ Then we show only the set of tags attached to any object of that kind.
111
+
112
+ <r:library:requested_tags />
113
+ <r:library:requested_tags:each>...</r:library:requested_tags:each>
74
114
 
115
+ Displays the currently-limiting set of tags.
116
+
117
+ <r:library:pages:each>...</r:library:pages:each>
118
+ <r:library:assets:each>...</r:library:assets:each>
119
+ <r:library:images:each>...</r:library:images:each>
120
+ <r:library:videos:each>...</r:library:videos:each>
121
+
122
+ Display the list of (that kind of) objects associated with the current tag set.
123
+
75
124
  ## Note about tag cloud prominence
76
125
 
77
126
  The calculation of prominence here applies a logarithmic curve to create a more even distribution of weight. It's continuous rather than banded, and sets the font size and opacity for each tag in a style attribute.
@@ -100,16 +149,31 @@ Include the sample tagcloud.css in your styles and put this somewhere in the pag
100
149
  <r:tag_cloud />
101
150
 
102
151
  Seek venture capital immediately.
152
+
153
+ ### To display a faceted image browser on a library page:
154
+
155
+ <r:library:if_requested_tags>
156
+ <p>Displaying pictures tagged with <r:library:requested_tags /></p>
157
+ </r:library:if_requested_tags>
158
+
159
+ <r:library:images:each paginated="true" per_page="20">
160
+ <r:assets:link size="full"><r:assets:image size="small" /></r:assets:link>
161
+ </r:library:images:each>
162
+
163
+ <r:library:tags for="images" />
103
164
 
104
- ## Next steps
165
+ ### To automate the illustration of a page based on tag-overlap:
105
166
 
106
- * auto-completer to improve tagging consistency.
167
+ <r:related_images:each limit="3">
168
+ <r:assets:image size="standard" />
169
+ <p class="caption"><r:assets:caption /></p>
170
+ </r:related_images:each>
107
171
 
108
172
  ## Requirements
109
173
 
110
- * Radiant 0.8.1
174
+ * Radiant 1 or radiant 0.9.x with the paperclipped extension.
111
175
 
112
- This is no longer compatible with 0.7 because we're doing a lot of :having in the scopes and you need rails 2.3 for that.
176
+ Radiant 1 is strongly recommended.
113
177
 
114
178
  ## Installation
115
179
 
@@ -123,10 +187,10 @@ The update task will bring over a couple of CSS files for styling tags but you'l
123
187
 
124
188
  ## Bugs
125
189
 
126
- Very likely. [Github issues](http://github.com/spanner/radiant-taggable-extension/issues), please, or for little things an email or github message is fine.
190
+ Quite likely. [Github issues](http://github.com/spanner/radiant-taggable-extension/issues), please, or for little things an email or github message is fine.
127
191
 
128
192
  ## Author and copyright
129
193
 
130
194
  * William Ross, for spanner. will at spanner.org
131
- * Copyright 2008-2010 spanner ltd
195
+ * Copyright 2008-2011 spanner ltd
132
196
  * released under the same terms as Rails and/or Radiant
data/Rakefile CHANGED
@@ -1,18 +1,3 @@
1
- begin
2
- require 'jeweler'
3
- Jeweler::Tasks.new do |gem|
4
- gem.name = "radiant-taggable-extension"
5
- gem.summary = %Q{Taggable Extension for Radiant CMS}
6
- gem.description = %Q{General purpose tagging extension: more versatile but less focused than the tags extension}
7
- gem.email = "will@spanner.org"
8
- gem.homepage = "http://github.com/spanner/radiant-taggable-extension"
9
- gem.authors = ["spanner"]
10
- gem.add_dependency "radiant", ">= 0.9.0"
11
- end
12
- rescue LoadError
13
- puts "Jeweler (or a dependency) not available. This is only required if you plan to package taggable as a gem."
14
- end
15
-
16
1
  # In rails 1.2, plugins aren't available in the path until they're loaded.
17
2
  # Check to see if the rspec plugin is installed first and require
18
3
  # it if it is. If not, use the gem version.
@@ -1,5 +1,23 @@
1
1
  module TaggableHelper
2
2
 
3
+ def clean_html(text)
4
+ Sanitize.clean(text, Sanitize::Config::RELAXED)
5
+ end
6
+
7
+ def strip_html(text)
8
+ Sanitize.clean(text)
9
+ end
10
+
11
+ def truncate_words(text='', options={})
12
+ return '' if text.blank?
13
+ ellipsis = options[:ellipsis] || '&hellip;'
14
+ limit = (options[:limit] || 64).to_i
15
+ text = strip_html(text) if options[:strip]
16
+ words = text.split
17
+ ellipsis = '' unless words.size > limit
18
+ words[0..(limit-1)].join(" ") + ellipsis
19
+ end
20
+
3
21
  def available_pointer_pages()
4
22
  root = Page.respond_to?(:homepage) ? Page.homepage : Page.find_by_parent_id(nil)
5
23
  options = pointer_option_branch(root)
@@ -0,0 +1,60 @@
1
+ class LibraryPage < Page
2
+ include WillPaginate::ViewHelpers
3
+
4
+ class RedirectRequired < StandardError
5
+ def initialize(message = nil); super end
6
+ end
7
+
8
+ 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
+ end
data/app/models/tag.rb CHANGED
@@ -129,12 +129,26 @@ class Tag < ActiveRecord::Base
129
129
  taggings.map {|t| t.tagged}
130
130
  end
131
131
 
132
- # Returns a list of all the page tagged with this tag.
132
+ # Returns a list of all the pages tagged with this tag.
133
133
 
134
134
  def pages
135
135
  Page.from_tags([self])
136
136
  end
137
137
 
138
+ # Returns a list of all the assets tagged with this tag.
139
+
140
+ def assets
141
+ Asset.from_tags([self])
142
+ end
143
+
144
+ # Returns a list of all the assets of a particular type tagged with this tag.
145
+
146
+ Asset.known_types.each do |type|
147
+ define_method type.to_s.pluralize.intern do
148
+ Asset.send("#{type.to_s.pluralize}".intern).from_tags([self])
149
+ end
150
+ end
151
+
138
152
  # Returns a list of all the tags that have been applied alongside this one.
139
153
 
140
154
  def coincident_tags
@@ -244,10 +258,13 @@ class Tag < ActiveRecord::Base
244
258
  # adds retrieval methods for a taggable class to this class and to Tagging.
245
259
 
246
260
  def self.define_retrieval_methods(classname)
261
+ define_method "#{classname.downcase}_taggings".to_sym do
262
+ self.taggings.of_a(classname)
263
+ end
247
264
  define_method classname.downcase.pluralize.to_sym do
248
265
  classname.constantize.send :from_tag, self
249
266
  end
250
- end
267
+ end
251
268
 
252
269
  protected
253
270
 
@@ -0,0 +1,20 @@
1
+ - fields_for :asset, @asset do |fields|
2
+ #extended-metadata{ :class => "row", :style => "display: none" }
3
+ %table.fieldset{ :cellpadding => "0", :cellspacing => "0", :border => "0" }
4
+ %tr
5
+ %td.label
6
+ = fields.label :caption
7
+ %td.field
8
+ = fields.text_field :caption, :class => 'textbox', :maxlength => 255
9
+ -if @asset.new_record? || @asset.image?
10
+ %tr
11
+ %td.label
12
+ Display
13
+ %td.field{:style => 'text-align: left;'}
14
+ = fields.check_box :furniture
15
+ = fields.label :furniture, "Site furniture? (not shown in lists and galleries)"
16
+ = render_region :extended_metadata
17
+ %p
18
+ %small
19
+ %a{ :id => "more-extended-metadata", :href => "#", :onclick => "#{toggle_javascript_for('extended-metadata')}; return false;" } More
20
+ %a{ :id => "less-extended-metadata", :href => "#", :onclick => "#{toggle_javascript_for('extended-metadata')}; return false;", :style => "display: none" } Less
@@ -0,0 +1,14 @@
1
+ - include_stylesheet 'admin/taggable'
2
+ - include_javascript 'autocomplete'
3
+ - include_javascript 'admin/taggable'
4
+
5
+ - fields_for :asset, @asset do |fields|
6
+ %p.keywords
7
+ %label{:for=>"asset_keywords"}
8
+ Tags
9
+ = fields.text_field :keywords, :class => 'textbox tagger'
10
+
11
+ %p.title
12
+ %label{:for=>"asset_title"}
13
+ Title
14
+ = fields.text_field :title, :class => 'textbox', :maxlength => 100
@@ -0,0 +1,22 @@
1
+ - include_stylesheet 'admin/assets'
2
+
3
+ - asset_taggings = @tag.taggings.of_a :asset
4
+ - if asset_taggings.any?
5
+ %h2
6
+ Tagged assets
7
+ %table.index
8
+ - asset_taggings.each do |tagging|
9
+ - asset = tagging.tagged
10
+ - if asset
11
+ - dom_id = "tagging_#{tagging.id}"
12
+ %tr{:id => dom_id}
13
+ %td.asset{:style => 'width: 48px'}
14
+ = link_to image_tag(asset.thumbnail(:icon)), edit_admin_asset_path(asset), :class => 'icon'
15
+ %td.asset-title
16
+ = link_to asset.title, edit_admin_asset_path(asset)
17
+ %td.actions
18
+ = link_to_remote image('minus') + ' detach', |
19
+ :html => { :class => "action", :title => "Detach tag from asset" }, |
20
+ :url => admin_tagging_url(tagging), :method => :delete, |
21
+ :after => "Effect.Fade('#{dom_id}', { duration: 0.5 })", |
22
+ :complete => "Element.remove('#{dom_id}');"
@@ -0,0 +1,22 @@
1
+ # This handy simplification is adapted from SphinxSearch (thanks)
2
+ # and originally came from Ultrasphinx
3
+ # it saves us a lot of including and bodging to make will_paginate's template calls work in the Page model
4
+
5
+ module Library
6
+ class LinkRenderer < WillPaginate::LinkRenderer
7
+ def initialize(tag)
8
+ @tag = tag
9
+ end
10
+
11
+ def page_link(page, text, attributes = {})
12
+ linkclass = %{ class="#{attributes[:class]}"} if attributes[:class]
13
+ linkrel = %{ rel="#{attributes[:rel]}"} if attributes[:rel]
14
+ %Q{<a href="#{@tag.locals.page.url}?page=#{page}"#{linkrel}#{linkclass}>#{text}</a>}
15
+ end
16
+
17
+ def page_span(page, text, attributes = {})
18
+ spanclass = attributes[:class]
19
+ %{<span class="#{attributes[:class]}">#{text}</span>}
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ module RadiantTaggableExtension
2
+ VERSION = '2.0.0.rc1'
3
+ SUMMARY = %q{Tagging, clouding and faceting extension for Radiant CMS}
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
+ URL = "http://spanner.org/radiant/taggable"
6
+ AUTHORS = ["William Ross"]
7
+ EMAIL = ["radiant@spanner.org"]
8
+ end
@@ -0,0 +1,151 @@
1
+ module Radius
2
+ module AssetTags
3
+ include Radiant::Taggable
4
+
5
+ class TagError < StandardError; end
6
+
7
+ %w{tags tags:each copyright if_copyright unless_copyright minigallery illustration}.each do |name|
8
+ deprecated_tag "assets:#{name}", :substitute => "asset:#{name}", :deadline => '2.0'
9
+ end
10
+
11
+
12
+ ############### assets: tags for displaying tags and relatives when we have an asset
13
+ # similar tags already exist for pages
14
+
15
+ desc %{
16
+ Cycles through all tags attached to present asset.
17
+
18
+ *Usage:*
19
+ <pre><code><r:assets:tags><r:tag:title /></r:assets:tags></code></pre>
20
+ }
21
+ tag 'asset:tags' do |tag|
22
+ raise TagError, "asset must be defined for asset:tags tag" unless tag.locals.asset
23
+ tag.locals.tags = tag.locals.asset.tags
24
+ tag.expand
25
+ end
26
+ tag 'asset:tags:each' do |tag|
27
+ tag.render('tags:each', tag.attr.dup, &tag.block)
28
+ end
29
+
30
+ desc %{
31
+ Lists all the assets similar to this asset (based on its tagging), in descending order of relatedness.
32
+
33
+ *Usage:*
34
+ <pre><code><r:related_assets:each>...</r:related_assets:each></code></pre>
35
+ }
36
+ tag 'related_assets' do |tag|
37
+ raise TagError, "asset must be defined for related_assets tag" unless tag.locals.asset
38
+ tag.locals.assets = tag.locals.asset.related_assets
39
+ tag.expand
40
+ end
41
+ tag 'related_assets:each' do |tag|
42
+ tag.render('assets:each', tag.attr.dup, &tag.block)
43
+ end
44
+
45
+ ############### extra asset fields
46
+
47
+ desc %{
48
+ Expands if the asset has a copyright notice.
49
+ }
50
+ tag "asset:if_copyright" do |tag|
51
+ options = tag.attr.dup
52
+ asset = find_asset(tag, options)
53
+ tag.expand if asset.respond_to?(method) && asset.send(method)
54
+ end
55
+
56
+ desc %{
57
+ Expands unless the asset has a copyright notice.
58
+ }
59
+ tag "asset:unless_copyright" do |tag|
60
+ options = tag.attr.dup
61
+ asset = find_asset(tag, options)
62
+ tag.expand unless asset.respond_to?(method) && asset.send(method)
63
+ end
64
+
65
+ desc %{
66
+ Renders the 'copyright' attribute of the asset.
67
+ }
68
+ tag "asset:copyright" do |tag|
69
+ options = tag.attr.dup
70
+ asset = find_asset(tag, options)
71
+ asset.send(method) rescue nil
72
+ end
73
+
74
+ ############### useful shorthands
75
+
76
+ desc %{
77
+ Renders an illustration block for the asset, with image and caption.
78
+
79
+ *Usage:*
80
+ <pre><code><r:assets:image [title="asset_title"] [size="icon|thumbnail"]></code></pre>
81
+ }
82
+ tag "asset:illustration" do |tag|
83
+ options = tag.attr.dup
84
+ options['size'] ||= 'illustration'
85
+ tag.locals.asset = find_asset(tag, options)
86
+ if tag.locals.asset.image?
87
+ result = %{<div class="illustration">}
88
+ result << tag.render('assets:image', options)
89
+ result << %{<p class="caption">#{tag.render('assets:caption', options)}</p>}
90
+ result << "</div>"
91
+ result
92
+ end
93
+ end
94
+
95
+ desc %{
96
+ Presents a standard marginal gallery block suitable for turning unobtrusively into a rollover or lightbox gallery.
97
+ We need to be able to work out a collection of assets: that can be defined already (eg by assets:all) or come from the current page.
98
+ Default preview size is 'large' and thumbnail size 'thumbnail' but you can specify any of your asset sizes.
99
+
100
+ *Usage:*
101
+ <pre><code>
102
+ <r:assets:images>
103
+ <r:assets:minigallery [size="..."] [thumbnail_size="..."] [tags="one,or,more,tags"] />
104
+ </r:assets:images>
105
+ </code></pre>
106
+
107
+ }
108
+ tag 'asset:minigallery' do |tag|
109
+ options = tag.attr.dup.symbolize_keys
110
+ raise TagError, "asset collection must be available for assets:minigallery tag" unless tag.locals.assets or tag.locals.page or tag.attr[:tags]
111
+ if options[:tags] && tags = Tag.from_list(options[:tags])
112
+ tag.locals.assets = Asset.images.from_all_tags(tags)
113
+ else
114
+ tag.locals.assets = tag.locals.page.assets
115
+ end
116
+ tag.locals.assets.images.to_a # because we can't let empty? trigger a call to count
117
+
118
+ unless tag.locals.assets.empty?
119
+ size = tag.attr['size'] || 'illustration'
120
+ thumbsize = tag.attr['thumbnail_size'] || 'icon'
121
+ result = ""
122
+ result << %{
123
+ <div class="minigallery">}
124
+ tag.locals.asset = tag.locals.assets.first
125
+ result << tag.render('assets:image', {'size' => size})
126
+ result << %{
127
+ <p class="caption">#{tag.render('assets:caption')}</p>
128
+ <ul class="thumbnails">}
129
+ if tag.locals.assets.size > 1
130
+ tag.locals.assets.each do |asset|
131
+ tag.locals.asset = asset
132
+ result << %{
133
+ <li class="thumbnail">
134
+ <a href="#{tag.render('assets:url', 'size' => 'illustration')}" title="#{asset.caption}" id="thumbnail_#{asset.id}">
135
+ }
136
+ result << tag.render('assets:image', {'size' => thumbsize, 'alt' => asset.title})
137
+ result << %{
138
+ </a>
139
+ </li>}
140
+ end
141
+ end
142
+ result << %{
143
+ </ul>
144
+ </div>}
145
+ result
146
+ end
147
+ end
148
+
149
+ end
150
+ end
151
+