@rohal12/twee-ts 1.3.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/dist/bin/twee-ts.js +118 -22
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-OGP2NXLJ.js → chunk-76URFKGD.js} +119 -23
- package/dist/chunk-76URFKGD.js.map +1 -0
- package/dist/index.cjs +118 -22
- 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 +118 -22
- 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-OGP2NXLJ.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) {
|
|
@@ -1473,7 +1565,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1473
1565
|
}
|
|
1474
1566
|
const options = opts.join(" ");
|
|
1475
1567
|
const zoom = String(story.twine2.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>`;
|
|
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) {
|
|
@@ -1489,7 +1581,7 @@ function ensureIFID(story, diagnostics) {
|
|
|
1489
1581
|
story.ifid = ifid;
|
|
1490
1582
|
diagnostics.push({
|
|
1491
1583
|
level: "error",
|
|
1492
|
-
message: `Story IFID not found. Add
|
|
1584
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
1493
1585
|
});
|
|
1494
1586
|
}
|
|
1495
1587
|
}
|
|
@@ -1500,7 +1592,7 @@ var import_node_path6 = require("path");
|
|
|
1500
1592
|
var CREATOR_NAME2 = "twee-ts";
|
|
1501
1593
|
function toTwine1Archive(story, _startName) {
|
|
1502
1594
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1503
|
-
return `<div id="storeArea" data-size="${count}"
|
|
1595
|
+
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
1504
1596
|
`;
|
|
1505
1597
|
}
|
|
1506
1598
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
@@ -1545,12 +1637,13 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1545
1637
|
return template;
|
|
1546
1638
|
}
|
|
1547
1639
|
function getTwine1PassageChunk(story) {
|
|
1640
|
+
const obfuscateRot13 = story.twine1.settings.get("obfuscate") === "rot13";
|
|
1548
1641
|
let data = "";
|
|
1549
1642
|
let count = 0;
|
|
1550
1643
|
for (const p of story.passages) {
|
|
1551
1644
|
if (hasTag(p, "Twine.private")) continue;
|
|
1552
1645
|
count++;
|
|
1553
|
-
data += passageToTiddler(p, count);
|
|
1646
|
+
data += passageToTiddler(p, count, obfuscateRot13);
|
|
1554
1647
|
}
|
|
1555
1648
|
return { data, count };
|
|
1556
1649
|
}
|
|
@@ -2049,11 +2142,14 @@ function storyToJSON(story) {
|
|
|
2049
2142
|
tags: p.tags,
|
|
2050
2143
|
text: p.text
|
|
2051
2144
|
};
|
|
2052
|
-
if (p.metadata
|
|
2145
|
+
if (p.metadata) {
|
|
2053
2146
|
const meta = {};
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
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
|
+
}
|
|
2057
2153
|
}
|
|
2058
2154
|
storyPassages.push(entry);
|
|
2059
2155
|
}
|