@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/bin/twee-ts.js
CHANGED
|
@@ -64,6 +64,10 @@ function attrEscape(s) {
|
|
|
64
64
|
if (s.length === 0) return s;
|
|
65
65
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'");
|
|
66
66
|
}
|
|
67
|
+
function fullAttrEscape(s) {
|
|
68
|
+
if (s.length === 0) return s;
|
|
69
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
70
|
+
}
|
|
67
71
|
function htmlEscape(s) {
|
|
68
72
|
if (s.length === 0) return s;
|
|
69
73
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -86,6 +90,13 @@ function commentSanitize(s) {
|
|
|
86
90
|
function htmlCommentSanitize(s) {
|
|
87
91
|
return s.replace(/-->/g, "-- >");
|
|
88
92
|
}
|
|
93
|
+
function rot13(s) {
|
|
94
|
+
if (s.length === 0) return s;
|
|
95
|
+
return s.replace(/[A-Za-z]/g, (ch) => {
|
|
96
|
+
const base = ch <= "Z" ? 65 : 97;
|
|
97
|
+
return String.fromCharCode((ch.charCodeAt(0) - base + 13) % 26 + base);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
89
100
|
function tweeUnescape(s) {
|
|
90
101
|
if (s.length === 0) return s;
|
|
91
102
|
let result = "";
|
|
@@ -150,12 +161,14 @@ function hasMetadataSize(p) {
|
|
|
150
161
|
return p.metadata != null && p.metadata.size != null && p.metadata.size !== "";
|
|
151
162
|
}
|
|
152
163
|
function hasAnyMetadata(p) {
|
|
153
|
-
|
|
164
|
+
if (!p.metadata) return false;
|
|
165
|
+
return Object.values(p.metadata).some((v) => v != null && v !== "");
|
|
154
166
|
}
|
|
155
167
|
function marshalMetadata(meta) {
|
|
156
168
|
const obj = {};
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
170
|
+
if (typeof value === "string" && value) obj[key] = value;
|
|
171
|
+
}
|
|
159
172
|
return JSON.stringify(obj);
|
|
160
173
|
}
|
|
161
174
|
function passageToTwee(p, outMode) {
|
|
@@ -198,9 +211,9 @@ function passageToPassagedata(p, pid) {
|
|
|
198
211
|
} else {
|
|
199
212
|
size = "100,100";
|
|
200
213
|
}
|
|
201
|
-
return `<tw-passagedata pid="${pid}" name=${quote(
|
|
214
|
+
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>`;
|
|
202
215
|
}
|
|
203
|
-
function passageToTiddler(p, pid) {
|
|
216
|
+
function passageToTiddler(p, pid, obfuscateRot13 = false) {
|
|
204
217
|
let position;
|
|
205
218
|
if (hasMetadataPosition(p)) {
|
|
206
219
|
position = p.metadata?.position ?? "";
|
|
@@ -211,7 +224,10 @@ function passageToTiddler(p, pid) {
|
|
|
211
224
|
const yp = x === 0 ? y : y + 1;
|
|
212
225
|
position = `${xp * 140 - 130},${yp * 140 - 130}`;
|
|
213
226
|
}
|
|
214
|
-
|
|
227
|
+
const created = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 12);
|
|
228
|
+
const content = obfuscateRot13 && p.name !== "StorySettings" ? tiddlerEscape(rot13(p.text)) : tiddlerEscape(p.text);
|
|
229
|
+
const nameComment = obfuscateRot13 ? `<!-- ${commentSanitize(p.name)} -->` : "";
|
|
230
|
+
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>`;
|
|
215
231
|
}
|
|
216
232
|
function countWords(p) {
|
|
217
233
|
let text = p.text;
|
|
@@ -254,6 +270,7 @@ function createStory() {
|
|
|
254
270
|
formatVersion: "",
|
|
255
271
|
options: /* @__PURE__ */ new Map(),
|
|
256
272
|
start: "",
|
|
273
|
+
tags: "",
|
|
257
274
|
tagColors: /* @__PURE__ */ new Map(),
|
|
258
275
|
zoom: 1
|
|
259
276
|
}
|
|
@@ -300,6 +317,7 @@ function marshalStoryData(story) {
|
|
|
300
317
|
}
|
|
301
318
|
if (options.length > 0) data.options = options;
|
|
302
319
|
if (story.twine2.start) data.start = story.twine2.start;
|
|
320
|
+
if (story.twine2.tags) data.tags = story.twine2.tags;
|
|
303
321
|
if (story.twine2.tagColors.size > 0) {
|
|
304
322
|
data["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
305
323
|
}
|
|
@@ -327,6 +345,7 @@ function unmarshalStoryData(story, json) {
|
|
|
327
345
|
}
|
|
328
346
|
}
|
|
329
347
|
if (typeof data.start === "string" && data.start) story.twine2.start = data.start;
|
|
348
|
+
if (typeof data.tags === "string") story.twine2.tags = data.tags;
|
|
330
349
|
if (typeof data["tag-colors"] === "object" && data["tag-colors"] !== null && !Array.isArray(data["tag-colors"])) {
|
|
331
350
|
for (const [tag, color] of Object.entries(data["tag-colors"])) {
|
|
332
351
|
if (typeof color === "string") story.twine2.tagColors.set(tag, color);
|
|
@@ -398,7 +417,7 @@ function storyAdd(story, p, diagnostics) {
|
|
|
398
417
|
processed = { ...p, text: marshalStoryData(story) };
|
|
399
418
|
} else {
|
|
400
419
|
diagnostics.push({
|
|
401
|
-
level: "
|
|
420
|
+
level: "warning",
|
|
402
421
|
message: `Cannot unmarshal "StoryData" compiler special passage; ${err}.`
|
|
403
422
|
});
|
|
404
423
|
}
|
|
@@ -621,17 +640,41 @@ function baseNameWithoutExt(filename) {
|
|
|
621
640
|
}
|
|
622
641
|
|
|
623
642
|
// src/formats.ts
|
|
643
|
+
function relaxedJSONParse(json) {
|
|
644
|
+
try {
|
|
645
|
+
return JSON.parse(json);
|
|
646
|
+
} catch {
|
|
647
|
+
}
|
|
648
|
+
let fixed = json;
|
|
649
|
+
fixed = fixed.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, '"$1"');
|
|
650
|
+
fixed = fixed.replace(/(?<=^|[{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/gm, '"$1":');
|
|
651
|
+
fixed = fixed.replace(/,\s*([}\]])/g, "$1");
|
|
652
|
+
return JSON.parse(fixed);
|
|
653
|
+
}
|
|
624
654
|
function parseFormatJSON(source, formatId) {
|
|
625
655
|
const first = source.indexOf("{");
|
|
626
656
|
const last = source.lastIndexOf("}");
|
|
627
657
|
if (first === -1 || last === -1) return null;
|
|
628
658
|
let chunk = source.slice(first, last + 1);
|
|
629
659
|
const parse = (json) => {
|
|
630
|
-
const raw =
|
|
660
|
+
const raw = relaxedJSONParse(json);
|
|
631
661
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
632
662
|
const obj = raw;
|
|
633
|
-
if (typeof obj.
|
|
634
|
-
|
|
663
|
+
if (typeof obj.version !== "string" || typeof obj.source !== "string") return null;
|
|
664
|
+
if (!parseSemver(obj.version)) return null;
|
|
665
|
+
const name = typeof obj.name === "string" ? obj.name : "Untitled Story Format";
|
|
666
|
+
const result = {
|
|
667
|
+
name,
|
|
668
|
+
version: obj.version,
|
|
669
|
+
source: obj.source,
|
|
670
|
+
proofing: obj.proofing === true
|
|
671
|
+
};
|
|
672
|
+
if (typeof obj.author === "string") result.author = obj.author;
|
|
673
|
+
if (typeof obj.description === "string") result.description = obj.description;
|
|
674
|
+
if (typeof obj.image === "string") result.image = obj.image;
|
|
675
|
+
if (typeof obj.url === "string") result.url = obj.url;
|
|
676
|
+
if (typeof obj.license === "string") result.license = obj.license;
|
|
677
|
+
return result;
|
|
635
678
|
};
|
|
636
679
|
try {
|
|
637
680
|
return parse(chunk);
|
|
@@ -697,17 +740,55 @@ function discoverFormats(searchDirs) {
|
|
|
697
740
|
format.name = data.name;
|
|
698
741
|
format.version = data.version;
|
|
699
742
|
format.proofing = data.proofing ?? false;
|
|
743
|
+
if (data.author) format.author = data.author;
|
|
744
|
+
if (data.description) format.description = data.description;
|
|
745
|
+
if (data.image) format.image = data.image;
|
|
746
|
+
if (data.url) format.url = data.url;
|
|
747
|
+
if (data.license) format.license = data.license;
|
|
700
748
|
} catch {
|
|
701
749
|
continue;
|
|
702
750
|
}
|
|
751
|
+
} else {
|
|
752
|
+
format.name = entry;
|
|
703
753
|
}
|
|
704
754
|
formats.set(entry, format);
|
|
705
755
|
break;
|
|
706
756
|
}
|
|
707
757
|
}
|
|
708
758
|
}
|
|
759
|
+
pruneFormats(formats);
|
|
709
760
|
return formats;
|
|
710
761
|
}
|
|
762
|
+
function pruneFormats(formats) {
|
|
763
|
+
const bestByNameMajor = /* @__PURE__ */ new Map();
|
|
764
|
+
const seenNameVersion = /* @__PURE__ */ new Set();
|
|
765
|
+
for (const [id, f] of formats) {
|
|
766
|
+
if (!f.isTwine2 || !f.name || !f.version) continue;
|
|
767
|
+
const v = parseSemver(f.version);
|
|
768
|
+
if (!v) continue;
|
|
769
|
+
const nameVersionKey = `${f.name}@${f.version}`;
|
|
770
|
+
if (seenNameVersion.has(nameVersionKey)) {
|
|
771
|
+
formats.delete(id);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
seenNameVersion.add(nameVersionKey);
|
|
775
|
+
const groupKey = `${f.name}@${v[0]}`;
|
|
776
|
+
const existing = bestByNameMajor.get(groupKey);
|
|
777
|
+
if (!existing || semverCompare(v, existing.version) > 0) {
|
|
778
|
+
bestByNameMajor.set(groupKey, { id, version: v });
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
for (const [id, f] of [...formats]) {
|
|
782
|
+
if (!f.isTwine2 || !f.name || !f.version) continue;
|
|
783
|
+
const v = parseSemver(f.version);
|
|
784
|
+
if (!v) continue;
|
|
785
|
+
const groupKey = `${f.name}@${v[0]}`;
|
|
786
|
+
const best = bestByNameMajor.get(groupKey);
|
|
787
|
+
if (best && best.id !== id) {
|
|
788
|
+
formats.delete(id);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
711
792
|
function getFormatSearchDirs(extraPaths = [], useTweegoPath = true) {
|
|
712
793
|
const subdirNames = ["storyformats", ".storyformats", "story-formats", "storyFormats", "targets"];
|
|
713
794
|
const basePaths = /* @__PURE__ */ new Set();
|
|
@@ -1144,8 +1225,11 @@ function parseTwee(source, options = {}) {
|
|
|
1144
1225
|
}
|
|
1145
1226
|
const parsed = raw;
|
|
1146
1227
|
const meta = {};
|
|
1147
|
-
|
|
1148
|
-
|
|
1228
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1229
|
+
if (typeof value === "string") {
|
|
1230
|
+
meta[key] = value;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1149
1233
|
current.metadata = meta;
|
|
1150
1234
|
} catch (e) {
|
|
1151
1235
|
diagnostics.push({
|
|
@@ -1159,7 +1243,11 @@ function parseTwee(source, options = {}) {
|
|
|
1159
1243
|
}
|
|
1160
1244
|
case 6 /* Content */: {
|
|
1161
1245
|
if (!current) break;
|
|
1162
|
-
|
|
1246
|
+
if (trim) {
|
|
1247
|
+
current.text = item.val.trim();
|
|
1248
|
+
} else {
|
|
1249
|
+
current.text = item.val.replace(/\n\s*$/, "");
|
|
1250
|
+
}
|
|
1163
1251
|
break;
|
|
1164
1252
|
}
|
|
1165
1253
|
default: {
|
|
@@ -1387,8 +1475,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1387
1475
|
}
|
|
1388
1476
|
}
|
|
1389
1477
|
parts.push(`<script role="script" id="twine-user-script" type="text/twine-javascript">${scriptContent}</script>`);
|
|
1478
|
+
const validTagColors = /* @__PURE__ */ new Set(["gray", "red", "orange", "yellow", "green", "blue", "purple"]);
|
|
1479
|
+
const hexColorPattern = /^#[0-9a-fA-F]{3,8}$/;
|
|
1390
1480
|
for (const [tag, color] of story.twine2.tagColors) {
|
|
1391
|
-
|
|
1481
|
+
if (validTagColors.has(color) || hexColorPattern.test(color)) {
|
|
1482
|
+
parts.push(`<tw-tag name="${attrEscape(tag)}" color="${attrEscape(color)}"></tw-tag>`);
|
|
1483
|
+
}
|
|
1392
1484
|
}
|
|
1393
1485
|
pid = 1;
|
|
1394
1486
|
for (const p of story.passages) {
|
|
@@ -1410,7 +1502,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1410
1502
|
}
|
|
1411
1503
|
const options = opts.join(" ");
|
|
1412
1504
|
const zoom = String(story.twine2.zoom);
|
|
1413
|
-
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>`;
|
|
1505
|
+
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>`;
|
|
1414
1506
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1415
1507
|
}
|
|
1416
1508
|
function ensureIFID(story, diagnostics) {
|
|
@@ -1426,7 +1518,7 @@ function ensureIFID(story, diagnostics) {
|
|
|
1426
1518
|
story.ifid = ifid;
|
|
1427
1519
|
diagnostics.push({
|
|
1428
1520
|
level: "error",
|
|
1429
|
-
message: `Story IFID not found. Add
|
|
1521
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
1430
1522
|
});
|
|
1431
1523
|
}
|
|
1432
1524
|
}
|
|
@@ -1437,7 +1529,7 @@ import { join as join3, dirname } from "path";
|
|
|
1437
1529
|
var CREATOR_NAME2 = "twee-ts";
|
|
1438
1530
|
function toTwine1Archive(story, _startName) {
|
|
1439
1531
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1440
|
-
return `<div id="storeArea" data-size="${count}"
|
|
1532
|
+
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
1441
1533
|
`;
|
|
1442
1534
|
}
|
|
1443
1535
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
@@ -1482,12 +1574,13 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1482
1574
|
return template;
|
|
1483
1575
|
}
|
|
1484
1576
|
function getTwine1PassageChunk(story) {
|
|
1577
|
+
const obfuscateRot13 = story.twine1.settings.get("obfuscate") === "rot13";
|
|
1485
1578
|
let data = "";
|
|
1486
1579
|
let count = 0;
|
|
1487
1580
|
for (const p of story.passages) {
|
|
1488
1581
|
if (hasTag(p, "Twine.private")) continue;
|
|
1489
1582
|
count++;
|
|
1490
|
-
data += passageToTiddler(p, count);
|
|
1583
|
+
data += passageToTiddler(p, count, obfuscateRot13);
|
|
1491
1584
|
}
|
|
1492
1585
|
return { data, count };
|
|
1493
1586
|
}
|
|
@@ -1986,11 +2079,14 @@ function storyToJSON(story) {
|
|
|
1986
2079
|
tags: p.tags,
|
|
1987
2080
|
text: p.text
|
|
1988
2081
|
};
|
|
1989
|
-
if (p.metadata
|
|
2082
|
+
if (p.metadata) {
|
|
1990
2083
|
const meta = {};
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2084
|
+
for (const [key, value] of Object.entries(p.metadata)) {
|
|
2085
|
+
if (typeof value === "string") meta[key] = value;
|
|
2086
|
+
}
|
|
2087
|
+
if (Object.keys(meta).length > 0) {
|
|
2088
|
+
entry.metadata = meta;
|
|
2089
|
+
}
|
|
1994
2090
|
}
|
|
1995
2091
|
storyPassages.push(entry);
|
|
1996
2092
|
}
|