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.
Files changed (68) hide show
  1. package/.eslintrc.json +7 -13
  2. package/.github/codeql-analysis.yml +51 -0
  3. package/README.md +9 -3
  4. package/build/extwee +0 -0
  5. package/build/extwee.web.min.js +1 -1
  6. package/docs/objects/story.md +1 -2
  7. package/index.js +2 -0
  8. package/jest.config.json +5 -0
  9. package/package.json +24 -21
  10. package/src/IFID/generate.js +20 -0
  11. package/src/JSON/parse.js +43 -0
  12. package/src/Passage.js +52 -3
  13. package/src/Story.js +266 -107
  14. package/src/StoryFormat/parse.js +190 -80
  15. package/src/StoryFormat.js +78 -88
  16. package/src/TWS/parse.js +2 -2
  17. package/src/Twee/parse.js +2 -3
  18. package/src/Twine1HTML/compile.js +2 -0
  19. package/src/Twine1HTML/parse.js +2 -3
  20. package/src/Twine2ArchiveHTML/compile.js +8 -0
  21. package/src/Twine2ArchiveHTML/parse.js +33 -3
  22. package/src/Twine2HTML/compile.js +31 -6
  23. package/src/Twine2HTML/parse.js +49 -54
  24. package/test/IFID/IFID.Generate.test.js +10 -0
  25. package/test/JSON/JSON.Parse.test.js +4 -4
  26. package/test/{Passage.test.js → Objects/Passage.test.js} +4 -4
  27. package/test/{Story.test.js → Objects/Story.test.js} +259 -50
  28. package/test/{StoryFormat.test.js → Objects/StoryFormat.test.js} +10 -3
  29. package/test/StoryFormat/StoryFormat.Parse.test.js +442 -55
  30. package/test/TWS/Parse.test.js +1 -1
  31. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +20 -4
  32. package/test/Twine2HTML/Twine2HTML.Compile.test.js +35 -120
  33. package/test/Twine2HTML/Twine2HTML.Parse.test.js +57 -38
  34. package/test/Twine2HTML/Twine2HTMLCompiler/format.js +9 -0
  35. package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +1 -1
  36. package/types/IFID/generate.d.ts +14 -0
  37. package/types/JSON/parse.d.ts +51 -0
  38. package/types/Passage.d.ts +117 -0
  39. package/types/Story.d.ts +230 -0
  40. package/types/StoryFormat/parse.d.ts +50 -0
  41. package/types/StoryFormat.d.ts +121 -0
  42. package/types/TWS/parse.d.ts +10 -0
  43. package/types/Twee/parse.d.ts +9 -0
  44. package/types/Twine1HTML/compile.d.ts +19 -0
  45. package/types/Twine1HTML/parse.d.ts +9 -0
  46. package/types/Twine2ArchiveHTML/compile.d.ts +14 -0
  47. package/types/Twine2ArchiveHTML/parse.d.ts +36 -0
  48. package/types/Twine2HTML/compile.d.ts +14 -0
  49. package/types/Twine2HTML/parse.d.ts +20 -0
  50. package/web-index.js +2 -0
  51. package/test/StoryFormat/StoryFormatParser/example.js +0 -3
  52. package/test/StoryFormat/StoryFormatParser/example2.js +0 -3
  53. package/test/StoryFormat/StoryFormatParser/format.js +0 -1
  54. package/test/StoryFormat/StoryFormatParser/format_doublename.js +0 -1
  55. package/test/StoryFormat/StoryFormatParser/harlowe.js +0 -3
  56. package/test/StoryFormat/StoryFormatParser/missingAuthor.js +0 -1
  57. package/test/StoryFormat/StoryFormatParser/missingDescription.js +0 -1
  58. package/test/StoryFormat/StoryFormatParser/missingImage.js +0 -1
  59. package/test/StoryFormat/StoryFormatParser/missingLicense.js +0 -1
  60. package/test/StoryFormat/StoryFormatParser/missingName.js +0 -1
  61. package/test/StoryFormat/StoryFormatParser/missingProofing.js +0 -1
  62. package/test/StoryFormat/StoryFormatParser/missingSource.js +0 -1
  63. package/test/StoryFormat/StoryFormatParser/missingURL.js +0 -1
  64. package/test/StoryFormat/StoryFormatParser/missingVersion.js +0 -1
  65. package/test/StoryFormat/StoryFormatParser/versionWrong.js +0 -1
  66. package/test/Twine2HTML/Twine2HTMLParser/missingName.html +0 -33
  67. package/test/Twine2HTML/Twine2HTMLParser/missingPID.html +0 -15
  68. 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 { v4 as uuidv4 } from 'uuid';
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.1';
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 = 0;
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('Ignored passage with same name as existing one!');
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
- * @param {string} name - Passage name to remove
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.forEachPassage((p) => {
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
- // Is there an IFID?
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
- // Twine 2 uses v4 (random) UUIDs, using only capital letters.
451
- metadata.ifid = uuidv4().toUpperCase();
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
- // Use existing (non-default) value.
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
- metadata.format = this.format;
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
- metadata['format-version'] = this.formatVersion;
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
- metadata.zoom = this.zoom;
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
- metadata.start = this.start;
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.forEachPassage((passage) => {
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
- // Prepare HTML content.
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
- // Does start exist?
526
- if (this.start === '') {
527
- // We can't create a Twine 2 HTML file without a starting passage.
528
- throw new Error('No starting passage!');
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
- // Try to find starting passage.
532
- // If it doesn't exist, we throw an error.
533
- if (this.getPassageByName(this.start) === null) {
534
- // We can't create a Twine 2 HTML file without a starting passage.
535
- throw new Error('Starting passage not found');
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
- this.forEachPassage((p) => {
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
- // Set starting passage PID.
553
- storyData += ` startnode="${startPID}"`;
554
-
555
- // Defaults to 'extwee' if missing.
556
- storyData += ` creator="${ encode( this.creator ) }"`;
557
-
558
- // Default to extwee version.
559
- storyData += ` creator-version="${this.creatorVersion}"`;
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
- // Check if IFID exists.
562
- if (this.IFID !== '') {
563
- // Write the existing IFID.
564
- storyData += ` ifid="${this.IFID}"`;
565
- } else {
566
- // Generate a new IFID.
567
- // Twine 2 uses v4 (random) UUIDs, using only capital letters.
568
- storyData += ` ifid="${uuidv4().toUpperCase()}"`;
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
- // Write existing or default value.
572
- storyData += ` zoom="${this.zoom}"`;
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
- // Write existing or default value.
575
- storyData += ` format="${ encode(this.#_format) }"`;
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
- // Write existing or default value.
578
- storyData += ` format-version="${this.#_formatVersion}"`;
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
- // Start the STYLE.
584
- storyData += '\t<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">';
726
+ // Filter out passages with tag of 'stylesheet'.
727
+ const stylesheetPassages = passages.filter((passage) => passage.tags.includes('stylesheet'));
585
728
 
586
- // Get stylesheet passages.
587
- const stylesheetPassages = this.getPassagesByTag('stylesheet');
729
+ // Remove stylesheet passages from the main array.
730
+ passages = passages.filter(p => !p.tags.includes('stylesheet'));
588
731
 
589
- // Concatenate passages.
590
- stylesheetPassages.forEach((passage) => {
591
- // Add text of passages.
592
- storyData += passage.text;
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
- // Close the STYLE.
596
- storyData += '</style>\n';
737
+ // Concatenate passages.
738
+ stylesheetPassages.forEach((passage) => {
739
+ // Add text of passages.
740
+ storyData += passage.text;
741
+ });
597
742
 
598
- // Start the SCRIPT.
599
- storyData += '\t<script role="script" id="twine-user-script" type="text/twine-javascript">';
743
+ // Close the STYLE.
744
+ storyData += '</style>\n';
745
+ }
600
746
 
601
- // Get stylesheet passages.
602
- const scriptPassages = this.getPassagesByTag('script');
747
+ // Filter out passages with tag of 'script'.
748
+ const scriptPassages = passages.filter((passage) => passage.tags.includes('script'));
603
749
 
604
- // Concatenate passages.
605
- scriptPassages.forEach((passage) => {
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
- // Close SCRIPT.
611
- storyData += '</script>\n';
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.forEachPassage((passage) => {
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.forEachPassage((p) => {
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 };