extwee 2.3.10 → 2.3.12

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 (180) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/CONTRIBUTING.md +305 -0
  3. package/README.md +16 -0
  4. package/build/extwee.core.min.js +1 -1
  5. package/build/extwee.twine1html.min.js +1 -1
  6. package/build/extwee.twine2archive.min.js +1 -1
  7. package/build/extwee.tws.min.js +1 -1
  8. package/package.json +8 -5
  9. package/src/Passage.js +8 -9
  10. package/src/Story.js +2 -2
  11. package/types/src/Story.d.ts +1 -1
  12. package/.github/FUNDING.yml +0 -3
  13. package/.github/codeql-analysis.yml +0 -51
  14. package/.github/dependabot.yml +0 -11
  15. package/.github/workflows/dependabot-automerge.yml +0 -23
  16. package/.github/workflows/nodejs.yml +0 -28
  17. package/.travis.yml +0 -13
  18. package/babel.config.json +0 -18
  19. package/docs/.nojekyll +0 -0
  20. package/docs/README.md +0 -224
  21. package/docs/_sidebar.md +0 -19
  22. package/docs/build/extwee.core.min.js +0 -1
  23. package/docs/build/extwee.twine1html.min.js +0 -1
  24. package/docs/build/extwee.twine2archive.min.js +0 -1
  25. package/docs/build/extwee.tws.min.js +0 -1
  26. package/docs/demos/compiler/extwee.core.min.js +0 -1
  27. package/docs/demos/compiler/index.css +0 -105
  28. package/docs/demos/compiler/index.html +0 -359
  29. package/docs/demos/decompile/extwee.core.min.js +0 -1
  30. package/docs/demos/decompile/index.css +0 -584
  31. package/docs/demos/decompile/index.html +0 -468
  32. package/docs/examples/dynamicPassages.md +0 -28
  33. package/docs/examples/jsonToTwee.md +0 -23
  34. package/docs/examples/twsToTwee.md +0 -25
  35. package/docs/formats/json.md +0 -17
  36. package/docs/formats/twee.md +0 -13
  37. package/docs/formats/twine1HTML.md +0 -13
  38. package/docs/formats/twine2ArchiveHTML.md +0 -13
  39. package/docs/formats/twine2HTML.md +0 -13
  40. package/docs/formats/tws.md +0 -9
  41. package/docs/index.html +0 -26
  42. package/docs/install/npm.md +0 -16
  43. package/docs/install/npx.md +0 -79
  44. package/docs/objects/passage.md +0 -47
  45. package/docs/objects/story.md +0 -69
  46. package/docs/objects/storyformat.md +0 -27
  47. package/eslint.config.js +0 -28
  48. package/extwee.config.json +0 -6
  49. package/extwee.config.md +0 -67
  50. package/jest.config.json +0 -5
  51. package/test/CLI/CLI.test.js +0 -49
  52. package/test/CLI/files/example.json +0 -1
  53. package/test/CLI/files/example6.twee +0 -22
  54. package/test/CLI/files/harlowe.js +0 -3
  55. package/test/CLI/files/input.html +0 -47
  56. package/test/CLI/files/output/test.twee +0 -0
  57. package/test/CLI/files/test.twee +0 -18
  58. package/test/CLI/files/tweeExample.twee +0 -17
  59. package/test/CLI/files/twine1/LICENSE.txt +0 -32
  60. package/test/CLI/files/twine1/code.js +0 -5
  61. package/test/CLI/files/twine1/engine.js +0 -43
  62. package/test/CLI/files/twine1/header.html +0 -325
  63. package/test/CLI/files/twine1Test.html +0 -371
  64. package/test/CLI/files/twineExample.html +0 -16
  65. package/test/Config/Config.test.js +0 -76
  66. package/test/Config/files/empty.json +0 -3
  67. package/test/Config/files/full.json +0 -8
  68. package/test/Config/files/invalid.json +0 -1
  69. package/test/Config/files/valid.json +0 -6
  70. package/test/Config/isDirectory.test.js +0 -50
  71. package/test/Config/isFile.test.js +0 -53
  72. package/test/Config/loadStoryFormat.test.js +0 -117
  73. package/test/Config/readDirectories.test.js +0 -78
  74. package/test/IFID/IFID.Generate.test.js +0 -10
  75. package/test/JSON/JSON.Parse.test.js +0 -316
  76. package/test/Objects/Passage.test.js +0 -270
  77. package/test/Objects/SnowmanCompatibility.test.js +0 -125
  78. package/test/Objects/Story.test.js +0 -1075
  79. package/test/Objects/StoryFormat.test.js +0 -219
  80. package/test/Roundtrip/Files/Example1.html +0 -64
  81. package/test/Roundtrip/Files/LICENSE +0 -19
  82. package/test/Roundtrip/Files/example1.twee +0 -10
  83. package/test/Roundtrip/Files/example2.twee +0 -27
  84. package/test/Roundtrip/Files/example4.twee +0 -27
  85. package/test/Roundtrip/Files/harlowe.js +0 -3
  86. package/test/Roundtrip/Files/round.html +0 -49
  87. package/test/Roundtrip/Roundtrip.test.js +0 -54
  88. package/test/StoryFormat/StoryFormat.Parse.test.js +0 -479
  89. package/test/TWS/Parse.test.js +0 -56
  90. package/test/TWS/TWSParser/Example5.tws +0 -414
  91. package/test/TWS/TWSParser/noscale.tws +0 -0
  92. package/test/TWS/TWSParser/nostory.tws +0 -0
  93. package/test/Twee/Twee.Escaping.test.js +0 -200
  94. package/test/Twee/Twee.Parse.test.js +0 -108
  95. package/test/Twee/TweeParser/cursed.twee +0 -16
  96. package/test/Twee/TweeParser/cycling.twee +0 -75
  97. package/test/Twee/TweeParser/emptytags.twee +0 -2
  98. package/test/Twee/TweeParser/example.twee +0 -32
  99. package/test/Twee/TweeParser/malformed.twee +0 -2
  100. package/test/Twee/TweeParser/missing.twee +0 -19
  101. package/test/Twee/TweeParser/multipleScriptPassages.twee +0 -19
  102. package/test/Twee/TweeParser/multipleStyleTag.twee +0 -19
  103. package/test/Twee/TweeParser/multipletags.twee +0 -10
  104. package/test/Twee/TweeParser/noTitle.twee +0 -2
  105. package/test/Twee/TweeParser/notes.twee +0 -16
  106. package/test/Twee/TweeParser/pasagemetadataerror.twee +0 -2
  107. package/test/Twee/TweeParser/scriptPassage.twee +0 -16
  108. package/test/Twee/TweeParser/singletag.twee +0 -13
  109. package/test/Twee/TweeParser/start.twee +0 -2
  110. package/test/Twee/TweeParser/startMetadata.twee +0 -14
  111. package/test/Twee/TweeParser/storydataerror.twee +0 -25
  112. package/test/Twee/TweeParser/style.twee +0 -16
  113. package/test/Twee/TweeParser/stylePassage.twee +0 -16
  114. package/test/Twee/TweeParser/test.twee +0 -25
  115. package/test/Twine1HTML/Twine1HTML.Compile.test.js +0 -180
  116. package/test/Twine1HTML/Twine1HTML.Parse.Web.test.js +0 -484
  117. package/test/Twine1HTML/Twine1HTML.Parse.test.js +0 -183
  118. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +0 -674
  119. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +0 -43
  120. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +0 -4
  121. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +0 -4
  122. package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +0 -1
  123. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +0 -32
  124. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +0 -4
  125. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +0 -327
  126. package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
  127. package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +0 -6
  128. package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +0 -6
  129. package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +0 -43
  130. package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +0 -372
  131. package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +0 -372
  132. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +0 -35
  133. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.Web.test.js +0 -293
  134. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +0 -42
  135. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +0 -6
  136. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +0 -3
  137. package/test/Twine2HTML/Twine2HTML.Compile.test.js +0 -139
  138. package/test/Twine2HTML/Twine2HTML.Parse.Web.test.js +0 -329
  139. package/test/Twine2HTML/Twine2HTML.Parse.test.js +0 -192
  140. package/test/Twine2HTML/Twine2HTMLCompiler/TestTags.html +0 -42
  141. package/test/Twine2HTML/Twine2HTMLCompiler/creator.html +0 -50
  142. package/test/Twine2HTML/Twine2HTMLCompiler/example6.twee +0 -16
  143. package/test/Twine2HTML/Twine2HTMLCompiler/format.js +0 -9
  144. package/test/Twine2HTML/Twine2HTMLCompiler/missingStoryTitle.twee +0 -29
  145. package/test/Twine2HTML/Twine2HTMLCompiler/test11.html +0 -121
  146. package/test/Twine2HTML/Twine2HTMLCompiler/test2.html +0 -58
  147. package/test/Twine2HTML/Twine2HTMLCompiler/test3.html +0 -49
  148. package/test/Twine2HTML/Twine2HTMLCompiler/test4.html +0 -50
  149. package/test/Twine2HTML/Twine2HTMLCompiler/test6.html +0 -49
  150. package/test/Twine2HTML/Twine2HTMLParser/Example1.html +0 -53
  151. package/test/Twine2HTML/Twine2HTMLParser/Tags.html +0 -15
  152. package/test/Twine2HTML/Twine2HTMLParser/lyingStartnode.html +0 -15
  153. package/test/Twine2HTML/Twine2HTMLParser/lyingTagColors.html +0 -48
  154. package/test/Twine2HTML/Twine2HTMLParser/missingCreator.html +0 -11
  155. package/test/Twine2HTML/Twine2HTMLParser/missingCreatorVersion.html +0 -11
  156. package/test/Twine2HTML/Twine2HTMLParser/missingFormat.html +0 -11
  157. package/test/Twine2HTML/Twine2HTMLParser/missingFormatVersion.html +0 -11
  158. package/test/Twine2HTML/Twine2HTMLParser/missingIFID.html +0 -11
  159. package/test/Twine2HTML/Twine2HTMLParser/missingPassageTags.html +0 -15
  160. package/test/Twine2HTML/Twine2HTMLParser/missingPosition.html +0 -15
  161. package/test/Twine2HTML/Twine2HTMLParser/missingScript.html +0 -14
  162. package/test/Twine2HTML/Twine2HTMLParser/missingSize.html +0 -35
  163. package/test/Twine2HTML/Twine2HTMLParser/missingStartnode.html +0 -11
  164. package/test/Twine2HTML/Twine2HTMLParser/missingStyle.html +0 -14
  165. package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +0 -11
  166. package/test/Twine2HTML/Twine2HTMLParser/tagColors.html +0 -31
  167. package/test/Twine2HTML/Twine2HTMLParser/twineExample.html +0 -23
  168. package/test/Twine2HTML/Twine2HTMLParser/twineExample2.html +0 -15
  169. package/test/Twine2HTML/Twine2HTMLParser/twineExample3.html +0 -15
  170. package/test/Twine2HTML/Twine2HTMLParser/unescaping.html +0 -33
  171. package/test/Web/web-core-coverage.test.js +0 -175
  172. package/test/Web/web-core-global.test.js +0 -93
  173. package/test/Web/web-core.test.js +0 -156
  174. package/test/Web/web-exports.test.js +0 -136
  175. package/test/Web/web-twine1html.test.js +0 -105
  176. package/test/Web/web-twine2archive.test.js +0 -96
  177. package/test/Web/web-tws.test.js +0 -77
  178. package/test/Web/window.Extwee.test.js +0 -97
  179. package/tsconfig.json +0 -21
  180. package/webpack.config.js +0 -47
