mnfst 0.5.14
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 +11 -0
- package/README.md +58 -0
- package/dist/manifest.accordion.css +81 -0
- package/dist/manifest.appwrite.auth.js +6247 -0
- package/dist/manifest.appwrite.data.js +1586 -0
- package/dist/manifest.appwrite.presence.js +1845 -0
- package/dist/manifest.avatar.css +113 -0
- package/dist/manifest.button.css +79 -0
- package/dist/manifest.checkbox.css +58 -0
- package/dist/manifest.code.css +453 -0
- package/dist/manifest.code.js +958 -0
- package/dist/manifest.code.min.css +1 -0
- package/dist/manifest.components.js +737 -0
- package/dist/manifest.css +3124 -0
- package/dist/manifest.data.js +11413 -0
- package/dist/manifest.dialog.css +130 -0
- package/dist/manifest.divider.css +77 -0
- package/dist/manifest.dropdown.css +278 -0
- package/dist/manifest.dropdowns.js +378 -0
- package/dist/manifest.form.css +169 -0
- package/dist/manifest.icons.js +161 -0
- package/dist/manifest.input.css +129 -0
- package/dist/manifest.js +302 -0
- package/dist/manifest.localization.js +571 -0
- package/dist/manifest.markdown.js +738 -0
- package/dist/manifest.min.css +1 -0
- package/dist/manifest.radio.css +38 -0
- package/dist/manifest.resize.css +233 -0
- package/dist/manifest.resize.js +442 -0
- package/dist/manifest.router.js +1207 -0
- package/dist/manifest.sidebar.css +102 -0
- package/dist/manifest.slides.css +80 -0
- package/dist/manifest.slides.js +173 -0
- package/dist/manifest.switch.css +44 -0
- package/dist/manifest.table.css +74 -0
- package/dist/manifest.tabs.js +273 -0
- package/dist/manifest.tailwind.js +578 -0
- package/dist/manifest.theme.css +119 -0
- package/dist/manifest.themes.js +109 -0
- package/dist/manifest.toast.css +92 -0
- package/dist/manifest.toasts.js +285 -0
- package/dist/manifest.tooltip.css +156 -0
- package/dist/manifest.tooltips.js +331 -0
- package/dist/manifest.typography.css +341 -0
- package/dist/manifest.utilities.css +399 -0
- package/dist/manifest.utilities.js +3197 -0
- package/package.json +63 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
/* Manifest Markdown */
|
|
2
|
+
|
|
3
|
+
// Cache for marked.js loading
|
|
4
|
+
let markedPromise = null;
|
|
5
|
+
|
|
6
|
+
// Cache for fetched markdown files to prevent duplicate requests
|
|
7
|
+
const markdownCache = new Map();
|
|
8
|
+
|
|
9
|
+
// Load marked.js from CDN
|
|
10
|
+
async function loadMarkedJS() {
|
|
11
|
+
if (typeof marked !== 'undefined') {
|
|
12
|
+
return marked;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Return existing promise if already loading
|
|
16
|
+
if (markedPromise) {
|
|
17
|
+
return markedPromise;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
markedPromise = new Promise((resolve, reject) => {
|
|
21
|
+
const script = document.createElement('script');
|
|
22
|
+
script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
|
|
23
|
+
script.onload = () => {
|
|
24
|
+
// Initialize marked.js
|
|
25
|
+
if (typeof marked !== 'undefined') {
|
|
26
|
+
resolve(marked);
|
|
27
|
+
} else {
|
|
28
|
+
console.error('[Manifest Markdown] Marked.js failed to load - marked is undefined');
|
|
29
|
+
markedPromise = null; // Reset so we can try again
|
|
30
|
+
reject(new Error('marked.js failed to load'));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
script.onerror = (error) => {
|
|
34
|
+
console.error('[Manifest Markdown] Script failed to load:', error);
|
|
35
|
+
markedPromise = null; // Reset so we can try again
|
|
36
|
+
reject(error);
|
|
37
|
+
};
|
|
38
|
+
document.head.appendChild(script);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return markedPromise;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Configure marked to preserve full language strings
|
|
45
|
+
async function configureMarked(marked) {
|
|
46
|
+
marked.use({
|
|
47
|
+
renderer: {
|
|
48
|
+
code(token) {
|
|
49
|
+
const lang = token.lang || '';
|
|
50
|
+
const text = token.text || '';
|
|
51
|
+
const escaped = token.escaped || false;
|
|
52
|
+
|
|
53
|
+
// Parse the language string to extract attributes
|
|
54
|
+
const attributes = parseLanguageString(lang);
|
|
55
|
+
|
|
56
|
+
// Build attributes for the x-code element
|
|
57
|
+
let xCodeAttributes = '';
|
|
58
|
+
if (attributes.title) {
|
|
59
|
+
xCodeAttributes += ` name="${attributes.title}"`;
|
|
60
|
+
}
|
|
61
|
+
if (attributes.language) {
|
|
62
|
+
xCodeAttributes += ` language="${attributes.language}"`;
|
|
63
|
+
}
|
|
64
|
+
if (attributes.numbers) {
|
|
65
|
+
xCodeAttributes += ' numbers';
|
|
66
|
+
}
|
|
67
|
+
if (attributes.copy) {
|
|
68
|
+
xCodeAttributes += ' copy';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// For x-code elements, use the raw text to preserve formatting
|
|
72
|
+
let code = text;
|
|
73
|
+
let preserveOriginal = '';
|
|
74
|
+
|
|
75
|
+
// For HTML language code blocks, preserve the original raw text to maintain indentation
|
|
76
|
+
if (attributes.language === 'html' || text.includes('<!DOCTYPE') || (text.includes('<html') && text.includes('<head') && text.includes('<body'))) {
|
|
77
|
+
// Store the original content in a data attribute to preserve indentation
|
|
78
|
+
preserveOriginal = ` data-original-content="${text.replace(/"/g, '"')}"`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Always create an x-code element, with or without attributes
|
|
82
|
+
return `<x-code${xCodeAttributes}${preserveOriginal}>${code}</x-code>\n`;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
// Configure marked to allow custom HTML tags
|
|
86
|
+
breaks: true,
|
|
87
|
+
gfm: true
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Add custom tokenizer for callout blocks
|
|
91
|
+
marked.use({
|
|
92
|
+
extensions: [{
|
|
93
|
+
name: 'callout',
|
|
94
|
+
level: 'block',
|
|
95
|
+
start(src) {
|
|
96
|
+
return src.match(/^:::/)?.index;
|
|
97
|
+
},
|
|
98
|
+
tokenizer(src) {
|
|
99
|
+
// Find the opening ::: and type
|
|
100
|
+
const openMatch = src.match(/^:::(.*?)(?:\n|$)/);
|
|
101
|
+
if (!openMatch) return;
|
|
102
|
+
|
|
103
|
+
// Parse the opening line for classes and icon
|
|
104
|
+
const openingLine = openMatch[1].trim();
|
|
105
|
+
let classes = '';
|
|
106
|
+
let iconValue = '';
|
|
107
|
+
|
|
108
|
+
// Match icon="value" pattern
|
|
109
|
+
const iconMatch = openingLine.match(/icon="([^"]+)"/);
|
|
110
|
+
if (iconMatch) {
|
|
111
|
+
iconValue = iconMatch[1];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Get all class names (remove icon attribute first)
|
|
115
|
+
classes = openingLine.replace(/\s*icon="[^"]+"\s*/, '').trim();
|
|
116
|
+
|
|
117
|
+
const startPos = openMatch[0].length;
|
|
118
|
+
|
|
119
|
+
// Find the closing ::: from the remaining content
|
|
120
|
+
const remainingContent = src.slice(startPos);
|
|
121
|
+
const closeMatch = remainingContent.match(/\n:::/);
|
|
122
|
+
|
|
123
|
+
if (closeMatch) {
|
|
124
|
+
const content = remainingContent.slice(0, closeMatch.index);
|
|
125
|
+
const raw = openMatch[0] + content + closeMatch[0];
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
type: 'callout',
|
|
129
|
+
raw: raw,
|
|
130
|
+
classes: classes,
|
|
131
|
+
iconValue: iconValue,
|
|
132
|
+
text: content.trim()
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
renderer(token) {
|
|
137
|
+
const classes = token.classes || '';
|
|
138
|
+
const iconValue = token.iconValue || '';
|
|
139
|
+
|
|
140
|
+
// For frame callouts, don't parse as markdown to avoid wrapping HTML in <p> tags
|
|
141
|
+
let parsedContent;
|
|
142
|
+
if (classes.includes('frame')) {
|
|
143
|
+
// Use raw content for frame callouts to preserve HTML structure
|
|
144
|
+
parsedContent = token.text;
|
|
145
|
+
} else {
|
|
146
|
+
// Parse the content as markdown to support nested markdown syntax
|
|
147
|
+
parsedContent = marked.parse(token.text);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const iconHtml = iconValue ? `<span x-icon="${iconValue}"></span>` : '';
|
|
151
|
+
|
|
152
|
+
// Create a temporary div to count top-level elements
|
|
153
|
+
const temp = document.createElement('div');
|
|
154
|
+
temp.innerHTML = parsedContent;
|
|
155
|
+
const elementCount = temp.children.length;
|
|
156
|
+
|
|
157
|
+
// Only wrap in a div if:
|
|
158
|
+
// 1. There are 2 or more elements AND
|
|
159
|
+
// 2. There's an icon (which needs the content to be wrapped as a sibling)
|
|
160
|
+
const needsWrapper = elementCount >= 2 && iconValue;
|
|
161
|
+
const wrappedContent = needsWrapper ?
|
|
162
|
+
`<div>${parsedContent}</div>` :
|
|
163
|
+
parsedContent;
|
|
164
|
+
|
|
165
|
+
return `<aside${classes ? ` class="${classes}"` : ''}>${iconHtml}${wrappedContent}</aside>\n`;
|
|
166
|
+
}
|
|
167
|
+
}]
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Configure marked to preserve custom HTML tags
|
|
171
|
+
marked.setOptions({
|
|
172
|
+
headerIds: false,
|
|
173
|
+
mangle: false
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Custom renderer for x-code-group to handle line breaks properly
|
|
178
|
+
function renderXCodeGroup(markdown) {
|
|
179
|
+
// Find x-code-group blocks and process them specially
|
|
180
|
+
const xCodeGroupRegex = /<x-code-group[^>]*>([\s\S]*?)<\/x-code-group>/g;
|
|
181
|
+
|
|
182
|
+
return markdown.replace(xCodeGroupRegex, (match, content) => {
|
|
183
|
+
// Ensure there's a line break after the opening tag if there isn't one
|
|
184
|
+
const processedContent = content.replace(/^(?!\s*\n)/, '\n');
|
|
185
|
+
|
|
186
|
+
return `<x-code-group>${processedContent}</x-code-group>`;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Post-process HTML to enable checkboxes by removing disabled attribute
|
|
191
|
+
function enableCheckboxes(html) {
|
|
192
|
+
// Create a temporary DOM element to parse the HTML
|
|
193
|
+
const temp = document.createElement('div');
|
|
194
|
+
temp.innerHTML = html;
|
|
195
|
+
|
|
196
|
+
// Find all checkbox inputs and remove disabled attribute
|
|
197
|
+
const checkboxes = temp.querySelectorAll('input[type="checkbox"]');
|
|
198
|
+
checkboxes.forEach(checkbox => {
|
|
199
|
+
checkbox.removeAttribute('disabled');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return temp.innerHTML;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check if highlight.js is available
|
|
206
|
+
function isHighlightJsAvailable() {
|
|
207
|
+
return typeof window.hljs !== 'undefined';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
// Parse language string to extract title and attributes
|
|
215
|
+
function parseLanguageString(languageString) {
|
|
216
|
+
if (!languageString || languageString.trim() === '') {
|
|
217
|
+
return { title: null, language: null, numbers: false, copy: false };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const parts = languageString.split(/\s+/);
|
|
221
|
+
|
|
222
|
+
const attributes = {
|
|
223
|
+
title: null,
|
|
224
|
+
language: null,
|
|
225
|
+
numbers: false,
|
|
226
|
+
copy: false
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
let i = 0;
|
|
230
|
+
while (i < parts.length) {
|
|
231
|
+
const part = parts[i];
|
|
232
|
+
|
|
233
|
+
// Check for attributes
|
|
234
|
+
if (part === 'numbers') {
|
|
235
|
+
attributes.numbers = true;
|
|
236
|
+
i++;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (part === 'copy') {
|
|
241
|
+
attributes.copy = true;
|
|
242
|
+
i++;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Check for quoted names (e.g., "Example")
|
|
247
|
+
if (part.startsWith('"') && part.endsWith('"')) {
|
|
248
|
+
// Single word quoted name
|
|
249
|
+
attributes.title = part.slice(1, -1);
|
|
250
|
+
i++;
|
|
251
|
+
continue;
|
|
252
|
+
} else if (part.startsWith('"')) {
|
|
253
|
+
// Multi-word quoted name
|
|
254
|
+
let fullName = part.slice(1);
|
|
255
|
+
i++;
|
|
256
|
+
while (i < parts.length) {
|
|
257
|
+
const nextPart = parts[i];
|
|
258
|
+
if (nextPart.endsWith('"')) {
|
|
259
|
+
fullName += ' ' + nextPart.slice(0, -1);
|
|
260
|
+
attributes.title = fullName;
|
|
261
|
+
i++;
|
|
262
|
+
break;
|
|
263
|
+
} else {
|
|
264
|
+
fullName += ' ' + nextPart;
|
|
265
|
+
i++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Store language identifiers (e.g., "css", "javascript", etc.)
|
|
272
|
+
// Use the first language identifier found
|
|
273
|
+
if (!attributes.language) {
|
|
274
|
+
attributes.language = part;
|
|
275
|
+
}
|
|
276
|
+
i++;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return attributes;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Preload marked.js as soon as script loads
|
|
283
|
+
loadMarkedJS().catch(() => {
|
|
284
|
+
// Silently ignore errors during preload
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Initialize plugin when either DOM is ready or Alpine is ready
|
|
288
|
+
async function initializeMarkdownPlugin() {
|
|
289
|
+
try {
|
|
290
|
+
// Load marked.js
|
|
291
|
+
const marked = await loadMarkedJS();
|
|
292
|
+
|
|
293
|
+
// Configure marked with all our custom settings
|
|
294
|
+
await configureMarked(marked);
|
|
295
|
+
|
|
296
|
+
// Configure marked to generate heading IDs
|
|
297
|
+
marked.use({
|
|
298
|
+
renderer: {
|
|
299
|
+
heading(token) {
|
|
300
|
+
// Extract text and level from the token
|
|
301
|
+
const text = token.text || '';
|
|
302
|
+
const level = token.depth || 1;
|
|
303
|
+
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-').replace(/^-+|-+$/g, '');
|
|
304
|
+
return `<h${level} id="${escapedText}">${text}</h${level}>`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Check if there are any elements with x-markdown already on the page
|
|
310
|
+
const existingMarkdownElements = document.querySelectorAll('[x-markdown]');
|
|
311
|
+
|
|
312
|
+
// Register markdown directive
|
|
313
|
+
Alpine.directive('markdown', (el, { expression, modifiers }, { effect, evaluateLater }) => {
|
|
314
|
+
|
|
315
|
+
// Handle null/undefined expressions gracefully
|
|
316
|
+
if (!expression) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Hide element initially to prevent flicker
|
|
321
|
+
el.style.opacity = '0';
|
|
322
|
+
el.style.transition = 'opacity 0.15s ease-in-out';
|
|
323
|
+
|
|
324
|
+
// Store original markdown content
|
|
325
|
+
let markdownSource = '';
|
|
326
|
+
let isUpdating = false;
|
|
327
|
+
let hasContent = false;
|
|
328
|
+
|
|
329
|
+
const normalizeContent = (content) => {
|
|
330
|
+
const lines = content.split('\n');
|
|
331
|
+
const commonIndent = lines
|
|
332
|
+
.filter(line => line.trim())
|
|
333
|
+
.reduce((min, line) => {
|
|
334
|
+
const indent = line.match(/^\s*/)[0].length;
|
|
335
|
+
return Math.min(min, indent);
|
|
336
|
+
}, Infinity);
|
|
337
|
+
|
|
338
|
+
return lines
|
|
339
|
+
.map(line => line.slice(commonIndent))
|
|
340
|
+
.join('\n')
|
|
341
|
+
.trim();
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const updateContent = async (element, newContent = null) => {
|
|
345
|
+
if (isUpdating) return;
|
|
346
|
+
isUpdating = true;
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
// Update source if new content provided
|
|
350
|
+
if (newContent !== null && newContent.trim() !== '') {
|
|
351
|
+
markdownSource = normalizeContent(newContent);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Skip if no content
|
|
355
|
+
if (!markdownSource || markdownSource.trim() === '') {
|
|
356
|
+
element.style.opacity = '0';
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Load marked.js and parse markdown
|
|
361
|
+
const marked = await loadMarkedJS();
|
|
362
|
+
const processedMarkdown = renderXCodeGroup(markdownSource);
|
|
363
|
+
let html = marked.parse(processedMarkdown);
|
|
364
|
+
|
|
365
|
+
// Post-process HTML to enable checkboxes (remove disabled attribute)
|
|
366
|
+
html = enableCheckboxes(html);
|
|
367
|
+
|
|
368
|
+
// Only update if content has changed and isn't empty
|
|
369
|
+
if (element.innerHTML !== html && html.trim() !== '') {
|
|
370
|
+
// Create a temporary container to hold the HTML
|
|
371
|
+
const temp = document.createElement('div');
|
|
372
|
+
temp.innerHTML = html;
|
|
373
|
+
|
|
374
|
+
// Replace the content
|
|
375
|
+
element.innerHTML = '';
|
|
376
|
+
while (temp.firstChild) {
|
|
377
|
+
element.appendChild(temp.firstChild);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Show element with content
|
|
381
|
+
hasContent = true;
|
|
382
|
+
element.style.opacity = '1';
|
|
383
|
+
} else if (!hasContent) {
|
|
384
|
+
// Keep hidden if no valid content
|
|
385
|
+
element.style.opacity = '0';
|
|
386
|
+
}
|
|
387
|
+
} finally {
|
|
388
|
+
isUpdating = false;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Handle inline markdown content (no expression or 'inline')
|
|
393
|
+
if (!expression || expression === 'inline') {
|
|
394
|
+
// Initial parse
|
|
395
|
+
markdownSource = normalizeContent(el.textContent);
|
|
396
|
+
updateContent(el);
|
|
397
|
+
|
|
398
|
+
// Set up mutation observer for streaming content
|
|
399
|
+
const observer = new MutationObserver((mutations) => {
|
|
400
|
+
let newContent = null;
|
|
401
|
+
|
|
402
|
+
for (const mutation of mutations) {
|
|
403
|
+
if (mutation.type === 'childList') {
|
|
404
|
+
const textNodes = Array.from(el.childNodes)
|
|
405
|
+
.filter(node => node.nodeType === Node.TEXT_NODE);
|
|
406
|
+
if (textNodes.length > 0) {
|
|
407
|
+
newContent = textNodes.map(node => node.textContent).join('');
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
} else if (mutation.type === 'characterData') {
|
|
411
|
+
newContent = mutation.target.textContent;
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (newContent && newContent.trim() !== '') {
|
|
417
|
+
updateContent(el, newContent);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
observer.observe(el, {
|
|
422
|
+
characterData: true,
|
|
423
|
+
childList: true,
|
|
424
|
+
subtree: true,
|
|
425
|
+
characterDataOldValue: true
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Handle expressions (file paths, inline strings, content references)
|
|
432
|
+
// Check if this is a simple string literal that needs to be quoted
|
|
433
|
+
let processedExpression = expression;
|
|
434
|
+
if (!expression.includes('+') && !expression.includes('`') && !expression.includes('${') &&
|
|
435
|
+
!expression.startsWith('$') && !expression.startsWith("'") && !expression.startsWith('"')) {
|
|
436
|
+
// Wrap simple string literals in quotes to prevent Alpine from treating them as expressions
|
|
437
|
+
processedExpression = `'${expression.replace(/'/g, "\\'")}'`;
|
|
438
|
+
}
|
|
439
|
+
const getMarkdownContent = evaluateLater(processedExpression);
|
|
440
|
+
|
|
441
|
+
// Track last processed content to prevent unnecessary re-renders
|
|
442
|
+
let lastProcessedContent = null;
|
|
443
|
+
|
|
444
|
+
effect(() => {
|
|
445
|
+
getMarkdownContent(async (pathOrContent) => {
|
|
446
|
+
// Reset visibility if content is empty/undefined
|
|
447
|
+
if (!pathOrContent || pathOrContent === undefined || pathOrContent === '') {
|
|
448
|
+
el.style.opacity = '0';
|
|
449
|
+
hasContent = false;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (pathOrContent === undefined) {
|
|
454
|
+
pathOrContent = expression;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Check if this looks like a file path (contains .md, .markdown, or starts with /)
|
|
458
|
+
const isFilePath = typeof pathOrContent === 'string' &&
|
|
459
|
+
(pathOrContent.includes('.md') ||
|
|
460
|
+
pathOrContent.includes('.markdown') ||
|
|
461
|
+
pathOrContent.startsWith('/') ||
|
|
462
|
+
pathOrContent.includes('/'));
|
|
463
|
+
|
|
464
|
+
let markdownContent = pathOrContent;
|
|
465
|
+
|
|
466
|
+
// If it's a file path, fetch the content (with caching)
|
|
467
|
+
if (isFilePath) {
|
|
468
|
+
try {
|
|
469
|
+
// Ensure the path is absolute from project root
|
|
470
|
+
let resolvedPath = pathOrContent;
|
|
471
|
+
|
|
472
|
+
// If it's a relative path (doesn't start with /), make it absolute from root
|
|
473
|
+
if (!pathOrContent.startsWith('/')) {
|
|
474
|
+
resolvedPath = '/' + pathOrContent;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Check cache first
|
|
478
|
+
if (markdownCache.has(resolvedPath)) {
|
|
479
|
+
markdownContent = markdownCache.get(resolvedPath);
|
|
480
|
+
} else {
|
|
481
|
+
const response = await fetch(resolvedPath);
|
|
482
|
+
if (response.ok) {
|
|
483
|
+
markdownContent = await response.text();
|
|
484
|
+
// Cache the content
|
|
485
|
+
markdownCache.set(resolvedPath, markdownContent);
|
|
486
|
+
} else {
|
|
487
|
+
console.warn(`[Manifest] Failed to fetch markdown file: ${resolvedPath}`);
|
|
488
|
+
markdownContent = `# Error Loading Content\n\nCould not load: ${resolvedPath}`;
|
|
489
|
+
// Cache error content too to prevent repeated failed requests
|
|
490
|
+
markdownCache.set(resolvedPath, markdownContent);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.error(`[Manifest] Error fetching markdown file: ${pathOrContent}`, error);
|
|
495
|
+
markdownContent = `# Error Loading Content\n\nCould not load: ${pathOrContent}\n\nError: ${error.message}`;
|
|
496
|
+
// Cache error content to prevent repeated failed requests
|
|
497
|
+
if (resolvedPath) {
|
|
498
|
+
markdownCache.set(resolvedPath, markdownContent);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Skip if content hasn't changed (prevents unnecessary re-renders)
|
|
504
|
+
if (markdownContent === lastProcessedContent) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
lastProcessedContent = markdownContent;
|
|
508
|
+
|
|
509
|
+
// Skip empty content
|
|
510
|
+
if (!markdownContent || markdownContent.trim() === '') {
|
|
511
|
+
el.style.opacity = '0';
|
|
512
|
+
hasContent = false;
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const marked = await loadMarkedJS();
|
|
517
|
+
let html = marked.parse(markdownContent);
|
|
518
|
+
|
|
519
|
+
// Post-process HTML to enable checkboxes (remove disabled attribute)
|
|
520
|
+
html = enableCheckboxes(html);
|
|
521
|
+
|
|
522
|
+
// Only update DOM if HTML actually changed
|
|
523
|
+
if (el.innerHTML !== html) {
|
|
524
|
+
// Create temporary container
|
|
525
|
+
const temp = document.createElement('div');
|
|
526
|
+
temp.innerHTML = html;
|
|
527
|
+
|
|
528
|
+
el.innerHTML = '';
|
|
529
|
+
while (temp.firstChild) {
|
|
530
|
+
el.appendChild(temp.firstChild);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Ensure Alpine processes the newly inserted HTML
|
|
534
|
+
if (window.Alpine && typeof window.Alpine.initTree === 'function') {
|
|
535
|
+
if (window.Alpine.nextTick) {
|
|
536
|
+
window.Alpine.nextTick(() => {
|
|
537
|
+
window.Alpine.initTree(el);
|
|
538
|
+
});
|
|
539
|
+
} else {
|
|
540
|
+
setTimeout(() => {
|
|
541
|
+
window.Alpine.initTree(el);
|
|
542
|
+
}, 0);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Code highlighting is handled by manifest.code.js plugin
|
|
548
|
+
|
|
549
|
+
// Show content with fade-in
|
|
550
|
+
hasContent = true;
|
|
551
|
+
el.style.opacity = '1';
|
|
552
|
+
|
|
553
|
+
// Extract headings for anchor links
|
|
554
|
+
const headings = [];
|
|
555
|
+
const headingElements = el.querySelectorAll('h1, h2, h3');
|
|
556
|
+
headingElements.forEach(heading => {
|
|
557
|
+
headings.push({
|
|
558
|
+
id: heading.id,
|
|
559
|
+
text: heading.textContent,
|
|
560
|
+
level: parseInt(heading.tagName.charAt(1))
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Store headings in Alpine data if 'headings' modifier is used
|
|
565
|
+
if (modifiers.includes('headings')) {
|
|
566
|
+
// Generate a unique ID for this markdown section
|
|
567
|
+
const sectionId = 'markdown-' + Math.random().toString(36).substr(2, 9);
|
|
568
|
+
el.setAttribute('data-headings-section', sectionId);
|
|
569
|
+
|
|
570
|
+
// Store headings in a global registry
|
|
571
|
+
if (!window._induxHeadings) {
|
|
572
|
+
window._induxHeadings = {};
|
|
573
|
+
}
|
|
574
|
+
window._induxHeadings[sectionId] = headings;
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// If there are existing elements with x-markdown, manually process them with proper Alpine context
|
|
581
|
+
if (existingMarkdownElements.length > 0) {
|
|
582
|
+
|
|
583
|
+
existingMarkdownElements.forEach(el => {
|
|
584
|
+
const expression = el.getAttribute('x-markdown');
|
|
585
|
+
|
|
586
|
+
// Create a temporary Alpine component context for this element
|
|
587
|
+
const tempComponent = Alpine.$data(el) || {};
|
|
588
|
+
|
|
589
|
+
// Use Alpine's evaluation system within the component context
|
|
590
|
+
const updateContent = async (element, newContent = null) => {
|
|
591
|
+
try {
|
|
592
|
+
if (!newContent) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Load marked.js and parse markdown
|
|
597
|
+
const marked = await loadMarkedJS();
|
|
598
|
+
const processedMarkdown = renderXCodeGroup(newContent);
|
|
599
|
+
let html = marked.parse(processedMarkdown);
|
|
600
|
+
|
|
601
|
+
// Post-process HTML to enable checkboxes (remove disabled attribute)
|
|
602
|
+
html = html.replace(/<input type="checkbox"([^>]*?)disabled([^>]*?)>/g, '<input type="checkbox"$1$2>');
|
|
603
|
+
|
|
604
|
+
// Create temporary container
|
|
605
|
+
const temp = document.createElement('div');
|
|
606
|
+
temp.innerHTML = html;
|
|
607
|
+
|
|
608
|
+
element.innerHTML = '';
|
|
609
|
+
while (temp.firstChild) {
|
|
610
|
+
element.appendChild(temp.firstChild);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Ensure Alpine processes the newly inserted HTML
|
|
614
|
+
// This is critical for data source expressions like $x.projects
|
|
615
|
+
// Try to wait for magic methods, but proceed anyway if not ready
|
|
616
|
+
const initAlpine = (retryCount = 0) => {
|
|
617
|
+
if (!window.Alpine || typeof window.Alpine.initTree !== 'function') {
|
|
618
|
+
if (retryCount < 5) {
|
|
619
|
+
setTimeout(() => initAlpine(retryCount + 1), 50);
|
|
620
|
+
}
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Check if $x magic method is available
|
|
625
|
+
const xMagic = window.Alpine?.magic?.('x');
|
|
626
|
+
const hasXMagic = typeof xMagic === 'function';
|
|
627
|
+
|
|
628
|
+
// DEBUG: Log markdown Alpine initialization
|
|
629
|
+
const hasExampleComponents = element.querySelectorAll('[x-header-modified], [class*="header-modified"]').length > 0;
|
|
630
|
+
if (hasExampleComponents) {
|
|
631
|
+
console.log('[DEBUG markdown] Initializing Alpine for markdown with components', {
|
|
632
|
+
hasXMagic,
|
|
633
|
+
retryCount,
|
|
634
|
+
element: element.tagName,
|
|
635
|
+
componentCount: element.querySelectorAll('x-header-modified').length,
|
|
636
|
+
stack: new Error().stack.split('\n').slice(1, 5).join('\n')
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// If magic method isn't ready, wait briefly but don't block forever
|
|
641
|
+
if (!hasXMagic && retryCount < 5) {
|
|
642
|
+
setTimeout(() => initAlpine(retryCount + 1), 50);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Use Alpine.nextTick if available, otherwise setTimeout
|
|
647
|
+
const scheduleInit = (fn) => {
|
|
648
|
+
if (window.Alpine?.nextTick) {
|
|
649
|
+
window.Alpine.nextTick(fn);
|
|
650
|
+
} else {
|
|
651
|
+
setTimeout(fn, 0);
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
scheduleInit(() => {
|
|
656
|
+
try {
|
|
657
|
+
window.Alpine.initTree(element);
|
|
658
|
+
} catch (e) {
|
|
659
|
+
console.error('[Manifest Markdown] Error initializing Alpine tree (updateContent):', e);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// Start initialization
|
|
665
|
+
initAlpine();
|
|
666
|
+
|
|
667
|
+
// Re-highlight code blocks after content update
|
|
668
|
+
// Code highlighting is handled by manifest.code.js plugin
|
|
669
|
+
} catch (error) {
|
|
670
|
+
console.error('[Manifest Markdown] Failed to process element:', error);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// Handle simple string expressions
|
|
675
|
+
if (expression.startsWith("'") && expression.endsWith("'")) {
|
|
676
|
+
const content = expression.slice(1, -1);
|
|
677
|
+
updateContent(el, content);
|
|
678
|
+
} else {
|
|
679
|
+
// For complex expressions, we need to force Alpine to re-process this element
|
|
680
|
+
|
|
681
|
+
// Remove and re-add the attribute to force Alpine to re-process it
|
|
682
|
+
const originalExpression = expression;
|
|
683
|
+
el.removeAttribute('x-markdown');
|
|
684
|
+
|
|
685
|
+
// Use a small delay to ensure the directive is registered
|
|
686
|
+
setTimeout(() => {
|
|
687
|
+
el.setAttribute('x-markdown', originalExpression);
|
|
688
|
+
}, 50);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
} catch (error) {
|
|
694
|
+
console.error('[Manifest] Failed to initialize markdown plugin:', error);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Track initialization to prevent duplicates
|
|
699
|
+
let markdownPluginInitialized = false;
|
|
700
|
+
|
|
701
|
+
async function ensureMarkdownPluginInitialized() {
|
|
702
|
+
if (markdownPluginInitialized) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (!window.Alpine || typeof window.Alpine.directive !== 'function') {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
markdownPluginInitialized = true;
|
|
710
|
+
await initializeMarkdownPlugin();
|
|
711
|
+
|
|
712
|
+
// If elements with x-markdown already exist, process them
|
|
713
|
+
// This handles the case where the plugin loads after components are swapped in
|
|
714
|
+
if (window.Alpine && typeof window.Alpine.initTree === 'function') {
|
|
715
|
+
const existingMarkdownElements = document.querySelectorAll('[x-markdown]');
|
|
716
|
+
existingMarkdownElements.forEach(el => {
|
|
717
|
+
// Only process if not already processed by Alpine
|
|
718
|
+
if (!el.__x) {
|
|
719
|
+
window.Alpine.initTree(el);
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Expose on window for loader to call if needed
|
|
726
|
+
window.ensureMarkdownPluginInitialized = ensureMarkdownPluginInitialized;
|
|
727
|
+
|
|
728
|
+
// Handle both DOMContentLoaded and alpine:init
|
|
729
|
+
if (document.readyState === 'loading') {
|
|
730
|
+
document.addEventListener('DOMContentLoaded', ensureMarkdownPluginInitialized);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
document.addEventListener('alpine:init', ensureMarkdownPluginInitialized);
|
|
734
|
+
|
|
735
|
+
// If Alpine is already initialized when this script loads, initialize immediately
|
|
736
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
|
737
|
+
ensureMarkdownPluginInitialized();
|
|
738
|
+
}
|