squiffy-runtime 6.0.0-alpha.15 → 6.0.0-alpha.17
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/animation.d.ts +11 -0
- package/dist/events.d.ts +8 -3
- package/dist/import.d.ts +4 -0
- package/dist/linkHandler.d.ts +8 -0
- package/dist/pluginManager.d.ts +23 -0
- package/dist/squiffy.runtime.d.ts +2 -2
- package/dist/squiffy.runtime.global.js +126 -0
- package/dist/squiffy.runtime.global.js.map +1 -0
- package/dist/squiffy.runtime.js +8785 -487
- package/dist/squiffy.runtime.js.map +1 -0
- package/dist/state.d.ts +19 -0
- package/dist/textProcessor.d.ts +8 -13
- package/dist/types.d.ts +5 -2
- package/dist/types.plugins.d.ts +27 -0
- package/dist/updater.d.ts +2 -0
- package/dist/utils.d.ts +1 -2
- package/package.json +12 -5
- package/src/__snapshots__/squiffy.runtime.test.ts.snap +53 -19
- package/src/animation.ts +68 -0
- package/src/events.ts +9 -10
- package/src/import.ts +5 -0
- package/src/linkHandler.ts +18 -0
- package/src/pluginManager.ts +74 -0
- package/src/plugins/animate.ts +97 -0
- package/src/plugins/index.ts +13 -0
- package/src/plugins/live.ts +83 -0
- package/src/plugins/random.ts +22 -0
- package/src/plugins/replaceLabel.ts +22 -0
- package/src/plugins/rotateSequence.ts +61 -0
- package/src/squiffy.runtime.test.ts +306 -134
- package/src/squiffy.runtime.ts +460 -332
- package/src/state.ts +106 -0
- package/src/textProcessor.ts +61 -164
- package/src/types.plugins.ts +41 -0
- package/src/types.ts +5 -2
- package/src/updater.ts +77 -0
- package/src/utils.ts +15 -12
- package/vite.config.ts +36 -0
- package/vitest.config.ts +9 -0
- package/vitest.setup.ts +16 -0
- package/dist/events.js +0 -35
- package/dist/squiffy.runtime.test.js +0 -394
- package/dist/textProcessor.js +0 -166
- package/dist/types.js +0 -1
- package/dist/utils.js +0 -14
package/src/squiffy.runtime.ts
CHANGED
|
@@ -1,158 +1,129 @@
|
|
|
1
|
-
import { SquiffyApi, SquiffyInitOptions,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { SquiffyApi, SquiffyInitOptions, Story, Section, Passage } from "./types.js";
|
|
2
|
+
import { TextProcessor } from "./textProcessor.js";
|
|
3
|
+
import { Emitter, SquiffyEventMap } from "./events.js";
|
|
4
|
+
import { State } from "./state.js";
|
|
5
|
+
import { updateStory } from "./updater.js";
|
|
6
|
+
import { PluginManager } from "./pluginManager.js";
|
|
7
|
+
import { Plugins } from "./plugins/index.js";
|
|
8
|
+
import { LinkHandler } from "./linkHandler.js";
|
|
9
|
+
import { Animation } from "./animation.js";
|
|
10
|
+
import { imports } from "./import.js";
|
|
5
11
|
|
|
6
|
-
export type { SquiffyApi } from "./types.js"
|
|
12
|
+
export type { SquiffyApi } from "./types.js";
|
|
7
13
|
|
|
8
|
-
export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
14
|
+
export const init = async (options: SquiffyInitOptions): Promise<SquiffyApi> => {
|
|
9
15
|
let story: Story;
|
|
10
16
|
let currentSection: Section;
|
|
11
17
|
let currentSectionElement: HTMLElement;
|
|
18
|
+
let currentPassageElement: HTMLElement;
|
|
12
19
|
let currentBlockOutputElement: HTMLElement;
|
|
13
20
|
let scrollPosition = 0;
|
|
14
|
-
let outputElement: HTMLElement;
|
|
15
|
-
let settings: SquiffySettings;
|
|
16
|
-
let storageFallback: Record<string, string> = {};
|
|
17
|
-
let textProcessor: TextProcessor;
|
|
18
21
|
const emitter = new Emitter<SquiffyEventMap>();
|
|
22
|
+
const transitions: (() => Promise<void>)[] = [];
|
|
23
|
+
let runningTransitions = false;
|
|
19
24
|
|
|
20
|
-
function
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
localStorage[story.id + '-' + attribute] = JSON.stringify(value);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
storageFallback[attribute] = JSON.stringify(value);
|
|
27
|
-
}
|
|
28
|
-
settings.onSet(attribute, value);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function get(attribute: string): any {
|
|
32
|
-
let result;
|
|
33
|
-
if (settings.persist && window.localStorage) {
|
|
34
|
-
result = localStorage[story.id + '-' + attribute];
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
result = storageFallback[attribute];
|
|
38
|
-
}
|
|
39
|
-
if (!result) return null;
|
|
40
|
-
return JSON.parse(result);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function handleLink(link: HTMLElement): boolean {
|
|
44
|
-
const outputSection = link.closest('.squiffy-output-section');
|
|
25
|
+
async function handleLink(link: HTMLElement): Promise<boolean> {
|
|
26
|
+
if (runningTransitions) return false;
|
|
27
|
+
const outputSection = link.closest(".squiffy-output-section");
|
|
45
28
|
if (outputSection !== currentSectionElement) return false;
|
|
46
29
|
|
|
47
|
-
if (link.classList.contains(
|
|
30
|
+
if (link.classList.contains("disabled")) return false;
|
|
31
|
+
|
|
32
|
+
const passage = link.getAttribute("data-passage");
|
|
33
|
+
const section = link.getAttribute("data-section");
|
|
48
34
|
|
|
49
|
-
|
|
50
|
-
let section = link.getAttribute('data-section');
|
|
51
|
-
const rotateAttr = link.getAttribute('data-rotate');
|
|
52
|
-
const sequenceAttr = link.getAttribute('data-sequence');
|
|
53
|
-
const rotateOrSequenceAttr = rotateAttr || sequenceAttr;
|
|
54
|
-
if (passage) {
|
|
35
|
+
if (passage !== null) {
|
|
55
36
|
disableLink(link);
|
|
56
|
-
set(
|
|
57
|
-
|
|
37
|
+
set("_turncount", get("_turncount") + 1);
|
|
38
|
+
await processLink(link);
|
|
58
39
|
if (passage) {
|
|
59
|
-
|
|
60
|
-
showPassage(passage);
|
|
40
|
+
currentBlockOutputElement = null;
|
|
41
|
+
await showPassage(passage);
|
|
61
42
|
}
|
|
62
|
-
const turnPassage =
|
|
43
|
+
const turnPassage = "@" + get("_turncount");
|
|
63
44
|
if (currentSection.passages) {
|
|
64
45
|
if (turnPassage in currentSection.passages) {
|
|
65
|
-
showPassage(turnPassage);
|
|
46
|
+
await showPassage(turnPassage);
|
|
66
47
|
}
|
|
67
|
-
if (
|
|
68
|
-
showPassage(
|
|
48
|
+
if ("@last" in currentSection.passages && get("_turncount") >= (currentSection.passageCount || 0)) {
|
|
49
|
+
await showPassage("@last");
|
|
69
50
|
}
|
|
70
51
|
}
|
|
71
52
|
|
|
72
|
-
emitter.emit(
|
|
53
|
+
emitter.emit("linkClick", { linkType: "passage" });
|
|
73
54
|
return true;
|
|
74
55
|
}
|
|
75
56
|
|
|
76
|
-
if (section) {
|
|
77
|
-
|
|
57
|
+
if (section !== null) {
|
|
58
|
+
await processLink(link);
|
|
78
59
|
if (section) {
|
|
79
|
-
go(section);
|
|
60
|
+
await go(section);
|
|
80
61
|
}
|
|
81
62
|
|
|
82
|
-
emitter.emit(
|
|
63
|
+
emitter.emit("linkClick", { linkType: "section" });
|
|
83
64
|
return true;
|
|
84
65
|
}
|
|
66
|
+
|
|
67
|
+
const [handled, type, result] = linkHandler.handleLink(link);
|
|
85
68
|
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
link.innerHTML = result[0]!.replace(/"/g, '"').replace(/'/g, '\'');
|
|
89
|
-
const dataAttribute = rotateAttr ? 'data-rotate' : 'data-sequence';
|
|
90
|
-
link.setAttribute(dataAttribute, result[1] || '');
|
|
91
|
-
if (!result[1]) {
|
|
69
|
+
if (handled) {
|
|
70
|
+
if (result?.disableLink) {
|
|
92
71
|
disableLink(link);
|
|
93
72
|
}
|
|
94
|
-
|
|
95
|
-
if (attribute) {
|
|
96
|
-
set(attribute, result[0]);
|
|
97
|
-
}
|
|
73
|
+
|
|
98
74
|
save();
|
|
99
75
|
|
|
100
|
-
emitter.emit(
|
|
76
|
+
emitter.emit("linkClick", { linkType: type });
|
|
101
77
|
return true;
|
|
102
78
|
}
|
|
103
79
|
|
|
104
80
|
return false;
|
|
105
81
|
}
|
|
106
82
|
|
|
107
|
-
function handleClick(event: Event) {
|
|
83
|
+
async function handleClick(event: Event) {
|
|
108
84
|
const target = event.target as HTMLElement;
|
|
109
|
-
if (target.classList.contains(
|
|
110
|
-
handleLink(target);
|
|
85
|
+
if (target.classList.contains("squiffy-link")) {
|
|
86
|
+
await handleLink(target);
|
|
111
87
|
}
|
|
112
88
|
}
|
|
113
89
|
|
|
114
90
|
function disableLink(link: Element) {
|
|
115
|
-
link.classList.add(
|
|
116
|
-
link.setAttribute(
|
|
91
|
+
link.classList.add("disabled");
|
|
92
|
+
link.setAttribute("tabindex", "-1");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function enableLink(link: Element) {
|
|
96
|
+
link.classList.remove("disabled");
|
|
97
|
+
link.removeAttribute("tabindex");
|
|
117
98
|
}
|
|
118
99
|
|
|
119
|
-
function begin() {
|
|
100
|
+
async function begin() {
|
|
120
101
|
if (!load()) {
|
|
121
|
-
go(story.start);
|
|
102
|
+
await go(story.start);
|
|
122
103
|
}
|
|
123
104
|
}
|
|
124
105
|
|
|
125
|
-
function processLink(link:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
if (first) {
|
|
136
|
-
target = section;
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
setAttribute(section);
|
|
140
|
-
}
|
|
106
|
+
async function processLink(link: HTMLElement) {
|
|
107
|
+
animation.runLinkAnimation(link);
|
|
108
|
+
await runTransitions();
|
|
109
|
+
const settersJson = link.getAttribute("data-set");
|
|
110
|
+
if (settersJson) {
|
|
111
|
+
const setters = JSON.parse(settersJson) as string[];
|
|
112
|
+
for (const attribute of setters) {
|
|
113
|
+
setAttribute(attribute);
|
|
141
114
|
}
|
|
142
|
-
|
|
143
|
-
});
|
|
144
|
-
return target;
|
|
115
|
+
}
|
|
145
116
|
}
|
|
146
117
|
|
|
147
118
|
function setAttribute(expr: string) {
|
|
148
|
-
expr = expr.replace(/^(\w*\s*):=(.*)$/, (_, name, value) => (name + "=" + ui.processText(value)));
|
|
119
|
+
expr = expr.replace(/^(\w*\s*):=(.*)$/, (_, name, value) => (name + "=" + ui.processText(value, true)));
|
|
149
120
|
const setRegex = /^([\w]*)\s*=\s*(.*)$/;
|
|
150
121
|
const setMatch = setRegex.exec(expr);
|
|
151
122
|
if (setMatch) {
|
|
152
123
|
const lhs = setMatch[1];
|
|
153
124
|
let rhs = setMatch[2];
|
|
154
125
|
if (isNaN(rhs as any)) {
|
|
155
|
-
if (startsWith(
|
|
126
|
+
if (rhs.startsWith("@")) rhs = get(rhs.substring(1));
|
|
156
127
|
set(lhs, rhs);
|
|
157
128
|
}
|
|
158
129
|
else {
|
|
@@ -160,33 +131,33 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
160
131
|
}
|
|
161
132
|
}
|
|
162
133
|
else {
|
|
163
|
-
const incDecRegex = /^([\w]*)\s*([
|
|
134
|
+
const incDecRegex = /^([\w]*)\s*([+\-*/])=\s*(.*)$/;
|
|
164
135
|
const incDecMatch = incDecRegex.exec(expr);
|
|
165
136
|
if (incDecMatch) {
|
|
166
137
|
const lhs = incDecMatch[1];
|
|
167
138
|
const op = incDecMatch[2];
|
|
168
139
|
let rhs = incDecMatch[3];
|
|
169
|
-
if (startsWith(
|
|
140
|
+
if (rhs.startsWith("@")) rhs = get(rhs.substring(1));
|
|
170
141
|
const rhsNumeric = parseFloat(rhs);
|
|
171
142
|
let value = get(lhs);
|
|
172
143
|
if (value === null) value = 0;
|
|
173
|
-
if (op ==
|
|
144
|
+
if (op == "+") {
|
|
174
145
|
value += rhsNumeric;
|
|
175
146
|
}
|
|
176
|
-
if (op ==
|
|
147
|
+
if (op == "-") {
|
|
177
148
|
value -= rhsNumeric;
|
|
178
149
|
}
|
|
179
|
-
if (op ==
|
|
150
|
+
if (op == "*") {
|
|
180
151
|
value *= rhsNumeric;
|
|
181
152
|
}
|
|
182
|
-
if (op ==
|
|
153
|
+
if (op == "/") {
|
|
183
154
|
value /= rhsNumeric;
|
|
184
155
|
}
|
|
185
156
|
set(lhs, value);
|
|
186
157
|
}
|
|
187
158
|
else {
|
|
188
159
|
let value = true;
|
|
189
|
-
if (startsWith(
|
|
160
|
+
if (expr.startsWith("not ")) {
|
|
190
161
|
expr = expr.substring(4);
|
|
191
162
|
value = false;
|
|
192
163
|
}
|
|
@@ -194,135 +165,143 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
194
165
|
}
|
|
195
166
|
}
|
|
196
167
|
}
|
|
197
|
-
|
|
198
|
-
function
|
|
199
|
-
const
|
|
200
|
-
const match = regex.exec(expr);
|
|
201
|
-
if (!match) return;
|
|
202
|
-
const label = match[1];
|
|
203
|
-
let text = match[2];
|
|
204
|
-
if (currentSection.passages && text in currentSection.passages) {
|
|
205
|
-
text = currentSection.passages[text].text || '';
|
|
206
|
-
}
|
|
207
|
-
else if (text in story.sections) {
|
|
208
|
-
text = story.sections[text].text || '';
|
|
209
|
-
}
|
|
210
|
-
const stripParags = /^<p>(.*)<\/p>$/;
|
|
211
|
-
const stripParagsMatch = stripParags.exec(text);
|
|
212
|
-
if (stripParagsMatch) {
|
|
213
|
-
text = stripParagsMatch[1];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const labelElement = outputElement.querySelector('.squiffy-label-' + label);
|
|
217
|
-
if (!labelElement) return;
|
|
218
|
-
|
|
219
|
-
labelElement.addEventListener('transitionend', function () {
|
|
220
|
-
labelElement.innerHTML = ui.processText(text);
|
|
221
|
-
|
|
222
|
-
labelElement.addEventListener('transitionend', function () {
|
|
223
|
-
save();
|
|
224
|
-
}, { once: true });
|
|
225
|
-
|
|
226
|
-
labelElement.classList.remove('fade-out');
|
|
227
|
-
labelElement.classList.add('fade-in');
|
|
228
|
-
}, { once: true });
|
|
229
|
-
|
|
230
|
-
labelElement.classList.add('fade-out');
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function go(sectionName: string) {
|
|
234
|
-
set('_transition', null);
|
|
168
|
+
|
|
169
|
+
async function go(sectionName: string) {
|
|
170
|
+
const oldCanGoBack = canGoBack();
|
|
235
171
|
newSection(sectionName);
|
|
236
172
|
currentSection = story.sections[sectionName];
|
|
237
173
|
if (!currentSection) return;
|
|
238
|
-
set(
|
|
239
|
-
setSeen(sectionName);
|
|
240
|
-
const master = story.sections[
|
|
174
|
+
set("_section", sectionName);
|
|
175
|
+
state.setSeen(sectionName);
|
|
176
|
+
const master = story.sections[""];
|
|
177
|
+
if (master?.clear || currentSection.clear) {
|
|
178
|
+
clearScreen();
|
|
179
|
+
}
|
|
241
180
|
if (master) {
|
|
242
|
-
run(master);
|
|
243
|
-
ui.write(master.text || '', "[[]]");
|
|
181
|
+
await run(master, "[[]]");
|
|
244
182
|
}
|
|
245
|
-
run(currentSection);
|
|
183
|
+
await run(currentSection, `[[${sectionName}]]`);
|
|
246
184
|
// The JS might have changed which section we're in
|
|
247
|
-
if (get(
|
|
248
|
-
set(
|
|
249
|
-
|
|
185
|
+
if (get("_section") == sectionName) {
|
|
186
|
+
set("_turncount", 0);
|
|
187
|
+
writeUndoLog();
|
|
250
188
|
save();
|
|
251
189
|
}
|
|
190
|
+
const newCanGoBack = canGoBack();
|
|
191
|
+
if (newCanGoBack != oldCanGoBack) {
|
|
192
|
+
emitter.emit("canGoBackChanged", { canGoBack: newCanGoBack });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function runJs(index: number, extra: any = null) {
|
|
197
|
+
const squiffy = {
|
|
198
|
+
get: get,
|
|
199
|
+
set: set,
|
|
200
|
+
ui: {
|
|
201
|
+
transition: addTransition,
|
|
202
|
+
write: ui.write,
|
|
203
|
+
scrollToEnd: ui.scrollToEnd,
|
|
204
|
+
},
|
|
205
|
+
story: {
|
|
206
|
+
go: go,
|
|
207
|
+
},
|
|
208
|
+
element: outputElementContainer,
|
|
209
|
+
import: imports,
|
|
210
|
+
...extra
|
|
211
|
+
};
|
|
212
|
+
story.js[index](squiffy, get, set);
|
|
252
213
|
}
|
|
253
214
|
|
|
254
|
-
function run(section: Section) {
|
|
255
|
-
if (section.clear) {
|
|
256
|
-
ui.clearScreen();
|
|
257
|
-
}
|
|
215
|
+
async function run(section: Section, source: string) {
|
|
258
216
|
if (section.attributes) {
|
|
259
|
-
processAttributes(section.attributes
|
|
217
|
+
await processAttributes(section.attributes);
|
|
260
218
|
}
|
|
261
219
|
if (section.jsIndex !== undefined) {
|
|
262
|
-
|
|
263
|
-
get: get,
|
|
264
|
-
set: set,
|
|
265
|
-
ui: {
|
|
266
|
-
transition: ui.transition,
|
|
267
|
-
},
|
|
268
|
-
story: {
|
|
269
|
-
go: go,
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
story.js[section.jsIndex](squiffy, get, set);
|
|
220
|
+
runJs(section.jsIndex);
|
|
273
221
|
}
|
|
222
|
+
|
|
223
|
+
ui.write(section.text || "", source);
|
|
224
|
+
|
|
225
|
+
await runTransitions();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function runTransitions() {
|
|
229
|
+
runningTransitions = true;
|
|
230
|
+
currentSectionElement.classList.add("links-disabled");
|
|
231
|
+
for (const transition of transitions) {
|
|
232
|
+
await transition();
|
|
233
|
+
}
|
|
234
|
+
transitions.length = 0;
|
|
235
|
+
runningTransitions = false;
|
|
236
|
+
currentSectionElement.classList.remove("links-disabled");
|
|
274
237
|
}
|
|
275
238
|
|
|
276
|
-
function showPassage(passageName: string) {
|
|
239
|
+
async function showPassage(passageName: string) {
|
|
240
|
+
const oldCanGoBack = canGoBack();
|
|
277
241
|
let passage = currentSection.passages && currentSection.passages[passageName];
|
|
278
|
-
const masterSection = story.sections[
|
|
242
|
+
const masterSection = story.sections[""];
|
|
279
243
|
if (!passage && masterSection && masterSection.passages) passage = masterSection.passages[passageName];
|
|
280
244
|
if (!passage) {
|
|
281
245
|
throw `No passage named ${passageName} in the current section or master section`;
|
|
282
246
|
}
|
|
283
|
-
setSeen(passageName);
|
|
247
|
+
state.setSeen(passageName);
|
|
248
|
+
|
|
249
|
+
const passages: Passage[] = [];
|
|
250
|
+
const runFns: (() => Promise<void>)[] = [];
|
|
251
|
+
|
|
284
252
|
if (masterSection && masterSection.passages) {
|
|
285
|
-
const masterPassage = masterSection.passages[
|
|
253
|
+
const masterPassage = masterSection.passages[""];
|
|
286
254
|
if (masterPassage) {
|
|
287
|
-
|
|
288
|
-
|
|
255
|
+
passages.push(masterPassage);
|
|
256
|
+
runFns.push(() => run(masterPassage, "[[]][]"));
|
|
289
257
|
}
|
|
290
258
|
}
|
|
291
|
-
|
|
259
|
+
|
|
260
|
+
const master = currentSection.passages && currentSection.passages[""];
|
|
292
261
|
if (master) {
|
|
293
|
-
|
|
294
|
-
|
|
262
|
+
passages.push(master);
|
|
263
|
+
runFns.push(() => run(master, `[[${get("_section")}]][]`));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
passages.push(passage);
|
|
267
|
+
runFns.push(() => run(passage, `[[${get("_section")}]][${passageName}]`));
|
|
268
|
+
|
|
269
|
+
if (passages.some(p => p.clear)) {
|
|
270
|
+
clearScreen();
|
|
271
|
+
createSectionElement();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
currentPassageElement = document.createElement("div");
|
|
275
|
+
currentPassageElement.classList.add("squiffy-output-passage");
|
|
276
|
+
currentPassageElement.setAttribute("data-passage", `${passageName}`);
|
|
277
|
+
|
|
278
|
+
currentSectionElement.appendChild(currentPassageElement);
|
|
279
|
+
currentBlockOutputElement = null;
|
|
280
|
+
|
|
281
|
+
for (const fn of runFns) {
|
|
282
|
+
await fn();
|
|
295
283
|
}
|
|
296
|
-
|
|
297
|
-
|
|
284
|
+
|
|
285
|
+
writeUndoLog();
|
|
298
286
|
save();
|
|
287
|
+
const newCanGoBack = canGoBack();
|
|
288
|
+
if (newCanGoBack != oldCanGoBack) {
|
|
289
|
+
emitter.emit("canGoBackChanged", { canGoBack: newCanGoBack });
|
|
290
|
+
}
|
|
299
291
|
}
|
|
300
292
|
|
|
301
|
-
function processAttributes(attributes: string[]) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
setAttribute(attribute);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
293
|
+
async function processAttributes(attributes: string[]) {
|
|
294
|
+
for (const attribute of attributes) {
|
|
295
|
+
setAttribute(attribute);
|
|
296
|
+
}
|
|
310
297
|
}
|
|
311
298
|
|
|
312
299
|
function restart() {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
storageFallback = {};
|
|
323
|
-
}
|
|
324
|
-
if (settings.scroll === 'element') {
|
|
325
|
-
outputElement.innerHTML = '';
|
|
300
|
+
state.reset();
|
|
301
|
+
// TODO: This feels like the wrong way of triggering location.reload()
|
|
302
|
+
// - should be a separate setting to the scroll setting.
|
|
303
|
+
if (settings.scroll === "element" || settings.scroll === "none") {
|
|
304
|
+
outputElement.innerHTML = "";
|
|
326
305
|
begin();
|
|
327
306
|
}
|
|
328
307
|
else {
|
|
@@ -331,106 +310,162 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
331
310
|
}
|
|
332
311
|
|
|
333
312
|
function save() {
|
|
334
|
-
|
|
313
|
+
// TODO: Queue up all attribute changes and save them only when this is called
|
|
314
|
+
set("_output", outputElement.innerHTML);
|
|
335
315
|
}
|
|
336
316
|
|
|
337
317
|
function load() {
|
|
338
|
-
const
|
|
339
|
-
|
|
318
|
+
const runUiJs = () => {
|
|
319
|
+
if (story.uiJsIndex !== undefined) {
|
|
320
|
+
runJs(story.uiJsIndex, {
|
|
321
|
+
registerAnimation: animation.registerAnimation.bind(animation),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
state.load();
|
|
327
|
+
const output = get("_output");
|
|
328
|
+
if (!output) {
|
|
329
|
+
runUiJs();
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
340
333
|
outputElement.innerHTML = output;
|
|
341
334
|
|
|
342
|
-
|
|
343
|
-
|
|
335
|
+
setCurrentSectionElement();
|
|
336
|
+
setCurrentPassageElement();
|
|
337
|
+
currentBlockOutputElement = outputElement.querySelector(".squiffy-output-block:last-child");
|
|
344
338
|
|
|
345
|
-
currentSection = story.sections[get(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
eval('(' + transition + ')()');
|
|
349
|
-
}
|
|
339
|
+
currentSection = story.sections[get("_section")];
|
|
340
|
+
runUiJs();
|
|
341
|
+
pluginManager.onLoad();
|
|
350
342
|
return true;
|
|
351
343
|
}
|
|
352
|
-
|
|
353
|
-
function setSeen(sectionName: string) {
|
|
354
|
-
let seenSections = get('_seen_sections');
|
|
355
|
-
if (!seenSections) seenSections = [];
|
|
356
|
-
if (seenSections.indexOf(sectionName) == -1) {
|
|
357
|
-
seenSections.push(sectionName);
|
|
358
|
-
set('_seen_sections', seenSections);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function seen(sectionName: string) {
|
|
363
|
-
const seenSections = get('_seen_sections');
|
|
364
|
-
if (!seenSections) return false;
|
|
365
|
-
return (seenSections.indexOf(sectionName) > -1);
|
|
366
|
-
}
|
|
367
344
|
|
|
368
345
|
function newBlockOutputElement() {
|
|
369
|
-
currentBlockOutputElement = document.createElement(
|
|
370
|
-
currentBlockOutputElement.classList.add(
|
|
371
|
-
currentSectionElement?.appendChild(currentBlockOutputElement);
|
|
346
|
+
currentBlockOutputElement = document.createElement("div");
|
|
347
|
+
currentBlockOutputElement.classList.add("squiffy-output-block");
|
|
348
|
+
(currentPassageElement || currentSectionElement)?.appendChild(currentBlockOutputElement);
|
|
372
349
|
}
|
|
373
350
|
|
|
374
|
-
function newSection(sectionName
|
|
351
|
+
function newSection(sectionName?: string) {
|
|
375
352
|
if (currentSectionElement) {
|
|
376
|
-
currentSectionElement.querySelectorAll(
|
|
377
|
-
const attribute = el.getAttribute(
|
|
353
|
+
currentSectionElement.querySelectorAll("input").forEach(el => {
|
|
354
|
+
const attribute = el.getAttribute("data-attribute") || el.id;
|
|
378
355
|
if (attribute) set(attribute, el.value);
|
|
379
356
|
el.disabled = true;
|
|
380
357
|
});
|
|
381
358
|
|
|
382
359
|
currentSectionElement.querySelectorAll("[contenteditable]").forEach(el => {
|
|
383
|
-
const attribute = el.getAttribute(
|
|
360
|
+
const attribute = el.getAttribute("data-attribute") || el.id;
|
|
384
361
|
if (attribute) set(attribute, el.innerHTML);
|
|
385
|
-
(el as HTMLElement).contentEditable =
|
|
362
|
+
(el as HTMLElement).contentEditable = "false";
|
|
386
363
|
});
|
|
387
364
|
|
|
388
|
-
currentSectionElement.querySelectorAll(
|
|
389
|
-
const attribute = el.getAttribute(
|
|
365
|
+
currentSectionElement.querySelectorAll("textarea").forEach(el => {
|
|
366
|
+
const attribute = el.getAttribute("data-attribute") || el.id;
|
|
390
367
|
if (attribute) set(attribute, el.value);
|
|
391
368
|
el.disabled = true;
|
|
392
369
|
});
|
|
393
370
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
currentSectionElement
|
|
402
|
-
|
|
403
|
-
|
|
371
|
+
|
|
372
|
+
currentPassageElement = null;
|
|
373
|
+
createSectionElement(sectionName);
|
|
374
|
+
newBlockOutputElement();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function createSectionElement(sectionName?: string) {
|
|
378
|
+
currentSectionElement = document.createElement("div");
|
|
379
|
+
currentSectionElement.classList.add("squiffy-output-section");
|
|
380
|
+
currentSectionElement.setAttribute("data-section", sectionName ?? get("_section"));
|
|
381
|
+
if (!sectionName) {
|
|
382
|
+
currentSectionElement.setAttribute("data-clear", "true");
|
|
404
383
|
}
|
|
405
384
|
outputElement.appendChild(currentSectionElement);
|
|
406
|
-
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function getClearStack() {
|
|
388
|
+
return outputElement.querySelector<HTMLElement>(".squiffy-clear-stack");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function clearScreen() {
|
|
392
|
+
// Callers should call createSectionElement() after calling this function, so there's a place for new
|
|
393
|
+
// output to go. We don't call this automatically within this function, in case we're just about to create
|
|
394
|
+
// a new section anyway.
|
|
395
|
+
let clearStack = getClearStack();
|
|
396
|
+
if (!clearStack) {
|
|
397
|
+
clearStack = document.createElement("div");
|
|
398
|
+
clearStack.classList.add("squiffy-clear-stack");
|
|
399
|
+
clearStack.style.display = "none";
|
|
400
|
+
outputElement.prepend(clearStack);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const clearStackItem = document.createElement("div");
|
|
404
|
+
clearStack.appendChild(clearStackItem);
|
|
405
|
+
|
|
406
|
+
// Move everything in the outputElement (except the clearStack itself) into the new clearStackItem
|
|
407
|
+
for (const child of [...outputElement.children]) {
|
|
408
|
+
if (child !== clearStack) {
|
|
409
|
+
clearStackItem.appendChild(child);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// NOTE: If we offer an option to disable the "back" feature, all of the above can be replaced with:
|
|
414
|
+
// outputElement.innerHTML = '';
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function unClearScreen() {
|
|
418
|
+
const clearStack = getClearStack();
|
|
419
|
+
for (const child of [...outputElement.children]) {
|
|
420
|
+
if (child !== clearStack) {
|
|
421
|
+
child.remove();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const clearStackItem = clearStack.children[clearStack.children.length - 1];
|
|
426
|
+
for (const child of [...clearStackItem.children]) {
|
|
427
|
+
outputElement.appendChild(child);
|
|
428
|
+
}
|
|
429
|
+
clearStackItem.remove();
|
|
407
430
|
}
|
|
408
431
|
|
|
409
432
|
const ui = {
|
|
410
433
|
write: (text: string, source: string) => {
|
|
411
|
-
if (!currentBlockOutputElement) return;
|
|
412
434
|
scrollPosition = outputElement.scrollHeight;
|
|
413
|
-
|
|
414
|
-
const
|
|
435
|
+
|
|
436
|
+
const html = ui.processText(text, false).trim();
|
|
437
|
+
|
|
438
|
+
// Previously, we skipped the rest of this if "html" came back as an empty string.
|
|
439
|
+
// But, we _do_ always want the block to be created, in the editor at least - as the
|
|
440
|
+
// author might be in the middle of an edit. When they start writing text for this
|
|
441
|
+
// source (section or passage), we want it to appear in the right place.
|
|
442
|
+
// TODO: What if this comes from a master section/passage though, and there's no
|
|
443
|
+
// text (just a script)? Or if there's conditional text that doesn't display?
|
|
444
|
+
|
|
445
|
+
if (!currentBlockOutputElement) {
|
|
446
|
+
newBlockOutputElement();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const div = document.createElement("div");
|
|
415
450
|
if (source) {
|
|
416
|
-
div.setAttribute(
|
|
451
|
+
div.setAttribute("data-source", source);
|
|
417
452
|
}
|
|
453
|
+
|
|
454
|
+
div.innerHTML = html;
|
|
455
|
+
pluginManager.onWrite(div);
|
|
418
456
|
currentBlockOutputElement.appendChild(div);
|
|
419
|
-
div.innerHTML = ui.processText(text);
|
|
420
|
-
|
|
421
457
|
ui.scrollToEnd();
|
|
422
458
|
},
|
|
423
459
|
clearScreen: () => {
|
|
424
|
-
|
|
425
|
-
|
|
460
|
+
clearScreen();
|
|
461
|
+
createSectionElement();
|
|
426
462
|
},
|
|
427
463
|
scrollToEnd: () => {
|
|
428
|
-
if (settings.scroll ===
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
464
|
+
if (settings.scroll === "none") {
|
|
465
|
+
// do nothing
|
|
466
|
+
}
|
|
467
|
+
else if (settings.scroll === "element") {
|
|
468
|
+
outputElement.lastElementChild.scrollIntoView({ block: "end", inline: "nearest", behavior: "smooth" });
|
|
434
469
|
}
|
|
435
470
|
else {
|
|
436
471
|
let scrollTo = scrollPosition;
|
|
@@ -438,103 +473,139 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
438
473
|
if (scrollTo > currentScrollTop) {
|
|
439
474
|
const maxScrollTop = document.documentElement.scrollHeight - window.innerHeight;
|
|
440
475
|
if (scrollTo > maxScrollTop) scrollTo = maxScrollTop;
|
|
441
|
-
window.scrollTo({ top: scrollTo, behavior:
|
|
476
|
+
window.scrollTo({ top: scrollTo, behavior: "smooth" });
|
|
442
477
|
}
|
|
443
478
|
}
|
|
444
479
|
},
|
|
445
|
-
processText: (text: string) => {
|
|
446
|
-
|
|
447
|
-
fulltext: text
|
|
448
|
-
};
|
|
449
|
-
return textProcessor.process(text, data);
|
|
450
|
-
},
|
|
451
|
-
transition: function (f: any) {
|
|
452
|
-
set('_transition', f.toString());
|
|
453
|
-
f();
|
|
480
|
+
processText: (text: string, inline: boolean) => {
|
|
481
|
+
return textProcessor.process(text, inline);
|
|
454
482
|
},
|
|
455
483
|
};
|
|
456
484
|
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
485
|
+
function update(newStory: Story) {
|
|
486
|
+
if (newStory.start != story.start) {
|
|
487
|
+
story = newStory;
|
|
488
|
+
state.reset();
|
|
489
|
+
outputElement.innerHTML = "";
|
|
490
|
+
go(story.start);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
updateStory(story, newStory, outputElement, ui, disableLink);
|
|
495
|
+
|
|
496
|
+
story = newStory;
|
|
460
497
|
|
|
461
|
-
|
|
462
|
-
|
|
498
|
+
setCurrentSectionElement();
|
|
499
|
+
setCurrentPassageElement();
|
|
463
500
|
}
|
|
464
501
|
|
|
465
|
-
function
|
|
466
|
-
|
|
502
|
+
function setCurrentSectionElement() {
|
|
503
|
+
// Multiple .squiffy-output-section elements may be "last-child" if some have been moved to the clear-stack,
|
|
504
|
+
// so we want the very last one
|
|
505
|
+
const allSectionElements = outputElement.querySelectorAll<HTMLElement>(".squiffy-output-section:last-child");
|
|
506
|
+
currentSectionElement = allSectionElements[allSectionElements.length - 1];
|
|
507
|
+
const sectionName = currentSectionElement.getAttribute("data-section");
|
|
508
|
+
currentSection = story.sections[sectionName];
|
|
467
509
|
}
|
|
468
510
|
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
.querySelectorAll("a.link-passage.disabled"))
|
|
473
|
-
.map((el: HTMLElement) => el.getAttribute("data-passage"));
|
|
511
|
+
function setCurrentPassageElement() {
|
|
512
|
+
currentPassageElement = currentSectionElement.querySelector(".squiffy-output-passage:last-child");
|
|
513
|
+
}
|
|
474
514
|
|
|
475
|
-
|
|
515
|
+
function getHistoryCount() {
|
|
516
|
+
const clearStack = getClearStack();
|
|
517
|
+
const sectionPassageCount = outputElement.querySelectorAll(".squiffy-output-section").length
|
|
518
|
+
+ outputElement.querySelectorAll(".squiffy-output-passage").length;
|
|
476
519
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const link = element.querySelector(`a.link-passage[data-passage="${passage}"]`);
|
|
480
|
-
if (link) disableLink(link);
|
|
520
|
+
if (!clearStack) {
|
|
521
|
+
return sectionPassageCount;
|
|
481
522
|
}
|
|
523
|
+
|
|
524
|
+
return sectionPassageCount + clearStack.children.length;
|
|
482
525
|
}
|
|
483
526
|
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
527
|
+
function canGoBack() {
|
|
528
|
+
return getHistoryCount() > 1;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function goBack() {
|
|
532
|
+
if (!canGoBack()) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const clearStack = getClearStack();
|
|
537
|
+
|
|
538
|
+
if (currentPassageElement) {
|
|
539
|
+
const currentPassage = currentPassageElement.getAttribute("data-passage");
|
|
540
|
+
doUndo(currentPassageElement.getAttribute("data-undo"));
|
|
541
|
+
currentPassageElement.remove();
|
|
542
|
+
|
|
543
|
+
// If there's nothing left in the outputElement except for an empty section element that
|
|
544
|
+
// was created when the screen was cleared, pop the clear-stack.
|
|
545
|
+
|
|
546
|
+
let hasEmptySection = false;
|
|
547
|
+
let hasOtherElements = false;
|
|
548
|
+
for (const child of outputElement.children) {
|
|
549
|
+
if (child === clearStack) {
|
|
550
|
+
continue;
|
|
495
551
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
updateElementTextPreservingDisabledPassageLinks(element, ui.processText(newSection.text));
|
|
500
|
-
}
|
|
552
|
+
if (child.getAttribute("data-clear") == "true" && child.children.length == 0) {
|
|
553
|
+
hasEmptySection = true;
|
|
554
|
+
continue;
|
|
501
555
|
}
|
|
556
|
+
hasOtherElements = true;
|
|
557
|
+
break;
|
|
502
558
|
}
|
|
503
559
|
|
|
504
|
-
if (!
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (!elements.length) continue;
|
|
560
|
+
if (hasEmptySection && !hasOtherElements) {
|
|
561
|
+
unClearScreen();
|
|
562
|
+
setCurrentSectionElement();
|
|
563
|
+
}
|
|
509
564
|
|
|
510
|
-
|
|
511
|
-
if (
|
|
512
|
-
|
|
513
|
-
for (const element of elements) {
|
|
514
|
-
const parentOutputBlock = element.closest('.squiffy-output-block');
|
|
515
|
-
parentOutputBlock.remove();
|
|
516
|
-
}
|
|
565
|
+
for (const link of currentSectionElement.querySelectorAll("a.squiffy-link[data-passage]")) {
|
|
566
|
+
if (link.getAttribute("data-passage") == currentPassage) {
|
|
567
|
+
enableLink(link);
|
|
517
568
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
setCurrentPassageElement();
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
doUndo(currentSectionElement.getAttribute("data-undo"));
|
|
575
|
+
currentSectionElement.remove();
|
|
576
|
+
|
|
577
|
+
// If there's nothing left in the outputElement except for the clear-stack, pop it
|
|
578
|
+
let hasOtherElements = false;
|
|
579
|
+
for (const child of [...outputElement.children]) {
|
|
580
|
+
if (child === clearStack) {
|
|
581
|
+
continue;
|
|
523
582
|
}
|
|
583
|
+
hasOtherElements = true;
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!hasOtherElements) {
|
|
588
|
+
unClearScreen();
|
|
524
589
|
}
|
|
590
|
+
|
|
591
|
+
setCurrentSectionElement();
|
|
592
|
+
setCurrentPassageElement();
|
|
525
593
|
}
|
|
526
594
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
currentSection = story.sections[sectionName];
|
|
595
|
+
if (!canGoBack()) {
|
|
596
|
+
emitter.emit("canGoBackChanged", { canGoBack: false });
|
|
597
|
+
}
|
|
531
598
|
}
|
|
532
599
|
|
|
533
|
-
|
|
600
|
+
// We create a separate div inside the passed-in element. This allows us to clear the text output, but
|
|
601
|
+
// without affecting any overlays that may have been added to the container (for transitions).
|
|
602
|
+
const outputElementContainer = options.element;
|
|
603
|
+
const outputElement = document.createElement("div");
|
|
604
|
+
outputElementContainer.appendChild(outputElement);
|
|
534
605
|
story = options.story;
|
|
535
606
|
|
|
536
|
-
settings = {
|
|
537
|
-
scroll: options.scroll ||
|
|
607
|
+
const settings = {
|
|
608
|
+
scroll: options.scroll || "body",
|
|
538
609
|
persist: (options.persist === undefined) ? true : options.persist,
|
|
539
610
|
onSet: options.onSet || (() => {})
|
|
540
611
|
};
|
|
@@ -544,26 +615,83 @@ export const init = (options: SquiffyInitOptions): SquiffyApi => {
|
|
|
544
615
|
settings.persist = false;
|
|
545
616
|
}
|
|
546
617
|
|
|
547
|
-
if (settings.scroll ===
|
|
548
|
-
outputElement.style.overflowY =
|
|
618
|
+
if (settings.scroll === "element") {
|
|
619
|
+
outputElement.style.overflowY = "auto";
|
|
549
620
|
}
|
|
550
621
|
|
|
551
|
-
outputElement.addEventListener(
|
|
552
|
-
outputElement.addEventListener(
|
|
622
|
+
outputElement.addEventListener("click", handleClick);
|
|
623
|
+
outputElement.addEventListener("keypress", async function (event) {
|
|
553
624
|
if (event.key !== "Enter") return;
|
|
554
|
-
handleClick(event);
|
|
625
|
+
await handleClick(event);
|
|
555
626
|
});
|
|
556
627
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
628
|
+
let undoLog: Record<string, any> = {};
|
|
629
|
+
|
|
630
|
+
const onSet = function(attribute: string, oldValue: any) {
|
|
631
|
+
if (attribute == "_output") return;
|
|
632
|
+
if (attribute in undoLog) return;
|
|
633
|
+
undoLog[attribute] = oldValue;
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const writeUndoLog = function() {
|
|
637
|
+
(currentPassageElement ?? currentSectionElement).setAttribute("data-undo", JSON.stringify(undoLog));
|
|
638
|
+
undoLog = {};
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
const doUndo = function(undosJson: string | null) {
|
|
642
|
+
if (!undosJson) return;
|
|
643
|
+
const undos = JSON.parse(undosJson) as Record<string, any>;
|
|
644
|
+
if (!undos) return;
|
|
645
|
+
for (const attribute of Object.keys(undos)) {
|
|
646
|
+
state.setInternal(attribute, undos[attribute], false);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
const state = new State(settings.persist, story.id || "", settings.onSet, emitter, onSet);
|
|
651
|
+
const get = state.get.bind(state);
|
|
652
|
+
const set = state.set.bind(state);
|
|
653
|
+
|
|
654
|
+
const textProcessor = new TextProcessor(story, state, () => currentSection);
|
|
655
|
+
const linkHandler = new LinkHandler();
|
|
656
|
+
|
|
657
|
+
const getSectionText = (sectionName: string) => {
|
|
658
|
+
if (sectionName in story.sections) {
|
|
659
|
+
return story.sections[sectionName].text || null;
|
|
660
|
+
}
|
|
661
|
+
return null;
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const getPassageText = (name: string) => {
|
|
665
|
+
if (currentSection.passages && name in currentSection.passages) {
|
|
666
|
+
return currentSection.passages[name].text || null;
|
|
667
|
+
} else if ("passages" in story.sections[""] && story.sections[""].passages && name in story.sections[""].passages) {
|
|
668
|
+
return story.sections[""].passages![name].text || null;
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
const addTransition = (fn: () => Promise<void>) => {
|
|
674
|
+
transitions.push(fn);
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
const animation = new Animation();
|
|
678
|
+
|
|
679
|
+
const pluginManager = new PluginManager(outputElement, textProcessor, state, linkHandler,
|
|
680
|
+
getSectionText, getPassageText, ui.processText, addTransition, animation, emitter);
|
|
681
|
+
pluginManager.add(Plugins.ReplaceLabel());
|
|
682
|
+
pluginManager.add(Plugins.RotateSequencePlugin());
|
|
683
|
+
pluginManager.add(Plugins.RandomPlugin());
|
|
684
|
+
pluginManager.add(Plugins.LivePlugin());
|
|
685
|
+
pluginManager.add(Plugins.AnimatePlugin());
|
|
560
686
|
|
|
561
687
|
return {
|
|
688
|
+
begin: begin,
|
|
562
689
|
restart: restart,
|
|
563
690
|
get: get,
|
|
564
691
|
set: set,
|
|
565
692
|
clickLink: handleLink,
|
|
566
693
|
update: update,
|
|
694
|
+
goBack: goBack,
|
|
567
695
|
on: (e, h) => emitter.on(e, h),
|
|
568
696
|
off: (e, h) => emitter.off(e, h),
|
|
569
697
|
once: (e, h) => emitter.once(e, h),
|