extwee 2.0.5 → 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 (215) 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 -489
  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/{HTMLParser → CLI/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/Files/example4.twee +27 -0
  71. package/test/{StoryFormatParser → Roundtrip/Files}/harlowe.js +2 -2
  72. package/test/Roundtrip/{round.html → Files/round.html} +6 -7
  73. package/test/Roundtrip/Roundtrip.test.js +54 -0
  74. package/test/Story.test.js +624 -355
  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 -5
  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}/test11.html +1 -3
  127. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test2.html +10 -11
  128. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test3.html +1 -2
  129. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test4.html +4 -5
  130. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test6.html +1 -2
  131. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Example1.html +52 -52
  132. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Tags.html +15 -15
  133. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingStartnode.html +15 -15
  134. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingTagColors.html +48 -48
  135. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreator.html +11 -11
  136. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreatorVersion.html +11 -11
  137. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormat.html +11 -11
  138. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormatVersion.html +11 -11
  139. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingIFID.html +11 -11
  140. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingName.html +33 -33
  141. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPID.html +15 -15
  142. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageName.html +15 -15
  143. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageTags.html +15 -15
  144. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPosition.html +15 -15
  145. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingScript.html +14 -14
  146. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingSize.html +35 -35
  147. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStartnode.html +11 -11
  148. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStyle.html +14 -14
  149. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingZoom.html +11 -11
  150. package/test/Twine2HTML/Twine2HTMLParser/twineExample.html +23 -0
  151. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample2.html +15 -15
  152. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample3.html +15 -15
  153. package/web-index.js +29 -0
  154. package/webpack.config.js +12 -0
  155. package/bin/extwee.js +0 -47
  156. package/src/FileReader.js +0 -33
  157. package/src/HTMLParser.js +0 -343
  158. package/src/HTMLWriter.js +0 -231
  159. package/src/StoryFormatParser.js +0 -142
  160. package/src/TweeParser.js +0 -161
  161. package/src/TweeWriter.js +0 -98
  162. package/story-formats/chapbook-1.2.0/format.js +0 -1
  163. package/story-formats/chapbook-1.2.0/logo.svg +0 -1
  164. package/story-formats/harlowe-1.2.4/format.js +0 -1
  165. package/story-formats/harlowe-1.2.4/icon.svg +0 -78
  166. package/story-formats/harlowe-2.1.0/format.js +0 -2
  167. package/story-formats/harlowe-2.1.0/icon.svg +0 -78
  168. package/story-formats/harlowe-3.1.0/format.js +0 -3
  169. package/story-formats/harlowe-3.1.0/icon.svg +0 -78
  170. package/story-formats/paperthin-1.0.0/format.js +0 -1
  171. package/story-formats/paperthin-1.0.0/icon.svg +0 -5
  172. package/story-formats/snowman-1.4.0/format.js +0 -1
  173. package/story-formats/snowman-1.4.0/icon.svg +0 -436
  174. package/story-formats/snowman-2.0.2/format.js +0 -1
  175. package/story-formats/snowman-2.0.2/icon.svg +0 -436
  176. package/story-formats/sugarcube-1.0.35/LICENSE +0 -23
  177. package/story-formats/sugarcube-1.0.35/format.js +0 -1
  178. package/story-formats/sugarcube-1.0.35/icon.svg +0 -56
  179. package/story-formats/sugarcube-2.31.1/LICENSE +0 -22
  180. package/story-formats/sugarcube-2.31.1/format.js +0 -1
  181. package/story-formats/sugarcube-2.31.1/icon.svg +0 -56
  182. package/test/CLI/test2.html +0 -47
  183. package/test/CLI/twineExample.html +0 -15
  184. package/test/CLI.test.js +0 -30
  185. package/test/FileReader/t1.txt +0 -1
  186. package/test/FileReader.test.js +0 -14
  187. package/test/HTMLParser.test.js +0 -177
  188. package/test/HTMLWriter/example6.twee +0 -16
  189. package/test/HTMLWriter.test.js +0 -289
  190. package/test/Roundtrip/example1.twee +0 -21
  191. package/test/Roundtrip.test.js +0 -48
  192. package/test/Story/startmeta.twee +0 -29
  193. package/test/StoryFormatParser.test.js +0 -91
  194. package/test/TweeParser/test.twee +0 -25
  195. package/test/TweeParser.test.js +0 -79
  196. package/test/TweeWriter/test1.twee +0 -18
  197. package/test/TweeWriter/test3.twee +0 -12
  198. package/test/TweeWriter/test4.twee +0 -14
  199. package/test/TweeWriter/test5.twee +0 -20
  200. package/test/TweeWriter.test.js +0 -85
  201. /package/test/CLI/{test.twee → files/test.twee} +0 -0
  202. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format.js +0 -0
  203. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format_doublename.js +0 -0
  204. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingAuthor.js +0 -0
  205. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingDescription.js +0 -0
  206. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingImage.js +0 -0
  207. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingLicense.js +0 -0
  208. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingName.js +0 -0
  209. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingProofing.js +0 -0
  210. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingSource.js +0 -0
  211. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingURL.js +0 -0
  212. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingVersion.js +0 -0
  213. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/versionWrong.js +0 -0
  214. /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/TestTags.html +0 -0
  215. /package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/tagColors.html +0 -0
