changeset 1132:9b2f28ecd125 redmine-2.2-integration

RedmineTags plugin: merged exiting with the most up-to-date version from the repo.
author luisf <luis.figueira@eecs.qmul.ac.uk>
date Wed, 09 Jan 2013 12:41:39 +0000
parents 2681af99688f
children 03049d5227e3
files plugins/redmine_tags/Gemfile plugins/redmine_tags/HISTORY.md plugins/redmine_tags/README.md plugins/redmine_tags/app/helpers/issues_helper.rb plugins/redmine_tags/app/helpers/tags_helper.rb plugins/redmine_tags/app/views/auto_completes/_tag_list.html.erb plugins/redmine_tags/app/views/auto_completes/_tag_list.json.erb plugins/redmine_tags/app/views/issues/_bulk_tags_form.html.erb plugins/redmine_tags/app/views/issues/_tags.html.erb plugins/redmine_tags/app/views/issues/_tags_form.html.erb plugins/redmine_tags/app/views/issues/_tags_sidebar.html.erb plugins/redmine_tags/app/views/tags/_settings.html.erb plugins/redmine_tags/assets/javascripts/tag-it.js plugins/redmine_tags/assets/stylesheets/jquery.tagit.css plugins/redmine_tags/assets/stylesheets/redmine_tags.css plugins/redmine_tags/config/locales/bg.yml plugins/redmine_tags/config/locales/de.yml plugins/redmine_tags/config/locales/en.yml plugins/redmine_tags/config/locales/fr.yml plugins/redmine_tags/config/locales/ru.yml plugins/redmine_tags/config/locales/zh.yml plugins/redmine_tags/config/routes.rb plugins/redmine_tags/init.rb plugins/redmine_tags/lib/redmine_tags.rb plugins/redmine_tags/lib/redmine_tags/hooks/model_issue_hook.rb plugins/redmine_tags/lib/redmine_tags/hooks/views_issues_hook.rb plugins/redmine_tags/lib/redmine_tags/patches/auto_completes_controller_patch.rb plugins/redmine_tags/lib/redmine_tags/patches/issue_patch.rb plugins/redmine_tags/lib/redmine_tags/patches/issues_helper_patch.rb plugins/redmine_tags/lib/redmine_tags/patches/queries_helper_patch.rb plugins/redmine_tags/lib/redmine_tags/patches/query_patch.rb
diffstat 31 files changed, 839 insertions(+), 100 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/Gemfile	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,1 @@
+gem "acts-as-taggable-on", "2.3.3"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/HISTORY.md	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,4 @@
+## 2.0.1 (unreleased)
+
+- Added Simplified Chinese translation. Thanks to archonwang.
+- Added Bulgarian translation. Thanks to Ivan Cenov.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/README.md	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,41 @@
+Redmine Tags
+============
+
+Allows marking up different models in Redmine with tags.
+Inspired by original redmine\_tags of Eric Davis.
+
+
+Supported models
+----------------
+
+- Issues
+
+
+Requirements
+------------
+
+- Redmine `>= 2.1.0`
+- acts-as-taggable-on `= 2.3.3`
+
+
+Installation
+------------
+
+- Clone this repository into `redmine/plugins/redmine_tags`
+- Install dependencies and migrate database:
+
+        cd redmine/
+        bundle install
+        RAILS_ENV=production rails generate acts_as_taggable_on:migration
+        RAILS_ENV=production rake db:migrate
+        RAILS_ENV=production rake redmine:plugins:migrate
+
+- Restart your Redmine web server (e.g. mongrel, thin, mod\_rails)
+
+
+License
+-------
+
+This plugin is licensed under the terms of GNU/GPL v3+.
+See COPYING and LICENSE for details.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/app/helpers/issues_helper.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,42 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module IssuesHelper
+  include TagsHelper
+
+  def sidebar_tags
+    unless @sidebar_tags
+      @sidebar_tags = []
+      if :none != RedmineTags.settings[:issues_sidebar].to_sym
+        @sidebar_tags = Issue.available_tags({
+          :project => @project,
+          :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1)
+        })
+      end
+    end
+    @sidebar_tags
+  end
+
+  def render_sidebar_tags
+    render_tags_list(sidebar_tags, {
+      :show_count => (RedmineTags.settings[:issues_show_count].to_i == 1),
+      :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1),
+      :style => RedmineTags.settings[:issues_sidebar].to_sym
+    })
+  end
+end
--- a/plugins/redmine_tags/app/helpers/tags_helper.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/app/helpers/tags_helper.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -63,8 +63,21 @@
   def render_tags_list(tags, options = {})
     unless tags.nil? or tags.empty?
       content, style = '', options.delete(:style)
-    
-      tags.sort! { |a,b| b.count <=> a.count }
+
+      # prevent ActsAsTaggableOn::TagsHelper from calling `all`
+      # otherwise we will need sort tags after `tag_cloud`
+      tags = tags.all if tags.respond_to?(:all)
+
+      case sorting = "#{RedmineTags.settings[:issues_sort_by]}:#{RedmineTags.settings[:issues_sort_order]}"
+        when "name:asc";    tags.sort! { |a,b| a.name <=> b.name }
+        when "name:desc";   tags.sort! { |a,b| b.name <=> a.name }
+        when "count:asc";   tags.sort! { |a,b| a.count <=> b.count }
+        when "count:desc";  tags.sort! { |a,b| b.count <=> a.count }
+        # Unknown sorting option. Fallback to default one
+        else
+          logger.warn "[redmine_tags] Unknown sorting option: <#{sorting}>"
+          tags.sort! { |a,b| a.name <=> b.name }
+      end
 
       if :list == style
         list_el, item_el = 'ul', 'li'
