Add dark website and documentation with GitHub Pages deployment
Include the updated desktop screenshot, active-monitor screenshot capture and editor locking, and Settings window drag areas.
This commit is contained in:
1 parent
e5e2b74a25
commit
0cd48bcc81
15 files changed
+1794
-12
No files matched your search
+130
@@ -0,0 +1,130 @@
|
||||
// Progressive enhancement for the handbook. Navigation and content work without JS.
|
||||
const search = document.querySelector("#docs-search");
|
||||
const results = document.querySelector("#search-results");
|
||||
const status = document.querySelector("#search-status");
|
||||
let indexPromise;
|
||||
if (search) {
|
||||
search.closest(".docs-search").hidden = false;
|
||||
const loadIndex = () =>
|
||||
(indexPromise ||= fetch("search-index.json")
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error("Search index unavailable");
|
||||
return response.json();
|
||||
})
|
||||
.catch((error) => {
|
||||
indexPromise = null;
|
||||
throw error;
|
||||
}));
|
||||
search.addEventListener("focus", () => {
|
||||
loadIndex().catch(() => {});
|
||||
});
|
||||
search.addEventListener("input", async () => {
|
||||
const query = search.value.trim().toLocaleLowerCase();
|
||||
if (!query) {
|
||||
results.hidden = true;
|
||||
results.replaceChildren();
|
||||
status.textContent = "";
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const index = await loadIndex();
|
||||
if (search.value.trim().toLocaleLowerCase() !== query) return;
|
||||
const words = query.split(/\s+/);
|
||||
const matches = index
|
||||
.filter((item) =>
|
||||
words.every((word) =>
|
||||
`${item.page} ${item.heading} ${item.text}`
|
||||
.toLocaleLowerCase()
|
||||
.includes(word),
|
||||
),
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Number(b.heading.toLocaleLowerCase().includes(query)) -
|
||||
Number(a.heading.toLocaleLowerCase().includes(query)),
|
||||
)
|
||||
.slice(0, 10);
|
||||
results.replaceChildren();
|
||||
for (const item of matches) {
|
||||
const link = document.createElement("a");
|
||||
link.href = item.url;
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = `${item.page} / ${item.heading}`;
|
||||
const snippet = document.createElement("span");
|
||||
const position = Math.max(
|
||||
0,
|
||||
item.text.toLocaleLowerCase().indexOf(words[0]) - 45,
|
||||
);
|
||||
snippet.textContent = `${position ? "…" : ""}${item.text.slice(position, position + 165)}${item.text.length > position + 165 ? "…" : ""}`;
|
||||
link.append(title, snippet);
|
||||
results.append(link);
|
||||
}
|
||||
if (!matches.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.textContent =
|
||||
"No matching sections. Try “wallpaper”, “shortcuts”, or “notifications”.";
|
||||
results.append(empty);
|
||||
}
|
||||
results.hidden = false;
|
||||
status.textContent = `${matches.length} matching sections.`;
|
||||
} catch {
|
||||
if (search.value.trim().toLocaleLowerCase() !== query) return;
|
||||
results.hidden = true;
|
||||
status.textContent = "Search is unavailable. Browse the guides below.";
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
results.hidden = true;
|
||||
search.focus();
|
||||
}
|
||||
if (
|
||||
event.key === "/" &&
|
||||
!["INPUT", "TEXTAREA"].includes(document.activeElement.tagName) &&
|
||||
!event.ctrlKey &&
|
||||
!event.metaKey &&
|
||||
!event.altKey
|
||||
) {
|
||||
event.preventDefault();
|
||||
search.focus();
|
||||
}
|
||||
if (
|
||||
event.key === "ArrowDown" &&
|
||||
document.activeElement === search &&
|
||||
!results.hidden
|
||||
) {
|
||||
const first = results.querySelector("a");
|
||||
if (first) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!event.target.closest(".docs-search")) results.hidden = true;
|
||||
});
|
||||
}
|
||||
if (navigator.clipboard?.writeText) {
|
||||
for (const block of document.querySelectorAll(".prose pre")) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "code-copy";
|
||||
button.textContent = "Copy";
|
||||
button.setAttribute("aria-label", "Copy code example");
|
||||
button.addEventListener("click", async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(
|
||||
block.querySelector("code").textContent,
|
||||
);
|
||||
button.textContent = "Copied";
|
||||
if (status) status.textContent = "Code example copied.";
|
||||
} catch {
|
||||
button.textContent = "Select to copy";
|
||||
if (status)
|
||||
status.textContent =
|
||||
"Copy failed. Select the code to copy it manually.";
|
||||
}
|
||||
});
|
||||
block.append(button);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user