extwee 2.0.6 → 2.2.1

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