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.
- package/CHANGELOG.md +8 -0
- 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/logs/arrays.html +79 -0
- package/logs/arrays.twee +64 -0
- package/logs/example.html +47 -0
- package/logs/fixing.js +23 -0
- package/logs/format-fixed.js +8 -0
- package/logs/format.js +8 -0
- package/logs/test-entities.cjs +40 -0
- package/package.json +23 -23
- package/src/CLI/ProcessConfig/loadStoryFormat.js +8 -0
- package/src/CLI/ProcessConfig/readDirectories.js +1 -3
- package/src/Config/parser.js +6 -6
- package/src/Config/reader.js +2 -2
- package/src/JSON/parse.js +2 -2
- package/src/Passage.js +44 -41
- package/src/Story.js +40 -35
- package/src/StoryFormat/parse.js +2 -2
- package/src/StoryFormat.js +11 -4
- package/src/TWS/parse.js +2 -2
- package/src/Twee/parse.js +3 -3
- package/src/Twine1HTML/compile.js +9 -9
- package/types/src/Passage.d.ts +44 -41
- package/types/src/Story.d.ts +32 -24
- package/types/src/StoryFormat.d.ts +11 -4
|
@@ -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'value');
|
|
11
|
+
const div2 = doc.createElement('div');
|
|
12
|
+
div2.setAttribute('data-test', 'test'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 'quote'';
|
|
20
|
+
div2.innerHTML = 'Text with 'quote'';
|
|
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's">Content</tw-passagedata>';
|
|
27
|
+
const html2 = '<tw-passagedata name="Test" data-value="It'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.
|
|
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
|
|
39
|
+
"node-html-parser": "^7.1.0",
|
|
40
40
|
"pickleparser": "^0.2.1",
|
|
41
|
-
"semver": "^7.7.
|
|
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.
|
|
48
|
-
"@eslint/js": "^
|
|
49
|
-
"@inquirer/prompts": "^8.2
|
|
50
|
-
"@types/node": "^25.
|
|
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.
|
|
51
|
+
"babel-loader": "^10.1.1",
|
|
53
52
|
"clean-jsdoc-theme": "^4.3.0",
|
|
54
|
-
"core-js": "^3.
|
|
55
|
-
"eslint": "^
|
|
56
|
-
"eslint-plugin-jest": "^29.
|
|
57
|
-
"eslint-plugin-jsdoc": "^62.
|
|
58
|
-
"globals": "^17.
|
|
59
|
-
"jest": "^30.
|
|
60
|
-
"jest-environment-jsdom": "^30.
|
|
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.
|
|
64
|
-
"webpack": "^5.
|
|
65
|
-
"webpack-bundle-analyzer": "^5.
|
|
66
|
-
"webpack-cli": "^
|
|
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) {
|
package/src/Config/parser.js
CHANGED
|
@@ -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;
|
package/src/Config/reader.js
CHANGED
|
@@ -20,13 +20,13 @@ export function reader(path) {
|
|
|
20
20
|
const contents = readFileSync(path, 'utf8');
|
|
21
21
|
|
|
22
22
|
// Parsed contents.
|
|
23
|
-
let parsedContents
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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.
|
|
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.
|
|
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
|
-
* @
|
|
30
|
-
* @
|
|
31
|
-
* @
|
|
32
|
-
* @
|
|
33
|
-
* @
|
|
34
|
-
* @
|
|
35
|
-
* @
|
|
36
|
-
* @
|
|
37
|
-
* @
|
|
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
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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 () {
|
package/src/StoryFormat/parse.js
CHANGED
|
@@ -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
|
/**
|