extwee 2.3.3 → 2.3.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.
- package/build/extwee.core.min.js +1 -1
- package/build/extwee.twine1html.min.js +1 -1
- package/build/extwee.twine2archive.min.js +1 -1
- package/build/extwee.tws.min.js +1 -1
- package/docs/build/extwee.core.min.js +1 -0
- package/docs/build/extwee.twine1html.min.js +1 -0
- package/docs/build/extwee.twine2archive.min.js +1 -0
- package/docs/build/extwee.tws.min.js +1 -0
- package/docs/demos/compiler/extwee.core.min.js +1 -0
- package/docs/demos/compiler/index.css +105 -0
- package/docs/demos/compiler/index.html +359 -0
- package/package.json +19 -18
- package/src/CLI/CommandLineProcessing.js +148 -153
- package/src/Passage.js +6 -4
- package/src/Story.js +1 -1
- package/src/Twee/parse.js +117 -21
- package/src/Twine2HTML/parse-web.js +7 -1
- package/src/Web/web-core.js +22 -2
- package/src/Web/web-twine1html.js +25 -5
- package/src/Web/web-twine2archive.js +25 -5
- package/src/Web/web-tws.js +22 -4
- package/test/Objects/Passage.test.js +1 -1
- package/test/Twee/Twee.Escaping.test.js +200 -0
- package/test/Twine1HTML/Twine1HTML.Parse.Web.test.js +484 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.Web.test.js +293 -0
- package/test/Twine2HTML/Twine2HTML.Parse.Web.test.js +329 -0
- package/test/Web/web-core-coverage.test.js +175 -0
- package/test/Web/web-core-global.test.js +93 -0
- package/test/Web/web-core.test.js +156 -0
- package/test/Web/web-twine1html.test.js +105 -0
- package/test/Web/web-twine2archive.test.js +96 -0
- package/test/Web/web-tws.test.js +77 -0
- package/test/Web/window.Extwee.test.js +7 -2
- package/types/src/Story.d.ts +1 -1
- package/types/src/Twee/parse.d.ts +21 -0
- package/types/src/Web/web-core.d.ts +23 -1
- package/types/src/Web/web-twine1html.d.ts +7 -0
- package/types/src/Web/web-twine2archive.d.ts +7 -0
- package/types/src/Web/web-tws.d.ts +5 -0
- package/webpack.config.js +2 -1
- package/src/Web/web-index.js +0 -31
|
@@ -2,14 +2,34 @@
|
|
|
2
2
|
import { parse as parseTwine2ArchiveHTML } from '../Twine2ArchiveHTML/parse-web.js';
|
|
3
3
|
import { compile as compileTwine2ArchiveHTML } from '../Twine2ArchiveHTML/compile.js';
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Create UMD-compatible export object
|
|
6
|
+
const Extwee = {
|
|
7
|
+
parseTwine2ArchiveHTML,
|
|
8
|
+
compileTwine2ArchiveHTML,
|
|
9
|
+
parse: parseTwine2ArchiveHTML, // For module consistency
|
|
10
|
+
compile: compileTwine2ArchiveHTML // For module consistency
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Export for webpack UMD build
|
|
14
|
+
export default Extwee;
|
|
15
|
+
|
|
16
|
+
// Also export individual functions for ES6 module usage
|
|
6
17
|
export {
|
|
7
18
|
parseTwine2ArchiveHTML as parse,
|
|
8
19
|
compileTwine2ArchiveHTML as compile
|
|
9
20
|
};
|
|
10
21
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
window
|
|
22
|
+
// Add to global Extwee object for direct usage
|
|
23
|
+
const globalObject = (function() {
|
|
24
|
+
if (typeof globalThis !== 'undefined') return globalThis;
|
|
25
|
+
if (typeof window !== 'undefined') return window;
|
|
26
|
+
if (typeof global !== 'undefined') return global;
|
|
27
|
+
if (typeof self !== 'undefined') return self;
|
|
28
|
+
return null;
|
|
29
|
+
})();
|
|
30
|
+
|
|
31
|
+
if (globalObject) {
|
|
32
|
+
globalObject.Extwee = globalObject.Extwee || {};
|
|
33
|
+
globalObject.Extwee.parseTwine2ArchiveHTML = parseTwine2ArchiveHTML;
|
|
34
|
+
globalObject.Extwee.compileTwine2ArchiveHTML = compileTwine2ArchiveHTML;
|
|
15
35
|
}
|
package/src/Web/web-tws.js
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
// TWS parser module
|
|
2
2
|
import { parse as parseTWS } from '../TWS/parse.js';
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// Create UMD-compatible export object
|
|
5
|
+
const Extwee = {
|
|
6
|
+
parseTWS,
|
|
7
|
+
parse: parseTWS // For module consistency
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Export for webpack UMD build
|
|
11
|
+
export default Extwee;
|
|
12
|
+
|
|
13
|
+
// Also export individual functions for ES6 module usage
|
|
5
14
|
export {
|
|
6
15
|
parseTWS as parse
|
|
7
16
|
};
|
|
8
17
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
// Add to global Extwee object for direct usage
|
|
19
|
+
const globalObject = (function() {
|
|
20
|
+
if (typeof globalThis !== 'undefined') return globalThis;
|
|
21
|
+
if (typeof window !== 'undefined') return window;
|
|
22
|
+
if (typeof global !== 'undefined') return global;
|
|
23
|
+
if (typeof self !== 'undefined') return self;
|
|
24
|
+
return null;
|
|
25
|
+
})();
|
|
26
|
+
|
|
27
|
+
if (globalObject) {
|
|
28
|
+
globalObject.Extwee = globalObject.Extwee || {};
|
|
29
|
+
globalObject.Extwee.parseTWS = parseTWS;
|
|
12
30
|
}
|
|
@@ -219,7 +219,7 @@ describe('Passage', () => {
|
|
|
219
219
|
|
|
220
220
|
it('Should escape meta-characters safely in Twee header', function () {
|
|
221
221
|
const p = new Passage('Where do tags begin? [well', '', ['hmm']);
|
|
222
|
-
expect(p.toTwee().includes('Where do tags begin? [well [hmm]')).toBe(true);
|
|
222
|
+
expect(p.toTwee().includes('Where do tags begin? \\[well [hmm]')).toBe(true);
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
it('Should produce valid HTML attributes', function () {
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { parse as parseTwee, escapeTweeMetacharacters, unescapeTweeMetacharacters } from '../../src/Twee/parse.js';
|
|
2
|
+
import Passage from '../../src/Passage.js';
|
|
3
|
+
|
|
4
|
+
describe('Twee Escaping', () => {
|
|
5
|
+
describe('unescapeTweeMetacharacters()', () => {
|
|
6
|
+
it('Should unescape square brackets', () => {
|
|
7
|
+
expect(unescapeTweeMetacharacters('Test\\[Name\\]')).toBe('Test[Name]');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('Should unescape curly braces', () => {
|
|
11
|
+
expect(unescapeTweeMetacharacters('Test\\{Name\\}')).toBe('Test{Name}');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('Should unescape backslashes', () => {
|
|
15
|
+
expect(unescapeTweeMetacharacters('Test\\\\Name')).toBe('Test\\Name');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('Should unescape any character after backslash (robust decoding)', () => {
|
|
19
|
+
expect(unescapeTweeMetacharacters('\\q')).toBe('q');
|
|
20
|
+
expect(unescapeTweeMetacharacters('\\x')).toBe('x');
|
|
21
|
+
expect(unescapeTweeMetacharacters('\\5')).toBe('5');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('Should handle complex combinations', () => {
|
|
25
|
+
expect(unescapeTweeMetacharacters('\\[Hello\\] \\{world\\} \\\\\\\\')).toBe('[Hello] {world} \\\\');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('Should handle non-string input gracefully', () => {
|
|
29
|
+
expect(unescapeTweeMetacharacters(null)).toBe(null);
|
|
30
|
+
expect(unescapeTweeMetacharacters(undefined)).toBe(undefined);
|
|
31
|
+
expect(unescapeTweeMetacharacters(123)).toBe(123);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('Should handle empty strings', () => {
|
|
35
|
+
expect(unescapeTweeMetacharacters('')).toBe('');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('Should handle strings without escape sequences', () => {
|
|
39
|
+
expect(unescapeTweeMetacharacters('Normal Text')).toBe('Normal Text');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('escapeTweeMetacharacters()', () => {
|
|
44
|
+
it('Should escape square brackets', () => {
|
|
45
|
+
expect(escapeTweeMetacharacters('Test[Name]')).toBe('Test\\[Name\\]');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('Should escape curly braces', () => {
|
|
49
|
+
expect(escapeTweeMetacharacters('Test{Name}')).toBe('Test\\{Name\\}');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('Should escape backslashes', () => {
|
|
53
|
+
expect(escapeTweeMetacharacters('Test\\Name')).toBe('Test\\\\Name');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('Should escape complex combinations', () => {
|
|
57
|
+
expect(escapeTweeMetacharacters('[Hello] {world} \\\\')).toBe('\\[Hello\\] \\{world\\} \\\\\\\\');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('Should handle non-string input gracefully', () => {
|
|
61
|
+
expect(escapeTweeMetacharacters(null)).toBe(null);
|
|
62
|
+
expect(escapeTweeMetacharacters(undefined)).toBe(undefined);
|
|
63
|
+
expect(escapeTweeMetacharacters(123)).toBe(123);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('Should handle empty strings', () => {
|
|
67
|
+
expect(escapeTweeMetacharacters('')).toBe('');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('Should handle strings without metacharacters', () => {
|
|
71
|
+
expect(escapeTweeMetacharacters('Normal Text')).toBe('Normal Text');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('Round-trip escaping', () => {
|
|
76
|
+
it('Should preserve text through escape -> unescape cycle', () => {
|
|
77
|
+
const original = 'Test[Name]{Value}\\Path';
|
|
78
|
+
const escaped = escapeTweeMetacharacters(original);
|
|
79
|
+
const unescaped = unescapeTweeMetacharacters(escaped);
|
|
80
|
+
expect(unescaped).toBe(original);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('Should handle complex nested scenarios', () => {
|
|
84
|
+
const original = '[{\\}]{[\\]}';
|
|
85
|
+
const escaped = escapeTweeMetacharacters(original);
|
|
86
|
+
const unescaped = unescapeTweeMetacharacters(escaped);
|
|
87
|
+
expect(unescaped).toBe(original);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Twee parsing with escaping', () => {
|
|
92
|
+
it('Should correctly parse escaped passage names', () => {
|
|
93
|
+
const content = ':: Test\\[Name\\] \\{with\\} \\\\brackets\nContent here';
|
|
94
|
+
const story = parseTwee(content);
|
|
95
|
+
expect(story.passages[0].name).toBe('Test[Name] {with} \\brackets');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('Should correctly parse escaped tags', () => {
|
|
99
|
+
const content = ':: TestPassage [tag\\[1\\] tag\\{2\\}]\nContent here';
|
|
100
|
+
const story = parseTwee(content);
|
|
101
|
+
expect(story.passages[0].tags).toEqual(['tag[1]', 'tag{2}']);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('Should handle mixed escaped and non-escaped content', () => {
|
|
105
|
+
const content = ':: Test\\[Passage [normal escaped\\]tag]\nContent here';
|
|
106
|
+
const story = parseTwee(content);
|
|
107
|
+
expect(story.passages[0].name).toBe('Test[Passage');
|
|
108
|
+
expect(story.passages[0].tags).toEqual(['normal', 'escaped]tag']);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('Should handle the cursed.twee test case correctly', () => {
|
|
112
|
+
const content = `:: StoryData
|
|
113
|
+
{
|
|
114
|
+
"ifid": "22F25A58-7062-4927-95B6-F424DDB2EC65",
|
|
115
|
+
"format": "Harlowe",
|
|
116
|
+
"format-version": "3.3.8",
|
|
117
|
+
"start": "[Hello] {world} \\\\\\\\",
|
|
118
|
+
"zoom": 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:: \\[Hello\\] \\{world\\} \\\\\\\\
|
|
122
|
+
Content here`;
|
|
123
|
+
|
|
124
|
+
const story = parseTwee(content);
|
|
125
|
+
expect(story.start).toBe('[Hello] {world} \\\\');
|
|
126
|
+
|
|
127
|
+
// Find the passage by the unescaped name
|
|
128
|
+
const passage = story.getPassageByName('[Hello] {world} \\\\');
|
|
129
|
+
expect(passage).not.toBeNull();
|
|
130
|
+
expect(passage.name).toBe('[Hello] {world} \\\\');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('Should handle complex metadata with escaped keys (if supported)', () => {
|
|
134
|
+
const content = ':: TestPassage {"position": "100,200", "size": "150,100"}\nContent here';
|
|
135
|
+
const story = parseTwee(content);
|
|
136
|
+
expect(story.passages[0].metadata.position).toBe('100,200');
|
|
137
|
+
expect(story.passages[0].metadata.size).toBe('150,100');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Twee generation with escaping', () => {
|
|
142
|
+
it('Should escape passage names when generating Twee', () => {
|
|
143
|
+
const passage = new Passage('Test[Name]', 'Content', [], {});
|
|
144
|
+
const tweeOutput = passage.toTwee();
|
|
145
|
+
expect(tweeOutput).toContain(':: Test\\[Name\\]');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('Should escape tag names when generating Twee', () => {
|
|
149
|
+
const passage = new Passage('TestPassage', 'Content', ['tag[1]', 'tag{2}'], {});
|
|
150
|
+
const tweeOutput = passage.toTwee();
|
|
151
|
+
expect(tweeOutput).toContain('[tag\\[1\\] tag\\{2\\}]');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('Should handle complex escaping scenarios', () => {
|
|
155
|
+
const passage = new Passage('Test[Name]{Value}\\Path', 'Content', ['complex[tag]'], {});
|
|
156
|
+
const tweeOutput = passage.toTwee();
|
|
157
|
+
expect(tweeOutput).toContain(':: Test\\[Name\\]\\{Value\\}\\\\Path');
|
|
158
|
+
expect(tweeOutput).toContain('[complex\\[tag\\]]');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('Should preserve normal names and tags without escaping', () => {
|
|
162
|
+
const passage = new Passage('NormalName', 'Content', ['normaltag'], {});
|
|
163
|
+
const tweeOutput = passage.toTwee();
|
|
164
|
+
expect(tweeOutput).toContain(':: NormalName');
|
|
165
|
+
expect(tweeOutput).toContain('[normaltag]');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('Edge cases', () => {
|
|
170
|
+
it('Should handle empty passage names gracefully', () => {
|
|
171
|
+
const content = ':: \nContent here';
|
|
172
|
+
expect(() => parseTwee(content)).toThrow('Malformed passage header!');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('Should handle passages with only escaped characters as names', () => {
|
|
176
|
+
const content = ':: \\[\\]\nContent here';
|
|
177
|
+
const story = parseTwee(content);
|
|
178
|
+
expect(story.passages[0].name).toBe('[]');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('Should handle multiple consecutive escapes', () => {
|
|
182
|
+
const content = ':: Test\\\\\\\\Name\nContent here';
|
|
183
|
+
const story = parseTwee(content);
|
|
184
|
+
expect(story.passages[0].name).toBe('Test\\\\Name');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('Should handle tags with only metacharacters', () => {
|
|
188
|
+
const content = ':: TestPassage [\\[\\] \\{\\}]\nContent here';
|
|
189
|
+
const story = parseTwee(content);
|
|
190
|
+
expect(story.passages[0].tags).toEqual(['[]', '{}']);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('Should handle mixed whitespace and escaping', () => {
|
|
194
|
+
const content = ':: Test\\[Name\\] [ tag\\[1\\] tag2 ]\nContent here';
|
|
195
|
+
const story = parseTwee(content);
|
|
196
|
+
expect(story.passages[0].name).toBe('Test[Name]');
|
|
197
|
+
expect(story.passages[0].tags).toEqual(['tag[1]', 'tag2']);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|