Commit 6977150f04be1bfe365e1cd86e3f8143c34a51bf
1 parent
5d526717
Exists in
master
and in
4 other branches
integrate tags plugins
Showing
6 changed files
with
212 additions
and
0 deletions
Show diff stats
Gemfile
@@ -21,6 +21,8 @@ gem "git" | @@ -21,6 +21,8 @@ gem "git" | ||
21 | gem "acts_as_list" | 21 | gem "acts_as_list" |
22 | gem 'rdiscount' | 22 | gem 'rdiscount' |
23 | 23 | ||
24 | +gem 'acts-as-taggable-on', '~>2.1.0' | ||
25 | + | ||
24 | group :assets do | 26 | group :assets do |
25 | gem 'sass-rails', " ~> 3.1.0" | 27 | gem 'sass-rails', " ~> 3.1.0" |
26 | gem 'coffee-rails', "~> 3.1.0" | 28 | gem 'coffee-rails', "~> 3.1.0" |
Gemfile.lock
@@ -54,6 +54,8 @@ GEM | @@ -54,6 +54,8 @@ GEM | ||
54 | activesupport (= 3.1.0) | 54 | activesupport (= 3.1.0) |
55 | activesupport (3.1.0) | 55 | activesupport (3.1.0) |
56 | multi_json (~> 1.0) | 56 | multi_json (~> 1.0) |
57 | + acts-as-taggable-on (2.1.1) | ||
58 | + rails | ||
57 | acts_as_list (0.1.4) | 59 | acts_as_list (0.1.4) |
58 | addressable (2.2.6) | 60 | addressable (2.2.6) |
59 | ansi (1.3.0) | 61 | ansi (1.3.0) |
@@ -246,6 +248,7 @@ PLATFORMS | @@ -246,6 +248,7 @@ PLATFORMS | ||
246 | ruby | 248 | ruby |
247 | 249 | ||
248 | DEPENDENCIES | 250 | DEPENDENCIES |
251 | + acts-as-taggable-on (~> 2.1.0) | ||
249 | acts_as_list | 252 | acts_as_list |
250 | annotate! | 253 | annotate! |
251 | autotest | 254 | autotest |
app/models/project.rb
@@ -9,6 +9,8 @@ class Project < ActiveRecord::Base | @@ -9,6 +9,8 @@ class Project < ActiveRecord::Base | ||
9 | has_many :notes, :dependent => :destroy | 9 | has_many :notes, :dependent => :destroy |
10 | has_many :snippets, :dependent => :destroy | 10 | has_many :snippets, :dependent => :destroy |
11 | 11 | ||
12 | + acts_as_taggable | ||
13 | + | ||
12 | validates :name, | 14 | validates :name, |
13 | :uniqueness => true, | 15 | :uniqueness => true, |
14 | :presence => true, | 16 | :presence => true, |
db/migrate/20111101222453_acts_as_taggable_on_migration.rb
0 → 100644
@@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
1 | +class ActsAsTaggableOnMigration < ActiveRecord::Migration | ||
2 | + def self.up | ||
3 | + create_table :tags do |t| | ||
4 | + t.string :name | ||
5 | + end | ||
6 | + | ||
7 | + create_table :taggings do |t| | ||
8 | + t.references :tag | ||
9 | + | ||
10 | + # You should make sure that the column created is | ||
11 | + # long enough to store the required class names. | ||
12 | + t.references :taggable, :polymorphic => true | ||
13 | + t.references :tagger, :polymorphic => true | ||
14 | + | ||
15 | + t.string :context | ||
16 | + | ||
17 | + t.datetime :created_at | ||
18 | + end | ||
19 | + | ||
20 | + add_index :taggings, :tag_id | ||
21 | + add_index :taggings, [:taggable_id, :taggable_type, :context] | ||
22 | + end | ||
23 | + | ||
24 | + def self.down | ||
25 | + drop_table :taggings | ||
26 | + drop_table :tags | ||
27 | + end | ||
28 | +end |
@@ -0,0 +1,143 @@ | @@ -0,0 +1,143 @@ | ||
1 | +/* Author: Alicia Liu */ | ||
2 | + | ||
3 | +(function ($) { | ||
4 | + | ||
5 | + $.widget("ui.tagify", { | ||
6 | + options: { | ||
7 | + delimiters: [13, 188], // what user can type to complete a tag in char codes: [enter], [comma] | ||
8 | + outputDelimiter: ',', // delimiter for tags in original input field | ||
9 | + cssClass: 'tagify-container', // CSS class to style the tagify div and tags, see stylesheet | ||
10 | + addTagPrompt: 'add tags' // placeholder text | ||
11 | + }, | ||
12 | + | ||
13 | + _create: function() { | ||
14 | + var self = this, | ||
15 | + el = self.element, | ||
16 | + opts = self.options; | ||
17 | + | ||
18 | + this.tags = []; | ||
19 | + | ||
20 | + // hide text field and replace with a div that contains it's own input field for entering tags | ||
21 | + this.tagInput = $("<input type='text'>") | ||
22 | + .attr( 'placeholder', opts.addTagPrompt ) | ||
23 | + .keypress( function(e) { | ||
24 | + var $this = $(this), | ||
25 | + pressed = e.which; | ||
26 | + | ||
27 | + for ( i in opts.delimiters ) { | ||
28 | + | ||
29 | + if (pressed == opts.delimiters[i]) { | ||
30 | + self.add( $this.val() ); | ||
31 | + e.preventDefault(); | ||
32 | + return false; | ||
33 | + } | ||
34 | + } | ||
35 | + }) | ||
36 | + // for some reason, in Safari, backspace is only recognized on keyup | ||
37 | + .keyup( function(e) { | ||
38 | + var $this = $(this), | ||
39 | + pressed = e.which; | ||
40 | + | ||
41 | + // if backspace is hit with no input, remove the last tag | ||
42 | + if (pressed == 8) { // backspace | ||
43 | + if ( $this.val() == "" ) { | ||
44 | + self.remove(); | ||
45 | + return false; | ||
46 | + } | ||
47 | + return; | ||
48 | + } | ||
49 | + }); | ||
50 | + | ||
51 | + this.tagDiv = $("<div></div>") | ||
52 | + .addClass( opts.cssClass ) | ||
53 | + .click( function() { | ||
54 | + $(this).children('input').focus(); | ||
55 | + }) | ||
56 | + .append( this.tagInput ) | ||
57 | + .insertAfter( el.hide() ); | ||
58 | + | ||
59 | + // if the field isn't empty, parse the field for tags, and prepopulate existing tags | ||
60 | + var initVal = $.trim( el.val() ); | ||
61 | + | ||
62 | + if ( initVal ) { | ||
63 | + var initTags = initVal.split( opts.outputDelimiter ); | ||
64 | + $.each( initTags, function(i, tag) { | ||
65 | + self.add( tag ); | ||
66 | + }); | ||
67 | + } | ||
68 | + }, | ||
69 | + | ||
70 | + _setOption: function( key, value ) { | ||
71 | + options.key = value; | ||
72 | + }, | ||
73 | + | ||
74 | + // add a tag, public function | ||
75 | + add: function(text) { | ||
76 | + var self = this; | ||
77 | + text = text || self.tagInput.val(); | ||
78 | + if (text) { | ||
79 | + var tagIndex = self.tags.length; | ||
80 | + | ||
81 | + var removeButton = $("<a href='#'>x</a>") | ||
82 | + .click( function() { | ||
83 | + self.remove( tagIndex ); | ||
84 | + return false; | ||
85 | + }); | ||
86 | + var newTag = $("<span></span>") | ||
87 | + .text( text ) | ||
88 | + .append( removeButton ); | ||
89 | + | ||
90 | + self.tagInput.before( newTag ); | ||
91 | + self.tags.push( text ); | ||
92 | + self.tagInput.val(''); | ||
93 | + } | ||
94 | + }, | ||
95 | + | ||
96 | + // remove a tag by index, public function | ||
97 | + // if index is blank, remove the last tag | ||
98 | + remove: function( tagIndex ) { | ||
99 | + var self = this; | ||
100 | + if ( tagIndex == null || tagIndex === (self.tags.length - 1) ) { | ||
101 | + this.tagDiv.children("span").last().remove(); | ||
102 | + self.tags.pop(); | ||
103 | + } | ||
104 | + if ( typeof(tagIndex) == 'number' ) { | ||
105 | + // otherwise just hide this tag, and we don't mess up the index | ||
106 | + this.tagDiv.children( "span:eq(" + tagIndex + ")" ).hide(); | ||
107 | + // we rely on the serialize function to remove null values | ||
108 | + delete( self.tags[tagIndex] ); | ||
109 | + } | ||
110 | + }, | ||
111 | + | ||
112 | + // serialize the tags with the given delimiter, and write it back into the tagified field | ||
113 | + serialize: function() { | ||
114 | + var self = this; | ||
115 | + var delim = self.options.outputDelimiter; | ||
116 | + var tagsStr = self.tags.join( delim ); | ||
117 | + | ||
118 | + // our tags might have deleted entries, remove them here | ||
119 | + var dupes = new RegExp(delim + delim + '+', 'g'); // regex: /,,+/g | ||
120 | + var ends = new RegExp('^' + delim + '|' + delim + '$', 'g'); // regex: /^,|,$/g | ||
121 | + var outputStr = tagsStr.replace( dupes, delim ).replace(ends, ''); | ||
122 | + | ||
123 | + self.element.val(outputStr); | ||
124 | + return outputStr; | ||
125 | + }, | ||
126 | + | ||
127 | + inputField: function() { | ||
128 | + return this.tagInput; | ||
129 | + }, | ||
130 | + | ||
131 | + containerDiv: function() { | ||
132 | + return this.tagDiv; | ||
133 | + }, | ||
134 | + | ||
135 | + // remove the div, and show original input | ||
136 | + destroy: function() { | ||
137 | + $.Widget.prototype.destroy.apply(this); | ||
138 | + this.tagDiv.remove(); | ||
139 | + this.element.show(); | ||
140 | + } | ||
141 | + }); | ||
142 | + | ||
143 | +})(jQuery); | ||
0 | \ No newline at end of file | 144 | \ No newline at end of file |
@@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
1 | +/* Tagify styles | ||
2 | +Author: Alicia Liu test | ||
3 | +*/ | ||
4 | + | ||
5 | +.tagify-container { | ||
6 | +} | ||
7 | + | ||
8 | +.tagify-container > span { | ||
9 | + display: inline-block; | ||
10 | + padding: 8px 11px 8px 11px; | ||
11 | + margin: 1px 5px 0px 0px; | ||
12 | + border-radius: 4px; | ||
13 | + border: 1px solid #d0e1ff; | ||
14 | + background-color: #d0e1ff; | ||
15 | + color: #0f326d; | ||
16 | + font-weight: bold; | ||
17 | + font-size: 14px; | ||
18 | +} | ||
19 | + | ||
20 | +.tagify-container > span > a { | ||
21 | + padding-left: 5px !important; | ||
22 | + color: #83a5e1; | ||
23 | + text-decoration: none; | ||
24 | + font-weight: bold; | ||
25 | +} | ||
26 | + | ||
27 | +.tagify-container > input { | ||
28 | + border: 0 none; | ||
29 | + width: 100px !important; | ||
30 | +} | ||
31 | + | ||
32 | +.tagify-container > input:focus { | ||
33 | + outline: none; | ||
34 | +} | ||
0 | \ No newline at end of file | 35 | \ No newline at end of file |