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 @@ +
note

This is a note.

diff --git a/server/fixtures/llms/anchor-mixed-content-nested.html b/server/fixtures/llms/anchor-mixed-content-nested.html new file mode 100644 index 00000000..2b902fd6 --- /dev/null +++ b/server/fixtures/llms/anchor-mixed-content-nested.html @@ -0,0 +1 @@ +
Feature image

Feature title

Feature description

diff --git a/server/fixtures/llms/anchor-mixed-content.html b/server/fixtures/llms/anchor-mixed-content.html new file mode 100644 index 00000000..95319e88 --- /dev/null +++ b/server/fixtures/llms/anchor-mixed-content.html @@ -0,0 +1 @@ +

Feature title

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
diff --git a/server/fixtures/llms/code-block.html b/server/fixtures/llms/code-block.html new file mode 100644 index 00000000..c2714ab8 --- /dev/null +++ b/server/fixtures/llms/code-block.html @@ -0,0 +1 @@ +
const x = 1;
const y = 2;
diff --git a/server/fixtures/llms/empty-comments.html b/server/fixtures/llms/empty-comments.html new file mode 100644 index 00000000..4b6152e7 --- /dev/null +++ b/server/fixtures/llms/empty-comments.html @@ -0,0 +1 @@ +

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 @@ +

How it works

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 @@ +

Check out this link

diff --git a/server/fixtures/llms/irrelevant-components.html b/server/fixtures/llms/irrelevant-components.html new file mode 100644 index 00000000..e474ec52 --- /dev/null +++ b/server/fixtures/llms/irrelevant-components.html @@ -0,0 +1 @@ +
Page header

Normal paragraph.

Did you find this page helpful?
Step checkpoint

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 @@ +

Page Title

Some content

Duplicate Title

diff --git a/server/fixtures/llms/reposition-h1.html b/server/fixtures/llms/reposition-h1.html new file mode 100644 index 00000000..8a9e4df9 --- /dev/null +++ b/server/fixtures/llms/reposition-h1.html @@ -0,0 +1 @@ +

Page title

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 @@ +
NOTE

This is a note.


diff --git a/server/fixtures/llms/result/anchor-mixed-content-nested.html b/server/fixtures/llms/result/anchor-mixed-content-nested.html new file mode 100644 index 00000000..7127b8b1 --- /dev/null +++ b/server/fixtures/llms/result/anchor-mixed-content-nested.html @@ -0,0 +1 @@ +

Feature title

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 title

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 @@ +

How it works

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 @@ +

Check out this link

diff --git a/server/fixtures/llms/result/irrelevant-components.html b/server/fixtures/llms/result/irrelevant-components.html new file mode 100644 index 00000000..8247c011 --- /dev/null +++ b/server/fixtures/llms/result/irrelevant-components.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 @@ +

Page Title

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 @@ +

Page title

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

Tab 1 content

Tab 2

Tab 2 content

diff --git a/server/fixtures/llms/result/var-component.html b/server/fixtures/llms/result/var-component.html new file mode 100644 index 00000000..87e8233d --- /dev/null +++ b/server/fixtures/llms/result/var-component.html @@ -0,0 +1 @@ +

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

\ No newline at end of file diff --git a/server/fixtures/llms/var-component.html b/server/fixtures/llms/var-component.html new file mode 100644 index 00000000..2119ba53 --- /dev/null +++ b/server/fixtures/llms/var-component.html @@ -0,0 +1 @@ +

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==