@open-slide/core 1.3.0 → 1.4.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.
- package/dist/{build-_276DMmJ.js → build-1Rqivz0d.js} +1 -1
- package/dist/cli/bin.js +3 -3
- package/dist/{config-BAwKWNtW.js → config-XZJnC_fu.js} +533 -59
- package/dist/{config-D9cZ1A0X.d.ts → config-s0YUbmUe.d.ts} +2 -1
- package/dist/{dev-BoqeVXVq.js → dev-0W8gYiSa.js} +1 -1
- package/dist/{en-CDKzoZvf.js → en-7GU-DHbJ.js} +13 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +40 -10
- package/dist/{preview-BLPxspc9.js → preview-DT9hJvzM.js} +1 -1
- package/dist/{types-JYG1cmwC.d.ts → types-QCpkHkiS.d.ts} +11 -1
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/package.json +1 -1
- package/skills/slide-authoring/SKILL.md +10 -2
- package/src/app/app.tsx +2 -0
- package/src/app/components/asset-view.tsx +36 -9
- package/src/app/components/inspector/inspect-overlay.tsx +49 -3
- package/src/app/components/inspector/inspector-panel.tsx +251 -24
- package/src/app/components/inspector/inspector-provider.tsx +390 -49
- package/src/app/components/player.tsx +25 -5
- package/src/app/components/present/control-bar.tsx +12 -0
- package/src/app/components/sidebar/folder-item.tsx +14 -3
- package/src/app/components/sidebar/sidebar.tsx +10 -0
- package/src/app/lib/export-pdf.ts +6 -0
- package/src/app/lib/inspector/use-editor.ts +9 -1
- package/src/app/lib/slides.ts +7 -0
- package/src/app/lib/use-slide-module.ts +48 -0
- package/src/app/routes/assets.tsx +9 -0
- package/src/app/routes/home-shell.tsx +23 -2
- package/src/app/routes/presenter.tsx +2 -20
- package/src/app/routes/slide.tsx +73 -40
- package/src/locale/en.ts +14 -4
- package/src/locale/ja.ts +14 -4
- package/src/locale/types.ts +11 -1
- package/src/locale/zh-cn.ts +14 -5
- package/src/locale/zh-tw.ts +14 -5
|
@@ -187,11 +187,12 @@ function offsetToLine(source, offset) {
|
|
|
187
187
|
}
|
|
188
188
|
function parseSource$2(source) {
|
|
189
189
|
try {
|
|
190
|
-
|
|
190
|
+
const ast = parse(source, {
|
|
191
191
|
sourceType: "module",
|
|
192
192
|
plugins: ["typescript", "jsx"],
|
|
193
193
|
errorRecovery: true
|
|
194
194
|
});
|
|
195
|
+
return ast.errors && ast.errors.length > 0 ? null : ast;
|
|
195
196
|
} catch {
|
|
196
197
|
return null;
|
|
197
198
|
}
|
|
@@ -202,6 +203,51 @@ function findInnermostJsxElement(ast, line, column) {
|
|
|
202
203
|
for (const n of findJsxAncestors(ast, line, column)) if (t$2.isJSXElement(n)) return n;
|
|
203
204
|
return null;
|
|
204
205
|
}
|
|
206
|
+
function findUniqueElementByText(ast, prevText) {
|
|
207
|
+
const hits = [];
|
|
208
|
+
walkJsx(ast, (n) => {
|
|
209
|
+
if (!t$2.isJSXElement(n)) return;
|
|
210
|
+
const parts = [];
|
|
211
|
+
collectTextRangeParts(n, parts);
|
|
212
|
+
if (textRangeContent(parts) !== prevText) return;
|
|
213
|
+
hits.push({
|
|
214
|
+
node: n,
|
|
215
|
+
size: (n.end ?? 0) - (n.start ?? 0)
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
if (hits.length === 0) return null;
|
|
219
|
+
hits.sort((a, b) => a.size - b.size);
|
|
220
|
+
const best = hits[0];
|
|
221
|
+
const bestStart = best.node.start ?? 0;
|
|
222
|
+
const bestEnd = best.node.end ?? 0;
|
|
223
|
+
const hasSiblingMatch = hits.slice(1).some(({ node }) => (node.start ?? 0) > bestStart || (node.end ?? 0) < bestEnd);
|
|
224
|
+
return hasSiblingMatch ? null : best.node;
|
|
225
|
+
}
|
|
226
|
+
function fallbackTextForOps(ops) {
|
|
227
|
+
for (const op of ops) if ((op.kind === "set-style" || op.kind === "set-text" || op.kind === "set-text-range-style") && op.prevText !== void 0) return op.prevText;
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
function hasOnlyTextOps(ops) {
|
|
231
|
+
return ops.length > 0 && ops.every((op) => op.kind === "set-text");
|
|
232
|
+
}
|
|
233
|
+
function elementTextMatches(element, prevText) {
|
|
234
|
+
const parts = [];
|
|
235
|
+
collectTextRangeParts(element, parts);
|
|
236
|
+
return textRangeContent(parts) === prevText;
|
|
237
|
+
}
|
|
238
|
+
function elementHasTextCandidate(ast, element, prevText) {
|
|
239
|
+
const norm = prevText.trim();
|
|
240
|
+
return collectElementTextCandidates(ast, element).some((candidate) => candidate.current === norm);
|
|
241
|
+
}
|
|
242
|
+
function findElementForEdit(ast, line, column, ops) {
|
|
243
|
+
const element = findInnermostJsxElement(ast, line, column);
|
|
244
|
+
const prevText = fallbackTextForOps(ops);
|
|
245
|
+
if (prevText === null) return element;
|
|
246
|
+
if (hasOnlyTextOps(ops) && element && (elementTextMatches(element, prevText) || elementHasTextCandidate(ast, element, prevText))) return element;
|
|
247
|
+
const textMatch = findUniqueElementByText(ast, prevText);
|
|
248
|
+
if (element && elementTextMatches(element, prevText)) return textMatch ?? element;
|
|
249
|
+
return textMatch ?? element;
|
|
250
|
+
}
|
|
205
251
|
function findJsxByStart(ast, line, column) {
|
|
206
252
|
let hit = null;
|
|
207
253
|
walkJsx(ast, (n) => {
|
|
@@ -227,27 +273,60 @@ function findJsxAttr(opening, name) {
|
|
|
227
273
|
function buildStyleSplice(source, element, ops) {
|
|
228
274
|
const opening = element.openingElement;
|
|
229
275
|
const existing = findJsxAttr(opening, "style");
|
|
230
|
-
const
|
|
276
|
+
const entries = [];
|
|
277
|
+
let hasRawEntry = false;
|
|
231
278
|
if (existing) {
|
|
232
279
|
const value = existing.value;
|
|
233
280
|
if (!value || !t$2.isJSXExpressionContainer(value)) return { error: "style attribute has unsupported form" };
|
|
234
281
|
const expr = value.expression;
|
|
235
|
-
if (!t$2.isObjectExpression(expr))
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
282
|
+
if (!t$2.isObjectExpression(expr)) {
|
|
283
|
+
if (typeof expr.start !== "number" || typeof expr.end !== "number") return { error: "style value missing source range" };
|
|
284
|
+
entries.push({
|
|
285
|
+
kind: "raw",
|
|
286
|
+
text: `...(${source.slice(expr.start, expr.end)})`
|
|
287
|
+
});
|
|
288
|
+
hasRawEntry = true;
|
|
289
|
+
} else for (const prop of expr.properties) if (t$2.isObjectProperty(prop) && !prop.computed) {
|
|
239
290
|
let keyName = null;
|
|
240
291
|
if (t$2.isIdentifier(prop.key)) keyName = prop.key.name;
|
|
241
292
|
else if (t$2.isStringLiteral(prop.key)) keyName = prop.key.value;
|
|
242
293
|
if (!keyName) return { error: "style has unsupported key" };
|
|
243
294
|
const v = prop.value;
|
|
244
|
-
if (typeof v.start !== "number" || typeof v.end !== "number") return { error: "style value missing source range" };
|
|
245
|
-
|
|
295
|
+
if (typeof prop.key.start !== "number" || typeof prop.key.end !== "number" || typeof v.start !== "number" || typeof v.end !== "number") return { error: "style value missing source range" };
|
|
296
|
+
entries.push({
|
|
297
|
+
kind: "prop",
|
|
298
|
+
key: keyName,
|
|
299
|
+
keyText: source.slice(prop.key.start, prop.key.end),
|
|
300
|
+
valueText: source.slice(v.start, v.end)
|
|
301
|
+
});
|
|
302
|
+
} else {
|
|
303
|
+
if (typeof prop.start !== "number" || typeof prop.end !== "number") return { error: "style value missing source range" };
|
|
304
|
+
entries.push({
|
|
305
|
+
kind: "raw",
|
|
306
|
+
text: source.slice(prop.start, prop.end)
|
|
307
|
+
});
|
|
308
|
+
hasRawEntry = true;
|
|
246
309
|
}
|
|
247
310
|
}
|
|
248
|
-
for (const op of ops)
|
|
249
|
-
|
|
250
|
-
|
|
311
|
+
for (const op of ops) {
|
|
312
|
+
const matching = entries.filter((entry) => entry.kind === "prop" && entry.key === op.key);
|
|
313
|
+
if (op.value === null) {
|
|
314
|
+
for (const entry of matching) entries.splice(entries.indexOf(entry), 1);
|
|
315
|
+
if (hasRawEntry) entries.push({
|
|
316
|
+
kind: "prop",
|
|
317
|
+
key: op.key,
|
|
318
|
+
keyText: op.key,
|
|
319
|
+
valueText: "undefined"
|
|
320
|
+
});
|
|
321
|
+
} else if (matching.length > 0) matching[matching.length - 1].valueText = jsString$1(op.value);
|
|
322
|
+
else entries.push({
|
|
323
|
+
kind: "prop",
|
|
324
|
+
key: op.key,
|
|
325
|
+
keyText: op.key,
|
|
326
|
+
valueText: jsString$1(op.value)
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (entries.length === 0) {
|
|
251
330
|
if (!existing) return null;
|
|
252
331
|
let from = existing.start ?? 0;
|
|
253
332
|
if (from > 0 && source[from - 1] === " ") from -= 1;
|
|
@@ -257,16 +336,29 @@ function buildStyleSplice(source, element, ops) {
|
|
|
257
336
|
text: ""
|
|
258
337
|
};
|
|
259
338
|
}
|
|
260
|
-
const propsText =
|
|
339
|
+
const propsText = entries.map((entry) => entry.kind === "prop" ? `${entry.keyText}: ${entry.valueText}` : entry.text).join(", ");
|
|
261
340
|
const newAttr = `style={{ ${propsText} }}`;
|
|
262
|
-
if (existing)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
341
|
+
if (existing) {
|
|
342
|
+
const lastAttr$1 = opening.attributes[opening.attributes.length - 1];
|
|
343
|
+
if (lastAttr$1 && lastAttr$1 !== existing && typeof lastAttr$1.end === "number") {
|
|
344
|
+
const attrsAfterStyle = source.slice(existing.end ?? 0, lastAttr$1.end).replace(/^[ \t]+/, "");
|
|
345
|
+
return {
|
|
346
|
+
from: existing.start ?? 0,
|
|
347
|
+
to: lastAttr$1.end,
|
|
348
|
+
text: `${attrsAfterStyle} ${newAttr}`
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
from: existing.start ?? 0,
|
|
353
|
+
to: existing.end ?? 0,
|
|
354
|
+
text: newAttr
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const lastAttr = opening.attributes[opening.attributes.length - 1];
|
|
358
|
+
const at = lastAttr?.end ?? opening.name.end ?? 0;
|
|
267
359
|
return {
|
|
268
|
-
from:
|
|
269
|
-
to:
|
|
360
|
+
from: at,
|
|
361
|
+
to: at,
|
|
270
362
|
text: ` ${newAttr}`
|
|
271
363
|
};
|
|
272
364
|
}
|
|
@@ -280,6 +372,10 @@ function meaningfulChildren(parent) {
|
|
|
280
372
|
return true;
|
|
281
373
|
});
|
|
282
374
|
}
|
|
375
|
+
function isOnlyMeaningfulChild(parent, child) {
|
|
376
|
+
const meaningful = meaningfulChildren(parent);
|
|
377
|
+
return meaningful.length === 1 && meaningful[0] === child;
|
|
378
|
+
}
|
|
283
379
|
function wrapSplice(parent, text) {
|
|
284
380
|
const first = parent.children[0];
|
|
285
381
|
const last = parent.children[parent.children.length - 1];
|
|
@@ -289,6 +385,60 @@ function wrapSplice(parent, text) {
|
|
|
289
385
|
text
|
|
290
386
|
};
|
|
291
387
|
}
|
|
388
|
+
function splitLinesWithOffsets(value) {
|
|
389
|
+
const lines = [];
|
|
390
|
+
let start = 0;
|
|
391
|
+
for (let i = 0; i < value.length; i++) {
|
|
392
|
+
const ch = value[i];
|
|
393
|
+
if (ch !== "\n" && ch !== "\r") continue;
|
|
394
|
+
lines.push({
|
|
395
|
+
text: value.slice(start, i),
|
|
396
|
+
start
|
|
397
|
+
});
|
|
398
|
+
if (ch === "\r" && value[i + 1] === "\n") i += 1;
|
|
399
|
+
start = i + 1;
|
|
400
|
+
}
|
|
401
|
+
lines.push({
|
|
402
|
+
text: value.slice(start),
|
|
403
|
+
start
|
|
404
|
+
});
|
|
405
|
+
return lines;
|
|
406
|
+
}
|
|
407
|
+
function cleanJsxTextWithOffsets(value) {
|
|
408
|
+
const lines = splitLinesWithOffsets(value);
|
|
409
|
+
let lastNonEmptyLine = 0;
|
|
410
|
+
for (let i = 0; i < lines.length; i++) if (lines[i].text.trim()) lastNonEmptyLine = i;
|
|
411
|
+
let text = "";
|
|
412
|
+
const offsets = [];
|
|
413
|
+
for (let i = 0; i < lines.length; i++) {
|
|
414
|
+
const chars = Array.from(lines[i].text, (ch, j) => ({
|
|
415
|
+
ch: ch === " " ? " " : ch,
|
|
416
|
+
offset: lines[i].start + j
|
|
417
|
+
}));
|
|
418
|
+
let from = 0;
|
|
419
|
+
let to = chars.length;
|
|
420
|
+
if (i !== 0) while (from < to && chars[from].ch === " ") from += 1;
|
|
421
|
+
if (i !== lines.length - 1) while (to > from && chars[to - 1].ch === " ") to -= 1;
|
|
422
|
+
if (from >= to) continue;
|
|
423
|
+
for (const item of chars.slice(from, to)) {
|
|
424
|
+
text += item.ch;
|
|
425
|
+
offsets.push(item.offset);
|
|
426
|
+
}
|
|
427
|
+
if (i !== lastNonEmptyLine) {
|
|
428
|
+
text += " ";
|
|
429
|
+
offsets.push(null);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
text,
|
|
434
|
+
offsets
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function isJsxBrElement(node) {
|
|
438
|
+
if (!t$2.isJSXElement(node)) return false;
|
|
439
|
+
const name = node.openingElement.name;
|
|
440
|
+
return t$2.isJSXIdentifier(name) && name.name.toLowerCase() === "br";
|
|
441
|
+
}
|
|
292
442
|
function collectTextCandidates(element, out) {
|
|
293
443
|
const meaningful = meaningfulChildren(element);
|
|
294
444
|
const isSole = meaningful.length === 1;
|
|
@@ -318,6 +468,226 @@ function collectTextCandidates(element, out) {
|
|
|
318
468
|
}
|
|
319
469
|
} else if (t$2.isJSXElement(child) || t$2.isJSXFragment(child)) collectTextCandidates(child, out);
|
|
320
470
|
}
|
|
471
|
+
function collectTextRangeParts(element, out) {
|
|
472
|
+
const parts = [];
|
|
473
|
+
collectTextRangePartsRaw(element, parts);
|
|
474
|
+
out.push(...normalizeTextRangeParts(parts));
|
|
475
|
+
}
|
|
476
|
+
function collectTextRangePartsRaw(element, out) {
|
|
477
|
+
for (const child of element.children) if (t$2.isJSXText(child)) {
|
|
478
|
+
const { text: current, offsets } = cleanJsxTextWithOffsets(child.value);
|
|
479
|
+
if (current) out.push({
|
|
480
|
+
node: child,
|
|
481
|
+
parent: element,
|
|
482
|
+
current,
|
|
483
|
+
raw: child.value,
|
|
484
|
+
text: formatJsxText,
|
|
485
|
+
offsets
|
|
486
|
+
});
|
|
487
|
+
} else if (t$2.isJSXExpressionContainer(child)) {
|
|
488
|
+
const expression = child.expression;
|
|
489
|
+
if (t$2.isStringLiteral(expression) || t$2.isNumericLiteral(expression)) {
|
|
490
|
+
const raw = String(expression.value);
|
|
491
|
+
const current = raw;
|
|
492
|
+
if (current) out.push({
|
|
493
|
+
node: child,
|
|
494
|
+
parent: element,
|
|
495
|
+
current,
|
|
496
|
+
raw,
|
|
497
|
+
text: (value) => `{${jsString$1(value)}}`,
|
|
498
|
+
offsets: Array.from({ length: current.length }, (_, i) => i)
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
} else if (isJsxBrElement(child)) out.push({
|
|
502
|
+
node: child,
|
|
503
|
+
current: "\n"
|
|
504
|
+
});
|
|
505
|
+
else if (t$2.isJSXElement(child) || t$2.isJSXFragment(child)) collectTextRangePartsRaw(child, out);
|
|
506
|
+
}
|
|
507
|
+
function normalizeTextRangeParts(parts) {
|
|
508
|
+
return parts.flatMap((part, index) => {
|
|
509
|
+
if (!("raw" in part)) return [part];
|
|
510
|
+
let start = 0;
|
|
511
|
+
let end = part.current.length;
|
|
512
|
+
if (parts[index - 1]?.current === "\n") while (start < end && /\s/.test(part.current[start] ?? "")) start++;
|
|
513
|
+
if (parts[index + 1]?.current === "\n") while (end > start && /\s/.test(part.current[end - 1] ?? "")) end--;
|
|
514
|
+
if (start === 0 && end === part.current.length) return [part];
|
|
515
|
+
if (start >= end) return [];
|
|
516
|
+
return [{
|
|
517
|
+
...part,
|
|
518
|
+
current: part.current.slice(start, end),
|
|
519
|
+
offsets: part.offsets.slice(start, end)
|
|
520
|
+
}];
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
function resetValueForRangeStyle(key) {
|
|
524
|
+
if (key === "fontWeight") return "400";
|
|
525
|
+
if (key === "fontStyle") return "normal";
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
function styleSpanForText(text, key, value) {
|
|
529
|
+
const styleValue = value ?? resetValueForRangeStyle(key);
|
|
530
|
+
if (styleValue === null) return formatJsxText(text);
|
|
531
|
+
return `<span style={{ ${key}: ${jsString$1(styleValue)} }}>${formatJsxText(text)}</span>`;
|
|
532
|
+
}
|
|
533
|
+
function textRangeContent(parts) {
|
|
534
|
+
return parts.map((part) => part.current).join("");
|
|
535
|
+
}
|
|
536
|
+
function compactText(value) {
|
|
537
|
+
return value.replace(/\s+/g, "");
|
|
538
|
+
}
|
|
539
|
+
function textMatchesExpected(current, expected) {
|
|
540
|
+
return current === expected || compactText(current) === compactText(expected);
|
|
541
|
+
}
|
|
542
|
+
function formatRichText(value, formatText = formatJsxText) {
|
|
543
|
+
return value.split("\n").map((part) => formatText(part)).join("<br />");
|
|
544
|
+
}
|
|
545
|
+
function formatOptionalText(value, formatText = formatJsxText) {
|
|
546
|
+
return value ? formatText(value) : "";
|
|
547
|
+
}
|
|
548
|
+
function textDiff(prevText, nextText) {
|
|
549
|
+
let start = 0;
|
|
550
|
+
while (start < prevText.length && start < nextText.length && prevText[start] === nextText[start]) start += 1;
|
|
551
|
+
let prevEnd = prevText.length;
|
|
552
|
+
let nextEnd = nextText.length;
|
|
553
|
+
while (prevEnd > start && nextEnd > start && prevText[prevEnd - 1] === nextText[nextEnd - 1]) {
|
|
554
|
+
prevEnd -= 1;
|
|
555
|
+
nextEnd -= 1;
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
start,
|
|
559
|
+
end: prevEnd,
|
|
560
|
+
value: nextText.slice(start, nextEnd)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function textLeafSplice(part, value) {
|
|
564
|
+
const rawRange = textLeafRawRange(part, 0, part.current.length);
|
|
565
|
+
if (!rawRange) return spliceRange(part.node, part.text(value));
|
|
566
|
+
const { rawStart, rawEnd } = rawRange;
|
|
567
|
+
return {
|
|
568
|
+
from: part.node.start ?? 0,
|
|
569
|
+
to: part.node.end ?? 0,
|
|
570
|
+
text: `${part.raw.slice(0, rawStart)}${formatRichText(value, part.text)}${part.raw.slice(rawEnd)}`
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function textLeafRawRange(part, start, end) {
|
|
574
|
+
if (start >= end) return null;
|
|
575
|
+
let first = null;
|
|
576
|
+
let last = null;
|
|
577
|
+
for (let i = start; i < end; i++) {
|
|
578
|
+
const offset = part.offsets[i];
|
|
579
|
+
if (offset === void 0) return null;
|
|
580
|
+
if (offset === null) continue;
|
|
581
|
+
first ??= offset;
|
|
582
|
+
last = offset;
|
|
583
|
+
}
|
|
584
|
+
if (first === null || last === null) return null;
|
|
585
|
+
return {
|
|
586
|
+
rawStart: first,
|
|
587
|
+
rawEnd: last + 1
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function buildTextRangeReplaceSplices(parts, start, end, value) {
|
|
591
|
+
const splices = [];
|
|
592
|
+
let offset = 0;
|
|
593
|
+
let inserted = false;
|
|
594
|
+
for (const part of parts) {
|
|
595
|
+
const partStart = offset;
|
|
596
|
+
const partEnd = partStart + part.current.length;
|
|
597
|
+
offset = partEnd;
|
|
598
|
+
const overlaps = start < partEnd && end > partStart;
|
|
599
|
+
const insertsHere = start === end && !inserted && start >= partStart && start <= partEnd;
|
|
600
|
+
if (!overlaps && !insertsHere) continue;
|
|
601
|
+
if ("raw" in part) {
|
|
602
|
+
const localStart = Math.max(start, partStart) - partStart;
|
|
603
|
+
const localEnd = overlaps ? Math.min(end, partEnd) - partStart : localStart;
|
|
604
|
+
const nextText = `${part.current.slice(0, localStart)}${inserted ? "" : value}${part.current.slice(localEnd)}`;
|
|
605
|
+
splices.push(textLeafSplice(part, nextText));
|
|
606
|
+
} else if (overlaps) splices.push(spliceRange(part.node, inserted ? "" : formatRichText(value)));
|
|
607
|
+
else if (insertsHere) {
|
|
608
|
+
const at = start === partStart ? part.node.start ?? 0 : part.node.end ?? 0;
|
|
609
|
+
splices.push({
|
|
610
|
+
from: at,
|
|
611
|
+
to: at,
|
|
612
|
+
text: formatRichText(value)
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
inserted = true;
|
|
616
|
+
}
|
|
617
|
+
if (!inserted && start === end && start === offset) {
|
|
618
|
+
const last = parts[parts.length - 1];
|
|
619
|
+
if (!last) return { error: "element has no editable text" };
|
|
620
|
+
if ("raw" in last) splices.push(textLeafSplice(last, `${last.current}${value}`));
|
|
621
|
+
else splices.push({
|
|
622
|
+
from: last.node.end ?? 0,
|
|
623
|
+
to: last.node.end ?? 0,
|
|
624
|
+
text: formatRichText(value)
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return splices;
|
|
628
|
+
}
|
|
629
|
+
function buildTextContentSplices(element, value, prevText) {
|
|
630
|
+
const parts = [];
|
|
631
|
+
collectTextRangeParts(element, parts);
|
|
632
|
+
const current = textRangeContent(parts);
|
|
633
|
+
if (!textMatchesExpected(current, prevText)) return { error: "no text candidate matches the current value" };
|
|
634
|
+
const diff = textDiff(current, value);
|
|
635
|
+
if (diff.start === diff.end && diff.value === "") return [];
|
|
636
|
+
return buildTextRangeReplaceSplices(parts, diff.start, diff.end, diff.value);
|
|
637
|
+
}
|
|
638
|
+
function buildTextRangeStyleSplices(ast, source, element, start, end, op, prevText) {
|
|
639
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end <= start) return { error: "invalid text range" };
|
|
640
|
+
const parts = [];
|
|
641
|
+
collectTextRangeParts(element, parts);
|
|
642
|
+
const current = prevText ?? textRangeContent(parts);
|
|
643
|
+
if (!current) return { error: "element has no editable text" };
|
|
644
|
+
if (end > current.length) return { error: "text range is out of bounds" };
|
|
645
|
+
const renderedText = textRangeContent(parts);
|
|
646
|
+
if (prevText !== void 0 && renderedText !== prevText) {
|
|
647
|
+
if (elementTextCandidateMatches(ast, element, prevText)) {
|
|
648
|
+
const result = buildStyleSplice(source, element, [op]);
|
|
649
|
+
if (result && "error" in result) return result;
|
|
650
|
+
return result ? [result] : [];
|
|
651
|
+
}
|
|
652
|
+
return { error: "no text candidate matches the current value" };
|
|
653
|
+
}
|
|
654
|
+
const splices = [];
|
|
655
|
+
let leafStart = 0;
|
|
656
|
+
for (const leaf of parts) {
|
|
657
|
+
const leafEnd = leafStart + leaf.current.length;
|
|
658
|
+
if (!("raw" in leaf)) {
|
|
659
|
+
leafStart = leafEnd;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const selectedStart = Math.max(start, leafStart);
|
|
663
|
+
const selectedEnd = Math.min(end, leafEnd);
|
|
664
|
+
if (selectedStart >= selectedEnd) {
|
|
665
|
+
leafStart = leafEnd;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (selectedStart === leafStart && selectedEnd === leafEnd && t$2.isJSXElement(leaf.parent) && leaf.parent !== element && isOnlyMeaningfulChild(leaf.parent, leaf.node)) {
|
|
669
|
+
const result = buildStyleSplice(source, leaf.parent, [op]);
|
|
670
|
+
if (result && "error" in result) return result;
|
|
671
|
+
if (result) splices.push(result);
|
|
672
|
+
leafStart = leafEnd;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const localStart = selectedStart - leafStart;
|
|
676
|
+
const localEnd = selectedEnd - leafStart;
|
|
677
|
+
const rawRange = textLeafRawRange(leaf, localStart, localEnd);
|
|
678
|
+
if (!rawRange) return { error: "text range source mismatch" };
|
|
679
|
+
const raw = leaf.raw;
|
|
680
|
+
const { rawStart, rawEnd } = rawRange;
|
|
681
|
+
const before = raw.slice(0, rawStart);
|
|
682
|
+
const selected = leaf.current.slice(localStart, localEnd);
|
|
683
|
+
const after = raw.slice(rawEnd);
|
|
684
|
+
const beforeText = t$2.isJSXText(leaf.node) ? before : formatOptionalText(before, leaf.text);
|
|
685
|
+
const afterText = t$2.isJSXText(leaf.node) ? after : formatOptionalText(after, leaf.text);
|
|
686
|
+
splices.push(spliceRange(leaf.node, `${beforeText}${styleSpanForText(selected, op.key, op.value)}${afterText}`));
|
|
687
|
+
leafStart = leafEnd;
|
|
688
|
+
}
|
|
689
|
+
return splices.length > 0 ? splices : null;
|
|
690
|
+
}
|
|
321
691
|
function propPassthroughName(element) {
|
|
322
692
|
const meaningful = meaningfulChildren(element);
|
|
323
693
|
if (meaningful.length !== 1) return null;
|
|
@@ -517,7 +887,7 @@ function collectArrayMapCandidates(ast, element) {
|
|
|
517
887
|
}
|
|
518
888
|
return out;
|
|
519
889
|
}
|
|
520
|
-
function
|
|
890
|
+
function collectElementTextCandidates(ast, element) {
|
|
521
891
|
const candidates = [];
|
|
522
892
|
collectTextCandidates(element, candidates);
|
|
523
893
|
if (candidates.length === 0) {
|
|
@@ -527,6 +897,14 @@ function buildTextSplice(ast, element, value, prevText) {
|
|
|
527
897
|
else if (passthrough && enclosing && componentDestructuresProp(enclosing.fn, passthrough)) candidates.push(...collectPropCallSiteCandidates(ast, enclosing.name, passthrough));
|
|
528
898
|
}
|
|
529
899
|
if (candidates.length === 0) candidates.push(...collectArrayMapCandidates(ast, element));
|
|
900
|
+
return candidates;
|
|
901
|
+
}
|
|
902
|
+
function elementTextCandidateMatches(ast, element, prevText) {
|
|
903
|
+
const norm = prevText.trim();
|
|
904
|
+
return collectElementTextCandidates(ast, element).some((candidate) => candidate.current === norm);
|
|
905
|
+
}
|
|
906
|
+
function buildTextSplice(ast, element, value, prevText) {
|
|
907
|
+
const candidates = collectElementTextCandidates(ast, element);
|
|
530
908
|
if (candidates.length === 0) return { error: "element has no editable text" };
|
|
531
909
|
if (candidates.length === 1) return candidates[0].splice(value);
|
|
532
910
|
if (prevText === void 0) return { error: "element has multiple text candidates; missing prevText" };
|
|
@@ -603,7 +981,7 @@ function planAssetImport(ast, assetPath) {
|
|
|
603
981
|
}
|
|
604
982
|
function planAssetAttr(ast, element, attr, assetPath) {
|
|
605
983
|
if (!attr || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) return { error: "invalid attribute name" };
|
|
606
|
-
if (!assetPath.startsWith("./assets/")) return { error: "asset path must start with ./assets/" };
|
|
984
|
+
if (!assetPath.startsWith("./assets/") && !assetPath.startsWith("@assets/")) return { error: "asset path must start with ./assets/ or @assets/" };
|
|
607
985
|
const { identifier, importSplice } = planAssetImport(ast, assetPath);
|
|
608
986
|
const opening = element.openingElement;
|
|
609
987
|
const newAttr = `${attr}={${identifier}}`;
|
|
@@ -641,7 +1019,7 @@ function readJsxNumberAttr(opening, name) {
|
|
|
641
1019
|
function planReplacePlaceholder(ast, element, assetPath) {
|
|
642
1020
|
const opening = element.openingElement;
|
|
643
1021
|
if (!t$2.isJSXIdentifier(opening.name) || opening.name.name !== "ImagePlaceholder") return { error: "not a placeholder" };
|
|
644
|
-
if (!assetPath.startsWith("./assets/")) return { error: "asset path must start with ./assets/" };
|
|
1022
|
+
if (!assetPath.startsWith("./assets/") && !assetPath.startsWith("@assets/")) return { error: "asset path must start with ./assets/ or @assets/" };
|
|
645
1023
|
const hint = readJsxStringAttr(opening, "hint") ?? "";
|
|
646
1024
|
const width = readJsxNumberAttr(opening, "width");
|
|
647
1025
|
const height = readJsxNumberAttr(opening, "height");
|
|
@@ -670,7 +1048,7 @@ function applyEdit(source, line, column, ops) {
|
|
|
670
1048
|
status: 422,
|
|
671
1049
|
error: "could not parse source"
|
|
672
1050
|
};
|
|
673
|
-
const element =
|
|
1051
|
+
const element = findElementForEdit(ast, line, column, ops);
|
|
674
1052
|
if (!element) return {
|
|
675
1053
|
ok: false,
|
|
676
1054
|
status: 422,
|
|
@@ -691,14 +1069,42 @@ function applyEdit(source, line, column, ops) {
|
|
|
691
1069
|
if (result) splices.push(result);
|
|
692
1070
|
}
|
|
693
1071
|
for (const op of ops) {
|
|
694
|
-
if (op.kind !== "set-text") continue;
|
|
695
|
-
const result =
|
|
696
|
-
|
|
1072
|
+
if (op.kind !== "set-text-range-style") continue;
|
|
1073
|
+
const result = buildTextRangeStyleSplices(ast, source, element, op.start, op.end, {
|
|
1074
|
+
key: op.key,
|
|
1075
|
+
value: op.value
|
|
1076
|
+
}, op.prevText);
|
|
1077
|
+
if (result && "error" in result) return {
|
|
697
1078
|
ok: false,
|
|
698
1079
|
status: 422,
|
|
699
1080
|
error: result.error
|
|
700
1081
|
};
|
|
701
|
-
splices.push(result);
|
|
1082
|
+
if (result) splices.push(...result);
|
|
1083
|
+
}
|
|
1084
|
+
for (const op of ops) {
|
|
1085
|
+
if (op.kind !== "set-text") continue;
|
|
1086
|
+
if (op.prevText !== void 0 && (op.value.includes("\n") || op.prevText.includes("\n"))) {
|
|
1087
|
+
const richResult = buildTextContentSplices(element, op.value, op.prevText);
|
|
1088
|
+
if (!("error" in richResult)) {
|
|
1089
|
+
splices.push(...richResult);
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const result = buildTextSplice(ast, element, op.value, op.prevText);
|
|
1094
|
+
if ("error" in result) {
|
|
1095
|
+
if (op.prevText === void 0) return {
|
|
1096
|
+
ok: false,
|
|
1097
|
+
status: 422,
|
|
1098
|
+
error: result.error
|
|
1099
|
+
};
|
|
1100
|
+
const richResult = buildTextContentSplices(element, op.value, op.prevText);
|
|
1101
|
+
if ("error" in richResult) return {
|
|
1102
|
+
ok: false,
|
|
1103
|
+
status: 422,
|
|
1104
|
+
error: result.error
|
|
1105
|
+
};
|
|
1106
|
+
splices.push(...richResult);
|
|
1107
|
+
} else splices.push(result);
|
|
702
1108
|
}
|
|
703
1109
|
const assetOps = ops.flatMap((op) => op.kind === "set-attr-asset" ? [op] : []);
|
|
704
1110
|
const placeholderOps = ops.flatMap((op) => op.kind === "replace-placeholder-with-image" ? [op] : []);
|
|
@@ -742,6 +1148,11 @@ function applyEdit(source, line, column, ops) {
|
|
|
742
1148
|
splices.sort((a, b) => b.from - a.from);
|
|
743
1149
|
let next = source;
|
|
744
1150
|
for (const sp of splices) next = next.slice(0, sp.from) + sp.text + next.slice(sp.to);
|
|
1151
|
+
if (!parseSource$2(next)) return {
|
|
1152
|
+
ok: false,
|
|
1153
|
+
status: 422,
|
|
1154
|
+
error: "edit would produce invalid source"
|
|
1155
|
+
};
|
|
745
1156
|
return {
|
|
746
1157
|
ok: true,
|
|
747
1158
|
source: next
|
|
@@ -1406,6 +1817,7 @@ function designPlugin(opts) {
|
|
|
1406
1817
|
//#region src/vite/files-plugin.ts
|
|
1407
1818
|
const FOLDER_ID_RE = /^f-[a-f0-9]{8}$/;
|
|
1408
1819
|
const SLIDE_ID_RE$1 = /^[a-z0-9_-]+$/i;
|
|
1820
|
+
const GLOBAL_SCOPE = "@global";
|
|
1409
1821
|
const COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
|
1410
1822
|
const ASSET_FORBIDDEN_RE = /[\x00-\x1F\x7F/\\:*?"<>|]/;
|
|
1411
1823
|
const ASSET_MAX_BYTES = 25 * 1024 * 1024;
|
|
@@ -1535,6 +1947,19 @@ function resolveAssetFile(slidesRoot, slideId, filename) {
|
|
|
1535
1947
|
if (!file.startsWith(assetsDir + path.sep)) return null;
|
|
1536
1948
|
return file;
|
|
1537
1949
|
}
|
|
1950
|
+
function resolveScopedAssetsDir(slidesRoot, globalAssetsRoot, scope) {
|
|
1951
|
+
if (scope === GLOBAL_SCOPE) return globalAssetsRoot;
|
|
1952
|
+
return resolveAssetsDir(slidesRoot, scope);
|
|
1953
|
+
}
|
|
1954
|
+
function resolveScopedAssetFile(slidesRoot, globalAssetsRoot, scope, filename) {
|
|
1955
|
+
if (scope === GLOBAL_SCOPE) {
|
|
1956
|
+
if (!validateAssetName(filename)) return null;
|
|
1957
|
+
const file = path.resolve(globalAssetsRoot, filename);
|
|
1958
|
+
if (!file.startsWith(globalAssetsRoot + path.sep)) return null;
|
|
1959
|
+
return file;
|
|
1960
|
+
}
|
|
1961
|
+
return resolveAssetFile(slidesRoot, scope, filename);
|
|
1962
|
+
}
|
|
1538
1963
|
function resolveSlideEntry(slidesRoot, slideId) {
|
|
1539
1964
|
if (!SLIDE_ID_RE$1.test(slideId)) return null;
|
|
1540
1965
|
const dir = path.resolve(slidesRoot, slideId);
|
|
@@ -1852,7 +2277,9 @@ function validateIcon(v) {
|
|
|
1852
2277
|
function filesPlugin(opts) {
|
|
1853
2278
|
const userCwd = opts.userCwd;
|
|
1854
2279
|
const slidesDir = opts.slidesDir ?? "slides";
|
|
2280
|
+
const assetsDir = opts.assetsDir ?? "assets";
|
|
1855
2281
|
const slidesRoot = path.resolve(userCwd, slidesDir);
|
|
2282
|
+
const globalAssetsRoot = path.resolve(userCwd, assetsDir);
|
|
1856
2283
|
const manifestPath = path.join(slidesRoot, ".folders.json");
|
|
1857
2284
|
return {
|
|
1858
2285
|
name: "open-slide:files",
|
|
@@ -1865,7 +2292,16 @@ function filesPlugin(opts) {
|
|
|
1865
2292
|
event: "open-slide:files-changed"
|
|
1866
2293
|
});
|
|
1867
2294
|
});
|
|
2295
|
+
server.watcher.add(globalAssetsRoot);
|
|
1868
2296
|
const onAssetChange = (p) => {
|
|
2297
|
+
if (p.startsWith(globalAssetsRoot + path.sep) || p === globalAssetsRoot) {
|
|
2298
|
+
server.ws.send({
|
|
2299
|
+
type: "custom",
|
|
2300
|
+
event: "open-slide:assets-changed",
|
|
2301
|
+
data: { slideId: GLOBAL_SCOPE }
|
|
2302
|
+
});
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
1869
2305
|
if (!p.startsWith(slidesRoot + path.sep)) return;
|
|
1870
2306
|
const rel = p.slice(slidesRoot.length + 1);
|
|
1871
2307
|
const parts = rel.split(path.sep);
|
|
@@ -1989,11 +2425,11 @@ function filesPlugin(opts) {
|
|
|
1989
2425
|
const fileMatch = url.pathname.match(/^\/([^/]+)\/([^/]+)$/);
|
|
1990
2426
|
if (listMatch && method === "GET") {
|
|
1991
2427
|
const slideId = listMatch[1];
|
|
1992
|
-
const assetsDir =
|
|
1993
|
-
if (!assetsDir) return json$1(res, 400, { error: "invalid slideId" });
|
|
2428
|
+
const assetsDir$1 = resolveScopedAssetsDir(slidesRoot, globalAssetsRoot, slideId);
|
|
2429
|
+
if (!assetsDir$1) return json$1(res, 400, { error: "invalid slideId" });
|
|
1994
2430
|
let entries;
|
|
1995
2431
|
try {
|
|
1996
|
-
entries = await fs.readdir(assetsDir);
|
|
2432
|
+
entries = await fs.readdir(assetsDir$1);
|
|
1997
2433
|
} catch (err) {
|
|
1998
2434
|
if (err.code === "ENOENT") return json$1(res, 200, { assets: [] });
|
|
1999
2435
|
throw err;
|
|
@@ -2001,7 +2437,7 @@ function filesPlugin(opts) {
|
|
|
2001
2437
|
const assets = [];
|
|
2002
2438
|
for (const name of entries) {
|
|
2003
2439
|
if (!validateAssetName(name)) continue;
|
|
2004
|
-
const stat = await fs.stat(path.join(assetsDir, name));
|
|
2440
|
+
const stat = await fs.stat(path.join(assetsDir$1, name));
|
|
2005
2441
|
if (!stat.isFile()) continue;
|
|
2006
2442
|
assets.push({
|
|
2007
2443
|
name,
|
|
@@ -2017,7 +2453,7 @@ function filesPlugin(opts) {
|
|
|
2017
2453
|
if (fileMatch) {
|
|
2018
2454
|
const slideId = fileMatch[1];
|
|
2019
2455
|
const filename = decodeURIComponent(fileMatch[2]);
|
|
2020
|
-
const file =
|
|
2456
|
+
const file = resolveScopedAssetFile(slidesRoot, globalAssetsRoot, slideId, filename);
|
|
2021
2457
|
if (!file) return json$1(res, 400, { error: "invalid path" });
|
|
2022
2458
|
if (method === "GET") try {
|
|
2023
2459
|
const buf = await fs.readFile(file);
|
|
@@ -2039,9 +2475,9 @@ function filesPlugin(opts) {
|
|
|
2039
2475
|
await fs.access(file);
|
|
2040
2476
|
return json$1(res, 409, { error: "asset exists" });
|
|
2041
2477
|
} catch {}
|
|
2042
|
-
const assetsDir =
|
|
2043
|
-
if (!assetsDir) return json$1(res, 400, { error: "invalid slideId" });
|
|
2044
|
-
await fs.mkdir(assetsDir, { recursive: true });
|
|
2478
|
+
const assetsDir$1 = resolveScopedAssetsDir(slidesRoot, globalAssetsRoot, slideId);
|
|
2479
|
+
if (!assetsDir$1) return json$1(res, 400, { error: "invalid slideId" });
|
|
2480
|
+
await fs.mkdir(assetsDir$1, { recursive: true });
|
|
2045
2481
|
const chunks = [];
|
|
2046
2482
|
let total = 0;
|
|
2047
2483
|
let oversized = false;
|
|
@@ -2076,7 +2512,7 @@ function filesPlugin(opts) {
|
|
|
2076
2512
|
ok: true,
|
|
2077
2513
|
name: filename
|
|
2078
2514
|
});
|
|
2079
|
-
const dest =
|
|
2515
|
+
const dest = resolveScopedAssetFile(slidesRoot, globalAssetsRoot, slideId, target);
|
|
2080
2516
|
if (!dest) return json$1(res, 400, { error: "invalid name" });
|
|
2081
2517
|
try {
|
|
2082
2518
|
await fs.access(dest);
|
|
@@ -2571,10 +3007,27 @@ async function generateSlidesModule(files, slidesRoot, isDev) {
|
|
|
2571
3007
|
const themesMap = {};
|
|
2572
3008
|
for (const e of entries) if (e.theme) themesMap[e.id] = e.theme;
|
|
2573
3009
|
const themesJson = JSON.stringify(themesMap);
|
|
2574
|
-
const
|
|
3010
|
+
const importTokens = JSON.stringify(Object.fromEntries(entries.map((e) => [e.id, 0])));
|
|
3011
|
+
const devRuntime = isDev ? `
|
|
3012
|
+
const slideImportTokens = ${importTokens};
|
|
3013
|
+
if (import.meta.hot) {
|
|
3014
|
+
import.meta.hot.on('open-slide:slide-changed', (data) => {
|
|
3015
|
+
const ids = Array.isArray(data?.slideIds) ? data.slideIds : data?.slideId ? [data.slideId] : [];
|
|
3016
|
+
const token = Date.now();
|
|
3017
|
+
for (const id of ids) {
|
|
3018
|
+
if (Object.prototype.hasOwnProperty.call(slideImportTokens, id)) slideImportTokens[id] = token;
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
` : "";
|
|
3023
|
+
const cases = entries.map((e) => {
|
|
3024
|
+
const importExpr = isDev ? `import(/* @vite-ignore */ ${JSON.stringify(`${e.importPath}?t=`)} + slideImportTokens[${JSON.stringify(e.id)}])` : `import(${JSON.stringify(e.importPath)})`;
|
|
3025
|
+
return ` case ${JSON.stringify(e.id)}: return ${importExpr};`;
|
|
3026
|
+
}).join("\n");
|
|
2575
3027
|
return `// virtual:open-slide/slides — generated
|
|
2576
3028
|
export const slideIds = ${ids};
|
|
2577
3029
|
export const slideThemes = ${themesJson};
|
|
3030
|
+
${devRuntime}
|
|
2578
3031
|
|
|
2579
3032
|
export async function loadSlide(id) {
|
|
2580
3033
|
switch (id) {
|
|
@@ -2590,6 +3043,32 @@ function openSlidePlugin(opts) {
|
|
|
2590
3043
|
const slidesRoot = path.resolve(userCwd, slidesDir);
|
|
2591
3044
|
const foldersManifestPath = path.join(slidesRoot, ".folders.json");
|
|
2592
3045
|
let isDev = false;
|
|
3046
|
+
const slideIdForEntry = (p) => {
|
|
3047
|
+
const rel = path.relative(slidesRoot, p);
|
|
3048
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) return null;
|
|
3049
|
+
const parts = rel.split(path.sep);
|
|
3050
|
+
if (parts.length !== 2) return null;
|
|
3051
|
+
if (!/^index\.(tsx|jsx|ts|js)$/.test(parts[1])) return null;
|
|
3052
|
+
return parts[0];
|
|
3053
|
+
};
|
|
3054
|
+
let slideChangeTimer = null;
|
|
3055
|
+
const pendingSlideChanges = new Set();
|
|
3056
|
+
const queueSlideChanged = (server, id) => {
|
|
3057
|
+
pendingSlideChanges.add(id);
|
|
3058
|
+
if (slideChangeTimer) clearTimeout(slideChangeTimer);
|
|
3059
|
+
slideChangeTimer = setTimeout(() => {
|
|
3060
|
+
slideChangeTimer = null;
|
|
3061
|
+
const mod = server.moduleGraph.getModuleById(resolved$1(SLIDES_VMOD));
|
|
3062
|
+
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
3063
|
+
const slideIds = Array.from(pendingSlideChanges);
|
|
3064
|
+
pendingSlideChanges.clear();
|
|
3065
|
+
server.ws.send({
|
|
3066
|
+
type: "custom",
|
|
3067
|
+
event: "open-slide:slide-changed",
|
|
3068
|
+
data: { slideIds }
|
|
3069
|
+
});
|
|
3070
|
+
}, 100);
|
|
3071
|
+
};
|
|
2593
3072
|
return {
|
|
2594
3073
|
name: "open-slide",
|
|
2595
3074
|
config(_c, env) {
|
|
@@ -2630,14 +3109,14 @@ function openSlidePlugin(opts) {
|
|
|
2630
3109
|
}
|
|
2631
3110
|
return null;
|
|
2632
3111
|
},
|
|
3112
|
+
handleHotUpdate(ctx) {
|
|
3113
|
+
const slideId = slideIdForEntry(ctx.file);
|
|
3114
|
+
if (!slideId) return;
|
|
3115
|
+
queueSlideChanged(ctx.server, slideId);
|
|
3116
|
+
return [];
|
|
3117
|
+
},
|
|
2633
3118
|
configureServer(server) {
|
|
2634
|
-
const isSlideEntry = (p) =>
|
|
2635
|
-
const rel = path.relative(slidesRoot, p);
|
|
2636
|
-
if (rel.startsWith("..") || path.isAbsolute(rel)) return false;
|
|
2637
|
-
const parts = rel.split(path.sep);
|
|
2638
|
-
if (parts.length !== 2) return false;
|
|
2639
|
-
return /^index\.(tsx|jsx|ts|js)$/.test(parts[1]);
|
|
2640
|
-
};
|
|
3119
|
+
const isSlideEntry = (p) => slideIdForEntry(p) !== null;
|
|
2641
3120
|
let reloadTimer = null;
|
|
2642
3121
|
const reload = () => {
|
|
2643
3122
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
@@ -2655,18 +3134,6 @@ function openSlidePlugin(opts) {
|
|
|
2655
3134
|
server.watcher.on("unlink", (p) => {
|
|
2656
3135
|
if (isSlideEntry(p)) reload();
|
|
2657
3136
|
});
|
|
2658
|
-
let slideThemeTimer = null;
|
|
2659
|
-
const invalidateSlidesVmod = () => {
|
|
2660
|
-
if (slideThemeTimer) clearTimeout(slideThemeTimer);
|
|
2661
|
-
slideThemeTimer = setTimeout(() => {
|
|
2662
|
-
slideThemeTimer = null;
|
|
2663
|
-
const mod = server.moduleGraph.getModuleById(resolved$1(SLIDES_VMOD));
|
|
2664
|
-
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
2665
|
-
}, 100);
|
|
2666
|
-
};
|
|
2667
|
-
server.watcher.on("change", (p) => {
|
|
2668
|
-
if (isSlideEntry(p)) invalidateSlidesVmod();
|
|
2669
|
-
});
|
|
2670
3137
|
let foldersTimer = null;
|
|
2671
3138
|
const invalidateFolders = () => {
|
|
2672
3139
|
if (foldersTimer) clearTimeout(foldersTimer);
|
|
@@ -2854,8 +3321,10 @@ async function createViteConfig(opts) {
|
|
|
2854
3321
|
const config = opts.config ?? await loadUserConfig(userCwd);
|
|
2855
3322
|
const slidesDir = config.slidesDir ?? "slides";
|
|
2856
3323
|
const themesDir = config.themesDir ?? "themes";
|
|
3324
|
+
const assetsDir = config.assetsDir ?? "assets";
|
|
2857
3325
|
const slidesAbs = path.resolve(userCwd, slidesDir);
|
|
2858
3326
|
const themesAbs = path.resolve(userCwd, themesDir);
|
|
3327
|
+
const assetsAbs = path.resolve(userCwd, assetsDir);
|
|
2859
3328
|
return {
|
|
2860
3329
|
root: APP_ROOT,
|
|
2861
3330
|
configFile: false,
|
|
@@ -2886,14 +3355,18 @@ async function createViteConfig(opts) {
|
|
|
2886
3355
|
}),
|
|
2887
3356
|
filesPlugin({
|
|
2888
3357
|
userCwd,
|
|
2889
|
-
slidesDir
|
|
3358
|
+
slidesDir,
|
|
3359
|
+
assetsDir
|
|
2890
3360
|
}),
|
|
2891
3361
|
currentPlugin({
|
|
2892
3362
|
userCwd,
|
|
2893
3363
|
slidesDir
|
|
2894
3364
|
})
|
|
2895
3365
|
],
|
|
2896
|
-
resolve: { alias: {
|
|
3366
|
+
resolve: { alias: {
|
|
3367
|
+
"@": APP_ROOT,
|
|
3368
|
+
"@assets": assetsAbs
|
|
3369
|
+
} },
|
|
2897
3370
|
optimizeDeps: {
|
|
2898
3371
|
entries: [path.join(APP_ROOT, "main.tsx")],
|
|
2899
3372
|
include: [
|
|
@@ -2925,7 +3398,8 @@ async function createViteConfig(opts) {
|
|
|
2925
3398
|
APP_ROOT,
|
|
2926
3399
|
userCwd,
|
|
2927
3400
|
slidesAbs,
|
|
2928
|
-
themesAbs
|
|
3401
|
+
themesAbs,
|
|
3402
|
+
assetsAbs
|
|
2929
3403
|
] }
|
|
2930
3404
|
},
|
|
2931
3405
|
build: {
|