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.
- package/CHANGELOG.md +219 -0
- package/CONTRIBUTING.md +305 -0
- package/README.md +16 -0
- package/build/extwee.core.min.js +1 -1
- package/build/extwee.twine1html.min.js +1 -1
- package/build/extwee.twine2archive.min.js +1 -1
- package/build/extwee.tws.min.js +1 -1
- package/package.json +8 -5
- package/src/Passage.js +8 -9
- package/src/Story.js +2 -2
- package/types/src/Story.d.ts +1 -1
- package/.github/FUNDING.yml +0 -3
- package/.github/codeql-analysis.yml +0 -51
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/dependabot-automerge.yml +0 -23
- package/.github/workflows/nodejs.yml +0 -28
- package/.travis.yml +0 -13
- package/babel.config.json +0 -18
- package/docs/.nojekyll +0 -0
- package/docs/README.md +0 -224
- package/docs/_sidebar.md +0 -19
- package/docs/build/extwee.core.min.js +0 -1
- package/docs/build/extwee.twine1html.min.js +0 -1
- package/docs/build/extwee.twine2archive.min.js +0 -1
- package/docs/build/extwee.tws.min.js +0 -1
- package/docs/demos/compiler/extwee.core.min.js +0 -1
- package/docs/demos/compiler/index.css +0 -105
- package/docs/demos/compiler/index.html +0 -359
- package/docs/demos/decompile/extwee.core.min.js +0 -1
- package/docs/demos/decompile/index.css +0 -584
- package/docs/demos/decompile/index.html +0 -468
- package/docs/examples/dynamicPassages.md +0 -28
- package/docs/examples/jsonToTwee.md +0 -23
- package/docs/examples/twsToTwee.md +0 -25
- package/docs/formats/json.md +0 -17
- package/docs/formats/twee.md +0 -13
- package/docs/formats/twine1HTML.md +0 -13
- package/docs/formats/twine2ArchiveHTML.md +0 -13
- package/docs/formats/twine2HTML.md +0 -13
- package/docs/formats/tws.md +0 -9
- package/docs/index.html +0 -26
- package/docs/install/npm.md +0 -16
- package/docs/install/npx.md +0 -79
- package/docs/objects/passage.md +0 -47
- package/docs/objects/story.md +0 -69
- package/docs/objects/storyformat.md +0 -27
- package/eslint.config.js +0 -28
- package/extwee.config.json +0 -6
- package/extwee.config.md +0 -67
- package/jest.config.json +0 -5
- package/test/CLI/CLI.test.js +0 -49
- package/test/CLI/files/example.json +0 -1
- package/test/CLI/files/example6.twee +0 -22
- package/test/CLI/files/harlowe.js +0 -3
- package/test/CLI/files/input.html +0 -47
- package/test/CLI/files/output/test.twee +0 -0
- package/test/CLI/files/test.twee +0 -18
- package/test/CLI/files/tweeExample.twee +0 -17
- package/test/CLI/files/twine1/LICENSE.txt +0 -32
- package/test/CLI/files/twine1/code.js +0 -5
- package/test/CLI/files/twine1/engine.js +0 -43
- package/test/CLI/files/twine1/header.html +0 -325
- package/test/CLI/files/twine1Test.html +0 -371
- package/test/CLI/files/twineExample.html +0 -16
- package/test/Config/Config.test.js +0 -76
- package/test/Config/files/empty.json +0 -3
- package/test/Config/files/full.json +0 -8
- package/test/Config/files/invalid.json +0 -1
- package/test/Config/files/valid.json +0 -6
- package/test/Config/isDirectory.test.js +0 -50
- package/test/Config/isFile.test.js +0 -53
- package/test/Config/loadStoryFormat.test.js +0 -117
- package/test/Config/readDirectories.test.js +0 -78
- package/test/IFID/IFID.Generate.test.js +0 -10
- package/test/JSON/JSON.Parse.test.js +0 -316
- package/test/Objects/Passage.test.js +0 -270
- package/test/Objects/SnowmanCompatibility.test.js +0 -125
- package/test/Objects/Story.test.js +0 -1075
- package/test/Objects/StoryFormat.test.js +0 -219
- package/test/Roundtrip/Files/Example1.html +0 -64
- package/test/Roundtrip/Files/LICENSE +0 -19
- package/test/Roundtrip/Files/example1.twee +0 -10
- package/test/Roundtrip/Files/example2.twee +0 -27
- package/test/Roundtrip/Files/example4.twee +0 -27
- package/test/Roundtrip/Files/harlowe.js +0 -3
- package/test/Roundtrip/Files/round.html +0 -49
- package/test/Roundtrip/Roundtrip.test.js +0 -54
- package/test/StoryFormat/StoryFormat.Parse.test.js +0 -479
- package/test/TWS/Parse.test.js +0 -56
- package/test/TWS/TWSParser/Example5.tws +0 -414
- package/test/TWS/TWSParser/noscale.tws +0 -0
- package/test/TWS/TWSParser/nostory.tws +0 -0
- package/test/Twee/Twee.Escaping.test.js +0 -200
- package/test/Twee/Twee.Parse.test.js +0 -108
- package/test/Twee/TweeParser/cursed.twee +0 -16
- package/test/Twee/TweeParser/cycling.twee +0 -75
- package/test/Twee/TweeParser/emptytags.twee +0 -2
- package/test/Twee/TweeParser/example.twee +0 -32
- package/test/Twee/TweeParser/malformed.twee +0 -2
- package/test/Twee/TweeParser/missing.twee +0 -19
- package/test/Twee/TweeParser/multipleScriptPassages.twee +0 -19
- package/test/Twee/TweeParser/multipleStyleTag.twee +0 -19
- package/test/Twee/TweeParser/multipletags.twee +0 -10
- package/test/Twee/TweeParser/noTitle.twee +0 -2
- package/test/Twee/TweeParser/notes.twee +0 -16
- package/test/Twee/TweeParser/pasagemetadataerror.twee +0 -2
- package/test/Twee/TweeParser/scriptPassage.twee +0 -16
- package/test/Twee/TweeParser/singletag.twee +0 -13
- package/test/Twee/TweeParser/start.twee +0 -2
- package/test/Twee/TweeParser/startMetadata.twee +0 -14
- package/test/Twee/TweeParser/storydataerror.twee +0 -25
- package/test/Twee/TweeParser/style.twee +0 -16
- package/test/Twee/TweeParser/stylePassage.twee +0 -16
- package/test/Twee/TweeParser/test.twee +0 -25
- package/test/Twine1HTML/Twine1HTML.Compile.test.js +0 -180
- package/test/Twine1HTML/Twine1HTML.Parse.Web.test.js +0 -484
- package/test/Twine1HTML/Twine1HTML.Parse.test.js +0 -183
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +0 -674
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +0 -43
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +0 -4
- package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +0 -4
- package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +0 -1
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +0 -32
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +0 -4
- package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +0 -327
- package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
- package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +0 -6
- package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +0 -6
- package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +0 -43
- package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +0 -372
- package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +0 -372
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +0 -35
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.Web.test.js +0 -293
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +0 -42
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +0 -6
- package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +0 -3
- package/test/Twine2HTML/Twine2HTML.Compile.test.js +0 -139
- package/test/Twine2HTML/Twine2HTML.Parse.Web.test.js +0 -329
- package/test/Twine2HTML/Twine2HTML.Parse.test.js +0 -192
- package/test/Twine2HTML/Twine2HTMLCompiler/TestTags.html +0 -42
- package/test/Twine2HTML/Twine2HTMLCompiler/creator.html +0 -50
- package/test/Twine2HTML/Twine2HTMLCompiler/example6.twee +0 -16
- package/test/Twine2HTML/Twine2HTMLCompiler/format.js +0 -9
- package/test/Twine2HTML/Twine2HTMLCompiler/missingStoryTitle.twee +0 -29
- package/test/Twine2HTML/Twine2HTMLCompiler/test11.html +0 -121
- package/test/Twine2HTML/Twine2HTMLCompiler/test2.html +0 -58
- package/test/Twine2HTML/Twine2HTMLCompiler/test3.html +0 -49
- package/test/Twine2HTML/Twine2HTMLCompiler/test4.html +0 -50
- package/test/Twine2HTML/Twine2HTMLCompiler/test6.html +0 -49
- package/test/Twine2HTML/Twine2HTMLParser/Example1.html +0 -53
- package/test/Twine2HTML/Twine2HTMLParser/Tags.html +0 -15
- package/test/Twine2HTML/Twine2HTMLParser/lyingStartnode.html +0 -15
- package/test/Twine2HTML/Twine2HTMLParser/lyingTagColors.html +0 -48
- package/test/Twine2HTML/Twine2HTMLParser/missingCreator.html +0 -11
- package/test/Twine2HTML/Twine2HTMLParser/missingCreatorVersion.html +0 -11
- package/test/Twine2HTML/Twine2HTMLParser/missingFormat.html +0 -11
- package/test/Twine2HTML/Twine2HTMLParser/missingFormatVersion.html +0 -11
- package/test/Twine2HTML/Twine2HTMLParser/missingIFID.html +0 -11
- package/test/Twine2HTML/Twine2HTMLParser/missingPassageTags.html +0 -15
- package/test/Twine2HTML/Twine2HTMLParser/missingPosition.html +0 -15
- package/test/Twine2HTML/Twine2HTMLParser/missingScript.html +0 -14
- package/test/Twine2HTML/Twine2HTMLParser/missingSize.html +0 -35
- package/test/Twine2HTML/Twine2HTMLParser/missingStartnode.html +0 -11
- package/test/Twine2HTML/Twine2HTMLParser/missingStyle.html +0 -14
- package/test/Twine2HTML/Twine2HTMLParser/missingZoom.html +0 -11
- package/test/Twine2HTML/Twine2HTMLParser/tagColors.html +0 -31
- package/test/Twine2HTML/Twine2HTMLParser/twineExample.html +0 -23
- package/test/Twine2HTML/Twine2HTMLParser/twineExample2.html +0 -15
- package/test/Twine2HTML/Twine2HTMLParser/twineExample3.html +0 -15
- package/test/Twine2HTML/Twine2HTMLParser/unescaping.html +0 -33
- package/test/Web/web-core-coverage.test.js +0 -175
- package/test/Web/web-core-global.test.js +0 -93
- package/test/Web/web-core.test.js +0 -156
- package/test/Web/web-exports.test.js +0 -136
- package/test/Web/web-twine1html.test.js +0 -105
- package/test/Web/web-twine2archive.test.js +0 -96
- package/test/Web/web-tws.test.js +0 -77
- package/test/Web/window.Extwee.test.js +0 -97
- package/tsconfig.json +0 -21
- 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
|
-
```
|
package/docs/formats/json.md
DELETED
|
@@ -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.
|
package/docs/formats/twee.md
DELETED
|
@@ -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.
|
package/docs/formats/tws.md
DELETED
|
@@ -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>
|