extwee 2.3.1 → 2.3.3

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 (43) hide show
  1. package/build/extwee.core.min.js +1 -0
  2. package/build/extwee.twine1html.min.js +1 -0
  3. package/build/extwee.twine2archive.min.js +1 -0
  4. package/build/extwee.tws.min.js +1 -0
  5. package/build/test-modular.html +126 -0
  6. package/eslint.config.js +4 -1
  7. package/package.json +20 -17
  8. package/src/IFID/generate.js +2 -2
  9. package/src/Story.js +1 -1
  10. package/src/Twine1HTML/parse-web.js +255 -0
  11. package/src/Twine2ArchiveHTML/parse-web.js +134 -0
  12. package/src/Twine2HTML/parse-web.js +428 -0
  13. package/src/Web/web-core.js +31 -0
  14. package/src/Web/web-index.js +31 -0
  15. package/src/Web/web-twine1html.js +15 -0
  16. package/src/Web/web-twine2archive.js +15 -0
  17. package/src/Web/web-tws.js +12 -0
  18. package/test/Config/Config.test.js +1 -1
  19. package/test/Config/isDirectory.test.js +15 -9
  20. package/test/Config/isFile.test.js +14 -11
  21. package/test/Config/loadStoryFormat.test.js +49 -33
  22. package/test/Config/readDirectories.test.js +25 -15
  23. package/test/Objects/Story.test.js +1 -0
  24. package/test/StoryFormat/StoryFormat.Parse.test.js +1 -0
  25. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +1 -0
  26. package/test/Twine2HTML/Twine2HTML.Parse.test.js +1 -0
  27. package/test/Web/window.Extwee.test.js +20 -13
  28. package/types/src/Story.d.ts +1 -1
  29. package/types/src/Twine1HTML/parse-web.d.ts +10 -0
  30. package/types/src/Twine2ArchiveHTML/parse-web.d.ts +37 -0
  31. package/types/src/Twine2HTML/parse-web.d.ts +21 -0
  32. package/types/src/Web/html-entities-lite.d.ts +12 -0
  33. package/types/src/Web/semver-lite.d.ts +10 -0
  34. package/types/src/Web/uuid-lite.d.ts +6 -0
  35. package/types/src/Web/web-core.d.ts +1 -0
  36. package/types/src/Web/web-index.d.ts +1 -0
  37. package/types/src/Web/web-twine1html.d.ts +3 -0
  38. package/types/src/Web/web-twine2archive.d.ts +3 -0
  39. package/types/src/Web/web-tws.d.ts +2 -0
  40. package/webpack.config.js +22 -2
  41. package/build/extwee.web.min.js +0 -2
  42. package/build/extwee.web.min.js.LICENSE.txt +0 -1
  43. package/web-index.js +0 -31
