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>;
@@ -29,68 +29,60 @@ export const init = (options) => {
29
29
  return null;
30
30
  return JSON.parse(result);
31
31
  }
32
- function initLinkHandler() {
33
- function handleLink(link) {
34
- if (link.classList.contains('disabled'))
35
- return;
36
- let passage = link.getAttribute('data-passage');
37
- let section = link.getAttribute('data-section');
38
- const rotateAttr = link.getAttribute('data-rotate');
39
- const sequenceAttr = link.getAttribute('data-sequence');
40
- const rotateOrSequenceAttr = rotateAttr || sequenceAttr;
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
- disableLink(link);
62
- section = processLink(section);
63
- if (section) {
64
- go(section);
65
- }
46
+ showPassage(passage);
66
47
  }
67
- else if (rotateOrSequenceAttr) {
68
- const result = rotate(rotateOrSequenceAttr, rotateAttr ? link.innerText : '');
69
- link.innerHTML = result[0].replace(/&quot;/g, '"').replace(/&#39;/g, '\'');
70
- const dataAttribute = rotateAttr ? 'data-rotate' : 'data-sequence';
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
- const attribute = link.getAttribute('data-attribute');
76
- if (attribute) {
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
- function handleClick(event) {
83
- const target = event.target;
84
- if (target.classList.contains('squiffy-link')) {
85
- handleLink(target);
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
- document.addEventListener('click', handleClick);
89
- document.addEventListener('keypress', function (event) {
90
- if (event.key !== "Enter")
91
- return;
92
- handleClick(event);
93
- });
66
+ else if (rotateOrSequenceAttr) {
67
+ const result = rotate(rotateOrSequenceAttr, rotateAttr ? link.innerText : '');
68
+ link.innerHTML = result[0].replace(/&quot;/g, '"').replace(/&#39;/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
- story.js[section.jsIndex]();
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
- initLinkHandler();
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.2",
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": "echo \"Error: no test specified\" && exit 1",
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
- "typescript": "^5.6.2"
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
+ });
@@ -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: (() => void)[];
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 initLinkHandler() {
77
- function handleLink(link: HTMLElement) {
78
- if (link.classList.contains('disabled')) return;
79
- let passage = link.getAttribute('data-passage');
80
- let section = link.getAttribute('data-section');
81
- const rotateAttr = link.getAttribute('data-rotate');
82
- const sequenceAttr = link.getAttribute('data-sequence');
83
- const rotateOrSequenceAttr = rotateAttr || sequenceAttr;
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
- disableLink(link);
105
- section = processLink(section);
106
- if (section) {
107
- go(section);
108
- }
107
+ showPassage(passage);
109
108
  }
110
- else if (rotateOrSequenceAttr) {
111
- const result = rotate(rotateOrSequenceAttr, rotateAttr ? link.innerText : '');
112
- link.innerHTML = result[0]!.replace(/&quot;/g, '"').replace(/&#39;/g, '\'');
113
- const dataAttribute = rotateAttr ? 'data-rotate' : 'data-sequence';
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
- const attribute = link.getAttribute('data-attribute');
119
- if (attribute) {
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
- function handleClick(event: Event) {
127
- const target = event.target as HTMLElement;
128
- if (target.classList.contains('squiffy-link')) {
129
- handleLink(target);
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
- document.addEventListener('click', handleClick);
134
- document.addEventListener('keypress', function (event) {
135
- if (event.key !== "Enter") return;
136
- handleClick(event);
137
- });
127
+ else if (rotateOrSequenceAttr) {
128
+ const result = rotate(rotateOrSequenceAttr, rotateAttr ? link.innerText : '');
129
+ link.innerHTML = result[0]!.replace(/&quot;/g, '"').replace(/&#39;/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
- story.js[section.jsIndex]();
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
- initLinkHandler();
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
  };