@farberg/reveal-template 1.1.30 → 1.1.31

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.
@@ -7,6 +7,14 @@
7
7
 
8
8
  <ul class="menu"><ul>
9
9
 
10
+ ---
11
+ ## Code Example
12
+
13
+ ```bash
14
+ # Create a Kafka cluster using the operator
15
+ kubectl apply -f https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/refs/heads/main/examples/kafka/kafka-ephemeral.yaml
16
+ ```
17
+
10
18
  ---
11
19
  ## Mermaid Example
12
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farberg/reveal-template",
3
- "version": "1.1.30",
3
+ "version": "1.1.31",
4
4
  "homepage": "https://github.com/pfisterer/reveal-template",
5
5
  "description": "Reveal.js template for Dennis' lectures",
6
6
  "main": "index.js",
@@ -10,9 +10,9 @@ Parameters:
10
10
 
11
11
  Example
12
12
 
13
- <a data-code='bash'
14
- data-begin="# Begin indicator in the file"
15
- data-end="# End indicator in the file"
13
+ <a data-code='bash'
14
+ data-begin="# Begin indicator in the file"
15
+ data-end="# End indicator in the file"
16
16
  data-link
17
17
  href="link/to/the/file.sh">Source code</a>
18
18
  */
@@ -26,6 +26,9 @@ function showError(el, err) {
26
26
  function showCode(el, language, code, link, outdent) {
27
27
  var newEl = document.createElement('pre');
28
28
  newEl.setAttribute('class', `language-${language}`);
29
+ // Keep the exact, un-escaped source so the copy button can reproduce it
30
+ // verbatim regardless of how we later restructure the DOM for display.
31
+ newEl.__rawCode = code
29
32
  //Replace special chars
30
33
  code = code.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;")
31
34
 
@@ -82,6 +85,43 @@ function injectStyles() {
82
85
  pre.with-copy-btn {
83
86
  position: relative;
84
87
  }
88
+ /* Soft-wrap long lines like VS Code's word wrap. white-space:pre-wrap is
89
+ a purely visual wrap: it does NOT insert real newlines into the text.
90
+ overflow-wrap:anywhere lets very long tokens / URLs break instead of
91
+ overflowing the slide. Copy fidelity is handled separately (see
92
+ addCopyButton / wrapCodeLines), so wrapping never leaks into copied text. */
93
+ .reveal pre.with-copy-btn,
94
+ .reveal pre.with-copy-btn code {
95
+ white-space: pre-wrap;
96
+ overflow-wrap: anywhere;
97
+ word-break: normal;
98
+ }
99
+ /* Each logical source line becomes its own block so wrapped continuation
100
+ rows can be hang-indented (text-indent pulls the first row back to the
101
+ margin; padding-left indents everything else). */
102
+ .reveal pre.with-copy-btn code .cl {
103
+ display: block;
104
+ position: relative;
105
+ padding-left: 2.5ch;
106
+ text-indent: -2.5ch;
107
+ }
108
+ /* Preserve the height of blank lines. Generated content is excluded from
109
+ selection/copy, so the zero-width space never reaches the clipboard. */
110
+ .reveal pre.with-copy-btn code .cl:empty::before {
111
+ content: "\\200b";
112
+ }
113
+ /* Continuation marker placed at each soft-wrap point (see wrapCodeLines).
114
+ user-select:none keeps it out of native select+copy. */
115
+ .reveal pre.with-copy-btn code .wrap-marker {
116
+ position: absolute;
117
+ left: 0;
118
+ text-indent: 0;
119
+ font-style: normal;
120
+ opacity: 0.4;
121
+ pointer-events: none;
122
+ user-select: none;
123
+ -webkit-user-select: none;
124
+ }
85
125
  .copy-code-btn {
86
126
  position: absolute;
87
127
  top: 0.4em;
@@ -114,6 +154,80 @@ function injectStyles() {
114
154
  document.head.appendChild(style);
115
155
  }
116
156
 
157
+ // Wrap each logical line of a (already highlighted) <code> element in its own
158
+ // <span class="cl"> block. hljs emits a single run of HTML with "\n"
159
+ // separators and token <span>s that may straddle newlines, so we tokenize the
160
+ // markup and re-open any spans still active when a line ends.
161
+ function wrapCodeLines(codeEl) {
162
+ const html = codeEl.innerHTML
163
+ const tokenRe = /(<\/?[^>]+>)|([^<]+)/g
164
+ let m
165
+ let open = [] // stack of currently-open tag strings (hljs uses <span>)
166
+ let buf = ''
167
+ let lines = []
168
+ const closers = () => open.map(() => '</span>').join('')
169
+ const reopen = () => open.join('')
170
+
171
+ while ((m = tokenRe.exec(html)) !== null) {
172
+ if (m[1]) {
173
+ const tag = m[1]
174
+ if (tag[1] === '/') { open.pop(); buf += tag }
175
+ else if (tag.endsWith('/>')) { buf += tag } // self-closing (rare)
176
+ else { open.push(tag); buf += tag }
177
+ } else {
178
+ const parts = m[2].split('\n')
179
+ for (let i = 0; i < parts.length; i++) {
180
+ if (i > 0) {
181
+ buf += closers() // close spans before the line break
182
+ lines.push(buf)
183
+ buf = reopen() // re-open them on the next line
184
+ }
185
+ buf += parts[i]
186
+ }
187
+ }
188
+ }
189
+ buf += closers()
190
+ lines.push(buf)
191
+
192
+ codeEl.innerHTML = lines.map(l => `<span class="cl">${l}</span>`).join('')
193
+ }
194
+
195
+ // Place a "↪" marker at the start of every wrapped continuation row. CSS has no
196
+ // selector for soft-wrap rows, so we measure them with a Range. The deck is
197
+ // rendered at a fixed layout width and merely CSS-scaled, so wrap positions are
198
+ // stable; we divide measured offsets by the current scale to get layout pixels.
199
+ function decorateWrappedLines(root, scale) {
200
+ if (!root) return
201
+ const s = scale || 1
202
+ for (const line of root.querySelectorAll('pre.with-copy-btn code .cl')) {
203
+ // Idempotent: clear any decoration from a previous pass before measuring.
204
+ for (const old of line.querySelectorAll('.wrap-marker')) old.remove()
205
+
206
+ const range = document.createRange()
207
+ range.selectNodeContents(line)
208
+ const rects = range.getClientRects()
209
+ if (!rects.length) continue
210
+
211
+ const base = line.getBoundingClientRect()
212
+ const tops = []
213
+ for (const r of rects) {
214
+ if (r.width === 0 && r.height === 0) continue
215
+ const t = Math.round(r.top - base.top)
216
+ if (!tops.length || Math.abs(tops[tops.length - 1] - t) > 2) tops.push(t)
217
+ }
218
+ if (tops.length <= 1) continue // line did not wrap
219
+
220
+ for (let i = 1; i < tops.length; i++) {
221
+ const marker = document.createElement('span')
222
+ marker.className = 'wrap-marker'
223
+ marker.textContent = '↪'
224
+ marker.setAttribute('aria-hidden', 'true')
225
+ marker.style.top = (tops[i] / s) + 'px'
226
+ line.appendChild(marker)
227
+ }
228
+ }
229
+ }
230
+
117
231
  function writeToClipboard(text) {
118
232
  if (navigator.clipboard && navigator.clipboard.writeText)
119
233
  return navigator.clipboard.writeText(text);
@@ -140,7 +254,13 @@ function addCopyButton(preEl) {
140
254
  btn.textContent = 'copy';
141
255
  btn.addEventListener('click', () => {
142
256
  const code = preEl.querySelector('code');
143
- const text = code ? code.innerText : preEl.innerText;
257
+ // Prefer the stashed raw source: it is immune to the per-line <span>
258
+ // wrapping and wrap markers used for display. textContent is the
259
+ // fallback (block-per-line collapses to no newlines under textContent,
260
+ // so raw is what we really want here).
261
+ const text = preEl.__rawCode != null
262
+ ? preEl.__rawCode
263
+ : (code ? code.textContent : preEl.textContent);
144
264
  writeToClipboard(text).then(() => {
145
265
  btn.textContent = '✓ copied';
146
266
  btn.classList.add('copied');
@@ -162,6 +282,17 @@ export default () => {
162
282
  init: (deck) => {
163
283
  injectStyles();
164
284
 
285
+ // Re-measure wrap points after fonts settle and on every layout change.
286
+ // Only the visible slide can be measured (hidden slides have no layout),
287
+ // so slidechanged re-runs decoration as each slide is shown.
288
+ const decorate = (root) => {
289
+ const run = () => decorateWrappedLines(root, deck.getScale());
290
+ if (document.fonts && document.fonts.ready)
291
+ document.fonts.ready.then(() => requestAnimationFrame(run));
292
+ else
293
+ requestAnimationFrame(run);
294
+ };
295
+
165
296
  deck.on('ready', async () => {
166
297
  const highlightPlugin = deck.getPlugin("highlight")
167
298
 
@@ -178,8 +309,12 @@ export default () => {
178
309
  // code text and overwrites the rendered diagram.
179
310
  for (let el of deck.getRevealElement().querySelectorAll("pre code[class]")) {
180
311
  if (!el.classList.contains("mermaid")) {
312
+ const pre = el.closest('pre')
313
+ // Capture raw source before highlighting/line-wrapping touches the DOM.
314
+ if (pre) pre.__rawCode = el.textContent
181
315
  highlightPlugin.hljs.highlightElement(el);
182
- addCopyButton(el.closest('pre'));
316
+ wrapCodeLines(el);
317
+ addCopyButton(pre);
183
318
  }
184
319
  }
185
320
 
@@ -225,7 +360,9 @@ export default () => {
225
360
 
226
361
  const newEl = showCode(el, language, code, showLink ? url : null, outdent)
227
362
  if (language !== 'mermaid') {
228
- highlightPlugin.hljs.highlightElement(newEl.querySelector('code') ?? newEl)
363
+ const codeEl = newEl.querySelector('code') ?? newEl
364
+ highlightPlugin.hljs.highlightElement(codeEl)
365
+ wrapCodeLines(codeEl)
229
366
  addCopyButton(newEl)
230
367
  }
231
368
  } catch (err) {
@@ -234,10 +371,15 @@ export default () => {
234
371
  }
235
372
  }
236
373
 
237
-
374
+ // First pass for the slide that is visible at startup.
375
+ decorate(deck.getRevealElement())
238
376
  })
239
377
 
240
-
378
+ // Slides other than the current one have no layout while hidden, so we
379
+ // (re)place wrap markers each time a slide becomes visible, and after
380
+ // any resize that changes the scale.
381
+ deck.on('slidechanged', (e) => decorate(e.currentSlide))
382
+ deck.on('resize', () => decorate(deck.getRevealElement()))
241
383
  }
242
384
  }
243
- }
385
+ }