squiffy-runtime 6.0.0-alpha.1 → 6.0.0-alpha.11

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.
@@ -0,0 +1,177 @@
1
+ import { startsWith, rotate } from "./utils.js";
2
+
3
+ export class TextProcessor {
4
+ get: (attribute: string) => any;
5
+ set: (attribute: string, value: any) => void;
6
+ story: any;
7
+ currentSection: any;
8
+ seen: (section: string) => boolean;
9
+ processAttributes: (attributes: string[]) => void;
10
+
11
+ constructor (get: (attribute: string) => any,
12
+ set: (attribute: string, value: any) => void,
13
+ story: any, currentSection: any,
14
+ seen: (section: string) => boolean,
15
+ processAttributes: (attributes: string[]) => void) {
16
+ this.get = get;
17
+ this.set = set;
18
+ this.story = story;
19
+ this.currentSection = currentSection;
20
+ this.seen = seen;
21
+ this.processAttributes = processAttributes;
22
+ }
23
+
24
+ process(text: string, data: any) {
25
+ let containsUnprocessedSection = false;
26
+ const open = text.indexOf('{');
27
+ let close;
28
+
29
+ if (open > -1) {
30
+ let nestCount = 1;
31
+ let searchStart = open + 1;
32
+ let finished = false;
33
+
34
+ while (!finished) {
35
+ const nextOpen = text.indexOf('{', searchStart);
36
+ const nextClose = text.indexOf('}', searchStart);
37
+
38
+ if (nextClose > -1) {
39
+ if (nextOpen > -1 && nextOpen < nextClose) {
40
+ nestCount++;
41
+ searchStart = nextOpen + 1;
42
+ } else {
43
+ nestCount--;
44
+ searchStart = nextClose + 1;
45
+ if (nestCount === 0) {
46
+ close = nextClose;
47
+ containsUnprocessedSection = true;
48
+ finished = true;
49
+ }
50
+ }
51
+ } else {
52
+ finished = true;
53
+ }
54
+ }
55
+ }
56
+
57
+ if (containsUnprocessedSection) {
58
+ const section = text.substring(open + 1, close);
59
+ const value = this.processTextCommand(section, data);
60
+ text = text.substring(0, open) + value + this.process(text.substring(close! + 1), data);
61
+ }
62
+
63
+ return (text);
64
+ }
65
+
66
+ processTextCommand(text: string, data: any) {
67
+ if (startsWith(text, 'if ')) {
68
+ return this.processTextCommand_If(text, data);
69
+ } else if (startsWith(text, 'else:')) {
70
+ return this.processTextCommand_Else(text, data);
71
+ } else if (startsWith(text, 'label:')) {
72
+ return this.processTextCommand_Label(text, data);
73
+ } else if (/^rotate[: ]/.test(text)) {
74
+ return this.processTextCommand_Rotate('rotate', text);
75
+ } else if (/^sequence[: ]/.test(text)) {
76
+ return this.processTextCommand_Rotate('sequence', text);
77
+ } else if (this.currentSection.passages && text in this.currentSection.passages) {
78
+ return this.process(this.currentSection.passages[text].text || '', data);
79
+ } else if (text in this.story.sections) {
80
+ return this.process(this.story.sections[text].text || '', data);
81
+ } else if (startsWith(text, '@') && !startsWith(text, '@replace')) {
82
+ this.processAttributes(text.substring(1).split(","));
83
+ return "";
84
+ }
85
+ return this.get(text);
86
+ }
87
+
88
+ processTextCommand_If(section: string, data: any) {
89
+ const command = section.substring(3);
90
+ const colon = command.indexOf(':');
91
+ if (colon == -1) {
92
+ return ('{if ' + command + '}');
93
+ }
94
+
95
+ const text = command.substring(colon + 1);
96
+ let condition = command.substring(0, colon);
97
+ condition = condition.replace("<", "&lt;");
98
+ const operatorRegex = /([\w ]*)(=|&lt;=|&gt;=|&lt;&gt;|&lt;|&gt;)(.*)/;
99
+ const match = operatorRegex.exec(condition);
100
+
101
+ let result = false;
102
+
103
+ if (match) {
104
+ const lhs = this.get(match[1]);
105
+ const op = match[2];
106
+ let rhs = match[3];
107
+
108
+ if (startsWith(rhs, '@')) rhs = this.get(rhs.substring(1));
109
+
110
+ if (op == '=' && lhs == rhs) result = true;
111
+ if (op == '&lt;&gt;' && lhs != rhs) result = true;
112
+ if (op == '&gt;' && lhs > rhs) result = true;
113
+ if (op == '&lt;' && lhs < rhs) result = true;
114
+ if (op == '&gt;=' && lhs >= rhs) result = true;
115
+ if (op == '&lt;=' && lhs <= rhs) result = true;
116
+ } else {
117
+ let checkValue = true;
118
+ if (startsWith(condition, 'not ')) {
119
+ condition = condition.substring(4);
120
+ checkValue = false;
121
+ }
122
+
123
+ if (startsWith(condition, 'seen ')) {
124
+ result = (this.seen(condition.substring(5)) == checkValue);
125
+ } else {
126
+ let value = this.get(condition);
127
+ if (value === null) value = false;
128
+ result = (value == checkValue);
129
+ }
130
+ }
131
+
132
+ const textResult = result ? this.process(text, data) : '';
133
+
134
+ data.lastIf = result;
135
+ return textResult;
136
+ }
137
+
138
+ processTextCommand_Else(section: string, data: any) {
139
+ if (!('lastIf' in data) || data.lastIf) return '';
140
+ const text = section.substring(5);
141
+ return this.process(text, data);
142
+ }
143
+
144
+ processTextCommand_Label(section: string, data: any) {
145
+ const command = section.substring(6);
146
+ const eq = command.indexOf('=');
147
+ if (eq == -1) {
148
+ return ('{label:' + command + '}');
149
+ }
150
+
151
+ const text = command.substring(eq + 1);
152
+ const label = command.substring(0, eq);
153
+
154
+ return '<span class="squiffy-label-' + label + '">' + this.process(text, data) + '</span>';
155
+ }
156
+
157
+ processTextCommand_Rotate(type: string, section: string) {
158
+ let options;
159
+ let attribute = '';
160
+ if (section.substring(type.length, type.length + 1) == ' ') {
161
+ const colon = section.indexOf(':');
162
+ if (colon == -1) {
163
+ return '{' + section + '}';
164
+ }
165
+ options = section.substring(colon + 1);
166
+ attribute = section.substring(type.length + 1, colon);
167
+ } else {
168
+ options = section.substring(type.length + 1);
169
+ }
170
+ // TODO: Check - previously there was no second parameter here
171
+ const rotation = rotate(options.replace(/"/g, '&quot;').replace(/'/g, '&#39;'), null);
172
+ if (attribute) {
173
+ this.set(attribute, rotation[0]);
174
+ }
175
+ return '<a class="squiffy-link" data-' + type + '="' + rotation[1] + '" data-attribute="' + attribute + '" role="link">' + rotation[0] + '</a>';
176
+ }
177
+ }
package/src/types.ts ADDED
@@ -0,0 +1,78 @@
1
+ import {SquiffyEventHandler, SquiffyEventMap} from "./events.js";
2
+
3
+ export interface SquiffyInitOptions {
4
+ element: HTMLElement;
5
+ story: Story;
6
+ scroll?: string,
7
+ persist?: boolean,
8
+ onSet?: (attribute: string, value: any) => void,
9
+ }
10
+
11
+ export interface SquiffySettings {
12
+ scroll: string,
13
+ persist: boolean,
14
+ onSet: (attribute: string, value: any) => void,
15
+ }
16
+
17
+ export interface SquiffyApi {
18
+ restart: () => void;
19
+ get: (attribute: string) => any;
20
+ set: (attribute: string, value: any) => void;
21
+ clickLink: (link: HTMLElement) => boolean;
22
+ update: (story: Story) => void;
23
+
24
+ on<E extends keyof SquiffyEventMap>(
25
+ event: E,
26
+ handler: SquiffyEventHandler<E>
27
+ ): () => void; // returns unsubscribe
28
+
29
+ off<E extends keyof SquiffyEventMap>(
30
+ event: E,
31
+ handler: SquiffyEventHandler<E>
32
+ ): void;
33
+
34
+ once<E extends keyof SquiffyEventMap>(
35
+ event: E,
36
+ handler: SquiffyEventHandler<E>
37
+ ): () => void;
38
+ }
39
+
40
+ // Previous versions of Squiffy had "squiffy", "get" and "set" as globals - we now pass these directly into JS functions.
41
+ // We may tidy up this API at some point, though that would be a breaking change.
42
+ interface SquiffyJsFunctionApi {
43
+ get: (attribute: string) => any;
44
+ set: (attribute: string, value: any) => void;
45
+ ui: {
46
+ transition: (f: any) => void;
47
+ };
48
+ story: {
49
+ go: (section: string) => void;
50
+ };
51
+ }
52
+
53
+ export interface Story {
54
+ js: ((
55
+ squiffy: SquiffyJsFunctionApi,
56
+ get: (attribute: string) => any,
57
+ set: (attribute: string, value: any) => void
58
+ ) => void)[];
59
+ start: string;
60
+ id?: string | null;
61
+ sections: Record<string, Section>;
62
+ }
63
+
64
+ export interface Section {
65
+ text?: string;
66
+ clear?: boolean;
67
+ attributes?: string[],
68
+ jsIndex?: number;
69
+ passages?: Record<string, Passage>;
70
+ passageCount?: number;
71
+ }
72
+
73
+ export interface Passage {
74
+ text?: string;
75
+ clear?: boolean;
76
+ attributes?: string[];
77
+ jsIndex?: number;
78
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,14 @@
1
+ export function startsWith(string: string, prefix: string) {
2
+ return string.substring(0, prefix.length) === prefix;
3
+ }
4
+
5
+ export function rotate(options: string, current: string | null) {
6
+ const colon = options.indexOf(':');
7
+ if (colon == -1) {
8
+ return [options, current];
9
+ }
10
+ const next = options.substring(0, colon);
11
+ let remaining = options.substring(colon + 1);
12
+ if (current) remaining += ':' + current;
13
+ return [next, remaining];
14
+ }
package/tsconfig.json CHANGED
@@ -7,7 +7,11 @@
7
7
  "noFallthroughCasesInSwitch": true,
8
8
  "skipLibCheck": true,
9
9
  "outDir": "dist",
10
+ "declaration": true,
11
+ "module": "NodeNext",
12
+ "moduleResolution": "nodenext",
13
+ "allowSyntheticDefaultImports": true
10
14
  },
11
- "include": ["src/squiffy.runtime.ts"]
15
+ "include": ["src/*.ts"]
12
16
  }
13
17