mirror of
https://github.com/discourse/discourse.git
synced 2025-03-22 18:55:41 +08:00
Support any number of tag intersections
This commit is contained in:
parent
d3792c0149
commit
037e9bb7b8
@ -43,7 +43,7 @@ export default Ember.Controller.extend(BulkTopicSelection, {
|
|||||||
needs: ["application"],
|
needs: ["application"],
|
||||||
|
|
||||||
tag: null,
|
tag: null,
|
||||||
secondaryTag: null,
|
additionalTags: null,
|
||||||
list: null,
|
list: null,
|
||||||
canAdminTag: Ember.computed.alias("currentUser.staff"),
|
canAdminTag: Ember.computed.alias("currentUser.staff"),
|
||||||
filterMode: null,
|
filterMode: null,
|
||||||
@ -73,8 +73,8 @@ export default Ember.Controller.extend(BulkTopicSelection, {
|
|||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
showAdminControls: function() {
|
showAdminControls: function() {
|
||||||
return !this.get('secondaryTag') && this.get('canAdminTag') && !this.get('category');
|
return this.get('additionalTags') && this.get('canAdminTag') && !this.get('category');
|
||||||
}.property('secondaryTag', 'canAdminTag', 'category'),
|
}.property('additionalTags', 'canAdminTag', 'category'),
|
||||||
|
|
||||||
loadMoreTopics() {
|
loadMoreTopics() {
|
||||||
return this.get("list").loadMore();
|
return this.get("list").loadMore();
|
||||||
|
@ -132,7 +132,7 @@ export default function() {
|
|||||||
this.route('showCategory' + filter.capitalize(), {path: '/c/:category/:tag_id/l/' + filter});
|
this.route('showCategory' + filter.capitalize(), {path: '/c/:category/:tag_id/l/' + filter});
|
||||||
this.route('showParentCategory' + filter.capitalize(), {path: '/c/:parent_category/:category/:tag_id/l/' + filter});
|
this.route('showParentCategory' + filter.capitalize(), {path: '/c/:parent_category/:category/:tag_id/l/' + filter});
|
||||||
});
|
});
|
||||||
this.route('show', {path: 'intersection/:tag_id/:secondary_tag_id'});
|
this.route('show', {path: 'intersection/:tag_id/*additional_tags'});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resource('tagGroups', {path: '/tag_groups'}, function() {
|
this.resource('tagGroups', {path: '/tag_groups'}, function() {
|
||||||
|
@ -14,8 +14,11 @@ export default Discourse.Route.extend({
|
|||||||
var tag = this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.tag_id) }),
|
var tag = this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.tag_id) }),
|
||||||
f = '';
|
f = '';
|
||||||
|
|
||||||
if (params.secondary_tag_id) {
|
if (params.additional_tags) {
|
||||||
this.set("secondaryTag", this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.secondary_tag_id) }));
|
this.set("additionalTags", _.compact(params.additional_tags.split('/')).map((tag) => {
|
||||||
|
return this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(tag) });
|
||||||
|
}));
|
||||||
|
this.set("additionalTagIds", _.pluck(this.get("additionalTags"), 'id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.category) {
|
if (params.category) {
|
||||||
@ -50,7 +53,6 @@ export default Discourse.Route.extend({
|
|||||||
const parentCategorySlug = this.get('parentCategorySlug');
|
const parentCategorySlug = this.get('parentCategorySlug');
|
||||||
const filter = this.get('navMode');
|
const filter = this.get('navMode');
|
||||||
const tag_id = (tag ? tag.id : 'none');
|
const tag_id = (tag ? tag.id : 'none');
|
||||||
const secondary_tag_id = this.get('secondaryTag.id');
|
|
||||||
|
|
||||||
if (categorySlug) {
|
if (categorySlug) {
|
||||||
var category = Discourse.Category.findBySlug(categorySlug, parentCategorySlug);
|
var category = Discourse.Category.findBySlug(categorySlug, parentCategorySlug);
|
||||||
@ -61,8 +63,8 @@ export default Discourse.Route.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.set('category', category);
|
this.set('category', category);
|
||||||
} else if (secondary_tag_id) {
|
} else if (_.any(this.get("additionalTags"))) {
|
||||||
params.filter = `tags/intersection/${tag_id}/${secondary_tag_id}`;
|
params.filter = `tags/intersection/${tag_id}/${this.get('additionalTagIds').join('/')}`;
|
||||||
this.set('category', null);
|
this.set('category', null);
|
||||||
} else {
|
} else {
|
||||||
params.filter = `tags/${tag_id}/l/${filter}`;
|
params.filter = `tags/${tag_id}/l/${filter}`;
|
||||||
@ -102,7 +104,7 @@ export default Discourse.Route.extend({
|
|||||||
this.controllerFor('tags.show').setProperties({
|
this.controllerFor('tags.show').setProperties({
|
||||||
model,
|
model,
|
||||||
tag: model,
|
tag: model,
|
||||||
secondaryTag: this.get('secondaryTag'),
|
additionalTags: this.get('additionalTags'),
|
||||||
category: this.get('category'),
|
category: this.get('category'),
|
||||||
filterMode: this.get('filterMode'),
|
filterMode: this.get('filterMode'),
|
||||||
navMode: this.get('navMode'),
|
navMode: this.get('navMode'),
|
||||||
@ -132,7 +134,7 @@ export default Discourse.Route.extend({
|
|||||||
// Pre-fill the tags input field
|
// Pre-fill the tags input field
|
||||||
if (controller.get('model.id')) {
|
if (controller.get('model.id')) {
|
||||||
var c = self.controllerFor('composer').get('model');
|
var c = self.controllerFor('composer').get('model');
|
||||||
c.set('tags', _.compact([controller.get('model.id'), controller.get('secondaryTag.id')]));
|
c.set('tags', _.flatten([controller.get('model.id')], controller.get('additionalTagIds')));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="list-controls">
|
<div class="list-controls">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{{#if tagNotification}}
|
{{#if tagNotification}}
|
||||||
{{#unless secondaryTag}}
|
{{#unless additionalTags}}
|
||||||
{{tag-notifications-button action="changeTagNotification"
|
{{tag-notifications-button action="changeTagNotification"
|
||||||
notificationLevel=tagNotification.notification_level}}
|
notificationLevel=tagNotification.notification_level}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
@ -33,10 +33,10 @@
|
|||||||
{{#link-to 'tags'}}{{i18n "tagging.tags"}}{{/link-to}}
|
{{#link-to 'tags'}}{{i18n "tagging.tags"}}{{/link-to}}
|
||||||
{{fa-icon "angle-right"}}
|
{{fa-icon "angle-right"}}
|
||||||
{{discourse-tag-bound tagRecord=tag style="simple"}}
|
{{discourse-tag-bound tagRecord=tag style="simple"}}
|
||||||
{{#if secondaryTag}}
|
{{#each additionalTags as |tag|}}
|
||||||
<span>&</span>
|
<span>&</span>
|
||||||
{{discourse-tag-bound tagRecord=secondaryTag style="simple"}}
|
{{discourse-tag-bound tagRecord=tag style="simple"}}
|
||||||
{{/if}}
|
{{/each}}
|
||||||
</h2>
|
</h2>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +44,7 @@ class TagsController < ::ApplicationController
|
|||||||
Discourse.filters.each do |filter|
|
Discourse.filters.each do |filter|
|
||||||
define_method("show_#{filter}") do
|
define_method("show_#{filter}") do
|
||||||
@tag_id = DiscourseTagging.clean_tag(params[:tag_id])
|
@tag_id = DiscourseTagging.clean_tag(params[:tag_id])
|
||||||
@secondary_tag_id = DiscourseTagging.clean_tag(params[:secondary_tag_id]) if params[:secondary_tag_id]
|
@additional_tags = params[:additional_tag_ids].to_s.split('/').map { |tag| DiscourseTagging.clean_tag(tag) }
|
||||||
|
|
||||||
page = params[:page].to_i
|
page = params[:page].to_i
|
||||||
list_opts = build_topic_list_options
|
list_opts = build_topic_list_options
|
||||||
@ -72,7 +72,7 @@ class TagsController < ::ApplicationController
|
|||||||
@list.more_topics_url = construct_url_with(:next, list_opts)
|
@list.more_topics_url = construct_url_with(:next, list_opts)
|
||||||
@list.prev_topics_url = construct_url_with(:prev, list_opts)
|
@list.prev_topics_url = construct_url_with(:prev, list_opts)
|
||||||
@rss = "tag"
|
@rss = "tag"
|
||||||
@description_meta = I18n.t("rss_by_tag", tag: [@tag_id, @secondary_tag_id].compact.join(' & '))
|
@description_meta = I18n.t("rss_by_tag", tag: tag_params.join(' & '))
|
||||||
@title = @description_meta
|
@title = @description_meta
|
||||||
|
|
||||||
canonical_url "#{Discourse.base_url_no_prefix}#{public_send(url_method(params.slice(:category, :parent_category)))}"
|
canonical_url "#{Discourse.base_url_no_prefix}#{public_send(url_method(params.slice(:category, :parent_category)))}"
|
||||||
@ -298,7 +298,7 @@ class TagsController < ::ApplicationController
|
|||||||
if params[:tag_id] == 'none'
|
if params[:tag_id] == 'none'
|
||||||
options[:no_tags] = true
|
options[:no_tags] = true
|
||||||
else
|
else
|
||||||
options[:tags] = [params[:tag_id], params[:secondary_tag_id]].compact
|
options[:tags] = tag_params
|
||||||
options[:match_all_tags] = true
|
options[:match_all_tags] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -317,4 +317,8 @@ class TagsController < ::ApplicationController
|
|||||||
raise Discourse::NotFound
|
raise Discourse::NotFound
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def tag_params
|
||||||
|
[@tag_id].concat(Array(@additional_tags))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -630,7 +630,7 @@ Discourse::Application.routes.draw do
|
|||||||
get '/:tag_id' => 'tags#show', as: 'tag_show'
|
get '/:tag_id' => 'tags#show', as: 'tag_show'
|
||||||
get '/c/:category/:tag_id' => 'tags#show', as: 'tag_category_show'
|
get '/c/:category/:tag_id' => 'tags#show', as: 'tag_category_show'
|
||||||
get '/c/:parent_category/:category/:tag_id' => 'tags#show', as: 'tag_parent_category_category_show'
|
get '/c/:parent_category/:category/:tag_id' => 'tags#show', as: 'tag_parent_category_category_show'
|
||||||
get '/intersection/:tag_id/:secondary_tag_id' => 'tags#show', as: 'tag_intersection'
|
get '/intersection/:tag_id/*additional_tag_ids' => 'tags#show', as: 'tag_intersection'
|
||||||
get '/:tag_id/notifications' => 'tags#notifications'
|
get '/:tag_id/notifications' => 'tags#notifications'
|
||||||
put '/:tag_id/notifications' => 'tags#update_notifications'
|
put '/:tag_id/notifications' => 'tags#update_notifications'
|
||||||
put '/:tag_id' => 'tags#update'
|
put '/:tag_id' => 'tags#update'
|
||||||
|
@ -464,10 +464,16 @@ class TopicQuery
|
|||||||
|
|
||||||
if @options[:match_all_tags]
|
if @options[:match_all_tags]
|
||||||
# ALL of the given tags:
|
# ALL of the given tags:
|
||||||
|
tags_count = @options[:tags].length
|
||||||
@options[:tags] = Tag.where(name: @options[:tags]).pluck(:id) unless @options[:tags][0].is_a?(Integer)
|
@options[:tags] = Tag.where(name: @options[:tags]).pluck(:id) unless @options[:tags][0].is_a?(Integer)
|
||||||
@options[:tags].each_with_index do |tag, index|
|
|
||||||
sql_alias = ['t', index].join
|
if tags_count == @options[:tags].length
|
||||||
result = result.joins("INNER JOIN topic_tags #{sql_alias} ON #{sql_alias}.topic_id = topics.id AND #{sql_alias}.tag_id = #{tag}")
|
@options[:tags].each_with_index do |tag, index|
|
||||||
|
sql_alias = ['t', index].join
|
||||||
|
result = result.joins("INNER JOIN topic_tags #{sql_alias} ON #{sql_alias}.topic_id = topics.id AND #{sql_alias}.tag_id = #{tag}")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result = result.none # don't return any results unless all tags exist in the database
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# ANY of the given tags:
|
# ANY of the given tags:
|
||||||
|
@ -4,11 +4,13 @@ describe TagsController do
|
|||||||
describe 'show_latest' do
|
describe 'show_latest' do
|
||||||
let(:tag) { Fabricate(:tag) }
|
let(:tag) { Fabricate(:tag) }
|
||||||
let(:other_tag) { Fabricate(:tag) }
|
let(:other_tag) { Fabricate(:tag) }
|
||||||
|
let(:third_tag) { Fabricate(:tag) }
|
||||||
let(:category) { Fabricate(:category) }
|
let(:category) { Fabricate(:category) }
|
||||||
let(:subcategory) { Fabricate(:category, parent_category_id: category.id) }
|
let(:subcategory) { Fabricate(:category, parent_category_id: category.id) }
|
||||||
|
|
||||||
let(:single_tag_topic) { Fabricate(:topic, tags: [tag]) }
|
let(:single_tag_topic) { Fabricate(:topic, tags: [tag]) }
|
||||||
let(:multi_tag_topic) { Fabricate(:topic, tags: [tag, other_tag]) }
|
let(:multi_tag_topic) { Fabricate(:topic, tags: [tag, other_tag]) }
|
||||||
|
let(:all_tag_topic) { Fabricate(:topic, tags: [tag, other_tag, third_tag]) }
|
||||||
|
|
||||||
context 'tagging disabled' do
|
context 'tagging disabled' do
|
||||||
it "returns 404" do
|
it "returns 404" do
|
||||||
@ -27,14 +29,31 @@ describe TagsController do
|
|||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can filter by multiple tags" do
|
it "can filter by two tags" do
|
||||||
single_tag_topic; multi_tag_topic
|
single_tag_topic; multi_tag_topic; all_tag_topic
|
||||||
xhr :get, :show_latest, tag_id: tag.name, secondary_tag_id: other_tag.name
|
xhr :get, :show_latest, tag_id: tag.name, additional_tag_ids: other_tag.name
|
||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
|
expect(assigns(:list).topics).to include all_tag_topic
|
||||||
expect(assigns(:list).topics).to include multi_tag_topic
|
expect(assigns(:list).topics).to include multi_tag_topic
|
||||||
expect(assigns(:list).topics).to_not include single_tag_topic
|
expect(assigns(:list).topics).to_not include single_tag_topic
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "can filter by multiple tags" do
|
||||||
|
single_tag_topic; multi_tag_topic; all_tag_topic
|
||||||
|
xhr :get, :show_latest, tag_id: tag.name, additional_tag_ids: "#{other_tag.name}/#{third_tag.name}"
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(assigns(:list).topics).to include all_tag_topic
|
||||||
|
expect(assigns(:list).topics).to_not include multi_tag_topic
|
||||||
|
expect(assigns(:list).topics).to_not include single_tag_topic
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not find any tags when a tag which doesn't exist is passed" do
|
||||||
|
single_tag_topic
|
||||||
|
xhr :get, :show_latest, tag_id: tag.name, additional_tag_ids: "notatag"
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(assigns(:list).topics).to_not include single_tag_topic
|
||||||
|
end
|
||||||
|
|
||||||
it "can filter by category and tag" do
|
it "can filter by category and tag" do
|
||||||
xhr :get, :show_latest, tag_id: tag.name, category: category.slug
|
xhr :get, :show_latest, tag_id: tag.name, category: category.slug
|
||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
|
Loading…
x
Reference in New Issue
Block a user