@@ -0,0 +1,126 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Extwee Modular Build Test</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ margin: 20px;
11
+ line-height: 1.6;
12
+ }
13
+ .module {
14
+ margin: 20px 0;
15
+ padding: 15px;
16
+ border: 1px solid #ddd;
17
+ border-radius: 5px;
18
+ }
19
+ .size-info {
20
+ color: #666;
21
+ font-size: 0.9em;
22
+ }
23
+ .success {
24
+ color: green;
25
+ }
26
+ .error {
27
+ color: red;
28
+ }
29
+ pre {
30
+ background: #f5f5f5;
31
+ padding: 10px;
32
+ border-radius: 3px;
33
+ overflow-x: auto;
34
+ }
35
+ </style>
36
+ </head>
37
+ <body>
38
+ <h1>Extwee Modular Build Test</h1>
39
+
40
+ <div class="module">
41
+ <h2>Core Module</h2>
42
+ <div class="size-info">File size: 79K</div>
43
+ <div id="core-result">Loading...</div>
44
+ </div>
45
+
46
+ <div class="module">
47
+ <h2>Twine1HTML Module</h2>
48
+ <div class="size-info">File size: 45K</div>
49
+ <div id="twine1html-result">Loading...</div>
50
+ </div>
51
+
52
+ <div class="module">
53
+ <h2>Twine2Archive Module</h2>
54
+ <div class="size-info">File size: 49K</div>
55
+ <div id="twine2archive-result">Loading...</div>
56
+ </div>
57
+
58
+ <div class="module">
59
+ <h2>TWS Module</h2>
60
+ <div class="size-info">File size: 52K</div>
61
+ <div id="tws-result">Loading...</div>
62
+ </div>
63
+
64
+ <div class="module">
65
+ <h2>Bundle Size Summary</h2>
66
+ <ul>
67
+ <li><strong>Core bundle:</strong> 79K (most common functionality)</li>
68
+ <li><strong>Individual modules:</strong> 45K-52K each (specialized parsers)</li>
69
+ <li><strong>All modules total:</strong> 225K (vs. original 290K - 22% reduction)</li>
70
+ <li><strong>Core only:</strong> 79K (vs. original 290K - 73% reduction)</li>
71
+ </ul>
72
+ <p><strong>Advantage:</strong> Users can load only what they need!</p>
73
+ </div>
74
+
75
+ <!-- Load Core Module -->
76
+ <script src="extwee.core.min.js"></script>
77
+ <script>
78
+ try {
79
+ console.log('Core Extwee:', typeof Extwee);
80
+ document.getElementById('core-result').innerHTML =
81
+ '<span class="success">✓ Core module loaded successfully</span><br>' +
82
+ 'Available: ' + Object.keys(Extwee).join(', ');
83
+ } catch (e) {
84
+ document.getElementById('core-result').innerHTML =
85
+ '<span class="error">✗ Error loading core: ' + e.message + '</span>';
86
+ }
87
+ </script>
88
+
89
+ <!-- Load Twine1HTML Module -->
90
+ <script src="extwee.twine1html.min.js"></script>
91
+ <script>
92
+ try {
93
+ // The Twine1HTML module should extend the same Extwee global
94
+ document.getElementById('twine1html-result').innerHTML =
95
+ '<span class="success">✓ Twine1HTML module loaded successfully</span>';
96
+ } catch (e) {
97
+ document.getElementById('twine1html-result').innerHTML =
98
+ '<span class="error">✗ Error loading Twine1HTML: ' + e.message + '</span>';
99
+ }
100
+ </script>
101
+
102
+ <!-- Load Twine2Archive Module -->
103
+ <script src="extwee.twine2archive.min.js"></script>
104
+ <script>
105
+ try {
106
+ document.getElementById('twine2archive-result').innerHTML =
107
+ '<span class="success">✓ Twine2Archive module loaded successfully</span>';
108
+ } catch (e) {
109
+ document.getElementById('twine2archive-result').innerHTML =
110
+ '<span class="error">✗ Error loading Twine2Archive: ' + e.message + '</span>';
111
+ }
112
+ </script>
113
+
114
+ <!-- Load TWS Module -->
115
+ <script src="extwee.tws.min.js"></script>
116
+ <script>
117
+ try {
118
+ document.getElementById('tws-result').innerHTML =
119
+ '<span class="success">✓ TWS module loaded successfully</span>';
120
+ } catch (e) {
121
+ document.getElementById('tws-result').innerHTML =
122
+ '<span class="error">✗ Error loading TWS: ' + e.message + '</span>';
123
+ }
124
+ </script>
125
+ </body>
126
+ </html>
package/eslint.config.js CHANGED
@@ -18,7 +18,10 @@ export default [
18
18
  jsdoc: jsdoc
19
19
  },
20
20
  rules: {
21
- 'jsdoc/require-description': 'warn'
21
+ 'jsdoc/require-description': 'warn',
22
+ 'jsdoc/check-tag-names': ['error', {
23
+ definedTags: ['jest-environment']
24
+ }]
22
25
  }
23
26
  },
24
27
  pluginJs.configs.recommended,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "extwee",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "A story compiler tool using Twine-compatible formats",
5
5
  "author": "Dan Cox",
6
6
  "main": "index.js",
@@ -8,10 +8,11 @@
8
8
  "extwee": "src/extwee.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "jest --runInBand",
11
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
12
12
  "lint": "eslint ./src/**/*.js --fix",
13
13
  "lint:test": "eslint ./test/**/*.test.js --fix",
14
14
  "build:web": "webpack",
15
+ "analyze:web": "webpack-bundle-analyzer build/extwee.web.min.js",
15
16
  "gen-types": "npx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly --outDir types",
16
17
  "all": "npm run lint && npm run lint:test && npm run test && npm run build:web && npm run gen-types"
17
18
  },
@@ -30,29 +31,31 @@
30
31
  "pickleparser": "^0.2.1",
31
32
  "semver": "^7.7.2",
32
33
  "shelljs": "^0.10.0",
33
- "uuid": "^11.1.0"
34
+ "uuid": "^12.0.0"
34
35
  },
