squiffy-runtime 6.0.0-alpha.2 → 6.0.0-alpha.4
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.
|
@@ -9,9 +9,20 @@ interface SquiffyApi {
|
|
|
9
9
|
restart: () => void;
|
|
10
10
|
get: (attribute: string) => any;
|
|
11
11
|
set: (attribute: string, value: any) => void;
|
|
12
|
+
clickLink: (link: HTMLElement) => void;
|
|
13
|
+
}
|
|
14
|
+
interface SquiffyJsFunctionApi {
|
|
15
|
+
get: (attribute: string) => any;
|
|
16
|
+
set: (attribute: string, value: any) => void;
|
|
17
|
+
ui: {
|
|
18
|
+
transition: (f: any) => void;
|
|
19
|
+
};
|
|
20
|
+
story: {
|
|
21
|
+
go: (section: string) => void;
|
|
22
|
+
};
|
|
12
23
|
}
|
|
13
24
|
interface Story {
|
|
14
|
-
js: (() => void)[];
|
|
25
|
+
js: ((squiffy: SquiffyJsFunctionApi, get: (attribute: string) => any, set: (attribute: string, value: any) => void) => void)[];
|
|
15
26
|
start: string;
|
|
16
27
|
id?: string | null;
|
|
17
28
|
sections: Record<string, Section>;
|
package/dist/squiffy.runtime.js
CHANGED
|
@@ -29,68 +29,60 @@ export const init = (options) => {
|
|
|
29
29
|
return null;
|
|
30
30
|
return JSON.parse(result);
|
|
31
31
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
function handleLink(link) {
|
|
33
|
+
if (link.classList.contains('disabled'))
|
|
34
|
+
return;
|
|
35
|
+
let passage = link.getAttribute('data-passage');
|
|
36
|
+
let section = link.getAttribute('data-section');
|
|
37
|
+
const rotateAttr = link.getAttribute('data-rotate');
|
|
38
|
+
const sequenceAttr = link.getAttribute('data-sequence');
|
|
39
|
+
const rotateOrSequenceAttr = rotateAttr || sequenceAttr;
|
|
40
|
+
if (passage) {
|
|
41
|
+
disableLink(link);
|
|
42
|
+
set('_turncount', get('_turncount') + 1);
|
|
43
|
+
passage = processLink(passage);
|
|
41
44
|
if (passage) {
|
|
42
|
-
disableLink(link);
|
|
43
|
-
set('_turncount', get('_turncount') + 1);
|
|
44
|
-
passage = processLink(passage);
|
|
45
|
-
if (passage) {
|
|
46
|
-
currentSectionElement?.appendChild(document.createElement('hr'));
|
|
47
|
-
showPassage(passage);
|
|
48
|
-
}
|
|
49
|
-
const turnPassage = '@' + get('_turncount');
|
|
50
|
-
if (currentSection.passages) {
|
|
51
|
-
if (turnPassage in currentSection.passages) {
|
|
52
|
-
showPassage(turnPassage);
|
|
53
|
-
}
|
|
54
|
-
if ('@last' in currentSection.passages && get('_turncount') >= (currentSection.passageCount || 0)) {
|
|
55
|
-
showPassage('@last');
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else if (section) {
|
|
60
45
|
currentSectionElement?.appendChild(document.createElement('hr'));
|
|
61
|
-
|
|
62
|
-
section = processLink(section);
|
|
63
|
-
if (section) {
|
|
64
|
-
go(section);
|
|
65
|
-
}
|
|
46
|
+
showPassage(passage);
|
|
66
47
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
link.setAttribute(dataAttribute, result[1] || '');
|
|
72
|
-
if (!result[1]) {
|
|
73
|
-
disableLink(link);
|
|
48
|
+
const turnPassage = '@' + get('_turncount');
|
|
49
|
+
if (currentSection.passages) {
|
|
50
|
+
if (turnPassage in currentSection.passages) {
|
|
51
|
+
showPassage(turnPassage);
|
|
74
52
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
set(attribute, result[0]);
|
|
53
|
+
if ('@last' in currentSection.passages && get('_turncount') >= (currentSection.passageCount || 0)) {
|
|
54
|
+
showPassage('@last');
|
|
78
55
|
}
|
|
79
|
-
save();
|
|
80
56
|
}
|
|
81
57
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
58
|
+
else if (section) {
|
|
59
|
+
currentSectionElement?.appendChild(document.createElement('hr'));
|
|
60
|
+
disableLink(link);
|
|
61
|
+
section = processLink(section);
|
|
62
|
+
if (section) {
|
|
63
|
+
go(section);
|
|
86
64
|
}
|
|
87
65
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
66
|
+
else if (rotateOrSequenceAttr) {
|
|
67
|
+
const result = rotate(rotateOrSequenceAttr, rotateAttr ? link.innerText : '');
|
|
68
|
+
link.innerHTML = result[0].replace(/"/g, '"').replace(/'/g, '\'');
|
|
69
|
+
const dataAttribute = rotateAttr ? 'data-rotate' : 'data-sequence';
|
|
70
|
+
link.setAttribute(dataAttribute, result[1] || '');
|
|
71
|
+
if (!result[1]) {
|
|
72
|
+
disableLink(link);
|
|
73
|
+
}
|
|
74
|
+
const attribute = link.getAttribute('data-attribute');
|
|
75
|
+
if (attribute) {
|
|
76
|
+
set(attribute, result[0]);
|
|
77
|
+
}
|
|
78
|
+
save();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function handleClick(event) {
|
|
82
|
+
const target = event.target;
|
|
83
|
+
if (target.classList.contains('squiffy-link')) {
|
|
84
|
+
handleLink(target);
|
|
85
|
+
}
|
|
94
86
|
}
|
|
95
87
|
function disableLink(link) {
|
|
96
88
|
link.classList.add('disabled');
|
|
@@ -238,7 +230,17 @@ export const init = (options) => {
|
|
|
238
230
|
processAttributes(section.attributes.map(line => line.replace(/^random\s*:\s*(\w+)\s*=\s*(.+)/i, (line, attr, options) => (options = options.split("|")) ? attr + " = " + options[Math.floor(Math.random() * options.length)] : line)));
|
|
239
231
|
}
|
|
240
232
|
if (section.jsIndex !== undefined) {
|
|
241
|
-
|
|
233
|
+
const squiffy = {
|
|
234
|
+
get: get,
|
|
235
|
+
set: set,
|
|
236
|
+
ui: {
|
|
237
|
+
transition: ui.transition,
|
|
238
|
+
},
|
|
239
|
+
story: {
|
|
240
|
+
go: go,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
story.js[section.jsIndex](squiffy, get, set);
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
function showPassage(passageName) {
|
|
@@ -585,11 +587,17 @@ export const init = (options) => {
|
|
|
585
587
|
if (settings.scroll === 'element') {
|
|
586
588
|
outputElement.style.overflowY = 'auto';
|
|
587
589
|
}
|
|
588
|
-
|
|
590
|
+
outputElement.addEventListener('click', handleClick);
|
|
591
|
+
outputElement.addEventListener('keypress', function (event) {
|
|
592
|
+
if (event.key !== "Enter")
|
|
593
|
+
return;
|
|
594
|
+
handleClick(event);
|
|
595
|
+
});
|
|
589
596
|
begin();
|
|
590
597
|
return {
|
|
591
598
|
restart: restart,
|
|
592
599
|
get: get,
|
|
593
600
|
set: set,
|
|
601
|
+
clickLink: handleLink
|
|
594
602
|
};
|
|
595
603
|
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squiffy-runtime",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.4",
|
|
4
4
|
"main": "dist/squiffy.runtime.js",
|
|
5
5
|
"types": "dist/squiffy.runtime.d.ts",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "vitest",
|
|
8
8
|
"build": "tsc"
|
|
9
9
|
},
|
|
10
10
|
"author": "Alex Warren",
|
|
11
|
+
"contributors": [
|
|
12
|
+
"CrisisSDK",
|
|
13
|
+
"mrangel",
|
|
14
|
+
"Luis Felipe Morales"
|
|
15
|
+
],
|
|
11
16
|
"license": "MIT",
|
|
12
17
|
"description": "",
|
|
13
18
|
"devDependencies": {
|
|
14
|
-
"
|
|
19
|
+
"@types/jsdom": "^21.1.7",
|
|
20
|
+
"global-jsdom": "^25.0.0",
|
|
21
|
+
"jsdom": "^25.0.0",
|
|
22
|
+
"squiffy-compiler": "^6.0.0-alpha.2",
|
|
23
|
+
"typescript": "^5.6.2",
|
|
24
|
+
"vitest": "^2.1.1"
|
|
15
25
|
}
|
|
16
26
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`"Hello world" script should run 1`] = `
|
|
4
|
+
"
|
|
5
|
+
<div id="squiffy-section-1"><div><p>Hello world</p></div></div>"
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
exports[`Click a passage link 1`] = `
|
|
9
|
+
"
|
|
10
|
+
<div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
|
|
11
|
+
<p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
|
|
12
|
+
<p>This should be <a class="squiffy-link link-passage" data-passage="passage" role="link" tabindex="0">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
|
|
13
|
+
<p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
|
|
14
|
+
<p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
|
|
15
|
+
<p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
|
|
16
|
+
<p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
|
|
17
|
+
<p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div>"
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
exports[`Click a passage link 2`] = `
|
|
21
|
+
"
|
|
22
|
+
<div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
|
|
23
|
+
<p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
|
|
24
|
+
<p>This should be <a class="squiffy-link link-passage disabled" data-passage="passage" role="link" tabindex="-1">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
|
|
25
|
+
<p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
|
|
26
|
+
<p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
|
|
27
|
+
<p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
|
|
28
|
+
<p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
|
|
29
|
+
<p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div><hr><div><p>Here's some text for the passage.</p></div></div>"
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
exports[`Click a section link 1`] = `
|
|
33
|
+
"
|
|
34
|
+
<div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
|
|
35
|
+
<p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
|
|
36
|
+
<p>This should be <a class="squiffy-link link-passage" data-passage="passage" role="link" tabindex="0">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
|
|
37
|
+
<p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
|
|
38
|
+
<p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
|
|
39
|
+
<p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
|
|
40
|
+
<p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
|
|
41
|
+
<p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div>"
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
exports[`Click a section link 2`] = `
|
|
45
|
+
"
|
|
46
|
+
<div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
|
|
47
|
+
<p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
|
|
48
|
+
<p>This should be <a class="squiffy-link link-passage disabled" data-passage="passage" role="link" tabindex="-1">a link to a passage</a>. Here's <a class="squiffy-link link-passage disabled" data-passage="passage2" role="link" tabindex="-1">another one</a>.</p>
|
|
49
|
+
<p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage disabled" data-passage="link" role="link" tabindex="-1">link</a> and this <a class="squiffy-link link-section disabled" data-section="section" role="link" tabindex="-1">section</a>.</p>
|
|
50
|
+
<p>And this goes to the <a class="squiffy-link link-section disabled" data-section="section2" role="link" tabindex="-1">next section</a>.</p>
|
|
51
|
+
<p>This line has links to <a class="squiffy-link link-section disabled" data-section="section 3" role="link" tabindex="-1">section 3</a> and <a class="squiffy-link link-section disabled" data-section="section four" role="link" tabindex="-1">section 4</a>.</p>
|
|
52
|
+
<p>This line has links to <a class="squiffy-link link-passage disabled" data-passage="passage 3" role="link" tabindex="-1">passage 3</a> and <a class="squiffy-link link-passage disabled" data-passage="passage four" role="link" tabindex="-1">passage 4</a>.</p>
|
|
53
|
+
<p>Oh look - <a class="squiffy-link link-passage disabled" data-passage="it's a passage with an apostrophe" role="link" tabindex="-1">it's a passage with an apostrophe</a>.</p></div><hr></div><div id="squiffy-section-2"><div><p>Another section is here.</p></div></div>"
|
|
54
|
+
`;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { expect, test, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import globalJsdom from 'global-jsdom';
|
|
4
|
+
import { init } from './squiffy.runtime.js';
|
|
5
|
+
import { compile } from 'squiffy-compiler';
|
|
6
|
+
|
|
7
|
+
const html = `
|
|
8
|
+
<!DOCTYPE html>
|
|
9
|
+
<html>
|
|
10
|
+
<head>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="squiffy">
|
|
14
|
+
</div>
|
|
15
|
+
<div id="test">
|
|
16
|
+
</div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const initScript = async (script: string) => {
|
|
22
|
+
globalJsdom(html);
|
|
23
|
+
const element = document.getElementById('squiffy');
|
|
24
|
+
|
|
25
|
+
if (!element) {
|
|
26
|
+
throw new Error('Element not found');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const compileResult = await compile({
|
|
30
|
+
scriptBaseFilename: "filename.squiffy", // TODO: This shouldn't be required
|
|
31
|
+
script: script,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!compileResult.success) {
|
|
35
|
+
throw new Error('Compile failed');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const story = compileResult.output.story;
|
|
39
|
+
const js = compileResult.output.js.map(jsLines => new Function('squiffy', 'get', 'set', jsLines.join('\n')));
|
|
40
|
+
|
|
41
|
+
const squiffyApi = init({
|
|
42
|
+
element: element,
|
|
43
|
+
story: {
|
|
44
|
+
js: js as any,
|
|
45
|
+
...story,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
squiffyApi,
|
|
51
|
+
element
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const findLink = (element: HTMLElement, linkType: string, linkText: string, onlyEnabled: boolean = false) => {
|
|
56
|
+
const links = element.querySelectorAll(`a.squiffy-link.link-${linkType}`);
|
|
57
|
+
return Array.from(links).find(link => link.textContent === linkText && (onlyEnabled ? !link.classList.contains("disabled") : true)) as HTMLElement;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getTestOutput = () => {
|
|
61
|
+
const testElement = document.getElementById('test');
|
|
62
|
+
if (!testElement) {
|
|
63
|
+
throw new Error('Test element not found');
|
|
64
|
+
}
|
|
65
|
+
return testElement.innerText;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let cleanup: { (): void };
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
cleanup = globalJsdom();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
cleanup();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('"Hello world" script should run', async () => {
|
|
79
|
+
const { element } = await initScript("Hello world");
|
|
80
|
+
expect(element.innerHTML).toMatchSnapshot();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('Click a section link', async () => {
|
|
84
|
+
const script = await fs.readFile('../examples/test/example.squiffy', 'utf-8');
|
|
85
|
+
const { squiffyApi, element } = await initScript(script);
|
|
86
|
+
expect(element.innerHTML).toMatchSnapshot();
|
|
87
|
+
|
|
88
|
+
expect(element.querySelectorAll('a.squiffy-link').length).toBe(10);
|
|
89
|
+
const linkToPassage = findLink(element, 'passage', 'a link to a passage');
|
|
90
|
+
const section3Link = findLink(element, 'section', 'section 3');
|
|
91
|
+
|
|
92
|
+
expect(linkToPassage).toBeDefined();
|
|
93
|
+
expect(section3Link).toBeDefined();
|
|
94
|
+
expect(linkToPassage.classList).not.toContain('disabled');
|
|
95
|
+
expect(section3Link.classList).not.toContain('disabled');
|
|
96
|
+
|
|
97
|
+
squiffyApi.clickLink(section3Link);
|
|
98
|
+
|
|
99
|
+
expect(linkToPassage.classList).toContain('disabled');
|
|
100
|
+
expect(section3Link.classList).toContain('disabled');
|
|
101
|
+
expect(element.innerHTML).toMatchSnapshot();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('Click a passage link', async () => {
|
|
105
|
+
const script = await fs.readFile('../examples/test/example.squiffy', 'utf-8');
|
|
106
|
+
const { squiffyApi, element } = await initScript(script);
|
|
107
|
+
expect(element.innerHTML).toMatchSnapshot();
|
|
108
|
+
|
|
109
|
+
expect(element.querySelectorAll('a.squiffy-link').length).toBe(10);
|
|
110
|
+
const linkToPassage = findLink(element, 'passage', 'a link to a passage');
|
|
111
|
+
const section3Link = findLink(element, 'section', 'section 3');
|
|
112
|
+
|
|
113
|
+
expect(linkToPassage).toBeDefined();
|
|
114
|
+
expect(section3Link).toBeDefined();
|
|
115
|
+
expect(linkToPassage.classList).not.toContain('disabled');
|
|
116
|
+
expect(section3Link.classList).not.toContain('disabled');
|
|
117
|
+
|
|
118
|
+
squiffyApi.clickLink(linkToPassage);
|
|
119
|
+
|
|
120
|
+
expect(linkToPassage.classList).toContain('disabled');
|
|
121
|
+
expect(section3Link.classList).not.toContain('disabled');
|
|
122
|
+
expect(element.innerHTML).toMatchSnapshot();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('Run JavaScript functions', async () => {
|
|
126
|
+
const script = `
|
|
127
|
+
document.getElementById('test').innerText = 'Initial JavaScript';
|
|
128
|
+
@set some_string = some_value
|
|
129
|
+
@set some_number = 5
|
|
130
|
+
|
|
131
|
+
+++Continue...
|
|
132
|
+
document.getElementById('test').innerText = "Value: " + get("some_number");
|
|
133
|
+
+++Continue...
|
|
134
|
+
document.getElementById('test').innerText = "Value: " + get("some_string");
|
|
135
|
+
set("some_number", 10);
|
|
136
|
+
+++Continue...
|
|
137
|
+
document.getElementById('test').innerText = "Value: " + get("some_number");
|
|
138
|
+
+++Continue...
|
|
139
|
+
@inc some_number
|
|
140
|
+
+++Continue...
|
|
141
|
+
document.getElementById('test').innerText = "Value: " + get("some_number");
|
|
142
|
+
+++Continue...
|
|
143
|
+
squiffy.story.go("other section");
|
|
144
|
+
[[other section]]:
|
|
145
|
+
document.getElementById('test').innerText = "In other section";
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const clickContinue = () => {
|
|
149
|
+
const continueLink = findLink(element, 'section', 'Continue...', true);
|
|
150
|
+
expect(continueLink).toBeDefined();
|
|
151
|
+
squiffyApi.clickLink(continueLink);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const { squiffyApi, element } = await initScript(script);
|
|
155
|
+
|
|
156
|
+
expect(getTestOutput()).toBe('Initial JavaScript');
|
|
157
|
+
clickContinue();
|
|
158
|
+
|
|
159
|
+
expect(getTestOutput()).toBe('Value: 5');
|
|
160
|
+
clickContinue();
|
|
161
|
+
|
|
162
|
+
expect(getTestOutput()).toBe('Value: some_value');
|
|
163
|
+
clickContinue();
|
|
164
|
+
|
|
165
|
+
expect(getTestOutput()).toBe('Value: 10');
|
|
166
|
+
|
|
167
|
+
clickContinue();
|
|
168
|
+
clickContinue();
|
|
169
|
+
expect(getTestOutput()).toBe('Value: 11');
|
|
170
|
+
|
|
171
|
+
clickContinue();
|
|
172
|
+
expect(getTestOutput()).toBe('In other section');
|
|
173
|
+
});
|
package/src/squiffy.runtime.ts
CHANGED
|
@@ -16,10 +16,28 @@ interface SquiffyApi {
|
|
|
16
16
|
restart: () => void;
|
|
17
17
|
get: (attribute: string) => any;
|
|
18
18
|
set: (attribute: string, value: any) => void;
|
|
19
|
+
clickLink: (link: HTMLElement) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Previous versions of Squiffy had "squiffy", "get" and "set" as globals - we now pass these directly into JS functions.
|
|
23
|
+
// We may tidy up this API at some point, though that would be a breaking change.
|
|
24
|
+
interface SquiffyJsFunctionApi {
|
|
25
|
+
get: (attribute: string) => any;
|
|
26
|
+
set: (attribute: string, value: any) => void;
|
|
27
|
+
ui: {
|
|
28
|
+
transition: (f: any) => void;
|
|
29
|
+
};
|
|
30
|
+
story: {
|
|
31
|
+
go: (section: string) => void;
|
|
32
|
+
};
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
interface Story {
|
|
22
|
-
js: ((
|
|
36
|
+
js: ((
|
|
37
|
+
squiffy: SquiffyJsFunctionApi,
|
|
38
|
+
get: (attribute: string) => any,
|
|
39
|
+
set: (attribute: string, value: any) => void
|
|
40
|
+
) => void)[];
|
|
23
41
|
start: string;
|
|
24
42
|
id?: string | null;
|
|
25
43
|
sections: Record<string, Section>;
|
|
@@ -73,68 +91,60 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
73
91
|
return JSON.parse(result);
|
|
74
92
|
}
|
|
75
93
|
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
function handleLink(link: HTMLElement) {
|
|
95
|
+
if (link.classList.contains('disabled')) return;
|
|
96
|
+
let passage = link.getAttribute('data-passage');
|
|
97
|
+
let section = link.getAttribute('data-section');
|
|
98
|
+
const rotateAttr = link.getAttribute('data-rotate');
|
|
99
|
+
const sequenceAttr = link.getAttribute('data-sequence');
|
|
100
|
+
const rotateOrSequenceAttr = rotateAttr || sequenceAttr;
|
|
101
|
+
if (passage) {
|
|
102
|
+
disableLink(link);
|
|
103
|
+
set('_turncount', get('_turncount') + 1);
|
|
104
|
+
passage = processLink(passage);
|
|
84
105
|
if (passage) {
|
|
85
|
-
disableLink(link);
|
|
86
|
-
set('_turncount', get('_turncount') + 1);
|
|
87
|
-
passage = processLink(passage);
|
|
88
|
-
if (passage) {
|
|
89
|
-
currentSectionElement?.appendChild(document.createElement('hr'));
|
|
90
|
-
showPassage(passage);
|
|
91
|
-
}
|
|
92
|
-
const turnPassage = '@' + get('_turncount');
|
|
93
|
-
if (currentSection.passages) {
|
|
94
|
-
if (turnPassage in currentSection.passages) {
|
|
95
|
-
showPassage(turnPassage);
|
|
96
|
-
}
|
|
97
|
-
if ('@last' in currentSection.passages && get('_turncount') >= (currentSection.passageCount || 0)) {
|
|
98
|
-
showPassage('@last');
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else if (section) {
|
|
103
106
|
currentSectionElement?.appendChild(document.createElement('hr'));
|
|
104
|
-
|
|
105
|
-
section = processLink(section);
|
|
106
|
-
if (section) {
|
|
107
|
-
go(section);
|
|
108
|
-
}
|
|
107
|
+
showPassage(passage);
|
|
109
108
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
link.setAttribute(dataAttribute, result[1] || '');
|
|
115
|
-
if (!result[1]) {
|
|
116
|
-
disableLink(link);
|
|
109
|
+
const turnPassage = '@' + get('_turncount');
|
|
110
|
+
if (currentSection.passages) {
|
|
111
|
+
if (turnPassage in currentSection.passages) {
|
|
112
|
+
showPassage(turnPassage);
|
|
117
113
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
set(attribute, result[0]);
|
|
114
|
+
if ('@last' in currentSection.passages && get('_turncount') >= (currentSection.passageCount || 0)) {
|
|
115
|
+
showPassage('@last');
|
|
121
116
|
}
|
|
122
|
-
save();
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
else if (section) {
|
|
120
|
+
currentSectionElement?.appendChild(document.createElement('hr'));
|
|
121
|
+
disableLink(link);
|
|
122
|
+
section = processLink(section);
|
|
123
|
+
if (section) {
|
|
124
|
+
go(section);
|
|
130
125
|
}
|
|
131
126
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
else if (rotateOrSequenceAttr) {
|
|
128
|
+
const result = rotate(rotateOrSequenceAttr, rotateAttr ? link.innerText : '');
|
|
129
|
+
link.innerHTML = result[0]!.replace(/"/g, '"').replace(/'/g, '\'');
|
|
130
|
+
const dataAttribute = rotateAttr ? 'data-rotate' : 'data-sequence';
|
|
131
|
+
link.setAttribute(dataAttribute, result[1] || '');
|
|
132
|
+
if (!result[1]) {
|
|
133
|
+
disableLink(link);
|
|
134
|
+
}
|
|
135
|
+
const attribute = link.getAttribute('data-attribute');
|
|
136
|
+
if (attribute) {
|
|
137
|
+
set(attribute, result[0]);
|
|
138
|
+
}
|
|
139
|
+
save();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function handleClick(event: Event) {
|
|
144
|
+
const target = event.target as HTMLElement;
|
|
145
|
+
if (target.classList.contains('squiffy-link')) {
|
|
146
|
+
handleLink(target);
|
|
147
|
+
}
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
function disableLink(link: Element) {
|
|
@@ -289,7 +299,17 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
289
299
|
processAttributes(section.attributes.map(line => line.replace(/^random\s*:\s*(\w+)\s*=\s*(.+)/i, (line, attr, options) => (options = options.split("|")) ? attr + " = " + options[Math.floor(Math.random() * options.length)] : line)));
|
|
290
300
|
}
|
|
291
301
|
if (section.jsIndex !== undefined) {
|
|
292
|
-
|
|
302
|
+
const squiffy = {
|
|
303
|
+
get: get,
|
|
304
|
+
set: set,
|
|
305
|
+
ui: {
|
|
306
|
+
transition: ui.transition,
|
|
307
|
+
},
|
|
308
|
+
story: {
|
|
309
|
+
go: go,
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
story.js[section.jsIndex](squiffy, get, set);
|
|
293
313
|
}
|
|
294
314
|
}
|
|
295
315
|
|
|
@@ -659,12 +679,18 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
659
679
|
outputElement.style.overflowY = 'auto';
|
|
660
680
|
}
|
|
661
681
|
|
|
662
|
-
|
|
682
|
+
outputElement.addEventListener('click', handleClick);
|
|
683
|
+
outputElement.addEventListener('keypress', function (event) {
|
|
684
|
+
if (event.key !== "Enter") return;
|
|
685
|
+
handleClick(event);
|
|
686
|
+
});
|
|
687
|
+
|
|
663
688
|
begin();
|
|
664
689
|
|
|
665
690
|
return {
|
|
666
691
|
restart: restart,
|
|
667
692
|
get: get,
|
|
668
693
|
set: set,
|
|
694
|
+
clickLink: handleLink
|
|
669
695
|
};
|
|
670
696
|
};
|