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.
Files changed (74) hide show
  1. package/.github/codeql-analysis.yml +51 -0
  2. package/README.md +9 -3
  3. package/build/extwee +0 -0
  4. package/build/extwee.web.min.js +1 -1
  5. package/docs/_sidebar.md +1 -0
  6. package/docs/examples/dynamicPassages.md +1 -1
  7. package/docs/examples/twsToTwee.md +1 -1
  8. package/docs/objects/story.md +1 -2
  9. package/index.js +3 -1
  10. package/package.json +22 -19
  11. package/src/IFID/generate.js +20 -0
  12. package/src/JSON/parse.js +44 -1
  13. package/src/Passage.js +61 -31
  14. package/src/Story.js +272 -110
  15. package/src/StoryFormat/parse.js +190 -80
  16. package/src/StoryFormat.js +78 -88
  17. package/src/TWS/parse.js +3 -3
  18. package/src/Twee/parse.js +3 -4
  19. package/src/Twine1HTML/compile.js +3 -1
  20. package/src/Twine1HTML/parse.js +3 -4
  21. package/src/Twine2ArchiveHTML/compile.js +9 -1
  22. package/src/Twine2ArchiveHTML/parse.js +33 -3
  23. package/src/Twine2HTML/compile.js +32 -7
  24. package/src/Twine2HTML/parse.js +51 -55
  25. package/test/IFID/IFID.Generate.test.js +10 -0
  26. package/test/JSON/JSON.Parse.test.js +24 -24
  27. package/test/Passage.test.js +69 -0
  28. package/test/Story.test.js +298 -49
  29. package/test/StoryFormat/StoryFormat.Parse.test.js +442 -55
  30. package/test/StoryFormat.test.js +9 -2
  31. package/test/TWS/Parse.test.js +1 -1
  32. package/test/Twine1HTML/Twine1HTML.Compile.test.js +1 -1
  33. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +1 -1
  34. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +20 -4
  35. package/test/Twine2HTML/Twine2HTML.Compile.test.js +36 -121
  36. package/test/Twine2HTML/Twine2HTML.Parse.test.js +63 -43
  37. package/test/Twine2HTML/Twine2HTMLCompiler/format.js +9 -0
  38. package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +1 -1
  39. package/test/Twine2HTML/Twine2HTMLParser/unescaping.html +33 -0
  40. package/tsconfig.json +21 -0
  41. package/types/index.d.ts +14 -0
  42. package/types/src/JSON/parse.d.ts +8 -0
  43. package/types/src/Passage.d.ts +72 -0
  44. package/types/src/Story.d.ts +161 -0
  45. package/types/src/StoryFormat/parse.d.ts +7 -0
  46. package/types/src/StoryFormat.d.ts +97 -0
  47. package/types/src/TWS/parse.d.ts +10 -0
  48. package/types/src/Twee/parse.d.ts +10 -0
  49. package/types/src/Twine1HTML/compile.d.ts +17 -0
  50. package/types/src/Twine1HTML/parse.d.ts +10 -0
  51. package/types/src/Twine2ArchiveHTML/compile.d.ts +6 -0
  52. package/types/src/Twine2ArchiveHTML/parse.d.ts +6 -0
  53. package/types/src/Twine2HTML/compile.d.ts +9 -0
  54. package/types/src/Twine2HTML/parse.d.ts +17 -0
  55. package/types/src/extwee.d.ts +2 -0
  56. package/web-index.js +3 -1
  57. package/test/StoryFormat/StoryFormatParser/example.js +0 -3
  58. package/test/StoryFormat/StoryFormatParser/example2.js +0 -3
  59. package/test/StoryFormat/StoryFormatParser/format.js +0 -1
  60. package/test/StoryFormat/StoryFormatParser/format_doublename.js +0 -1
  61. package/test/StoryFormat/StoryFormatParser/harlowe.js +0 -3
  62. package/test/StoryFormat/StoryFormatParser/missingAuthor.js +0 -1
  63. package/test/StoryFormat/StoryFormatParser/missingDescription.js +0 -1
  64. package/test/StoryFormat/StoryFormatParser/missingImage.js +0 -1
  65. package/test/StoryFormat/StoryFormatParser/missingLicense.js +0 -1
  66. package/test/StoryFormat/StoryFormatParser/missingName.js +0 -1
  67. package/test/StoryFormat/StoryFormatParser/missingProofing.js +0 -1
  68. package/test/StoryFormat/StoryFormatParser/missingSource.js +0 -1
  69. package/test/StoryFormat/StoryFormatParser/missingURL.js +0 -1
  70. package/test/StoryFormat/StoryFormatParser/missingVersion.js +0 -1
  71. package/test/StoryFormat/StoryFormatParser/versionWrong.js +0 -1
  72. package/test/Twine2HTML/Twine2HTMLParser/missingName.html +0 -33
  73. package/test/Twine2HTML/Twine2HTMLParser/missingPID.html +0 -15
  74. 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 { v4 as uuidv4 } from 'uuid';
