@rohal12/spindle 0.52.7 → 0.52.9
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/README.md +26 -0
- package/dist/pkg/format.js +1 -1
- package/dist/pkg/headless.js +986 -854
- package/dist/pkg/macro-registry.json +18 -0
- package/package.json +1 -1
- package/src/action-registry.ts +9 -0
- package/src/class-registry.ts +1 -5
- package/src/components/Passage.tsx +35 -39
- package/src/components/PassageDialog.tsx +2 -2
- package/src/components/StoryInterface.tsx +2 -2
- package/src/components/macros/Checkbox.tsx +3 -8
- package/src/components/macros/Computed.tsx +1 -1
- package/src/components/macros/Cycle.tsx +3 -6
- package/src/components/macros/ExprDisplay.tsx +12 -29
- package/src/components/macros/If.tsx +35 -41
- package/src/components/macros/Include.tsx +2 -1
- package/src/components/macros/Listbox.tsx +5 -7
- package/src/components/macros/Print.tsx +3 -5
- package/src/components/macros/Radiobutton.tsx +3 -5
- package/src/components/macros/Repeat.tsx +2 -1
- package/src/components/macros/Switch.tsx +6 -13
- package/src/components/macros/Timed.tsx +14 -18
- package/src/components/macros/Type.tsx +2 -1
- package/src/components/macros/VarDisplay.tsx +12 -32
- package/src/components/macros/Widget.tsx +49 -20
- package/src/components/macros/branches.ts +18 -0
- package/src/components/macros/display.ts +19 -0
- package/src/components/macros/input-macro.ts +33 -11
- package/src/components/macros/macro-args.ts +7 -0
- package/src/components/macros/option-utils.ts +10 -7
- package/src/define-macro.ts +4 -6
- package/src/expression.ts +58 -40
- package/src/hooks/use-action.ts +7 -15
- package/src/index.tsx +5 -15
- package/src/interpolation.ts +34 -41
- package/src/markup/ast.ts +4 -3
- package/src/markup/render.tsx +121 -65
- package/src/parser.ts +119 -13
- package/src/registry.ts +13 -12
- package/src/saves/save-manager.ts +18 -13
- package/src/saves/storage.ts +19 -20
- package/src/story-api.ts +30 -73
- package/src/structural.ts +1 -18
- package/src/types-drift-check.ts +37 -50
- package/src/utils/macro-names.ts +40 -0
- package/src/utils/namespace.ts +17 -0
- package/src/widgets/widget-registry.ts +5 -4
|
@@ -182,6 +182,13 @@
|
|
|
182
182
|
"next"
|
|
183
183
|
],
|
|
184
184
|
"interpolate": true,
|
|
185
|
+
"parameters": [
|
|
186
|
+
{
|
|
187
|
+
"name": "delay",
|
|
188
|
+
"type": "delay",
|
|
189
|
+
"required": true
|
|
190
|
+
}
|
|
191
|
+
],
|
|
185
192
|
"source": "builtin"
|
|
186
193
|
},
|
|
187
194
|
{
|
|
@@ -208,6 +215,17 @@
|
|
|
208
215
|
"name": "widget",
|
|
209
216
|
"block": true,
|
|
210
217
|
"subMacros": [],
|
|
218
|
+
"parameters": [
|
|
219
|
+
{
|
|
220
|
+
"name": "name",
|
|
221
|
+
"type": "text",
|
|
222
|
+
"required": true
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "parameters",
|
|
226
|
+
"type": "expression"
|
|
227
|
+
}
|
|
228
|
+
],
|
|
211
229
|
"source": "builtin"
|
|
212
230
|
},
|
|
213
231
|
{
|
package/package.json
CHANGED
package/src/action-registry.ts
CHANGED
|
@@ -29,6 +29,15 @@ export interface StoryAction {
|
|
|
29
29
|
perform: (value?: unknown) => void;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** The fields of a StoryAction that are left out when unset. */
|
|
33
|
+
export const OPTIONAL_ACTION_FIELDS = [
|
|
34
|
+
'target',
|
|
35
|
+
'variable',
|
|
36
|
+
'options',
|
|
37
|
+
'value',
|
|
38
|
+
'disabled',
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
32
41
|
/** A mounted control's hold on its action ID. */
|
|
33
42
|
export interface ActionRegistration {
|
|
34
43
|
/**
|
package/src/class-registry.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
// Class registry for preserving class instances across clone/save/load cycles.
|
|
2
2
|
|
|
3
|
-
import { setOwn } from './
|
|
4
|
-
import { hasOwn } from './utils/namespace';
|
|
5
|
-
|
|
6
|
-
// Copying and comparing story values live with the structural diff
|
|
7
|
-
export { deepClone, deepEqual, type DeepCloneOptions } from './structural';
|
|
3
|
+
import { hasOwn, setOwn } from './utils/namespace';
|
|
8
4
|
|
|
9
5
|
type Constructor = new (...args: any[]) => any;
|
|
10
6
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { tokenize } from '../markup/tokenizer';
|
|
9
9
|
import { buildAST, type ASTNode } from '../markup/ast';
|
|
10
10
|
import { renderNodes, NobrContext } from '../markup/render';
|
|
11
|
-
import {
|
|
11
|
+
import { useStoryFields } from '../hooks/use-story-fields';
|
|
12
12
|
import type { Passage as PassageData } from '../parser';
|
|
13
13
|
import { sourceLocationOf } from '../utils/source-location';
|
|
14
14
|
import { emitFromRender } from '../event-emitter';
|
|
@@ -40,6 +40,21 @@ export function renderPassageContent(passage: PassageData) {
|
|
|
40
40
|
return renderNodes(ast, nobr ? { nobr: true } : undefined);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The content of a special passage shown with every passage (PassageHeader,
|
|
45
|
+
* PassageFooter, PassageDone), or null. One that fails to render is logged
|
|
46
|
+
* and left out, so it can't take the passage down with it.
|
|
47
|
+
*/
|
|
48
|
+
function renderSpecialPassage(passage: PassageData | undefined) {
|
|
49
|
+
if (!passage) return null;
|
|
50
|
+
try {
|
|
51
|
+
return renderPassageContent(passage);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(`spindle: Error in ${passage.name}:`, err);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
interface PassageProps {
|
|
44
59
|
passage: PassageData;
|
|
45
60
|
dataTransition?: string;
|
|
@@ -59,7 +74,7 @@ export function Passage({
|
|
|
59
74
|
dataTransition,
|
|
60
75
|
navigationId,
|
|
61
76
|
}: PassageProps) {
|
|
62
|
-
const storyData =
|
|
77
|
+
const { storyData } = useStoryFields('storyData');
|
|
63
78
|
const isCodePassage = CODE_PASSAGES.has(passage.name);
|
|
64
79
|
const [doneReady, setDoneReady] = useState(false);
|
|
65
80
|
|
|
@@ -76,35 +91,21 @@ export function Passage({
|
|
|
76
91
|
}
|
|
77
92
|
}, [passage.content, passage.name]);
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
: storyData?.passages.get(
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const donePassage = isCodePassage
|
|
86
|
-
? undefined
|
|
87
|
-
: storyData?.passages.get('PassageDone');
|
|
88
|
-
|
|
89
|
-
const headerContent = useMemo(() => {
|
|
90
|
-
if (!headerPassage) return null;
|
|
91
|
-
try {
|
|
92
|
-
return renderPassageContent(headerPassage);
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.error('spindle: Error in PassageHeader:', err);
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}, [headerPassage?.content]);
|
|
94
|
+
// Code passages are shown without the special passages
|
|
95
|
+
const special = (name: string) =>
|
|
96
|
+
isCodePassage ? undefined : storyData?.passages.get(name);
|
|
97
|
+
const headerPassage = special('PassageHeader');
|
|
98
|
+
const footerPassage = special('PassageFooter');
|
|
99
|
+
const donePassage = special('PassageDone');
|
|
98
100
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}, [footerPassage?.content]);
|
|
101
|
+
const headerContent = useMemo(
|
|
102
|
+
() => renderSpecialPassage(headerPassage),
|
|
103
|
+
[headerPassage?.content],
|
|
104
|
+
);
|
|
105
|
+
const footerContent = useMemo(
|
|
106
|
+
() => renderSpecialPassage(footerPassage),
|
|
107
|
+
[footerPassage?.content],
|
|
108
|
+
);
|
|
108
109
|
|
|
109
110
|
// Defer PassageDone to after DOM commit
|
|
110
111
|
useEffect(() => {
|
|
@@ -112,15 +113,10 @@ export function Passage({
|
|
|
112
113
|
return () => setDoneReady(false);
|
|
113
114
|
}, [passage.name]);
|
|
114
115
|
|
|
115
|
-
const doneContent = useMemo(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
} catch (err) {
|
|
120
|
-
console.error('spindle: Error in PassageDone:', err);
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
}, [doneReady, donePassage?.content]);
|
|
116
|
+
const doneContent = useMemo(
|
|
117
|
+
() => (doneReady ? renderSpecialPassage(donePassage) : null),
|
|
118
|
+
[doneReady, donePassage?.content],
|
|
119
|
+
);
|
|
124
120
|
|
|
125
121
|
// Signal that this passage's DOM is committed (after descendants' layout
|
|
126
122
|
// effects, before paint). Runs once per mount: PassageDisplay keys the
|
|
@@ -3,7 +3,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef } from 'preact/hooks';
|
|
|
3
3
|
import { tokenize } from '../markup/tokenizer';
|
|
4
4
|
import { buildAST } from '../markup/ast';
|
|
5
5
|
import { renderNodes, NobrContext } from '../markup/render';
|
|
6
|
-
import {
|
|
6
|
+
import { useStoryFields } from '../hooks/use-story-fields';
|
|
7
7
|
import { emitFromRender } from '../event-emitter';
|
|
8
8
|
import { useModalFocus } from '../hooks/use-modal-focus';
|
|
9
9
|
import { errorMessage } from '../utils/error-message';
|
|
@@ -38,7 +38,7 @@ export function PassageDialog({
|
|
|
38
38
|
onCloseRef.current = onClose;
|
|
39
39
|
const stableOnClose = useCallback(() => onCloseRef.current(), []);
|
|
40
40
|
|
|
41
|
-
const storyData =
|
|
41
|
+
const { storyData } = useStoryFields('storyData');
|
|
42
42
|
|
|
43
43
|
const passage = passageName
|
|
44
44
|
? storyData?.passages.get(passageName)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo } from 'preact/hooks';
|
|
2
|
-
import {
|
|
2
|
+
import { useStoryFields } from '../hooks/use-story-fields';
|
|
3
3
|
import { tokenize } from '../markup/tokenizer';
|
|
4
4
|
import { buildAST } from '../markup/ast';
|
|
5
5
|
import { renderInlineNodes, NobrContext } from '../markup/render';
|
|
@@ -9,7 +9,7 @@ const DEFAULT_MARKUP =
|
|
|
9
9
|
'<header class="story-menubar">{story-title}{back}{forward}{restart}{quicksave}{quickload}{saves}{settings}</header>\n{passage}';
|
|
10
10
|
|
|
11
11
|
export function StoryInterface() {
|
|
12
|
-
const storyData =
|
|
12
|
+
const { storyData } = useStoryFields('storyData');
|
|
13
13
|
|
|
14
14
|
const overridePassage = storyData?.passages.get('StoryInterface');
|
|
15
15
|
const markup =
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
|
+
import { VARIABLE_PARAMETER, useVariableAction } from './input-macro';
|
|
2
3
|
|
|
3
4
|
defineMacro({
|
|
4
5
|
name: 'checkbox',
|
|
5
6
|
storeVar: true,
|
|
6
|
-
parameters: [
|
|
7
|
-
{ name: 'variable', type: 'variable', required: true },
|
|
8
|
-
{ name: 'label', type: 'text' },
|
|
9
|
-
],
|
|
7
|
+
parameters: [VARIABLE_PARAMETER, { name: 'label', type: 'text' }],
|
|
10
8
|
render(_props, ctx) {
|
|
11
9
|
const label = ctx.args.label ?? '';
|
|
12
10
|
|
|
13
|
-
ctx
|
|
11
|
+
useVariableAction(ctx, {
|
|
14
12
|
type: 'checkbox',
|
|
15
|
-
key: `$${ctx.varName}`,
|
|
16
|
-
authorId: ctx.id,
|
|
17
13
|
label: label || ctx.varName || '',
|
|
18
|
-
variable: ctx.varName,
|
|
19
14
|
value: !!ctx.value,
|
|
20
15
|
perform: (v) => ctx.setValue!(v !== undefined ? !!v : !ctx.value),
|
|
21
16
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useStoryStore } from '../../store';
|
|
2
2
|
import { evaluate } from '../../expression';
|
|
3
3
|
import { readState } from '../../execute-mutation';
|
|
4
|
-
import { deepEqual } from '../../
|
|
4
|
+
import { deepEqual } from '../../structural';
|
|
5
5
|
import { defineMacro } from '../../define-macro';
|
|
6
6
|
import { MacroError, logMacroError } from './MacroError';
|
|
7
7
|
import { checkVariableName } from '../../utils/namespace';
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
|
+
import { VARIABLE_PARAMETER, useVariableAction } from './input-macro';
|
|
2
3
|
|
|
3
4
|
defineMacro({
|
|
4
5
|
name: 'cycle',
|
|
5
6
|
block: true,
|
|
6
7
|
storeVar: true,
|
|
7
|
-
parameters: [
|
|
8
|
+
parameters: [VARIABLE_PARAMETER],
|
|
8
9
|
render({ children = [] }, ctx) {
|
|
9
10
|
const options = ctx.extractOptions(children);
|
|
10
11
|
|
|
@@ -16,14 +17,10 @@ defineMacro({
|
|
|
16
17
|
ctx.setValue!(options[nextIndex]);
|
|
17
18
|
};
|
|
18
19
|
|
|
19
|
-
ctx
|
|
20
|
+
useVariableAction(ctx, {
|
|
20
21
|
type: 'cycle',
|
|
21
|
-
key: `$${ctx.varName}`,
|
|
22
|
-
authorId: ctx.id,
|
|
23
22
|
label: ctx.value == null ? options[0] || '' : String(ctx.value),
|
|
24
|
-
variable: ctx.varName,
|
|
25
23
|
options,
|
|
26
|
-
value: ctx.value,
|
|
27
24
|
perform: (v) => {
|
|
28
25
|
if (v !== undefined) {
|
|
29
26
|
ctx.setValue!(v);
|
|
@@ -1,41 +1,24 @@
|
|
|
1
1
|
import { evaluate } from '../../expression';
|
|
2
2
|
import { useInterpolate } from '../../hooks/use-interpolate';
|
|
3
3
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
4
|
+
import { display, wrapContent } from './display';
|
|
5
|
+
import type { ExpressionNode } from '../../markup/ast';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
id?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function ExprDisplay({ expression, className, id }: ExprDisplayProps) {
|
|
7
|
+
/** An expression display: `{$hp + 1}`. */
|
|
8
|
+
export function ExprDisplay({ node }: { node: ExpressionNode }) {
|
|
9
|
+
const { expression } = node;
|
|
12
10
|
const resolve = useInterpolate();
|
|
13
|
-
className = resolve(className);
|
|
14
|
-
id = resolve(id);
|
|
11
|
+
const className = resolve(node.className);
|
|
12
|
+
const id = resolve(node.id);
|
|
15
13
|
const [variables, temporary, localsValues, transient] = useMergedLocals();
|
|
16
14
|
|
|
17
|
-
let
|
|
15
|
+
let text: string;
|
|
18
16
|
try {
|
|
19
|
-
|
|
20
|
-
expression,
|
|
21
|
-
variables,
|
|
22
|
-
temporary,
|
|
23
|
-
localsValues,
|
|
24
|
-
transient,
|
|
17
|
+
text = display(
|
|
18
|
+
evaluate(expression, variables, temporary, localsValues, transient),
|
|
25
19
|
);
|
|
26
|
-
display = value == null ? '' : String(value);
|
|
27
20
|
} catch {
|
|
28
|
-
|
|
21
|
+
text = `{error: ${expression}}`;
|
|
29
22
|
}
|
|
30
|
-
|
|
31
|
-
if (className || id)
|
|
32
|
-
return (
|
|
33
|
-
<span
|
|
34
|
-
id={id}
|
|
35
|
-
class={className}
|
|
36
|
-
>
|
|
37
|
-
{display}
|
|
38
|
-
</span>
|
|
39
|
-
);
|
|
40
|
-
return <>{display}</>;
|
|
23
|
+
return wrapContent(className, id, text);
|
|
41
24
|
}
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
2
|
import { MacroError } from './MacroError';
|
|
3
3
|
import type { Branch } from '../../markup/ast';
|
|
4
|
+
import { selectBranch } from './branches';
|
|
5
|
+
import { wrapContent } from './display';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The {if}/{elseif}/{else} branch whose condition holds, else the {else}
|
|
9
|
+
* branch, else null: the chain ends at its first bare branch. Throws what a
|
|
10
|
+
* condition throws.
|
|
11
|
+
*/
|
|
12
|
+
function selectIfBranch(
|
|
13
|
+
branches: Branch[],
|
|
14
|
+
evaluate: (expr: string) => unknown,
|
|
15
|
+
): Branch | null {
|
|
16
|
+
const end = branches.findIndex((branch) => branch.rawArgs === '');
|
|
17
|
+
return selectBranch(end < 0 ? branches : branches.slice(0, end + 1), (expr) =>
|
|
18
|
+
Boolean(evaluate(expr)),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
4
21
|
|
|
5
22
|
defineMacro({
|
|
6
23
|
name: 'if',
|
|
@@ -8,49 +25,26 @@ defineMacro({
|
|
|
8
25
|
interpolate: true,
|
|
9
26
|
merged: true,
|
|
10
27
|
render({ branches = [] }, ctx) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
{children}
|
|
22
|
-
</span>
|
|
23
|
-
);
|
|
24
|
-
return <>{children}</>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
for (const branch of branches) {
|
|
28
|
-
if (branch.rawArgs === '') {
|
|
29
|
-
return renderBranch(branch);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
if (ctx.evaluate!(branch.rawArgs)) {
|
|
34
|
-
return renderBranch(branch);
|
|
35
|
-
}
|
|
36
|
-
} catch (err) {
|
|
37
|
-
return (
|
|
38
|
-
<MacroError
|
|
39
|
-
macro="if"
|
|
40
|
-
error={err}
|
|
41
|
-
/>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
28
|
+
let branch: Branch | null;
|
|
29
|
+
try {
|
|
30
|
+
branch = selectIfBranch(branches, ctx.evaluate!);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
return (
|
|
33
|
+
<MacroError
|
|
34
|
+
macro="if"
|
|
35
|
+
error={err}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
44
38
|
}
|
|
45
|
-
|
|
46
|
-
return
|
|
39
|
+
if (!branch) return null;
|
|
40
|
+
return wrapContent(
|
|
41
|
+
ctx.resolve!(branch.className),
|
|
42
|
+
ctx.resolve!(branch.id),
|
|
43
|
+
ctx.renderNodes(branch.children),
|
|
44
|
+
);
|
|
47
45
|
},
|
|
48
46
|
text({ branches = [] }, ctx) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return ctx.renderText(branch.children);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return '';
|
|
47
|
+
const branch = selectIfBranch(branches, ctx.evaluate);
|
|
48
|
+
return branch ? ctx.renderText(branch.children) : '';
|
|
55
49
|
},
|
|
56
50
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useStoryStore } from '../../store';
|
|
2
|
+
import { useStoryFields } from '../../hooks/use-story-fields';
|
|
2
3
|
import { tokenize } from '../../markup/tokenizer';
|
|
3
4
|
import { buildAST } from '../../markup/ast';
|
|
4
5
|
import { NobrContext } from '../../markup/render';
|
|
@@ -16,7 +17,7 @@ defineMacro({
|
|
|
16
17
|
{ name: 'passage', type: 'expression', required: true },
|
|
17
18
|
],
|
|
18
19
|
render(_props, ctx) {
|
|
19
|
-
const storyData =
|
|
20
|
+
const { storyData } = useStoryFields('storyData');
|
|
20
21
|
|
|
21
22
|
const { inline } = ctx.args;
|
|
22
23
|
const passageName = evaluatePassageName(ctx.args.passage, ctx.evaluate!);
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
|
+
import { VARIABLE_PARAMETER, useVariableAction } from './input-macro';
|
|
3
|
+
import { display } from './display';
|
|
2
4
|
|
|
3
5
|
defineMacro({
|
|
4
6
|
name: 'listbox',
|
|
5
7
|
subMacros: ['option'],
|
|
6
8
|
storeVar: true,
|
|
7
|
-
parameters: [
|
|
9
|
+
parameters: [VARIABLE_PARAMETER],
|
|
8
10
|
render({ children = [] }, ctx) {
|
|
9
11
|
const options = ctx.extractOptions(children);
|
|
10
12
|
|
|
11
|
-
ctx
|
|
13
|
+
useVariableAction(ctx, {
|
|
12
14
|
type: 'listbox',
|
|
13
|
-
key: `$${ctx.varName}`,
|
|
14
|
-
authorId: ctx.id,
|
|
15
15
|
label: ctx.varName!,
|
|
16
|
-
variable: ctx.varName,
|
|
17
16
|
options,
|
|
18
|
-
value: ctx.value,
|
|
19
17
|
perform: (v) => {
|
|
20
18
|
if (v !== undefined) ctx.setValue!(String(v));
|
|
21
19
|
},
|
|
@@ -25,7 +23,7 @@ defineMacro({
|
|
|
25
23
|
<select
|
|
26
24
|
id={ctx.id}
|
|
27
25
|
class={ctx.cls}
|
|
28
|
-
value={
|
|
26
|
+
value={display(ctx.value)}
|
|
29
27
|
onChange={(e) => ctx.setValue!((e.target as HTMLSelectElement).value)}
|
|
30
28
|
>
|
|
31
29
|
{options.map((opt) => (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
2
|
import { MacroError } from './MacroError';
|
|
3
|
+
import { display } from './display';
|
|
3
4
|
|
|
4
5
|
defineMacro({
|
|
5
6
|
name: 'print',
|
|
@@ -8,9 +9,7 @@ defineMacro({
|
|
|
8
9
|
merged: true,
|
|
9
10
|
render({ rawArgs }, ctx) {
|
|
10
11
|
try {
|
|
11
|
-
|
|
12
|
-
const display = result == null ? '' : String(result);
|
|
13
|
-
return ctx.wrap(display);
|
|
12
|
+
return ctx.wrap(display(ctx.evaluate!(rawArgs)));
|
|
14
13
|
} catch (err) {
|
|
15
14
|
return (
|
|
16
15
|
<MacroError
|
|
@@ -21,7 +20,6 @@ defineMacro({
|
|
|
21
20
|
}
|
|
22
21
|
},
|
|
23
22
|
text({ rawArgs }, ctx) {
|
|
24
|
-
|
|
25
|
-
return result == null ? '' : String(result);
|
|
23
|
+
return display(ctx.evaluate(rawArgs));
|
|
26
24
|
},
|
|
27
25
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
|
+
import { VARIABLE_PARAMETER, useVariableAction } from './input-macro';
|
|
2
3
|
|
|
3
4
|
defineMacro({
|
|
4
5
|
name: 'radiobutton',
|
|
5
6
|
storeVar: true,
|
|
6
7
|
parameters: [
|
|
7
|
-
|
|
8
|
+
VARIABLE_PARAMETER,
|
|
8
9
|
{ name: 'value', type: 'text', required: true },
|
|
9
10
|
{ name: 'label', type: 'text' },
|
|
10
11
|
],
|
|
@@ -12,13 +13,10 @@ defineMacro({
|
|
|
12
13
|
const radioValue = ctx.args.value ?? '';
|
|
13
14
|
const label = ctx.args.label ?? '';
|
|
14
15
|
|
|
15
|
-
ctx
|
|
16
|
+
useVariableAction(ctx, {
|
|
16
17
|
type: 'radiobutton',
|
|
17
18
|
key: `$${ctx.varName}:${radioValue}`,
|
|
18
|
-
authorId: ctx.id,
|
|
19
19
|
label: label || radioValue,
|
|
20
|
-
variable: ctx.varName,
|
|
21
|
-
value: ctx.value,
|
|
22
20
|
perform: () => ctx.setValue!(radioValue),
|
|
23
21
|
});
|
|
24
22
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createContext } from 'preact';
|
|
2
2
|
import { defineMacro } from '../../define-macro';
|
|
3
|
+
import { DELAY_PARAMETER } from './macro-args';
|
|
3
4
|
|
|
4
5
|
export const RepeatContext = createContext<{ stop: () => void }>({
|
|
5
6
|
stop: () => {},
|
|
@@ -9,7 +10,7 @@ defineMacro({
|
|
|
9
10
|
name: 'repeat',
|
|
10
11
|
block: true,
|
|
11
12
|
interpolate: true,
|
|
12
|
-
parameters: [
|
|
13
|
+
parameters: [DELAY_PARAMETER],
|
|
13
14
|
render({ children = [] }, ctx) {
|
|
14
15
|
const { useState, useEffect, useCallback } = ctx.hooks;
|
|
15
16
|
|
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
2
|
import { MacroError } from './MacroError';
|
|
3
3
|
import type { Branch } from '../../markup/ast';
|
|
4
|
+
import { selectBranch } from './branches';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The first {case} branch whose value equals `value`, else the {default}
|
|
7
8
|
* branch, else null. Throws what a case expression throws.
|
|
8
9
|
*/
|
|
9
|
-
function
|
|
10
|
+
function selectCase(
|
|
10
11
|
value: unknown,
|
|
11
12
|
branches: Branch[],
|
|
12
13
|
evaluate: (expr: string) => unknown,
|
|
13
14
|
): Branch | null {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const branch = branches[i]!;
|
|
17
|
-
if (branch.rawArgs === '') {
|
|
18
|
-
defaultBranch = branch;
|
|
19
|
-
} else if (value === evaluate(branch.rawArgs)) {
|
|
20
|
-
return branch;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return defaultBranch;
|
|
15
|
+
// The first branch holds the content before the first {case}
|
|
16
|
+
return selectBranch(branches.slice(1), (expr) => value === evaluate(expr));
|
|
24
17
|
}
|
|
25
18
|
|
|
26
19
|
defineMacro({
|
|
@@ -43,7 +36,7 @@ defineMacro({
|
|
|
43
36
|
|
|
44
37
|
let branch: Branch | null;
|
|
45
38
|
try {
|
|
46
|
-
branch =
|
|
39
|
+
branch = selectCase(switchValue, branches, ctx.evaluate!);
|
|
47
40
|
} catch (err) {
|
|
48
41
|
return (
|
|
49
42
|
<MacroError
|
|
@@ -56,7 +49,7 @@ defineMacro({
|
|
|
56
49
|
return branch && <>{ctx.renderNodes(branch.children)}</>;
|
|
57
50
|
},
|
|
58
51
|
text({ rawArgs, branches = [] }, ctx) {
|
|
59
|
-
const branch =
|
|
52
|
+
const branch = selectCase(ctx.evaluate(rawArgs), branches, ctx.evaluate);
|
|
60
53
|
return branch ? ctx.renderText(branch.children) : '';
|
|
61
54
|
},
|
|
62
55
|
});
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { parseDelay } from '../../utils/parse-delay';
|
|
2
1
|
import { defineMacro } from '../../define-macro';
|
|
2
|
+
import { DELAY_PARAMETER, parseMacroArgs } from './macro-args';
|
|
3
|
+
import { wrapContent } from './display';
|
|
4
|
+
|
|
5
|
+
/** The parameters of {timed} and of each {next}: the delay before it shows. */
|
|
6
|
+
const TIMED_PARAMETERS = [DELAY_PARAMETER] as const;
|
|
3
7
|
|
|
4
8
|
defineMacro({
|
|
5
9
|
name: 'timed',
|
|
6
10
|
subMacros: ['next'],
|
|
7
11
|
interpolate: true,
|
|
12
|
+
parameters: TIMED_PARAMETERS,
|
|
8
13
|
render({ branches = [] }, ctx) {
|
|
9
14
|
const { useState, useEffect, useMemo } = ctx.hooks;
|
|
10
15
|
|
|
11
16
|
const sections = useMemo(() => {
|
|
12
17
|
return branches.map((branch) => ({
|
|
13
|
-
delay: branch.rawArgs
|
|
18
|
+
delay: parseMacroArgs(branch.rawArgs, TIMED_PARAMETERS).delay ?? 0,
|
|
14
19
|
nodes: branch.children,
|
|
15
20
|
className: branch.className,
|
|
16
21
|
id: branch.id,
|
|
@@ -35,21 +40,12 @@ defineMacro({
|
|
|
35
40
|
if (visibleIndex < 0) return ctx.wrap(null);
|
|
36
41
|
|
|
37
42
|
const section = sections[visibleIndex]!;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
id={sectionId}
|
|
46
|
-
class={sectionClass}
|
|
47
|
-
>
|
|
48
|
-
{content}
|
|
49
|
-
</span>
|
|
50
|
-
) : (
|
|
51
|
-
content
|
|
52
|
-
);
|
|
53
|
-
return ctx.wrap(inner);
|
|
43
|
+
return ctx.wrap(
|
|
44
|
+
wrapContent(
|
|
45
|
+
ctx.resolve!(section.className),
|
|
46
|
+
ctx.resolve!(section.id),
|
|
47
|
+
ctx.renderNodes(section.nodes),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
54
50
|
},
|
|
55
51
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { defineMacro } from '../../define-macro';
|
|
2
|
+
import { DELAY_PARAMETER } from './macro-args';
|
|
2
3
|
|
|
3
4
|
defineMacro({
|
|
4
5
|
name: 'type',
|
|
5
6
|
block: true,
|
|
6
7
|
interpolate: true,
|
|
7
|
-
parameters: [
|
|
8
|
+
parameters: [DELAY_PARAMETER],
|
|
8
9
|
render({ children = [] }, ctx) {
|
|
9
10
|
const { useState, useEffect, useRef } = ctx.hooks;
|
|
10
11
|
const speed = ctx.args.delay ?? 0;
|