Add tag functionality as provider for other plugins
This commit is contained in:
parent
1f41f55097
commit
80c0d5c821
121
app/helpers/additionals_tag_helper.rb
Normal file
121
app/helpers/additionals_tag_helper.rb
Normal file
@ -0,0 +1,121 @@
|
||||
module AdditionalsTagHelper
|
||||
# Renders list of tags
|
||||
# Clouds are rendered as block <tt>div</tt> with internal <tt>span</t> per tag.
|
||||
# Lists are rendered as unordered lists <tt>ul</tt>. Lists are ordered by
|
||||
# <tt>tag.count</tt> descending.
|
||||
# === Parameters
|
||||
# * <i>tags</i> = Array of Tag instances
|
||||
# * <i>options</i> = (optional) Options (override system settings)
|
||||
# * show_count - Boolean. Whenever show tag counts
|
||||
# * style - list, cloud
|
||||
def render_additionals_tags_list(tags, options = {})
|
||||
return if tags.blank?
|
||||
|
||||
content = ''
|
||||
style = options.delete(:style)
|
||||
|
||||
# prevent ActsAsTaggableOn::TagsHelper from calling `all`
|
||||
# otherwise we will need sort tags after `tag_cloud`
|
||||
tags = tags.all if tags.respond_to?(:all)
|
||||
|
||||
if style == :list
|
||||
list_el = 'ul'
|
||||
item_el = 'li'
|
||||
else
|
||||
list_el = 'div'
|
||||
item_el = 'span'
|
||||
tags = cloudify(tags)
|
||||
end
|
||||
|
||||
content = content.html_safe
|
||||
tag_cloud tags, (1..8).to_a do |tag, weight|
|
||||
content << ' '.html_safe + content_tag(item_el,
|
||||
render_additionals_tag_link(tag, options),
|
||||
class: "tag-pass-#{weight}",
|
||||
style: (style == :simple_cloud ? 'font-size: 1em;' : '')) + ' '.html_safe
|
||||
end
|
||||
|
||||
content_tag(list_el, content, class: 'tags', style: (style == :simple_cloud ? 'text-align: left;' : ''))
|
||||
end
|
||||
|
||||
def render_additionals_tag_link_line(tags)
|
||||
s = []
|
||||
tags.each do |tag|
|
||||
s << render_additionals_tag_link(tag)
|
||||
end
|
||||
safe_join(s, ', ')
|
||||
end
|
||||
|
||||
# Returns tag link
|
||||
# === Parameters
|
||||
# * <i>tag</i> = Instance of Tag
|
||||
# * <i>options</i> = (optional) Options (override system settings)
|
||||
# * show_count - Boolean. Whenever show tag counts
|
||||
def render_additionals_tag_link(tag, options = {})
|
||||
filters = [[:tags, '=', tag.name]]
|
||||
content = if options[:use_search]
|
||||
link_to(tag, controller: 'search', action: 'index', id: @project, q: tag.name, wiki_pages: true, issues: true)
|
||||
else
|
||||
additionals_link_to_filter(tag.name, filters, project_id: @project)
|
||||
end
|
||||
if options[:show_count]
|
||||
content << content_tag('span', " (#{tag.count})", class: 'tag-count')
|
||||
end
|
||||
|
||||
style = { class: 'tag-label-color', style: "background-color: #{tag_color(tag)}" }
|
||||
content_tag('span', content, style)
|
||||
end
|
||||
|
||||
def additionals_link_to_filter(title, filters, options = {})
|
||||
options.merge! additionals_link_to_filter_options(filters)
|
||||
link_to title, options
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tag_cloud(tags, classes)
|
||||
return [] if tags.empty?
|
||||
|
||||
max_count = tags.sort_by(&:count).last.count.to_f
|
||||
|
||||
tags.each do |tag|
|
||||
index = ((tag.count / max_count) * (classes.size - 1))
|
||||
yield tag, classes[index.nan? ? 0 : index.round]
|
||||
end
|
||||
end
|
||||
|
||||
def tag_color(tag)
|
||||
tag_name = tag.respond_to?(:name) ? tag.name : tag
|
||||
"##{Digest::MD5.hexdigest(tag_name)[0..5]}"
|
||||
end
|
||||
|
||||
def cloudify(tags)
|
||||
new_tags = []
|
||||
trigger = true
|
||||
tags.each do |tag|
|
||||
new_tags.send((trigger ? 'push' : 'unshift'), tag)
|
||||
trigger = !trigger
|
||||
end
|
||||
new_tags
|
||||
end
|
||||
|
||||
def additionals_link_to_filter_options(filters)
|
||||
options = {
|
||||
controller: controller_name,
|
||||
action: 'index',
|
||||
set_filter: 1,
|
||||
fields: [],
|
||||
values: {},
|
||||
operators: {}
|
||||
}
|
||||
|
||||
filters.each do |f|
|
||||
name, operator, value = f
|
||||
options[:fields].push(name)
|
||||
options[:operators][name] = operator
|
||||
options[:values][name] = [value]
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
end
|
52
app/models/additionals_tag.rb
Normal file
52
app/models/additionals_tag.rb
Normal file
@ -0,0 +1,52 @@
|
||||
class AdditionalsTag
|
||||
def self.get_available_tags(klass, options = {}, permission = nil)
|
||||
table_name = klass.table_name
|
||||
scope = RedmineCrm::Tag.where({})
|
||||
scope = scope.where("#{Project.table_name}.id = ?", options[:project]) if options[:project]
|
||||
scope = scope.where(tag_access(permission))
|
||||
scope = scope.where("LOWER(#{RedmineCrm::Tag.table_name}.name) LIKE ?", "%#{options[:name_like].downcase}%") if options[:name_like]
|
||||
|
||||
joins = []
|
||||
joins << "JOIN #{RedmineCrm::Tagging.table_name} " \
|
||||
"ON #{RedmineCrm::Tagging.table_name}.tag_id = #{RedmineCrm::Tag.table_name}.id "
|
||||
joins << "JOIN #{table_name} " \
|
||||
"ON #{table_name}.id = #{RedmineCrm::Tagging.table_name}.taggable_id " \
|
||||
"AND #{RedmineCrm::Tagging.table_name}.taggable_type = '#{klass}' "
|
||||
joins << "JOIN #{Project.table_name} ON #{table_name}.project_id = #{Project.table_name}.id "
|
||||
|
||||
scope = scope.select("#{RedmineCrm::Tag.table_name}.*, " \
|
||||
"COUNT(DISTINCT #{RedmineCrm::Tagging.table_name}.taggable_id) AS count")
|
||||
scope = scope.joins(joins.flatten)
|
||||
scope = scope.group("#{RedmineCrm::Tag.table_name}.id, #{RedmineCrm::Tag.table_name}.name").having('COUNT(*) > 0')
|
||||
scope = scope.order("#{RedmineCrm::Tag.table_name}.name")
|
||||
scope
|
||||
end
|
||||
|
||||
def self.tag_access(permission)
|
||||
cond = ''
|
||||
projects_allowed = if permission.nil?
|
||||
Project.visible.pluck(:id)
|
||||
else
|
||||
Project.where(Project.allowed_to_condition(User.current, permission)).pluck(:id)
|
||||
end
|
||||
unless projects_allowed.empty?
|
||||
cond << "#{Project.table_name}.id IN (#{projects_allowed.join(',')})"
|
||||
end
|
||||
cond
|
||||
end
|
||||
|
||||
def self.remove_unused_tags
|
||||
unused = RedmineCrm::Tag.find_by_sql(<<-SQL)
|
||||
SELECT * FROM tags WHERE id NOT IN (
|
||||
SELECT DISTINCT tag_id FROM taggings
|
||||
)
|
||||
SQL
|
||||
unused.each(&:destroy)
|
||||
end
|
||||
|
||||
def self.sql_for_tags_field(klass, operator, value)
|
||||
compare = operator.eql?('=') ? 'IN' : 'NOT IN'
|
||||
ids_list = klass.tagged_with(value).map(&:id).push(0).join(',')
|
||||
"( #{klass.table_name}.id #{compare} (#{ids_list}) ) "
|
||||
end
|
||||
end
|
5
app/views/additionals/_tag_list.html.erb
Normal file
5
app/views/additionals/_tag_list.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<% unless entry.tag_list.empty? %>
|
||||
<div class="tags attribute"><span class="label"><%= l(:field_tag_list) %>:</span>
|
||||
<%= safe_join(entry.tag_counts.map { |t| render_additionals_tag_link(t, show_count: false) }, ' ') %>
|
||||
</div>
|
||||
<% end %>
|
15
app/views/auto_completes/_additionals_tag.html.erb
Normal file
15
app/views/auto_completes/_additionals_tag.html.erb
Normal file
@ -0,0 +1,15 @@
|
||||
<div id="<%= name %>_tag_candidates" class="autocomplete"></div>
|
||||
<%= javascript_tag do %>
|
||||
$('<%= id %>').tagit({
|
||||
tagSource: function(search, showChoices) {
|
||||
var that = this;
|
||||
$.ajax({
|
||||
url: '<%= send("auto_complete_#{name}_tags_url", @project) %>',
|
||||
data: {q: search.term},
|
||||
success: function(choices) {
|
||||
showChoices(that._subtractArray(jQuery.parseJSON(choices), that.assignedTags()));
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
<% end %>
|
4
app/views/auto_completes/_additionals_tag_list.html.erb
Normal file
4
app/views/auto_completes/_additionals_tag_list.html.erb
Normal file
@ -0,0 +1,4 @@
|
||||
<%= raw @tags.map {|tag|
|
||||
tag.name
|
||||
}.to_json
|
||||
%>
|
4
app/views/auto_completes/_additionals_tag_list.json.erb
Normal file
4
app/views/auto_completes/_additionals_tag_list.json.erb
Normal file
@ -0,0 +1,4 @@
|
||||
<%= raw @tags.map {|tag|
|
||||
tag.name
|
||||
}.to_json
|
||||
%>
|
@ -150,3 +150,60 @@
|
||||
.smiley-failure {
|
||||
background-image: url(../images/smileys/failure-frame.png);
|
||||
}
|
||||
|
||||
/* TAGs */
|
||||
|
||||
ul.tags {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tag-label-color {
|
||||
padding: 2px 4px;
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.5);
|
||||
font-size: 10px !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.tag-label-color a::before {
|
||||
font-family: FontAwesome;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
content: "\f02b"; /* fa-tag */
|
||||
padding-right: 3px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
ul.tags li { margin: 0.25em 0; }
|
||||
|
||||
div.tags .tag-pass-1 { font-size: 0.8em; }
|
||||
div.tags .tag-pass-2 { font-size: 0.9em; }
|
||||
div.tags .tag-pass-3 { font-size: 1em; }
|
||||
div.tags .tag-pass-4 { font-size: 1.1em; }
|
||||
div.tags .tag-pass-5 { font-size: 1.2em; }
|
||||
div.tags .tag-pass-6 { font-size: 1.3em; }
|
||||
div.tags .tag-pass-7 { font-size: 1.4em; }
|
||||
div.tags .tag-pass-8 { font-size: 1.5em; }
|
||||
|
||||
.tag-count { font-size: 0.75em; }
|
||||
.tag-label-color a { color: white; }
|
||||
|
||||
.tagit.ui-widget {
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -130,3 +130,6 @@ de:
|
||||
permission_edit_issue_author: Ticket Autor bearbeiten
|
||||
permission_change_new_issue_author: Autor bei Ticketerstellung festlegen
|
||||
label_issue_autowatch_involved: Involvierte Benutzer automatisch als Beobachter hinzufügen
|
||||
field_tag_list: Tags
|
||||
field_tags: Tags
|
||||
label_add_tags: Tags hinzufügen
|
||||
|
Loading…
x
Reference in New Issue
Block a user