starmark 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,554 @@
1
+ function stripQuotes(value) {
2
+ const trimmed = value.trim();
3
+ if (
4
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
5
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
6
+ ) {
7
+ return trimmed.slice(1, -1).replace(/\\(['"])/g, "$1");
8
+ }
9
+
10
+ return trimmed;
11
+ }
12
+
13
+ function parseScalar(raw) {
14
+ const value = raw.trim();
15
+ if (value === "") {
16
+ return "";
17
+ }
18
+
19
+ if (value === "null" || value === "~") {
20
+ return null;
21
+ }
22
+
23
+ if (value === "true") {
24
+ return true;
25
+ }
26
+
27
+ if (value === "false") {
28
+ return false;
29
+ }
30
+
31
+ if (
32
+ (value.startsWith('"') && value.endsWith('"')) ||
33
+ (value.startsWith("'") && value.endsWith("'"))
34
+ ) {
35
+ return stripQuotes(value);
36
+ }
37
+
38
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) {
39
+ return Number(value);
40
+ }
41
+
42
+ if (/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?$/.test(value)) {
43
+ return value;
44
+ }
45
+
46
+ return value;
47
+ }
48
+
49
+ function countIndent(line) {
50
+ const match = line.match(/^ */);
51
+ return match ? match[0].length : 0;
52
+ }
53
+
54
+ function isBlank(line) {
55
+ return line.trim() === "";
56
+ }
57
+
58
+ function parseInlineArray(value) {
59
+ const inner = value.slice(1, -1).trim();
60
+ if (inner === "") {
61
+ return [];
62
+ }
63
+
64
+ const items = [];
65
+ let current = "";
66
+ let quote = null;
67
+
68
+ for (let index = 0; index < inner.length; index += 1) {
69
+ const char = inner[index];
70
+
71
+ if (quote) {
72
+ current += char;
73
+ if (char === quote && inner[index - 1] !== "\\") {
74
+ quote = null;
75
+ }
76
+ continue;
77
+ }
78
+
79
+ if (char === '"' || char === "'") {
80
+ quote = char;
81
+ current += char;
82
+ continue;
83
+ }
84
+
85
+ if (char === ",") {
86
+ items.push(parseScalar(current));
87
+ current = "";
88
+ continue;
89
+ }
90
+
91
+ current += char;
92
+ }
93
+
94
+ if (current.trim() !== "") {
95
+ items.push(parseScalar(current));
96
+ }
97
+
98
+ return items;
99
+ }
100
+
101
+ function parseInlineObject(value) {
102
+ const inner = value.slice(1, -1).trim();
103
+ if (inner === "") {
104
+ return {};
105
+ }
106
+
107
+ const result = {};
108
+ let currentKey = "";
109
+ let currentValue = "";
110
+ let quote = null;
111
+ let readingKey = true;
112
+
113
+ for (let index = 0; index < inner.length; index += 1) {
114
+ const char = inner[index];
115
+
116
+ if (quote) {
117
+ if (readingKey) {
118
+ currentKey += char;
119
+ } else {
120
+ currentValue += char;
121
+ }
122
+ if (char === quote && inner[index - 1] !== "\\") {
123
+ quote = null;
124
+ }
125
+ continue;
126
+ }
127
+
128
+ if (char === '"' || char === "'") {
129
+ quote = char;
130
+ if (readingKey) {
131
+ currentKey += char;
132
+ } else {
133
+ currentValue += char;
134
+ }
135
+ continue;
136
+ }
137
+
138
+ if (readingKey) {
139
+ if (char === ":") {
140
+ readingKey = false;
141
+ continue;
142
+ }
143
+ currentKey += char;
144
+ continue;
145
+ }
146
+
147
+ if (char === ",") {
148
+ result[currentKey.trim()] = parseScalar(currentValue);
149
+ currentKey = "";
150
+ currentValue = "";
151
+ readingKey = true;
152
+ continue;
153
+ }
154
+
155
+ currentValue += char;
156
+ }
157
+
158
+ if (currentKey.trim() !== "") {
159
+ result[currentKey.trim()] = parseScalar(currentValue);
160
+ }
161
+
162
+ return result;
163
+ }
164
+
165
+ function parseBlock(lines, startIndex, baseIndent) {
166
+ const firstLine = lines[startIndex];
167
+ const trimmed = firstLine.trim();
168
+
169
+ if (trimmed.startsWith("- ")) {
170
+ return parseArrayBlock(lines, startIndex, baseIndent);
171
+ }
172
+
173
+ return parseObjectBlock(lines, startIndex, baseIndent);
174
+ }
175
+
176
+ function parseObjectBlock(lines, startIndex, baseIndent) {
177
+ const result = {};
178
+ let index = startIndex;
179
+
180
+ while (index < lines.length) {
181
+ const line = lines[index];
182
+ if (isBlank(line)) {
183
+ index += 1;
184
+ continue;
185
+ }
186
+
187
+ const indent = countIndent(line);
188
+ if (indent < baseIndent) {
189
+ break;
190
+ }
191
+
192
+ if (indent > baseIndent) {
193
+ throw new Error(`Unexpected indentation at line ${index + 1}`);
194
+ }
195
+
196
+ const trimmed = line.trim();
197
+ const colonIndex = trimmed.indexOf(":");
198
+ if (colonIndex === -1) {
199
+ throw new Error(`Expected key at line ${index + 1}`);
200
+ }
201
+
202
+ const key = trimmed.slice(0, colonIndex).trim();
203
+ const remainder = trimmed.slice(colonIndex + 1).trim();
204
+ index += 1;
205
+
206
+ if (remainder === "") {
207
+ while (index < lines.length && isBlank(lines[index])) {
208
+ index += 1;
209
+ }
210
+
211
+ if (index >= lines.length) {
212
+ result[key] = null;
213
+ continue;
214
+ }
215
+
216
+ const childIndent = countIndent(lines[index]);
217
+ if (childIndent <= baseIndent) {
218
+ result[key] = null;
219
+ continue;
220
+ }
221
+
222
+ const [value, nextIndex] = parseBlock(lines, index, childIndent);
223
+ result[key] = value;
224
+ index = nextIndex;
225
+ continue;
226
+ }
227
+
228
+ if (remainder.startsWith("[") && remainder.endsWith("]")) {
229
+ result[key] = parseInlineArray(remainder);
230
+ continue;
231
+ }
232
+
233
+ if (remainder.startsWith("{") && remainder.endsWith("}")) {
234
+ result[key] = parseInlineObject(remainder);
235
+ continue;
236
+ }
237
+
238
+ result[key] = parseScalar(remainder);
239
+ }
240
+
241
+ return [result, index];
242
+ }
243
+
244
+ function parseArrayBlock(lines, startIndex, baseIndent) {
245
+ const result = [];
246
+ let index = startIndex;
247
+
248
+ while (index < lines.length) {
249
+ const line = lines[index];
250
+ if (isBlank(line)) {
251
+ index += 1;
252
+ continue;
253
+ }
254
+
255
+ const indent = countIndent(line);
256
+ if (indent < baseIndent) {
257
+ break;
258
+ }
259
+
260
+ if (indent > baseIndent) {
261
+ throw new Error(`Unexpected indentation at line ${index + 1}`);
262
+ }
263
+
264
+ const trimmed = line.trim();
265
+ if (!trimmed.startsWith("- ")) {
266
+ break;
267
+ }
268
+
269
+ const itemText = trimmed.slice(2).trim();
270
+ index += 1;
271
+
272
+ if (itemText === "") {
273
+ while (index < lines.length && isBlank(lines[index])) {
274
+ index += 1;
275
+ }
276
+
277
+ if (index >= lines.length) {
278
+ result.push(null);
279
+ continue;
280
+ }
281
+
282
+ const childIndent = countIndent(lines[index]);
283
+ if (childIndent <= baseIndent) {
284
+ result.push(null);
285
+ continue;
286
+ }
287
+
288
+ const [value, nextIndex] = parseBlock(lines, index, childIndent);
289
+ result.push(value);
290
+ index = nextIndex;
291
+ continue;
292
+ }
293
+
294
+ const colonIndex = itemText.indexOf(": ");
295
+ if (colonIndex !== -1) {
296
+ const itemKey = itemText.slice(0, colonIndex).trim();
297
+ const remainder = itemText.slice(colonIndex + 2).trim();
298
+ const itemObject = { [itemKey]: parseScalar(remainder) };
299
+
300
+ while (index < lines.length && !isBlank(lines[index])) {
301
+ const nextLine = lines[index];
302
+ const nextIndent = countIndent(nextLine);
303
+ if (nextIndent <= baseIndent) {
304
+ break;
305
+ }
306
+
307
+ const nextTrimmed = nextLine.trim();
308
+ const nextColonIndex = nextTrimmed.indexOf(":");
309
+ if (nextColonIndex === -1) {
310
+ break;
311
+ }
312
+
313
+ const nextKey = nextTrimmed.slice(0, nextColonIndex).trim();
314
+ const nextRemainder = nextTrimmed.slice(nextColonIndex + 1).trim();
315
+ index += 1;
316
+
317
+ if (nextRemainder === "") {
318
+ while (index < lines.length && isBlank(lines[index])) {
319
+ index += 1;
320
+ }
321
+
322
+ if (index >= lines.length || countIndent(lines[index]) <= nextIndent) {
323
+ itemObject[nextKey] = null;
324
+ continue;
325
+ }
326
+
327
+ const [value, nextIndex] = parseBlock(lines, index, countIndent(lines[index]));
328
+ itemObject[nextKey] = value;
329
+ index = nextIndex;
330
+ continue;
331
+ }
332
+
333
+ if (nextRemainder.startsWith("[") && nextRemainder.endsWith("]")) {
334
+ itemObject[nextKey] = parseInlineArray(nextRemainder);
335
+ continue;
336
+ }
337
+
338
+ itemObject[nextKey] = parseScalar(nextRemainder);
339
+ }
340
+
341
+ result.push(itemObject);
342
+ continue;
343
+ }
344
+
345
+ if (itemText.startsWith("[") && itemText.endsWith("]")) {
346
+ result.push(parseInlineArray(itemText));
347
+ continue;
348
+ }
349
+
350
+ if (itemText.startsWith("{") && itemText.endsWith("}")) {
351
+ result.push(parseInlineObject(itemText));
352
+ continue;
353
+ }
354
+
355
+ result.push(parseScalar(itemText));
356
+ }
357
+
358
+ return [result, index];
359
+ }
360
+
361
+ export function parseFrontmatter(text) {
362
+ const trimmed = text.trim();
363
+ if (trimmed === "") {
364
+ return {};
365
+ }
366
+
367
+ const lines = trimmed.split(/\r?\n/);
368
+ const [value] = parseBlock(lines, 0, 0);
369
+ return value ?? {};
370
+ }
371
+
372
+ function needsQuotes(value) {
373
+ if (value === "") {
374
+ return true;
375
+ }
376
+
377
+ if (/^\d/.test(value) && /^-?\d+(?:\.\d+)?$/.test(value)) {
378
+ return true;
379
+ }
380
+
381
+ if (value === "true" || value === "false" || value === "null" || value === "~") {
382
+ return true;
383
+ }
384
+
385
+ if (/[:[\]{},#&*!|>'"%@`]/.test(value)) {
386
+ return true;
387
+ }
388
+
389
+ if (/^\s|\s$/.test(value)) {
390
+ return true;
391
+ }
392
+
393
+ return false;
394
+ }
395
+
396
+ function quoteString(value) {
397
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
398
+ return `"${escaped}"`;
399
+ }
400
+
401
+ function stringifyScalar(value) {
402
+ if (value === null) {
403
+ return "null";
404
+ }
405
+
406
+ if (typeof value === "boolean") {
407
+ return value ? "true" : "false";
408
+ }
409
+
410
+ if (typeof value === "number") {
411
+ return String(value);
412
+ }
413
+
414
+ const stringValue = String(value);
415
+ return needsQuotes(stringValue) ? quoteString(stringValue) : stringValue;
416
+ }
417
+
418
+ function isPlainObject(value) {
419
+ return value !== null && typeof value === "object" && !Array.isArray(value);
420
+ }
421
+
422
+ function stringifyObjectEntry(key, value, indent) {
423
+ const pad = " ".repeat(indent);
424
+
425
+ if (Array.isArray(value)) {
426
+ if (value.length === 0) {
427
+ return `${pad}${key}: []`;
428
+ }
429
+
430
+ const rendered = stringifyArray(value, indent + 1);
431
+ return `${pad}${key}:\n${rendered}`;
432
+ }
433
+
434
+ if (isPlainObject(value)) {
435
+ const entries = Object.entries(value);
436
+ if (entries.length === 0) {
437
+ return `${pad}${key}: {}`;
438
+ }
439
+
440
+ const rendered = stringifyObject(value, indent + 1);
441
+ return `${pad}${key}:\n${rendered}`;
442
+ }
443
+
444
+ return `${pad}${key}: ${stringifyScalar(value)}`;
445
+ }
446
+
447
+ function stringifyArrayItem(value, indent) {
448
+ const pad = " ".repeat(indent);
449
+
450
+ if (Array.isArray(value)) {
451
+ if (value.length === 0) {
452
+ return `${pad}- []`;
453
+ }
454
+
455
+ const rendered = stringifyArray(value, indent + 1);
456
+ return `${pad}-\n${rendered}`;
457
+ }
458
+
459
+ if (isPlainObject(value)) {
460
+ const entries = Object.entries(value);
461
+ if (entries.length === 0) {
462
+ return `${pad}- {}`;
463
+ }
464
+
465
+ const [firstKey, firstValue] = entries[0];
466
+ const rest = entries.slice(1);
467
+ let lines = [`${pad}- ${firstKey}: ${stringifyScalar(firstValue)}`];
468
+
469
+ for (const [key, entryValue] of rest) {
470
+ lines.push(stringifyObjectEntry(key, entryValue, indent + 1));
471
+ }
472
+
473
+ return lines.join("\n");
474
+ }
475
+
476
+ return `${pad}- ${stringifyScalar(value)}`;
477
+ }
478
+
479
+ function stringifyArray(value, indent) {
480
+ return value.map((item) => stringifyArrayItem(item, indent)).join("\n");
481
+ }
482
+
483
+ function stringifyObject(value, indent) {
484
+ return Object.entries(value)
485
+ .map(([key, entryValue]) => stringifyObjectEntry(key, entryValue, indent))
486
+ .join("\n");
487
+ }
488
+
489
+ export function stringifyFrontmatter(value) {
490
+ if (value === null || value === undefined) {
491
+ return "";
492
+ }
493
+
494
+ if (Array.isArray(value)) {
495
+ return stringifyArray(value, 0);
496
+ }
497
+
498
+ if (isPlainObject(value)) {
499
+ const entries = Object.entries(value);
500
+ if (entries.length === 0) {
501
+ return "";
502
+ }
503
+
504
+ return stringifyObject(value, 0);
505
+ }
506
+
507
+ return stringifyScalar(value);
508
+ }
509
+
510
+ export function normalizeFrontmatter(value) {
511
+ const trimmed = (value ?? "").trim();
512
+ return trimmed === "" ? null : trimmed;
513
+ }
514
+
515
+ export function inferValueType(value) {
516
+ if (value === null) {
517
+ return "null";
518
+ }
519
+
520
+ if (typeof value === "boolean") {
521
+ return "boolean";
522
+ }
523
+
524
+ if (typeof value === "number") {
525
+ return "number";
526
+ }
527
+
528
+ if (Array.isArray(value)) {
529
+ return "array";
530
+ }
531
+
532
+ if (isPlainObject(value)) {
533
+ return "object";
534
+ }
535
+
536
+ return "string";
537
+ }
538
+
539
+ export function defaultValueForType(type) {
540
+ switch (type) {
541
+ case "boolean":
542
+ return false;
543
+ case "number":
544
+ return 0;
545
+ case "null":
546
+ return null;
547
+ case "array":
548
+ return [];
549
+ case "object":
550
+ return {};
551
+ default:
552
+ return "";
553
+ }
554
+ }
@@ -0,0 +1,52 @@
1
+ const SVG_ATTRS =
2
+ 'width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"';
3
+
4
+ function icon(content) {
5
+ return `<svg ${SVG_ATTRS}>${content}</svg>`;
6
+ }
7
+
8
+ export const icons = {
9
+ undo: icon(
10
+ '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5 5.5 5.5 0 0 1-5.5 5.5H11"/>',
11
+ ),
12
+ redo: icon(
13
+ '<path d="m15 14 5-5-5-5"/><path d="M20 9H9.5A5.5 5.5 0 0 0 4 14.5 5.5 5.5 0 0 0 9.5 20H13"/>',
14
+ ),
15
+ bold: icon(
16
+ '<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 0 8H6z"/>',
17
+ ),
18
+ italic: icon(
19
+ '<line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>',
20
+ ),
21
+ strikethrough: icon(
22
+ '<path d="M16 4H9a3 3 0 0 0-2.83 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><path d="M4 12h16"/>',
23
+ ),
24
+ link: icon(
25
+ '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
26
+ ),
27
+ image: icon(
28
+ '<rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>',
29
+ ),
30
+ arrowUp: icon('<path d="m18 15-6-6-6 6"/>'),
31
+ arrowDown: icon('<path d="m6 9 6 6 6-6"/>'),
32
+ chevronLeft: icon('<path d="m15 18-6-6 6-6"/>'),
33
+ edit: icon(
34
+ '<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/>',
35
+ ),
36
+ plus: icon('<path d="M5 12h14"/><path d="M12 5v14"/>'),
37
+ trash: icon(
38
+ '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/>',
39
+ ),
40
+ folder: icon(
41
+ '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
42
+ ),
43
+ fileText: icon(
44
+ '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>',
45
+ ),
46
+ layout: icon(
47
+ '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/>',
48
+ ),
49
+ collapseAll: icon(
50
+ '<path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/>',
51
+ ),
52
+ };
@@ -0,0 +1,113 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>starmark</title>
7
+ <link rel="stylesheet" href="/styles.css" />
8
+ </head>
9
+ <body>
10
+ <header class="top-bar">
11
+ <div class="top-bar-inner">
12
+ <h1 class="top-bar-title">starmark</h1>
13
+ <button id="projects-menu-btn" type="button" class="top-bar-btn">
14
+ Projects
15
+ </button>
16
+ </div>
17
+ </header>
18
+
19
+ <dialog id="projects-dialog" class="projects-dialog">
20
+ <div class="dialog-header">
21
+ <h2>Projects</h2>
22
+ <button id="projects-dialog-close" type="button" class="dialog-close" aria-label="Close">
23
+ &times;
24
+ </button>
25
+ </div>
26
+
27
+ <section class="picker">
28
+ <label for="folder-path">Astro project folder</label>
29
+ <div class="picker-row">
30
+ <input
31
+ id="folder-path"
32
+ type="text"
33
+ placeholder="/path/to/your/astro-site"
34
+ spellcheck="false"
35
+ />
36
+ <button id="browse-btn" type="button">Browse…</button>
37
+ <button id="scan-btn" type="button" class="primary">Scan</button>
38
+ </div>
39
+ <p id="scan-info" class="scan-info" hidden></p>
40
+ </section>
41
+
42
+ <section id="projects-section" class="projects" hidden>
43
+ <h3 class="projects-heading">Remembered</h3>
44
+ <ul id="project-list" class="project-list"></ul>
45
+ </section>
46
+ </dialog>
47
+
48
+ <main class="container">
49
+ <section id="list-view" class="results">
50
+ <div class="results-header">
51
+ <h2>Content &amp; pages</h2>
52
+ <div class="results-header-actions">
53
+ <button
54
+ id="collapse-all-btn"
55
+ type="button"
56
+ class="results-icon-btn"
57
+ hidden
58
+ aria-label="Collapse all folders"
59
+ title="Collapse all folders"
60
+ ></button>
61
+ <span id="file-count" class="file-count"></span>
62
+ </div>
63
+ </div>
64
+ <div id="search-box" class="search-box" hidden>
65
+ <input
66
+ id="file-search"
67
+ type="search"
68
+ placeholder="Filter files…"
69
+ spellcheck="false"
70
+ aria-label="Filter files"
71
+ />
72
+ </div>
73
+ <p id="empty-state" class="empty-state">
74
+ Scanning for <code>.md</code> and <code>.mdx</code> files…
75
+ </p>
76
+ <ul id="file-list" class="file-tree" hidden></ul>
77
+ </section>
78
+
79
+ <section id="edit-view" class="edit-view" hidden>
80
+ <div class="edit-sticky-bar">
81
+ <div class="edit-header">
82
+ <button id="edit-back-btn" type="button" class="edit-back-btn" aria-label="Back to file list">
83
+ Back
84
+ </button>
85
+ <div class="edit-file-info">
86
+ <h2 id="edit-file-name" class="edit-file-name"></h2>
87
+ <p id="edit-file-path" class="edit-file-path"></p>
88
+ </div>
89
+ <button id="edit-save-btn" type="button" class="edit-save-btn primary" disabled>
90
+ Save
91
+ </button>
92
+ </div>
93
+ <div id="edit-toolbar" class="edit-toolbar" role="toolbar" aria-label="Editor toolbar"></div>
94
+ </div>
95
+ <details id="frontmatter-panel" class="frontmatter-panel" hidden>
96
+ <summary class="frontmatter-panel-header">Frontmatter</summary>
97
+ <div id="frontmatter-editor" class="frontmatter-editor"></div>
98
+ </details>
99
+ <div
100
+ id="markdown-editor"
101
+ class="markdown-editor"
102
+ contenteditable="true"
103
+ spellcheck="false"
104
+ role="textbox"
105
+ aria-multiline="true"
106
+ aria-labelledby="edit-file-name"
107
+ ></div>
108
+ </section>
109
+ </main>
110
+
111
+ <script src="/app.js" type="module"></script>
112
+ </body>
113
+ </html>