@farberg/reveal-template 1.1.18 → 1.1.20
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/css/dhbw-print.css +6 -1
- package/css/dhbw.css +127 -34
- package/docs/00 - Introduction.md +18 -6
- package/docs/k8s-deployment.cast +94 -0
- package/docs/local-dev.html +20 -9
- package/docs/npm.html +14 -7
- package/init-reveal.js +175 -92
- package/package.json +6 -6
- package/plugins/reveal-plugin-asciinema.js +5 -8
- package/plugins/reveal-plugin-dir-tree.js +339 -96
- package/plugins/reveal-plugin-show-code-snippets.js +30 -5
- package/plugins/reveal-plugin-show-qr-code.js +6 -7
- package/scripts/generate_toc.js +1 -0
|
@@ -1,154 +1,397 @@
|
|
|
1
1
|
/*
|
|
2
2
|
Create a zip from existing files for download and display a tree of files
|
|
3
3
|
|
|
4
|
-
<pre class="dirtree"
|
|
4
|
+
Attributes on <pre class="dirtree">:
|
|
5
|
+
data-zipname="archive.zip" — name of the downloaded zip (default: download.zip)
|
|
6
|
+
data-line-height="1.6" — line spacing (default: 1.85)
|
|
7
|
+
data-width="auto" — container width, e.g. "400px", "60%", "auto" (default: auto)
|
|
8
|
+
|
|
9
|
+
<pre class="dirtree" data-zipname="k8s-Exercise.zip" data-line-height="1.6" data-width="480px">
|
|
5
10
|
code/k8s-demo-app/01-mariadb-deployment.yaml
|
|
6
11
|
code/k8s-demo-app/02-k8s-mariadb-service.yaml
|
|
7
|
-
code/k8s-demo-app/03-memcached-deployment-and-service.yaml
|
|
8
|
-
code/k8s-demo-app/04-app-deployment-minikube.yaml
|
|
9
|
-
code/k8s-demo-app/04-app-service-and-ingress.yaml
|
|
10
12
|
code/k8s-demo-app/Dockerfile
|
|
11
13
|
code/k8s-demo-app/package.json
|
|
12
|
-
code/k8s-demo-app/server.js
|
|
13
14
|
</pre>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
|
-
const
|
|
17
|
+
const dirTreeFactory = ({ zip, strToU8 }) => ({
|
|
20
18
|
id: 'dir_tree',
|
|
21
19
|
init: (deck) => {
|
|
22
20
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
function injectStyles() {
|
|
22
|
+
if (document.getElementById('dirtree-styles')) return;
|
|
23
|
+
const style = document.createElement('style');
|
|
24
|
+
style.id = 'dirtree-styles';
|
|
25
|
+
style.textContent = `
|
|
26
|
+
.dirtree-container {
|
|
27
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace;
|
|
28
|
+
background: transparent;
|
|
29
|
+
border-radius: 10px;
|
|
30
|
+
padding: 38px 18px 14px 18px;
|
|
31
|
+
border: 1px solid #d0d7de;
|
|
32
|
+
display: inline-block;
|
|
33
|
+
min-width: 280px;
|
|
34
|
+
max-width: 90%;
|
|
35
|
+
text-align: left;
|
|
36
|
+
position: relative !important;
|
|
37
|
+
}
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
ul.dirtree {
|
|
40
|
+
list-style: none !important;
|
|
41
|
+
padding: 0 !important;
|
|
42
|
+
margin: 0 !important;
|
|
43
|
+
}
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
ul.dirtree ul.dirtree {
|
|
46
|
+
padding-left: 0 !important;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
li.dirtree {
|
|
50
|
+
color: #1f2328;
|
|
51
|
+
position: relative;
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
li.dirtree::before,
|
|
58
|
+
li.dirtree::after {
|
|
59
|
+
display: none !important;
|
|
60
|
+
content: none !important;
|
|
61
|
+
border: none !important;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
li.dirtree.root {
|
|
65
|
+
color: #0969da;
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
margin-bottom: 2px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.dirtree-dir-name {
|
|
71
|
+
color: #0969da;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
a.dirtree-file-link {
|
|
75
|
+
color: #116329;
|
|
76
|
+
text-decoration: none;
|
|
77
|
+
transition: color 0.15s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
a.dirtree-file-link:hover {
|
|
81
|
+
color: #6639ba;
|
|
82
|
+
text-decoration: underline;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Copy button — hidden by default, shown on li hover */
|
|
86
|
+
.dirtree-copy-btn {
|
|
87
|
+
display: inline-flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
margin-left: 8px;
|
|
90
|
+
padding: 0px 6px;
|
|
91
|
+
background: transparent;
|
|
92
|
+
border: 1px solid #d0d7de;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
color: #57606a;
|
|
95
|
+
font-size: 0.78em;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
opacity: 0 !important;
|
|
98
|
+
pointer-events: none !important;
|
|
99
|
+
transition: opacity 0.15s, background 0.15s, color 0.15s;
|
|
100
|
+
user-select: none;
|
|
101
|
+
line-height: 1.6;
|
|
102
|
+
font-family: inherit;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
li.dirtree:hover > .dirtree-copy-btn {
|
|
106
|
+
opacity: 1 !important;
|
|
107
|
+
pointer-events: auto !important;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.dirtree-copy-btn:hover {
|
|
111
|
+
background: #f6f8fa;
|
|
112
|
+
border-color: #8c959f;
|
|
113
|
+
color: #1f2328;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.dirtree-copy-btn.copied {
|
|
117
|
+
color: #116329;
|
|
118
|
+
border-color: #116329;
|
|
119
|
+
background: #dafbe1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Download button */
|
|
123
|
+
.dirtree-download-btn {
|
|
124
|
+
position: absolute !important;
|
|
125
|
+
top: 10px !important;
|
|
126
|
+
right: 14px !important;
|
|
127
|
+
padding: 2px 10px;
|
|
128
|
+
background: transparent;
|
|
129
|
+
border: 1px solid #d0d7de;
|
|
130
|
+
border-radius: 6px;
|
|
131
|
+
color: #cf222e !important;
|
|
132
|
+
font-size: 0.85em;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
text-decoration: none !important;
|
|
135
|
+
transition: background 0.15s, border-color 0.15s;
|
|
136
|
+
font-family: inherit;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.dirtree-download-btn:hover {
|
|
140
|
+
background: #fff5f5 !important;
|
|
141
|
+
border-color: #cf222e !important;
|
|
142
|
+
color: #cf222e !important;
|
|
143
|
+
}
|
|
33
144
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
145
|
+
.dirtree-prefix {
|
|
146
|
+
color: #8c959f;
|
|
147
|
+
user-select: none;
|
|
148
|
+
white-space: pre;
|
|
149
|
+
flex-shrink: 0;
|
|
37
150
|
}
|
|
38
151
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
152
|
+
.dirtree-icon {
|
|
153
|
+
margin-right: 4px;
|
|
154
|
+
flex-shrink: 0;
|
|
42
155
|
}
|
|
156
|
+
`;
|
|
157
|
+
document.head.appendChild(style);
|
|
158
|
+
}
|
|
43
159
|
|
|
44
|
-
|
|
160
|
+
function getFileIcon(name, ext) {
|
|
161
|
+
if (name.toLowerCase() === 'dockerfile') return '🐳';
|
|
162
|
+
const icons = {
|
|
163
|
+
js: '📜', ts: '📜', jsx: '📜', tsx: '📜',
|
|
164
|
+
json: '📋', yaml: '📋', yml: '📋', toml: '📋', xml: '📋',
|
|
165
|
+
md: '📝', txt: '📝', rst: '📝',
|
|
166
|
+
html: '🌐', css: '🎨', scss: '🎨',
|
|
167
|
+
sh: '⚙️', bash: '⚙️', zsh: '⚙️',
|
|
168
|
+
py: '🐍', rb: '💎', go: '🔷', rs: '🦀', java: '☕',
|
|
169
|
+
png: '🖼️', jpg: '🖼️', jpeg: '🖼️', svg: '🖼️', gif: '🖼️',
|
|
170
|
+
zip: '📦', tar: '📦', gz: '📦',
|
|
171
|
+
sql: '🗄️',
|
|
172
|
+
};
|
|
173
|
+
return icons[ext] || '📄';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function writeToClipboard(text) {
|
|
177
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
178
|
+
return navigator.clipboard.writeText(text);
|
|
45
179
|
}
|
|
180
|
+
// Fallback for HTTP / non-secure contexts
|
|
181
|
+
const ta = document.createElement('textarea');
|
|
182
|
+
ta.value = text;
|
|
183
|
+
ta.style.cssText = 'position:fixed;opacity:0;pointer-events:none';
|
|
184
|
+
document.body.appendChild(ta);
|
|
185
|
+
ta.focus();
|
|
186
|
+
ta.select();
|
|
187
|
+
try {
|
|
188
|
+
document.execCommand('copy');
|
|
189
|
+
return Promise.resolve();
|
|
190
|
+
} catch (e) {
|
|
191
|
+
return Promise.reject(e);
|
|
192
|
+
} finally {
|
|
193
|
+
document.body.removeChild(ta);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
46
196
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
197
|
+
function makeCopyButton(filePath) {
|
|
198
|
+
const btn = document.createElement('button');
|
|
199
|
+
btn.classList.add('dirtree-copy-btn');
|
|
200
|
+
btn.title = 'Copy file contents';
|
|
201
|
+
btn.textContent = 'copy';
|
|
202
|
+
btn.addEventListener('click', e => {
|
|
203
|
+
e.preventDefault();
|
|
204
|
+
btn.textContent = '…';
|
|
205
|
+
fetch(filePath, { credentials: 'include' })
|
|
206
|
+
.then(r => {
|
|
207
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
208
|
+
return r.text();
|
|
209
|
+
})
|
|
210
|
+
.then(text => writeToClipboard(text))
|
|
211
|
+
.then(() => {
|
|
212
|
+
btn.textContent = '✓ copied';
|
|
213
|
+
btn.classList.add('copied');
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
btn.textContent = 'copy';
|
|
216
|
+
btn.classList.remove('copied');
|
|
217
|
+
}, 1500);
|
|
218
|
+
})
|
|
219
|
+
.catch(() => {
|
|
220
|
+
btn.textContent = '✗ error';
|
|
221
|
+
setTimeout(() => { btn.textContent = 'copy'; }, 1500);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
return btn;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getFileList(el) {
|
|
228
|
+
return el.innerText.split('\n').filter(e => e.trim().length > 0);
|
|
229
|
+
}
|
|
51
230
|
|
|
52
|
-
|
|
53
|
-
|
|
231
|
+
function createTree(files) {
|
|
232
|
+
function getPath(tree, path) {
|
|
233
|
+
let node = tree[path[0]];
|
|
234
|
+
if (!node) { node = {}; tree[path[0]] = node; }
|
|
235
|
+
const rest = path.slice(1);
|
|
236
|
+
return rest.length > 0 ? getPath(node, rest) : node;
|
|
237
|
+
}
|
|
238
|
+
const tree = {};
|
|
239
|
+
for (const file of files) {
|
|
240
|
+
const parts = file.split('/');
|
|
241
|
+
const filename = parts.pop();
|
|
242
|
+
getPath(tree, parts)[filename] = filename;
|
|
54
243
|
}
|
|
55
|
-
return tree
|
|
244
|
+
return tree;
|
|
245
|
+
}
|
|
56
246
|
|
|
247
|
+
function downloadBlob(blob, filename) {
|
|
248
|
+
const url = URL.createObjectURL(blob);
|
|
249
|
+
const a = document.createElement('a');
|
|
250
|
+
a.href = url;
|
|
251
|
+
a.download = filename;
|
|
252
|
+
document.body.appendChild(a);
|
|
253
|
+
a.click();
|
|
254
|
+
document.body.removeChild(a);
|
|
255
|
+
URL.revokeObjectURL(url);
|
|
57
256
|
}
|
|
58
257
|
|
|
59
258
|
function download(files, zipName) {
|
|
60
|
-
Promise.all(files.map(
|
|
61
|
-
.then(
|
|
62
|
-
|
|
63
|
-
const hasUnauthorized = promises.some(p => p.status === 401);
|
|
64
|
-
if (hasUnauthorized) {
|
|
65
|
-
console.log("Authentication required (dir-tree), reloading page");
|
|
259
|
+
Promise.all(files.map(f => fetch(f, { credentials: 'include' })))
|
|
260
|
+
.then(responses => {
|
|
261
|
+
if (responses.some(r => r.status === 401)) {
|
|
66
262
|
window.location.reload();
|
|
67
263
|
return;
|
|
68
264
|
}
|
|
69
|
-
return Promise.all(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
78
|
-
|
|
265
|
+
return Promise.all(responses.map(r => r.text())).then(values => {
|
|
266
|
+
const entries = {};
|
|
267
|
+
values.forEach((text, i) => { entries[files[i]] = strToU8(text); });
|
|
268
|
+
zip(entries, (err, data) => {
|
|
269
|
+
if (err) { console.error('zip failed', err); return; }
|
|
270
|
+
downloadBlob(new Blob([data], { type: 'application/zip' }), zipName);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
79
274
|
}
|
|
80
275
|
|
|
81
|
-
function displayTree(el, treeNode, path,
|
|
82
|
-
|
|
83
|
-
for (let name of Object.getOwnPropertyNames(treeNode)) {
|
|
84
|
-
const o = treeNode[name]
|
|
85
|
-
const isDir = typeof o === "object"
|
|
276
|
+
function displayTree(el, treeNode, path, isRoot, files, zipName, ancestorLast) {
|
|
277
|
+
const names = Object.getOwnPropertyNames(treeNode);
|
|
86
278
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
279
|
+
names.forEach((name, index) => {
|
|
280
|
+
const o = treeNode[name];
|
|
281
|
+
const isDir = typeof o === 'object';
|
|
282
|
+
const isLast = index === names.length - 1;
|
|
91
283
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
li.innerHTML = name + " "
|
|
284
|
+
const li = document.createElement('li');
|
|
285
|
+
li.classList.add('dirtree');
|
|
95
286
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
a.innerHTML = `(download ${zipName})`
|
|
99
|
-
a.href = "#zip/" + zipName
|
|
100
|
-
a.style = "color: #e10217";
|
|
101
|
-
a.addEventListener('click', e => {
|
|
102
|
-
e.preventDefault()
|
|
103
|
-
download(files, zipName)
|
|
104
|
-
})
|
|
287
|
+
if (isRoot) {
|
|
288
|
+
li.classList.add('root');
|
|
105
289
|
|
|
290
|
+
const icon = document.createElement('span');
|
|
291
|
+
icon.classList.add('dirtree-icon');
|
|
292
|
+
icon.textContent = '📁';
|
|
106
293
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
294
|
+
const nameSpan = document.createElement('span');
|
|
295
|
+
nameSpan.classList.add('dirtree-dir-name');
|
|
296
|
+
nameSpan.textContent = name;
|
|
110
297
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
298
|
+
li.appendChild(icon);
|
|
299
|
+
li.appendChild(nameSpan);
|
|
300
|
+
el.appendChild(li);
|
|
114
301
|
|
|
115
|
-
|
|
302
|
+
const ul = document.createElement('ul');
|
|
303
|
+
ul.classList.add('dirtree');
|
|
304
|
+
li.appendChild(ul);
|
|
305
|
+
displayTree(ul, o, `${path}/${name}`, false, files, zipName, []);
|
|
116
306
|
|
|
117
307
|
} else {
|
|
308
|
+
const contPrefix = ancestorLast.map(last => last ? ' ' : '│ ').join('');
|
|
309
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
310
|
+
|
|
311
|
+
const prefixSpan = document.createElement('span');
|
|
312
|
+
prefixSpan.classList.add('dirtree-prefix');
|
|
313
|
+
prefixSpan.textContent = contPrefix + connector;
|
|
314
|
+
li.appendChild(prefixSpan);
|
|
315
|
+
|
|
316
|
+
if (isDir) {
|
|
317
|
+
const icon = document.createElement('span');
|
|
318
|
+
icon.classList.add('dirtree-icon');
|
|
319
|
+
icon.textContent = '📂';
|
|
118
320
|
|
|
119
|
-
|
|
120
|
-
|
|
321
|
+
const nameSpan = document.createElement('span');
|
|
322
|
+
nameSpan.classList.add('dirtree-dir-name');
|
|
323
|
+
nameSpan.textContent = name;
|
|
121
324
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
325
|
+
li.appendChild(icon);
|
|
326
|
+
li.appendChild(nameSpan);
|
|
327
|
+
el.appendChild(li);
|
|
125
328
|
|
|
126
|
-
|
|
329
|
+
const ul = document.createElement('ul');
|
|
330
|
+
ul.classList.add('dirtree');
|
|
331
|
+
li.appendChild(ul);
|
|
332
|
+
displayTree(ul, o, `${path}/${name}`, false, files, zipName, [...ancestorLast, isLast]);
|
|
333
|
+
|
|
334
|
+
} else {
|
|
335
|
+
const ext = name.includes('.') ? name.split('.').pop().toLowerCase() : '';
|
|
336
|
+
|
|
337
|
+
const icon = document.createElement('span');
|
|
338
|
+
icon.classList.add('dirtree-icon');
|
|
339
|
+
icon.textContent = getFileIcon(name, ext);
|
|
340
|
+
|
|
341
|
+
const finalPath = `${path}/${o}`.replace(/^\//, '');
|
|
342
|
+
const a = document.createElement('a');
|
|
343
|
+
a.classList.add('dirtree-file-link');
|
|
344
|
+
a.href = finalPath;
|
|
345
|
+
a.textContent = o;
|
|
346
|
+
|
|
347
|
+
li.appendChild(icon);
|
|
348
|
+
li.appendChild(a);
|
|
349
|
+
li.appendChild(makeCopyButton(finalPath));
|
|
350
|
+
el.appendChild(li);
|
|
351
|
+
}
|
|
127
352
|
}
|
|
128
|
-
}
|
|
353
|
+
});
|
|
129
354
|
}
|
|
130
355
|
|
|
131
356
|
deck.on('ready', () => {
|
|
132
|
-
|
|
133
|
-
const zipName = el.getAttribute("data-zipname") || "download.zip"
|
|
134
|
-
const stripPrefix = el.getAttribute("data-strip-prefix")
|
|
357
|
+
injectStyles();
|
|
135
358
|
|
|
136
|
-
|
|
137
|
-
const
|
|
359
|
+
for (const el of deck.getRevealElement().querySelectorAll('pre.dirtree')) {
|
|
360
|
+
const zipName = el.getAttribute('data-zipname') || 'download.zip';
|
|
361
|
+
const lineHeight = el.getAttribute('data-line-height') || '1.85';
|
|
362
|
+
const width = el.getAttribute('data-width') || 'auto';
|
|
138
363
|
|
|
139
|
-
const
|
|
140
|
-
|
|
364
|
+
const files = getFileList(el);
|
|
365
|
+
const tree = createTree(files);
|
|
141
366
|
|
|
142
|
-
|
|
367
|
+
const container = document.createElement('div');
|
|
368
|
+
container.classList.add('dirtree-container');
|
|
369
|
+
container.style.lineHeight = lineHeight;
|
|
370
|
+
if (width !== 'auto') container.style.width = width;
|
|
143
371
|
|
|
144
|
-
|
|
145
|
-
|
|
372
|
+
// Forward font-size (and other inline styles) from the <pre>
|
|
373
|
+
if (el.getAttribute('style')) container.setAttribute('style', el.getAttribute('style'));
|
|
374
|
+
// Re-apply data-driven styles on top (inline style from <pre> must not override these)
|
|
375
|
+
container.style.lineHeight = lineHeight;
|
|
376
|
+
if (width !== 'auto') container.style.width = width;
|
|
146
377
|
|
|
147
|
-
|
|
378
|
+
const btn = document.createElement('a');
|
|
379
|
+
btn.classList.add('dirtree-download-btn');
|
|
380
|
+
btn.textContent = '⬇ ' + zipName;
|
|
381
|
+
btn.href = '#zip/' + zipName;
|
|
382
|
+
btn.addEventListener('click', e => { e.preventDefault(); download(files, zipName); });
|
|
383
|
+
container.appendChild(btn);
|
|
148
384
|
|
|
385
|
+
const ul = document.createElement('ul');
|
|
386
|
+
ul.classList.add('dirtree');
|
|
387
|
+
container.appendChild(ul);
|
|
149
388
|
|
|
150
|
-
|
|
151
|
-
}
|
|
389
|
+
displayTree(ul, tree, '', true, files, zipName, []);
|
|
152
390
|
|
|
391
|
+
el.parentElement.replaceChild(container, el);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
})
|
|
153
396
|
|
|
154
|
-
export default
|
|
397
|
+
export default dirTreeFactory;
|
|
@@ -105,21 +105,43 @@ function injectStyles() {
|
|
|
105
105
|
document.head.appendChild(style);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
function writeToClipboard(text) {
|
|
109
|
+
if (navigator.clipboard && navigator.clipboard.writeText)
|
|
110
|
+
return navigator.clipboard.writeText(text);
|
|
111
|
+
const ta = document.createElement('textarea');
|
|
112
|
+
ta.value = text;
|
|
113
|
+
ta.style.cssText = 'position:fixed;opacity:0;pointer-events:none';
|
|
114
|
+
document.body.appendChild(ta);
|
|
115
|
+
ta.focus();
|
|
116
|
+
ta.select();
|
|
117
|
+
try {
|
|
118
|
+
document.execCommand('copy');
|
|
119
|
+
return Promise.resolve();
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return Promise.reject(e);
|
|
122
|
+
} finally {
|
|
123
|
+
document.body.removeChild(ta);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
function addCopyButton(preEl) {
|
|
109
128
|
preEl.classList.add('with-copy-btn');
|
|
110
129
|
const btn = document.createElement('button');
|
|
111
130
|
btn.className = 'copy-code-btn';
|
|
112
|
-
btn.textContent = '
|
|
131
|
+
btn.textContent = 'copy';
|
|
113
132
|
btn.addEventListener('click', () => {
|
|
114
133
|
const code = preEl.querySelector('code');
|
|
115
134
|
const text = code ? code.innerText : preEl.innerText;
|
|
116
|
-
|
|
117
|
-
btn.textContent = '
|
|
135
|
+
writeToClipboard(text).then(() => {
|
|
136
|
+
btn.textContent = '✓ copied';
|
|
118
137
|
btn.classList.add('copied');
|
|
119
138
|
setTimeout(() => {
|
|
120
|
-
btn.textContent = '
|
|
139
|
+
btn.textContent = 'copy';
|
|
121
140
|
btn.classList.remove('copied');
|
|
122
141
|
}, 1500);
|
|
142
|
+
}).catch(() => {
|
|
143
|
+
btn.textContent = '✗ error';
|
|
144
|
+
setTimeout(() => { btn.textContent = 'copy'; }, 1500);
|
|
123
145
|
});
|
|
124
146
|
});
|
|
125
147
|
preEl.appendChild(btn);
|
|
@@ -148,6 +170,7 @@ export default () => {
|
|
|
148
170
|
for (let el of deck.getRevealElement().querySelectorAll("pre code[class]")) {
|
|
149
171
|
if (!el.classList.contains("mermaid")) {
|
|
150
172
|
highlightPlugin.hljs.highlightElement(el);
|
|
173
|
+
addCopyButton(el.closest('pre'));
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
176
|
|
|
@@ -191,8 +214,10 @@ export default () => {
|
|
|
191
214
|
code = outdent(code)
|
|
192
215
|
|
|
193
216
|
const newEl = showCode(el, language, code, showLink ? url : null, outdent)
|
|
194
|
-
if (language !== 'mermaid')
|
|
217
|
+
if (language !== 'mermaid') {
|
|
195
218
|
highlightPlugin.hljs.highlightElement(newEl.querySelector('code') ?? newEl)
|
|
219
|
+
addCopyButton(newEl)
|
|
220
|
+
}
|
|
196
221
|
} catch (err) {
|
|
197
222
|
console.error(`show-code-snippets: failed to load ${url}:`, err)
|
|
198
223
|
showError(el, `Failed to load ${url}: ${err.message}`)
|
|
@@ -19,18 +19,17 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
function showLinkToSlidesAndQrCode(deck, url) {
|
|
22
|
+
function showLinkToSlidesAndQrCode(QrCreator, deck, url) {
|
|
23
23
|
if (url === undefined)
|
|
24
24
|
return
|
|
25
25
|
|
|
26
26
|
let qrElements = deck.getSlidesElement().getElementsByClassName('qrcodeforslides')
|
|
27
27
|
for (let qrel of qrElements) {
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
QrCreator.render({
|
|
30
30
|
text: url,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
31
|
+
size: 500,
|
|
32
|
+
}, qrel);
|
|
34
33
|
|
|
35
34
|
console.log("QR Code for slides generated", qrel, url)
|
|
36
35
|
}
|
|
@@ -43,7 +42,7 @@ function showLinkToSlidesAndQrCode(deck, url) {
|
|
|
43
42
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
export default () => {
|
|
45
|
+
export default (QrCreator) => {
|
|
47
46
|
return {
|
|
48
47
|
id: 'show_qr_code',
|
|
49
48
|
init: (deck) => {
|
|
@@ -60,7 +59,7 @@ export default () => {
|
|
|
60
59
|
return res.json();
|
|
61
60
|
})
|
|
62
61
|
.then(json => {
|
|
63
|
-
if (json) showLinkToSlidesAndQrCode(deck, json.homepage)
|
|
62
|
+
if (json) showLinkToSlidesAndQrCode(QrCreator, deck, json.homepage)
|
|
64
63
|
})
|
|
65
64
|
.catch(err => console.log("Error fetching info json", err))
|
|
66
65
|
else
|
package/scripts/generate_toc.js
CHANGED
|
@@ -17,6 +17,7 @@ let md_files = fs.readdirSync(".")
|
|
|
17
17
|
.filter(file => path.extname(file).toLowerCase() === ".md")
|
|
18
18
|
.filter(file => !path.basename(file, ".md").startsWith("__"))
|
|
19
19
|
.filter(file => path.basename(file, ".md") !== "README")
|
|
20
|
+
.filter(file => path.basename(file, ".md") !== "CLAUDE")
|
|
20
21
|
|
|
21
22
|
let generateTable = md_files.map(file => file.match(structuredNameRegexp)).reduce((a, b) => a && b, true)
|
|
22
23
|
|