DEV: Plugin API for customizing text in the composer conditionally

Co-authored-by: Isaac Janzen <issac.janzen@discourse.org>
This commit is contained in:
Robin Ward 2021-10-07 13:25:55 -04:00
parent 3eb737c014
commit bef5223672
6 changed files with 117 additions and 5 deletions

View File

@ -24,8 +24,15 @@ export default Component.extend({
options: alias("model.replyOptions"), options: alias("model.replyOptions"),
action: alias("model.action"), action: alias("model.action"),
@discourseComputed("options", "action") // Note we update when some other attributes like tag/category change to allow
// text customizations to use those.
@discourseComputed("options", "action", "model.tags", "model.category")
actionTitle(opts, action) { actionTitle(opts, action) {
let result = this.model.customizationFor("actionTitle");
if (result) {
return result;
}
if (TITLES[action]) { if (TITLES[action]) {
return I18n.t(TITLES[action]); return I18n.t(TITLES[action]);
} }

View File

@ -240,13 +240,22 @@ export default Controller.extend({
return SAVE_ICONS[modelAction]; return SAVE_ICONS[modelAction];
}, },
// Note we update when some other attributes like tag/category change to allow
// text customizations to use those.
@discourseComputed( @discourseComputed(
"model.action", "model.action",
"isWhispering", "isWhispering",
"model.editConflict", "model.editConflict",
"model.privateMessage" "model.privateMessage",
"model.tags",
"model.category"
) )
saveLabel(modelAction, isWhispering, editConflict, privateMessage) { saveLabel(modelAction, isWhispering, editConflict, privateMessage) {
let result = this.model.customizationFor("saveLabel");
if (result) {
return result;
}
if (editConflict) { if (editConflict) {
return "composer.overwrite_edit"; return "composer.overwrite_edit";
} else if (isWhispering) { } else if (isWhispering) {

View File

@ -37,7 +37,9 @@ import {
registerIconRenderer, registerIconRenderer,
replaceIcon, replaceIcon,
} from "discourse-common/lib/icon-library"; } from "discourse-common/lib/icon-library";
import Composer from "discourse/models/composer"; import Composer, {
registerCustomizationCallback,
} from "discourse/models/composer";
import DiscourseBanner from "discourse/components/discourse-banner"; import DiscourseBanner from "discourse/components/discourse-banner";
import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts"; import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts";
import Sharing from "discourse/lib/sharing"; import Sharing from "discourse/lib/sharing";
@ -87,7 +89,7 @@ import { addSearchSuggestion } from "discourse/widgets/search-menu-results";
import { CUSTOM_USER_SEARCH_OPTIONS } from "select-kit/components/user-chooser"; import { CUSTOM_USER_SEARCH_OPTIONS } from "select-kit/components/user-chooser";
// If you add any methods to the API ensure you bump up this number // If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = "0.12.3"; const PLUGIN_API_VERSION = "0.12.5";
// This helper prevents us from applying the same `modifyClass` over and over in test mode. // This helper prevents us from applying the same `modifyClass` over and over in test mode.
function canModify(klass, type, resolverName, changes) { function canModify(klass, type, resolverName, changes) {
@ -1471,6 +1473,28 @@ class PluginApi {
{ ignoreMissing: true } { ignoreMissing: true }
); );
} }
/**
* Support for customizing the composer text. By providing a callback. Callbacks should
* return `null` or `undefined` if you don't need a customization based on the current state.
*
* ```
* api.customizeComposerText({
* actionTitle(model) {
* if (model.hello) {
* return "hello.world";
* }
* },
*
* saveLabel(model) {
* return "my.custom_save_label_key";
* }
* })
*
*/
customizeComposerText(callbacks) {
registerCustomizationCallback(callbacks);
}
} }
// from http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number // from http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number

View File

@ -24,6 +24,15 @@ import { isEmpty } from "@ember/utils";
import { propertyNotEqual } from "discourse/lib/computed"; import { propertyNotEqual } from "discourse/lib/computed";
import { throwAjaxError } from "discourse/lib/ajax-error"; import { throwAjaxError } from "discourse/lib/ajax-error";
let _customizations = [];
export function registerCustomizationCallback(cb) {
_customizations.push(cb);
}
export function resetComposerCustomizations() {
_customizations = [];
}
// The actions the composer can take // The actions the composer can take
export const CREATE_TOPIC = "createTopic", export const CREATE_TOPIC = "createTopic",
CREATE_SHARED_DRAFT = "createSharedDraft", CREATE_SHARED_DRAFT = "createSharedDraft",
@ -1305,6 +1314,18 @@ const Composer = RestModel.extend({
this.set("draftSaving", false); this.set("draftSaving", false);
}); });
}, },
customizationFor(type) {
for (let i = 0; i < _customizations.length; i++) {
let cb = _customizations[i][type];
if (cb) {
let result = cb(this);
if (result) {
return result;
}
}
}
},
}); });
Composer.reopenClass({ Composer.reopenClass({

View File

@ -12,7 +12,8 @@ import { click, currentURL, fillIn, visit } from "@ember/test-helpers";
import { skip, test } from "qunit"; import { skip, test } from "qunit";
import Draft from "discourse/models/draft"; import Draft from "discourse/models/draft";
import I18n from "I18n"; import I18n from "I18n";
import { NEW_TOPIC_KEY } from "discourse/models/composer"; import { CREATE_TOPIC, NEW_TOPIC_KEY } from "discourse/models/composer";
import { withPluginApi } from "discourse/lib/plugin-api";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import { run } from "@ember/runloop"; import { run } from "@ember/runloop";
import selectKit from "discourse/tests/helpers/select-kit-helper"; import selectKit from "discourse/tests/helpers/select-kit-helper";
@ -1008,3 +1009,51 @@ acceptance("Composer", function (needs) {
assert.notOk(exists(".discard-draft-modal .save-draft")); assert.notOk(exists(".discard-draft-modal .save-draft"));
}); });
}); });
acceptance("Composer - Customizations", function (needs) {
needs.user();
needs.site({ can_tag_topics: true });
function customComposerAction(composer) {
return (
(composer.tags || []).indexOf("monkey") !== -1 &&
composer.action === CREATE_TOPIC
);
}
needs.hooks.beforeEach(() => {
withPluginApi("0.8.14", (api) => {
api.customizeComposerText({
actionTitle(model) {
if (customComposerAction(model)) {
return "custom text";
}
},
saveLabel(model) {
if (customComposerAction(model)) {
return "composer.emoji";
}
},
});
});
});
test("Supports text customization", async function (assert) {
await visit("/");
await click("#create-topic");
assert.equal(query(".action-title").innerText, I18n.t("topic.create_long"));
assert.equal(
query(".save-or-cancel button").innerText,
I18n.t("composer.create_topic")
);
const tags = selectKit(".mini-tag-chooser");
await tags.expand();
await tags.selectRowByValue("monkey");
assert.equal(query(".action-title").innerText, "custom text");
assert.equal(
query(".save-or-cancel button").innerText,
I18n.t("composer.emoji")
);
});
});

