extwee 2.3.14 → 2.3.15

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,40 @@
1
+ const jsdom = require('jsdom');
2
+ const { JSDOM } = jsdom;
3
+
4
+ // Test in a DOM context
5
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
6
+ const doc = dom.window.document;
7
+
8
+ // Create elements with both encodings
9
+ const div1 = doc.createElement('div');
10
+ div1.setAttribute('data-test', 'test&apos;value');
11
+ const div2 = doc.createElement('div');
12
+ div2.setAttribute('data-test', 'test&#39;value');
13
+
14
+ console.log('div1 getAttribute:', div1.getAttribute('data-test'));
15
+ console.log('div2 getAttribute:', div2.getAttribute('data-test'));
16
+ console.log('Are they equal?', div1.getAttribute('data-test') === div2.getAttribute('data-test'));
17
+
18
+ // Test with innerHTML
19
+ div1.innerHTML = 'Text with &apos;quote&apos;';
20
+ div2.innerHTML = 'Text with &#39;quote&#39;';
21
+ console.log('div1 textContent:', div1.textContent);
22
+ console.log('div2 textContent:', div2.textContent);
23
+ console.log('Text content equal?', div1.textContent === div2.textContent);
24
+
25
+ // Test parsing HTML with both
26
+ const html1 = '<tw-passagedata name="Test" data-value="It&apos;s">Content</tw-passagedata>';
27
+ const html2 = '<tw-passagedata name="Test" data-value="It&#39;s">Content</tw-passagedata>';
28
+
29
+ const container1 = doc.createElement('div');
30
+ const container2 = doc.createElement('div');
31
+ container1.innerHTML = html1;
32
+ container2.innerHTML = html2;
33
+
34
+ const passage1 = container1.querySelector('tw-passagedata');
35
+ const passage2 = container2.querySelector('tw-passagedata');
36
+
37
+ console.log('\nParsing tw-passagedata:');
38
+ console.log('passage1 data-value:', passage1.getAttribute('data-value'));
39
+ console.log('passage2 data-value:', passage2.getAttribute('data-value'));
40
+ console.log('Attributes equal?', passage1.getAttribute('data-value') === passage2.getAttribute('data-value'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "extwee",
3
- "version": "2.3.14",
3
+ "version": "2.3.15",
4
4
  "description": "A story compiler tool using Twine-compatible formats",
5
5
  "author": "Dan Cox",
6
6
  "main": "index.js",
@@ -17,11 +17,11 @@
17
17
  },
18
18
  "scripts": {
19
19
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
20
- "lint": "eslint ./src/**/*.js --fix",
21
- "lint:test": "eslint ./test/**/*.test.js --fix",
20
+ "lint": "eslint \"./src/**/*.js\" --fix",
21
+ "lint:test": "eslint \"./test/**/*.test.js\" --fix",
22
22
  "build:web": "webpack",
23
23
  "analyze:web": "webpack-bundle-analyzer build/extwee.web.min.js",
24
- "gen-types": "npx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly --outDir types",
24
+ "gen-types": "npx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly --outDir types --skipLibCheck",
25
25
  "copy:build": "cp build/*.js docs/build",
26
26
  "all": "npm run lint && npm run lint:test && npm run test && npm run build:web && npm run gen-types && npm run copy:build"
27
27
  },
@@ -36,34 +36,34 @@
36
36
  "commander": "^14.0.3",
37
37
  "graphemer": "^1.4.0",
38
38
  "html-entities": "^2.6.0",
39
- "node-html-parser": "^7.0.2",
39
+ "node-html-parser": "^7.1.0",
40
40
  "pickleparser": "^0.2.1",
41
- "semver": "^7.7.3",
42
- "shelljs": "^0.10.0"
41
+ "semver": "^7.7.4"
43
42
  },
