mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 11:13:38 +08:00
UX: Better composer hyperlink modal (#8160)
This commit is contained in:
parent
1ee633cea4
commit
30cda1761d
|
@ -21,6 +21,7 @@ import { wantsNewWindow } from "discourse/lib/intercept-click";
|
|||
import { translations } from "pretty-text/emoji/data";
|
||||
import { emojiSearch, isSkinTonableEmoji } from "pretty-text/emoji";
|
||||
import { emojiUrlFor } from "discourse/lib/text";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
// Our head can be a static string or a function that returns a string
|
||||
// based on input (like for numbered lists).
|
||||
|
@ -89,7 +90,7 @@ class Toolbar {
|
|||
id: "link",
|
||||
group: "insertions",
|
||||
shortcut: "K",
|
||||
action: (...args) => this.context.send("showLinkModal", args)
|
||||
sendAction: event => this.context.send("showLinkModal", event)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -213,9 +214,6 @@ export function onToolbarCreate(func) {
|
|||
export default Ember.Component.extend({
|
||||
classNames: ["d-editor"],
|
||||
ready: false,
|
||||
insertLinkHidden: true,
|
||||
linkUrl: "",
|
||||
linkText: "",
|
||||
lastSel: null,
|
||||
_mouseTrap: null,
|
||||
showLink: true,
|
||||
|
@ -946,21 +944,23 @@ export default Ember.Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
showLinkModal() {
|
||||
showLinkModal(toolbarEvent) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("linkUrl", "");
|
||||
this.set("linkText", "");
|
||||
|
||||
let linkText = "";
|
||||
this._lastSel = this._getSelected();
|
||||
|
||||
if (this._lastSel) {
|
||||
this.set("linkText", this._lastSel.value.trim());
|
||||
linkText = this._lastSel.value.trim();
|
||||
}
|
||||
|
||||
this.set("insertLinkHidden", false);
|
||||
showModal("insert-hyperlink").setProperties({
|
||||
linkText: linkText,
|
||||
_lastSel: this._lastSel,
|
||||
toolbarEvent
|
||||
});
|
||||
},
|
||||
|
||||
formatCode() {
|
||||
|
@ -1004,29 +1004,6 @@ export default Ember.Component.extend({
|
|||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
insertLink() {
|
||||
const origLink = this.linkUrl;
|
||||
const linkUrl =
|
||||
origLink.indexOf("://") === -1 ? `http://${origLink}` : origLink;
|
||||
const sel = this._lastSel;
|
||||
|
||||
if (Ember.isEmpty(linkUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linkText = this.linkText || "";
|
||||
if (linkText.length) {
|
||||
this._addText(sel, `[${linkText}](${linkUrl})`);
|
||||
} else {
|
||||
if (sel.value) {
|
||||
this._addText(sel, `[${sel.value}](${linkUrl})`);
|
||||
} else {
|
||||
this._addText(sel, `[${origLink}](${linkUrl})`);
|
||||
this._selectText(sel.start + 1, origLink.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
linkUrl: "",
|
||||
linkText: "",
|
||||
|
||||
onShow() {
|
||||
Ember.run.next(() =>
|
||||
$(this)
|
||||
.find("input.link-url")
|
||||
.focus()
|
||||
);
|
||||
},
|
||||
|
||||
actions: {
|
||||
ok() {
|
||||
const origLink = this.linkUrl;
|
||||
const linkUrl =
|
||||
origLink.indexOf("://") === -1 ? `http://${origLink}` : origLink;
|
||||
const sel = this._lastSel;
|
||||
|
||||
if (Ember.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.set("linkUrl", "");
|
||||
this.set("linkText", "");
|
||||
this.send("closeModal");
|
||||
},
|
||||
cancel() {
|
||||
this.send("closeModal");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,13 +1,3 @@
|
|||
<div class='d-editor-overlay hidden'></div>
|
||||
|
||||
<div class='d-editor-modals'>
|
||||
{{#d-editor-modal class="insert-link" hidden=insertLinkHidden okAction=(action "insertLink")}}
|
||||
<h3>{{i18n "composer.link_dialog_title"}}</h3>
|
||||
{{text-field value=linkUrl placeholderKey="composer.link_url_placeholder" class="link-url"}}
|
||||
{{text-field value=linkText placeholderKey="composer.link_optional_text" class="link-text"}}
|
||||
{{/d-editor-modal}}
|
||||
</div>
|
||||
|
||||
<div class='d-editor-container'>
|
||||
<div class="d-editor-textarea-wrapper {{if disabled "disabled"}}">
|
||||
<div class='d-editor-button-bar'>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{{#d-modal-body title="composer.link_dialog_title" class="insert-link"}}
|
||||
<div class="inputs">
|
||||
{{text-field value=linkUrl placeholderKey="composer.link_url_placeholder" class="link-url"}}
|
||||
</div>
|
||||
<div class="inputs">
|
||||
{{text-field value=linkText placeholderKey="composer.link_optional_text" class="link-text"}}
|
||||
</div>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button class="btn-primary" label="composer.modal_ok" action=(action "ok")}}
|
||||
{{d-button class="btn-danger" label="composer.modal_cancel" action=(action "cancel")}}
|
||||
</div>
|
|
@ -199,6 +199,12 @@
|
|||
&.full-height-modal {
|
||||
max-height: calc(100vh - 150px);
|
||||
}
|
||||
|
||||
&.insert-link {
|
||||
input {
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
||||
textarea {
|
||||
width: 99%;
|
||||
height: 80px;
|
||||
|
|
|
@ -4,43 +4,12 @@
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.d-editor-overlay {
|
||||
position: absolute;
|
||||
background-color: black;
|
||||
opacity: 0.8;
|
||||
z-index: z("modal", "overlay");
|
||||
}
|
||||
|
||||
.d-editor-modals {
|
||||
position: absolute;
|
||||
z-index: z("modal", "content");
|
||||
}
|
||||
|
||||
.d-editor {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.d-editor .d-editor-modal {
|
||||
min-width: 400px;
|
||||
@media screen and (max-width: 424px) {
|
||||
min-width: 300px;
|
||||
}
|
||||
position: absolute;
|
||||
background-color: $secondary;
|
||||
border: 1px solid $primary-low;
|
||||
padding: 1em;
|
||||
top: 25px;
|
||||
|
||||
input {
|
||||
width: 95%;
|
||||
}
|
||||
h3 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.d-editor-textarea-wrapper,
|
||||
.d-editor-preview-wrapper {
|
||||
flex: 1;
|
||||
|
|
|
@ -44,6 +44,12 @@
|
|||
.category-chooser {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.modal-body.insert-link {
|
||||
input {
|
||||
min-width: 450px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-category-modal {
|
||||
|
|
73
test/javascripts/acceptance/composer-hyperlink-test.js.es6
Normal file
73
test/javascripts/acceptance/composer-hyperlink-test.js.es6
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { acceptance } from "helpers/qunit-helpers";
|
||||
|
||||
acceptance("Composer - Hyperlink", {
|
||||
loggedIn: true
|
||||
});
|
||||
|
||||
QUnit.test("add a hyperlink to a reply", async assert => {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await click(".topic-post:first-child button.reply");
|
||||
await fillIn(".d-editor-input", "This is a link to ");
|
||||
|
||||
assert.equal(
|
||||
find(".insert-link.modal-body").length,
|
||||
0,
|
||||
"no hyperlink modal by default"
|
||||
);
|
||||
|
||||
await click(".d-editor button.link");
|
||||
assert.equal(
|
||||
find(".insert-link.modal-body").length,
|
||||
1,
|
||||
"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.equal(
|
||||
find(".d-editor-input").val(),
|
||||
"This is a link to [Google](http://google.com)",
|
||||
"adds link with url and text, prepends 'http://'"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".insert-link.modal-body").length,
|
||||
0,
|
||||
"modal dismissed after submitting link"
|
||||
);
|
||||
|
||||
await fillIn(".d-editor-input", "Reset textarea contents.");
|
||||
|
||||
await click(".d-editor button.link");
|
||||
await fillIn(".modal-body .link-url", "google.com");
|
||||
await fillIn(".modal-body .link-text", "Google");
|
||||
await click(".modal-footer button.btn-danger");
|
||||
|
||||
assert.equal(
|
||||
find(".d-editor-input").val(),
|
||||
"Reset textarea contents.",
|
||||
"adds link with url and text, prepends 'http://'"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".insert-link.modal-body").length,
|
||||
0,
|
||||
"modal dismissed after cancelling"
|
||||
);
|
||||
|
||||
const textarea = find("#reply-control .d-editor-input")[0];
|
||||
textarea.selectionStart = 0;
|
||||
textarea.selectionEnd = 6;
|
||||
await click(".d-editor button.link");
|
||||
|
||||
await fillIn(".modal-body .link-url", "somelink.com");
|
||||
await click(".modal-footer button.btn-primary");
|
||||
|
||||
assert.equal(
|
||||
find(".d-editor-input").val(),
|
||||
"[Reset](http://somelink.com) textarea contents.",
|
||||
"adds link to a selected text"
|
||||
);
|
||||
});
|
|
@ -200,62 +200,6 @@ testCase(`italic with a multiline selection`, async function(assert, textarea) {
|
|||
assert.equal(textarea.selectionEnd, 12);
|
||||
});
|
||||
|
||||
testCase("link modal (cancel)", async function(assert) {
|
||||
assert.equal(find(".insert-link.hidden").length, 1);
|
||||
|
||||
await click("button.link");
|
||||
assert.equal(find(".insert-link.hidden").length, 0);
|
||||
|
||||
await click(".insert-link button.btn-danger");
|
||||
assert.equal(find(".insert-link.hidden").length, 1);
|
||||
assert.equal(this.value, "hello world.");
|
||||
});
|
||||
|
||||
testCase("link modal (simple link)", async function(assert, textarea) {
|
||||
await click("button.link");
|
||||
|
||||
const url = "http://eviltrout.com";
|
||||
|
||||
await fillIn(".insert-link input.link-url", url);
|
||||
await click(".insert-link button.btn-primary");
|
||||
assert.equal(find(".insert-link.hidden").length, 1);
|
||||
assert.equal(this.value, `hello world.[${url}](${url})`);
|
||||
assert.equal(textarea.selectionStart, 13);
|
||||
assert.equal(textarea.selectionEnd, 13 + url.length);
|
||||
});
|
||||
|
||||
testCase("link modal auto http addition", async function(assert) {
|
||||
await click("button.link");
|
||||
await fillIn(".insert-link input.link-url", "sam.com");
|
||||
await click(".insert-link button.btn-primary");
|
||||
assert.equal(this.value, `hello world.[sam.com](http://sam.com)`);
|
||||
});
|
||||
|
||||
testCase("link modal (simple link) with selected text", async function(
|
||||
assert,
|
||||
textarea
|
||||
) {
|
||||
textarea.selectionStart = 0;
|
||||
textarea.selectionEnd = 12;
|
||||
|
||||
await click("button.link");
|
||||
assert.equal(find("input.link-text")[0].value, "hello world.");
|
||||
|
||||
await fillIn(".insert-link input.link-url", "http://eviltrout.com");
|
||||
await click(".insert-link button.btn-primary");
|
||||
assert.equal(find(".insert-link.hidden").length, 1);
|
||||
assert.equal(this.value, "[hello world.](http://eviltrout.com)");
|
||||
});
|
||||
|
||||
testCase("link modal (link with description)", async function(assert) {
|
||||
await click("button.link");
|
||||
await fillIn(".insert-link input.link-url", "http://eviltrout.com");
|
||||
await fillIn(".insert-link input.link-text", "evil trout");
|
||||
await click(".insert-link button.btn-primary");
|
||||
assert.equal(find(".insert-link.hidden").length, 1);
|
||||
assert.equal(this.value, "hello world.[evil trout](http://eviltrout.com)");
|
||||
});
|
||||
|
||||
componentTest("advanced code", {
|
||||
template: "{{d-editor value=value}}",
|
||||
beforeEach() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user