extwee 2.0.6 → 2.2.0

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 (216) hide show
  1. package/.eslintrc.json +25 -25
  2. package/.github/FUNDING.yml +3 -0
  3. package/.github/dependabot.yml +11 -0
  4. package/.github/workflows/nodejs.yml +25 -24
  5. package/.travis.yml +13 -13
  6. package/CODE_OF_CONDUCT.md +82 -82
  7. package/LICENSE +21 -21
  8. package/README.md +173 -36
  9. package/SECURITY.md +12 -12
  10. package/babel.config.json +18 -22
  11. package/build/extwee +0 -0
  12. package/build/extwee.exe +0 -0
  13. package/build/extwee.web.min.js +2 -0
  14. package/build/extwee.web.min.js.LICENSE.txt +1 -0
  15. package/docs/.nojekyll +0 -0
  16. package/docs/README.md +167 -0
  17. package/docs/_sidebar.md +19 -0
  18. package/docs/examples/dynamicPassages.md +28 -0
  19. package/docs/examples/jsonToTwee.md +23 -0
  20. package/docs/examples/twsToTwee.md +25 -0
  21. package/docs/formats/json.md +17 -0
  22. package/docs/formats/twee.md +13 -0
  23. package/docs/formats/twine1HTML.md +13 -0
  24. package/docs/formats/twine2ArchiveHTML.md +13 -0
  25. package/docs/formats/twine2HTML.md +13 -0
  26. package/docs/formats/tws.md +9 -0
  27. package/docs/index.html +26 -0
  28. package/docs/install/binaries.md +9 -0
  29. package/docs/install/npm.md +20 -0
  30. package/docs/install/npx.md +9 -0
  31. package/docs/objects/passage.md +47 -0
  32. package/docs/objects/story.md +70 -0
  33. package/docs/objects/storyformat.md +27 -0
  34. package/index.html +22 -0
  35. package/index.js +29 -31
  36. package/package.json +65 -58
  37. package/src/JSON/parse.js +128 -0
  38. package/src/Passage.js +298 -202
  39. package/src/Story.js +650 -523
  40. package/src/StoryFormat/parse.js +134 -0
  41. package/src/StoryFormat.js +259 -300
  42. package/src/TWS/parse.js +86 -0
  43. package/src/Twee/parse.js +157 -0
  44. package/src/Twine1HTML/compile.js +58 -0
  45. package/src/Twine1HTML/parse.js +134 -0
  46. package/src/Twine2ArchiveHTML/compile.js +36 -0
  47. package/src/Twine2ArchiveHTML/parse.js +49 -0
  48. package/src/Twine2HTML/compile.js +35 -0
  49. package/src/Twine2HTML/parse.js +348 -0
  50. package/src/extwee.js +206 -0
  51. package/test/CLI/CLI.test.js +49 -0
  52. package/test/CLI/files/example.json +1 -0
  53. package/test/CLI/files/example6.twee +22 -0
  54. package/test/{Roundtrip → CLI/files}/harlowe.js +2 -2
  55. package/test/CLI/{input.html → files/input.html} +47 -47
  56. package/test/CLI/files/output/test.twee +0 -0
  57. package/test/CLI/{tweeExample.twee → files/tweeExample.twee} +17 -17
  58. package/test/CLI/files/twine1/LICENSE.txt +32 -0
  59. package/test/CLI/files/twine1/code.js +5 -0
  60. package/test/CLI/files/twine1/engine.js +43 -0
  61. package/test/CLI/files/twine1/header.html +325 -0
  62. package/test/CLI/files/twine1Test.html +371 -0
  63. package/test/CLI/{twineExample.html → files/twineExample.html} +16 -15
  64. package/test/JSON/JSON.Parse.test.js +316 -0
  65. package/test/Passage.test.js +175 -104
  66. package/test/Roundtrip/{Example1.html → Files/Example1.html} +63 -63
  67. package/test/Roundtrip/Files/LICENSE +19 -0
  68. package/test/Roundtrip/Files/example1.twee +10 -0
  69. package/test/Roundtrip/{example2.twee → Files/example2.twee} +27 -18
  70. package/test/Roundtrip/{example4.twee → Files/example4.twee} +27 -27
  71. package/test/{StoryFormatParser → Roundtrip/Files}/harlowe.js +2 -2
  72. package/test/Roundtrip/{round.html → Files/round.html} +6 -4
  73. package/test/Roundtrip/Roundtrip.test.js +54 -0
  74. package/test/Story.test.js +638 -423
  75. package/test/StoryFormat/StoryFormat.Parse.test.js +91 -0
  76. package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example.js +3 -3
  77. package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example2.js +3 -3
  78. package/test/{CLI → StoryFormat/StoryFormatParser}/harlowe.js +2 -2
  79. package/test/StoryFormat.test.js +152 -152
  80. package/test/TWS/Parse.test.js +78 -0
  81. package/test/TWS/TWSParser/Example1.tws +150 -0
  82. package/test/TWS/TWSParser/Example5.tws +414 -0
  83. package/test/TWS/TWSParser/noscale.tws +0 -0
  84. package/test/TWS/TWSParser/nostory.tws +0 -0
  85. package/test/Twee/Twee.Parse.test.js +76 -0
  86. package/test/{TweeParser → Twee/TweeParser}/emptytags.twee +2 -2
  87. package/test/{TweeParser → Twee/TweeParser}/example.twee +32 -32
  88. package/test/{TweeParser → Twee/TweeParser}/missing.twee +19 -19
  89. package/test/{TweeParser → Twee/TweeParser}/multipleScriptPassages.twee +19 -19
  90. package/test/{TweeParser → Twee/TweeParser}/multipleStyleTag.twee +19 -19
  91. package/test/{TweeParser → Twee/TweeParser}/multipletags.twee +10 -10
  92. package/test/{TweeParser → Twee/TweeParser}/noTitle.twee +2 -2
  93. package/test/{TweeParser → Twee/TweeParser}/notes.twee +16 -16
  94. package/test/{TweeParser → Twee/TweeParser}/pasagemetadataerror.twee +2 -2
  95. package/test/{TweeParser → Twee/TweeParser}/scriptPassage.twee +16 -16
  96. package/test/{TweeParser → Twee/TweeParser}/singletag.twee +13 -13
  97. package/test/{TweeParser → Twee/TweeParser}/startMetadata.twee +14 -14
  98. package/test/{TweeParser → Twee/TweeParser}/storydataerror.twee +25 -25
  99. package/test/{TweeParser → Twee/TweeParser}/stylePassage.twee +16 -16
  100. package/test/{Story → Twee/TweeParser}/test.twee +25 -25
  101. package/test/Twine1HTML/Twine1HTML.Compile.test.js +180 -0
  102. package/test/Twine1HTML/Twine1HTML.Parse.test.js +183 -0
  103. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +674 -0
  104. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +43 -0
  105. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +4 -0
  106. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +4 -0
  107. package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +1 -0
  108. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +32 -0
  109. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +4 -0
  110. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +327 -0
  111. package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
  112. package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +6 -0
  113. package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +6 -0
  114. package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +43 -0
  115. package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +372 -0
  116. package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +372 -0
  117. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +35 -0
  118. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +25 -0
  119. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +6 -0
  120. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +3 -0
  121. package/test/Twine2HTML/Twine2HTML.Compile.test.js +224 -0
  122. package/test/Twine2HTML/Twine2HTML.Parse.test.js +172 -0
  123. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/creator.html +4 -4
  124. package/test/{CLI → Twine2HTML/Twine2HTMLCompiler}/example6.twee +15 -15
  125. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/missingStoryTitle.twee +29 -29
  126. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test2.html +10 -8
  127. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test3.html +1 -1
  128. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test4.html +4 -4
  129. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Example1.html +52 -52
  130. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Tags.html +15 -15
  131. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingStartnode.html +15 -15
  132. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingTagColors.html +48 -48
  133. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreator.html +11 -11
  134. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreatorVersion.html +11 -11
  135. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormat.html +11 -11
  136. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormatVersion.html +11 -11
  137. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingIFID.html +11 -11
  138. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingName.html +33 -33
  139. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPID.html +15 -15
  140. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageName.html +15 -15
  141. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageTags.html +15 -15
  142. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPosition.html +15 -15
  143. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingScript.html +14 -14
  144. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingSize.html +35 -35
  145. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStartnode.html +11 -11
  146. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStyle.html +14 -14
  147. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingZoom.html +11 -11
  148. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample.html +23 -23
  149. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample2.html +15 -15
  150. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample3.html +15 -15
  151. package/web-index.js +29 -0
  152. package/webpack.config.js +12 -0
  153. package/bin/extwee.js +0 -47
  154. package/src/FileReader.js +0 -33
  155. package/src/HTMLParser.js +0 -345
  156. package/src/HTMLWriter.js +0 -231
  157. package/src/StoryFormatParser.js +0 -142
  158. package/src/TweeParser.js +0 -161
  159. package/src/TweeWriter.js +0 -121
  160. package/story-formats/chapbook-1.2.0/format.js +0 -1
  161. package/story-formats/chapbook-1.2.0/logo.svg +0 -1
  162. package/story-formats/harlowe-1.2.4/format.js +0 -1
  163. package/story-formats/harlowe-1.2.4/icon.svg +0 -78
  164. package/story-formats/harlowe-2.1.0/format.js +0 -2
  165. package/story-formats/harlowe-2.1.0/icon.svg +0 -78
  166. package/story-formats/harlowe-3.1.0/format.js +0 -3
  167. package/story-formats/harlowe-3.1.0/icon.svg +0 -78
  168. package/story-formats/paperthin-1.0.0/format.js +0 -1
  169. package/story-formats/paperthin-1.0.0/icon.svg +0 -5
  170. package/story-formats/snowman-1.4.0/format.js +0 -1
  171. package/story-formats/snowman-1.4.0/icon.svg +0 -436
  172. package/story-formats/snowman-2.0.2/format.js +0 -1
  173. package/story-formats/snowman-2.0.2/icon.svg +0 -436
  174. package/story-formats/sugarcube-1.0.35/LICENSE +0 -23
  175. package/story-formats/sugarcube-1.0.35/format.js +0 -1
  176. package/story-formats/sugarcube-1.0.35/icon.svg +0 -56
  177. package/story-formats/sugarcube-2.31.1/LICENSE +0 -22
  178. package/story-formats/sugarcube-2.31.1/format.js +0 -1
  179. package/story-formats/sugarcube-2.31.1/icon.svg +0 -56
  180. package/test/CLI/test2.html +0 -45
  181. package/test/CLI.test.js +0 -30
  182. package/test/FileReader/t1.txt +0 -1
  183. package/test/FileReader.test.js +0 -14
  184. package/test/HTMLParser.test.js +0 -177
  185. package/test/HTMLWriter/example6.twee +0 -16
  186. package/test/HTMLWriter.test.js +0 -287
  187. package/test/Roundtrip/example1.twee +0 -21
  188. package/test/Roundtrip.test.js +0 -48
  189. package/test/Story/startmeta.twee +0 -29
  190. package/test/StoryFormatParser.test.js +0 -91
  191. package/test/TweeParser/test.twee +0 -25
  192. package/test/TweeParser.test.js +0 -79
  193. package/test/TweeWriter/test1.twee +0 -18
  194. package/test/TweeWriter/test3.twee +0 -12
  195. package/test/TweeWriter/test4.twee +0 -14
  196. package/test/TweeWriter/test5.twee +0 -20
  197. package/test/TweeWriter/test6.twee +0 -15
  198. package/test/TweeWriter/test7.twee +0 -15
  199. package/test/TweeWriter.test.js +0 -107
  200. /package/test/CLI/{test.twee → files/test.twee} +0 -0
  201. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format.js +0 -0
  202. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format_doublename.js +0 -0
  203. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingAuthor.js +0 -0
  204. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingDescription.js +0 -0
  205. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingImage.js +0 -0
  206. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingLicense.js +0 -0
  207. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingName.js +0 -0
  208. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingProofing.js +0 -0
  209. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingSource.js +0 -0
  210. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingURL.js +0 -0
  211. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingVersion.js +0 -0
  212. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/versionWrong.js +0 -0
  213. /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/TestTags.html +0 -0
  214. /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test11.html +0 -0
  215. /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test6.html +0 -0
  216. /package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/tagColors.html +0 -0