2
+ import { generate as generateIFID } from './IFID/generate.js';
3
+ import { encode } from 'html-entities';
3
4
 
4
5
  const creatorName = 'extwee';
5
- const version = '2.2.0';
6
-
7
- export default class Story {
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 = 0;
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
- this.#_creatorVersion = version;
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('Ignored passage with same name as existing one!');
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
- * @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.
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.forEachPassage((p) => {
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
- // Is there an IFID?
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
- // Twine 2 uses v4 (random) UUIDs, using only capital letters.
450
- 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.');
451
512
  } else {
452
- // Use existing (non-default) value.
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
- metadata.format = this.format;
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
- 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
+ }
465
534
 
466
535
  /**
467
536
  * zoom: (decimal) Optional. Maps to <tw-storydata zoom>.
468
537
  */
469
- metadata.zoom = this.zoom;
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
- metadata.start = this.start;
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.forEachPassage((passage) => {
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
- // Prepare HTML content.
519
- let storyData = `<tw-storydata name="${this.name}"`;
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
- this.forEachPassage((p) => {
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
- // Set starting passage PID.
552
- storyData += ` startnode="${startPID}"`;
553
-
554
- // Defaults to 'extwee' if missing.
555
- storyData += ` creator="${this.creator}"`;
556
-
557
- // Default to extwee version.
558
- 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
+ }
559
677
 
560
- // Check if IFID exists.
561
- if (this.IFID !== '') {
562
- // Write the existing IFID.
563
- storyData += ` ifid="${this.IFID}"`;
564
- } else {
565
- // Generate a new IFID.
566
- // Twine 2 uses v4 (random) UUIDs, using only capital letters.
567
- 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 ) }"`;
568
693
  }
569
694
 
570
- // Write existing or default value.
571
- 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
+ }
572
701
 
573
- // Write existing or default value.
574
- storyData += ` format="${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
+ }
575
708
 
576
- // Write existing or default value.
577
- 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
+ }
578
722
 
579
723
  // Add the default attributes.
580
724
  storyData += ' options hidden>\n';
581
725
 
582
- // Start the STYLE.
583
- 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'));
584
728
 
585
- // Get stylesheet passages.
586
- const stylesheetPassages = this.getPassagesByTag('stylesheet');
729
+ // Remove stylesheet passages from the main array.
730
+ passages = passages.filter(p => !p.tags.includes('stylesheet'));
587
731
 
588
- // Concatenate passages.
589
- stylesheetPassages.forEach((passage) => {
590
- // Add text of passages.
591
- storyData += passage.text;
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
- // Close the STYLE.
595
- storyData += '</style>\n';
737
+ // Concatenate passages.
738
+ stylesheetPassages.forEach((passage) => {
739
+ // Add text of passages.
740
+ storyData += passage.text;
741
+ });
596
742
 
597
- // Start the SCRIPT.
598
- storyData += '\t<script role="script" id="twine-user-script" type="text/twine-javascript">';
743
+ // Close the STYLE.
744
+ storyData += '</style>\n';
745
+ }
599
746
 
600
- // Get stylesheet passages.
601
- const scriptPassages = this.getPassagesByTag('script');
747
+ // Filter out passages with tag of 'script'.
748
+ const scriptPassages = passages.filter((passage) => passage.tags.includes('script'));
602
749
 
603
- // Concatenate passages.
604
- scriptPassages.forEach((passage) => {
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
- // Close SCRIPT.
610
- 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
+ }
611
767
 
612
768
  // Reset the PID counter.
613
769
  PIDcounter = 1;
614
770
 
615
771
  // Build the passages HTML.
616
- this.forEachPassage((passage) => {
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.forEachPassage((p) => {
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 };