retold-content-system 1.0.0

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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +107 -0
  3. package/build-codejar-bundle.js +29 -0
  4. package/build-codemirror-bundle.js +29 -0
  5. package/codejar-entry.js +10 -0
  6. package/codemirror-entry.js +16 -0
  7. package/content/Dogs.txt.md +2 -0
  8. package/content/README.md +35 -0
  9. package/content/_sidebar.md +3 -0
  10. package/content/_topbar.md +1 -0
  11. package/content/cover.md +12 -0
  12. package/content/getting-started.md +73 -0
  13. package/css/content-system.css +42 -0
  14. package/css/github.css +118 -0
  15. package/docs/.nojekyll +0 -0
  16. package/docs/README.md +24 -0
  17. package/docs/_sidebar.md +16 -0
  18. package/docs/_topbar.md +6 -0
  19. package/docs/cli.md +119 -0
  20. package/docs/cover.md +16 -0
  21. package/docs/css/docuserve.css +73 -0
  22. package/docs/editor-guide.md +137 -0
  23. package/docs/getting-started.md +73 -0
  24. package/docs/index.html +39 -0
  25. package/docs/keyboard-shortcuts.md +40 -0
  26. package/docs/retold-catalog.json +81 -0
  27. package/docs/retold-keyword-index.json +19 -0
  28. package/docs/topics.md +83 -0
  29. package/html/codejar-bundle.js +16 -0
  30. package/html/codemirror-bundle.js +29982 -0
  31. package/html/edit.html +25 -0
  32. package/html/index.html +25 -0
  33. package/html/preview.html +19 -0
  34. package/package.json +70 -0
  35. package/server.js +43 -0
  36. package/source/Pict-Application-ContentEditor-Configuration.json +15 -0
  37. package/source/Pict-Application-ContentEditor.js +1361 -0
  38. package/source/Pict-Application-ContentReader-Configuration.json +15 -0
  39. package/source/Pict-Application-ContentReader.js +91 -0
  40. package/source/Pict-ContentSystem-Bundle.js +21 -0
  41. package/source/cli/ContentSystem-CLI-Program.js +15 -0
  42. package/source/cli/ContentSystem-CLI-Run.js +3 -0
  43. package/source/cli/ContentSystem-Server-Setup.js +405 -0
  44. package/source/cli/commands/ContentSystem-Command-Serve.js +104 -0
  45. package/source/providers/Pict-Provider-ContentEditor.js +198 -0
  46. package/source/views/PictView-Editor-CodeEditor.js +271 -0
  47. package/source/views/PictView-Editor-Layout.js +1194 -0
  48. package/source/views/PictView-Editor-MarkdownEditor.js +115 -0
  49. package/source/views/PictView-Editor-MarkdownReference.js +801 -0
  50. package/source/views/PictView-Editor-SettingsPanel.js +563 -0
  51. package/source/views/PictView-Editor-TopBar.js +366 -0
  52. package/source/views/PictView-Editor-Topics.js +1025 -0
