extwee 2.2.1 → 2.2.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.
- package/.eslintrc.json +7 -13
- 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/objects/story.md +1 -2
- package/index.js +2 -0
- package/jest.config.json +5 -0
- package/package.json +24 -21
- package/src/IFID/generate.js +20 -0
- package/src/JSON/parse.js +43 -0
- package/src/Passage.js +52 -3
- package/src/Story.js +266 -107
- package/src/StoryFormat/parse.js +190 -80
- package/src/StoryFormat.js +78 -88
- package/src/TWS/parse.js +2 -2
- package/src/Twee/parse.js +2 -3
- package/src/Twine1HTML/compile.js +2 -0
- package/src/Twine1HTML/parse.js +2 -3
- package/src/Twine2ArchiveHTML/compile.js +8 -0
- package/src/Twine2ArchiveHTML/parse.js +33 -3
- package/src/Twine2HTML/compile.js +31 -6
- package/src/Twine2HTML/parse.js +49 -54
- package/test/IFID/IFID.Generate.test.js +10 -0
- package/test/JSON/JSON.Parse.test.js +4 -4
- package/test/{Passage.test.js → Objects/Passage.test.js} +4 -4
- package/test/{Story.test.js → Objects/Story.test.js} +259 -50
- package/test/{StoryFormat.test.js → Objects/StoryFormat.test.js} +10 -3
- package/test/StoryFormat/StoryFormat.Parse.test.js +442 -55
- package/test/TWS/Parse.test.js +1 -1
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +20 -4
- package/test/Twine2HTML/Twine2HTML.Compile.test.js +35 -120
- package/test/Twine2HTML/Twine2HTML.Parse.test.js +57 -38
- package/test/Twine2HTML/Twine2HTMLCompiler/format.js +9 -0
- package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +1 -1
- package/types/IFID/generate.d.ts +14 -0
- package/types/JSON/parse.d.ts +51 -0
- package/types/Passage.d.ts +117 -0
- package/types/Story.d.ts +230 -0
- package/types/StoryFormat/parse.d.ts +50 -0
- package/types/StoryFormat.d.ts +121 -0
- package/types/TWS/parse.d.ts +10 -0
- package/types/Twee/parse.d.ts +9 -0
- package/types/Twine1HTML/compile.d.ts +19 -0
- package/types/Twine1HTML/parse.d.ts +9 -0
- package/types/Twine2ArchiveHTML/compile.d.ts +14 -0
- package/types/Twine2ArchiveHTML/parse.d.ts +36 -0
- package/types/Twine2HTML/compile.d.ts +14 -0
- package/types/Twine2HTML/parse.d.ts +20 -0
- package/web-index.js +2 -0
- 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,16 +1,50 @@
|
|
|
1
1
|
import Passage from './Passage.js';
|
|
2
|
-
import {
|
|
2
|
+
import { generate as generateIFID } from './IFID/generate.js';
|
|
3
3
|
import { encode } from 'html-entities';
|
|
4
4
|
|
|
5
5
|
const creatorName = 'extwee';
|
|
6
|
-
const creatorVersion = '2.2.
|
|
7
|
-
|
|
6
|
+
const creatorVersion = '2.2.3';
|
|
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
|
+
*/
|
|
8
42
|
class Story {
|
|
9
43
|
/**
|
|
10
44
|
* Internal name of story
|
|
11
45
|
* @private
|
|
12
46
|
*/
|
|
13
|
-
#_name = '';
|
|
47
|
+
#_name = 'Untitled Story';
|
|
14
48
|
|
|
15
49
|
/**
|
|
16
50
|
* Internal start
|
|
@@ -37,8 +71,9 @@ class Story {
|
|
|
37
71
|
|
|
38
72
|
/**
|
|
39
73
|
* Internal zoom level
|
|
74
|
+
* Default is 1 (100%)
|
|
40
75
|
*/
|
|
41
|
-
#_zoom =
|
|
76
|
+
#_zoom = 1;
|
|
42
77
|
|
|
43
78
|
/**
|
|
44
79
|
* Passages
|
|
@@ -74,12 +109,16 @@ class Story {
|
|
|
74
109
|
* Creates a story.
|
|
75
110
|
* @param {string} name - Name of the story.
|
|
76
111
|
*/
|
|
77
|
-
constructor (name = '') {
|
|
112
|
+
constructor (name = 'Untitled Story') {
|
|
78
113
|
// Every story has a name.
|
|
79
114
|
this.name = name;
|
|
115
|
+
|
|
80
116
|
// Store the creator.
|
|
81
117
|
this.#_creator = creatorName;
|
|
118
|
+
|
|
119
|
+
// Store the creator version.
|
|
82
120
|
this.#_creatorVersion = creatorVersion;
|
|
121
|
+
|
|
83
122
|
// Set metadata to an object.
|
|
84
123
|
this.#_metadata = {};
|
|
85
124
|
}
|
|
@@ -119,13 +158,13 @@ class Story {
|
|
|
119
158
|
}
|
|
120
159
|
|
|
121
160
|
/**
|
|
122
|
-
* Interactive Fiction ID (IFID) of Story
|
|
161
|
+
* Interactive Fiction ID (IFID) of Story.
|
|
123
162
|
* @returns {string} IFID
|
|
124
163
|
*/
|
|
125
164
|
get IFID () { return this.#_IFID; }
|
|
126
165
|
|
|
127
166
|
/**
|
|
128
|
-
* @param {string} i - Replacement IFID
|
|
167
|
+
* @param {string} i - Replacement IFID.
|
|
129
168
|
*/
|
|
130
169
|
set IFID (i) {
|
|
131
170
|
if (typeof i === 'string') {
|
|
@@ -255,10 +294,38 @@ class Story {
|
|
|
255
294
|
}
|
|
256
295
|
}
|
|
257
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
|
+
|
|
258
323
|
/**
|
|
259
324
|
* Add a passage to the story.
|
|
260
|
-
* `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
|
|
261
327
|
* @param {Passage} p - Passage to add to Story.
|
|
328
|
+
* @returns {number} Return new length of passages array.
|
|
262
329
|
*/
|
|
263
330
|
addPassage (p) {
|
|
264
331
|
// Check if passed argument is a Passage.
|
|
@@ -271,9 +338,9 @@ class Story {
|
|
|
271
338
|
// If it does, we ignore it and return.
|
|
272
339
|
if (this.getPassageByName(p.name) !== null) {
|
|
273
340
|
// Warn user
|
|
274
|
-
console.warn(
|
|
341
|
+
console.warn(`Warning: A passage with the name "${p.name}" already exists!`);
|
|
275
342
|
//
|
|
276
|
-
return;
|
|
343
|
+
return this.#_passages.length;
|
|
277
344
|
}
|
|
278
345
|
|
|
279
346
|
// Parse StoryData.
|
|
@@ -317,7 +384,7 @@ class Story {
|
|
|
317
384
|
}
|
|
318
385
|
|
|
319
386
|
// Don't add StoryData to passages.
|
|
320
|
-
return;
|
|
387
|
+
return this.#_passages.length;
|
|
321
388
|
}
|
|
322
389
|
|
|
323
390
|
// Parse StoryTitle.
|
|
@@ -326,24 +393,28 @@ class Story {
|
|
|
326
393
|
// Set internal name based on StoryTitle.
|
|
327
394
|
this.name = p.text;
|
|
328
395
|
// Once we override story.name, return.
|
|
329
|
-
return;
|
|
396
|
+
return this.#_passages.length;
|
|
330
397
|
}
|
|
331
398
|
|
|
332
399
|
// This is not StoryData or StoryTitle.
|
|
333
400
|
// Push the passage to the array.
|
|
334
|
-
this.#_passages.push(p);
|
|
401
|
+
return this.#_passages.push(p);
|
|
335
402
|
}
|
|
336
403
|
|
|
337
404
|
/**
|
|
338
405
|
* Remove a passage from the story by name.
|
|
339
|
-
* @
|
|
406
|
+
* @method removePassageByName
|
|
407
|
+
* @param {string} name - Passage name to remove.
|
|
408
|
+
* @returns {number} Return new length of passages array.
|
|
340
409
|
*/
|
|
341
410
|
removePassageByName (name) {
|
|
342
411
|
this.#_passages = this.#_passages.filter(passage => passage.name !== name);
|
|
412
|
+
return this.#_passages.length;
|
|
343
413
|
}
|
|
344
414
|
|
|
345
415
|
/**
|
|
346
416
|
* Find passages by tag.
|
|
417
|
+
* @method getPassagesByTag
|
|
347
418
|
* @param {string} t - Passage name to search for
|
|
348
419
|
* @returns {Array} Return array of passages
|
|
349
420
|
*/
|
|
@@ -357,6 +428,7 @@ class Story {
|
|
|
357
428
|
|
|
358
429
|
/**
|
|
359
430
|
* Find passage by name.
|
|
431
|
+
* @method getPassageByName
|
|
360
432
|
* @param {string} name - Passage name to search for
|
|
361
433
|
* @returns {Passage | null} Return passage or null
|
|
362
434
|
*/
|
|
@@ -369,32 +441,16 @@ class Story {
|
|
|
369
441
|
|
|
370
442
|
/**
|
|
371
443
|
* Size (number of passages).
|
|
444
|
+
* @method size
|
|
372
445
|
* @returns {number} Return number of passages
|
|
373
446
|
*/
|
|
374
447
|
size () {
|
|
375
448
|
return this.#_passages.length;
|
|
376
449
|
}
|
|
377
450
|
|
|
378
|
-
/**
|
|
379
|
-
* forEach-style iterator of passages in Story.
|
|
380
|
-
* @param {Function} callback - Callback function
|
|
381
|
-
*/
|
|
382
|
-
forEachPassage (callback) {
|
|
383
|
-
// Check if argument is a function.
|
|
384
|
-
if (typeof callback !== 'function') {
|
|
385
|
-
// Throw error
|
|
386
|
-
throw new Error('Callback must be a function!');
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Use internal forEach.
|
|
390
|
-
this.#_passages.forEach((element, index) => {
|
|
391
|
-
// Call callback function with element and index.
|
|
392
|
-
callback(element, index);
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
|
|
396
451
|
/**
|
|
397
452
|
* Export Story as JSON representation.
|
|
453
|
+
* @method toJSON
|
|
398
454
|
* @returns {string} JSON string.
|
|
399
455
|
*/
|
|
400
456
|
toJSON () {
|
|
@@ -414,7 +470,7 @@ class Story {
|
|
|
414
470
|
};
|
|
415
471
|
|
|
416
472
|
// For each passage, convert into simple object.
|
|
417
|
-
this.
|
|
473
|
+
this.passages.forEach((p) => {
|
|
418
474
|
s.passages.push({
|
|
419
475
|
name: p.name,
|
|
420
476
|
tags: p.tags,
|
|
@@ -432,6 +488,8 @@ class Story {
|
|
|
432
488
|
*
|
|
433
489
|
* See: Twee 3 Specification
|
|
434
490
|
* (https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md)
|
|
491
|
+
*
|
|
492
|
+
* @method toTwee
|
|
435
493
|
* @returns {string} Twee String
|
|
436
494
|
*/
|
|
437
495
|
toTwee () {
|
|
@@ -444,37 +502,57 @@ class Story {
|
|
|
444
502
|
/**
|
|
445
503
|
* ifid: (string) Required. Maps to <tw-storydata ifid>.
|
|
446
504
|
*/
|
|
447
|
-
//
|
|
448
|
-
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) {
|
|
449
507
|
// Generate a new IFID for this work.
|
|
450
|
-
|
|
451
|
-
|
|
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.');
|
|
452
512
|
} else {
|
|
453
|
-
//
|
|
513
|
+
// Write the IFID.
|
|
454
514
|
metadata.ifid = this.IFID;
|
|
455
515
|
}
|
|
456
516
|
|
|
457
517
|
/**
|
|
458
518
|
* format: (string) Optional. Maps to <tw-storydata format>.
|
|
459
|
-
|
|
460
|
-
|
|
519
|
+
*/
|
|
520
|
+
// Does format exist?
|
|
521
|
+
if (this.format !== '') {
|
|
522
|
+
// Write the existing format.
|
|
523
|
+
metadata.format = this.format;
|
|
524
|
+
}
|
|
461
525
|
|
|
462
526
|
/**
|
|
463
527
|
* format-version: (string) Optional. Maps to <tw-storydata format-version>.
|
|
464
528
|
*/
|
|
465
|
-
|
|
529
|
+
// Does formatVersion exist?
|
|
530
|
+
if (this.formatVersion !== '') {
|
|
531
|
+
// Write the existing formatVersion.
|
|
532
|
+
metadata['format-version'] = this.formatVersion;
|
|
533
|
+
}
|
|
466
534
|
|
|
467
535
|
/**
|
|
468
536
|
* zoom: (decimal) Optional. Maps to <tw-storydata zoom>.
|
|
469
537
|
*/
|
|
470
|
-
|
|
538
|
+
// Does zoom exist?
|
|
539
|
+
if (this.zoom !== 0) {
|
|
540
|
+
// Write the existing zoom.
|
|
541
|
+
metadata.zoom = this.zoom;
|
|
542
|
+
}
|
|
471
543
|
|
|
472
544
|
/**
|
|
473
545
|
* start: (string) Optional.
|
|
474
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.
|
|
475
549
|
*/
|
|
476
|
-
|
|
477
|
-
|
|
550
|
+
// Does start exist?
|
|
551
|
+
if (this.start !== '') {
|
|
552
|
+
// Write the existing start.
|
|
553
|
+
metadata.start = this.start;
|
|
554
|
+
}
|
|
555
|
+
|
|
478
556
|
/**
|
|
479
557
|
* tag-colors: (object of tag(string):color(string) pairs) Optional.
|
|
480
558
|
* Pairs map to <tw-tag> nodes as <tw-tag name>:<tw-tag color>.
|
|
@@ -500,7 +578,7 @@ class Story {
|
|
|
500
578
|
outputContents += '\n\n';
|
|
501
579
|
|
|
502
580
|
// For each passage, append it to the output.
|
|
503
|
-
this.
|
|
581
|
+
this.passages.forEach((passage) => {
|
|
504
582
|
outputContents += passage.toTwee();
|
|
505
583
|
});
|
|
506
584
|
|
|
@@ -513,108 +591,185 @@ class Story {
|
|
|
513
591
|
*
|
|
514
592
|
* See: Twine 2 HTML Output
|
|
515
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
|
|
516
606
|
* @returns {string} Twine 2 HTML string
|
|
517
607
|
*/
|
|
518
608
|
toTwine2HTML () {
|
|
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
|
+
//
|
|
520
620
|
let storyData = `<tw-storydata name="${ encode( this.name ) }"`;
|
|
521
|
-
// Passage Identification (PID) counter.
|
|
522
|
-
// (Twine 2 starts with 1, so we mirror that.)
|
|
523
|
-
let PIDcounter = 1;
|
|
524
621
|
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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() }"`;
|
|
529
637
|
}
|
|
530
638
|
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
639
|
+
// 'Start' passage (if there is not a 'start' value set).
|
|
640
|
+
let startPassagePID = null;
|
|
641
|
+
|
|
642
|
+
// Passage Identification (PID) counter.
|
|
643
|
+
// (Twine 2 starts with 1, so we mirror that.)
|
|
644
|
+
let PIDcounter = 1;
|
|
537
645
|
|
|
538
646
|
// Set initial PID value.
|
|
539
647
|
let startPID = 1;
|
|
648
|
+
|
|
540
649
|
// We have to do a bit of nonsense here.
|
|
541
650
|
// Twine 2 HTML cares about PID values.
|
|
542
|
-
|
|
651
|
+
passages.forEach((p) => {
|
|
543
652
|
// Have we found the starting passage?
|
|
544
653
|
if (p.name === this.start) {
|
|
545
654
|
// If so, set the PID based on index.
|
|
546
655
|
startPID = PIDcounter;
|
|
547
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
|
+
|
|
548
664
|
// Increase and keep looking.
|
|
549
665
|
PIDcounter++;
|
|
550
666
|
});
|
|
551
667
|
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
+
}
|
|
560
677
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
//
|
|
568
|
-
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 ) }"`;
|
|
569
693
|
}
|
|
570
694
|
|
|
571
|
-
//
|
|
572
|
-
|
|
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
|
+
}
|
|
573
701
|
|
|
574
|
-
//
|
|
575
|
-
|
|
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
|
+
}
|
|
576
708
|
|
|
577
|
-
//
|
|
578
|
-
|
|
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
|
+
}
|
|
579
722
|
|
|
580
723
|
// Add the default attributes.
|
|
581
724
|
storyData += ' options hidden>\n';
|
|
582
725
|
|
|
583
|
-
//
|
|
584
|
-
|
|
726
|
+
// Filter out passages with tag of 'stylesheet'.
|
|
727
|
+
const stylesheetPassages = passages.filter((passage) => passage.tags.includes('stylesheet'));
|
|
585
728
|
|
|
586
|
-
//
|
|
587
|
-
|
|
729
|
+
// Remove stylesheet passages from the main array.
|
|
730
|
+
passages = passages.filter(p => !p.tags.includes('stylesheet'));
|
|
588
731
|
|
|
589
|
-
//
|
|
590
|
-
stylesheetPassages.
|
|
591
|
-
//
|
|
592
|
-
storyData +=
|
|
593
|
-
});
|
|
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">';
|
|
594
736
|
|
|
595
|
-
|
|
596
|
-
|
|
737
|
+
// Concatenate passages.
|
|
738
|
+
stylesheetPassages.forEach((passage) => {
|
|
739
|
+
// Add text of passages.
|
|
740
|
+
storyData += passage.text;
|
|
741
|
+
});
|
|
597
742
|
|
|
598
|
-
|
|
599
|
-
|
|
743
|
+
// Close the STYLE.
|
|
744
|
+
storyData += '</style>\n';
|
|
745
|
+
}
|
|
600
746
|
|
|
601
|
-
//
|
|
602
|
-
const scriptPassages =
|
|
747
|
+
// Filter out passages with tag of 'script'.
|
|
748
|
+
const scriptPassages = passages.filter((passage) => passage.tags.includes('script'));
|
|
603
749
|
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
// Add text of passages.
|
|
607
|
-
storyData += passage.text;
|
|
608
|
-
});
|
|
750
|
+
// Remove script passages from the main array.
|
|
751
|
+
passages = passages.filter(p => !p.tags.includes('script'));
|
|
609
752
|
|
|
610
|
-
//
|
|
611
|
-
|
|
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
|
+
}
|
|
612
767
|
|
|
613
768
|
// Reset the PID counter.
|
|
614
769
|
PIDcounter = 1;
|
|
615
770
|
|
|
616
771
|
// Build the passages HTML.
|
|
617
|
-
this.
|
|
772
|
+
this.passages.forEach((passage) => {
|
|
618
773
|
// Append each passage element using the PID counter.
|
|
619
774
|
storyData += passage.toTwine2HTML(PIDcounter);
|
|
620
775
|
// Increase counter inside loop.
|
|
@@ -633,6 +788,8 @@ class Story {
|
|
|
633
788
|
*
|
|
634
789
|
* See: Twine 1 HTML Output
|
|
635
790
|
* (https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md)
|
|
791
|
+
*
|
|
792
|
+
* @method toTwine1HTML
|
|
636
793
|
* @returns {string} Twine 1 HTML string.
|
|
637
794
|
*/
|
|
638
795
|
toTwine1HTML () {
|
|
@@ -640,7 +797,7 @@ class Story {
|
|
|
640
797
|
let outputContents = '';
|
|
641
798
|
|
|
642
799
|
// Process passages (if any).
|
|
643
|
-
this.
|
|
800
|
+
this.passages.forEach((p) => {
|
|
644
801
|
// Output HTML output per passage.
|
|
645
802
|
outputContents += `\t${p.toTwine1HTML()}`;
|
|
646
803
|
});
|
|
@@ -648,6 +805,8 @@ class Story {
|
|
|
648
805
|
// Return Twine 1 HTML content.
|
|
649
806
|
return outputContents;
|
|
650
807
|
}
|
|
808
|
+
|
|
809
|
+
|
|
651
810
|
}
|
|
652
811
|
|
|
653
812
|
export { Story, creatorName, creatorVersion };
|