put the code copy button into the markdown processor

This commit is contained in:
Oleksandr 2025-11-22 09:00:00 +02:00
parent 7f75bba052
commit b9accda035
Signed by: Xanazf
GPG key ID: 821EEC32761AC17C
20 changed files with 176 additions and 211 deletions

2
.pnp.cjs generated
View file

@ -50,6 +50,7 @@ const RAW_RUNTIME_STATE =
["astro-breadcrumbs", "virtual:a9b1222052dffa20c83605ac26b64fd717aa2982dc89da74b78301a8333c50a120c12db3f68c240302341b52215e986347cefceb71633b5918936083bd9430ce#npm:3.3.1"],\ ["astro-breadcrumbs", "virtual:a9b1222052dffa20c83605ac26b64fd717aa2982dc89da74b78301a8333c50a120c12db3f68c240302341b52215e986347cefceb71633b5918936083bd9430ce#npm:3.3.1"],\
["astro-icon", "npm:1.1.5"],\ ["astro-icon", "npm:1.1.5"],\
["hast-util-from-html", "npm:2.0.3"],\ ["hast-util-from-html", "npm:2.0.3"],\
["hastscript", "npm:9.0.1"],\
["jsonc-parser", "npm:3.3.1"],\ ["jsonc-parser", "npm:3.3.1"],\
["pagefind", "npm:1.4.0"],\ ["pagefind", "npm:1.4.0"],\
["quickshell-docs", "workspace:."],\ ["quickshell-docs", "workspace:."],\
@ -6380,6 +6381,7 @@ const RAW_RUNTIME_STATE =
["astro-breadcrumbs", "virtual:a9b1222052dffa20c83605ac26b64fd717aa2982dc89da74b78301a8333c50a120c12db3f68c240302341b52215e986347cefceb71633b5918936083bd9430ce#npm:3.3.1"],\ ["astro-breadcrumbs", "virtual:a9b1222052dffa20c83605ac26b64fd717aa2982dc89da74b78301a8333c50a120c12db3f68c240302341b52215e986347cefceb71633b5918936083bd9430ce#npm:3.3.1"],\
["astro-icon", "npm:1.1.5"],\ ["astro-icon", "npm:1.1.5"],\
["hast-util-from-html", "npm:2.0.3"],\ ["hast-util-from-html", "npm:2.0.3"],\
["hastscript", "npm:9.0.1"],\
["jsonc-parser", "npm:3.3.1"],\ ["jsonc-parser", "npm:3.3.1"],\
["pagefind", "npm:1.4.0"],\ ["pagefind", "npm:1.4.0"],\
["quickshell-docs", "workspace:."],\ ["quickshell-docs", "workspace:."],\

View file

