radiant-taggable-extension 1.2.3 → 1.2.4

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 CHANGED
@@ -7,7 +7,9 @@ This is another way to apply tags to objects in your radiant site and retrieve o
7
7
  This extension differs from `tags` in a few ways that matter to me but may not to you:
8
8
 
9
9
  * We're not so focused on tag clouds - though you can still make them - but more on archival and linking functions.
10
- * We replace the keywords mechanism on pages rather than adding another one.
10
+ * We subvert the keywords mechanism on pages rather than adding another one.
11
+ * The tag-choosing and tag-removal interface is (about to be) quite nice.
12
+ * It's editorially versatile: tags can be used as page pointers and their visibility is controllable
11
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.
12
14
  * We don't use `has_many_polymorphs` (it burns!)
13
15
  * Or any of the tagging libraries: it only takes a few scopes
@@ -17,7 +19,12 @@ When you first install the extension you shouldn't see much difference: all we d
17
19
 
18
20
  ## New
19
21
 
20
- I've just 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.
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
+
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
+
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.
21
28
 
22
29
  ## Status
23
30
 
@@ -27,10 +34,11 @@ The underlying code is fairly well broken-in and has been in production for a co
27
34
 
28
35
  Not too bad, I think. Most of the heavy retrieval functions have been squashed down into single queries. Each of these:
29
36
 
30
- Tag.most_popular(50)
31
- Tag.coincident_with(tag1, tag2, tag3)
32
- Page.tagged_with(tag1, tag2, tag3)
33
- Page.related_pages # equivalent to Page.tagged_with(self.attached_tags) - [self]
37
+ Tag.most_popular(50)
38
+ Tag.coincident_with(tag1, tag2, tag3)
39
+ Page.tagged_with(tag1, tag2, tag3)
40
+ Page.related_pages # equivalent to Page.tagged_with(self.attached_tags) - [self]
41
+ Tag.suggested_by('stem')
34
42
 
35
43
  is handled in a single pass.
36
44
 
@@ -44,26 +52,26 @@ This extension creates several radius tags. There are two kinds:
44
52
 
45
53
  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)
46
54
 
47
- <r:tag:title />
48
- <r:tag:description />
49
- <r:tag:pages:each>...</r:tag:pages:each>
55
+ <r:tag:title />
56
+ <r:tag:description />
57
+ <r:tag:pages:each>...</r:tag:pages:each>
50
58
 
51
59
  currently only available in a tag cloud (or a `top_tags` list):
52
60
 
53
- <r:tag:use_count />
61
+ <r:tag:use_count />
54
62
 
55
63
  ### presenting page information
56
64
 
57
65
  These display the tag-associations of a given page.
58
66
 
59
- <r:if_tags>...</r:if_tags>
60
- <r:unless_tags>...</r:unless_tags>
61
- <r:tags:each>...</r:tags:each>
62
- <r:related_pages:each>...</r:related_pages:each>
63
- <r:tag_cloud [url=""] />
67
+ <r:if_tags>...</r:if_tags>
68
+ <r:unless_tags>...</r:unless_tags>
69
+ <r:tags:each>...</r:tags:each>
70
+ <r:related_pages:each>...</r:related_pages:each>
71
+ <r:tag_cloud [url=""] />
64
72
 
65
73
  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.
66
-
74
+
67
75
  ## Note about tag cloud prominence
68
76
 
69
77
  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.
@@ -76,27 +84,27 @@ Add tags to your pages by putting a comma-separated list in the 'keywords' box.
76
84
 
77
85
  Put this in your layout:
78
86
 
79
- <r:if_tags>
80
- <h3>See also</h3>
81
- <ul>
82
- <r:related_pages.each>
83
- <li><r:link /></li>
84
- </r:related_pages.each>
85
- </ul>
86
- </r:if_tags>
87
+ <r:if_tags>
88
+ <h3>See also</h3>
89
+ <ul>
90
+ <r:related_pages.each>
91
+ <li><r:link /></li>
92
+ </r:related_pages.each>
93
+ </ul>
94
+ </r:if_tags>
87
95
 
