diff --git a/app/assets/javascripts/discourse/controllers/insert-hyperlink.js.es6 b/app/assets/javascripts/discourse/controllers/insert-hyperlink.js.es6 index d4f15d95cf..313ab332c3 100644 --- a/app/assets/javascripts/discourse/controllers/insert-hyperlink.js.es6 +++ b/app/assets/javascripts/discourse/controllers/insert-hyperlink.js.es6 @@ -1,15 +1,140 @@ import ModalFunctionality from "discourse/mixins/modal-functionality"; +import { searchForTerm } from "discourse/lib/search"; export default Ember.Controller.extend(ModalFunctionality, { - linkUrl: "", - linkText: "", + _debounced: null, + _activeSearch: null, onShow() { - Ember.run.next(() => - $(this) - .find("input.link-url") - .focus() - ); + this.setProperties({ + linkUrl: "", + linkText: "", + searchResults: [], + searchLoading: false, + selectedRow: -1 + }); + + Ember.run.scheduleOnce("afterRender", () => { + const element = document.querySelector(".insert-link"); + + element.addEventListener("keydown", e => this.keyDown(e)); + + element + .closest(".modal-inner-container") + .addEventListener("mousedown", e => this.mouseDown(e)); + + document.querySelector("input.link-url").focus(); + }); + }, + + keyDown(e) { + switch (e.which) { + case 40: + this.highlightRow(e, "down"); + break; + case 38: + this.highlightRow(e, "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); + e.preventDefault(); + e.stopPropagation(); + } + break; + case 27: + // Esc should cancel dropdown first + if (this.searchResults.length) { + this.set("searchResults", []); + e.preventDefault(); + e.stopPropagation(); + } + break; + } + }, + + mouseDown(e) { + if (!e.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.indexOf("http") === -1) { + 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); + + Ember.run.cancel(this._debounced); }, actions: { @@ -35,12 +160,20 @@ export default Ember.Controller.extend(ModalFunctionality, { this.toolbarEvent.selectText(sel.start + 1, origLink.length); } } - this.set("linkUrl", ""); - this.set("linkText", ""); this.send("closeModal"); }, cancel() { this.send("closeModal"); + }, + linkClick(e) { + if (!e.metaKey && !e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + this.selectLink(e.target); + } + }, + search() { + this._debounced = Ember.run.debounce(this, this.triggerSearch, 400); } } }); diff --git a/app/assets/javascripts/discourse/templates/modal/insert-hyperlink.hbs b/app/assets/javascripts/discourse/templates/modal/insert-hyperlink.hbs index 8aa0607266..ec15a2b501 100644 --- a/app/assets/javascripts/discourse/templates/modal/insert-hyperlink.hbs +++ b/app/assets/javascripts/discourse/templates/modal/insert-hyperlink.hbs @@ -1,6 +1,27 @@ {{#d-modal-body title="composer.link_dialog_title" class="insert-link"}}