@@ -1,468 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Extwee Decompiler Demo</title>
7
- <link rel="stylesheet" href="index.css">
8
- <script src="./extwee.core.min.js"></script>
9
- </head>
10
- <body>
11
- <div class="container">
12
- <header>
13
- <h1>Extwee Decompiler Demo</h1>
14
- <p>Upload a Twine 2 HTML file to analyze its structure and extract statistics.</p>
15
- </header>
16
-
17
- <main>
18
- <section class="upload-section">
19
- <div class="file-input-container">
20
- <input type="file" id="fileInput" accept=".html,.htm" />
21
- <label for="fileInput" class="file-input-label">
22
- <span class="file-input-text">Choose Twine 2 HTML File</span>
23
- <span class="file-input-icon">📁</span>
24
- </label>
25
- </div>
26
- <div class="file-info" id="fileInfo"></div>
27
- </section>
28
-
29
- <section class="results-section" id="resultsSection" style="display: none;">
30
- <div class="tabs">
31
- <button class="tab-button active" data-tab="overview">Overview</button>
32
- <button class="tab-button" data-tab="passages">Passages</button>
33
- <button class="tab-button" data-tab="tags">Tags</button>
34
- <button class="tab-button" data-tab="metadata">Metadata</button>
35
- </div>
36
-
37
- <div class="tab-content active" id="overview">
38
- <div class="stats-grid">
39
- <div class="stat-card">
40
- <div class="stat-number" id="passageCount">0</div>
41
- <div class="stat-label">Passages</div>
42
- </div>
43
- <div class="stat-card">
44
- <div class="stat-number" id="wordCount">0</div>
45
- <div class="stat-label">Total Words</div>
46
- </div>
47
- <div class="stat-card">
48
- <div class="stat-number" id="characterCount">0</div>
49
- <div class="stat-label">Characters</div>
50
- </div>
51
- <div class="stat-card">
52
- <div class="stat-number" id="tagCount">0</div>
53
- <div class="stat-label">Unique Tags</div>
54
- </div>
55
- </div>
56
-
57
- <div class="story-info">
58
- <h3>Story Information</h3>
59
- <div class="info-grid">
60
- <div class="info-item">
61
- <label>Title:</label>
62
- <span id="storyTitle">-</span>
63
- </div>
64
- <div class="info-item">
65
- <label>IFID:</label>
66
- <span id="storyIFID">-</span>
67
- </div>
68
- <div class="info-item">
69
- <label>Format:</label>
70
- <span id="storyFormat">-</span>
71
- </div>
72
- <div class="info-item">
73
- <label>Format Version:</label>
74
- <span id="storyFormatVersion">-</span>
75
- </div>
76
- <div class="info-item">
77
- <label>Start Passage:</label>
78
- <span id="startPassage">-</span>
79
- </div>
80
- <div class="info-item">
81
- <label>Creator:</label>
82
- <span id="storyCreator">-</span>
83
- </div>
84
- </div>
85
- </div>
86
- </div>
87
-
88
- <div class="tab-content" id="passages">
89
- <div class="passages-header">
90
- <h3>Passages (<span id="passageListCount">0</span>)</h3>
91
- <div class="search-container">
92
- <input type="text" id="passageSearch" placeholder="Search passages...">
93
- </div>
94
- </div>
95
- <div class="passages-list" id="passagesList"></div>
96
- </div>
97
-
98
- <div class="tab-content" id="tags">
99
- <div class="tags-header">
100
- <h3>Tags Overview</h3>
101
- </div>
102
- <div class="tags-container" id="tagsContainer"></div>
103
- </div>
104
-
105
- <div class="tab-content" id="metadata">
106
- <div class="metadata-container">
107
- <h3>Technical Details</h3>
108
- <div class="metadata-content" id="metadataContent"></div>
109
- </div>
110
- </div>
111
- </section>
112
-
113
- <section class="error-section" id="errorSection" style="display: none;">
114
- <div class="error-message">
115
- <h3>Error</h3>
116
- <p id="errorText"></p>
117
- </div>
118
- </section>
119
- </main>
120
- </div>
121
-
122
- <script>
123
- class TwineDecompiler {
124
- constructor() {
125
- this.story = null;
126
- this.initializeEventListeners();
127
- }
128
-
129
- initializeEventListeners() {
130
- const fileInput = document.getElementById('fileInput');
131
- const tabButtons = document.querySelectorAll('.tab-button');
132
- const passageSearch = document.getElementById('passageSearch');
133
-
134
- fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
135
-
136
- tabButtons.forEach(button => {
137
- button.addEventListener('click', (e) => this.switchTab(e.target.dataset.tab));
138
- });
139
-
140
- passageSearch.addEventListener('input', (e) => this.filterPassages(e.target.value));
141
- }
142
-
143
- async handleFileUpload(event) {
144
- const file = event.target.files[0];
145
- if (!file) return;
146
-
147
- this.showFileInfo(file);
148
- this.hideError();
149
- this.hideResults();
150
-
151
- try {
152
- const content = await this.readFile(file);
153
-
154
- // First, let's validate that this looks like Twine 2 HTML
155
- if (!content.includes('<tw-storydata')) {
156
- throw new Error('Not Twine 2 HTML content');
157
- }
158
-
159
- this.story = Extwee.parseTwine2HTML(content);
160
- this.displayResults();
161
- } catch (error) {
162
- console.error('Parsing error:', error);
163
-
164
- // Provide more helpful error messages for common issues
165
- let errorMessage = error.message;
166
- if (error.message.includes('Story name must be a string')) {
167
- errorMessage = 'The Twine 2 HTML file has an invalid story name attribute. This usually means the file is corrupted or not a standard Twine 2 HTML export.';
168
- } else if (error.message.includes('Not Twine 2 HTML content')) {
169
- errorMessage = 'This file does not appear to be a valid Twine 2 HTML export. Please make sure you uploaded a file exported from Twine 2 using "Publish to File".';
170
- } else if (error.message.includes('Content is not a string')) {
171
- errorMessage = 'Unable to read the file content. Please make sure the file is not corrupted and is a text-based HTML file.';
172
- } else if (error.message.includes('Passages are required to have PID')) {
173
- errorMessage = 'The Twine 2 HTML file contains passages without proper IDs. This suggests the file may be corrupted or manually edited.';
174
- }
175
-
176
- this.showError(errorMessage);
177
- }
178
- }
179
-
180
- readFile(file) {
181
- return new Promise((resolve, reject) => {
182
- const reader = new FileReader();
183
- reader.onload = (e) => resolve(e.target.result);
184
- reader.onerror = (e) => reject(new Error('Failed to read file'));
185
- reader.readAsText(file);
186
- });
187
- }
188
-
189
- showFileInfo(file) {
190
- const fileInfo = document.getElementById('fileInfo');
191
- const fileSize = (file.size / 1024).toFixed(2);
192
- const lastModified = new Date(file.lastModified).toLocaleString();
193
- fileInfo.innerHTML = `
194
- <strong>File:</strong> ${this.escapeHtml(file.name)} (${fileSize} KB)<br>
195
- <strong>Last Modified:</strong> ${this.escapeHtml(lastModified)}
196
- `;
197
- }
198
-
199
- displayResults() {
200
- this.showResults();
201
- this.populateOverview();
202
- this.populatePassages();
203
- this.populateTags();
204
- this.populateMetadata();
205
- }
206
-
207
- populateOverview() {
208
- const passages = this.story.passages;
209
- const allText = passages.map(p => p.text).join(' ');
210
- const wordCount = this.countWords(allText);
211
- const characterCount = allText.length;
212
- const uniqueTags = this.getUniqueTags(passages);
213
-
214
- document.getElementById('passageCount').textContent = passages.length;
215
- document.getElementById('wordCount').textContent = wordCount.toLocaleString();
216
- document.getElementById('characterCount').textContent = characterCount.toLocaleString();
217
- document.getElementById('tagCount').textContent = uniqueTags.length;
218
-
219
- document.getElementById('storyTitle').textContent = this.story.name || 'Untitled';
220
- document.getElementById('storyIFID').textContent = this.story.IFID || 'None';
221
- document.getElementById('storyFormat').textContent = this.story.format || 'Unknown';
222
- document.getElementById('storyFormatVersion').textContent = this.story.formatVersion || 'Unknown';
223
- document.getElementById('startPassage').textContent = this.story.start || 'None';
224
- document.getElementById('storyCreator').textContent = this.story.creator || 'Unknown';
225
- }
226
-
227
- populatePassages() {
228
- const passagesList = document.getElementById('passagesList');
229
- const passageListCount = document.getElementById('passageListCount');
230
-
231
- passageListCount.textContent = this.story.passages.length;
232
- passagesList.innerHTML = '';
233
-
234
- this.story.passages.forEach((passage, index) => {
235
- const passageElement = this.createPassageElement(passage, index);
236
- passagesList.appendChild(passageElement);
237
- });
238
- }
239
-
240
- createPassageElement(passage, index) {
241
- const div = document.createElement('div');
242
- div.className = 'passage-item';
243
-
244
- const wordCount = this.countWords(passage.text);
245
- const characterCount = passage.text.length;
246
- const tagsHtml = passage.tags.length > 0
247
- ? passage.tags.map(tag => `<span class="tag">${tag}</span>`).join('')
248
- : '<span class="no-tags">No tags</span>';
249
-
250
- div.innerHTML = `
251
- <div class="passage-header">
252
- <h4 class="passage-name">${this.escapeHtml(passage.name)}</h4>
253
- <div class="passage-stats">
254
- <span class="stat">${wordCount} words</span>
255
- <span class="stat">${characterCount} chars</span>
256
- </div>
257
- </div>
258
- <div class="passage-tags">${tagsHtml}</div>
259
- <div class="passage-preview">
260
- ${this.createTextPreview(passage.text)}
261
- </div>
262
- `;
263
-
264
- return div;
265
- }
266
-
267
- populateTags() {
268
- const tagsContainer = document.getElementById('tagsContainer');
269
- const tagStats = this.getTagStatistics();
270
-
271
- if (tagStats.length === 0) {
272
- tagsContainer.innerHTML = '<p class="no-data">No tags found in this story.</p>';
273
- return;
274
- }
275
-
276
- tagsContainer.innerHTML = '';
277
-
278
- tagStats.forEach(tagStat => {
279
- const tagElement = document.createElement('div');
280
- tagElement.className = 'tag-stat';
281
-
282
- const color = this.story.tagColors[tagStat.name] || '#666';
283
-
284
- tagElement.innerHTML = `
285
- <div class="tag-stat-header">
286
- <span class="tag-name" style="border-left: 4px solid ${color};">${tagStat.name}</span>
287
- <span class="tag-count">${tagStat.count} passage${tagStat.count !== 1 ? 's' : ''}</span>
288
- </div>
289
- <div class="tag-passages">
290
- ${tagStat.passages.map(p => `<span class="passage-link">${this.escapeHtml(p)}</span>`).join(', ')}
291
- </div>
292
- `;
293
-
294
- tagsContainer.appendChild(tagElement);
295
- });
296
- }
297
-
298
- populateMetadata() {
299
- const metadataContent = document.getElementById('metadataContent');
300
-
301
- const metadata = {
302
- 'Story Properties': {
303
- 'Name': this.story.name,
304
- 'IFID': this.story.IFID,
305
- 'Start Passage': this.story.start,
306
- 'Format': this.story.format,
307
- 'Format Version': this.story.formatVersion,
308
- 'Creator': this.story.creator,
309
- 'Creator Version': this.story.creatorVersion,
310
- 'Zoom Level': this.story.zoom
311
- },
312
- 'Statistics': {
313
- 'Total Passages': this.story.passages.length,
314
- 'Total Words': this.countWords(this.story.passages.map(p => p.text).join(' ')),
315
- 'Total Characters': this.story.passages.map(p => p.text).join('').length,
316
- 'Unique Tags': new Set(this.story.passages.flatMap(p => p.tags)).size,
317
- 'Average Words per Passage': Math.round(this.countWords(this.story.passages.map(p => p.text).join(' ')) / this.story.passages.length)
318
- }
319
- };
320
-
321
- if (Object.keys(this.story.metadata).length > 0) {
322
- metadata['Additional Metadata'] = this.story.metadata;
323
- }
324
-
325
- metadataContent.innerHTML = '';
326
-
327
- Object.entries(metadata).forEach(([section, data]) => {
328
- const sectionDiv = document.createElement('div');
329
- sectionDiv.className = 'metadata-section';
330
-
331
- const sectionTitle = document.createElement('h4');
332
- sectionTitle.textContent = section;
333
- sectionDiv.appendChild(sectionTitle);
334
-
335
- const dataList = document.createElement('dl');
336
- dataList.className = 'metadata-list';
337
-
338
- Object.entries(data).forEach(([key, value]) => {
339
- const dt = document.createElement('dt');
340
- dt.textContent = key;
341
- const dd = document.createElement('dd');
342
- dd.textContent = value || 'N/A';
343
-
344
- dataList.appendChild(dt);
345
- dataList.appendChild(dd);
346
- });
347
-
348
- sectionDiv.appendChild(dataList);
349
- metadataContent.appendChild(sectionDiv);
350
- });
351
- }
352
-
353
- getTagStatistics() {
354
- const tagMap = new Map();
355
-
356
- this.story.passages.forEach(passage => {
357
- passage.tags.forEach(tag => {
358
- if (!tagMap.has(tag)) {
359
- tagMap.set(tag, []);
360
- }
361
- tagMap.get(tag).push(passage.name);
362
- });
363
- });
364
-
365
- return Array.from(tagMap.entries())
366
- .map(([name, passages]) => ({
367
- name,
368
- count: passages.length,
369
- passages: passages.sort()
370
- }))
371
- .sort((a, b) => b.count - a.count);
372
- }
373
-
374
- countWords(text) {
375
- if (!text) return 0;
376
- // Safely remove HTML tags using DOM parsing for security
377
- const tempDiv = document.createElement('div');
378
- tempDiv.innerHTML = text;
379
- const cleanText = (tempDiv.textContent || tempDiv.innerText || '').trim();
380
- return cleanText ? cleanText.split(/\s+/).length : 0;
381
- }
382
-
383
- getUniqueTags(passages) {
384
- const tags = new Set();
385
- passages.forEach(passage => {
386
- passage.tags.forEach(tag => tags.add(tag));
387
- });
388
- return Array.from(tags);
389
- }
390
-
391
- createTextPreview(text, maxLength = 200) {
392
- if (!text) return '<em>Empty passage</em>';
393
-
394
- // Safely remove HTML tags using DOM parsing for security
395
- const tempDiv = document.createElement('div');
396
- tempDiv.innerHTML = text;
397
- const cleanText = tempDiv.textContent || tempDiv.innerText || '';
398
- const trimmedText = cleanText.trim();
399
-
400
- if (trimmedText.length <= maxLength) {
401
- return this.escapeHtml(trimmedText);
402
- }
403
-
404
- return this.escapeHtml(trimmedText.substring(0, maxLength)) + '...';
405
- }
406
-
407
- filterPassages(searchTerm) {
408
- const passageItems = document.querySelectorAll('.passage-item');
409
- const term = searchTerm.toLowerCase();
410
-
411
- passageItems.forEach(item => {
412
- const passageName = item.querySelector('.passage-name').textContent.toLowerCase();
413
- const passageText = item.querySelector('.passage-preview').textContent.toLowerCase();
414
- const passageTags = Array.from(item.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase());
415
-
416
- const matches = passageName.includes(term) ||
417
- passageText.includes(term) ||
418
- passageTags.some(tag => tag.includes(term));
419
-
420
- item.style.display = matches ? 'block' : 'none';
421
- });
422
- }
423
-
424
- switchTab(tabName) {
425
- // Update tab buttons
426
- document.querySelectorAll('.tab-button').forEach(btn => {
427
- btn.classList.remove('active');
428
- });
429
- document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
430
-
431
- // Update tab content
432
- document.querySelectorAll('.tab-content').forEach(content => {
433
- content.classList.remove('active');
434
- });
435
- document.getElementById(tabName).classList.add('active');
436
- }
437
-
438
- showResults() {
439
- document.getElementById('resultsSection').style.display = 'block';
440
- }
441
-
442
- hideResults() {
443
- document.getElementById('resultsSection').style.display = 'none';
444
- }
445
-
446
- showError(message) {
447
- document.getElementById('errorText').textContent = message;
448
- document.getElementById('errorSection').style.display = 'block';
449
- }
450
-
451
- hideError() {
452
- document.getElementById('errorSection').style.display = 'none';
453
- }
454
-
455
- escapeHtml(text) {
456
- const div = document.createElement('div');
457
- div.textContent = text;
458
- return div.innerHTML;
459
- }
460
- }
461
-
462
- // Initialize the decompiler when the page loads
463
- document.addEventListener('DOMContentLoaded', () => {
464
- new TwineDecompiler();
465
- });
466
- </script>
467
- </body>
468
- </html>
@@ -1,28 +0,0 @@
1
- # Dynamically Generating Passages
2
-
3
- Through using the API, it is possible to dynamically create passages and then export this story into Twee (or JSON) using only the **Story** and **Passage** objects.
4
-
5
- Both **Story** and **Passage** objects can be created through the `new` keyword in JavaScript. After creating a **Story**, the use of the method `addPassage(Passage)` can be used to add new passages.
6
-
7
- In the following example, the use of a `for()` loop is used to generate 20 passages and a starting passage is set. Finally, the **Story** data is converted into Twee and written to an output file.
8
-
9
- ```javascript
10
- // Import only Story and Passage.
11
- import { Story, Passage } from 'extwee';
12
- // Import only writeFileSync() for writing to files.
13
- import { writeFileSync } from 'node:fs';
14
-
15
- // Create the story.
16
- const example = new Story( 'Example' );
17
-
18
- // Generate 20 passages and add them to the story.
19
- for(let i = 0; i < 20; i++) {
20
- example.addPassage( new Passage( `Passage ${i}`, 'Some Text') );
21
- }
22
-
23
- // Set a starting passage.
24
- example.start = 'Passage 1';
25
-
26
- // Create a Twee file.
27
- writeFileSync( 'example.twee', example.toTwee() );
28
- ```
@@ -1,23 +0,0 @@
1
- # Converting Twine 2 JSON to Twee 3
2
-
3
- Conversion from Twine 2 JSON to Twee 3 requires multiple steps:
4
-
5
- 1. Read the JSON file.
6
- 2. Use `parseJSON()` to convert JSON into a **Story** object.
7
- 3. Using `Story.toTwee()`, convert the **Story** object into Twee 3.
8
-
9
- ```javascript
10
- // Import only Story and parseJSON().
11
- import { Story, parseJSON } from 'extwee';
12
- // Import only readFileSync() and writeFileSync() for reading and writing to files.
13
- import { readFileSync, writeFileSync } from 'node:fs';
14
-
15
- // Read in the JSON file
16
- const inputFile = readFileSync( 'example.json', 'utf-8' );
17
-
18
- // Convert from Twine 2 JSON to Story.
19
- const s = parseJSON(inputFile);
20
-
21
- // Write Twee output
22
- writeFileSync( 'output.twee', s.toTwee() );
23
- ```
@@ -1,25 +0,0 @@
1
- # TWS To Twee
2
-
3
- Converting from TWS to Twee 3 is similar to many other conversion processes with one small difference. TWS conversion needs to be begin from the [**Buffer** data type in JavaScript](https://nodejs.org/api/buffer.html).
4
-
5
- 1. Read the binary file.
6
- 2. Convert the binary files into a Buffer.
7
- 3. Parse the Buffer into a Story.
8
- 4. Convert Story data into Twee.
9
-
10
- ```javascript
11
- // Import only readFileSynce() and writeFileSync().
12
- import { readFileSync, writeFileSync } from 'node:fs';
13
- // Only import Story and parseTWS().
14
- import { Story, parseTWS } from 'extwee';
15
-
16
- // Read the file contents using binary encoding.
17
- const contents = readFileSync( 'Example1.tws', 'binary' );
18
- // Convert from binary into Buffer.
19
- const b = Buffer.from( contents, 'binary' );
20
- // convert TWS into Story.
21
- const s = parseTWS( b );
22
-
23
- // Write Twee to output file.
24
- writeFileSync( 'example.twee', s.toTwee() );
25
- ```
@@ -1,17 +0,0 @@
1
- # JSON
2
-
3
- The [Twine 2 JSON specification](https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-jsonoutput-doc.md) defines a format for storing a Twine 2 compatible story and passage data in JavaScript Objection Notation (JSON).
4
-
5
- Extwee can perform two actions.
6
-
7
- ## Parse
8
-
9
- When using the `parseJSON()` function (or `JSON/parse.js` export), incoming Twine 2 JSON will be converted into a [**Story**](/objects/story.md) object.
10
-
11
- ## Output
12
-
13
- Every **Story** object can create a JSON representation of its data using the `Story.toJSON()` method.
14
-
15
- ## Format Caution
16
-
17
- As of December 2023, no current story compilation (other than Extwee) or version of Twine supports input or output of the Twine 2 JSON format.
@@ -1,13 +0,0 @@
1
- # Twee
2
-
3
- The [Twee 3 specification](https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md) defines a human-readable format for storing a Twine 2 compatible story and passage data.
4
-
5
- Extwee can perform two actions with Twee.
6
-
7
- ## Parse
8
-
9
- When using the `parseTwee()` function (or `Twee/parse.js` export), incoming Twee will be converted into a [**Story**](/objects/story.md) object.
10
-
11
- ## Output
12
-
13
- Every **Story** object can create a Twee representation of its data using the `Story.toTwee()` method.
@@ -1,13 +0,0 @@
1
- # Twine 1 HTML
2
-
3
- The [Twine 1 HTML Output specification](https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md) defines the elements and attributes for encoding Twine 1 story and passage data.
4
-
5
- Extwee can perform two actions with Twine 1 HTML.
6
-
7
- ## Parsing
8
-
9
- When using the `parseTwine1HTML()` function (or `Twine1HTML/parse.js` export), incoming Twine 1 HTML will be converted into a [**Story**](/objects/story.md) object.
10
-
11
- ## Compilation
12
-
13
- When using the `compileTwine1HTML()` function (or `Twine2HTML/compile.js` export), [**Story**](/objects/story.md) objects can be compiled into Twine 1 HTML output with additional content from `engine.js`, `header.html`, and `code.js` files.
@@ -1,13 +0,0 @@
1
- # Twine 2 Archive HTML
2
-
3
- The [Twine 2 Archive HTML specification](https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-archive-spec.md) defines a collection of Twine 2 stories stored in HTML.
4
-
5
- Extwee can perform two actions with Twine 2 archive HTML.
6
-
7
- ## Parsing
8
-
9
- When using the `parseTwine2ArchiveHTML()` function (or `Twine2ArchiveHTML/parse.js` export), incoming Twine 2 Archive HTML will be converted into an array of [**Story**](/objects/story.md) objects.
10
-
11
- ## Compilation
12
-
13
- When using the `compileTwine2ArchiveHTML()` function (or `Twine2ArchiveHTML/compile.js` export), an array of **Story** objects can be converted into Twine 2 Archive HTML.
@@ -1,13 +0,0 @@
1
- # Twine 2 HTML
2
-
3
- The [Twine 2 HTML Output specification](https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md) defines the elements and attributes for encoding Twine 2 story and passage data.
4
-
5
- Extwee can perform two actions with Twine 2 HTML.
6
-
7
- ## Parsing
8
-
9
- When using the `parseTwine2HTML()` function (or `Twine2HTML/parse.js` export), incoming Twine 2 HTML will be converted into a [**Story**](/objects/story.md) object.
10
-
11
- ## Compilation
12
-
13
- When using the `compileTwine2HTML()` function (or `Twine2HTML/compile.js` export), [**Story**](/objects/story.md) and [**StoryFormat**](/objects/storyformat.md) objects can be compiled into Twine 2 HTML output.
@@ -1,9 +0,0 @@
1
- # TWS
2
-
3
- The [Twine 1 TWS documentation](https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-twsoutput.md) details the Python pickle format used in Twine 1.
4
-
5
- Extwee can only parse TWS files.
6
-
7
- ## Parsing
8
-
9
- When using the `parseTWS()` function (or `TWS/parse.js` export), incoming TWS will be converted into a [**Story**](/objects/story.md) object.
package/docs/index.html DELETED
@@ -1,26 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>Document</title>
6
- <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
7
- <meta name="description" content="Description">
8
- <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
9
- <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/themes/buble.css" />
10
- </head>
11
- <body>
12
- <div id="app"></div>
13
- <script>
14
- window.$docsify = {
15
- name: 'Extwee Documentation',
16
- repo: 'https://github.com/videlais/extwee',
17
- loadSidebar: true
18
- }
19
- </script>
20
- <!-- Docsify v4 -->
21
- <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
22
- <script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/js/docsify-themeable.min.js"></script>
23
- <script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
24
- <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-javascript.min.js"></script>
25
- </body>
26
- </html>