44
43
  "devDependencies": {
45
44
  "@babel/cli": "^7.28.6",
46
45
  "@babel/core": "^7.29.0",
47
- "@babel/preset-env": "^7.29.0",
48
- "@eslint/js": "^9.39.2",
49
- "@inquirer/prompts": "^8.2.0",
50
- "@types/node": "^25.2.0",
46
+ "@babel/preset-env": "^7.29.2",
47
+ "@eslint/js": "^10.0.1",
48
+ "@inquirer/prompts": "^8.3.2",
49
+ "@types/node": "^25.5.0",
51
50
  "@types/semver": "^7.7.1",
52
- "babel-loader": "^10.0.0",
51
+ "babel-loader": "^10.1.1",
53
52
  "clean-jsdoc-theme": "^4.3.0",
54
- "core-js": "^3.48.0",
55
- "eslint": "^9.39.2",
56
- "eslint-plugin-jest": "^29.12.1",
57
- "eslint-plugin-jsdoc": "^62.5.0",
58
- "globals": "^17.3.0",
59
- "jest": "^30.2.0",
60
- "jest-environment-jsdom": "^30.2.0",
53
+ "core-js": "^3.49.0",
54
+ "eslint": "^10.1.0",
55
+ "eslint-plugin-jest": "^29.15.1",
56
+ "eslint-plugin-jsdoc": "^62.8.1",
57
+ "globals": "^17.4.0",
58
+ "jest": "^30.3.0",
59
+ "jest-environment-jsdom": "^30.3.0",
61
60
  "regenerator-runtime": "^0.14.1",
61
+ "shelljs": "^0.10.0",
62
62
  "typescript": "^5.9.3",
63
- "typescript-eslint": "^8.54.0",
64
- "webpack": "^5.104.1",
65
- "webpack-bundle-analyzer": "^5.2.0",
66
- "webpack-cli": "^6.0.1"
63
+ "typescript-eslint": "^8.57.2",
64
+ "webpack": "^5.105.4",
65
+ "webpack-bundle-analyzer": "^5.3.0",
66
+ "webpack-cli": "^7.0.2"
67
67
  },
