Mercurial > hg > soundsoftware-site
changeset 1132:9b2f28ecd125 redmine-2.2-integration
RedmineTags plugin: merged exiting with the most up-to-date version from the repo.
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] } }})