From ea61c91774ecdecf0ccc06eba095cf314c4deb93 Mon Sep 17 00:00:00 2001 From: robert Date: Mon, 23 Mar 2026 16:22:23 -0400 Subject: [PATCH] Removing id:discourse.resolver-resolutions notice. --- .../discourse/initializers/bookmark-url.js | 337 +++++++++--------- 1 file changed, 172 insertions(+), 165 deletions(-) diff --git a/assets/javascripts/discourse/initializers/bookmark-url.js b/assets/javascripts/discourse/initializers/bookmark-url.js index 711df23..dae6979 100644 --- a/assets/javascripts/discourse/initializers/bookmark-url.js +++ b/assets/javascripts/discourse/initializers/bookmark-url.js @@ -17,197 +17,204 @@ const STRINGS = { }; export default apiInitializer("1.8.0", (api) => { - if (!api.container.lookup("site-settings:main").bookmark_url_enabled) { + if (!api.container.lookup("service:site-settings").bookmark_url_enabled) { return; } - api.modifyClass("component:composer-editor", { - pluginId: "bookmark-url", + let titleInputEl = null; + let titlePasteHandler = null; + let pendingUrl = null; - didInsertElement() { - this._super(...arguments); - this._attachTitlePasteListener(); - }, + // ---- Bar UI ------------------------------------------------------- - willDestroyElement() { - this._super(...arguments); - if (this._titleInputEl && this._titlePasteHandler) { - this._titleInputEl.removeEventListener( - "paste", - this._titlePasteHandler, - true - ); - } - }, + function showArticleBar(url) { + hideArticleBar(); - // ---- Title paste interception ------------------------------------- - // capture:true so our handler runs before Discourse's, then - // preventDefault + stopImmediatePropagation so Discourse never sees - // the paste event — no title lookup, no onebox race. + const bar = document.createElement("div"); + bar.className = "bookmark-url-bar"; + bar.innerHTML = ` + 📄 + ${STRINGS.bar_label} + + + + `; - _attachTitlePasteListener(attempts = 0) { - const input = document.querySelector("#reply-title"); - if (input) { - this._titleInputEl = input; - this._titlePasteHandler = (e) => { - const text = (e.clipboardData || window.clipboardData) - .getData("text/plain") - .trim(); - if (!URL_REGEX.test(text)) return; + bar.querySelector(".bookmark-url-btn").addEventListener("click", () => { + fetchAndPopulate(url); + }); - e.preventDefault(); - e.stopImmediatePropagation(); + bar.querySelector(".bookmark-url-onebox-btn").addEventListener("click", () => { + hideArticleBar(); + commitUrlToModel(); + }); - // Show URL in the field visually without going through Ember binding - input.value = text; - this._pendingUrl = text; - this._showArticleBar(text); - }; - input.addEventListener("paste", this._titlePasteHandler, true); - } else if (attempts < 15) { - setTimeout(() => this._attachTitlePasteListener(attempts + 1), 200); - } - }, + bar.querySelector(".bookmark-url-dismiss").addEventListener("click", () => { + hideArticleBar(); + pendingUrl = null; + }); - // ---- Bar UI ------------------------------------------------------- + const container = document.querySelector(".d-editor-container"); + if (container) { + container.parentElement.insertBefore(bar, container); + } else { + document + .querySelector(".composer-fields") + ?.insertAdjacentElement("afterbegin", bar); + } + } - _showArticleBar(url) { - this._hideArticleBar(); + function hideArticleBar() { + document + .querySelectorAll(".bookmark-url-bar") + .forEach((el) => el.remove()); + } - const bar = document.createElement("div"); - bar.className = "bookmark-url-bar"; - bar.innerHTML = ` - 📄 - ${STRINGS.bar_label} - - - - `; + function setStatus(message, type = "info") { + const bar = document.querySelector(".bookmark-url-bar"); + if (!bar) return; + let status = bar.querySelector(".bookmark-url-status"); + if (!status) { + status = document.createElement("span"); + status.className = "bookmark-url-status"; + bar.appendChild(status); + } + status.textContent = message; + status.className = `bookmark-url-status bookmark-url-status--${type}`; + } - bar.querySelector(".bookmark-url-btn").addEventListener("click", () => { - this._fetchAndPopulate(url); + // Release the URL into Ember's data-binding so Discourse handles it normally + function commitUrlToModel() { + const input = titleInputEl; + const url = pendingUrl; + if (!input || !url) return; + input.value = url; + input.dispatchEvent(new Event("input", { bubbles: true })); + pendingUrl = null; + } + + // ---- Fetch & populate --------------------------------------------- + + async function fetchAndPopulate(url) { + const bar = document.querySelector(".bookmark-url-bar"); + const btn = bar?.querySelector(".bookmark-url-btn"); + + if (btn) { + btn.disabled = true; + btn.textContent = STRINGS.fetching; + } + setStatus(STRINGS.fetching, "info"); + + try { + const data = await ajax("/bookmark-url/extract", { + type: "POST", + data: { url }, }); - bar.querySelector(".bookmark-url-onebox-btn").addEventListener("click", () => { - this._hideArticleBar(); - this._commitUrlToModel(); - }); - - bar.querySelector(".bookmark-url-dismiss").addEventListener("click", () => { - this._hideArticleBar(); - this._pendingUrl = null; - }); - - const container = this.element.querySelector(".d-editor-container"); - if (container) { - container.parentElement.insertBefore(bar, container); - } else { - this.element.insertAdjacentElement("afterbegin", bar); - } - }, - - _hideArticleBar() { - this.element - ?.querySelectorAll(".bookmark-url-bar") - .forEach((el) => el.remove()); - }, - - _setStatus(message, type = "info") { - const bar = this.element?.querySelector(".bookmark-url-bar"); - if (!bar) return; - let status = bar.querySelector(".bookmark-url-status"); - if (!status) { - status = document.createElement("span"); - status.className = "bookmark-url-status"; - bar.appendChild(status); - } - status.textContent = message; - status.className = `bookmark-url-status bookmark-url-status--${type}`; - }, - - // Release the URL into Ember's data-binding so Discourse handles it normally - _commitUrlToModel() { - const input = this._titleInputEl; - const url = this._pendingUrl; - if (!input || !url) return; - input.value = url; - input.dispatchEvent(new Event("input", { bubbles: true })); - this._pendingUrl = null; - }, - - // ---- Fetch & populate --------------------------------------------- - - async _fetchAndPopulate(url) { - const bar = this.element?.querySelector(".bookmark-url-bar"); - const btn = bar?.querySelector(".bookmark-url-btn"); + if (data.error) throw new Error(data.error); + populateComposer(data); + setStatus(STRINGS.success, "success"); + setTimeout(() => hideArticleBar(), 3000); + } catch (err) { + const msg = + err.jqXHR?.responseJSON?.error || + err.message || + STRINGS.error_generic; + setStatus(`${STRINGS.error_prefix} ${msg}`, "error"); if (btn) { - btn.disabled = true; - btn.textContent = STRINGS.fetching; + btn.disabled = false; + btn.textContent = STRINGS.retry_button; } - this._setStatus(STRINGS.fetching, "info"); + } + } - try { - const data = await ajax("/bookmark-url/extract", { - type: "POST", - data: { url }, - }); + function populateComposer(data) { + const composerService = api.container.lookup("service:composer"); + const composerModel = composerService?.model; + if (!composerModel) return; - if (data.error) throw new Error(data.error); + const lines = []; + const siteName = data.site_name ? `**${data.site_name}**` : ""; + const byline = data.byline ? ` — *${data.byline}*` : ""; + if (siteName || byline) { + lines.push(`> ${siteName}${byline}`); + lines.push(`> ${STRINGS.source_label}: <${data.url}>`); + lines.push(""); + } else { + lines.push(`> ${STRINGS.source_label}: <${data.url}>`); + lines.push(""); + } - this._populateComposer(data); - this._setStatus(STRINGS.success, "success"); - setTimeout(() => this._hideArticleBar(), 3000); - } catch (err) { - const msg = - err.jqXHR?.responseJSON?.error || - err.message || - STRINGS.error_generic; - this._setStatus(`${STRINGS.error_prefix} ${msg}`, "error"); - if (btn) { - btn.disabled = false; - btn.textContent = STRINGS.retry_button; - } - } - }, + if (data.description) { + lines.push(`*${data.description}*`); + lines.push(""); + lines.push("---"); + lines.push(""); + } - _populateComposer(data) { - const composerModel = this.get("composer.model"); - if (!composerModel) return; + lines.push(data.markdown || ""); - const lines = []; - const siteName = data.site_name ? `**${data.site_name}**` : ""; - const byline = data.byline ? ` — *${data.byline}*` : ""; - if (siteName || byline) { - lines.push(`> ${siteName}${byline}`); - lines.push(`> ${STRINGS.source_label}: <${data.url}>`); - lines.push(""); - } else { - lines.push(`> ${STRINGS.source_label}: <${data.url}>`); - lines.push(""); - } + composerModel.set("title", data.title || pendingUrl || ""); + composerModel.set("reply", lines.join("\n")); + pendingUrl = null; - if (data.description) { - lines.push(`*${data.description}*`); - lines.push(""); - lines.push("---"); - lines.push(""); - } + if (titleInputEl) { + titleInputEl.value = composerModel.get("title"); + } + } - lines.push(data.markdown || ""); + // ---- Title paste interception ------------------------------------- + // capture:true so our handler runs before Discourse's, then + // preventDefault + stopImmediatePropagation so Discourse never sees + // the paste event — no title lookup, no onebox race. - composerModel.set("title", data.title || this._pendingUrl || ""); - composerModel.set("reply", lines.join("\n")); - this._pendingUrl = null; + function attachTitlePasteListener(attempts = 0) { + const input = document.querySelector("#reply-title"); + if (input) { + titleInputEl = input; + titlePasteHandler = (e) => { + const text = (e.clipboardData || window.clipboardData) + .getData("text/plain") + .trim(); + if (!URL_REGEX.test(text)) return; - if (this._titleInputEl) { - this._titleInputEl.value = composerModel.get("title"); - } - }, + e.preventDefault(); + e.stopImmediatePropagation(); + + // Show URL in the field visually without going through Ember binding + input.value = text; + pendingUrl = text; + showArticleBar(text); + }; + input.addEventListener("paste", titlePasteHandler, true); + } else if (attempts < 15) { + setTimeout(() => attachTitlePasteListener(attempts + 1), 200); + } + } + + function detachTitlePasteListener() { + if (titleInputEl && titlePasteHandler) { + titleInputEl.removeEventListener("paste", titlePasteHandler, true); + } + titleInputEl = null; + titlePasteHandler = null; + pendingUrl = null; + hideArticleBar(); + } + + // ---- Composer lifecycle ------------------------------------------- + + api.onAppEvent("composer:opened", () => { + attachTitlePasteListener(); + }); + + api.onAppEvent("composer:closed", () => { + detachTitlePasteListener(); }); });