squiffy-runtime 6.0.0-alpha.3 → 6.0.0-alpha.5

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.
@@ -587,8 +587,8 @@ export const init = (options) => {
587
587
  if (settings.scroll === 'element') {
588
588
  outputElement.style.overflowY = 'auto';
589
589
  }
590
- document.addEventListener('click', handleClick);
591
- document.addEventListener('keypress', function (event) {
590
+ outputElement.addEventListener('click', handleClick);
591
+ outputElement.addEventListener('keypress', function (event) {
592
592
  if (event.key !== "Enter")
593
593
  return;
594
594
  handleClick(event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squiffy-runtime",
3
- "version": "6.0.0-alpha.3",
3
+ "version": "6.0.0-alpha.5",
4
4
  "main": "dist/squiffy.runtime.js",
5
5
  "types": "dist/squiffy.runtime.d.ts",
6
6
  "scripts": {
@@ -2,53 +2,93 @@
2
2
 
3
3
  exports[`"Hello world" script should run 1`] = `
4
4
  "
5
- <div id="squiffy-section-1"><div><p>Hello world</p></div></div>"
5
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Hello world</p></div></div></div>"
6
6
  `;
7
7
 
8
8
  exports[`Click a passage link 1`] = `
9
9
  "
10
- <div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
10
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
11
11
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
12
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
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
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
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
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>"
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></div>"
18
18
  `;
19
19
 
20
20
  exports[`Click a passage link 2`] = `
21
21
  "
22
- <div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
22
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
23
23
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
24
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
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
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
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
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>"
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></div><div class="squiffy-output-block"><div data-source="[[Introduction]][passage]"><p>Here's some text for the passage.</p></div></div></div>"
30
30
  `;
31
31
 
32
32
  exports[`Click a section link 1`] = `
33
33
  "
34
- <div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
34
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
35
35
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
36
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
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
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
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
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>"
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></div>"
42
42
  `;
43
43
 
44
44
  exports[`Click a section link 2`] = `
45
45
  "
46
- <div id="squiffy-section-1"><div><h1>Squiffy Test</h1>
46
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
47
47
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
48
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
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
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
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
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>"
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></div></div><div class="squiffy-output-section" id="squiffy-section-2" data-section="section 3"><div class="squiffy-output-block"><div data-source="[[section 3]]"><p>Another section is here.</p></div></div></div>"
54
+ `;
55
+
56
+ exports[`Delete passage 1`] = `
57
+ "
58
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div><div class="squiffy-output-block"><div data-source="[[_default]][a]"><p>New passage</p></div></div></div>"
59
+ `;
60
+
61
+ exports[`Delete passage 2`] = `
62
+ "
63
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div><div class="squiffy-output-block"></div></div>"
64
+ `;
65
+
66
+ exports[`Delete section 1`] = `
67
+ "
68
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-section disabled" data-section="a" role="link" tabindex="-1">a</a></p></div></div></div><div class="squiffy-output-section" id="squiffy-section-2" data-section="a"><div class="squiffy-output-block"><div data-source="[[a]]"><p>New section</p></div></div></div>"
69
+ `;
70
+
71
+ exports[`Delete section 2`] = `
72
+ "
73
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-section disabled" data-section="a" role="link" tabindex="-1">a</a></p></div></div></div><div class="squiffy-output-section" id="squiffy-section-2" data-section="a"><div class="squiffy-output-block"></div></div>"
74
+ `;
75
+
76
+ exports[`Update default section output 1`] = `
77
+ "
78
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Hello world</p></div></div></div>"
79
+ `;
80
+
81
+ exports[`Update default section output 2`] = `
82
+ "
83
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Updated content</p></div></div></div>"
84
+ `;
85
+
86
+ exports[`Update passage output 1`] = `
87
+ "
88
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div><div class="squiffy-output-block"><div data-source="[[_default]][a]"><p>Passage a content</p></div></div></div>"
89
+ `;
90
+
91
+ exports[`Update passage output 2`] = `
92
+ "
93
+ <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div><div class="squiffy-output-block"><div data-source="[[_default]][a]"><p>Updated passage content</p></div></div></div>"
54
94
  `;
@@ -2,7 +2,7 @@ import { expect, test, beforeEach, afterEach } from 'vitest';
2
2
  import fs from 'fs/promises';
3
3
  import globalJsdom from 'global-jsdom';
4
4
  import { init } from './squiffy.runtime.js';
5
- import { compile } from 'squiffy-compiler';
5
+ import { compile as squiffyCompile } from 'squiffy-compiler';
6
6
 
7
7
  const html = `
8
8
  <!DOCTYPE html>
@@ -18,15 +18,8 @@ const html = `
18
18
  </html>
19
19
  `;
20
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({
21
+ const compile = async (script: string) => {
22
+ const compileResult = await squiffyCompile({
30
23
  scriptBaseFilename: "filename.squiffy", // TODO: This shouldn't be required
31
24
  script: script,
32
25
  });
@@ -38,12 +31,27 @@ const initScript = async (script: string) => {
38
31
  const story = compileResult.output.story;
39
32
  const js = compileResult.output.js.map(jsLines => new Function('squiffy', 'get', 'set', jsLines.join('\n')));
40
33
 
41
- const squiffyApi = init({
42
- element: element,
34
+ return {
43
35
  story: {
44
36
  js: js as any,
45
37
  ...story,
46
38
  },
39
+ };
40
+ }
41
+
42
+ const initScript = async (script: string) => {
43
+ globalJsdom(html);
44
+ const element = document.getElementById('squiffy');
45
+
46
+ if (!element) {
47
+ throw new Error('Element not found');
48
+ }
49
+
50
+ const compileResult = await compile(script);
51
+
52
+ const squiffyApi = init({
53
+ element: element,
54
+ story: compileResult.story,
47
55
  });
48
56
 
49
57
  return {
@@ -89,8 +97,8 @@ test('Click a section link', async () => {
89
97
  const linkToPassage = findLink(element, 'passage', 'a link to a passage');
90
98
  const section3Link = findLink(element, 'section', 'section 3');
91
99
 
92
- expect(linkToPassage).toBeDefined();
93
- expect(section3Link).toBeDefined();
100
+ expect(linkToPassage).not.toBeNull();
101
+ expect(section3Link).not.toBeNull();
94
102
  expect(linkToPassage.classList).not.toContain('disabled');
95
103
  expect(section3Link.classList).not.toContain('disabled');
96
104
 
@@ -110,8 +118,8 @@ test('Click a passage link', async () => {
110
118
  const linkToPassage = findLink(element, 'passage', 'a link to a passage');
111
119
  const section3Link = findLink(element, 'section', 'section 3');
112
120
 
113
- expect(linkToPassage).toBeDefined();
114
- expect(section3Link).toBeDefined();
121
+ expect(linkToPassage).not.toBeNull();
122
+ expect(section3Link).not.toBeNull();
115
123
  expect(linkToPassage.classList).not.toContain('disabled');
116
124
  expect(section3Link.classList).not.toContain('disabled');
117
125
 
@@ -147,7 +155,7 @@ test('Run JavaScript functions', async () => {
147
155
 
148
156
  const clickContinue = () => {
149
157
  const continueLink = findLink(element, 'section', 'Continue...', true);
150
- expect(continueLink).toBeDefined();
158
+ expect(continueLink).not.toBeNull();
151
159
  squiffyApi.clickLink(continueLink);
152
160
  };
153
161
 
@@ -170,4 +178,109 @@ test('Run JavaScript functions', async () => {
170
178
 
171
179
  clickContinue();
172
180
  expect(getTestOutput()).toBe('In other section');
181
+ });
182
+
183
+ function getSectionContent(element: HTMLElement, section: string) {
184
+ return element.querySelector(`[data-source='[[${section}]]'] p`)?.textContent || null;
185
+ }
186
+
187
+ function getPassageContent(element: HTMLElement, section: string, passage: string) {
188
+ return element.querySelector(`[data-source='[[${section}]][${passage}]'] p`)?.textContent || null;
189
+ }
190
+
191
+ test('Update default section output', async () => {
192
+ const { squiffyApi, element } = await initScript("Hello world");
193
+ let defaultOutput = getSectionContent(element, '_default');
194
+ expect(defaultOutput).toBe('Hello world');
195
+ expect(element.innerHTML).toMatchSnapshot();
196
+
197
+ const updated = await compile("Updated content");
198
+ squiffyApi.update(updated.story);
199
+ defaultOutput = getSectionContent(element, '_default');
200
+ expect(defaultOutput).toBe('Updated content');
201
+ expect(element.innerHTML).toMatchSnapshot();
202
+ });
203
+
204
+ test('Update passage output', async () => {
205
+ const { squiffyApi, element } = await initScript(`Click this: [a]
206
+
207
+ [a]:
208
+ Passage a content`);
209
+
210
+ const link = findLink(element, 'passage', 'a');
211
+ squiffyApi.clickLink(link);
212
+
213
+ let defaultOutput = getSectionContent(element, '_default');
214
+ expect(defaultOutput).toBe('Click this: a');
215
+ let passageOutput = getPassageContent(element, '_default', 'a');
216
+ expect(passageOutput).toBe('Passage a content');
217
+ expect(element.innerHTML).toMatchSnapshot();
218
+
219
+ const updated = await compile(`Click this: [a]
220
+
221
+ [a]:
222
+ Updated passage content`);
223
+
224
+ squiffyApi.update(updated.story);
225
+
226
+ defaultOutput = getSectionContent(element, '_default');
227
+ expect(defaultOutput).toBe('Click this: a');
228
+
229
+ passageOutput = getPassageContent(element, '_default', 'a');
230
+ expect(passageOutput).toBe('Updated passage content');
231
+ expect(element.innerHTML).toMatchSnapshot();
232
+ });
233
+
234
+ test('Delete section', async () => {
235
+ const { squiffyApi, element } = await initScript(`Click this: [[a]]
236
+
237
+ [[a]]:
238
+ New section`);
239
+
240
+ const link = findLink(element, 'section', 'a');
241
+ squiffyApi.clickLink(link);
242
+
243
+ let defaultOutput = getSectionContent(element, '_default');
244
+ expect(defaultOutput).toBe('Click this: a');
245
+ let sectionOutput = getSectionContent(element, 'a');
246
+ expect(sectionOutput).toBe('New section');
247
+ expect(element.innerHTML).toMatchSnapshot();
248
+
249
+ const updated = await compile(`Click this: [[a]]`);
250
+
251
+ squiffyApi.update(updated.story);
252
+
253
+ defaultOutput = getSectionContent(element, '_default');
254
+ expect(defaultOutput).toBe('Click this: a');
255
+ sectionOutput = getSectionContent(element, 'a');
256
+ expect(sectionOutput).toBeNull();
257
+
258
+ expect(element.innerHTML).toMatchSnapshot();
259
+ });
260
+
261
+ test('Delete passage', async () => {
262
+ const { squiffyApi, element } = await initScript(`Click this: [a]
263
+
264
+ [a]:
265
+ New passage`);
266
+
267
+ const link = findLink(element, 'passage', 'a');
268
+ squiffyApi.clickLink(link);
269
+
270
+ let defaultOutput = getSectionContent(element, '_default');
271
+ expect(defaultOutput).toBe('Click this: a');
272
+ let passageOutput = getPassageContent(element, '_default', 'a');
273
+ expect(passageOutput).toBe('New passage');
274
+ expect(element.innerHTML).toMatchSnapshot();
275
+
276
+ const updated = await compile(`Click this: [a]`);
277
+
278
+ squiffyApi.update(updated.story);
279
+
280
+ defaultOutput = getSectionContent(element, '_default');
281
+ expect(defaultOutput).toBe('Click this: a');
282
+ passageOutput = getPassageContent(element, '_default', 'a');
283
+ expect(passageOutput).toBeNull();
284
+
285
+ expect(element.innerHTML).toMatchSnapshot();
173
286
  });
@@ -17,6 +17,7 @@ interface SquiffyApi {
17
17
  get: (attribute: string) => any;
18
18
  set: (attribute: string, value: any) => void;
19
19
  clickLink: (link: HTMLElement) => void;
20
+ update: (story: Story) => void;
20
21
  }
21
22
 
22
23
  // Previous versions of Squiffy had "squiffy", "get" and "set" as globals - we now pass these directly into JS functions.
@@ -63,6 +64,7 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
63
64
  let story: Story;
64
65
  let currentSection: Section;
65
66
  let currentSectionElement: HTMLElement;
67
+ let currentBlockOutputElement: HTMLElement;
66
68
  let scrollPosition = 0;
67
69
  let outputElement: HTMLElement;
68
70
  let settings: SquiffySettings;
@@ -103,7 +105,7 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
103
105
  set('_turncount', get('_turncount') + 1);
104
106
  passage = processLink(passage);
105
107
  if (passage) {
106
- currentSectionElement?.appendChild(document.createElement('hr'));
108
+ newBlockOutputElement();
107
109
  showPassage(passage);
108
110
  }
109
111
  const turnPassage = '@' + get('_turncount');
@@ -117,7 +119,6 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
117
119
  }
118
120
  }
119
121
  else if (section) {
120
- currentSectionElement?.appendChild(document.createElement('hr'));
121
122
  disableLink(link);
122
123
  section = processLink(section);
123
124
  if (section) {
@@ -270,23 +271,23 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
270
271
  labelElement.classList.add('fade-out');
271
272
  }
272
273
 
273
- function go(section: string) {
274
+ function go(sectionName: string) {
274
275
  set('_transition', null);
275
- newSection();
276
- currentSection = story.sections[section];
276
+ newSection(sectionName);
277
+ currentSection = story.sections[sectionName];
277
278
  if (!currentSection) return;
278
- set('_section', section);
279
- setSeen(section);
279
+ set('_section', sectionName);
280
+ setSeen(sectionName);
280
281
  const master = story.sections[''];
281
282
  if (master) {
282
283
  run(master);
283
- ui.write(master.text || '');
284
+ ui.write(master.text || '', "[[]]");
284
285
  }
285
286
  run(currentSection);
286
287
  // The JS might have changed which section we're in
287
- if (get('_section') == section) {
288
+ if (get('_section') == sectionName) {
288
289
  set('_turncount', 0);
289
- ui.write(currentSection.text || '');
290
+ ui.write(currentSection.text || '', `[[${sectionName}]]`);
290
291
  save();
291
292
  }
292
293
  }
@@ -323,16 +324,16 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
323
324
  const masterPassage = masterSection.passages[''];
324
325
  if (masterPassage) {
325
326
  run(masterPassage);
326
- ui.write(masterPassage.text || '');
327
+ ui.write(masterPassage.text || '', `[[]][]`);
327
328
  }
328
329
  }
329
330
  const master = currentSection.passages && currentSection.passages[''];
330
331
  if (master) {
331
332
  run(master);
332
- ui.write(master.text || '');
333
+ ui.write(master.text || '', `[[${get("_section")}]][]`);
333
334
  }
334
335
  run(passage);
335
- ui.write(passage.text || '');
336
+ ui.write(passage.text || '', `[[${get("_section")}]][${passageName}]`);
336
337
  save();
337
338
  }
338
339
 
@@ -376,9 +377,10 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
376
377
  const output = get('_output');
377
378
  if (!output) return false;
378
379
  outputElement.innerHTML = output;
379
- const element = document.getElementById(get('_output-section'));
380
- if (!element) return false;
381
- currentSectionElement = element;
380
+
381
+ currentSectionElement = outputElement.querySelector('.squiffy-output-section:last-child');
382
+ currentBlockOutputElement = outputElement.querySelector('.squiffy-output-block:last-child');
383
+
382
384
  currentSection = story.sections[get('_section')];
383
385
  const transition = get('_transition');
384
386
  if (transition) {
@@ -401,8 +403,14 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
401
403
  if (!seenSections) return false;
402
404
  return (seenSections.indexOf(sectionName) > -1);
403
405
  }
406
+
407
+ function newBlockOutputElement() {
408
+ currentBlockOutputElement = document.createElement('div');
409
+ currentBlockOutputElement.classList.add('squiffy-output-block');
410
+ currentSectionElement?.appendChild(currentBlockOutputElement);
411
+ }
404
412
 
405
- function newSection() {
413
+ function newSection(sectionName: string | null) {
406
414
  if (currentSectionElement) {
407
415
  disableLinks(currentSectionElement.querySelectorAll('.squiffy-link'));
408
416
  currentSectionElement.querySelectorAll('input').forEach(el => {
@@ -429,26 +437,32 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
429
437
  const id = 'squiffy-section-' + sectionCount;
430
438
 
431
439
  currentSectionElement = document.createElement('div');
440
+ currentSectionElement.classList.add('squiffy-output-section');
432
441
  currentSectionElement.id = id;
442
+ if (sectionName) {
443
+ currentSectionElement.setAttribute('data-section', `${sectionName}`);
444
+ }
433
445
  outputElement.appendChild(currentSectionElement);
434
-
435
- set('_output-section', id);
446
+ newBlockOutputElement();
436
447
  }
437
448
 
438
449
  const ui = {
439
- write: (text: string) => {
440
- if (!currentSectionElement) return;
450
+ write: (text: string, source: string) => {
451
+ if (!currentBlockOutputElement) return;
441
452
  scrollPosition = outputElement.scrollHeight;
442
453
 
443
454
  const div = document.createElement('div');
444
- currentSectionElement.appendChild(div);
455
+ if (source) {
456
+ div.setAttribute('data-source', source);
457
+ }
458
+ currentBlockOutputElement.appendChild(div);
445
459
  div.innerHTML = ui.processText(text);
446
460
 
447
461
  ui.scrollToEnd();
448
462
  },
449
463
  clearScreen: () => {
450
464
  outputElement.innerHTML = '';
451
- newSection();
465
+ newSection(null);
452
466
  },
453
467
  scrollToEnd: () => {
454
468
  if (settings.scroll === 'element') {
@@ -661,6 +675,60 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
661
675
  return [next, remaining];
662
676
  }
663
677
 
678
+ function getSectionContent(section: string) {
679
+ return outputElement.querySelectorAll(`[data-source='[[${section}]]']`);
680
+ }
681
+
682
+ function getPassageContent(section: string, passage: string) {
683
+ return outputElement.querySelectorAll(`[data-source='[[${section}]][${passage}]']`);
684
+ }
685
+
686
+ function update(newStory: Story) {
687
+ // TODO: Re-disable clicked links after update
688
+
689
+ for (const existingSection of Object.keys(story.sections)) {
690
+ const elements = getSectionContent(existingSection);
691
+ if (elements.length) {
692
+ const newSection = newStory.sections[existingSection];
693
+ if (!newSection) {
694
+ // section has been deleted
695
+ for (const element of elements) {
696
+ element.remove();
697
+ }
698
+ }
699
+ else if (newSection.text && newSection.text != story.sections[existingSection].text) {
700
+ // section has been updated
701
+ for (const element of elements) {
702
+ element.innerHTML = ui.processText(newSection.text);
703
+ }
704
+ }
705
+ }
706
+
707
+ if (!story.sections[existingSection].passages) continue;
708
+
709
+ for (const existingPassage of Object.keys(story.sections[existingSection].passages)) {
710
+ const elements = getPassageContent(existingSection, existingPassage);
711
+ if (!elements.length) continue;
712
+
713
+ const newPassage = newStory.sections[existingSection]?.passages && newStory.sections[existingSection]?.passages[existingPassage];
714
+ if (!newPassage) {
715
+ // passage has been deleted
716
+ for (const element of elements) {
717
+ element.remove();
718
+ }
719
+ }
720
+ else if (newPassage.text && newPassage.text != story.sections[existingSection].passages[existingPassage].text) {
721
+ // passage has been updated
722
+ for (const element of elements) {
723
+ element.innerHTML = ui.processText(newPassage.text);
724
+ }
725
+ }
726
+ }
727
+ }
728
+
729
+ story = newStory;
730
+ }
731
+
664
732
  outputElement = options.element;
665
733
  story = options.story;
666
734
 
@@ -679,8 +747,8 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
679
747
  outputElement.style.overflowY = 'auto';
680
748
  }
681
749
 
682
- document.addEventListener('click', handleClick);
683
- document.addEventListener('keypress', function (event) {
750
+ outputElement.addEventListener('click', handleClick);
751
+ outputElement.addEventListener('keypress', function (event) {
684
752
  if (event.key !== "Enter") return;
685
753
  handleClick(event);
686
754
  });
@@ -691,6 +759,7 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
691
759
  restart: restart,
692
760
  get: get,
693
761
  set: set,
694
- clickLink: handleLink
762
+ clickLink: handleLink,
763
+ update: update,
695
764
  };
696
765
  };