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 +38 -30
- data/VERSION +1 -1
- data/app/controllers/admin/tags_controller.rb +9 -3
- data/app/helpers/taggable_helper.rb +21 -0
- data/app/models/tag.rb +63 -8
- data/app/views/admin/pages/_edit_title.html.haml +6 -22
- data/app/views/admin/tags/_form.html.haml +34 -20
- data/app/views/admin/tags/cloud.html.haml +0 -7
- data/app/views/admin/tags/edit.html.haml +4 -7
- data/app/views/admin/tags/index.html.haml +14 -9
- data/app/views/admin/tags/new.html.haml +4 -3
- data/config/locales/en.yml +9 -0
- data/db/migrate/20110316210834_structural_tags.rb +17 -0
- data/db/migrate/20110411075109_metaphones.rb +15 -0
- data/lib/taggable_admin_ui.rb +3 -3
- data/lib/taggable_model.rb +15 -6
- data/lib/taggable_page.rb +6 -2
- data/lib/taggable_tags.rb +172 -99
- data/lib/text/double_metaphone.rb +356 -0
- data/lib/text/metaphone.rb +97 -0
- data/public/javascripts/admin/taggable.js +19 -0
- data/public/javascripts/autocomplete.js +334 -0
- data/public/stylesheets/sass/admin/taggable.sass +89 -0
- data/radiant-taggable-extension.gemspec +11 -3
- data/taggable_extension.rb +3 -16
- metadata +13 -5
- data/public/stylesheets/admin/tags.css +0 -20
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
|
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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
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.
|
1
|
+
1.2.4
|
@@ -1,8 +1,14 @@
|
|
1
1
|
class Admin::TagsController < Admin::ResourceController
|
2
|
+
helper :taggable
|
2
3
|
|
3
|
-
def index
|
4
|
-
|
5
|
-
|
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
|
-
|
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.
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
-
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
16
|
-
|
28
|
+
- render_region :form_bottom do |form_bottom_region|
|
29
|
+
- form_bottom_region.edit_timestamp do
|
30
|
+
= updated_stamp @tag
|
17
31
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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 "← 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
|
-
-
|
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
|
-
|
10
|
-
= render :partial => 'form', :object => form
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
= render :partial => 'form'
|
@@ -1,4 +1,7 @@
|
|
1
|
-
- include_stylesheet 'admin/
|
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.
|
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
|
+
—
|
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
|
-
//]
|