mr-md 1.0.4 → 2.0.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/dist/builder.d.ts +6 -20
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +38 -97
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +92 -0
- package/dist/cli/generate.d.ts +2 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +171 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +89 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -0
- package/dist/client/app.js +282 -107
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/renderer/blocks.d.ts.map +1 -1
- package/dist/renderer/blocks.js +88 -16
- package/dist/renderer/html-neo.d.ts +7 -0
- package/dist/renderer/html-neo.d.ts.map +1 -0
- package/dist/renderer/html-neo.js +173 -0
- package/dist/renderer/html.d.ts.map +1 -1
- package/dist/renderer/html.js +36 -7
- package/dist/renderer/index-neo.d.ts +4 -0
- package/dist/renderer/index-neo.d.ts.map +1 -0
- package/dist/renderer/index-neo.js +469 -0
- package/dist/renderer/index.d.ts +1 -2
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +29 -379
- package/dist/renderer/markdown.d.ts +1 -1
- package/dist/renderer/markdown.d.ts.map +1 -1
- package/dist/renderer/markdown.js +3 -3
- package/dist/renderer/utils.d.ts +1 -1
- package/dist/renderer/utils.d.ts.map +1 -1
- package/dist/renderer/utils.js +41 -34
- package/dist/styles/theme-neo.css +1369 -0
- package/dist/styles/theme.css +412 -127
- package/dist/types.d.ts +8 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/builder.ts +49 -125
- package/src/cli/dev.ts +102 -0
- package/src/cli/generate.ts +191 -0
- package/src/cli/init.ts +97 -0
- package/src/cli.ts +29 -0
- package/src/client/app.js +282 -107
- package/src/index.ts +1 -1
- package/src/renderer/blocks.ts +89 -15
- package/src/renderer/html.ts +36 -7
- package/src/renderer/index.ts +30 -394
- package/src/renderer/markdown.ts +3 -2
- package/src/renderer/utils.ts +43 -36
- package/src/styles/theme.css +412 -127
- package/src/types.ts +8 -12
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
export async function runInit() {
|
|
5
|
+
console.log("Initializing md project structure...");
|
|
6
|
+
|
|
7
|
+
const dirs = [
|
|
8
|
+
"chapters",
|
|
9
|
+
"chapters/01-chapter",
|
|
10
|
+
"chapters/01-chapter/lessons",
|
|
11
|
+
"chapters/01-chapter/lessons/01-lesson",
|
|
12
|
+
"chapters/01-chapter/lessons/01-lesson/sims",
|
|
13
|
+
"chapters/01-chapter/lessons/01-lesson/media",
|
|
14
|
+
"chapters/01-chapter/lessons/01-lesson/quizzes",
|
|
15
|
+
"chapters/01-chapter/lessons/01-lesson/content",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const dir of dirs) {
|
|
19
|
+
const fullPath = path.resolve(process.cwd(), dir);
|
|
20
|
+
if (!fs.existsSync(fullPath)) {
|
|
21
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
22
|
+
console.log(` Created directory: ${dir}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const chapterTsPath = path.resolve(process.cwd(), "chapters/01-chapter/chapter.ts");
|
|
27
|
+
if (!fs.existsSync(chapterTsPath)) {
|
|
28
|
+
fs.writeFileSync(chapterTsPath, `import { chapter } from "mr-md";
|
|
29
|
+
import { firstLesson } from "./lessons/01-lesson/lesson.js";
|
|
30
|
+
|
|
31
|
+
export const firstChapter = chapter("First Chapter", ctx => {
|
|
32
|
+
ctx.lesson(firstLesson);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (import.meta.main) {
|
|
36
|
+
firstChapter.build();
|
|
37
|
+
}
|
|
38
|
+
`, "utf-8");
|
|
39
|
+
console.log(" Created: chapters/01-chapter/chapter.ts");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lessonTsPath = path.resolve(process.cwd(), "chapters/01-chapter/lessons/01-lesson/lesson.ts");
|
|
43
|
+
if (!fs.existsSync(lessonTsPath)) {
|
|
44
|
+
fs.writeFileSync(lessonTsPath, `import { lesson } from "mr-md";
|
|
45
|
+
|
|
46
|
+
export const firstLesson = lesson("First Lesson", { contentBase: import.meta.dir }, ctx => {
|
|
47
|
+
ctx.markdown("Welcome to your first lesson!");
|
|
48
|
+
});
|
|
49
|
+
`, "utf-8");
|
|
50
|
+
console.log(" Created: chapters/01-chapter/lessons/01-lesson/lesson.ts");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const packageJsonPath = path.resolve(process.cwd(), "package.json");
|
|
54
|
+
let pkg: any = {};
|
|
55
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
56
|
+
try {
|
|
57
|
+
pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error(" Failed to parse existing package.json, ignoring.");
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
pkg = {
|
|
63
|
+
name: "my-md-project",
|
|
64
|
+
version: "1.0.0",
|
|
65
|
+
private: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pkg.scripts = {
|
|
70
|
+
...(pkg.scripts || {}),
|
|
71
|
+
build: "bun chapters/01-chapter/chapter.ts",
|
|
72
|
+
dev: "md dev",
|
|
73
|
+
g: "md g",
|
|
74
|
+
generate: "md generate"
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let mrMdVersion = "latest";
|
|
78
|
+
try {
|
|
79
|
+
// Find mr-md's own package.json to get its version
|
|
80
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
81
|
+
const ownPkgPath = path.resolve(__dirname, "../../package.json");
|
|
82
|
+
const ownPkg = JSON.parse(fs.readFileSync(ownPkgPath, "utf-8"));
|
|
83
|
+
mrMdVersion = ownPkg.version;
|
|
84
|
+
} catch (e) {
|
|
85
|
+
// Fallback if unable to read
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pkg.dependencies = {
|
|
89
|
+
...(pkg.dependencies || {}),
|
|
90
|
+
"mr-md": mrMdVersion
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
94
|
+
console.log(" Updated: package.json");
|
|
95
|
+
|
|
96
|
+
console.log("\nDone! You can now run `npm run dev` or `bun run dev` to start the local development server.");
|
|
97
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const command = args[0];
|
|
5
|
+
|
|
6
|
+
if (!command) {
|
|
7
|
+
console.error("Usage: md <command> [args]");
|
|
8
|
+
console.error("Commands:");
|
|
9
|
+
console.error(" init Scaffold a new mr-md project");
|
|
10
|
+
console.error(" g Generate resources (ch, lesson, quiz)");
|
|
11
|
+
console.error(" dev Start local dev server");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
switch (command) {
|
|
16
|
+
case "init":
|
|
17
|
+
import("./cli/init.js").then((m) => m.runInit());
|
|
18
|
+
break;
|
|
19
|
+
case "g":
|
|
20
|
+
case "generate":
|
|
21
|
+
import("./cli/generate.js").then((m) => m.runGenerate(args.slice(1)));
|
|
22
|
+
break;
|
|
23
|
+
case "dev":
|
|
24
|
+
import("./cli/dev.js").then((m) => m.runDev(args.slice(1)));
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
console.error(`Unknown command: ${command}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
package/src/client/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
function bkSimDoc(js, props, loop, dependencies) {
|
|
2
|
-
const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '"') + '"></' + 'script>').join("
|
|
2
|
+
const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '"') + '"></' + 'script>').join("\\n");
|
|
3
3
|
return (
|
|
4
4
|
"<!DOCTYPE html><html><head>" +
|
|
5
5
|
scriptTags +
|
|
@@ -15,12 +15,12 @@ function bkSimDoc(js, props, loop, dependencies) {
|
|
|
15
15
|
";window.bkSetupCalled=false;" +
|
|
16
16
|
'window.bkCanvasPoint=function(e,c){const r=(c||e.currentTarget||e.target).getBoundingClientRect(),w=(c&&c.__bkLogicalW)||800,h=(c&&c.__bkLogicalH)||500;return{x:(e.clientX-r.left)*w/r.width,y:(e.clientY-r.top)*h/r.height}};' +
|
|
17
17
|
'window.bkFitCanvas=function(c,reqW,reqH,o){if(!c)return{scale:1,width:reqW,height:reqH,cssScale:1};const d=window.devicePixelRatio||1;const w=reqW;const h=reqH;c.__bkLogicalW=w;c.__bkLogicalH=h;c.style.width=w+"px";c.style.height=h+"px";c.style.position="relative";c.style.left="auto";c.style.top="auto";c.style.transformOrigin="center center";const sx=window.innerWidth/w,sy=window.innerHeight/h,cssS=Math.max(sx,sy);c.style.transform="scale("+cssS+")";const pw=Math.max(1,Math.round(w*d)),ph=Math.max(1,Math.round(h*d));if(!o||o.bitmap!==false){if(c.width!==pw||c.height!==ph){c.width=pw;c.height=ph}}return{scale:d,width:w,height:h,cssScale:cssS}};' +
|
|
18
|
-
'window.bkSetup=function(w,h,f){window.bkSetupCalled=true;const c=document.getElementById("c");if(!c)return;const ctx=c.getContext("2d");function l(){
|
|
18
|
+
'window.bkSetup=function(w,h,f){window.bkSetupCalled=true;const c=document.getElementById("c");if(!c)return;const ctx=c.getContext("2d");let loopId=null;let fit=window.bkFitCanvas(c,w,h);function l(){if(window.innerWidth>=32&&window.innerHeight>=32){ctx.save();ctx.scale(fit.scale,fit.scale);f(ctx,fit.width,fit.height);ctx.restore()}if(window.__loop){loopId=requestAnimationFrame(l)}else{loopId=null}}function i(){if(window.innerWidth>=32&&window.innerHeight>=32){fit=window.bkFitCanvas(c,w,h);l()}else{requestAnimationFrame(i)}}i();window.addEventListener("resize",function(){fit=window.bkFitCanvas(c,w,h);if(!window.__loop&&window.innerWidth>=32&&window.innerHeight>=32&&!loopId){ctx.save();ctx.scale(fit.scale,fit.scale);f(ctx,fit.width,fit.height);ctx.restore()}});window.addEventListener("message",function(event){if(!event.data)return;if(event.data.type==="bk:play"){window.__loop=true;if(!loopId)loopId=requestAnimationFrame(l)}else if(event.data.type==="bk:pause"){window.__loop=false}});};' +
|
|
19
19
|
'window.addEventListener("message",function(event){if(!event.data||event.data.type!=="bk:set-props")return;window.__simProps=Object.assign({},window.__simProps,event.data.props);window.dispatchEvent(new CustomEvent("bk:props",{detail:window.__simProps}));});' +
|
|
20
20
|
"try{" +
|
|
21
21
|
js +
|
|
22
22
|
'}catch(e){console.error("Simulation Error:",e);document.body.innerHTML="<div style=\'padding: 20px; color: red; font-family: monospace;\'>Error: "+e.message+"</div>"}' +
|
|
23
|
-
"if(!window.bkSetupCalled){function fallbackScale(){window.bkFitCanvas(document.getElementById('c'),800,500,{bitmap:false});
|
|
23
|
+
"if(!window.bkSetupCalled){function fallbackScale(){window.bkFitCanvas(document.getElementById('c'),800,500,{bitmap:false});}fallbackScale();window.addEventListener('resize', fallbackScale);}" +
|
|
24
24
|
"</" + "script></body></html>"
|
|
25
25
|
);
|
|
26
26
|
}
|
|
@@ -41,64 +41,55 @@ function bkReadSimProps(figure) {
|
|
|
41
41
|
return props;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function bkRestartSim(iframe, config, props) {
|
|
45
|
-
iframe.srcdoc = bkSimDoc(config.js, props, config.loop, config.dependencies);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
function bkWireSimControls() {
|
|
49
|
-
|
|
50
|
-
const
|
|
45
|
+
const handler = (e) => {
|
|
46
|
+
const input = e.target.closest?.("[data-bk-prop]");
|
|
47
|
+
if (!input) return;
|
|
48
|
+
const figure = input.closest(".bk-object");
|
|
49
|
+
if (!figure) return;
|
|
51
50
|
const iframe = figure.querySelector("iframe");
|
|
52
|
-
if (!
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
figure.querySelectorAll("[data-bk-prop]").forEach((input) => {
|
|
62
|
-
input.addEventListener("input", () => {
|
|
63
|
-
const props = bkReadSimProps(figure);
|
|
64
|
-
iframe.contentWindow?.postMessage({ type: "bk:set-props", props }, "*");
|
|
65
|
-
});
|
|
66
|
-
input.addEventListener("change", () => {
|
|
67
|
-
const props = bkReadSimProps(figure);
|
|
68
|
-
iframe.contentWindow?.postMessage({ type: "bk:set-props", props }, "*");
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
});
|
|
51
|
+
if (!iframe) return;
|
|
52
|
+
const props = bkReadSimProps(figure);
|
|
53
|
+
iframe.contentWindow?.postMessage({ type: "bk:set-props", props }, "*");
|
|
54
|
+
};
|
|
55
|
+
document.addEventListener("input", handler, { passive: true });
|
|
56
|
+
document.addEventListener("change", handler, { passive: true });
|
|
72
57
|
}
|
|
73
58
|
|
|
74
59
|
function bkWireMaximizeControls() {
|
|
75
|
-
document.
|
|
76
|
-
btn.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
60
|
+
document.addEventListener("click", (e) => {
|
|
61
|
+
const btn = e.target.closest?.(".bk-object-maximize");
|
|
62
|
+
if (!btn) return;
|
|
63
|
+
const obj = btn.closest(".bk-object");
|
|
64
|
+
if (!obj) return;
|
|
65
|
+
const isMax = obj.classList.toggle("bk-object--maximized");
|
|
66
|
+
if (isMax) {
|
|
67
|
+
document.body.style.overflow = "hidden";
|
|
68
|
+
btn.innerHTML =
|
|
69
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/></svg>';
|
|
70
|
+
} else {
|
|
71
|
+
document.body.style.overflow = "";
|
|
72
|
+
btn.innerHTML =
|
|
73
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>';
|
|
74
|
+
}
|
|
90
75
|
});
|
|
91
76
|
}
|
|
92
77
|
|
|
93
78
|
function bkWireInteractiveFrames() {
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
79
|
+
const activate = (e) => {
|
|
80
|
+
const obj = e.target.closest?.(".bk-object");
|
|
81
|
+
if (!obj) return;
|
|
82
|
+
const frame = obj.querySelector(".bk-embed-interactive");
|
|
83
|
+
if (frame) {
|
|
84
|
+
frame.classList.add("is-interactive");
|
|
85
|
+
const iframe = frame.querySelector("iframe");
|
|
86
|
+
if (iframe && iframe.contentWindow) {
|
|
87
|
+
iframe.contentWindow.postMessage({ type: "bk:play" }, "*");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
document.addEventListener("pointerdown", activate, { passive: true });
|
|
92
|
+
document.addEventListener("focusin", activate, { passive: true });
|
|
102
93
|
|
|
103
94
|
const exitInteractive = (e) => {
|
|
104
95
|
document
|
|
@@ -107,11 +98,56 @@ function bkWireInteractiveFrames() {
|
|
|
107
98
|
const container = frame.closest(".bk-object") || frame;
|
|
108
99
|
if (!container.contains(e.target)) {
|
|
109
100
|
frame.classList.remove("is-interactive");
|
|
101
|
+
const iframe = frame.querySelector("iframe");
|
|
102
|
+
if (iframe && iframe.contentWindow) {
|
|
103
|
+
iframe.contentWindow.postMessage({ type: "bk:pause" }, "*");
|
|
104
|
+
}
|
|
110
105
|
}
|
|
111
106
|
});
|
|
112
107
|
};
|
|
113
108
|
document.addEventListener("pointerdown", exitInteractive, { passive: true });
|
|
114
109
|
document.addEventListener("focusin", exitInteractive, { passive: true });
|
|
110
|
+
|
|
111
|
+
const obs = new IntersectionObserver((entries) => {
|
|
112
|
+
entries.forEach((e) => {
|
|
113
|
+
const frame = e.target;
|
|
114
|
+
const iframe = frame.querySelector("iframe");
|
|
115
|
+
if (!e.isIntersecting) {
|
|
116
|
+
if (frame.classList.contains("is-interactive")) {
|
|
117
|
+
frame.classList.remove("is-interactive");
|
|
118
|
+
}
|
|
119
|
+
if (iframe && iframe.contentWindow) {
|
|
120
|
+
iframe.contentWindow.postMessage({ type: "bk:pause" }, "*");
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
if (frame.dataset.isAnimation === "true") {
|
|
124
|
+
if (iframe && iframe.contentWindow) {
|
|
125
|
+
iframe.contentWindow.postMessage({ type: "bk:play" }, "*");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}, { threshold: 0 });
|
|
131
|
+
|
|
132
|
+
document.querySelectorAll(".bk-embed-interactive").forEach((frame) => {
|
|
133
|
+
obs.observe(frame);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
document.addEventListener('contentvisibilityautostatechange', (e) => {
|
|
137
|
+
const frame = e.target;
|
|
138
|
+
if (frame && frame.classList && frame.classList.contains('bk-embed-interactive')) {
|
|
139
|
+
const iframe = frame.querySelector('iframe');
|
|
140
|
+
if (!iframe || !iframe.contentWindow) return;
|
|
141
|
+
|
|
142
|
+
if (e.skipped) {
|
|
143
|
+
iframe.contentWindow.postMessage({ type: "bk:pause" }, "*");
|
|
144
|
+
} else {
|
|
145
|
+
if (frame.dataset.isAnimation === "true" || frame.classList.contains("is-interactive")) {
|
|
146
|
+
iframe.contentWindow.postMessage({ type: "bk:play" }, "*");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}, { capture: true });
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
function bkWireSidebarToggle() {
|
|
@@ -128,14 +164,58 @@ function bkWireSidebarToggle() {
|
|
|
128
164
|
);
|
|
129
165
|
}
|
|
130
166
|
|
|
167
|
+
function bkBroadcastTheme(targetWindow) {
|
|
168
|
+
const root = document.documentElement;
|
|
169
|
+
const styles = getComputedStyle(root);
|
|
170
|
+
const state = {
|
|
171
|
+
theme: root.dataset.theme || "light",
|
|
172
|
+
palette: root.dataset.palette || "ink",
|
|
173
|
+
ui: root.dataset.ui || "standard",
|
|
174
|
+
colors: {
|
|
175
|
+
bg: styles.getPropertyValue('--bg').trim(),
|
|
176
|
+
paper: styles.getPropertyValue('--paper').trim(),
|
|
177
|
+
line: styles.getPropertyValue('--line').trim(),
|
|
178
|
+
'line-strong': styles.getPropertyValue('--line-strong').trim(),
|
|
179
|
+
text: styles.getPropertyValue('--text').trim(),
|
|
180
|
+
'text-light': styles.getPropertyValue('--text-light').trim(),
|
|
181
|
+
accent: styles.getPropertyValue('--accent').trim(),
|
|
182
|
+
'accent-soft': styles.getPropertyValue('--accent-soft').trim()
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
if (targetWindow) {
|
|
186
|
+
targetWindow.postMessage({ type: "bk:theme-sync", state }, "*");
|
|
187
|
+
} else {
|
|
188
|
+
document.querySelectorAll(".bk-embed-interactive iframe").forEach(iframe => {
|
|
189
|
+
if (iframe.contentWindow) iframe.contentWindow.postMessage({ type: "bk:theme-sync", state }, "*");
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
window.addEventListener("message", function(e) {
|
|
195
|
+
if (e.data && e.data.type === "bk:request-theme") {
|
|
196
|
+
// Small delay to ensure CSS has applied if this happens right on load
|
|
197
|
+
requestAnimationFrame(() => bkBroadcastTheme(e.source));
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Watch for OS theme changes if on auto
|
|
202
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
203
|
+
if (document.documentElement.dataset.theme === "auto") {
|
|
204
|
+
bkBroadcastTheme();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
131
208
|
function bkWireThemeControls() {
|
|
132
209
|
const root = document.documentElement;
|
|
133
210
|
const button = document.getElementById("bk-settings-button");
|
|
134
211
|
const panel = document.getElementById("bk-theme-panel");
|
|
135
212
|
const themeBtns = document.querySelectorAll("#bk-theme-icons button");
|
|
136
|
-
|
|
137
|
-
const
|
|
213
|
+
const paletteBtns = document.querySelectorAll("#bk-palette-icons button");
|
|
214
|
+
const uiBtns = document.querySelectorAll("#bk-ui-icons button");
|
|
215
|
+
let savedTheme = localStorage.getItem("bk-theme");
|
|
216
|
+
if (!savedTheme) savedTheme = "auto";
|
|
138
217
|
const savedPalette = localStorage.getItem("bk-palette");
|
|
218
|
+
const savedUi = localStorage.getItem("bk-ui");
|
|
139
219
|
|
|
140
220
|
function updateThemeBtn(val) {
|
|
141
221
|
themeBtns.forEach(b => {
|
|
@@ -144,23 +224,55 @@ function bkWireThemeControls() {
|
|
|
144
224
|
});
|
|
145
225
|
}
|
|
146
226
|
|
|
227
|
+
const proPalettes = {
|
|
228
|
+
ink: "elixir",
|
|
229
|
+
field: "trunk",
|
|
230
|
+
ember: "lava"
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const proIcons = {
|
|
234
|
+
elixir: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3v4.4L4.8 14.5a2.5 2.5 0 0 0 2.2 3.5h10a2.5 2.5 0 0 0 2.2-3.5L15 7.4V3"></path><path d="M9 14h6"></path><path d="M10 3h4"></path></svg>',
|
|
235
|
+
trunk: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m8 3 4 8 5-5 5 15H2L8 3z"></path></svg>',
|
|
236
|
+
lava: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 21 4.5-9"></path><path d="M16.5 12 21 21"></path><path d="m11 21 1.4-2.8"></path><path d="m15 21-1.4-2.8"></path><path d="M11 6a3 3 0 0 1 2-2 3 3 0 0 1 2 2"></path><path d="M12 12a3 3 0 0 0 3-3"></path><path d="M9 12a3 3 0 0 1-3-3"></path><path d="M12 21v-3.5"></path><path d="M10 18h4"></path></svg>'
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const normalIcons = {
|
|
240
|
+
ink: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path></svg>',
|
|
241
|
+
field: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"></path><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"></path></svg>',
|
|
242
|
+
ember: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.5 14.5A2.5 2.5 0 0011 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 11-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 002.5 2.5z"></path></svg>'
|
|
243
|
+
};
|
|
244
|
+
|
|
147
245
|
function updatePaletteBtn(val) {
|
|
148
246
|
paletteBtns.forEach(b => {
|
|
149
|
-
|
|
247
|
+
const basePalette = b.dataset.palette;
|
|
248
|
+
|
|
249
|
+
if (val === proPalettes[basePalette]) {
|
|
250
|
+
b.innerHTML = proIcons[val];
|
|
251
|
+
} else {
|
|
252
|
+
b.innerHTML = normalIcons[basePalette];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if(basePalette === val || proPalettes[basePalette] === val) b.classList.add("active");
|
|
256
|
+
else b.classList.remove("active");
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function updateUiBtn(val) {
|
|
261
|
+
uiBtns.forEach(b => {
|
|
262
|
+
if(b.dataset.ui === val) b.classList.add("active");
|
|
150
263
|
else b.classList.remove("active");
|
|
151
264
|
});
|
|
152
265
|
}
|
|
153
266
|
|
|
154
267
|
if (savedTheme) {
|
|
155
268
|
updateThemeBtn(savedTheme);
|
|
156
|
-
savedTheme === "auto"
|
|
157
|
-
? root.removeAttribute("data-theme")
|
|
158
|
-
: root.setAttribute("data-theme", savedTheme);
|
|
159
269
|
}
|
|
160
270
|
if (savedPalette) {
|
|
161
271
|
const normalizedPalette = savedPalette === "green" ? "field" : savedPalette;
|
|
162
272
|
updatePaletteBtn(normalizedPalette);
|
|
163
|
-
|
|
273
|
+
}
|
|
274
|
+
if (savedUi) {
|
|
275
|
+
updateUiBtn(savedUi);
|
|
164
276
|
}
|
|
165
277
|
|
|
166
278
|
button &&
|
|
@@ -190,18 +302,43 @@ function bkWireThemeControls() {
|
|
|
190
302
|
const val = btn.dataset.theme;
|
|
191
303
|
localStorage.setItem("bk-theme", val);
|
|
192
304
|
updateThemeBtn(val);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
: root.setAttribute("data-theme", val);
|
|
305
|
+
root.setAttribute("data-theme", val);
|
|
306
|
+
bkBroadcastTheme();
|
|
196
307
|
});
|
|
197
308
|
});
|
|
198
309
|
|
|
199
|
-
|
|
310
|
+
paletteBtns.forEach(btn => {
|
|
311
|
+
btn.addEventListener("click", (e) => {
|
|
312
|
+
const baseVal = btn.dataset.palette;
|
|
313
|
+
let currentVal = root.getAttribute("data-palette") || "ink";
|
|
314
|
+
let newVal = baseVal;
|
|
315
|
+
|
|
316
|
+
if (e.detail === 2) {
|
|
317
|
+
if (currentVal === proPalettes[baseVal]) {
|
|
318
|
+
newVal = baseVal;
|
|
319
|
+
} else {
|
|
320
|
+
newVal = proPalettes[baseVal];
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
if (currentVal === proPalettes[baseVal]) {
|
|
324
|
+
newVal = proPalettes[baseVal];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
localStorage.setItem("bk-palette", newVal);
|
|
329
|
+
updatePaletteBtn(newVal);
|
|
330
|
+
root.setAttribute("data-palette", newVal);
|
|
331
|
+
bkBroadcastTheme();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
uiBtns.forEach(btn => {
|
|
200
336
|
btn.addEventListener("click", () => {
|
|
201
|
-
const val = btn.dataset.
|
|
202
|
-
localStorage.setItem("bk-
|
|
203
|
-
|
|
204
|
-
root.setAttribute("data-
|
|
337
|
+
const val = btn.dataset.ui;
|
|
338
|
+
localStorage.setItem("bk-ui", val);
|
|
339
|
+
updateUiBtn(val);
|
|
340
|
+
root.setAttribute("data-ui", val);
|
|
341
|
+
bkBroadcastTheme();
|
|
205
342
|
});
|
|
206
343
|
});
|
|
207
344
|
}
|
|
@@ -251,23 +388,28 @@ function bkWireCodeCopy() {
|
|
|
251
388
|
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
|
|
252
389
|
btn.setAttribute("aria-label", "Copy code");
|
|
253
390
|
btn.title = "Copy code";
|
|
254
|
-
|
|
255
|
-
btn.addEventListener("click", async () => {
|
|
256
|
-
try {
|
|
257
|
-
await navigator.clipboard.writeText(code.textContent || "");
|
|
258
|
-
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
|
259
|
-
btn.classList.add("copied");
|
|
260
|
-
setTimeout(() => {
|
|
261
|
-
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
|
|
262
|
-
btn.classList.remove("copied");
|
|
263
|
-
}, 2000);
|
|
264
|
-
} catch (err) {
|
|
265
|
-
console.error("Failed to copy", err);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
391
|
container.appendChild(btn);
|
|
270
392
|
});
|
|
393
|
+
|
|
394
|
+
document.addEventListener("click", async (e) => {
|
|
395
|
+
const btn = e.target.closest?.(".bk-copy-btn");
|
|
396
|
+
if (!btn) return;
|
|
397
|
+
const container = btn.closest(".bk-code-block");
|
|
398
|
+
if (!container) return;
|
|
399
|
+
const code = container.querySelector("code");
|
|
400
|
+
if (!code) return;
|
|
401
|
+
try {
|
|
402
|
+
await navigator.clipboard.writeText(code.textContent || "");
|
|
403
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
|
|
404
|
+
btn.classList.add("copied");
|
|
405
|
+
setTimeout(() => {
|
|
406
|
+
btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
|
|
407
|
+
btn.classList.remove("copied");
|
|
408
|
+
}, 2000);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
console.error("Failed to copy", err);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
271
413
|
}
|
|
272
414
|
|
|
273
415
|
// Active sidebar link on scroll
|
|
@@ -279,36 +421,69 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
279
421
|
bkWireInteractiveFrames();
|
|
280
422
|
bkWireCodeCopy();
|
|
281
423
|
|
|
282
|
-
const sections = document.querySelectorAll(
|
|
283
|
-
'[id^="heading-"], [id^="section-"], [id^="quiz-"]',
|
|
284
|
-
);
|
|
285
424
|
const navLinks = document.querySelectorAll(".bk-nav-item");
|
|
425
|
+
if (!navLinks.length) return;
|
|
286
426
|
|
|
287
|
-
|
|
427
|
+
const nav = document.querySelector(".bk-nav");
|
|
428
|
+
let pill = document.querySelector(".bk-nav-active-pill");
|
|
429
|
+
if (nav && !pill) {
|
|
430
|
+
pill = document.createElement("div");
|
|
431
|
+
pill.className = "bk-nav-active-pill";
|
|
432
|
+
nav.prepend(pill);
|
|
433
|
+
}
|
|
288
434
|
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
435
|
+
const sections = [];
|
|
436
|
+
navLinks.forEach((l) => {
|
|
437
|
+
const id = l.dataset.id;
|
|
438
|
+
if (id) {
|
|
439
|
+
const el = document.getElementById(id);
|
|
440
|
+
if (el) sections.push({ id, el, link: l, isAbove: false });
|
|
441
|
+
}
|
|
442
|
+
});
|
|
297
443
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
444
|
+
if (!sections.length) return;
|
|
445
|
+
|
|
446
|
+
function setActive(idx) {
|
|
447
|
+
sections.forEach((s, i) => {
|
|
448
|
+
if (i === idx) {
|
|
449
|
+
s.link.classList.add("active");
|
|
450
|
+
if (pill) {
|
|
451
|
+
pill.style.top = s.link.offsetTop + "px";
|
|
452
|
+
pill.style.height = s.link.offsetHeight + "px";
|
|
453
|
+
pill.style.opacity = "1";
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
s.link.classList.remove("active");
|
|
306
457
|
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
310
460
|
|
|
311
|
-
|
|
312
|
-
|
|
461
|
+
function updateActive() {
|
|
462
|
+
let activeIdx = 0;
|
|
463
|
+
for (let i = 0; i < sections.length; i++) {
|
|
464
|
+
if (sections[i].isAbove) activeIdx = i;
|
|
465
|
+
}
|
|
466
|
+
setActive(activeIdx);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const mainScrollContainer = document.querySelector(".bk-main");
|
|
470
|
+
const sectionObs = new IntersectionObserver((entries) => {
|
|
471
|
+
for (const entry of entries) {
|
|
472
|
+
const section = sections.find(s => s.el === entry.target);
|
|
473
|
+
if (!section) continue;
|
|
474
|
+
if (entry.isIntersecting) {
|
|
475
|
+
section.isAbove = true;
|
|
476
|
+
} else {
|
|
477
|
+
section.isAbove = entry.boundingClientRect.top < (entry.rootBounds?.top ?? 0);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
updateActive();
|
|
481
|
+
}, {
|
|
482
|
+
root: mainScrollContainer || null,
|
|
483
|
+
rootMargin: "0px 0px -75% 0px",
|
|
484
|
+
threshold: 0
|
|
313
485
|
});
|
|
486
|
+
|
|
487
|
+
sections.forEach(s => sectionObs.observe(s.el));
|
|
488
|
+
setActive(0);
|
|
314
489
|
});
|
package/src/index.ts
CHANGED