@@ -75,8 +88,9 @@
         raise "Unknown list style"
       end
 
+      content = content.html_safe
       tag_cloud tags, (1..8).to_a do |tag, weight|
-        content << " " + content_tag(item_el, render_project_tag_link(tag, options), :class => "tag-nube-#{weight}") + " "
+        content << " ".html_safe + content_tag(item_el, render_tag_link(tag, options), :class => "tag-nube-#{weight}") + " ".html_safe
       end
 
       content_tag(list_el, content, :class => 'tags')
@@ -84,7 +98,8 @@
   end
 
   private
-  # put most massive tags in the middle
+
+  # make snowball. first tags comes in th middle.
   def cloudify(tags)
     temp, tags, trigger = tags, [], true
     temp.each do |tag|
--- a/plugins/redmine_tags/app/views/auto_completes/_tag_list.html.erb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/app/views/auto_completes/_tag_list.html.erb	Wed Jan 09 12:41:39 2013 +0000
@@ -1,6 +1,4 @@
-<ul>
-<% @tags.each do |tag| -%>
-  <%= content_tag 'li', h('%s (%d)' % [tag.name, tag.count]), :name => tag.name %>
-<% end -%>
-  <%= content_tag 'li', l(:auto_complete_new_tag) % @name, :name => @name %>
-</ul>
+<%= raw @tags.collect {|tag| 
+       tag.name
+    }.to_json
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/app/views/auto_completes/_tag_list.json.erb	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,4 @@
+<%= raw @tags.collect {|tag| 
+       tag.name
+    }.to_json
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/app/views/issues/_bulk_tags_form.html.erb	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,1 @@
+<%= render :partial => "issues/tags_form", :locals => {:issue => Issue.new({:project => issues.first.project})} %>
--- a/plugins/redmine_tags/app/views/issues/_tags.html.erb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/app/views/issues/_tags.html.erb	Wed Jan 09 12:41:39 2013 +0000
@@ -1,6 +1,6 @@
 <% unless issue.tag_list.empty? %>
   <tr>
     <td><b><%=l(:tags)%>:</b></td>
-    <td><%= issue.tag_counts.collect{ |t| render_tag_link(t, :show_count => false, :open_only => false) }.join(', ') %></td>
+    <td><%= safe_join(issue.tag_counts.collect{ |t| render_tag_link(t, :show_count => false, :open_only => false) }, ', ') %></td>
   </tr>
 <% end %>
--- a/plugins/redmine_tags/app/views/issues/_tags_form.html.erb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/app/views/issues/_tags_form.html.erb	Wed Jan 09 12:41:39 2013 +0000
@@ -1,8 +1,29 @@
-<% fields_for :issue, issue, :builder => TabularFormBuilder do |f| -%>
 <div>
-  <p id="issue_tags"><%= f.text_field :tag_list, :label => :tags, :size => 60, :class => 'hol' %></p>
+  <p id="issue_tags">
+  <% issue = Issue.new({:project => issues.first.project}) if defined? issues %>
+  <% text_field_options = {:label => :tags, :size => 60, :class => 'hol'} %>
+  <% if defined? form %>
+    <%= form.text_field :tag_list, text_field_options %>
+  <% else %>
+    <%= label_tag :tags, nil, :for => :issue_tag_list  %>
+    <%= text_field_tag 'issue[tag_list]', "", :class => text_field_options[:class], :size => text_field_options[:size], :id => :issue_tag_list %>
+  <% end %>
+  </p>
   <div id="issue_tag_candidates" class="autocomplete"></div>
-  <%= javascript_include_tag 'tags_input', :plugin => 'redmine_tags' %>
-  <%= javascript_tag "observeIssueTagsField('#{url_for(:controller => 'auto_completes', :action => 'issue_tags', :project_id => issue.project)}')" %>
+  <%= stylesheet_link_tag 'jquery.tagit.css', :plugin => 'redmine_tags' %>
+  <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %>
+  <%= javascript_include_tag 'tag-it', :plugin => 'redmine_tags' %>
+  <%= javascript_tag "$('#issue_tag_list').tagit({
+		tagSource: function(search, showChoices) {
+		  var that = this;
+		  $.ajax({
+			url: '#{url_for(:controller => 'auto_completes', :action => 'issue_tags', :project_id => issue.project.id)}',
+			data: {q: search.term},
+			success: function(choices) {
+			  showChoices(that._subtractArray(jQuery.parseJSON(choices), that.assignedTags()));
+			}
+		  });
+		},
+	});
+" %>
 </div>
-<% end -%>
--- a/plugins/redmine_tags/app/views/issues/_tags_sidebar.html.erb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/app/views/issues/_tags_sidebar.html.erb	Wed Jan 09 12:41:39 2013 +0000
@@ -1,4 +1,5 @@
 <% unless sidebar_tags.empty? -%>
+  <%= stylesheet_link_tag 'jquery.tagit.css', :plugin => 'redmine_tags' %>
   <%= stylesheet_link_tag 'redmine_tags', :plugin => 'redmine_tags' %>
   <h3><%= l(:tags) %></h3>
   <%= render_sidebar_tags %>
--- a/plugins/redmine_tags/app/views/tags/_settings.html.erb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/app/views/tags/_settings.html.erb	Wed Jan 09 12:41:39 2013 +0000
@@ -11,4 +11,9 @@
     <label><%= l(:issues_open_only) %></label>
     <%= check_box_tag 'settings[issues_open_only]', 1, 1 == @settings[:issues_open_only].to_i %>
   </p>
