radiant-taggable-extension 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
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
- //]