still trying to fix the default onebox snatch.
This commit is contained in:
@@ -12,7 +12,22 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Inject a helper button + status banner into the composer
|
||||
// Intercept Discourse's native title URL lookup at the CLASS level.
|
||||
// Instance-patching is too late — Discourse's own titleChanged observer
|
||||
// may fire before ours. This wraps the prototype method so the flag wins
|
||||
// regardless of observer order.
|
||||
// -----------------------------------------------------------------------
|
||||
api.modifyClass("model:composer", {
|
||||
pluginId: "url-to-article",
|
||||
|
||||
_titleLookup() {
|
||||
if (this._urlToArticleSuppressLookup) return;
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Inject helper button + status banner into the composer
|
||||
// -----------------------------------------------------------------------
|
||||
api.modifyClass("component:composer-editor", {
|
||||
pluginId: "url-to-article",
|
||||
@@ -28,20 +43,51 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
},
|
||||
|
||||
_setupUrlToArticle() {
|
||||
// Watch the title field — it lives outside the composer-editor DOM,
|
||||
// so we observe via the composer model's `title` property.
|
||||
const composer = this.get("composer");
|
||||
if (!composer) return;
|
||||
|
||||
this._titleObserver = () => this._onTitleChanged();
|
||||
composer.addObserver("model.title", this, "_titleObserver");
|
||||
|
||||
// Intercept paste on the title input BEFORE Ember's data-binding fires,
|
||||
// so the suppression flag is set before Discourse's titleChanged observer runs.
|
||||
this._attachTitlePasteListener();
|
||||
},
|
||||
|
||||
_attachTitlePasteListener() {
|
||||
const tryAttach = (attempts = 0) => {
|
||||
const input = document.querySelector("#reply-title");
|
||||
if (input) {
|
||||
this._titlePasteHandler = (e) => {
|
||||
const text = (
|
||||
e.clipboardData || window.clipboardData
|
||||
).getData("text/plain").trim();
|
||||
if (URL_REGEX.test(text)) {
|
||||
const model = this.get("composer.model");
|
||||
if (model) model._urlToArticleSuppressLookup = true;
|
||||
}
|
||||
};
|
||||
input.addEventListener("paste", this._titlePasteHandler, true);
|
||||
this._titleInputEl = input;
|
||||
} else if (attempts < 10) {
|
||||
setTimeout(() => tryAttach(attempts + 1), 200);
|
||||
}
|
||||
};
|
||||
tryAttach();
|
||||
},
|
||||
|
||||
_teardownUrlToArticle() {
|
||||
const composer = this.get("composer");
|
||||
if (!composer) return;
|
||||
composer.removeObserver("model.title", this, "_titleObserver");
|
||||
this._restoreNativeTitleLookup();
|
||||
this._clearSuppression();
|
||||
if (this._titleInputEl && this._titlePasteHandler) {
|
||||
this._titleInputEl.removeEventListener(
|
||||
"paste",
|
||||
this._titlePasteHandler,
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_onTitleChanged() {
|
||||
@@ -49,18 +95,19 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
const match = title.trim().match(URL_REGEX);
|
||||
|
||||
if (!match) {
|
||||
this._restoreNativeTitleLookup();
|
||||
this._clearSuppression();
|
||||
this._hideArticleBar();
|
||||
return;
|
||||
}
|
||||
|
||||
const url = match[1];
|
||||
|
||||
if (this._lastDetectedUrl === url) return; // Same URL — no-op
|
||||
if (this._lastDetectedUrl === url) return;
|
||||
this._lastDetectedUrl = url;
|
||||
|
||||
// Suppress Discourse's native title lookup so it doesn't race our bar
|
||||
this._suppressNativeTitleLookup();
|
||||
// Belt-and-suspenders: ensure suppression is set even if paste listener
|
||||
// missed it (e.g. URL typed manually rather than pasted).
|
||||
const model = this.get("composer.model");
|
||||
if (model) model._urlToArticleSuppressLookup = true;
|
||||
|
||||
const autoPopulate = api.container
|
||||
.lookup("site-settings:main")
|
||||
@@ -73,26 +120,15 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
}
|
||||
},
|
||||
|
||||
// ---- Native title-lookup suppression ------------------------------
|
||||
// ---- Suppression helpers ------------------------------------------
|
||||
|
||||
_suppressNativeTitleLookup() {
|
||||
_clearSuppression() {
|
||||
const model = this.get("composer.model");
|
||||
if (!model || this._originalTitleLookup) return;
|
||||
if (typeof model._titleLookup === "function") {
|
||||
this._originalTitleLookup = model._titleLookup.bind(model);
|
||||
model._titleLookup = () => {};
|
||||
}
|
||||
if (model) delete model._urlToArticleSuppressLookup;
|
||||
},
|
||||
|
||||
_restoreNativeTitleLookup() {
|
||||
const model = this.get("composer.model");
|
||||
if (!model || !this._originalTitleLookup) return;
|
||||
model._titleLookup = this._originalTitleLookup;
|
||||
this._originalTitleLookup = null;
|
||||
},
|
||||
|
||||
_triggerNativeTitleLookup() {
|
||||
this._restoreNativeTitleLookup();
|
||||
_triggerNativeLookup() {
|
||||
this._clearSuppression();
|
||||
const model = this.get("composer.model");
|
||||
if (model && typeof model._titleLookup === "function") {
|
||||
model._titleLookup();
|
||||
@@ -102,7 +138,7 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
// ---- Bar UI -------------------------------------------------------
|
||||
|
||||
_showArticleBar(url) {
|
||||
this._hideArticleBar(); // remove any existing bar first
|
||||
this._hideArticleBar();
|
||||
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "url-to-article-bar";
|
||||
@@ -126,13 +162,13 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
bar.querySelector(".url-to-article-onebox-btn").addEventListener("click", () => {
|
||||
this._hideArticleBar();
|
||||
this._lastDetectedUrl = null;
|
||||
this._triggerNativeTitleLookup();
|
||||
this._triggerNativeLookup();
|
||||
});
|
||||
|
||||
bar.querySelector(".url-to-article-dismiss").addEventListener("click", () => {
|
||||
this._hideArticleBar();
|
||||
this._restoreNativeTitleLookup();
|
||||
this._lastDetectedUrl = null; // Allow re-detection if title changes
|
||||
this._clearSuppression();
|
||||
this._lastDetectedUrl = null;
|
||||
});
|
||||
|
||||
const toolbarEl = this.element.querySelector(".d-editor-container");
|
||||
@@ -142,7 +178,9 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
},
|
||||
|
||||
_hideArticleBar() {
|
||||
this.element?.querySelectorAll(".url-to-article-bar").forEach((el) => el.remove());
|
||||
this.element
|
||||
?.querySelectorAll(".url-to-article-bar")
|
||||
.forEach((el) => el.remove());
|
||||
},
|
||||
|
||||
_setStatus(message, type = "info") {
|
||||
@@ -182,14 +220,19 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
}
|
||||
|
||||
this._populateComposer(data);
|
||||
this._restoreNativeTitleLookup();
|
||||
this._clearSuppression();
|
||||
this._setStatus(I18n.t("url_to_article.success"), "success");
|
||||
|
||||
// Auto-hide bar after 3 seconds on success
|
||||
setTimeout(() => this._hideArticleBar(), 3000);
|
||||
} catch (err) {
|
||||
const msg = err.jqXHR?.responseJSON?.error || err.message || I18n.t("url_to_article.error_generic");
|
||||
this._setStatus(`${I18n.t("url_to_article.error_prefix")} ${msg}`, "error");
|
||||
const msg =
|
||||
err.jqXHR?.responseJSON?.error ||
|
||||
err.message ||
|
||||
I18n.t("url_to_article.error_generic");
|
||||
this._setStatus(
|
||||
`${I18n.t("url_to_article.error_prefix")} ${msg}`,
|
||||
"error"
|
||||
);
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = I18n.t("url_to_article.retry_button");
|
||||
@@ -201,10 +244,8 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
const composerModel = this.get("composer.model");
|
||||
if (!composerModel) return;
|
||||
|
||||
// Build the article body in Markdown
|
||||
const lines = [];
|
||||
|
||||
// Attribution header
|
||||
const siteName = data.site_name ? `**${data.site_name}**` : "";
|
||||
const byline = data.byline ? ` — *${data.byline}*` : "";
|
||||
if (siteName || byline) {
|
||||
@@ -227,7 +268,6 @@ export default apiInitializer("1.8.0", (api) => {
|
||||
|
||||
const body = lines.join("\n");
|
||||
|
||||
// Only set title if it's still the raw URL (avoid overwriting edited titles)
|
||||
const currentTitle = composerModel.get("title") || "";
|
||||
if (currentTitle.trim() === data.url || currentTitle.trim() === "") {
|
||||
composerModel.set("title", data.title || data.url);
|
||||
|
||||
Reference in New Issue
Block a user