UX: Better composer hyperlink modal (#8160)

This commit is contained in:
Penar Musaraj 2019-10-08 16:19:07 -04:00 committed by GitHub
parent 1ee633cea4
commit 30cda1761d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 154 additions and 130 deletions

View File

@ -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);
}
}
}
}
});

View File

@ -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");
}
}
});

View File

@ -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'>

View File

@ -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>

View File

@ -199,6 +199,12 @@
&.full-height-modal {
max-height: calc(100vh - 150px);
}
&.insert-link {
input {
min-width: 300px;
}
}
textarea {
width: 99%;
height: 80px;

View File

@ -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;

View File

@ -44,6 +44,12 @@
.category-chooser {
width: 50%;
}
.modal-body.insert-link {
input {
min-width: 450px;
}
}
}
.edit-category-modal {

View 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"
);
});

View File

@ -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() {