DEV: Migrate insert-hyperlink to the new modal api (#23051)
This commit is contained in:
parent
c280c1c52b
commit
bc26e6c4b2
|
@ -32,7 +32,6 @@ import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
|||
import loadScript from "discourse/lib/load-script";
|
||||
import { resolveCachedShortUrls } from "pretty-text/upload-short-url";
|
||||
import { inject as service } from "@ember/service";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { siteDir } from "discourse/lib/text-direction";
|
||||
import { translations } from "pretty-text/emoji/data";
|
||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
|
@ -40,6 +39,7 @@ import { action, computed } from "@ember/object";
|
|||
import TextareaTextManipulation, {
|
||||
getHead,
|
||||
} from "discourse/mixins/textarea-text-manipulation";
|
||||
import InsertHyperlink from "discourse/components/modal/insert-hyperlink";
|
||||
|
||||
function getButtonLabel(labelKey, defaultLabel) {
|
||||
// use the Font Awesome icon if the label matches the default
|
||||
|
@ -218,6 +218,9 @@ export function onToolbarCreate(func) {
|
|||
}
|
||||
|
||||
export default Component.extend(TextareaTextManipulation, {
|
||||
emojiStore: service("emoji-store"),
|
||||
modal: service(),
|
||||
|
||||
classNames: ["d-editor"],
|
||||
ready: false,
|
||||
lastSel: null,
|
||||
|
@ -225,7 +228,6 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
showLink: true,
|
||||
emojiPickerIsActive: false,
|
||||
emojiFilter: "",
|
||||
emojiStore: service("emoji-store"),
|
||||
isEditorFocused: false,
|
||||
processPreview: true,
|
||||
composerFocusSelector: "#reply-control .d-editor-input",
|
||||
|
@ -770,9 +772,11 @@ export default Component.extend(TextareaTextManipulation, {
|
|||
linkText = this._lastSel.value;
|
||||
}
|
||||
|
||||
showModal("insert-hyperlink").setProperties({
|
||||
linkText,
|
||||
toolbarEvent,
|
||||
this.modal.show(InsertHyperlink, {
|
||||
model: {
|
||||
linkText,
|
||||
toolbarEvent,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
{{! template-lint-disable no-pointer-down-event-binding }}
|
||||
<DModal
|
||||
{{on "keydown" this.keyDown}}
|
||||
{{on "mousedown" this.mouseDown}}
|
||||
@closeModal={{@closeModal}}
|
||||
@title={{i18n "composer.link_dialog_title"}}
|
||||
@bodyClass="insert-link"
|
||||
class="insert-hyperlink-modal"
|
||||
>
|
||||
<:body>
|
||||
<div class="inputs">
|
||||
<input
|
||||
{{on "input" this.search}}
|
||||
value={{this.linkUrl}}
|
||||
placeholder={{i18n "composer.link_url_placeholder"}}
|
||||
type="text"
|
||||
autofocus="autofocus"
|
||||
class="link-url"
|
||||
/>
|
||||
|
||||
{{#if this.searchLoading}}
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.searchResults}}
|
||||
<div class="internal-link-results">
|
||||
{{#each this.searchResults as |result|}}
|
||||
<a
|
||||
{{on "click" this.linkClick}}
|
||||
href={{result.url}}
|
||||
data-title={{result.fancy_title}}
|
||||
class="search-link"
|
||||
>
|
||||
<TopicStatus @topic={{result}} @disableActions={{true}} />
|
||||
{{replace-emoji result.title}}
|
||||
<div class="search-category">
|
||||
{{#if result.category.parentCategory}}
|
||||
{{category-link result.category.parentCategory}}
|
||||
{{/if}}
|
||||
{{category-link result.category hideParent=true}}
|
||||
{{discourse-tags result}}
|
||||
</div>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="inputs">
|
||||
<input
|
||||
{{on "input" this.updateLinkText}}
|
||||
value={{this.linkText}}
|
||||
placeholder={{i18n "composer.link_optional_text"}}
|
||||
type="text"
|
||||
class="link-text"
|
||||
/>
|
||||
</div>
|
||||
</:body>
|
||||
|
||||
<:footer>
|
||||
<DButton
|
||||
@action={{this.ok}}
|
||||
@label="composer.modal_ok"
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
/>
|
||||
|
||||
<DButton
|
||||
@action={{@closeModal}}
|
||||
@label="composer.modal_cancel"
|
||||
class="btn-danger"
|
||||
/>
|
||||
</:footer>
|
||||
</DModal>
|
|
@ -0,0 +1,164 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { cancel } from "@ember/runloop";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { prefixProtocol } from "discourse/lib/url";
|
||||
import { searchForTerm } from "discourse/lib/search";
|
||||
|
||||
export default class InsertHyperlink extends Component {
|
||||
@tracked linkText = this.args.model.linkText;
|
||||
@tracked linkUrl = "";
|
||||
@tracked selectedRow = -1;
|
||||
@tracked searchResults = [];
|
||||
@tracked searchLoading = false;
|
||||
_debounced;
|
||||
_activeSearch;
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
cancel(this._debounced);
|
||||
}
|
||||
|
||||
highlightRow(e, direction) {
|
||||
const index =
|
||||
direction === "down" ? this.selectedRow + 1 : this.selectedRow - 1;
|
||||
|
||||
if (index > -1 && index < this.searchResults.length) {
|
||||
document
|
||||
.querySelectorAll(".internal-link-results .search-link")
|
||||
[index].focus();
|
||||
this.selectedRow = index;
|
||||
} else {
|
||||
this.selectedRow = -1;
|
||||
document.querySelector("input.link-url").focus();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
selectLink(el) {
|
||||
this.searchResults = [];
|
||||
this.linkUrl = el.href;
|
||||
this.selectedRow = -1;
|
||||
|
||||
if (!this.linkText && el.dataset.title) {
|
||||
this.linkText = el.dataset.title;
|
||||
}
|
||||
|
||||
document.querySelector("input.link-text").focus();
|
||||
}
|
||||
|
||||
async triggerSearch() {
|
||||
if (this.linkUrl.length < 4 || this.linkUrl.startsWith("http")) {
|
||||
this.abortSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
this.searchLoading = true;
|
||||
this._activeSearch = searchForTerm(this.linkUrl, {
|
||||
typeFilter: "topic",
|
||||
});
|
||||
|
||||
try {
|
||||
const results = await this._activeSearch;
|
||||
this.searchResults = results?.topics || [];
|
||||
} finally {
|
||||
this.searchLoading = false;
|
||||
this._activeSearch = null;
|
||||
}
|
||||
}
|
||||
|
||||
abortSearch() {
|
||||
this._activeSearch?.abort();
|
||||
|
||||
this.searchResults = [];
|
||||
this.searchLoading = false;
|
||||
}
|
||||
|
||||
@action
|
||||
keyDown(event) {
|
||||
switch (event.key) {
|
||||
case "ArrowDown":
|
||||
this.highlightRow(event, "down");
|
||||
break;
|
||||
case "ArrowUp":
|
||||
this.highlightRow(event, "up");
|
||||
break;
|
||||
case "Enter":
|
||||
// override Enter behavior when a row is selected
|
||||
if (this.selectedRow > -1) {
|
||||
const selected = document.querySelectorAll(
|
||||
".internal-link-results .search-link"
|
||||
)[this.selectedRow];
|
||||
this.selectLink(selected);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
break;
|
||||
case "Escape":
|
||||
// Esc should cancel dropdown first
|
||||
if (this.searchResults.length) {
|
||||
this.searchResults = [];
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
this.args.closeModal();
|
||||
document.querySelector(".d-editor-input")?.focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
mouseDown(event) {
|
||||
if (!event.target.closest(".inputs")) {
|
||||
this.searchResults = [];
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
ok() {
|
||||
const origLink = this.linkUrl;
|
||||
const linkUrl = prefixProtocol(origLink);
|
||||
const sel = this.args.model.toolbarEvent.selected;
|
||||
|
||||
if (isEmpty(linkUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linkText = this.linkText || "";
|
||||
|
||||
if (linkText.length) {
|
||||
this.args.model.toolbarEvent.addText(`[${linkText}](${linkUrl})`);
|
||||
} else if (sel.value) {
|
||||
this.args.model.toolbarEvent.addText(`[${sel.value}](${linkUrl})`);
|
||||
} else {
|
||||
this.args.model.toolbarEvent.addText(`[${origLink}](${linkUrl})`);
|
||||
this.args.model.toolbarEvent.selectText(sel.start + 1, origLink.length);
|
||||
}
|
||||
|
||||
this.args.closeModal();
|
||||
}
|
||||
|
||||
@action
|
||||
linkClick(e) {
|
||||
if (!e.metaKey && !e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.selectLink(e.target.closest(".search-link"));
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
updateLinkText(event) {
|
||||
this.linkText = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
search(event) {
|
||||
this.linkUrl = event.target.value;
|
||||
this._debounced = discourseDebounce(this, this.triggerSearch, 400);
|
||||
}
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
import { cancel, schedule } from "@ember/runloop";
|
||||
import Controller from "@ember/controller";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { prefixProtocol } from "discourse/lib/url";
|
||||
import { searchForTerm } from "discourse/lib/search";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
_debounced: null,
|
||||
_activeSearch: null,
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
linkUrl: "",
|
||||
linkText: "",
|
||||
searchResults: [],
|
||||
searchLoading: false,
|
||||
selectedRow: -1,
|
||||
});
|
||||
|
||||
schedule("afterRender", () => {
|
||||
const element = document.querySelector(".insert-link");
|
||||
element.addEventListener("keydown", this.keyDown);
|
||||
|
||||
element
|
||||
.closest(".modal-inner-container")
|
||||
.addEventListener("mousedown", this.mouseDown);
|
||||
});
|
||||
},
|
||||
|
||||
@bind
|
||||
keyDown(event) {
|
||||
switch (event.which) {
|
||||
case 40:
|
||||
this.highlightRow(event, "down");
|
||||
break;
|
||||
case 38:
|
||||
this.highlightRow(event, "up");
|
||||
break;
|
||||
case 13:
|
||||
// override Enter behaviour when a row is selected
|
||||
if (this.selectedRow > -1) {
|
||||
const selected = document.querySelectorAll(
|
||||
".internal-link-results .search-link"
|
||||
)[this.selectedRow];
|
||||
this.selectLink(selected);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
break;
|
||||
case 27:
|
||||
// Esc should cancel dropdown first
|
||||
if (this.searchResults.length) {
|
||||
this.set("searchResults", []);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
this.send("closeModal");
|
||||
document.querySelector(".d-editor-input")?.focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
mouseDown(event) {
|
||||
if (!event.target.closest(".inputs")) {
|
||||
this.set("searchResults", []);
|
||||
}
|
||||
},
|
||||
|
||||
highlightRow(e, direction) {
|
||||
const index =
|
||||
direction === "down" ? this.selectedRow + 1 : this.selectedRow - 1;
|
||||
|
||||
if (index > -1 && index < this.searchResults.length) {
|
||||
document
|
||||
.querySelectorAll(".internal-link-results .search-link")
|
||||
[index].focus();
|
||||
this.set("selectedRow", index);
|
||||
} else {
|
||||
this.set("selectedRow", -1);
|
||||
document.querySelector("input.link-url").focus();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
selectLink(el) {
|
||||
this.setProperties({
|
||||
linkUrl: el.href,
|
||||
searchResults: [],
|
||||
selectedRow: -1,
|
||||
});
|
||||
|
||||
if (!this.linkText && el.dataset.title) {
|
||||
this.set("linkText", el.dataset.title);
|
||||
}
|
||||
|
||||
document.querySelector("input.link-text").focus();
|
||||
},
|
||||
|
||||
triggerSearch() {
|
||||
if (this.linkUrl.length > 3 && !this.linkUrl.startsWith("http")) {
|
||||
this.set("searchLoading", true);
|
||||
this._activeSearch = searchForTerm(this.linkUrl, {
|
||||
typeFilter: "topic",
|
||||
});
|
||||
this._activeSearch
|
||||
.then((results) => {
|
||||
if (results && results.topics && results.topics.length > 0) {
|
||||
this.set("searchResults", results.topics);
|
||||
} else {
|
||||
this.set("searchResults", []);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("searchLoading", false);
|
||||
this._activeSearch = null;
|
||||
});
|
||||
} else {
|
||||
this.abortSearch();
|
||||
}
|
||||
},
|
||||
|
||||
abortSearch() {
|
||||
if (this._activeSearch) {
|
||||
this._activeSearch.abort();
|
||||
}
|
||||
this.setProperties({
|
||||
searchResults: [],
|
||||
searchLoading: false,
|
||||
});
|
||||
},
|
||||
|
||||
onClose() {
|
||||
const element = document.querySelector(".insert-link");
|
||||
element.removeEventListener("keydown", this.keyDown);
|
||||
element
|
||||
.closest(".modal-inner-container")
|
||||
.removeEventListener("mousedown", this.mouseDown);
|
||||
|
||||
cancel(this._debounced);
|
||||
},
|
||||
|
||||
actions: {
|
||||
ok() {
|
||||
const origLink = this.linkUrl;
|
||||
const linkUrl = prefixProtocol(origLink);
|
||||
const sel = this.toolbarEvent.selected;
|
||||
|
||||
if (isEmpty(linkUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linkText = this.linkText || "";
|
||||
|
||||
if (linkText.length) {
|
||||
this.toolbarEvent.addText(`[${linkText}](${linkUrl})`);
|
||||
} else {
|
||||
if (sel.value) {
|
||||
this.toolbarEvent.addText(`[${sel.value}](${linkUrl})`);
|
||||
} else {
|
||||
this.toolbarEvent.addText(`[${origLink}](${linkUrl})`);
|
||||
this.toolbarEvent.selectText(sel.start + 1, origLink.length);
|
||||
}
|
||||
}
|
||||
this.send("closeModal");
|
||||
},
|
||||
cancel() {
|
||||
this.send("closeModal");
|
||||
},
|
||||
linkClick(e) {
|
||||
if (!e.metaKey && !e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.selectLink(e.target.closest(".search-link"));
|
||||
}
|
||||
},
|
||||
search() {
|
||||
this._debounced = discourseDebounce(this, this.triggerSearch, 400);
|
||||
},
|
||||
},
|
||||
});
|
|
@ -33,7 +33,6 @@ const KNOWN_LEGACY_MODALS = [
|
|||
"history",
|
||||
"ignore-duration-with-username",
|
||||
"ignore-duration",
|
||||
"insert-hyperlink",
|
||||
"jump-to-post",
|
||||
"login",
|
||||
"move-to-topic",
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
<DModalBody @title="composer.link_dialog_title" @class="insert-link">
|
||||
<form id="insert-hyperlink-form" {{on "submit" (action "ok")}}>
|
||||
<div class="inputs">
|
||||
<TextField
|
||||
@value={{this.linkUrl}}
|
||||
@placeholderKey="composer.link_url_placeholder"
|
||||
@class="link-url"
|
||||
@key-up={{action "search"}}
|
||||
@autofocus="autofocus"
|
||||
/>
|
||||
{{#if this.searchLoading}}
|
||||
{{loading-spinner}}
|
||||
{{/if}}
|
||||
{{#if this.searchResults}}
|
||||
<div class="internal-link-results">
|
||||
{{#each this.searchResults as |result|}}
|
||||
<a
|
||||
class="search-link"
|
||||
href={{result.url}}
|
||||
onclick={{action "linkClick"}}
|
||||
data-title={{result.fancy_title}}
|
||||
>
|
||||
<TopicStatus @topic={{result}} @disableActions={{true}} />
|
||||
{{replace-emoji result.title}}
|
||||
<div class="search-category">
|
||||
{{#if result.category.parentCategory}}
|
||||
{{category-link result.category.parentCategory}}
|
||||
{{/if}}
|
||||
{{category-link result.category hideParent=true}}
|
||||
{{discourse-tags result}}
|
||||
</div>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="inputs">
|
||||
<TextField
|
||||
@value={{this.linkText}}
|
||||
@placeholderKey="composer.link_optional_text"
|
||||
@class="link-text"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</DModalBody>
|
||||
|
||||
<div class="modal-footer">
|
||||
<DButton
|
||||
@class="btn-primary"
|
||||
@label="composer.modal_ok"
|
||||
@action={{action "ok"}}
|
||||
@type="submit"
|
||||
@form="insert-hyperlink-form"
|
||||
/>
|
||||
|
||||
<DButton
|
||||
@class="btn-danger"
|
||||
@label="composer.modal_cancel"
|
||||
@action={{action "cancel"}}
|
||||
/>
|
||||
</div>
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
acceptance,
|
||||
exists,
|
||||
query,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
|
||||
|
@ -14,28 +10,27 @@ acceptance("Composer - Hyperlink", function (needs) {
|
|||
await click(".topic-post:first-child button.reply");
|
||||
await fillIn(".d-editor-input", "This is a link to ");
|
||||
|
||||
assert.ok(
|
||||
!exists(".insert-link.modal-body"),
|
||||
"no hyperlink modal by default"
|
||||
);
|
||||
assert
|
||||
.dom(".insert-link.modal-body")
|
||||
.doesNotExist("no hyperlink modal by default");
|
||||
|
||||
await click(".d-editor button.link");
|
||||
assert.ok(exists(".insert-link.modal-body"), "hyperlink modal visible");
|
||||
assert.dom(".insert-link.modal-body").exists("hyperlink modal visible");
|
||||
|
||||
await fillIn(".modal-body .link-url", "google.com");
|
||||
await fillIn(".modal-body .link-text", "Google");
|
||||
await click(".modal-footer button.btn-primary");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".d-editor-input").value,
|
||||
"This is a link to [Google](https://google.com)",
|
||||
"adds link with url and text, prepends 'https://'"
|
||||
);
|
||||
assert
|
||||
.dom(".d-editor-input")
|
||||
.hasValue(
|
||||
"This is a link to [Google](https://google.com)",
|
||||
"adds link with url and text, prepends 'https://'"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
!exists(".insert-link.modal-body"),
|
||||
"modal dismissed after submitting link"
|
||||
);
|
||||
assert
|
||||
.dom(".insert-link.modal-body")
|
||||
.doesNotExist("modal dismissed after submitting link");
|
||||
|
||||
await fillIn(".d-editor-input", "Reset textarea contents.");
|
||||
|
||||
|
@ -44,16 +39,16 @@ acceptance("Composer - Hyperlink", function (needs) {
|
|||
await fillIn(".modal-body .link-text", "Google");
|
||||
await click(".modal-footer button.btn-danger");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".d-editor-input").value,
|
||||
"Reset textarea contents.",
|
||||
"doesn’t insert anything after cancelling"
|
||||
);
|
||||
assert
|
||||
.dom(".d-editor-input")
|
||||
.hasValue(
|
||||
"Reset textarea contents.",
|
||||
"doesn’t insert anything after cancelling"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
!exists(".insert-link.modal-body"),
|
||||
"modal dismissed after cancelling"
|
||||
);
|
||||
assert
|
||||
.dom(".insert-link.modal-body")
|
||||
.doesNotExist("modal dismissed after cancelling");
|
||||
|
||||
const textarea = query("#reply-control .d-editor-input");
|
||||
textarea.selectionStart = 0;
|
||||
|
@ -63,48 +58,49 @@ acceptance("Composer - Hyperlink", function (needs) {
|
|||
await fillIn(".modal-body .link-url", "somelink.com");
|
||||
await click(".modal-footer button.btn-primary");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".d-editor-input").value,
|
||||
"[Reset](https://somelink.com) textarea contents.",
|
||||
"adds link to a selected text"
|
||||
);
|
||||
assert
|
||||
.dom(".d-editor-input")
|
||||
.hasValue(
|
||||
"[Reset](https://somelink.com) textarea contents.",
|
||||
"adds link to a selected text"
|
||||
);
|
||||
|
||||
await fillIn(".d-editor-input", "");
|
||||
|
||||
await click(".d-editor button.link");
|
||||
await fillIn(".modal-body .link-url", "http://google.com");
|
||||
await triggerKeyEvent(".modal-body .link-url", "keyup", "Space");
|
||||
assert.ok(
|
||||
!exists(".internal-link-results"),
|
||||
"does not show internal links search dropdown when inputting a url"
|
||||
);
|
||||
assert
|
||||
.dom(".internal-link-results")
|
||||
.doesNotExist(
|
||||
"does not show internal links search dropdown when inputting a url"
|
||||
);
|
||||
|
||||
await fillIn(".modal-body .link-url", "local");
|
||||
await triggerKeyEvent(".modal-body .link-url", "keyup", "Space");
|
||||
assert.ok(
|
||||
exists(".internal-link-results"),
|
||||
"shows internal links search dropdown when entering keywords"
|
||||
);
|
||||
assert
|
||||
.dom(".internal-link-results")
|
||||
.exists("shows internal links search dropdown when entering keywords");
|
||||
|
||||
await triggerKeyEvent(".insert-link", "keydown", "ArrowDown");
|
||||
await triggerKeyEvent(".insert-link", "keydown", "Enter");
|
||||
|
||||
assert.ok(
|
||||
!exists(".internal-link-results"),
|
||||
"search dropdown dismissed after selecting an internal link"
|
||||
);
|
||||
assert
|
||||
.dom(".internal-link-results")
|
||||
.doesNotExist(
|
||||
"search dropdown dismissed after selecting an internal link"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
query(".link-url").value.includes("http"),
|
||||
"replaces link url field with internal link"
|
||||
);
|
||||
assert
|
||||
.dom(".link-url")
|
||||
.hasValue(/http/, "replaces link url field with internal link");
|
||||
|
||||
await triggerKeyEvent(".insert-link", "keydown", "Escape");
|
||||
|
||||
assert.strictEqual(
|
||||
document.activeElement.classList.contains("d-editor-input"),
|
||||
true,
|
||||
"focus stays on composer after dismissing modal using Esc key"
|
||||
);
|
||||
assert
|
||||
.dom(".d-editor-input")
|
||||
.isFocused(
|
||||
"focus stays on composer after dismissing modal using Esc key"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
renderUserStatusHtml,
|
||||
} from "discourse/lib/user-status-on-autocomplete";
|
||||
import ChatModalChannelSummary from "discourse/plugins/chat/discourse/components/chat/modal/channel-summary";
|
||||
import InsertHyperlink from "discourse/components/modal/insert-hyperlink";
|
||||
|
||||
export default class ChatComposer extends Component {
|
||||
@service capabilities;
|
||||
|
@ -347,10 +348,12 @@ export default class ChatComposer extends Component {
|
|||
|
||||
const selected = this.composer.textarea.getSelected("", { lineVal: true });
|
||||
const linkText = selected?.value;
|
||||
showModal("insert-hyperlink").setProperties({
|
||||
linkText,
|
||||
toolbarEvent: {
|
||||
addText: (text) => this.composer.textarea.addText(selected, text),
|
||||
this.modal.show(InsertHyperlink, {
|
||||
model: {
|
||||
linkText,
|
||||
toolbarEvent: {
|
||||
addText: (text) => this.composer.textarea.addText(selected, text),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue