diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index 8cd3c541bae..dc2e9fe00ab 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -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); - } - } } } }); diff --git a/app/assets/javascripts/discourse/controllers/insert-hyperlink.js.es6 b/app/assets/javascripts/discourse/controllers/insert-hyperlink.js.es6 new file mode 100644 index 00000000000..d4f15d95cfc --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/insert-hyperlink.js.es6 @@ -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"); + } + } +}); diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs index 1f825661455..9435d744f80 100644 --- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -1,13 +1,3 @@ - - -
- {{#d-editor-modal class="insert-link" hidden=insertLinkHidden okAction=(action "insertLink")}} -

{{i18n "composer.link_dialog_title"}}

- {{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}} -
-
diff --git a/app/assets/javascripts/discourse/templates/modal/insert-hyperlink.hbs b/app/assets/javascripts/discourse/templates/modal/insert-hyperlink.hbs new file mode 100644 index 00000000000..8aa06072666 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/modal/insert-hyperlink.hbs @@ -0,0 +1,13 @@ +{{#d-modal-body title="composer.link_dialog_title" class="insert-link"}} +
+ {{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-modal-body}} + + diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss index 52722452159..bf181bfc70a 100644 --- a/app/assets/stylesheets/common/base/modal.scss +++ b/app/assets/stylesheets/common/base/modal.scss @@ -199,6 +199,12 @@ &.full-height-modal { max-height: calc(100vh - 150px); } + + &.insert-link { + input { + min-width: 300px; + } + } textarea { width: 99%; height: 80px; diff --git a/app/assets/stylesheets/common/d-editor.scss b/app/assets/stylesheets/common/d-editor.scss index 4937bc71fc5..89aa1e92218 100644 --- a/app/assets/stylesheets/common/d-editor.scss +++ b/app/assets/stylesheets/common/d-editor.scss @@ -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; diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss index 861c62442de..9a2adb962b4 100644 --- a/app/assets/stylesheets/desktop/modal.scss +++ b/app/assets/stylesheets/desktop/modal.scss @@ -44,6 +44,12 @@ .category-chooser { width: 50%; } + + .modal-body.insert-link { + input { + min-width: 450px; + } + } } .edit-category-modal { diff --git a/test/javascripts/acceptance/composer-hyperlink-test.js.es6 b/test/javascripts/acceptance/composer-hyperlink-test.js.es6 new file mode 100644 index 00000000000..52ff6c7d035 --- /dev/null +++ b/test/javascripts/acceptance/composer-hyperlink-test.js.es6 @@ -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" + ); +}); diff --git a/test/javascripts/components/d-editor-test.js.es6 b/test/javascripts/components/d-editor-test.js.es6 index 7566ee8b16f..cb008f03233 100644 --- a/test/javascripts/components/d-editor-test.js.es6 +++ b/test/javascripts/components/d-editor-test.js.es6 @@ -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() {