package/src/Story.js CHANGED
@@ -1,523 +1,650 @@
1
- import Passage from './Passage.js';
2
-
3
- const name = 'extwee';
4
- const version = '2.0.6';
5
-
6
- /**
7
- * @class Story
8
- * @module Story
9
- */
10
- export default class Story {
11
- /**
12
- * Internal name of story
13
- *
14
- * @private
15
- */
16
- #_name = '';
17
-
18
- /**
19
- * Internal start
20
- *
21
- * @private
22
- */
23
- #_start = '';
24
-
25
- /**
26
- * Internal IFID
27
- *
28
- * @private
29
- */
30
- #_IFID = '';
31
-
32
- /**
33
- * Internal story format
34
- *
35
- * @private
36
- */
37
- #_format = '';
38
-
39
- /**
40
- * Internal version of story format
41
- */
42
- #_formatVersion = '';
43
-
44
- /**
45
- * Internal zoom level
46
- */
47
- #_zoom = 0;
48
-
49
- /**
50
- * Passages
51
- *
52
- * @private
53
- */
54
- #_passages = [];
55
-
56
- /**
57
- * Creator
58
- *
59
- * @private
60
- */
61
- #_creator = '';
62
-
63
- /**
64
- * CreatorVersion
65
- *
66
- * @private
67
- */
68
- #_creatorVersion = '';
69
-
70
- /**
71
- * Metadata
72
- *
73
- * @private
74
- */
75
- #_metadata = null;
76
-
77
- /**
78
- * Tag Colors
79
- *
80
- * @private
81
- */
82
- #_tagColors = {};
83
-
84
- /**
85
- * Internal PID counter
86
- *
87
- * @private
88
- */
89
- #_PIDCounter = 1;
90
-
91
- /**
92
- * @function Story
93
- * @class
94
- */
95
- constructor () {
96
- // Store the creator and version
97
- this.#_creator = name;
98
- this.#_creatorVersion = version;
99
- // Set metadata to an object
100
- this.#_metadata = {};
101
- }
102
-
103
- /**
104
- * Each story has a name
105
- *
106
- * @public
107
- * @readonly
108
- * @memberof Story
109
- * @returns {string} Name
110
- */
111
- get name () { return this.#_name; }
112
-
113
- /**
114
- * @param {string} a - Replacement story name
115
- */
116
- set name (a) {
117
- if (typeof a === 'string') {
118
- this.#_name = a;
119
- } else {
120
- throw new Error('Story name must be a string');
121
- }
122
- }
123
-
124
- /**
125
- * Tag Colors object (each property is a tag and its color)
126
- *
127
- * @public
128
- * @readonly
129
- * @memberof Story
130
- * @returns {object} tag colors array
131
- */
132
- get tagColors () { return this.#_tagColors; }
133
-
134
- /**
135
- * @param {object} a - Replacement tag colors
136
- */
137
- set tagColors (a) {
138
- if (a instanceof Object) {
139
- this.#_tagColors = a;
140
- } else {
141
- throw new Error('Tag colors must be an object!');
142
- }
143
- }
144
-
145
- /**
146
- * Interactive Fiction ID (IFID) of Story
147
- *
148
- * @public
149
- * @readonly
150
- * @memberof Story
151
- * @returns {string} IFID
152
- */
153
- get IFID () { return this.#_IFID; }
154
-
155
- /**
156
- * @param {string} i - Replacement IFID
157
- */
158
- set IFID (i) {
159
- if (typeof i === 'string') {
160
- this.#_IFID = i;
161
- } else {
162
- throw new Error('IFID must be a String!');
163
- }
164
- }
165
-
166
- /**
167
- * Name of start passage
168
- *
169
- * @public
170
- * @readonly
171
- * @memberof Story
172
- * @returns {string} start
173
- */
174
- get start () { return this.#_start; }
175
-
176
- /**
177
- * @param {string} s - Replacement start
178
- */
179
- set start (s) {
180
- if (typeof s === 'string') {
181
- this.#_start = s;
182
- } else {
183
- throw new Error('start (passage name) must be a String!');
184
- }
185
- }
186
-
187
- /**
188
- * Story format version of Story
189
- *
190
- * @public
191
- * @readonly
192
- * @memberof Story
193
- * @returns {string} story format version
194
- */
195
- get formatVersion () { return this.#_formatVersion; }
196
-
197
- /**
198
- * @param {string} f - Replacement format version
199
- */
200
- set formatVersion (f) {
201
- if (typeof f === 'string') {
202
- this.#_formatVersion = f;
203
- } else {
204
- throw new Error('Story format version must be a String!');
205
- }
206
- }
207
-
208
- /**
209
- * Metadata of Story
210
- *
211
- * @public
212
- * @readonly
213
- * @memberof Story
214
- * @returns {object} metadata of story
215
- */
216
- get metadata () { return this.#_metadata; }
217
-
218
- /**
219
- * @param {object} o - Replacement metadata
220
- */
221
- set metadata (o) {
222
- if (typeof o === 'object') {
223
- this.#_metadata = o;
224
- } else {
225
- throw new Error('Story metadata must be Object!');
226
- }
227
- }
228
-
229
- /**
230
- * Story format of Story
231
- *
232
- * @public
233
- * @readonly
234
- * @memberof Story
235
- * @returns {string} format
236
- */
237
- get format () { return this.#_format; }
238
-
239
- /**
240
- * @param {string} f - Replacement format
241
- */
242
- set format (f) {
243
- if (typeof f === 'string') {
244
- this.#_format = f;
245
- } else {
246
- throw new Error('Story format must be a String!');
247
- }
248
- }
249
-
250
- /**
251
- * Program used to create Story
252
- *
253
- * @public
254
- * @memberof Story
255
- * @returns {string} Creator Program
256
- */
257
- get creator () { return this.#_creator; }
258
-
259
- /**
260
- * @param {string} c - Creator Program of Story
261
- */
262
- set creator (c) {
263
- if (typeof c === 'string') {
264
- this.#_creator = c;
265
- } else {
266
- throw new Error('Creator must be String');
267
- }
268
- }
269
-
270
- /**
271
- * Version used to create Story
272
- *
273
- * @public
274
- * @memberof Story
275
- * @returns {string} Version
276
- */
277
- get creatorVersion () { return this.#_creatorVersion; }
278
-
279
- /**
280
- * @param {string} c - Version of creator program
281
- */
282
- set creatorVersion (c) {
283
- if (typeof c === 'string') {
284
- this.#_creatorVersion = c;
285
- } else {
286
- throw new Error('Creator version must be a string!');
287
- }
288
- }
289
-
290
- /**
291
- * Zoom level
292
- *
293
- * @public
294
- * @memberof Story
295
- * @returns {number} Zoom level
296
- */
297
- get zoom () { return this.#_zoom; }
298
-
299
- /**
300
- * @param {number} n - Replacement zoom level
301
- */
302
- set zoom (n) {
303
- if (typeof n === 'number') {
304
- // Parse float with a fixed length and then force into Number
305
- this.#_zoom = Number(Number.parseFloat(n).toFixed(2));
306
- } else {
307
- throw new Error('Zoom level must be a Number!');
308
- }
309
- }
310
-
311
- /**
312
- * Add a passage to the story
313
- *
314
- * @public
315
- * @function addPassage
316
- * @memberof Story
317
- * @param {Passage} p - Passage to add to Story
318
- */
319
- addPassage (p) {
320
- // Check if passed argument is a Passage
321
- if (p instanceof Passage) {
322
- // Does this passage already exist in the collection?
323
- if (this.getPassageByName(p.name) === null) {
324
- // StoryData is the only passage with special parsing needs.
325
- // All other passages are added to the internal passages array.
326
- if (p.name === 'StoryData') {
327
- // Try to parse JSON
328
- try {
329
- // Attempt to parse storyData JSON
330
- const metadata = JSON.parse(p.text);
331
-
332
- // IFID
333
- if (Object.prototype.hasOwnProperty.call(metadata, 'ifid')) {
334
- this.IFID = metadata.ifid;
335
- }
336
-
337
- // format
338
- if (Object.prototype.hasOwnProperty.call(metadata, 'format')) {
339
- this.format = metadata.format;
340
- }
341
-
342
- // formatVersion
343
- if (Object.prototype.hasOwnProperty.call(metadata, 'formatVersion')) {
344
- this.formatVersion = metadata.formatVersion;
345
- }
346
-
347
- // zoom
348
- if (Object.prototype.hasOwnProperty.call(metadata, 'zoom')) {
349
- this.zoom = metadata.zoom;
350
- }
351
-
352
- // start
353
- if (Object.prototype.hasOwnProperty.call(metadata, 'start')) {
354
- this.start = metadata.start;
355
- }
356
-
357
- // tag colors
358
- if (Object.prototype.hasOwnProperty.call(metadata, 'tag-colors')) {
359
- this.tagColors = metadata['tag-colors'];
360
- }
361
- } catch (event) {
362
- // Ignore errors
363
- }
364
- } else {
365
- // Look for Start
366
- if (p.name === 'Start' && this.start === '') {
367
- // Set Start as starting passage (unless overwritten by start property)
368
- this.start = 'Start';
369
- }
370
-
371
- // Test for default value.
372
- // (This might occur if using Story directly
373
- // outside of using HTMLParser or TweeParser.)
374
- if (p.pid === -1) {
375
- // Set the internal counter.
376
- p.pid = this.#_PIDCounter;
377
- // Update the internal counter.
378
- this.#_PIDCounter++;
379
- }
380
-
381
- // Push the passage to the array
382
- this.#_passages.push(p);
383
- }
384
- } else {
385
- // Warn user
386
- console.warn('Ignored passage with same name as existing one!');
387
- }
388
- } else {
389
- // We can only add passages to array
390
- throw new Error('Can only add Passages to the story!');
391
- }
392
- }
393
-
394
- /**
395
- * Remove a passage from the story by name
396
- *
397
- * @public
398
- * @function removePassageByName
399
- * @memberof Story
400
- * @param {string} name - Passage name to remove
401
- */
402
- removePassageByName (name) {
403
- this.#_passages = this.#_passages.filter(passage => passage.name !== name);
404
- }
405
-
406
- /**
407
- * Find passages by tag
408
- *
409
- * @public
410
- * @function getPassagesByTag
411
- * @memberof Story
412
- * @param {string} t - Passage name to search for
413
- * @returns {Array} Return array of passages
414
- */
415
- getPassagesByTag (t) {
416
- // Look through passages
417
- return this.#_passages.filter((passage) => {
418
- // Look through each passage's tags
419
- return passage.tags.some((tag) => t === tag);
420
- });
421
- }
422
-
423
- /**
424
- * Find passage by name
425
- *
426
- * @public
427
- * @function getPassageByName
428
- * @memberof Story
429
- * @param {string} name - Passage name to search for
430
- * @returns {Passage | null} Return passage or null
431
- */
432
- getPassageByName (name) {
433
- // Look through passages
434
- const results = this.#_passages.find((passage) => passage.name === name);
435
- // Return entry or null, if not found
436
- return results !== undefined ? results : null;
437
- }
438
-
439
- /**
440
- * Find passage by PID
441
- *
442
- * @public
443
- * @function getPassageByPID
444
- * @memberof Story
445
- * @param {number} pid - Passage PID to search for
446
- * @returns {Passage | null} Return passage or null
447
- */
448
- getPassageByPID (pid) {
449
- // Look through passages
450
- const results = this.#_passages.find((passage) => passage.pid === pid);
451
- // Return passages or null
452
- return results !== undefined ? results : null;
453
- }
454
-
455
- /**
456
- * Size (number of passages).
457
- * Does not include StoryAuthor or StoryTitle.
458
- * Does not include passages with 'script' or 'stylesheet' tags.
459
- *
460
- * @public
461
- * @function size
462
- * @memberof Story
463
- * @returns {number} Return number of passages
464
- */
465
- size () {
466
- let count = 0;
467
- this.#_passages.forEach((passage) => {
468
- // Exclude StoryTitle
469
- if (passage.name === 'StoryTitle') {
470
- return;
471
- }
472
-
473
- // Exclude if 'script' tags exists
474
- if (passage.tags.includes('script')) {
475
- return;
476
- }
477
-
478
- // Exclude if 'stylesheet' exists
479
- if (passage.tags.includes('stylesheet')) {
480
- return;
481
- }
482
-
483
- count++;
484
- });
485
- return count;
486
- }
487
-
488
- /**
489
- * forEach-style iterator of passages in Story
490
- *
491
- * @public
492
- * @function forEach
493
- * @memberof Story
494
- * @param {Function} callback - Callback function
495
- */
496
- forEach (callback) {
497
- // Check if argument is a function
498
- if (typeof callback !== 'function') {
499
- // Throw error
500
- throw new Error('Callback must be a function!');
501
- }
502
-
503
- // Use internal forEach
504
- this.#_passages.forEach((element, index) => {
505
- // Exclude StoryTitle
506
- if (element.name === 'StoryTitle') {
507
- return;
508
- }
509
-
510
- // Exclude if 'script' tags exists
511
- if (element.tags.includes('script')) {
512
- return;
513
- }
514
-
515
- // Exclude if 'stylesheet' exists
516
- if (element.tags.includes('stylesheet')) {
517
- return;
518
- }
519
- // Call callback function with element and index
520
- callback(element, index);
521
- });
522
- }
523
- }
1
+ import Passage from './Passage.js';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+
4
+ const creatorName = 'extwee';
5
+ const version = '2.2.0';
6
+
7
+ export default class Story {
8
+ /**
9
+ * Internal name of story
10
+ * @private
11
+ */
12
+ #_name = '';
13
+
14
+ /**
15
+ * Internal start
16
+ * @private
17
+ */
18
+ #_start = '';
19
+
20
+ /**
21
+ * Internal IFID
22
+ * @private
23
+ */
24
+ #_IFID = '';
25
+
26
+ /**
27
+ * Internal story format
28
+ * @private
29
+ */
30
+ #_format = '';
31
+
32
+ /**
33
+ * Internal version of story format
34
+ */
35
+ #_formatVersion = '';
36
+
37
+ /**
38
+ * Internal zoom level
39
+ */
40
+ #_zoom = 0;
41
+
42
+ /**
43
+ * Passages
44
+ * @private
45
+ */
46
+ #_passages = [];
47
+
48
+ /**
49
+ * Creator
50
+ * @private
51
+ */
52
+ #_creator = '';
53
+
54
+ /**
55
+ * CreatorVersion
56
+ * @private
57
+ */
58
+ #_creatorVersion = '';
59
+
60
+ /**
61
+ * Metadata
62
+ * @private
63
+ */
64
+ #_metadata = null;
65
+
66
+ /**
67
+ * Tag Colors
68
+ * @private
69
+ */
70
+ #_tagColors = {};
71
+
72
+ /**
73
+ * Creates a story.
74
+ * @param {string} name - Name of the story.
75
+ */
76
+ constructor (name = '') {
77
+ // Every story has a name.
78
+ this.name = name;
79
+ // Store the creator.
80
+ this.#_creator = creatorName;
81
+ this.#_creatorVersion = version;
82
+ // Set metadata to an object.
83
+ this.#_metadata = {};
84
+ }
85
+
86
+ /**
87
+ * Each story has a name
88
+ * @returns {string} Name
89
+ */
90
+ get name () { return this.#_name; }
91
+
92
+ /**
93
+ * @param {string} a - Replacement story name
94
+ */
95
+ set name (a) {
96
+ if (typeof a === 'string') {
97
+ this.#_name = a;
98
+ } else {
99
+ throw new Error('Story name must be a string');
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Tag Colors object (each property is a tag and its color)
105
+ * @returns {object} tag colors array
106
+ */
107
+ get tagColors () { return this.#_tagColors; }
108
+
109
+ /**
110
+ * @param {object} a - Replacement tag colors
111
+ */
112
+ set tagColors (a) {
113
+ if (a instanceof Object) {
114
+ this.#_tagColors = a;
115
+ } else {
116
+ throw new Error('Tag colors must be an object!');
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Interactive Fiction ID (IFID) of Story
122
+ * @returns {string} IFID
123
+ */
124
+ get IFID () { return this.#_IFID; }
125
+
126
+ /**
127
+ * @param {string} i - Replacement IFID
128
+ */
129
+ set IFID (i) {
130
+ if (typeof i === 'string') {
131
+ this.#_IFID = i;
132
+ } else {
133
+ throw new Error('IFID must be a String!');
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Name of start passage.
139
+ * @returns {string} start
140
+ */
141
+ get start () { return this.#_start; }
142
+
143
+ /**
144
+ * @param {string} s - Replacement start
145
+ */
146
+ set start (s) {
147
+ if (typeof s === 'string') {
148
+ this.#_start = s;
149
+ } else {
150
+ throw new Error('start (passage name) must be a String!');
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Story format version of Story.
156
+ * @returns {string} story format version
157
+ */
158
+ get formatVersion () { return this.#_formatVersion; }
159
+
160
+ /**
161
+ * @param {string} f - Replacement format version
162
+ */
163
+ set formatVersion (f) {
164
+ if (typeof f === 'string') {
165
+ this.#_formatVersion = f;
166
+ } else {
167
+ throw new Error('Story format version must be a String!');
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Metadata of Story.
173
+ * @returns {object} metadata of story
174
+ */
175
+ get metadata () { return this.#_metadata; }
176
+
177
+ /**
178
+ * @param {object} o - Replacement metadata
179
+ */
180
+ set metadata (o) {
181
+ if (typeof o === 'object') {
182
+ this.#_metadata = o;
183
+ } else {
184
+ throw new Error('Story metadata must be Object!');
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Story format of Story.
190
+ * @returns {string} format
191
+ */
192
+ get format () { return this.#_format; }
193
+
194
+ /**
195
+ * @param {string} f - Replacement format
196
+ */
197
+ set format (f) {
198
+ if (typeof f === 'string') {
199
+ this.#_format = f;
200
+ } else {
201
+ throw new Error('Story format must be a String!');
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Program used to create Story.
207
+ * @returns {string} Creator Program
208
+ */
209
+ get creator () { return this.#_creator; }
210
+
211
+ /**
212
+ * @param {string} c - Creator Program of Story
213
+ */
214
+ set creator (c) {
215
+ if (typeof c === 'string') {
216
+ this.#_creator = c;
217
+ } else {
218
+ throw new Error('Creator must be String');
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Version used to create Story.
224
+ * @returns {string} Version
225
+ */
226
+ get creatorVersion () { return this.#_creatorVersion; }
227
+
228
+ /**
229
+ * @param {string} c - Version of creator program
230
+ */
231
+ set creatorVersion (c) {
232
+ if (typeof c === 'string') {
233
+ this.#_creatorVersion = c;
234
+ } else {
235
+ throw new Error('Creator version must be a string!');
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Zoom level.
241
+ * @returns {number} Zoom level
242
+ */
243
+ get zoom () { return this.#_zoom; }
244
+
245
+ /**
246
+ * @param {number} n - Replacement zoom level
247
+ */
248
+ set zoom (n) {
249
+ if (typeof n === 'number') {
250
+ // Parse float with a fixed length and then force into Number
251
+ this.#_zoom = Number(Number.parseFloat(n).toFixed(2));
252
+ } else {
253
+ throw new Error('Zoom level must be a Number!');
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Add a passage to the story.
259
+ * `StoryData` will override story metadata and `StoryTitle` will override story name.
260
+ * @param {Passage} p - Passage to add to Story.
261
+ */
262
+ addPassage (p) {
263
+ // Check if passed argument is a Passage.
264
+ if (!(p instanceof Passage)) {
265
+ // We can only add passages to array.
266
+ throw new Error('Can only add Passages to the story!');
267
+ }
268
+
269
+ // Does this passage already exist in the collection?
270
+ // If it does, we ignore it and return.
271
+ if (this.getPassageByName(p.name) !== null) {
272
+ // Warn user
273
+ console.warn('Ignored passage with same name as existing one!');
274
+ //
275
+ return;
276
+ }
277
+
278
+ // Parse StoryData.
279
+ if (p.name === 'StoryData') {
280
+ // Try to parse JSON.
281
+ try {
282
+ // Attempt to parse storyData JSON.
283
+ const metadata = JSON.parse(p.text);
284
+
285
+ // IFID.
286
+ if (Object.prototype.hasOwnProperty.call(metadata, 'ifid')) {
287
+ this.IFID = metadata.ifid;
288
+ }
289
+
290
+ // Format.
291
+ if (Object.prototype.hasOwnProperty.call(metadata, 'format')) {
292
+ this.format = metadata.format;
293
+ }
294
+
295
+ // formatVersion.
296
+ if (Object.prototype.hasOwnProperty.call(metadata, 'format-version')) {
297
+ this.formatVersion = metadata['format-version'];
298
+ }
299
+
300
+ // Zoom.
301
+ if (Object.prototype.hasOwnProperty.call(metadata, 'zoom')) {
302
+ this.zoom = metadata.zoom;
303
+ }
304
+
305
+ // Start.
306
+ if (Object.prototype.hasOwnProperty.call(metadata, 'start')) {
307
+ this.start = metadata.start;
308
+ }
309
+
310
+ // Tag colors.
311
+ if (Object.prototype.hasOwnProperty.call(metadata, 'tag-colors')) {
312
+ this.tagColors = metadata['tag-colors'];
313
+ }
314
+ } catch (event) {
315
+ // Ignore errors.
316
+ }
317
+
318
+ // Don't add StoryData to passages.
319
+ return;
320
+ }
321
+
322
+ // Parse StoryTitle.
323
+ if (p.name === 'StoryTitle') {
324
+ // If there is a StoryTitle passage, we accept the name.
325
+ // Set internal name based on StoryTitle.
326
+ this.name = p.text;
327
+ // Once we override story.name, return.
328
+ return;
329
+ }
330
+
331
+ // This is not StoryData or StoryTitle.
332
+ // Push the passage to the array.
333
+ this.#_passages.push(p);
334
+ }
335
+
336
+ /**
337
+ * Remove a passage from the story by name.
338
+ * @param {string} name - Passage name to remove
339
+ */
340
+ removePassageByName (name) {
341
+ this.#_passages = this.#_passages.filter(passage => passage.name !== name);
342
+ }
343
+
344
+ /**
345
+ * Find passages by tag.
346
+ * @param {string} t - Passage name to search for
347
+ * @returns {Array} Return array of passages
348
+ */
349
+ getPassagesByTag (t) {
350
+ // Look through passages
351
+ return this.#_passages.filter((passage) => {
352
+ // Look through each passage's tags
353
+ return passage.tags.some((tag) => t === tag);
354
+ });
355
+ }
356
+
357
+ /**
358
+ * Find passage by name.
359
+ * @param {string} name - Passage name to search for
360
+ * @returns {Passage | null} Return passage or null
361
+ */
362
+ getPassageByName (name) {
363
+ // Look through passages
364
+ const results = this.#_passages.find((passage) => passage.name === name);
365
+ // Return entry or null, if not found
366
+ return results !== undefined ? results : null;
367
+ }
368
+
369
+ /**
370
+ * Size (number of passages).
371
+ * @returns {number} Return number of passages
372
+ */
373
+ size () {
374
+ return this.#_passages.length;
375
+ }
376
+
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
+ /**
396
+ * Export Story as JSON representation.
397
+ * @returns {string} JSON string.
398
+ */
399
+ toJSON () {
400
+ // Create an initial object for later serialization.
401
+ const s = {
402
+ name: this.name,
403
+ tagColors: this.tagColors,
404
+ ifid: this.IFID,
405
+ start: this.start,
406
+ formatVersion: this.formatVersion,
407
+ metadata: this.metadata,
408
+ format: this.format,
409
+ creator: this.creator,
410
+ creatorVersion: this.creatorVersion,
411
+ zoom: this.zoom,
412
+ passages: []
413
+ };
414
+
415
+ // For each passage, convert into simple object.
416
+ this.forEachPassage((p) => {
417
+ s.passages.push({
418
+ name: p.name,
419
+ tags: p.tags,
420
+ metadata: p.metadata,
421
+ text: p.text
422
+ });
423
+ });
424
+
425
+ // Return stringified Story object.
426
+ return JSON.stringify(s, null, 4);
427
+ }
428
+
429
+ /**
430
+ * Return Twee representation.
431
+ *
432
+ * See: Twee 3 Specification
433
+ * (https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md)
434
+ * @returns {string} Twee String
435
+ */
436
+ toTwee () {
437
+ // Write the StoryData first.
438
+ let outputContents = ':: StoryData\n';
439
+
440
+ // Create default object.
441
+ const metadata = {};
442
+
443
+ /**
444
+ * ifid: (string) Required. Maps to <tw-storydata ifid>.
445
+ */
446
+ // Is there an IFID?
447
+ if (this.IFID === '') {
448
+ // Generate a new IFID for this work.
449
+ // Twine 2 uses v4 (random) UUIDs, using only capital letters.
450
+ metadata.ifid = uuidv4().toUpperCase();
451
+ } else {
452
+ // Use existing (non-default) value.
453
+ metadata.ifid = this.IFID;
454
+ }
455
+
456
+ /**
457
+ * format: (string) Optional. Maps to <tw-storydata format>.
458
+ */
459
+ metadata.format = this.format;
460
+
461
+ /**
462
+ * format-version: (string) Optional. Maps to <tw-storydata format-version>.
463
+ */
464
+ metadata['format-version'] = this.formatVersion;
465
+
466
+ /**
467
+ * zoom: (decimal) Optional. Maps to <tw-storydata zoom>.
468
+ */
469
+ metadata.zoom = this.zoom;
470
+
471
+ /**
472
+ * start: (string) Optional.
473
+ * Maps to <tw-passagedata name> of the node whose pid matches <tw-storydata startnode>.
474
+ */
475
+ metadata.start = this.start;
476
+
477
+ /**
478
+ * tag-colors: (object of tag(string):color(string) pairs) Optional.
479
+ * Pairs map to <tw-tag> nodes as <tw-tag name>:<tw-tag color>.
480
+ */
481
+ const numberOfColors = Object.keys(this.tagColors).length;
482
+
483
+ // Are there any colors?
484
+ if (numberOfColors > 0) {
485
+ // Add a tag-colors property
486
+ metadata['tag-colors'] = this.tagColors;
487
+ }
488
+
489
+ // Write out the story metadata.
490
+ outputContents += `${JSON.stringify(metadata, undefined, 2)}`;
491
+
492
+ // Add two newlines.
493
+ outputContents += '\n\n';
494
+
495
+ // Write story name as StoryTitle.
496
+ outputContents += ':: StoryTitle\n' + this.name;
497
+
498
+ // Add two newlines.
499
+ outputContents += '\n\n';
500
+
501
+ // For each passage, append it to the output.
502
+ this.forEachPassage((passage) => {
503
+ outputContents += passage.toTwee();
504
+ });
505
+
506
+ // Return the Twee string.
507
+ return outputContents;
508
+ }
509
+
510
+ /**
511
+ * Return Twine 2 HTML.
512
+ *
513
+ * See: Twine 2 HTML Output
514
+ * (https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md)
515
+ * @returns {string} Twine 2 HTML string
516
+ */
517
+ toTwine2HTML () {
518
+ // Prepare HTML content.
519
+ let storyData = `<tw-storydata name="${this.name}"`;
520
+ // Passage Identification (PID) counter.
521
+ // (Twine 2 starts with 1, so we mirror that.)
522
+ let PIDcounter = 1;
523
+
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
+ // Set initial PID value.
538
+ let startPID = 1;
539
+ // We have to do a bit of nonsense here.
540
+ // Twine 2 HTML cares about PID values.
541
+ this.forEachPassage((p) => {
542
+ // Have we found the starting passage?
543
+ if (p.name === this.start) {
544
+ // If so, set the PID based on index.
545
+ startPID = PIDcounter;
546
+ }
547
+ // Increase and keep looking.
548
+ PIDcounter++;
549
+ });
550
+
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}"`;
559
+
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()}"`;
568
+ }
569
+
570
+ // Write existing or default value.
571
+ storyData += ` zoom="${this.zoom}"`;
572
+
573
+ // Write existing or default value.
574
+ storyData += ` format="${this.#_format}"`;
575
+
576
+ // Write existing or default value.
577
+ storyData += ` format-version="${this.#_formatVersion}"`;
578
+
579
+ // Add the default attributes.
580
+ storyData += ' options hidden>\n';
581
+
582
+ // Start the STYLE.
583
+ storyData += '\t<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">';
584
+
585
+ // Get stylesheet passages.
586
+ const stylesheetPassages = this.getPassagesByTag('stylesheet');
587
+
588
+ // Concatenate passages.
589
+ stylesheetPassages.forEach((passage) => {
590
+ // Add text of passages.
591
+ storyData += passage.text;
592
+ });
593
+
594
+ // Close the STYLE.
595
+ storyData += '</style>\n';
596
+
597
+ // Start the SCRIPT.
598
+ storyData += '\t<script role="script" id="twine-user-script" type="text/twine-javascript">';
599
+
600
+ // Get stylesheet passages.
601
+ const scriptPassages = this.getPassagesByTag('script');
602
+
603
+ // Concatenate passages.
604
+ scriptPassages.forEach((passage) => {
605
+ // Add text of passages.
606
+ storyData += passage.text;
607
+ });
608
+
609
+ // Close SCRIPT.
610
+ storyData += '</script>\n';
611
+
612
+ // Reset the PID counter.
613
+ PIDcounter = 1;
614
+
615
+ // Build the passages HTML.
616
+ this.forEachPassage((passage) => {
617
+ // Append each passage element using the PID counter.
618
+ storyData += passage.toTwine2HTML(PIDcounter);
619
+ // Increase counter inside loop.
620
+ PIDcounter++;
621
+ });
622
+
623
+ // Close the HTML element.
624
+ storyData += '</tw-storydata>';
625
+
626
+ // Return HTML contents.
627
+ return storyData;
628
+ }
629
+
630
+ /**
631
+ * Return Twine 1 HTML.
632
+ *
633
+ * See: Twine 1 HTML Output
634
+ * (https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md)
635
+ * @returns {string} Twine 1 HTML string.
636
+ */
637
+ toTwine1HTML () {
638
+ // Begin HTML output.
639
+ let outputContents = '';
640
+
641
+ // Process passages (if any).
642
+ this.forEachPassage((p) => {
643
+ // Output HTML output per passage.
644
+ outputContents += `\t${p.toTwine1HTML()}`;
645
+ });
646
+
647
+ // Return Twine 1 HTML content.
648
+ return outputContents;
649
+ }
650
+ }