extwee 2.2.5 → 2.2.6

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/README.md CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## Story Compilation
29
29
 
30
- The process of *story compilation* converts human-readable content, what is generally called a *story*, into a playable format such as HyperText Markup Language (HTML). The result is then presented as links or other visual, interactive elements for another party to interact with to see its content.
30
+ The process of *story compilation* converts human-readable content, what is generally called a *story*, into a playable format such as HyperText Markup Language (HTML). The result is then presented as links or other visual, interactive elements for another party.
31
31
 
32
32
  The term *compilation* is used because different parts of code are put together in a specific arrangement to enable later play. As part of Twine-compatible HTML, this means combining JavaScript code (generally a "story format") with story HTML data.
33
33
 
@@ -59,12 +59,13 @@ Extwee supports multiple historical and current Twine-compatible formats.
59
59
 
60
60
  | Format | Input | Output |
61
61
  |----------------------------------------------------------------------------------------------------------------------------------|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
62
- | [ Twine 1 HTML (2006 - 2015) ]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md ) | Yes | Partial support. Twine 1 HTML can be produced, but the `StorySettings` optional passage introduced in Twine 1.4.0 requires external libraries like jQuery not included with Extwee. |
63
- | [ Twine 1 TWS (2009 - 2015) ]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-twsoutput.md ) | Yes | Extwee does not support TWS (Python pickle) output because no current version of Twine or other story compilation tool produces this historical format. |
64
- | [ Twine 2 HTML (2015 - Present) ]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md ) | Yes | Yes |
65
- | [ Twine 2 Archive HTML (2015 - Present) ]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-archive-spec.md ) | Yes | Yes |
66
- | [ Twee 3 (2021 - Present) ]( https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md ) | Yes | Yes |
67
- | [ Twine 2 JSON (2023 - Present) ]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-jsonoutput-doc.md ) | Yes | Yes |
62
+ | [Twine 1 HTML (2006 - 2015)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md ) | Yes | Partial support. Twine 1 HTML can be produced, but the `StorySettings` optional passage introduced in Twine 1.4.0 requires external libraries like jQuery not included with Extwee. |
63
+ | [Twine 1 TWS (2009 - 2015)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-twsoutput.md ) | Yes | Extwee does not support TWS (Python pickle) output because no current version of Twine or other story compilation tool produces this historical format. |
64
+ | [Twine 2 HTML (2015 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md ) | Yes | Yes |
65
+ | [Twine 2 Archive HTML (2015 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-archive-spec.md ) | Yes | Yes |
66
+ | [Twee 3 (2021 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md ) | Yes | Yes |
67
+ | [Twine 2 JSON (2023 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-jsonoutput-doc.md ) | Yes | Yes |
68
+ | [Twine 2 Story Format (2015 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-storyformats-spec.md ) | Yes | Yes |
68
69
 
69
70
  **Note:** Round-trip translations can present problems because of required fields and properties per format. Some metadata may be added or removed based on the specification being followed.
70
71
 
@@ -86,6 +87,8 @@ An object must be created using either the `new` keyword in JavaScript or as the
86
87
 
87
88
  Story and Passage objects can generate multiple output formats: `toTwee()`, `toTwine1HTML()`, `toTwine2HTML()`, and `toJSON()`. Stories cannot be played in a browser without the corresponding compiler combining it with story format data.
88
89
 
90
+ The StoryFormat object supports `toString()` method of producing a tab-separated JSON output and `toJSON()` method of generating JSON output matching the Twine 2 Story Format Specification.
91
+
89
92
  <p align="right">(<a href="#readme-top">back to top</a>)</p>
90
93
 
91
94
  ### Parsers
@@ -110,8 +113,11 @@ Compiles story, story formats, or other data into an archive or playable format.
110
113
  - `compileTwine2HTML()`
111
114
  - `compileTwine1HTML()`
112
115
  - `compileTwine2ArchiveHTML()`
116
+ - `compileStoryFormat()`
117
+
118
+ To create playable Twine 1 HTML, an `engine.js` file must be supplied. (See [Story Format Archive](https://github.com/videlais/story-formats-archive/tree/main/official/twine1/engine/1.4.2) for available Twine 1 `engine.js` file.)
113
119
 
114
- **Note:** In order to create playable Twine 1 HTML, an `engine.js` file must be supplied.
120
+ Compilation of a story format adds the necessary function wrapper to convert the JSON output, via `toJSON()`, to JSONP.
115
121
 
116
122
  ### Support Functionality
117
123
 
@@ -129,7 +135,9 @@ Extwee has [documentation hosted on GitHub Pages](https://videlais.github.io/ext
129
135
 
130
136
  ## Command-Line Usage
131
137
 
132
- Extwee supports a command-line interface for four general scenarios:
138
+ Extwee supports a command-line interface for four general scenarios.
139
+
140
+ **Notes:** As of Extwee 2.2.5, short and long command-line option flags are separated. Short options use one hyphen followed by one character and all long options begin with two hyphens and a name as word.
133
141
 
134
142
  ### Compiling Twee 3 + Twine 2 Story Format into Twine 2 HTML
135
143
 
@@ -145,10 +153,12 @@ De-compile Twine 2 HTML into Twee 3:
145
153
 
146
154
  ### Compiling Twee 3 into Twine 1 HTML
147
155
 
148
- Enabling Twine 1 mode requires using the `--twine1` flag.
156
+ Enabling Twine 1 mode requires using the `--twine1` long flag.
149
157
 
150
158
  Because Twine 1 story formats can be split across files, compilation requires the "engine" from Twine 1 named `engine.js`, the name of the story format, and then its `header.html` template code and the optional but often included `code.js` file.
151
159
 
160
+ (Refer to the [Story Formats Archive](https://github.com/videlais/story-formats-archive) for access to historic `engine.js` and other files.)
161
+
152
162
  `extwee --twine1 -c -i <tweeFile> -o <Twine1HTML> --engine <engineJS> --name <storyFormatName> --codejs <CodeJS> --header <header>`
153
163
 
154
164
  ### De-compiling Twine 1 HTML into Twee 3
@@ -159,15 +169,11 @@ Enabling Twine 1 mode requires using the `--twine1` flag.
159
169
 
160
170
  <p align="right">(<a href="#readme-top">back to top</a>)</p>
161
171
 
162
- ## Roadmap
163
-
164
- Each major version has its own GitHub project:
165
-
166
- - [Road to Extwee 2.4.0](https://github.com/users/videlais/projects/4)
167
-
168
172
  ## Tree Shaking Support
169
173
 
170
- For [advanced tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) patterns, most formats are broke into `compile.js` and `parse.js` files exporting associated `compile()` and `parse()` functions. When using the API, it is possible to only import a single function or object to produce smaller and potentially faster code in projects dependent on Extwee functionality.
174
+ For [advanced tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) patterns, most formats are broke into `compile.js` and `parse.js` files exporting associated `compile()` and `parse()` functions.
175
+
176
+ When using the API, it is possible to only import a single function or object to produce smaller and potentially faster code in projects dependent on Extwee functionality.
171
177
 
172
178
  <p align="right">(<a href="#readme-top">back to top</a>)</p>
173
179
 
package/index.js CHANGED
@@ -8,6 +8,7 @@ import { parse as parseTWS } from './src/TWS/parse.js';
8
8
  import { compile as compileTwine1HTML } from './src/Twine1HTML/compile.js';
9
9
  import { compile as compileTwine2HTML } from './src/Twine2HTML/compile.js';
10
10
  import { compile as compileTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/compile.js';
11
+ import { compile as compileStoryFormat } from './src/StoryFormat/compile.js';
11
12
  import { generate as generateIFID } from './src/IFID/generate.js';
12
13
  import { Story } from './src/Story.js';
13
14
  import Passage from './src/Passage.js';
@@ -24,6 +25,7 @@ export {
24
25
  compileTwine1HTML,
25
26
  compileTwine2HTML,
26
27
  compileTwine2ArchiveHTML,
28
+ compileStoryFormat,
27
29
  generateIFID,
28
30
  Story,
29
31
  Passage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "extwee",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "description": "A story compiler tool using Twine-compatible formats",
5
5
  "author": "Dan Cox",
6
6
  "main": "index.js",
@@ -30,28 +30,29 @@
30
30
  "node-html-parser": "^7.0.1",
31
31
  "pickleparser": "^0.2.1",
32
32
  "semver": "^7.7.1",
33
- "uuid": "^11.0.5"
33
+ "uuid": "^11.1.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@babel/cli": "^7.26.4",
37
- "@babel/core": "^7.26.8",
38
- "@babel/preset-env": "^7.26.8",
39
- "@eslint/js": "^9.20.0",
37
+ "@babel/core": "^7.26.10",
38
+ "@babel/preset-env": "^7.26.9",
39
+ "@eslint/js": "^9.22.0",
40
+ "@types/semver": "^7.5.8",
40
41
  "@types/uuid": "^10.0.0",
41
- "babel-loader": "^9.2.1",
42
+ "babel-loader": "^10.0.0",
42
43
  "clean-jsdoc-theme": "^4.3.0",
43
- "core-js": "^3.40.0",
44
- "esbuild": "^0.25.0",
45
- "eslint": "^9.20.0",
44
+ "core-js": "^3.41.0",
45
+ "esbuild": "^0.25.1",
46
+ "eslint": "^9.22.0",
46
47
  "eslint-plugin-jest": "^28.11.0",
47
- "eslint-plugin-jsdoc": "^50.6.3",
48
- "globals": "^15.14.0",
48
+ "eslint-plugin-jsdoc": "^50.6.6",
49
+ "globals": "^16.0.0",
49
50
  "jest": "^29.7.0",
50
51
  "regenerator-runtime": "^0.14.1",
51
- "shelljs": "^0.8.5",
52
- "typescript": "^5.7.3",
53
- "typescript-eslint": "^8.23.0",
54
- "webpack": "^5.97.1",
52
+ "shelljs": "^0.9.1",
53
+ "typescript": "^5.8.2",
54
+ "typescript-eslint": "^8.26.1",
55
+ "webpack": "^5.98.0",
55
56
  "webpack-cli": "^6.0.1"
56
57
  },
57
58
  "repository": {
package/src/Story.js CHANGED
@@ -5,7 +5,7 @@ import { encode } from 'html-entities';
5
5
  const creatorName = 'extwee';
6
6
 
7
7
  // Set the creator version.
8
- const creatorVersion = '2.2.5';
8
+ const creatorVersion = '2.2.6';
9
9
 
10
10
  /**
11
11
  * Story class.
@@ -781,6 +781,15 @@ class Story {
781
781
  PIDcounter++;
782
782
  });
783
783
 
784
+ // Generate <tw-tag> elements for each tag, if any.
785
+ const tagList = Object.keys(this.tagColors);
786
+
787
+ // For each tag, generate a <tw-tag> element.
788
+ tagList.forEach((tag) => {
789
+ // Add the <tw-tag> element.
790
+ storyData += `\t<tw-tag name="${tag}" color="${this.tagColors[tag]}"></tw-tag>\n`;
791
+ });
792
+
784
793
  // Close the HTML element.
785
794
  storyData += '</tw-storydata>';
786
795
 
@@ -0,0 +1,19 @@
1
+ import StoryFormat from '../StoryFormat.js';
2
+
3
+ /**
4
+ * Compiles a {@link StoryFormat} object into a JSONP string for writing to a `format.js` file.
5
+ * @see {@link https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-storyformats-spec.md Twine 2 Story Formats Specification}
6
+ * @param {StoryFormat} storyFormat Story format object to compile.
7
+ * @returns {string} JSONP string.
8
+ */
9
+ function compile (storyFormat) {
10
+ // Test if storyFormat is a StoryFormat object.
11
+ if (!(storyFormat instanceof StoryFormat)) {
12
+ throw new TypeError('Error: Incoming object is not a storyFormat object');
13
+ }
14
+
15
+ // Create a JSONP string wrapped with the function window.StoryFormat.
16
+ return `window.storyFormat(${JSON.stringify(storyFormat)})`;
17
+ }
18
+
19
+ export { compile };
@@ -1,3 +1,5 @@
1
+ import { valid } from 'semver';
2
+
1
3
  /**
2
4
  * StoryFormat representing a Twine 2 story format.
3
5
  *
@@ -85,6 +87,19 @@ export default class StoryFormat {
85
87
  */
86
88
  #_source = '';
87
89
 
90
+ // Constructor with all parameters.
91
+ constructor(name = 'Untitled Story Format', version = '', description = '', author = '', image = '', url = '', license = '', proofing = false, source = '') {
92
+ this.name = name;
93
+ this.version = version;
94
+ this.description = description;
95
+ this.author = author;
96
+ this.image = image;
97
+ this.url = url;
98
+ this.license = license;
99
+ this.proofing = proofing;
100
+ this.source = source;
101
+ }
102
+
88
103
  /**
89
104
  * Name
90
105
  * @returns {string} Name.
@@ -246,4 +261,40 @@ export default class StoryFormat {
246
261
  toString() {
247
262
  return JSON.stringify(this, null, "\t");
248
263
  }
264
+
265
+ /**
266
+ * Produces a JSON representation of the story format object.
267
+ * @method toJSON
268
+ * @returns {object} - A JSON representation of the story format.
269
+ */
270
+ toJSON() {
271
+ // name: (string) Optional. The name of the story format. (Omitting the name will lead to an Untitled Story Format.)
272
+ // Set a default name.
273
+ let name = "Untitled Story Format";
274
+
275
+ // Check if the story format is not an empty string.
276
+ if (this.name.length > 0) {
277
+ // Update the name.
278
+ name = this.name;
279
+ }
280
+
281
+ // version: (string) Required, and semantic version-style formatting (x.y.z, e.g., 1.2.1) of the version is also required.
282
+
283
+ // Check if the version is valid. If not, throw an error.
284
+ if (!valid(this.version)) {
285
+ throw new TypeError('ERROR: Version must be a valid semantic version!');
286
+ }
287
+
288
+ return JSON.stringify({
289
+ name,
290
+ version: this.version,
291
+ description: this.description,
292
+ author: this.author,
293
+ image: this.image,
294
+ url: this.url,
295
+ license: this.license,
296
+ proofing: this.proofing,
297
+ source: this.source
298
+ });
299
+ }
249
300
  }
@@ -819,6 +819,49 @@ describe('Story', () => {
819
819
  // Expect the creator to be encoded.
820
820
  expect(result.includes(`creator="${creatorName}"`)).not.toBe(true);
821
821
  });
822
+
823
+ it('Should not encode story tag colors if none are present', () => {
824
+ // Add passage.
825
+ s.addPassage(new Passage('Start', 'Word'));
826
+ // Set start.
827
+ s.start = 'Start';
828
+ // Create HTML.
829
+ const result = s.toTwine2HTML();
830
+ // Expect the tag colors to not be encoded if none are present.
831
+ expect(result.includes('<tw-tag')).toBe(false);
832
+ });
833
+
834
+ it('Should encode single story tag color', () => {
835
+ // Add passage.
836
+ s.addPassage(new Passage('Start', 'Word'));
837
+ // Set start.
838
+ s.start = 'Start';
839
+ // Add tag colors.
840
+ s.tagColors = {
841
+ bar: 'green'
842
+ };
843
+ // Create HTML.
844
+ const result = s.toTwine2HTML();
845
+ // Expect the tag colors to be encoded.
846
+ expect(result.includes('<tw-tag name="bar" color="green"></tw-tag>')).toBe(true);
847
+ });
848
+
849
+ it('Should encode multiple story tag colors', () => {
850
+ // Add passage.
851
+ s.addPassage(new Passage('Start', 'Word'));
852
+ // Set start.
853
+ s.start = 'Start';
854
+ // Add tag colors.
855
+ s.tagColors = {
856
+ bar: 'green',
857
+ foo: 'red'
858
+ };
859
+ // Create HTML.
860
+ const result = s.toTwine2HTML();
861
+ // Expect the tag colors to be encoded.
862
+ expect(result.includes('<tw-tag name="bar" color="green"></tw-tag>')).toBe(true);
863
+ expect(result.includes('<tw-tag name="foo" color="red"></tw-tag>')).toBe(true);
864
+ });
822
865
  });
823
866
 
824
867
  describe('toTwine1HTML()', function () {
@@ -15,6 +15,29 @@ describe('StoryFormat', () => {
15
15
  });
16
16
  });
17
17
 
18
+ describe('Constructor with default values', () => {
19
+ it('Should create an instance of StoryFormat', () => {
20
+ const sf = new StoryFormat();
21
+ expect(sf).toBeInstanceOf(StoryFormat);
22
+ });
23
+ });
24
+
25
+ describe('Constructor with parameters', () => {
26
+ it('Should create an instance of StoryFormat with parameters', () => {
27
+ const sf = new StoryFormat('name', 'version', 'description', 'author', 'image', 'url', 'license', true, 'source');
28
+ expect(sf).toBeInstanceOf(StoryFormat);
29
+ expect(sf.name).toBe('name');
30
+ expect(sf.version).toBe('version');
31
+ expect(sf.description).toBe('description');
32
+ expect(sf.author).toBe('author');
33
+ expect(sf.image).toBe('image');
34
+ expect(sf.url).toBe('url');
35
+ expect(sf.license).toBe('license');
36
+ expect(sf.proofing).toBe(true);
37
+ expect(sf.source).toBe('source');
38
+ });
39
+ });
40
+
18
41
  describe('name', () => {
19
42
  it('Set new String', () => {
20
43
  const sf = new StoryFormat();
@@ -153,7 +176,44 @@ describe('StoryFormat', () => {
153
176
  describe('toString', () => {
154
177
  it('Should return string representation', () => {
155
178
  const sf = new StoryFormat();
179
+ sf.version = '1.0.0';
156
180
  expect(sf.toString()).toBe(JSON.stringify(sf, null, '\t'));
157
181
  });
182
+
183
+ it('Should throw error if version is not valid', () => {
184
+ const sf = new StoryFormat();
185
+ expect(() => {
186
+ sf.toString();
187
+ }).toThrow();
188
+ });
189
+ });
190
+
191
+ describe('toJSON', () => {
192
+ it('Should return JSON representation with default name', () => {
193
+ const sf = new StoryFormat();
194
+ sf.version = '1.0.0';
195
+ expect(sf.toJSON().includes("Untitled Story Format")).toEqual(true);
196
+ });
197
+
198
+ it('Should return JSON representation with custom name', () => {
199
+ const sf = new StoryFormat();
200
+ sf.name = 'Custom Name';
201
+ sf.version = '1.0.0';
202
+ expect(sf.toJSON().includes("Custom Name")).toEqual(true);
203
+ });
204
+
205
+ it('Should overwrite with default name if name is empty string', () => {
206
+ const sf = new StoryFormat();
207
+ sf.name = '';
208
+ sf.version = '1.0.0';
209
+ expect(sf.toJSON().includes("Untitled Story Format")).toEqual(true);
210
+ });
211
+
212
+ it('Should throw error if version is not valid', () => {
213
+ const sf = new StoryFormat();
214
+ expect(() => {
215
+ sf.toJSON();
216
+ }).toThrow();
217
+ });
158
218
  });
159
219
  });
package/types/Story.d.ts CHANGED
@@ -226,5 +226,5 @@ export class Story {
226
226
  #private;
227
227
  }
228
228
  export const creatorName: "extwee";
229
- export const creatorVersion: "2.2.5";
229
+ export const creatorVersion: "2.2.6";
230
230
  import Passage from './Passage.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Compiles a {@link StoryFormat} object into a JSONP string for writing to a `format.js` file.
3
+ * @see {@link https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-storyformats-spec.md Twine 2 Story Formats Specification}
4
+ * @param {StoryFormat} storyFormat Story format object to compile.
5
+ * @returns {string} JSONP string.
6
+ */
7
+ export function compile(storyFormat: StoryFormat): string;
8
+ import StoryFormat from '../StoryFormat.js';
@@ -30,6 +30,7 @@
30
30
  * sf.source = 'New';
31
31
  */
32
32
  export default class StoryFormat {
33
+ constructor(name?: string, version?: string, description?: string, author?: string, image?: string, url?: string, license?: string, proofing?: boolean, source?: string);
33
34
  /**
34
35
  * @param {string} n - Replacement name.
35
36
  */
@@ -117,5 +118,11 @@ export default class StoryFormat {
117
118
  * @returns {string} - A string representation of the story format.
118
119
  */
119
120
  toString(): string;
121
+ /**
122
+ * Produces a JSON representation of the story format object.
123
+ * @method toJSON
124
+ * @returns {object} - A JSON representation of the story format.
125
+ */
126
+ toJSON(): object;
120
127
  #private;
121
128
  }