@rohal12/spindle 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pkg/format.js +1 -1
- package/package.json +1 -1
- package/src/components/Passage.tsx +12 -3
- package/src/components/StoryInterface.tsx +8 -2
- package/src/components/macros/Include.tsx +8 -1
- package/src/components/macros/Nobr.tsx +15 -0
- package/src/macros/register-builtins.ts +1 -0
- package/src/markup/ast.ts +1 -0
- package/src/markup/render.tsx +17 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMemo, useEffect, useState } from 'preact/hooks';
|
|
2
2
|
import { tokenize } from '../markup/tokenizer';
|
|
3
3
|
import { buildAST } from '../markup/ast';
|
|
4
|
-
import { renderNodes } from '../markup/render';
|
|
4
|
+
import { renderNodes, NobrContext } from '../markup/render';
|
|
5
5
|
import { useStoryStore } from '../store';
|
|
6
6
|
import type { Passage as PassageData } from '../parser';
|
|
7
7
|
import { sourceLocationOf } from '../utils/source-location';
|
|
@@ -9,7 +9,8 @@ import { sourceLocationOf } from '../utils/source-location';
|
|
|
9
9
|
export function renderPassageContent(passage: PassageData) {
|
|
10
10
|
const tokens = tokenize(passage.content);
|
|
11
11
|
const ast = buildAST(tokens);
|
|
12
|
-
|
|
12
|
+
const nobr = passage.tags.includes('nobr');
|
|
13
|
+
return renderNodes(ast, nobr ? { nobr: true } : undefined);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
interface PassageProps {
|
|
@@ -88,7 +89,9 @@ export function Passage({ passage, dataTransition }: PassageProps) {
|
|
|
88
89
|
}
|
|
89
90
|
}, [doneReady, donePassage?.content]);
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
const nobr = passage.tags.includes('nobr');
|
|
93
|
+
|
|
94
|
+
const inner = (
|
|
92
95
|
<div
|
|
93
96
|
class="passage"
|
|
94
97
|
data-passage={passage.name}
|
|
@@ -101,4 +104,10 @@ export function Passage({ passage, dataTransition }: PassageProps) {
|
|
|
101
104
|
{doneContent && <div hidden>{doneContent}</div>}
|
|
102
105
|
</div>
|
|
103
106
|
);
|
|
107
|
+
|
|
108
|
+
return nobr ? (
|
|
109
|
+
<NobrContext.Provider value={true}>{inner}</NobrContext.Provider>
|
|
110
|
+
) : (
|
|
111
|
+
inner
|
|
112
|
+
);
|
|
104
113
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useStoryStore } from '../store';
|
|
2
2
|
import { tokenize } from '../markup/tokenizer';
|
|
3
3
|
import { buildAST } from '../markup/ast';
|
|
4
|
-
import { renderInlineNodes } from '../markup/render';
|
|
4
|
+
import { renderInlineNodes, NobrContext } from '../markup/render';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_MARKUP =
|
|
7
7
|
'<header class="story-menubar">{story-title}{back}{forward}{restart}{quicksave}{quickload}{saves}{settings}</header>\n{passage}';
|
|
@@ -12,11 +12,17 @@ export function StoryInterface() {
|
|
|
12
12
|
const overridePassage = storyData?.passages.get('StoryInterface');
|
|
13
13
|
const markup =
|
|
14
14
|
overridePassage !== undefined ? overridePassage.content : DEFAULT_MARKUP;
|
|
15
|
+
const nobr = overridePassage?.tags.includes('nobr') ?? false;
|
|
15
16
|
|
|
16
17
|
try {
|
|
17
18
|
const tokens = tokenize(markup);
|
|
18
19
|
const ast = buildAST(tokens);
|
|
19
|
-
|
|
20
|
+
const rendered = <>{renderInlineNodes(ast)}</>;
|
|
21
|
+
return nobr ? (
|
|
22
|
+
<NobrContext.Provider value={true}>{rendered}</NobrContext.Provider>
|
|
23
|
+
) : (
|
|
24
|
+
rendered
|
|
25
|
+
);
|
|
20
26
|
} catch (err) {
|
|
21
27
|
return (
|
|
22
28
|
<span class="error">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useStoryStore } from '../../store';
|
|
2
2
|
import { tokenize } from '../../markup/tokenizer';
|
|
3
3
|
import { buildAST } from '../../markup/ast';
|
|
4
|
+
import { NobrContext } from '../../markup/render';
|
|
4
5
|
import { defineMacro } from '../../define-macro';
|
|
5
6
|
|
|
6
7
|
defineMacro({
|
|
@@ -33,10 +34,16 @@ defineMacro({
|
|
|
33
34
|
);
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
const nobr = passage.tags.includes('nobr');
|
|
36
38
|
const tokens = tokenize(passage.content);
|
|
37
39
|
const ast = buildAST(tokens);
|
|
38
|
-
const content = inline
|
|
40
|
+
const content = inline
|
|
41
|
+
? ctx.renderInlineNodes(ast)
|
|
42
|
+
: ctx.renderNodes(ast, nobr ? { nobr: true } : undefined);
|
|
39
43
|
|
|
44
|
+
if (nobr) {
|
|
45
|
+
return ctx.wrap(ctx.h(NobrContext.Provider, { value: true }, content));
|
|
46
|
+
}
|
|
40
47
|
return ctx.wrap(content);
|
|
41
48
|
},
|
|
42
49
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineMacro } from '../../define-macro';
|
|
2
|
+
import { NobrContext } from '../../markup/render';
|
|
3
|
+
|
|
4
|
+
defineMacro({
|
|
5
|
+
name: 'nobr',
|
|
6
|
+
render({ children = [] }, ctx) {
|
|
7
|
+
return ctx.wrap(
|
|
8
|
+
ctx.h(
|
|
9
|
+
NobrContext.Provider,
|
|
10
|
+
{ value: true },
|
|
11
|
+
ctx.renderNodes(children, { nobr: true }),
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -20,6 +20,7 @@ import '../components/macros/Dialog';
|
|
|
20
20
|
import '../components/macros/SaveManager';
|
|
21
21
|
import '../components/macros/SettingsControls';
|
|
22
22
|
import '../components/macros/Include';
|
|
23
|
+
import '../components/macros/Nobr';
|
|
23
24
|
import '../components/macros/Goto';
|
|
24
25
|
import '../components/macros/Unset';
|
|
25
26
|
import '../components/macros/Textbox';
|
package/src/markup/ast.ts
CHANGED
package/src/markup/render.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createContext } from 'preact';
|
|
2
|
+
import { useContext } from 'preact/hooks';
|
|
2
3
|
import { VarDisplay } from '../components/macros/VarDisplay';
|
|
3
4
|
import { WidgetInvocation } from '../components/macros/WidgetInvocation';
|
|
4
5
|
import { getWidget } from '../widgets/widget-registry';
|
|
@@ -21,6 +22,7 @@ const defaultUpdater: LocalsUpdater = {
|
|
|
21
22
|
|
|
22
23
|
export const LocalsValuesContext = createContext<Record<string, unknown>>({});
|
|
23
24
|
export const LocalsUpdateContext = createContext<LocalsUpdater>(defaultUpdater);
|
|
25
|
+
export const NobrContext = createContext(false);
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Convert an HTML string (from micromark) to Preact VNodes,
|
|
@@ -29,9 +31,15 @@ export const LocalsUpdateContext = createContext<LocalsUpdater>(defaultUpdater);
|
|
|
29
31
|
function htmlToPreact(
|
|
30
32
|
html: string,
|
|
31
33
|
components: preact.ComponentChildren[],
|
|
34
|
+
nobr = false,
|
|
32
35
|
): preact.ComponentChildren {
|
|
33
36
|
const temp = document.createElement('div');
|
|
34
37
|
temp.innerHTML = html.trim();
|
|
38
|
+
if (nobr) {
|
|
39
|
+
for (const p of Array.from(temp.querySelectorAll(':scope > p'))) {
|
|
40
|
+
p.replaceWith(...Array.from(p.childNodes));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
35
43
|
const children = Array.from(temp.childNodes).map((child, i) =>
|
|
36
44
|
convertDomNode(child, i, components),
|
|
37
45
|
);
|
|
@@ -74,6 +82,7 @@ function convertDomNode(
|
|
|
74
82
|
|
|
75
83
|
function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
|
|
76
84
|
const resolve = useInterpolate();
|
|
85
|
+
const nobr = useContext(NobrContext);
|
|
77
86
|
const attrs: Record<string, unknown> = {};
|
|
78
87
|
for (const [k, v] of Object.entries(node.attributes)) {
|
|
79
88
|
attrs[k] = resolve(v) ?? v;
|
|
@@ -81,7 +90,9 @@ function HtmlNodeRenderer({ node }: { node: HtmlNode }) {
|
|
|
81
90
|
return h(
|
|
82
91
|
node.tag,
|
|
83
92
|
attrs,
|
|
84
|
-
node.children.length > 0
|
|
93
|
+
node.children.length > 0
|
|
94
|
+
? renderNodes(node.children, nobr ? { nobr: true } : undefined)
|
|
95
|
+
: undefined,
|
|
85
96
|
);
|
|
86
97
|
}
|
|
87
98
|
|
|
@@ -210,7 +221,10 @@ function getVariableTextValue(node: VariableNode): string {
|
|
|
210
221
|
* After micromark processes the combined string, the HTML is parsed back into
|
|
211
222
|
* Preact VNodes with placeholders replaced by the real rendered components.
|
|
212
223
|
*/
|
|
213
|
-
export function renderNodes(
|
|
224
|
+
export function renderNodes(
|
|
225
|
+
nodes: ASTNode[],
|
|
226
|
+
options?: { nobr?: boolean },
|
|
227
|
+
): preact.ComponentChildren {
|
|
214
228
|
if (nodes.length === 0) return null;
|
|
215
229
|
|
|
216
230
|
// If there's no text at all, render nodes directly without markdown
|
|
@@ -241,5 +255,5 @@ export function renderNodes(nodes: ASTNode[]): preact.ComponentChildren {
|
|
|
241
255
|
const html = markdownToHtml(combined);
|
|
242
256
|
|
|
243
257
|
// Convert HTML to Preact VNodes, replacing placeholders with components
|
|
244
|
-
return htmlToPreact(html, components);
|
|
258
|
+
return htmlToPreact(html, components, options?.nobr);
|
|
245
259
|
}
|