mnfst 0.5.81 → 0.5.82
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/LICENSE +1 -1
- package/lib/manifest.accordion.css +4 -4
- package/lib/manifest.avatar.css +8 -8
- package/lib/manifest.button.css +7 -7
- package/lib/manifest.checkbox.css +5 -5
- package/lib/manifest.code.css +148 -201
- package/lib/manifest.code.js +841 -881
- package/lib/manifest.code.min.css +1 -1
- package/lib/manifest.colorpicker.css +11 -11
- package/lib/manifest.css +253 -207
- package/lib/manifest.data.js +4 -1
- package/lib/manifest.dialog.css +2 -2
- package/lib/manifest.divider.css +2 -2
- package/lib/manifest.dropdown.css +9 -9
- package/lib/manifest.form.css +10 -10
- package/lib/manifest.input.css +9 -9
- package/lib/manifest.integrity.json +6 -6
- package/lib/manifest.js +5 -5
- package/lib/manifest.markdown.js +129 -108
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.radio.css +1 -1
- package/lib/manifest.range.css +7 -7
- package/lib/manifest.resize.css +1 -1
- package/lib/manifest.sidebar.css +2 -3
- package/lib/manifest.slides.css +5 -5
- package/lib/manifest.svg.js +34 -27
- package/lib/manifest.switch.css +4 -4
- package/lib/manifest.table.css +4 -4
- package/lib/manifest.theme.css +46 -41
- package/lib/manifest.toast.css +7 -7
- package/lib/manifest.tooltip.css +3 -3
- package/lib/manifest.tooltips.js +41 -0
- package/lib/manifest.typography.css +109 -50
- package/lib/manifest.utilities.css +41 -53
- package/package.json +1 -1
package/lib/manifest.markdown.js
CHANGED
|
@@ -17,11 +17,8 @@ if (typeof window !== 'undefined') {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// Cache for DOMPurify loading (only fetched when the .safe modifier is used)
|
|
21
|
-
let purifyPromise = null;
|
|
22
|
-
|
|
23
20
|
// DOMPurify config tuned for Manifest's markdown output. The markdown
|
|
24
|
-
// extensions emit <x-
|
|
21
|
+
// extensions emit <x-icon> custom elements and `x-*` directive attributes that must
|
|
25
22
|
// survive sanitization, so custom-element handling is enabled with a
|
|
26
23
|
// tag-name allowlist (x-*) and an attribute filter that rejects event
|
|
27
24
|
// handlers (on*). DOMPurify's defaults handle <script>, javascript: URLs,
|
|
@@ -34,29 +31,36 @@ const MARKDOWN_PURIFY_CONFIG = {
|
|
|
34
31
|
}
|
|
35
32
|
};
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
// DOMPurify loader is defined on window by whichever of svg/markdown loads
|
|
35
|
+
// first (see manifest.svg.js). Declaring `let purifyPromise` here at top
|
|
36
|
+
// level collides with svg.js's identical declaration in the realm's shared
|
|
37
|
+
// global lexical environment, so we use the shared loader instead.
|
|
38
|
+
if (!window.ManifestDOMPurify) {
|
|
39
|
+
window.ManifestDOMPurify = {
|
|
40
|
+
_promise: null,
|
|
41
|
+
load() {
|
|
42
|
+
if (typeof window.DOMPurify !== 'undefined') return Promise.resolve(window.DOMPurify);
|
|
43
|
+
if (this._promise) return this._promise;
|
|
44
|
+
this._promise = new Promise((resolve, reject) => {
|
|
45
|
+
const script = document.createElement('script');
|
|
46
|
+
script.src = 'https://cdn.jsdelivr.net/npm/dompurify@latest/dist/purify.min.js';
|
|
47
|
+
script.onload = () => {
|
|
48
|
+
if (typeof window.DOMPurify !== 'undefined') {
|
|
49
|
+
resolve(window.DOMPurify);
|
|
50
|
+
} else {
|
|
51
|
+
this._promise = null;
|
|
52
|
+
reject(new Error('DOMPurify failed to load'));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
script.onerror = (err) => {
|
|
56
|
+
this._promise = null;
|
|
57
|
+
reject(err);
|
|
58
|
+
};
|
|
59
|
+
document.head.appendChild(script);
|
|
60
|
+
});
|
|
61
|
+
return this._promise;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
// Sanitize HTML if the .safe modifier was used; pass-through otherwise.
|
|
@@ -67,7 +71,7 @@ async function loadDOMPurify() {
|
|
|
67
71
|
async function maybeSanitizeMarkdownHtml(html, safe) {
|
|
68
72
|
if (!safe) return html;
|
|
69
73
|
try {
|
|
70
|
-
const DOMPurify = await
|
|
74
|
+
const DOMPurify = await window.ManifestDOMPurify.load();
|
|
71
75
|
return DOMPurify.sanitize(html, MARKDOWN_PURIFY_CONFIG);
|
|
72
76
|
} catch {
|
|
73
77
|
// Loader failure — fall back to escaping rather than silently emitting
|
|
@@ -117,7 +121,7 @@ async function loadMarkedJS() {
|
|
|
117
121
|
// HTML-escape a string for safe interpolation inside an attribute value.
|
|
118
122
|
// Used by the code-fence renderer below — title/language strings come from
|
|
119
123
|
// the markdown source, so without escaping a fence like ```js " onclick=alert(1) x="
|
|
120
|
-
// could inject arbitrary attributes onto the <
|
|
124
|
+
// could inject arbitrary attributes onto the <pre> element.
|
|
121
125
|
function escapeForAttribute(s) {
|
|
122
126
|
return String(s)
|
|
123
127
|
.replace(/&/g, '&')
|
|
@@ -126,45 +130,49 @@ function escapeForAttribute(s) {
|
|
|
126
130
|
.replace(/>/g, '>');
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
// Escape a literal HTML fragment so it displays as source text when placed
|
|
134
|
+
// inside a <code> element. Used for the ::: frame demo modifier, where the
|
|
135
|
+
// same content is rendered live AND shown below as its own source.
|
|
136
|
+
function escapeForText(s) {
|
|
137
|
+
return String(s)
|
|
138
|
+
.replace(/&/g, '&')
|
|
139
|
+
.replace(/</g, '<')
|
|
140
|
+
.replace(/>/g, '>');
|
|
141
|
+
}
|
|
142
|
+
|
|
129
143
|
// Configure marked to preserve full language strings
|
|
130
144
|
async function configureMarked(marked) {
|
|
131
145
|
marked.use({
|
|
132
146
|
renderer: {
|
|
147
|
+
// Render fenced code blocks as <pre x-code="…"><code>…</code></pre>.
|
|
148
|
+
// The code plugin's directive then handles highlighting, copy
|
|
149
|
+
// buttons, collapse, line numbers, etc. — same code path whether
|
|
150
|
+
// the block was authored in HTML or markdown.
|
|
133
151
|
code(token) {
|
|
134
152
|
const lang = token.lang || '';
|
|
135
153
|
const text = token.text || '';
|
|
136
|
-
const escaped = token.escaped || false;
|
|
137
|
-
|
|
138
|
-
// Parse the language string to extract attributes
|
|
139
|
-
const attributes = parseLanguageString(lang);
|
|
140
154
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
const attrs = parseLanguageString(lang);
|
|
156
|
+
|
|
157
|
+
let preAttrs = '';
|
|
158
|
+
// x-code carries the language as its value (empty string for
|
|
159
|
+
// "no explicit language; auto-detect")
|
|
160
|
+
preAttrs += ` x-code="${attrs.language ? escapeForAttribute(attrs.language) : ''}"`;
|
|
161
|
+
if (attrs.title) preAttrs += ` name="${escapeForAttribute(attrs.title)}"`;
|
|
162
|
+
if (attrs.lines) preAttrs += ' lines';
|
|
163
|
+
if (attrs.copy) preAttrs += ' copy';
|
|
164
|
+
if (attrs.edit) preAttrs += ' edit';
|
|
165
|
+
if (attrs.collapse !== null) {
|
|
166
|
+
preAttrs += attrs.collapse === ''
|
|
167
|
+
? ' collapse'
|
|
168
|
+
: ` collapse="${escapeForAttribute(attrs.collapse)}"`;
|
|
154
169
|
}
|
|
170
|
+
if (attrs.from) preAttrs += ` from="${escapeForAttribute(attrs.from)}"`;
|
|
155
171
|
|
|
156
|
-
//
|
|
157
|
-
let code
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// For HTML language code blocks, preserve the original raw text to maintain indentation
|
|
161
|
-
if (attributes.language === 'html' || text.includes('<!DOCTYPE') || (text.includes('<html') && text.includes('<head') && text.includes('<body'))) {
|
|
162
|
-
// Store the original content in a data attribute to preserve indentation
|
|
163
|
-
preserveOriginal = ` data-original-content="${text.replace(/"/g, '"')}"`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Always create an x-code element, with or without attributes
|
|
167
|
-
return `<x-code${xCodeAttributes}${preserveOriginal}>${code}</x-code>\n`;
|
|
172
|
+
// marked has already HTML-escaped `text`. It's safe to drop
|
|
173
|
+
// it inside <code> and let the code plugin read .textContent
|
|
174
|
+
// as the highlight source.
|
|
175
|
+
return `<pre${preAttrs}><code>${text}</code></pre>\n`;
|
|
168
176
|
}
|
|
169
177
|
},
|
|
170
178
|
// Configure marked to allow custom HTML tags
|
|
@@ -219,9 +227,17 @@ async function configureMarked(marked) {
|
|
|
219
227
|
}
|
|
220
228
|
},
|
|
221
229
|
renderer(token) {
|
|
222
|
-
|
|
230
|
+
let classes = token.classes || '';
|
|
223
231
|
const iconValue = token.iconValue || '';
|
|
224
232
|
|
|
233
|
+
// `::: frame demo` — render the frame contents live AND emit
|
|
234
|
+
// a sibling <pre x-code="html"> showing the same source. Lets
|
|
235
|
+
// authors write the example once and have it both rendered
|
|
236
|
+
// and documented. Strip `demo` from the class list so the
|
|
237
|
+
// resulting <aside> has just `frame`.
|
|
238
|
+
const isDemo = /\bframe\b/.test(classes) && /\bdemo\b/.test(classes);
|
|
239
|
+
if (isDemo) classes = classes.replace(/\bdemo\b/, '').replace(/\s+/g, ' ').trim();
|
|
240
|
+
|
|
225
241
|
// For frame callouts, don't parse as markdown to avoid wrapping HTML in <p> tags
|
|
226
242
|
let parsedContent;
|
|
227
243
|
if (classes.includes('frame')) {
|
|
@@ -247,7 +263,11 @@ async function configureMarked(marked) {
|
|
|
247
263
|
`<div>${parsedContent}</div>` :
|
|
248
264
|
parsedContent;
|
|
249
265
|
|
|
250
|
-
|
|
266
|
+
const aside = `<aside${classes ? ` class="${classes}"` : ''}>${iconHtml}${wrappedContent}</aside>`;
|
|
267
|
+
if (isDemo) {
|
|
268
|
+
return `${aside}\n<pre x-code="html" copy><code>${escapeForText(token.text.trim())}</code></pre>\n`;
|
|
269
|
+
}
|
|
270
|
+
return `${aside}\n`;
|
|
251
271
|
}
|
|
252
272
|
}]
|
|
253
273
|
});
|
|
@@ -259,17 +279,16 @@ async function configureMarked(marked) {
|
|
|
259
279
|
});
|
|
260
280
|
}
|
|
261
281
|
|
|
262
|
-
//
|
|
282
|
+
// Markdown preprocessor: ensure that block-level HTML containers (the
|
|
283
|
+
// wrappers authors use to group fenced code blocks into tabs, frames, etc.)
|
|
284
|
+
// have a blank line after their opening tag so marked treats the contents
|
|
285
|
+
// as block-level markdown rather than raw inline HTML. Without this, a
|
|
286
|
+
// fenced ```js immediately after `<div x-code-group>` is treated as text.
|
|
263
287
|
function renderXCodeGroup(markdown) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// Ensure there's a line break after the opening tag if there isn't one
|
|
269
|
-
const processedContent = content.replace(/^(?!\s*\n)/, '\n');
|
|
270
|
-
|
|
271
|
-
return `<x-code-group>${processedContent}</x-code-group>`;
|
|
272
|
-
});
|
|
288
|
+
return markdown.replace(
|
|
289
|
+
/(<(?:div|section|article|aside)[^>]*\bx-code-group\b[^>]*>)(?!\s*\n)/g,
|
|
290
|
+
'$1\n'
|
|
291
|
+
);
|
|
273
292
|
}
|
|
274
293
|
|
|
275
294
|
// Post-process HTML to enable checkboxes by removing disabled attribute
|
|
@@ -296,68 +315,69 @@ function isHighlightJsAvailable() {
|
|
|
296
315
|
|
|
297
316
|
|
|
298
317
|
|
|
299
|
-
// Parse
|
|
318
|
+
// Parse a fence's info-string into an attributes bag. Supported tokens:
|
|
319
|
+
// javascript language (first non-flag bareword)
|
|
320
|
+
// "Tab name" quoted name → name attribute (tabs / title bar)
|
|
321
|
+
// lines line numbers gutter
|
|
322
|
+
// copy copy button
|
|
323
|
+
// edit CodeJar editor
|
|
324
|
+
// collapse collapse with default threshold (20 lines)
|
|
325
|
+
// collapse=10 collapse to first 10 lines
|
|
326
|
+
// from=#demo pull source from referenced element
|
|
300
327
|
function parseLanguageString(languageString) {
|
|
301
|
-
if (!languageString || languageString.trim() === '') {
|
|
302
|
-
return { title: null, language: null, numbers: false, copy: false };
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const parts = languageString.split(/\s+/);
|
|
306
|
-
|
|
307
328
|
const attributes = {
|
|
308
329
|
title: null,
|
|
309
330
|
language: null,
|
|
310
|
-
|
|
311
|
-
copy: false
|
|
331
|
+
lines: false,
|
|
332
|
+
copy: false,
|
|
333
|
+
edit: false,
|
|
334
|
+
collapse: null, // null = not collapsible; '' = default threshold; '10' = explicit
|
|
335
|
+
from: null
|
|
312
336
|
};
|
|
337
|
+
if (!languageString || languageString.trim() === '') return attributes;
|
|
313
338
|
|
|
339
|
+
const parts = languageString.split(/\s+/);
|
|
314
340
|
let i = 0;
|
|
315
341
|
while (i < parts.length) {
|
|
316
342
|
const part = parts[i];
|
|
317
343
|
|
|
318
|
-
|
|
319
|
-
if (part === '
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
344
|
+
if (part === 'lines') { attributes.lines = true; i++; continue; }
|
|
345
|
+
if (part === 'copy') { attributes.copy = true; i++; continue; }
|
|
346
|
+
if (part === 'edit') { attributes.edit = true; i++; continue; }
|
|
347
|
+
if (part === 'collapse') { attributes.collapse = ''; i++; continue; }
|
|
348
|
+
if (part.startsWith('collapse=')) {
|
|
349
|
+
attributes.collapse = part.slice('collapse='.length).replace(/^"|"$/g, '');
|
|
350
|
+
i++; continue;
|
|
323
351
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
i++;
|
|
328
|
-
continue;
|
|
352
|
+
if (part.startsWith('from=')) {
|
|
353
|
+
attributes.from = part.slice('from='.length).replace(/^"|"$/g, '');
|
|
354
|
+
i++; continue;
|
|
329
355
|
}
|
|
330
356
|
|
|
331
|
-
//
|
|
332
|
-
if (part.startsWith('"') && part.endsWith('"')) {
|
|
333
|
-
// Single word quoted name
|
|
357
|
+
// Quoted name handling — single-word "Foo" or multi-word "Foo Bar Baz"
|
|
358
|
+
if (part.startsWith('"') && part.endsWith('"') && part.length > 1) {
|
|
334
359
|
attributes.title = part.slice(1, -1);
|
|
335
|
-
i++;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// Multi-word quoted name
|
|
360
|
+
i++; continue;
|
|
361
|
+
}
|
|
362
|
+
if (part.startsWith('"')) {
|
|
339
363
|
let fullName = part.slice(1);
|
|
340
364
|
i++;
|
|
341
365
|
while (i < parts.length) {
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
344
|
-
fullName += ' ' +
|
|
366
|
+
const next = parts[i];
|
|
367
|
+
if (next.endsWith('"')) {
|
|
368
|
+
fullName += ' ' + next.slice(0, -1);
|
|
345
369
|
attributes.title = fullName;
|
|
346
370
|
i++;
|
|
347
371
|
break;
|
|
348
|
-
} else {
|
|
349
|
-
fullName += ' ' + nextPart;
|
|
350
|
-
i++;
|
|
351
372
|
}
|
|
373
|
+
fullName += ' ' + next;
|
|
374
|
+
i++;
|
|
352
375
|
}
|
|
353
376
|
continue;
|
|
354
377
|
}
|
|
355
378
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
if (!attributes.language) {
|
|
359
|
-
attributes.language = part;
|
|
360
|
-
}
|
|
379
|
+
// Unrecognized bareword → treat as language (first one wins)
|
|
380
|
+
if (!attributes.language) attributes.language = part;
|
|
361
381
|
i++;
|
|
362
382
|
}
|
|
363
383
|
|
|
@@ -415,7 +435,8 @@ async function initializeMarkdownPlugin() {
|
|
|
415
435
|
// (`x-markdown.safe="$x.user.bio"`), parsed HTML is run through
|
|
416
436
|
// DOMPurify before injection. Default is unsanitized — Manifest's
|
|
417
437
|
// design lets authors render raw HTML and custom-element extensions
|
|
418
|
-
// (x-
|
|
438
|
+
// (x-icon, callouts) and directive attributes (x-code, etc.) freely.
|
|
439
|
+
// Use .safe when the markdown
|
|
419
440
|
// source can contain content from untrusted parties (Appwrite
|
|
420
441
|
// collections, API responses, crowdsourced translations, etc.).
|
|
421
442
|
const safe = Array.isArray(modifiers) && modifiers.includes('safe');
|