@rohal12/twee-ts 1.1.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/bin/twee-ts.js +258 -166
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-QTXRKA4T.js → chunk-OGP2NXLJ.js} +255 -160
- package/dist/chunk-OGP2NXLJ.js.map +1 -0
- package/dist/index.cjs +288 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +257 -164
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.d.ts +7 -1
- package/dist/plugins/rollup.js +2 -2
- package/dist/plugins/rollup.js.map +1 -1
- package/dist/plugins/vite.d.ts +7 -1
- package/dist/plugins/vite.js +2 -2
- package/dist/plugins/vite.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-QTXRKA4T.js.map +0 -1
|
@@ -69,13 +69,26 @@ function tweeEscape(s) {
|
|
|
69
69
|
if (s.length === 0) return s;
|
|
70
70
|
return s.replace(/[\\[\]{}]/g, (ch) => "\\" + ch);
|
|
71
71
|
}
|
|
72
|
+
function jsStringEscape(s) {
|
|
73
|
+
if (s.length === 0) return s;
|
|
74
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
75
|
+
}
|
|
76
|
+
function commentSanitize(s) {
|
|
77
|
+
return s.replace(/\*\//g, "* /");
|
|
78
|
+
}
|
|
79
|
+
function htmlCommentSanitize(s) {
|
|
80
|
+
return s.replace(/-->/g, "-- >");
|
|
81
|
+
}
|
|
72
82
|
function tweeUnescape(s) {
|
|
73
83
|
if (s.length === 0) return s;
|
|
74
84
|
let result = "";
|
|
75
85
|
for (let i = 0; i < s.length; i++) {
|
|
76
86
|
if (s[i] === "\\") {
|
|
77
87
|
i++;
|
|
78
|
-
if (i >= s.length)
|
|
88
|
+
if (i >= s.length) {
|
|
89
|
+
result += "\\";
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
79
92
|
}
|
|
80
93
|
result += s[i];
|
|
81
94
|
}
|
|
@@ -165,16 +178,16 @@ function passageToPassagedata(p, pid) {
|
|
|
165
178
|
let position;
|
|
166
179
|
let size;
|
|
167
180
|
if (hasMetadataPosition(p)) {
|
|
168
|
-
position = p.metadata
|
|
181
|
+
position = p.metadata?.position ?? "";
|
|
169
182
|
} else {
|
|
170
183
|
const x = pid % 10;
|
|
171
|
-
|
|
184
|
+
const y = Math.floor(pid / 10);
|
|
172
185
|
const xp = x === 0 ? 10 : x;
|
|
173
186
|
const yp = x === 0 ? y : y + 1;
|
|
174
187
|
position = `${xp * 125 - 25},${yp * 125 - 25}`;
|
|
175
188
|
}
|
|
176
189
|
if (hasMetadataSize(p)) {
|
|
177
|
-
size = p.metadata
|
|
190
|
+
size = p.metadata?.size ?? "";
|
|
178
191
|
} else {
|
|
179
192
|
size = "100,100";
|
|
180
193
|
}
|
|
@@ -183,10 +196,10 @@ function passageToPassagedata(p, pid) {
|
|
|
183
196
|
function passageToTiddler(p, pid) {
|
|
184
197
|
let position;
|
|
185
198
|
if (hasMetadataPosition(p)) {
|
|
186
|
-
position = p.metadata
|
|
199
|
+
position = p.metadata?.position ?? "";
|
|
187
200
|
} else {
|
|
188
201
|
const x = pid % 10;
|
|
189
|
-
|
|
202
|
+
const y = Math.floor(pid / 10);
|
|
190
203
|
const xp = x === 0 ? 10 : x;
|
|
191
204
|
const yp = x === 0 ? y : y + 1;
|
|
192
205
|
position = `${xp * 140 - 130},${yp * 140 - 130}`;
|
|
@@ -205,15 +218,17 @@ function countWords(p) {
|
|
|
205
218
|
}
|
|
206
219
|
function applyTagAliases(passages, aliases) {
|
|
207
220
|
const entries = Object.entries(aliases);
|
|
208
|
-
if (entries.length === 0) return;
|
|
209
|
-
|
|
210
|
-
const original =
|
|
221
|
+
if (entries.length === 0) return [...passages];
|
|
222
|
+
return passages.map((p) => {
|
|
223
|
+
const original = p.tags;
|
|
224
|
+
const added = [];
|
|
211
225
|
for (const [alias, canonical] of entries) {
|
|
212
|
-
if (original.includes(alias) && !
|
|
213
|
-
|
|
226
|
+
if (original.includes(alias) && !original.includes(canonical) && !added.includes(canonical)) {
|
|
227
|
+
added.push(canonical);
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
|
-
|
|
230
|
+
return added.length > 0 ? { ...p, tags: [...original, ...added] } : p;
|
|
231
|
+
});
|
|
217
232
|
}
|
|
218
233
|
function quote(s) {
|
|
219
234
|
return `"${s}"`;
|
|
@@ -285,27 +300,32 @@ function marshalStoryData(story) {
|
|
|
285
300
|
return JSON.stringify(data, null, " ");
|
|
286
301
|
}
|
|
287
302
|
function unmarshalStoryData(story, json) {
|
|
288
|
-
let
|
|
303
|
+
let raw;
|
|
289
304
|
try {
|
|
290
|
-
|
|
305
|
+
raw = JSON.parse(json);
|
|
291
306
|
} catch (e) {
|
|
292
307
|
return `Cannot unmarshal "StoryData"; ${e instanceof Error ? e.message : String(e)}`;
|
|
293
308
|
}
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
310
|
+
return 'Cannot unmarshal "StoryData"; expected a JSON object';
|
|
311
|
+
}
|
|
312
|
+
const data = raw;
|
|
313
|
+
if (typeof data.ifid === "string" && data.ifid) story.ifid = data.ifid.toUpperCase();
|
|
314
|
+
if (typeof data.format === "string" && data.format) story.twine2.format = data.format;
|
|
315
|
+
if (typeof data["format-version"] === "string" && data["format-version"])
|
|
316
|
+
story.twine2.formatVersion = data["format-version"];
|
|
317
|
+
if (Array.isArray(data.options)) {
|
|
298
318
|
for (const opt of data.options) {
|
|
299
|
-
story.twine2.options.set(opt, true);
|
|
319
|
+
if (typeof opt === "string") story.twine2.options.set(opt, true);
|
|
300
320
|
}
|
|
301
321
|
}
|
|
302
|
-
if (data.start) story.twine2.start = data.start;
|
|
303
|
-
if (data["tag-colors"]) {
|
|
322
|
+
if (typeof data.start === "string" && data.start) story.twine2.start = data.start;
|
|
323
|
+
if (typeof data["tag-colors"] === "object" && data["tag-colors"] !== null && !Array.isArray(data["tag-colors"])) {
|
|
304
324
|
for (const [tag, color] of Object.entries(data["tag-colors"])) {
|
|
305
|
-
story.twine2.tagColors.set(tag, color);
|
|
325
|
+
if (typeof color === "string") story.twine2.tagColors.set(tag, color);
|
|
306
326
|
}
|
|
307
327
|
}
|
|
308
|
-
if (data.zoom
|
|
328
|
+
if (typeof data.zoom === "number" && data.zoom !== 0) story.twine2.zoom = data.zoom;
|
|
309
329
|
return null;
|
|
310
330
|
}
|
|
311
331
|
function unmarshalStorySettings(story, text, diagnostics) {
|
|
@@ -348,6 +368,7 @@ function unmarshalStorySettings(story, text, diagnostics) {
|
|
|
348
368
|
}
|
|
349
369
|
}
|
|
350
370
|
function storyAdd(story, p, diagnostics) {
|
|
371
|
+
let processed = p;
|
|
351
372
|
switch (p.name) {
|
|
352
373
|
case "StoryIncludes":
|
|
353
374
|
diagnostics.push({
|
|
@@ -367,7 +388,7 @@ function storyAdd(story, p, diagnostics) {
|
|
|
367
388
|
});
|
|
368
389
|
}
|
|
369
390
|
}
|
|
370
|
-
p
|
|
391
|
+
processed = { ...p, text: marshalStoryData(story) };
|
|
371
392
|
} else {
|
|
372
393
|
diagnostics.push({
|
|
373
394
|
level: "error",
|
|
@@ -379,12 +400,14 @@ function storyAdd(story, p, diagnostics) {
|
|
|
379
400
|
case "StorySettings":
|
|
380
401
|
unmarshalStorySettings(story, p.text, diagnostics);
|
|
381
402
|
break;
|
|
382
|
-
case "StoryTitle":
|
|
383
|
-
|
|
384
|
-
|
|
403
|
+
case "StoryTitle": {
|
|
404
|
+
const trimmed = p.text.trim();
|
|
405
|
+
processed = { ...p, text: trimmed };
|
|
406
|
+
story.name = trimmed;
|
|
385
407
|
break;
|
|
408
|
+
}
|
|
386
409
|
}
|
|
387
|
-
storyAppend(story,
|
|
410
|
+
storyAppend(story, processed, diagnostics);
|
|
388
411
|
}
|
|
389
412
|
function getStoryStats(story) {
|
|
390
413
|
let storyPassages = 0;
|
|
@@ -438,7 +461,7 @@ var MEDIA_TYPE_MAP = {
|
|
|
438
461
|
webm: "video/webm"
|
|
439
462
|
};
|
|
440
463
|
function mediaTypeFromExt(ext) {
|
|
441
|
-
return MEDIA_TYPE_MAP[ext] ?? "";
|
|
464
|
+
return MEDIA_TYPE_MAP[ext] ?? "application/octet-stream";
|
|
442
465
|
}
|
|
443
466
|
function mediaTypeFromFilename(filename) {
|
|
444
467
|
return mediaTypeFromExt(normalizedFileExt(filename));
|
|
@@ -497,23 +520,49 @@ function getFilenames(pathnames, outFilename) {
|
|
|
497
520
|
}
|
|
498
521
|
|
|
499
522
|
// src/formats.ts
|
|
500
|
-
import {
|
|
523
|
+
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
501
524
|
import { join as join2 } from "path";
|
|
502
525
|
import { homedir } from "os";
|
|
526
|
+
|
|
527
|
+
// src/util.ts
|
|
528
|
+
import { readFileSync } from "fs";
|
|
529
|
+
import { parse as parsePath } from "path";
|
|
530
|
+
function readUTF8(filename) {
|
|
531
|
+
let content = readFileSync(filename, "utf-8");
|
|
532
|
+
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
533
|
+
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
534
|
+
return content;
|
|
535
|
+
}
|
|
536
|
+
function readBase64(filename) {
|
|
537
|
+
return readFileSync(filename).toString("base64");
|
|
538
|
+
}
|
|
539
|
+
function baseNameWithoutExt(filename) {
|
|
540
|
+
const { name, ext } = parsePath(filename);
|
|
541
|
+
return name || ext;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/formats.ts
|
|
503
545
|
function parseFormatJSON(source, formatId) {
|
|
504
546
|
const first = source.indexOf("{");
|
|
505
547
|
const last = source.lastIndexOf("}");
|
|
506
548
|
if (first === -1 || last === -1) return null;
|
|
507
549
|
let chunk = source.slice(first, last + 1);
|
|
550
|
+
const parse = (json) => {
|
|
551
|
+
const raw = JSON.parse(json);
|
|
552
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
553
|
+
const obj = raw;
|
|
554
|
+
if (typeof obj.name !== "string" || typeof obj.version !== "string" || typeof obj.source !== "string") return null;
|
|
555
|
+
return { name: obj.name, version: obj.version, source: obj.source, proofing: obj.proofing === true };
|
|
556
|
+
};
|
|
508
557
|
try {
|
|
509
|
-
return
|
|
558
|
+
return parse(chunk);
|
|
510
559
|
} catch {
|
|
511
560
|
if (formatId.toLowerCase().startsWith("harlowe")) {
|
|
512
561
|
const setupIdx = chunk.lastIndexOf(',"setup": function');
|
|
513
562
|
if (setupIdx !== -1) {
|
|
514
563
|
chunk = chunk.slice(0, setupIdx) + "}";
|
|
515
564
|
try {
|
|
516
|
-
return
|
|
565
|
+
return parse(chunk);
|
|
517
566
|
} catch {
|
|
518
567
|
}
|
|
519
568
|
}
|
|
@@ -521,12 +570,6 @@ function parseFormatJSON(source, formatId) {
|
|
|
521
570
|
return null;
|
|
522
571
|
}
|
|
523
572
|
}
|
|
524
|
-
function readUTF8(filename) {
|
|
525
|
-
let content = readFileSync(filename, "utf-8");
|
|
526
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
527
|
-
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
528
|
-
return content;
|
|
529
|
-
}
|
|
530
573
|
function discoverFormats(searchDirs) {
|
|
531
574
|
const formats = /* @__PURE__ */ new Map();
|
|
532
575
|
const baseFilenames = ["format.js", "header.html"];
|
|
@@ -621,9 +664,11 @@ function parseSemver(v) {
|
|
|
621
664
|
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
622
665
|
}
|
|
623
666
|
function semverCompare(a, b) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
667
|
+
const [aMajor, aMinor, aPatch] = a;
|
|
668
|
+
const [bMajor, bMinor, bPatch] = b;
|
|
669
|
+
if (aMajor !== bMajor) return aMajor - bMajor;
|
|
670
|
+
if (aMinor !== bMinor) return aMinor - bMinor;
|
|
671
|
+
if (aPatch !== bPatch) return aPatch - bPatch;
|
|
627
672
|
return 0;
|
|
628
673
|
}
|
|
629
674
|
function getFormatIdByNameAndVersion(formats, name, version) {
|
|
@@ -654,7 +699,6 @@ function readFormatSource(format) {
|
|
|
654
699
|
}
|
|
655
700
|
|
|
656
701
|
// src/loader.ts
|
|
657
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
658
702
|
import { basename } from "path";
|
|
659
703
|
|
|
660
704
|
// src/lexer.ts
|
|
@@ -931,13 +975,13 @@ function hasTwee2Syntax(s) {
|
|
|
931
975
|
}
|
|
932
976
|
function twee2ToV3(s) {
|
|
933
977
|
if (!hasTwee2Syntax(s)) return s;
|
|
934
|
-
|
|
978
|
+
let result = s.replace(twee2HeaderRe, (_, p1, p2, p3) => {
|
|
935
979
|
const tags = p2 ?? "";
|
|
936
980
|
const pos = p3 ?? "";
|
|
937
981
|
return `${p1}${tags} {"position":"${pos}"}`;
|
|
938
982
|
});
|
|
939
|
-
|
|
940
|
-
return
|
|
983
|
+
result = result.replace(twee2BadPosRe, "$1");
|
|
984
|
+
return result;
|
|
941
985
|
}
|
|
942
986
|
|
|
943
987
|
// src/parser.ts
|
|
@@ -1015,7 +1059,11 @@ function parseTwee(source, options = {}) {
|
|
|
1015
1059
|
return { passages, diagnostics };
|
|
1016
1060
|
}
|
|
1017
1061
|
try {
|
|
1018
|
-
const
|
|
1062
|
+
const raw = JSON.parse(item.val);
|
|
1063
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1064
|
+
throw new Error("expected a JSON object");
|
|
1065
|
+
}
|
|
1066
|
+
const parsed = raw;
|
|
1019
1067
|
const meta = {};
|
|
1020
1068
|
if (typeof parsed.position === "string") meta.position = parsed.position;
|
|
1021
1069
|
if (typeof parsed.size === "string") meta.size = parsed.size;
|
|
@@ -1035,6 +1083,16 @@ function parseTwee(source, options = {}) {
|
|
|
1035
1083
|
current.text = trim ? item.val.trim() : item.val;
|
|
1036
1084
|
break;
|
|
1037
1085
|
}
|
|
1086
|
+
default: {
|
|
1087
|
+
const _exhaustive = item.type;
|
|
1088
|
+
diagnostics.push({
|
|
1089
|
+
level: "error",
|
|
1090
|
+
message: `line ${item.line}: Unhandled lexer item type: ${_exhaustive}.`,
|
|
1091
|
+
file: filename,
|
|
1092
|
+
line: item.line
|
|
1093
|
+
});
|
|
1094
|
+
return { passages, diagnostics };
|
|
1095
|
+
}
|
|
1038
1096
|
}
|
|
1039
1097
|
lastType = item.type;
|
|
1040
1098
|
}
|
|
@@ -1045,15 +1103,6 @@ function parseTwee(source, options = {}) {
|
|
|
1045
1103
|
}
|
|
1046
1104
|
|
|
1047
1105
|
// src/loader.ts
|
|
1048
|
-
function readUTF82(filename) {
|
|
1049
|
-
let content = readFileSync2(filename, "utf-8");
|
|
1050
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1051
|
-
content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1052
|
-
return content;
|
|
1053
|
-
}
|
|
1054
|
-
function readBase64(filename) {
|
|
1055
|
-
return readFileSync2(filename).toString("base64");
|
|
1056
|
-
}
|
|
1057
1106
|
function loadSources(story, filenames, opts, diagnostics, processedFiles) {
|
|
1058
1107
|
for (const filename of filenames) {
|
|
1059
1108
|
if (processedFiles.has(filename)) {
|
|
@@ -1152,7 +1201,7 @@ function loadInlineSources(story, sources, opts, diagnostics) {
|
|
|
1152
1201
|
}
|
|
1153
1202
|
}
|
|
1154
1203
|
function loadTwee(story, filename, opts, diagnostics) {
|
|
1155
|
-
const source =
|
|
1204
|
+
const source = readUTF8(filename);
|
|
1156
1205
|
const result = parseTwee(source, {
|
|
1157
1206
|
filename,
|
|
1158
1207
|
trim: opts.trim ?? true,
|
|
@@ -1164,12 +1213,12 @@ function loadTwee(story, filename, opts, diagnostics) {
|
|
|
1164
1213
|
}
|
|
1165
1214
|
}
|
|
1166
1215
|
function loadTagged(story, tag, filename, diagnostics) {
|
|
1167
|
-
const source =
|
|
1216
|
+
const source = readUTF8(filename);
|
|
1168
1217
|
storyAdd(story, { name: basename(filename), tags: [tag], text: source }, diagnostics);
|
|
1169
1218
|
}
|
|
1170
1219
|
function loadMedia(story, tag, filename, diagnostics) {
|
|
1171
1220
|
const source = readBase64(filename);
|
|
1172
|
-
const name =
|
|
1221
|
+
const name = baseNameWithoutExt(filename);
|
|
1173
1222
|
storyAdd(
|
|
1174
1223
|
story,
|
|
1175
1224
|
{
|
|
@@ -1183,7 +1232,7 @@ function loadMedia(story, tag, filename, diagnostics) {
|
|
|
1183
1232
|
function loadFont(story, filename, diagnostics) {
|
|
1184
1233
|
const source = readBase64(filename);
|
|
1185
1234
|
const name = basename(filename);
|
|
1186
|
-
const family =
|
|
1235
|
+
const family = baseNameWithoutExt(filename);
|
|
1187
1236
|
const ext = normalizedFileExt(filename);
|
|
1188
1237
|
const mediaType = mediaTypeFromExt(ext);
|
|
1189
1238
|
const hint = fontFormatHint(ext);
|
|
@@ -1201,9 +1250,11 @@ function loadFont(story, filename, diagnostics) {
|
|
|
1201
1250
|
);
|
|
1202
1251
|
}
|
|
1203
1252
|
|
|
1253
|
+
// src/version.ts
|
|
1254
|
+
var VERSION = "1.2.0";
|
|
1255
|
+
|
|
1204
1256
|
// src/output-twine2.ts
|
|
1205
1257
|
var CREATOR_NAME = "Twee-ts";
|
|
1206
|
-
var CREATOR_VERSION = "0.1.0";
|
|
1207
1258
|
function toTwine2Archive(story, startName, diagnostics) {
|
|
1208
1259
|
return getTwine2DataChunk(story, startName, diagnostics) + "\n";
|
|
1209
1260
|
}
|
|
@@ -1231,12 +1282,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1231
1282
|
}
|
|
1232
1283
|
let styleContent = "";
|
|
1233
1284
|
if (stylesheets.length === 1) {
|
|
1234
|
-
styleContent = stylesheets[0]
|
|
1285
|
+
styleContent = stylesheets[0]?.text ?? "";
|
|
1235
1286
|
} else if (stylesheets.length > 1) {
|
|
1236
1287
|
let pidS = 1;
|
|
1237
1288
|
for (const p of stylesheets) {
|
|
1238
1289
|
if (pidS > 1 && !styleContent.endsWith("\n")) styleContent += "\n";
|
|
1239
|
-
styleContent += `/* twine-user-stylesheet #${pidS}: "${p.name}" */
|
|
1290
|
+
styleContent += `/* twine-user-stylesheet #${pidS}: "${commentSanitize(p.name)}" */
|
|
1240
1291
|
`;
|
|
1241
1292
|
styleContent += p.text;
|
|
1242
1293
|
pidS++;
|
|
@@ -1245,12 +1296,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1245
1296
|
parts.push(`<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">${styleContent}</style>`);
|
|
1246
1297
|
let scriptContent = "";
|
|
1247
1298
|
if (scripts.length === 1) {
|
|
1248
|
-
scriptContent = scripts[0]
|
|
1299
|
+
scriptContent = scripts[0]?.text ?? "";
|
|
1249
1300
|
} else if (scripts.length > 1) {
|
|
1250
1301
|
let pidS = 1;
|
|
1251
1302
|
for (const p of scripts) {
|
|
1252
1303
|
if (pidS > 1 && !scriptContent.endsWith("\n")) scriptContent += "\n";
|
|
1253
|
-
scriptContent += `/* twine-user-script #${pidS}: "${p.name}" */
|
|
1304
|
+
scriptContent += `/* twine-user-script #${pidS}: "${commentSanitize(p.name)}" */
|
|
1254
1305
|
`;
|
|
1255
1306
|
scriptContent += p.text;
|
|
1256
1307
|
pidS++;
|
|
@@ -1279,32 +1330,32 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1279
1330
|
if (val) opts.push(opt);
|
|
1280
1331
|
}
|
|
1281
1332
|
const options = opts.join(" ");
|
|
1282
|
-
const zoom =
|
|
1283
|
-
const wrapper = `<!-- UUID://${story.ifid}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${startID}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(
|
|
1333
|
+
const zoom = String(story.twine2.zoom);
|
|
1334
|
+
const wrapper = `<!-- UUID://${htmlCommentSanitize(story.ifid)}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${attrEscape(startID)}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(VERSION)}" ifid="${attrEscape(story.ifid)}" zoom="${attrEscape(zoom)}" format="${attrEscape(story.twine2.format)}" format-version="${attrEscape(story.twine2.formatVersion)}" options="${attrEscape(options)}" hidden>`;
|
|
1284
1335
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1285
1336
|
}
|
|
1286
1337
|
function ensureIFID(story, diagnostics) {
|
|
1287
1338
|
if (story.ifid !== "") return;
|
|
1288
1339
|
if (story.legacyIFID !== "") {
|
|
1340
|
+
story.ifid = story.legacyIFID;
|
|
1289
1341
|
diagnostics.push({
|
|
1290
1342
|
level: "warning",
|
|
1291
1343
|
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
1292
1344
|
});
|
|
1293
|
-
story.ifid = story.legacyIFID;
|
|
1294
1345
|
} else {
|
|
1295
|
-
|
|
1346
|
+
const ifid = generateIFID();
|
|
1347
|
+
story.ifid = ifid;
|
|
1296
1348
|
diagnostics.push({
|
|
1297
|
-
level: "
|
|
1298
|
-
message: `Story IFID not found
|
|
1349
|
+
level: "error",
|
|
1350
|
+
message: `Story IFID not found. Add a StoryData passage with: {"ifid":"${ifid}"}`
|
|
1299
1351
|
});
|
|
1300
1352
|
}
|
|
1301
1353
|
}
|
|
1302
1354
|
|
|
1303
1355
|
// src/output-twine1.ts
|
|
1304
|
-
import { readFileSync as
|
|
1356
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1305
1357
|
import { join as join3, dirname } from "path";
|
|
1306
1358
|
var CREATOR_NAME2 = "twee-ts";
|
|
1307
|
-
var CREATOR_VERSION2 = "0.1.0";
|
|
1308
1359
|
function toTwine1Archive(story, _startName) {
|
|
1309
1360
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1310
1361
|
return `<div id="storeArea" data-size="${count}" hidden>${data}</div>
|
|
@@ -1326,26 +1377,27 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1326
1377
|
template = tryReplaceComponent(template, '"MODERNIZR"', join3(parentDir, "modernizr.js"), true);
|
|
1327
1378
|
}
|
|
1328
1379
|
const displayStart = startName === "Start" ? "" : startName;
|
|
1329
|
-
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${
|
|
1380
|
+
template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${VERSION}`);
|
|
1330
1381
|
template = template.replace('"TIME"', `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1331
|
-
template = template.replace('"START_AT"', `"${displayStart}"`);
|
|
1382
|
+
template = template.replace('"START_AT"', `"${jsStringEscape(displayStart)}"`);
|
|
1332
1383
|
template = template.replace('"STORY_SIZE"', `"${count}"`);
|
|
1333
1384
|
if (template.includes('"STORY"')) {
|
|
1334
1385
|
template = template.replace('"STORY"', data);
|
|
1335
1386
|
} else {
|
|
1336
1387
|
let footer;
|
|
1337
1388
|
try {
|
|
1338
|
-
footer =
|
|
1389
|
+
footer = readFileSync2(join3(formatDir, "footer.html"), "utf-8");
|
|
1339
1390
|
} catch {
|
|
1340
1391
|
footer = "</div>\n</body>\n</html>\n";
|
|
1341
1392
|
}
|
|
1342
1393
|
template += data + footer;
|
|
1343
1394
|
}
|
|
1344
1395
|
if (story.ifid) {
|
|
1396
|
+
const safeIfid = htmlCommentSanitize(story.ifid);
|
|
1345
1397
|
if (template.includes('<div id="store-area"')) {
|
|
1346
|
-
template = template.replace('<div id="store-area"', `<!-- UUID://${
|
|
1398
|
+
template = template.replace('<div id="store-area"', `<!-- UUID://${safeIfid}// --><div id="store-area"`);
|
|
1347
1399
|
} else {
|
|
1348
|
-
template = template.replace('<div id="storeArea"', `<!-- UUID://${
|
|
1400
|
+
template = template.replace('<div id="storeArea"', `<!-- UUID://${safeIfid}// --><div id="storeArea"`);
|
|
1349
1401
|
}
|
|
1350
1402
|
}
|
|
1351
1403
|
return template;
|
|
@@ -1360,13 +1412,18 @@ function getTwine1PassageChunk(story) {
|
|
|
1360
1412
|
}
|
|
1361
1413
|
return { data, count };
|
|
1362
1414
|
}
|
|
1363
|
-
function tryReplaceComponent(template, placeholder, componentPath,
|
|
1415
|
+
function tryReplaceComponent(template, placeholder, componentPath, required) {
|
|
1364
1416
|
if (!template.includes(placeholder)) return template;
|
|
1365
1417
|
try {
|
|
1366
|
-
let content =
|
|
1418
|
+
let content = readFileSync2(componentPath, "utf-8");
|
|
1367
1419
|
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1368
1420
|
return template.replace(placeholder, content);
|
|
1369
|
-
} catch {
|
|
1421
|
+
} catch (e) {
|
|
1422
|
+
if (required) {
|
|
1423
|
+
throw new Error(
|
|
1424
|
+
`Required format component not found: ${componentPath}: ${e instanceof Error ? e.message : String(e)}`
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1370
1427
|
return template;
|
|
1371
1428
|
}
|
|
1372
1429
|
}
|
|
@@ -1381,16 +1438,6 @@ function toTwee(story, outMode) {
|
|
|
1381
1438
|
}
|
|
1382
1439
|
|
|
1383
1440
|
// src/modules.ts
|
|
1384
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
1385
|
-
import { basename as basename2 } from "path";
|
|
1386
|
-
function readUTF83(filename) {
|
|
1387
|
-
let content = readFileSync4(filename, "utf-8");
|
|
1388
|
-
if (content.charCodeAt(0) === 65279) content = content.slice(1);
|
|
1389
|
-
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1390
|
-
}
|
|
1391
|
-
function readBase642(filename) {
|
|
1392
|
-
return readFileSync4(filename).toString("base64");
|
|
1393
|
-
}
|
|
1394
1441
|
function loadModules(filenames) {
|
|
1395
1442
|
const processed = /* @__PURE__ */ new Set();
|
|
1396
1443
|
const headTags = [];
|
|
@@ -1420,16 +1467,16 @@ function loadModules(filenames) {
|
|
|
1420
1467
|
return headTags.join("\n");
|
|
1421
1468
|
}
|
|
1422
1469
|
function loadModuleTagged(tag, filename) {
|
|
1423
|
-
const source =
|
|
1470
|
+
const source = readUTF8(filename).trim();
|
|
1424
1471
|
if (source.length === 0) return null;
|
|
1425
|
-
const family =
|
|
1472
|
+
const family = baseNameWithoutExt(filename);
|
|
1426
1473
|
const idSlug = `${tag}-module-${slugify(family)}`;
|
|
1427
1474
|
const mimeType = tag === "script" ? "text/javascript" : "text/css";
|
|
1428
1475
|
return `<${tag} id="${idSlug}" type="${mimeType}">${source}</${tag}>`;
|
|
1429
1476
|
}
|
|
1430
1477
|
function loadModuleFont(filename) {
|
|
1431
|
-
const source =
|
|
1432
|
-
const family =
|
|
1478
|
+
const source = readBase64(filename);
|
|
1479
|
+
const family = baseNameWithoutExt(filename);
|
|
1433
1480
|
const idSlug = `style-module-${slugify(family)}`;
|
|
1434
1481
|
const ext = normalizedFileExt(filename);
|
|
1435
1482
|
const mediaType = mediaTypeFromExt(ext);
|
|
@@ -1439,7 +1486,7 @@ function loadModuleFont(filename) {
|
|
|
1439
1486
|
src: url("data:${mediaType};base64,${source}") format("${hint}");
|
|
1440
1487
|
}</style>`;
|
|
1441
1488
|
}
|
|
1442
|
-
function modifyHead(html, modulePaths, headFile) {
|
|
1489
|
+
function modifyHead(html, modulePaths, headFile, diagnostics) {
|
|
1443
1490
|
const parts = [];
|
|
1444
1491
|
if (modulePaths.length > 0) {
|
|
1445
1492
|
const modules = loadModules(modulePaths).trim();
|
|
@@ -1447,9 +1494,13 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1447
1494
|
}
|
|
1448
1495
|
if (headFile) {
|
|
1449
1496
|
try {
|
|
1450
|
-
const source =
|
|
1497
|
+
const source = readUTF8(headFile).trim();
|
|
1451
1498
|
if (source.length > 0) parts.push(source);
|
|
1452
|
-
} catch {
|
|
1499
|
+
} catch (e) {
|
|
1500
|
+
diagnostics?.push({
|
|
1501
|
+
level: "warning",
|
|
1502
|
+
message: `Failed to read head file "${headFile}": ${e instanceof Error ? e.message : String(e)}`
|
|
1503
|
+
});
|
|
1453
1504
|
}
|
|
1454
1505
|
}
|
|
1455
1506
|
if (parts.length > 0) {
|
|
@@ -1460,7 +1511,7 @@ function modifyHead(html, modulePaths, headFile) {
|
|
|
1460
1511
|
}
|
|
1461
1512
|
|
|
1462
1513
|
// src/remote-formats.ts
|
|
1463
|
-
import { mkdirSync, readFileSync as
|
|
1514
|
+
import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
1464
1515
|
import { join as join4 } from "path";
|
|
1465
1516
|
import { homedir as homedir2 } from "os";
|
|
1466
1517
|
var DEFAULT_SFA_INDICES = [
|
|
@@ -1476,6 +1527,24 @@ var indexCache = /* @__PURE__ */ new Map();
|
|
|
1476
1527
|
function clearIndexCache() {
|
|
1477
1528
|
indexCache.clear();
|
|
1478
1529
|
}
|
|
1530
|
+
function isValidEntry(val) {
|
|
1531
|
+
if (typeof val !== "object" || val === null || Array.isArray(val)) return false;
|
|
1532
|
+
const obj = val;
|
|
1533
|
+
return typeof obj.name === "string" && typeof obj.version === "string" && typeof obj.checksums === "object" && obj.checksums !== null && !Array.isArray(obj.checksums);
|
|
1534
|
+
}
|
|
1535
|
+
function validateSFAIndex(json) {
|
|
1536
|
+
if (typeof json !== "object" || json === null) {
|
|
1537
|
+
throw new Error("SFA index is not an object");
|
|
1538
|
+
}
|
|
1539
|
+
const obj = json;
|
|
1540
|
+
const twine1 = Array.isArray(obj["twine1"]) ? obj["twine1"].filter(isValidEntry) : [];
|
|
1541
|
+
const twine2 = Array.isArray(obj["twine2"]) ? obj["twine2"].filter(isValidEntry) : [];
|
|
1542
|
+
return { twine1, twine2 };
|
|
1543
|
+
}
|
|
1544
|
+
function makeFormatId(name, version) {
|
|
1545
|
+
const major = parseSemver(version)?.[0] ?? "0";
|
|
1546
|
+
return `${name.toLowerCase().replace(/\s+/g, "-")}-${major}`;
|
|
1547
|
+
}
|
|
1479
1548
|
async function fetchIndex(url) {
|
|
1480
1549
|
const cached = indexCache.get(url);
|
|
1481
1550
|
if (cached) return cached;
|
|
@@ -1483,37 +1552,42 @@ async function fetchIndex(url) {
|
|
|
1483
1552
|
if (!res.ok) {
|
|
1484
1553
|
throw new Error(`Failed to fetch format index from ${url}: ${res.status} ${res.statusText}`);
|
|
1485
1554
|
}
|
|
1486
|
-
const data = await res.json();
|
|
1555
|
+
const data = validateSFAIndex(await res.json());
|
|
1487
1556
|
indexCache.set(url, data);
|
|
1488
1557
|
return data;
|
|
1489
1558
|
}
|
|
1490
1559
|
function findEntry(index, name, version) {
|
|
1491
1560
|
const wanted = parseSemver(version);
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1561
|
+
const searchArrays = [
|
|
1562
|
+
{ entries: index.twine2 ?? [], formatType: "twine2" },
|
|
1563
|
+
{ entries: index.twine1 ?? [], formatType: "twine1" }
|
|
1564
|
+
];
|
|
1565
|
+
let bestResult;
|
|
1494
1566
|
let bestVersion = null;
|
|
1495
|
-
for (const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
if (
|
|
1502
|
-
bestVersion
|
|
1503
|
-
|
|
1567
|
+
for (const { entries, formatType } of searchArrays) {
|
|
1568
|
+
for (const entry of entries) {
|
|
1569
|
+
if (entry.name.toLowerCase() !== name.toLowerCase()) continue;
|
|
1570
|
+
const have = parseSemver(entry.version);
|
|
1571
|
+
if (!have) continue;
|
|
1572
|
+
if (wanted && semverCompare(have, wanted) === 0) return { entry, formatType };
|
|
1573
|
+
if (wanted === null || have[0] === wanted[0] && semverCompare(have, wanted) >= 0) {
|
|
1574
|
+
if (!bestVersion || semverCompare(have, bestVersion) > 0) {
|
|
1575
|
+
bestVersion = have;
|
|
1576
|
+
bestResult = { entry, formatType };
|
|
1577
|
+
}
|
|
1504
1578
|
}
|
|
1505
1579
|
}
|
|
1506
1580
|
}
|
|
1507
|
-
return
|
|
1581
|
+
return bestResult;
|
|
1508
1582
|
}
|
|
1509
1583
|
async function verifySHA256(content, expectedHex) {
|
|
1510
1584
|
const digest = await crypto.subtle.digest("SHA-256", content);
|
|
1511
1585
|
const hex = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1512
1586
|
return hex === expectedHex.toLowerCase();
|
|
1513
1587
|
}
|
|
1514
|
-
function getDownloadUrl(indexUrl, entry) {
|
|
1588
|
+
function getDownloadUrl(indexUrl, entry, formatType) {
|
|
1515
1589
|
const base = indexUrl.replace(/\/index\.json$/, "");
|
|
1516
|
-
return `${base}
|
|
1590
|
+
return `${base}/${formatType}/${entry.name}/${entry.version}/format.js`;
|
|
1517
1591
|
}
|
|
1518
1592
|
async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
1519
1593
|
const res = await fetch(downloadUrl);
|
|
@@ -1524,13 +1598,14 @@ async function fetchAndCacheFormat(entry, downloadUrl) {
|
|
|
1524
1598
|
const checksumKey = Object.keys(entry.checksums ?? {}).find((k) => k.endsWith("format.js"));
|
|
1525
1599
|
if (checksumKey) {
|
|
1526
1600
|
const expected = entry.checksums[checksumKey];
|
|
1601
|
+
if (!expected) throw new Error(`Missing checksum value for key "${checksumKey}"`);
|
|
1527
1602
|
const encoder = new TextEncoder();
|
|
1528
1603
|
const valid = await verifySHA256(encoder.encode(text), expected);
|
|
1529
1604
|
if (!valid) {
|
|
1530
1605
|
throw new Error(`Checksum verification failed for ${entry.name} ${entry.version}`);
|
|
1531
1606
|
}
|
|
1532
1607
|
}
|
|
1533
|
-
const id =
|
|
1608
|
+
const id = makeFormatId(entry.name, entry.version);
|
|
1534
1609
|
const data = parseFormatJSON(text, id);
|
|
1535
1610
|
if (!data) {
|
|
1536
1611
|
throw new Error(`Failed to parse format JSON from ${downloadUrl}`);
|
|
@@ -1560,7 +1635,7 @@ async function fetchDirectFormat(url) {
|
|
|
1560
1635
|
if (!data) {
|
|
1561
1636
|
throw new Error(`Failed to parse format JSON from ${url}`);
|
|
1562
1637
|
}
|
|
1563
|
-
const id =
|
|
1638
|
+
const id = makeFormatId(data.name, data.version);
|
|
1564
1639
|
const cacheDir = getCacheDir();
|
|
1565
1640
|
const formatDir = join4(cacheDir, data.name, data.version);
|
|
1566
1641
|
mkdirSync(formatDir, { recursive: true });
|
|
@@ -1576,6 +1651,7 @@ async function fetchDirectFormat(url) {
|
|
|
1576
1651
|
};
|
|
1577
1652
|
}
|
|
1578
1653
|
async function resolveRemoteFormat(name, version, indices, urls) {
|
|
1654
|
+
let lastError;
|
|
1579
1655
|
if (urls) {
|
|
1580
1656
|
for (const url of urls) {
|
|
1581
1657
|
try {
|
|
@@ -1587,7 +1663,8 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1587
1663
|
return info;
|
|
1588
1664
|
}
|
|
1589
1665
|
}
|
|
1590
|
-
} catch {
|
|
1666
|
+
} catch (e) {
|
|
1667
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1591
1668
|
}
|
|
1592
1669
|
}
|
|
1593
1670
|
}
|
|
@@ -1595,24 +1672,26 @@ async function resolveRemoteFormat(name, version, indices, urls) {
|
|
|
1595
1672
|
for (const indexUrl of allIndices) {
|
|
1596
1673
|
try {
|
|
1597
1674
|
const index = await fetchIndex(indexUrl);
|
|
1598
|
-
const
|
|
1599
|
-
if (
|
|
1600
|
-
const cached = getCachedFormat(entry.name, entry.version);
|
|
1675
|
+
const result = findEntry(index, name, version);
|
|
1676
|
+
if (result) {
|
|
1677
|
+
const cached = getCachedFormat(result.entry.name, result.entry.version);
|
|
1601
1678
|
if (cached) return cached;
|
|
1602
|
-
const downloadUrl = getDownloadUrl(indexUrl, entry);
|
|
1603
|
-
return await fetchAndCacheFormat(entry, downloadUrl);
|
|
1679
|
+
const downloadUrl = getDownloadUrl(indexUrl, result.entry, result.formatType);
|
|
1680
|
+
return await fetchAndCacheFormat(result.entry, downloadUrl);
|
|
1604
1681
|
}
|
|
1605
|
-
} catch {
|
|
1682
|
+
} catch (e) {
|
|
1683
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
1606
1684
|
}
|
|
1607
1685
|
}
|
|
1686
|
+
if (lastError) throw lastError;
|
|
1608
1687
|
return void 0;
|
|
1609
1688
|
}
|
|
1610
1689
|
function getCachedFormat(name, version) {
|
|
1611
1690
|
const formatPath = join4(getCacheDir(), name, version, "format.js");
|
|
1612
1691
|
try {
|
|
1613
1692
|
if (!existsSync(formatPath)) return void 0;
|
|
1614
|
-
const source =
|
|
1615
|
-
const id =
|
|
1693
|
+
const source = readFileSync3(formatPath, "utf-8");
|
|
1694
|
+
const id = makeFormatId(name, version);
|
|
1616
1695
|
const data = parseFormatJSON(source, id);
|
|
1617
1696
|
if (!data) return void 0;
|
|
1618
1697
|
return {
|
|
@@ -1629,6 +1708,7 @@ function getCachedFormat(name, version) {
|
|
|
1629
1708
|
}
|
|
1630
1709
|
|
|
1631
1710
|
// src/compiler.ts
|
|
1711
|
+
var CREATOR_NAME3 = "Twee-ts";
|
|
1632
1712
|
var DEFAULT_FORMAT_ID = "sugarcube-2";
|
|
1633
1713
|
var DEFAULT_START_NAME = "Start";
|
|
1634
1714
|
var TweeTsError = class extends Error {
|
|
@@ -1664,10 +1744,7 @@ async function buildOutput(options) {
|
|
|
1664
1744
|
loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
|
|
1665
1745
|
loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
|
|
1666
1746
|
if (options.tagAliases) {
|
|
1667
|
-
applyTagAliases(story.passages, options.tagAliases);
|
|
1668
|
-
}
|
|
1669
|
-
const fatalErrors = diagnostics.filter((d) => d.level === "error");
|
|
1670
|
-
if (fatalErrors.length > 0 && outputMode === "html") {
|
|
1747
|
+
story.passages = applyTagAliases(story.passages, options.tagAliases);
|
|
1671
1748
|
}
|
|
1672
1749
|
let format;
|
|
1673
1750
|
let formatId = options.formatId ?? "";
|
|
@@ -1686,7 +1763,11 @@ async function buildOutput(options) {
|
|
|
1686
1763
|
const remoteVersion = story.twine2.formatVersion || "";
|
|
1687
1764
|
try {
|
|
1688
1765
|
format = await resolveRemoteFormat(remoteName, remoteVersion, options.formatIndices, options.formatUrls);
|
|
1689
|
-
} catch {
|
|
1766
|
+
} catch (e) {
|
|
1767
|
+
diagnostics.push({
|
|
1768
|
+
level: "warning",
|
|
1769
|
+
message: `Remote format fetch failed for "${remoteName}": ${e instanceof Error ? e.message : String(e)}`
|
|
1770
|
+
});
|
|
1690
1771
|
}
|
|
1691
1772
|
}
|
|
1692
1773
|
if (!format) {
|
|
@@ -1697,14 +1778,7 @@ async function buildOutput(options) {
|
|
|
1697
1778
|
});
|
|
1698
1779
|
}
|
|
1699
1780
|
}
|
|
1700
|
-
|
|
1701
|
-
if (options.startPassage) {
|
|
1702
|
-
startName = options.startPassage;
|
|
1703
|
-
} else if (story.twine2.start) {
|
|
1704
|
-
startName = story.twine2.start;
|
|
1705
|
-
} else {
|
|
1706
|
-
startName = DEFAULT_START_NAME;
|
|
1707
|
-
}
|
|
1781
|
+
const startName = options.startPassage || story.twine2.start || DEFAULT_START_NAME;
|
|
1708
1782
|
if (testMode) {
|
|
1709
1783
|
story.twine2.options.set("debug", true);
|
|
1710
1784
|
}
|
|
@@ -1723,8 +1797,7 @@ async function buildOutput(options) {
|
|
|
1723
1797
|
case "json":
|
|
1724
1798
|
output = storyToJSON(story);
|
|
1725
1799
|
break;
|
|
1726
|
-
case "html":
|
|
1727
|
-
default: {
|
|
1800
|
+
case "html": {
|
|
1728
1801
|
if (!storyHas(story, startName)) {
|
|
1729
1802
|
diagnostics.push({
|
|
1730
1803
|
level: "error",
|
|
@@ -1746,9 +1819,13 @@ async function buildOutput(options) {
|
|
|
1746
1819
|
output = toTwine1HTML(story, format, startName, diagnostics);
|
|
1747
1820
|
}
|
|
1748
1821
|
const modulePaths = options.modules ? getFilenames(options.modules) : [];
|
|
1749
|
-
output = modifyHead(output, modulePaths, options.headFile);
|
|
1822
|
+
output = modifyHead(output, modulePaths, options.headFile, diagnostics);
|
|
1750
1823
|
break;
|
|
1751
1824
|
}
|
|
1825
|
+
default: {
|
|
1826
|
+
const _exhaustive = outputMode;
|
|
1827
|
+
throw new TweeTsError(`Unhandled output mode: ${_exhaustive}`, diagnostics);
|
|
1828
|
+
}
|
|
1752
1829
|
}
|
|
1753
1830
|
const stats = {
|
|
1754
1831
|
...getStoryStats(story),
|
|
@@ -1757,31 +1834,49 @@ async function buildOutput(options) {
|
|
|
1757
1834
|
return { output, story, format, diagnostics, stats };
|
|
1758
1835
|
}
|
|
1759
1836
|
function storyToJSON(story) {
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1837
|
+
const scripts = [];
|
|
1838
|
+
const stylesheets = [];
|
|
1839
|
+
const storyPassages = [];
|
|
1840
|
+
for (const p of story.passages) {
|
|
1841
|
+
if (p.name === "StoryTitle" || p.name === "StoryData") continue;
|
|
1842
|
+
if (hasTag(p, "Twine.private")) continue;
|
|
1843
|
+
if (hasTag(p, "script")) {
|
|
1844
|
+
scripts.push(p.text);
|
|
1845
|
+
} else if (hasTag(p, "stylesheet")) {
|
|
1846
|
+
stylesheets.push(p.text);
|
|
1847
|
+
} else {
|
|
1848
|
+
const entry = {
|
|
1765
1849
|
name: p.name,
|
|
1766
1850
|
tags: p.tags,
|
|
1767
|
-
text: p.text
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
tagColors: Object.fromEntries(story.twine2.tagColors),
|
|
1775
|
-
options: Object.fromEntries(story.twine2.options),
|
|
1776
|
-
zoom: story.twine2.zoom
|
|
1851
|
+
text: p.text
|
|
1852
|
+
};
|
|
1853
|
+
if (p.metadata && (p.metadata.position || p.metadata.size)) {
|
|
1854
|
+
const meta = {};
|
|
1855
|
+
if (p.metadata.position) meta.position = p.metadata.position;
|
|
1856
|
+
if (p.metadata.size) meta.size = p.metadata.size;
|
|
1857
|
+
entry.metadata = meta;
|
|
1777
1858
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1859
|
+
storyPassages.push(entry);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
const obj = {
|
|
1863
|
+
name: story.name
|
|
1864
|
+
};
|
|
1865
|
+
if (story.ifid) obj.ifid = story.ifid;
|
|
1866
|
+
if (story.twine2.format) obj.format = story.twine2.format;
|
|
1867
|
+
if (story.twine2.formatVersion) obj["format-version"] = story.twine2.formatVersion;
|
|
1868
|
+
if (story.twine2.start) obj.start = story.twine2.start;
|
|
1869
|
+
if (story.twine2.tagColors.size > 0) obj["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
1870
|
+
if (story.twine2.zoom !== 1) obj.zoom = story.twine2.zoom;
|
|
1871
|
+
obj.creator = CREATOR_NAME3;
|
|
1872
|
+
obj["creator-version"] = VERSION;
|
|
1873
|
+
obj.style = stylesheets.join("\n");
|
|
1874
|
+
obj.script = scripts.join("\n");
|
|
1875
|
+
obj.passages = storyPassages;
|
|
1876
|
+
return JSON.stringify(obj, null, 2);
|
|
1782
1877
|
}
|
|
1783
1878
|
|
|
1784
1879
|
export {
|
|
1785
1880
|
compile
|
|
1786
1881
|
};
|
|
1787
|
-
//# sourceMappingURL=chunk-
|
|
1882
|
+
//# sourceMappingURL=chunk-OGP2NXLJ.js.map
|