+  <p>
+    <label><%= l(:issues_sort_by) %></label>
+    <%= select_tag 'settings[issues_sort_by]', options_for_select(%w(name count).collect{|v| [l("issues_sort_by_#{v}"), v]}, @settings[:issues_sort_by]) %>
+    <%= select_tag 'settings[issues_sort_order]', options_for_select(%w(asc desc).collect{|v| [l("issues_sort_order_#{v}"), v]}, @settings[:issues_sort_order]) %>
+  </p>
 </fieldset>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/assets/javascripts/tag-it.js	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,392 @@
+/*
+* jQuery UI Tag-it!
+*
+* @version v2.0 (06/2011)
+*
+* Copyright 2011, Levy Carneiro Jr.
+* Released under the MIT license.
+* http://aehlke.github.com/tag-it/LICENSE
+*
+* Homepage:
+*   http://aehlke.github.com/tag-it/
+*
+* Authors:
+*   Levy Carneiro Jr.
+*   Martin Rehfeld
+*   Tobias Schmidt
+*   Skylar Challand
+*   Alex Ehlke
+*
+* Maintainer:
+*   Alex Ehlke - Twitter: @aehlke
+*
+* Dependencies:
+*   jQuery v1.4+
+*   jQuery UI v1.8+
+*/
+(function($) {
+
+    $.widget('ui.tagit', {
+        options: {
+            itemName          : 'item',
+            fieldName         : 'tags',
+            availableTags     : [],
+            tagSource         : null,
+            removeConfirmation: false,
+            caseSensitive     : true,
+            placeholderText   : null,
+
+            // When enabled, quotes are not neccesary
+            // for inputting multi-word tags.
+            allowSpaces: false,
+
+            // Whether to animate tag removals or not.
+            animate: true,
+
+            // The below options are for using a single field instead of several
+            // for our form values.
+            //
+            // When enabled, will use a single hidden field for the form,
+            // rather than one per tag. It will delimit tags in the field
+            // with singleFieldDelimiter.
+            //
+            // The easiest way to use singleField is to just instantiate tag-it
+            // on an INPUT element, in which case singleField is automatically
+            // set to true, and singleFieldNode is set to that element. This 
+            // way, you don't need to fiddle with these options.
+            singleField: false,
+
+            singleFieldDelimiter: ',',
+
+            // Set this to an input DOM node to use an existing form field.
+            // Any text in it will be erased on init. But it will be
+            // populated with the text of tags as they are created,
+            // delimited by singleFieldDelimiter.
+            //
+            // If this is not set, we create an input node for it,
+            // with the name given in settings.fieldName, 
+            // ignoring settings.itemName.
+            singleFieldNode: null,
+
+            // Optionally set a tabindex attribute on the input that gets
+            // created for tag-it.
+            tabIndex: null,
+
+
+            // Event callbacks.
+            onTagAdded  : null,
+            onTagRemoved: null,
+            onTagClicked: null
+        },
+
+
+        _create: function() {
+            // for handling static scoping inside callbacks
+            var that = this;
+
+            // There are 2 kinds of DOM nodes this widget can be instantiated on:
+            //     1. UL, OL, or some element containing either of these.
+            //     2. INPUT, in which case 'singleField' is overridden to true,
+            //        a UL is created and the INPUT is hidden.
+            if (this.element.is('input')) {
+                this.tagList = $('<ul></ul>').insertAfter(this.element);
+                this.options.singleField = true;
+                this.options.singleFieldNode = this.element;
+                this.element.css('display', 'none');
+            } else {
+                this.tagList = this.element.find('ul, ol').andSelf().last();
+            }
+
+            this._tagInput = $('<input type="text" />').addClass('ui-widget-content');
+            if (this.options.tabIndex) {
+                this._tagInput.attr('tabindex', this.options.tabIndex);
+            }
+            if (this.options.placeholderText) {
+                this._tagInput.attr('placeholder', this.options.placeholderText);
+            }
+
+            this.options.tagSource = this.options.tagSource || function(search, showChoices) {
+                var filter = search.term.toLowerCase();
+                var choices = $.grep(this.options.availableTags, function(element) {
+                    // Only match autocomplete options that begin with the search term.
+                    // (Case insensitive.)
+                    return (element.toLowerCase().indexOf(filter) === 0);
+                });
+                showChoices(this._subtractArray(choices, this.assignedTags()));
+            };
+
+            // Bind tagSource callback functions to this context.
+            if ($.isFunction(this.options.tagSource)) {
+                this.options.tagSource = $.proxy(this.options.tagSource, this);
+            }
+
+            this.tagList
+                .addClass('tagit')
+                .addClass('ui-widget ui-widget-content ui-corner-all')
+                // Create the input field.
+                .append($('<li class="tagit-new"></li>').append(this._tagInput))
+                .click(function(e) {
+                    var target = $(e.target);
+                    if (target.hasClass('tagit-label')) {
+                        that._trigger('onTagClicked', e, target.closest('.tagit-choice'));
+                    } else {
+                        // Sets the focus() to the input field, if the user
+                        // clicks anywhere inside the UL. This is needed
+                        // because the input field needs to be of a small size.
+                        that._tagInput.focus();
+                    }
+                });
+
+            // Add existing tags from the list, if any.
+            this.tagList.children('li').each(function() {
+                if (!$(this).hasClass('tagit-new')) {
+                    that.createTag($(this).html(), $(this).attr('class'));
+                    $(this).remove();
+                }
+            });
+
+            // Single field support.
+            if (this.options.singleField) {
+                if (this.options.singleFieldNode) {
+                    // Add existing tags from the input field.
+                    var node = $(this.options.singleFieldNode);
+                    var tags = node.val().split(this.options.singleFieldDelimiter);
+                    node.val('');
+                    $.each(tags, function(index, tag) {
+                        that.createTag(tag);
+                    });
+                } else {
+                    // Create our single field input after our list.
+                    this.options.singleFieldNode = this.tagList.after('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />');
+                }
+            }
+
+            // Events.
+            this._tagInput
+                .keydown(function(event) {
+                    // Backspace is not detected within a keypress, so it must use keydown.
+                    if (event.which == $.ui.keyCode.BACKSPACE && that._tagInput.val() === '') {
+                        var tag = that._lastTag();
+                        if (!that.options.removeConfirmation || tag.hasClass('remove')) {
+                            // When backspace is pressed, the last tag is deleted.
+                            that.removeTag(tag);
+                        } else if (that.options.removeConfirmation) {
+                            tag.addClass('remove ui-state-highlight');
+                        }
+                    } else if (that.options.removeConfirmation) {
+                        that._lastTag().removeClass('remove ui-state-highlight');
+                    }
+
+                    // Comma/Space/Enter are all valid delimiters for new tags,
+                    // except when there is an open quote or if setting allowSpaces = true.
+                    // Tab will also create a tag, unless the tag input is empty, in which case it isn't caught.
+                    if (
+                        event.which == $.ui.keyCode.COMMA ||
+                        event.which == $.ui.keyCode.ENTER ||
+                        (
+                            event.which == $.ui.keyCode.TAB &&
+                            that._tagInput.val() !== ''
+                        ) ||
+                        (
+                            event.which == $.ui.keyCode.SPACE &&
+                            that.options.allowSpaces !== true &&
+                            (
+                                $.trim(that._tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
+                                (
+                                    $.trim(that._tagInput.val()).charAt(0) == '"' &&
+                                    $.trim(that._tagInput.val()).charAt($.trim(that._tagInput.val()).length - 1) == '"' &&
+                                    $.trim(that._tagInput.val()).length - 1 !== 0
+                                )
+                            )
+                        )
+                    ) {
+                        event.preventDefault();
+                        that.createTag(that._cleanedInput());
+
+                        // The autocomplete doesn't close automatically when TAB is pressed.
+                        // So let's ensure that it closes.
+                        that._tagInput.autocomplete('close');
+                    }
+                }).blur(function(e){
+                    // Create a tag when the element loses focus (unless it's empty).
+                    that.createTag(that._cleanedInput());
+                });
+                
+
+            // Autocomplete.
+            if (this.options.availableTags || this.options.tagSource) {
+                this._tagInput.autocomplete({
+                    source: this.options.tagSource,
+                    select: function(event, ui) {
+                        // Delete the last tag if we autocomplete something despite the input being empty
+                        // This happens because the input's blur event causes the tag to be created when
+                        // the user clicks an autocomplete item.
+                        // The only artifact of this is that while the user holds down the mouse button
+                        // on the selected autocomplete item, a tag is shown with the pre-autocompleted text,
+                        // and is changed to the autocompleted text upon mouseup.
+                        if (that._tagInput.val() === '') {
+                            that.removeTag(that._lastTag(), false);
+                        }
+                        that.createTag(ui.item.value);
+                        // Preventing the tag input to be updated with the chosen value.
+                        return false;
+                    }
+                });
+            }
+        },
+
+        _cleanedInput: function() {
+            // Returns the contents of the tag input, cleaned and ready to be passed to createTag
+            return $.trim(this._tagInput.val().replace(/^"(.*)"$/, '$1'));
+        },
+
+        _lastTag: function() {
+            return this.tagList.children('.tagit-choice:last');
+        },
+
+        assignedTags: function() {
+            // Returns an array of tag string values
+            var that = this;
+            var tags = [];
+            if (this.options.singleField) {
+                tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);
+                if (tags[0] === '') {
+                    tags = [];
+                }
+            } else {
+                this.tagList.children('.tagit-choice').each(function() {
+                    tags.push(that.tagLabel(this));
+                });
+            }
+            return tags;
+        },
+
+        _updateSingleTagsField: function(tags) {
+            // Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter
+            $(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter));
+        },
+
+        _subtractArray: function(a1, a2) {
+            var result = [];
+            for (var i = 0; i < a1.length; i++) {
+                if ($.inArray(a1[i], a2) == -1) {
+                    result.push(a1[i]);
+                }
+            }
+            return result;
+        },
+
+        tagLabel: function(tag) {
+            // Returns the tag's string label.
+            if (this.options.singleField) {
+                return $(tag).children('.tagit-label').text();
+            } else {
+                return $(tag).children('input').val();
+            }
+        },
+
+        _isNew: function(value) {
+            var that = this;
+            var isNew = true;
+            this.tagList.children('.tagit-choice').each(function(i) {
+                if (that._formatStr(value) == that._formatStr(that.tagLabel(this))) {
+                    isNew = false;
+                    return false;
+                }
+            });
+            return isNew;
+        },
+
+        _formatStr: function(str) {
+            if (this.options.caseSensitive) {
+                return str;
+            }
+            return $.trim(str.toLowerCase());
+        },
+
+        createTag: function(value, additionalClass) {
+            var that = this;
+            // Automatically trims the value of leading and trailing whitespace.
+            value = $.trim(value);
+
+            if (!this._isNew(value) || value === '') {
+                return false;
+            }
+
+            var label = $(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(value);
+
+            // Create tag.
+            var tag = $('<li></li>')
+                .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
+                .addClass(additionalClass)
+                .append(label);
+
+            // Button for removing the tag.
+            var removeTagIcon = $('<span></span>')
+                .addClass('ui-icon ui-icon-close');
+            var removeTag = $('<a><span class="text-icon">\xd7</span></a>') // \xd7 is an X
+                .addClass('tagit-close')
+                .append(removeTagIcon)
+                .click(function(e) {
+                    // Removes a tag when the little 'x' is clicked.
+                    that.removeTag(tag);
+                });
+            tag.append(removeTag);
+
+            // Unless options.singleField is set, each tag has a hidden input field inline.
+            if (this.options.singleField) {
+                var tags = this.assignedTags();
+                tags.push(value);
+                this._updateSingleTagsField(tags);
+            } else {
+                var escapedValue = label.html();
+                tag.append('<input type="hidden" style="display:none;" value="' + escapedValue + '" name="' + this.options.itemName + '[' + this.options.fieldName + '][]" />');
+            }
+
+            this._trigger('onTagAdded', null, tag);
+
+            // Cleaning the input.
+            this._tagInput.val('');
+
+            // insert tag
+            this._tagInput.parent().before(tag);
+        },
+        
+        removeTag: function(tag, animate) {
+            animate = animate || this.options.animate;
+
+            tag = $(tag);
+
+            this._trigger('onTagRemoved', null, tag);
+
+            if (this.options.singleField) {
+                var tags = this.assignedTags();
+                var removedTagLabel = this.tagLabel(tag);
+                tags = $.grep(tags, function(el){
+                    return el != removedTagLabel;
+                });
+                this._updateSingleTagsField(tags);
+            }
+            // Animate the removal.
+            if (animate) {
+                tag.fadeOut('fast').hide('blind', {direction: 'horizontal'}, 'fast', function(){
+                    tag.remove();
+                }).dequeue();
+            } else {
+                tag.remove();
+            }
+        },
+
+        removeAll: function() {
+            // Removes all tags.
+            var that = this;
+            this.tagList.children('.tagit-choice').each(function(index, tag) {
+                that.removeTag(tag, false);
+            });
+        }
+
+    });
+
+})(jQuery);
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/assets/stylesheets/jquery.tagit.css	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,54 @@
+ul.tagit {
+    padding: 1px 5px;
+    overflow: auto;
+    margin-left: inherit; /* usually we don't want the regular ul margins. */
+    margin-right: inherit;
+}
+ul.tagit li {
+    display: block;
+    float: left;
+    margin: 2px 5px 2px 0;
+}
+ul.tagit li.tagit-choice {
+    padding: .2em 18px .2em .5em;
+    position: relative;
+    line-height: inherit;
+}
+ul.tagit li.tagit-new {
+    padding: .25em 4px .25em 0;
+}
+
+ul.tagit li.tagit-choice a.tagit-label {
+    cursor: pointer;
+    text-decoration: none;
+}
+ul.tagit li.tagit-choice .tagit-close {
+    cursor: pointer;
+    position: absolute;
+    right: .1em;
+    top: 50%;
+    margin-top: -8px;
+}
+
+/* used for some custom themes that don't need image icons */
+ul.tagit li.tagit-choice .tagit-close .text-icon {
+    display: none;
+}
+
+ul.tagit li.tagit-choice input {
+    display: block;
+    float: left;
+    margin: 2px 5px 2px 0;
+}
+ul.tagit input[type="text"] {
+    -moz-box-sizing:    border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing:         border-box;
+
+    border: none;
+    margin: 0;
+    padding: 0;
+    width: inherit;
+    background-color: inherit;
+    outline: none;
+}
--- a/plugins/redmine_tags/assets/stylesheets/redmine_tags.css	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/assets/stylesheets/redmine_tags.css	Wed Jan 09 12:41:39 2013 +0000
@@ -16,13 +16,12 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
- */
+*/
 
 ul.tags { list-style: none; padding: 0px; }
 ul.tags li { margin: .25em 0px; }
 
 div.tags { text-align: center; }
