@farberg/reveal-template 1.1.28 → 1.1.30
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/package.json
CHANGED
|
@@ -119,6 +119,231 @@ const dirTreeFactory = ({ zip, strToU8 }) => ({
|
|
|
119
119
|
background: #dafbe1;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/* Preview button — same hover behaviour as the copy button */
|
|
123
|
+
.dirtree-preview-btn {
|
|
124
|
+
display: inline-flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
margin-left: 6px;
|
|
127
|
+
padding: 0px 6px;
|
|
128
|
+
background: transparent;
|
|
129
|
+
border: 1px solid #d0d7de;
|
|
130
|
+
border-radius: 4px;
|
|
131
|
+
color: #57606a;
|
|
132
|
+
font-size: 0.78em;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
opacity: 0 !important;
|
|
135
|
+
pointer-events: none !important;
|
|
136
|
+
transition: opacity 0.15s, background 0.15s, color 0.15s;
|
|
137
|
+
user-select: none;
|
|
138
|
+
line-height: 1.6;
|
|
139
|
+
font-family: inherit;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
li.dirtree:hover > .dirtree-preview-btn {
|
|
143
|
+
opacity: 1 !important;
|
|
144
|
+
pointer-events: auto !important;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.dirtree-preview-btn:hover {
|
|
148
|
+
background: #f6f8fa;
|
|
149
|
+
border-color: #8c959f;
|
|
150
|
+
color: #1f2328;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Preview modal */
|
|
154
|
+
.dirtree-modal-overlay {
|
|
155
|
+
position: fixed;
|
|
156
|
+
inset: 0;
|
|
157
|
+
background: rgba(27, 31, 36, 0.55);
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: center;
|
|
161
|
+
z-index: 10000;
|
|
162
|
+
padding: 4vh 4vw;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.dirtree-modal {
|
|
166
|
+
background: #ffffff;
|
|
167
|
+
border: 1px solid #d0d7de;
|
|
168
|
+
border-radius: 10px;
|
|
169
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
170
|
+
display: flex;
|
|
171
|
+
flex-direction: column;
|
|
172
|
+
max-width: 1100px;
|
|
173
|
+
width: 100%;
|
|
174
|
+
max-height: 92vh;
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
text-align: left;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.dirtree-modal-header {
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: space-between;
|
|
183
|
+
gap: 12px;
|
|
184
|
+
padding: 10px 14px;
|
|
185
|
+
border-bottom: 1px solid #d0d7de;
|
|
186
|
+
background: #f6f8fa;
|
|
187
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.dirtree-modal-title {
|
|
191
|
+
color: #1f2328;
|
|
192
|
+
font-size: 0.95rem;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
word-break: break-all;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.dirtree-modal-actions {
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: 8px;
|
|
201
|
+
flex-shrink: 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.dirtree-modal-btn {
|
|
205
|
+
padding: 2px 10px;
|
|
206
|
+
background: transparent;
|
|
207
|
+
border: 1px solid #d0d7de;
|
|
208
|
+
border-radius: 6px;
|
|
209
|
+
color: #57606a;
|
|
210
|
+
font-size: 0.85rem;
|
|
211
|
+
cursor: pointer;
|
|
212
|
+
font-family: inherit;
|
|
213
|
+
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.dirtree-modal-btn:hover {
|
|
217
|
+
background: #ffffff;
|
|
218
|
+
border-color: #8c959f;
|
|
219
|
+
color: #1f2328;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.dirtree-modal-btn.copied {
|
|
223
|
+
color: #116329;
|
|
224
|
+
border-color: #116329;
|
|
225
|
+
background: #dafbe1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.dirtree-modal-body {
|
|
229
|
+
margin: 0;
|
|
230
|
+
padding: 0;
|
|
231
|
+
overflow: auto;
|
|
232
|
+
background: #ffffff;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.dirtree-modal-body pre {
|
|
236
|
+
margin: 0;
|
|
237
|
+
box-shadow: none;
|
|
238
|
+
background: transparent;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.dirtree-modal-body code {
|
|
242
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace;
|
|
243
|
+
/* Default size; the A−/A+ buttons override this via inline style. */
|
|
244
|
+
font-size: 16px;
|
|
245
|
+
line-height: 1.55;
|
|
246
|
+
color: #24292e;
|
|
247
|
+
white-space: pre;
|
|
248
|
+
tab-size: 4;
|
|
249
|
+
padding: 14px 16px;
|
|
250
|
+
display: block;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* ── Light syntax theme (GitHub), scoped to the preview modal ──
|
|
254
|
+
The slides use the global dark zenburn theme; these rules win by
|
|
255
|
+
specificity (.dirtree-modal-body .hljs-* beats .hljs-*) so only
|
|
256
|
+
the modal renders light. */
|
|
257
|
+
.dirtree-modal-body code.hljs {
|
|
258
|
+
background: transparent;
|
|
259
|
+
color: #24292e;
|
|
260
|
+
}
|
|
261
|
+
.dirtree-modal-body .hljs-comment,
|
|
262
|
+
.dirtree-modal-body .hljs-quote {
|
|
263
|
+
color: #6a737d;
|
|
264
|
+
}
|
|
265
|
+
.dirtree-modal-body .hljs-keyword,
|
|
266
|
+
.dirtree-modal-body .hljs-selector-tag,
|
|
267
|
+
.dirtree-modal-body .hljs-doctag {
|
|
268
|
+
color: #d73a49;
|
|
269
|
+
}
|
|
270
|
+
.dirtree-modal-body .hljs-string,
|
|
271
|
+
.dirtree-modal-body .hljs-meta .hljs-string,
|
|
272
|
+
.dirtree-modal-body .hljs-regexp {
|
|
273
|
+
color: #032f62;
|
|
274
|
+
}
|
|
275
|
+
.dirtree-modal-body .hljs-number,
|
|
276
|
+
.dirtree-modal-body .hljs-literal,
|
|
277
|
+
.dirtree-modal-body .hljs-attr,
|
|
278
|
+
.dirtree-modal-body .hljs-attribute,
|
|
279
|
+
.dirtree-modal-body .hljs-variable,
|
|
280
|
+
.dirtree-modal-body .hljs-template-variable {
|
|
281
|
+
color: #005cc5;
|
|
282
|
+
}
|
|
283
|
+
.dirtree-modal-body .hljs-title,
|
|
284
|
+
.dirtree-modal-body .hljs-section,
|
|
285
|
+
.dirtree-modal-body .hljs-name,
|
|
286
|
+
.dirtree-modal-body .hljs-selector-id,
|
|
287
|
+
.dirtree-modal-body .hljs-selector-class {
|
|
288
|
+
color: #6f42c1;
|
|
289
|
+
}
|
|
290
|
+
.dirtree-modal-body .hljs-built_in,
|
|
291
|
+
.dirtree-modal-body .hljs-type,
|
|
292
|
+
.dirtree-modal-body .hljs-symbol,
|
|
293
|
+
.dirtree-modal-body .hljs-bullet,
|
|
294
|
+
.dirtree-modal-body .hljs-link {
|
|
295
|
+
color: #e36209;
|
|
296
|
+
}
|
|
297
|
+
.dirtree-modal-body .hljs-meta {
|
|
298
|
+
color: #6a737d;
|
|
299
|
+
}
|
|
300
|
+
.dirtree-modal-body .hljs-emphasis { font-style: italic; }
|
|
301
|
+
.dirtree-modal-body .hljs-strong { font-weight: bold; }
|
|
302
|
+
|
|
303
|
+
/* ── Dark syntax theme (GitHub Dark), scoped + .dark modifier ──
|
|
304
|
+
Higher specificity than the light rules above so toggling the
|
|
305
|
+
.dark class on the body switches the whole modal. */
|
|
306
|
+
.dirtree-modal-body.dark { background: #0d1117; }
|
|
307
|
+
.dirtree-modal-body.dark code { color: #c9d1d9; }
|
|
308
|
+
.dirtree-modal-body.dark code.hljs { background: transparent; color: #c9d1d9; }
|
|
309
|
+
.dirtree-modal-body.dark .hljs-comment,
|
|
310
|
+
.dirtree-modal-body.dark .hljs-quote,
|
|
311
|
+
.dirtree-modal-body.dark .hljs-meta {
|
|
312
|
+
color: #8b949e;
|
|
313
|
+
}
|
|
314
|
+
.dirtree-modal-body.dark .hljs-keyword,
|
|
315
|
+
.dirtree-modal-body.dark .hljs-selector-tag,
|
|
316
|
+
.dirtree-modal-body.dark .hljs-doctag {
|
|
317
|
+
color: #ff7b72;
|
|
318
|
+
}
|
|
319
|
+
.dirtree-modal-body.dark .hljs-string,
|
|
320
|
+
.dirtree-modal-body.dark .hljs-meta .hljs-string,
|
|
321
|
+
.dirtree-modal-body.dark .hljs-regexp {
|
|
322
|
+
color: #a5d6ff;
|
|
323
|
+
}
|
|
324
|
+
.dirtree-modal-body.dark .hljs-number,
|
|
325
|
+
.dirtree-modal-body.dark .hljs-literal,
|
|
326
|
+
.dirtree-modal-body.dark .hljs-attr,
|
|
327
|
+
.dirtree-modal-body.dark .hljs-attribute,
|
|
328
|
+
.dirtree-modal-body.dark .hljs-variable,
|
|
329
|
+
.dirtree-modal-body.dark .hljs-template-variable {
|
|
330
|
+
color: #79c0ff;
|
|
331
|
+
}
|
|
332
|
+
.dirtree-modal-body.dark .hljs-title,
|
|
333
|
+
.dirtree-modal-body.dark .hljs-section,
|
|
334
|
+
.dirtree-modal-body.dark .hljs-name,
|
|
335
|
+
.dirtree-modal-body.dark .hljs-selector-id,
|
|
336
|
+
.dirtree-modal-body.dark .hljs-selector-class {
|
|
337
|
+
color: #d2a8ff;
|
|
338
|
+
}
|
|
339
|
+
.dirtree-modal-body.dark .hljs-built_in,
|
|
340
|
+
.dirtree-modal-body.dark .hljs-type,
|
|
341
|
+
.dirtree-modal-body.dark .hljs-symbol,
|
|
342
|
+
.dirtree-modal-body.dark .hljs-bullet,
|
|
343
|
+
.dirtree-modal-body.dark .hljs-link {
|
|
344
|
+
color: #ffa657;
|
|
345
|
+
}
|
|
346
|
+
|
|
122
347
|
/* Download button */
|
|
123
348
|
.dirtree-download-btn {
|
|
124
349
|
position: absolute !important;
|
|
@@ -224,6 +449,194 @@ const dirTreeFactory = ({ zip, strToU8 }) => ({
|
|
|
224
449
|
return btn;
|
|
225
450
|
}
|
|
226
451
|
|
|
452
|
+
// Reveal's highlight plugin owns the hljs instance (window.hljs is not set).
|
|
453
|
+
// Returns it if the plugin is loaded, else null.
|
|
454
|
+
function getHljs() {
|
|
455
|
+
const plugin = deck.getPlugin('highlight');
|
|
456
|
+
return (plugin && plugin.hljs) || null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Maps file extensions to highlight.js language classes so the modal can
|
|
460
|
+
// syntax-highlight the preview when reveal's highlight plugin is loaded.
|
|
461
|
+
// Unknown extensions fall back to plain text.
|
|
462
|
+
function hljsLanguage(ext) {
|
|
463
|
+
const map = {
|
|
464
|
+
js: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript',
|
|
465
|
+
json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'ini', xml: 'xml', html: 'xml',
|
|
466
|
+
css: 'css', scss: 'scss', md: 'markdown',
|
|
467
|
+
sh: 'bash', bash: 'bash', zsh: 'bash',
|
|
468
|
+
py: 'python', rb: 'ruby', go: 'go', rs: 'rust', java: 'java', sql: 'sql',
|
|
469
|
+
dockerfile: 'dockerfile',
|
|
470
|
+
};
|
|
471
|
+
return map[ext] || null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Single, lazily-created modal reused for every preview. Closing hides it
|
|
475
|
+
// (display:none) rather than removing it from the DOM.
|
|
476
|
+
let modalRefs = null;
|
|
477
|
+
|
|
478
|
+
// View preferences persist across previews within a session.
|
|
479
|
+
const FONT_MIN = 11, FONT_MAX = 32, FONT_STEP = 2;
|
|
480
|
+
let previewFontPx = 16;
|
|
481
|
+
let previewTheme = 'light'; // 'light' | 'dark'
|
|
482
|
+
|
|
483
|
+
function ensureModal() {
|
|
484
|
+
if (modalRefs) return modalRefs;
|
|
485
|
+
|
|
486
|
+
const overlay = document.createElement('div');
|
|
487
|
+
overlay.classList.add('dirtree-modal-overlay');
|
|
488
|
+
overlay.style.display = 'none';
|
|
489
|
+
|
|
490
|
+
const modal = document.createElement('div');
|
|
491
|
+
modal.classList.add('dirtree-modal');
|
|
492
|
+
|
|
493
|
+
const header = document.createElement('div');
|
|
494
|
+
header.classList.add('dirtree-modal-header');
|
|
495
|
+
|
|
496
|
+
const title = document.createElement('span');
|
|
497
|
+
title.classList.add('dirtree-modal-title');
|
|
498
|
+
|
|
499
|
+
const actions = document.createElement('div');
|
|
500
|
+
actions.classList.add('dirtree-modal-actions');
|
|
501
|
+
|
|
502
|
+
const body = document.createElement('div');
|
|
503
|
+
body.classList.add('dirtree-modal-body');
|
|
504
|
+
const pre = document.createElement('pre');
|
|
505
|
+
const code = document.createElement('code');
|
|
506
|
+
pre.appendChild(code);
|
|
507
|
+
body.appendChild(pre);
|
|
508
|
+
|
|
509
|
+
// Applies the current font-size and theme preferences to the modal.
|
|
510
|
+
function applyView() {
|
|
511
|
+
code.style.fontSize = previewFontPx + 'px';
|
|
512
|
+
body.classList.toggle('dark', previewTheme === 'dark');
|
|
513
|
+
themeBtn.textContent = previewTheme === 'dark' ? '☀ light' : '🌙 dark';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const fontDecBtn = document.createElement('button');
|
|
517
|
+
fontDecBtn.classList.add('dirtree-modal-btn');
|
|
518
|
+
fontDecBtn.title = 'Decrease font size';
|
|
519
|
+
fontDecBtn.textContent = 'A−';
|
|
520
|
+
fontDecBtn.addEventListener('click', () => {
|
|
521
|
+
previewFontPx = Math.max(FONT_MIN, previewFontPx - FONT_STEP);
|
|
522
|
+
applyView();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const fontIncBtn = document.createElement('button');
|
|
526
|
+
fontIncBtn.classList.add('dirtree-modal-btn');
|
|
527
|
+
fontIncBtn.title = 'Increase font size';
|
|
528
|
+
fontIncBtn.textContent = 'A+';
|
|
529
|
+
fontIncBtn.addEventListener('click', () => {
|
|
530
|
+
previewFontPx = Math.min(FONT_MAX, previewFontPx + FONT_STEP);
|
|
531
|
+
applyView();
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const themeBtn = document.createElement('button');
|
|
535
|
+
themeBtn.classList.add('dirtree-modal-btn');
|
|
536
|
+
themeBtn.title = 'Toggle light / dark theme';
|
|
537
|
+
themeBtn.addEventListener('click', () => {
|
|
538
|
+
previewTheme = previewTheme === 'dark' ? 'light' : 'dark';
|
|
539
|
+
applyView();
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const copyBtn = document.createElement('button');
|
|
543
|
+
copyBtn.classList.add('dirtree-modal-btn');
|
|
544
|
+
copyBtn.textContent = 'copy';
|
|
545
|
+
|
|
546
|
+
const closeBtn = document.createElement('button');
|
|
547
|
+
closeBtn.classList.add('dirtree-modal-btn');
|
|
548
|
+
closeBtn.textContent = '✕ close';
|
|
549
|
+
|
|
550
|
+
actions.appendChild(fontDecBtn);
|
|
551
|
+
actions.appendChild(fontIncBtn);
|
|
552
|
+
actions.appendChild(themeBtn);
|
|
553
|
+
actions.appendChild(copyBtn);
|
|
554
|
+
actions.appendChild(closeBtn);
|
|
555
|
+
header.appendChild(title);
|
|
556
|
+
header.appendChild(actions);
|
|
557
|
+
|
|
558
|
+
modal.appendChild(header);
|
|
559
|
+
modal.appendChild(body);
|
|
560
|
+
overlay.appendChild(modal);
|
|
561
|
+
document.body.appendChild(overlay);
|
|
562
|
+
|
|
563
|
+
function close() {
|
|
564
|
+
overlay.style.display = 'none';
|
|
565
|
+
// Re-enable reveal's keyboard navigation that we paused while open.
|
|
566
|
+
deck.configure({ keyboard: true });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Close on backdrop click, but not when clicking inside the modal.
|
|
570
|
+
overlay.addEventListener('click', e => { if (e.target === overlay) close(); });
|
|
571
|
+
closeBtn.addEventListener('click', close);
|
|
572
|
+
document.addEventListener('keydown', e => {
|
|
573
|
+
if (overlay.style.display !== 'none' && e.key === 'Escape') {
|
|
574
|
+
e.stopPropagation();
|
|
575
|
+
close();
|
|
576
|
+
}
|
|
577
|
+
}, true);
|
|
578
|
+
|
|
579
|
+
copyBtn.addEventListener('click', () => {
|
|
580
|
+
writeToClipboard(code.textContent).then(() => {
|
|
581
|
+
copyBtn.textContent = '✓ copied';
|
|
582
|
+
copyBtn.classList.add('copied');
|
|
583
|
+
setTimeout(() => {
|
|
584
|
+
copyBtn.textContent = 'copy';
|
|
585
|
+
copyBtn.classList.remove('copied');
|
|
586
|
+
}, 1500);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
applyView(); // initialise font-size, theme class and toggle label
|
|
591
|
+
|
|
592
|
+
modalRefs = { overlay, title, code, body, close, applyView };
|
|
593
|
+
return modalRefs;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function openPreview(filePath, fileName, ext) {
|
|
597
|
+
const { overlay, title, code, body, applyView } = ensureModal();
|
|
598
|
+
title.textContent = fileName;
|
|
599
|
+
code.textContent = 'Loading…';
|
|
600
|
+
// Reset highlight state so the reused element can be re-highlighted.
|
|
601
|
+
code.removeAttribute('class');
|
|
602
|
+
code.removeAttribute('data-highlighted');
|
|
603
|
+
applyView(); // re-apply font-size/theme (class reset above clears nothing inline, but keeps them in sync)
|
|
604
|
+
body.scrollTop = 0;
|
|
605
|
+
|
|
606
|
+
// Pause reveal navigation so arrow keys scroll the code, not the slides.
|
|
607
|
+
deck.configure({ keyboard: false });
|
|
608
|
+
overlay.style.display = 'flex';
|
|
609
|
+
|
|
610
|
+
fetch(filePath, { credentials: 'include' })
|
|
611
|
+
.then(r => {
|
|
612
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
613
|
+
return r.text();
|
|
614
|
+
})
|
|
615
|
+
.then(text => {
|
|
616
|
+
code.textContent = text;
|
|
617
|
+
const hljs = getHljs();
|
|
618
|
+
const lang = hljsLanguage(fileName.toLowerCase() === 'dockerfile' ? 'dockerfile' : ext);
|
|
619
|
+
if (hljs) {
|
|
620
|
+
// Hint the language when known; otherwise let hljs auto-detect.
|
|
621
|
+
if (lang) code.classList.add('language-' + lang);
|
|
622
|
+
try { hljs.highlightElement(code); } catch (e) { /* leave as plain text */ }
|
|
623
|
+
}
|
|
624
|
+
})
|
|
625
|
+
.catch(err => { code.textContent = 'Failed to load file: ' + err.message; });
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function makePreviewButton(filePath, fileName, ext) {
|
|
629
|
+
const btn = document.createElement('button');
|
|
630
|
+
btn.classList.add('dirtree-preview-btn');
|
|
631
|
+
btn.title = 'Preview file contents';
|
|
632
|
+
btn.textContent = 'view';
|
|
633
|
+
btn.addEventListener('click', e => {
|
|
634
|
+
e.preventDefault();
|
|
635
|
+
openPreview(filePath, fileName, ext);
|
|
636
|
+
});
|
|
637
|
+
return btn;
|
|
638
|
+
}
|
|
639
|
+
|
|
227
640
|
function getFileList(el) {
|
|
228
641
|
return el.innerText.split('\n').filter(e => e.trim().length > 0);
|
|
229
642
|
}
|
|
@@ -346,6 +759,7 @@ const dirTreeFactory = ({ zip, strToU8 }) => ({
|
|
|
346
759
|
|
|
347
760
|
li.appendChild(icon);
|
|
348
761
|
li.appendChild(a);
|
|
762
|
+
li.appendChild(makePreviewButton(finalPath, o, ext));
|
|
349
763
|
li.appendChild(makeCopyButton(finalPath));
|
|
350
764
|
el.appendChild(li);
|
|
351
765
|
}
|
|
@@ -3,7 +3,8 @@ Shows a code snippet after loading it from a file.
|
|
|
3
3
|
|
|
4
4
|
Parameters:
|
|
5
5
|
- data-code: Language
|
|
6
|
-
- data-begin: Begin indicator in the file (optional)
|
|
6
|
+
- data-begin: Begin indicator in the file (optional; substring match, first occurrence)
|
|
7
|
+
- data-begin-nth: Start after the Nth occurrence of data-begin instead of the first (optional, 1-based)
|
|
7
8
|
- data-end: End indicator in the file (optional)
|
|
8
9
|
- data-link: Show a link to the file (optional)
|
|
9
10
|
|
|
@@ -45,17 +46,25 @@ function showCode(el, language, code, link, outdent) {
|
|
|
45
46
|
return newEl
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function extractBeginEndSnippet(code, beginMarker, endMarker) {
|
|
49
|
+
function extractBeginEndSnippet(code, beginMarker, endMarker, beginNth) {
|
|
49
50
|
let lines = code.split('\n');
|
|
50
51
|
let out = "";
|
|
51
|
-
let beginFound = beginMarker ? false : true
|
|
52
52
|
beginMarker = beginMarker ? beginMarker.trim() : null
|
|
53
|
+
// Trim and null-guard endMarker: otherwise indexOf(null) below coerces to
|
|
54
|
+
// indexOf("null"), so a line containing "null" would end the snippet early.
|
|
55
|
+
endMarker = endMarker ? endMarker.trim() : null
|
|
56
|
+
// Markers are substring-matched. By default the snippet starts after the
|
|
57
|
+
// first line containing beginMarker; data-begin-nth selects a later one.
|
|
58
|
+
let nth = beginNth && beginNth > 0 ? beginNth : 1
|
|
59
|
+
let seen = 0
|
|
60
|
+
let beginFound = beginMarker ? false : true
|
|
53
61
|
|
|
54
62
|
for (let line of lines) {
|
|
55
63
|
if (!beginFound && line.indexOf(beginMarker) >= 0) {
|
|
56
|
-
|
|
64
|
+
if (++seen >= nth)
|
|
65
|
+
beginFound = true;
|
|
57
66
|
continue;
|
|
58
|
-
} else if (beginFound && line.indexOf(endMarker) >= 0) {
|
|
67
|
+
} else if (beginFound && endMarker && line.indexOf(endMarker) >= 0) {
|
|
59
68
|
break;
|
|
60
69
|
} else if (!beginFound) {
|
|
61
70
|
continue;
|
|
@@ -179,6 +188,7 @@ export default () => {
|
|
|
179
188
|
let language = el.getAttribute("data-code");
|
|
180
189
|
let url = el.getAttribute("href");
|
|
181
190
|
let beginMarker = el.getAttribute("data-begin")
|
|
191
|
+
let beginNth = parseInt(el.getAttribute("data-begin-nth"), 10) || 1
|
|
182
192
|
let endMarker = el.getAttribute("data-end")
|
|
183
193
|
let showLink = el.hasAttribute("data-link")
|
|
184
194
|
let outdentCode = el.hasAttribute("data-outdent")
|
|
@@ -203,7 +213,7 @@ export default () => {
|
|
|
203
213
|
continue
|
|
204
214
|
}
|
|
205
215
|
const text = await response.text()
|
|
206
|
-
let code = extractBeginEndSnippet(text, beginMarker, endMarker)
|
|
216
|
+
let code = extractBeginEndSnippet(text, beginMarker, endMarker, beginNth)
|
|
207
217
|
|
|
208
218
|
if (!code) {
|
|
209
219
|
showError(el, `No content extracted from ${url} (begin=${beginMarker}, end=${endMarker})`)
|