package/web-index.js ADDED
@@ -0,0 +1,29 @@
1
+ import { parse as parseTwee } from './src/Twee/parse.js';
2
+ import { parse as parseJSON } from './src/JSON/parse.js';
3
+ import { parse as parseStoryFormat } from './src/StoryFormat/parse.js';
4
+ import { parse as parseTwine1HTML } from './src/Twine1HTML/parse.js';
5
+ import { parse as parseTwine2HTML } from './src/Twine2HTML/parse.js';
6
+ import { parse as parseTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/parse.js';
7
+ import { parse as parseTWS } from './src/TWS/parse.js';
8
+ import { compile as compileTwine1HTML } from './src/Twine1HTML/compile.js';
9
+ import { compile as compileTwine2HTML } from './src/Twine2HTML/compile.js';
10
+ import { compile as compileTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/compile.js';
11
+ import Story from './src/Story.js';
12
+ import Passage from './src/Passage.js';
13
+ import StoryFormat from './src/StoryFormat.js';
14
+
15
+ window.Extwee = {
16
+ parseTwee,
17
+ parseJSON,
18
+ parseTWS,
19
+ parseStoryFormat,
20
+ parseTwine1HTML,
21
+ parseTwine2HTML,
22
+ parseTwine2ArchiveHTML,
23
+ compileTwine1HTML,
24
+ compileTwine2HTML,
25
+ compileTwine2ArchiveHTML,
26
+ Story,
27
+ Passage,
28
+ StoryFormat
29
+ };
@@ -0,0 +1,12 @@
1
+ import path from 'node:path';
2
+
3
+ export default {
4
+ mode: 'production',
5
+ entry: {
6
+ 'extwee.web.min': './web-index.js'
7
+ },
8
+ output: {
9
+ path: path.resolve('./', 'build'),
10
+ filename: '[name].js',
11
+ },
12
+ };
package/bin/extwee.js DELETED
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * @fileoverview CLI for Extwee
5
- * @author Dan Cox
6
- */
7
-
8
- // Import everything
9
- import Extwee from '../index.js';
10
- // Import Commander
11
- import { Command } from 'commander';
12
-
13
- // Create a new Command
14
- const program = new Command();
15
-
16
- program
17
- .name('extwee')
18
- .version('2.0.5')
19
- .option('-c', 'From Twee into HTML')
20
- .option('-d', 'From HTML into Twee')
21
- .option('-s <storyformat>', 'Path to storyformat')
22
- .option('-i <inputFile>', 'Path to input file')
23
- .option('-o <outputFile>', 'Path to output file');
24
-
25
- // Parse the passed arguments
26
- program.parse(process.argv);
27
-
28
- // Create object of passed arguments parsed by Commander
29
- const options = program.opts();
30
-
31
- // Decompile branch
32
- if(options.d === true) {
33
- const inputHTML = Extwee.readFile(options.i);
34
- const storyObject = Extwee.parseHTML(inputHTML);
35
- Extwee.writeTwee(storyObject, options.o);
36
- process.exit();
37
- }
38
-
39
- // Compile branch
40
- if(options.c === true) {
41
- const inputTwee = Extwee.readFile(options.i);
42
- const story = Extwee.parseTwee(inputTwee);
43
- const inputStoryFormat = Extwee.readFile(options.s);
44
- const parsedStoryFormat = Extwee.parseStoryFormat(inputStoryFormat);
45
- Extwee.writeHTML(options.o, story, parsedStoryFormat);
46
- process.exit();
47
- }
package/src/FileReader.js DELETED
@@ -1,33 +0,0 @@
1
- import fs from 'fs';
2
- /**
3
- * @class FileReader
4
- * @module FileReader
5
- */
6
- export default class FileReader {
7
- /**
8
- * Read a text file
9
- *
10
- * @static
11
- * @public
12
- * @function readFile
13
- * @memberof FileReader
14
- * @param {string} file - Path of file to read
15
- * @returns {string} Content of file
16
- */
17
- static read (file) {
18
- // Setup default value
19
- let contents = '';
20
-
21
- // Attempt to find the file
22
- if (fs.existsSync(file)) {
23
- // The file exists.
24
- contents = fs.readFileSync(file, 'utf8');
25
- } else {
26
- // Throw error if file does not exist
27
- throw new Error('Error: File not found!');
28
- }
29
-
30
- // Return default or updated values
31
- return contents;
32
- }
33
- }
package/src/HTMLParser.js DELETED
@@ -1,343 +0,0 @@
1
- /**
2
- * @external HTML
3
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML|HTML}
4
- */
5
-
6
- import { parse as HtmlParser } from 'node-html-parser';
7
- import Story from './Story.js';
8
- import Passage from './Passage.js';
9
- /**
10
- * @class HTMLParser
11
- * @module HTMLParser
12
- */
13
- export default class HTMLParser {
14
- /**
15
- * Parse HTML text into a JS DOM-like object
16
- *
17
- * @public
18
- * @static
19
- * @function parse
20
- * @param {string} content - Content to parse
21
- * @returns {Story} story
22
- */
23
- static parse (content) {
24
- let story = null;
25
- let startNode = null;
26
-
27
- // Send to node-html-parser
28
- // Enable getting the content of 'script', 'style', and 'pre' elements
29
- // Get back a DOM
30
- const dom = new HtmlParser(
31
- content,
32
- {
33
- lowerCaseTagName: false,
34
- script: true,
35
- style: true,
36
- pre: true
37
- });
38
-
39
- // Pull out the tw-storydata element
40
- const storyData = dom.querySelector('tw-storydata');
41
-
42
- // Does the <tw-storydata> element exist?
43
- if (storyData !== null) {
44
- // Create a Story.
45
- story = new Story();
46
-
47
- /**
48
- * name: (string) Required.
49
- * The name of the story.
50
- */
51
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'name')) {
52
- // Create StoryTitle passage based on name
53
- story.addPassage(new Passage('StoryTitle', storyData.attributes.name));
54
- } else {
55
- // Name is a required filed. Warn user.
56
- console.warn('Twine 2 HTML must have a name!');
57
- // Set a default name
58
- story.addPassage(new Passage('StoryTitle', 'Untitled'));
59
- }
60
-
61
- /**
62
- * ifid: (string) Required.
63
- * An IFID is a sequence of between 8 and 63 characters,
64
- * each of which shall be a digit, a capital letter or a
65
- * hyphen that uniquely identify a story (see Treaty of Babel).
66
- */
67
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'ifid')) {
68
- // Update story IFID
69
- story.IFID = storyData.attributes.ifid;
70
- } else {
71
- // Name is a required filed. Warn user.
72
- console.warn('Twine 2 HTML must have an IFID!');
73
- }
74
-
75
- /**
76
- * creator: (string) Optional.
77
- * The name of program used to create the file.
78
- */
79
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator')) {
80
- // Update story creator
81
- story.creator = storyData.attributes.creator;
82
- }
83
-
84
- /**
85
- * creator-version: (string) Optional.
86
- * The version of the program used to create the file.
87
- */
88
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'creator-version')) {
89
- // Update story creator version
90
- story.creatorVersion = storyData.attributes['creator-version'];
91
- }
92
-
93
- /**
94
- * format: (string) Optional.
95
- * The story format used to create the story.
96
- */
97
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format')) {
98
- // Update story format
99
- story.format = storyData.attributes.format;
100
- }
101
-
102
- /**
103
- * format-version: (string) Optional.
104
- * The version of the story format used to create the story.
105
- */
106
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'format-version')) {
107
- // Update story format version
108
- story.formatVersion = storyData.attributes['format-version'];
109
- }
110
-
111
- /**
112
- * zoom: (string) Optional.
113
- * The decimal level of zoom (i.e. 1.0 is 100% and 1.2 would be 120% zoom level).
114
- */
115
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'zoom')) {
116
- // Update story zoom
117
- story.zoom = Number(Number.parseFloat(storyData.attributes.zoom).toFixed(2));
118
- }
119
-
120
- /**
121
- * startnode: (string) Optional.
122
- * The PID matching a <tw-passagedata> element whose content should be displayed first.
123
- */
124
- if (Object.prototype.hasOwnProperty.call(storyData.attributes, 'startnode')) {
125
- // Take string value and convert to Int
126
- startNode = Number.parseInt(storyData.attributes.startnode, 10);
127
- }
128
- } else {
129
- // If there is not a <tw-storydata> element, this is not a Twine 2 story!
130
- throw new Error('Not a Twine 2-style file!');
131
- }
132
-
133
- // Pull out the tw-passagedata elements
134
- const storyPassages = dom.querySelectorAll('tw-passagedata');
135
-
136
- // Move through the passages
137
- for (const passage in storyPassages) {
138
- // Get the passage attributes
139
- const attr = storyPassages[passage].attributes;
140
- // Get the passage text
141
- const text = storyPassages[passage].rawText;
142
-
143
- // Set a default position.
144
- let position = null;
145
- // Does position exist?
146
- if (Object.prototype.hasOwnProperty.call(attr, 'position')) {
147
- // Update position.
148
- position = attr.position;
149
- }
150
-
151
- // Set a default size.
152
- let size = null;
153
- // Does size exist?
154
- if (Object.prototype.hasOwnProperty.call(attr, 'size')) {
155
- // Update size.
156
- size = attr.size;
157
- }
158
-
159
- /**
160
- * name: (string) Required.
161
- * The name of the passage.
162
- *
163
- * https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md#passages
164
- */
165
- // Create a default value
166
- let name = null;
167
- // Does name exist?
168
- if (Object.prototype.hasOwnProperty.call(attr, 'name')) {
169
- // Escape the name
170
- name = HTMLParser.escapeMetacharacters(attr.name);
171
- } else {
172
- console.warn('Encountered passage without a name! Will not add.');
173
- }
174
-
175
- // Create empty tag array.
176
- let tags = [];
177
- // Does the tags attribute exist?
178
- if (Object.prototype.hasOwnProperty.call(attr, 'tags')) {
179
- // Escape any tags
180
- // (Attributes can, themselves, be empty strings.)
181
- if (attr.tags.length > 0 && attr.tags !== '""') {
182
- // Escape the tags
183
- tags = HTMLParser.escapeMetacharacters(attr.tags);
184
- // Split by spaces into an array
185
- tags = tags.split(' ');
186
- }
187
-
188
- // Remove any empty strings.
189
- tags = tags.filter(tag => tag !== '');
190
- }
191
-
192
- // Create metadata for passage.
193
- const metadata = {};
194
-
195
- // Does position exist?
196
- if (position !== null) {
197
- // Add the property to metadata
198
- metadata.position = position;
199
- }
200
-
201
- // Does size exist?
202
- if (size !== null) {
203
- // Add the property to metadata
204
- metadata.size = size;
205
- }
206
-
207
- /**
208
- * pid: (string) Required.
209
- * The Passage ID (PID).
210
- * (Note: This is subject to change during editing with Twine 2.)
211
- *
212
- * https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md#passages
213
- */
214
- // Create a default PID
215
- let pid = -1;
216
- // Does pid exist?
217
- if (Object.prototype.hasOwnProperty.call(attr, 'pid')) {
218
- // Parse string into int
219
- // Update PID
220
- pid = Number.parseInt(attr.pid, 10);
221
- } else {
222
- console.warn('Passages are required to have PID. Will not add!');
223
- }
224
-
225
- // If passage is missing name and PID (required attributes),
226
- // they are not added.
227
- if (name !== null && pid !== -1) {
228
- // Add a new Passage into an array
229
- story.addPassage(
230
- new Passage(
231
- name,
232
- text,
233
- tags,
234
- metadata,
235
- pid
236
- )
237
- );
238
- }
239
- }
240
-
241
- // Look for the style element
242
- const styleElement = dom.querySelector('#twine-user-stylesheet');
243
-
244
- // Does the style element exist?
245
- if (styleElement !== null) {
246
- // Check if there is any content.
247
- if (styleElement.rawText.length > 0) {
248
- // Update stylesheet passage
249
- story.addPassage(new Passage(
250
- 'UserStylesheet',
251
- styleElement.rawText,
252
- ['stylesheet'])
253
- );
254
- }
255
- }
256
-
257
- // Look for the script element
258
- const scriptElement = dom.querySelector('#twine-user-script');
259
-
260
- // Does the script element exist?
261
- if (scriptElement !== null) {
262
- // Check if there is any content.
263
- if (scriptElement.rawText.length > 0) {
264
- story.addPassage(new Passage(
265
- 'UserScript',
266
- scriptElement.rawText,
267
- ['script'])
268
- );
269
- }
270
- }
271
-
272
- // Was there a startNode?
273
- if (startNode !== null) {
274
- // Try to find starting passage by PID.
275
- const startingPassage = story.getPassageByPID(startNode);
276
- // Does the passage exist (yet)?
277
- if (startingPassage !== null) {
278
- // If so, update property to name of passage.
279
- story.start = startingPassage.name;
280
- } else {
281
- throw new Error('Invalid startnode detected in <tw-storydata>!');
282
- }
283
- }
284
-
285
- // Look for all <tw-tag> elements
286
- const twTags = dom.querySelectorAll('tw-tag');
287
-
288
- // Parse through the entries
289
- twTags.forEach((tags) => {
290
- // Parse each tag element
291
- const attributes = tags.attributes;
292
-
293
- // Create default value for name
294
- let name = '';
295
-
296
- // Create default value for color
297
- let color = '';
298
-
299
- // Check for name
300
- if (Object.prototype.hasOwnProperty.call(attributes, 'name')) {
301
- name = attributes.name;
302
- }
303
-
304
- // Check for color
305
- if (Object.prototype.hasOwnProperty.call(attributes, 'color')) {
306
- color = attributes.color;
307
- }
308
-
309
- // If both are not empty strings, use them.
310
- if (name !== '' && color !== '') {
311
- // Add name and color to the object
312
- story.tagColors[name] = color;
313
- }
314
- });
315
-
316
- // Return the parsed story
317
- return story;
318
- }
319
-
320
- /**
321
- * Try to escape meta-characters
322
- *
323
- * @public
324
- * @static
325
- * @function escapeMetacharacters
326
- * @param {string} result - Text to parse
327
- * @returns {string} Escaped characters
328
- */
329
- static escapeMetacharacters (result) {
330
- // Replace any single backslash with two of them
331
- result = result.replace(/\\/g, '\\');
332
- // Double-escape escaped {
333
- result = result.replace(/\\\{/g, '\\\\{');
334
- // Double-escape escaped }
335
- result = result.replace(/\\\}/g, '\\\\}');
336
- // Double-escape escaped [
337
- result = result.replace(/\\\[/g, '\\\\[');
338
- // Double-escape escaped ]
339
- result = result.replace(/\\\]/g, '\\\\]');
340
-
341
- return result;
342
- }
343
- }
package/src/HTMLWriter.js DELETED
@@ -1,231 +0,0 @@
1
- /**
2
- * @external Story
3
- * @see Story.js
4
- * @external StoryFormat
5
- * @see StoryFormat.js
6
- */
7
-
8
- import fs from 'fs';
9
- import Story from './Story.js';
10
- import StoryFormat from './StoryFormat.js';
11
- import { v4 as uuidv4 } from 'uuid';
12
-
13
- /**
14
- * @class HTMLWriter
15
- * @module HTMLWriter
16
- */
17
- export default class HTMLWriter {
18
- /**
19
- * Write story to file using story format and adding any CSS and JS
20
- *
21
- * @public
22
- * @static
23
- * @function writeFile
24
- * @param {string} file - File to write
25
- * @param {Story} story - Story object to write
26
- * @param {StoryFormat} storyFormat - StoryFormat to write
27
- */
28
- static write (file, story, storyFormat) {
29
- if (!(story instanceof Story)) {
30
- throw new Error('Error: story must be a Story object!');
31
- }
32
-
33
- if (!(storyFormat instanceof StoryFormat)) {
34
- throw new Error('storyFormat must be a StoryFormat object!');
35
- }
36
-
37
- let outputContents = '';
38
- let storyData = '';
39
-
40
- // Look for StoryTitle
41
- const storyTitle = story.getPassageByName('StoryTitle');
42
-
43
- // Does the passage exist?
44
- if (storyTitle != null) {
45
- // Always overwrite any existing name with StoryTitle (per spec)
46
- story.name = storyTitle.text;
47
-
48
- // Use story.name for name.
49
- storyData += `<tw-storydata name="${story.name}"`;
50
- } else {
51
- throw new Error("'name' is required attribute. (Add StoryTitle to story.)");
52
- }
53
-
54
- // Does start exist?
55
- if (story.start !== '') {
56
- // Try to get starting passage
57
- const startingPassage = story.getPassageByName(story.start);
58
- // Does it exist currently?
59
- if (startingPassage !== null) {
60
- // Add the starting passage
61
- storyData += ` startnode="${startingPassage.pid}"`;
62
- } else {
63
- // Throw error if no starting passage exists
64
- throw new Error('Starting passage not found');
65
- }
66
- } else {
67
- // Throw error if no starting passage exists
68
- throw new Error('No starting passage found!');
69
- }
70
-
71
- // Defaults to 'extwee' if missing.
72
- storyData += ` creator="${story.creator}"`;
73
-
74
- // Default to extwee version.
75
- storyData += ` creator-version="${story.creatorVersion}"`;
76
-
77
- // Check if IFID exists.
78
- if (story.IFID !== '') {
79
- // Write the existing IFID
80
- storyData += ` ifid="${story.IFID}"`;
81
- } else {
82
- // Generate a new IFID
83
- // Twine 2 uses v4 (random) UUIDs, using only capital letters
84
- storyData += ` ifid="${uuidv4().toUpperCase()}"`;
85
- }
86
-
87
- // Write existing or default value.
88
- storyData += ` zoom="${story.zoom}"`;
89
-
90
- // Write existing or default value.
91
- storyData += ` format="${storyFormat.name}"`;
92
-
93
- // Write existing or default value.
94
- storyData += ` format-version="${storyFormat.version}"`;
95
-
96
- // Add the default.
97
- storyData += ' options hidden>\n';
98
-
99
- // Start the STYLE.
100
- storyData += '\t<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">';
101
-
102
- // Get stylesheet passages
103
- const stylesheetPassages = story.getPassagesByTag('stylesheet');
104
-
105
- // Concatenate passages
106
- stylesheetPassages.forEach((passage) => {
107
- // Add text of passages
108
- storyData += passage.text;
109
- // Remove from story
110
- story.removePassage(passage);
111
- });
112
-
113
- // Close the STYLE
114
- storyData += '</style>\n';
115
-
116
- // Start the SCRIPT
117
- storyData += '\t<script role="script" id="twine-user-script" type="text/twine-javascript">';
118
-
119
- // Get stylesheet passages
120
- const scriptPassages = story.getPassagesByTag('script');
121
-
122
- // Concatenate passages
123
- scriptPassages.forEach((passage) => {
124
- // Add text of passages
125
- storyData += passage.text;
126
- // Remove from story
127
- story.removePassage(passage);
128
- });
129
-
130
- // Close SCRIPT
131
- storyData += '</script>\n';
132
-
133
- // Build the passages
134
- story.forEach((passage) => {
135
- // Start the passage element
136
- storyData += '\t<tw-passagedata';
137
-
138
- /**
139
- * pid: (string) Required.
140
- * The Passage ID (PID).
141
- */
142
- storyData += ` pid="${passage.pid}"`;
143
-
144
- /**
145
- * name: (string) Required.
146
- * The name of the passage.
147
- */
148
- storyData += ` name="${passage.name}"`;
149
-
150
- /**
151
- * tags: (string) Optional.
152
- * Any tags for the passage separated by spaces.
153
- */
154
- if (passage.tags.length > 1) {
155
- storyData += ` tags="${passage.tags.join(' ')}" `;
156
- } else if (passage.tags.length === 1) {
157
- storyData += ` tags="${passage.tags[0]}" `;
158
- }
159
-
160
- /**
161
- * position: (string) Optional.
162
- * Comma-separated X and Y position of the upper-left of the passage
163
- * when viewed within the Twine 2 editor.
164
- */
165
- if (Object.prototype.hasOwnProperty.call(passage.metadata, 'position')) {
166
- storyData += ` position="${passage.metadata.position}" `;
167
- }
168
-
169
- /**
170
- * size: (string) Optional.
171
- * Comma-separated width and height of the passage
172
- * when viewed within the Twine 2 editor.
173
- */
174
- if (Object.prototype.hasOwnProperty.call(passage.metadata, 'size')) {
175
- storyData += `size="${passage.metadata.size}" `;
176
- }
177
-
178
- storyData += `>${HTMLWriter.escape(passage.text)}</tw-passagedata>\n`;
179
- });
180
-
181
- storyData += '</tw-storydata>';
182
-
183
- // Replace the story name in the source file
184
- storyFormat.source = storyFormat.source.replaceAll(/{{STORY_NAME}}/gm, story.name);
185
-
186
- // Replace the story data
187
- storyFormat.source = storyFormat.source.replaceAll(/{{STORY_DATA}}/gm, storyData);
188
-
189
- // Combine everything together.
190
- outputContents += storyFormat.source;
191
-
192
- try {
193
- // Try to write.
194
- fs.writeFileSync(file, outputContents);
195
- } catch (event) {
196
- // Throw error
197
- throw new Error('Error: Cannot write HTML file!');
198
- }
199
- }
200
-
201
- /**
202
- * Escape HTML characters
203
- *
204
- * @public
205
- * @static
206
- * @function escape
207
- * @param {string} text - Text to escape
208
- * @returns {string} Escaped text
209
- */
210
- static escape (text) {
211
- // Throw error if text is not a string
212
- if (Object.prototype.toString.call(text) !== '[object String]') {
213
- throw new Error('Text argument is not a String');
214
- }
215
-
216
- const rules = [
217
- ['&', '&amp;'],
218
- ['<', '&lt;'],
219
- ['>', '&gt;'],
220
- ['"', '&quot;'],
221
- ["'", '&#x27;'],
222
- ['`', '&#x60;']
223
- ];
224
-
225
- rules.forEach(([rule, template]) => {
226
- text = text.replaceAll(rule, template);
227
- });
228
-
229
- return text;
230
- }
231
- }