drafter-mcp 0.25.1 → 0.26.1
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/drafter-mcp.js +462 -93
- package/package.json +1 -1
package/drafter-mcp.js
CHANGED
|
@@ -23233,7 +23233,7 @@ function toError(value) {
|
|
|
23233
23233
|
return value instanceof Error ? value : new Error(String(value));
|
|
23234
23234
|
}
|
|
23235
23235
|
// package.json
|
|
23236
|
-
var version = "0.
|
|
23236
|
+
var version = "0.26.1";
|
|
23237
23237
|
// ../fountain/src/inline.ts
|
|
23238
23238
|
const MARKERS$1 = /[*_\\]/;
|
|
23239
23239
|
const isSpace$10 = (code) => code === void 0 || /\s/.test(String.fromCharCode(code));
|
|
@@ -25892,7 +25892,7 @@ function columns(s) {
|
|
|
25892
25892
|
return n;
|
|
25893
25893
|
}
|
|
25894
25894
|
const joined = (runs) => runs.map((run) => run.text).join("");
|
|
25895
|
-
const isBlank$
|
|
25895
|
+
const isBlank$2 = (line) => joined(line.runs).trim() === "" && (!line.right || joined(line.right.runs).trim() === "");
|
|
25896
25896
|
function colFor(frame, length) {
|
|
25897
25897
|
if (frame.align === "center") return frame.indent + Math.floor((frame.width - length) / 2);
|
|
25898
25898
|
if (frame.align === "right") return frame.indent + frame.width - length;
|
|
@@ -26072,7 +26072,7 @@ function titlePage(tp, template, height, diagnostics) {
|
|
|
26072
26072
|
page: {
|
|
26073
26073
|
number: 0,
|
|
26074
26074
|
lines: rows.map((row, k) => {
|
|
26075
|
-
if (row && !isBlank$
|
|
26075
|
+
if (row && !isBlank$2(row)) last = k;
|
|
26076
26076
|
return row ?? blankLine(tp.span.from);
|
|
26077
26077
|
}),
|
|
26078
26078
|
sceneNumbersLeft: [],
|
|
@@ -26293,7 +26293,7 @@ var Items = class {
|
|
|
26293
26293
|
lines,
|
|
26294
26294
|
spaceBefore: p.style.spaceBefore,
|
|
26295
26295
|
spaceAfter: p.style.spaceAfter ?? 0,
|
|
26296
|
-
keepWithNext: p.style.keepWithNext === true || lines.every(isBlank$
|
|
26296
|
+
keepWithNext: p.style.keepWithNext === true || lines.every(isBlank$2),
|
|
26297
26297
|
keepTogether: p.style.keepTogether === true
|
|
26298
26298
|
};
|
|
26299
26299
|
}
|
|
@@ -26324,7 +26324,7 @@ var Items = class {
|
|
|
26324
26324
|
if (!p) continue;
|
|
26325
26325
|
const kind = this.#blocks[m].kind;
|
|
26326
26326
|
for (const line of this.#lines(m, p)) {
|
|
26327
|
-
const tag = isBlank$
|
|
26327
|
+
const tag = isBlank$2(line) ? "gap" : kind === "parenthetical" ? "paren" : "text";
|
|
26328
26328
|
body.push(line);
|
|
26329
26329
|
tags.push(tag);
|
|
26330
26330
|
spoken.push(spoken.at(-1) + (tag === "text" ? 1 : 0));
|
|
@@ -26453,7 +26453,7 @@ var Filler = class {
|
|
|
26453
26453
|
*/
|
|
26454
26454
|
#canStart(item, room, space) {
|
|
26455
26455
|
if (item.type === "block") {
|
|
26456
|
-
const lead = Math.max(0, item.lines.findIndex((line) => !isBlank$
|
|
26456
|
+
const lead = Math.max(0, item.lines.findIndex((line) => !isBlank$2(line)));
|
|
26457
26457
|
return space + (item.keepTogether ? item.lines.length : Math.min(item.lines.length, lead + this.template.keepWithNextLines)) <= room;
|
|
26458
26458
|
}
|
|
26459
26459
|
if (item.type === "dual") return space + item.rows.length <= room;
|
|
@@ -26593,7 +26593,7 @@ var Pager = class {
|
|
|
26593
26593
|
/** Adds a line, starting a new page when this one is full. A page never starts blank. */
|
|
26594
26594
|
put(line) {
|
|
26595
26595
|
if (this.#lines.length >= this.height) this.newPage();
|
|
26596
|
-
const blank = isBlank$
|
|
26596
|
+
const blank = isBlank$2(line);
|
|
26597
26597
|
if (blank && this.#lines.length === 0) return;
|
|
26598
26598
|
const at = {
|
|
26599
26599
|
page: this.pages.length + 1,
|
|
@@ -30949,18 +30949,18 @@ const MOST_HOOK = 200;
|
|
|
30949
30949
|
/** The planned length's bounds, in pages. */
|
|
30950
30950
|
const FEWEST_PAGES = 1;
|
|
30951
30951
|
const MOST_PAGES = 999;
|
|
30952
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30952
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30953
30953
|
/** The `storyMap` part of a parsed metadata block (`doc.meta`), read strictly. */
|
|
30954
30954
|
function storyMapMeta(meta) {
|
|
30955
30955
|
const hooks = Object.create(null);
|
|
30956
|
-
const part = isRecord(meta) ? meta.storyMap : void 0;
|
|
30957
|
-
if (!isRecord(part)) return {
|
|
30956
|
+
const part = isRecord$1(meta) ? meta.storyMap : void 0;
|
|
30957
|
+
if (!isRecord$1(part)) return {
|
|
30958
30958
|
plannedPages: null,
|
|
30959
30959
|
hooks
|
|
30960
30960
|
};
|
|
30961
30961
|
const pages = part.plannedPages;
|
|
30962
30962
|
const plannedPages = typeof pages === "number" && Number.isInteger(pages) && pages >= FEWEST_PAGES && pages <= MOST_PAGES ? pages : null;
|
|
30963
|
-
if (isRecord(part.hooks)) {
|
|
30963
|
+
if (isRecord$1(part.hooks)) {
|
|
30964
30964
|
for (const [key, hook] of Object.entries(part.hooks)) if (typeof hook === "string" && hook.length <= MOST_HOOK) hooks[key] = hook;
|
|
30965
30965
|
}
|
|
30966
30966
|
return {
|
|
@@ -31525,6 +31525,66 @@ function shares(amounts) {
|
|
|
31525
31525
|
}
|
|
31526
31526
|
return whole;
|
|
31527
31527
|
}
|
|
31528
|
+
// ../markdown/src/meta.ts
|
|
31529
|
+
const OPENER = "<!-- drafter ";
|
|
31530
|
+
const CLOSER = " -->";
|
|
31531
|
+
function isSpace$6(code) {
|
|
31532
|
+
return code === 32 || code === 9 || code === 10 || code === 13;
|
|
31533
|
+
}
|
|
31534
|
+
/**
|
|
31535
|
+
* The document's metadata block: its last line (trailing spaces and blank lines aside), starting
|
|
31536
|
+
* at the line's start (so not indented code) as `<!-- drafter `, ending ` -->`, and holding a JSON
|
|
31537
|
+
* object. Anything else, a comment with words after it, or one whose JSON doesn't read as an
|
|
31538
|
+
* object, is the writer's own text. One backwards scan, then one JSON parse: linear.
|
|
31539
|
+
*/
|
|
31540
|
+
function findMarkdownMeta(text) {
|
|
31541
|
+
let end = text.length;
|
|
31542
|
+
while (end > 0 && isSpace$6(text.charCodeAt(end - 1))) end--;
|
|
31543
|
+
const from = text.lastIndexOf("\n", end - 1) + 1;
|
|
31544
|
+
if (end - from < 17) return null;
|
|
31545
|
+
if (!text.startsWith(OPENER, from) || !text.startsWith(CLOSER, end - 4)) return null;
|
|
31546
|
+
let meta;
|
|
31547
|
+
try {
|
|
31548
|
+
meta = JSON.parse(text.slice(from + 13, end - 4));
|
|
31549
|
+
} catch {
|
|
31550
|
+
return null;
|
|
31551
|
+
}
|
|
31552
|
+
if (meta === null || typeof meta !== "object" || Array.isArray(meta)) return null;
|
|
31553
|
+
return {
|
|
31554
|
+
from,
|
|
31555
|
+
to: end,
|
|
31556
|
+
meta
|
|
31557
|
+
};
|
|
31558
|
+
}
|
|
31559
|
+
/**
|
|
31560
|
+
* The document's text and its metadata. The body is the text without the block, the line break
|
|
31561
|
+
* after it, and the one blank line before it that `setMarkdownMeta` wrote, so a body written with
|
|
31562
|
+
* `setMarkdownMeta` comes back exactly. No block: the whole text, and `meta: null`.
|
|
31563
|
+
*/
|
|
31564
|
+
function readMarkdownMeta(text) {
|
|
31565
|
+
const block = findMarkdownMeta(text);
|
|
31566
|
+
if (!block) return {
|
|
31567
|
+
body: text,
|
|
31568
|
+
meta: null
|
|
31569
|
+
};
|
|
31570
|
+
const before = text.slice(0, block.from);
|
|
31571
|
+
const nl = text.startsWith("\r\n", block.to) ? "\r\n" : text.startsWith("\n", block.to) ? "\n" : before.endsWith("\r\n\r\n") ? "\r\n" : "\n";
|
|
31572
|
+
const gap = nl + nl;
|
|
31573
|
+
return {
|
|
31574
|
+
body: before.endsWith(gap) ? before.slice(0, -gap.length) : before,
|
|
31575
|
+
meta: block.meta
|
|
31576
|
+
};
|
|
31577
|
+
}
|
|
31578
|
+
/**
|
|
31579
|
+
* The document as prose: its body (`readMarkdownMeta`), the text without its trailing metadata
|
|
31580
|
+
* block, the line breaks after it, and the blank line `setMarkdownMeta` wrote before it. The block
|
|
31581
|
+
* is Drafter's, not the writer's: the Preview, Print, and every export draw the prose, the word
|
|
31582
|
+
* counts count it, and the cut finder and Compare compare it. The prose is the text's start, so an
|
|
31583
|
+
* offset into it is the same offset into the text. No block: the whole text.
|
|
31584
|
+
*/
|
|
31585
|
+
function markdownProse(text) {
|
|
31586
|
+
return readMarkdownMeta(text).body;
|
|
31587
|
+
}
|
|
31528
31588
|
// ../markdown/src/words.ts
|
|
31529
31589
|
/**
|
|
31530
31590
|
* Whether a UTF-16 code unit is one that `/\s/` matches: the ASCII spaces (tab, line feed,
|
|
@@ -31533,7 +31593,7 @@ function shares(amounts) {
|
|
|
31533
31593
|
* medium mathematical space, the ideographic space, and the byte-order mark. A comparison per
|
|
31534
31594
|
* character, where a regular expression per character cost more than the reparse on a long script.
|
|
31535
31595
|
*/
|
|
31536
|
-
function isSpace$
|
|
31596
|
+
function isSpace$5(code) {
|
|
31537
31597
|
if (code <= 32) return code === 32 || code >= 9 && code <= 13;
|
|
31538
31598
|
if (code < 160) return false;
|
|
31539
31599
|
return code === 160 || code === 5760 || code >= 8192 && code <= 8202 || code === 8232 || code === 8233 || code === 8239 || code === 8287 || code === 12288 || code === 65279;
|
|
@@ -31543,7 +31603,7 @@ function wordsIn(text) {
|
|
|
31543
31603
|
let count = 0;
|
|
31544
31604
|
let inWord = false;
|
|
31545
31605
|
for (let i = 0; i < text.length; i++) {
|
|
31546
|
-
const space = isSpace$
|
|
31606
|
+
const space = isSpace$5(text.charCodeAt(i));
|
|
31547
31607
|
if (!space && !inWord) count++;
|
|
31548
31608
|
inWord = !space;
|
|
31549
31609
|
}
|
|
@@ -31580,7 +31640,7 @@ const SMALL = /* @__PURE__ */ new Set([
|
|
|
31580
31640
|
const cased = (c) => c.toLowerCase() !== c.toUpperCase();
|
|
31581
31641
|
const isUpper$1 = (c) => cased(c) && c === c.toUpperCase();
|
|
31582
31642
|
const isLower = (c) => cased(c) && c === c.toLowerCase();
|
|
31583
|
-
const isSpace$
|
|
31643
|
+
const isSpace$4 = (c) => c !== void 0 && c !== "" && c.trim() === "";
|
|
31584
31644
|
/** One word in title case: its first letter, and the first after a hyphen, in capitals. */
|
|
31585
31645
|
function titleWord(word, first) {
|
|
31586
31646
|
const chars = Array.from(word);
|
|
@@ -31603,7 +31663,7 @@ function titleWord(word, first) {
|
|
|
31603
31663
|
function titleCase(text) {
|
|
31604
31664
|
let first = true;
|
|
31605
31665
|
return text.split(/(\s+)/).map((part) => {
|
|
31606
|
-
if (part === "" || isSpace$
|
|
31666
|
+
if (part === "" || isSpace$4(part[0])) return part;
|
|
31607
31667
|
const word = titleWord(part, first);
|
|
31608
31668
|
first = false;
|
|
31609
31669
|
return word;
|
|
@@ -31631,7 +31691,7 @@ function withoutNumber(text) {
|
|
|
31631
31691
|
function slashParts(part) {
|
|
31632
31692
|
const out = [];
|
|
31633
31693
|
let from = 0;
|
|
31634
|
-
for (let i = 0; i < part.length; i++) if (part[i] === "/" && (isSpace$
|
|
31694
|
+
for (let i = 0; i < part.length; i++) if (part[i] === "/" && (isSpace$4(part[i - 1]) || isSpace$4(part[i + 1]))) {
|
|
31635
31695
|
out.push(part.slice(from, i));
|
|
31636
31696
|
from = i + 1;
|
|
31637
31697
|
}
|
|
@@ -31647,7 +31707,7 @@ function placeWords(location) {
|
|
|
31647
31707
|
const text = withoutNumber(location);
|
|
31648
31708
|
const parts = [];
|
|
31649
31709
|
let from = 0;
|
|
31650
|
-
for (let i = 1; i < text.length - 1; i++) if (DASHES$1.has(text[i]) && isSpace$
|
|
31710
|
+
for (let i = 1; i < text.length - 1; i++) if (DASHES$1.has(text[i]) && isSpace$4(text[i - 1]) && isSpace$4(text[i + 1])) {
|
|
31651
31711
|
parts.push(text.slice(from, i));
|
|
31652
31712
|
from = i + 1;
|
|
31653
31713
|
}
|
|
@@ -31662,7 +31722,7 @@ const followsOn = (key) => key.startsWith("LATER ") || key.endsWith(" LATER");
|
|
|
31662
31722
|
*/
|
|
31663
31723
|
function followOn(location) {
|
|
31664
31724
|
const text = location.trimEnd();
|
|
31665
|
-
for (let i = text.length - 2; i > 0; i--) if (DASHES$1.has(text[i]) && isSpace$
|
|
31725
|
+
for (let i = text.length - 2; i > 0; i--) if (DASHES$1.has(text[i]) && isSpace$4(text[i - 1]) && isSpace$4(text[i + 1])) {
|
|
31666
31726
|
const part = text.slice(i + 1).trim();
|
|
31667
31727
|
if (!followsOn(part.toUpperCase().split(/\s+/).join(" "))) break;
|
|
31668
31728
|
return {
|
|
@@ -31828,7 +31888,7 @@ const TRAILING = /* @__PURE__ */ new Set([
|
|
|
31828
31888
|
]);
|
|
31829
31889
|
/** A comma or dashes just after a name (or its age), and the spaces after them: anchored, linear. */
|
|
31830
31890
|
const LEAD = /^[,\-–—―][\s,\-–—―]*/u;
|
|
31831
|
-
const isSpace$
|
|
31891
|
+
const isSpace$3 = (c) => c !== void 0 && c.trim() === "";
|
|
31832
31892
|
const isLetter = (c) => c !== void 0 && /\p{L}/u.test(c);
|
|
31833
31893
|
const isStop = (c) => c === "." || c === "!" || c === "?";
|
|
31834
31894
|
/** Whether the period at `i` closes an abbreviation or an initial (`Dr.`, `J.`). */
|
|
@@ -31851,7 +31911,7 @@ function sentenceEnd(text, from = 0, limit = text.length) {
|
|
|
31851
31911
|
let j = i + 1;
|
|
31852
31912
|
while (j < text.length && isStop(text[j])) j++;
|
|
31853
31913
|
while (j < text.length && CLOSERS.has(text[j])) j++;
|
|
31854
|
-
if (j < text.length && !isSpace$
|
|
31914
|
+
if (j < text.length && !isSpace$3(text[j])) {
|
|
31855
31915
|
i = j - 1;
|
|
31856
31916
|
continue;
|
|
31857
31917
|
}
|
|
@@ -31976,7 +32036,7 @@ function nameCase(text, map, names, ages = true) {
|
|
|
31976
32036
|
at += 2;
|
|
31977
32037
|
}
|
|
31978
32038
|
if (!ages) {
|
|
31979
|
-
const age = ageAt(text, isSpace$
|
|
32039
|
+
const age = ageAt(text, isSpace$3(text[at]) ? at + 1 : at);
|
|
31980
32040
|
if (age) at = age.end;
|
|
31981
32041
|
}
|
|
31982
32042
|
}
|
|
@@ -31997,7 +32057,7 @@ function withoutMarks(text) {
|
|
|
31997
32057
|
return text.replace(SCREEN_MARKS, (mark, at) => {
|
|
31998
32058
|
if (mark.startsWith("(") || !mark.endsWith(".")) return GONE;
|
|
31999
32059
|
let next = at + mark.length;
|
|
32000
|
-
while (isSpace$
|
|
32060
|
+
while (isSpace$3(text[next])) next++;
|
|
32001
32061
|
const c = text[next];
|
|
32002
32062
|
return c === void 0 || c !== c.toLowerCase() && c === c.toUpperCase() ? STOP_KEPT : GONE;
|
|
32003
32063
|
}).replace(/\s+/g, " ").replace(/ ?\uE001(?=[,.;:!?])/g, "").replace(/\uE001/g, "").replace(/ ?\uE000/g, ".").replace(/\s+/g, " ").trim();
|
|
@@ -32055,7 +32115,7 @@ function pastCapitals(text, at) {
|
|
|
32055
32115
|
let i = at;
|
|
32056
32116
|
for (let n = 0; n < NAME_WORDS; n++) {
|
|
32057
32117
|
let j = i;
|
|
32058
|
-
while (isSpace$
|
|
32118
|
+
while (isSpace$3(text[j])) j++;
|
|
32059
32119
|
let k = j;
|
|
32060
32120
|
while (k < text.length && /[\p{L}\p{M}'’.-]/u.test(text[k])) k++;
|
|
32061
32121
|
const word = text.slice(j, k).replace(/\.$/, "");
|
|
@@ -32072,11 +32132,11 @@ function pastCapitals(text, at) {
|
|
|
32072
32132
|
*/
|
|
32073
32133
|
function introducedAt(text, at) {
|
|
32074
32134
|
let j = pastCapitals(text, at);
|
|
32075
|
-
while (isSpace$
|
|
32135
|
+
while (isSpace$3(text[j])) j++;
|
|
32076
32136
|
if (ageAt(text, j)) return true;
|
|
32077
32137
|
const aside = asideEnd(text, j);
|
|
32078
32138
|
if (aside >= 0) j = aside;
|
|
32079
|
-
while (isSpace$
|
|
32139
|
+
while (isSpace$3(text[j])) j++;
|
|
32080
32140
|
const c = text[j];
|
|
32081
32141
|
return c === "," || c === "-" || c !== void 0 && DASHES.has(c);
|
|
32082
32142
|
}
|
|
@@ -32087,7 +32147,7 @@ function introducedAt(text, at) {
|
|
|
32087
32147
|
function hookFrom(text, at, map, names) {
|
|
32088
32148
|
let i = pastCapitals(text, at);
|
|
32089
32149
|
let j = i;
|
|
32090
|
-
while (isSpace$
|
|
32150
|
+
while (isSpace$3(text[j])) j++;
|
|
32091
32151
|
const aged = ageAt(text, j);
|
|
32092
32152
|
const aside = aged ? aged.end : asideEnd(text, j);
|
|
32093
32153
|
if (aside >= 0) i = aside;
|
|
@@ -32098,7 +32158,7 @@ function hookFrom(text, at, map, names) {
|
|
|
32098
32158
|
rest = rest.slice(lead.length);
|
|
32099
32159
|
if (lead !== "") for (let k = 0; k < rest.length; k++) {
|
|
32100
32160
|
const c = rest[k];
|
|
32101
|
-
const hyphen = c === "-" && (rest[k + 1] === "-" || isSpace$
|
|
32161
|
+
const hyphen = c === "-" && (rest[k + 1] === "-" || isSpace$3(rest[k - 1]) && isSpace$3(rest[k + 1]));
|
|
32102
32162
|
if (c === "," || DASHES.has(c) || hyphen) {
|
|
32103
32163
|
rest = rest.slice(0, k);
|
|
32104
32164
|
break;
|
|
@@ -33421,7 +33481,7 @@ function groupRows(rows, oldAnchors, newAnchors) {
|
|
|
33421
33481
|
});
|
|
33422
33482
|
return groups;
|
|
33423
33483
|
}
|
|
33424
|
-
const FENCE$
|
|
33484
|
+
const FENCE$2 = /^ {0,3}(`{3,}|~{3,})/;
|
|
33425
33485
|
const HEADING = /^ {0,3}(#{1,3})(?:[ \t]+(.*?))?(?:[ \t]+#+)?[ \t]*$/;
|
|
33426
33486
|
/**
|
|
33427
33487
|
* The lines of a Markdown text that start a heading of level 1 to 3, with its text, skipping fenced
|
|
@@ -33431,7 +33491,7 @@ function markdownAnchors(text) {
|
|
|
33431
33491
|
const anchors = [];
|
|
33432
33492
|
let fence = null;
|
|
33433
33493
|
splitLines(text).lines.forEach((line, i) => {
|
|
33434
|
-
const open = FENCE$
|
|
33494
|
+
const open = FENCE$2.exec(line);
|
|
33435
33495
|
if (fence !== null) {
|
|
33436
33496
|
if (open && open[1][0] === fence[0] && open[1].length >= fence.length && !line.slice(open[0].length).trim()) fence = null;
|
|
33437
33497
|
return;
|
|
@@ -33450,39 +33510,8 @@ function markdownAnchors(text) {
|
|
|
33450
33510
|
});
|
|
33451
33511
|
return anchors;
|
|
33452
33512
|
}
|
|
33453
|
-
// ../markdown/src/meta.ts
|
|
33454
|
-
const OPENER = "<!-- drafter ";
|
|
33455
|
-
const CLOSER = " -->";
|
|
33456
|
-
function isSpace$3(code) {
|
|
33457
|
-
return code === 32 || code === 9 || code === 10 || code === 13;
|
|
33458
|
-
}
|
|
33459
|
-
/**
|
|
33460
|
-
* The document's metadata block: its last line (trailing spaces and blank lines aside), starting
|
|
33461
|
-
* at the line's start (so not indented code) as `<!-- drafter `, ending ` -->`, and holding a JSON
|
|
33462
|
-
* object. Anything else, a comment with words after it, or one whose JSON doesn't read as an
|
|
33463
|
-
* object, is the writer's own text. One backwards scan, then one JSON parse: linear.
|
|
33464
|
-
*/
|
|
33465
|
-
function findMarkdownMeta(text) {
|
|
33466
|
-
let end = text.length;
|
|
33467
|
-
while (end > 0 && isSpace$3(text.charCodeAt(end - 1))) end--;
|
|
33468
|
-
const from = text.lastIndexOf("\n", end - 1) + 1;
|
|
33469
|
-
if (end - from < 17) return null;
|
|
33470
|
-
if (!text.startsWith(OPENER, from) || !text.startsWith(CLOSER, end - 4)) return null;
|
|
33471
|
-
let meta;
|
|
33472
|
-
try {
|
|
33473
|
-
meta = JSON.parse(text.slice(from + 13, end - 4));
|
|
33474
|
-
} catch {
|
|
33475
|
-
return null;
|
|
33476
|
-
}
|
|
33477
|
-
if (meta === null || typeof meta !== "object" || Array.isArray(meta)) return null;
|
|
33478
|
-
return {
|
|
33479
|
-
from,
|
|
33480
|
-
to: end,
|
|
33481
|
-
meta
|
|
33482
|
-
};
|
|
33483
|
-
}
|
|
33484
33513
|
// ../markdown/src/title.ts
|
|
33485
|
-
const isBlank = (c) => c === " " || c === " ";
|
|
33514
|
+
const isBlank$1 = (c) => c === " " || c === " ";
|
|
33486
33515
|
/**
|
|
33487
33516
|
* An ATX heading's parts, read by hand: up to three spaces, 1 to 6 `#` (`prefix`), then a space or
|
|
33488
33517
|
* a tab or the line's end, and the text, without the blanks around it or a closing run of `#`s (one
|
|
@@ -33496,23 +33525,23 @@ function atx(line) {
|
|
|
33496
33525
|
if (hashes < 1 || hashes > 6) return void 0;
|
|
33497
33526
|
const prefix = line.slice(0, i + hashes);
|
|
33498
33527
|
i += hashes;
|
|
33499
|
-
if (i < line.length && !isBlank(line[i])) return void 0;
|
|
33528
|
+
if (i < line.length && !isBlank$1(line[i])) return void 0;
|
|
33500
33529
|
let start = i;
|
|
33501
|
-
while (start < line.length && isBlank(line[start])) start++;
|
|
33530
|
+
while (start < line.length && isBlank$1(line[start])) start++;
|
|
33502
33531
|
let end = line.length;
|
|
33503
|
-
while (end > start && isBlank(line[end - 1])) end--;
|
|
33532
|
+
while (end > start && isBlank$1(line[end - 1])) end--;
|
|
33504
33533
|
let close = end;
|
|
33505
33534
|
while (close > start && line[close - 1] === "#") close--;
|
|
33506
|
-
if (close < end && (close === start || isBlank(line[close - 1]))) {
|
|
33535
|
+
if (close < end && (close === start || isBlank$1(line[close - 1]))) {
|
|
33507
33536
|
end = close;
|
|
33508
|
-
while (end > start && isBlank(line[end - 1])) end--;
|
|
33537
|
+
while (end > start && isBlank$1(line[end - 1])) end--;
|
|
33509
33538
|
}
|
|
33510
33539
|
return {
|
|
33511
33540
|
prefix,
|
|
33512
33541
|
text: line.slice(start, end)
|
|
33513
33542
|
};
|
|
33514
33543
|
}
|
|
33515
|
-
const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
33544
|
+
const FENCE$1 = /^ {0,3}(`{3,}|~{3,})/;
|
|
33516
33545
|
const SETEXT = /^ {0,3}(?:=+|-+)[ \t]*$/;
|
|
33517
33546
|
/**
|
|
33518
33547
|
* The label of a footnote definition at a line's start, after any indentation, quote marks, and
|
|
@@ -33521,15 +33550,15 @@ const SETEXT = /^ {0,3}(?:=+|-+)[ \t]*$/;
|
|
|
33521
33550
|
function definitionLabel(line) {
|
|
33522
33551
|
let i = 0;
|
|
33523
33552
|
for (;;) {
|
|
33524
|
-
while (isBlank(line[i]) || line[i] === ">") i++;
|
|
33553
|
+
while (isBlank$1(line[i]) || line[i] === ">") i++;
|
|
33525
33554
|
const c = line[i];
|
|
33526
|
-
if ((c === "-" || c === "*" || c === "+") && isBlank(line[i + 1])) {
|
|
33555
|
+
if ((c === "-" || c === "*" || c === "+") && isBlank$1(line[i + 1])) {
|
|
33527
33556
|
i++;
|
|
33528
33557
|
continue;
|
|
33529
33558
|
}
|
|
33530
33559
|
let j = i;
|
|
33531
33560
|
while (j - i < 9 && (line[j] ?? "") >= "0" && (line[j] ?? "") <= "9") j++;
|
|
33532
|
-
if (j > i && (line[j] === "." || line[j] === ")") && isBlank(line[j + 1])) {
|
|
33561
|
+
if (j > i && (line[j] === "." || line[j] === ")") && isBlank$1(line[j + 1])) {
|
|
33533
33562
|
i = j + 1;
|
|
33534
33563
|
continue;
|
|
33535
33564
|
}
|
|
@@ -33552,7 +33581,7 @@ function definedLabels(source) {
|
|
|
33552
33581
|
if (end < 0) end = source.length;
|
|
33553
33582
|
const line = source.slice(start, end);
|
|
33554
33583
|
start = end + 1;
|
|
33555
|
-
const opens = FENCE.exec(line);
|
|
33584
|
+
const opens = FENCE$1.exec(line);
|
|
33556
33585
|
if (fence !== void 0) {
|
|
33557
33586
|
if (opens?.[1]?.startsWith(fence.charAt(0)) && opens[1].length >= fence.length) fence = void 0;
|
|
33558
33587
|
continue;
|
|
@@ -33669,7 +33698,7 @@ function squeeze(text) {
|
|
|
33669
33698
|
for (let i = 0; i <= text.length; i++) {
|
|
33670
33699
|
const c = text[i];
|
|
33671
33700
|
const mark = c === "*" || c === "_" || c === "`";
|
|
33672
|
-
const blank = c !== void 0 && !mark && isSpace$
|
|
33701
|
+
const blank = c !== void 0 && !mark && isSpace$5(text.charCodeAt(i));
|
|
33673
33702
|
if (c !== void 0 && !mark && !blank) {
|
|
33674
33703
|
if (from < 0) {
|
|
33675
33704
|
if (space && parts.length > 0) parts.push(" ");
|
|
@@ -33708,7 +33737,7 @@ function markdownTitle(source) {
|
|
|
33708
33737
|
if (end < 0) end = source.length;
|
|
33709
33738
|
const line = source.slice(start, end);
|
|
33710
33739
|
start = end + 1;
|
|
33711
|
-
const opens = FENCE.exec(line);
|
|
33740
|
+
const opens = FENCE$1.exec(line);
|
|
33712
33741
|
if (fence !== void 0) {
|
|
33713
33742
|
if (opens?.[1]?.startsWith(fence.charAt(0)) && opens[1].length >= fence.length) fence = void 0;
|
|
33714
33743
|
before = "";
|
|
@@ -33730,6 +33759,248 @@ function markdownTitle(source) {
|
|
|
33730
33759
|
before = line.trim() === "" || /^ {4}/.test(line) ? "" : line;
|
|
33731
33760
|
}
|
|
33732
33761
|
}
|
|
33762
|
+
// ../novel/src/structure.ts
|
|
33763
|
+
const RANK = {
|
|
33764
|
+
part: 0,
|
|
33765
|
+
chapter: 1,
|
|
33766
|
+
section: 2
|
|
33767
|
+
};
|
|
33768
|
+
const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
33769
|
+
const ATX = /^ {0,3}(#{1,6})(?:[ \t]([^\n]*))?$/;
|
|
33770
|
+
const BREAK = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/;
|
|
33771
|
+
const SETEXT_1 = /^ {0,3}=+[ \t]*$/;
|
|
33772
|
+
const SETEXT_2 = /^ {0,3}-+[ \t]*$/;
|
|
33773
|
+
const NOT_PARAGRAPH_START = /^(?: {4}|\t|<!--)/;
|
|
33774
|
+
const CONTAINER_START = /^ {0,3}(?:>|[-*+](?:[ \t]|$)|\d{1,9}[.)](?:[ \t]|$))/;
|
|
33775
|
+
const PART = /^part(?:\s|$)/i;
|
|
33776
|
+
function isBlank(char) {
|
|
33777
|
+
return char === " " || char === " " || char === "\r";
|
|
33778
|
+
}
|
|
33779
|
+
/** An ATX heading's text: trimmed, without a closing run of `#` (which a space or tab must precede). */
|
|
33780
|
+
function atxTitle(rest) {
|
|
33781
|
+
let a = 0;
|
|
33782
|
+
let b = rest.length;
|
|
33783
|
+
while (a < b && isBlank(rest[a])) a++;
|
|
33784
|
+
while (b > a && isBlank(rest[b - 1])) b--;
|
|
33785
|
+
let c = b;
|
|
33786
|
+
while (c > a && rest[c - 1] === "#") c--;
|
|
33787
|
+
if (c < b && (c === a || isBlank(rest[c - 1]))) {
|
|
33788
|
+
b = c;
|
|
33789
|
+
while (b > a && isBlank(rest[b - 1])) b--;
|
|
33790
|
+
}
|
|
33791
|
+
return rest.slice(a, b);
|
|
33792
|
+
}
|
|
33793
|
+
function slug(title) {
|
|
33794
|
+
return title.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
|
|
33795
|
+
}
|
|
33796
|
+
/** Reads a novel's structure in one pass. The metadata comment, and what it holds, is not read. */
|
|
33797
|
+
function novelStructure(text) {
|
|
33798
|
+
const end = findMarkdownMeta(text)?.from ?? text.length;
|
|
33799
|
+
const roots = [];
|
|
33800
|
+
const seen = /* @__PURE__ */ new Map();
|
|
33801
|
+
const stack = [];
|
|
33802
|
+
let firstHeading = -1;
|
|
33803
|
+
let fence = null;
|
|
33804
|
+
let paraStart = -1;
|
|
33805
|
+
let paraText = "";
|
|
33806
|
+
let container = false;
|
|
33807
|
+
const open = (wanted, title, from, to) => {
|
|
33808
|
+
if (firstHeading < 0) firstHeading = from;
|
|
33809
|
+
const top = stack.length > 0 ? stack[stack.length - 1].node.kind : null;
|
|
33810
|
+
const kind = wanted === "section" && (top === null || top === "part") ? "chapter" : wanted;
|
|
33811
|
+
while (stack.length > 0 && RANK[stack[stack.length - 1].node.kind] >= RANK[kind]) {
|
|
33812
|
+
const done = stack.pop();
|
|
33813
|
+
done.node.span.to = from;
|
|
33814
|
+
}
|
|
33815
|
+
const siblings = stack.length > 0 ? stack[stack.length - 1].node.children : roots;
|
|
33816
|
+
const key = `${kind}:${slug(title)}`;
|
|
33817
|
+
const n = seen.get(key) ?? 0;
|
|
33818
|
+
seen.set(key, n + 1);
|
|
33819
|
+
const node = {
|
|
33820
|
+
id: `${key}:${n}`,
|
|
33821
|
+
kind,
|
|
33822
|
+
title,
|
|
33823
|
+
heading: {
|
|
33824
|
+
from,
|
|
33825
|
+
to
|
|
33826
|
+
},
|
|
33827
|
+
span: {
|
|
33828
|
+
from,
|
|
33829
|
+
to: end
|
|
33830
|
+
},
|
|
33831
|
+
words: 0,
|
|
33832
|
+
ownWords: 0,
|
|
33833
|
+
children: []
|
|
33834
|
+
};
|
|
33835
|
+
siblings.push(node);
|
|
33836
|
+
stack.push({ node });
|
|
33837
|
+
};
|
|
33838
|
+
let pos = 0;
|
|
33839
|
+
while (pos < end) {
|
|
33840
|
+
let eol = text.indexOf("\n", pos);
|
|
33841
|
+
if (eol < 0 || eol > end) eol = end;
|
|
33842
|
+
const next = eol < end ? eol + 1 : end;
|
|
33843
|
+
let lineEnd = eol;
|
|
33844
|
+
if (lineEnd > pos && text.charCodeAt(lineEnd - 1) === 13) lineEnd--;
|
|
33845
|
+
const line = text.slice(pos, lineEnd);
|
|
33846
|
+
const fenceMatch = FENCE.exec(line);
|
|
33847
|
+
if (fence) {
|
|
33848
|
+
if (fenceMatch && fenceMatch[1][0] === fence.char && fenceMatch[1].length >= fence.size && line.trim() === fenceMatch[1]) fence = null;
|
|
33849
|
+
paraStart = -1;
|
|
33850
|
+
} else if (fenceMatch && !(fenceMatch[1][0] === "`" && line.includes("`", fenceMatch[0].length))) {
|
|
33851
|
+
fence = {
|
|
33852
|
+
char: fenceMatch[1][0],
|
|
33853
|
+
size: fenceMatch[1].length
|
|
33854
|
+
};
|
|
33855
|
+
paraStart = -1;
|
|
33856
|
+
} else if (line.trim() === "") {
|
|
33857
|
+
paraStart = -1;
|
|
33858
|
+
container = false;
|
|
33859
|
+
} else {
|
|
33860
|
+
const atx = ATX.exec(line);
|
|
33861
|
+
if (atx) {
|
|
33862
|
+
paraStart = -1;
|
|
33863
|
+
const level = atx[1].length;
|
|
33864
|
+
if (level <= 2) {
|
|
33865
|
+
const title = atxTitle(atx[2] ?? "");
|
|
33866
|
+
open(level === 2 ? "section" : PART.test(title) ? "part" : "chapter", title, pos, lineEnd);
|
|
33867
|
+
}
|
|
33868
|
+
} else if (paraStart >= 0 && SETEXT_1.test(line)) {
|
|
33869
|
+
open(PART.test(paraText) ? "part" : "chapter", paraText, paraStart, lineEnd);
|
|
33870
|
+
paraStart = -1;
|
|
33871
|
+
} else if (paraStart >= 0 && SETEXT_2.test(line)) {
|
|
33872
|
+
open("section", paraText, paraStart, lineEnd);
|
|
33873
|
+
paraStart = -1;
|
|
33874
|
+
} else if (BREAK.test(line)) {
|
|
33875
|
+
paraStart = -1;
|
|
33876
|
+
if (stack.length > 0) open("section", "", pos, lineEnd);
|
|
33877
|
+
} else if (CONTAINER_START.test(line)) {
|
|
33878
|
+
paraStart = -1;
|
|
33879
|
+
container = true;
|
|
33880
|
+
} else if (container) {} else if (paraStart >= 0) paraText += ` ${line.trim()}`;
|
|
33881
|
+
else if (!NOT_PARAGRAPH_START.test(line)) {
|
|
33882
|
+
paraStart = pos;
|
|
33883
|
+
paraText = line.trim();
|
|
33884
|
+
}
|
|
33885
|
+
}
|
|
33886
|
+
pos = next;
|
|
33887
|
+
}
|
|
33888
|
+
const count = (node) => {
|
|
33889
|
+
const own = node.children.length > 0 ? node.children[0].heading.from : node.span.to;
|
|
33890
|
+
node.ownWords = wordsIn(node.title) + wordsIn(text.slice(node.heading.to, own));
|
|
33891
|
+
let words = node.ownWords;
|
|
33892
|
+
for (const child of node.children) {
|
|
33893
|
+
count(child);
|
|
33894
|
+
words += child.words;
|
|
33895
|
+
}
|
|
33896
|
+
node.words = words;
|
|
33897
|
+
};
|
|
33898
|
+
let words = 0;
|
|
33899
|
+
for (const node of roots) {
|
|
33900
|
+
count(node);
|
|
33901
|
+
words += node.words;
|
|
33902
|
+
}
|
|
33903
|
+
const stop = firstHeading < 0 ? end : firstHeading;
|
|
33904
|
+
const preamble = stop > 0 ? {
|
|
33905
|
+
from: 0,
|
|
33906
|
+
to: stop
|
|
33907
|
+
} : null;
|
|
33908
|
+
if (preamble) words += wordsIn(text.slice(0, stop));
|
|
33909
|
+
return {
|
|
33910
|
+
preamble,
|
|
33911
|
+
parts: roots.some((n) => n.kind === "part"),
|
|
33912
|
+
nodes: roots,
|
|
33913
|
+
words
|
|
33914
|
+
};
|
|
33915
|
+
}
|
|
33916
|
+
// ../novel/src/facts.ts
|
|
33917
|
+
const STATUSES = [
|
|
33918
|
+
"todo",
|
|
33919
|
+
"draft",
|
|
33920
|
+
"revised",
|
|
33921
|
+
"done"
|
|
33922
|
+
];
|
|
33923
|
+
/** The longest synopsis kept, in UTF-16 code units: a longer one is cut there when read or written. */
|
|
33924
|
+
const MAX_SYNOPSIS = 2e3;
|
|
33925
|
+
const MAX_TARGET = 1e7;
|
|
33926
|
+
function isRecord(value) {
|
|
33927
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
33928
|
+
}
|
|
33929
|
+
function target(value) {
|
|
33930
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_TARGET ? value : void 0;
|
|
33931
|
+
}
|
|
33932
|
+
/** The text cut to `max` code units, never leaving half of a surrogate pair at the cut. */
|
|
33933
|
+
function cut(value, max) {
|
|
33934
|
+
let s = value.slice(0, max);
|
|
33935
|
+
const last = s.charCodeAt(s.length - 1);
|
|
33936
|
+
if (last >= 55296 && last <= 56319) s = s.slice(0, -1);
|
|
33937
|
+
return s;
|
|
33938
|
+
}
|
|
33939
|
+
/** A manuscript field or contact line cut to `MAX_DETAIL`, as the book keeps it. */
|
|
33940
|
+
function cutDetail(value) {
|
|
33941
|
+
return cut(value, 300);
|
|
33942
|
+
}
|
|
33943
|
+
const DETAIL_KEYS = [
|
|
33944
|
+
"title",
|
|
33945
|
+
"byline",
|
|
33946
|
+
"legalName",
|
|
33947
|
+
"surname",
|
|
33948
|
+
"shortTitle"
|
|
33949
|
+
];
|
|
33950
|
+
function manuscript(value) {
|
|
33951
|
+
if (!isRecord(value)) return void 0;
|
|
33952
|
+
const out = {};
|
|
33953
|
+
for (const key of DETAIL_KEYS) {
|
|
33954
|
+
const v = value[key];
|
|
33955
|
+
if (typeof v === "string" && v.trim() !== "") out[key] = cutDetail(v);
|
|
33956
|
+
}
|
|
33957
|
+
if (Array.isArray(value.contact)) {
|
|
33958
|
+
const lines = [];
|
|
33959
|
+
for (const item of value.contact) {
|
|
33960
|
+
if (typeof item !== "string") continue;
|
|
33961
|
+
for (const line of item.split(/\r\n|\r|\n/)) {
|
|
33962
|
+
const t = cutDetail(line.trim());
|
|
33963
|
+
if (t !== "") lines.push(t);
|
|
33964
|
+
}
|
|
33965
|
+
}
|
|
33966
|
+
if (lines.length > 0) out.contact = lines.slice(0, 10);
|
|
33967
|
+
}
|
|
33968
|
+
if (value.font === "times" || value.font === "courier") out.font = value.font;
|
|
33969
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
33970
|
+
}
|
|
33971
|
+
function newNodes() {
|
|
33972
|
+
return Object.create(null);
|
|
33973
|
+
}
|
|
33974
|
+
function readOne(raw) {
|
|
33975
|
+
if (!isRecord(raw)) return null;
|
|
33976
|
+
const facts = {};
|
|
33977
|
+
const status = raw.status;
|
|
33978
|
+
if (typeof status === "string" && STATUSES.includes(status)) facts.status = status;
|
|
33979
|
+
if (typeof raw.synopsis === "string" && raw.synopsis !== "") facts.synopsis = cut(raw.synopsis, MAX_SYNOPSIS);
|
|
33980
|
+
const t = target(raw.target);
|
|
33981
|
+
if (t !== void 0) facts.target = t;
|
|
33982
|
+
return Object.keys(facts).length > 0 ? facts : null;
|
|
33983
|
+
}
|
|
33984
|
+
/** The facts kept in a document's metadata comment; empty when there are none or they are unreadable. */
|
|
33985
|
+
function readFacts(text) {
|
|
33986
|
+
const out = { nodes: newNodes() };
|
|
33987
|
+
let novel;
|
|
33988
|
+
try {
|
|
33989
|
+
novel = readMarkdownMeta(text).meta?.novel;
|
|
33990
|
+
} catch {
|
|
33991
|
+
return out;
|
|
33992
|
+
}
|
|
33993
|
+
if (!isRecord(novel)) return out;
|
|
33994
|
+
const bookTarget = target(novel.bookTarget);
|
|
33995
|
+
if (bookTarget !== void 0) out.bookTarget = bookTarget;
|
|
33996
|
+
const details = manuscript(novel.manuscript);
|
|
33997
|
+
if (details) out.manuscript = details;
|
|
33998
|
+
if (isRecord(novel.nodes)) for (const id of Object.keys(novel.nodes)) {
|
|
33999
|
+
const facts = readOne(novel.nodes[id]);
|
|
34000
|
+
if (facts) out.nodes[id] = facts;
|
|
34001
|
+
}
|
|
34002
|
+
return out;
|
|
34003
|
+
}
|
|
33733
34004
|
// src/formats.ts
|
|
33734
34005
|
const NO_BYTES = /* @__PURE__ */ new Uint8Array();
|
|
33735
34006
|
/**
|
|
@@ -33746,6 +34017,45 @@ function formatOf(name) {
|
|
|
33746
34017
|
return importKindOf(name) ?? reopenFormatOf(name, NO_BYTES);
|
|
33747
34018
|
}
|
|
33748
34019
|
// src/load.ts
|
|
34020
|
+
/** Whether the text's metadata comment names the novel template (Drafter's own file kind). */
|
|
34021
|
+
function isNovelText(text) {
|
|
34022
|
+
try {
|
|
34023
|
+
return readMarkdownMeta(text).meta?.template === "novel";
|
|
34024
|
+
} catch {
|
|
34025
|
+
return false;
|
|
34026
|
+
}
|
|
34027
|
+
}
|
|
34028
|
+
/**
|
|
34029
|
+
* The title of a unit with none of its own. An untitled section is the one a scene break starts,
|
|
34030
|
+
* which the app calls "Scene break"; a part or chapter with an empty heading is named by its kind.
|
|
34031
|
+
*/
|
|
34032
|
+
const untitled = (kind) => kind === "section" ? "Scene break" : `(untitled ${kind})`;
|
|
34033
|
+
function flatten(nodes, into) {
|
|
34034
|
+
for (const node of nodes) {
|
|
34035
|
+
into.push(node);
|
|
34036
|
+
flatten(node.children, into);
|
|
34037
|
+
}
|
|
34038
|
+
return into;
|
|
34039
|
+
}
|
|
34040
|
+
/**
|
|
34041
|
+
* A novel's units as comparison anchors (their heading lines from 0, as `markdownAnchors` gives
|
|
34042
|
+
* for plain Markdown), so drafts and compares number them as `read_scene` does.
|
|
34043
|
+
*/
|
|
34044
|
+
function novelAnchors(text) {
|
|
34045
|
+
const { starts } = splitLines(text);
|
|
34046
|
+
let line = 0;
|
|
34047
|
+
return novelNodes(text).map((node) => {
|
|
34048
|
+
while (line + 1 < starts.length && starts[line + 1] <= node.heading.from) line++;
|
|
34049
|
+
return {
|
|
34050
|
+
line,
|
|
34051
|
+
title: node.title === "" ? untitled(node.kind) : node.title
|
|
34052
|
+
};
|
|
34053
|
+
});
|
|
34054
|
+
}
|
|
34055
|
+
/** A novel's parts, chapters, and sections in document order: the units every tool numbers. */
|
|
34056
|
+
function novelNodes(text) {
|
|
34057
|
+
return flatten(novelStructure(text).nodes, []);
|
|
34058
|
+
}
|
|
33749
34059
|
const NOT_READ = "Drafter for Claude doesn’t read that kind of file. It reads Fountain, plain text, Markdown, Final Draft, Highland, and Fade In files, and Drafter’s HTML and EPUB exports.";
|
|
33750
34060
|
const SCREENPLAY$2 = builtins["us-screenplay"];
|
|
33751
34061
|
let parses = 0;
|
|
@@ -33840,9 +34150,33 @@ function loadScript(file, format, content, text, importNotes, wordsPerMinute) {
|
|
|
33840
34150
|
importNotes
|
|
33841
34151
|
};
|
|
33842
34152
|
}
|
|
34153
|
+
function loadNovel(file, format, content, text, end) {
|
|
34154
|
+
const nodes = novelNodes(text);
|
|
34155
|
+
const facts = readFacts(text).nodes;
|
|
34156
|
+
return {
|
|
34157
|
+
kind: "markdown",
|
|
34158
|
+
file,
|
|
34159
|
+
format,
|
|
34160
|
+
content,
|
|
34161
|
+
text,
|
|
34162
|
+
title: markdownTitle(text) ?? null,
|
|
34163
|
+
sections: nodes.map((node, i) => ({
|
|
34164
|
+
title: node.title === "" ? untitled(node.kind) : node.title,
|
|
34165
|
+
from: node.heading.from,
|
|
34166
|
+
to: Math.min(end, nodes[i + 1]?.heading.from ?? end)
|
|
34167
|
+
})),
|
|
34168
|
+
novel: nodes.map((node) => ({
|
|
34169
|
+
kind: node.kind,
|
|
34170
|
+
words: node.words,
|
|
34171
|
+
ownWords: node.ownWords,
|
|
34172
|
+
...facts[node.id]
|
|
34173
|
+
}))
|
|
34174
|
+
};
|
|
34175
|
+
}
|
|
33843
34176
|
function loadMarkdown(file, format, content, text) {
|
|
33844
34177
|
const meta = findMarkdownMeta(text);
|
|
33845
34178
|
const end = meta ? meta.from : text.length;
|
|
34179
|
+
if (isNovelText(text)) return loadNovel(file, format, content, text, end);
|
|
33846
34180
|
const { starts } = splitLines(text);
|
|
33847
34181
|
const anchors = markdownAnchors(text).filter((anchor) => (starts[anchor.line] ?? 0) < end);
|
|
33848
34182
|
const sections = anchors.map((anchor, i) => {
|
|
@@ -33860,7 +34194,8 @@ function loadMarkdown(file, format, content, text) {
|
|
|
33860
34194
|
content,
|
|
33861
34195
|
text,
|
|
33862
34196
|
title: markdownTitle(text) ?? null,
|
|
33863
|
-
sections
|
|
34197
|
+
sections,
|
|
34198
|
+
novel: null
|
|
33864
34199
|
};
|
|
33865
34200
|
}
|
|
33866
34201
|
/** The file a tool's path names, read and parsed (or the cached parse, while it is unchanged). */
|
|
@@ -35979,7 +36314,24 @@ const overviewOutput = object({
|
|
|
35979
36314
|
sections: array(object({
|
|
35980
36315
|
n: number$1().int(),
|
|
35981
36316
|
title: string(),
|
|
35982
|
-
|
|
36317
|
+
/** A novel's: the whole passage, its sections and chapters included. */
|
|
36318
|
+
words: number$1().int(),
|
|
36319
|
+
/** A novel's: the words before its first section or chapter. */
|
|
36320
|
+
own_words: number$1().int().optional(),
|
|
36321
|
+
kind: _enum([
|
|
36322
|
+
"part",
|
|
36323
|
+
"chapter",
|
|
36324
|
+
"section"
|
|
36325
|
+
]).optional(),
|
|
36326
|
+
status: _enum([
|
|
36327
|
+
"todo",
|
|
36328
|
+
"draft",
|
|
36329
|
+
"revised",
|
|
36330
|
+
"done"
|
|
36331
|
+
]).optional(),
|
|
36332
|
+
synopsis: string().optional(),
|
|
36333
|
+
synopsis_cut: literal(true).optional(),
|
|
36334
|
+
target: number$1().int().optional()
|
|
35983
36335
|
})).optional(),
|
|
35984
36336
|
words: number$1().int().optional(),
|
|
35985
36337
|
left_out: leftOut,
|
|
@@ -36041,12 +36393,26 @@ function bounded(items, share, more) {
|
|
|
36041
36393
|
const sectionWords = (text) => wordsIn(text.replace(/^ {0,3}#{1,3}(?=[ \t]|$)/, "").replace(/[ \t]+#+[ \t]*(?=\n|$)/, ""));
|
|
36042
36394
|
function markdownOverview(loaded, args, ctx, include) {
|
|
36043
36395
|
const { text, sections } = loaded;
|
|
36044
|
-
const
|
|
36045
|
-
|
|
36046
|
-
|
|
36047
|
-
|
|
36048
|
-
|
|
36049
|
-
|
|
36396
|
+
const shown = visibleMarkdownIn(text);
|
|
36397
|
+
const own = sections.map((section) => sectionWords(shown(section.from, section.to)));
|
|
36398
|
+
const items = sections.map((section, i) => {
|
|
36399
|
+
const unit = loaded.novel?.[i];
|
|
36400
|
+
const synopsis = unit?.synopsis === void 0 ? void 0 : clipText(unit.synopsis, TEXT_LIMITS.synopsis);
|
|
36401
|
+
return {
|
|
36402
|
+
n: i + 1,
|
|
36403
|
+
title: clipText(section.title, TEXT_LIMITS.heading).text,
|
|
36404
|
+
words: unit ? unit.words : own[i],
|
|
36405
|
+
...unit ? {
|
|
36406
|
+
kind: unit.kind,
|
|
36407
|
+
own_words: unit.ownWords,
|
|
36408
|
+
...unit.status === void 0 ? {} : { status: unit.status },
|
|
36409
|
+
...synopsis ? { synopsis: synopsis.text } : {},
|
|
36410
|
+
...synopsis?.cut ? { synopsis_cut: true } : {},
|
|
36411
|
+
...unit.target === void 0 ? {} : { target: unit.target }
|
|
36412
|
+
} : {}
|
|
36413
|
+
};
|
|
36414
|
+
});
|
|
36415
|
+
const words = wordsIn(shown(0, sections[0]?.from ?? text.length)) + items.reduce((sum, _s, i) => sum + own[i], 0);
|
|
36050
36416
|
const now = cursorBase("script_overview", loaded, {
|
|
36051
36417
|
script: args.script,
|
|
36052
36418
|
include
|
|
@@ -36210,7 +36576,7 @@ async function overview(args, ctx) {
|
|
|
36210
36576
|
const scriptOverview = {
|
|
36211
36577
|
name: "script_overview",
|
|
36212
36578
|
title: "Script overview",
|
|
36213
|
-
description: "The shape of one script: its title page (never the contact), template, pages, and running time; its acts with their synopses; every scene with its heading, place, time of day, act, page, length, synopsis, and who is in it; every character’s speeches, words, share of the talking, scenes, long absences, and who they are most often with; its places; what is still to be written; and a count of problems. For a Markdown document, its title and sections. Notes and the boneyard are left out unless include asks for them. Pass `next` back as `cursor` for more scenes. A field ending `_cut` means its text was too long and only its start is shown; one ending `_more` counts what didn’t fit.",
|
|
36579
|
+
description: "The shape of one script: its title page (never the contact), template, pages, and running time; its acts with their synopses; every scene with its heading, place, time of day, act, page, length, synopsis, and who is in it; every character’s speeches, words, share of the talking, scenes, long absences, and who they are most often with; its places; what is still to be written; and a count of problems. For a Markdown document, its title and sections; for a novel, its parts, chapters, and sections, each with its words and the status, synopsis, and word target the writer keeps for it. Notes and the boneyard are left out unless include asks for them. Pass `next` back as `cursor` for more scenes. A field ending `_cut` means its text was too long and only its start is shown; one ending `_more` counts what didn’t fit.",
|
|
36214
36580
|
input: overviewInput,
|
|
36215
36581
|
output: overviewOutput,
|
|
36216
36582
|
run: overview
|
|
@@ -36646,7 +37012,8 @@ const listed = object({
|
|
|
36646
37012
|
kind: _enum([
|
|
36647
37013
|
"screenplay",
|
|
36648
37014
|
"stage play",
|
|
36649
|
-
"markdown"
|
|
37015
|
+
"markdown",
|
|
37016
|
+
"novel"
|
|
36650
37017
|
]).nullable(),
|
|
36651
37018
|
title: string().nullable(),
|
|
36652
37019
|
/** The title was longer than TEXT_LIMITS.heading, and is its start. */
|
|
@@ -36704,11 +37071,12 @@ function scriptSummary(content, text) {
|
|
|
36704
37071
|
};
|
|
36705
37072
|
}
|
|
36706
37073
|
function markdownSummary(content, text) {
|
|
37074
|
+
const novel = isNovelText(text);
|
|
36707
37075
|
return {
|
|
36708
37076
|
content,
|
|
36709
|
-
kind: "markdown",
|
|
37077
|
+
kind: novel ? "novel" : "markdown",
|
|
36710
37078
|
title: markdownTitle(text) ?? null,
|
|
36711
|
-
scenes: markdownAnchors(text).length
|
|
37079
|
+
scenes: novel ? novelNodes(text).length : markdownAnchors(text).length
|
|
36712
37080
|
};
|
|
36713
37081
|
}
|
|
36714
37082
|
const tooBig$1 = () => new Refused("it is too big");
|
|
@@ -36912,7 +37280,7 @@ async function describe(file, ctx, spend) {
|
|
|
36912
37280
|
const listScripts = {
|
|
36913
37281
|
name: "list_scripts",
|
|
36914
37282
|
title: "List scripts",
|
|
36915
|
-
description: "Lists the scripts, plays, and documents in the writer’s folders (or one folder, given as a path that starts with a folder’s name): each file’s path, kind, title, scene count, date, size, and how many drafts and cuts Drafter keeps beside it, with a manifest: what Drafter found inside each file (`content`), whether that matches its name (`matches_name`), and why Drafter won’t read it (`refused`). Pass `next` back as `cursor` for the rest.",
|
|
37283
|
+
description: "Lists the scripts, plays, and documents in the writer’s folders (or one folder, given as a path that starts with a folder’s name): each file’s path, kind (screenplay, stage play, novel, or markdown), title, scene count, date, size, and how many drafts and cuts Drafter keeps beside it, with a manifest: what Drafter found inside each file (`content`), whether that matches its name (`matches_name`), and why Drafter won’t read it (`refused`). Pass `next` back as `cursor` for the rest.",
|
|
36916
37284
|
input,
|
|
36917
37285
|
output,
|
|
36918
37286
|
run
|
|
@@ -37992,11 +38360,11 @@ const draftsOutput = object({
|
|
|
37992
38360
|
});
|
|
37993
38361
|
const draftSummaries = new LoadCache(999);
|
|
37994
38362
|
/** A draft's pages, scenes, and words: from its text, never returned. */
|
|
37995
|
-
function figures(text, kind) {
|
|
38363
|
+
function figures(text, kind, novel = false) {
|
|
37996
38364
|
if (kind === "markdown") return {
|
|
37997
38365
|
pages: null,
|
|
37998
|
-
scenes: markdownAnchors(text).length,
|
|
37999
|
-
words: wordsIn(text)
|
|
38366
|
+
scenes: (novel ? novelAnchors(text) : markdownAnchors(text)).length,
|
|
38367
|
+
words: wordsIn(markdownProse(text))
|
|
38000
38368
|
};
|
|
38001
38369
|
const doc = parse(text);
|
|
38002
38370
|
const template = templateFor(doc);
|
|
@@ -38009,7 +38377,7 @@ function figures(text, kind) {
|
|
|
38009
38377
|
}
|
|
38010
38378
|
/** Whether a draft's summary is kept already (so reading it again costs nothing). */
|
|
38011
38379
|
const summarised = (file) => draftSummaries.get(file.real, file.size, file.mtimeMs) !== void 0;
|
|
38012
|
-
async function summaryOf(file, kind) {
|
|
38380
|
+
async function summaryOf(file, kind, novel = false) {
|
|
38013
38381
|
const cached = draftSummaries.get(file.real, file.size, file.mtimeMs);
|
|
38014
38382
|
if (cached !== void 0) return cached;
|
|
38015
38383
|
const read = await readDraft(file, kind);
|
|
@@ -38030,7 +38398,7 @@ async function summaryOf(file, kind) {
|
|
|
38030
38398
|
scenes: null,
|
|
38031
38399
|
words: null,
|
|
38032
38400
|
figures: false
|
|
38033
|
-
} : figures(read.text, kind),
|
|
38401
|
+
} : figures(read.text, kind, novel),
|
|
38034
38402
|
matches: read.matches
|
|
38035
38403
|
} };
|
|
38036
38404
|
}
|
|
@@ -38040,6 +38408,7 @@ async function summaryOf(file, kind) {
|
|
|
38040
38408
|
async function listDrafts(args, ctx) {
|
|
38041
38409
|
const loaded = await load(args.script, ctx);
|
|
38042
38410
|
const kind = kindOf(loaded);
|
|
38411
|
+
const isNovel = loaded.kind === "markdown" && loaded.novel !== null;
|
|
38043
38412
|
const files = await draftFiles(loaded, ctx);
|
|
38044
38413
|
const now = cursorBase("list_drafts", loaded, {
|
|
38045
38414
|
script: args.script,
|
|
@@ -38070,7 +38439,7 @@ async function listDrafts(args, ctx) {
|
|
|
38070
38439
|
const size = fresh ? checked.size : 0;
|
|
38071
38440
|
const parse = fresh && checked.size <= 5242880 ? checked.size : 0;
|
|
38072
38441
|
if (examined > 0 && (read + size > PAGE_READ_BYTES || parsed > 0 && parsed + parse > PAGE_PARSE_BYTES)) break;
|
|
38073
|
-
const summary = checked ? await summaryOf(checked, kind) : null;
|
|
38442
|
+
const summary = checked ? await summaryOf(checked, kind, isNovel) : null;
|
|
38074
38443
|
read += size;
|
|
38075
38444
|
parsed += parse;
|
|
38076
38445
|
if (!summary) {
|
|
@@ -38268,7 +38637,7 @@ async function compareDrafts(args, ctx) {
|
|
|
38268
38637
|
left.notes += shown.leftOut.notes;
|
|
38269
38638
|
left.boneyard += shown.leftOut.boneyard;
|
|
38270
38639
|
}
|
|
38271
|
-
const anchorsOf = kind === "markdown" ?
|
|
38640
|
+
const anchorsOf = kind !== "markdown" ? sceneAnchors : loaded.kind === "markdown" && loaded.novel !== null ? novelAnchors : markdownAnchors;
|
|
38272
38641
|
const oldAnchors = anchorsOf(shownOld.text);
|
|
38273
38642
|
const newAnchors = anchorsOf(shownNew.text);
|
|
38274
38643
|
const { rows } = diffRows(shownOld.text, shownNew.text, {
|
package/package.json
CHANGED