DEV: Add focusComposer to composer controller (#15872)
This commit adds a new helpful function to the composer controller which can be used to focus the composer and insert text, regardless of whether the consumer knows whether the composer is open or has a draft. This is good for cases where an action needs to copy text to the composer or open it with text after navigating to a URL. The inspiration for this addition is the discourse-chat plugin, which needs to be able to copy quoted markdown from the chat and insert it into the composer, and unlike in the topic controller we have no idea of the state of the composer from chat.
This commit is contained in:
parent
ab5361d69a
commit
7850ee318f
|
@ -6,7 +6,7 @@ import {
|
|||
authorizesOneOrMoreExtensions,
|
||||
uploadIcon,
|
||||
} from "discourse/lib/uploads";
|
||||
import { cancel, run } from "@ember/runloop";
|
||||
import { cancel, run, scheduleOnce } from "@ember/runloop";
|
||||
import {
|
||||
cannotPostAgain,
|
||||
durationTextFromSeconds,
|
||||
|
@ -396,6 +396,52 @@ export default Controller.extend({
|
|||
return uploadIcon(this.currentUser.staff, this.siteSettings);
|
||||
},
|
||||
|
||||
// Use this to open the composer when you are not sure whether it is
|
||||
// already open and whether it already has a draft being worked on. Supports
|
||||
// options to append text once the composer is open if required.
|
||||
//
|
||||
// opts:
|
||||
//
|
||||
// - fallbackToNewTopic: if true, and there is no draft, the composer will
|
||||
// be opened with the create_topic action and a new topic draft key
|
||||
// - insertText: the text to append to the composer once it is opened
|
||||
// - openOpts: this object will be passed to this.open if fallbackToNewTopic is
|
||||
// true
|
||||
@action
|
||||
focusComposer(opts = {}) {
|
||||
if (this.get("model.viewOpen")) {
|
||||
this._focusAndInsertText(opts.insertText);
|
||||
} else {
|
||||
const opened = this.openIfDraft();
|
||||
if (!opened && opts.fallbackToNewTopic) {
|
||||
this.open(
|
||||
Object.assign(
|
||||
{
|
||||
action: Composer.CREATE_TOPIC,
|
||||
draftKey: Composer.NEW_TOPIC_KEY,
|
||||
},
|
||||
opts.openOpts || {}
|
||||
)
|
||||
).then(() => {
|
||||
this._focusAndInsertText(opts.insertText);
|
||||
});
|
||||
} else if (opened) {
|
||||
this._focusAndInsertText(opts.insertText);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_focusAndInsertText(insertText) {
|
||||
scheduleOnce("afterRender", () => {
|
||||
const input = document.querySelector("textarea.d-editor-input");
|
||||
input && input.focus();
|
||||
|
||||
if (insertText) {
|
||||
this.model.appendText(insertText, null, { new_line: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
openIfDraft(event) {
|
||||
if (this.get("model.viewDraft")) {
|
||||
|
@ -407,7 +453,10 @@ export default Controller.extend({
|
|||
}
|
||||
|
||||
this.set("model.composeState", Composer.OPEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { isAppWebview } from "discourse/lib/utilities";
|
||||
import { later, run, schedule, throttle } from "@ember/runloop";
|
||||
import { later, run, throttle } from "@ember/runloop";
|
||||
import {
|
||||
nextTopicUrl,
|
||||
previousTopicUrl,
|
||||
|
@ -413,16 +413,11 @@ export default {
|
|||
|
||||
focusComposer(event) {
|
||||
const composer = this.container.lookup("controller:composer");
|
||||
if (composer.get("model.viewOpen")) {
|
||||
preventKeyboardEvent(event);
|
||||
|
||||
schedule("afterRender", () => {
|
||||
const input = document.querySelector("textarea.d-editor-input");
|
||||
input && input.focus();
|
||||
});
|
||||
} else {
|
||||
composer.openIfDraft(event);
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
composer.focusComposer(event);
|
||||
},
|
||||
|
||||
fullscreenComposer() {
|
||||
|
|
|
@ -660,6 +660,10 @@ const Composer = RestModel.extend({
|
|||
}
|
||||
}
|
||||
|
||||
if (opts && opts.new_line) {
|
||||
text = "\n\n" + text.trim();
|
||||
}
|
||||
|
||||
this.set("reply", before + text + after);
|
||||
|
||||
return before.length + text.length;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { run } from "@ember/runloop";
|
||||
import { click, currentURL, fillIn, visit } from "@ember/test-helpers";
|
||||
import { click, currentURL, fillIn, settled, visit } from "@ember/test-helpers";
|
||||
import { toggleCheckDraftPopup } from "discourse/controllers/composer";
|
||||
import LinkLookup from "discourse/lib/link-lookup";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { CREATE_TOPIC, NEW_TOPIC_KEY } from "discourse/models/composer";
|
||||
import Composer, {
|
||||
CREATE_TOPIC,
|
||||
NEW_TOPIC_KEY,
|
||||
} from "discourse/models/composer";
|
||||
import Draft from "discourse/models/draft";
|
||||
import {
|
||||
acceptance,
|
||||
|
@ -17,7 +20,7 @@ import {
|
|||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import I18n from "I18n";
|
||||
import { test } from "qunit";
|
||||
import { skip, test } from "qunit";
|
||||
import { Promise } from "rsvp";
|
||||
import sinon from "sinon";
|
||||
|
||||
|
@ -918,3 +921,101 @@ acceptance("Composer - Customizations", function (needs) {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
// all of these are broken on legacy ember qunit for...some reason. commenting
|
||||
// until we are fully on ember cli.
|
||||
acceptance("Composer - Focus Open and Closed", function (needs) {
|
||||
needs.user();
|
||||
|
||||
skip("Focusing a composer which is not open with create topic", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
|
||||
const composer = this.container.lookup("controller:composer");
|
||||
composer.focusComposer({ fallbackToNewTopic: true });
|
||||
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
document.activeElement.classList.contains("d-editor-input"),
|
||||
true,
|
||||
"composer is opened and focused"
|
||||
);
|
||||
assert.strictEqual(composer.model.action, Composer.CREATE_TOPIC);
|
||||
});
|
||||
|
||||
skip("Focusing a composer which is not open with create topic and append text", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
|
||||
const composer = this.container.lookup("controller:composer");
|
||||
composer.focusComposer({
|
||||
fallbackToNewTopic: true,
|
||||
insertText: "this is appended",
|
||||
});
|
||||
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
document.activeElement.classList.contains("d-editor-input"),
|
||||
true,
|
||||
"composer is opened and focused"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query("textarea.d-editor-input").value.trim(),
|
||||
"this is appended"
|
||||
);
|
||||
});
|
||||
|
||||
skip("Focusing a composer which is already open", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
|
||||
const composer = this.container.lookup("controller:composer");
|
||||
composer.focusComposer();
|
||||
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
document.activeElement.classList.contains("d-editor-input"),
|
||||
true,
|
||||
"composer is opened and focused"
|
||||
);
|
||||
});
|
||||
|
||||
skip("Focusing a composer which is already open and append text", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
|
||||
const composer = this.container.lookup("controller:composer");
|
||||
composer.focusComposer({ insertText: "this is some appended text" });
|
||||
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
document.activeElement.classList.contains("d-editor-input"),
|
||||
true,
|
||||
"composer is opened and focused"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query("textarea.d-editor-input").value.trim(),
|
||||
"this is some appended text"
|
||||
);
|
||||
});
|
||||
|
||||
skip("Focusing a composer which is not open that has a draft", async function (assert) {
|
||||
await visit("/t/this-is-a-test-topic/9");
|
||||
|
||||
await click(".topic-post:nth-of-type(1) button.edit");
|
||||
await fillIn(".d-editor-input", "This is a dirty reply");
|
||||
await click(".toggle-minimize");
|
||||
|
||||
const composer = this.container.lookup("controller:composer");
|
||||
composer.focusComposer({ insertText: "this is some appended text" });
|
||||
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
document.activeElement.classList.contains("d-editor-input"),
|
||||
true,
|
||||
"composer is opened and focused"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query("textarea.d-editor-input").value.trim(),
|
||||
"This is a dirty reply\n\nthis is some appended text"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue