diff --git a/docs/javascripts/page-actions.js b/docs/javascripts/page-actions.js new file mode 100644 index 0000000..e011cda --- /dev/null +++ b/docs/javascripts/page-actions.js @@ -0,0 +1,75 @@ +/* + * Copy-as-Markdown actions for each documentation page. + * + * The llmstxt plugin writes a .md counterpart next to every rendered page, so + * the source is just the current path with the extension swapped. The controls + * start hidden and are revealed only once that file is confirmed to exist, + * which keeps them off any page the plugin does not cover. + */ + +function markdownUrlForCurrentPage() { + const path = window.location.pathname; + // use_directory_urls is false, so pages are served as *.html. Directory-style + // URLs are still possible for the site root. + if (path.endsWith(".html")) { + return path.replace(/\.html$/, ".md"); + } + return path.replace(/\/$/, "") + "/index.md"; +} + +function initPageActions() { + const container = document.querySelector("[data-ik-page-actions]"); + if (!container) { + return; + } + + const button = container.querySelector("[data-ik-copy]"); + const label = container.querySelector("[data-ik-copy-label]"); + const view = container.querySelector("[data-ik-view]"); + const defaultLabel = label.textContent; + const url = markdownUrlForCurrentPage(); + + view.href = url; + + // A HEAD request avoids pulling the whole file just to decide whether to + // show the controls. + fetch(url, { method: "HEAD" }) + .then((response) => { + if (response.ok) { + container.hidden = false; + } + }) + .catch(() => { + /* Leave the controls hidden. */ + }); + + let resetTimer = null; + + button.addEventListener("click", async () => { + if (resetTimer) { + window.clearTimeout(resetTimer); + } + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Request failed with ${response.status}`); + } + // Requires a secure context; GitHub Pages and localhost both qualify. + await navigator.clipboard.writeText(await response.text()); + label.textContent = "Copied"; + } catch (error) { + label.textContent = "Copy failed"; + } + resetTimer = window.setTimeout(() => { + label.textContent = defaultLabel; + }, 2000); + }); +} + +// document$ is Material's per-page observable, which also fires after instant +// navigation. Fall back to a plain listener if the theme bundle is absent. +if (typeof document$ !== "undefined") { + document$.subscribe(initPageActions); +} else { + document.addEventListener("DOMContentLoaded", initPageActions); +} diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 0e65eb2..e11d1db 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -5,4 +5,24 @@ Switch to latest stable version. -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block content %} + {#- + Every page also ships as Markdown, generated by the llmstxt plugin. These + actions expose that file so a page can be handed to an LLM without going + through rendered HTML. The target URL is derived in page-actions.js rather + than here, since it is just the current path with a different extension. + -#} + + {{ super() }} +{% endblock %} diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index b108476..b75ee42 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -159,4 +159,52 @@ Badge Styles .text-bg-danger { color: #fff; background-color: #dc3545; -} \ No newline at end of file +} +/* +=============================== +Page Actions (Copy as Markdown) +=============================== +*/ +.ik-page-actions { + display: flex; + gap: 0.4rem; + justify-content: flex-end; + margin: 0 0 -1.2rem; +} + +.ik-page-actions[hidden] { + display: none; +} + +.ik-page-actions__button { + display: inline-flex; + align-items: center; + gap: 0.3rem; + padding: 0.25rem 0.5rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 0.2rem; + background: none; + color: var(--md-default-fg-color--light); + font-size: 0.65rem; + cursor: pointer; + transition: color 125ms, border-color 125ms; +} + +/* The theme underlines links inside content; these read as buttons. */ +.ik-page-actions__button, +.ik-page-actions__button:hover, +.ik-page-actions__button:focus { + text-decoration: none; +} + +.ik-page-actions__button:hover, +.ik-page-actions__button:focus { + color: var(--md-accent-fg-color); + border-color: var(--md-accent-fg-color); +} + +.ik-page-actions__button svg { + width: 0.9rem; + height: 0.9rem; + fill: currentcolor; +} diff --git a/mkdocs.yml b/mkdocs.yml index 04d223b..0355682 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,12 +1,19 @@ site_name: IKPyKit Docs extra_css: - stylesheets/extra.css +extra_javascript: + - javascripts/page-actions.js repo_url: https://github.com/IsolationKernel/ikpykit site_url: https://isolationkernel.github.io/ikpykit remote_branch: docs_dev site_description: Python library for Isolation Kernel Toolkit. site_author: Isolation Kernel Team use_directory_urls: false +# custom_dir below points inside docs/, so mkdocs would otherwise also copy +# the theme template into the site as a page, serving raw Jinja at +# /overrides/main.html. +exclude_docs: | + overrides/ copyright: Copyright © 2024 - 2026 Xin Han nav: