extwee 2.2.6 → 2.3.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.
- package/.github/dependabot.yml +1 -1
- package/.github/workflows/dependabot-automerge.yml +23 -0
- package/.github/workflows/nodejs.yml +4 -1
- package/README.md +9 -0
- package/SECURITY.md +1 -1
- package/build/extwee.web.min.js +2 -0
- package/build/extwee.web.min.js.LICENSE.txt +1 -0
- package/docs/README.md +79 -55
- package/docs/_sidebar.md +0 -1
- package/docs/install/npm.md +0 -4
- package/docs/install/npx.md +70 -0
- package/extwee.config.json +6 -0
- package/extwee.config.md +67 -0
- package/package.json +22 -22
- package/src/CLI/CommandLineProcessing.js +196 -0
- package/src/CLI/ProcessConfig/loadStoryFormat.js +102 -0
- package/src/CLI/ProcessConfig/readDirectories.js +46 -0
- package/src/CLI/ProcessConfig.js +175 -0
- package/src/CLI/isDirectory.js +27 -0
- package/src/CLI/isFile.js +28 -0
- package/src/Config/parser.js +30 -8
- package/src/Passage.js +17 -2
- package/src/Story.js +92 -1
- package/src/extwee.js +20 -195
- package/test/Config/Config.test.js +40 -10
- package/test/Config/files/full.json +8 -0
- package/test/Config/files/valid.json +4 -3
- package/test/Config/isDirectory.test.js +44 -0
- package/test/Config/isFile.test.js +50 -0
- package/test/Config/loadStoryFormat.test.js +101 -0
- package/test/Config/readDirectories.test.js +68 -0
- package/test/Objects/Passage.test.js +5 -0
- package/test/Objects/Story.test.js +131 -0
- package/test/TWS/Parse.test.js +0 -22
- package/test/Web/window.Extwee.test.js +85 -0
- package/types/Story.d.ts +25 -0
- package/types/index.d.ts +4 -2
- package/types/src/CLI/CommandLineProcessing.d.ts +8 -0
- package/types/src/CLI/ProcessConfig/loadStoryFormat.d.ts +20 -0
- package/types/src/CLI/ProcessConfig/readDirectories.d.ts +9 -0
- package/types/src/CLI/ProcessConfig.d.ts +12 -0
- package/types/src/CLI/isDirectory.d.ts +1 -0
- package/types/src/CLI/isFile.d.ts +1 -0
- package/types/src/Config/parser.d.ts +6 -0
- package/types/src/Config/reader.d.ts +11 -0
- package/types/src/IFID/generate.d.ts +14 -0
- package/types/src/JSON/parse.d.ts +44 -1
- package/types/src/Passage.d.ts +49 -4
- package/types/src/Story.d.ts +110 -16
- package/types/src/StoryFormat/compile.d.ts +8 -0
- package/types/src/StoryFormat/parse.d.ts +46 -3
- package/types/src/StoryFormat.d.ts +69 -38
- package/types/src/TWS/parse.d.ts +3 -3
- package/types/src/Twee/parse.d.ts +3 -4
- package/types/src/Twine1HTML/compile.d.ts +3 -1
- package/types/src/Twine1HTML/parse.d.ts +3 -4
- package/types/src/Twine2ArchiveHTML/compile.d.ts +8 -0
- package/types/src/Twine2ArchiveHTML/parse.d.ts +31 -1
- package/types/src/Twine2HTML/compile.d.ts +7 -2
- package/types/src/Twine2HTML/parse.d.ts +12 -9
- package/docs/install/binaries.md +0 -9
- package/index.html +0 -22
- package/test/TWS/TWSParser/Example1.tws +0 -150
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { isDirectory } from "../isDirectory.js";
|
|
2
|
+
import { isFile } from "../isFile.js";
|
|
3
|
+
import { readDirectories } from "./readDirectories.js";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Load the story format from the story-formats directory.
|
|
8
|
+
* @function loadStoryFormat
|
|
9
|
+
* @description This function loads the story format from the story-formats directory.
|
|
10
|
+
* It checks if the story-formats directory exists, if the named story format exists,
|
|
11
|
+
* if the version directory exists, and if the format.js file exists.
|
|
12
|
+
* If any of these checks fail, the function will exit the process with an error message.
|
|
13
|
+
* If all checks pass, the function will return the contents of the format.js file.
|
|
14
|
+
* @param {string} storyFormatName - The name of the story format.
|
|
15
|
+
* @param {string} storyFormatVersion - The version of the story format.
|
|
16
|
+
* @returns {string} - The contents of the format.js file.
|
|
17
|
+
* @throws {Error} - If the story-formats directory does not exist, if the named story format does not exist,
|
|
18
|
+
* if the version directory does not exist, or if the format.js file does not exist.
|
|
19
|
+
* @example
|
|
20
|
+
* // Load the story format from the story-formats directory.
|
|
21
|
+
* const storyFormat = loadStoryFormat('Harlowe', '3.2.0');
|
|
22
|
+
* console.log(storyFormat);
|
|
23
|
+
* // Output: The contents of the format.js file.
|
|
24
|
+
*/
|
|
25
|
+
export function loadStoryFormat(storyFormatName, storyFormatVersion) {
|
|
26
|
+
// If the story-formats directory does not exist, throw error.
|
|
27
|
+
if (isDirectory('story-formats') === false) {
|
|
28
|
+
throw new Error(`Error: story-formats directory does not exist. Consider running 'npx sfa-get' to download the latest story formats.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Does the named story format exist in the story-formats directory?
|
|
32
|
+
const isStoryFormatDir = isDirectory(`story-formats/${storyFormatName}`);
|
|
33
|
+
|
|
34
|
+
// If the story format directory does not exist, throw error.
|
|
35
|
+
if (isStoryFormatDir === false) {
|
|
36
|
+
throw new Error(`Error: story format ${storyFormatName} does not exist in the story-formats directory.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let filepath = `story-formats/${storyFormatName}/format.js`;
|
|
40
|
+
|
|
41
|
+
// If the story format version is 'latest', check if the format.js file exists.
|
|
42
|
+
// If the format.js file does not exist, check if there are version directories.
|
|
43
|
+
// If there are version directories, get the latest version and set the filepath to that version directory and format.js file.
|
|
44
|
+
// If the format.js file exists, return its contents.
|
|
45
|
+
|
|
46
|
+
// Check if storyFormatVersion is "latest"
|
|
47
|
+
if (storyFormatVersion === 'latest' && isFile(filepath) === false) {
|
|
48
|
+
// Read the directories in the story format directory.
|
|
49
|
+
// The directories are expected to be version directories.
|
|
50
|
+
let directories = readDirectories(`story-formats/${storyFormatName}`);
|
|
51
|
+
|
|
52
|
+
console.log("!!! directories", directories);
|
|
53
|
+
|
|
54
|
+
// Check if there are any version directories.
|
|
55
|
+
if (directories.length === 0) {
|
|
56
|
+
// If there are no version directories, throw error.
|
|
57
|
+
throw new Error(`Error: story format ${storyFormatName} does not have any version directories.`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Sort the directories in descending order.
|
|
61
|
+
directories.sort((a, b) => {
|
|
62
|
+
return b.localeCompare(a, undefined, { numeric: true });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Get the latest version directory.
|
|
66
|
+
// The latest version is the last directory in the sorted list.
|
|
67
|
+
const latestVersion = directories[0];
|
|
68
|
+
|
|
69
|
+
// Set the filepath to the latest version directory.
|
|
70
|
+
filepath = `story-formats/${storyFormatName}/${latestVersion}/format.js`;
|
|
71
|
+
|
|
72
|
+
// Is there a 'format.js' file in the version directory?
|
|
73
|
+
let isFormatFile = isFile(filepath);
|
|
74
|
+
|
|
75
|
+
// If the format.js file does not exist, exit the process.
|
|
76
|
+
if (isFormatFile === false) {
|
|
77
|
+
throw new Error(`Error: story format ${storyFormatName} version ${storyFormatVersion} does not have a format.js file.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// If the story format version is not 'latest', check if version directories exists.
|
|
82
|
+
if(storyFormatVersion !== 'latest') {
|
|
83
|
+
// Does the named story format have a version directory?
|
|
84
|
+
const isVersionDir = isDirectory(`story-formats/${storyFormatName}/${storyFormatVersion}`);
|
|
85
|
+
|
|
86
|
+
// If the version directory does not exist, throw error.
|
|
87
|
+
if (isVersionDir === false) {
|
|
88
|
+
throw new Error(`Error: story format ${storyFormatName} version ${storyFormatVersion} does not exist in the story-formats directory.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Set an initial path based on the version.
|
|
92
|
+
filepath = `story-formats/${storyFormatName}/${storyFormatVersion}/format.js`;
|
|
93
|
+
|
|
94
|
+
// If the format.js file does not exist, throw error.
|
|
95
|
+
if (isFile(filepath) === false) {
|
|
96
|
+
throw new Error(`Error: story format ${storyFormatName} version ${storyFormatVersion} does not have a format.js file.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// If the format.js file exists, read and return its contents.
|
|
101
|
+
return readFileSync(filepath, 'utf-8');
|
|
102
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { isDirectory } from '../isDirectory.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read the contents of a directory and returns all directories.
|
|
6
|
+
* @function readDirectories
|
|
7
|
+
* @description This function reads the contents of a directory and returns a list of directories.
|
|
8
|
+
* @param {string} directory - The path to the directory to read.
|
|
9
|
+
* @returns {Array<string>} - An array of directories in the directory.
|
|
10
|
+
* @throws {Error} - If the directory does not exist or if there is an error reading the directory.
|
|
11
|
+
*/
|
|
12
|
+
export function readDirectories(directory) {
|
|
13
|
+
|
|
14
|
+
// Create default response.
|
|
15
|
+
let results = [];
|
|
16
|
+
|
|
17
|
+
// Check if the directory exists.
|
|
18
|
+
const isDir = isDirectory(directory);
|
|
19
|
+
// If the directory does not exist, return an empty array
|
|
20
|
+
// and log an error message.
|
|
21
|
+
if (isDir == false) {
|
|
22
|
+
console.error(`Error: Directory ${directory} does not exist.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Read the directory and return the list of files.
|
|
26
|
+
try {
|
|
27
|
+
results = readdirSync(directory);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`Error reading directory ${directory}:`, error);
|
|
30
|
+
results = [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check if results is an array.
|
|
34
|
+
// This should not happen, but for some reason it can.
|
|
35
|
+
if (!Array.isArray(results)) {
|
|
36
|
+
results = [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Filter the list to only include directories.
|
|
40
|
+
const directoriesOnly = results.filter((item) => {
|
|
41
|
+
return isDirectory(`${directory}/${item}`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Return the list of directories.
|
|
45
|
+
return directoriesOnly;
|
|
46
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { isFile } from './isFile.js';
|
|
3
|
+
import {
|
|
4
|
+
parseTwine2HTML,
|
|
5
|
+
parseTwee,
|
|
6
|
+
parseStoryFormat,
|
|
7
|
+
parseTwine1HTML,
|
|
8
|
+
compileTwine2HTML
|
|
9
|
+
} from '../../index.js';
|
|
10
|
+
import { loadStoryFormat } from './ProcessConfig/loadStoryFormat.js';
|
|
11
|
+
|
|
12
|
+
// Look for the config file in the current directory.
|
|
13
|
+
const configFile = 'extwee.config.json';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if the config file exists.
|
|
17
|
+
* @function ConfigFilePresent
|
|
18
|
+
* @description This function checks if the config file exists in the current directory.
|
|
19
|
+
* If the config file does not exist, the function will return false.
|
|
20
|
+
* If the config file exists, the function will return true.
|
|
21
|
+
* The config file is used to store configuration options for the Extwee CLI.
|
|
22
|
+
* @returns {boolean} - true if the config file exists, false otherwise.
|
|
23
|
+
*/
|
|
24
|
+
export const ConfigFilePresent = () => {
|
|
25
|
+
// Check if the config file exists.
|
|
26
|
+
return isFile(configFile);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Processes the config file, if present.
|
|
31
|
+
* @function ConfigFileProcessing
|
|
32
|
+
* @description This function processes the config file.
|
|
33
|
+
* It checks if the config file exists and if it does, it reads the config file.
|
|
34
|
+
* If the config file does not exist, the function will exit the process with an error message.
|
|
35
|
+
* The config file is used to store configuration options for the Extwee CLI.
|
|
36
|
+
* @returns {void}
|
|
37
|
+
* @throws {Error} - If the config file does not exist or if there is an error parsing the config file.
|
|
38
|
+
*/
|
|
39
|
+
export function ConfigFileProcessing() {
|
|
40
|
+
// Check if the config file exists.
|
|
41
|
+
const configFileExists = isFile(configFile);
|
|
42
|
+
|
|
43
|
+
// If the config file does not exist, exit the process.
|
|
44
|
+
if (configFileExists === false) {
|
|
45
|
+
console.error(`Error: ${configFile} does not exist.`);
|
|
46
|
+
// Exit the process.
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If the config file exists, read it.
|
|
51
|
+
let configFileContents = {};
|
|
52
|
+
// If the config file exists and no command-line arguments are passed, read the config file.
|
|
53
|
+
|
|
54
|
+
// Read the config file.
|
|
55
|
+
const configFileData = readFileSync(configFile, 'utf-8');
|
|
56
|
+
|
|
57
|
+
// Parse the config file.
|
|
58
|
+
try {
|
|
59
|
+
configFileContents = JSON.parse(configFileData);
|
|
60
|
+
console.log(`Using config file: ${configFile}`, configFileContents);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// There was an error parsing the config file.
|
|
63
|
+
console.error(`Error parsing ${configFile}: ${e}`);
|
|
64
|
+
// Exit the process.
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// First, we need to know the mode (compile or decompile).
|
|
69
|
+
|
|
70
|
+
// Does the config file the "mode" property?
|
|
71
|
+
if (configFileContents.mode === undefined) {
|
|
72
|
+
// The config file does not have a "mode" property.
|
|
73
|
+
console.error(`Error: ${configFile} does not have a "mode" property.`);
|
|
74
|
+
// Exit the process.
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if the mode is valid.
|
|
79
|
+
if (configFileContents.mode !== 'compile' && configFileContents.mode !== 'decompile') {
|
|
80
|
+
// The mode is not valid.
|
|
81
|
+
console.error(`Error: ${configFile} has an invalid "mode" property. Must be "compile" or "decompile".`);
|
|
82
|
+
// Exit the process.
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if the "input" property is present.
|
|
87
|
+
if (configFileContents.input === undefined) {
|
|
88
|
+
// The config file does not have an "input" property.
|
|
89
|
+
console.error(`Error: ${configFile} does not have an "input" property.`);
|
|
90
|
+
// Exit the process.
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if the "output" property is present.
|
|
95
|
+
if (configFileContents.output === undefined) {
|
|
96
|
+
// The config file does not have an "input" property.
|
|
97
|
+
console.error(`Error: ${configFile} does not have an "output" property.`);
|
|
98
|
+
// Exit the process.
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if de-compile mode is enabled.
|
|
103
|
+
const isDecompileMode = (configFileContents.mode === "decompile");
|
|
104
|
+
|
|
105
|
+
// Check if compile mode is enabled.
|
|
106
|
+
const isCompileMode = (configFileContents.mode === "compile");
|
|
107
|
+
|
|
108
|
+
// Check if Twine 1 is enabled.
|
|
109
|
+
const isTwine1Mode = (configFileContents.twine1Project === true);
|
|
110
|
+
|
|
111
|
+
// Check if story-format-version is present.
|
|
112
|
+
if (configFileContents.storyFormatVersion === undefined) {
|
|
113
|
+
// If not present, assume "latest" version.
|
|
114
|
+
configFileContents.storyFormatVersion = 'latest';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check if Twine 2 is enabled.
|
|
118
|
+
// By default, Twine 2 is enabled and only disabled if Twine 1 is enabled.
|
|
119
|
+
// This is because Twine 2 is the default mode for Extwee.
|
|
120
|
+
const isTwine2Mode = (isTwine1Mode === false ? true : false);
|
|
121
|
+
|
|
122
|
+
// De-compile Twine 2 HTML into Twee 3 branch.
|
|
123
|
+
if (isTwine2Mode === true && isDecompileMode === true) {
|
|
124
|
+
// Read the input HTML file.
|
|
125
|
+
const inputHTML = readFileSync(configFileContents.input, 'utf-8');
|
|
126
|
+
|
|
127
|
+
// Parse the input HTML file into Story object.
|
|
128
|
+
const storyObject = parseTwine2HTML(inputHTML);
|
|
129
|
+
|
|
130
|
+
// Write the output file from Story as Twee 3.
|
|
131
|
+
writeFileSync(configFileContents.output, storyObject.toTwee());
|
|
132
|
+
|
|
133
|
+
// Exit the process.
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Compile Twee 3 into Twine 2 HTML branch.
|
|
138
|
+
if (isTwine2Mode === true && isCompileMode === true) {
|
|
139
|
+
// Read the input file.
|
|
140
|
+
const inputTwee = readFileSync(configFileContents.input, 'utf-8');
|
|
141
|
+
|
|
142
|
+
// Parse the input file.
|
|
143
|
+
const story = parseTwee(inputTwee);
|
|
144
|
+
|
|
145
|
+
// Read the story format file.
|
|
146
|
+
const inputStoryFormat = loadStoryFormat(configFileContents.storyFormatName, configFileContents.storyFormatVersion);
|
|
147
|
+
|
|
148
|
+
// Parse the story format file.
|
|
149
|
+
const parsedStoryFormat = parseStoryFormat(inputStoryFormat);
|
|
150
|
+
|
|
151
|
+
// Compile the story.
|
|
152
|
+
const Twine2HTML = compileTwine2HTML(story, parsedStoryFormat);
|
|
153
|
+
|
|
154
|
+
// Write the output file.
|
|
155
|
+
writeFileSync(configFileContents.output, Twine2HTML);
|
|
156
|
+
|
|
157
|
+
// Exit the process.
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// De-compile Twine 1 HTML into Twee 3 branch.
|
|
162
|
+
if (isTwine1Mode === true && isDecompileMode === true) {
|
|
163
|
+
// Read the input HTML file.
|
|
164
|
+
const inputHTML = readFileSync(configFileContents.input, 'utf-8');
|
|
165
|
+
|
|
166
|
+
// Parse the input HTML file into Story object.
|
|
167
|
+
const storyObject = parseTwine1HTML(inputHTML);
|
|
168
|
+
|
|
169
|
+
// Write the output file from Story as Twee 3.
|
|
170
|
+
writeFileSync(configFileContents.output, storyObject.toTwee());
|
|
171
|
+
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Check if a passed option is a valid directory.
|
|
5
|
+
* @function isDirectory
|
|
6
|
+
* @description Check if a directory exists.
|
|
7
|
+
* @param {string} path - Path to directory.
|
|
8
|
+
* @returns {boolean} True if directory exists, false if not.
|
|
9
|
+
*/
|
|
10
|
+
export const isDirectory = (path) => {
|
|
11
|
+
// set default.
|
|
12
|
+
let result = false;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Attempt t0 get stats.
|
|
16
|
+
const stats = statSync(path);
|
|
17
|
+
|
|
18
|
+
// Return if path is a directory.
|
|
19
|
+
result = stats.isDirectory();
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// If there was an error, log it.
|
|
22
|
+
console.error(`Error: ${e}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Return either the default (false) or the result (true).
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Import fs.
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Check if a passed option is a valid file.
|
|
6
|
+
* @function isFile
|
|
7
|
+
* @description Check if a file exists.
|
|
8
|
+
* @param {string} path - Path to file.
|
|
9
|
+
* @returns {boolean} True if file exists, false if not.
|
|
10
|
+
*/
|
|
11
|
+
export const isFile = (path) => {
|
|
12
|
+
// set default.
|
|
13
|
+
let result = false;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Attempt t0 get stats.
|
|
17
|
+
const stats = statSync(path);
|
|
18
|
+
|
|
19
|
+
// Return if path is a file.
|
|
20
|
+
result = stats.isFile();
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// If there was an error, log it.
|
|
23
|
+
console.error(`Error: ${e}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Return either the default (false) or the result (true).
|
|
27
|
+
return result;
|
|
28
|
+
};
|
package/src/Config/parser.js
CHANGED
|
@@ -12,8 +12,11 @@ export function parser(obj) {
|
|
|
12
12
|
// Extracted results.
|
|
13
13
|
let results = {
|
|
14
14
|
StoryFormat: null,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Input: null,
|
|
16
|
+
Output: null,
|
|
17
|
+
Mode: null,
|
|
18
|
+
Twine1Project: false,
|
|
19
|
+
StoryFormatVersion: null
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
// Does the object contain 'StoryFormat'?
|
|
@@ -21,14 +24,33 @@ export function parser(obj) {
|
|
|
21
24
|
results.StoryFormat = obj['story-format'];
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
// Does the object contain '
|
|
25
|
-
if (Object.hasOwnProperty.call(obj, 'story-
|
|
26
|
-
results.
|
|
27
|
+
// Does the object contain 'StoryFormatVersion'?
|
|
28
|
+
if (Object.hasOwnProperty.call(obj, 'story-format-version')) {
|
|
29
|
+
results.StoryFormatVersion = obj['story-format-version'];
|
|
30
|
+
} else {
|
|
31
|
+
results.StoryFormatVersion = "latest";
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
// Does the object contain '
|
|
30
|
-
if (Object.hasOwnProperty.call(obj, '
|
|
31
|
-
results.
|
|
34
|
+
// Does the object contain 'mode'?
|
|
35
|
+
if (Object.hasOwnProperty.call(obj, 'mode')) {
|
|
36
|
+
results.Mode = obj['mode'];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Does the object contain 'input'?
|
|
40
|
+
if (Object.hasOwnProperty.call(obj, 'input')) {
|
|
41
|
+
results.Input = obj['input'];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Does the object contain 'output'?
|
|
45
|
+
if (Object.hasOwnProperty.call(obj, 'output')) {
|
|
46
|
+
results.Output = obj['output'];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Does the object contain 'twine1-project'?
|
|
50
|
+
if (Object.hasOwnProperty.call(obj, 'twine1-project')) {
|
|
51
|
+
results.Twine1Project = obj['twine1-project'];
|
|
52
|
+
} else {
|
|
53
|
+
results.Twine1Project = false;
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
// Return the extracted results.
|
package/src/Passage.js
CHANGED
|
@@ -190,8 +190,23 @@ export default class Passage {
|
|
|
190
190
|
content += ` ${JSON.stringify(this.metadata)}`;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
//
|
|
194
|
-
|
|
193
|
+
// Split the text into lines.
|
|
194
|
+
const lines = this.text.split('\n');
|
|
195
|
+
|
|
196
|
+
// For each line, check if it begins with a double-colon.
|
|
197
|
+
for (let i = 0; i < lines.length; i++) {
|
|
198
|
+
// Check if the line begins with a double-colon.
|
|
199
|
+
if (lines[i].startsWith('::')) {
|
|
200
|
+
// Escape the double-colon.
|
|
201
|
+
lines[i] = `\\${lines[i]}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Rejoin the lines.
|
|
206
|
+
const output = lines.join('\n');
|
|
207
|
+
|
|
208
|
+
// Add newline and text.
|
|
209
|
+
content += `\n${output}\n\n`;
|
|
195
210
|
|
|
196
211
|
// Return string.
|
|
197
212
|
return content;
|
package/src/Story.js
CHANGED
|
@@ -2,10 +2,12 @@ import Passage from './Passage.js';
|
|
|
2
2
|
import { generate as generateIFID } from './IFID/generate.js';
|
|
3
3
|
import { encode } from 'html-entities';
|
|
4
4
|
|
|
5
|
+
// Set the creator name.
|
|
6
|
+
// This is used to identify the program that created the story.
|
|
5
7
|
const creatorName = 'extwee';
|
|
6
8
|
|
|
7
9
|
// Set the creator version.
|
|
8
|
-
const creatorVersion = '2.
|
|
10
|
+
const creatorVersion = '2.3.1';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Story class.
|
|
@@ -22,6 +24,8 @@ const creatorVersion = '2.2.6';
|
|
|
22
24
|
* @property {string} creatorVersion - Version used to create Story.
|
|
23
25
|
* @property {object} metadata - Metadata of Story.
|
|
24
26
|
* @property {object} tagColors - Tag Colors
|
|
27
|
+
* @property {string} storyJavaScript - Story JavaScript
|
|
28
|
+
* @property {string} storyStylesheet - Story Stylesheet
|
|
25
29
|
* @method {number} addPassage - Add a passage to the story and returns the new length of the passages array.
|
|
26
30
|
* @method {number} removePassageByName - Remove a passage from the story by name and returns the new length of the passages array.
|
|
27
31
|
* @method {Array} getPassagesByTag - Find passages by tag.
|
|
@@ -107,6 +111,18 @@ class Story {
|
|
|
107
111
|
*/
|
|
108
112
|
#_tagColors = {};
|
|
109
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Story JavaScript
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
#_storyJavaScript = '';
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Story Stylesheet
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
#_storyStylesheet = '';
|
|
125
|
+
|
|
110
126
|
/**
|
|
111
127
|
* Creates a story.
|
|
112
128
|
* @param {string} name - Name of the story.
|
|
@@ -322,6 +338,45 @@ class Story {
|
|
|
322
338
|
}
|
|
323
339
|
}
|
|
324
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Story stylesheet data can be set as a passage, property value, or both.
|
|
343
|
+
* @returns {string} storyStylesheet
|
|
344
|
+
*/
|
|
345
|
+
get storyStylesheet () {
|
|
346
|
+
return this.#_storyStylesheet;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @param {string} s - Replacement story stylesheet
|
|
351
|
+
*/
|
|
352
|
+
set storyStylesheet (s) {
|
|
353
|
+
if (typeof s === 'string') {
|
|
354
|
+
this.#_storyStylesheet = s;
|
|
355
|
+
} else {
|
|
356
|
+
throw new Error('Story stylesheet must be a string!');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get story JavaScript.
|
|
362
|
+
* @returns {string} storyJavaScript
|
|
363
|
+
*/
|
|
364
|
+
get storyJavaScript () {
|
|
365
|
+
return this.#_storyJavaScript;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Set story JavaScript.
|
|
370
|
+
* @param {string} s - Replacement story JavaScript
|
|
371
|
+
*/
|
|
372
|
+
set storyJavaScript (s) {
|
|
373
|
+
if (typeof s === 'string') {
|
|
374
|
+
this.#_storyJavaScript = s;
|
|
375
|
+
} else {
|
|
376
|
+
throw new Error('Story JavaScript must be a string!');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
325
380
|
/**
|
|
326
381
|
* Add a passage to the story.
|
|
327
382
|
* Passing `StoryData` will override story metadata and `StoryTitle` will override story name.
|
|
@@ -484,6 +539,8 @@ class Story {
|
|
|
484
539
|
creator: this.creator,
|
|
485
540
|
creatorVersion: this.creatorVersion,
|
|
486
541
|
zoom: this.zoom,
|
|
542
|
+
style: this.storyStylesheet,
|
|
543
|
+
script: this.storyJavaScript,
|
|
487
544
|
passages: []
|
|
488
545
|
};
|
|
489
546
|
|
|
@@ -595,6 +652,16 @@ class Story {
|
|
|
595
652
|
// Add two newlines.
|
|
596
653
|
outputContents += '\n\n';
|
|
597
654
|
|
|
655
|
+
// Write out the story stylesheet, if any.
|
|
656
|
+
if (this.#_storyStylesheet.length > 0) {
|
|
657
|
+
outputContents += ':: StoryStylesheet [stylesheet]\n' + this.#_storyStylesheet + '\n\n';
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Write out the story JavaScript, if any.
|
|
661
|
+
if (this.#_storyJavaScript.length > 0) {
|
|
662
|
+
outputContents += ':: StoryJavaScript [script]\n' + this.#_storyJavaScript + '\n\n';
|
|
663
|
+
}
|
|
664
|
+
|
|
598
665
|
// For each passage, append it to the output.
|
|
599
666
|
this.passages.forEach((passage) => {
|
|
600
667
|
outputContents += passage.toTwee();
|
|
@@ -620,6 +687,10 @@ class Story {
|
|
|
620
687
|
* - `format`: (string) Optional. The format of the story.
|
|
621
688
|
* - `format-version`: (string) Optional. The version of the format of the story.
|
|
622
689
|
*
|
|
690
|
+
* Because story stylesheet data can be represented as a passage, property value, or both, all approaches are encoded.
|
|
691
|
+
*
|
|
692
|
+
* Because story JavaScript can be represented as a passage, property value, or both, all approaches are encoded.
|
|
693
|
+
*
|
|
623
694
|
* @method toTwine2HTML
|
|
624
695
|
* @returns {string} Twine 2 HTML string
|
|
625
696
|
*/
|
|
@@ -728,6 +799,9 @@ class Story {
|
|
|
728
799
|
// Add the default attributes.
|
|
729
800
|
storyData += ' options hidden>\n';
|
|
730
801
|
|
|
802
|
+
// We may have passages with tags of 'stylesheet', story stylesheet data, both, or none.
|
|
803
|
+
|
|
804
|
+
// Step 1: Add all passages with tag of 'stylesheet' to the stylesheet element.
|
|
731
805
|
// Filter out passages with tag of 'stylesheet'.
|
|
732
806
|
const stylesheetPassages = passages.filter((passage) => passage.tags.includes('stylesheet'));
|
|
733
807
|
|
|
@@ -749,6 +823,16 @@ class Story {
|
|
|
749
823
|
storyData += '</style>\n';
|
|
750
824
|
}
|
|
751
825
|
|
|
826
|
+
// Step 2: Check if the internal stylesheet data is empty.
|
|
827
|
+
// If it is not empty, add it to the stylesheet element.
|
|
828
|
+
if (this.#_storyStylesheet.length > 0) {
|
|
829
|
+
// Add the internal stylesheet.
|
|
830
|
+
storyData += `\t<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">${this.#_storyStylesheet}</style>\n`;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// We may have passages with tags of 'script', story JavaScript data, both, or none.
|
|
834
|
+
|
|
835
|
+
// Step 1: Add all passages with tag of 'script' to the script element.
|
|
752
836
|
// Filter out passages with tag of 'script'.
|
|
753
837
|
const scriptPassages = passages.filter((passage) => passage.tags.includes('script'));
|
|
754
838
|
|
|
@@ -770,6 +854,13 @@ class Story {
|
|
|
770
854
|
storyData += '</script>\n';
|
|
771
855
|
}
|
|
772
856
|
|
|
857
|
+
// Step 2: Check if the internal JavaScript data is empty.
|
|
858
|
+
// If it is not empty, add it to the script element.
|
|
859
|
+
if (this.#_storyJavaScript.length > 0) {
|
|
860
|
+
// Add the internal JavaScript.
|
|
861
|
+
storyData += `\t<script role="script" id="twine-user-script" type="text/twine-javascript">${this.#_storyJavaScript}</script>\n`;
|
|
862
|
+
}
|
|
863
|
+
|
|
773
864
|
// Reset the PID counter.
|
|
774
865
|
PIDcounter = 1;
|
|
775
866
|
|