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}}
+
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
+ }
+ ]
+ }
+ ]
+ }
}
};