View File

@ -37,6 +37,7 @@ import { resetUsernameDecorators } from "discourse/helpers/decorate-username-sel
import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget"; import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget";
import { resetUserSearchCache } from "discourse/lib/user-search"; import { resetUserSearchCache } from "discourse/lib/user-search";
import { resetCardClickListenerSelector } from "discourse/mixins/card-contents-base"; import { resetCardClickListenerSelector } from "discourse/mixins/card-contents-base";
import { resetComposerCustomizations } from "discourse/models/composer";
import sessionFixtures from "discourse/tests/fixtures/session-fixtures"; import sessionFixtures from "discourse/tests/fixtures/session-fixtures";
import { setTopicList } from "discourse/lib/topic-list-tracker"; import { setTopicList } from "discourse/lib/topic-list-tracker";
import sinon from "sinon"; import sinon from "sinon";
@ -280,6 +281,7 @@ export function acceptance(name, optionsOrCallback) {
resetCustomPostMessageCallbacks(); resetCustomPostMessageCallbacks();
resetUserSearchCache(); resetUserSearchCache();
resetCardClickListenerSelector(); resetCardClickListenerSelector();
resetComposerCustomizations();
resetPostMenuExtraButtons(); resetPostMenuExtraButtons();
clearNavItems(); clearNavItems();
setTopicList(null); setTopicList(null);