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.
- package/.eslintrc.json +25 -25
- package/.github/FUNDING.yml +3 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/nodejs.yml +25 -24
- package/.travis.yml +13 -13
- package/CODE_OF_CONDUCT.md +82 -82
- package/LICENSE +21 -21
- package/README.md +173 -36
- package/SECURITY.md +12 -12
- package/babel.config.json +18 -22
- package/build/extwee +0 -0
- package/build/extwee.exe +0 -0
- package/build/extwee.web.min.js +2 -0
- package/build/extwee.web.min.js.LICENSE.txt +1 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +167 -0
- package/docs/_sidebar.md +19 -0
- package/docs/examples/dynamicPassages.md +28 -0
- package/docs/examples/jsonToTwee.md +23 -0
- package/docs/examples/twsToTwee.md +25 -0
- package/docs/formats/json.md +17 -0
- package/docs/formats/twee.md +13 -0
- package/docs/formats/twine1HTML.md +13 -0
- package/docs/formats/twine2ArchiveHTML.md +13 -0
- package/docs/formats/twine2HTML.md +13 -0
- package/docs/formats/tws.md +9 -0
- package/docs/index.html +26 -0
- package/docs/install/binaries.md +9 -0
- package/docs/install/npm.md +20 -0
- package/docs/install/npx.md +9 -0
- package/docs/objects/passage.md +47 -0
- package/docs/objects/story.md +70 -0
- package/docs/objects/storyformat.md +27 -0
- package/index.html +22 -0
- package/index.js +29 -31
- package/package.json +65 -58
- package/src/JSON/parse.js +128 -0
- package/src/Passage.js +298 -202
- package/src/Story.js +650 -523
- package/src/StoryFormat/parse.js +134 -0
- package/src/StoryFormat.js +259 -300
- package/src/TWS/parse.js +86 -0
- package/src/Twee/parse.js +157 -0
- package/src/Twine1HTML/compile.js +58 -0
- package/src/Twine1HTML/parse.js +134 -0
- package/src/Twine2ArchiveHTML/compile.js +36 -0
- package/src/Twine2ArchiveHTML/parse.js +49 -0
- package/src/Twine2HTML/compile.js +35 -0
- package/src/Twine2HTML/parse.js +348 -0
- package/src/extwee.js +206 -0
- package/test/CLI/CLI.test.js +49 -0
- package/test/CLI/files/example.json +1 -0
- package/test/CLI/files/example6.twee +22 -0
- package/test/{Roundtrip → CLI/files}/harlowe.js +2 -2
- package/test/CLI/{input.html → files/input.html} +47 -47
- package/test/CLI/files/output/test.twee +0 -0
- package/test/CLI/{tweeExample.twee → files/tweeExample.twee} +17 -17
- package/test/CLI/files/twine1/LICENSE.txt +32 -0
- package/test/CLI/files/twine1/code.js +5 -0
- package/test/CLI/files/twine1/engine.js +43 -0
- package/test/CLI/files/twine1/header.html +325 -0
- package/test/CLI/files/twine1Test.html +371 -0
- package/test/CLI/{twineExample.html → files/twineExample.html} +16 -15
- package/test/JSON/JSON.Parse.test.js +316 -0
- package/test/Passage.test.js +175 -104
- package/test/Roundtrip/{Example1.html → Files/Example1.html} +63 -63
- package/test/Roundtrip/Files/LICENSE +19 -0
- package/test/Roundtrip/Files/example1.twee +10 -0
- package/test/Roundtrip/{example2.twee → Files/example2.twee} +27 -18
- package/test/Roundtrip/{example4.twee → Files/example4.twee} +27 -27
- package/test/{StoryFormatParser → Roundtrip/Files}/harlowe.js +2 -2
- package/test/Roundtrip/{round.html → Files/round.html} +6 -4
- package/test/Roundtrip/Roundtrip.test.js +54 -0
- package/test/Story.test.js +638 -423
- package/test/StoryFormat/StoryFormat.Parse.test.js +91 -0
- package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example.js +3 -3
- package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example2.js +3 -3
- package/test/{CLI → StoryFormat/StoryFormatParser}/harlowe.js +2 -2
- package/test/StoryFormat.test.js +152 -152
- package/test/TWS/Parse.test.js +78 -0
- package/test/TWS/TWSParser/Example1.tws +150 -0
- package/test/TWS/TWSParser/Example5.tws +414 -0
- package/test/TWS/TWSParser/noscale.tws +0 -0
- package/test/TWS/TWSParser/nostory.tws +0 -0
- package/test/Twee/Twee.Parse.test.js +76 -0
- package/test/{TweeParser → Twee/TweeParser}/emptytags.twee +2 -2
- package/test/{TweeParser → Twee/TweeParser}/example.twee +32 -32
- package/test/{TweeParser → Twee/TweeParser}/missing.twee +19 -19
- package/test/{TweeParser → Twee/TweeParser}/multipleScriptPassages.twee +19 -19
- package/test/{TweeParser → Twee/TweeParser}/multipleStyleTag.twee +19 -19
- package/test/{TweeParser → Twee/TweeParser}/multipletags.twee +10 -10
- package/test/{TweeParser → Twee/TweeParser}/noTitle.twee +2 -2
- package/test/{TweeParser → Twee/TweeParser}/notes.twee +16 -16
- package/test/{TweeParser → Twee/TweeParser}/pasagemetadataerror.twee +2 -2
- package/test/{TweeParser → Twee/TweeParser}/scriptPassage.twee +16 -16
- package/test/{TweeParser → Twee/TweeParser}/singletag.twee +13 -13
- package/test/{TweeParser → Twee/TweeParser}/startMetadata.twee +14 -14
- package/test/{TweeParser → Twee/TweeParser}/storydataerror.twee +25 -25
- package/test/{TweeParser → Twee/TweeParser}/stylePassage.twee +16 -16
- package/test/{Story → Twee/TweeParser}/test.twee +25 -25
- package/test/Twine1HTML/Twine1HTML.Compile.test.js +180 -0
- package/test/Twine1HTML/Twine1HTML.Parse.test.js +183 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +674 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +43 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +4 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +4 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +1 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +32 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +4 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +327 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +6 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +6 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +43 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +372 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +372 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +35 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +25 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +6 -0
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +3 -0
- package/test/Twine2HTML/Twine2HTML.Compile.test.js +224 -0
- package/test/Twine2HTML/Twine2HTML.Parse.test.js +172 -0
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/creator.html +4 -4
- package/test/{CLI → Twine2HTML/Twine2HTMLCompiler}/example6.twee +15 -15
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/missingStoryTitle.twee +29 -29
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test2.html +10 -8
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test3.html +1 -1
- package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test4.html +4 -4
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Example1.html +52 -52
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Tags.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingStartnode.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingTagColors.html +48 -48
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreator.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreatorVersion.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormat.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormatVersion.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingIFID.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingName.html +33 -33
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPID.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageName.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageTags.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPosition.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingScript.html +14 -14
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingSize.html +35 -35
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStartnode.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStyle.html +14 -14
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingZoom.html +11 -11
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample.html +23 -23
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample2.html +15 -15
- package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample3.html +15 -15
- package/web-index.js +29 -0
- package/webpack.config.js +12 -0
- package/bin/extwee.js +0 -47
- package/src/FileReader.js +0 -33
- package/src/HTMLParser.js +0 -345
- package/src/HTMLWriter.js +0 -231
- package/src/StoryFormatParser.js +0 -142
- package/src/TweeParser.js +0 -161
- package/src/TweeWriter.js +0 -121
- package/story-formats/chapbook-1.2.0/format.js +0 -1
- package/story-formats/chapbook-1.2.0/logo.svg +0 -1
- package/story-formats/harlowe-1.2.4/format.js +0 -1
- package/story-formats/harlowe-1.2.4/icon.svg +0 -78
- package/story-formats/harlowe-2.1.0/format.js +0 -2
- package/story-formats/harlowe-2.1.0/icon.svg +0 -78
- package/story-formats/harlowe-3.1.0/format.js +0 -3
- package/story-formats/harlowe-3.1.0/icon.svg +0 -78
- package/story-formats/paperthin-1.0.0/format.js +0 -1
- package/story-formats/paperthin-1.0.0/icon.svg +0 -5
- package/story-formats/snowman-1.4.0/format.js +0 -1
- package/story-formats/snowman-1.4.0/icon.svg +0 -436
- package/story-formats/snowman-2.0.2/format.js +0 -1
- package/story-formats/snowman-2.0.2/icon.svg +0 -436
- package/story-formats/sugarcube-1.0.35/LICENSE +0 -23
- package/story-formats/sugarcube-1.0.35/format.js +0 -1
- package/story-formats/sugarcube-1.0.35/icon.svg +0 -56
- package/story-formats/sugarcube-2.31.1/LICENSE +0 -22
- package/story-formats/sugarcube-2.31.1/format.js +0 -1
- package/story-formats/sugarcube-2.31.1/icon.svg +0 -56
- package/test/CLI/test2.html +0 -45
- package/test/CLI.test.js +0 -30
- package/test/FileReader/t1.txt +0 -1
- package/test/FileReader.test.js +0 -14
- package/test/HTMLParser.test.js +0 -177
- package/test/HTMLWriter/example6.twee +0 -16
- package/test/HTMLWriter.test.js +0 -287
- package/test/Roundtrip/example1.twee +0 -21
- package/test/Roundtrip.test.js +0 -48
- package/test/Story/startmeta.twee +0 -29
- package/test/StoryFormatParser.test.js +0 -91
- package/test/TweeParser/test.twee +0 -25
- package/test/TweeParser.test.js +0 -79
- package/test/TweeWriter/test1.twee +0 -18
- package/test/TweeWriter/test3.twee +0 -12
- package/test/TweeWriter/test4.twee +0 -14
- package/test/TweeWriter/test5.twee +0 -20
- package/test/TweeWriter/test6.twee +0 -15
- package/test/TweeWriter/test7.twee +0 -15
- package/test/TweeWriter.test.js +0 -107
- /package/test/CLI/{test.twee → files/test.twee} +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format_doublename.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingAuthor.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingDescription.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingImage.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingLicense.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingName.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingProofing.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingSource.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingURL.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingVersion.js +0 -0
- /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/versionWrong.js +0 -0
- /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/TestTags.html +0 -0
- /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test11.html +0 -0
- /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test6.html +0 -0
- /package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/tagColors.html +0 -0
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.removePassageByName(passage.name);
|
|
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.removePassageByName(passage.name);
|
|
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
|
-
['&', '&'],
|
|
218
|
-
['<', '<'],
|
|
219
|
-
['>', '>'],
|
|
220
|
-
['"', '"'],
|
|
221
|
-
["'", '''],
|
|
222
|
-
['`', '`']
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
rules.forEach(([rule, template]) => {
|
|
226
|
-
text = text.replaceAll(rule, template);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
return text;
|
|
230
|
-
}
|
|
231
|
-
}
|
package/src/StoryFormatParser.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import StoryFormat from './StoryFormat.js';
|
|
2
|
-
import semver from 'semver';
|
|
3
|
-
/**
|
|
4
|
-
* @class StoryFormatParser
|
|
5
|
-
* @module StoryFormatParser
|
|
6
|
-
*/
|
|
7
|
-
export default class StoryFormatParser {
|
|
8
|
-
/**
|
|
9
|
-
* Parse a Story Format file
|
|
10
|
-
*
|
|
11
|
-
* @public
|
|
12
|
-
* @static
|
|
13
|
-
* @memberof StoryFormatParser
|
|
14
|
-
* @function parse
|
|
15
|
-
* @param {string} contents - Content
|
|
16
|
-
* @returns {StoryFormat} StoryFormat object
|
|
17
|
-
*/
|
|
18
|
-
static parse (contents) {
|
|
19
|
-
// Harlowe has malformed JSON, so we have to test for it
|
|
20
|
-
const harlowePosition = contents.indexOf('harlowe');
|
|
21
|
-
|
|
22
|
-
if (harlowePosition !== -1) {
|
|
23
|
-
// The 'setup' property is malformed
|
|
24
|
-
const setupPosition = contents.lastIndexOf(',"setup": function');
|
|
25
|
-
contents = contents.slice(0, setupPosition) + '}';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Find the start of story format or -1, if not found
|
|
29
|
-
const openingCurlyBracketPosition = contents.indexOf('{');
|
|
30
|
-
// Find the end of story format or -1, if not found
|
|
31
|
-
const closingCurlyBracketPosition = contents.lastIndexOf('}');
|
|
32
|
-
|
|
33
|
-
// Look for JSON among the story format
|
|
34
|
-
// If either is -1, this is not valid JSON
|
|
35
|
-
if (openingCurlyBracketPosition === -1 || closingCurlyBracketPosition === -1) {
|
|
36
|
-
// Either start or end curly brackets were now found!
|
|
37
|
-
throw new Error('Unable to find Twine2 JSON chunk!');
|
|
38
|
-
} else {
|
|
39
|
-
// Slice out the JSON based on curly brackets
|
|
40
|
-
contents = contents.slice(openingCurlyBracketPosition, closingCurlyBracketPosition + 1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Create an object literal
|
|
44
|
-
let jsonContent = {};
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
jsonContent = JSON.parse(contents);
|
|
48
|
-
} catch (event) {
|
|
49
|
-
throw new Error('Unable to parse Twine2 JSON chunk!');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The following keys are found in most or all story formats:
|
|
54
|
-
* - name: (string) Optional. Name of the story format.
|
|
55
|
-
* (Omitting the name will lead to an Untitled Story Format.)
|
|
56
|
-
* - version: (string) Required, and semantic version-style formatting
|
|
57
|
-
* (x.y.z, e.g., 1.2.1) of the version is also required.
|
|
58
|
-
* - author: (string) Optional.
|
|
59
|
-
* - description: (string) Optional.
|
|
60
|
-
* - image: (string) Optional. The filename of an image (ideally SVG)
|
|
61
|
-
* served from the same directory as the format.js file.
|
|
62
|
-
* - url: (string) Optional. The URL of the directory containing the format.js file.
|
|
63
|
-
* - license: (string) Optional.
|
|
64
|
-
* - proofing: (boolean) Optional (defaults to false). True if the story format
|
|
65
|
-
* is a "proofing" format. The distinction is relevant only in the Twine 2 UI.
|
|
66
|
-
* - source: (string) Required. Full HTML output of the story format.
|
|
67
|
-
* (See: https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-storyformats-spec.md)
|
|
68
|
-
*/
|
|
69
|
-
|
|
70
|
-
// Name is optional, so we have to test for it
|
|
71
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'name')) {
|
|
72
|
-
// Use the default name
|
|
73
|
-
jsonContent.name = 'Untitled Story Format';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Author is optional, so we have to test for it
|
|
77
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'author')) {
|
|
78
|
-
// Use the default author
|
|
79
|
-
jsonContent.author = '';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Description is optional, so we have to test for it
|
|
83
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'description')) {
|
|
84
|
-
// Use the default description
|
|
85
|
-
jsonContent.description = '';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Image is optional, so we have to test for it
|
|
89
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'image')) {
|
|
90
|
-
// Use the default image
|
|
91
|
-
jsonContent.image = '';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// URL is optional, so we have to test for it
|
|
95
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'url')) {
|
|
96
|
-
// Use the default url
|
|
97
|
-
jsonContent.url = '';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// License is optional, so we have to test for it
|
|
101
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'license')) {
|
|
102
|
-
// Use the default license
|
|
103
|
-
jsonContent.license = '';
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Proofing is optional, so we have to test for it
|
|
107
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'proofing')) {
|
|
108
|
-
// Use the default proofing
|
|
109
|
-
jsonContent.proofing = false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Version is required, so we have to test for it
|
|
113
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'version')) {
|
|
114
|
-
// Throw error
|
|
115
|
-
throw new Error('Processed story format does not have required version property!');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Test if version is semantic-style, which is required
|
|
119
|
-
if (semver.valid(jsonContent.version) === null) {
|
|
120
|
-
throw new Error('Processed story format\'s version is not a valid semantic value!');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Source is required, so we have to test for it
|
|
124
|
-
if (!Object.prototype.hasOwnProperty.call(jsonContent, 'source')) {
|
|
125
|
-
// Throw error
|
|
126
|
-
throw new Error('Processed story format does not have required source property!');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Pass all values to the constructor and return the result
|
|
130
|
-
return new StoryFormat(
|
|
131
|
-
jsonContent.name,
|
|
132
|
-
jsonContent.version,
|
|
133
|
-
jsonContent.description,
|
|
134
|
-
jsonContent.author,
|
|
135
|
-
jsonContent.image,
|
|
136
|
-
jsonContent.url,
|
|
137
|
-
jsonContent.license,
|
|
138
|
-
jsonContent.proofing,
|
|
139
|
-
jsonContent.source
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
}
|
package/src/TweeParser.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import Passage from './Passage.js';
|
|
2
|
-
import Story from './Story.js';
|
|
3
|
-
/**
|
|
4
|
-
* @class TweeParser
|
|
5
|
-
* @module TweeParser
|
|
6
|
-
*/
|
|
7
|
-
export default class TweeParser {
|
|
8
|
-
/**
|
|
9
|
-
* Parse Twee
|
|
10
|
-
*
|
|
11
|
-
* @public
|
|
12
|
-
* @static
|
|
13
|
-
* @function parse
|
|
14
|
-
* @param {string} fileContents - File contents to parse
|
|
15
|
-
* @returns {Story} story
|
|
16
|
-
*/
|
|
17
|
-
static parse (fileContents) {
|
|
18
|
-
// Create Story.
|
|
19
|
-
const story = new Story();
|
|
20
|
-
|
|
21
|
-
// Throw error if fileContents is not a string
|
|
22
|
-
if (Object.prototype.toString.call(fileContents) !== '[object String]') {
|
|
23
|
-
throw new Error('Contents not a String');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
let adjusted = '';
|
|
27
|
-
|
|
28
|
-
// Check if there are extra content in the files
|
|
29
|
-
// If so, cut it all out for the parser
|
|
30
|
-
if (fileContents[0] !== ':' && fileContents[1] !== ':') {
|
|
31
|
-
adjusted = fileContents.slice(fileContents.indexOf('::'), fileContents.length);
|
|
32
|
-
} else {
|
|
33
|
-
adjusted = fileContents;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Split the file based on the passage sigil (::) proceeded by a newline
|
|
37
|
-
const parsingPassages = adjusted.split('\n::');
|
|
38
|
-
|
|
39
|
-
// Fix the first result
|
|
40
|
-
parsingPassages[0] = parsingPassages[0].slice(2, parsingPassages[0].length);
|
|
41
|
-
|
|
42
|
-
// Set the initial pid
|
|
43
|
-
let pid = 1;
|
|
44
|
-
|
|
45
|
-
// Iterate through the passages
|
|
46
|
-
parsingPassages.forEach((passage) => {
|
|
47
|
-
// Set default values
|
|
48
|
-
let tags = '';
|
|
49
|
-
let metadata = '';
|
|
50
|
-
let text = '';
|
|
51
|
-
let name = '';
|
|
52
|
-
|
|
53
|
-
// Header is everything to the first newline
|
|
54
|
-
let header = passage.slice(0, passage.indexOf('\n'));
|
|
55
|
-
// Text is everything else
|
|
56
|
-
// (Also eat the leading newline character.)
|
|
57
|
-
// (And trim any remaining whitespace.)
|
|
58
|
-
text = passage.substring(header.length + 1, passage.length).trim();
|
|
59
|
-
|
|
60
|
-
// Test for metadata
|
|
61
|
-
const openingCurlyBracketPosition = header.lastIndexOf('{');
|
|
62
|
-
const closingCurlyBracketPosition = header.lastIndexOf('}');
|
|
63
|
-
|
|
64
|
-
if (openingCurlyBracketPosition !== -1 && closingCurlyBracketPosition !== -1) {
|
|
65
|
-
// Save the text metadata
|
|
66
|
-
metadata = header.slice(openingCurlyBracketPosition, closingCurlyBracketPosition + 1);
|
|
67
|
-
|
|
68
|
-
// Remove the metadata from the header
|
|
69
|
-
header = header.substring(0, openingCurlyBracketPosition) + header.substring(closingCurlyBracketPosition + 1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// There was passage metadata
|
|
73
|
-
if (metadata.length > 0) {
|
|
74
|
-
// Try to parse the metadata
|
|
75
|
-
try {
|
|
76
|
-
metadata = JSON.parse(metadata);
|
|
77
|
-
} catch (event) {
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
// There wasn't any metadata, so set default
|
|
81
|
-
metadata = {};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Test for tags
|
|
85
|
-
const openingSquareBracketPosition = header.lastIndexOf('[');
|
|
86
|
-
const closingSquareBracketPosition = header.lastIndexOf(']');
|
|
87
|
-
|
|
88
|
-
if (openingSquareBracketPosition !== -1 && closingSquareBracketPosition !== -1) {
|
|
89
|
-
tags = header.slice(openingSquareBracketPosition, closingSquareBracketPosition + 1);
|
|
90
|
-
|
|
91
|
-
// Remove the tags from the header
|
|
92
|
-
header = header.substring(0, openingSquareBracketPosition) + header.substring(closingSquareBracketPosition + 1);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Parse tags
|
|
96
|
-
if (tags.length > 0) {
|
|
97
|
-
// Eat the opening and closing square brackets
|
|
98
|
-
tags = tags.substring(1, tags.length - 1);
|
|
99
|
-
|
|
100
|
-
// Set empty default
|
|
101
|
-
let tagsArray = [];
|
|
102
|
-
|
|
103
|
-
// Test if tags is not single, empty string
|
|
104
|
-
if (!(tags === '')) {
|
|
105
|
-
tagsArray = tags.split(' ');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// There are multiple tags
|
|
109
|
-
if (tagsArray.length > 1) {
|
|
110
|
-
// Create future array
|
|
111
|
-
const futureTagArray = [];
|
|
112
|
-
|
|
113
|
-
// Move through entries
|
|
114
|
-
// Add a trimmed version into future array
|
|
115
|
-
tagsArray.forEach((tag) => { futureTagArray.push(tag.trim()); });
|
|
116
|
-
|
|
117
|
-
// Set the tags back to the future array
|
|
118
|
-
tags = futureTagArray;
|
|
119
|
-
} else if (tagsArray.length === 1) {
|
|
120
|
-
// There was only one tag
|
|
121
|
-
// Store it
|
|
122
|
-
const temp = tags;
|
|
123
|
-
|
|
124
|
-
// Switch tags over to an array
|
|
125
|
-
tags = [];
|
|
126
|
-
// Push the single entry
|
|
127
|
-
tags.push(temp);
|
|
128
|
-
} else {
|
|
129
|
-
// Make sure tags is set to empty array if no tags were found
|
|
130
|
-
tags = [];
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
// There were no tags, so set it to an empty array;
|
|
134
|
-
tags = [];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Filter out any empty string tags
|
|
138
|
-
tags = tags.filter(tag => tag !== '');
|
|
139
|
-
|
|
140
|
-
// Trim any remaining whitespace
|
|
141
|
-
header = header.trim();
|
|
142
|
-
|
|
143
|
-
// Check if there is a name left
|
|
144
|
-
if (header.length > 0) {
|
|
145
|
-
name = header;
|
|
146
|
-
} else {
|
|
147
|
-
// No name left. Something went wrong. Blame user.
|
|
148
|
-
throw new Error('Malformed passage header!');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// addPassage() method does all the work
|
|
152
|
-
story.addPassage(new Passage(name, text, tags, metadata, pid));
|
|
153
|
-
|
|
154
|
-
// Increase pid
|
|
155
|
-
pid++;
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Return Story.
|
|
159
|
-
return story;
|
|
160
|
-
}
|
|
161
|
-
}
|
package/src/TweeWriter.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @external Story
|
|
3
|
-
* @see Story.js
|
|
4
|
-
* @external Passage
|
|
5
|
-
* @see Passage.js
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import Story from './Story.js';
|
|
10
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @class TweeWriter
|
|
14
|
-
* @module TweeWriter
|
|
15
|
-
*/
|
|
16
|
-
export default class TweeWriter {
|
|
17
|
-
/**
|
|
18
|
-
* Write to a file using a Story object
|
|
19
|
-
*
|
|
20
|
-
* @static
|
|
21
|
-
* @param {Story} story - Story format to write
|
|
22
|
-
* @param {string} file - File to write to
|
|
23
|
-
* @returns {void}
|
|
24
|
-
*/
|
|
25
|
-
static write (story, file) {
|
|
26
|
-
if (!(story instanceof Story)) {
|
|
27
|
-
throw new Error('Not a Story object!');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Write the StoryData first.
|
|
31
|
-
let outputContents = ':: StoryData\n';
|
|
32
|
-
|
|
33
|
-
// Create default object.
|
|
34
|
-
const metadata = {};
|
|
35
|
-
|
|
36
|
-
// Is there an IFID?
|
|
37
|
-
if (story.IFID === '') {
|
|
38
|
-
// Generate a new IFID for this work.
|
|
39
|
-
// Twine 2 uses v4 (random) UUIDs, using only capital letters.
|
|
40
|
-
metadata.ifid = uuidv4().toUpperCase();
|
|
41
|
-
} else {
|
|
42
|
-
// Use existing (non-default) value.
|
|
43
|
-
metadata.ifid = story.IFID;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Is there a format?
|
|
47
|
-
if (story.format !== '') {
|
|
48
|
-
// Using existing format
|
|
49
|
-
metadata.format = story.format;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Is there a formatVersion?
|
|
53
|
-
if (story.formatVersion !== '') {
|
|
54
|
-
// Using existing format version
|
|
55
|
-
metadata['format-version'] = story.formatVersion;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Is there a zoom?
|
|
59
|
-
if (story.zoom !== 0) {
|
|
60
|
-
// Using existing zoom.
|
|
61
|
-
metadata.zoom = story.zoom;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Is there a start?
|
|
65
|
-
if (story.start !== '') {
|
|
66
|
-
// Using existing start
|
|
67
|
-
metadata.start = story.start;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Get number of colors.
|
|
71
|
-
const numberOfColors = Object.keys(story.tagColors).length;
|
|
72
|
-
// Are there any colors?
|
|
73
|
-
if (numberOfColors > 0) {
|
|
74
|
-
// Add a tag-colors property
|
|
75
|
-
metadata['tag-colors'] = story.tagColors;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Write out the story metadata.
|
|
79
|
-
outputContents += `${JSON.stringify(metadata, undefined, 2)}`;
|
|
80
|
-
|
|
81
|
-
// Add two newlines.
|
|
82
|
-
outputContents += '\n\n';
|
|
83
|
-
|
|
84
|
-
// Look for StoryTitle
|
|
85
|
-
const storyTitlePassage = story.getPassageByName('StoryTitle');
|
|
86
|
-
|
|
87
|
-
// Does it exist?
|
|
88
|
-
if (storyTitlePassage !== null) {
|
|
89
|
-
// Append StoryTitle content
|
|
90
|
-
outputContents += storyTitlePassage.toString();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// For each passage, append it to the output.
|
|
94
|
-
story.forEach((passage) => {
|
|
95
|
-
// For each passage, append it to the output.
|
|
96
|
-
outputContents += passage.toString();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Look for 'script' passages
|
|
100
|
-
const scriptPassages = story.getPassagesByTag('script');
|
|
101
|
-
// For each 'script', add it back
|
|
102
|
-
scriptPassages.forEach(passage => {
|
|
103
|
-
outputContents += passage.toString();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Look for 'stylesheet' passages
|
|
107
|
-
const stylesheetPassages = story.getPassagesByTag('stylesheet');
|
|
108
|
-
// For each 'stylesheet', add it back
|
|
109
|
-
stylesheetPassages.forEach(passage => {
|
|
110
|
-
outputContents += passage.toString();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
// Try to write
|
|
115
|
-
fs.writeFileSync(file, outputContents);
|
|
116
|
-
} catch (event) {
|
|
117
|
-
// Throw error
|
|
118
|
-
throw new Error('Error: Cannot write Twee file!');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|