68
68
  "repository": {
69
69
  "type": "git",
@@ -23,6 +23,14 @@ import { readFileSync } from "node:fs";
23
23
  * // Output: The contents of the format.js file.
24
24
  */
25
25
  export function loadStoryFormat(storyFormatName, storyFormatVersion) {
26
+ // Validate path components to prevent path traversal.
27
+ if (/[/\\]|\.\./.test(storyFormatName)) {
28
+ throw new Error('Error: story format name contains invalid characters.');
29
+ }
30
+ if (/[/\\]|\.\./.test(storyFormatVersion)) {
31
+ throw new Error('Error: story format version contains invalid characters.');
32
+ }
33
+
26
34
  // If the story-formats directory does not exist, throw error.
27
35
  if (isDirectory('story-formats') === false) {
28
36
  throw new Error(`Error: story-formats directory does not exist. Consider running 'npx sfa-get' to download the latest story formats.`);
@@ -11,9 +11,6 @@ import { isDirectory } from '../isDirectory.js';
11
11
  */
12
12
  export function readDirectories(directory) {
13
13
 
14
- // Create default response.
15
- let results = [];
16
-
17
14
  // Check if the directory exists.
18
15
  const isDir = isDirectory(directory);
19
16
  // If the directory does not exist, return an empty array
@@ -23,6 +20,7 @@ export function readDirectories(directory) {
23
20
  }
24
21
 
25
22
  // Read the directory and return the list of files.
23
+ let results;
26
24
  try {
27
25
  results = readdirSync(directory);
28
26
  } catch (error) {
@@ -20,34 +20,34 @@ export function parser(obj) {
20
20
  };
21
21
 
22
22
  // Does the object contain 'StoryFormat'?
23
- if (Object.hasOwnProperty.call(obj, 'story-format')) {
23
+ if (Object.prototype.hasOwnProperty.call(obj, 'story-format')) {
24
24
  results.StoryFormat = obj['story-format'];
25
25
  }
26
26
 
27
27
  // Does the object contain 'StoryFormatVersion'?
28
- if (Object.hasOwnProperty.call(obj, 'story-format-version')) {
28
+ if (Object.prototype.hasOwnProperty.call(obj, 'story-format-version')) {
29
29
  results.StoryFormatVersion = obj['story-format-version'];
30
30
  } else {
31
31
  results.StoryFormatVersion = "latest";
32
32
  }
33
33
 
34
34
  // Does the object contain 'mode'?
35
- if (Object.hasOwnProperty.call(obj, 'mode')) {
35
+ if (Object.prototype.hasOwnProperty.call(obj, 'mode')) {
36
36
  results.Mode = obj['mode'];
37
37
  }
38
38
 
39
39
  // Does the object contain 'input'?
40
- if (Object.hasOwnProperty.call(obj, 'input')) {
40
+ if (Object.prototype.hasOwnProperty.call(obj, 'input')) {
41
41
  results.Input = obj['input'];
42
42
  }
43
43
 
44
44
  // Does the object contain 'output'?
45
- if (Object.hasOwnProperty.call(obj, 'output')) {
45
+ if (Object.prototype.hasOwnProperty.call(obj, 'output')) {
46
46
  results.Output = obj['output'];
47
47
  }
48
48
 
49
49
  // Does the object contain 'twine1-project'?
50
- if (Object.hasOwnProperty.call(obj, 'twine1-project')) {
50
+ if (Object.prototype.hasOwnProperty.call(obj, 'twine1-project')) {
51
51
  results.Twine1Project = obj['twine1-project'];
52
52
  } else {
53
53
  results.Twine1Project = false;
@@ -20,13 +20,13 @@ export function reader(path) {
20
20
  const contents = readFileSync(path, 'utf8');
21
21
 
22
22
  // Parsed contents.
23
- let parsedContents = null;
23
+ let parsedContents;
24
24
 
25
25
  // Try to parse the contents into JSON object.
26
26
  try {
27
27
  parsedContents = JSON.parse(contents);
28
28
  } catch (error) {
29
- throw new Error(`Error: File ${path} is not a valid JSON file. ${error.message}`);
29
+ throw new Error(`Error: File ${path} is not a valid JSON file. ${error.message}`, { cause: error });
30
30
  }
31
31
 
32
32
  // Return the parsed contents.
package/src/JSON/parse.js CHANGED
@@ -52,7 +52,7 @@ import Passage from '../Passage.js';
52
52
  */
53
53
  function parse (jsonString) {
54
54
  // Create future object.
55
- let result = {};
55
+ let result;
56
56
 
57
57
  // Create Story.
58
58
  const s = new Story();
@@ -61,7 +61,7 @@ function parse (jsonString) {
61
61
  try {
62
62
  result = JSON.parse(jsonString);
63
63
  } catch (error) {
64
- throw new Error(`Error: JSON could not be parsed! ${error.message}`);
64
+ throw new Error(`Error: JSON could not be parsed! ${error.message}`, { cause: error });
65
65
  }
66
66
 
67
67
  // Name
package/src/Passage.js CHANGED
@@ -2,42 +2,42 @@ import { encode } from 'html-entities';
2
2
  import { escapeTweeMetacharacters } from './Twee/parse.js';
3
3
 
4
4
  /**
5
- * Passage class.
6
- * @class
7
- * @classdesc Represents a passage in a Twine story.
8
- * @property {string} name - Name of the passage.
9
- * @property {Array} tags - Tags for the passage.
10
- * @property {object} metadata - Metadata for the passage.
11
- * @property {string} text - Text content of the passage.
12
- * @method {string} toTwee - Return a Twee representation.
13
- * @method {string} toJSON - Return JSON representation.
14
- * @method {string} toTwine2HTML - Return Twine 2 HTML representation.
15
- * @method {string} toTwine1HTML - Return Twine 1 HTML representation.
16
- * @example
17
- * const p = new Passage('Start', 'This is the start of the story.');
18
- * console.log(p.toTwee());
19
- * // :: Start
20
- * // This is the start of the story.
21
- * //
22
- * console.log(p.toJSON());
23
- * // {"name":"Start","tags":[],"metadata":{},"text":"This is the start of the story."}
24
- * console.log(p.toTwine2HTML());
25
- * // <tw-passagedata pid="1" name="Start" tags="" >This is the start of the story.</tw-passagedata>
26
- * console.log(p.toTwine1HTML());
27
- * // <div tiddler="Start" tags="" modifier="extwee" twine-position="10,10">This is the start of the story.</div>
28
- * @example
29
- * const p = new Passage('Start', 'This is the start of the story.', ['start', 'beginning'], {position: '10,10', size: '100,100'});
30
- * console.log(p.toTwee());
31
- * // :: Start [start beginning] {"position":"10,10","size":"100,100"}
32
- * // This is the start of the story.
33
- * //
34
- * console.log(p.toJSON());
35
- * // {"name":"Start","tags":["start","beginning"],"metadata":{"position":"10,10","size":"100,100"},"text":"This is the start of the story."}
36
- * console.log(p.toTwine2HTML());
37
- * // <tw-passagedata pid="1" name="Start" tags="start beginning" position="10,10" size="100,100">This is the start of the story.</tw-passagedata>
38
- * console.log(p.toTwine1HTML());
39
- * // <div tiddler="Start" tags="start beginning" modifier="extwee" twine-position="10,10">This is the start of the story.</div>
40
- */
5
+ * Passage class.
6
+ * @class
7
+ * @classdesc Represents a passage in a Twine story.
8
+ * @property {string} name - Name of the passage.
9
+ * @property {Array} tags - Tags for the passage.
10
+ * @property {object} metadata - Metadata for the passage.
11
+ * @property {string} text - Text content of the passage.
12
+ * @function toTwee - Return a Twee representation.
13
+ * @function toJSON - Return JSON representation.
14
+ * @function toTwine2HTML - Return Twine 2 HTML representation.
15
+ * @function toTwine1HTML - Return Twine 1 HTML representation.
16
+ * @example
17
+ * const p = new Passage('Start', 'This is the start of the story.');
18
+ * console.log(p.toTwee());
19
+ * // :: Start
20
+ * // This is the start of the story.
21
+ * //
22
+ * console.log(p.toJSON());
23
+ * // {"name":"Start","tags":[],"metadata":{},"text":"This is the start of the story."}
24
+ * console.log(p.toTwine2HTML());
25
+ * // <tw-passagedata pid="1" name="Start" tags="" >This is the start of the story.</tw-passagedata>
26
+ * console.log(p.toTwine1HTML());
27
+ * // <div tiddler="Start" tags="" modifier="extwee" twine-position="10,10">This is the start of the story.</div>
28
+ * @example
29
+ * const p = new Passage('Start', 'This is the start of the story.', ['start', 'beginning'], {position: '10,10', size: '100,100'});
30
+ * console.log(p.toTwee());
31
+ * // :: Start [start beginning] {"position":"10,10","size":"100,100"}
32
+ * // This is the start of the story.
33
+ * //
34
+ * console.log(p.toJSON());
35
+ * // {"name":"Start","tags":["start","beginning"],"metadata":{"position":"10,10","size":"100,100"},"text":"This is the start of the story."}
36
+ * console.log(p.toTwine2HTML());
37
+ * // <tw-passagedata pid="1" name="Start" tags="start beginning" position="10,10" size="100,100">This is the start of the story.</tw-passagedata>
38
+ * console.log(p.toTwine1HTML());
39
+ * // <div tiddler="Start" tags="start beginning" modifier="extwee" twine-position="10,10">This is the start of the story.</div>
40
+ */
41
41
  export default class Passage {
42
42
  /**
43
43
  * Name of the Passage
@@ -95,6 +95,7 @@ export default class Passage {
95
95
  get name () { return this.#_name; }
96
96
 
97
97
  /**
98
+ * Set passage name.
98
99
  * @param {string} s - Name to replace
99
100
  * @throws {Error} Name must be a String!
100
101
  */
@@ -113,6 +114,7 @@ export default class Passage {
113
114
  get tags () { return this.#_tags; }
114
115
 
115
116
  /**
117
+ * Set passage tags.
116
118
  * @param {Array} t - Replacement array
117
119
  * @throws {Error} Tags must be an array!
118
120
  */
@@ -133,6 +135,7 @@ export default class Passage {
133
135
  get metadata () { return this.#_metadata; }
134
136
 
135
137
  /**
138
+ * Set passage metadata.
136
139
  * @param {object} m - Replacement object
137
140
  * @throws {Error} Metadata must be an object literal!
138
141
  */
@@ -152,6 +155,7 @@ export default class Passage {
152
155
  get text () { return this.#_text; }
153
156
 
154
157
  /**
158
+ * Set passage text.
155
159
  * @param {string} t - Replacement text
156
160
  * @throws {Error} Text should be a String!
157
161
  */
@@ -168,8 +172,7 @@ export default class Passage {
168
172
  * Return a Twee representation.
169
173
  *
170
174
  * See: https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md
171
- *
172
- * @method toTwee
175
+ * @function toTwee
173
176
  * @returns {string} String form of passage.
174
177
  */
175
178
  toTwee () {
@@ -216,7 +219,7 @@ export default class Passage {
216
219
 
217
220
  /**
218
221
  * Return JSON representation.
219
- * @method toJSON
222
+ * @function toJSON
220
223
  * @returns {string} JSON string.
221
224
  */
222
225
  toJSON () {
@@ -235,7 +238,7 @@ export default class Passage {
235
238
  /**
236
239
  * Return Twine 2 HTML representation.
237
240
  * (Default Passage ID is 1.)
238
- * @method toTwine2HTML
241
+ * @function toTwine2HTML
239
242
  * @param {number} pid - Passage ID (PID) to record in HTML.
240
243
  * @returns {string} Twine 2 HTML string.
241
244
  */
@@ -292,7 +295,7 @@ export default class Passage {
292
295
 
293
296
  /**
294
297
  * Return Twine 1 HTML representation.
295
- * @method toTwine1HTML
298
+ * @function toTwine1HTML
296
299
  * @returns {string} Twine 1 HTML string.
297
300
  */
298
301
  toTwine1HTML () {
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.14';
10
+ const creatorVersion = '2.3.15';
11
11
 
12
12
  /**
13
13
  * Story class.
@@ -19,22 +19,22 @@ const creatorVersion = '2.3.14';
19
19
  * @property {string} format - Story format of Story.
20
20
  * @property {string} formatVersion - Story format version of Story.
21
21
  * @property {number} zoom - Zoom level.
22
- * @property {Array} passages - Array of Passage objects. @see {@link Passage}
22
+ * @property {Array} passages - Array of Passage objects. See {@link Passage}.
23
23
  * @property {string} creator - Program used to create Story.
24
24
  * @property {string} creatorVersion - Version used to create Story.
25
25
  * @property {object} metadata - Metadata of Story.
26
26
  * @property {object} tagColors - Tag Colors
27
27
  * @property {string} storyJavaScript - Story JavaScript
28
28
  * @property {string} storyStylesheet - Story Stylesheet
29
- * @method {number} addPassage - Add a passage to the story and returns the new length of the passages array.
30
- * @method {number} removePassageByName - Remove a passage from the story by name and returns the new length of the passages array.
31
- * @method {Array} getPassagesByTag - Find passages by tag.
32
- * @method {Array} getPassageByName - Find passage by name.
33
- * @method {number} size - Size (number of passages).
34
- * @method {string} toJSON - Export Story as JSON representation.
35
- * @method {string} toTwee - Return Twee representation.
36
- * @method {string} toTwine2HTML - Return Twine 2 HTML representation.
37
- * @method {string} toTwine1HTML - Return Twine 1 HTML representation.
29
+ * @function addPassage - Add a passage to the story and returns the new length of the passages array.
30
+ * @function removePassageByName - Remove a passage from the story by name and returns the new length of the passages array.
31
+ * @function getPassagesByTag - Find passages by tag.
32
+ * @function getPassageByName - Find passage by name.
33
+ * @function size - Size (number of passages).
34
+ * @function toJSON - Export Story as JSON representation.
35
+ * @function toTwee - Return Twee representation.
36
+ * @function toTwine2HTML - Return Twine 2 HTML representation.
37
+ * @function toTwine1HTML - Return Twine 1 HTML representation.
38
38
  * @example
39
39
  * const story = new Story('My Story');
40
40
  * story.IFID = '12345678-1234-5678-1234-567812345678';
@@ -148,6 +148,7 @@ class Story {
148
148
  get name () { return this.#_name; }
149
149
 
150
150
  /**
151
+ * Set story name.
151
152
  * @param {string} a - Replacement story name
152
153
  */
153
154
  set name (a) {
@@ -165,13 +166,14 @@ class Story {
165
166
  get tagColors () { return this.#_tagColors; }
166
167
 
167
168
  /**
169
+ * Set tag colors.
168
170
  * @param {object} a - Replacement tag colors
169
171
  */
170
172
  set tagColors (a) {
171
- if (a instanceof Object) {
173
+ if (a !== null && typeof a === 'object' && !Array.isArray(a)) {
172
174
  this.#_tagColors = a;
173
175
  } else {
174
- throw new Error('Tag colors must be an object!');
176
+ throw new Error('Tag colors must be a plain object!');
175
177
  }
176
178
  }
177
179
 
@@ -182,6 +184,7 @@ class Story {
182
184
  get IFID () { return this.#_IFID; }
183
185
 
184
186
  /**
187
+ * Set story IFID.
185
188
  * @param {string} i - Replacement IFID.
186
189
  */
187
190
  set IFID (i) {
@@ -199,6 +202,7 @@ class Story {
199
202
  get start () { return this.#_start; }
200
203
 
201
204
  /**
205
+ * Set start passage name.
202
206
  * @param {string} s - Replacement start
203
207
  */
204
208
  set start (s) {
@@ -216,6 +220,7 @@ class Story {
216
220
  get formatVersion () { return this.#_formatVersion; }
217
221
 
218
222
  /**
223
+ * Set story format version.
219
224
  * @param {string} f - Replacement format version
220
225
  */
221
226
  set formatVersion (f) {
@@ -233,13 +238,14 @@ class Story {
233
238
  get metadata () { return this.#_metadata; }
234
239
 
235
240
  /**
241
+ * Set story metadata.
236
242
  * @param {object} o - Replacement metadata
237
243
  */
238
244
  set metadata (o) {
239
- if (typeof o === 'object') {
245
+ if (o !== null && typeof o === 'object') {
240
246
  this.#_metadata = o;
241
247
  } else {
242
- throw new Error('Story metadata must be Object!');
248
+ throw new Error('Story metadata must be a non-null Object!');
243
249
  }
244
250
  }
245
251
 
@@ -250,6 +256,7 @@ class Story {
250
256
  get format () { return this.#_format; }
251
257
 
252
258
  /**
259
+ * Set story format.
253
260
  * @param {string} f - Replacement format
254
261
  */
255
262
  set format (f) {
@@ -267,6 +274,7 @@ class Story {
267
274
  get creator () { return this.#_creator; }
268
275
 
269
276
  /**
277
+ * Set creator program.
270
278
  * @param {string} c - Creator Program of Story
271
279
  */
272
280
  set creator (c) {
@@ -284,6 +292,7 @@ class Story {
284
292
  get creatorVersion () { return this.#_creatorVersion; }
285
293
 
286
294
  /**
295
+ * Set creator version.
287
296
  * @param {string} c - Version of creator program
288
297
  */
289
298
  set creatorVersion (c) {
@@ -301,14 +310,15 @@ class Story {
301
310
  get zoom () { return this.#_zoom; }
302
311
 
303
312
  /**
313
+ * Set zoom level.
304
314
  * @param {number} n - Replacement zoom level
305
315
  */
306
316
  set zoom (n) {
307
- if (typeof n === 'number') {
317
+ if (typeof n === 'number' && Number.isFinite(n)) {
308
318
  // Parse float with a fixed length and then force into Number
309
319
  this.#_zoom = Number(Number.parseFloat(n).toFixed(2));
310
320
  } else {
311
- throw new Error('Zoom level must be a Number!');
321
+ throw new Error('Zoom level must be a finite Number!');
312
322
  }
313
323
  }
314
324
 
@@ -316,7 +326,7 @@ class Story {
316
326
  * Passages in Story.
317
327
  * @returns {Array} Passages
318
328
  * @property {Array} passages - Passages
319
- */
329
+ */
320
330
  get passages () { return this.#_passages; }
321
331
 
322
332
  /**
@@ -347,6 +357,7 @@ class Story {
347
357
  }
348
358
 
349
359
  /**
360
+ * Set story stylesheet.
350
361
  * @param {string} s - Replacement story stylesheet
351
362
  */
352
363
  set storyStylesheet (s) {
@@ -380,7 +391,7 @@ class Story {
380
391
  /**
381
392
  * Add a passage to the story.
382
393
  * Passing `StoryData` will override story metadata and `StoryTitle` will override story name.
383
- * @method addPassage
394
+ * @function addPassage
384
395
  * @param {Passage} p - Passage to add to Story.
385
396
  * @returns {number} Return new length of passages array.
386
397
  */
@@ -492,7 +503,7 @@ class Story {
492
503
 
493
504
  /**
494
505
  * Remove a passage from the story by name.
495
- * @method removePassageByName
506
+ * @function removePassageByName
496
507
  * @param {string} name - Passage name to remove.
497
508
  * @returns {number} Return new length of passages array.
498
509
  */
@@ -503,7 +514,7 @@ class Story {
503
514
 
504
515
  /**
505
516
  * Find passages by tag.
506
- * @method getPassagesByTag
517
+ * @function getPassagesByTag
507
518
  * @param {string} t - Passage name to search for
508
519
  * @returns {Array} Return array of passages
509
520
  */
@@ -517,7 +528,7 @@ class Story {
517
528
 
518
529
  /**
519
530
  * Find passage by name.
520
- * @method getPassageByName
531
+ * @function getPassageByName
521
532
  * @param {string} name - Passage name to search for
522
533
  * @returns {Passage | null} Return passage or null
523
534
  */
@@ -530,7 +541,7 @@ class Story {
530
541
 
531
542
  /**
532
543
  * Size (number of passages).
533
- * @method size
544
+ * @function size
534
545
  * @returns {number} Return number of passages
535
546
  */
536
547
  size () {
@@ -539,7 +550,7 @@ class Story {
539
550
 
540
551
  /**
541
552
  * Export Story as JSON representation.
542
- * @method toJSON
553
+ * @function toJSON
543
554
  * @returns {string} JSON string.
544
555
  */
545
556
  toJSON () {
@@ -579,8 +590,7 @@ class Story {
579
590
  *
580
591
  * See: Twee 3 Specification
581
592
  * (https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md)
582
- *
583
- * @method toTwee
593
+ * @function toTwee
584
594
  * @returns {string} Twee String
585
595
  */
586
596
  toTwee () {
@@ -607,7 +617,7 @@ class Story {
607
617
 
608
618
  /**
609
619
  * format: (string) Optional. Maps to <tw-storydata format>.
610
- */
620
+ */
611
621
  // Does format exist?
612
622
  if (this.format !== '') {
613
623
  // Write the existing format.
@@ -706,8 +716,7 @@ class Story {
706
716
  * Because story stylesheet data can be represented as a passage, property value, or both, all approaches are encoded.
707
717
  *
708
718
  * Because story JavaScript can be represented as a passage, property value, or both, all approaches are encoded.
709
- *
710
- * @method toTwine2HTML
719
+ * @function toTwine2HTML
711
720
  * @returns {string} Twine 2 HTML string
712
721
  */
713
722
  toTwine2HTML () {
@@ -852,9 +861,6 @@ class Story {
852
861
  // Filter out passages with tag of 'script'.
853
862
  const scriptPassages = passages.filter((passage) => passage.tags.includes('script'));
854
863
 
855
- // Remove script passages from the main array.
856
- passages = passages.filter(p => !p.tags.includes('script'));
857
-
858
864
  // Were there any script passages?
859
865
  if (scriptPassages.length > 0) {
860
866
  // Start the SCRIPT.
@@ -894,7 +900,7 @@ class Story {
894
900
  // For each tag, generate a <tw-tag> element.
895
901
  tagList.forEach((tag) => {
896
902
  // Add the <tw-tag> element.
897
- storyData += `\t<tw-tag name="${tag}" color="${this.tagColors[tag]}"></tw-tag>\n`;
903
+ storyData += `\t<tw-tag name="${encode(tag)}" color="${encode(String(this.tagColors[tag]))}"></tw-tag>\n`;
898
904
  });
899
905
 
900
906
  // Close the HTML element.
@@ -909,8 +915,7 @@ class Story {
909
915
  *
910
916
  * See: Twine 1 HTML Output
911
917
  * (https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md)
912
- *
913
- * @method toTwine1HTML
918
+ * @function toTwine1HTML
914
919
  * @returns {string} Twine 1 HTML string.
915
920
  */
916
921
  toTwine1HTML () {
@@ -85,13 +85,13 @@ function parse (contents) {
85
85
  }
86
86
 
87
87
  // Create an object literal
88
- let jsonContent = {};
88
+ let jsonContent;
89
89
 
90
90
  // Attempt to parse the JSON.
91
91
  try {
92
92
  jsonContent = JSON.parse(contents);
93
93
  } catch (error) {
94
- throw new Error(`Error: Unable to parse Twine 2 JSON chunk! ${error.message}`);
94
+ throw new Error(`Error: Unable to parse Twine 2 JSON chunk! ${error.message}`, { cause: error });
95
95
  }
96
96
 
97
97
  /**