88
96
  ### To display a tag cloud on a section front page:
89
97
 
90
98
  Include the sample tagcloud.css in your styles and put this somewhere in the page or layout:
91
99
 
92
- <r:tag_cloud />
100
+ <r:tag_cloud />
93
101
 
94
102
  Seek venture capital immediately.
95
-
103
+
96
104
  ## Next steps
97
105
 
98
106
  * auto-completer to improve tagging consistency.
99
-
107
+
100
108
  ## Requirements
101
109
 
102
110
  * Radiant 0.8.1
@@ -107,9 +115,9 @@ This is no longer compatible with 0.7 because we're doing a lot of :having in th
107
115
 
108
116
  As usual:
109
117
 
110
- git clone git://github.com/spanner/radiant-taggable-extension.git vendor/extensions/taggable
111
- rake radiant:extensions:taggable:migrate
112
- rake radiant:extensions:taggable:update
118
+ git clone git://github.com/spanner/radiant-taggable-extension.git vendor/extensions/taggable
119
+ rake radiant:extensions:taggable:migrate
120
+ rake radiant:extensions:taggable:update
113
121
 
114
122
  The update task will bring over a couple of CSS files for styling tags but you'll want to improve those.
115
123
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.3
1
+ 1.2.4
@@ -1,8 +1,14 @@
1
1
  class Admin::TagsController < Admin::ResourceController
2
+ helper :taggable
2
3
 
3
- def index
4
- @tags = Tag.with_count.sort
5
- response_for :plural
4
+ def index
5
+ tags = params[:query] ? Tag.suggested_by(params[:query]) : Tag.with_count
6
+ @tags = tags.sort
7
+ respond_to do |wants|
8
+ wants.xml { render :xml => @tags }
9
+ wants.json { render :json => { 'query' => params[:query], 'suggestions' => @tags.map(&:title) } }
10
+ wants.any
11
+ end
6
12
  end
7
13
 
8
14
  def show
@@ -0,0 +1,21 @@
1
+ module TaggableHelper
2
+
3
+ def available_pointer_pages()
4
+ root = Page.respond_to?(:homepage) ? Page.homepage : Page.find_by_parent_id(nil)
5
+ options = pointer_option_branch(root)
6
+ options.unshift ['<none>', nil]
7
+ options
8
+ end
9
+
10
+ def pointer_option_branch(page, depth=0)
11
+ options = []
12
+ unless page.virtual? || page.sheet? || page.has_pointer?
13
+ options << ["#{". " * depth}#{h(page.title)}", page.id]
14
+ page.children.each do |child|
15
+ options += pointer_option_branch(child, depth + 1)
16
+ end
17
+ end
18
+ options
19
+ end
20
+
21
+ end
data/app/models/tag.rb CHANGED
@@ -1,12 +1,41 @@
1
+ require "text/metaphone"
2
+
1
3
  class Tag < ActiveRecord::Base
2
4
  attr_accessor :cloud_band, :cloud_size
3
5
 
4
6
  belongs_to :created_by, :class_name => 'User'
5
7
  belongs_to :updated_by, :class_name => 'User'
8
+ belongs_to :page
6
9
  has_many :taggings, :dependent => :destroy
7
- is_site_scoped if respond_to? :is_site_scoped
10
+ before_save :calculate_metaphone
11
+
8
12
  has_site if respond_to? :has_site
9
13
 
