summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock3
-rw-r--r--app/models/project.rb2
-rw-r--r--db/migrate/20111101222453_acts_as_taggable_on_migration.rb28
-rw-r--r--vendor/assets/javascripts/jquery.tagify.js143
-rw-r--r--vendor/assets/stylesheets/jquery-ui/jquery.tagify.css34
6 files changed, 212 insertions, 0 deletions
diff --git a/Gemfile b/Gemfile
index 8da55b10956..f32c13f070f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -21,6 +21,8 @@ gem "git"
gem "acts_as_list"
gem 'rdiscount'
+gem 'acts-as-taggable-on', '~>2.1.0'
+
group :assets do
gem 'sass-rails', " ~> 3.1.0"
gem 'coffee-rails', "~> 3.1.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index f66e832e4a6..e557ee432e9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -54,6 +54,8 @@ GEM
activesupport (= 3.1.0)
activesupport (3.1.0)
multi_json (~> 1.0)
+ acts-as-taggable-on (2.1.1)
+ rails
acts_as_list (0.1.4)
addressable (2.2.6)
ansi (1.3.0)
@@ -246,6 +248,7 @@ PLATFORMS
ruby
DEPENDENCIES
+ acts-as-taggable-on (~> 2.1.0)
acts_as_list
annotate!
autotest
diff --git a/app/models/project.rb b/app/models/project.rb
index befa1c6b14e..de68f451e03 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -9,6 +9,8 @@ class Project < ActiveRecord::Base
has_many :notes, :dependent => :destroy
has_many :snippets, :dependent => :destroy
+ acts_as_taggable
+
validates :name,
:uniqueness => true,
:presence => true,
diff --git a/db/migrate/20111101222453_acts_as_taggable_on_migration.rb b/db/migrate/20111101222453_acts_as_taggable_on_migration.rb
new file mode 100644
index 00000000000..16610615f9d
--- /dev/null
+++ b/db/migrate/20111101222453_acts_as_taggable_on_migration.rb
@@ -0,0 +1,28 @@
+class ActsAsTaggableOnMigration < ActiveRecord::Migration
+ def self.up
+ create_table :tags do |t|
+ t.string :name
+ end
+
+ create_table :taggings do |t|
+ t.references :tag
+
+ # You should make sure that the column created is
+ # long enough to store the required class names.
+ t.references :taggable, :polymorphic => true
+ t.references :tagger, :polymorphic => true
+
+ t.string :context
+
+ t.datetime :created_at
+ end
+
+ add_index :taggings, :tag_id
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
+ end
+
+ def self.down
+ drop_table :taggings
+ drop_table :tags
+ end
+end
diff --git a/vendor/assets/javascripts/jquery.tagify.js b/vendor/assets/javascripts/jquery.tagify.js
new file mode 100644
index 00000000000..f22d4c71191
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.tagify.js
@@ -0,0 +1,143 @@
+/* Author: Alicia Liu */
+
+(function ($) {
+
+ $.widget("ui.tagify", {
+ options: {
+ delimiters: [13, 188], // what user can type to complete a tag in char codes: [enter], [comma]
+ outputDelimiter: ',', // delimiter for tags in original input field
+ cssClass: 'tagify-container', // CSS class to style the tagify div and tags, see stylesheet
+ addTagPrompt: 'add tags' // placeholder text
+ },
+
+ _create: function() {
+ var self = this,
+ el = self.element,
+ opts = self.options;
+
+ this.tags = [];
+
+ // hide text field and replace with a div that contains it's own input field for entering tags
+ this.tagInput = $("<input type='text'>")
+ .attr( 'placeholder', opts.addTagPrompt )
+ .keypress( function(e) {
+ var $this = $(this),
+ pressed = e.which;
+
+ for ( i in opts.delimiters ) {
+
+ if (pressed == opts.delimiters[i]) {
+ self.add( $this.val() );
+ e.preventDefault();
+ return false;
+ }
+ }
+ })
+ // for some reason, in Safari, backspace is only recognized on keyup
+ .keyup( function(e) {
+ var $this = $(this),
+ pressed = e.which;
+
+ // if backspace is hit with no input, remove the last tag
+ if (pressed == 8) { // backspace
+ if ( $this.val() == "" ) {
+ self.remove();
+ return false;
+ }
+ return;
+ }
+ });
+
+ this.tagDiv = $("<div></div>")
+ .addClass( opts.cssClass )
+ .click( function() {
+ $(this).children('input').focus();
+ })
+ .append( this.tagInput )
+ .insertAfter( el.hide() );
+
+ // if the field isn't empty, parse the field for tags, and prepopulate existing tags
+ var initVal = $.trim( el.val() );
+
+ if ( initVal ) {
+ var initTags = initVal.split( opts.outputDelimiter );
+ $.each( initTags, function(i, tag) {
+ self.add( tag );
+ });
+ }
+ },
+
+ _setOption: function( key, value ) {
+ options.key = value;
+ },
+
+ // add a tag, public function
+ add: function(text) {
+ var self = this;
+ text = text || self.tagInput.val();
+ if (text) {
+ var tagIndex = self.tags.length;
+
+ var removeButton = $("<a href='#'>x</a>")
+ .click( function() {
+ self.remove( tagIndex );
+ return false;
+ });
+ var newTag = $("<span></span>")
+ .text( text )
+ .append( removeButton );
+
+ self.tagInput.before( newTag );
+ self.tags.push( text );
+ self.tagInput.val('');
+ }
+ },
+
+ // remove a tag by index, public function
+ // if index is blank, remove the last tag
+ remove: function( tagIndex ) {
+ var self = this;
+ if ( tagIndex == null || tagIndex === (self.tags.length - 1) ) {
+ this.tagDiv.children("span").last().remove();
+ self.tags.pop();
+ }
+ if ( typeof(tagIndex) == 'number' ) {
+ // otherwise just hide this tag, and we don't mess up the index
+ this.tagDiv.children( "span:eq(" + tagIndex + ")" ).hide();
+ // we rely on the serialize function to remove null values
+ delete( self.tags[tagIndex] );
+ }
+ },
+
+ // serialize the tags with the given delimiter, and write it back into the tagified field
+ serialize: function() {
+ var self = this;
+ var delim = self.options.outputDelimiter;
+ var tagsStr = self.tags.join( delim );
+
+ // our tags might have deleted entries, remove them here
+ var dupes = new RegExp(delim + delim + '+', 'g'); // regex: /,,+/g
+ var ends = new RegExp('^' + delim + '|' + delim + '$', 'g'); // regex: /^,|,$/g
+ var outputStr = tagsStr.replace( dupes, delim ).replace(ends, '');
+
+ self.element.val(outputStr);
+ return outputStr;
+ },
+
+ inputField: function() {
+ return this.tagInput;
+ },
+
+ containerDiv: function() {
+ return this.tagDiv;
+ },
+
+ // remove the div, and show original input
+ destroy: function() {
+ $.Widget.prototype.destroy.apply(this);
+ this.tagDiv.remove();
+ this.element.show();
+ }
+ });
+
+})(jQuery); \ No newline at end of file
diff --git a/vendor/assets/stylesheets/jquery-ui/jquery.tagify.css b/vendor/assets/stylesheets/jquery-ui/jquery.tagify.css
new file mode 100644
index 00000000000..d6c178f7132
--- /dev/null
+++ b/vendor/assets/stylesheets/jquery-ui/jquery.tagify.css
@@ -0,0 +1,34 @@
+/* Tagify styles
+Author: Alicia Liu test
+*/
+
+.tagify-container {
+}
+
+.tagify-container > span {
+ display: inline-block;
+ padding: 8px 11px 8px 11px;
+ margin: 1px 5px 0px 0px;
+ border-radius: 4px;
+ border: 1px solid #d0e1ff;
+ background-color: #d0e1ff;
+ color: #0f326d;
+ font-weight: bold;
+ font-size: 14px;
+}
+
+.tagify-container > span > a {
+ padding-left: 5px !important;
+ color: #83a5e1;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+.tagify-container > input {
+ border: 0 none;
+ width: 100px !important;
+}
+
+.tagify-container > input:focus {
+ outline: none;
+} \ No newline at end of file