@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
|
@@ -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) {
|
|
@@ -1331,7 +1423,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1331
1423
|
}
|
|
1332
1424
|
const options = opts.join(" ");
|
|
1333
1425
|
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>`;
|
|
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) {
|
|
@@ -1347,7 +1439,7 @@ function ensureIFID(story, diagnostics) {
|
|
|
1347
1439
|
story.ifid = ifid;
|
|
1348
1440
|
diagnostics.push({
|
|
1349
1441
|
level: "error",
|
|
1350
|
-
message: `Story IFID not found. Add
|
|
1442
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
1351
1443
|
});
|
|
1352
1444
|
}
|
|
1353
1445
|
}
|
|
@@ -1358,7 +1450,7 @@ import { join as join3, dirname } from "path";
|
|
|
1358
1450
|
var CREATOR_NAME2 = "twee-ts";
|
|
1359
1451
|
function toTwine1Archive(story, _startName) {
|
|
1360
1452
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1361
|
-
return `<div id="storeArea" data-size="${count}"
|
|
1453
|
+
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
1362
1454
|
`;
|
|
1363
1455
|
}
|
|
1364
1456
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
@@ -1403,12 +1495,13 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1403
1495
|
return template;
|
|
1404
1496
|
}
|
|
1405
1497
|
function getTwine1PassageChunk(story) {
|
|
1498
|
+
const obfuscateRot13 = story.twine1.settings.get("obfuscate") === "rot13";
|
|
1406
1499
|
let data = "";
|
|
1407
1500
|
let count = 0;
|
|
1408
1501
|
for (const p of story.passages) {
|
|
1409
1502
|
if (hasTag(p, "Twine.private")) continue;
|
|
1410
1503
|
count++;
|
|
1411
|
-
data += passageToTiddler(p, count);
|
|
1504
|
+
data += passageToTiddler(p, count, obfuscateRot13);
|
|
1412
1505
|
}
|
|
1413
1506
|
return { data, count };
|
|
1414
1507
|
}
|
|
@@ -1850,11 +1943,14 @@ function storyToJSON(story) {
|
|
|
1850
1943
|
tags: p.tags,
|
|
1851
1944
|
text: p.text
|
|
1852
1945
|
};
|
|
1853
|
-
if (p.metadata
|
|
1946
|
+
if (p.metadata) {
|
|
1854
1947
|
const meta = {};
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
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
|
+
}
|
|
1858
1954
|
}
|
|
1859
1955
|
storyPassages.push(entry);
|
|
1860
1956
|
}
|
|
@@ -1879,4 +1975,4 @@ function storyToJSON(story) {
|
|
|
1879
1975
|
export {
|
|
1880
1976
|
compile
|
|
1881
1977
|
};
|
|
1882
|
-
//# sourceMappingURL=chunk-
|
|
1978
|
+
//# sourceMappingURL=chunk-76URFKGD.js.map
|