35
36
  "devDependencies": {
36
- "@babel/cli": "^7.28.0",
37
- "@babel/core": "^7.28.0",
38
- "@babel/preset-env": "^7.28.0",
39
- "@eslint/js": "^9.29.0",
40
- "@inquirer/prompts": "^7.6.0",
41
- "@types/semver": "^7.7.0",
37
+ "@babel/cli": "^7.28.3",
38
+ "@babel/core": "^7.28.4",
39
+ "@babel/preset-env": "^7.28.3",
40
+ "@eslint/js": "^9.35.0",
41
+ "@inquirer/prompts": "^7.8.4",
42
+ "@types/node": "^24.3.1",
43
+ "@types/semver": "^7.7.1",
42
44
  "@types/uuid": "^10.0.0",
43
45
  "babel-loader": "^10.0.0",
44
46
  "clean-jsdoc-theme": "^4.3.0",
45
- "core-js": "^3.43.0",
46
- "eslint": "^9.29.0",
47
+ "core-js": "^3.45.1",
48
+ "eslint": "^9.35.0",
47
49
  "eslint-plugin-jest": "^29.0.1",
48
- "eslint-plugin-jsdoc": "^51.0.1",
50
+ "eslint-plugin-jsdoc": "^54.5.0",
49
51
  "globals": "^16.3.0",
50
- "jest": "^30.0.4",
51
- "jest-environment-jsdom": "^30.0.4",
52
+ "jest": "^30.1.3",
53
+ "jest-environment-jsdom": "^30.1.2",
52
54
  "regenerator-runtime": "^0.14.1",
53
- "typescript": "^5.8.3",
54
- "typescript-eslint": "^8.34.0",
55
- "webpack": "^5.99.9",
55
+ "typescript": "^5.9.2",
56
+ "typescript-eslint": "^8.42.0",
57
+ "webpack": "^5.101.3",
58
+ "webpack-bundle-analyzer": "^4.10.2",
56
59
  "webpack-cli": "^6.0.1"
57
60
  },
