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.
Files changed (41) hide show
  1. package/build/extwee.core.min.js +1 -1
  2. package/build/extwee.twine1html.min.js +1 -1
  3. package/build/extwee.twine2archive.min.js +1 -1
  4. package/build/extwee.tws.min.js +1 -1
  5. package/docs/build/extwee.core.min.js +1 -0
  6. package/docs/build/extwee.twine1html.min.js +1 -0
  7. package/docs/build/extwee.twine2archive.min.js +1 -0
  8. package/docs/build/extwee.tws.min.js +1 -0
  9. package/docs/demos/compiler/extwee.core.min.js +1 -0
  10. package/docs/demos/compiler/index.css +105 -0
  11. package/docs/demos/compiler/index.html +359 -0
  12. package/package.json +19 -18
  13. package/src/CLI/CommandLineProcessing.js +148 -153
  14. package/src/Passage.js +6 -4
  15. package/src/Story.js +1 -1
  16. package/src/Twee/parse.js +117 -21
  17. package/src/Twine2HTML/parse-web.js +7 -1
  18. package/src/Web/web-core.js +22 -2
  19. package/src/Web/web-twine1html.js +25 -5
  20. package/src/Web/web-twine2archive.js +25 -5
  21. package/src/Web/web-tws.js +22 -4
  22. package/test/Objects/Passage.test.js +1 -1
  23. package/test/Twee/Twee.Escaping.test.js +200 -0
  24. package/test/Twine1HTML/Twine1HTML.Parse.Web.test.js +484 -0
  25. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.Web.test.js +293 -0
  26. package/test/Twine2HTML/Twine2HTML.Parse.Web.test.js +329 -0
  27. package/test/Web/web-core-coverage.test.js +175 -0
  28. package/test/Web/web-core-global.test.js +93 -0
  29. package/test/Web/web-core.test.js +156 -0
  30. package/test/Web/web-twine1html.test.js +105 -0
  31. package/test/Web/web-twine2archive.test.js +96 -0
  32. package/test/Web/web-tws.test.js +77 -0
  33. package/test/Web/window.Extwee.test.js +7 -2
  34. package/types/src/Story.d.ts +1 -1
  35. package/types/src/Twee/parse.d.ts +21 -0
  36. package/types/src/Web/web-core.d.ts +23 -1
  37. package/types/src/Web/web-twine1html.d.ts +7 -0
  38. package/types/src/Web/web-twine2archive.d.ts +7 -0
  39. package/types/src/Web/web-tws.d.ts +5 -0
  40. package/webpack.config.js +2 -1
  41. 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
- // Export for use as a separate module
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
- // Also add to global Extwee if it exists
12
- if (typeof window !== 'undefined' && window.Extwee) {
13
- window.Extwee.parseTwine2ArchiveHTML = parseTwine2ArchiveHTML;
14
- window.Extwee.compileTwine2ArchiveHTML = compileTwine2ArchiveHTML;
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
  }
@@ -1,12 +1,30 @@
1
1
  // TWS parser module
2
2
  import { parse as parseTWS } from '../TWS/parse.js';
3
3
 
4
- // Export for use as a separate module
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
- // Also add to global Extwee if it exists
10
- if (typeof window !== 'undefined' && window.Extwee) {
11
- window.Extwee.parseTWS = parseTWS;
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
+ });