@hyperbook/markdown 0.21.0 → 0.22.1
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/dist/assets/client.js +86 -23
- package/dist/assets/code.css +17 -0
- package/dist/assets/directive-abc-music/client.js +30 -4
- package/dist/assets/directive-archive/client.js +30 -13
- package/dist/assets/directive-bookmarks/client.js +22 -5
- package/dist/assets/directive-download/client.js +24 -4
- package/dist/assets/directive-mermaid/client.js +41 -4
- package/dist/assets/directive-protect/client.js +27 -3
- package/dist/assets/directive-pyide/client.js +197 -48
- package/dist/assets/directive-pyide/style.css +51 -10
- package/dist/assets/directive-pyide/webworker.js +73 -29
- package/dist/assets/directive-scratchblock/client.js +18 -3
- package/dist/assets/directive-slideshow/client.js +26 -5
- package/dist/assets/directive-tabs/client.js +27 -10
- package/dist/assets/prism/prism.css +3 -1
- package/dist/assets/prism/prism.js +5 -1
- package/dist/index.js +98 -11
- package/dist/index.js.map +2 -2
- package/package.json +3 -3
|
@@ -11,79 +11,228 @@ hyperbook.python = (function () {
|
|
|
11
11
|
`${HYPERBOOK_ASSETS}directive-pyide/webworker.js`
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
let callback = null;
|
|
15
|
+
/**
|
|
16
|
+
* @type Uint8Array
|
|
17
|
+
*/
|
|
18
|
+
let interruptBuffer;
|
|
19
|
+
/**
|
|
20
|
+
* @type Int32Array
|
|
21
|
+
*/
|
|
22
|
+
let stdinBuffer;
|
|
23
|
+
if (window.crossOriginIsolated) {
|
|
24
|
+
interruptBuffer = new Uint8Array(new SharedArrayBuffer(1));
|
|
25
|
+
stdinBuffer = new Int32Array(new SharedArrayBuffer(1024));
|
|
26
|
+
pyodideWorker.postMessage({
|
|
27
|
+
type: "setStdinBuffer",
|
|
28
|
+
payload: { stdinBuffer },
|
|
29
|
+
});
|
|
30
|
+
pyodideWorker.postMessage({
|
|
31
|
+
type: "setInterruptBuffer",
|
|
32
|
+
payload: { interruptBuffer },
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
interruptBuffer = new ArrayBuffer(1);
|
|
36
|
+
pyodideWorker.postMessage({
|
|
37
|
+
type: "setInterruptBuffer",
|
|
38
|
+
payload: { interruptBuffer },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
16
41
|
|
|
17
|
-
const asyncRun = (id) => {
|
|
18
|
-
if (
|
|
42
|
+
const asyncRun = (id, type) => {
|
|
43
|
+
if (callback) return;
|
|
19
44
|
|
|
20
|
-
|
|
21
|
-
updateRunning();
|
|
45
|
+
interruptBuffer[0] = 0;
|
|
22
46
|
return (script, context) => {
|
|
23
|
-
// the id could be generated more carefully
|
|
24
47
|
return new Promise((onSuccess) => {
|
|
25
|
-
|
|
48
|
+
callback = onSuccess;
|
|
49
|
+
updateRunning(id, type);
|
|
26
50
|
pyodideWorker.postMessage({
|
|
27
|
-
|
|
28
|
-
|
|
51
|
+
type: "run",
|
|
52
|
+
payload: {
|
|
53
|
+
...context,
|
|
54
|
+
python: script,
|
|
55
|
+
},
|
|
29
56
|
id,
|
|
30
57
|
});
|
|
31
58
|
});
|
|
32
59
|
};
|
|
33
60
|
};
|
|
34
61
|
|
|
35
|
-
|
|
62
|
+
function interruptExecution() {
|
|
63
|
+
// 2 stands for SIGINT.
|
|
64
|
+
interruptBuffer[0] = 2;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const updateRunning = (id, type) => {
|
|
68
|
+
const elems = document.getElementsByClassName("directive-pyide");
|
|
36
69
|
for (let elem of elems) {
|
|
37
70
|
const run = elem.getElementsByClassName("run")[0];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
const test = elem.getElementsByClassName("test")[0];
|
|
72
|
+
if (callback) {
|
|
73
|
+
if (elem.id === id && type === "run") {
|
|
74
|
+
run.textContent = "Running (Click to stop) ...";
|
|
75
|
+
run.addEventListener("click", interruptExecution);
|
|
76
|
+
} else if (test && elem.id === id && type === "test") {
|
|
77
|
+
test.textContent = "Testing (Click to stop) ...";
|
|
78
|
+
test.addEventListener("click", interruptExecution);
|
|
79
|
+
} else {
|
|
80
|
+
run.classList.add("running");
|
|
81
|
+
run.disabled = true;
|
|
82
|
+
if (test) {
|
|
83
|
+
test.classList.add("running");
|
|
84
|
+
test.disabled = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
41
87
|
} else {
|
|
42
88
|
run.classList.remove("running");
|
|
43
89
|
run.textContent = "Run";
|
|
90
|
+
run.disabled = false;
|
|
91
|
+
run.removeEventListener("click", interruptExecution);
|
|
92
|
+
if (test) {
|
|
93
|
+
test.classList.remove("running");
|
|
94
|
+
test.textContent = "Test";
|
|
95
|
+
test.disabled = false;
|
|
96
|
+
test.removeEventListener("click", interruptExecution);
|
|
97
|
+
}
|
|
44
98
|
}
|
|
45
|
-
run.disabled = isRunning;
|
|
46
99
|
}
|
|
47
100
|
};
|
|
48
101
|
|
|
49
102
|
pyodideWorker.onmessage = (event) => {
|
|
50
|
-
const { id,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
103
|
+
const { id, type, payload } = event.data;
|
|
104
|
+
switch (type) {
|
|
105
|
+
case "stdout": {
|
|
106
|
+
const output = document
|
|
107
|
+
.getElementById(id)
|
|
108
|
+
.getElementsByClassName("output")[0];
|
|
109
|
+
output.appendChild(document.createTextNode(payload + "\n"));
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "error": {
|
|
113
|
+
const onSuccess = callback;
|
|
114
|
+
onSuccess({ error: payload });
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "success": {
|
|
118
|
+
const onSuccess = callback;
|
|
119
|
+
onSuccess({ results: payload });
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
57
122
|
}
|
|
58
|
-
const onSuccess = callbacks[id];
|
|
59
|
-
delete callbacks[id];
|
|
60
|
-
isRunning = false;
|
|
61
|
-
updateRunning();
|
|
62
|
-
onSuccess(data);
|
|
63
123
|
};
|
|
64
124
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
125
|
+
const init = (root) => {
|
|
126
|
+
const elems = root.getElementsByClassName("directive-pyide");
|
|
127
|
+
|
|
128
|
+
for (let elem of elems) {
|
|
129
|
+
const editor = elem.getElementsByClassName("editor")[0];
|
|
130
|
+
const run = elem.getElementsByClassName("run")[0];
|
|
131
|
+
const test = elem.getElementsByClassName("test")[0];
|
|
132
|
+
const output = elem.getElementsByClassName("output")[0];
|
|
133
|
+
const input = elem.getElementsByClassName("input")[0];
|
|
134
|
+
const outputBtn = elem.getElementsByClassName("output-btn")[0];
|
|
135
|
+
const inputBtn = elem.getElementsByClassName("input-btn")[0];
|
|
136
|
+
const id = elem.id;
|
|
137
|
+
let tests = [];
|
|
138
|
+
try {
|
|
139
|
+
tests = JSON.parse(atob(elem.getAttribute("data-tests")));
|
|
140
|
+
} catch (e) {}
|
|
141
|
+
|
|
142
|
+
function showInput() {
|
|
143
|
+
outputBtn.classList.remove("active");
|
|
144
|
+
inputBtn.classList.add("active");
|
|
145
|
+
output.classList.add("hidden");
|
|
146
|
+
input.classList.remove("hidden");
|
|
147
|
+
}
|
|
148
|
+
function showOutput() {
|
|
149
|
+
outputBtn.classList.add("active");
|
|
150
|
+
inputBtn.classList.remove("active");
|
|
151
|
+
output.classList.remove("hidden");
|
|
152
|
+
input.classList.add("hidden");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
outputBtn?.addEventListener("click", showOutput);
|
|
156
|
+
inputBtn?.addEventListener("click", showInput);
|
|
157
|
+
|
|
158
|
+
test?.addEventListener("click", async () => {
|
|
159
|
+
showOutput();
|
|
160
|
+
if (callback) return;
|
|
161
|
+
|
|
162
|
+
output.innerHTML = "";
|
|
163
|
+
|
|
164
|
+
const script = editor.value;
|
|
165
|
+
for (let test of tests) {
|
|
166
|
+
const testCode = test.code.replace("#SCRIPT#", script);
|
|
167
|
+
|
|
168
|
+
const heading = document.createElement("div");
|
|
169
|
+
console.log(test);
|
|
170
|
+
heading.innerHTML = `== Test ${test.name} ==`;
|
|
171
|
+
heading.classList.add("test-heading");
|
|
172
|
+
output.appendChild(heading);
|
|
173
|
+
|
|
174
|
+
await asyncRun(id, "test")(testCode, {})
|
|
175
|
+
.then(({ results, error }) => {
|
|
176
|
+
if (results) {
|
|
177
|
+
output.textContent += results;
|
|
178
|
+
} else if (error) {
|
|
179
|
+
output.textContent += error;
|
|
180
|
+
}
|
|
181
|
+
callback = null;
|
|
182
|
+
updateRunning(id, "test");
|
|
183
|
+
})
|
|
184
|
+
.catch((e) => {
|
|
185
|
+
output.textContent = `Error: ${e}`;
|
|
186
|
+
console.log(e);
|
|
187
|
+
callback = null;
|
|
188
|
+
updateRunning(id, "test");
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
run?.addEventListener("click", async () => {
|
|
194
|
+
showOutput();
|
|
195
|
+
if (callback) return;
|
|
196
|
+
|
|
197
|
+
const script = editor.value;
|
|
198
|
+
output.innerHTML = "";
|
|
199
|
+
asyncRun(id, "run")(script, {
|
|
200
|
+
inputs: input.value.split("\n"),
|
|
83
201
|
})
|
|
84
|
-
|
|
85
|
-
|
|
202
|
+
.then(({ results, error }) => {
|
|
203
|
+
if (results) {
|
|
204
|
+
output.textContent += results;
|
|
205
|
+
} else if (error) {
|
|
206
|
+
output.textContent += error;
|
|
207
|
+
}
|
|
208
|
+
callback = null;
|
|
209
|
+
updateRunning(id, "run");
|
|
210
|
+
})
|
|
211
|
+
.catch((e) => {
|
|
212
|
+
output.textContent = `Error: ${e}`;
|
|
213
|
+
console.log(e);
|
|
214
|
+
callback = null;
|
|
215
|
+
updateRunning(id, "run");
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const observer = new MutationObserver((mutations) => {
|
|
222
|
+
mutations.forEach((mutation) => {
|
|
223
|
+
if (mutation.addedNodes.length) {
|
|
224
|
+
mutation.addedNodes.forEach((node) => {
|
|
225
|
+
if (node.type === 1 && node.classList?.contains("directive-pyide")) {
|
|
226
|
+
init(node);
|
|
227
|
+
}
|
|
86
228
|
});
|
|
229
|
+
}
|
|
87
230
|
});
|
|
88
|
-
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
234
|
+
|
|
235
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
236
|
+
init(document);
|
|
237
|
+
});
|
|
89
238
|
})();
|
|
@@ -14,15 +14,30 @@
|
|
|
14
14
|
|
|
15
15
|
.directive-pyide .container {
|
|
16
16
|
width: 100%;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
height: 200px;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.directive-pyide .output,
|
|
24
|
+
.directive-pyide .input {
|
|
25
|
+
white-space: pre;
|
|
26
|
+
height: 100%;
|
|
17
27
|
border: 1px solid var(--color-spacer);
|
|
18
28
|
border-radius: 8px;
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
border-top-left-radius: 0;
|
|
30
|
+
border-top-right-radius: 0;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
.directive-pyide .output {
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
padding: 16px;
|
|
35
|
+
margin-bottom: 0px;
|
|
36
|
+
font-family: hyperbook-monospace, monospace;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.directive-pyide .hidden {
|
|
40
|
+
display: none;
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
.directive-pyide .editor-container {
|
|
@@ -41,26 +56,52 @@
|
|
|
41
56
|
flex: 1;
|
|
42
57
|
}
|
|
43
58
|
|
|
44
|
-
.directive-pyide
|
|
45
|
-
|
|
59
|
+
.directive-pyide .buttons {
|
|
60
|
+
display: flex;
|
|
46
61
|
border: 1px solid var(--color-spacer);
|
|
47
62
|
border-radius: 8px;
|
|
48
63
|
border-bottom: none;
|
|
49
64
|
border-bottom-left-radius: 0;
|
|
50
65
|
border-bottom-right-radius: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.directive-pyide .test-heading {
|
|
69
|
+
font-size: 1.1em;
|
|
70
|
+
font-weight: bold;
|
|
71
|
+
text-decoration: underline;
|
|
72
|
+
margin-top: 8px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.directive-pyide .test-heading:first-of-type {
|
|
76
|
+
margin-top: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.directive-pyide button {
|
|
80
|
+
flex: 1;
|
|
81
|
+
padding: 8px 16px;
|
|
82
|
+
border: none;
|
|
83
|
+
border-right: 1px solid var(--color-spacer);
|
|
51
84
|
background-color: var(--color--background);
|
|
52
85
|
color: var(--color-text);
|
|
53
86
|
cursor: pointer;
|
|
54
87
|
}
|
|
55
88
|
|
|
89
|
+
.directive-pyide .buttons:last-child {
|
|
90
|
+
border-right: none;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.directive-pyide button.active {
|
|
94
|
+
background-color: var(--color-spacer);
|
|
95
|
+
}
|
|
96
|
+
|
|
56
97
|
.directive-pyide button:hover {
|
|
57
98
|
background-color: var(--color-spacer);
|
|
58
99
|
}
|
|
59
100
|
|
|
60
101
|
.directive-pyide button.running {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
102
|
+
pointer-events: none;
|
|
103
|
+
cursor: not-allowed;
|
|
104
|
+
opacity: 0.5;
|
|
64
105
|
}
|
|
65
106
|
|
|
66
107
|
@media screen and (min-width: 1024px) {
|
|
@@ -69,7 +110,7 @@
|
|
|
69
110
|
height: calc(100dvh - 128px);
|
|
70
111
|
|
|
71
112
|
.output {
|
|
72
|
-
|
|
113
|
+
height: 100%;
|
|
73
114
|
}
|
|
74
115
|
|
|
75
116
|
.container {
|
|
@@ -3,40 +3,84 @@
|
|
|
3
3
|
// and `.wasm` files as well:
|
|
4
4
|
importScripts("https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js");
|
|
5
5
|
|
|
6
|
+
class StdinHandler {
|
|
7
|
+
constructor(results, options) {
|
|
8
|
+
this.results = results;
|
|
9
|
+
this.idx = 0;
|
|
10
|
+
Object.assign(this, options);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
stdin() {
|
|
14
|
+
return this.results[this.idx++];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
async function loadPyodideAndPackages() {
|
|
7
|
-
|
|
19
|
+
var pyodide = await loadPyodide();
|
|
20
|
+
self.pyodide = pyodide;
|
|
8
21
|
await self.pyodide.loadPackage([]);
|
|
9
22
|
}
|
|
10
23
|
let pyodideReadyPromise = loadPyodideAndPackages();
|
|
11
24
|
|
|
12
|
-
self.onmessage = async (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
self.onmessage = async ({ data: { id, type, payload } }) => {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case "run": {
|
|
28
|
+
// make sure loading is done
|
|
29
|
+
await pyodideReadyPromise;
|
|
30
|
+
// Don't bother yet with this line, suppose our API is built in such a way:
|
|
31
|
+
const { python, inputs, ...context } = payload;
|
|
32
|
+
// The worker copies the context in its own "memory" (an object mapping name to values)
|
|
33
|
+
for (const key of Object.keys(context)) {
|
|
34
|
+
self[key] = context[key];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
self.pyodide.setStdin(new StdinHandler(inputs));
|
|
38
|
+
|
|
39
|
+
self.pyodide.setStdout({
|
|
40
|
+
batched: (msg) => {
|
|
41
|
+
self.postMessage({ id, type: "stdout", payload: msg });
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
self.pyodide.setStderr({
|
|
46
|
+
batched: (msg) => {
|
|
47
|
+
self.postMessage({ id, type: "stderr", payload: msg });
|
|
48
|
+
},
|
|
49
|
+
});
|
|
21
50
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
// Now is the easy part, the one that is similar to working in the main thread:
|
|
52
|
+
const filename = "<exec>";
|
|
53
|
+
try {
|
|
54
|
+
await self.pyodide.loadPackagesFromImports(python);
|
|
55
|
+
const dict = self.pyodide.globals.get("dict");
|
|
56
|
+
const globals = dict();
|
|
57
|
+
let results = await self.pyodide.runPythonAsync(python, {
|
|
58
|
+
globals,
|
|
59
|
+
locals: globals,
|
|
60
|
+
filename,
|
|
61
|
+
});
|
|
62
|
+
globals.destroy();
|
|
63
|
+
dict.destroy();
|
|
64
|
+
self.postMessage({ type: "success", id, payload: results });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// clean up trackback
|
|
67
|
+
let message = error.message;
|
|
68
|
+
if (message.startsWith("Traceback")) {
|
|
69
|
+
const lines = message?.split("\n") || [];
|
|
70
|
+
const i = lines.findIndex((line) => line.includes(filename));
|
|
71
|
+
message = lines[0] + "\n" + lines.slice(i).join("\n");
|
|
72
|
+
self.postMessage({ type: "error", payload: message, id });
|
|
73
|
+
}
|
|
74
|
+
self.postMessage({ type: "error", payload: message, id });
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "setInterruptBuffer": {
|
|
79
|
+
const { interruptBuffer } = payload;
|
|
80
|
+
// make sure loading is done
|
|
81
|
+
await pyodideReadyPromise;
|
|
82
|
+
self.pyodide.setInterruptBuffer(interruptBuffer);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
41
85
|
}
|
|
42
86
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
hyperbook.scratchblock = (function () {
|
|
2
|
-
|
|
2
|
+
const init = () => {
|
|
3
3
|
scratchblocks.renderMatching("pre.directive-scratchblock", {
|
|
4
4
|
style: "scratch3",
|
|
5
5
|
languages: [
|
|
@@ -24,7 +24,22 @@ hyperbook.scratchblock = (function () {
|
|
|
24
24
|
],
|
|
25
25
|
scale: 1,
|
|
26
26
|
});
|
|
27
|
-
}
|
|
27
|
+
};
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
30
|
+
init();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Observe for new elements
|
|
34
|
+
const observer = new MutationObserver((mutations) => {
|
|
35
|
+
mutations.forEach((mutation) => {
|
|
36
|
+
mutation.addedNodes.forEach((node) => {
|
|
37
|
+
if (node.nodeType === 1 && node.matches("pre.directive-scratchblock")) {
|
|
38
|
+
init();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
30
45
|
})();
|
|
@@ -50,15 +50,36 @@ hyperbook.slideshow = (function () {
|
|
|
50
50
|
window.hyperbook.slideshow.update(id);
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
const init = (root) => {
|
|
54
|
+
const slideshows = root.getElementsByClassName("slideshow");
|
|
55
|
+
for (let slideshow of slideshows) {
|
|
56
|
+
const id = slideshow.getAttribute("data-id");
|
|
57
|
+
update(id);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Initialize existing slideshows on document load
|
|
62
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
63
|
+
init(document);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Observe for new slideshows added to the DOM
|
|
67
|
+
const observer = new MutationObserver((mutations) => {
|
|
68
|
+
mutations.forEach((mutation) => {
|
|
69
|
+
mutation.addedNodes.forEach((node) => {
|
|
70
|
+
if (node.nodeType === 1) { // Element node
|
|
71
|
+
init(node);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
58
78
|
|
|
59
79
|
return {
|
|
60
80
|
update,
|
|
61
81
|
moveBy,
|
|
62
82
|
setActive,
|
|
83
|
+
init,
|
|
63
84
|
};
|
|
64
85
|
})();
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
hyperbook.tabs = (function () {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
const init = (root) => {
|
|
3
|
+
let allTabs = root.querySelectorAll(".directive-tabs .tab[data-tabs-id]");
|
|
4
|
+
allTabs.forEach((tab) =>
|
|
5
|
+
tab.addEventListener("click", () => {
|
|
6
|
+
selectTab(
|
|
7
|
+
tab.getAttribute("data-tabs-id"),
|
|
8
|
+
tab.getAttribute("data-tab-id"),
|
|
9
|
+
);
|
|
10
|
+
}),
|
|
11
|
+
);
|
|
12
|
+
};
|
|
12
13
|
|
|
13
14
|
function selectTab(tabsId, tabId) {
|
|
14
15
|
let relevantTabButtons = document.querySelectorAll(
|
|
@@ -31,7 +32,23 @@ hyperbook.tabs = (function () {
|
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
init(document.body);
|
|
36
|
+
|
|
37
|
+
// Observe for new tabs added to the DOM
|
|
38
|
+
const observer = new MutationObserver((mutations) => {
|
|
39
|
+
mutations.forEach((mutation) => {
|
|
40
|
+
mutation.addedNodes.forEach((node) => {
|
|
41
|
+
if (node.nodeType === 1) { // Element node
|
|
42
|
+
init(node);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
49
|
+
|
|
34
50
|
return {
|
|
35
51
|
selectTab,
|
|
52
|
+
init,
|
|
36
53
|
};
|
|
37
54
|
})();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
/* PrismJS 1.29.0
|
|
2
|
-
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
|
2
|
+
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+java+plsql+python+sql&plugins=line-numbers+toolbar+copy-to-clipboard+download-button */
|
|
3
3
|
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
|
4
|
+
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
|
|
5
|
+
div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}
|