extwee 2.2.0 → 2.2.2
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/.github/codeql-analysis.yml +51 -0
- package/README.md +9 -3
- package/build/extwee +0 -0
- package/build/extwee.web.min.js +1 -1
- package/docs/_sidebar.md +1 -0
- package/docs/examples/dynamicPassages.md +1 -1
- package/docs/examples/twsToTwee.md +1 -1
- package/docs/objects/story.md +1 -2
- package/index.js +3 -1
- package/package.json +22 -19
- package/src/IFID/generate.js +20 -0
- package/src/JSON/parse.js +44 -1
- package/src/Passage.js +61 -31
- package/src/Story.js +272 -110
- package/src/StoryFormat/parse.js +190 -80
- package/src/StoryFormat.js +78 -88
- package/src/TWS/parse.js +3 -3
- package/src/Twee/parse.js +3 -4
- package/src/Twine1HTML/compile.js +3 -1
- package/src/Twine1HTML/parse.js +3 -4
- package/src/Twine2ArchiveHTML/compile.js +9 -1
- package/src/Twine2ArchiveHTML/parse.js +33 -3
- package/src/Twine2HTML/compile.js +32 -7
- package/src/Twine2HTML/parse.js +51 -55
- package/test/IFID/IFID.Generate.test.js +10 -0
- package/test/JSON/JSON.Parse.test.js +24 -24
- package/test/Passage.test.js +69 -0
- package/test/Story.test.js +298 -49
- package/test/StoryFormat/StoryFormat.Parse.test.js +442 -55
- package/test/StoryFormat.test.js +9 -2
- package/test/TWS/Parse.test.js +1 -1
- package/test/Twine1HTML/Twine1HTML.Compile.test.js +1 -1
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +1 -1
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +20 -4
- package/test/Twine2HTML/Twine2HTML.Compile.test.js +36 -121
- package/test/Twine2HTML/Twine2HTML.Parse.test.js +63 -43
- package/test/Twine2HTML/Twine2HTMLCompiler/format.js +9 -0
- package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +1 -1
- package/test/Twine2HTML/Twine2HTMLParser/unescaping.html +33 -0
- package/tsconfig.json +21 -0
- package/types/index.d.ts +14 -0
- package/types/src/JSON/parse.d.ts +8 -0
- package/types/src/Passage.d.ts +72 -0
- package/types/src/Story.d.ts +161 -0
- package/types/src/StoryFormat/parse.d.ts +7 -0
- package/types/src/StoryFormat.d.ts +97 -0
- package/types/src/TWS/parse.d.ts +10 -0
- package/types/src/Twee/parse.d.ts +10 -0
- package/types/src/Twine1HTML/compile.d.ts +17 -0
- package/types/src/Twine1HTML/parse.d.ts +10 -0
- package/types/src/Twine2ArchiveHTML/compile.d.ts +6 -0
- package/types/src/Twine2ArchiveHTML/parse.d.ts +6 -0
- package/types/src/Twine2HTML/compile.d.ts +9 -0
- package/types/src/Twine2HTML/parse.d.ts +17 -0
- package/types/src/extwee.d.ts +2 -0
- package/web-index.js +3 -1
- package/test/StoryFormat/StoryFormatParser/example.js +0 -3
- package/test/StoryFormat/StoryFormatParser/example2.js +0 -3
- package/test/StoryFormat/StoryFormatParser/format.js +0 -1
- package/test/StoryFormat/StoryFormatParser/format_doublename.js +0 -1
- package/test/StoryFormat/StoryFormatParser/harlowe.js +0 -3
- package/test/StoryFormat/StoryFormatParser/missingAuthor.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingDescription.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingImage.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingLicense.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingName.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingProofing.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingSource.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingURL.js +0 -1
- package/test/StoryFormat/StoryFormatParser/missingVersion.js +0 -1
- package/test/StoryFormat/StoryFormatParser/versionWrong.js +0 -1
- package/test/Twine2HTML/Twine2HTMLParser/missingName.html +0 -33
- package/test/Twine2HTML/Twine2HTMLParser/missingPID.html +0 -15
- package/test/Twine2HTML/Twine2HTMLParser/missingPassageName.html +0 -15
package/src/Story.js
CHANGED
|
@@ -1,15 +1,50 @@
|
|
|
1
1
|
import Passage from './Passage.js';
|
|
2
|
-
import {
|
|
2
|
+
import { generate as generateIFID } from './IFID/generate.js';
|
|
3
|
+
import { encode } from 'html-entities';
|
|
3
4
|
|
|
4
5
|
const creatorName = 'extwee';
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const creatorVersion = '2.2.2';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Story class.
|
|
10
|
+
* @class
|
|
11
|
+
* @classdesc Represents a Twine story.
|
|
12
|
+
* @property {string} name - Name of the story.
|
|
13
|
+
* @property {string} IFID - Interactive Fiction ID (IFID) of Story.
|
|
14
|
+
* @property {string} start - Name of start passage.
|
|
15
|
+
* @property {string} format - Story format of Story.
|
|
16
|
+
* @property {string} formatVersion - Story format version of Story.
|
|
17
|
+
* @property {number} zoom - Zoom level.
|
|
18
|
+
* @property {Array} passages - Array of Passage objects. @see {@link Passage}
|
|
19
|
+
* @property {string} creator - Program used to create Story.
|
|
20
|
+
* @property {string} creatorVersion - Version used to create Story.
|
|
21
|
+
* @property {object} metadata - Metadata of Story.
|
|
22
|
+
* @property {object} tagColors - Tag Colors
|
|
23
|
+
* @method {number} addPassage - Add a passage to the story and returns the new length of the passages array.
|
|
24
|
+
* @method {number} removePassageByName - Remove a passage from the story by name and returns the new length of the passages array.
|
|
25
|
+
* @method {Array} getPassagesByTag - Find passages by tag.
|
|
26
|
+
* @method {Array} getPassageByName - Find passage by name.
|
|
27
|
+
* @method {number} size - Size (number of passages).
|
|
28
|
+
* @method {string} toJSON - Export Story as JSON representation.
|
|
29
|
+
* @method {string} toTwee - Return Twee representation.
|
|
30
|
+
* @method {string} toTwine2HTML - Return Twine 2 HTML representation.
|
|
31
|
+
* @method {string} toTwine1HTML - Return Twine 1 HTML representation.
|
|
32
|
+
* @example
|
|
33
|
+
* const story = new Story('My Story');
|
|
34
|
+
* story.IFID = '12345678-1234-5678-1234-567812345678';
|
|
35
|
+
* story.start = 'Start';
|
|
36
|
+
* story.format = 'SugarCube';
|
|
37
|
+
* story.formatVersion = '2.31.0';
|
|
38
|
+
* story.zoom = 1;
|
|
39
|
+
* story.creator = 'extwee';
|
|
40
|
+
* story.creatorVersion = '2.2.1';
|
|
41
|
+
*/
|
|
42
|
+
class Story {
|
|
8
43
|
/**
|
|
9
44
|
* Internal name of story
|
|
10
45
|
* @private
|
|
11
46
|
*/
|
|
12
|
-
#_name = '';
|
|
47
|
+
#_name = 'Untitled Story';
|
|
13
48
|
|
|
14
49
|
/**
|
|
15
50
|
* Internal start
|
|
@@ -36,8 +71,9 @@ export default class Story {
|
|
|
36
71
|
|
|
37
72
|
/**
|
|
38
73
|
* Internal zoom level
|
|
74
|
+
* Default is 1 (100%)
|
|
39
75
|
*/
|
|
40
|
-
#_zoom =
|
|
76
|
+
#_zoom = 1;
|
|
41
77
|
|
|
42
78
|
/**
|
|
43
79
|
* Passages
|
|
@@ -73,12 +109,16 @@ export default class Story {
|
|
|
73
109
|
* Creates a story.
|
|
74
110
|
* @param {string} name - Name of the story.
|
|
75
111
|
*/
|
|
76
|
-
constructor (name = '') {
|
|
112
|
+
constructor (name = 'Untitled Story') {
|
|
77
113
|
// Every story has a name.
|
|
78
114
|
this.name = name;
|
|
115
|
+
|
|
79
116
|
// Store the creator.
|
|
80
117
|
this.#_creator = creatorName;
|
|
81
|
-
|
|
118
|
+
|
|
119
|
+
// Store the creator version.
|
|
120
|
+
this.#_creatorVersion = creatorVersion;
|
|
121
|
+
|
|
82
122
|
// Set metadata to an object.
|
|
83
123
|
this.#_metadata = {};
|
|
84
124
|
}
|
|
@@ -118,13 +158,13 @@ export default class Story {
|
|
|
118
158
|
}
|
|
119
159
|
|
|
120
160
|
/**
|
|
121
|
-
* Interactive Fiction ID (IFID) of Story
|
|
161
|
+
* Interactive Fiction ID (IFID) of Story.
|
|
122
162
|
* @returns {string} IFID
|
|
123
163
|
*/
|
|
124
164
|
get IFID () { return this.#_IFID; }
|
|
125
165
|
|
|
126
166
|
/**
|
|
127
|
-
* @param {string} i - Replacement IFID
|
|
167
|
+
* @param {string} i - Replacement IFID.
|
|
128
168
|
*/
|
|
129
169
|
set IFID (i) {
|
|
130
170
|
if (typeof i === 'string') {
|
|
@@ -254,10 +294,38 @@ export default class Story {
|
|
|
254
294
|
}
|
|
255
295
|
}
|
|
256
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Passages in Story.
|
|
299
|
+
* @returns {Array} Passages
|
|
300
|
+
* @property {Array} passages - Passages
|
|
301
|
+
*/
|
|
302
|
+
get passages () { return this.#_passages; }
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Set passages in Story.
|
|
306
|
+
* @param {Array} p - Replacement passages
|
|
307
|
+
* @property {Array} passages - Passages
|
|
308
|
+
* @throws {Error} Passages must be an Array!
|
|
309
|
+
* @throws {Error} Passages must be an Array of Passage objects!
|
|
310
|
+
*/
|
|
311
|
+
set passages (p) {
|
|
312
|
+
if (Array.isArray(p)) {
|
|
313
|
+
if (p.every((passage) => passage instanceof Passage)) {
|
|
314
|
+
this.#_passages = p;
|
|
315
|
+
} else {
|
|
316
|
+
throw new Error('Passages must be an Array of Passage objects!');
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
throw new Error('Passages must be an Array!');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
257
323
|
/**
|
|
258
324
|
* Add a passage to the story.
|
|
259
|
-
* `StoryData` will override story metadata and `StoryTitle` will override story name.
|
|
325
|
+
* Passing `StoryData` will override story metadata and `StoryTitle` will override story name.
|
|
326
|
+
* @method addPassage
|
|
260
327
|
* @param {Passage} p - Passage to add to Story.
|
|
328
|
+
* @returns {number} Return new length of passages array.
|
|
261
329
|
*/
|
|
262
330
|
addPassage (p) {
|
|
263
331
|
// Check if passed argument is a Passage.
|
|
@@ -270,9 +338,9 @@ export default class Story {
|
|
|
270
338
|
// If it does, we ignore it and return.
|
|
271
339
|
if (this.getPassageByName(p.name) !== null) {
|
|
272
340
|
// Warn user
|
|
273
|
-
console.warn(
|
|
341
|
+
console.warn(`Warning: A passage with the name "${p.name}" already exists!`);
|
|
274
342
|
//
|
|
275
|
-
return;
|
|
343
|
+
return this.#_passages.length;
|
|
276
344
|
}
|
|
277
345
|
|
|
278
346
|
// Parse StoryData.
|
|
@@ -316,7 +384,7 @@ export default class Story {
|
|
|
316
384
|
}
|
|
317
385
|
|
|
318
386
|
// Don't add StoryData to passages.
|
|
319
|
-
return;
|
|
387
|
+
return this.#_passages.length;
|
|
320
388
|
}
|
|
321
389
|
|
|
322
390
|
// Parse StoryTitle.
|
|
@@ -325,24 +393,28 @@ export default class Story {
|
|
|
325
393
|
// Set internal name based on StoryTitle.
|
|
326
394
|
this.name = p.text;
|
|
327
395
|
// Once we override story.name, return.
|
|
328
|
-
return;
|
|
396
|
+
return this.#_passages.length;
|
|
329
397
|
}
|
|
330
398
|
|
|
331
399
|
// This is not StoryData or StoryTitle.
|
|
332
400
|
// Push the passage to the array.
|
|
333
|
-
this.#_passages.push(p);
|
|
401
|
+
return this.#_passages.push(p);
|
|
334
402
|
}
|
|
335
403
|
|
|
336
404
|
/**
|
|
337
405
|
* Remove a passage from the story by name.
|
|
338
|
-
* @
|
|
406
|
+
* @method removePassageByName
|
|
407
|
+
* @param {string} name - Passage name to remove.
|
|
408
|
+
* @returns {number} Return new length of passages array.
|
|
339
409
|
*/
|
|
340
410
|
removePassageByName (name) {
|
|
341
411
|
this.#_passages = this.#_passages.filter(passage => passage.name !== name);
|
|
412
|
+
return this.#_passages.length;
|
|
342
413
|
}
|
|
343
414
|
|
|
344
415
|
/**
|
|
345
416
|
* Find passages by tag.
|
|
417
|
+
* @method getPassagesByTag
|
|
346
418
|
* @param {string} t - Passage name to search for
|
|
347
419
|
* @returns {Array} Return array of passages
|
|
348
420
|
*/
|
|
@@ -356,6 +428,7 @@ export default class Story {
|
|
|
356
428
|
|
|
357
429
|
/**
|
|
358
430
|
* Find passage by name.
|
|
431
|
+
* @method getPassageByName
|
|
359
432
|
* @param {string} name - Passage name to search for
|
|
360
433
|
* @returns {Passage | null} Return passage or null
|
|
361
434
|
*/
|
|
@@ -368,32 +441,16 @@ export default class Story {
|
|
|
368
441
|
|
|
369
442
|
/**
|
|
370
443
|
* Size (number of passages).
|
|
444
|
+
* @method size
|
|
371
445
|
* @returns {number} Return number of passages
|
|
372
446
|
*/
|
|
373
447
|
size () {
|
|
374
448
|
return this.#_passages.length;
|
|
375
449
|
}
|
|
376
450
|
|
|
377
|
-
/**
|
|
378
|
-
* forEach-style iterator of passages in Story.
|
|
379
|
-
* @param {Function} callback - Callback function
|
|
380
|
-
*/
|
|
381
|
-
forEachPassage (callback) {
|
|
382
|
-
// Check if argument is a function.
|
|
383
|
-
if (typeof callback !== 'function') {
|
|
384
|
-
// Throw error
|
|
385
|
-
throw new Error('Callback must be a function!');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Use internal forEach.
|
|
389
|
-
this.#_passages.forEach((element, index) => {
|
|
390
|
-
// Call callback function with element and index.
|
|
391
|
-
callback(element, index);
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
451
|
/**
|
|
396
452
|
* Export Story as JSON representation.
|
|
453
|
+
* @method toJSON
|
|
397
454
|
* @returns {string} JSON string.
|
|
398
455
|
*/
|
|
399
456
|
toJSON () {
|
|
@@ -413,7 +470,7 @@ export default class Story {
|
|
|
413
470
|
};
|
|
414
471
|
|
|
415
472
|
// For each passage, convert into simple object.
|
|
416
|
-
this.
|
|
473
|
+
this.passages.forEach((p) => {
|
|
417
474
|
s.passages.push({
|
|
418
475
|
name: p.name,
|
|
419
476
|
tags: p.tags,
|
|
@@ -431,6 +488,8 @@ export default class Story {
|
|
|
431
488
|
*
|
|
432
489
|
* See: Twee 3 Specification
|
|
433
490
|
* (https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md)
|
|
491
|
+
*
|
|
492
|
+
* @method toTwee
|
|
434
493
|
* @returns {string} Twee String
|
|
435
494
|
*/
|
|
436
495
|
toTwee () {
|
|
@@ -443,37 +502,57 @@ export default class Story {
|
|
|
443
502
|
/**
|
|
444
503
|
* ifid: (string) Required. Maps to <tw-storydata ifid>.
|
|
445
504
|
*/
|
|
446
|
-
//
|
|
447
|
-
if (this.IFID ===
|
|
505
|
+
// Test if IFID is in UUID format.
|
|
506
|
+
if (this.IFID.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/) === null) {
|
|
448
507
|
// Generate a new IFID for this work.
|
|
449
|
-
|
|
450
|
-
|
|
508
|
+
metadata.ifid = generateIFID();
|
|
509
|
+
|
|
510
|
+
// Write the existing IFID.
|
|
511
|
+
console.warn('Warning: IFID is not in UUIDv4 format! A new IFID was generated.');
|
|
451
512
|
} else {
|
|
452
|
-
//
|
|
513
|
+
// Write the IFID.
|
|
453
514
|
metadata.ifid = this.IFID;
|
|
454
515
|
}
|
|
455
516
|
|
|
456
517
|
/**
|
|
457
518
|
* format: (string) Optional. Maps to <tw-storydata format>.
|
|
458
|
-
|
|
459
|
-
|
|
519
|
+
*/
|
|
520
|
+
// Does format exist?
|
|
521
|
+
if (this.format !== '') {
|
|
522
|
+
// Write the existing format.
|
|
523
|
+
metadata.format = this.format;
|
|
524
|
+
}
|
|
460
525
|
|
|
461
526
|
/**
|
|
462
527
|
* format-version: (string) Optional. Maps to <tw-storydata format-version>.
|
|
463
528
|
*/
|
|
464
|
-
|
|
529
|
+
// Does formatVersion exist?
|
|
530
|
+
if (this.formatVersion !== '') {
|
|
531
|
+
// Write the existing formatVersion.
|
|
532
|
+
metadata['format-version'] = this.formatVersion;
|
|
533
|
+
}
|
|
465
534
|
|
|
466
535
|
/**
|
|
467
536
|
* zoom: (decimal) Optional. Maps to <tw-storydata zoom>.
|
|
468
537
|
*/
|
|
469
|
-
|
|
538
|
+
// Does zoom exist?
|
|
539
|
+
if (this.zoom !== 0) {
|
|
540
|
+
// Write the existing zoom.
|
|
541
|
+
metadata.zoom = this.zoom;
|
|
542
|
+
}
|
|
470
543
|
|
|
471
544
|
/**
|
|
472
545
|
* start: (string) Optional.
|
|
473
546
|
* Maps to <tw-passagedata name> of the node whose pid matches <tw-storydata startnode>.
|
|
547
|
+
*
|
|
548
|
+
* If there is no start value, the "Start" passage is assumed to be the starting passage.
|
|
474
549
|
*/
|
|
475
|
-
|
|
476
|
-
|
|
550
|
+
// Does start exist?
|
|
551
|
+
if (this.start !== '') {
|
|
552
|
+
// Write the existing start.
|
|
553
|
+
metadata.start = this.start;
|
|
554
|
+
}
|
|
555
|
+
|
|
477
556
|
/**
|
|
478
557
|
* tag-colors: (object of tag(string):color(string) pairs) Optional.
|
|
479
558
|
* Pairs map to <tw-tag> nodes as <tw-tag name>:<tw-tag color>.
|
|
@@ -499,7 +578,7 @@ export default class Story {
|
|
|
499
578
|
outputContents += '\n\n';
|
|
500
579
|
|
|
501
580
|
// For each passage, append it to the output.
|
|
502
|
-
this.
|
|
581
|
+
this.passages.forEach((passage) => {
|
|
503
582
|
outputContents += passage.toTwee();
|
|
504
583
|
});
|
|
505
584
|
|
|
@@ -512,108 +591,185 @@ export default class Story {
|
|
|
512
591
|
*
|
|
513
592
|
* See: Twine 2 HTML Output
|
|
514
593
|
* (https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md)
|
|
594
|
+
*
|
|
595
|
+
* The only required attributes are `name` and `ifid` of the `<tw-storydata>` element. All others are optional.
|
|
596
|
+
*
|
|
597
|
+
* The `<tw-storydata>` element may have any number of optional attributes, which are:
|
|
598
|
+
* - `startnode`: (integer) Optional. The PID of the starting passage.
|
|
599
|
+
* - `creator`: (string) Optional. The name of the program that created the story.
|
|
600
|
+
* - `creator-version`: (string) Optional. The version of the program that created the story.
|
|
601
|
+
* - `zoom`: (decimal) Optional. The zoom level of the story.
|
|
602
|
+
* - `format`: (string) Optional. The format of the story.
|
|
603
|
+
* - `format-version`: (string) Optional. The version of the format of the story.
|
|
604
|
+
*
|
|
605
|
+
* @method toTwine2HTML
|
|
515
606
|
* @returns {string} Twine 2 HTML string
|
|
516
607
|
*/
|
|
517
608
|
toTwine2HTML () {
|
|
518
|
-
//
|
|
519
|
-
|
|
609
|
+
// Get the passages.
|
|
610
|
+
// Make a local copy, as we might be modifying it.
|
|
611
|
+
let passages = this.passages;
|
|
612
|
+
|
|
613
|
+
// Twine 2 HTML starts with a <tw-storydata> element.
|
|
614
|
+
// See: Twine 2 HTML Output
|
|
615
|
+
|
|
616
|
+
// name: (string) Required. The name of the story.
|
|
617
|
+
//
|
|
618
|
+
// Maps to <tw-storydata name>.
|
|
619
|
+
//
|
|
620
|
+
let storyData = `<tw-storydata name="${ encode( this.name ) }"`;
|
|
621
|
+
|
|
622
|
+
// ifid: (string) Required.
|
|
623
|
+
// An IFID is a sequence of between 8 and 63 characters,
|
|
624
|
+
// each of which shall be a digit, a capital letter or a
|
|
625
|
+
// hyphen that uniquely identify a story (see Treaty of Babel).
|
|
626
|
+
//
|
|
627
|
+
// Maps to <tw-storydata ifid>.
|
|
628
|
+
//
|
|
629
|
+
// Check if IFID exists.
|
|
630
|
+
if (this.IFID !== '') {
|
|
631
|
+
// Write the existing IFID.
|
|
632
|
+
storyData += ` ifid="${ this.IFID }"`;
|
|
633
|
+
} else {
|
|
634
|
+
// Generate a new IFID.
|
|
635
|
+
// Twine 2 uses v4 (random) UUIDs, using only capital letters.
|
|
636
|
+
storyData += ` ifid="${ generateIFID() }"`;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// 'Start' passage (if there is not a 'start' value set).
|
|
640
|
+
let startPassagePID = null;
|
|
641
|
+
|
|
520
642
|
// Passage Identification (PID) counter.
|
|
521
643
|
// (Twine 2 starts with 1, so we mirror that.)
|
|
522
644
|
let PIDcounter = 1;
|
|
523
645
|
|
|
524
|
-
// Does start exist?
|
|
525
|
-
if (this.start === '') {
|
|
526
|
-
// We can't create a Twine 2 HTML file without a starting passage.
|
|
527
|
-
throw new Error('No starting passage!');
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Try to find starting passage.
|
|
531
|
-
// If it doesn't exist, we throw an error.
|
|
532
|
-
if (this.getPassageByName(this.start) === null) {
|
|
533
|
-
// We can't create a Twine 2 HTML file without a starting passage.
|
|
534
|
-
throw new Error('Starting passage not found');
|
|
535
|
-
}
|
|
536
|
-
|
|
537
646
|
// Set initial PID value.
|
|
538
647
|
let startPID = 1;
|
|
648
|
+
|
|
539
649
|
// We have to do a bit of nonsense here.
|
|
540
650
|
// Twine 2 HTML cares about PID values.
|
|
541
|
-
|
|
651
|
+
passages.forEach((p) => {
|
|
542
652
|
// Have we found the starting passage?
|
|
543
653
|
if (p.name === this.start) {
|
|
544
654
|
// If so, set the PID based on index.
|
|
545
655
|
startPID = PIDcounter;
|
|
546
656
|
}
|
|
657
|
+
|
|
658
|
+
// Have we found the 'Start' passage?
|
|
659
|
+
if (p.name === 'Start') {
|
|
660
|
+
// If so, set the PID based on index.
|
|
661
|
+
startPassagePID = PIDcounter;
|
|
662
|
+
}
|
|
663
|
+
|
|
547
664
|
// Increase and keep looking.
|
|
548
665
|
PIDcounter++;
|
|
549
666
|
});
|
|
550
667
|
|
|
551
|
-
//
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
//
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
668
|
+
// startnode: (integer) Optional.
|
|
669
|
+
//
|
|
670
|
+
// Maps to <tw-storydata startnode>.
|
|
671
|
+
//
|
|
672
|
+
// Check if startnode exists.
|
|
673
|
+
if(this.start !== '') {
|
|
674
|
+
// Set starting passage PID.
|
|
675
|
+
storyData += ` startnode="${startPID}"`;
|
|
676
|
+
}
|
|
559
677
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
//
|
|
567
|
-
storyData += `
|
|
678
|
+
/**
|
|
679
|
+
* If we came from Twee or another source, we might not have a start value.
|
|
680
|
+
*
|
|
681
|
+
* We might, however, have a passage with the name "Start".
|
|
682
|
+
*/
|
|
683
|
+
if(this.start === '' && startPassagePID !== null) {
|
|
684
|
+
// Set starting passage PID.
|
|
685
|
+
storyData += ` startnode="${startPassagePID}"`;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// creator: (string) Optional. The name of the program that created the story.
|
|
689
|
+
// Maps to <tw-storydata creator>.
|
|
690
|
+
if(this.creator !== '') {
|
|
691
|
+
// Write existing creator.
|
|
692
|
+
storyData += ` creator="${ encode( this.creator ) }"`;
|
|
568
693
|
}
|
|
569
694
|
|
|
570
|
-
//
|
|
571
|
-
|
|
695
|
+
// creator-version: (string) Optional. The version of the program that created the story.
|
|
696
|
+
// Maps to <tw-storydata creator-version>.
|
|
697
|
+
if(this.creatorVersion !== '') {
|
|
698
|
+
// Default to extwee version.
|
|
699
|
+
storyData += ` creator-version="${this.creatorVersion}"`;
|
|
700
|
+
}
|
|
572
701
|
|
|
573
|
-
//
|
|
574
|
-
|
|
702
|
+
// zoom: (decimal) Optional. The zoom level of the story.
|
|
703
|
+
// Maps to <tw-storydata zoom>.
|
|
704
|
+
if(this.zoom !== 1) {
|
|
705
|
+
// Write existing or default value.
|
|
706
|
+
storyData += ` zoom="${this.zoom}"`;
|
|
707
|
+
}
|
|
575
708
|
|
|
576
|
-
//
|
|
577
|
-
|
|
709
|
+
// format: (string) Optional. The format of the story.
|
|
710
|
+
// Maps to <tw-storydata format>.
|
|
711
|
+
if(this.format !== '') {
|
|
712
|
+
// Write existing or default value.
|
|
713
|
+
storyData += ` format="${this.format}"`;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// format-version: (string) Optional. The version of the format of the story.
|
|
717
|
+
// Maps to <tw-storydata format-version>.
|
|
718
|
+
if(this.formatVersion !== '') {
|
|
719
|
+
// Write existing or default value.
|
|
720
|
+
storyData += ` format-version="${this.formatVersion}"`;
|
|
721
|
+
}
|
|
578
722
|
|
|
579
723
|
// Add the default attributes.
|
|
580
724
|
storyData += ' options hidden>\n';
|
|
581
725
|
|
|
582
|
-
//
|
|
583
|
-
|
|
726
|
+
// Filter out passages with tag of 'stylesheet'.
|
|
727
|
+
const stylesheetPassages = passages.filter((passage) => passage.tags.includes('stylesheet'));
|
|
584
728
|
|
|
585
|
-
//
|
|
586
|
-
|
|
729
|
+
// Remove stylesheet passages from the main array.
|
|
730
|
+
passages = passages.filter(p => !p.tags.includes('stylesheet'));
|
|
587
731
|
|
|
588
|
-
//
|
|
589
|
-
stylesheetPassages.
|
|
590
|
-
//
|
|
591
|
-
storyData +=
|
|
592
|
-
});
|
|
732
|
+
// Were there any stylesheet passages?
|
|
733
|
+
if (stylesheetPassages.length > 0) {
|
|
734
|
+
// Start the STYLE.
|
|
735
|
+
storyData += '\t<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">';
|
|
593
736
|
|
|
594
|
-
|
|
595
|
-
|
|
737
|
+
// Concatenate passages.
|
|
738
|
+
stylesheetPassages.forEach((passage) => {
|
|
739
|
+
// Add text of passages.
|
|
740
|
+
storyData += passage.text;
|
|
741
|
+
});
|
|
596
742
|
|
|
597
|
-
|
|
598
|
-
|
|
743
|
+
// Close the STYLE.
|
|
744
|
+
storyData += '</style>\n';
|
|
745
|
+
}
|
|
599
746
|
|
|
600
|
-
//
|
|
601
|
-
const scriptPassages =
|
|
747
|
+
// Filter out passages with tag of 'script'.
|
|
748
|
+
const scriptPassages = passages.filter((passage) => passage.tags.includes('script'));
|
|
602
749
|
|
|
603
|
-
//
|
|
604
|
-
|
|
605
|
-
// Add text of passages.
|
|
606
|
-
storyData += passage.text;
|
|
607
|
-
});
|
|
750
|
+
// Remove script passages from the main array.
|
|
751
|
+
passages = passages.filter(p => !p.tags.includes('script'));
|
|
608
752
|
|
|
609
|
-
//
|
|
610
|
-
|
|
753
|
+
// Were there any script passages?
|
|
754
|
+
if (scriptPassages.length > 0) {
|
|
755
|
+
// Start the SCRIPT.
|
|
756
|
+
storyData += '\t<script role="script" id="twine-user-script" type="text/twine-javascript">';
|
|
757
|
+
|
|
758
|
+
// Concatenate passages.
|
|
759
|
+
scriptPassages.forEach((passage) => {
|
|
760
|
+
// Add text of passages.
|
|
761
|
+
storyData += passage.text;
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// Close SCRIPT.
|
|
765
|
+
storyData += '</script>\n';
|
|
766
|
+
}
|
|
611
767
|
|
|
612
768
|
// Reset the PID counter.
|
|
613
769
|
PIDcounter = 1;
|
|
614
770
|
|
|
615
771
|
// Build the passages HTML.
|
|
616
|
-
this.
|
|
772
|
+
this.passages.forEach((passage) => {
|
|
617
773
|
// Append each passage element using the PID counter.
|
|
618
774
|
storyData += passage.toTwine2HTML(PIDcounter);
|
|
619
775
|
// Increase counter inside loop.
|
|
@@ -632,6 +788,8 @@ export default class Story {
|
|
|
632
788
|
*
|
|
633
789
|
* See: Twine 1 HTML Output
|
|
634
790
|
* (https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md)
|
|
791
|
+
*
|
|
792
|
+
* @method toTwine1HTML
|
|
635
793
|
* @returns {string} Twine 1 HTML string.
|
|
636
794
|
*/
|
|
637
795
|
toTwine1HTML () {
|
|
@@ -639,7 +797,7 @@ export default class Story {
|
|
|
639
797
|
let outputContents = '';
|
|
640
798
|
|
|
641
799
|
// Process passages (if any).
|
|
642
|
-
this.
|
|
800
|
+
this.passages.forEach((p) => {
|
|
643
801
|
// Output HTML output per passage.
|
|
644
802
|
outputContents += `\t${p.toTwine1HTML()}`;
|
|
645
803
|
});
|
|
@@ -647,4 +805,8 @@ export default class Story {
|
|
|
647
805
|
// Return Twine 1 HTML content.
|
|
648
806
|
return outputContents;
|
|
649
807
|
}
|
|
808
|
+
|
|
809
|
+
|
|
650
810
|
}
|
|
811
|
+
|
|
812
|
+
export { Story, creatorName, creatorVersion };
|