From f8825cc9ad1bd53f37ee1a95913399077815b950 Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Fri, 19 Apr 2024 14:19:03 -0500 Subject: [PATCH 1/9] feat: allow users to open sway playground for full code examples --- .gitignore | 4 +- .../quickstart/building-a-smart-contract.mdx | 1 + src/components/Pre.tsx | 100 ++++--- src/lib/plugins/code-import.ts | 106 +++---- src/lib/plugins/rehype-code.ts | 263 ++++++++++-------- 5 files changed, 261 insertions(+), 213 deletions(-) diff --git a/.gitignore b/.gitignore index 5d494c6bf..31bad23dc 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel @@ -48,4 +49,5 @@ node_modules notes # Generated files -src/generated/ \ No newline at end of file +src/generated/ +dist \ No newline at end of file diff --git a/docs/guides/docs/quickstart/building-a-smart-contract.mdx b/docs/guides/docs/quickstart/building-a-smart-contract.mdx index 0aeace6e1..6ed94d5cd 100644 --- a/docs/guides/docs/quickstart/building-a-smart-contract.mdx +++ b/docs/guides/docs/quickstart/building-a-smart-contract.mdx @@ -262,6 +262,7 @@ action={{ comment="all" commentType="/*" lang="sway" + showOpenPlayground={true} /> ### Build the Contract diff --git a/src/components/Pre.tsx b/src/components/Pre.tsx index b9d8e599c..1a557e8f1 100644 --- a/src/components/Pre.tsx +++ b/src/components/Pre.tsx @@ -1,6 +1,14 @@ import type { ThemeUtilsCSS } from '@fuel-ui/css'; import { cssObj } from '@fuel-ui/css'; -import { Box, Button, Icon, IconButton, Text, toast } from '@fuel-ui/react'; +import { + Box, + Button, + Flex, + Icon, + IconButton, + Text, + toast, +} from '@fuel-ui/react'; import { Children, type ReactNode, useState } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import darkTheme from 'react-syntax-highlighter/dist/cjs/styles/prism/night-owl'; @@ -14,12 +22,14 @@ type PreProps = { __lines?: number; __code?: string; className?: string; + showOpenPlayground?: boolean; }; export function Pre({ css, children, title, + showOpenPlayground, __code: code, __lines: lines = 0, ...props @@ -43,48 +53,66 @@ export function Pre({ toast.success('Copied to clipboard'); } + async function openSwayPlayground() { + const playgroundCode = code ?? ''; + // TODO: this will break if sway playground changes urls + window.open('https://www.sway-playground.org/', '_blank'); + // TODO: this will break if the storage key in sway playground is changed + // or the playground changes how it stores the abi + localStorage.setItem('playground_abi', playgroundCode); + } + function toggleExpand() { setExpanded(!expanded); } return ( - - {title && {title}} - - {!props['data-language'] ? ( - - {gqlCode} - - ) : ( -
{children}
- )} + <> + + {title && {title}} + + {!props['data-language'] ? ( + + {gqlCode} + + ) : ( +
{children}
+ )} +
+ + {needExpand && ( + + )} + } + variant='ghost' + intent='base' + aria-label='Copy to Clipboard' + onClick={handleCopy} + /> +
- - {needExpand && ( - - )} - } - variant='ghost' - intent='base' - aria-label='Copy to Clipboard' - onClick={handleCopy} - /> - -
+ + )} + ); } diff --git a/src/lib/plugins/code-import.ts b/src/lib/plugins/code-import.ts index d529e1553..52d74dc63 100644 --- a/src/lib/plugins/code-import.ts +++ b/src/lib/plugins/code-import.ts @@ -1,15 +1,15 @@ -import fs from 'node:fs'; -import { EOL } from 'os'; -import path from 'path'; -import * as acorn from 'acorn'; -import * as walk from 'acorn-walk'; -import * as prettier from 'prettier'; -import type { Root } from 'remark-gfm'; -import { visit } from 'unist-util-visit'; -import { FUEL_TESTNET } from '~/src/config/constants'; - -import { getEndCommentType } from './text-import'; -import type { CommentTypes } from './text-import'; +import fs from "node:fs"; +import { EOL } from "os"; +import path from "path"; +import * as acorn from "acorn"; +import * as walk from "acorn-walk"; +import * as prettier from "prettier"; +import type { Root } from "remark-gfm"; +import { visit } from "unist-util-visit"; +import { FUEL_TESTNET } from "~/src/config/constants"; + +import { getEndCommentType } from "./text-import"; +import type { CommentTypes } from "./text-import"; interface Block { content: string; @@ -19,8 +19,8 @@ interface Block { function toAST(content: string) { return acorn.parse(content, { - ecmaVersion: 'latest', - sourceType: 'module', + ecmaVersion: "latest", + sourceType: "module", }); } @@ -36,7 +36,7 @@ function extractLines( let end; if (toLine) { end = toLine; - } else if (lines[lines.length - 1] === '') { + } else if (lines[lines.length - 1] === "") { end = lines.length - 1; } else { end = lines.length; @@ -46,13 +46,13 @@ function extractLines( return lines .slice(start - 1, end) .filter((_line, index) => newLines.includes(index)) - .join('\n'); + .join("\n"); } - return lines.slice(start - 1, end).join('\n'); + return lines.slice(start - 1, end).join("\n"); } function escapeRegExp(string: string): string { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function extractCommentBlock( @@ -66,7 +66,7 @@ function extractCommentBlock( let lineEnd = -1; const anchorStack: string[] = []; - const endCommentType = getEndCommentType(commentType) || ''; + const endCommentType = getEndCommentType(commentType) || ""; const startAnchorRegex = new RegExp( `${escapeRegExp(commentType)}\\s*ANCHOR\\s*:\\s*${escapeRegExp( @@ -84,7 +84,7 @@ function extractCommentBlock( if (lineStart === -1) { lineStart = i; } - anchorStack.push('anchor'); + anchorStack.push("anchor"); } else if (endAnchorRegex.test(lines[i])) { anchorStack.pop(); if (anchorStack.length === 0 && lineEnd === -1) { @@ -102,21 +102,21 @@ function extractCommentBlock( lineEnd = lines.length - 1; } - if (trim === 'true') { + if (trim === "true") { // Adjust lineStart and lineEnd to exclude the anchor comments // and the code block markers (```), if present. lineStart = lines.findIndex( - (line, index) => index > lineStart && line.includes('```') + (line, index) => index > lineStart && line.includes("```") ) + 1; lineEnd = lines.findIndex( - (line, index) => index > lineStart && line.includes('```') + (line, index) => index > lineStart && line.includes("```") ); lineEnd = lineEnd === -1 ? lines.length : lineEnd; } let newLines = lines.slice(lineStart, lineEnd); - newLines = newLines.filter((line) => !line.includes('ANCHOR')); + newLines = newLines.filter((line) => !line.includes("ANCHOR")); // Dedent the lines here: const toDedent = minWhitespace(newLines); @@ -124,7 +124,7 @@ function extractCommentBlock( newLines = dedent(newLines, toDedent); } - const linesContent = newLines.join(EOL).replace(/\n{3,}/g, '\n\n'); + const linesContent = newLines.join(EOL).replace(/\n{3,}/g, "\n\n"); return { content: linesContent.trim(), @@ -135,7 +135,7 @@ function extractCommentBlock( function minWhitespace(lines: string[]): number { return lines - .filter((line) => line.trim() !== '') // ignore blank lines + .filter((line) => line.trim() !== "") // ignore blank lines .map((line) => { const matchResult = line.match(/^(\s*)/); return matchResult ? matchResult[0].length : 0; @@ -145,7 +145,7 @@ function minWhitespace(lines: string[]): number { function dedent(lines: string[], amount: number): string[] { const regex = new RegExp(`^\\s{${amount}}`); - return lines.map((line) => line.replace(regex, '')); + return lines.map((line) => line.replace(regex, "")); } function getLineOffsets(str: string) { @@ -161,13 +161,13 @@ function extractTestCase(source: string, testCase: string) { let charStart = 0; let charEnd = 0; - let content = ''; - const chars = source.split(''); + let content = ""; + const chars = source.split(""); const linesOffset = getLineOffsets(source); // biome-ignore lint/suspicious/noExplicitAny: walk.fullAncestor(ast, (node: any, _state, ancestors) => { - if (node.name === 'test') { + if (node.name === "test") { // biome-ignore lint/suspicious/noExplicitAny: const parent = ancestors.reverse()[1] as any; const args = parent.arguments || []; @@ -175,8 +175,8 @@ function extractTestCase(source: string, testCase: string) { if (val && val === testCase) { const body = args[1]?.body; - content = chars.slice(body.start, body.end).join('').slice(1, -1); - content = prettier.format(content, { parser: 'babel' }).trimEnd(); + content = chars.slice(body.start, body.end).join("").slice(1, -1); + content = prettier.format(content, { parser: "babel" }).trimEnd(); charStart = body.start; charEnd = body.end; } @@ -200,11 +200,11 @@ export function codeImport() { const dirname = file.data.rawDocumentData?.sourceFileDir; // biome-ignore lint/suspicious/noExplicitAny: const nodes: [any, number | undefined, any][] = []; - if (dirname.startsWith('docs/fuels-wallet')) return; + if (dirname.startsWith("docs/fuels-wallet")) return; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, 'mdxJsxFlowElement', (node: any, idx, parent) => { - if (node.name === 'CodeImport') { + visit(tree, "mdxJsxFlowElement", (node: any, idx, parent) => { + if (node.name === "CodeImport") { // biome-ignore lint/suspicious/noExplicitAny: nodes.push([node as any, idx, parent]); } @@ -212,37 +212,37 @@ export function codeImport() { nodes.forEach(([node]) => { const attr = node.attributes; - let content = ''; + let content = ""; if (!attr.length) { - throw new Error('CodeImport needs to have properties defined'); + throw new Error("CodeImport needs to have properties defined"); } // biome-ignore lint/suspicious/noExplicitAny: - const file = attr.find((i: any) => i.name === 'file')?.value; + const file = attr.find((i: any) => i.name === "file")?.value; // biome-ignore lint/suspicious/noExplicitAny: - let lineStart = attr.find((i: any) => i.name === 'lineStart')?.value; + let lineStart = attr.find((i: any) => i.name === "lineStart")?.value; // biome-ignore lint/suspicious/noExplicitAny: - let lineEnd = attr.find((i: any) => i.name === 'lineEnd')?.value; + let lineEnd = attr.find((i: any) => i.name === "lineEnd")?.value; // biome-ignore lint/suspicious/noExplicitAny: - const comment = attr.find((i: any) => i.name === 'comment')?.value; + const comment = attr.find((i: any) => i.name === "comment")?.value; const commentType = attr.find( // biome-ignore lint/suspicious/noExplicitAny: - (i: any) => i.name === 'commentType' + (i: any) => i.name === "commentType" )?.value; // biome-ignore lint/suspicious/noExplicitAny: - const trim = attr.find((i: any) => i.name === 'trim')?.value; + const trim = attr.find((i: any) => i.name === "trim")?.value; let linesIncluded = // biome-ignore lint/suspicious/noExplicitAny: - attr.find((i: any) => i.name === 'linesIncluded')?.value || []; + attr.find((i: any) => i.name === "linesIncluded")?.value || []; // biome-ignore lint/suspicious/noExplicitAny: - const testCase = attr.find((i: any) => i.name === 'testCase')?.value; + const testCase = attr.find((i: any) => i.name === "testCase")?.value; const fileAbsPath = path.resolve(path.join(rootDir, dirname), file); const lang = // biome-ignore lint/suspicious/noExplicitAny: - attr.find((i: any) => i.name === 'lang')?.value || - path.extname(fileAbsPath).replace('.', ''); - const fileContent = fs.readFileSync(fileAbsPath, 'utf8'); + attr.find((i: any) => i.name === "lang")?.value || + path.extname(fileAbsPath).replace(".", ""); + const fileContent = fs.readFileSync(fileAbsPath, "utf8"); const resp = Array.isArray(linesIncluded) ? 0 : linesIncluded; if (resp !== 0) { @@ -272,17 +272,17 @@ export function codeImport() { content = fileContent; } - content = content.replaceAll('{props.fuelTestnet}', FUEL_TESTNET); + content = content.replaceAll("{props.fuelTestnet}", FUEL_TESTNET); const newAttrs = [ { - name: '__content', - type: 'mdxJsxAttribute', + name: "__content", + type: "mdxJsxAttribute", value: content, }, { - name: '__language', - type: 'mdxJsxAttribute', + name: "__language", + type: "mdxJsxAttribute", value: lang, }, ]; diff --git a/src/lib/plugins/rehype-code.ts b/src/lib/plugins/rehype-code.ts index 48d371787..daca12ba8 100644 --- a/src/lib/plugins/rehype-code.ts +++ b/src/lib/plugins/rehype-code.ts @@ -1,16 +1,16 @@ -import { readFileSync } from 'fs'; -import { join as pathJoin } from 'path'; -import * as fs from 'fs/promises'; -import { toText } from 'hast-util-to-text'; -import { h } from 'hastscript'; -import prettier from 'prettier'; -import type { Options as RehypeCodeOptions } from 'rehype-pretty-code'; -import rehypeCode from 'rehype-pretty-code'; -import type { Root } from 'remark-gfm'; -import { getHighlighter as shikiGetHighlighter } from 'shiki'; -import type { PluggableList } from 'unified'; -import { visit } from 'unist-util-visit'; -import { FUEL_TESTNET } from '~/src/config/constants'; +import { readFileSync } from "fs"; +import { join as pathJoin } from "path"; +import * as fs from "fs/promises"; +import { toText } from "hast-util-to-text"; +import { h } from "hastscript"; +import prettier from "prettier"; +import type { Options as RehypeCodeOptions } from "rehype-pretty-code"; +import rehypeCode from "rehype-pretty-code"; +import type { Root } from "remark-gfm"; +import { getHighlighter as shikiGetHighlighter } from "shiki"; +import type { PluggableList } from "unified"; +import { visit } from "unist-util-visit"; +import { FUEL_TESTNET } from "~/src/config/constants"; // Shiki loads languages and themes using "fs" instead of "import", so Next.js // doesn't bundle them into production build. To work around, we manually copy @@ -19,7 +19,7 @@ import { FUEL_TESTNET } from '~/src/config/constants'; // Note that they are only referenced on server side // See: https://github.com/shikijs/shiki/issues/138 const getShikiPath = (): string => { - return pathJoin(process.cwd(), 'public/shiki'); + return pathJoin(process.cwd(), "public/shiki"); }; const touched = { current: false }; @@ -33,7 +33,7 @@ const touchShikiPath = (): void => { touched.current = true; }; -const getHighlighter: RehypeCodeOptions['getHighlighter'] = async (options) => { +const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { touchShikiPath(); const pathFolder = `${getShikiPath()}/languages`; @@ -45,64 +45,64 @@ const getHighlighter: RehypeCodeOptions['getHighlighter'] = async (options) => { ...(options as any), langs: [ { - id: 'rust', - scopeName: 'source.rust', + id: "rust", + scopeName: "source.rust", path: `${pathFolder}/rust.tmLanguage.json`, - displayName: 'Rust', - aliases: ['rs'], + displayName: "Rust", + aliases: ["rs"], }, { - id: 'javascript', - scopeName: 'source.js', + id: "javascript", + scopeName: "source.js", path: `${pathFolder}/javascript.tmLanguage.json`, - displayName: 'JavaScript', - aliases: ['js'], + displayName: "JavaScript", + aliases: ["js"], }, { - id: 'typescript', - scopeName: 'source.ts', + id: "typescript", + scopeName: "source.ts", path: `${pathFolder}/typescript.tmLanguage.json`, - displayName: 'TypeScript', - aliases: ['ts'], + displayName: "TypeScript", + aliases: ["ts"], }, { - id: 'tsx', - scopeName: 'source.tsx', + id: "tsx", + scopeName: "source.tsx", path: `${pathFolder}/tsx.tmLanguage.json`, - displayName: 'TSX', + displayName: "TSX", }, { - id: 'jsx', - scopeName: 'source.js.jsx', + id: "jsx", + scopeName: "source.js.jsx", path: `${pathFolder}/jsx.tmLanguage.json`, - displayName: 'JSX', + displayName: "JSX", }, { - id: 'json', - scopeName: 'source.json', + id: "json", + scopeName: "source.json", path: `${pathFolder}/json.tmLanguage.json`, - displayName: 'JSON', + displayName: "JSON", }, { - id: 'toml', - scopeName: 'source.toml', + id: "toml", + scopeName: "source.toml", path: `${pathFolder}/toml.tmLanguage.json`, - displayName: 'TOML', + displayName: "TOML", }, { - id: 'graphql', - scopeName: 'source.graphql', + id: "graphql", + scopeName: "source.graphql", path: `${pathFolder}/graphql.tmLanguage.json`, - displayName: 'GraphQL', - embeddedLangs: ['javascript', 'typescript', 'jsx', 'tsx'], + displayName: "GraphQL", + embeddedLangs: ["javascript", "typescript", "jsx", "tsx"], }, { - id: 'sway', - scopeName: 'source.sway', + id: "sway", + scopeName: "source.sway", path: `${pathFolder}/sway.tmLanguage.json`, }, { - id: 'html', + id: "html", name: "html", scopeName: "text.html.basic", path: `${pathFolder}/html.tmLanguage.json`, @@ -115,15 +115,15 @@ const getHighlighter: RehypeCodeOptions['getHighlighter'] = async (options) => { // biome-ignore lint/suspicious/noExplicitAny: function isElement(value: any): value is Element { - return value ? value.type === 'element' : false; + return value ? value.type === "element" : false; } // biome-ignore lint/suspicious/noExplicitAny: function isCodeEl(node: any, parent: any) { return ( - (node.tagName === 'code' && + (node.tagName === "code" && isElement(parent) && - parent.tagName === 'pre') || - node.tagName === 'inlineCode' + parent.tagName === "pre") || + node.tagName === "inlineCode" ); } @@ -135,24 +135,24 @@ function processCodeGroup(nodes: any[]): any[] { return ( nodes // biome-ignore lint/suspicious/noExplicitAny: - .filter((n: any) => n.tagName === 'pre') + .filter((n: any) => n.tagName === "pre") // biome-ignore lint/suspicious/noExplicitAny: .map((pre: any) => { const language = pre.children?.[0]?.properties?.className?.[0].replace( - 'language-', - '' - ) ?? ''; + "language-", + "" + ) ?? ""; const code = pre.children?.[0]?.children // biome-ignore lint/suspicious/noExplicitAny: ?.map((child: any) => child.value) - .join(''); + .join(""); - const child = h('code', { class: language }, code); + const child = h("code", { class: language }, code); return { - type: 'element', - tagName: 'pre', + type: "element", + tagName: "pre", properties: { language: language, code: code, @@ -174,10 +174,10 @@ function codeGroup2() { tree.children.forEach((node: any, index: number) => { if ( node.children && - node.children[0]?.type === 'text' && - node.children[0]?.value.trim().startsWith(':::') + node.children[0]?.type === "text" && + node.children[0]?.value.trim().startsWith(":::") ) { - if (node.children[0]?.value.trim() === '::: code-group') { + if (node.children[0]?.value.trim() === "::: code-group") { end = null; start = index; } else if (start !== null) { @@ -185,8 +185,8 @@ function codeGroup2() { const children = processCodeGroup(nodes); // biome-ignore lint/suspicious/noExplicitAny: const codeTabsElement: any = { - type: 'mdxJsxFlowElement', - name: 'CodeTabs', + type: "mdxJsxFlowElement", + name: "CodeTabs", children: children, }; tree.children.splice(start, end - start + 1, codeTabsElement); @@ -220,8 +220,8 @@ function codeGroup() { const children = processCodeGroup(codeGroupNodes); // biome-ignore lint/suspicious/noExplicitAny: const codeTabsElement: any = { - type: 'mdxJsxFlowElement', - name: 'CodeTabs', + type: "mdxJsxFlowElement", + name: "CodeTabs", children: children, }; tree.children.splice(start, end - start, codeTabsElement); @@ -236,8 +236,8 @@ function codeGroup() { function hasCodeGroup(node: any): boolean { return ( node.children && - node.children[0]?.type === 'text' && - node.children[0]?.value.trim() === '::: code-group' + node.children[0]?.type === "text" && + node.children[0]?.value.trim() === "::: code-group" ); } @@ -245,8 +245,8 @@ function hasCodeGroup(node: any): boolean { function hasEndOfCodeGroup(node: any): boolean { return ( node.children && - node.children[0].type === 'text' && - node.children[0].value.trim() === ':::' + node.children[0].type === "text" && + node.children[0].value.trim() === ":::" ); } @@ -256,28 +256,28 @@ function hasEndOfCodeGroup(node: any): boolean { function codeLanguage() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, _idx: any, parent: any) => { + visit(tree, "", (node: any, _idx: any, parent: any) => { if (!isCodeEl(node, parent)) return; if (!node.properties) node.properties = {}; const lang = node.properties?.className?.[0]; - if (lang?.includes('rust')) { - node.properties.className[0] = 'language-rust'; + if (lang?.includes("rust")) { + node.properties.className[0] = "language-rust"; } - if (lang?.includes('sway')) { - node.properties.className[0] = 'language-sway'; + if (lang?.includes("sway")) { + node.properties.className[0] = "language-sway"; } - if (lang?.includes('ts')) { - node.properties.className[0] = 'language-typescript'; + if (lang?.includes("ts")) { + node.properties.className[0] = "language-typescript"; } - if (lang?.includes('tsx')) { - node.properties.className[0] = 'language-typescript'; + if (lang?.includes("tsx")) { + node.properties.className[0] = "language-typescript"; } - if (lang?.includes('sh')) { - node.properties.className[0] = 'language-sh'; + if (lang?.includes("sh")) { + node.properties.className[0] = "language-sh"; } - if (lang?.includes('json')) { - node.properties.className[0] = 'language-json'; + if (lang?.includes("json")) { + node.properties.className[0] = "language-json"; } }); }; @@ -286,37 +286,37 @@ function codeLanguage() { // biome-ignore lint/suspicious/noExplicitAny: function isGraphQLCodeSamples(node: any) { return ( - node.name === 'CodeExamples' && + node.name === "CodeExamples" && // biome-ignore lint/suspicious/noExplicitAny: - node.attributes?.find((a: any) => a.name === '__ts_content') + node.attributes?.find((a: any) => a.name === "__ts_content") ); } // biome-ignore lint/suspicious/noExplicitAny: function getGraphQLCodeTabs(node: any) { const codeProps = { - className: ['language-typescript'], - 'data-language': 'typescript', + className: ["language-typescript"], + "data-language": "typescript", }; const prettierProps = { - parser: 'typescript', + parser: "typescript", semi: true, singleQuote: true, }; // biome-ignore lint/suspicious/noExplicitAny: const findProp = (name: string) => (a: any) => a.name === name; - const tsContent = node.attributes?.find(findProp('__ts_content')); - const apolloContent = node.attributes?.find(findProp('__apollo_content')); - const urqlContent = node.attributes?.find(findProp('__urql_content')); - const filepath = node.attributes?.find(findProp('__filepath')); + const tsContent = node.attributes?.find(findProp("__ts_content")); + const apolloContent = node.attributes?.find(findProp("__apollo_content")); + const urqlContent = node.attributes?.find(findProp("__urql_content")); + const filepath = node.attributes?.find(findProp("__filepath")); - const tsCodeContent = tsContent?.value ?? ''; + const tsCodeContent = tsContent?.value ?? ""; const tsCodeRaw = prettier.format(tsCodeContent, prettierProps); - const tsCode = h('code', codeProps, tsCodeRaw); + const tsCode = h("code", codeProps, tsCodeRaw); - const testnet = filepath.value.includes('/beta-4/') ? 'beta-4' : FUEL_TESTNET; + const testnet = filepath.value.includes("/beta-4/") ? "beta-4" : FUEL_TESTNET; const apolloImport = `import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; @@ -324,9 +324,9 @@ function getGraphQLCodeTabs(node: any) { uri: 'https://${testnet}.fuel.network/graphql', cache: new InMemoryCache(), });\n\n`; - const apolloContentValue = apolloImport + apolloContent?.value ?? ''; + const apolloContentValue = apolloImport + apolloContent?.value ?? ""; const apolloRaw = prettier.format(apolloContentValue, prettierProps); - const apolloCode = h('code', codeProps, apolloRaw); + const apolloCode = h("code", codeProps, apolloRaw); const urlqImport = `import { Client, cacheExchange, fetchExchange } from 'urql'; @@ -334,46 +334,46 @@ function getGraphQLCodeTabs(node: any) { url: 'https:/${testnet}.fuel.network/graphql', exchanges: [cacheExchange, fetchExchange], });\n\n`; - const urlQContentValue = urlqImport + urqlContent?.value ?? ''; + const urlQContentValue = urlqImport + urqlContent?.value ?? ""; const urlQRaw = prettier.format(urlQContentValue, prettierProps); - const urqlCode = h('code', codeProps, urlQRaw); + const urqlCode = h("code", codeProps, urlQRaw); return { tsCode, apolloCode, urqlCode }; } function codeImport() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, 'mdxJsxFlowElement', (node: any) => { - if (node.name !== 'CodeImport' && node.name !== 'CodeExamples') return; + visit(tree, "mdxJsxFlowElement", (node: any) => { + if (node.name !== "CodeImport" && node.name !== "CodeExamples") return; // biome-ignore lint/suspicious/noExplicitAny: - const content = node.attributes?.find((a: any) => a.name === '__content'); + const content = node.attributes?.find((a: any) => a.name === "__content"); if (isGraphQLCodeSamples(node)) { const { tsCode, apolloCode, urqlCode } = getGraphQLCodeTabs(node); - const tsPre = h('element'); - tsPre.tagName = 'pre'; + const tsPre = h("element"); + tsPre.tagName = "pre"; tsPre.children = [tsCode]; - const apolloPre = h('element'); - apolloPre.tagName = 'pre'; + const apolloPre = h("element"); + apolloPre.tagName = "pre"; apolloPre.children = [apolloCode]; - const urlqPre = h('element'); - urlqPre.tagName = 'pre'; + const urlqPre = h("element"); + urlqPre.tagName = "pre"; urlqPre.children = [urqlCode]; node.children = [tsPre, apolloPre, urlqPre]; return; } - node.type = 'element'; - node.tagName = 'pre'; + node.type = "element"; + node.tagName = "pre"; // biome-ignore lint/suspicious/noExplicitAny: - const lang = node.attributes?.find((a: any) => a.name === '__language'); + const lang = node.attributes?.find((a: any) => a.name === "__language"); const code = h( - 'code', + "code", { class: lang?.value }, - content?.value.replace(/\r/g, '') + content?.value.replace(/\r/g, "") ); node.children = [code]; }); @@ -386,13 +386,13 @@ function codeImport() { function addLines() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, _idx: any, parent: any) => { + visit(tree, "", (node: any, _idx: any, parent: any) => { if (!isCodeEl(node, parent)) return; let counter = 1; // biome-ignore lint/suspicious/noExplicitAny: node.children = node.children.reduce((acc: any, node: any) => { - if (node.properties?.['data-line'] === '') { - node.properties['data-line'] = counter; + if (node.properties?.["data-line"] === "") { + node.properties["data-line"] = counter; counter = counter + 1; } return acc.concat(node); @@ -401,11 +401,27 @@ function addLines() { }; } +function addShowPlayground() { + return function transformer(tree: Root) { + // biome-ignore lint/suspicious/noExplicitAny: + visit(tree, "", (node: any, _, parent: any) => { + // WARNING this could break if rehype-pretty-code changes its implementation + // or we stop using rehype-pretty-code + // rehype-pretty-code wraps our pre elements in a div which is why this is needed + if (node.tagName !== "pre" && parent?.tagName !== "div") return; + if (!node.properties) node.properties = {}; + node.properties.showOpenPlayground = parent.attributes?.find( + (i: any) => i.name === "showOpenPlayground" + )?.value.value; + }); + }; +} + function addRawCode() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any) => { - if (node.tagName !== 'pre') return; + visit(tree, "", (node: any) => { + if (node.tagName !== "pre") return; const text = toText(node); if (!node.properties) node.properties = {}; node.properties.__code = text; @@ -416,11 +432,11 @@ function addRawCode() { function addNumberOfLines() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, _idx: any, parent: any) => { + visit(tree, "", (node: any, _idx: any, parent: any) => { if (!node.properties) node.properties = {}; if (!isCodeEl(node, parent)) { const text = toText(node); - const lines = text.split('\n').length; + const lines = text.split("\n").length; node.properties.__lines = lines; } }); @@ -428,18 +444,18 @@ function addNumberOfLines() { } const getRehypeCodeOptions = ( - theme: 'light' | 'dark' + theme: "light" | "dark" ): Partial => { - const themeFileName: string = theme === 'light' ? 'github-light' : 'dracula'; + const themeFileName: string = theme === "light" ? "github-light" : "dracula"; return { theme: JSON.parse( - readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, 'utf-8') + readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, "utf-8") ), getHighlighter, }; }; -export const getMdxCode = (theme: 'light' | 'dark'): PluggableList => [ +export const getMdxCode = (theme: "light" | "dark"): PluggableList => [ codeImport, codeGroup, codeGroup2, @@ -448,4 +464,5 @@ export const getMdxCode = (theme: 'light' | 'dark'): PluggableList => [ addLines, addRawCode, addNumberOfLines, + addShowPlayground, ]; From 54043f65f04265f7c54eeb561b8a128f5cd426fd Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Mon, 22 Apr 2024 15:10:43 -0500 Subject: [PATCH 2/9] fix: lint --- src/lib/plugins/code-import.ts | 106 +++++++------- src/lib/plugins/fix-indent.ts | 5 +- src/lib/plugins/links.ts | 2 +- src/lib/plugins/rehype-code.ts | 256 ++++++++++++++++----------------- 4 files changed, 184 insertions(+), 185 deletions(-) diff --git a/src/lib/plugins/code-import.ts b/src/lib/plugins/code-import.ts index 52d74dc63..d529e1553 100644 --- a/src/lib/plugins/code-import.ts +++ b/src/lib/plugins/code-import.ts @@ -1,15 +1,15 @@ -import fs from "node:fs"; -import { EOL } from "os"; -import path from "path"; -import * as acorn from "acorn"; -import * as walk from "acorn-walk"; -import * as prettier from "prettier"; -import type { Root } from "remark-gfm"; -import { visit } from "unist-util-visit"; -import { FUEL_TESTNET } from "~/src/config/constants"; - -import { getEndCommentType } from "./text-import"; -import type { CommentTypes } from "./text-import"; +import fs from 'node:fs'; +import { EOL } from 'os'; +import path from 'path'; +import * as acorn from 'acorn'; +import * as walk from 'acorn-walk'; +import * as prettier from 'prettier'; +import type { Root } from 'remark-gfm'; +import { visit } from 'unist-util-visit'; +import { FUEL_TESTNET } from '~/src/config/constants'; + +import { getEndCommentType } from './text-import'; +import type { CommentTypes } from './text-import'; interface Block { content: string; @@ -19,8 +19,8 @@ interface Block { function toAST(content: string) { return acorn.parse(content, { - ecmaVersion: "latest", - sourceType: "module", + ecmaVersion: 'latest', + sourceType: 'module', }); } @@ -36,7 +36,7 @@ function extractLines( let end; if (toLine) { end = toLine; - } else if (lines[lines.length - 1] === "") { + } else if (lines[lines.length - 1] === '') { end = lines.length - 1; } else { end = lines.length; @@ -46,13 +46,13 @@ function extractLines( return lines .slice(start - 1, end) .filter((_line, index) => newLines.includes(index)) - .join("\n"); + .join('\n'); } - return lines.slice(start - 1, end).join("\n"); + return lines.slice(start - 1, end).join('\n'); } function escapeRegExp(string: string): string { - return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function extractCommentBlock( @@ -66,7 +66,7 @@ function extractCommentBlock( let lineEnd = -1; const anchorStack: string[] = []; - const endCommentType = getEndCommentType(commentType) || ""; + const endCommentType = getEndCommentType(commentType) || ''; const startAnchorRegex = new RegExp( `${escapeRegExp(commentType)}\\s*ANCHOR\\s*:\\s*${escapeRegExp( @@ -84,7 +84,7 @@ function extractCommentBlock( if (lineStart === -1) { lineStart = i; } - anchorStack.push("anchor"); + anchorStack.push('anchor'); } else if (endAnchorRegex.test(lines[i])) { anchorStack.pop(); if (anchorStack.length === 0 && lineEnd === -1) { @@ -102,21 +102,21 @@ function extractCommentBlock( lineEnd = lines.length - 1; } - if (trim === "true") { + if (trim === 'true') { // Adjust lineStart and lineEnd to exclude the anchor comments // and the code block markers (```), if present. lineStart = lines.findIndex( - (line, index) => index > lineStart && line.includes("```") + (line, index) => index > lineStart && line.includes('```') ) + 1; lineEnd = lines.findIndex( - (line, index) => index > lineStart && line.includes("```") + (line, index) => index > lineStart && line.includes('```') ); lineEnd = lineEnd === -1 ? lines.length : lineEnd; } let newLines = lines.slice(lineStart, lineEnd); - newLines = newLines.filter((line) => !line.includes("ANCHOR")); + newLines = newLines.filter((line) => !line.includes('ANCHOR')); // Dedent the lines here: const toDedent = minWhitespace(newLines); @@ -124,7 +124,7 @@ function extractCommentBlock( newLines = dedent(newLines, toDedent); } - const linesContent = newLines.join(EOL).replace(/\n{3,}/g, "\n\n"); + const linesContent = newLines.join(EOL).replace(/\n{3,}/g, '\n\n'); return { content: linesContent.trim(), @@ -135,7 +135,7 @@ function extractCommentBlock( function minWhitespace(lines: string[]): number { return lines - .filter((line) => line.trim() !== "") // ignore blank lines + .filter((line) => line.trim() !== '') // ignore blank lines .map((line) => { const matchResult = line.match(/^(\s*)/); return matchResult ? matchResult[0].length : 0; @@ -145,7 +145,7 @@ function minWhitespace(lines: string[]): number { function dedent(lines: string[], amount: number): string[] { const regex = new RegExp(`^\\s{${amount}}`); - return lines.map((line) => line.replace(regex, "")); + return lines.map((line) => line.replace(regex, '')); } function getLineOffsets(str: string) { @@ -161,13 +161,13 @@ function extractTestCase(source: string, testCase: string) { let charStart = 0; let charEnd = 0; - let content = ""; - const chars = source.split(""); + let content = ''; + const chars = source.split(''); const linesOffset = getLineOffsets(source); // biome-ignore lint/suspicious/noExplicitAny: walk.fullAncestor(ast, (node: any, _state, ancestors) => { - if (node.name === "test") { + if (node.name === 'test') { // biome-ignore lint/suspicious/noExplicitAny: const parent = ancestors.reverse()[1] as any; const args = parent.arguments || []; @@ -175,8 +175,8 @@ function extractTestCase(source: string, testCase: string) { if (val && val === testCase) { const body = args[1]?.body; - content = chars.slice(body.start, body.end).join("").slice(1, -1); - content = prettier.format(content, { parser: "babel" }).trimEnd(); + content = chars.slice(body.start, body.end).join('').slice(1, -1); + content = prettier.format(content, { parser: 'babel' }).trimEnd(); charStart = body.start; charEnd = body.end; } @@ -200,11 +200,11 @@ export function codeImport() { const dirname = file.data.rawDocumentData?.sourceFileDir; // biome-ignore lint/suspicious/noExplicitAny: const nodes: [any, number | undefined, any][] = []; - if (dirname.startsWith("docs/fuels-wallet")) return; + if (dirname.startsWith('docs/fuels-wallet')) return; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "mdxJsxFlowElement", (node: any, idx, parent) => { - if (node.name === "CodeImport") { + visit(tree, 'mdxJsxFlowElement', (node: any, idx, parent) => { + if (node.name === 'CodeImport') { // biome-ignore lint/suspicious/noExplicitAny: nodes.push([node as any, idx, parent]); } @@ -212,37 +212,37 @@ export function codeImport() { nodes.forEach(([node]) => { const attr = node.attributes; - let content = ""; + let content = ''; if (!attr.length) { - throw new Error("CodeImport needs to have properties defined"); + throw new Error('CodeImport needs to have properties defined'); } // biome-ignore lint/suspicious/noExplicitAny: - const file = attr.find((i: any) => i.name === "file")?.value; + const file = attr.find((i: any) => i.name === 'file')?.value; // biome-ignore lint/suspicious/noExplicitAny: - let lineStart = attr.find((i: any) => i.name === "lineStart")?.value; + let lineStart = attr.find((i: any) => i.name === 'lineStart')?.value; // biome-ignore lint/suspicious/noExplicitAny: - let lineEnd = attr.find((i: any) => i.name === "lineEnd")?.value; + let lineEnd = attr.find((i: any) => i.name === 'lineEnd')?.value; // biome-ignore lint/suspicious/noExplicitAny: - const comment = attr.find((i: any) => i.name === "comment")?.value; + const comment = attr.find((i: any) => i.name === 'comment')?.value; const commentType = attr.find( // biome-ignore lint/suspicious/noExplicitAny: - (i: any) => i.name === "commentType" + (i: any) => i.name === 'commentType' )?.value; // biome-ignore lint/suspicious/noExplicitAny: - const trim = attr.find((i: any) => i.name === "trim")?.value; + const trim = attr.find((i: any) => i.name === 'trim')?.value; let linesIncluded = // biome-ignore lint/suspicious/noExplicitAny: - attr.find((i: any) => i.name === "linesIncluded")?.value || []; + attr.find((i: any) => i.name === 'linesIncluded')?.value || []; // biome-ignore lint/suspicious/noExplicitAny: - const testCase = attr.find((i: any) => i.name === "testCase")?.value; + const testCase = attr.find((i: any) => i.name === 'testCase')?.value; const fileAbsPath = path.resolve(path.join(rootDir, dirname), file); const lang = // biome-ignore lint/suspicious/noExplicitAny: - attr.find((i: any) => i.name === "lang")?.value || - path.extname(fileAbsPath).replace(".", ""); - const fileContent = fs.readFileSync(fileAbsPath, "utf8"); + attr.find((i: any) => i.name === 'lang')?.value || + path.extname(fileAbsPath).replace('.', ''); + const fileContent = fs.readFileSync(fileAbsPath, 'utf8'); const resp = Array.isArray(linesIncluded) ? 0 : linesIncluded; if (resp !== 0) { @@ -272,17 +272,17 @@ export function codeImport() { content = fileContent; } - content = content.replaceAll("{props.fuelTestnet}", FUEL_TESTNET); + content = content.replaceAll('{props.fuelTestnet}', FUEL_TESTNET); const newAttrs = [ { - name: "__content", - type: "mdxJsxAttribute", + name: '__content', + type: 'mdxJsxAttribute', value: content, }, { - name: "__language", - type: "mdxJsxAttribute", + name: '__language', + type: 'mdxJsxAttribute', value: lang, }, ]; diff --git a/src/lib/plugins/fix-indent.ts b/src/lib/plugins/fix-indent.ts index d41764984..70c0604cc 100644 --- a/src/lib/plugins/fix-indent.ts +++ b/src/lib/plugins/fix-indent.ts @@ -44,17 +44,16 @@ function setValueOnNode(node: any, cb: (value: string) => string) { export function fixIndent() { // biome-ignore lint/suspicious/noExplicitAny: return function transformer(tree: Root, _file: any) { - function normalizeIndentation(lines: string[]) { const minLeadingIndentations = lines.reduce((min, line) => { const match = line.match(/^[\s\t]*/); - if(/^\s*$/.test(line)){ + if (/^\s*$/.test(line)) { return min; } const leadingIndentations = match ? match[0].length : 0; return Math.min(min, leadingIndentations); }, Number.POSITIVE_INFINITY); - return lines.map(line => line.substring(minLeadingIndentations)); + return lines.map((line) => line.substring(minLeadingIndentations)); } // biome-ignore lint/suspicious/noExplicitAny: diff --git a/src/lib/plugins/links.ts b/src/lib/plugins/links.ts index 7daa50c99..046c4c83f 100644 --- a/src/lib/plugins/links.ts +++ b/src/lib/plugins/links.ts @@ -95,7 +95,7 @@ export function handleLinks( newUrl = newUrl.replace('{{versions.fuel-types}}', 'latest'); } - newUrl = newUrl.replace("fuels-ts/abi-typegen/", "fuels-ts/typegen/"); + newUrl = newUrl.replace('fuels-ts/abi-typegen/', 'fuels-ts/typegen/'); return newUrl; } diff --git a/src/lib/plugins/rehype-code.ts b/src/lib/plugins/rehype-code.ts index daca12ba8..3504af73c 100644 --- a/src/lib/plugins/rehype-code.ts +++ b/src/lib/plugins/rehype-code.ts @@ -1,16 +1,16 @@ -import { readFileSync } from "fs"; -import { join as pathJoin } from "path"; -import * as fs from "fs/promises"; -import { toText } from "hast-util-to-text"; -import { h } from "hastscript"; -import prettier from "prettier"; -import type { Options as RehypeCodeOptions } from "rehype-pretty-code"; -import rehypeCode from "rehype-pretty-code"; -import type { Root } from "remark-gfm"; -import { getHighlighter as shikiGetHighlighter } from "shiki"; -import type { PluggableList } from "unified"; -import { visit } from "unist-util-visit"; -import { FUEL_TESTNET } from "~/src/config/constants"; +import { readFileSync } from 'fs'; +import { join as pathJoin } from 'path'; +import * as fs from 'fs/promises'; +import { toText } from 'hast-util-to-text'; +import { h } from 'hastscript'; +import prettier from 'prettier'; +import type { Options as RehypeCodeOptions } from 'rehype-pretty-code'; +import rehypeCode from 'rehype-pretty-code'; +import type { Root } from 'remark-gfm'; +import { getHighlighter as shikiGetHighlighter } from 'shiki'; +import type { PluggableList } from 'unified'; +import { visit } from 'unist-util-visit'; +import { FUEL_TESTNET } from '~/src/config/constants'; // Shiki loads languages and themes using "fs" instead of "import", so Next.js // doesn't bundle them into production build. To work around, we manually copy @@ -19,7 +19,7 @@ import { FUEL_TESTNET } from "~/src/config/constants"; // Note that they are only referenced on server side // See: https://github.com/shikijs/shiki/issues/138 const getShikiPath = (): string => { - return pathJoin(process.cwd(), "public/shiki"); + return pathJoin(process.cwd(), 'public/shiki'); }; const touched = { current: false }; @@ -33,7 +33,7 @@ const touchShikiPath = (): void => { touched.current = true; }; -const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { +const getHighlighter: RehypeCodeOptions['getHighlighter'] = async (options) => { touchShikiPath(); const pathFolder = `${getShikiPath()}/languages`; @@ -45,66 +45,66 @@ const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { ...(options as any), langs: [ { - id: "rust", - scopeName: "source.rust", + id: 'rust', + scopeName: 'source.rust', path: `${pathFolder}/rust.tmLanguage.json`, - displayName: "Rust", - aliases: ["rs"], + displayName: 'Rust', + aliases: ['rs'], }, { - id: "javascript", - scopeName: "source.js", + id: 'javascript', + scopeName: 'source.js', path: `${pathFolder}/javascript.tmLanguage.json`, - displayName: "JavaScript", - aliases: ["js"], + displayName: 'JavaScript', + aliases: ['js'], }, { - id: "typescript", - scopeName: "source.ts", + id: 'typescript', + scopeName: 'source.ts', path: `${pathFolder}/typescript.tmLanguage.json`, - displayName: "TypeScript", - aliases: ["ts"], + displayName: 'TypeScript', + aliases: ['ts'], }, { - id: "tsx", - scopeName: "source.tsx", + id: 'tsx', + scopeName: 'source.tsx', path: `${pathFolder}/tsx.tmLanguage.json`, - displayName: "TSX", + displayName: 'TSX', }, { - id: "jsx", - scopeName: "source.js.jsx", + id: 'jsx', + scopeName: 'source.js.jsx', path: `${pathFolder}/jsx.tmLanguage.json`, - displayName: "JSX", + displayName: 'JSX', }, { - id: "json", - scopeName: "source.json", + id: 'json', + scopeName: 'source.json', path: `${pathFolder}/json.tmLanguage.json`, - displayName: "JSON", + displayName: 'JSON', }, { - id: "toml", - scopeName: "source.toml", + id: 'toml', + scopeName: 'source.toml', path: `${pathFolder}/toml.tmLanguage.json`, - displayName: "TOML", + displayName: 'TOML', }, { - id: "graphql", - scopeName: "source.graphql", + id: 'graphql', + scopeName: 'source.graphql', path: `${pathFolder}/graphql.tmLanguage.json`, - displayName: "GraphQL", - embeddedLangs: ["javascript", "typescript", "jsx", "tsx"], + displayName: 'GraphQL', + embeddedLangs: ['javascript', 'typescript', 'jsx', 'tsx'], }, { - id: "sway", - scopeName: "source.sway", + id: 'sway', + scopeName: 'source.sway', path: `${pathFolder}/sway.tmLanguage.json`, }, { - id: "html", - name: "html", - scopeName: "text.html.basic", + id: 'html', + name: 'html', + scopeName: 'text.html.basic', path: `${pathFolder}/html.tmLanguage.json`, }, ], @@ -115,15 +115,15 @@ const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { // biome-ignore lint/suspicious/noExplicitAny: function isElement(value: any): value is Element { - return value ? value.type === "element" : false; + return value ? value.type === 'element' : false; } // biome-ignore lint/suspicious/noExplicitAny: function isCodeEl(node: any, parent: any) { return ( - (node.tagName === "code" && + (node.tagName === 'code' && isElement(parent) && - parent.tagName === "pre") || - node.tagName === "inlineCode" + parent.tagName === 'pre') || + node.tagName === 'inlineCode' ); } @@ -135,24 +135,24 @@ function processCodeGroup(nodes: any[]): any[] { return ( nodes // biome-ignore lint/suspicious/noExplicitAny: - .filter((n: any) => n.tagName === "pre") + .filter((n: any) => n.tagName === 'pre') // biome-ignore lint/suspicious/noExplicitAny: .map((pre: any) => { const language = pre.children?.[0]?.properties?.className?.[0].replace( - "language-", - "" - ) ?? ""; + 'language-', + '' + ) ?? ''; const code = pre.children?.[0]?.children // biome-ignore lint/suspicious/noExplicitAny: ?.map((child: any) => child.value) - .join(""); + .join(''); - const child = h("code", { class: language }, code); + const child = h('code', { class: language }, code); return { - type: "element", - tagName: "pre", + type: 'element', + tagName: 'pre', properties: { language: language, code: code, @@ -174,10 +174,10 @@ function codeGroup2() { tree.children.forEach((node: any, index: number) => { if ( node.children && - node.children[0]?.type === "text" && - node.children[0]?.value.trim().startsWith(":::") + node.children[0]?.type === 'text' && + node.children[0]?.value.trim().startsWith(':::') ) { - if (node.children[0]?.value.trim() === "::: code-group") { + if (node.children[0]?.value.trim() === '::: code-group') { end = null; start = index; } else if (start !== null) { @@ -185,8 +185,8 @@ function codeGroup2() { const children = processCodeGroup(nodes); // biome-ignore lint/suspicious/noExplicitAny: const codeTabsElement: any = { - type: "mdxJsxFlowElement", - name: "CodeTabs", + type: 'mdxJsxFlowElement', + name: 'CodeTabs', children: children, }; tree.children.splice(start, end - start + 1, codeTabsElement); @@ -220,8 +220,8 @@ function codeGroup() { const children = processCodeGroup(codeGroupNodes); // biome-ignore lint/suspicious/noExplicitAny: const codeTabsElement: any = { - type: "mdxJsxFlowElement", - name: "CodeTabs", + type: 'mdxJsxFlowElement', + name: 'CodeTabs', children: children, }; tree.children.splice(start, end - start, codeTabsElement); @@ -236,8 +236,8 @@ function codeGroup() { function hasCodeGroup(node: any): boolean { return ( node.children && - node.children[0]?.type === "text" && - node.children[0]?.value.trim() === "::: code-group" + node.children[0]?.type === 'text' && + node.children[0]?.value.trim() === '::: code-group' ); } @@ -245,8 +245,8 @@ function hasCodeGroup(node: any): boolean { function hasEndOfCodeGroup(node: any): boolean { return ( node.children && - node.children[0].type === "text" && - node.children[0].value.trim() === ":::" + node.children[0].type === 'text' && + node.children[0].value.trim() === ':::' ); } @@ -256,28 +256,28 @@ function hasEndOfCodeGroup(node: any): boolean { function codeLanguage() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "", (node: any, _idx: any, parent: any) => { + visit(tree, '', (node: any, _idx: any, parent: any) => { if (!isCodeEl(node, parent)) return; if (!node.properties) node.properties = {}; const lang = node.properties?.className?.[0]; - if (lang?.includes("rust")) { - node.properties.className[0] = "language-rust"; + if (lang?.includes('rust')) { + node.properties.className[0] = 'language-rust'; } - if (lang?.includes("sway")) { - node.properties.className[0] = "language-sway"; + if (lang?.includes('sway')) { + node.properties.className[0] = 'language-sway'; } - if (lang?.includes("ts")) { - node.properties.className[0] = "language-typescript"; + if (lang?.includes('ts')) { + node.properties.className[0] = 'language-typescript'; } - if (lang?.includes("tsx")) { - node.properties.className[0] = "language-typescript"; + if (lang?.includes('tsx')) { + node.properties.className[0] = 'language-typescript'; } - if (lang?.includes("sh")) { - node.properties.className[0] = "language-sh"; + if (lang?.includes('sh')) { + node.properties.className[0] = 'language-sh'; } - if (lang?.includes("json")) { - node.properties.className[0] = "language-json"; + if (lang?.includes('json')) { + node.properties.className[0] = 'language-json'; } }); }; @@ -286,37 +286,37 @@ function codeLanguage() { // biome-ignore lint/suspicious/noExplicitAny: function isGraphQLCodeSamples(node: any) { return ( - node.name === "CodeExamples" && + node.name === 'CodeExamples' && // biome-ignore lint/suspicious/noExplicitAny: - node.attributes?.find((a: any) => a.name === "__ts_content") + node.attributes?.find((a: any) => a.name === '__ts_content') ); } // biome-ignore lint/suspicious/noExplicitAny: function getGraphQLCodeTabs(node: any) { const codeProps = { - className: ["language-typescript"], - "data-language": "typescript", + className: ['language-typescript'], + 'data-language': 'typescript', }; const prettierProps = { - parser: "typescript", + parser: 'typescript', semi: true, singleQuote: true, }; // biome-ignore lint/suspicious/noExplicitAny: const findProp = (name: string) => (a: any) => a.name === name; - const tsContent = node.attributes?.find(findProp("__ts_content")); - const apolloContent = node.attributes?.find(findProp("__apollo_content")); - const urqlContent = node.attributes?.find(findProp("__urql_content")); - const filepath = node.attributes?.find(findProp("__filepath")); + const tsContent = node.attributes?.find(findProp('__ts_content')); + const apolloContent = node.attributes?.find(findProp('__apollo_content')); + const urqlContent = node.attributes?.find(findProp('__urql_content')); + const filepath = node.attributes?.find(findProp('__filepath')); - const tsCodeContent = tsContent?.value ?? ""; + const tsCodeContent = tsContent?.value ?? ''; const tsCodeRaw = prettier.format(tsCodeContent, prettierProps); - const tsCode = h("code", codeProps, tsCodeRaw); + const tsCode = h('code', codeProps, tsCodeRaw); - const testnet = filepath.value.includes("/beta-4/") ? "beta-4" : FUEL_TESTNET; + const testnet = filepath.value.includes('/beta-4/') ? 'beta-4' : FUEL_TESTNET; const apolloImport = `import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; @@ -324,9 +324,9 @@ function getGraphQLCodeTabs(node: any) { uri: 'https://${testnet}.fuel.network/graphql', cache: new InMemoryCache(), });\n\n`; - const apolloContentValue = apolloImport + apolloContent?.value ?? ""; + const apolloContentValue = apolloImport + apolloContent?.value ?? ''; const apolloRaw = prettier.format(apolloContentValue, prettierProps); - const apolloCode = h("code", codeProps, apolloRaw); + const apolloCode = h('code', codeProps, apolloRaw); const urlqImport = `import { Client, cacheExchange, fetchExchange } from 'urql'; @@ -334,46 +334,46 @@ function getGraphQLCodeTabs(node: any) { url: 'https:/${testnet}.fuel.network/graphql', exchanges: [cacheExchange, fetchExchange], });\n\n`; - const urlQContentValue = urlqImport + urqlContent?.value ?? ""; + const urlQContentValue = urlqImport + urqlContent?.value ?? ''; const urlQRaw = prettier.format(urlQContentValue, prettierProps); - const urqlCode = h("code", codeProps, urlQRaw); + const urqlCode = h('code', codeProps, urlQRaw); return { tsCode, apolloCode, urqlCode }; } function codeImport() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "mdxJsxFlowElement", (node: any) => { - if (node.name !== "CodeImport" && node.name !== "CodeExamples") return; + visit(tree, 'mdxJsxFlowElement', (node: any) => { + if (node.name !== 'CodeImport' && node.name !== 'CodeExamples') return; // biome-ignore lint/suspicious/noExplicitAny: - const content = node.attributes?.find((a: any) => a.name === "__content"); + const content = node.attributes?.find((a: any) => a.name === '__content'); if (isGraphQLCodeSamples(node)) { const { tsCode, apolloCode, urqlCode } = getGraphQLCodeTabs(node); - const tsPre = h("element"); - tsPre.tagName = "pre"; + const tsPre = h('element'); + tsPre.tagName = 'pre'; tsPre.children = [tsCode]; - const apolloPre = h("element"); - apolloPre.tagName = "pre"; + const apolloPre = h('element'); + apolloPre.tagName = 'pre'; apolloPre.children = [apolloCode]; - const urlqPre = h("element"); - urlqPre.tagName = "pre"; + const urlqPre = h('element'); + urlqPre.tagName = 'pre'; urlqPre.children = [urqlCode]; node.children = [tsPre, apolloPre, urlqPre]; return; } - node.type = "element"; - node.tagName = "pre"; + node.type = 'element'; + node.tagName = 'pre'; // biome-ignore lint/suspicious/noExplicitAny: - const lang = node.attributes?.find((a: any) => a.name === "__language"); + const lang = node.attributes?.find((a: any) => a.name === '__language'); const code = h( - "code", + 'code', { class: lang?.value }, - content?.value.replace(/\r/g, "") + content?.value.replace(/\r/g, '') ); node.children = [code]; }); @@ -386,13 +386,13 @@ function codeImport() { function addLines() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "", (node: any, _idx: any, parent: any) => { + visit(tree, '', (node: any, _idx: any, parent: any) => { if (!isCodeEl(node, parent)) return; let counter = 1; // biome-ignore lint/suspicious/noExplicitAny: node.children = node.children.reduce((acc: any, node: any) => { - if (node.properties?.["data-line"] === "") { - node.properties["data-line"] = counter; + if (node.properties?.['data-line'] === '') { + node.properties['data-line'] = counter; counter = counter + 1; } return acc.concat(node); @@ -404,14 +404,14 @@ function addLines() { function addShowPlayground() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "", (node: any, _, parent: any) => { + visit(tree, '', (node: any, _, parent: any) => { // WARNING this could break if rehype-pretty-code changes its implementation // or we stop using rehype-pretty-code // rehype-pretty-code wraps our pre elements in a div which is why this is needed - if (node.tagName !== "pre" && parent?.tagName !== "div") return; + if (node.tagName !== 'pre' && parent?.tagName !== 'div') return; if (!node.properties) node.properties = {}; node.properties.showOpenPlayground = parent.attributes?.find( - (i: any) => i.name === "showOpenPlayground" + (i: any) => i.name === 'showOpenPlayground' )?.value.value; }); }; @@ -420,8 +420,8 @@ function addShowPlayground() { function addRawCode() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "", (node: any) => { - if (node.tagName !== "pre") return; + visit(tree, '', (node: any) => { + if (node.tagName !== 'pre') return; const text = toText(node); if (!node.properties) node.properties = {}; node.properties.__code = text; @@ -432,11 +432,11 @@ function addRawCode() { function addNumberOfLines() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "", (node: any, _idx: any, parent: any) => { + visit(tree, '', (node: any, _idx: any, parent: any) => { if (!node.properties) node.properties = {}; if (!isCodeEl(node, parent)) { const text = toText(node); - const lines = text.split("\n").length; + const lines = text.split('\n').length; node.properties.__lines = lines; } }); @@ -444,18 +444,18 @@ function addNumberOfLines() { } const getRehypeCodeOptions = ( - theme: "light" | "dark" + theme: 'light' | 'dark' ): Partial => { - const themeFileName: string = theme === "light" ? "github-light" : "dracula"; + const themeFileName: string = theme === 'light' ? 'github-light' : 'dracula'; return { theme: JSON.parse( - readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, "utf-8") + readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, 'utf-8') ), getHighlighter, }; }; -export const getMdxCode = (theme: "light" | "dark"): PluggableList => [ +export const getMdxCode = (theme: 'light' | 'dark'): PluggableList => [ codeImport, codeGroup, codeGroup2, From dee4d9fd061f031fc8868540c618c81afc4f0fdf Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Tue, 23 Apr 2024 17:06:10 -0500 Subject: [PATCH 3/9] wip: try to get sway open playground examples --- package.json | 5 +- pnpm-lock.yaml | 166 +++++++++---- src/components/Pre.tsx | 3 + src/lib/md-doc.ts | 239 +++++++++--------- src/lib/plugins/code-import.ts | 106 ++++---- src/lib/plugins/mdbook-example-import.ts | 93 +++---- src/lib/plugins/plugins.ts | 146 +++++------ src/lib/plugins/rehype-code.ts | 297 +++++++++++++---------- src/lib/plugins/toc.ts | 22 +- 9 files changed, 602 insertions(+), 475 deletions(-) diff --git a/package.json b/package.json index 3707c2a46..ce8f77525 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "hast-util-to-text": "^3.1.2", "hastscript": "^7.2.0", "markdownlint-cli": "^0.37.0", + "mdast-util-to-hast": "^12.3.0", "next": "^13.5.6", "next-contentlayer": "^0.3.4", "node-html-markdown": "^1.3.0", @@ -78,14 +79,14 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.46.1", "react-syntax-highlighter": "^15.5.0", - "rehype-pretty-code": "^0.10.0", + "rehype-pretty-code": "^0.13.1", "remark": "^14.0.3", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", "remark-mdx": "^2.3.0", "remark-parse": "^10.0.2", "remark-slug": "^7.0.1", - "shiki": "^0.14.4", + "shiki": "^1.3.0", "strip-indent": "^4.0.0", "swr": "^2.2.2", "toml": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56de7482c..59356bbeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ dependencies: markdownlint-cli: specifier: ^0.37.0 version: 0.37.0 + mdast-util-to-hast: + specifier: ^12.3.0 + version: 12.3.0 next: specifier: ^13.5.6 version: 13.5.6(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) @@ -152,8 +155,8 @@ dependencies: specifier: ^15.5.0 version: 15.5.0(react@18.2.0) rehype-pretty-code: - specifier: ^0.10.0 - version: 0.10.0(shiki@0.14.4) + specifier: ^0.13.1 + version: 0.13.1(shiki@1.3.0) remark: specifier: ^14.0.3 version: 14.0.3 @@ -173,8 +176,8 @@ dependencies: specifier: ^7.0.1 version: 7.0.1 shiki: - specifier: ^0.14.4 - version: 0.14.4 + specifier: ^1.3.0 + version: 1.3.0 strip-indent: specifier: ^4.0.0 version: 4.0.0 @@ -4774,6 +4777,10 @@ packages: '@scure/base': 1.1.3 dev: false + /@shikijs/core@1.3.0: + resolution: {integrity: sha512-7fedsBfuILDTBmrYZNFI8B6ATTxhQAasUHllHmjvSZPnoq4bULWoTpHwmuQvZ8Aq03/tAa2IGo6RXqWtHdWaCA==} + dev: false + /@swc/helpers@0.4.14: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} dependencies: @@ -4947,6 +4954,12 @@ packages: '@types/unist': 2.0.8 dev: false + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.0 + dev: false + /@types/lodash@4.14.202: resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} dev: false @@ -5166,10 +5179,6 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - /ansi-sequence-parser@1.1.1: - resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} - dev: false - /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -5922,6 +5931,12 @@ packages: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} dev: false + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: false + /dexie-observable@4.0.1-beta.13(dexie@3.2.4): resolution: {integrity: sha512-axmgPk7yjoPwj+0DdQIE5s1MBXi+6XcIFIjBKdQAmSGpsLQSth/LHvMOQ3q3Wj6pwIE5hqIxg2GL75sVqQbhEw==} peerDependencies: @@ -6867,15 +6882,6 @@ packages: dependencies: function-bind: 1.1.2 - /hash-obj@4.0.0: - resolution: {integrity: sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==} - engines: {node: '>=12'} - dependencies: - is-obj: 3.0.0 - sort-keys: 5.0.0 - type-fest: 1.4.0 - dev: false - /hash-wasm@4.9.0: resolution: {integrity: sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==} dev: false @@ -6893,6 +6899,17 @@ packages: dependencies: function-bind: 1.1.2 + /hast-util-from-html@2.0.1: + resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.1 + parse5: 7.1.2 + vfile: 6.0.1 + vfile-message: 4.0.2 + dev: false + /hast-util-from-parse5@7.1.2: resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} dependencies: @@ -6905,6 +6922,19 @@ packages: web-namespaces: 2.0.1 dev: false + /hast-util-from-parse5@8.0.1: + resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.0 + devlop: 1.1.0 + hastscript: 8.0.0 + property-information: 6.2.0 + vfile: 6.0.1 + vfile-location: 5.0.2 + web-namespaces: 2.0.1 + dev: false + /hast-util-from-text@2.0.1: resolution: {integrity: sha512-tE5xXi0J8Op7TKdAa1tw7idyRPSjJqG86o3+QQSQYCcBtCLgsdFjc3E6dhvz7eLh1FkZlZiSVycTv+IfXjS7KA==} dependencies: @@ -6934,6 +6964,12 @@ packages: '@types/hast': 2.3.5 dev: false + /hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + dependencies: + '@types/hast': 3.0.4 + dev: false + /hast-util-raw@7.2.3: resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} dependencies: @@ -7005,6 +7041,12 @@ packages: '@types/hast': 2.3.5 dev: false + /hast-util-to-string@3.0.0: + resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} + dependencies: + '@types/hast': 3.0.4 + dev: false + /hast-util-to-text@3.1.2: resolution: {integrity: sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==} dependencies: @@ -7038,6 +7080,16 @@ packages: space-separated-tokens: 2.0.2 dev: false + /hastscript@8.0.0: + resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + dev: false + /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -7316,11 +7368,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - /is-obj@3.0.0: - resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} - engines: {node: '>=12'} - dev: false - /is-plain-obj@3.0.0: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} engines: {node: '>=10'} @@ -8716,6 +8763,12 @@ packages: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} dev: false + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: false + /pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: @@ -9385,16 +9438,27 @@ packages: define-properties: 1.2.0 functions-have-names: 1.2.3 - /rehype-pretty-code@0.10.0(shiki@0.14.4): - resolution: {integrity: sha512-qCD071Y+vUxEy9yyrATPk2+W9q7qCbzZgtc9suZhu75bmRQvOlBhJt4d3WvqSMTamkKoFkvqtCjyAk+ggH+aXQ==} - engines: {node: '>=16'} + /rehype-parse@9.0.0: + resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==} + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.1 + unified: 11.0.4 + dev: false + + /rehype-pretty-code@0.13.1(shiki@1.3.0): + resolution: {integrity: sha512-Lw3cZohiw5J2NMMD0M11W9HlIKrZ7JjxfJmY0nxVa/HG5oMT+kkhcrUEFB5ajaEk/E9uR8+n9AmQbGJci9/TqA==} + engines: {node: '>=18'} peerDependencies: - shiki: 0.x + shiki: ^1.0.0 dependencies: - '@types/hast': 2.3.5 - hash-obj: 4.0.0 + '@types/hast': 3.0.4 + hast-util-to-string: 3.0.0 parse-numeric-range: 1.3.0 - shiki: 0.14.4 + rehype-parse: 9.0.0 + shiki: 1.3.0 + unified: 11.0.4 + unist-util-visit: 5.0.0 dev: false /rehype-stringify@9.0.4: @@ -9681,13 +9745,10 @@ packages: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} dev: true - /shiki@0.14.4: - resolution: {integrity: sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==} + /shiki@1.3.0: + resolution: {integrity: sha512-9aNdQy/etMXctnPzsje1h1XIGm9YfRcSksKOGqZWXA/qP9G18/8fpz5Bjpma8bOgz3tqIpjERAd6/lLjFyzoww==} dependencies: - ansi-sequence-parser: 1.1.1 - jsonc-parser: 3.2.0 - vscode-oniguruma: 1.7.0 - vscode-textmate: 8.0.0 + '@shikijs/core': 1.3.0 dev: false /shimmer@1.2.1: @@ -9765,13 +9826,6 @@ packages: smart-buffer: 4.2.0 dev: true - /sort-keys@5.0.0: - resolution: {integrity: sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==} - engines: {node: '>=12'} - dependencies: - is-plain-obj: 4.1.0 - dev: false - /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -10162,6 +10216,7 @@ packages: /type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} + dev: true /type-fest@3.13.1: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} @@ -10244,6 +10299,18 @@ packages: vfile: 5.3.7 dev: false + /unified@11.0.4: + resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} + dependencies: + '@types/unist': 3.0.0 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.1.0 + vfile: 6.0.1 + dev: false + /unist-util-find-after@4.0.1: resolution: {integrity: sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==} dependencies: @@ -10444,6 +10511,13 @@ packages: vfile: 5.3.7 dev: false + /vfile-location@5.0.2: + resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} + dependencies: + '@types/unist': 3.0.0 + vfile: 6.0.1 + dev: false + /vfile-message@3.1.4: resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} dependencies: @@ -10485,14 +10559,6 @@ packages: js-git: 0.7.8 dev: true - /vscode-oniguruma@1.7.0: - resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} - dev: false - - /vscode-textmate@8.0.0: - resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} - dev: false - /watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'} diff --git a/src/components/Pre.tsx b/src/components/Pre.tsx index 1a557e8f1..f334d2223 100644 --- a/src/components/Pre.tsx +++ b/src/components/Pre.tsx @@ -45,6 +45,9 @@ export function Pre({ ? codeStr.slice(0, -1) : codeStr; + console.log(`showOpenPlayground`, showOpenPlayground); + console.log(`className`, props.className); + function handleCopy() { const copiedCode = code ?? gqlCode; typeof window !== 'undefined' && diff --git a/src/lib/md-doc.ts b/src/lib/md-doc.ts index 24cb60848..2ac7ea47e 100644 --- a/src/lib/md-doc.ts +++ b/src/lib/md-doc.ts @@ -1,32 +1,33 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; -import { compile } from '@mdx-js/mdx'; -import { addRawDocumentToVFile } from 'contentlayer/core'; -import type { MdDoc } from 'contentlayer/generated'; -import { codeExamples as beta4CodeExamples } from '~/docs/beta-4/fuel-graphql-docs/src/lib/code-examples'; -import { codeImport as beta4WalletCodeImport } from '~/docs/beta-4/fuels-wallet/packages/docs/src/lib/code-import'; -import { codeExamples } from '~/docs/fuel-graphql-docs/src/lib/code-examples'; -import { codeImport as walletCodeImport } from '~/docs/fuels-wallet/packages/docs/src/lib/code-import'; -import { codeExamples as nightlyCodeExamples } from '~/docs/nightly/fuel-graphql-docs/src/lib/code-examples'; -import { codeImport as nightlyWalletCodeImport } from '~/docs/nightly/fuels-wallet/packages/docs/src/lib/code-import'; -import { codeImport } from '~/src/lib/plugins/code-import'; -import { textImport } from '~/src/lib/plugins/text-import'; - -import { DOCS_DIRECTORY } from '../config/constants'; -import type { Config, DocType, SidebarLinkItem, VersionSet } from '../types'; - -import { Docs } from './md-docs'; -import { rehypePlugins, remarkPlugins } from './md-plugins'; -import { rehypeExtractHeadings } from './plugins/toc'; -import getDocVersion from './versions'; - -const isPreview = process.env.VERCEL_ENV === 'preview'; +import { readFileSync } from "fs"; +import { join } from "path"; +import { compile } from "@mdx-js/mdx"; +import { addRawDocumentToVFile } from "contentlayer/core"; +import type { MdDoc } from "contentlayer/generated"; +import { defaultHandlers } from "mdast-util-to-hast"; +import { codeExamples as beta4CodeExamples } from "~/docs/beta-4/fuel-graphql-docs/src/lib/code-examples"; +import { codeImport as beta4WalletCodeImport } from "~/docs/beta-4/fuels-wallet/packages/docs/src/lib/code-import"; +import { codeExamples } from "~/docs/fuel-graphql-docs/src/lib/code-examples"; +import { codeImport as walletCodeImport } from "~/docs/fuels-wallet/packages/docs/src/lib/code-import"; +import { codeExamples as nightlyCodeExamples } from "~/docs/nightly/fuel-graphql-docs/src/lib/code-examples"; +import { codeImport as nightlyWalletCodeImport } from "~/docs/nightly/fuels-wallet/packages/docs/src/lib/code-import"; +import { codeImport } from "~/src/lib/plugins/code-import"; +import { textImport } from "~/src/lib/plugins/text-import"; + +import { DOCS_DIRECTORY } from "../config/constants"; +import type { Config, DocType, SidebarLinkItem, VersionSet } from "../types"; + +import { Docs } from "./md-docs"; +import { rehypePlugins, remarkPlugins } from "./md-plugins"; +import { rehypeExtractHeadings } from "./plugins/toc"; +import getDocVersion from "./versions"; + +const isPreview = process.env.VERCEL_ENV === "preview"; const branchUrl = `https://${process.env.VERCEL_BRANCH_URL}/`; -const docConfigPath = join(DOCS_DIRECTORY, '../src/config/docs.json'); -const configFile = JSON.parse(readFileSync(docConfigPath, 'utf8')); +const docConfigPath = join(DOCS_DIRECTORY, "../src/config/docs.json"); +const configFile = JSON.parse(readFileSync(docConfigPath, "utf8")); const BASE_URL = - isPreview && branchUrl ? branchUrl : 'https://docs.fuel.network/'; + isPreview && branchUrl ? branchUrl : "https://docs.fuel.network/"; export class Doc { md: MdDoc; @@ -35,15 +36,15 @@ export class Doc { constructor(slug: string[], mdDocs: MdDoc[]) { const isIntroQuickstartContract = - slug[slug.length - 1] === 'quickstart-contract'; + slug[slug.length - 1] === "quickstart-contract"; const isIntroQuickstartFrontend = - slug[slug.length - 1] === 'quickstart-frontend'; + slug[slug.length - 1] === "quickstart-frontend"; let actualSlug = slug; if (isIntroQuickstartContract) { - actualSlug = ['guides', 'quickstart', 'building-a-smart-contract']; + actualSlug = ["guides", "quickstart", "building-a-smart-contract"]; } else if (isIntroQuickstartFrontend) { - actualSlug = ['guides', 'quickstart', 'building-a-frontend']; + actualSlug = ["guides", "quickstart", "building-a-frontend"]; } const item = Docs.findDoc(actualSlug, mdDocs); @@ -52,87 +53,87 @@ export class Doc { } if (isIntroQuickstartContract) { - item.title = 'Quickstart Contract'; + item.title = "Quickstart Contract"; } else if (isIntroQuickstartFrontend) { - item.title = 'Quickstart Frontend'; + item.title = "Quickstart Frontend"; } - const config = this.#getConfig(slug.join('/')); - const splitPath = item._raw.flattenedPath.split('/'); - let fileType = '.md'; + const config = this.#getConfig(slug.join("/")); + const splitPath = item._raw.flattenedPath.split("/"); + let fileType = ".md"; if ( - config.repository.includes('docs-hub') || - config.repository.includes('graphql-docs') || - config.repository.includes('wallet') + config.repository.includes("docs-hub") || + config.repository.includes("graphql-docs") || + config.repository.includes("wallet") ) { - fileType = '.mdx'; + fileType = ".mdx"; } if ( - item._raw.sourceFileName === 'index.md' || - item._raw.sourceFileName === 'index.mdx' + item._raw.sourceFileName === "index.md" || + item._raw.sourceFileName === "index.mdx" ) { fileType = `/index${fileType}`; } - const branch = config.repository.includes('graphql-docs') - ? 'main' - : 'master'; + const branch = config.repository.includes("graphql-docs") + ? "main" + : "master"; const actualPath = `/tree/${branch}/${splitPath - .join('/') - .replace('/nightly/', '/') - .replace('docs/fuels-ts/', '') - .replace('docs/fuels-rs/', '') - .replace('docs/fuels-wallet/', '') - .replace('docs/fuel-graphql-docs/', '') - .replace('docs/sway/', '') - .replace('docs/fuel-specs/', '')}`; + .join("/") + .replace("/nightly/", "/") + .replace("docs/fuels-ts/", "") + .replace("docs/fuels-rs/", "") + .replace("docs/fuels-wallet/", "") + .replace("docs/fuel-graphql-docs/", "") + .replace("docs/sway/", "") + .replace("docs/fuel-specs/", "")}`; let pageLink = `${config.repository}${actualPath}${fileType}`; - if (pageLink.includes('breaking-change-log/breaking-changes-log')) { + if (pageLink.includes("breaking-change-log/breaking-changes-log")) { pageLink = - 'https://github.com/FuelLabs/breaking-change-log/blob/master/breaking-changes-log.md'; + "https://github.com/FuelLabs/breaking-change-log/blob/master/breaking-changes-log.md"; } - if (pageLink.includes('/master/')) { - let versionSet: VersionSet = 'default'; - if (item.slug.includes('/nightly/')) { - versionSet = 'nightly'; + if (pageLink.includes("/master/")) { + let versionSet: VersionSet = "default"; + if (item.slug.includes("/nightly/")) { + versionSet = "nightly"; } const version = getDocVersion(pageLink, versionSet); - if (version !== 'master') { + if (version !== "master") { pageLink = pageLink - .replace('/tree/master/', `/tree/${version}/`) - .replace('/blob/master/', `/blob/${version}/`); + .replace("/tree/master/", `/tree/${version}/`) + .replace("/blob/master/", `/blob/${version}/`); } } this.md = item; this.config = config; - const split = item.slug.split('/'); + const split = item.slug.split("/"); let category = item.category; - if (!category && item.slug.includes('docs/')) { + if (!category && item.slug.includes("docs/")) { const isNotDefault = - item.slug.includes('/nightly/') || item.slug.includes('/beta-4/'); + item.slug.includes("/nightly/") || item.slug.includes("/beta-4/"); const index = isNotDefault ? 3 : 2; const isIndex = split.length === index; - category = split[isIndex ? index - 1 : index].replaceAll('-', ' '); + category = split[isIndex ? index - 1 : index].replaceAll("-", " "); } - let versionSet = 'default'; - if (item.slug.includes('/nightly/')) { - versionSet = 'nightly'; - } else if (item.slug.includes('/beta-4/')) { - versionSet = 'beta-4'; + let versionSet = "default"; + if (item.slug.includes("/nightly/")) { + versionSet = "nightly"; + } else if (item.slug.includes("/beta-4/")) { + versionSet = "beta-4"; } const doc = { pageLink, _raw: item._raw, - originalSlug: slug.join('/'), + originalSlug: slug.join("/"), slug: item.slug, title: this.#getTitle(item.title), parent: item.parent ?? null, @@ -151,17 +152,17 @@ export class Doc { #getConfig(slug: string): Config { let newSlug = slug - .replace('docs/nightly/', 'docs/') - .replace('docs/beta-4/', 'docs/'); + .replace("docs/nightly/", "docs/") + .replace("docs/beta-4/", "docs/"); try { - if (newSlug.startsWith('docs/')) { - newSlug = newSlug.replace('docs/', ''); + if (newSlug.startsWith("docs/")) { + newSlug = newSlug.replace("docs/", ""); } - if (newSlug.startsWith('.')) { - newSlug = newSlug.split('/')[1].replace('.md', ''); + if (newSlug.startsWith(".")) { + newSlug = newSlug.split("/")[1].replace(".md", ""); } - if (newSlug.includes('/')) { - newSlug = newSlug.split('/')[0]; + if (newSlug.includes("/")) { + newSlug = newSlug.split("/")[0]; } return configFile[newSlug]; } catch (e) { @@ -171,24 +172,36 @@ export class Doc { #getTitle(title?: string) { if (title) return title; - return this.config.title || ''; + return this.config.title || ""; } async getCode() { const doc = this.md; - const codeLight = await this.getCodeForTheme('light', doc); - const codeDark = await this.getCodeForTheme('dark', doc); + const codeLight = await this.getCodeForTheme("light", doc); + const codeDark = await this.getCodeForTheme("dark", doc); return { light: String(codeLight), dark: String(codeDark) }; } - async getCodeForTheme(theme: 'light' | 'dark', doc: MdDoc) { + async getCodeForTheme(theme: "light" | "dark", doc: MdDoc) { const plugins = rehypePlugins(theme); const code = await compile(doc.body.raw, { - outputFormat: 'function-body', - format: doc._raw.contentType === 'markdown' ? 'md' : 'mdx', - providerImportSource: '@mdx-js/react', + outputFormat: "function-body", + format: doc._raw.contentType === "markdown" ? "md" : "mdx", + providerImportSource: "@mdx-js/react", remarkPlugins: this.#remarkPlugins(), + remarkRehypeOptions: { + handlers: { + code: (state, node) => { + const res = defaultHandlers.code(state, node); + if (node.showOpenPlayground && res.tagName === "pre") { + if (!res.properties) res.properties = {}; + res.properties.showOpenPlayground = true; + } + return res; + }, + }, + }, rehypePlugins: [ ...plugins, rehypeExtractHeadings({ @@ -203,41 +216,41 @@ export class Doc { slugForSitemap() { let slug = this.item.slug; - if (slug.endsWith('/index')) { - slug = slug.replace('/index', ''); + if (slug.endsWith("/index")) { + slug = slug.replace("/index", ""); } return this.#createUrl(slug); } sidebarLinks(slug: string) { let configSlug = this.config.slug; - if (slug.includes('/nightly/')) { + if (slug.includes("/nightly/")) { configSlug = `nightly-${this.config.slug}`; - } else if (slug.includes('/beta-4/')) { + } else if (slug.includes("/beta-4/")) { configSlug = `beta-4-${this.config.slug}`; } - let guideName = this.item.slug.split('/')[0]; + let guideName = this.item.slug.split("/")[0]; const linksPath = join( DOCS_DIRECTORY, `../src/generated/sidebar-links/${configSlug}.json` ); - const links = JSON.parse(readFileSync(linksPath, 'utf8')); + const links = JSON.parse(readFileSync(linksPath, "utf8")); if ( - (configSlug === 'guides' || - configSlug === 'nightly-guides' || - configSlug === 'beta-4-guides') && + (configSlug === "guides" || + configSlug === "nightly-guides" || + configSlug === "beta-4-guides") && guideName ) { - if (configSlug === 'nightly-guides') { + if (configSlug === "nightly-guides") { guideName = `${guideName}/nightly`; - } else if (configSlug === 'beta-4-guides') { + } else if (configSlug === "beta-4-guides") { guideName = `${guideName}/beta-4`; } const slug = this.item.slug - .replace(`${guideName}/`, '') - .replace('/index', ''); + .replace(`${guideName}/`, "") + .replace("/index", ""); - const key = slug.split('/')[0].replaceAll('-', '_'); + const key = slug.split("/")[0].replaceAll("-", "_"); const guideLinks = [links[key]]; return guideLinks as SidebarLinkItem[]; } @@ -268,7 +281,7 @@ export class Doc { const idx = result.findIndex((i) => { if (!i.slug) return false; return ( - `docs/${i.slug}`.startsWith(slug || '') || i.slug.startsWith(slug || '') + `docs/${i.slug}`.startsWith(slug || "") || i.slug.startsWith(slug || "") ); }); @@ -281,16 +294,16 @@ export class Doc { #parseSlug(slug?: string) { if (!slug) return null; - let newSlug = slug.replace('../', ''); - newSlug = newSlug.startsWith('./') ? newSlug.slice(2) : newSlug; - if (newSlug.endsWith('/index')) { - newSlug = newSlug.replace('/index', ''); + let newSlug = slug.replace("../", ""); + newSlug = newSlug.startsWith("./") ? newSlug.slice(2) : newSlug; + if (newSlug.endsWith("/index")) { + newSlug = newSlug.replace("/index", ""); } return newSlug; } #createUrl(slug: string) { - return `${BASE_URL}${slug.replace('../', '').replace('./', '')}`; + return `${BASE_URL}${slug.replace("../", "").replace("./", "")}`; } #remarkPlugins() { @@ -299,27 +312,27 @@ export class Doc { const slug = this.md.slug; - if (slug.startsWith('docs/wallet/')) { + if (slug.startsWith("docs/wallet/")) { // biome-ignore lint/suspicious/noExplicitAny: plugins = plugins.concat([[walletCodeImport, { filepath }] as any]); - } else if (slug.startsWith('docs/nightly/wallet/')) { + } else if (slug.startsWith("docs/nightly/wallet/")) { plugins = plugins.concat([ // biome-ignore lint/suspicious/noExplicitAny: [nightlyWalletCodeImport, { filepath }] as any, ]); - } else if (slug.startsWith('docs/beta-4/wallet/')) { + } else if (slug.startsWith("docs/beta-4/wallet/")) { // biome-ignore lint/suspicious/noExplicitAny: plugins = plugins.concat([[beta4WalletCodeImport, { filepath }] as any]); - } else if (slug.startsWith('docs/graphql/')) { + } else if (slug.startsWith("docs/graphql/")) { // biome-ignore lint/suspicious/noExplicitAny: plugins = plugins.concat([[codeExamples, { filepath }] as any]); - } else if (slug.startsWith('docs/nightly/graphql/')) { + } else if (slug.startsWith("docs/nightly/graphql/")) { // biome-ignore lint/suspicious/noExplicitAny: plugins = plugins.concat([[nightlyCodeExamples, { filepath }] as any]); - } else if (slug.startsWith('docs/beta-4/graphql/')) { + } else if (slug.startsWith("docs/beta-4/graphql/")) { // biome-ignore lint/suspicious/noExplicitAny: plugins = plugins.concat([[beta4CodeExamples, { filepath }] as any]); - } else if (slug.includes('guides') || slug.includes('/intro/')) { + } else if (slug.includes("guides") || slug.includes("/intro/")) { // biome-ignore lint/suspicious/noExplicitAny: plugins = plugins.concat([[codeImport, { filepath }] as any]); // biome-ignore lint/suspicious/noExplicitAny: diff --git a/src/lib/plugins/code-import.ts b/src/lib/plugins/code-import.ts index d529e1553..52d74dc63 100644 --- a/src/lib/plugins/code-import.ts +++ b/src/lib/plugins/code-import.ts @@ -1,15 +1,15 @@ -import fs from 'node:fs'; -import { EOL } from 'os'; -import path from 'path'; -import * as acorn from 'acorn'; -import * as walk from 'acorn-walk'; -import * as prettier from 'prettier'; -import type { Root } from 'remark-gfm'; -import { visit } from 'unist-util-visit'; -import { FUEL_TESTNET } from '~/src/config/constants'; - -import { getEndCommentType } from './text-import'; -import type { CommentTypes } from './text-import'; +import fs from "node:fs"; +import { EOL } from "os"; +import path from "path"; +import * as acorn from "acorn"; +import * as walk from "acorn-walk"; +import * as prettier from "prettier"; +import type { Root } from "remark-gfm"; +import { visit } from "unist-util-visit"; +import { FUEL_TESTNET } from "~/src/config/constants"; + +import { getEndCommentType } from "./text-import"; +import type { CommentTypes } from "./text-import"; interface Block { content: string; @@ -19,8 +19,8 @@ interface Block { function toAST(content: string) { return acorn.parse(content, { - ecmaVersion: 'latest', - sourceType: 'module', + ecmaVersion: "latest", + sourceType: "module", }); } @@ -36,7 +36,7 @@ function extractLines( let end; if (toLine) { end = toLine; - } else if (lines[lines.length - 1] === '') { + } else if (lines[lines.length - 1] === "") { end = lines.length - 1; } else { end = lines.length; @@ -46,13 +46,13 @@ function extractLines( return lines .slice(start - 1, end) .filter((_line, index) => newLines.includes(index)) - .join('\n'); + .join("\n"); } - return lines.slice(start - 1, end).join('\n'); + return lines.slice(start - 1, end).join("\n"); } function escapeRegExp(string: string): string { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function extractCommentBlock( @@ -66,7 +66,7 @@ function extractCommentBlock( let lineEnd = -1; const anchorStack: string[] = []; - const endCommentType = getEndCommentType(commentType) || ''; + const endCommentType = getEndCommentType(commentType) || ""; const startAnchorRegex = new RegExp( `${escapeRegExp(commentType)}\\s*ANCHOR\\s*:\\s*${escapeRegExp( @@ -84,7 +84,7 @@ function extractCommentBlock( if (lineStart === -1) { lineStart = i; } - anchorStack.push('anchor'); + anchorStack.push("anchor"); } else if (endAnchorRegex.test(lines[i])) { anchorStack.pop(); if (anchorStack.length === 0 && lineEnd === -1) { @@ -102,21 +102,21 @@ function extractCommentBlock( lineEnd = lines.length - 1; } - if (trim === 'true') { + if (trim === "true") { // Adjust lineStart and lineEnd to exclude the anchor comments // and the code block markers (```), if present. lineStart = lines.findIndex( - (line, index) => index > lineStart && line.includes('```') + (line, index) => index > lineStart && line.includes("```") ) + 1; lineEnd = lines.findIndex( - (line, index) => index > lineStart && line.includes('```') + (line, index) => index > lineStart && line.includes("```") ); lineEnd = lineEnd === -1 ? lines.length : lineEnd; } let newLines = lines.slice(lineStart, lineEnd); - newLines = newLines.filter((line) => !line.includes('ANCHOR')); + newLines = newLines.filter((line) => !line.includes("ANCHOR")); // Dedent the lines here: const toDedent = minWhitespace(newLines); @@ -124,7 +124,7 @@ function extractCommentBlock( newLines = dedent(newLines, toDedent); } - const linesContent = newLines.join(EOL).replace(/\n{3,}/g, '\n\n'); + const linesContent = newLines.join(EOL).replace(/\n{3,}/g, "\n\n"); return { content: linesContent.trim(), @@ -135,7 +135,7 @@ function extractCommentBlock( function minWhitespace(lines: string[]): number { return lines - .filter((line) => line.trim() !== '') // ignore blank lines + .filter((line) => line.trim() !== "") // ignore blank lines .map((line) => { const matchResult = line.match(/^(\s*)/); return matchResult ? matchResult[0].length : 0; @@ -145,7 +145,7 @@ function minWhitespace(lines: string[]): number { function dedent(lines: string[], amount: number): string[] { const regex = new RegExp(`^\\s{${amount}}`); - return lines.map((line) => line.replace(regex, '')); + return lines.map((line) => line.replace(regex, "")); } function getLineOffsets(str: string) { @@ -161,13 +161,13 @@ function extractTestCase(source: string, testCase: string) { let charStart = 0; let charEnd = 0; - let content = ''; - const chars = source.split(''); + let content = ""; + const chars = source.split(""); const linesOffset = getLineOffsets(source); // biome-ignore lint/suspicious/noExplicitAny: walk.fullAncestor(ast, (node: any, _state, ancestors) => { - if (node.name === 'test') { + if (node.name === "test") { // biome-ignore lint/suspicious/noExplicitAny: const parent = ancestors.reverse()[1] as any; const args = parent.arguments || []; @@ -175,8 +175,8 @@ function extractTestCase(source: string, testCase: string) { if (val && val === testCase) { const body = args[1]?.body; - content = chars.slice(body.start, body.end).join('').slice(1, -1); - content = prettier.format(content, { parser: 'babel' }).trimEnd(); + content = chars.slice(body.start, body.end).join("").slice(1, -1); + content = prettier.format(content, { parser: "babel" }).trimEnd(); charStart = body.start; charEnd = body.end; } @@ -200,11 +200,11 @@ export function codeImport() { const dirname = file.data.rawDocumentData?.sourceFileDir; // biome-ignore lint/suspicious/noExplicitAny: const nodes: [any, number | undefined, any][] = []; - if (dirname.startsWith('docs/fuels-wallet')) return; + if (dirname.startsWith("docs/fuels-wallet")) return; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, 'mdxJsxFlowElement', (node: any, idx, parent) => { - if (node.name === 'CodeImport') { + visit(tree, "mdxJsxFlowElement", (node: any, idx, parent) => { + if (node.name === "CodeImport") { // biome-ignore lint/suspicious/noExplicitAny: nodes.push([node as any, idx, parent]); } @@ -212,37 +212,37 @@ export function codeImport() { nodes.forEach(([node]) => { const attr = node.attributes; - let content = ''; + let content = ""; if (!attr.length) { - throw new Error('CodeImport needs to have properties defined'); + throw new Error("CodeImport needs to have properties defined"); } // biome-ignore lint/suspicious/noExplicitAny: - const file = attr.find((i: any) => i.name === 'file')?.value; + const file = attr.find((i: any) => i.name === "file")?.value; // biome-ignore lint/suspicious/noExplicitAny: - let lineStart = attr.find((i: any) => i.name === 'lineStart')?.value; + let lineStart = attr.find((i: any) => i.name === "lineStart")?.value; // biome-ignore lint/suspicious/noExplicitAny: - let lineEnd = attr.find((i: any) => i.name === 'lineEnd')?.value; + let lineEnd = attr.find((i: any) => i.name === "lineEnd")?.value; // biome-ignore lint/suspicious/noExplicitAny: - const comment = attr.find((i: any) => i.name === 'comment')?.value; + const comment = attr.find((i: any) => i.name === "comment")?.value; const commentType = attr.find( // biome-ignore lint/suspicious/noExplicitAny: - (i: any) => i.name === 'commentType' + (i: any) => i.name === "commentType" )?.value; // biome-ignore lint/suspicious/noExplicitAny: - const trim = attr.find((i: any) => i.name === 'trim')?.value; + const trim = attr.find((i: any) => i.name === "trim")?.value; let linesIncluded = // biome-ignore lint/suspicious/noExplicitAny: - attr.find((i: any) => i.name === 'linesIncluded')?.value || []; + attr.find((i: any) => i.name === "linesIncluded")?.value || []; // biome-ignore lint/suspicious/noExplicitAny: - const testCase = attr.find((i: any) => i.name === 'testCase')?.value; + const testCase = attr.find((i: any) => i.name === "testCase")?.value; const fileAbsPath = path.resolve(path.join(rootDir, dirname), file); const lang = // biome-ignore lint/suspicious/noExplicitAny: - attr.find((i: any) => i.name === 'lang')?.value || - path.extname(fileAbsPath).replace('.', ''); - const fileContent = fs.readFileSync(fileAbsPath, 'utf8'); + attr.find((i: any) => i.name === "lang")?.value || + path.extname(fileAbsPath).replace(".", ""); + const fileContent = fs.readFileSync(fileAbsPath, "utf8"); const resp = Array.isArray(linesIncluded) ? 0 : linesIncluded; if (resp !== 0) { @@ -272,17 +272,17 @@ export function codeImport() { content = fileContent; } - content = content.replaceAll('{props.fuelTestnet}', FUEL_TESTNET); + content = content.replaceAll("{props.fuelTestnet}", FUEL_TESTNET); const newAttrs = [ { - name: '__content', - type: 'mdxJsxAttribute', + name: "__content", + type: "mdxJsxAttribute", value: content, }, { - name: '__language', - type: 'mdxJsxAttribute', + name: "__language", + type: "mdxJsxAttribute", value: lang, }, ]; diff --git a/src/lib/plugins/mdbook-example-import.ts b/src/lib/plugins/mdbook-example-import.ts index 95d3469f5..e4214d6dc 100644 --- a/src/lib/plugins/mdbook-example-import.ts +++ b/src/lib/plugins/mdbook-example-import.ts @@ -1,8 +1,8 @@ -import fs from 'node:fs'; -import { EOL } from 'os'; -import path from 'path'; -import type { Parent } from 'unist-util-visit/lib'; -import type { VersionSet } from '~/src/types'; +import fs from "node:fs"; +import { EOL } from "os"; +import path from "path"; +import type { Parent } from "unist-util-visit/lib"; +import type { VersionSet } from "~/src/types"; function extractCommentBlock(content: string, comment: string | null) { const lines = content.split(EOL); @@ -15,7 +15,7 @@ function extractCommentBlock(content: string, comment: string | null) { let foundStart = false; for (let i = 0; i < lines.length; i++) { - const trimmed = lines[i].replace(/\s/g, ''); + const trimmed = lines[i].replace(/\s/g, ""); const start = trimmed === `//ANCHOR:${comment}` || @@ -41,18 +41,18 @@ function extractCommentBlock(content: string, comment: string | null) { let trimmedLines = newLines.filter((line) => { const thisLine = line.trimStart(); return ( - thisLine.startsWith('// ANCHOR') === false && - thisLine.startsWith('// #region') === false && - thisLine.startsWith('// #endregion') === false + thisLine.startsWith("// ANCHOR") === false && + thisLine.startsWith("// #region") === false && + thisLine.startsWith("// #endregion") === false ); }); trimmedLines = trimmedLines.map((line) => { - if (line.trimStart().startsWith('// #context')) { - return line.replace('// #context ', ''); + if (line.trimStart().startsWith("// #context")) { + return line.replace("// #context ", ""); } return line; }); - const linesContent = trimmedLines.join('\n'); + const linesContent = trimmedLines.join("\n"); return linesContent; } @@ -73,38 +73,42 @@ export function handleExampleImports( // biome-ignore lint/suspicious/noExplicitAny: parent: Parent ) { - let content = ''; - let filePath = node.value.replace(/(\.\.\/)+/g, ''); + // add showOpenPlayground property if the node is a full example + if (node.value.includes("examples") && node.value.slice(-5, -2) === ".sw") { + node.showOpenPlayground = true; + } + let content = ""; + let filePath = node.value.replace(/(\.\.\/)+/g, ""); let exampleName = null; let paths = []; - let versionSet: VersionSet = 'default'; - if (dirname.includes('docs/nightly/')) { - versionSet = 'nightly'; - } else if (dirname.includes('docs/beta-4/')) { - versionSet = 'beta-4'; + let versionSet: VersionSet = "default"; + if (dirname.includes("docs/nightly/")) { + versionSet = "nightly"; + } else if (dirname.includes("docs/beta-4/")) { + versionSet = "beta-4"; } - if (node.type === 'code') { + if (node.type === "code") { // handle mdbook docs example format - filePath = filePath.replace('{{#include ', '').replace('}}', ''); - paths = filePath.split(':'); - if (paths.length > 1) exampleName = filePath.split(':').pop(); - } else if (node.type === 'text') { + filePath = filePath.replace("{{#include ", "").replace("}}", ""); + paths = filePath.split(":"); + if (paths.length > 1) exampleName = filePath.split(":").pop(); + } else if (node.type === "text") { // handle ts-sdk docs example format - filePath = filePath.replace('<<< @/', '').replace('<<< @', ''); + filePath = filePath.replace("<<< @/", "").replace("<<< @", ""); if ( - filePath.startsWith('docs-snippets') || - filePath.startsWith('demo-fuels') + filePath.startsWith("docs-snippets") || + filePath.startsWith("demo-fuels") ) { filePath = `apps/${filePath}`; } - const pathData = filePath.split('{'); + const pathData = filePath.split("{"); filePath = pathData[0]; - paths = filePath.split('#'); - if (paths.length > 1) exampleName = filePath.split('#').pop(); + paths = filePath.split("#"); + if (paths.length > 1) exampleName = filePath.split("#").pop(); } // if there is an example at the end of the url, remove it from the filepath @@ -112,30 +116,33 @@ export function handleExampleImports( filePath = paths[0]; } - const bookPathIndex = versionSet === 'default' ? 1 : 2; - const bookPath = dirname.split('/')[bookPathIndex]; - const docsPath = versionSet === 'default' ? 'docs/' : `docs/${versionSet}/`; + const bookPathIndex = versionSet === "default" ? 1 : 2; + const bookPath = dirname.split("/")[bookPathIndex]; + const docsPath = versionSet === "default" ? "docs/" : `docs/${versionSet}/`; let fileAbsPath = path.resolve( path.join(rootDir, `${docsPath}${bookPath}/`), filePath ); - if (node.type === 'text') { - node.type = 'code'; - const paths = filePath.split('.'); + if (node.type === "text") { + node.type = "code"; + const paths = filePath.split("."); let fileType = paths[paths.length - 1]; - if (fileType === 'sw') fileType = 'rust'; + if (fileType === "sw") fileType = "rust"; node.lang = fileType; - parent.type = 'root'; + parent.type = "root"; } try { - if (fileAbsPath.includes('/fuels-ts/')) { + if (fileAbsPath.includes("/fuels-ts/")) { fileAbsPath = fileAbsPath - .replace('fuels-ts/demo', 'fuels-ts/apps/demo') - .replace('fuels-ts/create-fuels-counter-guide', 'fuels-ts/apps/create-fuels-counter-guide'); + .replace("fuels-ts/demo", "fuels-ts/apps/demo") + .replace( + "fuels-ts/create-fuels-counter-guide", + "fuels-ts/apps/create-fuels-counter-guide" + ); } - const fileContent = fs.readFileSync(fileAbsPath, 'utf8'); + const fileContent = fs.readFileSync(fileAbsPath, "utf8"); const cachedFile = getFilesOnCache(fileAbsPath); const oldContent = oldContentMap.get(node.value); @@ -147,7 +154,7 @@ export function handleExampleImports( content = extractCommentBlock(fileContent, exampleName); } catch (err) { - console.error('ERROR GETTING EXAMPLE CODE:', err); + console.error("ERROR GETTING EXAMPLE CODE:", err); } if (!content) { diff --git a/src/lib/plugins/plugins.ts b/src/lib/plugins/plugins.ts index af9e23c7f..9475f8af9 100644 --- a/src/lib/plugins/plugins.ts +++ b/src/lib/plugins/plugins.ts @@ -1,21 +1,21 @@ -import { join } from 'path'; -import type { Root } from 'remark-gfm'; -import { visit } from 'unist-util-visit'; -import type { Parent } from 'unist-util-visit/lib'; -import { versions as beta4Versions } from '~/docs/beta-4/fuels-ts/packages/versions/src'; -import { versions as defaultVersions } from '~/docs/fuels-ts/packages/versions/src'; -import { versions as nightlyVersions } from '~/docs/nightly/fuels-ts/packages/versions/src'; +import { join } from "path"; +import type { Root } from "remark-gfm"; +import { visit } from "unist-util-visit"; +import type { Parent } from "unist-util-visit/lib"; +import { versions as beta4Versions } from "~/docs/beta-4/fuels-ts/packages/versions/src"; +import { versions as defaultVersions } from "~/docs/fuels-ts/packages/versions/src"; +import { versions as nightlyVersions } from "~/docs/nightly/fuels-ts/packages/versions/src"; -import { handleForcGenDocs } from './forc-gen-docs'; -import { handleLinks } from './links'; -import { handleExampleImports } from './mdbook-example-import'; -import { handleRustVersion } from './rust-versions'; -import { handleScriptLink } from './ts-docs'; +import { handleForcGenDocs } from "./forc-gen-docs"; +import { handleLinks } from "./links"; +import { handleExampleImports } from "./mdbook-example-import"; +import { handleRustVersion } from "./rust-versions"; +import { handleScriptLink } from "./ts-docs"; import { handleDemoComp, handlePlayerComp, handleWalletImages, -} from './wallet-docs'; +} from "./wallet-docs"; type TSVersions = { FORC: string; @@ -30,53 +30,53 @@ const conditions = { // biome-ignore lint/suspicious/noExplicitAny: forcGen: (tree: any, node: any, filepath: string) => { return ( - filepath.includes('sway/docs/book/src/forc') && + filepath.includes("sway/docs/book/src/forc") && (tree.children.length === 1 || tree.children.length === 2) && - node.type === 'root' + node.type === "root" ); }, - // biome-ignore lint/suspicious/noExplicitAny: + // biome-ignore lint/suspicious/noExplicitAny exampleImport: (node: any) => { return ( - (node.type === 'code' && node.value.startsWith('{{#include')) || - (node.type === 'text' && node.value.startsWith('<<< @')) + (node.type === "code" && node.value.startsWith("{{#include")) || + (node.type === "text" && node.value.startsWith("<<< @")) ); }, // biome-ignore lint/suspicious/noExplicitAny: walletImages: (node: any, filepath: string) => { - return node.type === 'image' && filepath.includes('/fuels-wallet/'); + return node.type === "image" && filepath.includes("/fuels-wallet/"); }, // biome-ignore lint/suspicious/noExplicitAny: walletComponents: (node: any, filepath: string) => { return ( - node.name === 'Demo' || - (node.name === 'Player' && filepath.includes('/fuels-wallet/')) + node.name === "Demo" || + (node.name === "Player" && filepath.includes("/fuels-wallet/")) ); }, // biome-ignore lint/suspicious/noExplicitAny: links: (node: any) => { return ( - ((node.type === 'link' || node.type === 'definition') && - node.url !== '..') || - (node.type === 'html' && node.value.includes(' { return ( - typeof node.value === 'string' && - (node.value.includes('{{fuels}}') || - node.value.includes('{{fuelCore}}') || - node.value.includes('{{forc}}')) + typeof node.value === "string" && + (node.value.includes("{{fuels}}") || + node.value.includes("{{fuelCore}}") || + node.value.includes("{{forc}}")) ); }, // biome-ignore lint/suspicious/noExplicitAny: hasScriptLink: (node: any) => { - return node.type === 'html' && node.value.includes('const url ='); + return node.type === "html" && node.value.includes("const url ="); }, // biome-ignore lint/suspicious/noExplicitAny: rustBookVersion: (node: any) => { - return node.value?.includes('{{versions.fuels}}'); + return node.value?.includes("{{versions.fuels}}"); }, }; @@ -87,23 +87,23 @@ export function handlePlugins() { const filepath = join(rootDir, file.data.rawDocumentData?.sourceFilePath); const dirname = file.data.rawDocumentData?.sourceFileDir; let versions = defaultVersions; - if (filepath.includes('/nightly/')) { + if (filepath.includes("/nightly/")) { versions = nightlyVersions; - } else if (filepath.includes('/beta-4/')) { + } else if (filepath.includes("/beta-4/")) { versions = beta4Versions; } - if (filepath.includes('/fuel-graphql-docs/')) { + if (filepath.includes("/fuel-graphql-docs/")) { handleGraphQLDocs(tree, filepath, dirname); - } else if (filepath.includes('/sway/')) { + } else if (filepath.includes("/sway/")) { handleSwayDocs(tree, filepath, rootDir, dirname); - } else if (filepath.includes('/fuels-wallet/')) { + } else if (filepath.includes("/fuels-wallet/")) { handleWalletDocs(tree, filepath, dirname); - } else if (filepath.includes('/fuels-ts/')) { + } else if (filepath.includes("/fuels-ts/")) { handleTSDocs(tree, rootDir, dirname, versions); - } else if (filepath.includes('/fuels-rs/')) { + } else if (filepath.includes("/fuels-rs/")) { handleRustBooks(tree, rootDir, dirname); - } else if (filepath.includes('/fuel-specs/')) { + } else if (filepath.includes("/fuel-specs/")) { handleMDBooks(tree, rootDir, dirname); } }; @@ -112,11 +112,11 @@ export function handlePlugins() { function handleGraphQLDocs(tree: Root, filepath: string, dirname: string) { const nodes: NodeArray = []; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, idx, parent) => { + visit(tree, "", (node: any, idx, parent) => { if ( - (node.name === 'a' && + (node.name === "a" && node.attributes && - node.attributes[0].value.includes('/docs/')) || + node.attributes[0].value.includes("/docs/")) || conditions.links(node) ) { // biome-ignore lint/suspicious/noExplicitAny: @@ -129,10 +129,10 @@ function handleGraphQLDocs(tree: Root, filepath: string, dirname: string) { if (newUrl) node.url = newUrl; } else { let url = node.attributes[0].value; - if (filepath.includes('nightly')) { - url = url.replace('/docs/', '/docs/nightly/graphql/'); + if (filepath.includes("nightly")) { + url = url.replace("/docs/", "/docs/nightly/graphql/"); } else { - url = url.replace('/docs/', '/docs/graphql/'); + url = url.replace("/docs/", "/docs/graphql/"); } node.attributes[0].value = url; } @@ -142,7 +142,7 @@ function handleGraphQLDocs(tree: Root, filepath: string, dirname: string) { function handleWalletDocs(tree: Root, filepath: string, dirname: string) { const nodes: NodeArray = []; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, idx, parent) => { + visit(tree, "", (node: any, idx, parent) => { if ( // update the image & video paths in the wallet docs conditions.walletImages(node, filepath) || @@ -159,10 +159,10 @@ function handleWalletDocs(tree: Root, filepath: string, dirname: string) { const imagePath = handleWalletImages(node); node.url = imagePath; } else if (conditions.walletComponents(node, filepath)) { - if (node.name === 'Player') { + if (node.name === "Player") { const videoPath = handlePlayerComp(node); node.attributes[0].value = videoPath; - } else if (node.name === 'Demo') { + } else if (node.name === "Demo") { const [elements, value] = handleDemoComp(node); node.attributes[0].value.data.estree.body[0].expression.elements = elements; @@ -183,7 +183,7 @@ function handleSwayDocs( ) { const nodes: NodeArray = []; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, idx, parent) => { + visit(tree, "", (node: any, idx, parent) => { if ( // get the generated docs for forc conditions.forcGen(tree, node, filepath) @@ -197,7 +197,7 @@ function handleSwayDocs( if (newTreeChildren) node.children = newTreeChildren; }); // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, idx, parent) => { + visit(tree, "", (node: any, idx, parent) => { if ( // handle example code imports in mdbook repos and the TS SDK docs conditions.exampleImport(node) || @@ -233,7 +233,7 @@ function handleTSDocs( newTree = handleScriptLink(newTree, versions); } // biome-ignore lint/suspicious/noExplicitAny: - visit(newTree, '', (node: any, idx, parent) => { + visit(newTree, "", (node: any, idx, parent) => { if ( // handle example code imports in mdbook repos and the TS SDK docs conditions.exampleImport(node) || @@ -241,9 +241,9 @@ function handleTSDocs( conditions.links(node) || // handle TS book versions conditions.tsBookVersions(node) || - (node.type === 'code' && node.lang === 'ts:line-numbers') || - (node.type === 'code' && !node.lang) || - node.type === 'image' + (node.type === "code" && node.lang === "ts:line-numbers") || + (node.type === "code" && !node.lang) || + node.type === "image" ) { // biome-ignore lint/suspicious/noExplicitAny: nodes.push([node as any, idx ?? null, parent as Parent]); @@ -257,22 +257,22 @@ function handleTSDocs( const newUrl = handleLinks(node, dirname, idx, parent, newTree); if (newUrl) node.url = newUrl; } else if (conditions.tsBookVersions(node)) { - if (typeof node.value === 'string') { + if (typeof node.value === "string") { node.value = node.value - .replaceAll('{{fuels}}', versions.FUELS) - .replaceAll('{{fuelCore}}', versions.FUEL_CORE) - .replaceAll('{{forc}}', versions.FORC); + .replaceAll("{{fuels}}", versions.FUELS) + .replaceAll("{{fuelCore}}", versions.FUEL_CORE) + .replaceAll("{{forc}}", versions.FORC); } - } else if (node.type === 'code' && node.lang === 'ts:line-numbers') { - node.lang = 'ts'; - } else if (node.type === 'code' && !node.lang) { - node.lang = 'sh'; - } else if (node.type === 'image') { - if (node.url.includes('/public/')) { + } else if (node.type === "code" && node.lang === "ts:line-numbers") { + node.lang = "ts"; + } else if (node.type === "code" && !node.lang) { + node.lang = "sh"; + } else if (node.type === "image") { + if (node.url.includes("/public/")) { const path = node.url - .replace('../../public/', '') - .replace('./public/', '') - .replace('.png', ''); + .replace("../../public/", "") + .replace("./public/", "") + .replace(".png", ""); node.url = `/api/image/${path}`; } } @@ -282,7 +282,7 @@ function handleTSDocs( function handleRustBooks(tree: Root, rootDir: string, dirname: string) { const nodes: NodeArray = []; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, idx, parent) => { + visit(tree, "", (node: any, idx, parent) => { if ( // handle example code imports in mdbook repos and the TS SDK docs conditions.exampleImport(node) || @@ -304,16 +304,16 @@ function handleRustBooks(tree: Root, rootDir: string, dirname: string) { if (newUrl) node.url = newUrl; // TODO: remove this once the rust book is updated - if (node.url.includes('faucet-beta-3.fuel.network')) { + if (node.url.includes("faucet-beta-3.fuel.network")) { node.url = node.url.replace( - 'faucet-beta-3.fuel.network', - 'faucet-beta-5.fuel.network' + "faucet-beta-3.fuel.network", + "faucet-beta-5.fuel.network" ); if ( node.children && - node.children[0].value === 'faucet-beta-3.fuel.network' + node.children[0].value === "faucet-beta-3.fuel.network" ) { - node.children[0].value = 'faucet-beta-5.fuel.network'; + node.children[0].value = "faucet-beta-5.fuel.network"; } } } else { @@ -326,7 +326,7 @@ function handleRustBooks(tree: Root, rootDir: string, dirname: string) { function handleMDBooks(tree: Root, rootDir: string, dirname: string) { const nodes: NodeArray = []; // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, idx, parent) => { + visit(tree, "", (node: any, idx, parent) => { if ( // handle example code imports in mdbook repos and the TS SDK docs conditions.exampleImport(node) || diff --git a/src/lib/plugins/rehype-code.ts b/src/lib/plugins/rehype-code.ts index 3504af73c..308707437 100644 --- a/src/lib/plugins/rehype-code.ts +++ b/src/lib/plugins/rehype-code.ts @@ -1,16 +1,16 @@ -import { readFileSync } from 'fs'; -import { join as pathJoin } from 'path'; -import * as fs from 'fs/promises'; -import { toText } from 'hast-util-to-text'; -import { h } from 'hastscript'; -import prettier from 'prettier'; -import type { Options as RehypeCodeOptions } from 'rehype-pretty-code'; -import rehypeCode from 'rehype-pretty-code'; -import type { Root } from 'remark-gfm'; -import { getHighlighter as shikiGetHighlighter } from 'shiki'; -import type { PluggableList } from 'unified'; -import { visit } from 'unist-util-visit'; -import { FUEL_TESTNET } from '~/src/config/constants'; +import { readFileSync } from "fs"; +import { join as pathJoin } from "path"; +import * as fs from "fs/promises"; +import { toText } from "hast-util-to-text"; +import { h } from "hastscript"; +import prettier from "prettier"; +import type { Options as RehypeCodeOptions } from "rehype-pretty-code"; +import rehypeCode from "rehype-pretty-code"; +import type { Root } from "remark-gfm"; +import { getHighlighter as shikiGetHighlighter } from "shiki"; +import type { Pluggable, PluggableList } from "unified"; +import { visit } from "unist-util-visit"; +import { FUEL_TESTNET } from "~/src/config/constants"; // Shiki loads languages and themes using "fs" instead of "import", so Next.js // doesn't bundle them into production build. To work around, we manually copy @@ -19,7 +19,7 @@ import { FUEL_TESTNET } from '~/src/config/constants'; // Note that they are only referenced on server side // See: https://github.com/shikijs/shiki/issues/138 const getShikiPath = (): string => { - return pathJoin(process.cwd(), 'public/shiki'); + return pathJoin(process.cwd(), "public/shiki"); }; const touched = { current: false }; @@ -33,7 +33,7 @@ const touchShikiPath = (): void => { touched.current = true; }; -const getHighlighter: RehypeCodeOptions['getHighlighter'] = async (options) => { +const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { touchShikiPath(); const pathFolder = `${getShikiPath()}/languages`; @@ -45,66 +45,66 @@ const getHighlighter: RehypeCodeOptions['getHighlighter'] = async (options) => { ...(options as any), langs: [ { - id: 'rust', - scopeName: 'source.rust', + name: "rust", + scopeName: "source.rust", path: `${pathFolder}/rust.tmLanguage.json`, - displayName: 'Rust', - aliases: ['rs'], + displayName: "Rust", + aliases: ["rs"], }, { - id: 'javascript', - scopeName: 'source.js', + name: "javascript", + scopeName: "source.js", path: `${pathFolder}/javascript.tmLanguage.json`, - displayName: 'JavaScript', - aliases: ['js'], + displayName: "JavaScript", + aliases: ["js"], }, { - id: 'typescript', - scopeName: 'source.ts', + name: "typescript", + scopeName: "source.ts", path: `${pathFolder}/typescript.tmLanguage.json`, - displayName: 'TypeScript', - aliases: ['ts'], + displayName: "TypeScript", + aliases: ["ts"], }, { - id: 'tsx', - scopeName: 'source.tsx', + name: "tsx", + scopeName: "source.tsx", path: `${pathFolder}/tsx.tmLanguage.json`, - displayName: 'TSX', + displayName: "TSX", }, { - id: 'jsx', - scopeName: 'source.js.jsx', + name: "jsx", + scopeName: "source.js.jsx", path: `${pathFolder}/jsx.tmLanguage.json`, - displayName: 'JSX', + displayName: "JSX", }, { - id: 'json', - scopeName: 'source.json', + name: "json", + scopeName: "source.json", path: `${pathFolder}/json.tmLanguage.json`, - displayName: 'JSON', + displayName: "JSON", }, { - id: 'toml', - scopeName: 'source.toml', + name: "toml", + scopeName: "source.toml", path: `${pathFolder}/toml.tmLanguage.json`, - displayName: 'TOML', + displayName: "TOML", }, { - id: 'graphql', - scopeName: 'source.graphql', + name: "graphql", + scopeName: "source.graphql", path: `${pathFolder}/graphql.tmLanguage.json`, - displayName: 'GraphQL', - embeddedLangs: ['javascript', 'typescript', 'jsx', 'tsx'], + displayName: "GraphQL", + embeddedLangs: ["javascript", "typescript", "jsx", "tsx"], }, { - id: 'sway', - scopeName: 'source.sway', + name: "sway", + scopeName: "source.sway", path: `${pathFolder}/sway.tmLanguage.json`, }, { - id: 'html', - name: 'html', - scopeName: 'text.html.basic', + id: "html", + name: "html", + scopeName: "text.html.basic", path: `${pathFolder}/html.tmLanguage.json`, }, ], @@ -115,15 +115,15 @@ const getHighlighter: RehypeCodeOptions['getHighlighter'] = async (options) => { // biome-ignore lint/suspicious/noExplicitAny: function isElement(value: any): value is Element { - return value ? value.type === 'element' : false; + return value ? value.type === "element" : false; } // biome-ignore lint/suspicious/noExplicitAny: function isCodeEl(node: any, parent: any) { return ( - (node.tagName === 'code' && + (node.tagName === "code" && isElement(parent) && - parent.tagName === 'pre') || - node.tagName === 'inlineCode' + parent.tagName === "pre") || + node.tagName === "inlineCode" ); } @@ -135,24 +135,24 @@ function processCodeGroup(nodes: any[]): any[] { return ( nodes // biome-ignore lint/suspicious/noExplicitAny: - .filter((n: any) => n.tagName === 'pre') + .filter((n: any) => n.tagName === "pre") // biome-ignore lint/suspicious/noExplicitAny: .map((pre: any) => { const language = pre.children?.[0]?.properties?.className?.[0].replace( - 'language-', - '' - ) ?? ''; + "language-", + "" + ) ?? ""; const code = pre.children?.[0]?.children // biome-ignore lint/suspicious/noExplicitAny: ?.map((child: any) => child.value) - .join(''); + .join(""); - const child = h('code', { class: language }, code); + const child = h("code", { class: language }, code); return { - type: 'element', - tagName: 'pre', + type: "element", + tagName: "pre", properties: { language: language, code: code, @@ -174,10 +174,10 @@ function codeGroup2() { tree.children.forEach((node: any, index: number) => { if ( node.children && - node.children[0]?.type === 'text' && - node.children[0]?.value.trim().startsWith(':::') + node.children[0]?.type === "text" && + node.children[0]?.value.trim().startsWith(":::") ) { - if (node.children[0]?.value.trim() === '::: code-group') { + if (node.children[0]?.value.trim() === "::: code-group") { end = null; start = index; } else if (start !== null) { @@ -185,8 +185,8 @@ function codeGroup2() { const children = processCodeGroup(nodes); // biome-ignore lint/suspicious/noExplicitAny: const codeTabsElement: any = { - type: 'mdxJsxFlowElement', - name: 'CodeTabs', + type: "mdxJsxFlowElement", + name: "CodeTabs", children: children, }; tree.children.splice(start, end - start + 1, codeTabsElement); @@ -220,8 +220,8 @@ function codeGroup() { const children = processCodeGroup(codeGroupNodes); // biome-ignore lint/suspicious/noExplicitAny: const codeTabsElement: any = { - type: 'mdxJsxFlowElement', - name: 'CodeTabs', + type: "mdxJsxFlowElement", + name: "CodeTabs", children: children, }; tree.children.splice(start, end - start, codeTabsElement); @@ -236,8 +236,8 @@ function codeGroup() { function hasCodeGroup(node: any): boolean { return ( node.children && - node.children[0]?.type === 'text' && - node.children[0]?.value.trim() === '::: code-group' + node.children[0]?.type === "text" && + node.children[0]?.value.trim() === "::: code-group" ); } @@ -245,8 +245,8 @@ function hasCodeGroup(node: any): boolean { function hasEndOfCodeGroup(node: any): boolean { return ( node.children && - node.children[0].type === 'text' && - node.children[0].value.trim() === ':::' + node.children[0].type === "text" && + node.children[0].value.trim() === ":::" ); } @@ -256,28 +256,28 @@ function hasEndOfCodeGroup(node: any): boolean { function codeLanguage() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, _idx: any, parent: any) => { + visit(tree, "", (node: any, _idx: any, parent: any) => { if (!isCodeEl(node, parent)) return; if (!node.properties) node.properties = {}; const lang = node.properties?.className?.[0]; - if (lang?.includes('rust')) { - node.properties.className[0] = 'language-rust'; + if (lang?.includes("rust")) { + node.properties.className[0] = "language-rust"; } - if (lang?.includes('sway')) { - node.properties.className[0] = 'language-sway'; + if (lang?.includes("sway")) { + node.properties.className[0] = "language-sway"; } - if (lang?.includes('ts')) { - node.properties.className[0] = 'language-typescript'; + if (lang?.includes("ts")) { + node.properties.className[0] = "language-typescript"; } - if (lang?.includes('tsx')) { - node.properties.className[0] = 'language-typescript'; + if (lang?.includes("tsx")) { + node.properties.className[0] = "language-typescript"; } - if (lang?.includes('sh')) { - node.properties.className[0] = 'language-sh'; + if (lang?.includes("sh")) { + node.properties.className[0] = "language-sh"; } - if (lang?.includes('json')) { - node.properties.className[0] = 'language-json'; + if (lang?.includes("json")) { + node.properties.className[0] = "language-json"; } }); }; @@ -286,37 +286,37 @@ function codeLanguage() { // biome-ignore lint/suspicious/noExplicitAny: function isGraphQLCodeSamples(node: any) { return ( - node.name === 'CodeExamples' && + node.name === "CodeExamples" && // biome-ignore lint/suspicious/noExplicitAny: - node.attributes?.find((a: any) => a.name === '__ts_content') + node.attributes?.find((a: any) => a.name === "__ts_content") ); } // biome-ignore lint/suspicious/noExplicitAny: function getGraphQLCodeTabs(node: any) { const codeProps = { - className: ['language-typescript'], - 'data-language': 'typescript', + className: ["language-typescript"], + "data-language": "typescript", }; const prettierProps = { - parser: 'typescript', + parser: "typescript", semi: true, singleQuote: true, }; // biome-ignore lint/suspicious/noExplicitAny: const findProp = (name: string) => (a: any) => a.name === name; - const tsContent = node.attributes?.find(findProp('__ts_content')); - const apolloContent = node.attributes?.find(findProp('__apollo_content')); - const urqlContent = node.attributes?.find(findProp('__urql_content')); - const filepath = node.attributes?.find(findProp('__filepath')); + const tsContent = node.attributes?.find(findProp("__ts_content")); + const apolloContent = node.attributes?.find(findProp("__apollo_content")); + const urqlContent = node.attributes?.find(findProp("__urql_content")); + const filepath = node.attributes?.find(findProp("__filepath")); - const tsCodeContent = tsContent?.value ?? ''; + const tsCodeContent = tsContent?.value ?? ""; const tsCodeRaw = prettier.format(tsCodeContent, prettierProps); - const tsCode = h('code', codeProps, tsCodeRaw); + const tsCode = h("code", codeProps, tsCodeRaw); - const testnet = filepath.value.includes('/beta-4/') ? 'beta-4' : FUEL_TESTNET; + const testnet = filepath.value.includes("/beta-4/") ? "beta-4" : FUEL_TESTNET; const apolloImport = `import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; @@ -324,9 +324,9 @@ function getGraphQLCodeTabs(node: any) { uri: 'https://${testnet}.fuel.network/graphql', cache: new InMemoryCache(), });\n\n`; - const apolloContentValue = apolloImport + apolloContent?.value ?? ''; + const apolloContentValue = apolloImport + apolloContent?.value ?? ""; const apolloRaw = prettier.format(apolloContentValue, prettierProps); - const apolloCode = h('code', codeProps, apolloRaw); + const apolloCode = h("code", codeProps, apolloRaw); const urlqImport = `import { Client, cacheExchange, fetchExchange } from 'urql'; @@ -334,48 +334,49 @@ function getGraphQLCodeTabs(node: any) { url: 'https:/${testnet}.fuel.network/graphql', exchanges: [cacheExchange, fetchExchange], });\n\n`; - const urlQContentValue = urlqImport + urqlContent?.value ?? ''; + const urlQContentValue = urlqImport + urqlContent?.value ?? ""; const urlQRaw = prettier.format(urlQContentValue, prettierProps); - const urqlCode = h('code', codeProps, urlQRaw); + const urqlCode = h("code", codeProps, urlQRaw); return { tsCode, apolloCode, urqlCode }; } function codeImport() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, 'mdxJsxFlowElement', (node: any) => { - if (node.name !== 'CodeImport' && node.name !== 'CodeExamples') return; + visit(tree, "mdxJsxFlowElement", (node: any) => { + if (node.name !== "CodeImport" && node.name !== "CodeExamples") return; // biome-ignore lint/suspicious/noExplicitAny: - const content = node.attributes?.find((a: any) => a.name === '__content'); + const content = node.attributes?.find((a: any) => a.name === "__content"); if (isGraphQLCodeSamples(node)) { const { tsCode, apolloCode, urqlCode } = getGraphQLCodeTabs(node); - const tsPre = h('element'); - tsPre.tagName = 'pre'; + const tsPre = h("element"); + tsPre.tagName = "pre"; tsPre.children = [tsCode]; - const apolloPre = h('element'); - apolloPre.tagName = 'pre'; + const apolloPre = h("element"); + apolloPre.tagName = "pre"; apolloPre.children = [apolloCode]; - const urlqPre = h('element'); - urlqPre.tagName = 'pre'; + const urlqPre = h("element"); + urlqPre.tagName = "pre"; urlqPre.children = [urqlCode]; node.children = [tsPre, apolloPre, urlqPre]; return; } - node.type = 'element'; - node.tagName = 'pre'; + node.type = "element"; + node.tagName = "pre"; // biome-ignore lint/suspicious/noExplicitAny: - const lang = node.attributes?.find((a: any) => a.name === '__language'); + const lang = node.attributes?.find((a: any) => a.name === "__language"); const code = h( - 'code', + "code", { class: lang?.value }, - content?.value.replace(/\r/g, '') + content?.value.replace(/\r/g, "") ); node.children = [code]; + if (!node.properties) node.properties = {}; }); }; } @@ -386,13 +387,13 @@ function codeImport() { function addLines() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, _idx: any, parent: any) => { + visit(tree, "", (node: any, _idx: any, parent: any) => { if (!isCodeEl(node, parent)) return; let counter = 1; // biome-ignore lint/suspicious/noExplicitAny: node.children = node.children.reduce((acc: any, node: any) => { - if (node.properties?.['data-line'] === '') { - node.properties['data-line'] = counter; + if (node.properties?.["data-line"] === "") { + node.properties["data-line"] = counter; counter = counter + 1; } return acc.concat(node); @@ -404,24 +405,41 @@ function addLines() { function addShowPlayground() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, _, parent: any) => { + visit(tree, "", (node: any, _, parent: any) => { // WARNING this could break if rehype-pretty-code changes its implementation // or we stop using rehype-pretty-code // rehype-pretty-code wraps our pre elements in a div which is why this is needed - if (node.tagName !== 'pre' && parent?.tagName !== 'div') return; + console.log(`node`, node); + console.log(`parent`, parent); + if (node.tagName !== "pre" || parent?.tagName !== "div") return; if (!node.properties) node.properties = {}; node.properties.showOpenPlayground = parent.attributes?.find( - (i: any) => i.name === 'showOpenPlayground' + (i: any) => i.name === "showOpenPlayground" )?.value.value; }); }; } +function addShowPlaygroundFromCode() { + return function transformer(tree: Root) { + // biome-ignore lint/suspicious/noExplicitAny: + visit(tree, "", (node: any, _, parent: any) => { + // WARNING this could break if rehype-pretty-code changes its implementation + // or we stop using rehype-pretty-code + // rehype-pretty-code wraps our pre elements in a div which is why this is needed + if (node.tagName !== "code" || parent?.tagName !== "pre") return; + console.log(`parent`, parent); + // if (!parent.properties) parent.properties = {}; + // parent.properties.showOpenPlayground = node.properties.showOpenPlayground; + }); + }; +} + function addRawCode() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any) => { - if (node.tagName !== 'pre') return; + visit(tree, "", (node: any) => { + if (node.tagName !== "pre") return; const text = toText(node); if (!node.properties) node.properties = {}; node.properties.__code = text; @@ -432,11 +450,11 @@ function addRawCode() { function addNumberOfLines() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, '', (node: any, _idx: any, parent: any) => { + visit(tree, "", (node: any, _idx: any, parent: any) => { if (!node.properties) node.properties = {}; if (!isCodeEl(node, parent)) { const text = toText(node); - const lines = text.split('\n').length; + const lines = text.split("\n").length; node.properties.__lines = lines; } }); @@ -444,25 +462,44 @@ function addNumberOfLines() { } const getRehypeCodeOptions = ( - theme: 'light' | 'dark' + theme: "light" | "dark" ): Partial => { - const themeFileName: string = theme === 'light' ? 'github-light' : 'dracula'; - return { + const themeFileName: string = theme === "light" ? "github-light" : "dracula"; + const temp = { theme: JSON.parse( - readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, 'utf-8') + readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, "utf-8") ), getHighlighter, + // transformers: [ + // { + // name: "test", + // preprocess() { + // console.log("here"); + // }, + // code(node) { + // console.log(`code`, node); + // }, + // pre(node) { + // console.log(`node`, node); + // }, + // span(node, _1, _2) { + // console.log(`node`, node); + // }, + // }, + // ], }; + return temp; }; -export const getMdxCode = (theme: 'light' | 'dark'): PluggableList => [ +export const getMdxCode = (theme: "light" | "dark"): PluggableList => [ codeImport, codeGroup, codeGroup2, codeLanguage, - [rehypeCode, getRehypeCodeOptions(theme)], + [rehypeCode, getRehypeCodeOptions(theme)] as Pluggable, addLines, addRawCode, addNumberOfLines, addShowPlayground, + // addShowPlaygroundFromCode, ]; diff --git a/src/lib/plugins/toc.ts b/src/lib/plugins/toc.ts index 28731ec17..eeda30919 100644 --- a/src/lib/plugins/toc.ts +++ b/src/lib/plugins/toc.ts @@ -1,7 +1,7 @@ -import { headingRank } from 'hast-util-heading-rank'; -import { toString as hastToString } from 'hast-util-to-string'; -import { visit } from 'unist-util-visit'; -import type { NodeHeading } from '~/src/types'; +import { headingRank } from "hast-util-heading-rank"; +import { toString as hastToString } from "hast-util-to-string"; +import { visit } from "unist-util-visit"; +import type { NodeHeading } from "~/src/types"; export function rehypeExtractHeadings({ headings, @@ -12,25 +12,25 @@ export function rehypeExtractHeadings({ }) { // biome-ignore lint/suspicious/noExplicitAny: return () => (tree: any) => { - visit(tree, 'element', (node) => { - node.properties['data-nightly'] = slug.includes('/nightly'); + visit(tree, "element", (node) => { + node.properties["data-nightly"] = slug.includes("/nightly"); const rank = headingRank(node); if (rank) { - node.properties['data-rank'] = `h${rank}`; + node.properties["data-rank"] = `h${rank}`; } - if (rank && node?.type === 'element') { + if (rank && node?.type === "element") { const firstChild = node.children?.[0]; - if (firstChild?.tagName === 'a') { + if (firstChild?.tagName === "a") { node.children[0] = firstChild.children?.[0]; } } - if (rank === 2 && node?.type === 'element') { + if (rank === 2 && node?.type === "element") { headings.push({ title: hastToString(node), id: node.properties.id?.toString(), }); } - if (rank === 3 && node?.type === 'element') { + if (rank === 3 && node?.type === "element") { const last = headings[headings.length - 1]; if (last) { last.children = last?.children || []; From 4343c268f1566c129f021848a8aa2b416077f7b5 Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Tue, 23 Apr 2024 17:23:34 -0500 Subject: [PATCH 4/9] fix: some language highlighting after bumping deps --- src/lib/plugins/rehype-code.ts | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/lib/plugins/rehype-code.ts b/src/lib/plugins/rehype-code.ts index 308707437..0b0e62135 100644 --- a/src/lib/plugins/rehype-code.ts +++ b/src/lib/plugins/rehype-code.ts @@ -33,9 +33,17 @@ const touchShikiPath = (): void => { touched.current = true; }; +const SHIKI_LANGUAGES_PATH = `${getShikiPath()}/languages`; + +const getLanguageGrammer = (grammerFile: string) => { + const languageGrammer = JSON.parse( + readFileSync(`${SHIKI_LANGUAGES_PATH}/${grammerFile}`, "utf-8") + ); + return languageGrammer; +}; + const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { touchShikiPath(); - const pathFolder = `${getShikiPath()}/languages`; const highlighter = await shikiGetHighlighter({ // This is technically not compatible with shiki's interface but @@ -47,65 +55,65 @@ const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { { name: "rust", scopeName: "source.rust", - path: `${pathFolder}/rust.tmLanguage.json`, displayName: "Rust", aliases: ["rs"], + ...getLanguageGrammer("rust.tmLanguage.json"), }, { name: "javascript", scopeName: "source.js", - path: `${pathFolder}/javascript.tmLanguage.json`, displayName: "JavaScript", aliases: ["js"], + ...getLanguageGrammer("javascript.tmLanguage.json"), }, { name: "typescript", scopeName: "source.ts", - path: `${pathFolder}/typescript.tmLanguage.json`, displayName: "TypeScript", aliases: ["ts"], + ...getLanguageGrammer("typescript.tmLanguage.json"), }, { name: "tsx", scopeName: "source.tsx", - path: `${pathFolder}/tsx.tmLanguage.json`, displayName: "TSX", + ...getLanguageGrammer("tsx.tmLanguage.json"), }, { name: "jsx", scopeName: "source.js.jsx", - path: `${pathFolder}/jsx.tmLanguage.json`, displayName: "JSX", + ...getLanguageGrammer("jsx.tmLanguage.json"), }, { name: "json", scopeName: "source.json", - path: `${pathFolder}/json.tmLanguage.json`, displayName: "JSON", + ...getLanguageGrammer("json.tmLanguage.json"), }, { name: "toml", scopeName: "source.toml", - path: `${pathFolder}/toml.tmLanguage.json`, displayName: "TOML", + ...getLanguageGrammer("toml.tmLanguage.json"), }, { name: "graphql", scopeName: "source.graphql", - path: `${pathFolder}/graphql.tmLanguage.json`, displayName: "GraphQL", embeddedLangs: ["javascript", "typescript", "jsx", "tsx"], + ...getLanguageGrammer("graphql.tmLanguage.json"), }, { name: "sway", scopeName: "source.sway", - path: `${pathFolder}/sway.tmLanguage.json`, + ...getLanguageGrammer("sway.tmLanguage.json"), }, { id: "html", name: "html", scopeName: "text.html.basic", - path: `${pathFolder}/html.tmLanguage.json`, + ...getLanguageGrammer("html.tmLanguage.json"), }, ], }); From 9f8462d5b248ab27b3a2bb9e846b6e32bbc68b33 Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Tue, 23 Apr 2024 17:43:44 -0500 Subject: [PATCH 5/9] style: fix styling of code blocks --- src/styles/index.css | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/styles/index.css b/src/styles/index.css index 7228463de..5e0763403 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -1,14 +1,14 @@ @font-face { - font-family: 'Inter'; - src: url('/fonts/Inter-Regular.woff2') format('woff2'); + font-family: "Inter"; + src: url("/fonts/Inter-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; } @font-face { - font-family: 'Inconsolata'; - src: url('/fonts/Inconsolata-Regular.ttf') format('truetype'); + font-family: "Inconsolata"; + src: url("/fonts/Inconsolata-Regular.ttf") format("truetype"); font-weight: 400; font-style: normal; font-display: swap; @@ -16,9 +16,9 @@ :root { --colors-preBg: #161616; - --fonts-sans: 'Inter', system-ui, sans-serif; - --fonts-display: 'Inter', system-ui, sans-serif; - --fonts-mono: 'Inconsolata'; + --fonts-sans: "Inter", system-ui, sans-serif; + --fonts-display: "Inter", system-ui, sans-serif; + --fonts-mono: "Inconsolata"; } html { @@ -43,7 +43,7 @@ h4, h5, h6 { font-weight: 600; - font-feature-settings: 'pnum' on, 'onum' on, 'ss04' on; + font-feature-settings: "pnum" on, "onum" on, "ss04" on; } a, @@ -65,8 +65,8 @@ tr > td { pre span, code { - font-family: 'Source Code Pro', Consolas, 'Ubuntu Mono', Menlo, - 'DejaVu Sans Mono', monospace, monospace; + font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, + "DejaVu Sans Mono", monospace, monospace; font-size: 1em; letter-spacing: -0.01em; } @@ -102,3 +102,11 @@ textarea { select { color: inherit !important; } + +/* Needed to remove unwanted styling from figure tag introduced by rehype-pretty-code */ +figure { + margin-block-start: 0%; + margin-block-end: 0%; + margin-inline-start: 0px; + margin-inline-end: 0px; +} From 1e49bb0511cef531767a1cfaa687a9b41274e86f Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Wed, 24 Apr 2024 09:45:53 -0500 Subject: [PATCH 6/9] fix: sway highlighting --- src/lib/plugins/rehype-code.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/plugins/rehype-code.ts b/src/lib/plugins/rehype-code.ts index 0b0e62135..9715113f9 100644 --- a/src/lib/plugins/rehype-code.ts +++ b/src/lib/plugins/rehype-code.ts @@ -105,9 +105,9 @@ const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { ...getLanguageGrammer("graphql.tmLanguage.json"), }, { - name: "sway", - scopeName: "source.sway", ...getLanguageGrammer("sway.tmLanguage.json"), + name: "sway", // The language grammer has the name as "Sway" and we need it to be "sway" + scopeName: "source.sway", }, { id: "html", @@ -417,8 +417,6 @@ function addShowPlayground() { // WARNING this could break if rehype-pretty-code changes its implementation // or we stop using rehype-pretty-code // rehype-pretty-code wraps our pre elements in a div which is why this is needed - console.log(`node`, node); - console.log(`parent`, parent); if (node.tagName !== "pre" || parent?.tagName !== "div") return; if (!node.properties) node.properties = {}; node.properties.showOpenPlayground = parent.attributes?.find( From 1843417053275b4ebe88f90874a09bdb81b31e25 Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Wed, 24 Apr 2024 10:54:30 -0500 Subject: [PATCH 7/9] fix: shell styling --- src/lib/plugins/rehype-code.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/lib/plugins/rehype-code.ts b/src/lib/plugins/rehype-code.ts index 9715113f9..b6f1f1d31 100644 --- a/src/lib/plugins/rehype-code.ts +++ b/src/lib/plugins/rehype-code.ts @@ -53,56 +53,56 @@ const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { ...(options as any), langs: [ { + ...getLanguageGrammer("rust.tmLanguage.json"), name: "rust", scopeName: "source.rust", displayName: "Rust", aliases: ["rs"], - ...getLanguageGrammer("rust.tmLanguage.json"), }, { + ...getLanguageGrammer("javascript.tmLanguage.json"), name: "javascript", scopeName: "source.js", displayName: "JavaScript", aliases: ["js"], - ...getLanguageGrammer("javascript.tmLanguage.json"), }, { + ...getLanguageGrammer("typescript.tmLanguage.json"), name: "typescript", scopeName: "source.ts", displayName: "TypeScript", aliases: ["ts"], - ...getLanguageGrammer("typescript.tmLanguage.json"), }, { + ...getLanguageGrammer("tsx.tmLanguage.json"), name: "tsx", scopeName: "source.tsx", displayName: "TSX", - ...getLanguageGrammer("tsx.tmLanguage.json"), }, { + ...getLanguageGrammer("jsx.tmLanguage.json"), name: "jsx", scopeName: "source.js.jsx", displayName: "JSX", - ...getLanguageGrammer("jsx.tmLanguage.json"), }, { + ...getLanguageGrammer("json.tmLanguage.json"), name: "json", scopeName: "source.json", displayName: "JSON", - ...getLanguageGrammer("json.tmLanguage.json"), }, { + ...getLanguageGrammer("toml.tmLanguage.json"), name: "toml", scopeName: "source.toml", displayName: "TOML", - ...getLanguageGrammer("toml.tmLanguage.json"), }, { + ...getLanguageGrammer("graphql.tmLanguage.json"), name: "graphql", scopeName: "source.graphql", displayName: "GraphQL", embeddedLangs: ["javascript", "typescript", "jsx", "tsx"], - ...getLanguageGrammer("graphql.tmLanguage.json"), }, { ...getLanguageGrammer("sway.tmLanguage.json"), @@ -110,10 +110,10 @@ const getHighlighter: RehypeCodeOptions["getHighlighter"] = async (options) => { scopeName: "source.sway", }, { + ...getLanguageGrammer("html.tmLanguage.json"), id: "html", name: "html", scopeName: "text.html.basic", - ...getLanguageGrammer("html.tmLanguage.json"), }, ], }); @@ -281,8 +281,10 @@ function codeLanguage() { if (lang?.includes("tsx")) { node.properties.className[0] = "language-typescript"; } + // Since rehype-pretty-code now adds languages found in className + // and we don't want styling for sh, overwrite to make it plaintext if (lang?.includes("sh")) { - node.properties.className[0] = "language-sh"; + node.properties.className[0] = "language-plaintext"; } if (lang?.includes("json")) { node.properties.className[0] = "language-json"; @@ -471,11 +473,15 @@ const getRehypeCodeOptions = ( theme: "light" | "dark" ): Partial => { const themeFileName: string = theme === "light" ? "github-light" : "dracula"; - const temp = { + return { theme: JSON.parse( readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, "utf-8") ), getHighlighter, + // filterMetaString: (str: string) => { + // console.log(`str`, str); + // return str.replace("sh", ""); + // }, // transformers: [ // { // name: "test", @@ -494,7 +500,6 @@ const getRehypeCodeOptions = ( // }, // ], }; - return temp; }; export const getMdxCode = (theme: "light" | "dark"): PluggableList => [ From a82b024e2bc8122f1c6b651b118451be083b83a6 Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Fri, 26 Apr 2024 09:03:43 -0500 Subject: [PATCH 8/9] wip --- src/components/Pre.tsx | 21 ++++++++----- src/lib/plugins/rehype-code.ts | 55 +++++++--------------------------- 2 files changed, 24 insertions(+), 52 deletions(-) diff --git a/src/components/Pre.tsx b/src/components/Pre.tsx index f334d2223..493cfb262 100644 --- a/src/components/Pre.tsx +++ b/src/components/Pre.tsx @@ -9,6 +9,7 @@ import { Text, toast, } from '@fuel-ui/react'; +import { toText } from 'hast-util-to-text'; import { Children, type ReactNode, useState } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import darkTheme from 'react-syntax-highlighter/dist/cjs/styles/prism/night-owl'; @@ -45,9 +46,6 @@ export function Pre({ ? codeStr.slice(0, -1) : codeStr; - console.log(`showOpenPlayground`, showOpenPlayground); - console.log(`className`, props.className); - function handleCopy() { const copiedCode = code ?? gqlCode; typeof window !== 'undefined' && @@ -57,12 +55,19 @@ export function Pre({ } async function openSwayPlayground() { + console.log(`code`, code); const playgroundCode = code ?? ''; - // TODO: this will break if sway playground changes urls - window.open('https://www.sway-playground.org/', '_blank'); - // TODO: this will break if the storage key in sway playground is changed - // or the playground changes how it stores the abi - localStorage.setItem('playground_abi', playgroundCode); + // WARNING: this will break if sway playground changes urls + const playgroundWindow = window.open( + 'https://www.sway-playground.org/', + '_blank' + ); + // WARNING: this will break if the storage key in sway playground is changed + // or the playground changes how it stores the contract code + playgroundWindow?.localStorage.setItem( + 'playground_contract', + playgroundCode + ); } function toggleExpand() { diff --git a/src/lib/plugins/rehype-code.ts b/src/lib/plugins/rehype-code.ts index b6f1f1d31..4891e997f 100644 --- a/src/lib/plugins/rehype-code.ts +++ b/src/lib/plugins/rehype-code.ts @@ -282,7 +282,7 @@ function codeLanguage() { node.properties.className[0] = "language-typescript"; } // Since rehype-pretty-code now adds languages found in className - // and we don't want styling for sh, overwrite to make it plaintext + // and we don't want styling for sh we overwrite className to make it plaintext if (lang?.includes("sh")) { node.properties.className[0] = "language-plaintext"; } @@ -412,33 +412,21 @@ function addLines() { }; } +// WARNING this could break if rehype-pretty-code changes its implementation +// or we stop using rehype-pretty-code +// rehype-pretty-code wraps our pre elements in a div which is why this is needed function addShowPlayground() { return function transformer(tree: Root) { // biome-ignore lint/suspicious/noExplicitAny: visit(tree, "", (node: any, _, parent: any) => { - // WARNING this could break if rehype-pretty-code changes its implementation - // or we stop using rehype-pretty-code - // rehype-pretty-code wraps our pre elements in a div which is why this is needed - if (node.tagName !== "pre" || parent?.tagName !== "div") return; + // This is less efficient than checking if the parent tagName is "figure" + // but I think it will be more resistant to breaking changes + const showOpenPlayground = + parent?.attributes?.find((i: any) => i.name === "showOpenPlayground") + ?.value.value || parent?.properties?.showOpenPlayground; + if (node.tagName !== "pre" || !showOpenPlayground) return; if (!node.properties) node.properties = {}; - node.properties.showOpenPlayground = parent.attributes?.find( - (i: any) => i.name === "showOpenPlayground" - )?.value.value; - }); - }; -} - -function addShowPlaygroundFromCode() { - return function transformer(tree: Root) { - // biome-ignore lint/suspicious/noExplicitAny: - visit(tree, "", (node: any, _, parent: any) => { - // WARNING this could break if rehype-pretty-code changes its implementation - // or we stop using rehype-pretty-code - // rehype-pretty-code wraps our pre elements in a div which is why this is needed - if (node.tagName !== "code" || parent?.tagName !== "pre") return; - console.log(`parent`, parent); - // if (!parent.properties) parent.properties = {}; - // parent.properties.showOpenPlayground = node.properties.showOpenPlayground; + node.properties.showOpenPlayground = showOpenPlayground; }); }; } @@ -478,27 +466,6 @@ const getRehypeCodeOptions = ( readFileSync(`${getShikiPath()}/themes/${themeFileName}.json`, "utf-8") ), getHighlighter, - // filterMetaString: (str: string) => { - // console.log(`str`, str); - // return str.replace("sh", ""); - // }, - // transformers: [ - // { - // name: "test", - // preprocess() { - // console.log("here"); - // }, - // code(node) { - // console.log(`code`, node); - // }, - // pre(node) { - // console.log(`node`, node); - // }, - // span(node, _1, _2) { - // console.log(`node`, node); - // }, - // }, - // ], }; }; From cbfa168012ca6c1c2af85aecb3281de46a429d0f Mon Sep 17 00:00:00 2001 From: Matt Auer Date: Tue, 7 May 2024 13:39:10 -0500 Subject: [PATCH 9/9] feat: open sway playground --- src/components/Pre.tsx | 18 ++++++++---------- src/config.ts | 4 ++++ 2 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 src/config.ts diff --git a/src/components/Pre.tsx b/src/components/Pre.tsx index 493cfb262..ccf8cee2d 100644 --- a/src/components/Pre.tsx +++ b/src/components/Pre.tsx @@ -15,6 +15,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import darkTheme from 'react-syntax-highlighter/dist/cjs/styles/prism/night-owl'; import lightTheme from 'react-syntax-highlighter/dist/cjs/styles/prism/one-light'; import useTheme from '~/src/hooks/useTheme'; +import { SWAY_PLAYGROUND_URL } from '../config'; type PreProps = { children: ReactNode; @@ -55,18 +56,15 @@ export function Pre({ } async function openSwayPlayground() { - console.log(`code`, code); const playgroundCode = code ?? ''; // WARNING: this will break if sway playground changes urls - const playgroundWindow = window.open( - 'https://www.sway-playground.org/', - '_blank' - ); - // WARNING: this will break if the storage key in sway playground is changed - // or the playground changes how it stores the contract code - playgroundWindow?.localStorage.setItem( - 'playground_contract', - playgroundCode + const playgroundWindow = window.open(SWAY_PLAYGROUND_URL, '_blank'); + // wait a bit for the page to load + // 30 ms was the minimum that worked, but I made it 45 for wiggle room + await new Promise((resolve) => setTimeout(resolve, 45)); + playgroundWindow?.postMessage( + JSON.stringify({ swayCode: playgroundCode }), + SWAY_PLAYGROUND_URL ); } diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 000000000..4db168121 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,4 @@ +export const SWAY_PLAYGROUND_URL = + process.env.NODE_ENV === "development" + ? "http://localhost:3001" + : "https://www.sway-playground.org/";