diff --git a/packages/lib-cheatsheet-local/src/app.tsx b/packages/lib-cheatsheet-local/src/app.tsx index 3e05ee56ef..cecfae869c 100644 --- a/packages/lib-cheatsheet-local/src/app.tsx +++ b/packages/lib-cheatsheet-local/src/app.tsx @@ -16,5 +16,8 @@ declare global { } export function App() { + if (document.cheatsheetInfo == null) { + throw new Error("Cheatsheet info is not available on the document."); + } return ; } diff --git a/packages/lib-node-common/package.json b/packages/lib-node-common/package.json index ed84af279b..51c9ee9267 100644 --- a/packages/lib-node-common/package.json +++ b/packages/lib-node-common/package.json @@ -21,6 +21,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", + "@types/node": "^24.13.4", "js-yaml": "^5.4.1" } } diff --git a/packages/lib-node-common/src/Cheatsheet.ts b/packages/lib-node-common/src/Cheatsheet.ts index 9b7295b71d..a28d67c8d4 100644 --- a/packages/lib-node-common/src/Cheatsheet.ts +++ b/packages/lib-node-common/src/Cheatsheet.ts @@ -1,6 +1,5 @@ import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; -import { parse } from "node-html-parser"; import type { CheatsheetInfo, IDE } from "@cursorless/lib-common"; import { getCheatsheetInfo, @@ -9,6 +8,7 @@ import { showWarning, } from "@cursorless/lib-common"; import type { FileSystemTalonSpokenForms } from "./FileSystemTalonSpokenForms"; +import { injectCheatsheetInfo } from "./injectCheatsheetInfo"; interface CheatSheetCommandArgV0 { version: 0; @@ -47,12 +47,11 @@ export async function showCheatsheet( ); const cheatsheetPath = path.join(ide.assetsRoot, "cheatsheet.html"); const cheatsheetContent = await readFile(cheatsheetPath, "utf8"); - const root = parse(cheatsheetContent); - root.getElementById("cheatsheet-data")!.textContent = - `document.cheatsheetInfo = ${JSON.stringify(cheatsheetInfo)};`; - - await writeFile(arg.outputPath, root.toString()); + await writeFile( + arg.outputPath, + injectCheatsheetInfo(cheatsheetContent, cheatsheetInfo), + ); } async function getCheatsheetInfoForCommand( diff --git a/packages/lib-node-common/src/injectCheatsheetInfo.ts b/packages/lib-node-common/src/injectCheatsheetInfo.ts new file mode 100644 index 0000000000..1f853dfa35 --- /dev/null +++ b/packages/lib-node-common/src/injectCheatsheetInfo.ts @@ -0,0 +1,21 @@ +import { parse, TextNode } from "node-html-parser"; +import type { CheatsheetInfo } from "@cursorless/lib-common"; + +export function injectCheatsheetInfo( + cheatsheetContent: string, + cheatsheetInfo: CheatsheetInfo, +): string { + const root = parse(cheatsheetContent); + const serializedCheatsheetInfo = JSON.stringify(cheatsheetInfo).replaceAll( + "<", + String.raw`\u003c`, + ); + + root + .getElementById("cheatsheet-data")! + .set_content( + new TextNode(`document.cheatsheetInfo = ${serializedCheatsheetInfo};`), + ); + + return root.toString(); +} diff --git a/packages/lib-node-common/test/Cheatsheet.test.ts b/packages/lib-node-common/test/Cheatsheet.test.ts new file mode 100644 index 0000000000..ced3beaf07 --- /dev/null +++ b/packages/lib-node-common/test/Cheatsheet.test.ts @@ -0,0 +1,41 @@ +import assert from "node:assert/strict"; +import { parse } from "node-html-parser"; +import type { CheatsheetInfo } from "@cursorless/lib-common"; +import { injectCheatsheetInfo } from "../src/injectCheatsheetInfo"; + +suite("Cheatsheet", () => { + test("injects cheatsheet info as executable script text", () => { + const cheatsheetInfo: CheatsheetInfo = { + sections: [ + { + id: "actions", + name: "Actions", + items: [ + { + id: "test-action", + type: "action", + variations: [ + { + spokenForm: "take ", + description: "Select A & B ", + }, + ], + }, + ], + }, + ], + }; + + const output = injectCheatsheetInfo( + '', + cheatsheetInfo, + ); + const script = parse(output).getElementById("cheatsheet-data")!; + + assert.equal( + script.rawText, + `document.cheatsheetInfo = ${JSON.stringify(cheatsheetInfo).replaceAll("<", String.raw`\u003c`)};`, + ); + assert.doesNotMatch(script.rawText, /&(?:amp|lt|quot);/u); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 514d1da9e3..b0fb903c63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -515,6 +515,9 @@ importers: '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 + '@types/node': + specifier: ^24.13.4 + version: 24.13.4 js-yaml: specifier: ^5.4.1 version: 5.4.1