From dd100b1737e9b2659dcd7fe96f889b0ed31368c4 Mon Sep 17 00:00:00 2001 From: Alice39s Date: Tue, 15 Sep 2026 23:25:25 +0900 Subject: [PATCH] fix(ui): point markdown actions at page source paths --- assets/js/ai-tools.js | 79 ++++++++++++++++----------------- overrides/partials/actions.html | 2 +- src/nmteam_support/plugin.py | 34 +++++++++----- tests/test_lifecycle.py | 78 ++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/assets/js/ai-tools.js b/assets/js/ai-tools.js index 0b27b8b..7b1ee61 100644 --- a/assets/js/ai-tools.js +++ b/assets/js/ai-tools.js @@ -16,12 +16,12 @@ var CLAUDE_CODE_REPOSITORY = "nm-Team/Support"; var CLAUDE_CODE_BRANCH = "main"; - function mdUrl(raw) { - var path = raw ? "/" + raw : "/index.md"; - if (/\/$/.test(path)) { - path += "index.md"; - } - return new URL(path, location.href).href; + // The page's own Markdown copy sits at its docs-relative source path. + // Anything that resolves away from this origin (a tampered data attribute, + // say) is refused instead of being opened or fetched. + function mdUrl(sourcePath) { + var url = new URL("/" + sourcePath.replace(/^\/+/, ""), location.href); + return url.origin === location.origin ? url : null; } function providerPrompt(url) { @@ -106,6 +106,9 @@ } function loadMarkdown(md) { + if (!md) { + return Promise.resolve(null); + } return fetch(md) .then(function (response) { var contentType = response.headers.get("Content-Type") || ""; @@ -146,40 +149,29 @@ }); } - function wireMarkdown(link, md) { - var availability; - - function checkAvailability() { - if (!availability) { - availability = fetch(md, { method: "HEAD" }) - .then(function (response) { - var contentType = response.headers.get("Content-Type") || ""; - return response.ok && contentType.indexOf("html") === -1; - }) - .catch(function () { - return false; - }); - } - return availability; + // Whether the Markdown copy is really served next to the rendered page. + function markdownAvailability(md) { + if (!md) { + return Promise.resolve(false); } + return fetch(md, { method: "HEAD" }) + .then(function (response) { + var contentType = response.headers.get("Content-Type") || ""; + return response.ok && contentType.indexOf("html") === -1; + }) + .catch(function () { + return false; + }); + } + function wireMarkdown(link, md, isAvailable) { link.href = md; link.addEventListener("click", function (event) { - event.preventDefault(); - var target = window.open("about:blank", "_blank"); - if (target) { - target.opener = null; + if (isAvailable()) { + return; } - checkAvailability().then(function (available) { - if (available && target) { - target.location.replace(md); - return; - } - if (target) { - target.close(); - } - showMarkdownUnavailable(); - }); + event.preventDefault(); + showMarkdownUnavailable(); }); } @@ -187,13 +179,20 @@ var trigger = box.querySelector(".ai-tools__trigger"); var copy = box.querySelector(".ai-tools__copy"); var menu = box.querySelector(".ai-tools__menu"); - var raw = box.getAttribute("data-md-url") || ""; + var sourcePath = box.getAttribute("data-md-path") || ""; var markdown = menu.querySelector('[data-ai="markdown"]'); - var md = mdUrl(raw); - var prompt = providerPrompt(md); + var md = mdUrl(sourcePath); + var mdHref = md ? md.href : ""; + var markdownAvailable = false; + var prompt = providerPrompt(mdHref); - wireCopy(copy, md); - wireMarkdown(markdown, md); + markdownAvailability(mdHref).then(function (available) { + markdownAvailable = available; + }); + wireCopy(copy, mdHref); + wireMarkdown(markdown, mdHref, function () { + return markdownAvailable; + }); [ "perplexity", "grok", diff --git a/overrides/partials/actions.html b/overrides/partials/actions.html index 2ea64dc..e5ad9f2 100644 --- a/overrides/partials/actions.html +++ b/overrides/partials/actions.html @@ -41,7 +41,7 @@ {% set source_url = "https://github.com/nm-Team/Support/blob/main/docs/" ~ page.file.src_uri %}