@rohal12/spindle 0.52.5 → 0.52.7
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/pkg/format.js +1 -1
- package/dist/pkg/headless.js +705 -576
- package/dist/pkg/macro-registry.json +255 -0
- package/dist/pkg/story-variables.js +54 -32
- package/package.json +1 -1
- package/src/components/macros/Button.tsx +2 -4
- package/src/components/macros/Checkbox.tsx +6 -13
- package/src/components/macros/Computed.tsx +38 -55
- package/src/components/macros/Cycle.tsx +1 -0
- package/src/components/macros/Dialog.tsx +7 -8
- package/src/components/macros/Do.tsx +2 -1
- package/src/components/macros/For.tsx +27 -78
- package/src/components/macros/Goto.tsx +6 -9
- package/src/components/macros/Include.tsx +10 -57
- package/src/components/macros/Listbox.tsx +1 -0
- package/src/components/macros/MacroError.tsx +37 -0
- package/src/components/macros/MacroLink.tsx +9 -24
- package/src/components/macros/Meter.tsx +19 -37
- package/src/components/macros/Numberbox.tsx +5 -32
- package/src/components/macros/PassageDisplay.tsx +37 -47
- package/src/components/macros/Print.tsx +1 -0
- package/src/components/macros/Radiobutton.tsx +8 -38
- package/src/components/macros/Repeat.tsx +3 -3
- package/src/components/macros/Set.tsx +3 -29
- package/src/components/macros/Switch.tsx +36 -37
- package/src/components/macros/Textarea.tsx +2 -28
- package/src/components/macros/Textbox.tsx +2 -29
- package/src/components/macros/Type.tsx +3 -3
- package/src/components/macros/Unset.tsx +17 -40
- package/src/components/macros/Watch.tsx +23 -91
- package/src/components/macros/WidgetInvocation.tsx +9 -71
- package/src/components/macros/detached-body.tsx +2 -2
- package/src/components/macros/input-macro.ts +48 -0
- package/src/components/macros/locals-scope.tsx +60 -0
- package/src/components/macros/macro-args.ts +280 -0
- package/src/components/macros/option-utils.ts +7 -12
- package/src/define-macro.ts +47 -30
- package/src/hooks/use-render-options.ts +26 -0
- package/src/js-lexer.ts +80 -45
- package/src/markup/render.tsx +2 -4
- package/src/registry.ts +48 -0
- package/types/index.d.ts +69 -6
- package/types/tooling.d.ts +33 -1
package/dist/pkg/headless.js
CHANGED
|
@@ -946,6 +946,13 @@ var UNCLOSED = -1;
|
|
|
946
946
|
var MALFORMED = -2;
|
|
947
947
|
/** A frame result: the scan stops at index `s` inside it (`STOPPED - s`). */
|
|
948
948
|
var STOPPED = -3;
|
|
949
|
+
/**
|
|
950
|
+
* The result of a frame that closes at `end`. `bodyStarted`: the code inside
|
|
951
|
+
* started a function or class body. That body replaced the one to come, and
|
|
952
|
+
* is gone once the frame is closed, so after the frame no body is to come —
|
|
953
|
+
* a scan that skips the frame must know, as must the frames around it.
|
|
954
|
+
*/
|
|
955
|
+
var frameEnd = (end, bodyStarted) => end * 2 + +bodyStarted;
|
|
949
956
|
/** The closers whose search for their frame a frame result may depend on. */
|
|
950
957
|
var CLOSERS = [
|
|
951
958
|
")",
|
|
@@ -1178,14 +1185,30 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1178
1185
|
]] : void 0;
|
|
1179
1186
|
/**
|
|
1180
1187
|
* How many function or class bodies to come the scan has started, and with
|
|
1181
|
-
* `parens`, that count when each open frame opened. Code in a
|
|
1182
|
-
*
|
|
1183
|
-
* follow once the frame is closed: none is to come then.
|
|
1188
|
+
* `braces` or `parens`, that count when each open frame opened. Code in a
|
|
1189
|
+
* frame that starts one replaces the one to come, and the new one can't
|
|
1190
|
+
* follow once the frame is closed: none is to come then. A scan skipping
|
|
1191
|
+
* the frame learns this from its result (`frameEnd`, `resume`), and counts
|
|
1192
|
+
* the body as started too, for the frames around it to record.
|
|
1184
1193
|
*/
|
|
1185
1194
|
let bodyStarts = 0;
|
|
1186
|
-
const startsAt = parens ? [0] : void 0;
|
|
1195
|
+
const startsAt = braces || parens ? [0] : void 0;
|
|
1187
1196
|
/** A `(…)` or `[…]` frame result found in `parens`, to skip to. */
|
|
1188
1197
|
let parenTo;
|
|
1198
|
+
/** The result of the frame at index `k`, closing at `i`. */
|
|
1199
|
+
const closedAt = (k) => frameEnd(i, startsAt !== void 0 && bodyStarts !== startsAt[k]);
|
|
1200
|
+
/**
|
|
1201
|
+
* Go on after a frame an earlier scan lexed, whose result (`frameEnd`) is
|
|
1202
|
+
* `result`: returns the index it closes at. A body started in it is gone
|
|
1203
|
+
* after it, with the one it replaced.
|
|
1204
|
+
*/
|
|
1205
|
+
function resume(result) {
|
|
1206
|
+
if (result % 2 === 1) {
|
|
1207
|
+
pendingBody = void 0;
|
|
1208
|
+
bodyStarts++;
|
|
1209
|
+
}
|
|
1210
|
+
return Math.floor(result / 2);
|
|
1211
|
+
}
|
|
1189
1212
|
/** The closer `c` looked for its frame and found index `k`. */
|
|
1190
1213
|
function lookedFor(c, k) {
|
|
1191
1214
|
if (!lowest) return;
|
|
@@ -1333,8 +1356,8 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1333
1356
|
if (lowest) {
|
|
1334
1357
|
for (let j = frames.length - 1; j >= k; j--) foldLowest(j);
|
|
1335
1358
|
lowest.length = k;
|
|
1336
|
-
startsAt.length = k;
|
|
1337
1359
|
}
|
|
1360
|
+
if (startsAt) startsAt.length = k;
|
|
1338
1361
|
frames.length = k;
|
|
1339
1362
|
if (stackIds) stackIds.length = k;
|
|
1340
1363
|
for (const list of Object.values(closers)) while (list.length && list[list.length - 1] >= k) list.pop();
|
|
@@ -1411,6 +1434,18 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1411
1434
|
stmtNext = f.kind === "block";
|
|
1412
1435
|
keyNext = f.kind === "object" || f.kind === "class";
|
|
1413
1436
|
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Go past frame `f` if an earlier scan lexed it: to just after it, or to
|
|
1439
|
+
* the end of the source when it never closes. Whether it did, or
|
|
1440
|
+
* MALFORMED when that scan found the code malformed.
|
|
1441
|
+
*/
|
|
1442
|
+
function skipLexed(f) {
|
|
1443
|
+
const end = known(f);
|
|
1444
|
+
if (end === void 0) return false;
|
|
1445
|
+
if (end === MALFORMED) return MALFORMED;
|
|
1446
|
+
i = end === UNCLOSED ? src.length : resume(end) + 1;
|
|
1447
|
+
return true;
|
|
1448
|
+
}
|
|
1414
1449
|
function openBrace() {
|
|
1415
1450
|
let f;
|
|
1416
1451
|
if (pendingBody?.depth === frames.length) {
|
|
@@ -1454,10 +1489,10 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1454
1489
|
return;
|
|
1455
1490
|
}
|
|
1456
1491
|
const closed = frames[k];
|
|
1492
|
+
const result = closedAt(k);
|
|
1457
1493
|
if (lowest && c !== "}") {
|
|
1458
1494
|
for (let j = frames.length - 1; j > k; j--) foldLowest(j);
|
|
1459
|
-
|
|
1460
|
-
recordParen(k, i * 2 + +bodyStarted);
|
|
1495
|
+
recordParen(k, result);
|
|
1461
1496
|
}
|
|
1462
1497
|
truncate(k);
|
|
1463
1498
|
if (c === "]" && !ctx.lookahead) ctx.brackets.set(closed.open, i);
|
|
@@ -1466,7 +1501,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1466
1501
|
stmtNext = closed.header;
|
|
1467
1502
|
} else if (c === "]") endPunct(c, false);
|
|
1468
1503
|
else {
|
|
1469
|
-
record(closed,
|
|
1504
|
+
record(closed, result);
|
|
1470
1505
|
afterBrace(closed);
|
|
1471
1506
|
}
|
|
1472
1507
|
}
|
|
@@ -1568,23 +1603,16 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1568
1603
|
} else if (ch === "`") {
|
|
1569
1604
|
literal(ch, i);
|
|
1570
1605
|
if (frames.length === 1) return i + 1;
|
|
1571
|
-
record(top(),
|
|
1606
|
+
record(top(), closedAt(frames.length - 1));
|
|
1572
1607
|
i++;
|
|
1573
1608
|
truncate(frames.length - 1);
|
|
1574
1609
|
endOperand();
|
|
1575
1610
|
} else if (ch === "$" && src.charAt(i + 1) === "{") {
|
|
1576
1611
|
const f = frame("expr", "}", i);
|
|
1577
1612
|
f.interpolation = true;
|
|
1578
|
-
const
|
|
1579
|
-
if (
|
|
1580
|
-
if (
|
|
1581
|
-
i = src.length;
|
|
1582
|
-
break;
|
|
1583
|
-
}
|
|
1584
|
-
if (end !== void 0) {
|
|
1585
|
-
i = end + 1;
|
|
1586
|
-
continue;
|
|
1587
|
-
}
|
|
1613
|
+
const skipped = skipLexed(f);
|
|
1614
|
+
if (skipped === MALFORMED) return malformed();
|
|
1615
|
+
if (skipped) continue;
|
|
1588
1616
|
literal("${", i);
|
|
1589
1617
|
i += 2;
|
|
1590
1618
|
nesting++;
|
|
@@ -1611,14 +1639,9 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1611
1639
|
if (ch === "`") {
|
|
1612
1640
|
endWord();
|
|
1613
1641
|
const f = frame("template", "`", i);
|
|
1614
|
-
const
|
|
1615
|
-
if (
|
|
1616
|
-
if (
|
|
1617
|
-
i = src.length;
|
|
1618
|
-
break;
|
|
1619
|
-
}
|
|
1620
|
-
if (end !== void 0) {
|
|
1621
|
-
i = end + 1;
|
|
1642
|
+
const skipped = skipLexed(f);
|
|
1643
|
+
if (skipped === MALFORMED) return malformed();
|
|
1644
|
+
if (skipped) {
|
|
1622
1645
|
endOperand();
|
|
1623
1646
|
continue;
|
|
1624
1647
|
}
|
|
@@ -1637,7 +1660,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1637
1660
|
lookedFor("}", k);
|
|
1638
1661
|
if (frames[k].interpolation) {
|
|
1639
1662
|
endWord();
|
|
1640
|
-
record(frames[k],
|
|
1663
|
+
record(frames[k], closedAt(k));
|
|
1641
1664
|
truncate(k);
|
|
1642
1665
|
nesting--;
|
|
1643
1666
|
literal(ch, i);
|
|
@@ -1719,7 +1742,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1719
1742
|
continue;
|
|
1720
1743
|
}
|
|
1721
1744
|
}
|
|
1722
|
-
if (ch === outer.closer && frames.length === 1) break;
|
|
1745
|
+
if (ch === outer.closer && (frames.length === 1 || ch === "]" && innermost("]") === 0 && innermost("}") === 0)) break;
|
|
1723
1746
|
trackCode(ch);
|
|
1724
1747
|
if (skipTo) {
|
|
1725
1748
|
const { frame: skipped, end } = skipTo;
|
|
@@ -1729,7 +1752,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1729
1752
|
i = src.length;
|
|
1730
1753
|
break;
|
|
1731
1754
|
}
|
|
1732
|
-
i = end;
|
|
1755
|
+
i = resume(end);
|
|
1733
1756
|
afterBrace(skipped);
|
|
1734
1757
|
i++;
|
|
1735
1758
|
continue;
|
|
@@ -1747,8 +1770,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
|
|
|
1747
1770
|
strict.stopped = true;
|
|
1748
1771
|
return STOPPED - result;
|
|
1749
1772
|
}
|
|
1750
|
-
i =
|
|
1751
|
-
if (result % 2 === 1) pendingBody = void 0;
|
|
1773
|
+
i = resume(result);
|
|
1752
1774
|
if (skipped.closer === ")") {
|
|
1753
1775
|
endPunct(")", skipped.header);
|
|
1754
1776
|
stmtNext = skipped.header;
|
|
@@ -3679,11 +3701,29 @@ function ExprDisplay({ expression, className, id }) {
|
|
|
3679
3701
|
return /* @__PURE__ */ jsx(Fragment$1, { children: display });
|
|
3680
3702
|
}
|
|
3681
3703
|
//#endregion
|
|
3682
|
-
//#region src/
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3704
|
+
//#region src/hooks/use-render-options.ts
|
|
3705
|
+
/**
|
|
3706
|
+
* The renderNodes() options of the enclosing content: nobr, inline and raw
|
|
3707
|
+
* rendering and the locals in scope. A macro or widget body rendered with
|
|
3708
|
+
* them renders as the content around it does.
|
|
3709
|
+
*/
|
|
3710
|
+
function useRenderOptions() {
|
|
3711
|
+
return {
|
|
3712
|
+
nobr: useContext(NobrContext),
|
|
3713
|
+
inline: useContext(InlineContext),
|
|
3714
|
+
raw: useContext(RawTextContext),
|
|
3715
|
+
locals: useContext(LocalsValuesContext)
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
//#endregion
|
|
3719
|
+
//#region src/components/macros/locals-scope.tsx
|
|
3720
|
+
/**
|
|
3721
|
+
* Render `body` in a new locals scope: the locals of `parentValues`, with
|
|
3722
|
+
* `ownKeys` (an iteration's loop variables, a widget's parameters) on top.
|
|
3723
|
+
* Locals the body assigns are kept in this scope.
|
|
3724
|
+
*/
|
|
3725
|
+
function LocalsScope({ body, parentValues, ownKeys }) {
|
|
3726
|
+
const options = useRenderOptions();
|
|
3687
3727
|
const [localMutations, setLocalMutations] = useState({});
|
|
3688
3728
|
const localState = useMemo(() => createNamespace(parentValues, ownKeys, localMutations), [
|
|
3689
3729
|
parentValues,
|
|
@@ -3710,29 +3750,22 @@ function WidgetBody({ body, parentValues, ownKeys }) {
|
|
|
3710
3750
|
children: /* @__PURE__ */ jsx(LocalsValuesContext.Provider, {
|
|
3711
3751
|
value: localState,
|
|
3712
3752
|
children: renderNodes(body, {
|
|
3713
|
-
|
|
3714
|
-
inline,
|
|
3715
|
-
raw,
|
|
3753
|
+
...options,
|
|
3716
3754
|
locals: localState
|
|
3717
3755
|
})
|
|
3718
3756
|
})
|
|
3719
3757
|
});
|
|
3720
3758
|
}
|
|
3759
|
+
//#endregion
|
|
3760
|
+
//#region src/components/macros/WidgetInvocation.tsx
|
|
3721
3761
|
function WidgetInvocation({ body, params, rawArgs, invocationChildren }) {
|
|
3722
|
-
const
|
|
3723
|
-
const
|
|
3724
|
-
const inline = useContext(InlineContext);
|
|
3725
|
-
const raw = useContext(RawTextContext);
|
|
3762
|
+
const renderOptions = useRenderOptions();
|
|
3763
|
+
const parentValues = renderOptions.locals;
|
|
3726
3764
|
const [mergedVars, mergedTemps, mergedLocals, mergedTrans] = useMergedLocals();
|
|
3727
3765
|
const childrenValue = invocationChildren?.length ? invocationChildren : null;
|
|
3728
3766
|
if (params.length === 0) return /* @__PURE__ */ jsx(WidgetChildrenContext.Provider, {
|
|
3729
3767
|
value: childrenValue,
|
|
3730
|
-
children: renderNodes(body,
|
|
3731
|
-
nobr,
|
|
3732
|
-
inline,
|
|
3733
|
-
raw,
|
|
3734
|
-
locals: parentValues
|
|
3735
|
-
})
|
|
3768
|
+
children: renderNodes(body, renderOptions)
|
|
3736
3769
|
});
|
|
3737
3770
|
const argExprs = rawArgs ? splitArgs(rawArgs) : [];
|
|
3738
3771
|
const values = [];
|
|
@@ -3753,7 +3786,7 @@ function WidgetInvocation({ body, params, rawArgs, invocationChildren }) {
|
|
|
3753
3786
|
}, values);
|
|
3754
3787
|
return /* @__PURE__ */ jsx(WidgetChildrenContext.Provider, {
|
|
3755
3788
|
value: childrenValue,
|
|
3756
|
-
children: /* @__PURE__ */ jsx(
|
|
3789
|
+
children: /* @__PURE__ */ jsx(LocalsScope, {
|
|
3757
3790
|
body,
|
|
3758
3791
|
parentValues,
|
|
3759
3792
|
ownKeys
|
|
@@ -3958,6 +3991,32 @@ function MacroError({ macro, error }) {
|
|
|
3958
3991
|
children: `{${macro} error${currentSourceLocation()}: ${errorMessage(error)}}`
|
|
3959
3992
|
});
|
|
3960
3993
|
}
|
|
3994
|
+
/** Log an error in the macro `{label}` (its name, or name and arguments). */
|
|
3995
|
+
function logMacroError(label, error) {
|
|
3996
|
+
console.error(`spindle: Error in {${label}}${currentSourceLocation()}:`, error);
|
|
3997
|
+
}
|
|
3998
|
+
/**
|
|
3999
|
+
* Run `effect` once, during the macro's first render. If it throws, the
|
|
4000
|
+
* error is logged under `{macro rawArgs}` and shown in place of the macro
|
|
4001
|
+
* from then on; otherwise the macro renders nothing.
|
|
4002
|
+
*/
|
|
4003
|
+
function useRunOnce(macro, rawArgs, effect) {
|
|
4004
|
+
const failure = useRef();
|
|
4005
|
+
if (failure.current === void 0) {
|
|
4006
|
+
failure.current = null;
|
|
4007
|
+
try {
|
|
4008
|
+
effect();
|
|
4009
|
+
} catch (error) {
|
|
4010
|
+
failure.current = { error };
|
|
4011
|
+
logMacroError(`${macro} ${rawArgs}`, error);
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
if (!failure.current) return null;
|
|
4015
|
+
return /* @__PURE__ */ jsx(MacroError, {
|
|
4016
|
+
macro,
|
|
4017
|
+
error: failure.current.error
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
3961
4020
|
//#endregion
|
|
3962
4021
|
//#region src/markup/render.tsx
|
|
3963
4022
|
var defaultUpdater = {
|
|
@@ -4327,11 +4386,8 @@ function resolveAttributeValue(name, value, scope, errors) {
|
|
|
4327
4386
|
}
|
|
4328
4387
|
function HtmlNodeRenderer({ node }) {
|
|
4329
4388
|
const scope = useTextScope();
|
|
4330
|
-
const nobr =
|
|
4331
|
-
const locals = useContext(LocalsValuesContext);
|
|
4332
|
-
const inRaw = useContext(RawTextContext);
|
|
4389
|
+
const { nobr, locals, raw: inRaw, inline: parentInline } = useRenderOptions();
|
|
4333
4390
|
const inSvg = useContext(SvgContext);
|
|
4334
|
-
const parentInline = useContext(InlineContext);
|
|
4335
4391
|
const tag = node.tag.toLowerCase();
|
|
4336
4392
|
const isSvgRoot = tag === "svg";
|
|
4337
4393
|
const isRawRoot = !inRaw && (isSvgRoot || PREFORMATTED_ELEMENTS.has(tag));
|
|
@@ -7494,18 +7550,236 @@ function collectText(nodes) {
|
|
|
7494
7550
|
return nodes.map((n) => n.type === "text" ? n.value : "").join("");
|
|
7495
7551
|
}
|
|
7496
7552
|
//#endregion
|
|
7553
|
+
//#region src/utils/parse-delay.ts
|
|
7554
|
+
/**
|
|
7555
|
+
* Parse a delay string into milliseconds.
|
|
7556
|
+
* Supports: "2s" → 2000, "500ms" → 500, bare number → ms
|
|
7557
|
+
*/
|
|
7558
|
+
function parseDelay(str) {
|
|
7559
|
+
const trimmed = str.trim();
|
|
7560
|
+
if (trimmed.endsWith("ms")) return parseFloat(trimmed.slice(0, -2));
|
|
7561
|
+
if (trimmed.endsWith("s")) return parseFloat(trimmed.slice(0, -1)) * 1e3;
|
|
7562
|
+
return parseFloat(trimmed);
|
|
7563
|
+
}
|
|
7564
|
+
//#endregion
|
|
7565
|
+
//#region src/components/macros/macro-args.ts
|
|
7566
|
+
/**
|
|
7567
|
+
* The macro argument layer: reads a macro's raw argument text into the
|
|
7568
|
+
* parameters its definition declares (see ParameterDef), so every macro
|
|
7569
|
+
* splits and unquotes its arguments the same way. defineMacro() passes the
|
|
7570
|
+
* result to the macro as `ctx.args`.
|
|
7571
|
+
*
|
|
7572
|
+
* Arguments are read in groups, split at `separator` parameters. A group's
|
|
7573
|
+
* `flag` parameters are taken off its start or end first. Its last
|
|
7574
|
+
* `expression` or `text` parameter (without one, its last parameter) reads
|
|
7575
|
+
* the text the others leave; each parameter before it reads one term from
|
|
7576
|
+
* the start, and each after it one term from the end. Terms are separated by
|
|
7577
|
+
* whitespace outside literals and brackets (see arg-utils.ts), and a term
|
|
7578
|
+
* that follows an operator belongs to the expression before it. A parameter
|
|
7579
|
+
* with nothing to read is unset (a flag or separator is false, options are
|
|
7580
|
+
* empty).
|
|
7581
|
+
*/
|
|
7582
|
+
/** The terms of `src`: the runs of text between its depth-0 whitespace. */
|
|
7583
|
+
function terms(src) {
|
|
7584
|
+
const spans = [];
|
|
7585
|
+
let start = 0;
|
|
7586
|
+
for (const i of [...topLevelIndices(src, isWhitespace), src.length]) {
|
|
7587
|
+
if (i > start) spans.push([start, i]);
|
|
7588
|
+
start = i + 1;
|
|
7589
|
+
}
|
|
7590
|
+
return spans;
|
|
7591
|
+
}
|
|
7592
|
+
var slice = (src, [start, end]) => src.slice(start, end);
|
|
7593
|
+
/**
|
|
7594
|
+
* The value of a `text` argument: one quoted string, or the text with any
|
|
7595
|
+
* loose quotes stripped (`"Red` → `Red`).
|
|
7596
|
+
*/
|
|
7597
|
+
function readText(src) {
|
|
7598
|
+
return readWholeQuoted(src) ?? stripLooseQuotes(src);
|
|
7599
|
+
}
|
|
7600
|
+
function readValue(param, src) {
|
|
7601
|
+
switch (param.type) {
|
|
7602
|
+
case "string": return readWholeQuoted(src) ?? void 0;
|
|
7603
|
+
case "text": return readText(src);
|
|
7604
|
+
case "names": return src.split(",").map((name) => name.trim());
|
|
7605
|
+
case "delay": return parseDelay(src);
|
|
7606
|
+
case "number": return Number(src);
|
|
7607
|
+
case "options": return readOptions(param.parameters ?? [], src);
|
|
7608
|
+
default: return src;
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7611
|
+
function unset(param) {
|
|
7612
|
+
if (param.type === "flag" || param.type === "separator") return false;
|
|
7613
|
+
return param.type === "options" ? {} : void 0;
|
|
7614
|
+
}
|
|
7615
|
+
var WORD_RE = /\w+/y;
|
|
7616
|
+
var DIGITS_RE = /\d+/y;
|
|
7617
|
+
/** Match the sticky regex `re` at `pos`, returning the matched text. */
|
|
7618
|
+
function matchAt(re, src, pos) {
|
|
7619
|
+
re.lastIndex = pos;
|
|
7620
|
+
return re.exec(src)?.[0] ?? null;
|
|
7621
|
+
}
|
|
7622
|
+
/**
|
|
7623
|
+
* Read keyword options (`goto "X" priority 5 once`). A keyword takes the
|
|
7624
|
+
* quoted string or digit run after it as its value; keywords that aren't
|
|
7625
|
+
* declared, and their values, are skipped.
|
|
7626
|
+
*/
|
|
7627
|
+
function readOptions(params, src) {
|
|
7628
|
+
const options = {};
|
|
7629
|
+
let i = 0;
|
|
7630
|
+
while (i < src.length) {
|
|
7631
|
+
const key = matchAt(WORD_RE, src, i);
|
|
7632
|
+
if (!key) {
|
|
7633
|
+
i = readQuoted(src, i)?.end ?? i + 1;
|
|
7634
|
+
continue;
|
|
7635
|
+
}
|
|
7636
|
+
i += key.length;
|
|
7637
|
+
let val;
|
|
7638
|
+
let j = i;
|
|
7639
|
+
while (j < src.length && isWhitespace(src[j])) j++;
|
|
7640
|
+
if (j > i) {
|
|
7641
|
+
const quoted = readQuoted(src, j);
|
|
7642
|
+
const digits = quoted ? null : matchAt(DIGITS_RE, src, j);
|
|
7643
|
+
if (quoted) {
|
|
7644
|
+
val = quoted.value;
|
|
7645
|
+
i = quoted.end;
|
|
7646
|
+
} else if (digits) {
|
|
7647
|
+
val = digits;
|
|
7648
|
+
i = j + digits.length;
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7651
|
+
const param = params.find((p) => p.name === key);
|
|
7652
|
+
if (param?.type === "flag") options[key] = true;
|
|
7653
|
+
else if (param?.type === "number") options[key] = Number(val ?? 0);
|
|
7654
|
+
else if (param) options[key] = val;
|
|
7655
|
+
}
|
|
7656
|
+
return options;
|
|
7657
|
+
}
|
|
7658
|
+
/** `inline %name` reads a transient variable; `inline % 2` is modulo. */
|
|
7659
|
+
var LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/;
|
|
7660
|
+
/**
|
|
7661
|
+
* The rest of `src` without the flag `word` as its last or first term, or
|
|
7662
|
+
* `null` if it has no such flag. Next to a binary operator the word is an
|
|
7663
|
+
* operand (`"a" + inline`), not a flag; the closing `/` of a regex literal
|
|
7664
|
+
* is not one.
|
|
7665
|
+
*/
|
|
7666
|
+
function takeFlag(src, word) {
|
|
7667
|
+
const spans = terms(src);
|
|
7668
|
+
if (spans.length < 2) return null;
|
|
7669
|
+
if (slice(src, spans[spans.length - 1]) === word) {
|
|
7670
|
+
const rest = src.slice(0, spans[spans.length - 2][1]);
|
|
7671
|
+
if (!endsWithOperator(rest)) return rest;
|
|
7672
|
+
}
|
|
7673
|
+
if (slice(src, spans[0]) === word) {
|
|
7674
|
+
const rest = src.slice(spans[1][0]);
|
|
7675
|
+
if (!LEADING_OPERATOR_RE.test(rest)) return rest;
|
|
7676
|
+
}
|
|
7677
|
+
return null;
|
|
7678
|
+
}
|
|
7679
|
+
/**
|
|
7680
|
+
* Where the separator `word` splits `src`, as the end of the text before it
|
|
7681
|
+
* and the start of the text after it, or `null` if it doesn't. A word is a
|
|
7682
|
+
* term with terms on both sides; `=` is an assignment at depth 0, not part
|
|
7683
|
+
* of `==` or `!=`.
|
|
7684
|
+
*/
|
|
7685
|
+
function findSeparator(src, word) {
|
|
7686
|
+
if (word === "=") {
|
|
7687
|
+
const indices = topLevelIndices(src, (ch) => ch === "=");
|
|
7688
|
+
for (let k = 0; k < indices.length; k++) {
|
|
7689
|
+
const i = indices[k];
|
|
7690
|
+
if (src[i + 1] === "=") {
|
|
7691
|
+
k++;
|
|
7692
|
+
continue;
|
|
7693
|
+
}
|
|
7694
|
+
if (src[i - 1] !== "!") return [i, i + 1];
|
|
7695
|
+
}
|
|
7696
|
+
return null;
|
|
7697
|
+
}
|
|
7698
|
+
const spans = terms(src);
|
|
7699
|
+
for (let k = 1; k < spans.length - 1; k++) if (slice(src, spans[k]) === word) return [spans[k - 1][1], spans[k + 1][0]];
|
|
7700
|
+
return null;
|
|
7701
|
+
}
|
|
7702
|
+
var takesRest = (param) => param.type === void 0 || param.type === "expression" || param.type === "text";
|
|
7703
|
+
/** Read the parameters of one group from `src` (`null`: no text) into `args`. */
|
|
7704
|
+
function readGroup(params, src, args) {
|
|
7705
|
+
let text = src?.trim() ?? "";
|
|
7706
|
+
for (const flag of params.filter((p) => p.type === "flag")) {
|
|
7707
|
+
const rest = src === null ? null : takeFlag(text, flag.name);
|
|
7708
|
+
args[flag.name] = rest !== null;
|
|
7709
|
+
text = rest ?? text;
|
|
7710
|
+
}
|
|
7711
|
+
const positional = params.filter((p) => p.type !== "flag");
|
|
7712
|
+
if (positional.length === 0) return;
|
|
7713
|
+
const lastText = positional.findIndex((p, i) => takesRest(p) && !positional.slice(i + 1).some(takesRest));
|
|
7714
|
+
const restIndex = lastText < 0 ? positional.length - 1 : lastText;
|
|
7715
|
+
const read = (param, value) => value === "" ? unset(param) : readValue(param, value);
|
|
7716
|
+
if (positional.length === 1) {
|
|
7717
|
+
args[positional[0].name] = read(positional[0], text);
|
|
7718
|
+
return;
|
|
7719
|
+
}
|
|
7720
|
+
const spans = terms(text);
|
|
7721
|
+
let from = 0;
|
|
7722
|
+
let to = spans.length;
|
|
7723
|
+
for (const param of positional.slice(0, restIndex)) args[param.name] = from < to ? read(param, slice(text, spans[from++])) : unset(param);
|
|
7724
|
+
for (const param of positional.slice(restIndex + 1).reverse()) {
|
|
7725
|
+
const value = from < to && !endsWithOperator(text.slice(0, spans[to - 1][0])) ? read(param, slice(text, spans[to - 1])) : void 0;
|
|
7726
|
+
args[param.name] = value ?? unset(param);
|
|
7727
|
+
if (value !== void 0) to--;
|
|
7728
|
+
}
|
|
7729
|
+
const rest = positional[restIndex];
|
|
7730
|
+
args[rest.name] = from < to ? read(rest, text.slice(spans[from][0], spans[to - 1][1])) : unset(rest);
|
|
7731
|
+
}
|
|
7732
|
+
/** Read `rawArgs` into the declared `parameters` (see above). */
|
|
7733
|
+
function parseMacroArgs(rawArgs, parameters) {
|
|
7734
|
+
const args = {};
|
|
7735
|
+
let rest = rawArgs.trim();
|
|
7736
|
+
let groupStart = 0;
|
|
7737
|
+
parameters.forEach((separator, i) => {
|
|
7738
|
+
if (separator.type !== "separator") return;
|
|
7739
|
+
const at = rest === null ? null : findSeparator(rest, separator.name);
|
|
7740
|
+
args[separator.name] = at !== null;
|
|
7741
|
+
const before = at ? rest.slice(0, at[0]) : rest;
|
|
7742
|
+
readGroup(parameters.slice(groupStart, i), before, args);
|
|
7743
|
+
rest = at ? rest.slice(at[1]) : null;
|
|
7744
|
+
groupStart = i + 1;
|
|
7745
|
+
});
|
|
7746
|
+
readGroup(parameters.slice(groupStart), rest, args);
|
|
7747
|
+
return args;
|
|
7748
|
+
}
|
|
7749
|
+
/**
|
|
7750
|
+
* The passage a passage-name argument names: the value of its expression
|
|
7751
|
+
* or, when that can't be evaluated (`{goto Bob's room}`), its text.
|
|
7752
|
+
*/
|
|
7753
|
+
function evaluatePassageName(expr, evaluate) {
|
|
7754
|
+
try {
|
|
7755
|
+
return String(evaluate(expr ?? ""));
|
|
7756
|
+
} catch {
|
|
7757
|
+
return readText(expr ?? "");
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
7760
|
+
/**
|
|
7761
|
+
* The variable a `storeVar` macro binds: its first term, as written. Custom
|
|
7762
|
+
* macros may bind one without declaring parameters.
|
|
7763
|
+
*/
|
|
7764
|
+
function readBoundVariable(rawArgs) {
|
|
7765
|
+
const src = rawArgs.trim();
|
|
7766
|
+
const first = terms(src)[0];
|
|
7767
|
+
return first ? slice(src, first) : "";
|
|
7768
|
+
}
|
|
7769
|
+
//#endregion
|
|
7497
7770
|
//#region src/components/macros/option-utils.ts
|
|
7771
|
+
/** `$var "placeholder"`, the arguments of the input macros. */
|
|
7498
7772
|
function parseVarArgs(rawArgs) {
|
|
7499
|
-
const
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7773
|
+
const { variable = "", placeholder = "" } = parseMacroArgs(rawArgs, [{
|
|
7774
|
+
name: "variable",
|
|
7775
|
+
type: "variable"
|
|
7776
|
+
}, {
|
|
7777
|
+
name: "placeholder",
|
|
7778
|
+
type: "string"
|
|
7779
|
+
}]);
|
|
7506
7780
|
return {
|
|
7507
|
-
varName,
|
|
7508
|
-
placeholder
|
|
7781
|
+
varName: variable.replace(/["']/g, ""),
|
|
7782
|
+
placeholder
|
|
7509
7783
|
};
|
|
7510
7784
|
}
|
|
7511
7785
|
/**
|
|
@@ -7536,6 +7810,7 @@ var sharedHooks = {
|
|
|
7536
7810
|
useContext
|
|
7537
7811
|
};
|
|
7538
7812
|
function defineMacro(config, source = "builtin") {
|
|
7813
|
+
const parameters = config.parameters ?? [];
|
|
7539
7814
|
function Wrapper(props) {
|
|
7540
7815
|
let className = props.className;
|
|
7541
7816
|
let id = props.id;
|
|
@@ -7546,18 +7821,13 @@ function defineMacro(config, source = "builtin") {
|
|
|
7546
7821
|
id = resolve(id);
|
|
7547
7822
|
}
|
|
7548
7823
|
const { update, getValues } = useContext(LocalsUpdateContext);
|
|
7549
|
-
const
|
|
7550
|
-
const inline = useContext(InlineContext);
|
|
7551
|
-
const raw = useContext(RawTextContext);
|
|
7552
|
-
const localsValues = useContext(LocalsValuesContext);
|
|
7824
|
+
const renderOptions = useRenderOptions();
|
|
7553
7825
|
const renderNodes$1 = (nodes, options) => renderNodes(nodes, {
|
|
7554
|
-
|
|
7555
|
-
inline,
|
|
7556
|
-
raw,
|
|
7557
|
-
locals: localsValues,
|
|
7826
|
+
...renderOptions,
|
|
7558
7827
|
...options
|
|
7559
7828
|
});
|
|
7560
7829
|
const ctx = {
|
|
7830
|
+
args: parseMacroArgs(props.rawArgs, parameters),
|
|
7561
7831
|
collectText,
|
|
7562
7832
|
sourceLocation: currentSourceLocation,
|
|
7563
7833
|
parseVarArgs,
|
|
@@ -7588,7 +7858,7 @@ function defineMacro(config, source = "builtin") {
|
|
|
7588
7858
|
ctx.evaluate = (expr) => evaluate(expr, merged[0], merged[1], merged[2], merged[3]);
|
|
7589
7859
|
}
|
|
7590
7860
|
if (config.storeVar) {
|
|
7591
|
-
const firstToken = props.rawArgs
|
|
7861
|
+
const firstToken = readBoundVariable(props.rawArgs).replace(/["']/g, "");
|
|
7592
7862
|
if (firstToken.startsWith("%")) return h("span", { class: "error" }, `{${config.name}}: transient variables (%${firstToken.slice(1)}) cannot be bound to input macros`);
|
|
7593
7863
|
const varExpr = firstToken.replace(/["']/g, "").replace(/^\$/, "");
|
|
7594
7864
|
const segments = varExpr.split(".");
|
|
@@ -7605,7 +7875,11 @@ function defineMacro(config, source = "builtin") {
|
|
|
7605
7875
|
return config.render(props, ctx);
|
|
7606
7876
|
}
|
|
7607
7877
|
registerMacro(config.name, Wrapper);
|
|
7608
|
-
|
|
7878
|
+
const text = config.text;
|
|
7879
|
+
registerMacroText(config.name, text && ((props, ctx) => text(props, {
|
|
7880
|
+
...ctx,
|
|
7881
|
+
args: parseMacroArgs(props.rawArgs, parameters)
|
|
7882
|
+
})));
|
|
7609
7883
|
const isBlock = config.block === true || config.block !== false && (config.subMacros?.length ?? 0) > 0;
|
|
7610
7884
|
registerMacroMetadata(config.name, {
|
|
7611
7885
|
name: config.name,
|
|
@@ -7615,7 +7889,7 @@ function defineMacro(config, source = "builtin") {
|
|
|
7615
7889
|
interpolate: config.interpolate,
|
|
7616
7890
|
merged: config.merged,
|
|
7617
7891
|
description: config.description,
|
|
7618
|
-
parameters: config.parameters,
|
|
7892
|
+
parameters: config.parameters && [...config.parameters],
|
|
7619
7893
|
source
|
|
7620
7894
|
});
|
|
7621
7895
|
if (config.subMacros) for (const sub of config.subMacros) registerSubMacro(sub);
|
|
@@ -8288,29 +8562,24 @@ defineMacro({
|
|
|
8288
8562
|
//#region src/components/macros/Set.tsx
|
|
8289
8563
|
defineMacro({
|
|
8290
8564
|
name: "set",
|
|
8565
|
+
parameters: [{
|
|
8566
|
+
name: "code",
|
|
8567
|
+
type: "expression",
|
|
8568
|
+
required: true
|
|
8569
|
+
}],
|
|
8291
8570
|
render({ rawArgs }, ctx) {
|
|
8292
|
-
|
|
8293
|
-
const failure = ctx.hooks.useRef(null);
|
|
8294
|
-
if (!ran.current) {
|
|
8295
|
-
ran.current = true;
|
|
8296
|
-
try {
|
|
8297
|
-
ctx.mutate(rawArgs);
|
|
8298
|
-
} catch (err) {
|
|
8299
|
-
failure.current = { error: err };
|
|
8300
|
-
console.error(`spindle: Error in {set ${rawArgs}}${ctx.sourceLocation()}:`, err);
|
|
8301
|
-
}
|
|
8302
|
-
}
|
|
8303
|
-
if (failure.current) return /* @__PURE__ */ jsx(MacroError, {
|
|
8304
|
-
macro: "set",
|
|
8305
|
-
error: failure.current.error
|
|
8306
|
-
});
|
|
8307
|
-
return null;
|
|
8571
|
+
return useRunOnce("set", rawArgs, () => ctx.mutate(rawArgs));
|
|
8308
8572
|
}
|
|
8309
8573
|
});
|
|
8310
8574
|
//#endregion
|
|
8311
8575
|
//#region src/components/macros/Print.tsx
|
|
8312
8576
|
defineMacro({
|
|
8313
8577
|
name: "print",
|
|
8578
|
+
parameters: [{
|
|
8579
|
+
name: "expression",
|
|
8580
|
+
type: "expression",
|
|
8581
|
+
required: true
|
|
8582
|
+
}],
|
|
8314
8583
|
interpolate: true,
|
|
8315
8584
|
merged: true,
|
|
8316
8585
|
render({ rawArgs }, ctx) {
|
|
@@ -8338,30 +8607,16 @@ defineMacro({
|
|
|
8338
8607
|
* applied, even `undefined`; a first evaluation that throws leaves it unset.
|
|
8339
8608
|
*/
|
|
8340
8609
|
var UNSET = Symbol("unset");
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
continue;
|
|
8352
|
-
}
|
|
8353
|
-
if (i > 0 && trimmed[i - 1] === "!") continue;
|
|
8354
|
-
const target = trimmed.slice(0, i).trim();
|
|
8355
|
-
const expr = trimmed.slice(i + 1).trim();
|
|
8356
|
-
if (!target.match(/^[$_@]\w+$/)) throw new Error(`{computed}: target must be $name, _name, or @name, got "${target}"`);
|
|
8357
|
-
checkVariableName(target.slice(1), target);
|
|
8358
|
-
return {
|
|
8359
|
-
target,
|
|
8360
|
-
expr
|
|
8361
|
-
};
|
|
8362
|
-
}
|
|
8363
|
-
}
|
|
8364
|
-
throw new Error(`{computed}: expected "target = expression", got "${rawArgs}"`);
|
|
8610
|
+
/** Check the arguments of "target = expression". */
|
|
8611
|
+
function assignmentArgs(rawArgs, args) {
|
|
8612
|
+
if (!args["="]) throw new Error(`{computed}: expected "target = expression", got "${rawArgs}"`);
|
|
8613
|
+
const { target = "", expression: expr = "" } = args;
|
|
8614
|
+
if (!target.match(/^[$_@]\w+$/)) throw new Error(`{computed}: target must be $name, _name, or @name, got "${target}"`);
|
|
8615
|
+
checkVariableName(target.slice(1), target);
|
|
8616
|
+
return {
|
|
8617
|
+
target,
|
|
8618
|
+
expr
|
|
8619
|
+
};
|
|
8365
8620
|
}
|
|
8366
8621
|
/**
|
|
8367
8622
|
* Evaluate `expr` against the current values — the store's state and the
|
|
@@ -8378,7 +8633,7 @@ function computeAndApply(expr, name, isTemp, isLocal, getLocals, rawArgs, prevRe
|
|
|
8378
8633
|
const { variables, temporary, transient } = readState();
|
|
8379
8634
|
newValue = evaluate(expr, variables, temporary, getLocals(), transient);
|
|
8380
8635
|
} catch (err) {
|
|
8381
|
-
|
|
8636
|
+
logMacroError(`computed ${rawArgs}`, err);
|
|
8382
8637
|
return;
|
|
8383
8638
|
}
|
|
8384
8639
|
if (!deepEqual(prevRef.current, newValue)) {
|
|
@@ -8386,7 +8641,7 @@ function computeAndApply(expr, name, isTemp, isLocal, getLocals, rawArgs, prevRe
|
|
|
8386
8641
|
if (isLocal) try {
|
|
8387
8642
|
localsUpdate(name, newValue);
|
|
8388
8643
|
} catch (err) {
|
|
8389
|
-
|
|
8644
|
+
logMacroError(`computed ${rawArgs}`, err);
|
|
8390
8645
|
}
|
|
8391
8646
|
else {
|
|
8392
8647
|
const state = useStoryStore.getState();
|
|
@@ -8398,12 +8653,28 @@ function computeAndApply(expr, name, isTemp, isLocal, getLocals, rawArgs, prevRe
|
|
|
8398
8653
|
defineMacro({
|
|
8399
8654
|
name: "computed",
|
|
8400
8655
|
merged: true,
|
|
8656
|
+
parameters: [
|
|
8657
|
+
{
|
|
8658
|
+
name: "target",
|
|
8659
|
+
type: "variable",
|
|
8660
|
+
required: true
|
|
8661
|
+
},
|
|
8662
|
+
{
|
|
8663
|
+
name: "=",
|
|
8664
|
+
type: "separator"
|
|
8665
|
+
},
|
|
8666
|
+
{
|
|
8667
|
+
name: "expression",
|
|
8668
|
+
type: "expression",
|
|
8669
|
+
required: true
|
|
8670
|
+
}
|
|
8671
|
+
],
|
|
8401
8672
|
render({ rawArgs }, ctx) {
|
|
8402
8673
|
const [mergedVars, mergedTemps, mergedLocals, mergedTrans] = ctx.merged;
|
|
8403
8674
|
let target;
|
|
8404
8675
|
let expr;
|
|
8405
8676
|
try {
|
|
8406
|
-
({target, expr} =
|
|
8677
|
+
({target, expr} = assignmentArgs(rawArgs, ctx.args));
|
|
8407
8678
|
} catch (err) {
|
|
8408
8679
|
return /* @__PURE__ */ jsx(MacroError, {
|
|
8409
8680
|
macro: "computed",
|
|
@@ -8415,14 +8686,13 @@ defineMacro({
|
|
|
8415
8686
|
const name = target.slice(1);
|
|
8416
8687
|
const localsUpdate = isLocal ? ctx.update : null;
|
|
8417
8688
|
const prevOutput = ctx.hooks.useRef(UNSET);
|
|
8689
|
+
const compute = () => computeAndApply(expr, name, isTemp, isLocal, ctx.getValues, rawArgs, prevOutput, localsUpdate);
|
|
8418
8690
|
const ran = ctx.hooks.useRef(false);
|
|
8419
8691
|
if (!ran.current) {
|
|
8420
8692
|
ran.current = true;
|
|
8421
|
-
|
|
8693
|
+
compute();
|
|
8422
8694
|
}
|
|
8423
|
-
ctx.hooks.useLayoutEffect(
|
|
8424
|
-
computeAndApply(expr, name, isTemp, isLocal, ctx.getValues, rawArgs, prevOutput, localsUpdate);
|
|
8425
|
-
}, [
|
|
8695
|
+
ctx.hooks.useLayoutEffect(compute, [
|
|
8426
8696
|
mergedVars,
|
|
8427
8697
|
mergedTemps,
|
|
8428
8698
|
mergedLocals,
|
|
@@ -8433,26 +8703,13 @@ defineMacro({
|
|
|
8433
8703
|
});
|
|
8434
8704
|
//#endregion
|
|
8435
8705
|
//#region src/components/macros/Meter.tsx
|
|
8436
|
-
/**
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
* trailing standalone `"…"` or `'…'` string is the label mode, with `\"`,
|
|
8440
|
-
* `\'` and `\\` escapes. A string after an operator (`$a ?? "5"`) is an
|
|
8441
|
-
* operand of the max expression, not the label.
|
|
8442
|
-
*/
|
|
8443
|
-
function parseMeterArgs(rawArgs) {
|
|
8444
|
-
const tokens = splitTopLevel(rawArgs.trim(), isWhitespace).filter(Boolean);
|
|
8445
|
-
let labelMode = "";
|
|
8446
|
-
const label = tokens.length >= 2 && !endsWithOperator(tokens.slice(0, -1).join(" ")) ? readWholeQuoted(tokens[tokens.length - 1]) : null;
|
|
8447
|
-
if (label !== null) {
|
|
8448
|
-
labelMode = label;
|
|
8449
|
-
tokens.pop();
|
|
8450
|
-
}
|
|
8451
|
-
if (tokens.length < 2) throw new Error("meter requires two arguments: {meter currentExpr maxExpr}");
|
|
8706
|
+
/** Check the arguments of `{meter currentExpr maxExpr ["label"]}`. */
|
|
8707
|
+
function meterArgs(args) {
|
|
8708
|
+
if (args.current === void 0 || args.max === void 0) throw new Error("meter requires two arguments: {meter currentExpr maxExpr}");
|
|
8452
8709
|
return {
|
|
8453
|
-
currentExpr:
|
|
8454
|
-
maxExpr:
|
|
8455
|
-
labelMode
|
|
8710
|
+
currentExpr: args.current,
|
|
8711
|
+
maxExpr: args.max,
|
|
8712
|
+
labelMode: args.label ?? ""
|
|
8456
8713
|
};
|
|
8457
8714
|
}
|
|
8458
8715
|
function formatLabel(current, max, labelMode) {
|
|
@@ -8465,9 +8722,25 @@ defineMacro({
|
|
|
8465
8722
|
name: "meter",
|
|
8466
8723
|
interpolate: true,
|
|
8467
8724
|
merged: true,
|
|
8468
|
-
|
|
8725
|
+
parameters: [
|
|
8726
|
+
{
|
|
8727
|
+
name: "current",
|
|
8728
|
+
type: "expression",
|
|
8729
|
+
required: true
|
|
8730
|
+
},
|
|
8731
|
+
{
|
|
8732
|
+
name: "max",
|
|
8733
|
+
type: "expression",
|
|
8734
|
+
required: true
|
|
8735
|
+
},
|
|
8736
|
+
{
|
|
8737
|
+
name: "label",
|
|
8738
|
+
type: "string"
|
|
8739
|
+
}
|
|
8740
|
+
],
|
|
8741
|
+
render(_props, ctx) {
|
|
8469
8742
|
try {
|
|
8470
|
-
const { currentExpr, maxExpr, labelMode } =
|
|
8743
|
+
const { currentExpr, maxExpr, labelMode } = meterArgs(ctx.args);
|
|
8471
8744
|
const current = Number(ctx.evaluate(currentExpr));
|
|
8472
8745
|
const max = Number(ctx.evaluate(maxExpr));
|
|
8473
8746
|
const pct = max === 0 ? 0 : Math.max(0, Math.min(100, current / max * 100));
|
|
@@ -8583,16 +8856,12 @@ function keyOf(val, ancestors) {
|
|
|
8583
8856
|
//#endregion
|
|
8584
8857
|
//#region src/components/macros/For.tsx
|
|
8585
8858
|
/**
|
|
8586
|
-
*
|
|
8859
|
+
* Check the arguments of "@item, @i of $list" or "@item of $list".
|
|
8587
8860
|
*/
|
|
8588
|
-
function
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
const
|
|
8592
|
-
const listExpr = rawArgs.slice(ofIdx + 4).trim();
|
|
8593
|
-
const vars = varsPart.split(",").map((v) => v.trim());
|
|
8594
|
-
const itemVar = vars[0];
|
|
8595
|
-
const indexVar = vars.length > 1 ? vars[1] : null;
|
|
8861
|
+
function loopArgs(rawArgs, args) {
|
|
8862
|
+
if (!args.of) throw new Error(`{for} requires "of" keyword: {for ${rawArgs}}`);
|
|
8863
|
+
const [itemVar = "", indexVar = null] = args.variables ?? [];
|
|
8864
|
+
const listExpr = args.list ?? "";
|
|
8596
8865
|
if (!itemVar.startsWith("@")) throw new Error(`{for} loop variable must use @ prefix: got "${itemVar}"`);
|
|
8597
8866
|
if (indexVar && !indexVar.startsWith("@")) throw new Error(`{for} index variable must use @ prefix: got "${indexVar}"`);
|
|
8598
8867
|
checkVariableName(itemVar.slice(1), itemVar);
|
|
@@ -8604,50 +8873,18 @@ function parseForArgs(rawArgs) {
|
|
|
8604
8873
|
};
|
|
8605
8874
|
}
|
|
8606
8875
|
function ForIteration({ parentValues, itemVar, itemValue, indexVar, indexValue, children }) {
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
[itemVar]: itemValue,
|
|
8610
|
-
...indexVar ? { [indexVar]: indexValue } : void 0
|
|
8611
|
-
}), [
|
|
8612
|
-
itemVar,
|
|
8613
|
-
itemValue,
|
|
8614
|
-
indexVar,
|
|
8615
|
-
indexValue
|
|
8616
|
-
]);
|
|
8617
|
-
const localState = useMemo(() => createNamespace(parentValues, ownKeys, localMutations), [
|
|
8876
|
+
return /* @__PURE__ */ jsx(LocalsScope, {
|
|
8877
|
+
body: children,
|
|
8618
8878
|
parentValues,
|
|
8619
|
-
ownKeys
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
setLocalMutations((prev) => ({
|
|
8629
|
-
...prev,
|
|
8630
|
-
[key]: value
|
|
8631
|
-
}));
|
|
8632
|
-
}, []);
|
|
8633
|
-
const updater = useMemo(() => ({
|
|
8634
|
-
update,
|
|
8635
|
-
getValues
|
|
8636
|
-
}), [update, getValues]);
|
|
8637
|
-
const nobr = useContext(NobrContext);
|
|
8638
|
-
const inline = useContext(InlineContext);
|
|
8639
|
-
const raw = useContext(RawTextContext);
|
|
8640
|
-
return /* @__PURE__ */ jsx(LocalsUpdateContext.Provider, {
|
|
8641
|
-
value: updater,
|
|
8642
|
-
children: /* @__PURE__ */ jsx(LocalsValuesContext.Provider, {
|
|
8643
|
-
value: localState,
|
|
8644
|
-
children: renderNodes(children, {
|
|
8645
|
-
nobr,
|
|
8646
|
-
inline,
|
|
8647
|
-
raw,
|
|
8648
|
-
locals: localState
|
|
8649
|
-
})
|
|
8650
|
-
})
|
|
8879
|
+
ownKeys: useMemo(() => ({
|
|
8880
|
+
[itemVar]: itemValue,
|
|
8881
|
+
...indexVar ? { [indexVar]: indexValue } : void 0
|
|
8882
|
+
}), [
|
|
8883
|
+
itemVar,
|
|
8884
|
+
itemValue,
|
|
8885
|
+
indexVar,
|
|
8886
|
+
indexValue
|
|
8887
|
+
])
|
|
8651
8888
|
});
|
|
8652
8889
|
}
|
|
8653
8890
|
defineMacro({
|
|
@@ -8655,21 +8892,29 @@ defineMacro({
|
|
|
8655
8892
|
block: true,
|
|
8656
8893
|
interpolate: true,
|
|
8657
8894
|
merged: true,
|
|
8895
|
+
parameters: [
|
|
8896
|
+
{
|
|
8897
|
+
name: "variables",
|
|
8898
|
+
type: "names",
|
|
8899
|
+
required: true
|
|
8900
|
+
},
|
|
8901
|
+
{
|
|
8902
|
+
name: "of",
|
|
8903
|
+
type: "separator"
|
|
8904
|
+
},
|
|
8905
|
+
{
|
|
8906
|
+
name: "list",
|
|
8907
|
+
type: "expression",
|
|
8908
|
+
required: true
|
|
8909
|
+
}
|
|
8910
|
+
],
|
|
8658
8911
|
render({ rawArgs, children = [] }, ctx) {
|
|
8659
8912
|
const parentValues = useContext(LocalsValuesContext);
|
|
8660
|
-
let
|
|
8661
|
-
try {
|
|
8662
|
-
parsed = parseForArgs(rawArgs);
|
|
8663
|
-
} catch (err) {
|
|
8664
|
-
return /* @__PURE__ */ jsx(MacroError, {
|
|
8665
|
-
macro: "for",
|
|
8666
|
-
error: err
|
|
8667
|
-
});
|
|
8668
|
-
}
|
|
8669
|
-
const { itemVar, indexVar, listExpr } = parsed;
|
|
8913
|
+
let loop;
|
|
8670
8914
|
let list;
|
|
8671
8915
|
try {
|
|
8672
|
-
|
|
8916
|
+
loop = loopArgs(rawArgs, ctx.args);
|
|
8917
|
+
const result = ctx.evaluate(loop.listExpr);
|
|
8673
8918
|
if (!Array.isArray(result)) return /* @__PURE__ */ jsx("span", {
|
|
8674
8919
|
class: "error",
|
|
8675
8920
|
children: `{for error: expression did not evaluate to an array}`
|
|
@@ -8681,6 +8926,7 @@ defineMacro({
|
|
|
8681
8926
|
error: err
|
|
8682
8927
|
});
|
|
8683
8928
|
}
|
|
8929
|
+
const { itemVar, indexVar } = loop;
|
|
8684
8930
|
const content = list.map((item, i) => /* @__PURE__ */ jsx(ForIteration, {
|
|
8685
8931
|
parentValues,
|
|
8686
8932
|
itemVar,
|
|
@@ -8692,7 +8938,7 @@ defineMacro({
|
|
|
8692
8938
|
return ctx.wrap(content);
|
|
8693
8939
|
},
|
|
8694
8940
|
text({ rawArgs, children = [] }, ctx) {
|
|
8695
|
-
const { itemVar, indexVar, listExpr } =
|
|
8941
|
+
const { itemVar, indexVar, listExpr } = loopArgs(rawArgs, ctx.args);
|
|
8696
8942
|
const list = ctx.evaluate(listExpr);
|
|
8697
8943
|
if (!Array.isArray(list)) throw new Error("expression did not evaluate to an array");
|
|
8698
8944
|
return list.map((item, i) => ctx.renderText(children, {
|
|
@@ -8712,7 +8958,7 @@ defineMacro({
|
|
|
8712
8958
|
try {
|
|
8713
8959
|
ctx.mutate(code);
|
|
8714
8960
|
} catch (err) {
|
|
8715
|
-
|
|
8961
|
+
logMacroError("do", err);
|
|
8716
8962
|
}
|
|
8717
8963
|
}, []);
|
|
8718
8964
|
return null;
|
|
@@ -8743,27 +8989,20 @@ function liveLocalsView(getValues) {
|
|
|
8743
8989
|
});
|
|
8744
8990
|
}
|
|
8745
8991
|
//#endregion
|
|
8746
|
-
//#region src/utils/parse-delay.ts
|
|
8747
|
-
/**
|
|
8748
|
-
* Parse a delay string into milliseconds.
|
|
8749
|
-
* Supports: "2s" → 2000, "500ms" → 500, bare number → ms
|
|
8750
|
-
*/
|
|
8751
|
-
function parseDelay(str) {
|
|
8752
|
-
const trimmed = str.trim();
|
|
8753
|
-
if (trimmed.endsWith("ms")) return parseFloat(trimmed.slice(0, -2));
|
|
8754
|
-
if (trimmed.endsWith("s")) return parseFloat(trimmed.slice(0, -1)) * 1e3;
|
|
8755
|
-
return parseFloat(trimmed);
|
|
8756
|
-
}
|
|
8757
|
-
//#endregion
|
|
8758
8992
|
//#region src/components/macros/Repeat.tsx
|
|
8759
8993
|
var RepeatContext = createContext({ stop: () => {} });
|
|
8760
8994
|
defineMacro({
|
|
8761
8995
|
name: "repeat",
|
|
8762
8996
|
block: true,
|
|
8763
8997
|
interpolate: true,
|
|
8764
|
-
|
|
8998
|
+
parameters: [{
|
|
8999
|
+
name: "delay",
|
|
9000
|
+
type: "delay",
|
|
9001
|
+
required: true
|
|
9002
|
+
}],
|
|
9003
|
+
render({ children = [] }, ctx) {
|
|
8765
9004
|
const { useState, useEffect, useCallback } = ctx.hooks;
|
|
8766
|
-
const delay =
|
|
9005
|
+
const delay = ctx.args.delay ?? 0;
|
|
8767
9006
|
const [count, setCount] = useState(0);
|
|
8768
9007
|
const [stopped, setStopped] = useState(false);
|
|
8769
9008
|
const stop = useCallback(() => setStopped(true), []);
|
|
@@ -8810,8 +9049,7 @@ defineMacro({
|
|
|
8810
9049
|
*/
|
|
8811
9050
|
function useDetachedBody() {
|
|
8812
9051
|
const updater = useContext(LocalsUpdateContext);
|
|
8813
|
-
const nobr =
|
|
8814
|
-
const inline = useContext(InlineContext);
|
|
9052
|
+
const { nobr, inline } = useRenderOptions();
|
|
8815
9053
|
const widgetChildren = useContext(WidgetChildrenContext);
|
|
8816
9054
|
const repeat = useContext(RepeatContext);
|
|
8817
9055
|
const closeDialog = useContext(DialogCloseContext);
|
|
@@ -8854,9 +9092,13 @@ defineMacro({
|
|
|
8854
9092
|
name: "button",
|
|
8855
9093
|
block: true,
|
|
8856
9094
|
interpolate: true,
|
|
9095
|
+
parameters: [{
|
|
9096
|
+
name: "label",
|
|
9097
|
+
type: "text",
|
|
9098
|
+
required: true
|
|
9099
|
+
}],
|
|
8857
9100
|
render({ rawArgs, children = [] }, ctx) {
|
|
8858
|
-
const
|
|
8859
|
-
const label = ctx.resolve?.(text) ?? rawArgs;
|
|
9101
|
+
const label = ctx.resolve?.(ctx.args.label ?? "") ?? rawArgs;
|
|
8860
9102
|
const runBody = useDetachedBody();
|
|
8861
9103
|
const handleClick = () => runBody(children);
|
|
8862
9104
|
ctx.useAction({
|
|
@@ -8876,30 +9118,22 @@ defineMacro({
|
|
|
8876
9118
|
});
|
|
8877
9119
|
//#endregion
|
|
8878
9120
|
//#region src/components/macros/MacroLink.tsx
|
|
8879
|
-
function parseLinkArgs(rawArgs) {
|
|
8880
|
-
const parts = [];
|
|
8881
|
-
const re = /(["'])((?:\\[^]|(?!\1)[^\\])*)\1/g;
|
|
8882
|
-
let m;
|
|
8883
|
-
while ((m = re.exec(rawArgs)) !== null) parts.push(unescapeQuoted(m[2]));
|
|
8884
|
-
if (parts.length >= 2) return {
|
|
8885
|
-
display: parts[0],
|
|
8886
|
-
passage: parts[1]
|
|
8887
|
-
};
|
|
8888
|
-
if (parts.length === 1) return {
|
|
8889
|
-
display: parts[0],
|
|
8890
|
-
passage: null
|
|
8891
|
-
};
|
|
8892
|
-
return {
|
|
8893
|
-
display: rawArgs.trim(),
|
|
8894
|
-
passage: null
|
|
8895
|
-
};
|
|
8896
|
-
}
|
|
8897
9121
|
defineMacro({
|
|
8898
9122
|
name: "link",
|
|
8899
9123
|
block: true,
|
|
8900
9124
|
interpolate: true,
|
|
9125
|
+
parameters: [{
|
|
9126
|
+
name: "text",
|
|
9127
|
+
type: "string",
|
|
9128
|
+
required: true
|
|
9129
|
+
}, {
|
|
9130
|
+
name: "passage",
|
|
9131
|
+
type: "string"
|
|
9132
|
+
}],
|
|
8901
9133
|
render({ rawArgs, children = [] }, ctx) {
|
|
8902
|
-
const {
|
|
9134
|
+
const { text } = ctx.args;
|
|
9135
|
+
const display = text ?? rawArgs.trim();
|
|
9136
|
+
const passage = text === void 0 ? null : ctx.args.passage ?? null;
|
|
8903
9137
|
const runBody = useDetachedBody();
|
|
8904
9138
|
const perform = () => {
|
|
8905
9139
|
runBody(children);
|
|
@@ -8928,9 +9162,27 @@ defineMacro({
|
|
|
8928
9162
|
});
|
|
8929
9163
|
//#endregion
|
|
8930
9164
|
//#region src/components/macros/Switch.tsx
|
|
9165
|
+
/**
|
|
9166
|
+
* The first {case} branch whose value equals `value`, else the {default}
|
|
9167
|
+
* branch, else null. Throws what a case expression throws.
|
|
9168
|
+
*/
|
|
9169
|
+
function selectBranch(value, branches, evaluate) {
|
|
9170
|
+
let defaultBranch = null;
|
|
9171
|
+
for (let i = 1; i < branches.length; i++) {
|
|
9172
|
+
const branch = branches[i];
|
|
9173
|
+
if (branch.rawArgs === "") defaultBranch = branch;
|
|
9174
|
+
else if (value === evaluate(branch.rawArgs)) return branch;
|
|
9175
|
+
}
|
|
9176
|
+
return defaultBranch;
|
|
9177
|
+
}
|
|
8931
9178
|
defineMacro({
|
|
8932
9179
|
name: "switch",
|
|
8933
9180
|
subMacros: ["case", "default"],
|
|
9181
|
+
parameters: [{
|
|
9182
|
+
name: "expression",
|
|
9183
|
+
type: "expression",
|
|
9184
|
+
required: true
|
|
9185
|
+
}],
|
|
8934
9186
|
merged: true,
|
|
8935
9187
|
render({ rawArgs, branches = [] }, ctx) {
|
|
8936
9188
|
let switchValue;
|
|
@@ -8942,35 +9194,20 @@ defineMacro({
|
|
|
8942
9194
|
error: err
|
|
8943
9195
|
});
|
|
8944
9196
|
}
|
|
8945
|
-
let
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
const caseValue = ctx.evaluate(branch.rawArgs);
|
|
8954
|
-
if (switchValue === caseValue) return /* @__PURE__ */ jsx(Fragment$1, { children: ctx.renderNodes(branch.children) });
|
|
8955
|
-
} catch (err) {
|
|
8956
|
-
return /* @__PURE__ */ jsx(MacroError, {
|
|
8957
|
-
macro: "case",
|
|
8958
|
-
error: err
|
|
8959
|
-
});
|
|
8960
|
-
}
|
|
9197
|
+
let branch;
|
|
9198
|
+
try {
|
|
9199
|
+
branch = selectBranch(switchValue, branches, ctx.evaluate);
|
|
9200
|
+
} catch (err) {
|
|
9201
|
+
return /* @__PURE__ */ jsx(MacroError, {
|
|
9202
|
+
macro: "case",
|
|
9203
|
+
error: err
|
|
9204
|
+
});
|
|
8961
9205
|
}
|
|
8962
|
-
|
|
8963
|
-
return null;
|
|
9206
|
+
return branch && /* @__PURE__ */ jsx(Fragment$1, { children: ctx.renderNodes(branch.children) });
|
|
8964
9207
|
},
|
|
8965
9208
|
text({ rawArgs, branches = [] }, ctx) {
|
|
8966
|
-
const
|
|
8967
|
-
|
|
8968
|
-
for (let i = 1; i < branches.length; i++) {
|
|
8969
|
-
const branch = branches[i];
|
|
8970
|
-
if (branch.rawArgs === "") defaultBranch = branch;
|
|
8971
|
-
else if (switchValue === ctx.evaluate(branch.rawArgs)) return ctx.renderText(branch.children);
|
|
8972
|
-
}
|
|
8973
|
-
return defaultBranch ? ctx.renderText(defaultBranch.children) : "";
|
|
9209
|
+
const branch = selectBranch(ctx.evaluate(rawArgs), branches, ctx.evaluate);
|
|
9210
|
+
return branch ? ctx.renderText(branch.children) : "";
|
|
8974
9211
|
}
|
|
8975
9212
|
});
|
|
8976
9213
|
//#endregion
|
|
@@ -9030,9 +9267,14 @@ defineMacro({
|
|
|
9030
9267
|
name: "type",
|
|
9031
9268
|
block: true,
|
|
9032
9269
|
interpolate: true,
|
|
9033
|
-
|
|
9270
|
+
parameters: [{
|
|
9271
|
+
name: "delay",
|
|
9272
|
+
type: "delay",
|
|
9273
|
+
required: true
|
|
9274
|
+
}],
|
|
9275
|
+
render({ children = [] }, ctx) {
|
|
9034
9276
|
const { useState, useEffect, useRef } = ctx.hooks;
|
|
9035
|
-
const speed =
|
|
9277
|
+
const speed = ctx.args.delay ?? 0;
|
|
9036
9278
|
const containerRef = useRef(null);
|
|
9037
9279
|
const [totalChars, setTotalChars] = useState(0);
|
|
9038
9280
|
const [visibleChars, setVisibleChars] = useState(0);
|
|
@@ -9213,11 +9455,18 @@ defineMacro({
|
|
|
9213
9455
|
name: "dialog",
|
|
9214
9456
|
block: true,
|
|
9215
9457
|
interpolate: true,
|
|
9216
|
-
|
|
9458
|
+
parameters: [{
|
|
9459
|
+
name: "label",
|
|
9460
|
+
type: "text",
|
|
9461
|
+
required: true
|
|
9462
|
+
}, {
|
|
9463
|
+
name: "noclose",
|
|
9464
|
+
type: "flag"
|
|
9465
|
+
}],
|
|
9466
|
+
render({ children = [] }, ctx) {
|
|
9217
9467
|
const [open, setOpen] = ctx.hooks.useState(false);
|
|
9218
|
-
const noclose =
|
|
9219
|
-
const
|
|
9220
|
-
const label = ctx.resolve?.(readWholeQuoted(labelRaw) ?? labelRaw.replace(/^["']|["']$/g, "")) ?? labelRaw;
|
|
9468
|
+
const { noclose } = ctx.args;
|
|
9469
|
+
const label = ctx.resolve(ctx.args.label ?? "");
|
|
9221
9470
|
const passageName = ctx.collectText(children).trim().replace(/^["']|["']$/g, "");
|
|
9222
9471
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
|
|
9223
9472
|
id: ctx.id,
|
|
@@ -9624,59 +9873,22 @@ defineMacro({
|
|
|
9624
9873
|
});
|
|
9625
9874
|
//#endregion
|
|
9626
9875
|
//#region src/components/macros/Include.tsx
|
|
9627
|
-
var FLAG = "inline";
|
|
9628
|
-
/**
|
|
9629
|
-
* Split a standalone `inline` flag off the start or end of the include
|
|
9630
|
-
* arguments. The flag only counts outside quotes, brackets, parentheses and
|
|
9631
|
-
* braces, separated by whitespace from the target expression, so passage
|
|
9632
|
-
* names and expressions containing the word stay intact (#201).
|
|
9633
|
-
*/
|
|
9634
|
-
function parseIncludeArgs(rawArgs) {
|
|
9635
|
-
const trimmed = rawArgs.trim();
|
|
9636
|
-
const spaces = topLevelIndices(trimmed, isWhitespace);
|
|
9637
|
-
let first = null;
|
|
9638
|
-
let last = null;
|
|
9639
|
-
if (spaces.length > 0) {
|
|
9640
|
-
let s = 0;
|
|
9641
|
-
while (s + 1 < spaces.length && spaces[s + 1] === spaces[s] + 1) s++;
|
|
9642
|
-
first = [spaces[0], spaces[s] + 1];
|
|
9643
|
-
let e = spaces.length - 1;
|
|
9644
|
-
while (e > 0 && spaces[e - 1] === spaces[e] - 1) e--;
|
|
9645
|
-
last = [spaces[e], spaces[spaces.length - 1] + 1];
|
|
9646
|
-
}
|
|
9647
|
-
if (last && last[1] === trimmed.length - 6 && trimmed.endsWith(FLAG)) {
|
|
9648
|
-
const rest = trimmed.slice(0, last[0]);
|
|
9649
|
-
if (!endsWithOperator(rest)) return {
|
|
9650
|
-
nameExpr: rest,
|
|
9651
|
-
inline: true
|
|
9652
|
-
};
|
|
9653
|
-
}
|
|
9654
|
-
if (first && first[0] === 6 && trimmed.startsWith(FLAG)) {
|
|
9655
|
-
const rest = trimmed.slice(first[1]);
|
|
9656
|
-
if (!/^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/.test(rest)) return {
|
|
9657
|
-
nameExpr: rest,
|
|
9658
|
-
inline: true
|
|
9659
|
-
};
|
|
9660
|
-
}
|
|
9661
|
-
return {
|
|
9662
|
-
nameExpr: trimmed,
|
|
9663
|
-
inline: false
|
|
9664
|
-
};
|
|
9665
|
-
}
|
|
9666
9876
|
defineMacro({
|
|
9667
9877
|
name: "include",
|
|
9668
9878
|
interpolate: true,
|
|
9669
9879
|
merged: true,
|
|
9670
|
-
|
|
9880
|
+
parameters: [{
|
|
9881
|
+
name: "inline",
|
|
9882
|
+
type: "flag"
|
|
9883
|
+
}, {
|
|
9884
|
+
name: "passage",
|
|
9885
|
+
type: "expression",
|
|
9886
|
+
required: true
|
|
9887
|
+
}],
|
|
9888
|
+
render(_props, ctx) {
|
|
9671
9889
|
const storyData = useStoryStore((s) => s.storyData);
|
|
9672
|
-
const {
|
|
9673
|
-
|
|
9674
|
-
try {
|
|
9675
|
-
const result = ctx.evaluate(nameExpr);
|
|
9676
|
-
passageName = String(result);
|
|
9677
|
-
} catch {
|
|
9678
|
-
passageName = nameExpr.replace(/^["']|["']$/g, "");
|
|
9679
|
-
}
|
|
9890
|
+
const { inline } = ctx.args;
|
|
9891
|
+
const passageName = evaluatePassageName(ctx.args.passage, ctx.evaluate);
|
|
9680
9892
|
const passage = storyData?.passages.get(passageName);
|
|
9681
9893
|
const ast = ctx.hooks.useMemo(() => passage ? buildAST(tokenize(passage.content)) : null, [passage]);
|
|
9682
9894
|
if (!storyData) return null;
|
|
@@ -9706,16 +9918,14 @@ defineMacro({
|
|
|
9706
9918
|
defineMacro({
|
|
9707
9919
|
name: "goto",
|
|
9708
9920
|
merged: true,
|
|
9709
|
-
|
|
9921
|
+
parameters: [{
|
|
9922
|
+
name: "passage",
|
|
9923
|
+
type: "expression",
|
|
9924
|
+
required: true
|
|
9925
|
+
}],
|
|
9926
|
+
render(_props, ctx) {
|
|
9710
9927
|
ctx.hooks.useLayoutEffect(() => {
|
|
9711
|
-
|
|
9712
|
-
try {
|
|
9713
|
-
const result = ctx.evaluate(rawArgs);
|
|
9714
|
-
passageName = String(result);
|
|
9715
|
-
} catch {
|
|
9716
|
-
passageName = rawArgs.replace(/^["']|["']$/g, "");
|
|
9717
|
-
}
|
|
9718
|
-
useStoryStore.getState().navigate(passageName);
|
|
9928
|
+
useStoryStore.getState().navigate(evaluatePassageName(ctx.args.passage, ctx.evaluate));
|
|
9719
9929
|
}, []);
|
|
9720
9930
|
return null;
|
|
9721
9931
|
}
|
|
@@ -9724,124 +9934,90 @@ defineMacro({
|
|
|
9724
9934
|
//#region src/components/macros/Unset.tsx
|
|
9725
9935
|
defineMacro({
|
|
9726
9936
|
name: "unset",
|
|
9937
|
+
parameters: [{
|
|
9938
|
+
name: "variable",
|
|
9939
|
+
type: "variable",
|
|
9940
|
+
required: true
|
|
9941
|
+
}],
|
|
9727
9942
|
render({ rawArgs }, ctx) {
|
|
9728
|
-
|
|
9729
|
-
const failure = ctx.hooks.useRef(null);
|
|
9730
|
-
if (!ran.current) {
|
|
9731
|
-
ran.current = true;
|
|
9943
|
+
return useRunOnce("unset", rawArgs, () => {
|
|
9732
9944
|
const state = useStoryStore.getState();
|
|
9733
|
-
const name =
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
else throw new Error(`{unset} expects a variable ($name, _name, %name, or @name), got "${name}"`);
|
|
9740
|
-
} catch (err) {
|
|
9741
|
-
failure.current = { error: err };
|
|
9742
|
-
console.error(`spindle: Error in {unset ${rawArgs}}${ctx.sourceLocation()}:`, err);
|
|
9743
|
-
}
|
|
9744
|
-
}
|
|
9745
|
-
if (failure.current) return /* @__PURE__ */ jsx(MacroError, {
|
|
9746
|
-
macro: "unset",
|
|
9747
|
-
error: failure.current.error
|
|
9945
|
+
const name = ctx.args.variable ?? "";
|
|
9946
|
+
if (name.startsWith("$")) state.deleteVariable(name.slice(1));
|
|
9947
|
+
else if (name.startsWith("_")) state.deleteTemporary(name.slice(1));
|
|
9948
|
+
else if (name.startsWith("%")) state.deleteTransient(name.slice(1));
|
|
9949
|
+
else if (name.startsWith("@")) ctx.update(name.slice(1), void 0);
|
|
9950
|
+
else throw new Error(`{unset} expects a variable ($name, _name, %name, or @name), got "${name}"`);
|
|
9748
9951
|
});
|
|
9749
|
-
return null;
|
|
9750
9952
|
}
|
|
9751
9953
|
});
|
|
9752
9954
|
//#endregion
|
|
9955
|
+
//#region src/components/macros/input-macro.ts
|
|
9956
|
+
/**
|
|
9957
|
+
* Define a text input macro, `{name $var "placeholder"}`: an `<input>` of
|
|
9958
|
+
* `type` (or, without one, a `<textarea>`) bound to the story variable.
|
|
9959
|
+
* `toValue` turns what the reader types, or an automation action's value
|
|
9960
|
+
* (`undefined` when it has none), into the variable's value: by default,
|
|
9961
|
+
* the text.
|
|
9962
|
+
*/
|
|
9963
|
+
function defineInputMacro(name, type, toValue = (input) => input !== void 0 ? String(input) : "") {
|
|
9964
|
+
defineMacro({
|
|
9965
|
+
name,
|
|
9966
|
+
storeVar: true,
|
|
9967
|
+
parameters: [{
|
|
9968
|
+
name: "variable",
|
|
9969
|
+
type: "variable",
|
|
9970
|
+
required: true
|
|
9971
|
+
}, {
|
|
9972
|
+
name: "placeholder",
|
|
9973
|
+
type: "string"
|
|
9974
|
+
}],
|
|
9975
|
+
render(_props, ctx) {
|
|
9976
|
+
const placeholder = ctx.args.placeholder ?? "";
|
|
9977
|
+
ctx.useAction({
|
|
9978
|
+
type: name,
|
|
9979
|
+
key: `$${ctx.varName}`,
|
|
9980
|
+
authorId: ctx.id,
|
|
9981
|
+
label: placeholder || ctx.varName,
|
|
9982
|
+
variable: ctx.varName,
|
|
9983
|
+
value: ctx.value,
|
|
9984
|
+
perform: (v) => ctx.setValue(toValue(v))
|
|
9985
|
+
});
|
|
9986
|
+
return h(type ? "input" : "textarea", {
|
|
9987
|
+
type,
|
|
9988
|
+
id: ctx.id,
|
|
9989
|
+
class: ctx.cls,
|
|
9990
|
+
value: ctx.value == null ? "" : String(ctx.value),
|
|
9991
|
+
placeholder,
|
|
9992
|
+
onInput: (e) => ctx.setValue(toValue(e.target.value))
|
|
9993
|
+
});
|
|
9994
|
+
}
|
|
9995
|
+
});
|
|
9996
|
+
}
|
|
9997
|
+
//#endregion
|
|
9753
9998
|
//#region src/components/macros/Textbox.tsx
|
|
9754
|
-
|
|
9755
|
-
name: "textbox",
|
|
9756
|
-
storeVar: true,
|
|
9757
|
-
render({ rawArgs }, ctx) {
|
|
9758
|
-
const { placeholder } = ctx.parseVarArgs(rawArgs);
|
|
9759
|
-
ctx.useAction({
|
|
9760
|
-
type: "textbox",
|
|
9761
|
-
key: `$${ctx.varName}`,
|
|
9762
|
-
authorId: ctx.id,
|
|
9763
|
-
label: placeholder || ctx.varName,
|
|
9764
|
-
variable: ctx.varName,
|
|
9765
|
-
value: ctx.value,
|
|
9766
|
-
perform: (v) => ctx.setValue(v !== void 0 ? String(v) : "")
|
|
9767
|
-
});
|
|
9768
|
-
return /* @__PURE__ */ jsx("input", {
|
|
9769
|
-
type: "text",
|
|
9770
|
-
id: ctx.id,
|
|
9771
|
-
class: ctx.cls,
|
|
9772
|
-
value: ctx.value == null ? "" : String(ctx.value),
|
|
9773
|
-
placeholder,
|
|
9774
|
-
onInput: (e) => ctx.setValue(e.target.value)
|
|
9775
|
-
});
|
|
9776
|
-
}
|
|
9777
|
-
});
|
|
9999
|
+
defineInputMacro("textbox", "text");
|
|
9778
10000
|
//#endregion
|
|
9779
10001
|
//#region src/components/macros/Numberbox.tsx
|
|
9780
|
-
|
|
9781
|
-
name: "numberbox",
|
|
9782
|
-
storeVar: true,
|
|
9783
|
-
render({ rawArgs }, ctx) {
|
|
9784
|
-
const { placeholder } = ctx.parseVarArgs(rawArgs);
|
|
9785
|
-
ctx.useAction({
|
|
9786
|
-
type: "numberbox",
|
|
9787
|
-
key: `$${ctx.varName}`,
|
|
9788
|
-
authorId: ctx.id,
|
|
9789
|
-
label: placeholder || ctx.varName,
|
|
9790
|
-
variable: ctx.varName,
|
|
9791
|
-
value: ctx.value,
|
|
9792
|
-
perform: (v) => ctx.setValue(v !== void 0 ? Number(v) : 0)
|
|
9793
|
-
});
|
|
9794
|
-
return /* @__PURE__ */ jsx("input", {
|
|
9795
|
-
type: "number",
|
|
9796
|
-
id: ctx.id,
|
|
9797
|
-
class: ctx.cls,
|
|
9798
|
-
value: ctx.value == null ? "" : String(ctx.value),
|
|
9799
|
-
placeholder,
|
|
9800
|
-
onInput: (e) => {
|
|
9801
|
-
const val = e.target.value;
|
|
9802
|
-
ctx.setValue(val === "" ? 0 : Number(val));
|
|
9803
|
-
}
|
|
9804
|
-
});
|
|
9805
|
-
}
|
|
9806
|
-
});
|
|
10002
|
+
defineInputMacro("numberbox", "number", (v) => v !== void 0 ? Number(v) : 0);
|
|
9807
10003
|
//#endregion
|
|
9808
10004
|
//#region src/components/macros/Textarea.tsx
|
|
9809
|
-
|
|
9810
|
-
name: "textarea",
|
|
9811
|
-
storeVar: true,
|
|
9812
|
-
render({ rawArgs }, ctx) {
|
|
9813
|
-
const { placeholder } = ctx.parseVarArgs(rawArgs);
|
|
9814
|
-
ctx.useAction({
|
|
9815
|
-
type: "textarea",
|
|
9816
|
-
key: `$${ctx.varName}`,
|
|
9817
|
-
authorId: ctx.id,
|
|
9818
|
-
label: placeholder || ctx.varName,
|
|
9819
|
-
variable: ctx.varName,
|
|
9820
|
-
value: ctx.value,
|
|
9821
|
-
perform: (v) => ctx.setValue(v !== void 0 ? String(v) : "")
|
|
9822
|
-
});
|
|
9823
|
-
return /* @__PURE__ */ jsx("textarea", {
|
|
9824
|
-
id: ctx.id,
|
|
9825
|
-
class: ctx.cls,
|
|
9826
|
-
value: ctx.value == null ? "" : String(ctx.value),
|
|
9827
|
-
placeholder,
|
|
9828
|
-
onInput: (e) => ctx.setValue(e.target.value)
|
|
9829
|
-
});
|
|
9830
|
-
}
|
|
9831
|
-
});
|
|
10005
|
+
defineInputMacro("textarea");
|
|
9832
10006
|
//#endregion
|
|
9833
10007
|
//#region src/components/macros/Checkbox.tsx
|
|
9834
|
-
function parseCheckboxLabel(rawArgs) {
|
|
9835
|
-
const match = rawArgs.match(/^\s*["']?\$?[\w.]+["']?\s+(.+)$/s);
|
|
9836
|
-
if (!match) return "";
|
|
9837
|
-
const rest = match[1].trimEnd() || match[1];
|
|
9838
|
-
return readWholeQuoted(rest) ?? stripLooseQuotes(rest);
|
|
9839
|
-
}
|
|
9840
10008
|
defineMacro({
|
|
9841
10009
|
name: "checkbox",
|
|
9842
10010
|
storeVar: true,
|
|
9843
|
-
|
|
9844
|
-
|
|
10011
|
+
parameters: [{
|
|
10012
|
+
name: "variable",
|
|
10013
|
+
type: "variable",
|
|
10014
|
+
required: true
|
|
10015
|
+
}, {
|
|
10016
|
+
name: "label",
|
|
10017
|
+
type: "text"
|
|
10018
|
+
}],
|
|
10019
|
+
render(_props, ctx) {
|
|
10020
|
+
const label = ctx.args.label ?? "";
|
|
9845
10021
|
ctx.useAction({
|
|
9846
10022
|
type: "checkbox",
|
|
9847
10023
|
key: `$${ctx.varName}`,
|
|
@@ -9864,38 +10040,28 @@ defineMacro({
|
|
|
9864
10040
|
});
|
|
9865
10041
|
//#endregion
|
|
9866
10042
|
//#region src/components/macros/Radiobutton.tsx
|
|
9867
|
-
function parseRadioArgs(rawArgs) {
|
|
9868
|
-
const head = rawArgs.match(/^\s*["']?\$?[\w.]+["']?\s+/);
|
|
9869
|
-
if (head) {
|
|
9870
|
-
const rest = rawArgs.slice(head[0].length).trim();
|
|
9871
|
-
const value = readQuoted(rest, 0);
|
|
9872
|
-
if (value && (value.end === rest.length || isWhitespace(rest[value.end]))) {
|
|
9873
|
-
const labelRaw = rest.slice(value.end).trim();
|
|
9874
|
-
const label = readWholeQuoted(labelRaw) ?? stripLooseQuotes(labelRaw);
|
|
9875
|
-
return {
|
|
9876
|
-
value: value.value,
|
|
9877
|
-
label
|
|
9878
|
-
};
|
|
9879
|
-
}
|
|
9880
|
-
}
|
|
9881
|
-
const match = rawArgs.match(/^\s*["']?\$?[\w.]+["']?\s+["'](.+?)["']\s+["']?(.+?)["']?\s*$/s);
|
|
9882
|
-
if (!match) {
|
|
9883
|
-
const parts = rawArgs.trim().split(/\s+/).slice(1);
|
|
9884
|
-
return {
|
|
9885
|
-
value: parts[0] ?? "",
|
|
9886
|
-
label: parts.slice(1).join(" ")
|
|
9887
|
-
};
|
|
9888
|
-
}
|
|
9889
|
-
return {
|
|
9890
|
-
value: match[1],
|
|
9891
|
-
label: match[2]
|
|
9892
|
-
};
|
|
9893
|
-
}
|
|
9894
10043
|
defineMacro({
|
|
9895
10044
|
name: "radiobutton",
|
|
9896
10045
|
storeVar: true,
|
|
9897
|
-
|
|
9898
|
-
|
|
10046
|
+
parameters: [
|
|
10047
|
+
{
|
|
10048
|
+
name: "variable",
|
|
10049
|
+
type: "variable",
|
|
10050
|
+
required: true
|
|
10051
|
+
},
|
|
10052
|
+
{
|
|
10053
|
+
name: "value",
|
|
10054
|
+
type: "text",
|
|
10055
|
+
required: true
|
|
10056
|
+
},
|
|
10057
|
+
{
|
|
10058
|
+
name: "label",
|
|
10059
|
+
type: "text"
|
|
10060
|
+
}
|
|
10061
|
+
],
|
|
10062
|
+
render(_props, ctx) {
|
|
10063
|
+
const radioValue = ctx.args.value ?? "";
|
|
10064
|
+
const label = ctx.args.label ?? "";
|
|
9899
10065
|
ctx.useAction({
|
|
9900
10066
|
type: "radiobutton",
|
|
9901
10067
|
key: `$${ctx.varName}:${radioValue}`,
|
|
@@ -9923,6 +10089,11 @@ defineMacro({
|
|
|
9923
10089
|
name: "listbox",
|
|
9924
10090
|
subMacros: ["option"],
|
|
9925
10091
|
storeVar: true,
|
|
10092
|
+
parameters: [{
|
|
10093
|
+
name: "variable",
|
|
10094
|
+
type: "variable",
|
|
10095
|
+
required: true
|
|
10096
|
+
}],
|
|
9926
10097
|
render({ children = [] }, ctx) {
|
|
9927
10098
|
const options = ctx.extractOptions(children);
|
|
9928
10099
|
ctx.useAction({
|
|
@@ -9955,6 +10126,11 @@ defineMacro({
|
|
|
9955
10126
|
name: "cycle",
|
|
9956
10127
|
block: true,
|
|
9957
10128
|
storeVar: true,
|
|
10129
|
+
parameters: [{
|
|
10130
|
+
name: "variable",
|
|
10131
|
+
type: "variable",
|
|
10132
|
+
required: true
|
|
10133
|
+
}],
|
|
9958
10134
|
render({ children = [] }, ctx) {
|
|
9959
10135
|
const options = ctx.extractOptions(children);
|
|
9960
10136
|
const handleClick = () => {
|
|
@@ -10191,6 +10367,16 @@ function setCSSProperties(el, config) {
|
|
|
10191
10367
|
el.style.setProperty("--passage-pause", `${config.pause / 1e3}s`);
|
|
10192
10368
|
}
|
|
10193
10369
|
/**
|
|
10370
|
+
* Set up `containerEl` for an outgoing transition and snapshot the passage
|
|
10371
|
+
* it shows, or return null if there is no container or passage.
|
|
10372
|
+
*/
|
|
10373
|
+
function snapshotPassage(containerEl, config) {
|
|
10374
|
+
if (!containerEl) return null;
|
|
10375
|
+
setCSSProperties(containerEl, config);
|
|
10376
|
+
const passageEl = containerEl.querySelector(".passage");
|
|
10377
|
+
return passageEl ? createSnapshot(passageEl, config.type) : null;
|
|
10378
|
+
}
|
|
10379
|
+
/**
|
|
10194
10380
|
* Remove all snapshot elements and crossfading class from a container.
|
|
10195
10381
|
*/
|
|
10196
10382
|
function cleanupSnapshots(containerEl) {
|
|
@@ -10258,18 +10444,12 @@ defineMacro({
|
|
|
10258
10444
|
setDisplayedPassage(currentPassage);
|
|
10259
10445
|
return;
|
|
10260
10446
|
}
|
|
10447
|
+
const snapshot = config.type === "fade-through" || config.type === "crossfade" ? snapshotPassage(containerEl, config) : null;
|
|
10448
|
+
if (!containerEl || !snapshot) {
|
|
10449
|
+
setDisplayedPassage(currentPassage);
|
|
10450
|
+
return;
|
|
10451
|
+
}
|
|
10261
10452
|
if (config.type === "fade-through") {
|
|
10262
|
-
if (!containerEl) {
|
|
10263
|
-
setDisplayedPassage(currentPassage);
|
|
10264
|
-
return;
|
|
10265
|
-
}
|
|
10266
|
-
setCSSProperties(containerEl, config);
|
|
10267
|
-
const passageEl = containerEl.querySelector(".passage");
|
|
10268
|
-
if (!passageEl) {
|
|
10269
|
-
setDisplayedPassage(currentPassage);
|
|
10270
|
-
return;
|
|
10271
|
-
}
|
|
10272
|
-
const snapshot = createSnapshot(passageEl, config.type);
|
|
10273
10453
|
containerEl.appendChild(snapshot);
|
|
10274
10454
|
setDisplayedPassage("");
|
|
10275
10455
|
const incomingDelay = config.duration + config.pause;
|
|
@@ -10282,27 +10462,12 @@ defineMacro({
|
|
|
10282
10462
|
}, cleanupDelay)];
|
|
10283
10463
|
return;
|
|
10284
10464
|
}
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
setDisplayedPassage(currentPassage);
|
|
10288
|
-
return;
|
|
10289
|
-
}
|
|
10290
|
-
setCSSProperties(containerEl, config);
|
|
10291
|
-
const passageEl = containerEl.querySelector(".passage");
|
|
10292
|
-
if (!passageEl) {
|
|
10293
|
-
setDisplayedPassage(currentPassage);
|
|
10294
|
-
return;
|
|
10295
|
-
}
|
|
10296
|
-
const snapshot = createSnapshot(passageEl, config.type);
|
|
10297
|
-
containerEl.classList.add("passage-container--crossfading");
|
|
10298
|
-
containerEl.appendChild(snapshot);
|
|
10299
|
-
setDisplayedPassage(currentPassage);
|
|
10300
|
-
timeoutsRef.current = [setTimeout(() => {
|
|
10301
|
-
cleanupSnapshots(containerEl);
|
|
10302
|
-
}, config.duration)];
|
|
10303
|
-
return;
|
|
10304
|
-
}
|
|
10465
|
+
containerEl.classList.add("passage-container--crossfading");
|
|
10466
|
+
containerEl.appendChild(snapshot);
|
|
10305
10467
|
setDisplayedPassage(currentPassage);
|
|
10468
|
+
timeoutsRef.current = [setTimeout(() => {
|
|
10469
|
+
cleanupSnapshots(containerEl);
|
|
10470
|
+
}, config.duration)];
|
|
10306
10471
|
}, [navigationId]);
|
|
10307
10472
|
const effectivePassage = renderDeferred ? storyData?.passages.get("StoryLoading") ?? null : displayedPassage ? storyData?.passages.get(displayedPassage) : null;
|
|
10308
10473
|
if (!effectivePassage && displayedPassage && !renderDeferred) return /* @__PURE__ */ jsxs("div", {
|
|
@@ -10334,87 +10499,46 @@ defineMacro({
|
|
|
10334
10499
|
});
|
|
10335
10500
|
//#endregion
|
|
10336
10501
|
//#region src/components/macros/Watch.tsx
|
|
10337
|
-
var WORD_RE = /\w+/y;
|
|
10338
|
-
var DIGITS_RE = /\d+/y;
|
|
10339
|
-
/** Match the sticky regex `re` at `pos`, returning the matched text. */
|
|
10340
|
-
function matchAt(re, src, pos) {
|
|
10341
|
-
re.lastIndex = pos;
|
|
10342
|
-
return re.exec(src)?.[0] ?? null;
|
|
10343
|
-
}
|
|
10344
|
-
/**
|
|
10345
|
-
* Parse `{watch "condition" keyword value …}`: a quoted condition followed
|
|
10346
|
-
* by keyword options (`goto "X"`, `dialog "X"`, `run "X"`, `name "X"`,
|
|
10347
|
-
* `priority N`, `once`). Quoted strings accept `\"`, `\'` and `\\` escapes.
|
|
10348
|
-
* Returns `null` when the arguments do not start with a quoted condition.
|
|
10349
|
-
*/
|
|
10350
|
-
function parseWatchArgs(rawArgs) {
|
|
10351
|
-
const raw = rawArgs.trim();
|
|
10352
|
-
const cond = readQuoted(raw, 0);
|
|
10353
|
-
if (!cond) return null;
|
|
10354
|
-
const options = {};
|
|
10355
|
-
let i = cond.end;
|
|
10356
|
-
while (i < raw.length) {
|
|
10357
|
-
const key = matchAt(WORD_RE, raw, i);
|
|
10358
|
-
if (!key) {
|
|
10359
|
-
i = readQuoted(raw, i)?.end ?? i + 1;
|
|
10360
|
-
continue;
|
|
10361
|
-
}
|
|
10362
|
-
i += key.length;
|
|
10363
|
-
let val;
|
|
10364
|
-
let j = i;
|
|
10365
|
-
while (j < raw.length && isWhitespace(raw[j])) j++;
|
|
10366
|
-
if (j > i) {
|
|
10367
|
-
const quoted = readQuoted(raw, j);
|
|
10368
|
-
const digits = quoted ? null : matchAt(DIGITS_RE, raw, j);
|
|
10369
|
-
if (quoted) {
|
|
10370
|
-
val = quoted.value;
|
|
10371
|
-
i = quoted.end;
|
|
10372
|
-
} else if (digits) {
|
|
10373
|
-
val = digits;
|
|
10374
|
-
i = j + digits.length;
|
|
10375
|
-
}
|
|
10376
|
-
}
|
|
10377
|
-
switch (key) {
|
|
10378
|
-
case "goto":
|
|
10379
|
-
options.goto = val;
|
|
10380
|
-
break;
|
|
10381
|
-
case "dialog":
|
|
10382
|
-
options.dialog = val;
|
|
10383
|
-
break;
|
|
10384
|
-
case "run":
|
|
10385
|
-
options.run = val;
|
|
10386
|
-
break;
|
|
10387
|
-
case "name":
|
|
10388
|
-
options.name = val;
|
|
10389
|
-
break;
|
|
10390
|
-
case "priority":
|
|
10391
|
-
options.priority = val ? Number(val) : 0;
|
|
10392
|
-
break;
|
|
10393
|
-
case "once":
|
|
10394
|
-
options.once = true;
|
|
10395
|
-
break;
|
|
10396
|
-
}
|
|
10397
|
-
}
|
|
10398
|
-
return {
|
|
10399
|
-
condition: cond.value,
|
|
10400
|
-
options
|
|
10401
|
-
};
|
|
10402
|
-
}
|
|
10403
|
-
/**
|
|
10404
|
-
* Parse the watcher name of `{unwatch "name"}`: a quoted string, with the
|
|
10405
|
-
* same escapes as `{watch}`, or a bare name.
|
|
10406
|
-
*/
|
|
10407
|
-
function parseUnwatchName(rawArgs) {
|
|
10408
|
-
const raw = rawArgs.trim();
|
|
10409
|
-
return readWholeQuoted(raw) ?? raw.replace(/^['"]|['"]$/g, "");
|
|
10410
|
-
}
|
|
10411
10502
|
defineMacro({
|
|
10412
10503
|
name: "watch",
|
|
10413
|
-
|
|
10504
|
+
parameters: [{
|
|
10505
|
+
name: "condition",
|
|
10506
|
+
type: "string",
|
|
10507
|
+
required: true
|
|
10508
|
+
}, {
|
|
10509
|
+
name: "options",
|
|
10510
|
+
type: "options",
|
|
10511
|
+
parameters: [
|
|
10512
|
+
{
|
|
10513
|
+
name: "goto",
|
|
10514
|
+
type: "string"
|
|
10515
|
+
},
|
|
10516
|
+
{
|
|
10517
|
+
name: "dialog",
|
|
10518
|
+
type: "string"
|
|
10519
|
+
},
|
|
10520
|
+
{
|
|
10521
|
+
name: "run",
|
|
10522
|
+
type: "string"
|
|
10523
|
+
},
|
|
10524
|
+
{
|
|
10525
|
+
name: "name",
|
|
10526
|
+
type: "string"
|
|
10527
|
+
},
|
|
10528
|
+
{
|
|
10529
|
+
name: "priority",
|
|
10530
|
+
type: "number"
|
|
10531
|
+
},
|
|
10532
|
+
{
|
|
10533
|
+
name: "once",
|
|
10534
|
+
type: "flag"
|
|
10535
|
+
}
|
|
10536
|
+
]
|
|
10537
|
+
}],
|
|
10538
|
+
render(_props, ctx) {
|
|
10414
10539
|
const { hooks } = ctx;
|
|
10415
|
-
const
|
|
10416
|
-
if (
|
|
10417
|
-
const { condition, options } = parsed;
|
|
10540
|
+
const { condition, options } = ctx.args;
|
|
10541
|
+
if (condition === void 0) return null;
|
|
10418
10542
|
const registered = hooks.useRef(false);
|
|
10419
10543
|
if (!registered.current) {
|
|
10420
10544
|
registered.current = true;
|
|
@@ -10425,8 +10549,13 @@ defineMacro({
|
|
|
10425
10549
|
});
|
|
10426
10550
|
defineMacro({
|
|
10427
10551
|
name: "unwatch",
|
|
10428
|
-
|
|
10429
|
-
|
|
10552
|
+
parameters: [{
|
|
10553
|
+
name: "name",
|
|
10554
|
+
type: "text",
|
|
10555
|
+
required: true
|
|
10556
|
+
}],
|
|
10557
|
+
render(_props, ctx) {
|
|
10558
|
+
const name = ctx.args.name ?? "";
|
|
10430
10559
|
const ran = ctx.hooks.useRef(false);
|
|
10431
10560
|
if (!ran.current) {
|
|
10432
10561
|
ran.current = true;
|