mnfst 0.5.163 → 0.5.164
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/lib/manifest.appwrite.auth.js +107 -199
- package/lib/manifest.appwrite.data.js +1 -3
- package/lib/manifest.charts.js +34 -79
- package/lib/manifest.chat.js +515 -0
- package/lib/manifest.code.js +110 -340
- package/lib/manifest.colorpicker.js +252 -551
- package/lib/manifest.combobox.js +73 -91
- package/lib/manifest.components.js +34 -95
- package/lib/manifest.css +0 -132
- package/lib/manifest.data.js +189 -562
- package/lib/manifest.datepicker.js +43 -128
- package/lib/manifest.dropdowns.js +18 -39
- package/lib/manifest.export.js +29 -147
- package/lib/manifest.icons.js +9 -24
- package/lib/manifest.integrity.json +23 -23
- package/lib/manifest.js +62 -126
- package/lib/manifest.localization.js +67 -200
- package/lib/manifest.markdown.js +65 -174
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.payments.js +40 -139
- package/lib/manifest.router.js +24 -73
- package/lib/manifest.status.js +20 -56
- package/lib/manifest.svg.js +12 -30
- package/lib/manifest.tooltips.js +28 -65
- package/lib/manifest.utilities.js +40 -107
- package/lib/manifest.virtual.js +15 -53
- package/package.json +1 -1
- package/lib/manifest.appwrite.presence.js +0 -1650
package/lib/manifest.code.js
CHANGED
|
@@ -1,73 +1,24 @@
|
|
|
1
1
|
/* Manifest Code
|
|
2
2
|
/* By Andrew Matlock under MIT license
|
|
3
3
|
/* https://manifestx.dev
|
|
4
|
-
/*
|
|
5
|
-
/* With reference to:
|
|
6
|
-
/* - highlight.js (https://highlightjs.org)
|
|
7
|
-
/* - CodeJar (https://github.com/antonmedv/codejar)
|
|
8
|
-
/*
|
|
9
|
-
/* Requires Alpine JS (alpinejs.dev) to operate.
|
|
10
|
-
/*
|
|
11
|
-
/* Public API
|
|
12
|
-
/* ----------
|
|
13
|
-
/* <pre x-code> Block; auto-detect language
|
|
14
|
-
/* <pre x-code="javascript"> Block; explicit language
|
|
15
|
-
/* <pre x-code="javascript" lines copy> Line numbers + floating copy button
|
|
16
|
-
/* <pre x-code="javascript" name="API call"> Adds a header/title bar
|
|
17
|
-
/* <pre x-code="javascript" collapse> Collapsed when long; default 20 lines
|
|
18
|
-
/* <pre x-code="javascript" collapse="10"> Collapsed to first 10 lines
|
|
19
|
-
/* <pre x-code="javascript" edit> CodeJar-powered editor
|
|
20
|
-
/* <pre x-code="html" from="#demo-id"> Content sourced from another element's innerHTML
|
|
21
|
-
/*
|
|
22
|
-
/* <code x-code="bash">npm i mnfst</code> Inline; highlighted in place
|
|
23
|
-
/* <code x-code="bash" copy>npm i mnfst</code>Inline; click-to-copy
|
|
24
|
-
/*
|
|
25
|
-
/* <div x-code-group> Tab strip across direct children with [name]
|
|
26
|
-
/* <aside class="frame" name="HTML">… Frame is a tab panel
|
|
27
|
-
/* <pre x-code="html" name="HTML">… Code block is a tab panel
|
|
28
|
-
/* … Children without [name] are always visible
|
|
29
|
-
/* </div>
|
|
30
4
|
*/
|
|
31
5
|
|
|
32
6
|
// ─── Library loaders ─────────────────────────────────────────────────────────
|
|
33
7
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// distinct language on the page. Used when every
|
|
39
|
-
// code block declares an explicit language.
|
|
40
|
-
// Loaded via ESM dynamic imports from esm.run
|
|
41
|
-
// (jsDelivr's CJS→ESM transpiler); the npm
|
|
42
|
-
// package's lib/core and lib/languages/* are
|
|
43
|
-
// CommonJS-only, so we go through esm.run to
|
|
44
|
-
// get browser-native ESM.
|
|
45
|
-
//
|
|
46
|
-
// Full mode (fallback): highlight.min.js (~42 KB gz with ~36 common
|
|
47
|
-
// languages, IIFE-wrapped). Used when any block
|
|
48
|
-
// requests auto-detect (empty/auto x-code value)
|
|
49
|
-
// since hljs needs the whole language set to
|
|
50
|
-
// pick. Loaded as a classic <script> from
|
|
51
|
-
// cdn-release because it's the only build with
|
|
52
|
-
// the languages baked in.
|
|
53
|
-
//
|
|
54
|
-
// We decide once on first call by scanning the page. If a later markdown
|
|
55
|
-
// injection introduces an auto-detect block, the next loadHighlightJS()
|
|
56
|
-
// transparently switches to full mode — the prior core load is harmless
|
|
57
|
-
// (hljs is a single global namespace, the full bundle overwrites it).
|
|
8
|
+
// Lean mode: core + one language module per explicit language (via esm.run
|
|
9
|
+
// ESM). Full mode: the whole bundle, used once any block wants auto-detect.
|
|
10
|
+
// Decided on first call; a later auto-detect block flips to full mode
|
|
11
|
+
// (harmless — the full bundle overwrites the single hljs global).
|
|
58
12
|
|
|
59
13
|
const HLJS_FULL_URL = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js';
|
|
60
|
-
// esm.run resolves extension-less specifiers;
|
|
61
|
-
// deprecation warning at module-evaluation time.
|
|
14
|
+
// esm.run resolves extension-less specifiers; ".js" triggers a deprecation warning.
|
|
62
15
|
const HLJS_CORE_URL = 'https://esm.run/highlight.js@11.11.1/lib/core';
|
|
63
16
|
const HLJS_LANG_BASE = 'https://esm.run/highlight.js@11.11.1/lib/languages/';
|
|
64
17
|
|
|
65
18
|
let hljsCorePromise = null;
|
|
66
19
|
let hljsFullPromise = null;
|
|
67
20
|
const langLoadPromises = new Map();
|
|
68
|
-
//
|
|
69
|
-
// block, any language-module load failure). Once flipped, never resets —
|
|
70
|
-
// we keep using the full bundle for the rest of the page lifecycle.
|
|
21
|
+
// Set once we've committed to the full bundle; never resets.
|
|
71
22
|
let usingFullBundle = false;
|
|
72
23
|
|
|
73
24
|
function injectScript(src) {
|
|
@@ -88,12 +39,8 @@ function injectScript(src) {
|
|
|
88
39
|
});
|
|
89
40
|
}
|
|
90
41
|
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// starts with very few). Threshold of 50 separates the two reliably —
|
|
94
|
-
// neither the lean core nor any realistic per-language top-up will reach
|
|
95
|
-
// 50 languages before the full bundle does, and the full bundle always
|
|
96
|
-
// ships well above that.
|
|
42
|
+
// Detect whether window.hljs is the full bundle (~190 langs) vs lean core.
|
|
43
|
+
// 50 separates the two reliably.
|
|
97
44
|
function hljsIsFullBundle() {
|
|
98
45
|
return typeof window.hljs?.listLanguages === 'function'
|
|
99
46
|
&& window.hljs.listLanguages().length > 50;
|
|
@@ -109,25 +56,16 @@ async function loadHighlightFull() {
|
|
|
109
56
|
}
|
|
110
57
|
|
|
111
58
|
async function loadHighlightCore() {
|
|
112
|
-
// Don't downgrade
|
|
113
|
-
//
|
|
114
|
-
// first), returning the lean core would mean OVERWRITING window.hljs
|
|
115
|
-
// with a 4-language instance, deleting the full bundle's grammars.
|
|
116
|
-
// Per-block callers downstream would then re-request languages,
|
|
117
|
-
// re-fire reactive bumps, and feed an Alpine re-eval loop. Just hand
|
|
118
|
-
// back the full instance — registerLanguage's "already includes"
|
|
119
|
-
// check below makes the rest of the lean path a no-op.
|
|
59
|
+
// Don't downgrade: if the full bundle is already on window.hljs, returning
|
|
60
|
+
// the lean core would clobber its grammars. Hand back the full instance.
|
|
120
61
|
if (hljsIsFullBundle()) return window.hljs;
|
|
121
62
|
if (hljsCorePromise) return hljsCorePromise;
|
|
122
63
|
hljsCorePromise = import(HLJS_CORE_URL).then(mod => {
|
|
123
|
-
// esm.run's CJS→ESM shim exposes hljs as the default export
|
|
124
|
-
//
|
|
125
|
-
// editor, etc.) that read `hljs` as a global keep working.
|
|
64
|
+
// esm.run's CJS→ESM shim exposes hljs as the default export; mirror
|
|
65
|
+
// onto window for call sites that read `hljs` as a global.
|
|
126
66
|
const hl = mod.default;
|
|
127
67
|
if (!hl) throw new Error('hljs undefined after core ESM import');
|
|
128
|
-
//
|
|
129
|
-
// bundle may have arrived in the meantime (its <script> tag races
|
|
130
|
-
// our ESM fetch). Don't clobber it with the lean core.
|
|
68
|
+
// Guard again: the full bundle's <script> may have raced our ESM fetch.
|
|
131
69
|
if (hljsIsFullBundle()) return window.hljs;
|
|
132
70
|
window.hljs = hl;
|
|
133
71
|
return hl;
|
|
@@ -146,9 +84,7 @@ async function registerLanguage(lang) {
|
|
|
146
84
|
const p = import(`${HLJS_LANG_BASE}${resolved}`)
|
|
147
85
|
.then(mod => { core.registerLanguage(resolved, mod.default); })
|
|
148
86
|
.catch(() => {
|
|
149
|
-
//
|
|
150
|
-
// grammar, etc.) the next call will fall back to the full bundle
|
|
151
|
-
// so the block at least renders with auto-detect.
|
|
87
|
+
// Module load failed — fall back to the full bundle (auto-detect).
|
|
152
88
|
usingFullBundle = true;
|
|
153
89
|
langLoadPromises.delete(resolved);
|
|
154
90
|
});
|
|
@@ -156,18 +92,13 @@ async function registerLanguage(lang) {
|
|
|
156
92
|
return p;
|
|
157
93
|
}
|
|
158
94
|
|
|
159
|
-
// Public entry point.
|
|
160
|
-
//
|
|
161
|
-
// `requestedLang`, or to auto-detect if we're in full mode.
|
|
95
|
+
// Public entry point. Returns hljs ready to highlight `requestedLang` (or to
|
|
96
|
+
// auto-detect in full mode).
|
|
162
97
|
async function loadHighlightJS(requestedLang = null) {
|
|
163
|
-
// Once we've committed to the full bundle (a prior block needed
|
|
164
|
-
// auto-detect, or a language module load failed) every subsequent
|
|
165
|
-
// call funnels through it.
|
|
166
98
|
if (usingFullBundle || hljsFullPromise) return loadHighlightFull();
|
|
167
|
-
// Plain-text
|
|
168
|
-
// call, but must NOT escalate to the full bundle or fetch a module.
|
|
99
|
+
// Plain-text needs an hljs instance but must not escalate or fetch a module.
|
|
169
100
|
if (PLAINTEXT_LANGS.has((requestedLang || '').toLowerCase())) return loadHighlightCore();
|
|
170
|
-
//
|
|
101
|
+
// Per-block auto-detect → escalate.
|
|
171
102
|
if (!requestedLang || requestedLang === 'auto') {
|
|
172
103
|
usingFullBundle = true;
|
|
173
104
|
return loadHighlightFull();
|
|
@@ -182,8 +113,7 @@ let codeJarPromise = null;
|
|
|
182
113
|
async function loadCodeJar() {
|
|
183
114
|
if (typeof window.CodeJar === 'function') return window.CodeJar;
|
|
184
115
|
if (codeJarPromise) return codeJarPromise;
|
|
185
|
-
// codejar@4.3.0
|
|
186
|
-
// on window for downstream consumers (e.g. the hero-editor demo).
|
|
116
|
+
// codejar@4.3.0 is ESM-only; import dynamically and expose on window.
|
|
187
117
|
codeJarPromise = import('https://cdn.jsdelivr.net/npm/codejar@4.3.0/dist/codejar.js')
|
|
188
118
|
.then(mod => {
|
|
189
119
|
window.CodeJar = mod.CodeJar;
|
|
@@ -196,8 +126,7 @@ async function loadCodeJar() {
|
|
|
196
126
|
return codeJarPromise;
|
|
197
127
|
}
|
|
198
128
|
|
|
199
|
-
//
|
|
200
|
-
// utility-class scanner doesn't fight with hljs's mutations.
|
|
129
|
+
// Ignore code classes/elements in the utilities scanner so it doesn't fight hljs.
|
|
201
130
|
if (window.ManifestUtilities) {
|
|
202
131
|
window.ManifestUtilities.addIgnoredClassPattern(/^hljs/);
|
|
203
132
|
window.ManifestUtilities.addIgnoredClassPattern(/^language-/);
|
|
@@ -212,20 +141,15 @@ if (window.ManifestUtilities) {
|
|
|
212
141
|
|
|
213
142
|
// ─── Language resolution ─────────────────────────────────────────────────────
|
|
214
143
|
|
|
215
|
-
// Common shortenings
|
|
216
|
-
//
|
|
217
|
-
// the supported-language check before calling hljs.
|
|
144
|
+
// Common shortenings; resolved here to short-circuit the supported-language
|
|
145
|
+
// check before calling hljs.
|
|
218
146
|
const LANGUAGE_ALIASES = {
|
|
219
147
|
js: 'javascript', ts: 'typescript', py: 'python', rb: 'ruby',
|
|
220
148
|
sh: 'bash', shell: 'bash', yml: 'yaml', html: 'xml', svg: 'xml'
|
|
221
149
|
};
|
|
222
150
|
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
// auto-detecting) for content the author explicitly marked plain. `txt` in
|
|
226
|
-
// particular has NO module on the CDN — importing it 404s, and the failed
|
|
227
|
-
// import escalates the whole page to the full bundle. Short-circuiting these
|
|
228
|
-
// keeps the lean path intact and skips the network round-trip entirely.
|
|
151
|
+
// "Render as plain text" languages — no module fetch, no auto-detect. `txt`
|
|
152
|
+
// in particular has no CDN module (a 404 would escalate the page to full mode).
|
|
229
153
|
const PLAINTEXT_LANGS = new Set(['txt', 'text', 'plain', 'plaintext', 'none', 'nohighlight']);
|
|
230
154
|
|
|
231
155
|
function resolveLanguage(hljs, langAttr) {
|
|
@@ -236,18 +160,12 @@ function resolveLanguage(hljs, langAttr) {
|
|
|
236
160
|
|
|
237
161
|
// ─── Content prep helpers ────────────────────────────────────────────────────
|
|
238
162
|
|
|
239
|
-
// Drop
|
|
240
|
-
// Authors typically write <pre x-code> on its own line, leaving stray newlines
|
|
241
|
-
// that throw off line numbering and the collapse threshold. Stripping multiple
|
|
242
|
-
// also covers the case where authors leave a blank line before </pre> for
|
|
243
|
-
// readability, which would otherwise render as a visible trailing gap.
|
|
163
|
+
// Drop leading/trailing blank lines (throw off line numbering and collapse).
|
|
244
164
|
function trimWrappingNewlines(text) {
|
|
245
165
|
return text.replace(/^(?:[ \t]*\n)+/, '').replace(/(?:\n[ \t]*)+$/, '');
|
|
246
166
|
}
|
|
247
167
|
|
|
248
|
-
// Remove the smallest common leading-whitespace block from every non-empty
|
|
249
|
-
// line. Sourced text that came indented under HTML markup looks indented
|
|
250
|
-
// inside the code block too, which is rarely what the author wants.
|
|
168
|
+
// Remove the smallest common leading-whitespace block from every non-empty line.
|
|
251
169
|
function dedent(text) {
|
|
252
170
|
const lines = text.split('\n');
|
|
253
171
|
let minIndent = Infinity;
|
|
@@ -266,20 +184,13 @@ function prepSource(raw) {
|
|
|
266
184
|
return dedent(trimWrappingNewlines(raw));
|
|
267
185
|
}
|
|
268
186
|
|
|
269
|
-
// Resolve
|
|
270
|
-
//
|
|
271
|
-
// source) before falling back to the host element's own content.
|
|
187
|
+
// Resolve an element's source, honouring `from="#id"` (reads the referenced
|
|
188
|
+
// element's innerHTML) before the host's own content.
|
|
272
189
|
//
|
|
273
|
-
// HTML
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
// would
|
|
277
|
-
// innerHTML serialises the DOM back to source text, and a textarea-based
|
|
278
|
-
// decode unwraps any `<`/`>` entities so mixed-style authoring also
|
|
279
|
-
// works. For non-HTML languages we keep textContent — JS/CSS/etc. content
|
|
280
|
-
// rarely contains tags, and innerHTML would re-encode any entities the
|
|
281
|
-
// author DID write back as < (preserving them in the highlighted output
|
|
282
|
-
// as literal text, which is wrong).
|
|
190
|
+
// HTML is the special case: authors write literal markup, so the browser parses
|
|
191
|
+
// it into real DOM nodes — textContent would lose the tags. innerHTML serialises
|
|
192
|
+
// back to source and a textarea decodes entities. Non-HTML keeps textContent
|
|
193
|
+
// (innerHTML would wrongly re-encode any entities the author did write).
|
|
283
194
|
function resolveSource(el) {
|
|
284
195
|
const fromRef = el.getAttribute('from');
|
|
285
196
|
if (fromRef) {
|
|
@@ -287,23 +198,16 @@ function resolveSource(el) {
|
|
|
287
198
|
if (target) return prepSource(target.innerHTML);
|
|
288
199
|
}
|
|
289
200
|
const lang = (el.getAttribute('x-code') || el.getAttribute('language') || '').toLowerCase();
|
|
290
|
-
// Markdown
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
// wrote — preserving valueless attributes (data-tailwind, not
|
|
294
|
-
// data-tailwind=""), multi-line attribute lists, and other formatting
|
|
295
|
-
// that an innerHTML round-trip would normalise away.
|
|
201
|
+
// Markdown emits <pre x-code><code>source</code></pre>. The <code> body is
|
|
202
|
+
// text-only, so textContent returns the exact author source (preserving
|
|
203
|
+
// valueless attributes and formatting an innerHTML round-trip would normalise).
|
|
296
204
|
const childCode = el.querySelector(':scope > code');
|
|
297
205
|
if (childCode) {
|
|
298
206
|
return prepSource(childCode.textContent);
|
|
299
207
|
}
|
|
300
|
-
// Raw HTML
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
// up the browser's `=""` normalisation here, which is unavoidable once
|
|
304
|
-
// the markup has been parsed into real DOM nodes. Authors who want exact
|
|
305
|
-
// source preservation should use a fenced markdown block or write the
|
|
306
|
-
// body as entity-escaped text inside the pre.
|
|
208
|
+
// Raw HTML written directly in an .html file: read innerHTML and decode
|
|
209
|
+
// entities. Valueless attributes pick up the browser's `=""` normalisation
|
|
210
|
+
// here — unavoidable once parsed into DOM nodes.
|
|
307
211
|
if (HTML_LIKE_LANGS.has(lang)) {
|
|
308
212
|
const decoder = document.createElement('textarea');
|
|
309
213
|
decoder.innerHTML = el.innerHTML;
|
|
@@ -312,19 +216,15 @@ function resolveSource(el) {
|
|
|
312
216
|
return prepSource(el.textContent);
|
|
313
217
|
}
|
|
314
218
|
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
// behaves the same regardless of the spelling used in the attribute.
|
|
219
|
+
// All map to "xml" internally; kept in one set so source-reading behaves the
|
|
220
|
+
// same regardless of spelling.
|
|
318
221
|
const HTML_LIKE_LANGS = new Set(['html', 'xml', 'svg', 'xhtml', 'rss', 'atom']);
|
|
319
222
|
|
|
320
223
|
// ─── Highlighting ────────────────────────────────────────────────────────────
|
|
321
224
|
|
|
322
|
-
//
|
|
323
|
-
// language hljs actually used (or null when no highlighting was applied), so
|
|
324
|
-
// callers can mark the host element accordingly.
|
|
225
|
+
// Highlight a <code> element. Returns the language hljs used (or null).
|
|
325
226
|
function highlightInto(codeEl, source, hljs, requestedLang) {
|
|
326
|
-
// Explicit plain text: keep the code-block chrome
|
|
327
|
-
// the background/padding) but apply no token colours and skip auto-detect.
|
|
227
|
+
// Explicit plain text: keep the code-block chrome, no token colours.
|
|
328
228
|
if (PLAINTEXT_LANGS.has((requestedLang || '').toLowerCase())) {
|
|
329
229
|
codeEl.textContent = source;
|
|
330
230
|
codeEl.className = 'hljs language-plaintext';
|
|
@@ -339,9 +239,8 @@ function highlightInto(codeEl, source, hljs, requestedLang) {
|
|
|
339
239
|
codeEl.dataset.highlighted = 'yes';
|
|
340
240
|
return lang;
|
|
341
241
|
}
|
|
342
|
-
// Auto-detect
|
|
343
|
-
//
|
|
344
|
-
// proximity, which fires constantly on any HTML-fragment example.
|
|
242
|
+
// Auto-detect. Skip HTML-looking content — hljs logs a noisy warning for
|
|
243
|
+
// < and > in close proximity.
|
|
345
244
|
codeEl.textContent = source;
|
|
346
245
|
if (!/^[^<]*<\w[^>]*>[^<]*<\/\w/.test(source)) {
|
|
347
246
|
try {
|
|
@@ -373,11 +272,8 @@ function setupInlineCopy(el) {
|
|
|
373
272
|
await navigator.clipboard.writeText(el.textContent);
|
|
374
273
|
el.classList.add('copied');
|
|
375
274
|
setTimeout(() => el.classList.remove('copied'), 1500);
|
|
376
|
-
// Progressive enhancement:
|
|
377
|
-
//
|
|
378
|
-
// flash the copied-icon in a tooltip as confirmation. Skipped
|
|
379
|
-
// entirely if either condition isn't met — the .copied class
|
|
380
|
-
// toggle above is always the baseline feedback.
|
|
275
|
+
// Progressive enhancement: flash a copied-icon tooltip when the
|
|
276
|
+
// tooltip plugin is present and no x-tooltip is already bound.
|
|
381
277
|
if (window.ManifestTooltips && typeof window.ManifestTooltips.showTransient === 'function'
|
|
382
278
|
&& !el.hasAttribute('x-tooltip')) {
|
|
383
279
|
window.ManifestTooltips.showTransient(
|
|
@@ -396,19 +292,13 @@ function setupInlineCopy(el) {
|
|
|
396
292
|
// ─── Block (<pre x-code>) ────────────────────────────────────────────────────
|
|
397
293
|
|
|
398
294
|
async function setupBlock(pre, hljs) {
|
|
399
|
-
// Build-once guard
|
|
400
|
-
// (processCodeElement for standalone, setupCodeGroup for panels) and a
|
|
401
|
-
// re-entry would read pre.textContent — which now includes the .lines
|
|
402
|
-
// gutter's "1\n2\n…" — as fresh source, repeatedly prepending the
|
|
403
|
-
// gutter digits to the code.
|
|
295
|
+
// Build-once guard — re-entry would read the .lines gutter digits back as source.
|
|
404
296
|
if (pre.dataset.codeBlockBuilt === 'yes') return;
|
|
405
297
|
pre.dataset.codeBlockBuilt = 'yes';
|
|
406
298
|
|
|
407
299
|
const source = resolveSource(pre);
|
|
408
300
|
const requested = pre.getAttribute('x-code') || pre.getAttribute('language');
|
|
409
301
|
|
|
410
|
-
// Reset internal structure. We always rebuild deterministically so the
|
|
411
|
-
// first call is the only one that lays anything out.
|
|
412
302
|
pre.innerHTML = '';
|
|
413
303
|
|
|
414
304
|
// ARIA: region + label when titled
|
|
@@ -416,10 +306,7 @@ async function setupBlock(pre, hljs) {
|
|
|
416
306
|
if (title && !pre.hasAttribute('aria-label')) pre.setAttribute('aria-label', title);
|
|
417
307
|
if (!pre.hasAttribute('role')) pre.setAttribute('role', 'region');
|
|
418
308
|
|
|
419
|
-
// Title bar —
|
|
420
|
-
// strip already shows the [name], so a per-panel title bar would
|
|
421
|
-
// duplicate it visually). The aria-label is set either way for assistive
|
|
422
|
-
// tech.
|
|
309
|
+
// Title bar — skipped inside a code-group (the tab strip already shows [name]).
|
|
423
310
|
const inGroup = !!pre.closest('[x-code-group]');
|
|
424
311
|
if (title && !inGroup) {
|
|
425
312
|
const header = document.createElement('header');
|
|
@@ -448,11 +335,8 @@ async function setupBlock(pre, hljs) {
|
|
|
448
335
|
const actualLang = highlightInto(code, source, hljs, requested);
|
|
449
336
|
pre.appendChild(code);
|
|
450
337
|
|
|
451
|
-
// Copy button (floating, top-end). Suppressed
|
|
452
|
-
//
|
|
453
|
-
// wrapper-level copy button (which targets the active panel) so the
|
|
454
|
-
// affordance stays at the outermost container's top-end regardless
|
|
455
|
-
// of which child(ren) carry [copy].
|
|
338
|
+
// Copy button (floating, top-end). Suppressed inside a group — the group
|
|
339
|
+
// owns a single wrapper-level copy button targeting the active panel.
|
|
456
340
|
if (pre.hasAttribute('copy') && !inGroup) setupBlockCopy(pre, code);
|
|
457
341
|
|
|
458
342
|
// Collapse
|
|
@@ -487,9 +371,7 @@ function setupCollapse(pre, code) {
|
|
|
487
371
|
const lineCount = code.textContent.split('\n').length;
|
|
488
372
|
if (lineCount <= collapseAt) return;
|
|
489
373
|
|
|
490
|
-
// Expose the threshold to CSS
|
|
491
|
-
// count exactly. Line-height in our typography is 1.5, so N lines === N
|
|
492
|
-
// × 1.5em of content height (plus pre padding handled by the selector).
|
|
374
|
+
// Expose the threshold to CSS for the max-height (line-height is 1.5).
|
|
493
375
|
pre.style.setProperty('--collapse-lines', String(collapseAt));
|
|
494
376
|
pre.setAttribute('data-collapsed', '');
|
|
495
377
|
|
|
@@ -498,10 +380,8 @@ function setupCollapse(pre, code) {
|
|
|
498
380
|
btn.type = 'button';
|
|
499
381
|
btn.setAttribute('aria-expanded', 'false');
|
|
500
382
|
const hiddenCount = lineCount - collapseAt;
|
|
501
|
-
// Visual label is locale-safe (
|
|
502
|
-
//
|
|
503
|
-
// "collapse". Screen readers receive an explicit English aria-label
|
|
504
|
-
// so the action remains intelligible regardless of the visual glyph.
|
|
383
|
+
// Visual label is locale-safe ("+N" / "−"); screen readers get an explicit
|
|
384
|
+
// English aria-label.
|
|
505
385
|
const updateLabel = () => {
|
|
506
386
|
const collapsed = pre.hasAttribute('data-collapsed');
|
|
507
387
|
btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
|
@@ -523,8 +403,7 @@ async function setupEditor(pre, code, lang, hljs) {
|
|
|
523
403
|
try {
|
|
524
404
|
const CodeJar = await loadCodeJar();
|
|
525
405
|
code.setAttribute('contenteditable', 'plaintext-only');
|
|
526
|
-
//
|
|
527
|
-
// to plain "true". CodeJar handles paste sanitization either way.
|
|
406
|
+
// Older Safari lacks plaintext-only — fall back to "true".
|
|
528
407
|
if (code.getAttribute('contenteditable') !== 'plaintext-only' && code.contentEditable !== 'plaintext-only') {
|
|
529
408
|
code.setAttribute('contenteditable', 'true');
|
|
530
409
|
}
|
|
@@ -543,12 +422,9 @@ async function setupEditor(pre, code, lang, hljs) {
|
|
|
543
422
|
indentOn: /[{[(]\s*$|<[a-zA-Z][^<>]*(?<!\/)>$/,
|
|
544
423
|
addClosing: true
|
|
545
424
|
});
|
|
546
|
-
// CodeJar applies
|
|
547
|
-
// is true no-wrap (lines extend, parent scrolls), so override.
|
|
425
|
+
// CodeJar applies pre-wrap; our blocks are no-wrap (parent scrolls).
|
|
548
426
|
code.style.whiteSpace = 'pre';
|
|
549
|
-
// Expose the editor instance on the host
|
|
550
|
-
// editor) can wire onUpdate / updateCode / save / restore without
|
|
551
|
-
// re-mounting CodeJar themselves.
|
|
427
|
+
// Expose the editor instance on the host for consumers (e.g. hero editor).
|
|
552
428
|
pre._codeJar = editor;
|
|
553
429
|
pre.dispatchEvent(new CustomEvent('code:editor-ready', { detail: { editor } }));
|
|
554
430
|
} catch { /* CodeJar load / mount failed — fail silently */ }
|
|
@@ -559,16 +435,11 @@ async function setupEditor(pre, code, lang, hljs) {
|
|
|
559
435
|
// Process one host element. Routes to inline vs block based on tag.
|
|
560
436
|
async function processCodeElement(el) {
|
|
561
437
|
if (el.dataset.codeProcessed === 'yes') return;
|
|
562
|
-
// Group-owned panels are handled wholesale by setupCodeGroup
|
|
563
|
-
// <pre x-code> sibling is consumed into a <code> child of the new
|
|
564
|
-
// group wrapper. Skip here so we don't double-process / re-parent.
|
|
438
|
+
// Group-owned panels are handled wholesale by setupCodeGroup — skip here.
|
|
565
439
|
if (el.parentElement && el.parentElement.hasAttribute('x-code-group')) return;
|
|
566
440
|
el.dataset.codeProcessed = 'yes';
|
|
567
441
|
|
|
568
442
|
try {
|
|
569
|
-
// Pass the requested language to the loader so it can stay in lean
|
|
570
|
-
// mode (core + only this language) when the rest of the page only
|
|
571
|
-
// uses explicit languages too.
|
|
572
443
|
const requestedLang = el.getAttribute('x-code') || el.getAttribute('language');
|
|
573
444
|
const hljs = await loadHighlightJS(requestedLang);
|
|
574
445
|
const tag = el.tagName;
|
|
@@ -595,61 +466,26 @@ async function processCodeElement(el) {
|
|
|
595
466
|
|
|
596
467
|
// ─── Code groups (tab strip across [name] siblings) ──────────────────────────
|
|
597
468
|
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
// <button role=tab>CSS</button> process time)
|
|
604
|
-
// </header>
|
|
605
|
-
// <pre x-code="html" name=HTML role=tabpanel>...full block...</pre>
|
|
606
|
-
// <pre x-code="css" name=CSS role=tabpanel style="display:none">...</pre>
|
|
607
|
-
// <aside class=frame name=Preview role=tabpanel style="display:none">...</aside>
|
|
608
|
-
// <button.copy> ← present when wrapper has [copy]
|
|
609
|
-
// </pre>
|
|
610
|
-
//
|
|
611
|
-
// Each code panel is a full <pre x-code> block with its own line numbers,
|
|
612
|
-
// collapse toggle, editor — i.e. it runs through setupBlock. The wrapper
|
|
613
|
-
// inherits per-panel feature attributes (lines, edit, collapse) to child
|
|
614
|
-
// panels that don't set them, so:
|
|
615
|
-
//
|
|
616
|
-
// <pre x-code-group lines>
|
|
617
|
-
// <pre x-code="html" name=HTML>...</pre>
|
|
618
|
-
// <pre x-code="css" name=CSS>...</pre>
|
|
619
|
-
// </pre>
|
|
620
|
-
//
|
|
621
|
-
// behaves as if each child had `lines` written on it directly. Children
|
|
622
|
-
// that DO set the attribute (or set it to a different value, e.g. per-panel
|
|
623
|
-
// collapse="5") win over inheritance.
|
|
624
|
-
//
|
|
625
|
-
// `copy` is NOT inherited — instead, when present on the wrapper, the
|
|
626
|
-
// plugin attaches a single copy button to the wrapper itself that targets
|
|
627
|
-
// whichever panel is currently active. This keeps the button on the
|
|
628
|
-
// element that actually carries the [copy] attribute, and avoids the
|
|
629
|
-
// visual clutter of one button per tab.
|
|
469
|
+
// A <pre|div x-code-group> wrapper coordinates tabs across its [name] children.
|
|
470
|
+
// Each code panel is a full <pre x-code> block run through setupBlock. Feature
|
|
471
|
+
// attrs (lines/edit/collapse) inherit from wrapper to children that don't set
|
|
472
|
+
// them; children that do win. `copy` is NOT inherited — a single wrapper-level
|
|
473
|
+
// copy button targets the active panel instead.
|
|
630
474
|
const GROUP_INHERITABLE_ATTRS = ['lines', 'edit', 'collapse'];
|
|
631
475
|
|
|
632
476
|
async function setupCodeGroup(group) {
|
|
633
477
|
if (group.dataset.groupProcessed === 'yes') return;
|
|
634
478
|
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
// panel is active). When nothing is named, the group has no tabs at all
|
|
638
|
-
// and renders as a borderless wrapper around its (always-visible) kids —
|
|
639
|
-
// a frame + code pair, for instance, with no title overhead.
|
|
479
|
+
// Panels are children with [name]; children without are ambient (always
|
|
480
|
+
// visible). No names → borderless wrapper around its kids, no tabs.
|
|
640
481
|
const sourcePanels = Array.from(group.children).filter(c => c.hasAttribute('name'));
|
|
641
482
|
const ambientChildren = Array.from(group.children).filter(c => !c.hasAttribute('name'));
|
|
642
483
|
if (sourcePanels.length === 0 && ambientChildren.length === 0) return;
|
|
643
|
-
// Claim
|
|
644
|
-
// observer can both arrive before the first call's `await` resolves)
|
|
645
|
-
// bail out — otherwise the wrapper accumulates duplicate tab strips.
|
|
484
|
+
// Claim synchronously so re-entrant callers (directive + observer) bail out.
|
|
646
485
|
group.dataset.groupProcessed = 'yes';
|
|
647
486
|
|
|
648
|
-
// Inherit feature attributes
|
|
649
|
-
//
|
|
650
|
-
// code pair in an unnamed group still benefits from group-level [lines]
|
|
651
|
-
// / [edit] / [collapse]. Run BEFORE setupBlock so the inherited attrs
|
|
652
|
-
// drive that block's setup.
|
|
487
|
+
// Inherit feature attributes to child <pre x-code> that don't set them.
|
|
488
|
+
// Run BEFORE setupBlock so inherited attrs drive its setup.
|
|
653
489
|
const allCodeChildren = [...sourcePanels, ...ambientChildren].filter(c => c.tagName === 'PRE');
|
|
654
490
|
for (const panel of allCodeChildren) {
|
|
655
491
|
for (const attr of GROUP_INHERITABLE_ATTRS) {
|
|
@@ -659,9 +495,7 @@ async function setupCodeGroup(group) {
|
|
|
659
495
|
}
|
|
660
496
|
}
|
|
661
497
|
|
|
662
|
-
// Ordered unique tab names.
|
|
663
|
-
// frame + a code block co-visible under one tab); each panel is shown
|
|
664
|
-
// independently when its tab activates.
|
|
498
|
+
// Ordered unique tab names. Panels may share a name (frame + code co-visible).
|
|
665
499
|
const tabNames = [];
|
|
666
500
|
for (const p of sourcePanels) {
|
|
667
501
|
const n = p.getAttribute('name');
|
|
@@ -670,9 +504,8 @@ async function setupCodeGroup(group) {
|
|
|
670
504
|
const active = tabNames[0];
|
|
671
505
|
const slugify = s => s.replace(/\s+/g, '-').toLowerCase();
|
|
672
506
|
|
|
673
|
-
// Preload hljs + every language
|
|
674
|
-
//
|
|
675
|
-
// without re-loading.
|
|
507
|
+
// Preload hljs + every language across the group so each setupBlock can
|
|
508
|
+
// highlight synchronously.
|
|
676
509
|
const codeLangs = allCodeChildren
|
|
677
510
|
.filter(p => p.hasAttribute('x-code'))
|
|
678
511
|
.map(p => p.getAttribute('x-code'))
|
|
@@ -683,12 +516,8 @@ async function setupCodeGroup(group) {
|
|
|
683
516
|
for (const l of codeLangs.slice(1)) await registerLanguage(l);
|
|
684
517
|
}
|
|
685
518
|
|
|
686
|
-
// Normalize wrapper to <pre
|
|
687
|
-
// <
|
|
688
|
-
// When converting from <div>, transplant children rather than re-creating
|
|
689
|
-
// — the same <pre x-code> nodes that Alpine has wired up keep their
|
|
690
|
-
// identity, so subsequent processOne calls (and any author refs into
|
|
691
|
-
// them) stay valid.
|
|
519
|
+
// Normalize wrapper to <pre> for CSS uniformity. When converting from
|
|
520
|
+
// <div>, transplant children so their Alpine-wired identity stays valid.
|
|
692
521
|
let pre;
|
|
693
522
|
if (group.tagName === 'PRE') {
|
|
694
523
|
pre = group;
|
|
@@ -701,12 +530,8 @@ async function setupCodeGroup(group) {
|
|
|
701
530
|
pre.dataset.groupProcessed = 'yes';
|
|
702
531
|
if (!pre.hasAttribute('role')) pre.setAttribute('role', 'region');
|
|
703
532
|
|
|
704
|
-
// Run each code child
|
|
705
|
-
//
|
|
706
|
-
// editor). Frames stay as-is. setupBlock detects the [x-code-group]
|
|
707
|
-
// ancestor and suppresses the per-panel title bar — the group header
|
|
708
|
-
// (when present) serves that role; for headerless groups, the pre is
|
|
709
|
-
// simply rendered without its own title.
|
|
533
|
+
// Run each code child through setupBlock for full feature treatment.
|
|
534
|
+
// Frames stay as-is; setupBlock suppresses the per-panel title bar in a group.
|
|
710
535
|
for (const panel of allCodeChildren) {
|
|
711
536
|
if (panel.hasAttribute('x-code')) {
|
|
712
537
|
await setupBlock(panel, hljs);
|
|
@@ -714,11 +539,8 @@ async function setupCodeGroup(group) {
|
|
|
714
539
|
}
|
|
715
540
|
}
|
|
716
541
|
|
|
717
|
-
// Header: tab strip
|
|
718
|
-
//
|
|
719
|
-
// the header for headerless groups lets authors pair a frame + code block
|
|
720
|
-
// inside <div x-code-group> with no repetitive "HTML" title — both render
|
|
721
|
-
// as ambient siblings inside the wrapper.
|
|
542
|
+
// Header: tab strip for multiple named panels, plain title bar for one,
|
|
543
|
+
// none when nothing is named.
|
|
722
544
|
const isSingleTab = tabNames.length === 1;
|
|
723
545
|
const isHeaderless = tabNames.length === 0;
|
|
724
546
|
let header = null;
|
|
@@ -731,12 +553,8 @@ async function setupCodeGroup(group) {
|
|
|
731
553
|
header.appendChild(titleEl);
|
|
732
554
|
if (!pre.hasAttribute('aria-label')) pre.setAttribute('aria-label', active);
|
|
733
555
|
} else if (!isHeaderless) {
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
// there are too many tabs to fit) without dragging sibling header
|
|
737
|
-
// content into the scroll region. CSS targets the inner element via
|
|
738
|
-
// [role="tablist"]. The unstyle class opts out of the generic tab
|
|
739
|
-
// bar styling in manifest.form.css; manifest.code.css styles it.
|
|
556
|
+
// role=tablist on an inner <div> so it can overflow-x scroll on its own.
|
|
557
|
+
// `unstyle` opts out of manifest.form.css's generic tab styling.
|
|
740
558
|
tablist = document.createElement('div');
|
|
741
559
|
tablist.className = 'unstyle';
|
|
742
560
|
tablist.setAttribute('role', 'tablist');
|
|
@@ -774,11 +592,8 @@ async function setupCodeGroup(group) {
|
|
|
774
592
|
}
|
|
775
593
|
if (header) pre.insertBefore(header, pre.firstChild);
|
|
776
594
|
|
|
777
|
-
// Wire ARIA / IDs
|
|
778
|
-
//
|
|
779
|
-
// standalone role="region" + aria-label shape (label set on the wrapper
|
|
780
|
-
// above) so there's no orphan tabpanel without a tablist. Headerless
|
|
781
|
-
// groups have no tabpanels — every child is ambient.
|
|
595
|
+
// Wire ARIA / IDs. Multi-tab: role="tabpanel" + aria-labelledby its tab.
|
|
596
|
+
// Single-tab keeps the region shape (no orphan tabpanel without a tablist).
|
|
782
597
|
sourcePanels.forEach((panel, i) => {
|
|
783
598
|
const name = panel.getAttribute('name');
|
|
784
599
|
panel.id = panel.id || `${slugify(name)}-panel-${i}`;
|
|
@@ -791,16 +606,9 @@ async function setupCodeGroup(group) {
|
|
|
791
606
|
}
|
|
792
607
|
});
|
|
793
608
|
|
|
794
|
-
// Wrapper-level copy button
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
// [copy]. setupBlock suppresses the per-panel copy button when inside a
|
|
798
|
-
// group (the inGroup check) so we never duplicate.
|
|
799
|
-
//
|
|
800
|
-
// Appended as a direct child of the <pre> (sibling to the header) so it
|
|
801
|
-
// can be absolutely positioned over the top-end of the wrapper without
|
|
802
|
-
// competing with the header's own overflow-scroll region. Same
|
|
803
|
-
// positioning rule as for a standalone <pre x-code copy>.
|
|
609
|
+
// Wrapper-level copy button — created when [copy] is on the wrapper or any
|
|
610
|
+
// panel. Appended as a direct child of <pre> so it can position over the
|
|
611
|
+
// top-end without competing with the header's scroll region.
|
|
804
612
|
const wrapperHasCopy = pre.hasAttribute('copy');
|
|
805
613
|
const anyPanelCopy = [...sourcePanels, ...ambientChildren].some(p => p.hasAttribute('copy'));
|
|
806
614
|
let copyBtn = null;
|
|
@@ -810,11 +618,8 @@ async function setupCodeGroup(group) {
|
|
|
810
618
|
copyBtn.type = 'button';
|
|
811
619
|
copyBtn.setAttribute('aria-label', 'Copy code to clipboard');
|
|
812
620
|
copyBtn.addEventListener('click', async () => {
|
|
813
|
-
//
|
|
814
|
-
//
|
|
815
|
-
// is what an author wants to take, not the rendered frame's
|
|
816
|
-
// text content. Headerless groups have no active name, so pick
|
|
817
|
-
// the first ambient <pre x-code> child instead.
|
|
621
|
+
// Prefer the <pre x-code> panel for copy (its source, not a frame's
|
|
622
|
+
// rendered text). Headerless groups pick the first ambient <pre x-code>.
|
|
818
623
|
let activePanel;
|
|
819
624
|
if (isHeaderless) {
|
|
820
625
|
activePanel = ambientChildren.find(p => p.tagName === 'PRE' && p.hasAttribute('x-code'));
|
|
@@ -833,13 +638,9 @@ async function setupCodeGroup(group) {
|
|
|
833
638
|
pre.appendChild(copyBtn);
|
|
834
639
|
}
|
|
835
640
|
|
|
836
|
-
// Visibility toggle
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
// visibility per-tab: when [copy] sits on the wrapper it stays visible
|
|
840
|
-
// for every tab; when it sits only on individual panels, the button
|
|
841
|
-
// shows for tabs whose panels carry [copy] and hides for the rest.
|
|
842
|
-
// Headerless groups have no tabs to switch, so activate() is a no-op.
|
|
641
|
+
// Visibility toggle via style.display (pre's display:flex outweighs the UA
|
|
642
|
+
// [hidden] rule). Also flips per-tab copy-button visibility when [copy] is
|
|
643
|
+
// per-panel rather than on the wrapper.
|
|
843
644
|
let activeName = active;
|
|
844
645
|
function activate(name) {
|
|
845
646
|
activeName = name;
|
|
@@ -863,11 +664,8 @@ async function setupCodeGroup(group) {
|
|
|
863
664
|
|
|
864
665
|
// ─── Page scan + observation ─────────────────────────────────────────────────
|
|
865
666
|
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
// so they flow through the same processor as authored <pre x-code> blocks.
|
|
869
|
-
// Accepts either a Document/Element (scans descendants) or a single <pre>
|
|
870
|
-
// element (adopts just that one).
|
|
667
|
+
// Promote markdown's <pre><code class="language-X"> to first-class hosts by
|
|
668
|
+
// setting x-code on the <pre>. Accepts a Document/Element (scans) or one <pre>.
|
|
871
669
|
function adoptMarkdownBlocks(root = document) {
|
|
872
670
|
if (root && root.tagName === 'PRE' && !root.hasAttribute('x-code')) {
|
|
873
671
|
const code = root.querySelector(':scope > code[class*="language-"]');
|
|
@@ -892,15 +690,9 @@ function adoptMarkdownBlocks(root = document) {
|
|
|
892
690
|
}
|
|
893
691
|
}
|
|
894
692
|
|
|
895
|
-
// Per-element IntersectionObserver — each
|
|
896
|
-
//
|
|
897
|
-
//
|
|
898
|
-
// router) shouldn't trigger loadHighlightJS for their hidden blocks.
|
|
899
|
-
// A page-wide scan would scoop them up and an auto-detect block in a
|
|
900
|
-
// hidden route would push the loader into full-bundle mode even
|
|
901
|
-
// though the visible route is lean-mode eligible.
|
|
902
|
-
// 2. Long pages with many code blocks don't pay the highlight cost up
|
|
903
|
-
// front — each block runs hljs only when it nears the viewport.
|
|
693
|
+
// Per-element IntersectionObserver — process each block only when it nears the
|
|
694
|
+
// viewport. Keeps hidden SPA routes from prematurely escalating the loader to
|
|
695
|
+
// full mode, and defers highlight cost on long pages.
|
|
904
696
|
let codeIO = null;
|
|
905
697
|
|
|
906
698
|
function ensureObserver() {
|
|
@@ -908,12 +700,9 @@ function ensureObserver() {
|
|
|
908
700
|
codeIO = new IntersectionObserver((entries, observer) => {
|
|
909
701
|
for (const entry of entries) {
|
|
910
702
|
if (!entry.isIntersecting) continue;
|
|
911
|
-
//
|
|
912
|
-
//
|
|
913
|
-
//
|
|
914
|
-
// has applied display:none. checkVisibility() is the source of
|
|
915
|
-
// truth; if the element is actually hidden, leave it observed so
|
|
916
|
-
// a future route change re-fires the IO when it becomes visible.
|
|
703
|
+
// During Alpine boot the IO can fire "intersecting" before the router
|
|
704
|
+
// applies display:none. checkVisibility() is the source of truth;
|
|
705
|
+
// if hidden, leave it observed to re-fire on a future route change.
|
|
917
706
|
const t = entry.target;
|
|
918
707
|
if (typeof t.checkVisibility === 'function' && !t.checkVisibility()) continue;
|
|
919
708
|
observer.unobserve(t);
|
|
@@ -924,19 +713,14 @@ function ensureObserver() {
|
|
|
924
713
|
}
|
|
925
714
|
|
|
926
715
|
function handleVisible(el) {
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
// scrolls — once hljs is loaded, everything currently on screen gets
|
|
931
|
-
// styled at once. Below-the-fold and hidden-route candidates stay
|
|
932
|
-
// observed and process when they later become visible.
|
|
716
|
+
// Once one block crosses into view, eagerly process every currently-visible
|
|
717
|
+
// candidate so highlighting doesn't "pop" in during scroll. Below-the-fold
|
|
718
|
+
// and hidden-route candidates stay observed for later.
|
|
933
719
|
const candidates = document.querySelectorAll(
|
|
934
720
|
'[x-code]:not([data-code-processed]),' +
|
|
935
721
|
'[x-code-group]:not([data-group-processed]),' +
|
|
936
722
|
'pre:not([x-code]):not([data-code-processed]) > code[class*="language-"],' +
|
|
937
|
-
// Copy-only inline codespans
|
|
938
|
-
// that also carry x-code — they're already covered by the first
|
|
939
|
-
// selector — and skip <code copy> inside a <pre> (block-level path).
|
|
723
|
+
// Copy-only inline codespans (`text`{copy}); skip x-code and in-pre ones.
|
|
940
724
|
'code[copy]:not([x-code]):not([data-code-processed]):not(pre > code)'
|
|
941
725
|
);
|
|
942
726
|
// Always include the triggering element (it's already known to be visible).
|
|
@@ -959,9 +743,7 @@ function processOne(el) {
|
|
|
959
743
|
adoptMarkdownBlocks(el.parentElement);
|
|
960
744
|
processCodeElement(el.parentElement);
|
|
961
745
|
} else if (el.tagName === 'CODE' && el.hasAttribute('copy')) {
|
|
962
|
-
// Copy-only inline codespan
|
|
963
|
-
// no highlighting, just wire the click-to-copy handler. Marking it
|
|
964
|
-
// as processed prevents re-discovery on subsequent scans.
|
|
746
|
+
// Copy-only inline codespan — just wire click-to-copy.
|
|
965
747
|
if (el.dataset.codeProcessed !== 'yes') {
|
|
966
748
|
el.dataset.codeProcessed = 'yes';
|
|
967
749
|
setupInlineCopy(el);
|
|
@@ -969,29 +751,21 @@ function processOne(el) {
|
|
|
969
751
|
}
|
|
970
752
|
}
|
|
971
753
|
|
|
972
|
-
//
|
|
973
|
-
//
|
|
974
|
-
//
|
|
975
|
-
// visible at call time are processed immediately, then unobserved — this
|
|
976
|
-
// makes markdown-plugin-injected blocks render synchronously instead of
|
|
977
|
-
// waiting for the next IO callback (which can stall in headless tests
|
|
978
|
-
// and feels laggy for live markdown updates).
|
|
754
|
+
// Observe every unprocessed candidate in `root` (idempotent). Already-visible
|
|
755
|
+
// elements process immediately so markdown-injected blocks render synchronously
|
|
756
|
+
// instead of waiting for the next IO callback.
|
|
979
757
|
function observeAll(root = document) {
|
|
980
758
|
const io = ensureObserver();
|
|
981
759
|
const candidates = [
|
|
982
760
|
...root.querySelectorAll('[x-code]:not([data-code-processed])'),
|
|
983
761
|
...root.querySelectorAll('[x-code-group]:not([data-group-processed])'),
|
|
984
762
|
...root.querySelectorAll('pre:not([x-code]):not([data-code-processed]) > code[class*="language-"]'),
|
|
985
|
-
// Copy-only inline codespans (
|
|
986
|
-
// `text`{copy} syntax. These need only the copy handler wired, no
|
|
987
|
-
// highlighting work.
|
|
763
|
+
// Copy-only inline codespans (`text`{copy}) — copy handler only.
|
|
988
764
|
...root.querySelectorAll('code[copy]:not([x-code]):not([data-code-processed]):not(pre > code)')
|
|
989
765
|
];
|
|
990
766
|
for (const el of candidates) {
|
|
991
767
|
io.observe(el);
|
|
992
|
-
//
|
|
993
|
-
// (display:none route panels, off-screen drawers): keep them observed
|
|
994
|
-
// so they highlight on the next intersection callback when revealed.
|
|
768
|
+
// Hidden elements stay observed to highlight when later revealed.
|
|
995
769
|
if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) continue;
|
|
996
770
|
io.unobserve(el);
|
|
997
771
|
processOne(el);
|
|
@@ -1013,17 +787,14 @@ function registerAlpine() {
|
|
|
1013
787
|
if (window.__manifestCodeDirectivesRegistered) return;
|
|
1014
788
|
window.__manifestCodeDirectivesRegistered = true;
|
|
1015
789
|
|
|
1016
|
-
// `x-code
|
|
1017
|
-
//
|
|
1018
|
-
// observe instead, so a hidden route's blocks don't run hljs until the
|
|
1019
|
-
// user actually navigates there.
|
|
790
|
+
// `x-code` on any element — observe rather than process, so hidden routes
|
|
791
|
+
// don't run hljs until navigated to.
|
|
1020
792
|
Alpine.directive('code', (el) => {
|
|
1021
793
|
if (el.dataset.codeProcessed === 'yes') return;
|
|
1022
794
|
ensureObserver().observe(el);
|
|
1023
795
|
});
|
|
1024
796
|
|
|
1025
|
-
// `x-code-group`
|
|
1026
|
-
// children. The wrapper has no expression value.
|
|
797
|
+
// `x-code-group` wrapper; tabs across [name] children.
|
|
1027
798
|
Alpine.directive('code-group', (el) => {
|
|
1028
799
|
if (el.dataset.groupProcessed === 'yes') return;
|
|
1029
800
|
ensureObserver().observe(el);
|
|
@@ -1056,8 +827,7 @@ async function ensureCodePluginInitialized() {
|
|
|
1056
827
|
|
|
1057
828
|
window.ensureCodePluginInitialized = ensureCodePluginInitialized;
|
|
1058
829
|
|
|
1059
|
-
// Expose select internals
|
|
1060
|
-
// in without re-implementing the loaders or processors.
|
|
830
|
+
// Expose select internals for the markdown plugin and other consumers.
|
|
1061
831
|
window.ManifestCode = {
|
|
1062
832
|
loadHighlightJS,
|
|
1063
833
|
loadCodeJar,
|