diff --git a/app/assets/javascripts/discourse/app/components/card-container.hbs b/app/assets/javascripts/discourse/app/components/card-container.hbs index 9bde984d351..06434655848 100644 --- a/app/assets/javascripts/discourse/app/components/card-container.hbs +++ b/app/assets/javascripts/discourse/app/components/card-container.hbs @@ -7,7 +7,6 @@ @showUser={{this.showUser}} @filterPosts={{this.filterPosts}} @composePrivateMessage={{route-action "composePrivateMessage"}} - @createNewMessageViaParams={{route-action "createNewMessageViaParams"}} role="dialog" /> @@ -15,5 +14,4 @@ @topic={{this.topic.model}} @showUser={{this.showUser}} @showGroup={{this.showGroup}} - @createNewMessageViaParams={{route-action "createNewMessageViaParams"}} /> \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/composer-action-title.hbs b/app/assets/javascripts/discourse/app/components/composer-action-title.hbs index 70c27a96f27..09de2cf2e82 100644 --- a/app/assets/javascripts/discourse/app/components/composer-action-title.hbs +++ b/app/assets/javascripts/discourse/app/components/composer-action-title.hbs @@ -2,8 +2,6 @@ @composerModel={{this.model}} @replyOptions={{this.model.replyOptions}} @canWhisper={{this.canWhisper}} - @openComposer={{this.openComposer}} - @closeComposer={{this.closeComposer}} @action={{this.model.action}} @tabindex={{this.tabindex}} @topic={{this.model.topic}} diff --git a/app/assets/javascripts/discourse/app/components/composer-container.hbs b/app/assets/javascripts/discourse/app/components/composer-container.hbs index 8e19f89aa30..0b4e430133c 100644 --- a/app/assets/javascripts/discourse/app/components/composer-container.hbs +++ b/app/assets/javascripts/discourse/app/components/composer-container.hbs @@ -46,8 +46,6 @@
diff --git a/app/assets/javascripts/discourse/app/components/group-card-contents.js b/app/assets/javascripts/discourse/app/components/group-card-contents.js index bdadccc22c0..173bfe6e247 100644 --- a/app/assets/javascripts/discourse/app/components/group-card-contents.js +++ b/app/assets/javascripts/discourse/app/components/group-card-contents.js @@ -8,10 +8,12 @@ import discourseComputed from "discourse-common/utils/decorators"; import { groupPath } from "discourse/lib/url"; import { setting } from "discourse/lib/computed"; import { modKeysPressed } from "discourse/lib/utilities"; +import { inject as service } from "@ember/service"; const maxMembersToDisplay = 10; export default Component.extend(CardContentsBase, CleansUp, { + composer: service(), elementId: "group-card", mentionSelector: "a.mention-group", classNames: ["no-bg", "group-card"], @@ -105,7 +107,7 @@ export default Component.extend(CardContentsBase, CleansUp, { }, messageGroup() { - this.createNewMessageViaParams({ + this.composer.openNewMessage({ recipients: this.get("group.name"), hasGroups: true, }); diff --git a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js index 27e3bc8218d..46685faa991 100644 --- a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js +++ b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js @@ -18,10 +18,10 @@ export default class TopicsController extends DiscoveryController.extend( DismissTopics ) { @service router; + @service composer; @controller discovery; period = null; - canCreateTopicOnCategory = null; selected = null; expandGloballyPinned = false; expandAllPinned = false; diff --git a/app/assets/javascripts/discourse/app/controllers/group.js b/app/assets/javascripts/discourse/app/controllers/group.js index babbd63f1e3..28e5fb3c21d 100644 --- a/app/assets/javascripts/discourse/app/controllers/group.js +++ b/app/assets/javascripts/discourse/app/controllers/group.js @@ -22,6 +22,7 @@ export default Controller.extend({ dialog: service(), currentUser: service(), router: service(), + composer: service(), counts: null, showing: "members", @@ -130,7 +131,7 @@ export default Controller.extend({ @action messageGroup() { - this.send("createNewMessageViaParams", { + this.composer.openNewMessage({ recipients: this.get("model.name"), hasGroups: true, }); diff --git a/app/assets/javascripts/discourse/app/controllers/navigation/categories.js b/app/assets/javascripts/discourse/app/controllers/navigation/categories.js index e39a8c8a750..2043c6721a6 100644 --- a/app/assets/javascripts/discourse/app/controllers/navigation/categories.js +++ b/app/assets/javascripts/discourse/app/controllers/navigation/categories.js @@ -1,6 +1,8 @@ import NavigationDefaultController from "discourse/controllers/navigation/default"; import { inject as controller } from "@ember/controller"; +import { inject as service } from "@ember/service"; export default class NavigationCategoriesController extends NavigationDefaultController { + @service composer; @controller("discovery/categories") discoveryCategories; } diff --git a/app/assets/javascripts/discourse/app/controllers/navigation/category.js b/app/assets/javascripts/discourse/app/controllers/navigation/category.js index c79ca3e9a97..1059ed62035 100644 --- a/app/assets/javascripts/discourse/app/controllers/navigation/category.js +++ b/app/assets/javascripts/discourse/app/controllers/navigation/category.js @@ -2,8 +2,11 @@ import NavigationDefaultController from "discourse/controllers/navigation/defaul import { calculateFilterMode } from "discourse/lib/filter-mode"; import { dependentKeyCompat } from "@ember/object/compat"; import { tracked } from "@glimmer/tracking"; +import { inject as service } from "@ember/service"; export default class NavigationCategoryController extends NavigationDefaultController { + @service composer; + @tracked category; @tracked filterType; @tracked noSubcategories; @@ -16,4 +19,22 @@ export default class NavigationCategoryController extends NavigationDefaultContr noSubcategories: this.noSubcategories, }); } + + get createTopicTargetCategory() { + if (this.category?.canCreateTopic) { + return this.category; + } + + if (this.siteSettings.default_subcategory_on_read_only_category) { + return this.category?.subcategoryWithCreateTopicPermission; + } + } + + get enableCreateTopicButton() { + return !!this.createTopicTargetCategory; + } + + get canCreateTopic() { + return this.currentUser?.can_create_topic; + } } diff --git a/app/assets/javascripts/discourse/app/controllers/navigation/default.js b/app/assets/javascripts/discourse/app/controllers/navigation/default.js index 8777c1b11c3..aaf9c70dcb7 100644 --- a/app/assets/javascripts/discourse/app/controllers/navigation/default.js +++ b/app/assets/javascripts/discourse/app/controllers/navigation/default.js @@ -7,6 +7,7 @@ import { tracked } from "@glimmer/tracking"; export default class NavigationDefaultController extends Controller { @service router; + @service composer; @controller discovery; @tracked category; diff --git a/app/assets/javascripts/discourse/app/mixins/open-composer.js b/app/assets/javascripts/discourse/app/mixins/open-composer.js deleted file mode 100644 index 68dd5f71c5e..00000000000 --- a/app/assets/javascripts/discourse/app/mixins/open-composer.js +++ /dev/null @@ -1,77 +0,0 @@ -// This mixin allows a route to open the composer -import Composer from "discourse/models/composer"; -import Mixin from "@ember/object/mixin"; -import { getOwner } from "discourse-common/lib/get-owner"; - -export default Mixin.create({ - openComposer(controller) { - let categoryId = controller.get("category.id"); - - if ( - this.siteSettings.default_subcategory_on_read_only_category && - !controller.canCreateTopicOnCategory - ) { - if (controller.canCreateTopicOnSubCategory) { - categoryId = controller.get("defaultSubcategory.id"); - } else { - categoryId = this.siteSettings.default_composer_category; - } - } - - if ( - categoryId && - !this.siteSettings.default_subcategory_on_read_only_category && - controller.category.isUncategorizedCategory && - !this.siteSettings.allow_uncategorized_topics - ) { - categoryId = null; - } - - getOwner(this) - .lookup("service:composer") - .open({ - prioritizedCategoryId: categoryId, - topicCategoryId: categoryId, - action: Composer.CREATE_TOPIC, - draftKey: controller.get("model.draft_key") || Composer.NEW_TOPIC_KEY, - draftSequence: controller.get("model.draft_sequence") || 0, - }); - }, - - openComposerWithTopicParams( - controller, - topicTitle, - topicBody, - topicCategoryId, - topicTags - ) { - getOwner(this) - .lookup("service:composer") - .open({ - action: Composer.CREATE_TOPIC, - topicTitle, - topicBody, - topicCategoryId, - topicTags, - draftKey: controller.get("model.draft_key") || Composer.NEW_TOPIC_KEY, - draftSequence: controller.get("model.draft_sequence"), - }); - }, - - openComposerWithMessageParams({ - recipients = "", - topicTitle = "", - topicBody = "", - hasGroups = false, - } = {}) { - getOwner(this).lookup("service:composer").open({ - action: Composer.PRIVATE_MESSAGE, - recipients, - topicTitle, - topicBody, - archetypeId: "private_message", - draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY, - hasGroups, - }); - }, -}); diff --git a/app/assets/javascripts/discourse/app/models/category.js b/app/assets/javascripts/discourse/app/models/category.js index 8bb0f762d92..53c81f194ee 100644 --- a/app/assets/javascripts/discourse/app/models/category.js +++ b/app/assets/javascripts/discourse/app/models/category.js @@ -345,6 +345,16 @@ const Category = RestModel.extend({ isUncategorizedCategory(id) { return Category.isUncategorized(id); }, + + get canCreateTopic() { + return this.permission === PermissionType.FULL; + }, + + get subcategoryWithCreateTopicPermission() { + return this.subcategories?.find( + (subcategory) => subcategory.canCreateTopic + ); + }, }); let _uncategorized; diff --git a/app/assets/javascripts/discourse/app/routes/application.js b/app/assets/javascripts/discourse/app/routes/application.js index 6837988ec5a..01afa81f3a2 100644 --- a/app/assets/javascripts/discourse/app/routes/application.js +++ b/app/assets/javascripts/discourse/app/routes/application.js @@ -3,7 +3,6 @@ import Category from "discourse/models/category"; import Composer from "discourse/models/composer"; import DiscourseRoute from "discourse/routes/discourse"; import I18n from "I18n"; -import OpenComposer from "discourse/mixins/open-composer"; import { ajax } from "discourse/lib/ajax"; import { findAll } from "discourse/models/login-method"; import { getOwner } from "discourse-common/lib/get-owner"; @@ -17,6 +16,7 @@ import { action } from "@ember/object"; import KeyboardShortcutsHelp from "discourse/components/modal/keyboard-shortcuts-help"; import NotActivatedModal from "../components/modal/not-activated"; import ForgotPassword from "discourse/components/modal/forgot-password"; +import deprecated from "discourse-common/lib/deprecated"; function unlessReadOnly(method, message) { return function () { @@ -38,7 +38,7 @@ function unlessStrictlyReadOnly(method, message) { }; } -const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { +const ApplicationRoute = DiscourseRoute.extend({ siteTitle: setting("title"), shortSiteDescription: setting("short_site_description"), documentTitle: service(), @@ -190,14 +190,17 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { user.checkEmail(); }, - createNewTopicViaParams(title, body, category_id, tags) { - this.openComposerWithTopicParams( - this.controllerFor("discovery/topics"), + createNewTopicViaParams(title, body, categoryId, tags) { + deprecated( + "createNewTopicViaParam on the application route is deprecated. Use the composer service instead", + { id: "discourse.createNewTopicViaParams" } + ); + getOwner(this).lookup("service:composer").openNewTopic({ title, body, - category_id, - tags - ); + categoryId, + tags, + }); }, createNewMessageViaParams({ @@ -206,10 +209,14 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { topicBody = "", hasGroups = false, } = {}) { - this.openComposerWithMessageParams({ + deprecated( + "createNewMessageViaParams on the application route is deprecated. Use the composer service instead", + { id: "discourse.createNewMessageViaParams" } + ); + getOwner(this).lookup("service:composer").openNewMessage({ recipients, - topicTitle, - topicBody, + title: topicTitle, + body: topicBody, hasGroups, }); }, diff --git a/app/assets/javascripts/discourse/app/routes/build-category-route.js b/app/assets/javascripts/discourse/app/routes/build-category-route.js index 739e7ff5da5..ea6bf0a86b4 100644 --- a/app/assets/javascripts/discourse/app/routes/build-category-route.js +++ b/app/assets/javascripts/discourse/app/routes/build-category-route.js @@ -13,7 +13,6 @@ import Category from "discourse/models/category"; import CategoryList from "discourse/models/category-list"; import DiscourseRoute from "discourse/routes/discourse"; import I18n from "I18n"; -import PermissionType from "discourse/models/permission-type"; import TopicList from "discourse/models/topic-list"; import { action } from "@ember/object"; import PreloadStore from "discourse/lib/preload-store"; @@ -152,33 +151,7 @@ class AbstractCategoryRoute extends DiscourseRoute { setupController(controller, model) { const topics = this.topics, - category = model.category, - canCreateTopic = topics.get("can_create_topic"); - - let canCreateTopicOnCategory = - canCreateTopic && category.get("permission") === PermissionType.FULL; - let cannotCreateTopicOnCategory = !canCreateTopicOnCategory; - let defaultSubcategory; - let canCreateTopicOnSubCategory; - - if (this.siteSettings.default_subcategory_on_read_only_category) { - cannotCreateTopicOnCategory = false; - - if (!canCreateTopicOnCategory && category.subcategories) { - defaultSubcategory = category.subcategories.find((subcategory) => { - return subcategory.get("permission") === PermissionType.FULL; - }); - canCreateTopicOnSubCategory = !!defaultSubcategory; - } - } - - this.controllerFor("navigation/category").setProperties({ - canCreateTopicOnCategory, - cannotCreateTopicOnCategory, - canCreateTopic, - canCreateTopicOnSubCategory, - defaultSubcategory, - }); + category = model.category; let topicOpts = { model: topics, @@ -189,10 +162,6 @@ class AbstractCategoryRoute extends DiscourseRoute { selected: [], noSubcategories: this.routeConfig && !!this.routeConfig.no_subcategories, expandAllPinned: true, - canCreateTopic, - canCreateTopicOnCategory, - canCreateTopicOnSubCategory, - defaultSubcategory, }; const p = category.get("params"); diff --git a/app/assets/javascripts/discourse/app/routes/discourse.js b/app/assets/javascripts/discourse/app/routes/discourse.js index da07704a23c..4deca988c30 100644 --- a/app/assets/javascripts/discourse/app/routes/discourse.js +++ b/app/assets/javascripts/discourse/app/routes/discourse.js @@ -1,9 +1,8 @@ -import Composer from "discourse/models/composer"; -import Draft from "discourse/models/draft"; import Route from "@ember/routing/route"; import { once } from "@ember/runloop"; import { seenUser } from "discourse/lib/user-presence"; import { getOwner } from "discourse-common/lib/get-owner"; +import deprecated from "discourse-common/lib/deprecated"; const DiscourseRoute = Route.extend({ showFooter: false, @@ -54,24 +53,14 @@ const DiscourseRoute = Route.extend({ }, openTopicDraft() { - const composer = getOwner(this).lookup("service:composer"); - - if ( - composer.get("model.action") === Composer.CREATE_TOPIC && - composer.get("model.draftKey") === Composer.NEW_TOPIC_KEY - ) { - composer.set("model.composeState", Composer.OPEN); - } else { - Draft.get(Composer.NEW_TOPIC_KEY).then((data) => { - if (data.draft) { - composer.open({ - action: Composer.CREATE_TOPIC, - draft: data.draft, - draftKey: Composer.NEW_TOPIC_KEY, - draftSequence: data.draft_sequence, - }); - } - }); + deprecated( + "DiscourseRoute#openTopicDraft is deprecated. Inject the composer service and call openNewTopic instead", + { id: "discourse.open-topic-draft" } + ); + if (this.currentUser?.has_topic_draft) { + return getOwner(this) + .lookup("service:composer") + .openNewTopic({ preferDraft: true }); } }, diff --git a/app/assets/javascripts/discourse/app/routes/discovery-categories.js b/app/assets/javascripts/discourse/app/routes/discovery-categories.js index 59f9e79a085..32206de29de 100644 --- a/app/assets/javascripts/discourse/app/routes/discovery-categories.js +++ b/app/assets/javascripts/discourse/app/routes/discovery-categories.js @@ -2,7 +2,6 @@ import CategoryList from "discourse/models/category-list"; import DiscourseRoute from "discourse/routes/discourse"; import EmberObject, { action } from "@ember/object"; import I18n from "I18n"; -import OpenComposer from "discourse/mixins/open-composer"; import PreloadStore from "discourse/lib/preload-store"; import TopicList from "discourse/models/topic-list"; import { ajax } from "discourse/lib/ajax"; @@ -13,9 +12,7 @@ import showModal from "discourse/lib/show-modal"; import Session from "discourse/models/session"; import { inject as service } from "@ember/service"; -export default class DiscoveryCategoriesRoute extends DiscourseRoute.extend( - OpenComposer -) { +export default class DiscoveryCategoriesRoute extends DiscourseRoute { @service router; renderTemplate() { @@ -157,15 +154,6 @@ export default class DiscoveryCategoriesRoute extends DiscourseRoute.extend( showModal("reorder-categories"); } - @action - createTopic() { - if (this.get("currentUser.has_topic_draft")) { - this.openTopicDraft(); - } else { - this.openComposer(this.controllerFor("discovery/categories")); - } - } - @action didTransition() { next(() => this.controllerFor("application").set("showFooter", true)); diff --git a/app/assets/javascripts/discourse/app/routes/discovery.js b/app/assets/javascripts/discourse/app/routes/discovery.js index 5af1c084ba7..b0faced7a57 100644 --- a/app/assets/javascripts/discourse/app/routes/discovery.js +++ b/app/assets/javascripts/discourse/app/routes/discovery.js @@ -1,5 +1,4 @@ import DiscourseRoute from "discourse/routes/discourse"; -import OpenComposer from "discourse/mixins/open-composer"; import User from "discourse/models/user"; import { setTopicList } from "discourse/lib/topic-list-tracker"; import { action } from "@ember/object"; @@ -9,9 +8,7 @@ import { resetCachedTopicList } from "discourse/lib/cached-topic-list"; The parent route for all discovery routes. Handles the logic for showing the loading spinners. **/ -export default class DiscoveryRoute extends DiscourseRoute.extend( - OpenComposer -) { +export default class DiscoveryRoute extends DiscourseRoute { queryParams = { filter: { refreshModel: true }, }; @@ -74,15 +71,6 @@ export default class DiscoveryRoute extends DiscourseRoute.extend( topic.clearPin(); } - @action - createTopic() { - if (this.get("currentUser.has_topic_draft")) { - this.openTopicDraft(); - } else { - this.openComposer(this.controllerFor("discovery/topics")); - } - } - @action dismissReadTopics(dismissTopics) { const operationType = dismissTopics ? "topics" : "posts"; diff --git a/app/assets/javascripts/discourse/app/routes/new-message.js b/app/assets/javascripts/discourse/app/routes/new-message.js index c9794ce116f..96f4143caa2 100644 --- a/app/assets/javascripts/discourse/app/routes/new-message.js +++ b/app/assets/javascripts/discourse/app/routes/new-message.js @@ -7,6 +7,7 @@ import { inject as service } from "@ember/service"; export default DiscourseRoute.extend({ dialog: service(), + composer: service(), beforeModel(transition) { const params = transition.to.queryParams; @@ -14,12 +15,12 @@ export default DiscourseRoute.extend({ const groupName = params.groupname || params.group_name; if (this.currentUser) { - this.replaceWith("discovery.latest").then((e) => { + this.replaceWith("discovery.latest").then(() => { if (params.username) { - e.send("createNewMessageViaParams", { + this.composer.openNewMessage({ recipients: params.username, - topicTitle: params.title, - topicBody: params.body, + title: params.title, + body: params.body, }); } else if (groupName) { // send a message to a group @@ -27,10 +28,10 @@ export default DiscourseRoute.extend({ .then((result) => { if (result.messageable) { next(() => - e.send("createNewMessageViaParams", { + this.composer.openNewMessage({ recipients: groupName, - topicTitle: params.title, - topicBody: params.body, + title: params.title, + body: params.body, }) ); } else { @@ -41,9 +42,9 @@ export default DiscourseRoute.extend({ }) .catch(() => this.dialog.alert(I18n.t("generic_error"))); } else { - e.send("createNewMessageViaParams", { - topicTitle: params.title, - topicBody: params.body, + this.composer.openNewMessage({ + title: params.title, + body: params.body, }); } }); diff --git a/app/assets/javascripts/discourse/app/routes/new-topic.js b/app/assets/javascripts/discourse/app/routes/new-topic.js index f66848f90b9..e4928b5c56e 100644 --- a/app/assets/javascripts/discourse/app/routes/new-topic.js +++ b/app/assets/javascripts/discourse/app/routes/new-topic.js @@ -2,76 +2,78 @@ import Category from "discourse/models/category"; import DiscourseRoute from "discourse/routes/discourse"; import cookie from "discourse/lib/cookie"; import { next } from "@ember/runloop"; +import { inject as service } from "@ember/service"; + +export default class extends DiscourseRoute { + @service composer; -export default DiscourseRoute.extend({ beforeModel(transition) { if (this.currentUser) { - let category, categoryId; - - if (transition.to.queryParams.category_id) { - categoryId = transition.to.queryParams.category_id; - category = Category.findById(categoryId); - } else if (transition.to.queryParams.category) { - const splitCategory = transition.to.queryParams.category.split("/"); - - category = this._getCategory( - splitCategory[0], - splitCategory[1], - "nameLower" - ); - - if (!category) { - category = this._getCategory( - splitCategory[0], - splitCategory[1], - "slug" - ); - } - - if (category) { - categoryId = category.id; - } - } + const category = this.parseCategoryFromTransition(transition); if (category) { - let route = "discovery.category"; - let params = { category, id: category.id }; - - this.replaceWith(route, params).then((e) => { - if (this.controllerFor("navigation/category").canCreateTopic) { - this._sendTransition(e, transition, categoryId); + this.replaceWith("discovery.category", { + category, + id: category.id, + }).then(() => { + if (this.currentUser.can_create_topic) { + this.openComposer({ transition, category }); } }); + } else if (transition.from) { + // Navigation from another ember route + transition.abort(); + this.openComposer({ transition }); } else { - if (transition.from) { - transition.abort(); - this.send("createNewTopicViaParams"); - } else { - this.replaceWith("discovery.latest").then((e) => { - if (this.controllerFor("navigation/default").canCreateTopic) { - this._sendTransition(e, transition); - } - }); - } + this.replaceWith("discovery.latest").then(() => { + if (this.currentUser.can_create_topic) { + this.openComposer({ transition }); + } + }); } } else { // User is not logged in cookie("destination_url", window.location.href); this.replaceWith("login"); } - }, + } - _sendTransition(event, transition, categoryId) { + openComposer({ transition, category }) { next(() => { - event.send( - "createNewTopicViaParams", - transition.to.queryParams.title, - transition.to.queryParams.body, - categoryId, - transition.to.queryParams.tags - ); + this.composer.openNewTopic({ + title: transition.to.queryParams.title, + body: transition.to.queryParams.body, + category, + tags: transition.to.queryParams.tags, + }); }); - }, + } + + parseCategoryFromTransition(transition) { + let category; + + if (transition.to.queryParams.category_id) { + const categoryId = transition.to.queryParams.category_id; + category = Category.findById(categoryId); + } else if (transition.to.queryParams.category) { + const splitCategory = transition.to.queryParams.category.split("/"); + + category = this._getCategory( + splitCategory[0], + splitCategory[1], + "nameLower" + ); + + if (!category) { + category = this._getCategory( + splitCategory[0], + splitCategory[1], + "slug" + ); + } + } + return category; + } _getCategory(mainCategory, subCategory, type) { let category; @@ -92,5 +94,5 @@ export default DiscourseRoute.extend({ } return category; - }, -}); + } +} diff --git a/app/assets/javascripts/discourse/app/services/composer.js b/app/assets/javascripts/discourse/app/services/composer.js index 53d5a1aa591..e58c74bc4a5 100644 --- a/app/assets/javascripts/discourse/app/services/composer.js +++ b/app/assets/javascripts/discourse/app/services/composer.js @@ -1,4 +1,9 @@ -import Composer, { SAVE_ICONS, SAVE_LABELS } from "discourse/models/composer"; +import Composer, { + CREATE_TOPIC, + NEW_TOPIC_KEY, + SAVE_ICONS, + SAVE_LABELS, +} from "discourse/models/composer"; import EmberObject, { action, computed } from "@ember/object"; import { alias, and, or, reads } from "@ember/object/computed"; import { @@ -567,33 +572,6 @@ export default class ComposerService extends Service { this.close(); } - @action - async openComposer(options, post, topic) { - await this.open(options); - - let url = post?.url || topic?.url; - const topicTitle = topic?.title; - - if (!url || !topicTitle) { - return; - } - - url = `${location.protocol}//${location.host}${url}`; - const link = `[${escapeExpression(topicTitle)}](${url})`; - const continueDiscussion = I18n.t("post.continue_discussion", { - postLink: link, - }); - - const reply = this.get("model.reply"); - if (reply?.includes(continueDiscussion)) { - return; - } - - this.model.prependText(continueDiscussion, { - new_line: true, - }); - } - @action onPopupMenuAction(menuAction) { return ( @@ -1339,6 +1317,62 @@ export default class ComposerService extends Service { } } + async #openNewTopicDraft() { + if ( + this.model?.action === Composer.CREATE_TOPIC && + this.model?.draftKey === Composer.NEW_TOPIC_KEY + ) { + this.set("model.composeState", Composer.OPEN); + } else { + const data = await Draft.get(Composer.NEW_TOPIC_KEY); + if (data.draft) { + return this.open({ + action: Composer.CREATE_TOPIC, + draft: data.draft, + draftKey: Composer.NEW_TOPIC_KEY, + draftSequence: data.draft_sequence, + }); + } + } + } + + @action + async openNewTopic({ + title, + body, + category, + tags, + preferDraft = false, + } = {}) { + if (preferDraft && this.currentUser.has_topic_draft) { + return this.#openNewTopicDraft(); + } else { + return this.open({ + prioritizedCategoryId: category?.id, + topicCategoryId: category?.id, + topicTitle: title, + topicBody: body, + topicTags: tags, + action: CREATE_TOPIC, + draftKey: NEW_TOPIC_KEY, + draftSequence: 0, + }); + } + } + + @action + async openNewMessage({ title, body, recipients, hasGroups }) { + return this.open({ + action: Composer.PRIVATE_MESSAGE, + recipients, + topicTitle: title, + topicBody: body, + archetypeId: "private_message", + draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY, + hasGroups, + }); + } + // Given a potential instance and options, set the model for this composer. async _setModel(optionalComposerModel, opts) { this.set("linkLookup", null); @@ -1406,6 +1440,12 @@ export default class ComposerService extends Service { this.model.set("reply", opts.topicBody); } + if (opts.prependText && !this.model.reply?.includes(opts.prependText)) { + this.model.prependText(opts.prependText, { + new_line: true, + }); + } + const defaultComposerHeight = this._getDefaultComposerHeight(); this.set("model.composerHeight", defaultComposerHeight); diff --git a/app/assets/javascripts/discourse/app/templates/discovery.hbs b/app/assets/javascripts/discourse/app/templates/discovery.hbs index 2eaecdf418a..1777b6050ea 100644 --- a/app/assets/javascripts/discourse/app/templates/discovery.hbs +++ b/app/assets/javascripts/discourse/app/templates/discovery.hbs @@ -3,7 +3,7 @@ {{#unless this.viewingCategoriesList}} {{/unless}}
diff --git a/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs index c4c1129178a..2f4b89f3d8d 100644 --- a/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs @@ -119,9 +119,12 @@ @message={{this.footerMessage}} > {{#if this.latest}} - {{#if this.canCreateTopicOnCategory}} + {{#if this.category.canCreateTopic}} {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/app/templates/mobile/discovery/topics.hbs index 869f996736a..32eb6348fbe 100644 --- a/app/assets/javascripts/discourse/app/templates/mobile/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/app/templates/mobile/discovery/topics.hbs @@ -76,9 +76,12 @@ @message={{this.footerMessage}} > {{#if this.latest}} - {{#if this.canCreateTopicOnCategory}} + {{#if this.category.canCreateTopic}} {{/if}} diff --git a/app/assets/javascripts/discourse/app/templates/navigation/categories.hbs b/app/assets/javascripts/discourse/app/templates/navigation/categories.hbs index 4c37aa422ed..9ddc0f1ed95 100644 --- a/app/assets/javascripts/discourse/app/templates/navigation/categories.hbs +++ b/app/assets/javascripts/discourse/app/templates/navigation/categories.hbs @@ -6,6 +6,6 @@ @reorderCategories={{route-action "reorderCategories"}} @canCreateTopic={{this.canCreateTopic}} @hasDraft={{this.currentUser.has_topic_draft}} - @createTopic={{route-action "createTopic"}} + @createTopic={{fn this.composer.openNewTopic (hash preferDraft=true)}} /> \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/templates/navigation/category.hbs b/app/assets/javascripts/discourse/app/templates/navigation/category.hbs index 593732b95c6..8d63ff9deb6 100644 --- a/app/assets/javascripts/discourse/app/templates/navigation/category.hbs +++ b/app/assets/javascripts/discourse/app/templates/navigation/category.hbs @@ -23,8 +23,11 @@ @filterMode={{this.filterMode}} @noSubcategories={{this.noSubcategories}} @canCreateTopic={{this.canCreateTopic}} - @createTopic={{route-action "createTopic"}} - @createTopicDisabled={{this.cannotCreateTopicOnCategory}} + @createTopic={{fn + this.composer.openNewTopic + (hash category=this.createTopicTargetCategory preferDraft=true) + }} + @createTopicDisabled={{not this.enableCreateTopicButton}} @hasDraft={{this.currentUser.has_topic_draft}} @editCategory={{route-action "editCategory" this.category}} /> diff --git a/app/assets/javascripts/discourse/app/templates/navigation/default.hbs b/app/assets/javascripts/discourse/app/templates/navigation/default.hbs index c075d552437..3ce41264012 100644 --- a/app/assets/javascripts/discourse/app/templates/navigation/default.hbs +++ b/app/assets/javascripts/discourse/app/templates/navigation/default.hbs @@ -3,7 +3,7 @@ @filterMode={{this.filterMode}} @canCreateTopic={{this.canCreateTopic}} @hasDraft={{this.currentUser.has_topic_draft}} - @createTopic={{route-action "createTopic"}} + @createTopic={{fn this.composer.openNewTopic (hash preferDraft=true)}} @skipCategoriesNavItem={{this.skipCategoriesNavItem}} /> \ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js index 84e4d5077f9..cafba648596 100644 --- a/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js +++ b/app/assets/javascripts/discourse/tests/fixtures/session-fixtures.js @@ -14,6 +14,7 @@ export default { moderator: true, staff: true, can_create_group: true, + can_create_topic: true, title: "co-founder", reply_count: 859, topic_count: 36, diff --git a/app/assets/javascripts/select-kit/addon/components/composer-actions.js b/app/assets/javascripts/select-kit/addon/components/composer-actions.js index 20b515605e9..1c1081b53f1 100644 --- a/app/assets/javascripts/select-kit/addon/components/composer-actions.js +++ b/app/assets/javascripts/select-kit/addon/components/composer-actions.js @@ -13,6 +13,7 @@ import { camelize } from "@ember/string"; import { equal, gt } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; import { inject as service } from "@ember/service"; +import { escapeExpression } from "discourse/lib/utilities"; // Component can get destroyed and lose state let _topicSnapshot = null; @@ -27,6 +28,7 @@ export function _clearSnapshots() { export default DropdownSelectBoxComponent.extend({ dialog: service(), + composer: service(), seq: 0, pluginApiIdentifiers: ["composer-actions"], classNames: ["composer-actions"], @@ -234,14 +236,32 @@ export default DropdownSelectBoxComponent.extend({ return items; }, + _continuedFromText(post, topic) { + let url = post?.url || topic?.url; + const topicTitle = topic?.title; + + if (!url || !topicTitle) { + return; + } + + url = `${location.protocol}//${location.host}${url}`; + const link = `[${escapeExpression(topicTitle)}](${url})`; + return I18n.t("post.continue_discussion", { + postLink: link, + }); + }, + _replyFromExisting(options, post, topic) { - this.closeComposer(); - this.openComposer(options, post, topic); + this.composer.closeComposer(); + this.composer.open({ + ...options, + prependText: this._continuedFromText(post, topic), + }); }, _openComposer(options) { - this.closeComposer(); - this.openComposer(options); + this.composer.closeComposer(); + this.composer.open(options); }, toggleWhisperSelected(options, model) { diff --git a/spec/system/composer/default_to_subcategory_spec.rb b/spec/system/composer/default_to_subcategory_spec.rb index c0ecc724264..1e2d43649d9 100644 --- a/spec/system/composer/default_to_subcategory_spec.rb +++ b/spec/system/composer/default_to_subcategory_spec.rb @@ -41,13 +41,9 @@ describe "Default to Subcategory when parent Category doesn't allow posting", ty end end describe "Category does not have subcategory" do - it "should have the 'New Topic' button enabled and default Subcategory set to latest default subcategory" do + it "should have the 'New Topic' button disabled" do category_page.visit(category_with_no_subcategory) - expect(category_page).to have_button("New Topic", disabled: false) - category_page.new_topic_button.click - select_kit = - PageObjects::Components::SelectKit.new("#reply-control.open .category-chooser") - expect(select_kit).to have_selected_value(default_latest_category.id) + expect(category_page).to have_button("New Topic", disabled: true) end end end @@ -70,13 +66,9 @@ describe "Default to Subcategory when parent Category doesn't allow posting", ty end describe "Can't post on parent category" do describe "Category does not have subcategory" do - it "should have the 'New Topic' button enabled and default Subcategory not set" do + it "should have the 'New Topic' button disabled" do category_page.visit(category_with_no_subcategory) - expect(category_page).to have_button("New Topic", disabled: false) - category_page.new_topic_button.click - select_kit = - PageObjects::Components::SelectKit.new("#reply-control.open .category-chooser") - expect(select_kit).to have_selected_name("category…") + expect(category_page).to have_button("New Topic", disabled: true) end end end