From d68b037536867c56e782c9b38cede51306d3d303 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Mon, 10 Nov 2025 15:55:02 +0800 Subject: [PATCH 01/20] feat(docs): add JavaScript language documentation --- docs/docs/languages/javascript.mdx | 84 +++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/docs/docs/languages/javascript.mdx b/docs/docs/languages/javascript.mdx index 7f6c373412..75daf34a06 100644 --- a/docs/docs/languages/javascript.mdx +++ b/docs/docs/languages/javascript.mdx @@ -1,3 +1,85 @@ # JavaScript -TODO... +[JavaScript](https://developer.mozilla.org/docs/Web/JavaScript) is a versatile, interpreted programming language that powers the dynamic behavior on most websites. + +LiveCodes runs JavaScript natively in the browser.Since JavaScript runs in the browser, it has a host environment with access to the DOM and other Web APIs, but it does not have Node.js features such as the file system or process information. + +## Demo + +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +export const params = { + template:"Javascript", + console:"open" +}; + + + +## Usage + +LiveCodes executes JavaScript directly in the browser. + +You can use it to test and demonstrate JavaScript code that interacts with the DOM, fetch APIs, or manipulate HTML elements dynamically. + +### Loading Modules + +Most of the core functionality in JavaScript is available directly in the browser without any additional installation. + +#### Standard Library + +JavaScript provides a standard set of built-in objects and functions which are available directly in the browser. + +#### External Packages +To use additional functionality not included in the core language, you can import external packages (libraries) from CDN links. + +```js +import _ from 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.min.js'; + +const arr = [1, 2, 3, 4, 5]; +console.log(_.shuffle(arr)); // shuffle the array +``` + +If you use JavaScript syntax extensions like JSX or JavaScript libraries/frameworks, you can select them directly in the programming language selection. + +## Language Info + +### Name + +`Javascript` + +### Aliases + +`js`,`node`,`ecmascript` + +### Extensions + +`js` + +### Editor + +`script` + +### Compiler + +JavaScript runs natively in the browser — no compilation is required. + +### Version + +Depends on the browser's JavaScript engine and its supported ECMAScript features. + +## Code Formatting + +Using [Prettier](https://prettier.io/) for code Formatting.You can format your code when saving the project, but you need to manually enable “Format on Save” in the settings. + +## Live Reload + +JavaScript code automatically reloads and executes when edited, allowing for real-time feedback. + +## Starter Template + +https://livecodes.io/?template=Javascript + +## Links + +- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) +- [Web API](https://developer.mozilla.org/en-US/docs/Web/API) \ No newline at end of file From 66d2012a2adf5b44867f8e399128f4e5d358a1b1 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Wed, 12 Nov 2025 16:10:04 +0800 Subject: [PATCH 02/20] feat(livecodes/reveal.js): integrate livecodes with reveal.js plugin --- package-lock.json | 27 ++++++++++++++++++++++ package.json | 29 +++++++++++++++++++---- scripts/build.js | 13 +++++++++++ src/sdk/package.sdk.json | 1 + src/sdk/reveal.ts | 50 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/sdk/reveal.ts diff --git a/package-lock.json b/package-lock.json index 74f1dc6bee..db779165b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "monaco-editor": "0.48.0", "patch-package": "7.0.2", "prismjs": "1.29.0", + "reveal.js": "^5.2.1", "split.js": "1.6.5", "yjs": "13.5.40" }, @@ -47,6 +48,7 @@ "@types/prettier": "2.1.6", "@types/prismjs": "1.16.3", "@types/react": "19.0.10", + "@types/reveal.js": "^5.2.1", "@typescript-eslint/eslint-plugin": "8.24.1", "@typescript-eslint/parser": "8.24.1", "@typescript/vfs": "1.5.3", @@ -4148,6 +4150,12 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/reveal.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/reveal.js/-/reveal.js-5.2.1.tgz", + "integrity": "sha512-egr+amW5iilXo94kEGyJv24bJozsu/XAOHnhMHLnaJkHVxoui2gsWqzByaltA5zfXDTH2F4WyWnAkhHRcpytIQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -16811,6 +16819,14 @@ "node": ">=0.10.0" } }, + "node_modules/reveal.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-5.2.1.tgz", + "integrity": "sha512-r7//6mIM5p34hFiDMvYfXgyjXqGRta+/psd9YtytsgRlrpRzFv4RbH76TXd2qD+7ZPZEbpBDhdRhJaFgfQ7zNQ==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -22857,6 +22873,12 @@ "csstype": "^3.0.2" } }, + "@types/reveal.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/reveal.js/-/reveal.js-5.2.1.tgz", + "integrity": "sha512-egr+amW5iilXo94kEGyJv24bJozsu/XAOHnhMHLnaJkHVxoui2gsWqzByaltA5zfXDTH2F4WyWnAkhHRcpytIQ==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -32334,6 +32356,11 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "reveal.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-5.2.1.tgz", + "integrity": "sha512-r7//6mIM5p34hFiDMvYfXgyjXqGRta+/psd9YtytsgRlrpRzFv4RbH76TXd2qD+7ZPZEbpBDhdRhJaFgfQ7zNQ==" + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", diff --git a/package.json b/package.json index ee375d445b..1af8e90bf9 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "monaco-editor": "0.48.0", "patch-package": "7.0.2", "prismjs": "1.29.0", + "reveal.js": "^5.2.1", "split.js": "1.6.5", "yjs": "13.5.40" }, @@ -110,6 +111,7 @@ "@types/prettier": "2.1.6", "@types/prismjs": "1.16.3", "@types/react": "19.0.10", + "@types/reveal.js": "^5.2.1", "@typescript-eslint/eslint-plugin": "8.24.1", "@typescript-eslint/parser": "8.24.1", "@typescript/vfs": "1.5.3", @@ -158,15 +160,32 @@ "singleQuote": true, "trailingComma": "all", "printWidth": 100, - "plugins": ["prettier-plugin-organize-imports"] + "plugins": [ + "prettier-plugin-organize-imports" + ] }, "jest": { "preset": "ts-jest", "testEnvironment": "jsdom", - "setupFiles": ["/.jest/setup.ts"], - "testPathIgnorePatterns": ["/node_modules/", "/build/", "/src/modules/"], - "collectCoverageFrom": ["src/**/*.ts", "!**/build/**", "!**/vendor/**", "!src/modules/**"], - "coverageReporters": ["json", "html", "lcov"], + "setupFiles": [ + "/.jest/setup.ts" + ], + "testPathIgnorePatterns": [ + "/node_modules/", + "/build/", + "/src/modules/" + ], + "collectCoverageFrom": [ + "src/**/*.ts", + "!**/build/**", + "!**/vendor/**", + "!src/modules/**" + ], + "coverageReporters": [ + "json", + "html", + "lcov" + ], "resolveJsonModule": true } } diff --git a/scripts/build.js b/scripts/build.js index 60b1976642..70907dcdc9 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -145,6 +145,19 @@ const sdkBuild = async () => { '@vue/runtime-core': 'vue', }, }), + esbuild.build({ + ...sdkOptions, + entryPoints: [sdkSrcDir + 'reveal.ts'], + outdir: undefined, + outfile: path.resolve(outDir, sdkOutDir, 'reveal.umd.js'), + format: 'iife', + }), + esbuild.build({ + ...sdkOptions, + entryPoints: [sdkSrcDir + 'reveal.ts'], + outdir: undefined, + outfile: path.resolve(outDir, sdkOutDir, 'reveal.js'), + }), ]); }; diff --git a/src/sdk/package.sdk.json b/src/sdk/package.sdk.json index 54d5841863..03fc1ade1d 100644 --- a/src/sdk/package.sdk.json +++ b/src/sdk/package.sdk.json @@ -14,6 +14,7 @@ "module": "./livecodes.js", "browser": "./livecodes.js", "types": "./livecodes.d.ts", + "jsdelivr": "./dist/reveal.umd.js", "exports": { ".": { "import": "./livecodes.js", diff --git a/src/sdk/reveal.ts b/src/sdk/reveal.ts new file mode 100644 index 0000000000..c5e7c80393 --- /dev/null +++ b/src/sdk/reveal.ts @@ -0,0 +1,50 @@ +import type Reveal from 'reveal.js'; +import type { EmbedOptions, Playground } from './models'; +import { createPlayground } from './index'; + +interface LiveOptions extends EmbedOptions { + sdkReady?: (sdk: Playground) => void; +} + +interface GlobalLiveCodesOptions extends Reveal.Options { + livecodes?: LiveOptions; + customStyle?: Partial; +} + +const initIframeStyle = (iframe: HTMLIFrameElement, styles: Partial) => { + for (const [key, value] of Object.entries(styles)) { + // @ts-ignore + iframe.style[key as any] = value; + } +}; + +export const LiveCodes = { + id: "LiveCodes", + init(deck: InstanceType) { + const ContainerList = document.querySelectorAll("[data-livecodes]"); + if (ContainerList.length <= 1) { + return; + } + const containers = Array.from(ContainerList); + const config = deck.getConfig() as GlobalLiveCodesOptions; + const globalOptions = config.livecodes || {}; + const sdkReadyfn = config.livecodes?.sdkReady; + const customStyle = config.customStyle || {}; + const promises = containers.map((container) => { + const localOptions = container.dataset.config || "{}"; + const finalOptions = { config: { ...globalOptions, ...JSON.parse(localOptions) } } + return createPlayground(container, finalOptions); + }); + Promise.all(promises).then((sdk) => { + const iframes = document.querySelectorAll('.livecodes'); + iframes.forEach(iframe => initIframeStyle(iframe, { maxWidth: "100%", maxHeight: "100%", ...customStyle })); + if (typeof sdkReadyfn === 'function') { + sdk.forEach((itemSdk) => sdkReadyfn(itemSdk)); + } + }); + }, +}; + +if (typeof window !== 'undefined') { + (window as any).LiveCodes = LiveCodes; +} \ No newline at end of file From c87b8da1a93fe0330fe293d6513cada8033a5d40 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 04:18:40 +0800 Subject: [PATCH 03/20] feat(reveal): implement LiveCodes SDK core logic --- src/sdk/reveal.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sdk/reveal.ts b/src/sdk/reveal.ts index c5e7c80393..7aca829438 100644 --- a/src/sdk/reveal.ts +++ b/src/sdk/reveal.ts @@ -2,15 +2,20 @@ import type Reveal from 'reveal.js'; import type { EmbedOptions, Playground } from './models'; import { createPlayground } from './index'; -interface LiveOptions extends EmbedOptions { +export interface LiveOptions extends EmbedOptions { sdkReady?: (sdk: Playground) => void; } -interface GlobalLiveCodesOptions extends Reveal.Options { +export interface GlobalLiveCodesOptions extends Reveal.Options { livecodes?: LiveOptions; customStyle?: Partial; } +export interface LiveCodesInstance { + id: string; + init(deck: InstanceType): void; +} + const initIframeStyle = (iframe: HTMLIFrameElement, styles: Partial) => { for (const [key, value] of Object.entries(styles)) { // @ts-ignore @@ -22,7 +27,7 @@ export const LiveCodes = { id: "LiveCodes", init(deck: InstanceType) { const ContainerList = document.querySelectorAll("[data-livecodes]"); - if (ContainerList.length <= 1) { + if (ContainerList.length < 1) { return; } const containers = Array.from(ContainerList); From 2b8cde3158711e973fdf03258b2b918e7377ee50 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 04:18:53 +0800 Subject: [PATCH 04/20] docs(reveal): add SDK usage documentation --- docs/docs/sdk/reveal.mdx | 161 +++++++++++++++++++++++++++++++++++++++ docs/sidebars.ts | 2 +- 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 docs/docs/sdk/reveal.mdx diff --git a/docs/docs/sdk/reveal.mdx b/docs/docs/sdk/reveal.mdx new file mode 100644 index 0000000000..410725a424 --- /dev/null +++ b/docs/docs/sdk/reveal.mdx @@ -0,0 +1,161 @@ +# Reveal SDK + +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +The Reveal.js SDK is a lightweight plugin that integrates the JavaScript SDK into Reveal.js, allowing you to embed interactive playgrounds directly within your slides. + +It has a very simple [implementation](https://github.com/live-codes/livecodes/blob/develop/src/sdk/reveal.ts) which you can easily modify in case you need. + +## Installation + +Please refer to the [SDK installation](./index.mdx#installation) section. + +## Usage + +To provide a mounting container, you just need to add the attribute data-livecodes to your container element, and then import the LiveCodes script. + +```html +
+``` + +There are two ways you can import the LiveCodes script + +1. Using a ` + + + +``` + +2. Using an ES module `import` statement +```js +import Reveal from "reveal.js"; +import { LiveCodes } from "reveal.js-plugin-livecodes"; + +Reveal.initialize({ + plugins: [LiveCodes], +}); +``` + +### TypeScript Support + +TypeScript types are exported from this module. + +`LiveOptions` – Configuration options for an individual playground, including the optional sdkReady callback. + +`GlobalLiveCodesOptions` – Global configuration for LiveCodes when used with Reveal.js, including optional customStyle. + +`LiveCodesInstance` – The LiveCodes object itself, containing an id and an init method for initializing with a Reveal.js deck. + +This allows TypeScript projects to import these types directly for full type safety and autocompletion: + +```ts +import type { LiveOptions, GlobalLiveCodesOptions, LiveCodesInstance } from '.livecodes/reveal.js'; +const myPlaygroundOptions: LiveOptions = { + sdkReady: (sdk) => { + console.log("Playground initialized:", sdk); + }, + theme: "dark", +}; + +const deckOptions: GlobalLiveCodesOptions = { + livecodes: myPlaygroundOptions, + customStyle: { + border: "1px solid #ddd", + borderRadius: "8px", + }, + controls: true, + slideNumber: true, +}; +``` + +### Config + +All [embed options](js-ts.mdx#embed-options) are available as config object with the corresponding key-values. If you don’t specify it, the default configuration object will be used. + +Example: + +```js +import { LiveCodes } from "./node_modules/mylpk/reveal.js"; + +const deck = new Reveal({ + plugins: [LiveCodes, Markdown], + livecodes: { + markup: { + language: "markdown", + content: "# hello World!", + }, + sdkReady: (item) => { + console.log(item); + }, + } +}); + +deck.initialize(); +``` + +You can provide an optional `sdkReady?: (sdk: Playground) => void` property, which will be called as a callback function after the initialization is complete. A callback function, that is provided with an instance of the JavaScript SDK representing the current playground. This allows making use of full capability of the SDK by calling SDK methods. + +In addition, you can also pass a CSS object `customStyle` to specify the CSS properties of the editor. + +Example: + +```js +import { LiveCodes } from "./node_modules/mylpk/reveal.js"; + +const deck = new Reveal({ + plugins: [LiveCodes, Markdown], + livecodes: { + markup: { + language: "markdown", + content: "# hello World!", + }, + }, + customStyle: { border: "5px solid pink", borderRadius: "8px" }, +}); + +deck.initialize(); +``` + +The CSS properties in customStyle will be applied directly to the embedded iframe. For example, with the settings above, you will see rounded corners and a pink border on the iframe. + +:::info +The customStyle object changes the CSS properties of the embedded iframe itself, not the outer container’s CSS properties. +::: + +If you want to apply specific configurations to a particular LiveCode editor, you can write a stringified object that conforms to [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#:~:text=The%20JSON.parse()%20static,object%20before%20it%20is%20returned.) into the data-config attribute of its container element. + +Example: +```html +
+ Slide 1 +
+
+``` + +## Demo + +export const sdkDemo = { + html:'\n\n
\n
\n
Slide 1\n
\n
\n
Slide 2\n
\n
\n
\n
\n\n\n' +}; + + + +## Related + +- [SDK Installation](./index.mdx#installation) +- [JS/TS SDK](./js-ts.mdx) +- [Vue SDK](./vue.mdx) +- [React SDK](./react.mdx) +- [Using SDK in Svelte](./svelte.mdx) +- [Using SDK in Solid](./solid.mdx) +- [Embedded Playgrounds](../features/embeds.mdx) diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 904912f997..7aad6ac021 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -85,7 +85,7 @@ const sidebars: SidebarsConfig = { type: 'doc', id: 'sdk/index', }, - items: ['sdk/js-ts', 'sdk/react', 'sdk/vue', 'sdk/svelte', 'sdk/solid', 'sdk/headless'], + items: ['sdk/js-ts', 'sdk/react', 'sdk/vue', 'sdk/svelte', 'sdk/solid', 'sdk/headless', "sdk/reveal"], }, { type: 'category', From 0c629a6622f153d299bc1ce845c8b49033a7f55e Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 04:19:02 +0800 Subject: [PATCH 05/20] test(reveal): add tests for LiveCodes SDK and iframe styling --- src/sdk/__tests__/reveal.test.ts | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/sdk/__tests__/reveal.test.ts diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts new file mode 100644 index 0000000000..9b73399f01 --- /dev/null +++ b/src/sdk/__tests__/reveal.test.ts @@ -0,0 +1,46 @@ +import { LiveCodes } from '../reveal'; +import { createPlayground } from "../index"; + +jest.mock('../index', () => ({ + createPlayground: jest.fn().mockImplementation((container) => { + const iframe = document.createElement('iframe'); + iframe.className = 'livecodes'; + container.appendChild(iframe); + return Promise.resolve({ playground: 'mocked' }); + }) +})); + +beforeEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); +}) + +test("should do nothing when no [data-livecodes] element exists", async () => { + const mockDeck = { getConfig: jest.fn().mockReturnValue({ livecodes: {} }) } as any; + LiveCodes.init(mockDeck); + expect(createPlayground).not.toHaveBeenCalled(); +}) + +test("should initializes playground and triggers sdkReady when a livecodes container with config exists", async () => { + const sdkReady = jest.fn(); + const container = document.createElement('div'); + container.dataset.livecodes = ''; + container.dataset.config = '{"script":{"language":"javascript","content":"console.log(123)"}}' + document.body.appendChild(container); + const mockDeck = { + getConfig: jest.fn().mockReturnValue({ livecodes: { markup: { language: "markdown", content: "# Hello world" }, sdkReady }, customStyle: { backgroundColor: "rgba(255,255,255,0.1)" } }) + } as any; + LiveCodes.init(mockDeck); + await new Promise(process.nextTick); + expect(createPlayground).toHaveBeenCalledTimes(1); + expect(sdkReady).toHaveBeenCalledTimes(1); + const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; + const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + expect(calledWith.config.script.language).toBe('javascript'); + expect(calledWith.config.script.content).toBe('console.log(123)'); + expect(calledWith.config.markup.language).toBe('markdown'); + expect(calledWith.config.markup.content).toBe('# Hello world'); + expect(iframe?.style.maxWidth).toBe("100%"); + expect(iframe?.style.maxHeight).toBe("100%"); + expect(iframe?.style.backgroundColor).toBe("rgba(255, 255, 255, 0.1)"); +}) From 7b322be5c53bd9c6fd58d2d49221376c1bb0eade Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 04:33:49 +0800 Subject: [PATCH 06/20] Keep the state in sync with upstream. --- docs/docs/languages/javascript.mdx | 84 +----------------------------- 1 file changed, 1 insertion(+), 83 deletions(-) diff --git a/docs/docs/languages/javascript.mdx b/docs/docs/languages/javascript.mdx index 75daf34a06..9f6f5ead44 100644 --- a/docs/docs/languages/javascript.mdx +++ b/docs/docs/languages/javascript.mdx @@ -1,85 +1,3 @@ # JavaScript -[JavaScript](https://developer.mozilla.org/docs/Web/JavaScript) is a versatile, interpreted programming language that powers the dynamic behavior on most websites. - -LiveCodes runs JavaScript natively in the browser.Since JavaScript runs in the browser, it has a host environment with access to the DOM and other Web APIs, but it does not have Node.js features such as the file system or process information. - -## Demo - -import LiveCodes from '../../src/components/LiveCodes.tsx'; - -export const params = { - template:"Javascript", - console:"open" -}; - - - -## Usage - -LiveCodes executes JavaScript directly in the browser. - -You can use it to test and demonstrate JavaScript code that interacts with the DOM, fetch APIs, or manipulate HTML elements dynamically. - -### Loading Modules - -Most of the core functionality in JavaScript is available directly in the browser without any additional installation. - -#### Standard Library - -JavaScript provides a standard set of built-in objects and functions which are available directly in the browser. - -#### External Packages -To use additional functionality not included in the core language, you can import external packages (libraries) from CDN links. - -```js -import _ from 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.min.js'; - -const arr = [1, 2, 3, 4, 5]; -console.log(_.shuffle(arr)); // shuffle the array -``` - -If you use JavaScript syntax extensions like JSX or JavaScript libraries/frameworks, you can select them directly in the programming language selection. - -## Language Info - -### Name - -`Javascript` - -### Aliases - -`js`,`node`,`ecmascript` - -### Extensions - -`js` - -### Editor - -`script` - -### Compiler - -JavaScript runs natively in the browser — no compilation is required. - -### Version - -Depends on the browser's JavaScript engine and its supported ECMAScript features. - -## Code Formatting - -Using [Prettier](https://prettier.io/) for code Formatting.You can format your code when saving the project, but you need to manually enable “Format on Save” in the settings. - -## Live Reload - -JavaScript code automatically reloads and executes when edited, allowing for real-time feedback. - -## Starter Template - -https://livecodes.io/?template=Javascript - -## Links - -- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) -- [Web API](https://developer.mozilla.org/en-US/docs/Web/API) \ No newline at end of file +TODO... \ No newline at end of file From 8be3f2bfc9d037c57ec8908c194435329b44cde5 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 05:03:10 +0800 Subject: [PATCH 07/20] style: format code according to Prettier --- src/sdk/__tests__/reveal.test.ts | 81 +++++++++++++++++--------------- src/sdk/reveal.ts | 75 +++++++++++++++-------------- 2 files changed, 82 insertions(+), 74 deletions(-) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index 9b73399f01..caec82b590 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -1,46 +1,51 @@ +import { createPlayground } from '../index'; import { LiveCodes } from '../reveal'; -import { createPlayground } from "../index"; jest.mock('../index', () => ({ - createPlayground: jest.fn().mockImplementation((container) => { - const iframe = document.createElement('iframe'); - iframe.className = 'livecodes'; - container.appendChild(iframe); - return Promise.resolve({ playground: 'mocked' }); - }) + createPlayground: jest.fn().mockImplementation((container) => { + const iframe = document.createElement('iframe'); + iframe.className = 'livecodes'; + container.appendChild(iframe); + return Promise.resolve({ playground: 'mocked' }); + }), })); beforeEach(() => { - document.body.innerHTML = ''; - jest.clearAllMocks(); -}) + document.body.innerHTML = ''; + jest.clearAllMocks(); +}); -test("should do nothing when no [data-livecodes] element exists", async () => { - const mockDeck = { getConfig: jest.fn().mockReturnValue({ livecodes: {} }) } as any; - LiveCodes.init(mockDeck); - expect(createPlayground).not.toHaveBeenCalled(); -}) +test('should do nothing when no [data-livecodes] element exists', async () => { + const mockDeck = { getConfig: jest.fn().mockReturnValue({ livecodes: {} }) } as any; + LiveCodes.init(mockDeck); + expect(createPlayground).not.toHaveBeenCalled(); +}); -test("should initializes playground and triggers sdkReady when a livecodes container with config exists", async () => { - const sdkReady = jest.fn(); - const container = document.createElement('div'); - container.dataset.livecodes = ''; - container.dataset.config = '{"script":{"language":"javascript","content":"console.log(123)"}}' - document.body.appendChild(container); - const mockDeck = { - getConfig: jest.fn().mockReturnValue({ livecodes: { markup: { language: "markdown", content: "# Hello world" }, sdkReady }, customStyle: { backgroundColor: "rgba(255,255,255,0.1)" } }) - } as any; - LiveCodes.init(mockDeck); - await new Promise(process.nextTick); - expect(createPlayground).toHaveBeenCalledTimes(1); - expect(sdkReady).toHaveBeenCalledTimes(1); - const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; - expect(calledWith.config.script.language).toBe('javascript'); - expect(calledWith.config.script.content).toBe('console.log(123)'); - expect(calledWith.config.markup.language).toBe('markdown'); - expect(calledWith.config.markup.content).toBe('# Hello world'); - expect(iframe?.style.maxWidth).toBe("100%"); - expect(iframe?.style.maxHeight).toBe("100%"); - expect(iframe?.style.backgroundColor).toBe("rgba(255, 255, 255, 0.1)"); -}) +test('should initializes playground and triggers sdkReady when a livecodes container with config exists', async () => { + const sdkReady = jest.fn(); + const container = document.createElement('div'); + container.dataset.livecodes = ''; + container.dataset.config = '{"script":{"language":"javascript","content":"console.log(123)"}}'; + document.body.appendChild(container); + const mockDeck = { + getConfig: jest + .fn() + .mockReturnValue({ + livecodes: { markup: { language: 'markdown', content: '# Hello world' }, sdkReady }, + customStyle: { backgroundColor: 'rgba(255,255,255,0.1)' }, + }), + } as any; + LiveCodes.init(mockDeck); + await new Promise(process.nextTick); + expect(createPlayground).toHaveBeenCalledTimes(1); + expect(sdkReady).toHaveBeenCalledTimes(1); + const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; + const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + expect(calledWith.config.script.language).toBe('javascript'); + expect(calledWith.config.script.content).toBe('console.log(123)'); + expect(calledWith.config.markup.language).toBe('markdown'); + expect(calledWith.config.markup.content).toBe('# Hello world'); + expect(iframe?.style.maxWidth).toBe('100%'); + expect(iframe?.style.maxHeight).toBe('100%'); + expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); +}); diff --git a/src/sdk/reveal.ts b/src/sdk/reveal.ts index 7aca829438..8a7e0e2546 100644 --- a/src/sdk/reveal.ts +++ b/src/sdk/reveal.ts @@ -1,55 +1,58 @@ import type Reveal from 'reveal.js'; -import type { EmbedOptions, Playground } from './models'; import { createPlayground } from './index'; +// eslint-disable-next-line import/order +import type { EmbedOptions, Playground } from './models'; export interface LiveOptions extends EmbedOptions { - sdkReady?: (sdk: Playground) => void; + sdkReady?: (sdk: Playground) => void; } export interface GlobalLiveCodesOptions extends Reveal.Options { - livecodes?: LiveOptions; - customStyle?: Partial; + livecodes?: LiveOptions; + customStyle?: Partial; } export interface LiveCodesInstance { - id: string; - init(deck: InstanceType): void; + id: string; + init(deck: InstanceType): void; } const initIframeStyle = (iframe: HTMLIFrameElement, styles: Partial) => { - for (const [key, value] of Object.entries(styles)) { - // @ts-ignore - iframe.style[key as any] = value; - } + for (const [key, value] of Object.entries(styles)) { + // @ts-ignore + iframe.style[key as any] = value; + } }; export const LiveCodes = { - id: "LiveCodes", - init(deck: InstanceType) { - const ContainerList = document.querySelectorAll("[data-livecodes]"); - if (ContainerList.length < 1) { - return; - } - const containers = Array.from(ContainerList); - const config = deck.getConfig() as GlobalLiveCodesOptions; - const globalOptions = config.livecodes || {}; - const sdkReadyfn = config.livecodes?.sdkReady; - const customStyle = config.customStyle || {}; - const promises = containers.map((container) => { - const localOptions = container.dataset.config || "{}"; - const finalOptions = { config: { ...globalOptions, ...JSON.parse(localOptions) } } - return createPlayground(container, finalOptions); - }); - Promise.all(promises).then((sdk) => { - const iframes = document.querySelectorAll('.livecodes'); - iframes.forEach(iframe => initIframeStyle(iframe, { maxWidth: "100%", maxHeight: "100%", ...customStyle })); - if (typeof sdkReadyfn === 'function') { - sdk.forEach((itemSdk) => sdkReadyfn(itemSdk)); - } - }); - }, + id: 'LiveCodes', + init(deck: InstanceType) { + const ContainerList = document.querySelectorAll('[data-livecodes]'); + if (ContainerList.length < 1) { + return; + } + const containers = Array.from(ContainerList); + const config = deck.getConfig() as GlobalLiveCodesOptions; + const globalOptions = config.livecodes || {}; + const sdkReadyfn = config.livecodes?.sdkReady; + const customStyle = config.customStyle || {}; + const promises = containers.map((container) => { + const localOptions = container.dataset.config || '{}'; + const finalOptions = { config: { ...globalOptions, ...JSON.parse(localOptions) } }; + return createPlayground(container, finalOptions); + }); + Promise.all(promises).then((sdk) => { + const iframes = document.querySelectorAll('.livecodes'); + iframes.forEach((iframe) => + initIframeStyle(iframe, { maxWidth: '100%', maxHeight: '100%', ...customStyle }), + ); + if (typeof sdkReadyfn === 'function') { + sdk.forEach((itemSdk) => sdkReadyfn(itemSdk)); + } + }); + }, }; if (typeof window !== 'undefined') { - (window as any).LiveCodes = LiveCodes; -} \ No newline at end of file + (window as any).LiveCodes = LiveCodes; +} From 68105936065d4e60b526c3dbe788d1986007a8c4 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 14:42:47 +0800 Subject: [PATCH 08/20] fix: correct package.json jsdelivr entry and update related documentation --- docs/docs/sdk/reveal.mdx | 6 +++--- src/sdk/package.sdk.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/sdk/reveal.mdx b/docs/docs/sdk/reveal.mdx index 410725a424..0915613700 100644 --- a/docs/docs/sdk/reveal.mdx +++ b/docs/docs/sdk/reveal.mdx @@ -56,7 +56,7 @@ TypeScript types are exported from this module. This allows TypeScript projects to import these types directly for full type safety and autocompletion: ```ts -import type { LiveOptions, GlobalLiveCodesOptions, LiveCodesInstance } from '.livecodes/reveal.js'; +import type { LiveOptions, GlobalLiveCodesOptions, LiveCodesInstance } from './node_modules/livecodes/reveal.js'; const myPlaygroundOptions: LiveOptions = { sdkReady: (sdk) => { console.log("Playground initialized:", sdk); @@ -82,7 +82,7 @@ All [embed options](js-ts.mdx#embed-options) are available as config object with Example: ```js -import { LiveCodes } from "./node_modules/mylpk/reveal.js"; +import { LiveCodes } from "./node_modules/livecodes/reveal.js"; const deck = new Reveal({ plugins: [LiveCodes, Markdown], @@ -107,7 +107,7 @@ In addition, you can also pass a CSS object `customStyle` to specify the CSS pro Example: ```js -import { LiveCodes } from "./node_modules/mylpk/reveal.js"; +import { LiveCodes } from "./node_modules/livecodes"/reveal.js"; const deck = new Reveal({ plugins: [LiveCodes, Markdown], diff --git a/src/sdk/package.sdk.json b/src/sdk/package.sdk.json index 03fc1ade1d..76564c3390 100644 --- a/src/sdk/package.sdk.json +++ b/src/sdk/package.sdk.json @@ -14,7 +14,7 @@ "module": "./livecodes.js", "browser": "./livecodes.js", "types": "./livecodes.d.ts", - "jsdelivr": "./dist/reveal.umd.js", + "jsdelivr": "./livecodes.js", "exports": { ".": { "import": "./livecodes.js", From a6041b98f08c7fe3788e765446b3112433755a11 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 15:00:19 +0800 Subject: [PATCH 09/20] fix: correct package.json jsdelivr entry and update related documentation --- docs/docs/sdk/reveal.mdx | 8 ++++---- src/sdk/package.sdk.json | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/docs/sdk/reveal.mdx b/docs/docs/sdk/reveal.mdx index 0915613700..3fa3bc3f19 100644 --- a/docs/docs/sdk/reveal.mdx +++ b/docs/docs/sdk/reveal.mdx @@ -36,7 +36,7 @@ There are two ways you can import the LiveCodes script 2. Using an ES module `import` statement ```js import Reveal from "reveal.js"; -import { LiveCodes } from "reveal.js-plugin-livecodes"; +import { LiveCodes } from 'livecodes'; Reveal.initialize({ plugins: [LiveCodes], @@ -56,7 +56,7 @@ TypeScript types are exported from this module. This allows TypeScript projects to import these types directly for full type safety and autocompletion: ```ts -import type { LiveOptions, GlobalLiveCodesOptions, LiveCodesInstance } from './node_modules/livecodes/reveal.js'; +import type { LiveOptions, GlobalLiveCodesOptions, LiveCodesInstance } from 'livecodes'; const myPlaygroundOptions: LiveOptions = { sdkReady: (sdk) => { console.log("Playground initialized:", sdk); @@ -82,7 +82,7 @@ All [embed options](js-ts.mdx#embed-options) are available as config object with Example: ```js -import { LiveCodes } from "./node_modules/livecodes/reveal.js"; +import { LiveCodes } from 'livecodes'; const deck = new Reveal({ plugins: [LiveCodes, Markdown], @@ -107,7 +107,7 @@ In addition, you can also pass a CSS object `customStyle` to specify the CSS pro Example: ```js -import { LiveCodes } from "./node_modules/livecodes"/reveal.js"; +import { LiveCodes } from 'livecodes'; const deck = new Reveal({ plugins: [LiveCodes, Markdown], diff --git a/src/sdk/package.sdk.json b/src/sdk/package.sdk.json index 76564c3390..7240310ea8 100644 --- a/src/sdk/package.sdk.json +++ b/src/sdk/package.sdk.json @@ -27,6 +27,9 @@ "./vue": { "import": "./vue.js" }, + "./reveal": { + "import": "./dist/reveal.umd.js" + }, "./package.json": "./package.json" } } From 97a65044e1bc2e822ad67c48bbda27e7ac3aa33d Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 15:01:06 +0800 Subject: [PATCH 10/20] fix: correct package.json jsdelivr entry and update related documentation --- src/sdk/package.sdk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdk/package.sdk.json b/src/sdk/package.sdk.json index 7240310ea8..4a5882acd1 100644 --- a/src/sdk/package.sdk.json +++ b/src/sdk/package.sdk.json @@ -28,7 +28,7 @@ "import": "./vue.js" }, "./reveal": { - "import": "./dist/reveal.umd.js" + "import": "./reveal.js" }, "./package.json": "./package.json" } From 73155ad3e7157d4a1bfc9215f5ae45804c549182 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 15:12:19 +0800 Subject: [PATCH 11/20] sync with the upstream --- docs/docs/languages/javascript.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/languages/javascript.mdx b/docs/docs/languages/javascript.mdx index 9f6f5ead44..7f6c373412 100644 --- a/docs/docs/languages/javascript.mdx +++ b/docs/docs/languages/javascript.mdx @@ -1,3 +1,3 @@ # JavaScript -TODO... \ No newline at end of file +TODO... From 46168ce8b8f842ecb46270be70a0738d5170bfce Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 17:49:59 +0800 Subject: [PATCH 12/20] fix(build): fix bug in build object parameters --- src/sdk/reveal.ts | 48 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/sdk/reveal.ts b/src/sdk/reveal.ts index 8a7e0e2546..3aa3d3c92a 100644 --- a/src/sdk/reveal.ts +++ b/src/sdk/reveal.ts @@ -1,7 +1,7 @@ import type Reveal from 'reveal.js'; import { createPlayground } from './index'; // eslint-disable-next-line import/order -import type { EmbedOptions, Playground } from './models'; +import type { Config, EmbedOptions, Playground } from './models'; export interface LiveOptions extends EmbedOptions { sdkReady?: (sdk: Playground) => void; @@ -24,6 +24,21 @@ const initIframeStyle = (iframe: HTMLIFrameElement, styles: Partial void, + config?: string | Partial, +) { + if (typeof config === 'string') { + await fetch(config) + .then((res) => res.json()) + .then((json) => sdkItem.setConfig(json)); + } + if (typeof sdkReadyfn === 'function') { + sdkReadyfn(sdkItem); + } +}; + export const LiveCodes = { id: 'LiveCodes', init(deck: InstanceType) { @@ -38,7 +53,32 @@ export const LiveCodes = { const customStyle = config.customStyle || {}; const promises = containers.map((container) => { const localOptions = container.dataset.config || '{}'; - const finalOptions = { config: { ...globalOptions, ...JSON.parse(localOptions) } }; + const parsedLocalOptions = JSON.parse(localOptions); + let finalOptions: EmbedOptions; + if (typeof globalOptions.config === 'string') { + finalOptions = { + ...globalOptions, + ...parsedLocalOptions, + config: { + ...parsedLocalOptions.config, + }, + }; + } else { + finalOptions = { + ...globalOptions, + ...parsedLocalOptions, + config: { + ...globalOptions.config, + ...parsedLocalOptions.config, + }, + }; + } + if ( + typeof finalOptions.config === 'object' && + Object.keys(finalOptions.config).length === 0 + ) { + delete finalOptions.config; + } return createPlayground(container, finalOptions); }); Promise.all(promises).then((sdk) => { @@ -46,8 +86,8 @@ export const LiveCodes = { iframes.forEach((iframe) => initIframeStyle(iframe, { maxWidth: '100%', maxHeight: '100%', ...customStyle }), ); - if (typeof sdkReadyfn === 'function') { - sdk.forEach((itemSdk) => sdkReadyfn(itemSdk)); + for (const sdkItem of sdk) { + applyConfigAndSdkFn(sdkItem, sdkReadyfn, globalOptions.config); } }); }, From 5b6aef3d89753c859ca9960db61b768fd24f9980 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 17:50:09 +0800 Subject: [PATCH 13/20] fix(docs): fix documentation errors --- docs/docs/sdk/reveal.mdx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/docs/sdk/reveal.mdx b/docs/docs/sdk/reveal.mdx index 3fa3bc3f19..7c73658064 100644 --- a/docs/docs/sdk/reveal.mdx +++ b/docs/docs/sdk/reveal.mdx @@ -87,10 +87,12 @@ import { LiveCodes } from 'livecodes'; const deck = new Reveal({ plugins: [LiveCodes, Markdown], livecodes: { - markup: { - language: "markdown", - content: "# hello World!", - }, + config:{ + markup: { + language: "markdown", + content: "# hello World!", + }, + } sdkReady: (item) => { console.log(item); }, @@ -112,10 +114,12 @@ import { LiveCodes } from 'livecodes'; const deck = new Reveal({ plugins: [LiveCodes, Markdown], livecodes: { - markup: { - language: "markdown", - content: "# hello World!", - }, + config:{ + markup: { + language: "markdown", + content: "# hello World!", + }, + } }, customStyle: { border: "5px solid pink", borderRadius: "8px" }, }); @@ -137,7 +141,7 @@ Example: Slide 1
``` @@ -145,7 +149,7 @@ Example: ## Demo export const sdkDemo = { - html:'\n\n
\n
\n
Slide 1\n
\n
\n
Slide 2\n
\n
\n
\n
\n\n\n' + html:'\n\n
\n
\n
Slide 1\n
\n
\n
Slide 2\n
\n
\n
\n
\n\n\n' }; From 1d48425fb8cf182f6e1b1e2a27714bca5756549b Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 17:50:21 +0800 Subject: [PATCH 14/20] test: improve functional tests --- src/sdk/__tests__/reveal.test.ts | 111 ++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index caec82b590..721f2e8f20 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -21,19 +21,21 @@ test('should do nothing when no [data-livecodes] element exists', async () => { expect(createPlayground).not.toHaveBeenCalled(); }); -test('should initializes playground and triggers sdkReady when a livecodes container with config exists', async () => { +test('should initializes playground and triggers sdkReady when a livecodes container with all configs exists', async () => { const sdkReady = jest.fn(); const container = document.createElement('div'); container.dataset.livecodes = ''; - container.dataset.config = '{"script":{"language":"javascript","content":"console.log(123)"}}'; + container.dataset.config = + '{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'; document.body.appendChild(container); const mockDeck = { - getConfig: jest - .fn() - .mockReturnValue({ - livecodes: { markup: { language: 'markdown', content: '# Hello world' }, sdkReady }, - customStyle: { backgroundColor: 'rgba(255,255,255,0.1)' }, - }), + getConfig: jest.fn().mockReturnValue({ + livecodes: { + config: { script: { language: 'javascript', content: 'console.log(456)' } }, + sdkReady, + }, + customStyle: { backgroundColor: 'rgba(255,255,255,0.1)' }, + }), } as any; LiveCodes.init(mockDeck); await new Promise(process.nextTick); @@ -43,9 +45,98 @@ test('should initializes playground and triggers sdkReady when a livecodes conta const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(123)'); - expect(calledWith.config.markup.language).toBe('markdown'); - expect(calledWith.config.markup.content).toBe('# Hello world'); expect(iframe?.style.maxWidth).toBe('100%'); expect(iframe?.style.maxHeight).toBe('100%'); expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); + +test('should initializes playground and triggers sdkReady when a livecodes container with global config exists', async () => { + const sdkReady = jest.fn(); + const container = document.createElement('div'); + container.dataset.livecodes = ''; + document.body.appendChild(container); + const mockDeck = { + getConfig: jest.fn().mockReturnValue({ + livecodes: { + config: { script: { language: 'javascript', content: 'console.log(456)' } }, + sdkReady, + }, + customStyle: { backgroundColor: 'rgba(255,255,255,0.1)' }, + }), + } as any; + LiveCodes.init(mockDeck); + await new Promise(process.nextTick); + expect(createPlayground).toHaveBeenCalledTimes(1); + expect(sdkReady).toHaveBeenCalledTimes(1); + const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; + const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + expect(calledWith.config.script.language).toBe('javascript'); + expect(calledWith.config.script.content).toBe('console.log(456)'); + expect(iframe?.style.maxWidth).toBe('100%'); + expect(iframe?.style.maxHeight).toBe('100%'); + expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); +}); + +test('should initializes playground and triggers sdkReady when a livecodes container with custom config exists', async () => { + const container = document.createElement('div'); + container.dataset.livecodes = ''; + container.dataset.config = + '{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'; + document.body.appendChild(container); + const mockDeck = { + getConfig: jest.fn().mockReturnValue({}), + } as any; + LiveCodes.init(mockDeck); + await new Promise(process.nextTick); + expect(createPlayground).toHaveBeenCalledTimes(1); + const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; + const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + expect(calledWith.config.script.language).toBe('javascript'); + expect(calledWith.config.script.content).toBe('console.log(123)'); + expect(iframe?.style.maxWidth).toBe('100%'); + expect(iframe?.style.maxHeight).toBe('100%'); +}); + +test('should Apply Custom Config Over Global Config And Trigger Sdk Ready', async () => { + const sdkReady = jest.fn(); + const container = document.createElement('div'); + container.dataset.livecodes = ''; + container.dataset.config = + '{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'; + document.body.appendChild(container); + const mockDeck = { + getConfig: jest.fn().mockReturnValue({ + livecodes: { + config: { script: { language: 'javascript', content: 'console.log(456)' } }, + sdkReady, + }, + customStyle: { backgroundColor: 'rgba(255,255,255,0.1)' }, + }), + } as any; + LiveCodes.init(mockDeck); + await new Promise(process.nextTick); + expect(createPlayground).toHaveBeenCalledTimes(1); + expect(sdkReady).toHaveBeenCalledTimes(1); + const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; + const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + expect(calledWith.config.script.language).toBe('javascript'); + expect(calledWith.config.script.content).toBe('console.log(123)'); + expect(iframe?.style.maxWidth).toBe('100%'); + expect(iframe?.style.maxHeight).toBe('100%'); + expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); +}); + +test('should initializes playground and triggers sdkReady when a livecodes container with no config', async () => { + const container = document.createElement('div'); + container.dataset.livecodes = ''; + document.body.appendChild(container); + const mockDeck = { + getConfig: jest.fn().mockReturnValue({}), + } as any; + LiveCodes.init(mockDeck); + await new Promise(process.nextTick); + expect(createPlayground).toHaveBeenCalledTimes(1); + const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + expect(iframe?.style.maxWidth).toBe('100%'); + expect(iframe?.style.maxHeight).toBe('100%'); +}); From 7c1dc7a69003151271f5a6acdd45a018a6382220 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 21:06:03 +0800 Subject: [PATCH 15/20] refactor(tests): extract helper functions to reduce duplication --- src/sdk/__tests__/reveal.test.ts | 67 ++++++++++++++------------------ 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index 721f2e8f20..f588805225 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -1,6 +1,23 @@ import { createPlayground } from '../index'; import { LiveCodes } from '../reveal'; +function expectIframeDefaultStyle(iframe: HTMLIFrameElement | null) { + expect(iframe?.style.maxWidth).toBe('100%'); + expect(iframe?.style.maxHeight).toBe('100%'); +} + +function expectCreatePlaygroundAndSdkFn(sdkReady: jest.MockedFunction<() => void>) { + expect(createPlayground).toHaveBeenCalledTimes(1); + expect(sdkReady).toHaveBeenCalledTimes(1); +} + +function createContainer(config: string = "") { + const container = document.createElement('div'); + container.dataset.livecodes = ''; + if (config.length > 0) container.dataset.config = config; + document.body.appendChild(container); +} + jest.mock('../index', () => ({ createPlayground: jest.fn().mockImplementation((container) => { const iframe = document.createElement('iframe'); @@ -23,11 +40,7 @@ test('should do nothing when no [data-livecodes] element exists', async () => { test('should initializes playground and triggers sdkReady when a livecodes container with all configs exists', async () => { const sdkReady = jest.fn(); - const container = document.createElement('div'); - container.dataset.livecodes = ''; - container.dataset.config = - '{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'; - document.body.appendChild(container); + createContainer('{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'); const mockDeck = { getConfig: jest.fn().mockReturnValue({ livecodes: { @@ -39,22 +52,18 @@ test('should initializes playground and triggers sdkReady when a livecodes conta } as any; LiveCodes.init(mockDeck); await new Promise(process.nextTick); - expect(createPlayground).toHaveBeenCalledTimes(1); - expect(sdkReady).toHaveBeenCalledTimes(1); + expectCreatePlaygroundAndSdkFn(sdkReady); const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(123)'); - expect(iframe?.style.maxWidth).toBe('100%'); - expect(iframe?.style.maxHeight).toBe('100%'); + expectIframeDefaultStyle(iframe); expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); test('should initializes playground and triggers sdkReady when a livecodes container with global config exists', async () => { const sdkReady = jest.fn(); - const container = document.createElement('div'); - container.dataset.livecodes = ''; - document.body.appendChild(container); + createContainer(); const mockDeck = { getConfig: jest.fn().mockReturnValue({ livecodes: { @@ -66,23 +75,17 @@ test('should initializes playground and triggers sdkReady when a livecodes conta } as any; LiveCodes.init(mockDeck); await new Promise(process.nextTick); - expect(createPlayground).toHaveBeenCalledTimes(1); - expect(sdkReady).toHaveBeenCalledTimes(1); + expectCreatePlaygroundAndSdkFn(sdkReady); const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(456)'); - expect(iframe?.style.maxWidth).toBe('100%'); - expect(iframe?.style.maxHeight).toBe('100%'); + expectIframeDefaultStyle(iframe); expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); test('should initializes playground and triggers sdkReady when a livecodes container with custom config exists', async () => { - const container = document.createElement('div'); - container.dataset.livecodes = ''; - container.dataset.config = - '{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'; - document.body.appendChild(container); + createContainer('{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'); const mockDeck = { getConfig: jest.fn().mockReturnValue({}), } as any; @@ -93,17 +96,12 @@ test('should initializes playground and triggers sdkReady when a livecodes conta const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(123)'); - expect(iframe?.style.maxWidth).toBe('100%'); - expect(iframe?.style.maxHeight).toBe('100%'); + expectIframeDefaultStyle(iframe); }); test('should Apply Custom Config Over Global Config And Trigger Sdk Ready', async () => { const sdkReady = jest.fn(); - const container = document.createElement('div'); - container.dataset.livecodes = ''; - container.dataset.config = - '{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'; - document.body.appendChild(container); + createContainer('{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'); const mockDeck = { getConfig: jest.fn().mockReturnValue({ livecodes: { @@ -115,21 +113,17 @@ test('should Apply Custom Config Over Global Config And Trigger Sdk Ready', asyn } as any; LiveCodes.init(mockDeck); await new Promise(process.nextTick); - expect(createPlayground).toHaveBeenCalledTimes(1); - expect(sdkReady).toHaveBeenCalledTimes(1); + expectCreatePlaygroundAndSdkFn(sdkReady); const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(123)'); - expect(iframe?.style.maxWidth).toBe('100%'); - expect(iframe?.style.maxHeight).toBe('100%'); + expectIframeDefaultStyle(iframe); expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); test('should initializes playground and triggers sdkReady when a livecodes container with no config', async () => { - const container = document.createElement('div'); - container.dataset.livecodes = ''; - document.body.appendChild(container); + createContainer(); const mockDeck = { getConfig: jest.fn().mockReturnValue({}), } as any; @@ -137,6 +131,5 @@ test('should initializes playground and triggers sdkReady when a livecodes conta await new Promise(process.nextTick); expect(createPlayground).toHaveBeenCalledTimes(1); const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; - expect(iframe?.style.maxWidth).toBe('100%'); - expect(iframe?.style.maxHeight).toBe('100%'); + expectIframeDefaultStyle(iframe); }); From 42f9b6acae3b85404a94081081b7bb0476b8ac77 Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 21:16:51 +0800 Subject: [PATCH 16/20] refactor(tests): extract helper functions to reduce duplication --- src/sdk/__tests__/reveal.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index f588805225..378ec79863 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -18,6 +18,10 @@ function createContainer(config: string = "") { document.body.appendChild(container); } +function getIframe() { + return document.querySelector('.livecodes'); +} + jest.mock('../index', () => ({ createPlayground: jest.fn().mockImplementation((container) => { const iframe = document.createElement('iframe'); @@ -54,7 +58,7 @@ test('should initializes playground and triggers sdkReady when a livecodes conta await new Promise(process.nextTick); expectCreatePlaygroundAndSdkFn(sdkReady); const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + const iframe = getIframe(); expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(123)'); expectIframeDefaultStyle(iframe); @@ -77,7 +81,7 @@ test('should initializes playground and triggers sdkReady when a livecodes conta await new Promise(process.nextTick); expectCreatePlaygroundAndSdkFn(sdkReady); const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + const iframe = getIframe(); expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(456)'); expectIframeDefaultStyle(iframe); @@ -93,7 +97,7 @@ test('should initializes playground and triggers sdkReady when a livecodes conta await new Promise(process.nextTick); expect(createPlayground).toHaveBeenCalledTimes(1); const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + const iframe = getIframe(); expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(123)'); expectIframeDefaultStyle(iframe); @@ -115,7 +119,7 @@ test('should Apply Custom Config Over Global Config And Trigger Sdk Ready', asyn await new Promise(process.nextTick); expectCreatePlaygroundAndSdkFn(sdkReady); const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; + const iframe = getIframe(); expect(calledWith.config.script.language).toBe('javascript'); expect(calledWith.config.script.content).toBe('console.log(123)'); expectIframeDefaultStyle(iframe); From 07e3b8d7101898441a7f93970f001278eb28129a Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 21:24:25 +0800 Subject: [PATCH 17/20] refactor(tests): extract helper functions to reduce duplication --- src/sdk/__tests__/reveal.test.ts | 50 -------------------------------- 1 file changed, 50 deletions(-) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index 378ec79863..c62a0c79ad 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -42,29 +42,6 @@ test('should do nothing when no [data-livecodes] element exists', async () => { expect(createPlayground).not.toHaveBeenCalled(); }); -test('should initializes playground and triggers sdkReady when a livecodes container with all configs exists', async () => { - const sdkReady = jest.fn(); - createContainer('{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'); - const mockDeck = { - getConfig: jest.fn().mockReturnValue({ - livecodes: { - config: { script: { language: 'javascript', content: 'console.log(456)' } }, - sdkReady, - }, - customStyle: { backgroundColor: 'rgba(255,255,255,0.1)' }, - }), - } as any; - LiveCodes.init(mockDeck); - await new Promise(process.nextTick); - expectCreatePlaygroundAndSdkFn(sdkReady); - const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = getIframe(); - expect(calledWith.config.script.language).toBe('javascript'); - expect(calledWith.config.script.content).toBe('console.log(123)'); - expectIframeDefaultStyle(iframe); - expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); -}); - test('should initializes playground and triggers sdkReady when a livecodes container with global config exists', async () => { const sdkReady = jest.fn(); createContainer(); @@ -88,21 +65,6 @@ test('should initializes playground and triggers sdkReady when a livecodes conta expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); -test('should initializes playground and triggers sdkReady when a livecodes container with custom config exists', async () => { - createContainer('{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'); - const mockDeck = { - getConfig: jest.fn().mockReturnValue({}), - } as any; - LiveCodes.init(mockDeck); - await new Promise(process.nextTick); - expect(createPlayground).toHaveBeenCalledTimes(1); - const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = getIframe(); - expect(calledWith.config.script.language).toBe('javascript'); - expect(calledWith.config.script.content).toBe('console.log(123)'); - expectIframeDefaultStyle(iframe); -}); - test('should Apply Custom Config Over Global Config And Trigger Sdk Ready', async () => { const sdkReady = jest.fn(); createContainer('{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'); @@ -125,15 +87,3 @@ test('should Apply Custom Config Over Global Config And Trigger Sdk Ready', asyn expectIframeDefaultStyle(iframe); expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); - -test('should initializes playground and triggers sdkReady when a livecodes container with no config', async () => { - createContainer(); - const mockDeck = { - getConfig: jest.fn().mockReturnValue({}), - } as any; - LiveCodes.init(mockDeck); - await new Promise(process.nextTick); - expect(createPlayground).toHaveBeenCalledTimes(1); - const iframe = document.querySelector('.livecodes') as HTMLIFrameElement | null; - expectIframeDefaultStyle(iframe); -}); From 26ae65e191529655530f3560aa800cb23960e95a Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 21:26:40 +0800 Subject: [PATCH 18/20] refactor(tests): extract helper functions to reduce duplication --- src/sdk/__tests__/reveal.test.ts | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index c62a0c79ad..adcf0392da 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -64,26 +64,3 @@ test('should initializes playground and triggers sdkReady when a livecodes conta expectIframeDefaultStyle(iframe); expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); - -test('should Apply Custom Config Over Global Config And Trigger Sdk Ready', async () => { - const sdkReady = jest.fn(); - createContainer('{"config":{"script":{"language":"javascript","content":"console.log(123)"}}}'); - const mockDeck = { - getConfig: jest.fn().mockReturnValue({ - livecodes: { - config: { script: { language: 'javascript', content: 'console.log(456)' } }, - sdkReady, - }, - customStyle: { backgroundColor: 'rgba(255,255,255,0.1)' }, - }), - } as any; - LiveCodes.init(mockDeck); - await new Promise(process.nextTick); - expectCreatePlaygroundAndSdkFn(sdkReady); - const calledWith = (createPlayground as jest.Mock).mock.calls[0][1]; - const iframe = getIframe(); - expect(calledWith.config.script.language).toBe('javascript'); - expect(calledWith.config.script.content).toBe('console.log(123)'); - expectIframeDefaultStyle(iframe); - expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); -}); From 21b37682588c3581ee05121541680a8491d71e9a Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 21:29:39 +0800 Subject: [PATCH 19/20] refactor(tests): extract helper functions to reduce duplication --- src/sdk/__tests__/reveal.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index adcf0392da..0a58d05ec5 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -64,3 +64,20 @@ test('should initializes playground and triggers sdkReady when a livecodes conta expectIframeDefaultStyle(iframe); expect(iframe?.style.backgroundColor).toBe('rgba(255, 255, 255, 0.1)'); }); + +test('should initialize multiple playgrounds when multiple livecodes containers exist', async () => { + const sdkReady = jest.fn(); + createContainer('{"config":{"script":{"language":"javascript","content":"console.log(1)"}}}'); + createContainer('{"config":{"script":{"language":"javascript","content":"console.log(2)"}}}'); + const mockDeck = { + getConfig: jest.fn().mockReturnValue({ + livecodes: { sdkReady }, + }), + } as any; + LiveCodes.init(mockDeck); + await new Promise(process.nextTick); + expect(createPlayground).toHaveBeenCalledTimes(2); + expect(sdkReady).toHaveBeenCalledTimes(2); + const iframes = document.querySelectorAll('.livecodes'); + expect(iframes.length).toBe(2); +}); \ No newline at end of file From ced4f51561d8bf0bfcb1f4467fb46b8022d8f37e Mon Sep 17 00:00:00 2001 From: rainflower12 <544739517m@gmail.com> Date: Fri, 14 Nov 2025 23:03:46 +0800 Subject: [PATCH 20/20] style: format reveal.test.ts with Prettier --- src/sdk/__tests__/reveal.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sdk/__tests__/reveal.test.ts b/src/sdk/__tests__/reveal.test.ts index 0a58d05ec5..ba7a85318e 100644 --- a/src/sdk/__tests__/reveal.test.ts +++ b/src/sdk/__tests__/reveal.test.ts @@ -11,7 +11,7 @@ function expectCreatePlaygroundAndSdkFn(sdkReady: jest.MockedFunction<() => void expect(sdkReady).toHaveBeenCalledTimes(1); } -function createContainer(config: string = "") { +function createContainer(config: string = '') { const container = document.createElement('div'); container.dataset.livecodes = ''; if (config.length > 0) container.dataset.config = config; @@ -80,4 +80,4 @@ test('should initialize multiple playgrounds when multiple livecodes containers expect(sdkReady).toHaveBeenCalledTimes(2); const iframes = document.querySelectorAll('.livecodes'); expect(iframes.length).toBe(2); -}); \ No newline at end of file +});