@ -23,6 +23,7 @@
"astro-breadcrumbs": "^3.3.1", "astro-breadcrumbs": "^3.3.1",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"hast-util-from-html": "^2.0.3", "hast-util-from-html": "^2.0.3",
"hastscript": "^9.0.1",
"rehype": "^13.0.2", "rehype": "^13.0.2",
"remark-github-blockquote-alert": "^2.0.0", "remark-github-blockquote-alert": "^2.0.0",
"solid-js": "^1.9.10", "solid-js": "^1.9.10",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -3,51 +3,34 @@
--- ---
<script> <script>
setTimeout(() => { // setTimeout(() => {
// code copy // // code copy
let blocks = document.querySelectorAll("pre"); // let blocks = document.querySelectorAll("pre");
if (blocks.length > 0) { // if (blocks.length > 0) {
blocks.forEach((block) => { // blocks.forEach((block) => {
let button = document.createElement("button"); // let button = document.createElement("button");
button.className = "copy-button"; // button.className = "copy-button";
button.innerHTML = `<svg // button.innerHTML = `<svg
xmlns="http://www.w3.org/2000/svg" // xmlns="http://www.w3.org/2000/svg"
width="1em" // width="1em"
height="1em" // height="1em"
viewBox="0 0 256 256" // viewBox="0 0 256 256"
> // >
<path // <path
fill="currentColor" // fill="currentColor"
d="M200 32h-36.26a47.92 47.92 0 0 0-71.48 0H56a16 16 0 0 0-16 16v168a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16m-72 0a32 32 0 0 1 32 32H96a32 32 0 0 1 32-32m72 184H56V48h26.75A47.9 47.9 0 0 0 80 64v8a8 8 0 0 0 8 8h80a8 8 0 0 0 8-8v-8a47.9 47.9 0 0 0-2.75-16H200Z" // d="M200 32h-36.26a47.92 47.92 0 0 0-71.48 0H56a16 16 0 0 0-16 16v168a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16m-72 0a32 32 0 0 1 32 32H96a32 32 0 0 1 32-32m72 184H56V48h26.75A47.9 47.9 0 0 0 80 64v8a8 8 0 0 0 8 8h80a8 8 0 0 0 8-8v-8a47.9 47.9 0 0 0-2.75-16H200Z"
/> // />
</svg> // </svg>
`; // `;
button.onclick = () => { // button.onclick = () => {
let snippet = block.innerText ?? ""; // let snippet = block.innerText ?? "";
navigator.clipboard.writeText(snippet); // navigator.clipboard.writeText(snippet);
button.classList.toggle("copied"); // button.classList.toggle("copied");
setTimeout(() => button.classList.remove("copied"), 1000); // setTimeout(() => button.classList.remove("copied"), 1000);
}; // };
block.appendChild(button); // block.appendChild(button);
}); // });
} // }
}, 3001) // }, 3001)
// heading copy
let headings = document.getElementsByClassName("heading")
if (headings.length > 0) {
for (const heading of headings) {
let button = heading.querySelector("span")
if (button) {
button.onclick = () => {
let link = window.location.href.split("#")[0];
link += `#-${heading.textContent?.slice(10).trimEnd().replaceAll(" ", "-").toLowerCase()}`;
window.location.href = link
navigator.clipboard.writeText(link);
heading.classList.toggle("copied")
setTimeout(() => heading.classList.remove("copied"), 1000);
}
}
}
}
</script> </script>

View file

@ -0,0 +1,9 @@
interface CopyButtonOptions {
duration?: number;
copyIcon?: string;
successIcon?: string;
display?: "hover" | "ready";
cssVariables?: string;
}
export type { CopyButtonOptions };

View file

