diff --git a/docusaurus.config.ts b/docusaurus.config.ts index acb286ed..0090daa3 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -32,13 +32,13 @@ import { extendedPostcssConfigPlugin } from "./server/postcss"; import { rehypeHLJS } from "./server/rehype-hljs"; import { definer as hcl } from "highlightjs-terraform"; import path from "path"; -import fs from "fs"; +import { llmsTxtPluginOptions } from "./server/llms"; const latestVersion = getLatestVersion(); function clayTrackingPlugin() { return { - name: 'clay-tracking', + name: "clay-tracking", injectHtmlTags() { return { postBodyTags: [ @@ -337,6 +337,7 @@ const config: Config = { extendedPostcssConfigPlugin, clayTrackingPlugin, process.env.NODE_ENV !== "production" && "@docusaurus/plugin-debug", + ["@signalwire/docusaurus-plugin-llms-txt", llmsTxtPluginOptions], ].filter(Boolean), }; diff --git a/package.json b/package.json index 93a27349..e4aaa28c 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@iconify-json/logos": "^1.2.10", "@iconify/tools": "^5.0.4", "@mdx-js/react": "^3.1.1", + "@signalwire/docusaurus-plugin-llms-txt": "2.0.0-alpha.7", "classnames": "^2.3.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -131,6 +132,10 @@ "wait-on": "^9.0.4", "yaml": "^2.8.2" }, + "resolutions": { + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, "browserslist": { "production": [ ">0.5%", diff --git a/server/fixtures/llms/admonition.html b/server/fixtures/llms/admonition.html new file mode 100644 index 00000000..914d118a --- /dev/null +++ b/server/fixtures/llms/admonition.html @@ -0,0 +1 @@ +
This is a note.

Feature description
Feature description
diff --git a/server/fixtures/llms/code-block-commands.html b/server/fixtures/llms/code-block-commands.html new file mode 100644 index 00000000..9c7ce915 --- /dev/null +++ b/server/fixtures/llms/code-block-commands.html @@ -0,0 +1 @@ +tsh login --proxy=example.teleport.sh
const x = 1;const y = 2;
Before comment.
After comment.
diff --git a/server/fixtures/llms/heading-anchor-links.html b/server/fixtures/llms/heading-anchor-links.html new file mode 100644 index 00000000..280e6820 --- /dev/null +++ b/server/fixtures/llms/heading-anchor-links.html @@ -0,0 +1 @@ +Some content
\ No newline at end of file diff --git a/server/fixtures/llms/heading-with-regular-link.html b/server/fixtures/llms/heading-with-regular-link.html new file mode 100644 index 00000000..929f8097 --- /dev/null +++ b/server/fixtures/llms/heading-with-regular-link.html @@ -0,0 +1 @@ +Normal paragraph.
Another paragraph.
diff --git a/server/fixtures/llms/reposition-h1-with-duplicate.html b/server/fixtures/llms/reposition-h1-with-duplicate.html new file mode 100644 index 00000000..e7116dee --- /dev/null +++ b/server/fixtures/llms/reposition-h1-with-duplicate.html @@ -0,0 +1 @@ +Some content
Some content
More content
\ No newline at end of file diff --git a/server/fixtures/llms/result/admonition.html b/server/fixtures/llms/result/admonition.html new file mode 100644 index 00000000..04a4d13f --- /dev/null +++ b/server/fixtures/llms/result/admonition.html @@ -0,0 +1 @@ +This is a note.
Feature description
diff --git a/server/fixtures/llms/result/anchor-mixed-content.html b/server/fixtures/llms/result/anchor-mixed-content.html new file mode 100644 index 00000000..7127b8b1 --- /dev/null +++ b/server/fixtures/llms/result/anchor-mixed-content.html @@ -0,0 +1 @@ +Feature description
diff --git a/server/fixtures/llms/result/code-block-commands.html b/server/fixtures/llms/result/code-block-commands.html new file mode 100644 index 00000000..259138bc --- /dev/null +++ b/server/fixtures/llms/result/code-block-commands.html @@ -0,0 +1 @@ +$ tsh login --proxy=example.teleport.sh
diff --git a/server/fixtures/llms/result/code-block.html b/server/fixtures/llms/result/code-block.html
new file mode 100644
index 00000000..ce1a99a6
--- /dev/null
+++ b/server/fixtures/llms/result/code-block.html
@@ -0,0 +1,2 @@
+const x = 1;
+const y = 2;
diff --git a/server/fixtures/llms/result/empty-comments.html b/server/fixtures/llms/result/empty-comments.html
new file mode 100644
index 00000000..af054a02
--- /dev/null
+++ b/server/fixtures/llms/result/empty-comments.html
@@ -0,0 +1 @@
+Before comment.
After comment.
diff --git a/server/fixtures/llms/result/heading-anchor-links.html b/server/fixtures/llms/result/heading-anchor-links.html new file mode 100644 index 00000000..8b87eebb --- /dev/null +++ b/server/fixtures/llms/result/heading-anchor-links.html @@ -0,0 +1 @@ +Some content
\ No newline at end of file diff --git a/server/fixtures/llms/result/heading-with-regular-link.html b/server/fixtures/llms/result/heading-with-regular-link.html new file mode 100644 index 00000000..929f8097 --- /dev/null +++ b/server/fixtures/llms/result/heading-with-regular-link.html @@ -0,0 +1 @@ +Normal paragraph.
Another paragraph.
diff --git a/server/fixtures/llms/result/reposition-h1-with-duplicate.html b/server/fixtures/llms/result/reposition-h1-with-duplicate.html new file mode 100644 index 00000000..2d0a4c59 --- /dev/null +++ b/server/fixtures/llms/result/reposition-h1-with-duplicate.html @@ -0,0 +1 @@ +Some content
diff --git a/server/fixtures/llms/result/reposition-h1.html b/server/fixtures/llms/result/reposition-h1.html new file mode 100644 index 00000000..dfa83bba --- /dev/null +++ b/server/fixtures/llms/result/reposition-h1.html @@ -0,0 +1 @@ +Some content
More content
diff --git a/server/fixtures/llms/result/tabs.html b/server/fixtures/llms/result/tabs.html new file mode 100644 index 00000000..2ed7a5c1 --- /dev/null +++ b/server/fixtures/llms/result/tabs.html @@ -0,0 +1 @@ +Tab 1 content
Tab 2 content
Connect to example.teleport.sh
diff --git a/server/fixtures/llms/tabs.html b/server/fixtures/llms/tabs.html new file mode 100644 index 00000000..b2a5ea3a --- /dev/null +++ b/server/fixtures/llms/tabs.html @@ -0,0 +1 @@ +Tab 1 content
Tab 2 content
Connect to example.teleport.sh
\ No newline at end of file diff --git a/server/llms/index.ts b/server/llms/index.ts new file mode 100644 index 00000000..77d3b7e1 --- /dev/null +++ b/server/llms/index.ts @@ -0,0 +1,126 @@ +import type { Options as PluginOptions } from "@signalwire/docusaurus-plugin-llms-txt"; +import rehypePrepareHTML from "./rehype-prepare-html"; + +// Define the sections for the llms.txt index file, including their routes and descriptions +const sections = [ + { + id: "get-started", + name: "Get Started", + description: + "Learn how to deploy a cluster, connect infrastructure, set up access controls, and review audit logs.", + routes: [{ route: "/docs/get-started/**" }], + position: 0, + }, + { + id: "core-concepts", + name: "Core Concepts", + description: "Learn the key components that make up Teleport.", + routes: [{ route: "/docs/core-concepts/**" }], + position: 1, + }, + { + id: "agentic-identity-framework", + name: "Agentic Identity Framework", + description: + "Design and reference implementation for the secure deployment of agents on infrastructure.", + routes: [{ route: "/docs/agentic-identity-framework/**" }], + position: 2, + }, + { + id: "installation", + name: "Installation", + description: + "How to install Teleport and Teleport's client tools on your platform, including binaries and instructions for Docker and Helm.", + routes: [{ route: "/docs/installation/**" }], + position: 3, + }, + { + id: "connect-your-client", + name: "Teleport User Guides", + description: + "Provides instructions to help users connect to infrastructure resources with Teleport.", + routes: [{ route: "/docs/connect-your-client/**" }], + position: 4, + }, + { + id: "zero-trust-access", + name: "Teleport Zero Trust Access", + description: + "Easy access to all your infrastructure, on a foundation of cryptographic identity and zero trust.", + routes: [{ route: "/docs/zero-trust-access/**" }], + position: 5, + }, + { + id: "machine-workload-identity", + name: "Machine & Workload Identity", + description: + "Use Teleport to replace long-lived secrets with identity-based authentication for your machines and workloads.", + routes: [{ route: "/docs/machine-workload-identity/**" }], + position: 6, + }, + { + id: "identity-governance", + name: "Identity Governance", + description: + "Manage on-demand access, privileges, and compliance for all your infrastructure.", + routes: [{ route: "/docs/identity-governance/**" }], + position: 7, + }, + { + id: "identity-security", + name: "Teleport Identity Security", + description: + "Teleport Identity Security centralizes access policy across your infrastructure, consolidates disparate identity audit logs, discovers shadow access, and alerts on access anomalies.", + routes: [{ route: "/docs/identity-security/**" }], + position: 8, + }, + { + id: "enroll-resources", + name: "Enroll Resources", + description: + "Teleport protects infrastructure resources such as servers, databases, and Kubernetes clusters by enforcing strong access controls and auditability.", + routes: [{ route: "/docs/enroll-resources/**" }], + position: 9, + }, + { + id: "reference-guides", + name: "Teleport Reference Guides", + description: + "Provides comprehensive information on configuration fields, Teleport commands, and other ways of interacting with Teleport.", + routes: [{ route: "/docs/reference/**" }], + position: 10, + }, +]; + +export const llmsTxtPluginOptions: PluginOptions = { + // Top-level runtime options + logLevel: 1, + onRouteError: "throw", + onSectionError: "throw", + runOnPostBuild: true, + + // Markdown file generation options + markdown: { + relativePaths: false, // Whether to use relative paths or absolute path URLs in the generated markdown files + beforeDefaultRehypePlugins: [rehypePrepareHTML], // Custom rehype plugins to clean up and reposition content for cleaner markdown output + remarkStringify: { + bullet: "-", + rule: "-", + ruleRepetition: 3, + }, + }, + + // llms.txt index file options + llmsTxt: { + sections, + autoSectionPosition: 11, + includeDocs: true, + includeBlog: false, + includePages: false, + excludeRoutes: ["/docs/tags/**"], + enableDescriptions: true, + siteTitle: "Teleport documentation", + siteDescription: + "Teleport is an identity-based access platform that secures servers, Kubernetes clusters, databases, internal applications, and desktops using short-lived certificates, detailed audit logging, and fine-grained role-based access controls tied to your SSO provider (e.g., Okta, GitHub, Google Workspace).", + }, +}; diff --git a/server/llms/rehype-prepare-html.test.ts b/server/llms/rehype-prepare-html.test.ts new file mode 100644 index 00000000..2ce7472e --- /dev/null +++ b/server/llms/rehype-prepare-html.test.ts @@ -0,0 +1,211 @@ +import { describe, expect, test } from "@jest/globals"; +import { readFileSync } from "fs"; +import { resolve } from "path"; +import { unified } from "unified"; +import rehypeParse from "rehype-parse"; +import rehypeStringify from "rehype-stringify"; +import rehypeProcessCustomComponents from "./rehype-prepare-html"; + +describe("server/llms/rehype-prepare-html", () => { + const transformer = (html: string) => + unified() + .use(rehypeParse, { fragment: true }) + .use(rehypeProcessCustomComponents) + .use(rehypeStringify) + .processSync(html); + + test("Var: replaces wrapper-input span with the input placeholder text", () => { + const result = transformer( + readFileSync(resolve("server/fixtures/llms/var-component.html"), "utf-8"), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/var-component.html"), + "utf-8", + ).trim(), + ); + }); + + test("Admonition: wraps block in hr separators and uppercases the type title", () => { + const result = transformer( + readFileSync(resolve("server/fixtures/llms/admonition.html"), "utf-8"), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/admonition.html"), + "utf-8", + ).trim(), + ); + }); + + test("Tabs: restructures so each tab label is directly followed by its content", () => { + const result = transformer( + readFileSync(resolve("server/fixtures/llms/tabs.html"), "utf-8"), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/tabs.html"), + "utf-8", + ).trim(), + ); + }); + + test("Code block: extracts line text into a clean pre/code structure", () => { + const result = transformer( + readFileSync(resolve("server/fixtures/llms/code-block.html"), "utf-8"), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/code-block.html"), + "utf-8", + ).trim(), + ); + }); + + test("Code block: prepends data-content prefix to command lines", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/code-block-commands.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/code-block-commands.html"), + "utf-8", + ).trim(), + ); + }); + + test("Link with inner content: only headings keep the link, other elements are unwrapped", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/anchor-mixed-content.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/anchor-mixed-content.html"), + "utf-8", + ).trim(), + ); + }); + + test("Link with deeply nested content: traverses into wrapper divs, headings keep the link and other elements are unwrapped", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/anchor-mixed-content-nested.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/anchor-mixed-content-nested.html"), + "utf-8", + ).trim(), + ); + }); + + test("Heading anchor links: removes hash-link anchors from all heading levels", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/heading-anchor-links.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/heading-anchor-links.html"), + "utf-8", + ).trim(), + ); + }); + + test("Heading anchor links: does not remove non-hash-link anchors from headings", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/heading-with-regular-link.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/heading-with-regular-link.html"), + "utf-8", + ).trim(), + ); + }); + + test("Irrelevant components: removes thumbsFeedback and checkpoint components, preserving other content", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/irrelevant-components.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/irrelevant-components.html"), + "utf-8", + ).trim(), + ); + }); + + test("Irrelevant components: removes empty HTML comments", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/empty-comments.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/empty-comments.html"), + "utf-8", + ).trim(), + ); + }); + + test("H1 repositioning: moves h1 out of a header element to the top of the tree", () => { + const result = transformer( + readFileSync(resolve("server/fixtures/llms/reposition-h1.html"), "utf-8"), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve("server/fixtures/llms/result/reposition-h1.html"), + "utf-8", + ).trim(), + ); + }); + + test("H1 repositioning: removes duplicate h1 elements after repositioning", () => { + const result = transformer( + readFileSync( + resolve("server/fixtures/llms/reposition-h1-with-duplicate.html"), + "utf-8", + ), + ); + + expect((result.value as string).trim()).toBe( + readFileSync( + resolve( + "server/fixtures/llms/result/reposition-h1-with-duplicate.html", + ), + "utf-8", + ).trim(), + ); + }); +}); diff --git a/server/llms/rehype-prepare-html.ts b/server/llms/rehype-prepare-html.ts new file mode 100644 index 00000000..f1c08825 --- /dev/null +++ b/server/llms/rehype-prepare-html.ts @@ -0,0 +1,427 @@ +import { Root, Element, Text, Comment } from "hast"; +import { Plugin } from "unified"; +import { visitParents, SKIP } from "unist-util-visit-parents"; + +/* + * Process html components for cleaner markdown ouput. + * + * Admonition: wrap the block in horizontal rule separators and turn the title into uppercase text + * Tabs: place tab content directly after the label + * Var: prevent duplicate text by keeping only the placeholder text + * Code blocks: extract text content and replace with clean structure
+ * Card links: prevent duplicate links in the markdown output
+ * Heading anchor links: remove hash-link anchors from headings
+ * Irrelevant components: remove interactive UI components (thumbsFeedback, checkpoint, docsHeader)
+ * H1 repositioning: move the h1 heading to the top of the document
+ */
+
+type CodeBlockEntry = {
+ outerWrapper: Element;
+ outerParent: Element | Root;
+ pre: Element;
+};
+
+type AnchorEntry = {
+ node: Element;
+ parent: Element | Root;
+};
+
+const rehypePrepareHTML: Plugin<[], Root, Root> = function () {
+ return (tree: Root) => {
+ const irrelevantClassPrefixes = [
+ "thumbsFeedback",
+ "checkpoint",
+ "docsHeader",
+ ]; // Add any other class prefixes in order to remove irrelevant components, if needed
+
+ // --- Track nodes for post-processing ---
+ const toRemove: Array<{
+ node: Element | Comment;
+ parent: Element | Root;
+ }> = [];
+
+ let h1: Element | null = null;
+ let headerElement: Element | null = null;
+ let headerParent: Element | Root | null = null;
+
+ const varNodes: Array<{ node: Element; parent: Element | Root }> = [];
+ const admonitionNodes: Array<{
+ node: Element;
+ parent: Element | Root;
+ }> = [];
+ const tabNodes: Element[] = [];
+
+ const codeBlocks: CodeBlockEntry[] = [];
+
+ const anchorEntries = new Map(); // track tags that wrap around content (card links)
+
+ // ----------- Collect nodes to modify or remove -----------
+
+ visitParents(tree, (node, ancestors) => {
+ // *** Empty comments ***
+ if (node.type === "comment") {
+ if ((node as Comment).value.trim() === "") {
+ const parent = ancestors[ancestors.length - 1] as Element | Root;
+ toRemove.push({ node: node as Comment, parent });
+ }
+ return;
+ }
+
+ if (node.type !== "element") return;
+
+ const element = node as Element;
+ const parent = ancestors[ancestors.length - 1] as Element | Root;
+ const classes = element.properties?.className;
+ const classArray = Array.isArray(classes) ? classes : [];
+
+ // *** Irrelevant components ***
+ if (
+ classArray.some((c) =>
+ irrelevantClassPrefixes.some((prefix) =>
+ String(c).startsWith(prefix),
+ ),
+ )
+ ) {
+ toRemove.push({ node: element, parent });
+ return SKIP; // don't traverse children (e.g. avoids capturing an h1 inside a removed node)
+ }
+
+ // *** Headings ***
+ if (/^h[1-6]$/.test(element.tagName)) {
+ // Remove hash-link anchor children in-place
+ element.children = element.children.filter((child) => {
+ if (child.type !== "element") return true;
+ const childEl = child as Element;
+ if (childEl.tagName !== "a") return true;
+ const childClasses = childEl.properties?.className;
+ const childClassArray = Array.isArray(childClasses)
+ ? childClasses
+ : [];
+ return !childClassArray.includes("hash-link");
+ });
+
+ // H1 tracking / duplicate removal
+ if (element.tagName === "h1") {
+ if (!h1) {
+ h1 = element;
+ // Find the nearest ancestor
+ for (let i = ancestors.length - 1; i >= 0; i--) {
+ const ancestor = ancestors[i];
+ if (
+ ancestor.type === "element" &&
+ (ancestor as Element).tagName === "header"
+ ) {
+ headerElement = ancestor as Element;
+ headerParent = ancestors[i - 1] as Element | Root;
+ break;
+ }
+ }
+ if (!headerElement) {
+ headerParent = parent;
+ }
+ } else {
+ toRemove.push({ node: element, parent });
+ }
+ }
+
+ // Card link detection: find nearest ancestor
+ for (let i = ancestors.length - 1; i >= 0; i--) {
+ const ancestor = ancestors[i];
+ if (ancestor.type !== "element") continue;
+ const ancestorEl = ancestor as Element;
+ if (ancestorEl.tagName !== "a") continue;
+ if (!anchorEntries.has(ancestorEl)) {
+ const anchorParent = ancestors[i - 1] as Element | Root;
+ anchorEntries.set(ancestorEl, {
+ node: ancestorEl,
+ parent: anchorParent,
+ });
+ }
+ break;
+ }
+
+ return;
+ }
+
+ // *** Var component ***
+ if (classArray.some((c) => String(c) === "wrapper-input")) {
+ varNodes.push({ node: element, parent });
+ return;
+ }
+
+ // *** Admonition ***
+ if (classArray.some((c) => String(c) === "theme-admonition")) {
+ admonitionNodes.push({ node: element, parent });
+ return;
+ }
+
+ // *** Tabs ***
+ if (classArray.some((c) => String(c) === "theme-tabs-container")) {
+ tabNodes.push(element);
+ return;
+ }
+
+ // *** Code blocks ***
+ // Identify the code blocks by looking for the code_* css module class
+ // outerParent > outerWrapper > innerDiv > pre
+ if (
+ element.tagName === "pre" &&
+ classArray.some((c) => String(c).startsWith("code_")) &&
+ ancestors.length >= 3
+ ) {
+ const outerWrapper = ancestors[ancestors.length - 2] as Element;
+ const outerParent = ancestors[ancestors.length - 3] as Element | Root;
+ codeBlocks.push({ outerWrapper, outerParent, pre: element });
+ return;
+ }
+ });
+
+ // ----------- Post-processing: removals & modifications -----------
+
+ // Apply removals: irrelevant components, empty comments, duplicate h1s
+ for (const { node, parent } of toRemove) {
+ parent.children = parent.children.filter((child) => child !== node);
+ }
+
+ // *** Var component: replace with just the placeholder text ***
+ for (const { node, parent } of varNodes) {
+ let placeholder = "";
+ for (const child of node.children) {
+ if (child.type === "element") {
+ const childEl = child as Element;
+ if (childEl.tagName === "input" && childEl.properties?.placeholder) {
+ placeholder = String(childEl.properties.placeholder);
+ break;
+ }
+ }
+ }
+ const idx = parent.children.indexOf(node);
+ if (idx >= 0) {
+ const textNode: Text = { type: "text", value: placeholder };
+ parent.children.splice(idx, 1, textNode);
+ }
+ }
+
+ // *** Admonition: add
separators and capitalize the type title ***
+ for (const { node, parent } of admonitionNodes) {
+ for (const child of node.children) {
+ if (child.type !== "element") continue;
+ const childEl = child as Element;
+ const childClasses = childEl.properties?.className;
+ const childClassArray = Array.isArray(childClasses) ? childClasses : [];
+ if (
+ !childClassArray.some((c) =>
+ String(c).startsWith("admonitionHeading"),
+ )
+ )
+ continue;
+ for (const headingChild of childEl.children) {
+ if (headingChild.type === "text") {
+ const textNode = headingChild as Text;
+ const trimmed = textNode.value.trim();
+ if (trimmed) {
+ textNode.value = trimmed.toUpperCase();
+ }
+ }
+ }
+ break;
+ }
+
+ const hr = (): Element => ({
+ type: "element",
+ tagName: "hr",
+ properties: {},
+ children: [],
+ });
+ const idx = parent.children.indexOf(node);
+ if (idx >= 0) {
+ parent.children.splice(idx, 0, hr()); // insert before
+ parent.children.splice(idx + 2, 0, hr()); // insert after
+ }
+ }
+
+ // *** Tabs: restructure so each tab title is directly followed by the content ***
+ for (const element of tabNodes) {
+ const tabItems: Element[] = [];
+ const tabPanels: Element[] = [];
+
+ for (const child of element.children) {
+ if (child.type !== "element") continue;
+ const childEl = child as Element;
+ if (
+ childEl.tagName === "ul" &&
+ childEl.properties?.role === "tablist"
+ ) {
+ for (const li of childEl.children) {
+ if (li.type === "element" && (li as Element).tagName === "li") {
+ tabItems.push(li as Element);
+ }
+ }
+ } else {
+ for (const panelChild of childEl.children) {
+ if (panelChild.type !== "element") continue;
+ const panelEl = panelChild as Element;
+ if (panelEl.properties?.role === "tabpanel") {
+ tabPanels.push(panelEl);
+ }
+ }
+ }
+ }
+
+ const newChildren: Element[] = [];
+ for (let i = 0; i < tabItems.length; i++) {
+ const label: Element = {
+ type: "element",
+ tagName: "strong",
+ properties: {},
+ children: tabItems[i].children,
+ };
+ newChildren.push(label);
+ if (tabPanels[i]) {
+ newChildren.push(tabPanels[i]);
+ }
+ }
+
+ element.children = newChildren;
+ }
+
+ // *** Code blocks: make sure the code blocks show up as expected ***
+ {
+ function extractInnerText(node: Element): string {
+ let text = "";
+ for (const child of node.children) {
+ if (child.type === "text") {
+ text += (child as Text).value;
+ } else if (child.type === "element") {
+ text += extractInnerText(child as Element);
+ }
+ }
+ return text;
+ }
+
+ for (const { outerWrapper, outerParent, pre } of codeBlocks) {
+ // Locate the scroll_* div inside the pre
+ let scrollDiv: Element | null = null;
+ for (const child of pre.children) {
+ if (child.type !== "element") continue;
+ const el = child as Element;
+ const elClasses = el.properties?.className;
+ const elClassArray = Array.isArray(elClasses) ? elClasses : [];
+ if (elClassArray.some((c) => String(c).startsWith("scroll_"))) {
+ scrollDiv = el;
+ break;
+ }
+ }
+ if (!scrollDiv) continue;
+
+ // Extract each line of the code block
+ const lines: string[] = [];
+ for (const child of scrollDiv.children) {
+ if (child.type !== "element") continue;
+ const el = child as Element;
+ const elClasses = el.properties?.className;
+ const elClassArray = Array.isArray(elClasses) ? elClasses : [];
+
+ if (elClassArray.some((c) => String(c).startsWith("command_"))) {
+ for (const cmdChild of el.children) {
+ if (cmdChild.type !== "element") continue;
+ const cmdEl = cmdChild as Element;
+ const cmdClasses = cmdEl.properties?.className;
+ const cmdClassArray = Array.isArray(cmdClasses) ? cmdClasses : [];
+ if (!cmdClassArray.some((c) => String(c).startsWith("line_")))
+ continue;
+ const prefix =
+ cmdEl.properties?.dataContent != null
+ ? String(cmdEl.properties.dataContent)
+ : "";
+ lines.push(prefix + extractInnerText(cmdEl));
+ }
+ } else if (
+ elClassArray.some(
+ (c) =>
+ String(c).startsWith("line_") ||
+ String(c).startsWith("comment_"),
+ )
+ ) {
+ lines.push(extractInnerText(el));
+ }
+ }
+
+ const cleanPre: Element = {
+ type: "element",
+ tagName: "pre",
+ properties: {},
+ children: [
+ {
+ type: "element",
+ tagName: "code",
+ properties: {},
+ children: [{ type: "text", value: lines.join("\n") }],
+ },
+ ],
+ };
+
+ const idx = outerParent.children.indexOf(outerWrapper);
+ if (idx >= 0) {
+ outerParent.children.splice(idx, 1, cleanPre);
+ }
+ }
+ }
+
+ // *** Card links: only headings keep the link ***
+ // Prevents the same link from being repeated multiple times in custom card components with an wrapper.
+ {
+ function collectNodes(
+ node: Element,
+ href: Element["properties"][string],
+ ): Element[] {
+ const results: Element[] = [];
+ for (const child of node.children) {
+ if (child.type !== "element") continue;
+ const childEl = child as Element;
+ if (/^h[1-6]$/.test(childEl.tagName)) {
+ results.push({
+ type: "element",
+ tagName: "a",
+ properties: { href },
+ children: [childEl],
+ });
+ } else if (/^(p)$/.test(childEl.tagName)) {
+ results.push(childEl);
+ } else if (/^(img|svg)$/.test(childEl.tagName)) {
+ continue;
+ } else {
+ const nested = collectNodes(childEl, href);
+ if (nested.length > 0) {
+ results.push(...nested);
+ } else {
+ // Keep other elements as is
+ results.push(childEl);
+ }
+ }
+ }
+ return results;
+ }
+
+ for (const { node, parent } of anchorEntries.values()) {
+ const href = node.properties?.href;
+ const newNodes = collectNodes(node, href);
+ const idx = parent.children.indexOf(node);
+ if (idx >= 0) {
+ parent.children.splice(idx, 1, ...newNodes);
+ }
+ }
+ }
+
+ // *** H1 repositioning: move the h1 to the top of the document ***
+ if (h1 && headerParent) {
+ const nodeToRemove = headerElement ?? h1;
+ headerParent.children = headerParent.children.filter(
+ (child) => child !== nodeToRemove,
+ );
+ headerParent.children.unshift(h1);
+ }
+ };
+};
+
+export default rehypePrepareHTML;
diff --git a/server/unist-augment.d.ts b/server/unist-augment.d.ts
new file mode 100644
index 00000000..de07386f
--- /dev/null
+++ b/server/unist-augment.d.ts
@@ -0,0 +1,12 @@
+export {};
+
+// Augments the `Data` interface in @types/unist to add an index signature,
+// resolving a type mismatch caused by @types/mdast bundling its own copy of
+// @types/unist whose `Data` already has [key: string]: unknown. Without this,
+// unist-util-visit's Node parameter is incompatible with nodes typed
+// against the top-level @types/unist package.
+declare module "unist" {
+ interface Data {
+ [key: string]: unknown;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Icon/icons.ts b/src/components/Icon/icons.ts
index bb24e6ed..c90513ec 100644
--- a/src/components/Icon/icons.ts
+++ b/src/components/Icon/icons.ts
@@ -119,6 +119,8 @@ export { default as youtube } from "./svg/youtube.svg";
export { default as rocketLaunch } from "./svg/rocket-launch.svg";
export { default as chat } from "./svg/chat-circle.svg";
export { default as lightbulb } from "./svg/lightbulb.svg";
+export { default as githubLogo } from "./svg/github-logo.svg";
+export { default as markdown } from "./svg/markdown.svg";
// Teleport svgs
export { default as agent } from "./teleport-svg/agent.svg";
diff --git a/src/components/Icon/svg/github-logo.svg b/src/components/Icon/svg/github-logo.svg
new file mode 100644
index 00000000..bfe5ae90
--- /dev/null
+++ b/src/components/Icon/svg/github-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Icon/svg/markdown.svg b/src/components/Icon/svg/markdown.svg
new file mode 100644
index 00000000..1f995f0a
--- /dev/null
+++ b/src/components/Icon/svg/markdown.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/components/PageActions/PageActions.module.css b/src/components/PageActions/PageActions.module.css
index 05a1f610..a293a083 100644
--- a/src/components/PageActions/PageActions.module.css
+++ b/src/components/PageActions/PageActions.module.css
@@ -1,26 +1,26 @@
.pageActions {
display: flex;
- gap: var(--m-4);
- padding: var(--m-2) 0;
+ gap: var(--m-3);
+ padding: var(--m-1-5) 0;
border-bottom: 1px solid var(--color-tonal-neutral-2);
margin-bottom: var(--m-5);
- @media (--sm-scr) {
- & > * {
- &:not(:nth-last-child(2)) {
- &::after {
- content: "";
- position: relative;
- transform: translateX(var(--m-2));
- width: 1px;
- height: 100%;
- background-color: var(--color-tonal-neutral-1);
- }
+ & > * {
+ &:not(:nth-last-child(2)) {
+ &::after {
+ content: "";
+ position: relative;
+ transform: translateX(var(--m-1-5));
+ width: 1px;
+ height: 100%;
+ background-color: var(--color-tonal-neutral-1);
}
}
}
@media (--lg-scr) {
+ gap: var(--m-4);
+ margin-bottom: var(--m-6);
& > * {
&:not(:last-child) {
&::after {
@@ -40,6 +40,11 @@
background-color: unset;
border: unset;
position: relative;
+
+ span {
+ width: 100%;
+ text-align: left;
+ }
}
.githubLink,
@@ -48,7 +53,7 @@
color: var(--color-foreground-slightly-muted);
display: flex;
align-items: center;
- font-size: var(--fs-text-md);
+ font-size: var(--fs-text-lg);
letter-spacing: 0.024px;
line-height: 1.625;
margin: 0;
@@ -58,11 +63,15 @@
& > svg {
width: 20px;
height: 20px;
- margin-right: var(--m-1);
+ min-width: 20px;
+ min-height: 20px;
+ margin-right: var(--m-0-5);
+ & > path {
+ fill: var(--color-foreground-muted);
+ }
}
@media (--xs-scr) {
- font-size: var(--fs-text-lg);
width: auto;
height: auto;
}
diff --git a/src/components/PageActions/PageActions.tsx b/src/components/PageActions/PageActions.tsx
index 5b88de08..b0f1cc92 100644
--- a/src/components/PageActions/PageActions.tsx
+++ b/src/components/PageActions/PageActions.tsx
@@ -4,25 +4,60 @@ import Icon from "../Icon";
import { useInkeepSearch } from "@site/src/hooks/useInkeepSearch";
import BrowserOnly from "@docusaurus/BrowserOnly";
import ThumbsFeedback from "../ThumbsFeedback";
+import {
+ copyPageContentAsMarkdown,
+ normalizeMarkdownPathname,
+} from "@site/src/utils/markdown";
+import { useMemo, useRef, useState } from "react";
+import { useWindowSize } from "@docusaurus/theme-common";
const PageActions: React.FC<{ pathname: string }> = ({ pathname }) => {
const { setIsOpen, ModalSearchAndChat, inkeepModalProps } = useInkeepSearch({
enableAIChat: true,
});
+ const windowSize = useWindowSize();
+ const [copiedMessage, setCopiedMessage] = useState("Copy for LLM");
+
+ const copyButtonRef = useRef(null);
+
+ const copyButtonWidth = useMemo(
+ () => copyButtonRef.current?.offsetWidth || 125,
+ [copyButtonRef.current?.offsetWidth],
+ );
+
return (
-
-
+
Report an Issue
+
+
+
+ View as Markdown
+
}>
{() => {
diff --git a/src/styles/global.css b/src/styles/global.css
index b8111c96..d37bbeb2 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -77,7 +77,7 @@ h6 {
color: var(--color-dark-purple);
font-size: var(--fs-header-1);
line-height: 1.375;
- margin-bottom: var(--m-0-5) !important;
+ margin-bottom: 0 !important;
@media (--md-scr) {
font-size: var(--fs-title-md);
diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts
new file mode 100644
index 00000000..36cc1196
--- /dev/null
+++ b/src/utils/markdown.ts
@@ -0,0 +1,25 @@
+export const normalizeMarkdownPathname = (pathname: string) => {
+ // root path should return "index"
+ if (pathname === "" || pathname === "/") return "/index.md";
+ // Remove any trailing slash for consistency
+ if (pathname.endsWith("/") && pathname.length > 1)
+ return `${pathname.slice(0, -1)}.md`;
+ return `${pathname}.md`;
+};
+
+export const copyPageContentAsMarkdown = async (pathname: string) => {
+ const normalizedPathname = normalizeMarkdownPathname(pathname);
+
+ try {
+ const response = await fetch(normalizedPathname);
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch markdown content: ${response.statusText}`,
+ );
+ }
+ const markdownContent = await response.text();
+ await navigator.clipboard.writeText(markdownContent);
+ } catch (error) {
+ console.error("Error copying markdown content:", error);
+ }
+};
diff --git a/yarn.lock b/yarn.lock
index eeb35147..276f8c8c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4213,6 +4213,24 @@
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
+"@signalwire/docusaurus-plugin-llms-txt@2.0.0-alpha.7":
+ version "2.0.0-alpha.7"
+ resolved "https://registry.yarnpkg.com/@signalwire/docusaurus-plugin-llms-txt/-/docusaurus-plugin-llms-txt-2.0.0-alpha.7.tgz#f36932c7c9074af1d19b9c86c6810f96aaf9305d"
+ integrity sha512-v9EcYXVNvMydIWVIzI1H2iC4/BNdystE0jJAQIFu68SHy1a13dESz9hn5YJE9Izx18QPny1jhXym/3wEP9+8LA==
+ dependencies:
+ fs-extra "^11.0.0"
+ hast-util-select "^6.0.4"
+ hast-util-to-html "^9.0.5"
+ hast-util-to-string "^3.0.1"
+ p-map "^7.0.2"
+ rehype-parse "^9"
+ rehype-remark "^10"
+ remark-gfm "^4"
+ remark-stringify "^11"
+ string-width "^5.0.0"
+ unified "^11"
+ unist-util-visit "^5"
+
"@sinclair/typebox@^0.27.8":
version "0.27.8"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
@@ -6511,6 +6529,11 @@ batch@0.6.1:
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
+bcp-47-match@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/bcp-47-match/-/bcp-47-match-2.0.3.tgz#603226f6e5d3914a581408be33b28a53144b09d0"
+ integrity sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==
+
better-opn@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-3.0.2.tgz#f96f35deaaf8f34144a4102651babcf00d1d8817"
@@ -7509,6 +7532,11 @@ css-select@^5.1.0:
domutils "^3.0.1"
nth-check "^2.0.1"
+css-selector-parser@^3.0.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-3.3.0.tgz#1a34220d76762c929ae99993df5a60721f505082"
+ integrity sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==
+
css-tree@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
@@ -8158,6 +8186,11 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
+direction@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/direction/-/direction-2.0.1.tgz#71800dd3c4fa102406502905d3866e65bdebb985"
+ integrity sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==
+
dns-packet@^5.2.2:
version "5.6.1"
resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f"
@@ -9199,6 +9232,15 @@ fs-extra@^10.0.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
+fs-extra@^11.0.0:
+ version "11.3.4"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.4.tgz#ab6934eca8bcf6f7f6b82742e33591f86301d6fc"
+ integrity sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs-extra@^11.1.1, fs-extra@^11.2.0:
version "11.3.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
@@ -9522,6 +9564,26 @@ hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
+hast-util-embedded@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz#be4477780fbbe079cdba22982e357a0de4ba853e"
+ integrity sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-is-element "^3.0.0"
+
+hast-util-from-html@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82"
+ integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ devlop "^1.1.0"
+ hast-util-from-parse5 "^8.0.0"
+ parse5 "^7.0.0"
+ vfile "^6.0.0"
+ vfile-message "^4.0.0"
+
hast-util-from-parse5@^8.0.0:
version "8.0.3"
resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e"
@@ -9536,6 +9598,20 @@ hast-util-from-parse5@^8.0.0:
vfile-location "^5.0.0"
web-namespaces "^2.0.0"
+hast-util-has-property@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz#4e595e3cddb8ce530ea92f6fc4111a818d8e7f93"
+ integrity sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hast-util-is-body-ok-link@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz#ef63cb2f14f04ecf775139cd92bda5026380d8b4"
+ integrity sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
hast-util-is-element@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz#6e31a6532c217e5b533848c7e52c9d9369ca0932"
@@ -9543,6 +9619,17 @@ hast-util-is-element@^3.0.0:
dependencies:
"@types/hast" "^3.0.0"
+hast-util-minify-whitespace@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz#7588fd1a53f48f1d30406b81959dffc3650daf55"
+ integrity sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-embedded "^3.0.0"
+ hast-util-is-element "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ unist-util-is "^6.0.0"
+
hast-util-parse-selector@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27"
@@ -9550,6 +9637,17 @@ hast-util-parse-selector@^4.0.0:
dependencies:
"@types/hast" "^3.0.0"
+hast-util-phrasing@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz#fa284c0cd4a82a0dd6020de8300a7b1ebffa1690"
+ integrity sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-embedded "^3.0.0"
+ hast-util-has-property "^3.0.0"
+ hast-util-is-body-ok-link "^3.0.0"
+ hast-util-is-element "^3.0.0"
+
hast-util-raw@^9.0.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e"
@@ -9569,6 +9667,27 @@ hast-util-raw@^9.0.0:
web-namespaces "^2.0.0"
zwitch "^2.0.0"
+hast-util-select@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/hast-util-select/-/hast-util-select-6.0.4.tgz#1d8f69657a57441d0ce0ade35887874d3e65a303"
+ integrity sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ bcp-47-match "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ css-selector-parser "^3.0.0"
+ devlop "^1.0.0"
+ direction "^2.0.0"
+ hast-util-has-property "^3.0.0"
+ hast-util-to-string "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ nth-check "^2.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+ unist-util-visit "^5.0.0"
+ zwitch "^2.0.0"
+
hast-util-to-estree@^3.0.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz#e654c1c9374645135695cc0ab9f70b8fcaf733d7"
@@ -9591,7 +9710,7 @@ hast-util-to-estree@^3.0.0:
unist-util-position "^5.0.0"
zwitch "^2.0.0"
-hast-util-to-html@^9.0.0:
+hast-util-to-html@^9.0.0, hast-util-to-html@^9.0.5:
version "9.0.5"
resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005"
integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==
@@ -9629,6 +9748,26 @@ hast-util-to-jsx-runtime@^2.0.0:
unist-util-position "^5.0.0"
vfile-message "^4.0.0"
+hast-util-to-mdast@^10.0.0:
+ version "10.1.2"
+ resolved "https://registry.yarnpkg.com/hast-util-to-mdast/-/hast-util-to-mdast-10.1.2.tgz#bc76f7f5f72f2cde4d6a66ad4cd0aba82bb79909"
+ integrity sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ hast-util-phrasing "^3.0.0"
+ hast-util-to-html "^9.0.0"
+ hast-util-to-text "^4.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-phrasing "^4.0.0"
+ mdast-util-to-hast "^13.0.0"
+ mdast-util-to-string "^4.0.0"
+ rehype-minify-whitespace "^6.0.0"
+ trim-trailing-lines "^2.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+
hast-util-to-parse5@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed"
@@ -9642,6 +9781,13 @@ hast-util-to-parse5@^8.0.0:
web-namespaces "^2.0.0"
zwitch "^2.0.0"
+hast-util-to-string@^3.0.0, hast-util-to-string@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz#a4f15e682849326dd211c97129c94b0c3e76527c"
+ integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
hast-util-to-text@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz#57b676931e71bf9cb852453678495b3080bfae3e"
@@ -12885,7 +13031,7 @@ nprogress@^0.2.0:
resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==
-nth-check@^2.0.1:
+nth-check@^2.0.0, nth-check@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
@@ -13123,6 +13269,11 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
+p-map@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.4.tgz#b81814255f542e252d5729dca4d66e5ec14935b8"
+ integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==
+
p-queue@^6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
@@ -14877,6 +15028,23 @@ rehype-highlight@^7.0.2:
unist-util-visit "^5.0.0"
vfile "^6.0.0"
+rehype-minify-whitespace@^6.0.0:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.2.tgz#7dd234ce0775656ce6b6b0aad0a6093de29b2278"
+ integrity sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-minify-whitespace "^1.0.0"
+
+rehype-parse@^9:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-9.0.1.tgz#9993bda129acc64c417a9d3654a7be38b2a94c20"
+ integrity sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-from-html "^2.0.0"
+ unified "^11.0.0"
+
rehype-raw@7.0.0, rehype-raw@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4"
@@ -14895,6 +15063,17 @@ rehype-recma@^1.0.0:
"@types/hast" "^3.0.0"
hast-util-to-estree "^3.0.0"
+rehype-remark@^10:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/rehype-remark/-/rehype-remark-10.0.1.tgz#f669fa68cfb8b5baaf4fa95476a923516111a43b"
+ integrity sha512-EmDndlb5NVwXGfUa4c9GPK+lXeItTilLhE6ADSaQuHr4JUlKw9MidzGzx4HpqZrNCt6vnHmEifXQiiA+CEnjYQ==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ hast-util-to-mdast "^10.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
rehype-stringify@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-10.0.1.tgz#2ec1ebc56c6aba07905d3b4470bdf0f684f30b75"
@@ -14970,7 +15149,7 @@ remark-frontmatter@^5.0.0:
micromark-extension-frontmatter "^2.0.0"
unified "^11.0.0"
-remark-gfm@^4.0.0, remark-gfm@^4.0.1:
+remark-gfm@^4, remark-gfm@^4.0.0, remark-gfm@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b"
integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==
@@ -15603,7 +15782,7 @@ remark-stringify@^10.0.0:
mdast-util-to-markdown "^1.0.0"
unified "^10.0.0"
-remark-stringify@^11.0.0:
+remark-stringify@^11, remark-stringify@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3"
integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==
@@ -16838,6 +17017,11 @@ trim-lines@^3.0.0:
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
+trim-trailing-lines@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-2.1.0.tgz#9aac7e89b09cb35badf663de7133c6de164f86df"
+ integrity sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==
+
trough@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
@@ -17112,7 +17296,7 @@ unified@^10.0.0, unified@^10.1.0:
trough "^2.0.0"
vfile "^5.0.0"
-unified@^11.0.0, unified@^11.0.3, unified@^11.0.4, unified@^11.0.5:
+unified@^11, unified@^11.0.0, unified@^11.0.3, unified@^11.0.4, unified@^11.0.5:
version "11.0.5"
resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1"
integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==
@@ -17274,6 +17458,15 @@ unist-util-visit@^4.0.0:
unist-util-is "^5.0.0"
unist-util-visit-parents "^5.1.1"
+unist-util-visit@^5:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468"
+ integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
unist-util-visit@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6"
@@ -17518,15 +17711,7 @@ vfile-location@^5.0.0:
"@types/unist" "^3.0.0"
vfile "^6.0.0"
-vfile-message@^3.0.0:
- version "3.1.4"
- resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea"
- integrity sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==
- dependencies:
- "@types/unist" "^2.0.0"
- unist-util-stringify-position "^3.0.0"
-
-vfile-message@^4.0.0:
+vfile-message@^3.0.0, vfile-message@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181"
integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==
@@ -17564,17 +17749,7 @@ vfile-statistics@^2.0.0:
vfile "^5.0.0"
vfile-message "^3.0.0"
-vfile@^5.0.0, vfile@^5.1.0:
- version "5.3.7"
- resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7"
- integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==
- dependencies:
- "@types/unist" "^2.0.0"
- is-buffer "^2.0.0"
- unist-util-stringify-position "^3.0.0"
- vfile-message "^3.0.0"
-
-vfile@^6.0.0, vfile@^6.0.1:
+vfile@^5.0.0, vfile@^5.1.0, vfile@^6.0.0, vfile@^6.0.1:
version "6.0.3"
resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab"
integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==