14
+ # returns the subset of tags meant for public display and selection
15
+
16
+ named_scope :visible, {
17
+ :conditions => ['visible = 1']
18
+ }
19
+
20
+ # returns the set of hidden editorial tags used for linking and labelling but
21
+ # not meant for public display.
22
+
23
+ named_scope :hidden, {
24
+ :conditions => ['visible = 0']
25
+ }
26
+
27
+ # returns the subset of structural tags (ie, those that are page links)
28
+
29
+ named_scope :structural, {
30
+ :conditions => ['page_id IS NOT NULL']
31
+ }
32
+
33
+ # returns the subset of tags without page links
34
+
35
+ named_scope :descriptive, {
36
+ :conditions => ['page_id IS NULL']
37
+ }
38
+
10
39
  # this is useful when we need to go back and add popularity to an already defined list of tags
11
40
 
12
41
  named_scope :in_this_list, lambda { |tags|
@@ -62,15 +91,32 @@ class Tag < ActiveRecord::Base
62
91
  :conditions => "tt.tagged_type = '#{klass}'",
63
92
  }
64
93
  }
94
+
95
+ # this should probably be sorted better but I want to keep it as quick an operation as possible
96
+ # so only the one query is allowed
97
+
98
+ named_scope :suggested_by, lambda { |term|
99
+ metaphone = Text::Metaphone.metaphone(term)
100
+ {
101
+ :conditions => ["tags.title LIKE ? OR tags.metaphone LIKE ?", "%#{term}%", "&#{metaphone}%"]
102
+ }
103
+ }
65
104
 
66
105
  def <=>(othertag)
67
- String.natcmp(self.title, othertag.title)
106
+ String.natcmp(self.title, othertag.title) # natural sort method defined in lib/natcomp.rb
68
107
  end
69
108
 
70
109
  def to_s
71
110
  title
72
111
  end
73
112
 
113
+ # returns true if this tag points to a page
114
+
115
+ def structural
116
+ !page_id.nil?
117
+ end
118
+ alias :structural? :structural
119
+
74
120
  # Standardises formatting of tag name in urls
75
121
 
76
122
  def clean_title
@@ -83,6 +129,8 @@ class Tag < ActiveRecord::Base
83
129
  taggings.map {|t| t.tagged}
84
130
  end
85
131
 
132
+ # Returns a list of all the page tagged with this tag.
133
+
86
134
  def pages
87
135
  Page.from_tags([self])
88
136
  end
@@ -100,7 +148,7 @@ class Tag < ActiveRecord::Base
100
148
  # Returns a list of all the tags that have been applied alongside _all_ of the supplied tags.
101
149
  # used to offer reductive facets on library pages
102
150
  # not very efficient at the moment, largely thanks to polymorphic tagging relationship
103
- # TODO: omit tags with no reductive power (ie applied to all the tagged items)
151
+ # TODO: omit tags with no reductive power (ie those applied to all the tagged items)
104
152
 
105
153
  def self.coincident_with(tags)
106
154
  related_tags = []
@@ -155,6 +203,7 @@ class Tag < ActiveRecord::Base
155
203
  end
156
204
  end
157
205
 
206
+ # applies a more sophisticated logarithmic weighting algorithm to a set of tags.
158
207
  # derived from here:
159
208
  # http://stackoverflow.com/questions/604953/what-is-the-correct-algorthm-for-a-logarthmic-distribution-curve-between-two-poin
160
209
 
@@ -181,6 +230,7 @@ class Tag < ActiveRecord::Base
181
230
  end
182
231
 
183
232
  # takes a list of tags and reaquires it from the database, this time with incidence.
233
+ # cheap call because it returns immediately if the list is already cloudable.
184
234
 
185
235
  def self.for_cloud(tags)
186
236
  return tags if tags.empty? || tags.first.cloud_size
@@ -193,11 +243,16 @@ class Tag < ActiveRecord::Base
193
243
 
194
244
  # adds retrieval methods for a taggable class to this class and to Tagging.
195
245
 
