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
+
+```
+
+## Demo
+
+export const sdkDemo = {
+ html:'\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\n'
+ html:'\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
+});