@ -13,6 +13,7 @@ import type {
VersionsData, VersionsData,
} from "./module"; } from "./module";
import type { SearchLists } from "./search"; import type { SearchLists } from "./search";
import type { CopyButtonOptions } from "./codeblock";
export type { export type {
QMLTypeLinkObject, QMLTypeLinkObject,
@ -28,4 +29,5 @@ export type {
VersionData, VersionData,
VersionsData, VersionsData,
SearchLists, SearchLists,
CopyButtonOptions,
}; };

View file

@ -14,12 +14,14 @@ import { remarkAlert } from "remark-github-blockquote-alert";
import rehypeShiki from "@shikijs/rehype"; import rehypeShiki from "@shikijs/rehype";
import sectionize from "@hbsnow/rehype-sectionize"; import sectionize from "@hbsnow/rehype-sectionize";
import type { ShikiTransformer } from "shiki"; import type { ShikiTransformer } from "shiki";
import { h } from "hastscript";
import { import {
getQMLTypeLinkObject, getQMLTypeLinkObject,
getQMLTypeLink, getQMLTypeLink,
getIconForLink, getIconForLink,
} from "./helpers.ts"; } from "./helpers.ts";
import type { CopyButtonOptions } from "@_types";
let currentVersion = "NOVERSION"; let currentVersion = "NOVERSION";
@ -155,7 +157,54 @@ const shikiRewriteTypelinks: ShikiTransformer = {
}, },
}; };
export const markdownConfig: AstroMarkdownOptions = { const shikiCopyButton: ShikiTransformer = {
name: "copy-button",
pre(node) {
const options: CopyButtonOptions = {
duration: 3000,
};
const button = h(
"button",
{
class: "copy-button",
role: "button",
"aria-label": "Copy to clipboard",
"alia-live": "polite",
// "data-code": removeCodeAnnotations(this.source),
onclick: `
navigator.clipboard.writeText(this.dataset.code);
this.classList.add('copied');
this.setAttribute('aria-pressed', 'true');
setTimeout(() => { this.classList.remove('copied'); this.setAttribute('aria-pressed', 'false');}, ${options.duration})
`,
},
[
h("span", { class: "ready" }),
h("span", { class: "success" }),
h(
"svg",
{
class: "copy-icon",
role: "icon",
xmlns: "http://www.w3.org/2000/svg",
width: "1em",
height: "1em",
viewBox: "0 0 256 256",
},
[
h("path", {
fill: "currentColor",
d: "M200 32h-36.26a47.92 47.92 0 0 0-71.48 0H56a16 16 0 0 0-16 16v168a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16m-72 0a32 32 0 0 1 32 32H96a32 32 0 0 1 32-32m72 184H56V48h26.75A47.9 47.9 0 0 0 80 64v8a8 8 0 0 0 8 8h80a8 8 0 0 0 8-8v-8a47.9 47.9 0 0 0-2.75-16H200Z",
}),
]
),
]
);
node.children.splice(0, 0, button);
},
};
const markdownConfig: AstroMarkdownOptions = {
syntaxHighlight: false, syntaxHighlight: false,
remarkPlugins: [ remarkPlugins: [
remarkParseAtTypes, remarkParseAtTypes,
@ -182,7 +231,7 @@ export const markdownConfig: AstroMarkdownOptions = {
}, },
defaultColor: false, defaultColor: false,
wrap: true, wrap: true,
transformers: [shikiRewriteTypelinks], transformers: [shikiRewriteTypelinks, shikiCopyButton],
}, },
], ],
// FIXME: incompatible types between unified/Plugin and Astro/RehypePlugin // FIXME: incompatible types between unified/Plugin and Astro/RehypePlugin
@ -206,7 +255,7 @@ async function getMarkdownProcessor(): Promise<MarkdownProcessor> {
return globalMarkdownProcessor; return globalMarkdownProcessor;
} }
export async function processMarkdown( async function processMarkdown(
version: string, version: string,
markdown: string markdown: string
): Promise<string> { ): Promise<string> {
@ -217,3 +266,5 @@ export async function processMarkdown(
currentVersion = "NOVERSION"; currentVersion = "NOVERSION";
return r; return r;
} }
export { markdownConfig, processMarkdown };

View file

@ -1,120 +0,0 @@
//# FIXME: fuseConfig.ts
// --
// generateSearchLists.ts
interface SearchLists {
slug: string;
link: string;
summary: string;
}
// --
// generateTypeData.ts
interface QuickshellBase {
type: string;
module: string;
name: string;
}
interface QuickshellInstance {
name?: string;
type: {
gadget?: QuickshellGadget;
type: string;
module: string;
name: string;
of?: QuickshellBase;
};
details?: string;
flags?: string[];
}
interface QuickshellGadget {
[key: string]: QuickshellInstance;
}
interface QuickshellProps {
[key: string]: QuickshellInstance;
}
interface QuickshellFunction {
ret: QuickshellInstance;
name: string;
id: string;
details: string;
params: QuickshellInstance[];
}
interface QuickshellSignal {
[key: string]: {
name: string;
details: string;
params: QuickshellInstance[];
};
}
interface QuickshellVariant {
[key: string]: {
name?: string;
details: string;
params?: QuickshellInstance[];
};
}
interface TypeData {
name: string;
description: string;
details: string;
flags?: string[];
contains?: string[];
super?: QuickshellBase;
properties?: QuickshellProps;
functions?: QuickshellFunction[];
signals?: QuickshellSignal;
variants?: QuickshellVariant;
subtypes?: QuickshellData[];
}
interface ModuleData {
name: string;
description: string;
details: string;
types: TypeData[];
}
interface VersionData {
name: string;
changelog?: string;
modules: ModuleData[];
}
interface VersionsData {
default: string;
versions: VersionData[];
}
// --
// helpers.ts
interface QMLTypeLinkObject {
type: string;
module?: string;
name?: string;
mtype?: string;
mname?: string;
}
// --
export type {
QuickshellBase,
QuickshellInstance,
QuickshellGadget,
QuickshellProps,
QuickshellFunction,
QuickshellSignal,
QuickshellVariant,
TypeData,
ModuleData,
VersionData,
VersionsData,
QMLTypeLinkObject,
};

View file

@ -21,3 +21,4 @@ const { title, description } = Astro.props;
<slot /> <slot />
</body> </body>
</html> </html>

View file

@ -15,16 +15,18 @@ interface Props {
title: string; title: string;
description: string; description: string;
headings?: ConfigHeading[]; headings?: ConfigHeading[];
type?: TypeData type?: TypeData;
} }
const { title, description, headings, type } = Astro.props; const { title, description, headings, type } = Astro.props;
let url = Astro.url.pathname.split("/").filter(s => s !== ""); let url = Astro.url.pathname.split("/").filter(s => s !== "");
const breadcrumbs = [{ const breadcrumbs = [
{
text: "custom", text: "custom",
href: "/", href: "/",
}]; },
];
let linkPath = ""; let linkPath = "";
if (url[0] === "docs") { if (url[0] === "docs") {
@ -90,4 +92,33 @@ for (const segment of url) {
<Footer/> <Footer/>
</body> </body>
</html> </html>
<script>
// FIXME: need to make this work properly, or fold into the markdown processor
let headings = document.getElementsByClassName("heading")
if (headings.length > 0) {
for (const heading of headings) {
let button = heading.querySelector("h2")
if (button) {
button.onclick = () => {
let link = window.location.href.split("#")[0];
link += `#${button.textContent?.trimEnd().replaceAll(" ", "-").toLowerCase()}`;
window.location.href = link
navigator.clipboard.writeText(link);
heading.classList.toggle("copied")
setTimeout(() => heading.classList.remove("copied"), 1000);
}
}
let spanButton = heading.querySelector("span")
if (spanButton) {
spanButton.onclick = () => {
let link = window.location.href.split("#")[0];
link += `#${spanButton.textContent?.trim().replaceAll(" ", "-").toLowerCase()}`;
window.location.href = link
navigator.clipboard.writeText(link);
spanButton.classList.toggle("copied")
setTimeout(() => heading.classList.remove("copied"), 1000);
}
}
}
}
</script>

View file

@ -7,10 +7,13 @@ export interface Props {
frontmatter: { frontmatter: {
title: string; title: string;
description?: string; description?: string;
} };
} }
const { headings, frontmatter: { title, description } } = Astro.props; const {
headings,
frontmatter: { title, description },
} = Astro.props;
--- ---
<GuideLayout title={title} description={description ?? ""} headings={headings}> <GuideLayout title={title} description={description ?? ""} headings={headings}>
<slot/> <slot/>

View file

@ -27,7 +27,7 @@ pre {
overflow: hidden; overflow: hidden;
text-wrap: wrap; text-wrap: wrap;
& > button { & .copy-button {
all: unset; all: unset;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
@ -45,6 +45,7 @@ pre {
background-color: hsl(var(--blue) 85% 35% / 0.1); background-color: hsl(var(--blue) 85% 35% / 0.1);
cursor: pointer; cursor: pointer;
transition: color 0.25s; transition: color 0.25s;
z-index: 10;
&:hover { &:hover {
color: hsl(var(--blue) 100% 75%); color: hsl(var(--blue) 100% 75%);

View file

@ -3736,7 +3736,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"hastscript@npm:^9.0.0": "hastscript@npm:^9.0.0, hastscript@npm:^9.0.1":
version: 9.0.1 version: 9.0.1
resolution: "hastscript@npm:9.0.1" resolution: "hastscript@npm:9.0.1"
dependencies: dependencies:
@ -5557,6 +5557,7 @@ __metadata:
astro-breadcrumbs: "npm:^3.3.1" astro-breadcrumbs: "npm:^3.3.1"
astro-icon: "npm:^1.1.5" astro-icon: "npm:^1.1.5"
hast-util-from-html: "npm:^2.0.3" hast-util-from-html: "npm:^2.0.3"
hastscript: "npm:^9.0.1"
jsonc-parser: "npm:^3.3.1" jsonc-parser: "npm:^3.3.1"
pagefind: "npm:^1.4.0" pagefind: "npm:^1.4.0"
rehype: "npm:^13.0.2" rehype: "npm:^13.0.2"