@rohal12/twee-ts 1.3.0 → 1.5.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 +139 -35
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-OGP2NXLJ.js → chunk-KI4QMLEN.js} +135 -34
- package/dist/chunk-KI4QMLEN.js.map +1 -0
- package/dist/index.cjs +135 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +135 -34
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.d.ts +1 -1
- package/dist/plugins/rollup.js +1 -1
- package/dist/plugins/vite.d.ts +1 -1
- package/dist/plugins/vite.js +1 -1
- package/dist/{types-B3qtPxs8.d.ts → types-BbEu2Szm.d.ts} +2 -0
- package/package.json +1 -1
- package/dist/chunk-OGP2NXLJ.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -38,6 +38,8 @@ interface CompileOptions {
|
|
|
38
38
|
noRemote?: boolean;
|
|
39
39
|
/** Map alias tags to canonical special tags (e.g. { library: 'script' }). */
|
|
40
40
|
tagAliases?: Record<string, string>;
|
|
41
|
+
/** Emit source file and line as data- attributes on passage elements. Default: false. */
|
|
42
|
+
sourceInfo?: boolean;
|
|
41
43
|
}
|
|
42
44
|
interface CompileToFileOptions extends CompileOptions {
|
|
43
45
|
/** Output file path. */
|
|
@@ -73,15 +75,21 @@ interface CompileStats {
|
|
|
73
75
|
words: number;
|
|
74
76
|
files: string[];
|
|
75
77
|
}
|
|
78
|
+
interface SourceLocation {
|
|
79
|
+
readonly file: string;
|
|
80
|
+
readonly line: number;
|
|
81
|
+
}
|
|
76
82
|
interface PassageMetadata {
|
|
77
83
|
position?: string;
|
|
78
84
|
size?: string;
|
|
85
|
+
[key: string]: string | undefined;
|
|
79
86
|
}
|
|
80
87
|
interface Passage {
|
|
81
88
|
name: string;
|
|
82
89
|
tags: string[];
|
|
83
90
|
text: string;
|
|
84
91
|
metadata?: PassageMetadata;
|
|
92
|
+
source?: SourceLocation;
|
|
85
93
|
}
|
|
86
94
|
interface Twine1Metadata {
|
|
87
95
|
settings: Map<string, string>;
|
|
@@ -91,6 +99,7 @@ interface Twine2Metadata {
|
|
|
91
99
|
formatVersion: string;
|
|
92
100
|
options: Map<string, boolean>;
|
|
93
101
|
start: string;
|
|
102
|
+
tags: string;
|
|
94
103
|
tagColors: Map<string, string>;
|
|
95
104
|
zoom: number;
|
|
96
105
|
}
|
|
@@ -109,12 +118,22 @@ interface StoryFormatInfo {
|
|
|
109
118
|
name: string;
|
|
110
119
|
version: string;
|
|
111
120
|
proofing: boolean;
|
|
121
|
+
author?: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
image?: string;
|
|
124
|
+
url?: string;
|
|
125
|
+
license?: string;
|
|
112
126
|
}
|
|
113
127
|
interface Twine2FormatJSON {
|
|
114
128
|
name: string;
|
|
115
129
|
version: string;
|
|
116
130
|
proofing?: boolean;
|
|
117
131
|
source: string;
|
|
132
|
+
author?: string;
|
|
133
|
+
description?: string;
|
|
134
|
+
image?: string;
|
|
135
|
+
url?: string;
|
|
136
|
+
license?: string;
|
|
118
137
|
}
|
|
119
138
|
declare const enum ItemType {
|
|
120
139
|
Error = 0,
|
|
@@ -159,6 +178,7 @@ interface TweeTsConfig {
|
|
|
159
178
|
testMode?: boolean;
|
|
160
179
|
noRemote?: boolean;
|
|
161
180
|
tagAliases?: Record<string, string>;
|
|
181
|
+
sourceInfo?: boolean;
|
|
162
182
|
}
|
|
163
183
|
|
|
164
184
|
declare class TweeTsError extends Error {
|
|
@@ -301,10 +321,13 @@ declare function parseTwee(source: string, options?: ParseOptions): ParseResult;
|
|
|
301
321
|
/**
|
|
302
322
|
* Parse the Twine 2 format.js JSON chunk.
|
|
303
323
|
* Handles Harlowe's malformed JSON by stripping the "setup" function property.
|
|
324
|
+
* Per spec, the name key is Optional.
|
|
304
325
|
*/
|
|
305
326
|
declare function parseFormatJSON(source: string, formatId: string): Twine2FormatJSON | null;
|
|
306
327
|
/**
|
|
307
328
|
* Discover all story formats in the given search directories.
|
|
329
|
+
* Implements SemVer-based version pruning: within each (name, major) group,
|
|
330
|
+
* only the highest minor.patch survives.
|
|
308
331
|
*/
|
|
309
332
|
declare function discoverFormats(searchDirs: string[]): Map<string, StoryFormatInfo>;
|
|
310
333
|
/**
|
|
@@ -350,4 +373,4 @@ declare function resolveRemoteFormat(name: string, version: string, indices?: st
|
|
|
350
373
|
/** Discover all cached remote formats. Returns a Map like discoverFormats(). */
|
|
351
374
|
declare function discoverCachedFormats(): Map<string, StoryFormatInfo>;
|
|
352
375
|
|
|
353
|
-
export { type BrokenLink, CONFIG_FILENAME, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type Diagnostic, type InlineSource, ItemType, type LexerItem, type OutputMode, type Passage, type PassageMetadata, type SFAIndex, type SFAIndexEntry, type SourceInput, type Story, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, applyTagAliases, compile, compileToFile, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, generateIFID, getCacheDir, getFormatSearchDirs, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
|
376
|
+
export { type BrokenLink, CONFIG_FILENAME, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type Diagnostic, type InlineSource, ItemType, type LexerItem, type OutputMode, type Passage, type PassageMetadata, type SFAIndex, type SFAIndexEntry, type SourceInput, type SourceLocation, type Story, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, applyTagAliases, compile, compileToFile, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, generateIFID, getCacheDir, getFormatSearchDirs, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ interface CompileOptions {
|
|
|
38
38
|
noRemote?: boolean;
|
|
39
39
|
/** Map alias tags to canonical special tags (e.g. { library: 'script' }). */
|
|
40
40
|
tagAliases?: Record<string, string>;
|
|
41
|
+
/** Emit source file and line as data- attributes on passage elements. Default: false. */
|
|
42
|
+
sourceInfo?: boolean;
|
|
41
43
|
}
|
|
42
44
|
interface CompileToFileOptions extends CompileOptions {
|
|
43
45
|
/** Output file path. */
|
|
@@ -73,15 +75,21 @@ interface CompileStats {
|
|
|
73
75
|
words: number;
|
|
74
76
|
files: string[];
|
|
75
77
|
}
|
|
78
|
+
interface SourceLocation {
|
|
79
|
+
readonly file: string;
|
|
80
|
+
readonly line: number;
|
|
81
|
+
}
|
|
76
82
|
interface PassageMetadata {
|
|
77
83
|
position?: string;
|
|
78
84
|
size?: string;
|
|
85
|
+
[key: string]: string | undefined;
|
|
79
86
|
}
|
|
80
87
|
interface Passage {
|
|
81
88
|
name: string;
|
|
82
89
|
tags: string[];
|
|
83
90
|
text: string;
|
|
84
91
|
metadata?: PassageMetadata;
|
|
92
|
+
source?: SourceLocation;
|
|
85
93
|
}
|
|
86
94
|
interface Twine1Metadata {
|
|
87
95
|
settings: Map<string, string>;
|
|
@@ -91,6 +99,7 @@ interface Twine2Metadata {
|
|
|
91
99
|
formatVersion: string;
|
|
92
100
|
options: Map<string, boolean>;
|
|
93
101
|
start: string;
|
|
102
|
+
tags: string;
|
|
94
103
|
tagColors: Map<string, string>;
|
|
95
104
|
zoom: number;
|
|
96
105
|
}
|
|
@@ -109,12 +118,22 @@ interface StoryFormatInfo {
|
|
|
109
118
|
name: string;
|
|
110
119
|
version: string;
|
|
111
120
|
proofing: boolean;
|
|
121
|
+
author?: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
image?: string;
|
|
124
|
+
url?: string;
|
|
125
|
+
license?: string;
|
|
112
126
|
}
|
|
113
127
|
interface Twine2FormatJSON {
|
|
114
128
|
name: string;
|
|
115
129
|
version: string;
|
|
116
130
|
proofing?: boolean;
|
|
117
131
|
source: string;
|
|
132
|
+
author?: string;
|
|
133
|
+
description?: string;
|
|
134
|
+
image?: string;
|
|
135
|
+
url?: string;
|
|
136
|
+
license?: string;
|
|
118
137
|
}
|
|
119
138
|
declare const enum ItemType {
|
|
120
139
|
Error = 0,
|
|
@@ -159,6 +178,7 @@ interface TweeTsConfig {
|
|
|
159
178
|
testMode?: boolean;
|
|
160
179
|
noRemote?: boolean;
|
|
161
180
|
tagAliases?: Record<string, string>;
|
|
181
|
+
sourceInfo?: boolean;
|
|
162
182
|
}
|
|
163
183
|
|
|
164
184
|
declare class TweeTsError extends Error {
|
|
@@ -301,10 +321,13 @@ declare function parseTwee(source: string, options?: ParseOptions): ParseResult;
|
|
|
301
321
|
/**
|
|
302
322
|
* Parse the Twine 2 format.js JSON chunk.
|
|
303
323
|
* Handles Harlowe's malformed JSON by stripping the "setup" function property.
|
|
324
|
+
* Per spec, the name key is Optional.
|
|
304
325
|
*/
|
|
305
326
|
declare function parseFormatJSON(source: string, formatId: string): Twine2FormatJSON | null;
|
|
306
327
|
/**
|
|
307
328
|
* Discover all story formats in the given search directories.
|
|
329
|
+
* Implements SemVer-based version pruning: within each (name, major) group,
|
|
330
|
+
* only the highest minor.patch survives.
|
|
308
331
|
*/
|
|
309
332
|
declare function discoverFormats(searchDirs: string[]): Map<string, StoryFormatInfo>;
|
|
310
333
|
/**
|
|
@@ -350,4 +373,4 @@ declare function resolveRemoteFormat(name: string, version: string, indices?: st
|
|
|
350
373
|
/** Discover all cached remote formats. Returns a Map like discoverFormats(). */
|
|
351
374
|
declare function discoverCachedFormats(): Map<string, StoryFormatInfo>;
|
|
352
375
|
|
|
353
|
-
export { type BrokenLink, CONFIG_FILENAME, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type Diagnostic, type InlineSource, ItemType, type LexerItem, type OutputMode, type Passage, type PassageMetadata, type SFAIndex, type SFAIndexEntry, type SourceInput, type Story, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, applyTagAliases, compile, compileToFile, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, generateIFID, getCacheDir, getFormatSearchDirs, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
|
376
|
+
export { type BrokenLink, CONFIG_FILENAME, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type Diagnostic, type InlineSource, ItemType, type LexerItem, type OutputMode, type Passage, type PassageMetadata, type SFAIndex, type SFAIndexEntry, type SourceInput, type SourceLocation, type Story, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, applyTagAliases, compile, compileToFile, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, generateIFID, getCacheDir, getFormatSearchDirs, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
package/dist/index.js
CHANGED
|
@@ -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 = "";
|
|
@@ -146,12 +157,14 @@ function hasMetadataSize(p) {
|
|
|
146
157
|
return p.metadata != null && p.metadata.size != null && p.metadata.size !== "";
|
|
147
158
|
}
|
|
148
159
|
function hasAnyMetadata(p) {
|
|
149
|
-
|
|
160
|
+
if (!p.metadata) return false;
|
|
161
|
+
return Object.values(p.metadata).some((v) => v != null && v !== "");
|
|
150
162
|
}
|
|
151
163
|
function marshalMetadata(meta) {
|
|
152
164
|
const obj = {};
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
166
|
+
if (typeof value === "string" && value) obj[key] = value;
|
|
167
|
+
}
|
|
155
168
|
return JSON.stringify(obj);
|
|
156
169
|
}
|
|
157
170
|
function passageToTwee(p, outMode) {
|
|
@@ -177,7 +190,7 @@ function passageToTwee(p, outMode) {
|
|
|
177
190
|
output += "\n\n";
|
|
178
191
|
return output;
|
|
179
192
|
}
|
|
180
|
-
function passageToPassagedata(p, pid) {
|
|
193
|
+
function passageToPassagedata(p, pid, options) {
|
|
181
194
|
let position;
|
|
182
195
|
let size;
|
|
183
196
|
if (hasMetadataPosition(p)) {
|
|
@@ -194,9 +207,13 @@ function passageToPassagedata(p, pid) {
|
|
|
194
207
|
} else {
|
|
195
208
|
size = "100,100";
|
|
196
209
|
}
|
|
197
|
-
|
|
210
|
+
let attrs = `<tw-passagedata pid="${pid}" name=${quote(fullAttrEscape(p.name))} tags=${quote(attrEscape(p.tags.join(" ")))} position=${quote(attrEscape(position))} size=${quote(attrEscape(size))}`;
|
|
211
|
+
if (options?.sourceInfo && p.source) {
|
|
212
|
+
attrs += ` data-source-file=${quote(attrEscape(p.source.file))} data-source-line="${p.source.line}"`;
|
|
213
|
+
}
|
|
214
|
+
return `${attrs}>${htmlEscape(p.text)}</tw-passagedata>`;
|
|
198
215
|
}
|
|
199
|
-
function passageToTiddler(p, pid) {
|
|
216
|
+
function passageToTiddler(p, pid, obfuscateRot13 = false) {
|
|
200
217
|
let position;
|
|
201
218
|
if (hasMetadataPosition(p)) {
|
|
202
219
|
position = p.metadata?.position ?? "";
|
|
@@ -207,7 +224,10 @@ function passageToTiddler(p, pid) {
|
|
|
207
224
|
const yp = x === 0 ? y : y + 1;
|
|
208
225
|
position = `${xp * 140 - 130},${yp * 140 - 130}`;
|
|
209
226
|
}
|
|
210
|
-
|
|
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>`;
|
|
211
231
|
}
|
|
212
232
|
function countWords(p) {
|
|
213
233
|
let text = p.text;
|
|
@@ -250,6 +270,7 @@ function createStory() {
|
|
|
250
270
|
formatVersion: "",
|
|
251
271
|
options: /* @__PURE__ */ new Map(),
|
|
252
272
|
start: "",
|
|
273
|
+
tags: "",
|
|
253
274
|
tagColors: /* @__PURE__ */ new Map(),
|
|
254
275
|
zoom: 1
|
|
255
276
|
}
|
|
@@ -296,6 +317,7 @@ function marshalStoryData(story) {
|
|
|
296
317
|
}
|
|
297
318
|
if (options.length > 0) data.options = options;
|
|
298
319
|
if (story.twine2.start) data.start = story.twine2.start;
|
|
320
|
+
if (story.twine2.tags) data.tags = story.twine2.tags;
|
|
299
321
|
if (story.twine2.tagColors.size > 0) {
|
|
300
322
|
data["tag-colors"] = Object.fromEntries(story.twine2.tagColors);
|
|
301
323
|
}
|
|
@@ -323,6 +345,7 @@ function unmarshalStoryData(story, json) {
|
|
|
323
345
|
}
|
|
324
346
|
}
|
|
325
347
|
if (typeof data.start === "string" && data.start) story.twine2.start = data.start;
|
|
348
|
+
if (typeof data.tags === "string") story.twine2.tags = data.tags;
|
|
326
349
|
if (typeof data["tag-colors"] === "object" && data["tag-colors"] !== null && !Array.isArray(data["tag-colors"])) {
|
|
327
350
|
for (const [tag, color] of Object.entries(data["tag-colors"])) {
|
|
328
351
|
if (typeof color === "string") story.twine2.tagColors.set(tag, color);
|
|
@@ -394,7 +417,7 @@ function storyAdd(story, p, diagnostics) {
|
|
|
394
417
|
processed = { ...p, text: marshalStoryData(story) };
|
|
395
418
|
} else {
|
|
396
419
|
diagnostics.push({
|
|
397
|
-
level: "
|
|
420
|
+
level: "warning",
|
|
398
421
|
message: `Cannot unmarshal "StoryData" compiler special passage; ${err}.`
|
|
399
422
|
});
|
|
400
423
|
}
|
|
@@ -617,17 +640,41 @@ function baseNameWithoutExt(filename) {
|
|
|
617
640
|
}
|
|
618
641
|
|
|
619
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
|
+
}
|
|
620
654
|
function parseFormatJSON(source, formatId) {
|
|
621
655
|
const first = source.indexOf("{");
|
|
622
656
|
const last = source.lastIndexOf("}");
|
|
623
657
|
if (first === -1 || last === -1) return null;
|
|
624
658
|
let chunk = source.slice(first, last + 1);
|
|
625
659
|
const parse = (json) => {
|
|
626
|
-
const raw =
|
|
660
|
+
const raw = relaxedJSONParse(json);
|
|
627
661
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
628
662
|
const obj = raw;
|
|
629
|
-
if (typeof obj.
|
|
630
|
-
|
|
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;
|
|
631
678
|
};
|
|
632
679
|
try {
|
|
633
680
|
return parse(chunk);
|
|
@@ -693,17 +740,55 @@ function discoverFormats(searchDirs) {
|
|
|
693
740
|
format.name = data.name;
|
|
694
741
|
format.version = data.version;
|
|
695
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;
|
|
696
748
|
} catch {
|
|
697
749
|
continue;
|
|
698
750
|
}
|
|
751
|
+
} else {
|
|
752
|
+
format.name = entry;
|
|
699
753
|
}
|
|
700
754
|
formats.set(entry, format);
|
|
701
755
|
break;
|
|
702
756
|
}
|
|
703
757
|
}
|
|
704
758
|
}
|
|
759
|
+
pruneFormats(formats);
|
|
705
760
|
return formats;
|
|
706
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
|
+
}
|
|
707
792
|
function getFormatSearchDirs(extraPaths = [], useTweegoPath = true) {
|
|
708
793
|
const subdirNames = ["storyformats", ".storyformats", "story-formats", "storyFormats", "targets"];
|
|
709
794
|
const basePaths = /* @__PURE__ */ new Set();
|
|
@@ -1106,7 +1191,7 @@ function parseTwee(source, options = {}) {
|
|
|
1106
1191
|
if (pCount > 1 && current) {
|
|
1107
1192
|
passages.push(current);
|
|
1108
1193
|
}
|
|
1109
|
-
current = { name: "", tags: [], text: "" };
|
|
1194
|
+
current = { name: "", tags: [], text: "", source: { file: filename, line: item.line } };
|
|
1110
1195
|
break;
|
|
1111
1196
|
case 3 /* Name */: {
|
|
1112
1197
|
if (!current) break;
|
|
@@ -1156,8 +1241,11 @@ function parseTwee(source, options = {}) {
|
|
|
1156
1241
|
}
|
|
1157
1242
|
const parsed = raw;
|
|
1158
1243
|
const meta = {};
|
|
1159
|
-
|
|
1160
|
-
|
|
1244
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1245
|
+
if (typeof value === "string") {
|
|
1246
|
+
meta[key] = value;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1161
1249
|
current.metadata = meta;
|
|
1162
1250
|
} catch (e) {
|
|
1163
1251
|
diagnostics.push({
|
|
@@ -1171,7 +1259,11 @@ function parseTwee(source, options = {}) {
|
|
|
1171
1259
|
}
|
|
1172
1260
|
case 6 /* Content */: {
|
|
1173
1261
|
if (!current) break;
|
|
1174
|
-
|
|
1262
|
+
if (trim) {
|
|
1263
|
+
current.text = item.val.trim();
|
|
1264
|
+
} else {
|
|
1265
|
+
current.text = item.val.replace(/\n\s*$/, "");
|
|
1266
|
+
}
|
|
1175
1267
|
break;
|
|
1176
1268
|
}
|
|
1177
1269
|
default: {
|
|
@@ -1346,20 +1438,20 @@ var VERSION = "1.2.0";
|
|
|
1346
1438
|
|
|
1347
1439
|
// src/output-twine2.ts
|
|
1348
1440
|
var CREATOR_NAME = "Twee-ts";
|
|
1349
|
-
function toTwine2Archive(story, startName, diagnostics) {
|
|
1350
|
-
return getTwine2DataChunk(story, startName, diagnostics) + "\n";
|
|
1441
|
+
function toTwine2Archive(story, startName, diagnostics, options) {
|
|
1442
|
+
return getTwine2DataChunk(story, startName, diagnostics, options) + "\n";
|
|
1351
1443
|
}
|
|
1352
|
-
function toTwine2HTML(story, format, startName, diagnostics) {
|
|
1444
|
+
function toTwine2HTML(story, format, startName, diagnostics, options) {
|
|
1353
1445
|
let template = readFormatSource(format);
|
|
1354
1446
|
if (template.includes("{{STORY_NAME}}")) {
|
|
1355
1447
|
template = template.replaceAll("{{STORY_NAME}}", htmlEscape(story.name));
|
|
1356
1448
|
}
|
|
1357
1449
|
if (template.includes("{{STORY_DATA}}")) {
|
|
1358
|
-
template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, diagnostics));
|
|
1450
|
+
template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, diagnostics, options));
|
|
1359
1451
|
}
|
|
1360
1452
|
return template;
|
|
1361
1453
|
}
|
|
1362
|
-
function getTwine2DataChunk(story, startName, diagnostics) {
|
|
1454
|
+
function getTwine2DataChunk(story, startName, diagnostics, options) {
|
|
1363
1455
|
ensureIFID(story, diagnostics);
|
|
1364
1456
|
const parts = [];
|
|
1365
1457
|
let startID = "";
|
|
@@ -1399,8 +1491,12 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1399
1491
|
}
|
|
1400
1492
|
}
|
|
1401
1493
|
parts.push(`<script role="script" id="twine-user-script" type="text/twine-javascript">${scriptContent}</script>`);
|
|
1494
|
+
const validTagColors = /* @__PURE__ */ new Set(["gray", "red", "orange", "yellow", "green", "blue", "purple"]);
|
|
1495
|
+
const hexColorPattern = /^#[0-9a-fA-F]{3,8}$/;
|
|
1402
1496
|
for (const [tag, color] of story.twine2.tagColors) {
|
|
1403
|
-
|
|
1497
|
+
if (validTagColors.has(color) || hexColorPattern.test(color)) {
|
|
1498
|
+
parts.push(`<tw-tag name="${attrEscape(tag)}" color="${attrEscape(color)}"></tw-tag>`);
|
|
1499
|
+
}
|
|
1404
1500
|
}
|
|
1405
1501
|
pid = 1;
|
|
1406
1502
|
for (const p of story.passages) {
|
|
@@ -1410,7 +1506,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1410
1506
|
if (p.name === "StorySettings" && story.twine1.settings.size === 0) {
|
|
1411
1507
|
continue;
|
|
1412
1508
|
}
|
|
1413
|
-
parts.push(passageToPassagedata(p, pid));
|
|
1509
|
+
parts.push(passageToPassagedata(p, pid, options));
|
|
1414
1510
|
if (startName === p.name) {
|
|
1415
1511
|
startID = String(pid);
|
|
1416
1512
|
}
|
|
@@ -1420,9 +1516,9 @@ function getTwine2DataChunk(story, startName, diagnostics) {
|
|
|
1420
1516
|
for (const [opt, val] of story.twine2.options) {
|
|
1421
1517
|
if (val) opts.push(opt);
|
|
1422
1518
|
}
|
|
1423
|
-
const
|
|
1519
|
+
const optionsStr = opts.join(" ");
|
|
1424
1520
|
const zoom = String(story.twine2.zoom);
|
|
1425
|
-
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(
|
|
1521
|
+
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(optionsStr)}" tags="${attrEscape(story.twine2.tags)}" hidden>`;
|
|
1426
1522
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
1427
1523
|
}
|
|
1428
1524
|
function ensureIFID(story, diagnostics) {
|
|
@@ -1438,7 +1534,7 @@ function ensureIFID(story, diagnostics) {
|
|
|
1438
1534
|
story.ifid = ifid;
|
|
1439
1535
|
diagnostics.push({
|
|
1440
1536
|
level: "error",
|
|
1441
|
-
message: `Story IFID not found. Add
|
|
1537
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
1442
1538
|
});
|
|
1443
1539
|
}
|
|
1444
1540
|
}
|
|
@@ -1449,7 +1545,7 @@ import { join as join3, dirname } from "path";
|
|
|
1449
1545
|
var CREATOR_NAME2 = "twee-ts";
|
|
1450
1546
|
function toTwine1Archive(story, _startName) {
|
|
1451
1547
|
const { data, count } = getTwine1PassageChunk(story);
|
|
1452
|
-
return `<div id="storeArea" data-size="${count}"
|
|
1548
|
+
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
1453
1549
|
`;
|
|
1454
1550
|
}
|
|
1455
1551
|
function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
@@ -1494,12 +1590,13 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
|
|
|
1494
1590
|
return template;
|
|
1495
1591
|
}
|
|
1496
1592
|
function getTwine1PassageChunk(story) {
|
|
1593
|
+
const obfuscateRot13 = story.twine1.settings.get("obfuscate") === "rot13";
|
|
1497
1594
|
let data = "";
|
|
1498
1595
|
let count = 0;
|
|
1499
1596
|
for (const p of story.passages) {
|
|
1500
1597
|
if (hasTag(p, "Twine.private")) continue;
|
|
1501
1598
|
count++;
|
|
1502
|
-
data += passageToTiddler(p, count);
|
|
1599
|
+
data += passageToTiddler(p, count, obfuscateRot13);
|
|
1503
1600
|
}
|
|
1504
1601
|
return { data, count };
|
|
1505
1602
|
}
|
|
@@ -1876,6 +1973,7 @@ async function buildOutput(options) {
|
|
|
1876
1973
|
const twee2Compat = options.twee2Compat ?? false;
|
|
1877
1974
|
const testMode = options.testMode ?? false;
|
|
1878
1975
|
const noRemote = options.noRemote ?? false;
|
|
1976
|
+
const sourceInfo = options.sourceInfo ?? false;
|
|
1879
1977
|
clearIndexCache();
|
|
1880
1978
|
const filePaths = [];
|
|
1881
1979
|
const inlineSources = [];
|
|
@@ -1937,7 +2035,7 @@ async function buildOutput(options) {
|
|
|
1937
2035
|
output = toTwee(story, outputMode);
|
|
1938
2036
|
break;
|
|
1939
2037
|
case "twine2-archive":
|
|
1940
|
-
output = toTwine2Archive(story, startName, diagnostics);
|
|
2038
|
+
output = toTwine2Archive(story, startName, diagnostics, { sourceInfo });
|
|
1941
2039
|
break;
|
|
1942
2040
|
case "twine1-archive":
|
|
1943
2041
|
output = toTwine1Archive(story, startName);
|
|
@@ -1956,7 +2054,7 @@ async function buildOutput(options) {
|
|
|
1956
2054
|
throw new TweeTsError("No story format available for HTML output.", diagnostics);
|
|
1957
2055
|
}
|
|
1958
2056
|
if (format.isTwine2) {
|
|
1959
|
-
output = toTwine2HTML(story, format, startName, diagnostics);
|
|
2057
|
+
output = toTwine2HTML(story, format, startName, diagnostics, { sourceInfo });
|
|
1960
2058
|
} else {
|
|
1961
2059
|
if (story.name === "" && !storyHas(story, "StoryTitle")) {
|
|
1962
2060
|
diagnostics.push({
|
|
@@ -1998,11 +2096,14 @@ function storyToJSON(story) {
|
|
|
1998
2096
|
tags: p.tags,
|
|
1999
2097
|
text: p.text
|
|
2000
2098
|
};
|
|
2001
|
-
if (p.metadata
|
|
2099
|
+
if (p.metadata) {
|
|
2002
2100
|
const meta = {};
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2101
|
+
for (const [key, value] of Object.entries(p.metadata)) {
|
|
2102
|
+
if (typeof value === "string") meta[key] = value;
|
|
2103
|
+
}
|
|
2104
|
+
if (Object.keys(meta).length > 0) {
|
|
2105
|
+
entry.metadata = meta;
|
|
2106
|
+
}
|
|
2006
2107
|
}
|
|
2007
2108
|
storyPassages.push(entry);
|
|
2008
2109
|
}
|
|
@@ -2186,7 +2287,7 @@ function validateConfig(data) {
|
|
|
2186
2287
|
errors.push(`"${key}" must be a string.`);
|
|
2187
2288
|
}
|
|
2188
2289
|
}
|
|
2189
|
-
for (const key of ["useTweegoPath", "trim", "twee2Compat", "testMode", "noRemote"]) {
|
|
2290
|
+
for (const key of ["useTweegoPath", "trim", "twee2Compat", "testMode", "noRemote", "sourceInfo"]) {
|
|
2190
2291
|
if (key in obj && typeof obj[key] !== "boolean") {
|
|
2191
2292
|
errors.push(`"${key}" must be a boolean.`);
|
|
2192
2293
|
}
|