58
61
  "repository": {
@@ -1,4 +1,4 @@
1
- import { v4 } from 'uuid';
1
+ import { v4 as uuidv4 } from 'uuid';
2
2
 
3
3
  /**
4
4
  * Generates an Interactive Fiction Identification (IFID) based the Treaty of Babel.
@@ -14,7 +14,7 @@ import { v4 } from 'uuid';
14
14
  * // => 'A1B2C3D4-E5F6-G7H8-I9J0-K1L2M3N4O5P6'
15
15
  */
16
16
  function generate () {
17
- return v4().toUpperCase();
17
+ return uuidv4().toUpperCase();
18
18
  }
19
19
 
20
20
  export { generate };
package/src/Story.js CHANGED
@@ -7,7 +7,7 @@ import { encode } from 'html-entities';
7
7
  const creatorName = 'extwee';
8
8
 
9
9
  // Set the creator version.
10
- const creatorVersion = '2.3.1';
10
+ const creatorVersion = '2.3.3';
11
11
 
12
12
  /**
13
13
  * Story class.
@@ -0,0 +1,255 @@
1
+ import Passage from '../Passage.js';
2
+ import { Story } from '../Story.js';
3
+
4
+ /**
5
+ * Lightweight HTML parser for web builds - specifically for Twine 1 HTML parsing
6
+ * This replaces node-html-parser to reduce bundle size
7
+ */
8
+ class LightweightTwine1Parser {
9
+ constructor(html) {
10
+ this.html = html;
11
+ this.doc = null;
12
+
13
+ // Parse HTML using browser's native DOMParser if available, otherwise fallback
14
+ if (typeof DOMParser !== 'undefined') {
15
+ const parser = new DOMParser();
16
+ this.doc = parser.parseFromString(html, 'text/html');
17
+ } else {
18
+ // Fallback for environments without DOMParser
19
+ this.doc = this.createSimpleDOM(html);
20
+ }
21
+ }
22
+
23
+ querySelector(selector) {
24
+ if (this.doc && this.doc.querySelector) {
25
+ return this.doc.querySelector(selector);
26
+ }
27
+
28
+ // Simple fallback implementation
29
+ if (selector === '#storeArea') {
30
+ const match = this.html.match(/<div[^>]*id=["']storeArea["'][^>]*>/i);
31
+ return match ? { found: true } : null;
32
+ }
33
+ if (selector === '#store-area') {
34
+ const match = this.html.match(/<div[^>]*id=["']store-area["'][^>]*>/i);
35
+ return match ? { found: true } : null;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ querySelectorAll(selector) {
41
+ if (this.doc && this.doc.querySelectorAll) {
42
+ return Array.from(this.doc.querySelectorAll(selector));
43
+ }
44
+
45
+ // Fallback implementation for [tiddler] elements
46
+ if (selector === '[tiddler]') {
47
+ return this.extractTiddlerElements();
48
+ }
49
+ return [];
50
+ }
51
+
52
+ extractTiddlerElements() {
53
+ const tiddlerRegex = /<div[^>]*tiddler=["']([^"']+)["'][^>]*>([\s\S]*?)<\/div>/gi;
54
+ const elements = [];
55
+ let match;
56
+
57
+ while ((match = tiddlerRegex.exec(this.html)) !== null) {
58
+ const elementHtml = match[0];
59
+ const attributes = this.parseAttributes(elementHtml);
60
+ const textContent = this.extractTextContent(match[2]);
61
+
62
+ elements.push({
63
+ attributes,
64
+ rawText: textContent
65
+ });
66
+ }
67
+
68
+ return elements;
69
+ }
70
+
71
+ parseAttributes(elementHtml) {
72
+ const attributes = {};
73
+
74
+ // Extract tiddler attribute
75
+ const tiddlerMatch = elementHtml.match(/tiddler=["']([^"']+)["']/i);
76
+ if (tiddlerMatch) {
77
+ attributes.tiddler = tiddlerMatch[1];
78
+ }
79
+
80
+ // Extract tags attribute
81
+ const tagsMatch = elementHtml.match(/tags=["']([^"']*)["']/i);
82
+ if (tagsMatch) {
83
+ attributes.tags = tagsMatch[1];
84
+ }
85
+
86
+ // Extract twine-position attribute
87
+ const positionMatch = elementHtml.match(/twine-position=["']([^"']+)["']/i);
88
+ if (positionMatch) {
89
+ attributes['twine-position'] = positionMatch[1];
90
+ }
91
+
92
+ // Extract modifier attribute
93
+ const modifierMatch = elementHtml.match(/modifier=["']([^"']+)["']/i);
94
+ if (modifierMatch) {
95
+ attributes.modifier = modifierMatch[1];
96
+ }
97
+
98
+ return attributes;
99
+ }
100
+
101
+ extractTextContent(html) {
102
+ // Remove HTML tags and decode basic entities
103
+ return html
104
+ .replace(/<[^>]*>/g, '') // Remove HTML tags
105
+ .replace(/&lt;/g, '<')
106
+ .replace(/&gt;/g, '>')
107
+ .replace(/&quot;/g, '"')
108
+ .replace(/&#39;/g, "'")
109
+ .replace(/&amp;/g, '&') // This should be last
110
+ .trim();
111
+ }
112
+
113
+ createSimpleDOM(html) {
114
+ // Minimal DOM-like object for fallback
115
+ return {
116
+ querySelector: (selector) => {
117
+ if (selector === '#storeArea' && html.includes('id="storeArea"')) {
118
+ return { found: true };
119
+ }
120
+ if (selector === '#store-area' && html.includes('id="store-area"')) {
121
+ return { found: true };
122
+ }
123
+ return null;
124
+ },
125
+ querySelectorAll: (selector) => {
126
+ if (selector === '[tiddler]') {
127
+ return this.extractTiddlerElements();
128
+ }
129
+ return [];
130
+ }
131
+ };
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Web-optimized Twine 1 HTML parser with reduced dependencies
137
+ * Parses Twine 1 HTML into a Story object using lightweight DOM parsing
138
+ * @see {@link https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md Twine 1 HTML Documentation}
139
+ * @function parse
140
+ * @param {string} content - Twine 1 HTML content to parse.
141
+ * @returns {Story} Story object
142
+ */
143
+ function parse(content) {
144
+ // Create a default Story.
145
+ const s = new Story();
146
+
147
+ // Use lightweight parser for web builds
148
+ const dom = new LightweightTwine1Parser(content);
149
+
150
+ // Look for `<div id="storeArea">`.
151
+ let storyData = dom.querySelector('#storeArea');
152
+
153
+ // Does the `<div id="storeArea">` element exist?
154
+ if (storyData === null) {
155
+ // Look for `<div id="store-area">`.
156
+ storyData = dom.querySelector('#store-area');
157
+ // Check for null
158
+ if (storyData == null) {
159
+ // Can't find any story data.
160
+ throw new Error('Cannot find #storeArea or #store-area!');
161
+ }
162
+ }
163
+
164
+ // Pull out the `[tiddler]` elements.
165
+ const storyPassages = dom.querySelectorAll('[tiddler]');
166
+
167
+ // Move through the passages.
168
+ for (const passage of storyPassages) {
169
+ // Get the passage attributes.
170
+ const attr = passage.attributes;
171
+ // Get the passage text.
172
+ const text = passage.rawText;
173
+
174
+ /**
175
+ * twine-position: (string) Required.
176
+ * Comma-separated X and Y coordinates of the passage within Twine 1.
177
+ */
178
+ // Set a default position.
179
+ let position = null;
180
+ // Does position exist?
181
+ if (Object.prototype.hasOwnProperty.call(attr, 'twine-position')) {
182
+ // Update position.
183
+ position = attr['twine-position'];
184
+ }
185
+
186
+ /**
187
+ * tiddler: (string) Required.
188
+ * The name of the passage.
189
+ */
190
+ // Create a default value.
191
+ const name = attr.tiddler;
192
+ // Is this `StoryTitle`?
193
+ if (name === 'StoryTitle') {
194
+ // If StoryTitle exists, we accept the story name.
195
+ s.name = text;
196
+ }
197
+
198
+ /**
199
+ * tags: (string) Required.
200
+ * Space-separated list of passages tags, if any.
201
+ */
202
+ // Create empty tag array.
203
+ let tags = [];
204
+ // Does the tags attribute exist?
205
+ if (Object.prototype.hasOwnProperty.call(attr, 'tags')) {
206
+ // Escape any tags
207
+ // (Attributes can, themselves, be empty strings.)
208
+ if (attr.tags.length > 0 && attr.tags !== '""') {
209
+ // Escape the tags.
210
+ tags = attr.tags;
211
+ // Split by spaces into an array.
212
+ tags = tags.split(' ');
213
+ }
214
+
215
+ // Remove any empty strings.
216
+ tags = tags.filter(tag => tag !== '');
217
+ }
218
+
219
+ // Create metadata for passage.
220
+ // We translate Twine 1 attribute into Twine 2 metadata.
221
+ const metadata = {};
222
+
223
+ // Does position exist?
224
+ if (position !== null) {
225
+ // Add the property to metadata
226
+ metadata.position = position;
227
+ }
228
+
229
+ /**
230
+ * modifier: (string) Optional.
231
+ * Name of the tool that last edited the passage.
232
+ * Generally, for versions of Twine 1, this value will be "twee".
233
+ * Twee compilers may place their own name (e.g. "tweego" for Tweego).
234
+ */
235
+ if (Object.prototype.hasOwnProperty.call(attr, 'modifier')) {
236
+ // In Twine 2, `creator` maps to Twine 1's `modifier`.
237
+ s.creator = attr.modifier;
238
+ }
239
+
240
+ // Add the passage.
241
+ s.addPassage(
242
+ new Passage(
243
+ name,
244
+ text,
245
+ tags,
246
+ metadata
247
+ )
248
+ );
249
+ }
250
+
251
+ // Return story object.
252
+ return s;
253
+ }
254
+
255
+ export { parse };
@@ -0,0 +1,134 @@
1
+ import { parse as parseTwine2HTML } from '../Twine2HTML/parse-web.js';
2
+
3
+ /**
4
+ * Lightweight HTML parser for web builds - specifically for Twine 2 Archive HTML parsing
5
+ * This replaces node-html-parser to reduce bundle size and uses browser DOM APIs
6
+ */
7
+ class LightweightTwine2ArchiveParser {
8
+ constructor(html) {
9
+ this.html = html;
10
+ this.doc = null;
11
+
12
+ // Parse HTML using browser's native DOMParser if available, otherwise fallback
13
+ if (typeof DOMParser !== 'undefined') {
14
+ const parser = new DOMParser();
15
+ this.doc = parser.parseFromString(html, 'text/html');
16
+ } else {
17
+ // Fallback for environments without DOMParser
18
+ this.doc = this.createSimpleDOM(html);
19
+ }
20
+ }
21
+
22
+ getElementsByTagName(tagName) {
23
+ if (this.doc && this.doc.getElementsByTagName) {
24
+ return Array.from(this.doc.getElementsByTagName(tagName));
25
+ }
26
+
27
+ // Fallback implementation for tw-storydata elements
28
+ if (tagName === 'tw-storydata') {
29
+ return this.extractStoryDataElements();
30
+ }
31
+ return [];
32
+ }
33
+
34
+ extractStoryDataElements() {
35
+ // Match tw-storydata elements with their complete content
36
+ const storyDataRegex = /<tw-storydata[^>]*>[\s\S]*?<\/tw-storydata>/gi;
37
+ const elements = [];
38
+ let match;
39
+
40
+ while ((match = storyDataRegex.exec(this.html)) !== null) {
41
+ const outerHTML = match[0];
42
+
43
+ elements.push({
44
+ outerHTML: outerHTML,
45
+ // For compatibility with the original parser interface
46
+ toString: () => outerHTML
47
+ });
48
+ }
49
+
50
+ return elements;
51
+ }
52
+
53
+ // eslint-disable-next-line no-unused-vars
54
+ createSimpleDOM(_htmlContent) {
55
+ // Minimal DOM-like object for fallback
56
+ return {
57
+ getElementsByTagName: (tagName) => {
58
+ if (tagName === 'tw-storydata') {
59
+ return this.extractStoryDataElements(this.htmlContent);
60
+ }
61
+ return [];
62
+ }
63
+ };
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Web-optimized Twine 2 Archive HTML parser with reduced dependencies
69
+ * Parse Twine 2 Archive HTML and returns an array of story objects using browser DOM APIs.
70
+ * @see {@link https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-archive-spec.md Twine 2 Archive Specification}
71
+ * @function parse
72
+ * @param {string} content - Content to parse for Twine 2 HTML elements.
73
+ * @throws {TypeError} - Content is not a string!
74
+ * @returns {Array} Array of stories found in content.
75
+ * @example
76
+ * const content = '<tw-storydata name="Untitled" startnode="1" creator="Twine" creator-version="2.3.9" ifid="A1B2C3D4-E5F6-G7H8-I9J0-K1L2M3N4O5P6" zoom="1" format="Harlowe" format-version="3.1.0" options="" hidden><style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css"></style><script role="script" id="twine-user-script" type="text/twine-javascript"></script><tw-passagedata pid="1" name="Untitled Passage" tags="" position="0,0" size="100,100"></tw-passagedata></tw-storydata>';
77
+ * console.log(parse(content));
78
+ * // => [
79
+ * // Story {
80
+ * // name: 'Untitled',
81
+ * // startnode: '1',
82
+ * // creator: 'Twine',
83
+ * // creatorVersion: '2.3.9',
84
+ * // ifid: 'A1B2C3D4-E5F6-G7H8-I9J0-K1L2M3N4O5P6',
85
+ * // zoom: '1',
86
+ * // format: 'Harlowe',
87
+ * // formatVersion: '3.1.0',
88
+ * // options: '',
89
+ * // hidden: '',
90
+ * // passages: [
91
+ * // Passage {
92
+ * // pid: '1',
93
+ * // name: 'Untitled Passage',
94
+ * // tags: '',
95
+ * // position: '0,0',
96
+ * // size: '100,100',
97
+ * // text: ''
98
+ * // }
99
+ * // ]
100
+ * // }
101
+ * // ]
102
+ */
103
+ function parse(content) {
104
+ // Can only parse string values.
105
+ if (typeof content !== 'string') {
106
+ throw new TypeError('Content is not a string!');
107
+ }
108
+
109
+ // Use lightweight parser for web builds
110
+ const dom = new LightweightTwine2ArchiveParser(content);
111
+
112
+ // Array of possible story elements.
113
+ const outputArray = [];
114
+
115
+ // Pull out the `<tw-storydata>` element.
116
+ const storyDataElements = dom.getElementsByTagName('tw-storydata');
117
+
118
+ // Did we find any elements?
119
+ if (storyDataElements.length === 0) {
120
+ // Produce a warning if no Twine 2 HTML content is found.
121
+ console.warn('Warning: No Twine 2 HTML content found!');
122
+ }
123
+
124
+ // Iterate through all `<tw-storydata>` elements.
125
+ for (const storyElement of storyDataElements) {
126
+ // Convert element back into HTML text and parse using web-optimized parser.
127
+ outputArray.push(parseTwine2HTML(storyElement.outerHTML));
128
+ }
129
+
130
+ // Return array.
131
+ return outputArray;
132
+ }
133
+
134
+ export { parse };