196
- def self.define_class_retrieval_methods(classname)
197
- Tagging.send :named_scope, "of_#{classname.downcase.pluralize}".intern, :conditions => { :tagged_type => classname.to_s }
198
- define_method("#{classname.downcase}_taggings") { self.taggings.send "of_#{classname.downcase.pluralize}".intern }
199
- define_method("#{classname.downcase.pluralize}") { self.send("#{classname.to_s.downcase}_taggings".intern).map{|l| l.tagged} }
246
+ def self.define_retrieval_methods(classname)
247
+ define_method classname.downcase.pluralize.to_sym do
248
+ classname.constantize.send :from_tag, self
249
+ end
250
+ end
251
+
252
+ protected
253
+
254
+ def calculate_metaphone
255
+ self.metaphone = Text::Metaphone.metaphone(self.title)
200
256
  end
201
-
202
257
  end
203
258
 
@@ -1,28 +1,12 @@
1
- - content_for :page_css do
2
- :sass
3
- p.title
4
- :width 70%
5
- p.keywords
6
- :width 28%
7
- :float right
8
- :margin 0 1% 0 0
9
- #content
10
- form
11
- .keywords
12
- .textbox
13
- :font-family Georgia, Palatino, "Times New Roman", Times, serif
14
- :font-size 200%
15
- :width 100%
16
- :color #c00
17
- :margin-top 4px
18
- #attributes
19
- :clear both
20
-
1
+ - include_stylesheet 'admin/taggable'
2
+ - include_javascript 'autocomplete'
3
+ - include_javascript 'admin/taggable'
4
+
21
5
  - fields_for :page, @page do |fields|
22
6
  %p.keywords
23
7
  %label{:for=>"page_keywords"}
24
- Tags
25
- = fields.text_field :keywords, :class => 'textbox'
8
+ =t('tags')
9
+ = fields.text_field :keywords, :class => 'textbox tagger'
26
10
  %p.title
27
11
  %label{:for=>"page_title"}= t('page_title')
28
12
  = fields.text_field :title, :class => 'textbox', :maxlength => 255
@@ -1,23 +1,37 @@
1
- = render_region :form_top
2
- - render_region :form do |form_region|
3
- .form-area
4
- - form_region.edit_name do
5
- %p.title
6
- = form.label :title
7
- = form.text_field :title, :class => 'textbox', :maxlength => 255
1
+ - form_for [:admin, @tag], :html => {'data-onsubmit_status' => onsubmit_status(@tag)} do |f|
2
+ = render_region :form_top
3
+ - render_region :form do |form_region|
4
+ .form-area
5
+ - form_region.edit_name do
6
+ %p.title
7
+ = f.label :title
8
+ = f.text_field :title, :class => 'textbox', :maxlength => 255
8
9
 
9
- - form_region.edit_description do
10
- %div
11
- = form.label :description
12
- = form.text_area :description, :class => "textarea", :style => "width: 100%"
10
+ - form_region.edit_role do
11
+ %p
12
+ = check_box_tag :structural, 1, @tag.structural?, {:class => 'toggle', :rel => 'toggle[pointer]'}
13
+ = f.label :structural, nil, :class => 'checkbox'
14
+ %div#pointer
15
+ %p
16
+ = f.label :page_id
17
+ = f.select :page_id, available_pointer_pages
18
+
19
+ - form_region.edit_description do
20
+ %p
21
+ = f.check_box :visible, {:class => 'toggle', :rel => 'toggle[description]'}
22
+ = f.label :visible, nil, :class => 'checkbox'
23
+ %div#description
24
+ %p.description
25
+ = f.label :description
26
+ = f.text_area :description, :class => "textarea", :style => "width: 100%"
13
27
 
14
- - render_region :form_bottom do |form_bottom_region|
15
- - form_bottom_region.edit_timestamp do
16
- = updated_stamp @tag
28
+ - render_region :form_bottom do |form_bottom_region|
29
+ - form_bottom_region.edit_timestamp do
30
+ = updated_stamp @tag
17
31
 
