From 2cb9e85d14b3c2bfbf79013042799a46dd252ead Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Thu, 30 Apr 2020 07:39:11 -1000 Subject: [PATCH] FEATURE: add category banner for why a user cannot post (#9576) * FEATURE: add category banner for why a user cannot post Adds a category banner for why a user is unable to post in a category. Also adds an extra alert for the user when a user is unable to create a topic in a category and they still try and click on the disabled-looking new topic button. --- .../components/category-read-only-banner.js | 11 ++ .../app/components/create-topic-button.js | 3 +- .../discourse/app/components/d-navigation.js | 40 +++++++ .../discourse/app/models/category.js | 3 +- .../components/category-read-only-banner.hbs | 7 ++ .../components/create-topic-button.hbs | 2 +- .../app/templates/components/d-navigation.hbs | 5 +- .../components/edit-category-settings.hbs | 14 +++ .../discourse/app/templates/discovery.hbs | 1 + app/controllers/categories_controller.rb | 1 + app/models/category.rb | 1 + app/serializers/site_category_serializer.rb | 3 +- config/locales/client.en.yml | 1 + ...00427222624_add_read_only_to_categories.rb | 7 ++ .../acceptance/category-banner-test.js | 89 +++++++++++++++ .../fixtures/discovery_fixtures.js | 102 ++++++++++++++++++ 16 files changed, 284 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/category-read-only-banner.js create mode 100644 app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs create mode 100644 db/migrate/20200427222624_add_read_only_to_categories.rb create mode 100644 test/javascripts/acceptance/category-banner-test.js diff --git a/app/assets/javascripts/discourse/app/components/category-read-only-banner.js b/app/assets/javascripts/discourse/app/components/category-read-only-banner.js new file mode 100644 index 00000000000..f1cd4a0bf0a --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/category-read-only-banner.js @@ -0,0 +1,11 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; +import { and } from "@ember/object/computed"; + +export default Component.extend({ + @discourseComputed + user() { + return this.currentUser; + }, + shouldShow: and("category.read_only_banner", "readOnly", "user") +}); diff --git a/app/assets/javascripts/discourse/app/components/create-topic-button.js b/app/assets/javascripts/discourse/app/components/create-topic-button.js index b2ab306a121..9642446c82f 100644 --- a/app/assets/javascripts/discourse/app/components/create-topic-button.js +++ b/app/assets/javascripts/discourse/app/components/create-topic-button.js @@ -1,5 +1,6 @@ import Component from "@ember/component"; export default Component.extend({ tagName: "", - label: "topic.create" + label: "topic.create", + btnClass: "btn-default" }); diff --git a/app/assets/javascripts/discourse/app/components/d-navigation.js b/app/assets/javascripts/discourse/app/components/d-navigation.js index df6dc3c0a92..bb862fd854b 100644 --- a/app/assets/javascripts/discourse/app/components/d-navigation.js +++ b/app/assets/javascripts/discourse/app/components/d-navigation.js @@ -15,6 +15,38 @@ export default Component.extend(FilterModeMixin, { return category && this.currentUser; }, + @discourseComputed("category", "createTopicDisabled") + categoryReadOnlyBanner(category, createTopicDisabled) { + if (category && this.currentUser && createTopicDisabled) { + return category.read_only_banner; + } + }, + + @discourseComputed( + "createTopicDisabled", + "hasDraft", + "categoryReadOnlyBanner" + ) + createTopicButtonDisabled( + createTopicDisabled, + hasDraft, + categoryReadOnlyBanner + ) { + if (categoryReadOnlyBanner && !hasDraft) { + return false; + } + return createTopicDisabled; + }, + + @discourseComputed("categoryReadOnlyBanner", "hasDraft") + createTopicClass(categoryReadOnlyBanner, hasDraft) { + if (categoryReadOnlyBanner && !hasDraft) { + return "btn-default disabled"; + } else { + return "btn-default"; + } + }, + @discourseComputed() categories() { return this.site.get("categoriesList"); @@ -65,6 +97,14 @@ export default Component.extend(FilterModeMixin, { this.reorderCategories(); break; } + }, + + clickCreateTopicButton() { + if (this.categoryReadOnlyBanner && !this.hasDraft) { + bootbox.alert(this.categoryReadOnlyBanner); + } else { + this.createTopic(); + } } } }); diff --git a/app/assets/javascripts/discourse/app/models/category.js b/app/assets/javascripts/discourse/app/models/category.js index d765f84d475..7d2b5905621 100644 --- a/app/assets/javascripts/discourse/app/models/category.js +++ b/app/assets/javascripts/discourse/app/models/category.js @@ -187,7 +187,8 @@ const Category = RestModel.extend({ "navigate_to_first_post_after_read" ), search_priority: this.search_priority, - reviewable_by_group_name: this.reviewable_by_group_name + reviewable_by_group_name: this.reviewable_by_group_name, + read_only_banner: this.read_only_banner }, type: id ? "PUT" : "POST" }); diff --git a/app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs b/app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs new file mode 100644 index 00000000000..4c00dc80445 --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/components/category-read-only-banner.hbs @@ -0,0 +1,7 @@ +{{#if shouldShow}} +
+
+ {{category.read_only_banner}} +
+
+{{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/components/create-topic-button.hbs b/app/assets/javascripts/discourse/app/templates/components/create-topic-button.hbs index 3a0e3ebfa93..76524d1478f 100644 --- a/app/assets/javascripts/discourse/app/templates/components/create-topic-button.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/create-topic-button.hbs @@ -1,6 +1,6 @@ {{#if canCreateTopic}} {{d-button - class="btn-default" + class=btnClass id="create-topic" action=action icon="plus" diff --git a/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs b/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs index 6a0d8e330c8..e9e0b9a34f1 100644 --- a/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/d-navigation.hbs @@ -25,9 +25,10 @@ {{create-topic-button canCreateTopic=canCreateTopic - action=createTopic - disabled=createTopicDisabled + action=(action "clickCreateTopicButton") + disabled=createTopicButtonDisabled label=createTopicLabel + btnClass=createTopicClass }} {{#if showCategoryEdit}} diff --git a/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs index f7b4981e7a5..ab17a307c33 100644 --- a/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs @@ -221,6 +221,20 @@ }} {{/if}} + +
+ + {{text-field + valueProperty="value" + id="read-only-message" + value=category.read_only_banner + options=(hash + placementStrategy="absolute" + ) + }} +
diff --git a/app/assets/javascripts/discourse/app/templates/discovery.hbs b/app/assets/javascripts/discourse/app/templates/discovery.hbs index 1c8f879125a..ddec84d2346 100644 --- a/app/assets/javascripts/discourse/app/templates/discovery.hbs +++ b/app/assets/javascripts/discourse/app/templates/discovery.hbs @@ -3,6 +3,7 @@ {{else}}
{{discourse-banner user=currentUser banner=site.banner}} + {{category-read-only-banner category=category readOnly=navigationCategory.cannotCreateTopicOnCategory}}
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index acb39e7cab2..770275a65ec 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -328,6 +328,7 @@ class CategoriesController < ApplicationController :allow_global_tags, :required_tag_group_name, :min_tags_from_required_group, + :read_only_banner, custom_fields: [params[:custom_fields].try(:keys)], permissions: [*p.try(:keys)], allowed_tags: [], diff --git a/app/models/category.rb b/app/models/category.rb index 2651602e160..bbf1e9b98cb 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -978,6 +978,7 @@ end # reviewable_by_group_id :integer # required_tag_group_id :integer # min_tags_from_required_group :integer default(1), not null +# read_only_banner :string # # Indexes # diff --git a/app/serializers/site_category_serializer.rb b/app/serializers/site_category_serializer.rb index 31549fd8a03..89868e93895 100644 --- a/app/serializers/site_category_serializer.rb +++ b/app/serializers/site_category_serializer.rb @@ -6,7 +6,8 @@ class SiteCategorySerializer < BasicCategorySerializer :allowed_tag_groups, :allow_global_tags, :min_tags_from_required_group, - :required_tag_group_name + :required_tag_group_name, + :read_only_banner def include_allowed_tags? SiteSetting.tagging_enabled diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 5674db76489..9c5d262701a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2795,6 +2795,7 @@ en: email_in_disabled_click: 'enable the "email in" setting.' mailinglist_mirror: "Category mirrors a mailing list" show_subcategory_list: "Show subcategory list above topics in this category." + read_only_banner: "Banner text when a user cannot create a topic in this category:" num_featured_topics: "Number of topics shown on the categories page:" subcategory_num_featured_topics: "Number of featured topics on parent category's page:" all_topics_wiki: "Make new topics wikis by default" diff --git a/db/migrate/20200427222624_add_read_only_to_categories.rb b/db/migrate/20200427222624_add_read_only_to_categories.rb new file mode 100644 index 00000000000..e3832213fba --- /dev/null +++ b/db/migrate/20200427222624_add_read_only_to_categories.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddReadOnlyToCategories < ActiveRecord::Migration[6.0] + def change + add_column :categories, :read_only_banner, :string + end +end diff --git a/test/javascripts/acceptance/category-banner-test.js b/test/javascripts/acceptance/category-banner-test.js new file mode 100644 index 00000000000..6bd32515078 --- /dev/null +++ b/test/javascripts/acceptance/category-banner-test.js @@ -0,0 +1,89 @@ +import { acceptance } from "helpers/qunit-helpers"; +import DiscoveryFixtures from "fixtures/discovery_fixtures"; + +acceptance("Category Banners", { + pretend(server, helper) { + server.get("/c/test-read-only-without-banner/5/l/latest.json", () => { + return helper.response( + DiscoveryFixtures["/latest_can_create_topic.json"] + ); + }); + server.get("/c/test-read-only-with-banner/6/l/latest.json", () => { + return helper.response( + DiscoveryFixtures["/latest_can_create_topic.json"] + ); + }); + }, + loggedIn: true, + site: { + categories: [ + { + id: 5, + name: "test read only without banner", + slug: "test-read-only-without-banner", + permission: null + }, + { + id: 6, + name: "test read only with banner", + slug: "test-read-only-with-banner", + permission: null, + read_only_banner: + "You need to video yourself doing the secret handshake to post here" + } + ] + } +}); + +QUnit.test("Does not display category banners when not set", async assert => { + await visit("/c/test-read-only-without-banner"); + + await click("#create-topic"); + assert.ok(!visible(".bootbox.modal"), "it does not pop up a modal"); + assert.ok( + !visible(".category-read-only-banner"), + "it does not show a banner" + ); +}); + +QUnit.test("Displays category banners when set", async assert => { + await visit("/c/test-read-only-with-banner"); + + await click("#create-topic"); + assert.ok(visible(".bootbox.modal"), "it pops up a modal"); + + await click(".modal-footer>.btn-primary"); + assert.ok(!visible(".bootbox.modal"), "it closes the modal"); + assert.ok(visible(".category-read-only-banner"), "it shows a banner"); +}); + +acceptance("Anonymous Category Banners", { + pretend(server, helper) { + server.get("/c/test-read-only-with-banner/6/l/latest.json", () => { + return helper.response( + DiscoveryFixtures["/latest_can_create_topic.json"] + ); + }); + }, + loggedIn: false, + site: { + categories: [ + { + id: 6, + name: "test read only with banner", + slug: "test-read-only-with-banner", + permission: null, + read_only_banner: + "You need to video yourself doing the secret handshake to post here" + } + ] + } +}); + +QUnit.test("Does not display category banners when set", async assert => { + await visit("/c/test-read-only-with-banner"); + assert.ok( + !visible(".category-read-only-banner"), + "it does not show a banner" + ); +}); diff --git a/test/javascripts/fixtures/discovery_fixtures.js b/test/javascripts/fixtures/discovery_fixtures.js index 01228b94370..9e09ab984a8 100644 --- a/test/javascripts/fixtures/discovery_fixtures.js +++ b/test/javascripts/fixtures/discovery_fixtures.js @@ -6120,5 +6120,107 @@ export default { } ] } + }, + "/latest_can_create_topic.json": { + users: [ + { + id: 1, + username: "tt1", + name: null, + avatar_template: "/letter_avatar_proxy/v4/letter/t/6de8d8/{size}.png" + } + ], + primary_groups: [], + topic_list: { + can_create_topic: true, + draft: null, + draft_key: "new_topic", + draft_sequence: 0, + per_page: 30, + topics: [ + { + id: 30, + title: "I am also creating a new topic here new topic", + fancy_title: "I am also creating a new topic here new topic", + slug: "i-am-also-creating-a-new-topic-here-new-topic", + posts_count: 6, + reply_count: 0, + highest_post_number: 6, + image_url: null, + created_at: "2020-04-27T23:47:44.218Z", + last_posted_at: "2020-04-28T22:45:47.529Z", + bumped: true, + bumped_at: "2020-04-28T22:02:20.215Z", + archetype: "regular", + unseen: false, + last_read_post_number: 5, + unread: 0, + new_posts: 0, + pinned: false, + unpinned: null, + visible: true, + closed: false, + archived: false, + notification_level: 1, + bookmarked: false, + liked: false, + tags: ["test", "test-tag"], + views: 6, + like_count: 0, + has_summary: false, + last_poster_username: "tt1", + category_id: 5, + pinned_globally: false, + featured_link: null, + posters: [ + { + extras: "latest single", + description: "Original Poster, Most Recent Poster", + user_id: 1, + primary_group_id: null + } + ] + }, + { + id: 29, + title: "About the test category category", + fancy_title: "About the test category category", + slug: "about-the-test-category-category", + posts_count: 5, + reply_count: 0, + highest_post_number: 5, + image_url: null, + created_at: "2020-04-27T22:15:49.424Z", + last_posted_at: "2020-04-27T23:51:06.249Z", + bumped: true, + bumped_at: "2020-04-27T22:15:49.424Z", + archetype: "regular", + unseen: false, + pinned: false, + unpinned: null, + visible: true, + closed: false, + archived: false, + bookmarked: null, + liked: null, + tags: [], + views: 1, + like_count: 0, + has_summary: false, + last_poster_username: "tt1", + category_id: 5, + pinned_globally: false, + featured_link: null, + posters: [ + { + extras: "latest single", + description: "Original Poster, Most Recent Poster", + user_id: 1, + primary_group_id: null + } + ] + } + ] + } } };