-div.tags h3 { text-align: left; }
 div.tags .tag-label { margin: .25em; }
 div.tags .tag-nube-1 { font-size: .8em; }
 div.tags .tag-nube-2 { font-size: .9em; }
@@ -35,6 +34,24 @@
 
 .tag-count { font-size: .75em; margin-left: .5em; }
 
+.tagit.ui-widget {
+	font-size: 1em;
+	margin: 0px;
+}
+
+ul.tagit li.tagit-choice {
+	color: #505050;
+	font-weight: normal;
+}
+
+ul.tagit li.tagit-choice:hover, ul.tagit li.tagit-choice.remove {
+	border-color: #6D95E0;
+}
+
+ul.tagit input[type="text"] {
+	background: transparent;
+}
+
 ul.projects .tags, ul.projects .no-tags { padding-left: 0.5em; color: #3e442c; font-size: 0.95em }
 table.projects th.tags { color: #3e442c; }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/config/locales/bg.yml	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,41 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# Bulgarian translation for redmine_tags
+# by Ivan Cenov (jwalker_@Skype) i_cenov@botevgrad.com
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+bg:
+  tags: Маркери
+  field_tags: Маркери
+  field_tag_list: Маркери
+  setting_issue_tags: Маркери на задачите
+  issues_sidebar: Показване на страничния панел като
+  issues_show_count: Показване на броя на задачите
+  issues_open_only: Само отворените задачи
+  issues_sort_by: Подреждане на маркерите по
+
+  issue_tags_sidebar_none: Да не се показват
+  issue_tags_sidebar_list: Списък
+  issue_tags_sidebar_cloud: Облак
+
+  issues_sort_by_name: Наименование
+  issues_sort_by_count: Количество задачи
+  issues_sort_order_asc: Нарастване
+  issues_sort_order_desc: Намаляване
+
+  auto_complete_new_tag: Добавяне на нов...
--- a/plugins/redmine_tags/config/locales/de.yml	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/config/locales/de.yml	Wed Jan 09 12:41:39 2013 +0000
@@ -2,9 +2,11 @@
 # redMine plugin, that adds tagging support.
 #
 # German translation for redmine_tags
-# by Terence Miller aka cforce, <cforce(at)gmx.de>
+# by: Terence Miller AKA cforce, <cforce(at)gmx.de>,
+#     Jörg Jans
 #
-# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+# Copyright (c) 2010 Terence Miller AKA cforce
+# Copyright (c) 2010 Jörg Jans
 #
 # redmine_tags is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,16 +22,22 @@
 # along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
 
 de:
- tags: Tags
- field_tags: Tags
- field_tag_list: Tags
- setting_issue_tags: Ticket Tags
- issues_sidebar: Zeige die Tags auf der Sidebar
- issues_show_count: Zeige die Ticketanzahl an
- issues_open_only: Zeige nur noch offene Tickets
+  tags: Tags
+  field_tags: Tags
+  field_tag_list: Tags
+  setting_issue_tags: Ticket Tags
+  issues_sidebar: Zeige die Tags auf der Sidebar
+  issues_show_count: Zeige die Ticketanzahl an
+  issues_open_only: Zeige nur noch offene Tickets
+  issues_sort_by: Sortiere Tags nach
 
- issue_tags_sidebar_none: Keine
- issue_tags_sidebar_list: Liste
- issue_tags_sidebar_cloud: Cloud
+  issue_tags_sidebar_none: Keine
+  issue_tags_sidebar_list: Liste
+  issue_tags_sidebar_cloud: Cloud
 
- auto_complete_new_tag: Hinzufügen...
+  issues_sort_by_name: Name
+  issues_sort_by_count: Anzahl tickets
+  issues_sort_order_asc: Aufsteigend
+  issues_sort_order_desc: Absteigend
+
+  auto_complete_new_tag: Hinzufügen...
--- a/plugins/redmine_tags/config/locales/en.yml	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/config/locales/en.yml	Wed Jan 09 12:41:39 2013 +0000
@@ -23,23 +23,26 @@
   tags: Tags
   field_tags: Tags
   field_tag_list: Tags
-  field_no_tags: "No tags"
-  label_tags_search: "Tags: "
   setting_issue_tags: Issues Tags
   issues_sidebar: Display tags on sidebar as
   issues_show_count: Display amount of issues
   issues_open_only: Display open issues only
+  issues_sort_by: Sort tags by
   
   issue_tags_sidebar_none: None
   issue_tags_sidebar_list: List
   issue_tags_sidebar_cloud: Cloud
 
+  issues_sort_by_name: Name
+  issues_sort_by_count: Issues amount
+  issues_sort_order_asc: Ascending
+  issues_sort_order_desc: Descending
+
   auto_complete_new_tag: Add new...
-  
+
   project_filtering_q_label: "Search for text:"
   project_filter_no_results: "No matching projects found"
   button_filter: "Filter"
   
   text_tags_info: "A tag can be any text you like, but they're most useful if you choose tags that are already being used for the same thing by other projects (where possible). <br />Some tag examples are: library, plugin, paper, c++, mir, alpha, stable, bsd, android, ...<br />Tags help others find your work: please don't forget to tag your projects!"
 
-
--- a/plugins/redmine_tags/config/locales/fr.yml	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/config/locales/fr.yml	Wed Jan 09 12:41:39 2013 +0000
@@ -2,9 +2,9 @@
 # redMine plugin, that adds tagging support.
 #
 # French translation for redmine_tags
-# by Stphane HANNEQUIN, <stephane.hannequin(at)aster-ingenierie.com>
+# by Stephane HANNEQUIN, <stephane.hannequin(at)aster-ingenierie.com>
 #
-# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+# Copyright (c) 2010 Stephane HANNEQUIN
 #
 # redmine_tags is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -27,9 +27,15 @@
   issues_sidebar: Afficher les Tags comme
   issues_show_count: Afficher le nombre de demande
   issues_open_only: N'afficher que les demandes ouvertes
+  issues_sort_by: (en) Sort tags by
   
   issue_tags_sidebar_none: Ne pas afficher
   issue_tags_sidebar_list: Liste
   issue_tags_sidebar_cloud: Nuage
 
+  issues_sort_by_name: (en) Name
+  issues_sort_by_count: (en) Issues amount
+  issues_sort_order_asc: (en) Ascending
+  issues_sort_order_desc: (en) Descending
+
   auto_complete_new_tag: Nouveau...
--- a/plugins/redmine_tags/config/locales/ru.yml	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/config/locales/ru.yml	Wed Jan 09 12:41:39 2013 +0000
@@ -27,9 +27,15 @@
   issues_sidebar: Боковую панель как
   issues_show_count: Показать кол-во задач
   issues_open_only: Только открытые задачи
+  issues_sort_by: Упорядочить метки по
   
   issue_tags_sidebar_none: Не показывать
   issue_tags_sidebar_list: Список
   issue_tags_sidebar_cloud: Облако
 
+  issues_sort_by_name: Названию
+  issues_sort_by_count: Кол-ву задач
+  issues_sort_order_asc: по возрастанию
+  issues_sort_order_desc: по убыванию
+
   auto_complete_new_tag: Добавить...
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/config/locales/zh.yml	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,41 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# Simplified Chinese translation for redmine_tags
+# by archonwang (https://github.com/archonwang)
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+zh:
+  tags: 标签
+  field_tags: 标签
+  field_tag_list: 标签
+  setting_issue_tags: 问题标签
+  issues_sidebar: 在侧边栏显示标签
+  issues_show_count: 显示问题计数
+  issues_open_only: 仅显示打开的问题
+  issues_sort_by: 标签按何种方式排序
+
+  issue_tags_sidebar_none: 无
+  issue_tags_sidebar_list: 列表显示
+  issue_tags_sidebar_cloud: 云显示
+
+  issues_sort_by_name: 名称
+  issues_sort_by_count: 问题计数
+  issues_sort_order_asc: 顺序
+  issues_sort_order_desc: 倒序
+
+  auto_complete_new_tag: 添加新标签 ... ...
--- a/plugins/redmine_tags/config/routes.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/config/routes.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -1,3 +1,4 @@
 RedmineApp::Application.routes.draw do
+  match '/issue_tags/auto_complete/:project_id', :to => 'auto_completes#issue_tags', :via => :get, :as => 'auto_complete_issue_tags'
   match 'projects/set_fieldset_status' => 'projects#set_fieldset_status', :constraints => {:method => :post}
-end
\ No newline at end of file
+end
--- a/plugins/redmine_tags/init.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/init.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -17,21 +17,25 @@
 # along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
 
 require 'redmine'
+require 'redmine_tags'
+
 
 Redmine::Plugin.register :redmine_tags do
   name        'redmine_tags'
   author      'Aleksey V Zapparov AKA "ixti"'
   description 'redMine tagging support'
-  version     '1.1.4'
-  url         'http://www.ixti.ru/'
-  author_url  'http://www.ixti.ru/'
+  version     '2.0.1-dev'
+  url         'https://github.com/ixti/redmine_tags/'
+  author_url  'http://www.ixti.net/'
 
-  requires_redmine :version_or_higher => '1.0.0'
+  requires_redmine :version_or_higher => '1.2.0'
 
   settings :default => {
     :issues_sidebar => 'none',
     :issues_show_count => 0,
-    :issues_open_only => 0
+    :issues_open_only => 0,
+    :issues_sort_by => 'name',
+    :issues_sort_order => 'asc'
   }, :partial => 'tags/settings'
 end
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -0,0 +1,21 @@
+# This file is a part of redmine_tags
+# redMine plugin, that adds tagging support.
+#
+# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
+#
+# redmine_tags is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# redmine_tags is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
+
+module RedmineTags
+  def self.settings() Setting[:plugin_redmine_tags] end
+end
--- a/plugins/redmine_tags/lib/redmine_tags/hooks/model_issue_hook.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags/hooks/model_issue_hook.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -24,6 +24,10 @@
         save_tags_to_issue(context, true)
       end
 
+      def controller_issues_bulk_edit_before_save(context={})
+        save_tags_to_issue(context, true)
+      end
+
       # Issue has an after_save method that calls reload (update_nested_set_attributes)
       # This makes it impossible for a new record to get a tag_list, it's
       # cleared on reload. So instead, hook in after the Issue#save to update
--- a/plugins/redmine_tags/lib/redmine_tags/hooks/views_issues_hook.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags/hooks/views_issues_hook.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -22,6 +22,7 @@
       render_on :view_issues_show_details_bottom, :partial => 'issues/tags'
       render_on :view_issues_form_details_bottom, :partial => 'issues/tags_form'
       render_on :view_issues_sidebar_planning_bottom, :partial => 'issues/tags_sidebar'
+      render_on :view_issues_bulk_edit_details_bottom, :partial => 'issues/tags_form'
     end
   end
 end
--- a/plugins/redmine_tags/lib/redmine_tags/patches/auto_completes_controller_patch.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags/patches/auto_completes_controller_patch.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -23,13 +23,20 @@
     module AutoCompletesControllerPatch
       def self.included(base)
         base.send(:include, InstanceMethods)
+
+        base.class_eval do
+          unloadable
+        end
       end
 
 
       module InstanceMethods
         def issue_tags
           @name = params[:q].to_s
-          @tags = Issue.available_tags :project_id => @project, :name_like => @name
+          @tags = Issue.available_tags({
+            :project_id => @project,
+            :name_like => @name
+          })
           render :layout => false, :partial => 'tag_list'
         end
 
--- a/plugins/redmine_tags/lib/redmine_tags/patches/issue_patch.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags/patches/issue_patch.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -27,10 +27,25 @@
         base.class_eval do
           unloadable
           acts_as_taggable
+
+          scope :on_project, lambda { |project|
+            project = project.id if project.is_a? Project
+            { :conditions => ["#{Project.table_name}.id=?", project] }
+          }
+
+          Issue.safe_attributes 'tag_list'
         end
       end
 
       module ClassMethods
+        TAGGING_IDS_LIMIT_SQL = <<-SQL
+          tag_id IN (
+            SELECT #{ActsAsTaggableOn::Tagging.table_name}.tag_id
+              FROM #{ActsAsTaggableOn::Tagging.table_name}
+             WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)
+          )
+        SQL
+
         # Returns available issue tags
         # === Parameters
         # * <i>options</i> = (optional) Options hash of
@@ -38,30 +53,22 @@
         #   * open_only - Boolean. Whenever search within open issues only.
         #   * name_like - String. Substring to filter found tags.
         def available_tags(options = {})
-          project   = options[:project]
-          open_only = options[:open_only]
-          name_like = options[:name_like]
-          options   = {}
-          visible   = ARCondition.new
-          
-          if project
-            project = project.id if project.is_a? Project
-            visible << ["#{Issue.table_name}.project_id = ?", project]
+          ids_scope = Issue.visible
+          ids_scope = ids_scope.on_project(options[:project]) if options[:project]
+          ids_scope = ids_scope.open if options[:open_only]
+
+          conditions = [""]
+
+          # limit to the tags matching given %name_like%
+          if options[:name_like]
+            conditions[0] << "#{ActsAsTaggableOn::Tag.table_name}.name LIKE ? AND "
+            conditions << "%#{options[:name_like].downcase}%"
           end
 
-          if open_only
-            visible << ["#{Issue.table_name}.status_id IN " +
-                        "( SELECT issue_status.id " + 
-                        "    FROM #{IssueStatus.table_name} issue_status " +
-                        "   WHERE issue_status.is_closed = ? )", false]
-          end
+          conditions[0] << TAGGING_IDS_LIMIT_SQL
+          conditions << ids_scope.map{ |issue| issue.id }.push(-1)
 
-          if name_like
-            visible << ["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", "%#{name_like.downcase}%"]
-          end
-
-          options[:conditions] = visible.conditions
-          self.all_tag_counts(options)
+          self.all_tag_counts(:conditions => conditions)
         end
       end
     end
--- a/plugins/redmine_tags/lib/redmine_tags/patches/issues_helper_patch.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags/patches/issues_helper_patch.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -1,20 +1,3 @@
-# This file is a part of redmine_tags
-# redMine plugin, that adds tagging support.
-#
-# Copyright (c) 2010 Aleksey V Zapparov AKA ixti
-#
-# redmine_tags is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# redmine_tags is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with redmine_tags.  If not, see <http://www.gnu.org/licenses/>.
 
 require_dependency 'issues_helper'
 
@@ -28,27 +11,25 @@
       module InstanceMethods
         include TagsHelper
 
-        def redmine_tags_settings
-            @redmine_tags_settings = Setting.plugin_redmine_tags unless @redmine_tags_settings
-            @redmine_tags_settings
-        end
-
         def sidebar_tags
           unless @sidebar_tags
             @sidebar_tags = []
-            if :none != redmine_tags_settings[:issues_sidebar].to_sym
-              @sidebar_tags = Issue.available_tags(:project => @project,
-                                                   :open_only => (redmine_tags_settings[:issues_open_only].to_i == 1))
+            if :none != RedmineTags.settings[:issues_sidebar].to_sym
+              @sidebar_tags = Issue.available_tags({
+                :project => @project,
+                :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1)
+              })
             end
           end
           @sidebar_tags
         end
 
         def render_sidebar_tags
