FEATURE: add filter to show topics that have not been tagged
This commit is contained in:
parent
8e87a727ef
commit
7c092b0fe0
|
@ -45,6 +45,20 @@ export default Ember.Component.extend({
|
||||||
return I18n.t("tagging.selector_all_tags");
|
return I18n.t("tagging.selector_all_tags");
|
||||||
}.property('tag'),
|
}.property('tag'),
|
||||||
|
|
||||||
|
@computed('firstCategory', 'secondCategory')
|
||||||
|
noTagsUrl() {
|
||||||
|
var url = '/tags';
|
||||||
|
if (this.get('currentCategory')) {
|
||||||
|
url += this.get('currentCategory.url');
|
||||||
|
}
|
||||||
|
return url + '/none';
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('tag')
|
||||||
|
noTagsLabel() {
|
||||||
|
return I18n.t("tagging.selector_no_tags");
|
||||||
|
},
|
||||||
|
|
||||||
dropdownButtonClass: function() {
|
dropdownButtonClass: function() {
|
||||||
var result = 'badge-category category-dropdown-button';
|
var result = 'badge-category category-dropdown-button';
|
||||||
if (Em.isNone(this.get('tag'))) {
|
if (Em.isNone(this.get('tag'))) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default Discourse.Route.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
model(params) {
|
model(params) {
|
||||||
var tag = this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.tag_id) }),
|
var tag = (params.tag_id === 'none' ? null : this.store.createRecord("tag", { id: Handlebars.Utils.escapeExpression(params.tag_id) })),
|
||||||
f = '';
|
f = '';
|
||||||
|
|
||||||
if (params.category) {
|
if (params.category) {
|
||||||
|
@ -25,8 +25,8 @@ export default Discourse.Route.extend({
|
||||||
if (params.category) { this.set('categorySlug', params.category); }
|
if (params.category) { this.set('categorySlug', params.category); }
|
||||||
if (params.parent_category) { this.set('parentCategorySlug', params.parent_category); }
|
if (params.parent_category) { this.set('parentCategorySlug', params.parent_category); }
|
||||||
|
|
||||||
if (this.get("currentUser")) {
|
if (tag && this.get("currentUser")) {
|
||||||
// If logged in, we should get the tag"s user settings
|
// If logged in, we should get the tag's user settings
|
||||||
return this.store.find("tagNotification", tag.get("id")).then(tn => {
|
return this.store.find("tagNotification", tag.get("id")).then(tn => {
|
||||||
this.set("tagNotification", tn);
|
this.set("tagNotification", tn);
|
||||||
return tag;
|
return tag;
|
||||||
|
@ -45,18 +45,19 @@ export default Discourse.Route.extend({
|
||||||
const categorySlug = this.get('categorySlug');
|
const categorySlug = this.get('categorySlug');
|
||||||
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');
|
||||||
|
|
||||||
if (categorySlug) {
|
if (categorySlug) {
|
||||||
var category = Discourse.Category.findBySlug(categorySlug, parentCategorySlug);
|
var category = Discourse.Category.findBySlug(categorySlug, parentCategorySlug);
|
||||||
if (parentCategorySlug) {
|
if (parentCategorySlug) {
|
||||||
params.filter = `tags/c/${parentCategorySlug}/${categorySlug}/${tag.id}/l/${filter}`;
|
params.filter = `tags/c/${parentCategorySlug}/${categorySlug}/${tag_id}/l/${filter}`;
|
||||||
} else {
|
} else {
|
||||||
params.filter = `tags/c/${categorySlug}/${tag.id}/l/${filter}`;
|
params.filter = `tags/c/${categorySlug}/${tag_id}/l/${filter}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set('category', category);
|
this.set('category', category);
|
||||||
} else {
|
} else {
|
||||||
params.filter = `tags/${tag.id}/l/${filter}`;
|
params.filter = `tags/${tag_id}/l/${filter}`;
|
||||||
this.set('category', null);
|
this.set('category', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,10 +75,18 @@ export default Discourse.Route.extend({
|
||||||
const filterText = I18n.t('filters.' + this.get('navMode').replace('/', '.') + '.title'),
|
const filterText = I18n.t('filters.' + this.get('navMode').replace('/', '.') + '.title'),
|
||||||
controller = this.controllerFor('tags.show');
|
controller = this.controllerFor('tags.show');
|
||||||
|
|
||||||
if (this.get('category')) {
|
if (controller.get('model.id')) {
|
||||||
return I18n.t('tagging.filters.with_category', { filter: filterText, tag: controller.get('model.id'), category: this.get('category.name')});
|
if (this.get('category')) {
|
||||||
|
return I18n.t('tagging.filters.with_category', { filter: filterText, tag: controller.get('model.id'), category: this.get('category.name')});
|
||||||
|
} else {
|
||||||
|
return I18n.t('tagging.filters.without_category', { filter: filterText, tag: controller.get('model.id')});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return I18n.t('tagging.filters.without_category', { filter: filterText, tag: controller.get('model.id')});
|
if (this.get('category')) {
|
||||||
|
return I18n.t('tagging.filters.untagged_with_category', { filter: filterText, category: this.get('category.name')});
|
||||||
|
} else {
|
||||||
|
return I18n.t('tagging.filters.untagged_without_category', { filter: filterText});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
{{#if tagId}}
|
{{#if tagId}}
|
||||||
<a href {{action "expand"}} class="badge-category {{tagClass}}">{{tagId}}</a>
|
<a href {{action "expand"}} class="badge-category {{tagClass}}">{{tagId}}</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
<!-- TODO: how to detect "no tags" is currently selected -->
|
||||||
<a href {{action "expand"}} class="badge-category {{tagClass}} home">{{allTagsLabel}}</a>
|
<a href {{action "expand"}} class="badge-category {{tagClass}} home">{{allTagsLabel}}</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
<a href {{action "expand"}} class={{dropdownButtonClass}}><i class={{iconClass}}></i></a>
|
<a href {{action "expand"}} class={{dropdownButtonClass}}><i class={{iconClass}}></i></a>
|
||||||
<section class="{{unless expanded 'hidden'}} category-dropdown-menu chooser">
|
<section class="{{unless expanded 'hidden'}} category-dropdown-menu chooser">
|
||||||
<div class='cat'><a href={{allTagsUrl}} data-drop-close="true" class='badge-category home'>{{allTagsLabel}}</a></div>
|
<div class='cat'><a href={{allTagsUrl}} data-drop-close="true" class='badge-category home'>{{allTagsLabel}}</a></div>
|
||||||
|
<div class='cat'><a href={{noTagsUrl}} data-drop-close="true" class='badge-category home'>{{noTagsLabel}}</a></div>
|
||||||
{{#if renderTags}}
|
{{#if renderTags}}
|
||||||
{{#each tags as |t|}}
|
{{#each tags as |t|}}
|
||||||
<div class='cat'>
|
<div class='cat'>
|
||||||
|
|
|
@ -57,7 +57,7 @@ class TagsController < ::ApplicationController
|
||||||
@list.more_topics_url = list_by_tag_path(tag_id: @tag_id, page: page + 1)
|
@list.more_topics_url = list_by_tag_path(tag_id: @tag_id, page: page + 1)
|
||||||
@rss = "tag"
|
@rss = "tag"
|
||||||
|
|
||||||
if @list.topics.size == 0 && !Tag.where(name: @tag_id).exists?
|
if @list.topics.size == 0 && params[:tag_id] != 'none' && !Tag.where(name: @tag_id).exists?
|
||||||
raise Discourse::NotFound
|
raise Discourse::NotFound
|
||||||
else
|
else
|
||||||
respond_with_list(@list)
|
respond_with_list(@list)
|
||||||
|
@ -203,7 +203,6 @@ class TagsController < ::ApplicationController
|
||||||
topic_ids: param_to_integer_list(:topic_ids),
|
topic_ids: param_to_integer_list(:topic_ids),
|
||||||
exclude_category_ids: params[:exclude_category_ids],
|
exclude_category_ids: params[:exclude_category_ids],
|
||||||
category: params[:category],
|
category: params[:category],
|
||||||
tags: [params[:tag_id]],
|
|
||||||
order: params[:order],
|
order: params[:order],
|
||||||
ascending: params[:ascending],
|
ascending: params[:ascending],
|
||||||
min_posts: params[:min_posts],
|
min_posts: params[:min_posts],
|
||||||
|
@ -217,6 +216,12 @@ class TagsController < ::ApplicationController
|
||||||
options[:no_subcategories] = true if params[:no_subcategories] == 'true'
|
options[:no_subcategories] = true if params[:no_subcategories] == 'true'
|
||||||
options[:slow_platform] = true if slow_platform?
|
options[:slow_platform] = true if slow_platform?
|
||||||
|
|
||||||
|
if params[:tag_id] == 'none'
|
||||||
|
options[:no_tags] = true
|
||||||
|
else
|
||||||
|
options[:tags] = [params[:tag_id]]
|
||||||
|
end
|
||||||
|
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2123,6 +2123,7 @@ en:
|
||||||
tagging:
|
tagging:
|
||||||
all_tags: "All Tags"
|
all_tags: "All Tags"
|
||||||
selector_all_tags: "all tags"
|
selector_all_tags: "all tags"
|
||||||
|
selector_no_tags: "no tags"
|
||||||
changed: "tags changed:"
|
changed: "tags changed:"
|
||||||
tags: "Tags"
|
tags: "Tags"
|
||||||
choose_for_topic: "choose optional tags for this topic"
|
choose_for_topic: "choose optional tags for this topic"
|
||||||
|
@ -2139,6 +2140,8 @@ en:
|
||||||
filters:
|
filters:
|
||||||
without_category: "%{filter} %{tag} topics"
|
without_category: "%{filter} %{tag} topics"
|
||||||
with_category: "%{filter} %{tag} topics in %{category}"
|
with_category: "%{filter} %{tag} topics in %{category}"
|
||||||
|
untagged_without_category: "%{filter} untagged topics"
|
||||||
|
untagged_with_category: "%{filter} untagged topics in %{category}"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
watching:
|
watching:
|
||||||
|
|
|
@ -20,6 +20,7 @@ class TopicQuery
|
||||||
visible
|
visible
|
||||||
category
|
category
|
||||||
tags
|
tags
|
||||||
|
no_tags
|
||||||
order
|
order
|
||||||
ascending
|
ascending
|
||||||
no_subcategories
|
no_subcategories
|
||||||
|
@ -465,6 +466,9 @@ class TopicQuery
|
||||||
else
|
else
|
||||||
result = result.where("tags.name in (?)", @options[:tags])
|
result = result.where("tags.name in (?)", @options[:tags])
|
||||||
end
|
end
|
||||||
|
elsif @options[:no_tags]
|
||||||
|
# the following will do: ("topics"."id" NOT IN (SELECT DISTINCT "topic_tags"."topic_id" FROM "topic_tags"))
|
||||||
|
result = result.where.not(:id => TopicTag.select(:topic_id).uniq)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -122,25 +122,32 @@ describe TopicQuery do
|
||||||
SiteSetting.tagging_enabled = true
|
SiteSetting.tagging_enabled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns topics with the tag when filtered to it" do
|
context "no category filter" do
|
||||||
tagged_topic1 = Fabricate(:topic, {tags: [tag]})
|
# create some topics before each test:
|
||||||
tagged_topic2 = Fabricate(:topic, {tags: [other_tag]})
|
let!(:tagged_topic1) { Fabricate(:topic, {tags: [tag]}) }
|
||||||
tagged_topic3 = Fabricate(:topic, {tags: [tag, other_tag]})
|
let!(:tagged_topic2) { Fabricate(:topic, {tags: [other_tag]}) }
|
||||||
no_tags_topic = Fabricate(:topic)
|
let!(:tagged_topic3) { Fabricate(:topic, {tags: [tag, other_tag]}) }
|
||||||
|
let!(:no_tags_topic) { Fabricate(:topic) }
|
||||||
|
|
||||||
expect(TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort)
|
it "returns topics with the tag when filtered to it" do
|
||||||
expect(TopicQuery.new(moderator, tags: [tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort)
|
expect(TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort)
|
||||||
|
expect(TopicQuery.new(moderator, tags: [tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort)
|
||||||
|
|
||||||
two_tag_topic = TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.find { |t| t.id == tagged_topic3.id }
|
two_tag_topic = TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.find { |t| t.id == tagged_topic3.id }
|
||||||
expect(two_tag_topic.tags.size).to eq(2)
|
expect(two_tag_topic.tags.size).to eq(2)
|
||||||
|
|
||||||
# topics with ANY of the given tags:
|
# topics with ANY of the given tags:
|
||||||
expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort)
|
expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort)
|
||||||
expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort)
|
expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort)
|
||||||
|
|
||||||
# TODO: topics with ALL of the given tags:
|
# TODO: topics with ALL of the given tags:
|
||||||
# expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort)
|
# expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort)
|
||||||
# expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort)
|
# expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can return topics with no tags" do
|
||||||
|
expect(TopicQuery.new(moderator, no_tags: true).list_latest.topics.map(&:id)).to eq([no_tags_topic.id])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "and categories too" do
|
context "and categories too" do
|
||||||
|
|
Loading…
Reference in New Issue