18
- - form_bottom_region.edit_buttons do
19
- %p.buttons
20
- = save_model_button(@tag)
21
- = save_model_and_continue_editing_button(@tag)
22
- or
23
- = link_to "Cancel", admin_tags_url
32
+ - form_bottom_region.edit_buttons do
33
+ %p.buttons
34
+ = save_model_button(@tag)
35
+ = save_model_and_continue_editing_button(@tag)
36
+ or
37
+ = link_to "Cancel", admin_tags_url
@@ -1,11 +1,6 @@
1
1
  - include_stylesheet 'admin/tags'
2
2
  - url = Radiant::Config['tags.page'] || '/tags'
3
3
 
4
- %h1 Tag cloud
5
-
6
- %p
7
- = link_to "&larr; view as list", admin_tags_url
8
-
9
4
  %div.cloud
10
5
  - @tags.each do |tag|
11
6
  = link_to tag.title, admin_tag_url(tag), :style => "font-size: #{tag.cloud_size.to_f * 2.5}em;"
@@ -13,5 +8,3 @@
13
8
  #actions
14
9
  %ul
15
10
  %li= link_to image('plus') + " " + "new tag", new_admin_tag_url
16
- %li= link_to "tag list", admin_tags_url, :class => 'minor'
17
- %li= link_to "tag cloud", cloud_admin_tags_url, :class => 'minor'
@@ -1,4 +1,6 @@
1
- - include_stylesheet 'admin/tags'
1
+ - @page_title = 'Tag: ' + @tag.title + ' - ' + default_page_title
2
+ - include_stylesheet 'admin/taggable'
3
+ - include_javascript 'admin/taggable'
2
4
 
3
5
  - render_region :main do |main|
4
6
  - main.edit_header do
@@ -6,9 +8,4 @@
6
8
  Edit Tag
7
9
 
8
10
  - main.edit_form do
9
- - form_for :tag, :url => admin_tag_path(@tag), :html => { :method => "put", :multipart => true } do |form|
10
- = render :partial => 'form', :object => form
11
-
12
-
13
-
14
-
11
+ = render :partial => 'form'
@@ -1,4 +1,7 @@
1
- - include_stylesheet 'admin/tags'
1
+ - include_stylesheet 'admin/taggable'
2
+ - include_javascript 'admin/taggable'
3
+
4
+ - @page_title = t('tags') + ' - ' + default_page_title
2
5
  = render_region :top
3
6
 
4
7
  #tags_table.outset
@@ -8,6 +11,8 @@
8
11
  - render_region :thead do |thead|
9
12
  - thead.title_header do
10
13
  %th.tag-title Title
14
+ - thead.link_header do
15
+ %th.tag-link Pointer
11
16
  - thead.description_header do
12
17
  %th.tag-description Description
13
18
  - thead.usage_header do
@@ -17,13 +22,20 @@
17
22
 
18
23
  %tbody
19
24
  - for tag in @tags
20
- %tr.node.level-1
25
+ %tr{:class => tag.visible? ? 'visible' : 'secret'}
21
26
  - render_region :tbody do |tbody|
22
27
  - tbody.title_cell do
23
28
  %td.tag-title
24
29
  = link_to image('tag', :alt => ''), edit_admin_tag_url(:id => tag.id)
25
30
  = link_to tag.title, edit_admin_tag_url(:id => tag.id)
26
31
 
32
+ - tbody.link_cell do
33
+ %td.tag-link
34
+ - if tag.page
35
+ = link_to tag.page.title, tag.page.url
36
+ - else
37
+ &mdash;
38
+
27
39
  - tbody.description_cell do
28
40
  %td.tag-description
29
41
  = tag.description
@@ -44,10 +56,3 @@
44
56
  #actions
45
57
  %ul
46
58
  %li= link_to image('plus') + " " + "new tag", new_admin_tag_url
47
- %li= link_to "tag list", admin_tags_url, :class => 'minor'
48
- %li= link_to "tag cloud", cloud_admin_tags_url, :class => 'minor'
49
-
50
- %script{ :type => "text/javascript"}
51
- // <! [CDATA[
52
- new RuledTable('tags')
53
- //]