-          render_tags_list(sidebar_tags,
-                          :show_count => (redmine_tags_settings[:issues_show_count].to_i == 1),
-                          :open_only => (redmine_tags_settings[:issues_open_only].to_i == 1),
-                          :style => redmine_tags_settings[:issues_sidebar].to_sym)
+          render_tags_list(sidebar_tags, {
+            :show_count => (RedmineTags.settings[:issues_show_count].to_i == 1),
+            :open_only => (RedmineTags.settings[:issues_open_only].to_i == 1),
+            :style => RedmineTags.settings[:issues_sidebar].to_sym
+          })
         end
       end
     end
--- a/plugins/redmine_tags/lib/redmine_tags/patches/queries_helper_patch.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags/patches/queries_helper_patch.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -25,6 +25,7 @@
         base.send(:include, InstanceMethods)
 
         base.class_eval do
+          unloadable
           alias_method :column_content_original, :column_content
           alias_method :column_content, :column_content_extended
         end
--- a/plugins/redmine_tags/lib/redmine_tags/patches/query_patch.rb	Wed Jan 09 11:53:24 2013 +0000
+++ b/plugins/redmine_tags/lib/redmine_tags/patches/query_patch.rb	Wed Jan 09 12:41:39 2013 +0000
@@ -41,16 +41,26 @@
       module InstanceMethods
         def statement_extended
           filter  = filters.delete 'tags'