@@ -0,0 +1,801 @@
1
+ const libPictView = require('pict-view');
2
+
3
+ /**
4
+ * Built-in Markdown reference content.
5
+ *
6
+ * Pre-rendered HTML covering GitHub-Flavored Markdown, KaTeX math,
7
+ * and Mermaid diagrams. All angle brackets and ampersands inside
8
+ * code blocks are entity-encoded so they render as literal text.
9
+ */
10
+ const _MarkdownReferenceContent = /*html*/`
11
+ <h2>Headings</h2>
12
+ <p>Use <code>#</code> through <code>######</code> for heading levels 1&ndash;6.</p>
13
+ <pre><code># Heading 1
14
+ ## Heading 2
15
+ ### Heading 3
16
+ #### Heading 4
17
+ ##### Heading 5
18
+ ###### Heading 6</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
19
+
20
+ <h2>Emphasis</h2>
21
+ <p>Bold, italic, strikethrough, and combinations.</p>
22
+ <pre><code>**bold text**
23
+ *italic text*
24
+ ***bold and italic***
25
+ ~~strikethrough~~
26
+ **bold and ~~strikethrough~~**</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
27
+
28
+ <h2>Inline Code</h2>
29
+ <p>Wrap text in backticks for inline code.</p>
30
+ <pre><code>Use \`console.log()\` to debug.
31
+ Use \`\`double backticks for \`literal\` backticks\`\`.</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
32
+
33
+ <h2>Links</h2>
34
+ <p>Inline links, reference links, and autolinks.</p>
35
+ <pre><code>[Link text](https://example.com)
36
+ [Link with title](https://example.com "Title text")
37
+
38
+ [Reference link][1]
39
+ [1]: https://example.com
40
+
41
+ Autolink: https://example.com
42
+ Email: user@example.com</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
43
+
44
+ <h2>Images</h2>
45
+ <pre><code>![Alt text](image.png)
46
+ ![Alt text](image.png "Image title")
47
+
48
+ [![Linked image](image.png)](https://example.com)</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
49
+
50
+ <h2>Unordered Lists</h2>
51
+ <p>Use <code>-</code>, <code>*</code>, or <code>+</code> for bullet items.</p>
52
+ <pre><code>- Item one
53
+ - Item two
54
+ - Nested item
55
+ - Another nested
56
+ - Deeply nested
57
+ - Item three</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
58
+
59
+ <h2>Ordered Lists</h2>
60
+ <pre><code>1. First item
61
+ 2. Second item
62
+ 3. Third item
63
+ 1. Sub-item A
64
+ 2. Sub-item B</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
65
+
66
+ <h2>Task Lists</h2>
67
+ <pre><code>- [x] Completed task
68
+ - [ ] Incomplete task
69
+ - [ ] Another task</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
70
+
71
+ <h2>Blockquotes</h2>
72
+ <pre><code>&gt; This is a blockquote.
73
+ &gt;
74
+ &gt; It can span multiple paragraphs.
75
+ &gt;
76
+ &gt;&gt; Nested blockquote.</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
77
+
78
+ <h2>Code Blocks</h2>
79
+ <p>Use triple backticks with an optional language identifier.</p>
80
+ <pre><code>\`\`\`javascript
81
+ function hello()
82
+ {
83
+ console.log("Hello, world!");
84
+ }
85
+ \`\`\`
86
+
87
+ \`\`\`python
88
+ def hello():
89
+ print("Hello, world!")
90
+ \`\`\`
91
+
92
+ \`\`\`css
93
+ .container {
94
+ display: flex;
95
+ gap: 16px;
96
+ }
97
+ \`\`\`</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
98
+
99
+ <h2>Tables</h2>
100
+ <p>Use pipes and hyphens. Colons control alignment.</p>
101
+ <pre><code>| Left Align | Center Align | Right Align |
102
+ |:-----------|:------------:|------------:|
103
+ | Cell 1 | Cell 2 | Cell 3 |
104
+ | Cell 4 | Cell 5 | Cell 6 |</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
105
+
106
+ <h2>Horizontal Rules</h2>
107
+ <p>Three or more hyphens, asterisks, or underscores.</p>
108
+ <pre><code>---
109
+
110
+ ***
111
+
112
+ ___</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
113
+
114
+ <h2>Footnotes</h2>
115
+ <pre><code>Here is a footnote reference[^1] and another[^note].
116
+
117
+ [^1]: This is the footnote content.
118
+ [^note]: Footnotes can have any label.</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
119
+
120
+ <h2>HTML in Markdown</h2>
121
+ <p>Raw HTML is allowed in GitHub-Flavored Markdown.</p>
122
+ <pre><code>&lt;details&gt;
123
+ &lt;summary&gt;Click to expand&lt;/summary&gt;
124
+
125
+ Hidden content here.
126
+
127
+ &lt;/details&gt;
128
+
129
+ &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;S&lt;/kbd&gt; to save.
130
+
131
+ &lt;mark&gt;Highlighted text&lt;/mark&gt;
132
+
133
+ &lt;sup&gt;superscript&lt;/sup&gt; and &lt;sub&gt;subscript&lt;/sub&gt;</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
134
+
135
+ <h2>Escaping Characters</h2>
136
+ <p>Backslash-escape special markdown characters.</p>
137
+ <pre><code>\\*not italic\\*
138
+ \\# not a heading
139
+ \\[not a link\\](url)
140
+ \\\`not code\\\`</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
141
+
142
+ <h2>Line Breaks</h2>
143
+ <pre><code>End a line with two spaces
144
+ to create a line break.
145
+
146
+ Or use a blank line
147
+
148
+ for a new paragraph.</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
149
+
150
+ <h2>KaTeX &mdash; Inline Math</h2>
151
+ <p>Wrap expressions with single dollar signs for inline math.</p>
152
+ <pre><code>The equation $E = mc^2$ is famous.
153
+
154
+ The quadratic formula is $x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$.
155
+
156
+ Greek letters: $\\alpha$, $\\beta$, $\\gamma$, $\\delta$, $\\theta$, $\\pi$.
157
+
158
+ Subscripts and superscripts: $x_i^2$ and $a_{n+1}$.</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
159
+
160
+ <h2>KaTeX &mdash; Display Math</h2>
161
+ <p>Use double dollar signs on their own lines for display (block) math.</p>
162
+
163
+ <h3>Integral</h3>
164
+ <pre><code>$$
165
+ \\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx = \\sqrt{\\pi}
166
+ $$</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
167
+
168
+ <h3>Summation</h3>
169
+ <pre><code>$$
170
+ \\sum_{n=1}^{\\infty} \\frac{1}{n^2} = \\frac{\\pi^2}{6}
171
+ $$</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
172
+
173
+ <h3>Matrix</h3>
174
+ <pre><code>$$
175
+ \\begin{bmatrix}
176
+ a &amp; b \\\\
177
+ c &amp; d
178
+ \\end{bmatrix}
179
+ \\begin{bmatrix}
180
+ x \\\\
181
+ y
182
+ \\end{bmatrix}
183
+ =
184
+ \\begin{bmatrix}
185
+ ax + by \\\\
186
+ cx + dy
187
+ \\end{bmatrix}
188
+ $$</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
189
+
190
+ <h3>Aligned Equations</h3>
191
+ <pre><code>$$
192
+ \\begin{aligned}
193
+ f(x) &amp;= x^2 + 2x + 1 \\\\
194
+ &amp;= (x + 1)^2
195
+ \\end{aligned}
196
+ $$</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
197
+
198
+ <h3>Cases (Piecewise)</h3>
199
+ <pre><code>$$
200
+ f(x) = \\begin{cases}
201
+ x^2 &amp; \\text{if } x \\geq 0 \\\\
202
+ -x^2 &amp; \\text{if } x &lt; 0
203
+ \\end{cases}
204
+ $$</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
205
+
206
+ <h3>Fractions &amp; Limits</h3>
207
+ <pre><code>$$
208
+ \\lim_{x \\to 0} \\frac{\\sin x}{x} = 1
209
+ $$</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
210
+
211
+ <h2>Mermaid &mdash; Flowchart</h2>
212
+ <pre><code>\`\`\`mermaid
213
+ graph TD
214
+ A[Start] --&gt; B{Decision}
215
+ B --&gt;|Yes| C[Do something]
216
+ B --&gt;|No| D[Do something else]
217
+ C --&gt; E[End]
218
+ D --&gt; E
219
+ \`\`\`</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
220
+
221
+ <h2>Mermaid &mdash; Sequence Diagram</h2>
222
+ <pre><code>\`\`\`mermaid
223
+ sequenceDiagram
224
+ participant A as Alice
225
+ participant B as Bob
226
+ A-&gt;&gt;B: Hello Bob
227
+ B--&gt;&gt;A: Hi Alice
228
+ A-&gt;&gt;B: How are you?
229
+ B--&gt;&gt;A: Great!
230
+ \`\`\`</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
231
+
232
+ <h2>Mermaid &mdash; Gantt Chart</h2>
233
+ <pre><code>\`\`\`mermaid
234
+ gantt
235
+ title Project Timeline
236
+ dateFormat YYYY-MM-DD
237
+ section Phase 1
238
+ Research :a1, 2024-01-01, 30d
239
+ Design :a2, after a1, 20d
240
+ section Phase 2
241
+ Development :b1, after a2, 40d
242
+ Testing :b2, after b1, 15d
243
+ \`\`\`</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
244
+
245
+ <h2>Mermaid &mdash; Class Diagram</h2>
246
+ <pre><code>\`\`\`mermaid
247
+ classDiagram
248
+ Animal &lt;|-- Duck
249
+ Animal &lt;|-- Fish
250
+ Animal : +int age
251
+ Animal : +String gender
252
+ Animal : +swim()
253
+ Duck : +String beakColor
254
+ Duck : +quack()
255
+ Fish : +int sizeInFeet
256
+ Fish : +canEat()
257
+ \`\`\`</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
258
+
259
+ <h2>Mermaid &mdash; State Diagram</h2>
260
+ <pre><code>\`\`\`mermaid
261
+ stateDiagram-v2
262
+ [*] --&gt; Idle
263
+ Idle --&gt; Processing : Start
264
+ Processing --&gt; Done : Complete
265
+ Processing --&gt; Error : Fail
266
+ Error --&gt; Idle : Reset
267
+ Done --&gt; [*]
268
+ \`\`\`</code><button class="md-ref-copy-btn" onclick="pict.views['ContentEditor-MarkdownReference'].copyCodeBlock(this)">Copy</button></pre>
269
+ `;
270
+
271
+ const _ViewConfiguration =
272
+ {
273
+ ViewIdentifier: "ContentEditor-MarkdownReference",
274
+
275
+ DefaultRenderable: "ContentEditor-MarkdownReference-Display",
276
+ DefaultDestinationAddress: "#ContentEditor-SidebarReference-Container",
277
+
278
+ AutoRender: false,
279
+
280
+ CSS: /*css*/`
281
+ .md-ref-container
282
+ {
283
+ display: flex;
284
+ flex-direction: column;
285
+ height: 100%;
286
+ background: #FAF8F4;
287
+ }
288
+ .md-ref-search-bar
289
+ {
290
+ position: sticky;
291
+ top: 0;
292
+ z-index: 5;
293
+ display: flex;
294
+ align-items: center;
295
+ gap: 4px;
296
+ padding: 6px 8px;
297
+ background: #F5F0EA;
298
+ border-bottom: 1px solid #DDD6CA;
299
+ flex-shrink: 0;
300
+ }
301
+ .md-ref-search-input
302
+ {
303
+ flex: 1;
304
+ min-width: 0;
305
+ padding: 5px 8px;
306
+ border: 1px solid #DDD6CA;
307
+ border-radius: 4px;
308
+ font-size: 0.8rem;
309
+ background: #FFF;
310
+ color: #3D3229;
311
+ outline: none;
312
+ }
313
+ .md-ref-search-input:focus
314
+ {
315
+ border-color: #2E7D74;
316
+ }
317
+ .md-ref-search-nav
318
+ {
319
+ background: transparent;
320
+ border: 1px solid #DDD6CA;
321
+ border-radius: 4px;
322
+ width: 26px;
323
+ height: 26px;
324
+ cursor: pointer;
325
+ color: #5E5549;
326
+ font-size: 0.7rem;
327
+ display: flex;
328
+ align-items: center;
329
+ justify-content: center;
330
+ flex-shrink: 0;
331
+ }
332
+ .md-ref-search-nav:hover
333
+ {
334
+ background: #EDE9E3;
335
+ }
336
+ .md-ref-search-nav:disabled
337
+ {
338
+ opacity: 0.3;
339
+ cursor: not-allowed;
340
+ }
341
+ .md-ref-search-count
342
+ {
343
+ font-size: 0.7rem;
344
+ color: #8A7F72;
345
+ white-space: nowrap;
346
+ min-width: 32px;
347
+ text-align: center;
348
+ flex-shrink: 0;
349
+ }
350
+ .md-ref-content
351
+ {
352
+ flex: 1;
353
+ overflow-y: auto;
354
+ padding: 12px;
355
+ font-size: 0.82rem;
356
+ line-height: 1.6;
357
+ color: #3D3229;
358
+ }
359
+ .md-ref-content h2
360
+ {
361
+ font-size: 0.95rem;
362
+ margin: 20px 0 8px 0;
363
+ padding-bottom: 4px;
364
+ border-bottom: 1px solid #EDE9E3;
365
+ color: #2E7D74;
366
+ }
367
+ .md-ref-content h2:first-child
368
+ {
369
+ margin-top: 0;
370
+ }
371
+ .md-ref-content h3
372
+ {
373
+ font-size: 0.85rem;
374
+ margin: 14px 0 6px 0;
375
+ color: #5E5549;
376
+ }
377
+ .md-ref-content p
378
+ {
379
+ margin: 6px 0;
380
+ }
381
+ .md-ref-content code
382
+ {
383
+ background: #F0EDE8;
384
+ padding: 1px 4px;
385
+ border-radius: 3px;
386
+ font-size: 0.78rem;
387
+ font-family: monospace;
388
+ }
389
+ .md-ref-content pre
390
+ {
391
+ background: #F0EDE8;
392
+ border: 1px solid #DDD6CA;
393
+ border-radius: 4px;
394
+ padding: 8px 10px;
395
+ overflow-x: auto;
396
+ font-size: 0.76rem;
397
+ line-height: 1.5;
398
+ margin: 6px 0;
399
+ position: relative;
400
+ }
401
+ .md-ref-content pre code
402
+ {
403
+ background: none;
404
+ padding: 0;
405
+ font-size: inherit;
406
+ }
407
+ .md-ref-copy-btn
408
+ {
409
+ position: absolute;
410
+ top: 4px;
411
+ right: 4px;
412
+ background: #FAF8F4;
413
+ border: 1px solid #DDD6CA;
414
+ border-radius: 3px;
415
+ padding: 2px 6px;
416
+ font-size: 0.65rem;
417
+ color: #8A7F72;
418
+ cursor: pointer;
419
+ opacity: 0;
420
+ transition: opacity 0.15s;
421
+ }
422
+ .md-ref-content pre:hover .md-ref-copy-btn
423
+ {
424
+ opacity: 1;
425
+ }
426
+ .md-ref-copy-btn:hover
427
+ {
428
+ background: #EDE9E3;
429
+ color: #3D3229;
430
+ }
431
+ /* Search highlight */
432
+ mark.md-ref-highlight
433
+ {
434
+ background: #FFEAA7;
435
+ color: inherit;
436
+ padding: 0;
437
+ border-radius: 2px;
438
+ }
439
+ mark.md-ref-highlight-active
440
+ {
441
+ background: #E8A94D;
442
+ color: #FFF;
443
+ }
444
+ `,
445
+
446
+ Templates:
447
+ [
448
+ {
449
+ Hash: "ContentEditor-MarkdownReference-Template",
450
+ Template: /*html*/`
451
+ <div class="md-ref-container">
452
+ <div class="md-ref-search-bar">
453
+ <input type="text" class="md-ref-search-input"
454
+ id="ContentEditor-MdRef-SearchInput"
455
+ placeholder="Search reference..."
456
+ oninput="{~P~}.views['ContentEditor-MarkdownReference'].onSearchInput(this.value)"
457
+ onkeydown="{~P~}.views['ContentEditor-MarkdownReference'].onSearchKeydown(event)">
458
+ <span class="md-ref-search-count" id="ContentEditor-MdRef-SearchCount"></span>
459
+ <button class="md-ref-search-nav" id="ContentEditor-MdRef-SearchPrev"
460
+ onclick="{~P~}.views['ContentEditor-MarkdownReference'].navigateMatch(-1)" disabled>&#x25B2;</button>
461
+ <button class="md-ref-search-nav" id="ContentEditor-MdRef-SearchNext"
462
+ onclick="{~P~}.views['ContentEditor-MarkdownReference'].navigateMatch(1)" disabled>&#x25BC;</button>
463
+ </div>
464
+ <div class="md-ref-content" id="ContentEditor-MdRef-Content">
465
+ ` + _MarkdownReferenceContent + `
466
+ </div>
467
+ </div>
468
+ `
469
+ }
470
+ ],
471
+
472
+ Renderables:
473
+ [
474
+ {
475
+ RenderableHash: "ContentEditor-MarkdownReference-Display",
476
+ TemplateHash: "ContentEditor-MarkdownReference-Template",
477
+ DestinationAddress: "#ContentEditor-SidebarReference-Container",
478
+ RenderMethod: "replace"
479
+ }
480
+ ]
481
+ };
482
+
483
+ /**
484
+ * Markdown Reference View
485
+ *
486
+ * A built-in, read-only reference for GitHub-Flavored Markdown, KaTeX
487
+ * math, and Mermaid diagrams. Provides a fixed search bar with
488
+ * prev/next navigation and copy-to-clipboard buttons on all code blocks.
489
+ */
490
+ class ContentEditorMarkdownReferenceView extends libPictView
491
+ {
492
+ constructor(pFable, pOptions, pServiceHash)
493
+ {
494
+ super(pFable, pOptions, pServiceHash);
495
+
496
+ this._hasRendered = false;
497
+ this._searchMatches = [];
498
+ this._currentMatchIndex = -1;
499
+ this._originalContent = '';
500
+ }
501
+
502
+ onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
503
+ {
504
+ this._hasRendered = true;
505
+
506
+ // Cache the original innerHTML for search restoration
507
+ let tmpContentEl = document.getElementById('ContentEditor-MdRef-Content');
508
+ if (tmpContentEl)
509
+ {
510
+ this._originalContent = tmpContentEl.innerHTML;
511
+ }
512
+
513
+ // Inject CSS
514
+ this.pict.CSSMap.injectCSS();
515
+
516
+ return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
517
+ }
518
+
519
+ /**
520
+ * Copy the text content of a code block to the clipboard.
521
+ *
522
+ * @param {HTMLElement} pButton - The copy button element
523
+ */
524
+ copyCodeBlock(pButton)
525
+ {
526
+ let tmpPre = pButton.closest('pre');
527
+ if (!tmpPre)
528
+ {
529
+ return;
530
+ }
531
+ let tmpCode = tmpPre.querySelector('code');
532
+ if (!tmpCode)
533
+ {
534
+ return;
535
+ }
536
+
537
+ let tmpText = tmpCode.textContent;
538
+
539
+ if (navigator.clipboard)
540
+ {
541
+ navigator.clipboard.writeText(tmpText).then(() =>
542
+ {
543
+ pButton.textContent = 'Copied!';
544
+ setTimeout(() =>
545
+ {
546
+ pButton.textContent = 'Copy';
547
+ }, 1500);
548
+ });
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Handle search input changes.
554
+ *
555
+ * @param {string} pQuery - The search query
556
+ */
557
+ onSearchInput(pQuery)
558
+ {
559
+ this._performSearch(pQuery);
560
+ }
561
+
562
+ /**
563
+ * Handle keyboard navigation in the search input.
564
+ *
565
+ * @param {KeyboardEvent} pEvent
566
+ */
567
+ onSearchKeydown(pEvent)
568
+ {
569
+ if (pEvent.key === 'Enter')
570
+ {
571
+ pEvent.preventDefault();
572
+ if (pEvent.shiftKey)
573
+ {
574
+ this.navigateMatch(-1);
575
+ }
576
+ else
577
+ {
578
+ this.navigateMatch(1);
579
+ }
580
+ }
581
+ if (pEvent.key === 'Escape')
582
+ {
583
+ pEvent.target.value = '';
584
+ this._clearSearch();
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Navigate to the previous or next search match.
590
+ *
591
+ * @param {number} pDirection - -1 for previous, 1 for next
592
+ */
593
+ navigateMatch(pDirection)
594
+ {
595
+ if (this._searchMatches.length === 0)
596
+ {
597
+ return;
598
+ }
599
+
600
+ // Remove active highlight from current
601
+ if (this._currentMatchIndex >= 0 && this._searchMatches[this._currentMatchIndex])
602
+ {
603
+ this._searchMatches[this._currentMatchIndex].classList.remove('md-ref-highlight-active');
604
+ }
605
+
606
+ this._currentMatchIndex += pDirection;
607
+
608
+ // Wrap around
609
+ if (this._currentMatchIndex >= this._searchMatches.length)
610
+ {
611
+ this._currentMatchIndex = 0;
612
+ }
613
+ if (this._currentMatchIndex < 0)
614
+ {
615
+ this._currentMatchIndex = this._searchMatches.length - 1;
616
+ }
617
+
618
+ // Activate and scroll into view
619
+ let tmpMatch = this._searchMatches[this._currentMatchIndex];
620
+ tmpMatch.classList.add('md-ref-highlight-active');
621
+ tmpMatch.scrollIntoView({ behavior: 'smooth', block: 'center' });
622
+
623
+ this._updateSearchCount();
624
+ }
625
+
626
+ /**
627
+ * Perform text search using TreeWalker to find and highlight matches.
628
+ *
629
+ * @param {string} pQuery - The search term
630
+ */
631
+ _performSearch(pQuery)
632
+ {
633
+ let tmpContentEl = document.getElementById('ContentEditor-MdRef-Content');
634
+ if (!tmpContentEl)
635
+ {
636
+ return;
637
+ }
638
+
639
+ // Restore original content to clear previous highlights
640
+ tmpContentEl.innerHTML = this._originalContent;
641
+
642
+ this._searchMatches = [];
643
+ this._currentMatchIndex = -1;
644
+
645
+ if (!pQuery || pQuery.length < 2)
646
+ {
647
+ this._updateSearchCount();
648
+ this._updateNavButtons();
649
+ return;
650
+ }
651
+
652
+ let tmpQueryLower = pQuery.toLowerCase();
653
+
654
+ // Walk all text nodes and wrap matches in <mark>
655
+ let tmpWalker = document.createTreeWalker(
656
+ tmpContentEl,
657
+ NodeFilter.SHOW_TEXT,
658
+ null,
659
+ false
660
+ );
661
+
662
+ let tmpNodesToProcess = [];
663
+ let tmpNode;
664
+ while ((tmpNode = tmpWalker.nextNode()))
665
+ {
666
+ if (tmpNode.nodeValue.toLowerCase().indexOf(tmpQueryLower) >= 0)
667
+ {
668
+ tmpNodesToProcess.push(tmpNode);
669
+ }
670
+ }
671
+
672
+ for (let i = 0; i < tmpNodesToProcess.length; i++)
673
+ {
674
+ this._highlightTextNode(tmpNodesToProcess[i], tmpQueryLower);
675
+ }
676
+
677
+ // Collect all mark elements
678
+ this._searchMatches = Array.from(
679
+ tmpContentEl.querySelectorAll('mark.md-ref-highlight')
680
+ );
681
+
682
+ // Auto-navigate to first match
683
+ if (this._searchMatches.length > 0)
684
+ {
685
+ this._currentMatchIndex = 0;
686
+ this._searchMatches[0].classList.add('md-ref-highlight-active');
687
+ this._searchMatches[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
688
+ }
689
+
690
+ this._updateSearchCount();
691
+ this._updateNavButtons();
692
+ }
693
+
694
+ /**
695
+ * Replace matching text in a text node with <mark> elements.
696
+ *
697
+ * @param {Text} pTextNode - The text node to process
698
+ * @param {string} pQueryLower - The lowercased search query
699
+ */
700
+ _highlightTextNode(pTextNode, pQueryLower)
701
+ {
702
+ let tmpText = pTextNode.nodeValue;
703
+ let tmpTextLower = tmpText.toLowerCase();
704
+ let tmpParent = pTextNode.parentNode;
705
+ let tmpFragment = document.createDocumentFragment();
706
+ let tmpLastIndex = 0;
707
+ let tmpIndex = tmpTextLower.indexOf(pQueryLower);
708
+
709
+ while (tmpIndex >= 0)
710
+ {
711
+ // Text before match
712
+ if (tmpIndex > tmpLastIndex)
713
+ {
714
+ tmpFragment.appendChild(
715
+ document.createTextNode(tmpText.substring(tmpLastIndex, tmpIndex))
716
+ );
717
+ }
718
+
719
+ // The match
720
+ let tmpMark = document.createElement('mark');
721
+ tmpMark.className = 'md-ref-highlight';
722
+ tmpMark.textContent = tmpText.substring(tmpIndex, tmpIndex + pQueryLower.length);
723
+ tmpFragment.appendChild(tmpMark);
724
+
725
+ tmpLastIndex = tmpIndex + pQueryLower.length;
726
+ tmpIndex = tmpTextLower.indexOf(pQueryLower, tmpLastIndex);
727
+ }
728
+
729
+ // Remaining text after last match
730
+ if (tmpLastIndex < tmpText.length)
731
+ {
732
+ tmpFragment.appendChild(
733
+ document.createTextNode(tmpText.substring(tmpLastIndex))
734
+ );
735
+ }
736
+
737
+ tmpParent.replaceChild(tmpFragment, pTextNode);
738
+ }
739
+
740
+ /**
741
+ * Update the match count display.
742
+ */
743
+ _updateSearchCount()
744
+ {
745
+ let tmpCountEl = document.getElementById('ContentEditor-MdRef-SearchCount');
746
+ if (!tmpCountEl)
747
+ {
748
+ return;
749
+ }
750
+
751
+ if (this._searchMatches.length === 0)
752
+ {
753
+ let tmpInput = document.getElementById('ContentEditor-MdRef-SearchInput');
754
+ if (tmpInput && tmpInput.value && tmpInput.value.length >= 2)
755
+ {
756
+ tmpCountEl.textContent = '0';
757
+ }
758
+ else
759
+ {
760
+ tmpCountEl.textContent = '';
761
+ }
762
+ }
763
+ else
764
+ {
765
+ tmpCountEl.textContent = (this._currentMatchIndex + 1) + '/' + this._searchMatches.length;
766
+ }
767
+ }
768
+
769
+ /**
770
+ * Enable/disable navigation buttons based on match count.
771
+ */
772
+ _updateNavButtons()
773
+ {
774
+ let tmpPrev = document.getElementById('ContentEditor-MdRef-SearchPrev');
775
+ let tmpNext = document.getElementById('ContentEditor-MdRef-SearchNext');
776
+ let tmpHasMatches = this._searchMatches.length > 0;
777
+
778
+ if (tmpPrev) tmpPrev.disabled = !tmpHasMatches;
779
+ if (tmpNext) tmpNext.disabled = !tmpHasMatches;
780
+ }
781
+
782
+ /**
783
+ * Clear search highlights and reset state.
784
+ */
785
+ _clearSearch()
786
+ {
787
+ let tmpContentEl = document.getElementById('ContentEditor-MdRef-Content');
788
+ if (tmpContentEl && this._originalContent)
789
+ {
790
+ tmpContentEl.innerHTML = this._originalContent;
791
+ }
792
+ this._searchMatches = [];
793
+ this._currentMatchIndex = -1;
794
+ this._updateSearchCount();
795
+ this._updateNavButtons();
796
+ }
797
+ }
798
+
799
+ module.exports = ContentEditorMarkdownReferenceView;
800
+
801
+ module.exports.default_configuration = _ViewConfiguration;