@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
package/dist/index.cjs
CHANGED
|
@@ -108,6 +108,10 @@ function attrEscape(s) {
|
|
|
108
108
|
if (s.length === 0) return s;
|
|
109
109
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'");
|
|
110
110
|
}
|
|
111
|
+
function fullAttrEscape(s) {
|
|
112
|
+
if (s.length === 0) return s;
|
|
113
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
114
|
+
}
|
|
111
115
|
function htmlEscape(s) {
|
|
112
116
|
if (s.length === 0) return s;
|
|
113
117
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -130,6 +134,13 @@ function commentSanitize(s) {
|
|
|
130
134
|
function htmlCommentSanitize(s) {
|
|
131
135
|
return s.replace(/-->/g, "-- >");
|
|
132
136
|
}
|
|
137
|
+
function rot13(s) {
|
|
138
|
+
if (s.length === 0) return s;
|
|
139
|
+
return s.replace(/[A-Za-z]/g, (ch) => {
|
|
140
|
+
const base = ch <= "Z" ? 65 : 97;
|
|
141
|
+
return String.fromCharCode((ch.charCodeAt(0) - base + 13) % 26 + base);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
133
144
|
function tweeUnescape(s) {
|
|
134
145
|
if (s.length === 0) return s;
|
|
135
146
|
let result = "";
|
|
@@ -197,12 +208,14 @@ function hasMetadataSize(p) {
|
|
|
197
208
|
return p.metadata != null && p.metadata.size != null && p.metadata.size !== "";
|
|
198
209
|
}
|
|
199
210
|
function hasAnyMetadata(p) {
|
|
200
|
-
|
|
211
|
+
if (!p.metadata) return false;
|
|
212
|
+
return Object.values(p.metadata).some((v) => v != null && v !== "");
|
|
201
213
|
}
|
|
202
214
|
function marshalMetadata(meta) {
|
|
203
215
|
const obj = {};
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
217
|
+
if (typeof value === "string" && value) obj[key] = value;
|
|
218
|
+
}
|
|
206
219
|
return JSON.stringify(obj);
|
|
207
220
|
}
|
|
208
221
|
function passageToTwee(p, outMode) {
|
|
@@ -245,9 +258,9 @@ function passageToPassagedata(p, pid) {
|
|
|
245
258
|
} else {
|
|
246
259
|
size = "100,100";
|
|
247
260
|
}
|
|
248
|
-
return `<tw-passagedata pid="${pid}" name=${quote(
|
|
261
|
+
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>`;
|
|
249
262
|
}
|
|
250
|
-
function passageToTiddler(p, pid) {
|
|
263
|
+
function passageToTiddler(p, pid, obfuscateRot13 = false) {
|
|
251
264
|
let position;
|
|
252
265
|
if (hasMetadataPosition(p)) {
|
|
253
266
|
position = p.metadata?.position ?? "";
|
|
@@ -258,7 +271,10 @@ function passageToTiddler(p, pid) {
|
|
|
258
271
|
const yp = x === 0 ? y : y + 1;
|
|
259
272
|
position = `${xp * 140 - 130},${yp * 140 - 130}`;
|
|
260
273
|
}
|
|
261
|
-
|
|
274
|
+
const created = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 12);
|
|
275
|
+
const content = obfuscateRot13 && p.name !== "StorySettings" ? tiddlerEscape(rot13(p.text)) : tiddlerEscape(p.text);
|
|
276
|
+
const nameComment = obfuscateRot13 ? `<!-- ${commentSanitize(p.name)} -->` : "";
|
|
277
|
+
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>`;
|
|
262
278
|
}
|
|
263
279
|
function countWords(p) {
|
|
264
280
|
let text = p.text;
|
|
@@ -301,6 +317,7 @@ function createStory() {
|
|
|
301
317
|
formatVersion: "",
|
|
302
318
|
options: /* @__PURE__ */ new Map(),
|
|
303
319
|
start: "",
|
|
320
|
+
tags: "",
|
|
304
321
|
tagColors: /* @__PURE__ */ new Map(),
|
|
305
322
|
zoom: 1
|
|
306
323
|
}
|
|
@@ -347,6 +364,7 @@ function marshalStoryData(story) {
|
|
|
347
364
|
}
|
|
348
365
|
if (options.length > 0) data.options = options;
|
|
349
366
|
if (story.twine2.start) data.start = story.twine2.start;
|
|
367
|
+
if (story.twine2.tags) data.tags = story.twine2.tags;
|
|
350
368
|
if (story.twine2.tagColors.size > 0) {
|
|
351
369
|
data["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
352
370
|
}
|
|
@@ -374,6 +392,7 @@ function unmarshalStoryData(story, json) {
|
|
|
374
392
|
}
|
|
375
393
|
}
|
|
376
394
|
if (typeof data.start === "string" && data.start) story.twine2.start = data.start;
|
|
395
|
+
if (typeof data.tags === "string") story.twine2.tags = data.tags;
|
|
377
396
|
if (typeof data["tag-colors"] === "object" && data["tag-colors"] !== null && !Array.isArray(data["tag-colors"])) {
|
|
378
397
|
for (const [tag, color] of Object.entries(data["tag-colors"])) {
|
|
379
398
|
if (typeof color === "string") story.twine2.tagColors.set(tag, color);
|
|
@@ -445,7 +464,7 @@ function storyAdd(story, p, diagnostics) {
|
|
|
445
464
|
processed = { ...p, text: marshalStoryData(story) };
|
|
446
465
|
} else {
|
|
447
466
|
diagnostics.push({
|
|
448
|
-
level: "
|
|
467
|
+
level: "warning",
|
|
449
468
|
message: `Cannot unmarshal "StoryData" compiler special passage; ${err}.`
|
|
450
469
|
});
|
|
451
470
|
}
|
|
@@ -668,17 +687,41 @@ function baseNameWithoutExt(filename) {
|
|
|
668
687
|
}
|
|
669
688
|
|
|
670
689
|
// src/formats.ts
|
|
690
|
+
function relaxedJSONParse(json) {
|
|
691
|
+
try {
|
|
692
|
+
return JSON.parse(json);
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
let fixed = json;
|
|
696
|
+
fixed = fixed.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, '"$1"');
|
|
697
|
+
fixed = fixed.replace(/(?<=^|[{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/gm, '"$1":');
|
|
698
|
+
fixed = fixed.replace(/,\s*([}\]])/g, "$1");
|
|
699
|
+
return JSON.parse(fixed);
|
|
700
|
+
}
|
|
671
701
|
function parseFormatJSON(source, formatId) {
|
|
672
702
|
const first = source.indexOf("{");
|
|
673
703
|
const last = source.lastIndexOf("}");
|
|
674
704
|
if (first === -1 || last === -1) return null;
|
|
675
705
|
let chunk = source.slice(first, last + 1);
|
|
676
706
|
const parse = (json) => {
|
|
677
|
-
const raw =
|
|
707
|
+
const raw = relaxedJSONParse(json);
|
|
678
708
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
679
709
|
const obj = raw;
|
|
680
|
-
if (typeof obj.
|
|
681
|
-
|
|
710
|
+
if (typeof obj.version !== "string" || typeof obj.source !== "string") return null;
|
|
711
|
+
if (!parseSemver(obj.version)) return null;
|
|
712
|
+
const name = typeof obj.name === "string" ? obj.name : "Untitled Story Format";
|
|
713
|
+
const result = {
|
|
714
|
+
name,
|
|
715
|
+
version: obj.version,
|
|
716
|
+
source: obj.source,
|
|
717
|
+
proofing: obj.proofing === true
|
|
718
|
+
};
|
|
719
|
+
if (typeof obj.author === "string") result.author = obj.author;
|
|
720
|
+
if (typeof obj.description === "string") result.description = obj.description;
|
|
721
|
+
if (typeof obj.image === "string") result.image = obj.image;
|
|
722
|
+
if (typeof obj.url === "string") result.url = obj.url;
|
|
723
|
+
if (typeof obj.license === "string") result.license = obj.license;
|
|
724
|
+
return result;
|
|
682
725
|
};
|
|
683
726
|
try {
|
|
684
727
|
return parse(chunk);
|
|
@@ -744,17 +787,55 @@ function discoverFormats(searchDirs) {
|
|
|
744
787
|
format.name = data.name;
|
|
745
788
|
format.version = data.version;
|
|
746
789
|
format.proofing = data.proofing ?? false;
|
|
790
|
+
if (data.author) format.author = data.author;
|
|
791
|
+
if (data.description) format.description = data.description;
|
|
792
|
+
if (data.image) format.image = data.image;
|
|
793
|
+
if (data.url) format.url = data.url;
|
|
794
|
+
if (data.license) format.license = data.license;
|
|
747
795
|
} catch {
|
|
748
796
|
continue;
|
|
749
797
|
}
|
|
798
|
+
} else {
|
|
799
|
+
format.name = entry;
|
|
750
800
|
}
|
|
751
801
|
formats.set(entry, format);
|
|
752
802
|
break;
|
|
753
803
|
}
|
|
754
804
|
}
|
|
755
805
|
}
|
|
806
|
+
pruneFormats(formats);
|
|
756
807
|
return formats;
|
|
757
808
|
}
|
|
809
|
+
function pruneFormats(formats) {
|
|
810
|
+
const bestByNameMajor = /* @__PURE__ */ new Map();
|
|
811
|
+
const seenNameVersion = /* @__PURE__ */ new Set();
|
|
812
|
+
for (const [id, f] of formats) {
|
|
813
|
+
if (!f.isTwine2 || !f.name || !f.version) continue;
|
|
814
|
+
const v = parseSemver(f.version);
|
|
815
|
+
if (!v) continue;
|
|
816
|
+
const nameVersionKey = `${f.name}@${f.version}`;
|
|
817
|
+
if (seenNameVersion.has(nameVersionKey)) {
|
|
818
|
+
formats.delete(id);
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
seenNameVersion.add(nameVersionKey);
|
|
822
|
+
const groupKey = `${f.name}@${v[0]}`;
|
|
823
|
+
const existing = bestByNameMajor.get(groupKey);
|
|
824
|
+
if (!existing || semverCompare(v, existing.version) > 0) {
|
|
825
|
+
bestByNameMajor.set(groupKey, { id, version: v });
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
for (const [id, f] of [...formats]) {
|
|
829
|
+
if (!f.isTwine2 || !f.name || !f.version) continue;
|
|
830
|
+
const v = parseSemver(f.version);
|
|
831
|
+
if (!v) continue;
|
|
832
|
+
const groupKey = `${f.name}@${v[0]}`;
|
|
833
|
+
const best = bestByNameMajor.get(groupKey);
|
|
834
|
+
if (best && best.id !== id) {
|
|
835
|
+
formats.delete(id);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
758
839
|
function getFormatSearchDirs(extraPaths = [], useTweegoPath = true) {
|
|
759
840
|
const subdirNames = ["storyformats", ".storyformats", "story-formats", "storyFormats", "targets"];
|
|
760
841
|
const basePaths = /* @__PURE__ */ new Set();
|
|
@@ -1207,8 +1288,11 @@ function parseTwee(source, options = {}) {
|
|
|
1207
1288
|
}
|
|
1208
1289
|
const parsed = raw;
|
|
1209
1290
|
const meta = {};
|
|
1210
|
-
|
|
1211
|
-
|
|
1291
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1292
|
+
if (typeof value === "string") {
|
|
1293
|
+
meta[key] = value;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1212
1296
|
current.metadata = meta;
|
|
1213
1297
|
} catch (e) {
|
|
1214
1298
|
diagnostics.push({
|
|
@@ -1222,7 +1306,11 @@ function parseTwee(source, options = {}) {
|
|
|
1222
1306
|
}
|
|
1223
1307
|
case 6 /* Content */: {
|
|
1224
1308
|
if (!current) break;
|
|
1225
|
-
|
|
1309
|
+
if (trim) {
|
|
1310
|
+
current.text = item.val.trim();
|
|
1311
|
+
} else {
|
|
1312
|
+
current.text = item.val.replace(/\n\s*$/, "");
|
|
1313
|
+
}
|
|
1226
1314
|
break;
|
|
1227
1315
|
}
|
|
1228
1316
|
default: {
|
|
@@ -1450,8 +1538,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1450
1538
|
}
|
|
1451
1539
|
}
|
|
1452
1540
|
parts.push(`<script role="script" id="twine-user-script" type="text/twine-javascript">${scriptContent}</script>`);
|
|
1541
|
+
const validTagColors = /* @__PURE__ */ new Set(["gray", "red", "orange", "yellow", "green", "blue", "purple"]);
|
|
1542
|
+
const hexColorPattern = /^#[0-9a-fA-F]{3,8}$/;
|
|
1453
1543
|
for (const [tag, color] of story.twine2.tagColors) {
|
|
1454
|
-
|
|
1544
|
+
if (validTagColors.has(color) || hexColorPattern.test(color)) {
|
|
1545
|
+
parts.push(`<tw-tag name="${attrEscape(tag)}" color="${attrEscape(color)}"></tw-tag>`);
|
|
1546
|
+
}
|
|
1455
1547
|
}
|
|
1456
1548
|
pid = 1;
|
|
1457
1549
|
for (const p of story.passages) {
|
|
@@ -1472,23 +1564,24 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1472
1564
|
if (val) opts.push(opt);
|
|
1473
1565
|
}
|
|
1474
1566
|
const options = opts.join(" ");
|
|
1475
|
-
const zoom =
|
|
1476
|
-
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>`;
|
|
1567
|
+
const zoom = String(story.twine2.zoom);
|
|
1568
|
+
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>`;
|
|
1477
1569
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1478
1570
|
}
|
|
1479
1571
|
function ensureIFID(story, diagnostics) {
|
|
1480
1572
|
if (story.ifid !== "") return;
|
|
1481
1573
|
if (story.legacyIFID !== "") {
|
|
1574
|
+
story.ifid = story.legacyIFID;
|
|
1482
1575
|
diagnostics.push({
|
|
1483
1576
|
level: "warning",
|
|
1484
1577
|
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
1485
1578
|
});
|
|
1486
|
-
story.ifid = story.legacyIFID;
|
|
1487
1579
|
} else {
|
|
1488
|
-
|
|
1580
|
+
const ifid = generateIFID();
|
|
1581
|
+
story.ifid = ifid;
|
|
1489
1582
|
diagnostics.push({
|
|
1490
|
-
level: "
|
|
1491
|
-
message: `Story IFID not found
|
|
1583
|
+
level: "error",
|
|
1584
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
1492
1585
|
});
|
|
1493
1586
|
}
|
|
1494
1587
|
}
|
|
@@ -1499,7 +1592,7 @@ var import_node_path6 = require("path");
|
|
|
1499
1592
|
var CREATOR_NAME2 = "twee-ts";
|
|
1500
1593
|
function toTwine1Archive(story, _startName) {
|
|
1501
1594
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1502
|
-
return `<div id="storeArea" data-size="${count}"
|
|
1595
|
+
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
1503
1596
|
`;
|
|
1504
1597
|
}
|
|
1505
1598
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
@@ -1544,12 +1637,13 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1544
1637
|
return template;
|
|
1545
1638
|
}
|
|
1546
1639
|
function getTwine1PassageChunk(story) {
|
|
1640
|
+
const obfuscateRot13 = story.twine1.settings.get("obfuscate") === "rot13";
|
|
1547
1641
|
let data = "";
|
|
1548
1642
|
let count = 0;
|
|
1549
1643
|
for (const p of story.passages) {
|
|
1550
1644
|
if (hasTag(p, "Twine.private")) continue;
|
|
1551
1645
|
count++;
|
|
1552
|
-
data += passageToTiddler(p, count);
|
|
1646
|
+
data += passageToTiddler(p, count, obfuscateRot13);
|
|
1553
1647
|
}
|
|
1554
1648
|
return { data, count };
|
|
1555
1649
|
}
|
|
@@ -1885,6 +1979,7 @@ function discoverCachedFormats() {
|
|
|
1885
1979
|
}
|
|
1886
1980
|
|
|
1887
1981
|
// src/compiler.ts
|
|
1982
|
+
var CREATOR_NAME3 = "Twee-ts";
|
|
1888
1983
|
var DEFAULT_FORMAT_ID = "sugarcube-2";
|
|
1889
1984
|
var DEFAULT_START_NAME = "Start";
|
|
1890
1985
|
var TweeTsError = class extends Error {
|
|
@@ -2031,28 +2126,49 @@ async function buildOutput(options) {
|
|
|
2031
2126
|
return { output, story, format, diagnostics, stats };
|
|
2032
2127
|
}
|
|
2033
2128
|
function storyToJSON(story) {
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2129
|
+
const scripts = [];
|
|
2130
|
+
const stylesheets = [];
|
|
2131
|
+
const storyPassages = [];
|
|
2132
|
+
for (const p of story.passages) {
|
|
2133
|
+
if (p.name === "StoryTitle" || p.name === "StoryData") continue;
|
|
2134
|
+
if (hasTag(p, "Twine.private")) continue;
|
|
2135
|
+
if (hasTag(p, "script")) {
|
|
2136
|
+
scripts.push(p.text);
|
|
2137
|
+
} else if (hasTag(p, "stylesheet")) {
|
|
2138
|
+
stylesheets.push(p.text);
|
|
2139
|
+
} else {
|
|
2140
|
+
const entry = {
|
|
2039
2141
|
name: p.name,
|
|
2040
2142
|
tags: p.tags,
|
|
2041
|
-
text: p.text
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2143
|
+
text: p.text
|
|
2144
|
+
};
|
|
2145
|
+
if (p.metadata) {
|
|
2146
|
+
const meta = {};
|
|
2147
|
+
for (const [key, value] of Object.entries(p.metadata)) {
|
|
2148
|
+
if (typeof value === "string") meta[key] = value;
|
|
2149
|
+
}
|
|
2150
|
+
if (Object.keys(meta).length > 0) {
|
|
2151
|
+
entry.metadata = meta;
|
|
2152
|
+
}
|
|
2051
2153
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2154
|
+
storyPassages.push(entry);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
const obj = {
|
|
2158
|
+
name: story.name
|
|
2159
|
+
};
|
|
2160
|
+
if (story.ifid) obj.ifid = story.ifid;
|
|
2161
|
+
if (story.twine2.format) obj.format = story.twine2.format;
|
|
2162
|
+
if (story.twine2.formatVersion) obj["format-version"] = story.twine2.formatVersion;
|
|
2163
|
+
if (story.twine2.start) obj.start = story.twine2.start;
|
|
2164
|
+
if (story.twine2.tagColors.size > 0) obj["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
2165
|
+
if (story.twine2.zoom !== 1) obj.zoom = story.twine2.zoom;
|
|
2166
|
+
obj.creator = CREATOR_NAME3;
|
|
2167
|
+
obj["creator-version"] = VERSION;
|
|
2168
|
+
obj.style = stylesheets.join("\n");
|
|
2169
|
+
obj.script = scripts.join("\n");
|
|
2170
|
+
obj.passages = storyPassages;
|
|
2171
|
+
return JSON.stringify(obj, null, 2);
|
|
2056
2172
|
}
|
|
2057
2173
|
|
|
2058
2174
|
// src/inspect.ts
|