-          clauses = statement_original
+          clauses = statement_original || ""
 
           if filter
             filters.merge!( 'tags' => filter )
 
-            values    = values_for('tags').clone
-            compare   = operator_for('tags').eql?('=') ? 'IN' : 'NOT IN'
-            ids_list  = Issue.tagged_with(values).collect{ |issue| issue.id }.push(0).join(',')
+            op = operator_for('tags')
+            case op
+            when '=', '!'
+              issues = Issue.tagged_with(values_for('tags').clone)
+            when '!*'
+              issues = Issue.tagged_with(ActsAsTaggableOn::Tag.all.map(&:to_s), :exclude => true)
+            else
+              issues = Issue.tagged_with(ActsAsTaggableOn::Tag.all.map(&:to_s), :any => true)
+            end
 
-            clauses << " AND ( #{Issue.table_name}.id #{compare} (#{ids_list}) ) "
+            compare   = op.eql?('!') ? 'NOT IN' : 'IN'
+            ids_list  = issues.collect{ |issue| issue.id }.push(0).join(',')
+
+            clauses << " AND " unless clauses.empty?
+            clauses << "( #{Issue.table_name}.id #{compare} (#{ids_list}) ) "
           end
 
           clauses
@@ -60,7 +70,8 @@
         def available_filters_extended
           unless @available_filters 
             available_filters_original.merge!({ 'tags' => {
-              :type   => :list,
+              :name   => l(:tags),
+              :type   => :list_optional,
               :order  => 6,
               :values => Issue.available_tags(:project => project).collect{ |t| [t.name, t.name] }
             }})