@rohal12/twee-ts 1.2.0 → 1.4.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 +157 -41
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-S4SWCQLW.js → chunk-76URFKGD.js} +158 -42
- package/dist/chunk-76URFKGD.js.map +1 -0
- package/dist/index.cjs +157 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +157 -41
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.js +1 -1
- package/dist/plugins/vite.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-S4SWCQLW.js.map +0 -1
|
@@ -57,6 +57,10 @@ function attrEscape(s) {
|
|
|
57
57
|
if (s.length === 0) return s;
|
|
58
58
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'");
|
|
59
59
|
}
|
|
60
|
+
function fullAttrEscape(s) {
|
|
61
|
+
if (s.length === 0) return s;
|
|
62
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
63
|
+
}
|
|
60
64
|
function htmlEscape(s) {
|
|
61
65
|
if (s.length === 0) return s;
|
|
62
66
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -79,6 +83,13 @@ function commentSanitize(s) {
|
|
|
79
83
|
function htmlCommentSanitize(s) {
|
|
80
84
|
return s.replace(/-->/g, "-- >");
|
|
81
85
|
}
|
|
86
|
+
function rot13(s) {
|
|
87
|
+
if (s.length === 0) return s;
|
|
88
|
+
return s.replace(/[A-Za-z]/g, (ch) => {
|
|
89
|
+
const base = ch <= "Z" ? 65 : 97;
|
|
90
|
+
return String.fromCharCode((ch.charCodeAt(0) - base + 13) % 26 + base);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
82
93
|
function tweeUnescape(s) {
|
|
83
94
|
if (s.length === 0) return s;
|
|
84
95
|
let result = "";
|
|
@@ -143,12 +154,14 @@ function hasMetadataSize(p) {
|
|
|
143
154
|
return p.metadata != null && p.metadata.size != null && p.metadata.size !== "";
|
|
144
155
|
}
|
|
145
156
|
function hasAnyMetadata(p) {
|
|
146
|
-
|
|
157
|
+
if (!p.metadata) return false;
|
|
158
|
+
return Object.values(p.metadata).some((v) => v != null && v !== "");
|
|
147
159
|
}
|
|
148
160
|
function marshalMetadata(meta) {
|
|
149
161
|
const obj = {};
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
163
|
+
if (typeof value === "string" && value) obj[key] = value;
|
|
164
|
+
}
|
|
152
165
|
return JSON.stringify(obj);
|
|
153
166
|
}
|
|
154
167
|
function passageToTwee(p, outMode) {
|
|
@@ -191,9 +204,9 @@ function passageToPassagedata(p, pid) {
|
|
|
191
204
|
} else {
|
|
192
205
|
size = "100,100";
|
|
193
206
|
}
|
|
194
|
-
return `<tw-passagedata pid="${pid}" name=${quote(
|
|
207
|
+
return `<tw-passagedata pid="${pid}" name=${quote(fullAttrEscape(p.name))} tags=${quote(attrEscape(p.tags.join(" ")))} position=${quote(attrEscape(position))} size=${quote(attrEscape(size))}>${htmlEscape(p.text)}</tw-passagedata>`;
|
|
195
208
|
}
|
|
196
|
-
function passageToTiddler(p, pid) {
|
|
209
|
+
function passageToTiddler(p, pid, obfuscateRot13 = false) {
|
|
197
210
|
let position;
|
|
198
211
|
if (hasMetadataPosition(p)) {
|
|
199
212
|
position = p.metadata?.position ?? "";
|
|
@@ -204,7 +217,10 @@ function passageToTiddler(p, pid) {
|
|
|
204
217
|
const yp = x === 0 ? y : y + 1;
|
|
205
218
|
position = `${xp * 140 - 130},${yp * 140 - 130}`;
|
|
206
219
|
}
|
|
207
|
-
|
|
220
|
+
const created = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 12);
|
|
221
|
+
const content = obfuscateRot13 && p.name !== "StorySettings" ? tiddlerEscape(rot13(p.text)) : tiddlerEscape(p.text);
|
|
222
|
+
const nameComment = obfuscateRot13 ? `<!-- ${commentSanitize(p.name)} -->` : "";
|
|
223
|
+
return `<div tiddler=${quote(attrEscape(p.name))} tags=${quote(attrEscape(p.tags.join(" ")))} created=${quote(created)} modifier=${quote("twee")} twine-position=${quote(attrEscape(position))}>${nameComment}${content}</div>`;
|
|
208
224
|
}
|
|
209
225
|
function countWords(p) {
|
|
210
226
|
let text = p.text;
|
|
@@ -247,6 +263,7 @@ function createStory() {
|
|
|
247
263
|
formatVersion: "",
|
|
248
264
|
options: /* @__PURE__ */ new Map(),
|
|
249
265
|
start: "",
|
|
266
|
+
tags: "",
|
|
250
267
|
tagColors: /* @__PURE__ */ new Map(),
|
|
251
268
|
zoom: 1
|
|
252
269
|
}
|
|
@@ -293,6 +310,7 @@ function marshalStoryData(story) {
|
|
|
293
310
|
}
|
|
294
311
|
if (options.length > 0) data.options = options;
|
|
295
312
|
if (story.twine2.start) data.start = story.twine2.start;
|
|
313
|
+
if (story.twine2.tags) data.tags = story.twine2.tags;
|
|
296
314
|
if (story.twine2.tagColors.size > 0) {
|
|
297
315
|
data["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
298
316
|
}
|
|
@@ -320,6 +338,7 @@ function unmarshalStoryData(story, json) {
|
|
|
320
338
|
}
|
|
321
339
|
}
|
|
322
340
|
if (typeof data.start === "string" && data.start) story.twine2.start = data.start;
|
|
341
|
+
if (typeof data.tags === "string") story.twine2.tags = data.tags;
|
|
323
342
|
if (typeof data["tag-colors"] === "object" && data["tag-colors"] !== null && !Array.isArray(data["tag-colors"])) {
|
|
324
343
|
for (const [tag, color] of Object.entries(data["tag-colors"])) {
|
|
325
344
|
if (typeof color === "string") story.twine2.tagColors.set(tag, color);
|
|
@@ -391,7 +410,7 @@ function storyAdd(story, p, diagnostics) {
|
|
|
391
410
|
processed = { ...p, text: marshalStoryData(story) };
|
|
392
411
|
} else {
|
|
393
412
|
diagnostics.push({
|
|
394
|
-
level: "
|
|
413
|
+
level: "warning",
|
|
395
414
|
message: `Cannot unmarshal "StoryData" compiler special passage; ${err}.`
|
|
396
415
|
});
|
|
397
416
|
}
|
|
@@ -542,17 +561,41 @@ function baseNameWithoutExt(filename) {
|
|
|
542
561
|
}
|
|
543
562
|
|
|
544
563
|
// src/formats.ts
|
|
564
|
+
function relaxedJSONParse(json) {
|
|
565
|
+
try {
|
|
566
|
+
return JSON.parse(json);
|
|
567
|
+
} catch {
|
|
568
|
+
}
|
|
569
|
+
let fixed = json;
|
|
570
|
+
fixed = fixed.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, '"$1"');
|
|
571
|
+
fixed = fixed.replace(/(?<=^|[{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/gm, '"$1":');
|
|
572
|
+
fixed = fixed.replace(/,\s*([}\]])/g, "$1");
|
|
573
|
+
return JSON.parse(fixed);
|
|
574
|
+
}
|
|
545
575
|
function parseFormatJSON(source, formatId) {
|
|
546
576
|
const first = source.indexOf("{");
|
|
547
577
|
const last = source.lastIndexOf("}");
|
|
548
578
|
if (first === -1 || last === -1) return null;
|
|
549
579
|
let chunk = source.slice(first, last + 1);
|
|
550
580
|
const parse = (json) => {
|
|
551
|
-
const raw =
|
|
581
|
+
const raw = relaxedJSONParse(json);
|
|
552
582
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
553
583
|
const obj = raw;
|
|
554
|
-
if (typeof obj.
|
|
555
|
-
|
|
584
|
+
if (typeof obj.version !== "string" || typeof obj.source !== "string") return null;
|
|
585
|
+
if (!parseSemver(obj.version)) return null;
|
|
586
|
+
const name = typeof obj.name === "string" ? obj.name : "Untitled Story Format";
|
|
587
|
+
const result = {
|
|
588
|
+
name,
|
|
589
|
+
version: obj.version,
|
|
590
|
+
source: obj.source,
|
|
591
|
+
proofing: obj.proofing === true
|
|
592
|
+
};
|
|
593
|
+
if (typeof obj.author === "string") result.author = obj.author;
|
|
594
|
+
if (typeof obj.description === "string") result.description = obj.description;
|
|
595
|
+
if (typeof obj.image === "string") result.image = obj.image;
|
|
596
|
+
if (typeof obj.url === "string") result.url = obj.url;
|
|
597
|
+
if (typeof obj.license === "string") result.license = obj.license;
|
|
598
|
+
return result;
|
|
556
599
|
};
|
|
557
600
|
try {
|
|
558
601
|
return parse(chunk);
|
|
@@ -618,17 +661,55 @@ function discoverFormats(searchDirs) {
|
|
|
618
661
|
format.name = data.name;
|
|
619
662
|
format.version = data.version;
|
|
620
663
|
format.proofing = data.proofing ?? false;
|
|
664
|
+
if (data.author) format.author = data.author;
|
|
665
|
+
if (data.description) format.description = data.description;
|
|
666
|
+
if (data.image) format.image = data.image;
|
|
667
|
+
if (data.url) format.url = data.url;
|
|
668
|
+
if (data.license) format.license = data.license;
|
|
621
669
|
} catch {
|
|
622
670
|
continue;
|
|
623
671
|
}
|
|
672
|
+
} else {
|
|
673
|
+
format.name = entry;
|
|
624
674
|
}
|
|
625
675
|
formats.set(entry, format);
|
|
626
676
|
break;
|
|
627
677
|
}
|
|
628
678
|
}
|
|
629
679
|
}
|
|
680
|
+
pruneFormats(formats);
|
|
630
681
|
return formats;
|
|
631
682
|
}
|
|
683
|
+
function pruneFormats(formats) {
|
|
684
|
+
const bestByNameMajor = /* @__PURE__ */ new Map();
|
|
685
|
+
const seenNameVersion = /* @__PURE__ */ new Set();
|
|
686
|
+
for (const [id, f] of formats) {
|
|
687
|
+
if (!f.isTwine2 || !f.name || !f.version) continue;
|
|
688
|
+
const v = parseSemver(f.version);
|
|
689
|
+
if (!v) continue;
|
|
690
|
+
const nameVersionKey = `${f.name}@${f.version}`;
|
|
691
|
+
if (seenNameVersion.has(nameVersionKey)) {
|
|
692
|
+
formats.delete(id);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
seenNameVersion.add(nameVersionKey);
|
|
696
|
+
const groupKey = `${f.name}@${v[0]}`;
|
|
697
|
+
const existing = bestByNameMajor.get(groupKey);
|
|
698
|
+
if (!existing || semverCompare(v, existing.version) > 0) {
|
|
699
|
+
bestByNameMajor.set(groupKey, { id, version: v });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
for (const [id, f] of [...formats]) {
|
|
703
|
+
if (!f.isTwine2 || !f.name || !f.version) continue;
|
|
704
|
+
const v = parseSemver(f.version);
|
|
705
|
+
if (!v) continue;
|
|
706
|
+
const groupKey = `${f.name}@${v[0]}`;
|
|
707
|
+
const best = bestByNameMajor.get(groupKey);
|
|
708
|
+
if (best && best.id !== id) {
|
|
709
|
+
formats.delete(id);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
632
713
|
function getFormatSearchDirs(extraPaths = [], useTweegoPath = true) {
|
|
633
714
|
const subdirNames = ["storyformats", ".storyformats", "story-formats", "storyFormats", "targets"];
|
|
634
715
|
const basePaths = /* @__PURE__ */ new Set();
|
|
@@ -1065,8 +1146,11 @@ function parseTwee(source, options = {}) {
|
|
|
1065
1146
|
}
|
|
1066
1147
|
const parsed = raw;
|
|
1067
1148
|
const meta = {};
|
|
1068
|
-
|
|
1069
|
-
|
|
1149
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1150
|
+
if (typeof value === "string") {
|
|
1151
|
+
meta[key] = value;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1070
1154
|
current.metadata = meta;
|
|
1071
1155
|
} catch (e) {
|
|
1072
1156
|
diagnostics.push({
|
|
@@ -1080,7 +1164,11 @@ function parseTwee(source, options = {}) {
|
|
|
1080
1164
|
}
|
|
1081
1165
|
case 6 /* Content */: {
|
|
1082
1166
|
if (!current) break;
|
|
1083
|
-
|
|
1167
|
+
if (trim) {
|
|
1168
|
+
current.text = item.val.trim();
|
|
1169
|
+
} else {
|
|
1170
|
+
current.text = item.val.replace(/\n\s*$/, "");
|
|
1171
|
+
}
|
|
1084
1172
|
break;
|
|
1085
1173
|
}
|
|
1086
1174
|
default: {
|
|
@@ -1308,8 +1396,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1308
1396
|
}
|
|
1309
1397
|
}
|
|
1310
1398
|
parts.push(`<script role="script" id="twine-user-script" type="text/twine-javascript">${scriptContent}</script>`);
|
|
1399
|
+
const validTagColors = /* @__PURE__ */ new Set(["gray", "red", "orange", "yellow", "green", "blue", "purple"]);
|
|
1400
|
+
const hexColorPattern = /^#[0-9a-fA-F]{3,8}$/;
|
|
1311
1401
|
for (const [tag, color] of story.twine2.tagColors) {
|
|
1312
|
-
|
|
1402
|
+
if (validTagColors.has(color) || hexColorPattern.test(color)) {
|
|
1403
|
+
parts.push(`<tw-tag name="${attrEscape(tag)}" color="${attrEscape(color)}"></tw-tag>`);
|
|
1404
|
+
}
|
|
1313
1405
|
}
|
|
1314
1406
|
pid = 1;
|
|
1315
1407
|
for (const p of story.passages) {
|
|
@@ -1330,23 +1422,24 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1330
1422
|
if (val) opts.push(opt);
|
|
1331
1423
|
}
|
|
1332
1424
|
const options = opts.join(" ");
|
|
1333
|
-
const 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>`;
|
|
1425
|
+
const zoom = String(story.twine2.zoom);
|
|
1426
|
+
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)}" tags="${attrEscape(story.twine2.tags)}" hidden>`;
|
|
1335
1427
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1336
1428
|
}
|
|
1337
1429
|
function ensureIFID(story, diagnostics) {
|
|
1338
1430
|
if (story.ifid !== "") return;
|
|
1339
1431
|
if (story.legacyIFID !== "") {
|
|
1432
|
+
story.ifid = story.legacyIFID;
|
|
1340
1433
|
diagnostics.push({
|
|
1341
1434
|
level: "warning",
|
|
1342
1435
|
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
1343
1436
|
});
|
|
1344
|
-
story.ifid = story.legacyIFID;
|
|
1345
1437
|
} else {
|
|
1346
|
-
|
|
1438
|
+
const ifid = generateIFID();
|
|
1439
|
+
story.ifid = ifid;
|
|
1347
1440
|
diagnostics.push({
|
|
1348
|
-
level: "
|
|
1349
|
-
message: `Story IFID not found
|
|
1441
|
+
level: "error",
|
|
1442
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
1350
1443
|
});
|
|
1351
1444
|
}
|
|
1352
1445
|
}
|
|
@@ -1357,7 +1450,7 @@ import { join as join3, dirname } from "path";
|
|
|
1357
1450
|
var CREATOR_NAME2 = "twee-ts";
|
|
1358
1451
|
function toTwine1Archive(story, _startName) {
|
|
1359
1452
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1360
|
-
return `<div id="storeArea" data-size="${count}"
|
|
1453
|
+
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
1361
1454
|
`;
|
|
1362
1455
|
}
|
|
1363
1456
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
@@ -1402,12 +1495,13 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1402
1495
|
return template;
|
|
1403
1496
|
}
|
|
1404
1497
|
function getTwine1PassageChunk(story) {
|
|
1498
|
+
const obfuscateRot13 = story.twine1.settings.get("obfuscate") === "rot13";
|
|
1405
1499
|
let data = "";
|
|
1406
1500
|
let count = 0;
|
|
1407
1501
|
for (const p of story.passages) {
|
|
1408
1502
|
if (hasTag(p, "Twine.private")) continue;
|
|
1409
1503
|
count++;
|
|
1410
|
-
data += passageToTiddler(p, count);
|
|
1504
|
+
data += passageToTiddler(p, count, obfuscateRot13);
|
|
1411
1505
|
}
|
|
1412
1506
|
return { data, count };
|
|
1413
1507
|
}
|
|
@@ -1707,6 +1801,7 @@ function getCachedFormat(name, version) {
|
|
|
1707
1801
|
}
|
|
1708
1802
|
|
|
1709
1803
|
// src/compiler.ts
|
|
1804
|
+
var CREATOR_NAME3 = "Twee-ts";
|
|
1710
1805
|
var DEFAULT_FORMAT_ID = "sugarcube-2";
|
|
1711
1806
|
var DEFAULT_START_NAME = "Start";
|
|
1712
1807
|
var TweeTsError = class extends Error {
|
|
@@ -1832,31 +1927,52 @@ async function buildOutput(options) {
|
|
|
1832
1927
|
return { output, story, format, diagnostics, stats };
|
|
1833
1928
|
}
|
|
1834
1929
|
function storyToJSON(story) {
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1930
|
+
const scripts = [];
|
|
1931
|
+
const stylesheets = [];
|
|
1932
|
+
const storyPassages = [];
|
|
1933
|
+
for (const p of story.passages) {
|
|
1934
|
+
if (p.name === "StoryTitle" || p.name === "StoryData") continue;
|
|
1935
|
+
if (hasTag(p, "Twine.private")) continue;
|
|
1936
|
+
if (hasTag(p, "script")) {
|
|
1937
|
+
scripts.push(p.text);
|
|
1938
|
+
} else if (hasTag(p, "stylesheet")) {
|
|
1939
|
+
stylesheets.push(p.text);
|
|
1940
|
+
} else {
|
|
1941
|
+
const entry = {
|
|
1840
1942
|
name: p.name,
|
|
1841
1943
|
tags: p.tags,
|
|
1842
|
-
text: p.text
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1944
|
+
text: p.text
|
|
1945
|
+
};
|
|
1946
|
+
if (p.metadata) {
|
|
1947
|
+
const meta = {};
|
|
1948
|
+
for (const [key, value] of Object.entries(p.metadata)) {
|
|
1949
|
+
if (typeof value === "string") meta[key] = value;
|
|
1950
|
+
}
|
|
1951
|
+
if (Object.keys(meta).length > 0) {
|
|
1952
|
+
entry.metadata = meta;
|
|
1953
|
+
}
|
|
1852
1954
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1955
|
+
storyPassages.push(entry);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
const obj = {
|
|
1959
|
+
name: story.name
|
|
1960
|
+
};
|
|
1961
|
+
if (story.ifid) obj.ifid = story.ifid;
|
|
1962
|
+
if (story.twine2.format) obj.format = story.twine2.format;
|
|
1963
|
+
if (story.twine2.formatVersion) obj["format-version"] = story.twine2.formatVersion;
|
|
1964
|
+
if (story.twine2.start) obj.start = story.twine2.start;
|
|
1965
|
+
if (story.twine2.tagColors.size > 0) obj["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
1966
|
+
if (story.twine2.zoom !== 1) obj.zoom = story.twine2.zoom;
|
|
1967
|
+
obj.creator = CREATOR_NAME3;
|
|
1968
|
+
obj["creator-version"] = VERSION;
|
|
1969
|
+
obj.style = stylesheets.join("\n");
|
|
1970
|
+
obj.script = scripts.join("\n");
|
|
1971
|
+
obj.passages = storyPassages;
|
|
1972
|
+
return JSON.stringify(obj, null, 2);
|
|
1857
1973
|
}
|
|
1858
1974
|
|
|
1859
1975
|
export {
|
|
1860
1976
|
compile
|
|
1861
1977
|
};
|
|
1862
|
-
//# sourceMappingURL=chunk-
|
|
1978
|
+
//# sourceMappingURL=chunk-76URFKGD.js.map
|