Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/javascripts/page-actions.js
Original file line number Diff line number Diff line change
@@ -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);
}
22 changes: 21 additions & 1 deletion docs/overrides/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,24 @@
<a href="{{ '../' ~ base_url }}">
<strong>Switch to latest stable version.</strong>
</a>
{% endblock %}
{% 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.
-#}
<div class="ik-page-actions" data-ik-page-actions hidden>
<button class="ik-page-actions__button" type="button" data-ik-copy>
{% include ".icons/material/content-copy.svg" %}
<span data-ik-copy-label>Copy as Markdown</span>
</button>
<a class="ik-page-actions__button" data-ik-view target="_blank" rel="noopener">
{% include ".icons/material/language-markdown-outline.svg" %}
<span>View Markdown</span>
</a>
</div>
{{ super() }}
{% endblock %}
50 changes: 49 additions & 1 deletion docs/stylesheets/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,52 @@ Badge Styles
.text-bg-danger {
color: #fff;
background-color: #dc3545;
}
}
/*
===============================
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;
}
7 changes: 7 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -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 &copy; 2024 - 2026 Xin Han

nav:
Expand Down