@wildwinter/app-shell 0.52.0 → 0.54.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/README.md +14 -2
- package/dist/{chunk-NGLZUDOT.js → chunk-5BKGDTYD.js} +3 -3
- package/dist/{chunk-NGLZUDOT.js.map → chunk-5BKGDTYD.js.map} +1 -1
- package/dist/{chunk-4QDD6GF3.js → chunk-XJM733E6.js} +3 -3
- package/dist/chunk-XJM733E6.js.map +1 -0
- package/dist/extension-copy.cjs +99 -0
- package/dist/extension-copy.cjs.map +1 -0
- package/dist/extension-copy.d.cts +54 -0
- package/dist/extension-copy.d.ts +54 -0
- package/dist/extension-copy.js +93 -0
- package/dist/extension-copy.js.map +1 -0
- package/dist/history.cjs +5 -0
- package/dist/history.cjs.map +1 -1
- package/dist/history.d.cts +3 -0
- package/dist/history.d.ts +3 -0
- package/dist/history.js +5 -0
- package/dist/history.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/property-list.cjs.map +1 -1
- package/dist/property-list.js +2 -2
- package/dist/value-control.cjs +1 -1
- package/dist/value-control.cjs.map +1 -1
- package/dist/value-control.d.cts +3 -0
- package/dist/value-control.d.ts +3 -0
- package/dist/value-control.js +1 -1
- package/dist/value-rows.cjs +93 -5
- package/dist/value-rows.cjs.map +1 -1
- package/dist/value-rows.d.cts +13 -1
- package/dist/value-rows.d.ts +13 -1
- package/dist/value-rows.js +94 -6
- package/dist/value-rows.js.map +1 -1
- package/package.json +9 -4
- package/dist/chunk-4QDD6GF3.js.map +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/extension-copy.ts
|
|
4
|
+
var quoted = (s) => `\u201C${s}\u201D`;
|
|
5
|
+
var curly = (s) => s.replace(/"([^"]*)"/g, (_, x) => quoted(x));
|
|
6
|
+
var cap = (s) => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;
|
|
7
|
+
var article = (s) => /^[aeiou]/i.test(s) ? "an" : "a";
|
|
8
|
+
function fieldPhrase(fields, path) {
|
|
9
|
+
const tokens = path.split(/\.|(?=\[)/).filter((t) => t !== "");
|
|
10
|
+
const top = fields.find((f) => f.name === tokens[0]);
|
|
11
|
+
const topLabel = top?.label ?? tokens[0] ?? "";
|
|
12
|
+
if (tokens.length <= 1) return topLabel;
|
|
13
|
+
const last = tokens[tokens.length - 1];
|
|
14
|
+
const index = /^\[(\d+)\]$/.exec(last);
|
|
15
|
+
if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;
|
|
16
|
+
const parts = [...top?.fields ?? [], ...top?.of?.fields ?? []];
|
|
17
|
+
return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;
|
|
18
|
+
}
|
|
19
|
+
function valueProblemSentence(code, message, w) {
|
|
20
|
+
const { field, node, extension: name, place } = w;
|
|
21
|
+
const on = `${field} on ${node}`;
|
|
22
|
+
const value = /^"[^"]*" is "([^"]*)"/.exec(message)?.[1];
|
|
23
|
+
switch (code) {
|
|
24
|
+
case "required":
|
|
25
|
+
return { text: `${node} needs ${article(field)} ${field}.` };
|
|
26
|
+
case "wrong-type":
|
|
27
|
+
return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };
|
|
28
|
+
case "below-min": {
|
|
29
|
+
const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message);
|
|
30
|
+
return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : void 0;
|
|
31
|
+
}
|
|
32
|
+
case "above-max": {
|
|
33
|
+
const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message);
|
|
34
|
+
return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : void 0;
|
|
35
|
+
}
|
|
36
|
+
case "not-a-value":
|
|
37
|
+
if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };
|
|
38
|
+
return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
|
|
39
|
+
case "not-a-choice":
|
|
40
|
+
return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
|
|
41
|
+
case "pick-ambiguous": {
|
|
42
|
+
const m = / is "([^"]*)", which is (.+)$/.exec(message);
|
|
43
|
+
return m ? { text: `${on} is ${quoted(m[1])}, which is ${curly(m[2])}, so it's ambiguous.`, next: "Rename one of them." } : void 0;
|
|
44
|
+
}
|
|
45
|
+
case "pick-clash": {
|
|
46
|
+
const m = /^"([^"]*)" is (.+), so a value naming it is ambiguous$/.exec(message);
|
|
47
|
+
return m ? { text: `${quoted(m[1])} is ${curly(m[2])}, so ${field} naming it is ambiguous.`, next: "Rename one of them." } : void 0;
|
|
48
|
+
}
|
|
49
|
+
case "not-a-stage":
|
|
50
|
+
return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its stages.`, next: `Pick one ${place}.` };
|
|
51
|
+
case "renamed-stage": {
|
|
52
|
+
const m = / is "([^"]*)", a stage this extension has since renamed "([^"]*)"/.exec(message);
|
|
53
|
+
return m ? { text: `${on} is ${quoted(m[1])}, a stage ${name} has since renamed ${quoted(m[2])}.`, next: `Pick ${quoted(m[2])} to update it.` } : void 0;
|
|
54
|
+
}
|
|
55
|
+
case "no-such-node": {
|
|
56
|
+
const m = / (?:names|is keyed by) (\w+) "([^"]*)", which does not exist/.exec(message);
|
|
57
|
+
return m ? { text: `${on} names the ${w.kindWord(m[1])} ${quoted(m[2])}, which isn't in the project.`, next: "Choose another, or clear it." } : void 0;
|
|
58
|
+
}
|
|
59
|
+
case "missing-id":
|
|
60
|
+
return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };
|
|
61
|
+
case "duplicate-id": {
|
|
62
|
+
const id = / repeats the id "([^"]*)"/.exec(message)?.[1];
|
|
63
|
+
return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? "")}.`, next: "Give each its own." };
|
|
64
|
+
}
|
|
65
|
+
case "unknown-field":
|
|
66
|
+
case "unknown-key":
|
|
67
|
+
return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: "It's kept, but your game won't get it." };
|
|
68
|
+
default:
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function extensionLine(extension, c) {
|
|
73
|
+
return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : ""}` : void 0;
|
|
74
|
+
}
|
|
75
|
+
function entryProblemLine(message, code, w) {
|
|
76
|
+
const entry = /^entries\.(\w+)\.([^.:\s[]+)(\S*?): (.*)$/.exec(message);
|
|
77
|
+
if (!entry) return void 0;
|
|
78
|
+
const [, kind, id, rest, said] = entry;
|
|
79
|
+
if (code === "orphan") {
|
|
80
|
+
return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: "They're kept, but your game won't get them." });
|
|
81
|
+
}
|
|
82
|
+
if (code === "unresolved") return extensionLine(w.extension, { text: "A merge left a clash in this data that hasn't been resolved.", next: "Resolve it before you publish." });
|
|
83
|
+
const field = fieldPhrase(w.fields(kind), rest.replace(/^\./, ""));
|
|
84
|
+
return extensionLine(w.extension, valueProblemSentence(code, said, {
|
|
85
|
+
extension: w.extension,
|
|
86
|
+
node: w.node(kind, id),
|
|
87
|
+
field,
|
|
88
|
+
place: w.place,
|
|
89
|
+
kindWord: w.kindWord
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exports.entryProblemLine = entryProblemLine;
|
|
94
|
+
exports.extensionLine = extensionLine;
|
|
95
|
+
exports.fieldPhrase = fieldPhrase;
|
|
96
|
+
exports.quoted = quoted;
|
|
97
|
+
exports.valueProblemSentence = valueProblemSentence;
|
|
98
|
+
//# sourceMappingURL=extension-copy.cjs.map
|
|
99
|
+
//# sourceMappingURL=extension-copy.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/extension-copy.ts"],"names":[],"mappings":"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","file":"extension-copy.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// An extension's problems, worded for the writer, in both apps.\n//\n// The command lines word an extension's problems for the file: \"entries.card.c_7k2.cue.sound: \"cue.sound\" is\n// required and not set\". The problems bar says the same for the writer: the extension by its name, the item as\n// the writer knows it, and the field by its label: \"Probe: The bell needs a sound in cue.\" Patterpad wrote these\n// rules first (its problem-copy.ts); they are the family's, here, so the two apps read alike.\n//\n// What is the app's is passed in: how it names the item (a line by its words, a card by its title), the words\n// for its node kinds, and where the writer goes to change a value (\"in the inspector\", \"on its page\"). The rules\n// key on the toolkit's code for the problem (`@wildwinter/toolkit/extensions`' Problem.code), which each app's\n// compiler carries on the issue; a code they do not know stands as the message it came with.\n//\n// No app state, no Electron, no DOM: words only.\n// ---------------------------------------------------------------------------\n\n/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */\nexport interface FieldWordsDecl {\n name: string;\n label?: string;\n fields?: readonly FieldWordsDecl[];\n of?: { fields?: readonly FieldWordsDecl[] };\n}\n\n/** What the app knows to word one problem about a value. */\nexport interface ValueProblemWords {\n /** The extension's name, which leads the line (\"Probe\"). */\n extension: string;\n /** The item, as the writer knows it (\"“What'll it be, stranger?”\", \"the card “bell”\"). */\n node: string;\n /** The field, by its label (`fieldPhrase`). */\n field: string;\n /** Where a value is changed, finishing \"Pick one ...\" (\"in the inspector\", \"on its page\"). */\n place: string;\n /** A node kind in the app's words (\"game event\", \"hand template\"). */\n kindWord(kind: string): string;\n}\n\nexport const quoted = (s: string): string => `“${s}”`;\n/** A check's own quoted names in the family's quotes. */\nconst curly = (s: string): string => s.replace(/\"([^\"]*)\"/g, (_, x: string) => quoted(x));\nconst cap = (s: string): string => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;\nconst article = (s: string): string => (/^[aeiou]/i.test(s) ? \"an\" : \"a\");\n\n/** A field inside a node's entry, by its label: \"Note\", \"sound in cue\", \"item 2 of words\". */\nexport function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string {\n const tokens = path.split(/\\.|(?=\\[)/).filter((t) => t !== \"\");\n const top = fields.find((f) => f.name === tokens[0]);\n const topLabel = top?.label ?? tokens[0] ?? \"\";\n if (tokens.length <= 1) return topLabel;\n const last = tokens[tokens.length - 1]!;\n const index = /^\\[(\\d+)\\]$/.exec(last);\n if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;\n const parts = [...(top?.fields ?? []), ...(top?.of?.fields ?? [])];\n return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;\n}\n\n/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code\n * the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */\nexport function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): { text: string; next?: string } | undefined {\n const { field, node, extension: name, place } = w;\n const on = `${field} on ${node}`;\n const value = /^\"[^\"]*\" is \"([^\"]*)\"/.exec(message)?.[1];\n switch (code) {\n case \"required\": return { text: `${node} needs ${article(field)} ${field}.` };\n case \"wrong-type\": return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };\n case \"below-min\": { const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : undefined; }\n case \"above-max\": { const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : undefined; }\n case \"not-a-value\":\n if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };\n return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"not-a-choice\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"pick-ambiguous\": {\n const m = / is \"([^\"]*)\", which is (.+)$/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, which is ${curly(m[2]!)}, so it's ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"pick-clash\": {\n const m = /^\"([^\"]*)\" is (.+), so a value naming it is ambiguous$/.exec(message);\n return m ? { text: `${quoted(m[1]!)} is ${curly(m[2]!)}, so ${field} naming it is ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"not-a-stage\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its stages.`, next: `Pick one ${place}.` };\n case \"renamed-stage\": {\n const m = / is \"([^\"]*)\", a stage this extension has since renamed \"([^\"]*)\"/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, a stage ${name} has since renamed ${quoted(m[2]!)}.`, next: `Pick ${quoted(m[2]!)} to update it.` } : undefined;\n }\n case \"no-such-node\": {\n const m = / (?:names|is keyed by) (\\w+) \"([^\"]*)\", which does not exist/.exec(message);\n return m ? { text: `${on} names the ${w.kindWord(m[1]!)} ${quoted(m[2]!)}, which isn't in the project.`, next: \"Choose another, or clear it.\" } : undefined;\n }\n case \"missing-id\": return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };\n case \"duplicate-id\": { const id = / repeats the id \"([^\"]*)\"/.exec(message)?.[1]; return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? \"\")}.`, next: \"Give each its own.\" }; }\n case \"unknown-field\": case \"unknown-key\":\n return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: \"It's kept, but your game won't get it.\" };\n default: return undefined;\n }\n}\n\n/** A problem's line: the extension's name, then the sentence and its next step. */\nexport function extensionLine(extension: string, c: { text: string; next?: string } | undefined): string | undefined {\n return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : \"\"}` : undefined;\n}\n\n/** What the app knows to word an entry problem (`entryProblemLine`). */\nexport interface EntryProblemWords {\n extension: string;\n /** The extension's declarations for a node kind. */\n fields(kind: string): readonly FieldWordsDecl[];\n /** A node as the writer knows it, by kind and id. */\n node(kind: string, id: string): string;\n place: string;\n kindWord(kind: string): string;\n}\n\n/**\n * The writer's line for a problem in a node's extension values, read from the command line's\n * \"entries.<kind>.<id>[.<path>]: <what is wrong>\" and the toolkit's code (`orphan` and `unresolved` included);\n * undefined for a message not of that shape, or a code the rules do not know.\n */\nexport function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined {\n const entry = /^entries\\.(\\w+)\\.([^.:\\s[]+)(\\S*?): (.*)$/.exec(message);\n if (!entry) return undefined;\n const [, kind, id, rest, said] = entry as unknown as [string, string, string, string, string];\n if (code === \"orphan\") {\n return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: \"They're kept, but your game won't get them.\" });\n }\n if (code === \"unresolved\") return extensionLine(w.extension, { text: \"A merge left a clash in this data that hasn't been resolved.\", next: \"Resolve it before you publish.\" });\n const field = fieldPhrase(w.fields(kind), rest.replace(/^\\./, \"\"));\n return extensionLine(w.extension, valueProblemSentence(code, said, {\n extension: w.extension, node: w.node(kind, id), field, place: w.place, kindWord: w.kindWord,\n }));\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */
|
|
2
|
+
interface FieldWordsDecl {
|
|
3
|
+
name: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
fields?: readonly FieldWordsDecl[];
|
|
6
|
+
of?: {
|
|
7
|
+
fields?: readonly FieldWordsDecl[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** What the app knows to word one problem about a value. */
|
|
11
|
+
interface ValueProblemWords {
|
|
12
|
+
/** The extension's name, which leads the line ("Probe"). */
|
|
13
|
+
extension: string;
|
|
14
|
+
/** The item, as the writer knows it ("“What'll it be, stranger?”", "the card “bell”"). */
|
|
15
|
+
node: string;
|
|
16
|
+
/** The field, by its label (`fieldPhrase`). */
|
|
17
|
+
field: string;
|
|
18
|
+
/** Where a value is changed, finishing "Pick one ..." ("in the inspector", "on its page"). */
|
|
19
|
+
place: string;
|
|
20
|
+
/** A node kind in the app's words ("game event", "hand template"). */
|
|
21
|
+
kindWord(kind: string): string;
|
|
22
|
+
}
|
|
23
|
+
declare const quoted: (s: string) => string;
|
|
24
|
+
/** A field inside a node's entry, by its label: "Note", "sound in cue", "item 2 of words". */
|
|
25
|
+
declare function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string;
|
|
26
|
+
/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code
|
|
27
|
+
* the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */
|
|
28
|
+
declare function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): {
|
|
29
|
+
text: string;
|
|
30
|
+
next?: string;
|
|
31
|
+
} | undefined;
|
|
32
|
+
/** A problem's line: the extension's name, then the sentence and its next step. */
|
|
33
|
+
declare function extensionLine(extension: string, c: {
|
|
34
|
+
text: string;
|
|
35
|
+
next?: string;
|
|
36
|
+
} | undefined): string | undefined;
|
|
37
|
+
/** What the app knows to word an entry problem (`entryProblemLine`). */
|
|
38
|
+
interface EntryProblemWords {
|
|
39
|
+
extension: string;
|
|
40
|
+
/** The extension's declarations for a node kind. */
|
|
41
|
+
fields(kind: string): readonly FieldWordsDecl[];
|
|
42
|
+
/** A node as the writer knows it, by kind and id. */
|
|
43
|
+
node(kind: string, id: string): string;
|
|
44
|
+
place: string;
|
|
45
|
+
kindWord(kind: string): string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The writer's line for a problem in a node's extension values, read from the command line's
|
|
49
|
+
* "entries.<kind>.<id>[.<path>]: <what is wrong>" and the toolkit's code (`orphan` and `unresolved` included);
|
|
50
|
+
* undefined for a message not of that shape, or a code the rules do not know.
|
|
51
|
+
*/
|
|
52
|
+
declare function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined;
|
|
53
|
+
|
|
54
|
+
export { type EntryProblemWords, type FieldWordsDecl, type ValueProblemWords, entryProblemLine, extensionLine, fieldPhrase, quoted, valueProblemSentence };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */
|
|
2
|
+
interface FieldWordsDecl {
|
|
3
|
+
name: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
fields?: readonly FieldWordsDecl[];
|
|
6
|
+
of?: {
|
|
7
|
+
fields?: readonly FieldWordsDecl[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** What the app knows to word one problem about a value. */
|
|
11
|
+
interface ValueProblemWords {
|
|
12
|
+
/** The extension's name, which leads the line ("Probe"). */
|
|
13
|
+
extension: string;
|
|
14
|
+
/** The item, as the writer knows it ("“What'll it be, stranger?”", "the card “bell”"). */
|
|
15
|
+
node: string;
|
|
16
|
+
/** The field, by its label (`fieldPhrase`). */
|
|
17
|
+
field: string;
|
|
18
|
+
/** Where a value is changed, finishing "Pick one ..." ("in the inspector", "on its page"). */
|
|
19
|
+
place: string;
|
|
20
|
+
/** A node kind in the app's words ("game event", "hand template"). */
|
|
21
|
+
kindWord(kind: string): string;
|
|
22
|
+
}
|
|
23
|
+
declare const quoted: (s: string) => string;
|
|
24
|
+
/** A field inside a node's entry, by its label: "Note", "sound in cue", "item 2 of words". */
|
|
25
|
+
declare function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string;
|
|
26
|
+
/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code
|
|
27
|
+
* the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */
|
|
28
|
+
declare function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): {
|
|
29
|
+
text: string;
|
|
30
|
+
next?: string;
|
|
31
|
+
} | undefined;
|
|
32
|
+
/** A problem's line: the extension's name, then the sentence and its next step. */
|
|
33
|
+
declare function extensionLine(extension: string, c: {
|
|
34
|
+
text: string;
|
|
35
|
+
next?: string;
|
|
36
|
+
} | undefined): string | undefined;
|
|
37
|
+
/** What the app knows to word an entry problem (`entryProblemLine`). */
|
|
38
|
+
interface EntryProblemWords {
|
|
39
|
+
extension: string;
|
|
40
|
+
/** The extension's declarations for a node kind. */
|
|
41
|
+
fields(kind: string): readonly FieldWordsDecl[];
|
|
42
|
+
/** A node as the writer knows it, by kind and id. */
|
|
43
|
+
node(kind: string, id: string): string;
|
|
44
|
+
place: string;
|
|
45
|
+
kindWord(kind: string): string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The writer's line for a problem in a node's extension values, read from the command line's
|
|
49
|
+
* "entries.<kind>.<id>[.<path>]: <what is wrong>" and the toolkit's code (`orphan` and `unresolved` included);
|
|
50
|
+
* undefined for a message not of that shape, or a code the rules do not know.
|
|
51
|
+
*/
|
|
52
|
+
declare function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined;
|
|
53
|
+
|
|
54
|
+
export { type EntryProblemWords, type FieldWordsDecl, type ValueProblemWords, entryProblemLine, extensionLine, fieldPhrase, quoted, valueProblemSentence };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/extension-copy.ts
|
|
2
|
+
var quoted = (s) => `\u201C${s}\u201D`;
|
|
3
|
+
var curly = (s) => s.replace(/"([^"]*)"/g, (_, x) => quoted(x));
|
|
4
|
+
var cap = (s) => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;
|
|
5
|
+
var article = (s) => /^[aeiou]/i.test(s) ? "an" : "a";
|
|
6
|
+
function fieldPhrase(fields, path) {
|
|
7
|
+
const tokens = path.split(/\.|(?=\[)/).filter((t) => t !== "");
|
|
8
|
+
const top = fields.find((f) => f.name === tokens[0]);
|
|
9
|
+
const topLabel = top?.label ?? tokens[0] ?? "";
|
|
10
|
+
if (tokens.length <= 1) return topLabel;
|
|
11
|
+
const last = tokens[tokens.length - 1];
|
|
12
|
+
const index = /^\[(\d+)\]$/.exec(last);
|
|
13
|
+
if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;
|
|
14
|
+
const parts = [...top?.fields ?? [], ...top?.of?.fields ?? []];
|
|
15
|
+
return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;
|
|
16
|
+
}
|
|
17
|
+
function valueProblemSentence(code, message, w) {
|
|
18
|
+
const { field, node, extension: name, place } = w;
|
|
19
|
+
const on = `${field} on ${node}`;
|
|
20
|
+
const value = /^"[^"]*" is "([^"]*)"/.exec(message)?.[1];
|
|
21
|
+
switch (code) {
|
|
22
|
+
case "required":
|
|
23
|
+
return { text: `${node} needs ${article(field)} ${field}.` };
|
|
24
|
+
case "wrong-type":
|
|
25
|
+
return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };
|
|
26
|
+
case "below-min": {
|
|
27
|
+
const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message);
|
|
28
|
+
return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : void 0;
|
|
29
|
+
}
|
|
30
|
+
case "above-max": {
|
|
31
|
+
const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message);
|
|
32
|
+
return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : void 0;
|
|
33
|
+
}
|
|
34
|
+
case "not-a-value":
|
|
35
|
+
if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };
|
|
36
|
+
return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
|
|
37
|
+
case "not-a-choice":
|
|
38
|
+
return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its choices.`, next: `Pick one ${place}.` };
|
|
39
|
+
case "pick-ambiguous": {
|
|
40
|
+
const m = / is "([^"]*)", which is (.+)$/.exec(message);
|
|
41
|
+
return m ? { text: `${on} is ${quoted(m[1])}, which is ${curly(m[2])}, so it's ambiguous.`, next: "Rename one of them." } : void 0;
|
|
42
|
+
}
|
|
43
|
+
case "pick-clash": {
|
|
44
|
+
const m = /^"([^"]*)" is (.+), so a value naming it is ambiguous$/.exec(message);
|
|
45
|
+
return m ? { text: `${quoted(m[1])} is ${curly(m[2])}, so ${field} naming it is ambiguous.`, next: "Rename one of them." } : void 0;
|
|
46
|
+
}
|
|
47
|
+
case "not-a-stage":
|
|
48
|
+
return { text: `${on} is ${quoted(value ?? "")}, which isn't one of its stages.`, next: `Pick one ${place}.` };
|
|
49
|
+
case "renamed-stage": {
|
|
50
|
+
const m = / is "([^"]*)", a stage this extension has since renamed "([^"]*)"/.exec(message);
|
|
51
|
+
return m ? { text: `${on} is ${quoted(m[1])}, a stage ${name} has since renamed ${quoted(m[2])}.`, next: `Pick ${quoted(m[2])} to update it.` } : void 0;
|
|
52
|
+
}
|
|
53
|
+
case "no-such-node": {
|
|
54
|
+
const m = / (?:names|is keyed by) (\w+) "([^"]*)", which does not exist/.exec(message);
|
|
55
|
+
return m ? { text: `${on} names the ${w.kindWord(m[1])} ${quoted(m[2])}, which isn't in the project.`, next: "Choose another, or clear it." } : void 0;
|
|
56
|
+
}
|
|
57
|
+
case "missing-id":
|
|
58
|
+
return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };
|
|
59
|
+
case "duplicate-id": {
|
|
60
|
+
const id = / repeats the id "([^"]*)"/.exec(message)?.[1];
|
|
61
|
+
return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? "")}.`, next: "Give each its own." };
|
|
62
|
+
}
|
|
63
|
+
case "unknown-field":
|
|
64
|
+
case "unknown-key":
|
|
65
|
+
return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: "It's kept, but your game won't get it." };
|
|
66
|
+
default:
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function extensionLine(extension, c) {
|
|
71
|
+
return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : ""}` : void 0;
|
|
72
|
+
}
|
|
73
|
+
function entryProblemLine(message, code, w) {
|
|
74
|
+
const entry = /^entries\.(\w+)\.([^.:\s[]+)(\S*?): (.*)$/.exec(message);
|
|
75
|
+
if (!entry) return void 0;
|
|
76
|
+
const [, kind, id, rest, said] = entry;
|
|
77
|
+
if (code === "orphan") {
|
|
78
|
+
return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: "They're kept, but your game won't get them." });
|
|
79
|
+
}
|
|
80
|
+
if (code === "unresolved") return extensionLine(w.extension, { text: "A merge left a clash in this data that hasn't been resolved.", next: "Resolve it before you publish." });
|
|
81
|
+
const field = fieldPhrase(w.fields(kind), rest.replace(/^\./, ""));
|
|
82
|
+
return extensionLine(w.extension, valueProblemSentence(code, said, {
|
|
83
|
+
extension: w.extension,
|
|
84
|
+
node: w.node(kind, id),
|
|
85
|
+
field,
|
|
86
|
+
place: w.place,
|
|
87
|
+
kindWord: w.kindWord
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { entryProblemLine, extensionLine, fieldPhrase, quoted, valueProblemSentence };
|
|
92
|
+
//# sourceMappingURL=extension-copy.js.map
|
|
93
|
+
//# sourceMappingURL=extension-copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/extension-copy.ts"],"names":[],"mappings":"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","file":"extension-copy.js","sourcesContent":["// ---------------------------------------------------------------------------\n// An extension's problems, worded for the writer, in both apps.\n//\n// The command lines word an extension's problems for the file: \"entries.card.c_7k2.cue.sound: \"cue.sound\" is\n// required and not set\". The problems bar says the same for the writer: the extension by its name, the item as\n// the writer knows it, and the field by its label: \"Probe: The bell needs a sound in cue.\" Patterpad wrote these\n// rules first (its problem-copy.ts); they are the family's, here, so the two apps read alike.\n//\n// What is the app's is passed in: how it names the item (a line by its words, a card by its title), the words\n// for its node kinds, and where the writer goes to change a value (\"in the inspector\", \"on its page\"). The rules\n// key on the toolkit's code for the problem (`@wildwinter/toolkit/extensions`' Problem.code), which each app's\n// compiler carries on the issue; a code they do not know stands as the message it came with.\n//\n// No app state, no Electron, no DOM: words only.\n// ---------------------------------------------------------------------------\n\n/** A field declaration as the wording reads it: its name, its label, and a record's or an item's own fields. */\nexport interface FieldWordsDecl {\n name: string;\n label?: string;\n fields?: readonly FieldWordsDecl[];\n of?: { fields?: readonly FieldWordsDecl[] };\n}\n\n/** What the app knows to word one problem about a value. */\nexport interface ValueProblemWords {\n /** The extension's name, which leads the line (\"Probe\"). */\n extension: string;\n /** The item, as the writer knows it (\"“What'll it be, stranger?”\", \"the card “bell”\"). */\n node: string;\n /** The field, by its label (`fieldPhrase`). */\n field: string;\n /** Where a value is changed, finishing \"Pick one ...\" (\"in the inspector\", \"on its page\"). */\n place: string;\n /** A node kind in the app's words (\"game event\", \"hand template\"). */\n kindWord(kind: string): string;\n}\n\nexport const quoted = (s: string): string => `“${s}”`;\n/** A check's own quoted names in the family's quotes. */\nconst curly = (s: string): string => s.replace(/\"([^\"]*)\"/g, (_, x: string) => quoted(x));\nconst cap = (s: string): string => `${s.charAt(0).toUpperCase()}${s.slice(1)}`;\nconst article = (s: string): string => (/^[aeiou]/i.test(s) ? \"an\" : \"a\");\n\n/** A field inside a node's entry, by its label: \"Note\", \"sound in cue\", \"item 2 of words\". */\nexport function fieldPhrase(fields: readonly FieldWordsDecl[], path: string): string {\n const tokens = path.split(/\\.|(?=\\[)/).filter((t) => t !== \"\");\n const top = fields.find((f) => f.name === tokens[0]);\n const topLabel = top?.label ?? tokens[0] ?? \"\";\n if (tokens.length <= 1) return topLabel;\n const last = tokens[tokens.length - 1]!;\n const index = /^\\[(\\d+)\\]$/.exec(last);\n if (index) return `item ${Number(index[1]) + 1} of ${topLabel}`;\n const parts = [...(top?.fields ?? []), ...(top?.of?.fields ?? [])];\n return `${parts.find((f) => f.name === last)?.label ?? last} in ${topLabel}`;\n}\n\n/** The sentence and its next step for one value a check refused, by the toolkit's code; undefined for a code\n * the rules do not know. `message` is the check's own sentence, which some sentences take their values from. */\nexport function valueProblemSentence(code: string | undefined, message: string, w: ValueProblemWords): { text: string; next?: string } | undefined {\n const { field, node, extension: name, place } = w;\n const on = `${field} on ${node}`;\n const value = /^\"[^\"]*\" is \"([^\"]*)\"/.exec(message)?.[1];\n switch (code) {\n case \"required\": return { text: `${node} needs ${article(field)} ${field}.` };\n case \"wrong-type\": return { text: `${on} isn't the kind of value ${name} expects.`, next: `Set it again ${place}.` };\n case \"below-min\": { const m = / is ([^,]+), below the least it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing below ${m[2]}.` } : undefined; }\n case \"above-max\": { const m = / is ([^,]+), above the most it may be, (.+)$/.exec(message); return m ? { text: `${on} is ${m[1]}, but ${name} takes nothing above ${m[2]}.` } : undefined; }\n case \"not-a-value\":\n if (/not among its flags$/.test(message)) return { text: `${on} has a flag that isn't one of its choices.`, next: `Pick from the choices ${place}.` };\n return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"not-a-choice\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its choices.`, next: `Pick one ${place}.` };\n case \"pick-ambiguous\": {\n const m = / is \"([^\"]*)\", which is (.+)$/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, which is ${curly(m[2]!)}, so it's ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"pick-clash\": {\n const m = /^\"([^\"]*)\" is (.+), so a value naming it is ambiguous$/.exec(message);\n return m ? { text: `${quoted(m[1]!)} is ${curly(m[2]!)}, so ${field} naming it is ambiguous.`, next: \"Rename one of them.\" } : undefined;\n }\n case \"not-a-stage\": return { text: `${on} is ${quoted(value ?? \"\")}, which isn't one of its stages.`, next: `Pick one ${place}.` };\n case \"renamed-stage\": {\n const m = / is \"([^\"]*)\", a stage this extension has since renamed \"([^\"]*)\"/.exec(message);\n return m ? { text: `${on} is ${quoted(m[1]!)}, a stage ${name} has since renamed ${quoted(m[2]!)}.`, next: `Pick ${quoted(m[2]!)} to update it.` } : undefined;\n }\n case \"no-such-node\": {\n const m = / (?:names|is keyed by) (\\w+) \"([^\"]*)\", which does not exist/.exec(message);\n return m ? { text: `${on} names the ${w.kindWord(m[1]!)} ${quoted(m[2]!)}, which isn't in the project.`, next: \"Choose another, or clear it.\" } : undefined;\n }\n case \"missing-id\": return { text: `An item in ${field} on ${node} has no id.`, next: `Give it one ${place}.` };\n case \"duplicate-id\": { const id = / repeats the id \"([^\"]*)\"/.exec(message)?.[1]; return { text: `Two items in ${field} on ${node} share the id ${quoted(id ?? \"\")}.`, next: \"Give each its own.\" }; }\n case \"unknown-field\": case \"unknown-key\":\n return { text: `${node} has a value for ${quoted(field)}, which ${name} doesn't declare any more.`, next: \"It's kept, but your game won't get it.\" };\n default: return undefined;\n }\n}\n\n/** A problem's line: the extension's name, then the sentence and its next step. */\nexport function extensionLine(extension: string, c: { text: string; next?: string } | undefined): string | undefined {\n return c ? `${extension}: ${cap(c.text)}${c.next ? ` ${c.next}` : \"\"}` : undefined;\n}\n\n/** What the app knows to word an entry problem (`entryProblemLine`). */\nexport interface EntryProblemWords {\n extension: string;\n /** The extension's declarations for a node kind. */\n fields(kind: string): readonly FieldWordsDecl[];\n /** A node as the writer knows it, by kind and id. */\n node(kind: string, id: string): string;\n place: string;\n kindWord(kind: string): string;\n}\n\n/**\n * The writer's line for a problem in a node's extension values, read from the command line's\n * \"entries.<kind>.<id>[.<path>]: <what is wrong>\" and the toolkit's code (`orphan` and `unresolved` included);\n * undefined for a message not of that shape, or a code the rules do not know.\n */\nexport function entryProblemLine(message: string, code: string | undefined, w: EntryProblemWords): string | undefined {\n const entry = /^entries\\.(\\w+)\\.([^.:\\s[]+)(\\S*?): (.*)$/.exec(message);\n if (!entry) return undefined;\n const [, kind, id, rest, said] = entry as unknown as [string, string, string, string, string];\n if (code === \"orphan\") {\n return extensionLine(w.extension, { text: `There are values for ${article(w.kindWord(kind))} ${w.kindWord(kind)} that isn't in the project any more.`, next: \"They're kept, but your game won't get them.\" });\n }\n if (code === \"unresolved\") return extensionLine(w.extension, { text: \"A merge left a clash in this data that hasn't been resolved.\", next: \"Resolve it before you publish.\" });\n const field = fieldPhrase(w.fields(kind), rest.replace(/^\\./, \"\"));\n return extensionLine(w.extension, valueProblemSentence(code, said, {\n extension: w.extension, node: w.node(kind, id), field, place: w.place, kindWord: w.kindWord,\n }));\n}\n"]}
|
package/dist/history.cjs
CHANGED
|
@@ -121,6 +121,11 @@ var History = class {
|
|
|
121
121
|
this.undoStack.push(entry);
|
|
122
122
|
return entry.after;
|
|
123
123
|
}
|
|
124
|
+
/** Forget every undone step: for a host that changes the files on its own (a step merged into the last one,
|
|
125
|
+
* a reload), after which redoing what was undone would put back the wrong bytes. */
|
|
126
|
+
clearRedo() {
|
|
127
|
+
this.redoStack = [];
|
|
128
|
+
}
|
|
124
129
|
/** An undo whose states could not be written: the step goes back where it was, so the author can try again
|
|
125
130
|
* once the file can be written. */
|
|
126
131
|
undoFailed() {
|
package/dist/history.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/history.ts"],"names":["existsSync","readFileSync","writeTextFilesAsync","deleteFileAsync","basename","rmSync","writeFileSync"],"mappings":";;;;;;;AA2CO,SAAS,cAAc,KAAA,EAA8B;AAC1D,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,MAAU,EAAE,IAAA,EAAM,OAAA,EAASA,aAAA,CAAW,IAAI,IAAIC,eAAA,CAAa,IAAA,EAAM,MAAM,CAAA,GAAI,MAAK,CAAE,CAAA;AACtG;AAgBA,eAAsB,YAAY,GAAA,EAAoC;AACpE,EAAA,MAAM,SAAS,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,IAAI,CAAA;AACjD,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,YAAY,IAAI,CAAA,CAAE,IAAI,CAAC,CAAA,MAAO,EAAE,QAAA,EAAU,CAAA,CAAE,MAAM,OAAA,EAAS,CAAA,CAAE,SAAS,CAAE,CAAA;AAC9G,EAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,MAAMC,+BAAA,CAAoB,MAAA,EAAQ,QAAQ,EAAE,YAAA,EAAc,MAAM,CAAA;AAC9E,MAAA,IAAI,CAAC,KAAA,CAAM,OAAA,EAAS,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA,EAAG,OAAO,KAAA,EAAM;AAAA,IAC3F,SAAS,CAAA,EAAG;AACV,MAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,kBAAkB,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,CAAA,EAAI,OAAO,KAAA,EAAM;AAAA,IAC1G;AAAA,EACF;AACA,EAAA,MAAM,YAAsB,EAAC;AAC7B,EAAA,KAAA,MAAW,KAAK,MAAA,EAAQ;AACtB,IAAA,IAAI,EAAE,OAAA,KAAY,IAAA,IAAQ,CAACF,aAAA,CAAW,CAAA,CAAE,IAAI,CAAA,EAAG;AAC/C,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAMG,2BAAA,CAAgB,CAAA,CAAE,IAAI,CAAA;AACzC,MAAA,IAAI,CAAC,IAAA,CAAK,OAAA,EAAS,SAAA,CAAU,KAAK,CAAA,EAAGC,aAAA,CAAS,CAAA,CAAE,IAAI,CAAC,CAAA,EAAA,EAAK,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,IAC1F,SAAS,CAAA,EAAG;AACV,MAAA,SAAA,CAAU,IAAA,CAAK,CAAA,EAAGA,aAAA,CAAS,CAAA,CAAE,IAAI,CAAC,CAAA,EAAA,EAAK,CAAA,YAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,MAAA,CAAO,CAAC,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,IACtF;AAAA,EACF;AACA,EAAA,IAAI,UAAU,MAAA,GAAS,CAAA,EAAG,OAAO,EAAE,IAAI,KAAA,EAAO,KAAA,EAAO,CAAA,gBAAA,EAAmB,SAAA,CAAU,KAAK,IAAI,CAAC,IAAI,KAAA,EAAO,MAAA,CAAO,SAAS,CAAA,EAAE;AAGzH,EAAA,KAAA,MAAW,KAAK,GAAA,EAAK;AACnB,IAAA,IAAI,CAAA,CAAE,UAAU,IAAA,EAAM;AACtB,IAAA,IAAI,CAAA,CAAE,YAAY,IAAA,EAAMC,SAAA,CAAO,EAAE,IAAA,EAAM,EAAE,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,SACjDC,gBAAA,CAAc,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,SAAS,MAAM,CAAA;AAAA,EAC9C;AACA,EAAA,OAAO,EAAE,IAAI,IAAA,EAAK;AACpB;AAOO,SAAS,aAAa,OAAA,EAAmC;AAC9D,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,EAAE,OAAO,CAAA;AAC/C,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,EAAE,MAAA,KAAW,QAAA,IAAY,CAAA,CAAE,MAAA,KAAW,WAAW,CAAA;AACtF,EAAA,MAAM,GAAA,GAAM,CAAC,CAAA,KAA8B;AACzC,IAAA,MAAM,IAAA,GAAOF,aAAA,CAAS,CAAA,CAAE,QAAQ,CAAA;AAChC,IAAA,MAAM,GAAA,GAAM,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,MAAA;AAC3B,IAAA,OAAO,GAAA,CAAI,SAAS,IAAI,CAAA,GAAI,MAAM,CAAA,EAAG,IAAI,KAAK,GAAG,CAAA,CAAA,CAAA;AAAA,EACnD,CAAA;AACA,EAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG,OAAO,CAAA,mBAAA,EAAsB,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAChF,EAAA,OAAO,CAAA,cAAA,EAAiB,OAAO,GAAA,CAAI,GAAG,EAAE,IAAA,CAAK,IAAI,KAAK,aAAa,CAAA,CAAA;AACrE;AAIO,IAAM,UAAA,GAAa;AAkC1B,SAAS,WAAA,CAAY,MAAmB,QAAA,EAAoC;AAC1E,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAuB;AAC1C,EAAA,KAAA,MAAW,KAAK,IAAA,EAAM,MAAA,CAAO,GAAA,CAAI,CAAA,CAAE,MAAM,CAAC,CAAA;AAC1C,EAAA,KAAA,MAAW,KAAK,QAAA,EAAU,MAAA,CAAO,GAAA,CAAI,CAAA,CAAE,MAAM,CAAC,CAAA;AAC9C,EAAA,OAAO,CAAC,GAAG,MAAA,CAAO,MAAA,EAAQ,CAAA;AAC5B;AAGA,IAAM,SAAA,GAAY,CAAC,KAAA,KAA4B,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAMA,aAAA,CAAS,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAE/E,IAAM,UAAN,MAAc;AAAA,EACX,YAAqB,EAAC;AAAA,EACtB,YAAqB,EAAC;AAAA,EACb,IAAA;AAAA,EAEjB,WAAA,CAAY,IAAA,GAAuB,EAAC,EAAG;AACrC,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,YAAY,EAAC;AAClB,IAAA,IAAA,CAAK,YAAY,EAAC;AAAA,EACpB;AAAA,EAEA,IAAI,OAAA,GAAmB;AAAE,IAAA,OAAO,IAAA,CAAK,UAAU,MAAA,GAAS,CAAA;AAAA,EAAG;AAAA,EAC3D,IAAI,OAAA,GAAmB;AAAE,IAAA,OAAO,IAAA,CAAK,UAAU,MAAA,GAAS,CAAA;AAAA,EAAG;AAAA;AAAA,EAE3D,IAAI,SAAA,GAAgC;AAAE,IAAA,OAAO,KAAK,SAAA,CAAU,IAAA,CAAK,SAAA,CAAU,MAAA,GAAS,CAAC,CAAA,EAAG,KAAA;AAAA,EAAO;AAAA,EAC/F,IAAI,SAAA,GAAgC;AAAE,IAAA,OAAO,KAAK,SAAA,CAAU,IAAA,CAAK,SAAA,CAAU,MAAA,GAAS,CAAC,CAAA,EAAG,KAAA;AAAA,EAAO;AAAA;AAAA;AAAA,EAI/F,MAAA,CAAO,KAAA,EAAe,GAAA,EAAa,MAAA,EAAqB,KAAA,EAA0B;AAChF,IAAA,MAAM,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,SAAA,CAAU,SAAS,CAAC,CAAA;AACpD,IAAA,IAAI,GAAA,IAAO,GAAA,CAAI,GAAA,KAAQ,GAAA,EAAK;AAC1B,MAAA,GAAA,CAAI,MAAA,GAAS,WAAA,CAAY,MAAA,EAAQ,GAAA,CAAI,MAAM,CAAA;AAC3C,MAAA,GAAA,CAAI,KAAA,GAAQ,WAAA,CAAY,GAAA,CAAI,KAAA,EAAO,KAAK,CAAA;AAAA,IAC1C,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,UAAU,IAAA,CAAK,EAAE,OAAO,GAAA,EAAK,MAAA,EAAQ,OAAO,CAAA;AACjD,MAAA,IAAI,KAAK,SAAA,CAAU,MAAA,GAAS,UAAA,EAAY,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,IAC/D;AACA,IAAA,IAAA,CAAK,YAAY,EAAC;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAA,CAAK,QAAqB,KAAA,EAA0B;AAClD,IAAA,MAAM,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,SAAA,CAAU,SAAS,CAAC,CAAA;AACpD,IAAA,IAAI,CAAC,GAAA,EAAK;AACV,IAAA,MAAM,KAAA,GAAQ,IAAI,GAAA,CAAI,GAAA,CAAI,MAAA,CAAO,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA;AACnD,IAAA,GAAA,CAAI,SAAS,CAAC,GAAG,GAAA,CAAI,MAAA,EAAQ,GAAG,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,KAAA,CAAM,GAAA,CAAI,CAAA,CAAE,IAAI,CAAC,CAAC,CAAA;AACxE,IAAA,GAAA,CAAI,KAAA,GAAQ,WAAA,CAAY,GAAA,CAAI,KAAA,EAAO,KAAK,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA,EAIA,IAAA,GAAgC;AAC9B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,SAAA,CAAU,GAAA,EAAI;AACjC,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,KAAK,CAAA;AACzB,IAAA,OAAO,KAAA,CAAM,MAAA;AAAA,EACf;AAAA;AAAA,EAGA,IAAA,GAAgC;AAC9B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,SAAA,CAAU,GAAA,EAAI;AACjC,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,KAAK,CAAA;AACzB,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACf;AAAA;AAAA;AAAA,EAIA,UAAA,GAAmB;AACjB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,SAAA,CAAU,GAAA,EAAI;AACjC,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,KAAK,CAAA;AAAA,EACtC;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,SAAA,CAAU,GAAA,EAAI;AACjC,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,KAAK,CAAA;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAAA,GAA8C;AAClD,IAAA,OAAO,IAAA,CAAK,MAAM,MAAM,CAAA;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAM,SAAA,GAA8C;AAClD,IAAA,OAAO,IAAA,CAAK,MAAM,MAAM,CAAA;AAAA,EAC1B;AAAA,EAEA,MAAc,MAAM,GAAA,EAAwD;AAC1E,IAAA,MAAM,IAAA,GAAO,GAAA,KAAQ,MAAA,GAAS,IAAA,CAAK,YAAY,IAAA,CAAK,SAAA;AACpD,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AAClC,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,IAAA,MAAM,QAAA,GAAW,GAAA,KAAQ,MAAA,GAAS,KAAA,CAAM,QAAQ,KAAA,CAAM,MAAA;AACtD,IAAA,MAAM,MAAA,GAAS,GAAA,KAAQ,MAAA,GAAS,KAAA,CAAM,SAAS,KAAA,CAAM,KAAA;AACrD,IAAA,MAAM,QAAA,GAAW,KAAK,IAAA,CAAK,QAAA;AAC3B,IAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAA,CAAK,IAAA,CAAK,IAAA,CAAK,QAAQ,aAAA,EAAe,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,EAAE,IAAA,EAAM,CAAA,CAAE,OAAO,CAAC,CAAC,CAAA;AAGpH,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM;AACrC,MAAA,IAAI,CAAA,CAAE,KAAA,KAAU,IAAA,EAAM,OAAO,KAAA;AAC7B,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,IAAI,CAAA,IAAK,IAAA;AACnC,MAAA,MAAM,CAAA,GAAI,QAAA,GAAW,CAAA,CAAE,IAAI,CAAA;AAC3B,MAAA,OAAO,CAAA,GAAI,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,OAAA,EAAS,GAAG,CAAA,GAAI,GAAA,KAAQ,CAAA,CAAE,OAAA;AAAA,IACjD,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AACpB,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AAEtB,MAAA,IAAA,CAAK,MAAA,GAAS,CAAA;AACd,MAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,MAAA,KAAW,CAAA,GAAI,GAAGA,aAAA,CAAS,OAAA,CAAQ,CAAC,CAAE,CAAC,CAAA,IAAA,CAAA,GAAS,CAAA,EAAG,SAAA,CAAU,OAAO,CAAC,CAAA,KAAA,CAAA;AAC1F,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA;AAAA,QAAO,OAAO,KAAA,CAAM,KAAA;AAAA,QAAO,KAAA,EAAO,OAAA;AAAA,QACtC,KAAA,EAAO,YAAY,GAAG,CAAA,EAAA,EAAK,MAAM,KAAK,CAAA,GAAA,EAAM,IAAI,CAAA,mBAAA,EAAsB,GAAA,KAAQ,MAAA,GAAS,YAAY,SAAS,CAAA,iDAAA,EAC9E,QAAQ,MAAA,GAAS,MAAA,GAAS,IAAI,CAAA,QAAA,EAAW,GAAA,KAAQ,MAAA,GAAS,QAAA,GAAW,QAAQ,CAAA,KAAA;AAAA,OAC7G;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAM;AAC/B,MAAA,MAAM,CAAA,GAAI,QAAA,GAAW,CAAA,CAAE,IAAI,CAAA;AAC3B,MAAA,OAAO,IAAI,EAAE,GAAG,CAAA,EAAG,OAAA,EAAS,EAAE,OAAA,CAAQ,CAAA,CAAE,OAAA,EAAS,OAAA,CAAQ,IAAI,CAAA,CAAE,IAAI,CAAA,IAAK,IAAI,GAAE,GAAI,CAAA;AAAA,IACpF,CAAC,CAAA;AACD,IAAA,IAAA,CAAK,GAAA,EAAI;AACT,IAAA,CAAC,QAAQ,MAAA,GAAS,IAAA,CAAK,YAAY,IAAA,CAAK,SAAA,EAAW,KAAK,KAAK,CAAA;AAC7D,IAAA,MAAM,UAAU,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,KAAA,IAAS,aAAa,MAAM,CAAA;AAC7D,IAAA,IAAI,CAAC,QAAQ,EAAA,EAAI;AACf,MAAA,IAAI,GAAA,KAAQ,MAAA,EAAQ,IAAA,CAAK,UAAA,EAAW;AAAA,gBAAa,UAAA,EAAW;AAC5D,MAAA,OAAO,EAAE,IAAI,KAAA,EAAO,KAAA,EAAO,MAAM,KAAA,EAAO,KAAA,EAAO,QAAQ,KAAA,EAAM;AAAA,IAC/D;AACA,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,MAAM,KAAA,EAAM;AAAA,EACxC;AACF","file":"history.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// The file-byte history: undo and redo for writes an app makes to files, the\n// files-are-truth way. A step is a set of file-state transitions (a path's\n// content before and after, null meaning absent); undo writes the befores\n// back, redo the afters. No command log: the bytes on disk are the model, so\n// restoring bytes is undo.\n//\n// Storyletter's, lifted into the shell (patterkit design/patterpad-undo.md,\n// ruling 2) so Patterpad's project-level undo is the same code. The two apps'\n// DOCUMENT undo still shares nothing (Patterpad's open scene is ProseMirror's);\n// this is the file half only.\n//\n// Three things beyond Storyletter's first version:\n//\n// - DRIFT IS REFUSED (ruling 5). Before an undo or redo, each file must still\n// say what the step left it saying (its after, for an undo; its before, for a\n// redo). A file changed since, by a pull, a merge or another app, would have\n// that work overwritten, so the step is refused, naming the file, and dropped\n// with every step below it: none of them can now be applied safely.\n// - A PER-FILE RESTORE HOOK, for a file an app restores its own way (Patterpad\n// restores a `.patterx` block by block but stamps its edit trail afresh, which\n// moves on every save and so must not count as drift).\n// - THE WRITER IS THE APP'S. Patterpad writes through its own ordered queue;\n// `applyStates` (all or nothing through the VC layer) is the default.\n//\n// Main-process code: it reads and writes files.\n// ---------------------------------------------------------------------------\n\nimport { existsSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { deleteFileAsync, writeTextFilesAsync } from \"@wildwinter/simple-vc-lib\";\nimport type { VCWriteOutcome } from \"@wildwinter/simple-vc-lib\";\n\n/** A file's content, or null when the file is absent (created / deleted). */\nexport interface FileState {\n path: string;\n content: string | null;\n /** Bookkeeping that lives beside the project but outside version control: restored with plain writes,\n * never checked out or added, once the versioned files have landed. */\n local?: boolean;\n}\n\n/** Read the current on-disk state of a set of paths. */\nexport function captureBefore(paths: string[]): FileState[] {\n return paths.map((path) => ({ path, content: existsSync(path) ? readFileSync(path, \"utf8\") : null }));\n}\n\n/** What applying a set of states came to. `wrote` says whether the writes landed before something else (a\n * delete) failed, which decides whether there is now a change on disk for the history to know about. */\nexport type Applied = { ok: true } | { ok: false; error: string; wrote: boolean };\n\n/** How a set of states is written: `applyStates`, or the app's own write path. */\nexport type StateWriter = (states: FileState[]) => Promise<Applied>;\n\n/**\n * Apply file states through the VC layer: write content, or delete when null.\n *\n * ALL OR NOTHING, and off the main thread (simple-vc-lib 0.5.0): every file is checked out first, and if any\n * one is refused nothing is written. The deletes run only once the whole batch has landed, so a rename whose\n * new path was refused never loses the old file.\n */\nexport async function applyStates(all: FileState[]): Promise<Applied> {\n const states = all.filter((s) => s.local !== true);\n const writes = states.filter((s) => s.content !== null).map((s) => ({ filePath: s.path, content: s.content! }));\n if (writes.length > 0) {\n try {\n const batch = await writeTextFilesAsync(writes, \"utf8\", { allOrNothing: true });\n if (!batch.success) return { ok: false, error: writeFailure(batch.results), wrote: false };\n } catch (e) {\n return { ok: false, error: `couldn't save: ${e instanceof Error ? e.message : String(e)}`, wrote: false };\n }\n }\n const undeleted: string[] = [];\n for (const s of states) {\n if (s.content !== null || !existsSync(s.path)) continue;\n try {\n const gone = await deleteFileAsync(s.path);\n if (!gone.success) undeleted.push(`${basename(s.path)} (${gone.message || gone.status})`);\n } catch (e) {\n undeleted.push(`${basename(s.path)} (${e instanceof Error ? e.message : String(e)})`);\n }\n }\n if (undeleted.length > 0) return { ok: false, error: `couldn't delete ${undeleted.join(\"; \")}`, wrote: writes.length > 0 };\n // The bookkeeping last, and only once the project's own files have landed, so it never says the project is\n // at a revision its files are not.\n for (const s of all) {\n if (s.local !== true) continue;\n if (s.content === null) rmSync(s.path, { force: true });\n else writeFileSync(s.path, s.content, \"utf8\");\n }\n return { ok: true };\n}\n\n/**\n * What a refused batch says: the file, and why. A refusal names who holds it (\"'docks.storyletdeck' is locked\n * by bob@bob-ws\"); every other file in an all-or-nothing batch reports only that it was not prepared because of\n * that one, which is not worth repeating.\n */\nexport function writeFailure(results: VCWriteOutcome[]): string {\n const failed = results.filter((r) => !r.success);\n const refused = failed.filter((r) => r.status === \"locked\" || r.status === \"outOfDate\");\n const say = (r: VCWriteOutcome): string => {\n const name = basename(r.filePath);\n const why = r.message || r.status;\n return why.includes(name) ? why : `${name} (${why})`;\n };\n if (refused.length > 0) return `nothing was saved: ${refused.map(say).join(\"; \")}`;\n return `couldn't save ${failed.map(say).join(\"; \") || \"the project\"}`;\n}\n\n/** How many steps Undo keeps: ProseMirror's default depth, the same in both apps. A step holds whole files\n * before and after, so an uncapped stack grew for as long as the window stayed open. */\nexport const UNDO_LIMIT = 100;\n\n/**\n * A file an app restores its own way. `same` says whether the file on disk still says what the step expects\n * (the drift check), and `restore` gives the content to write for the target, from what is on disk now. A\n * `.patterx` compares and restores every block but its edit trail, which the undo stamps afresh.\n */\nexport interface FileRestorer {\n same(expected: string | null, current: string | null): boolean;\n restore(target: string | null, current: string | null): string | null;\n}\n\nexport interface HistoryOptions {\n /** How an undo or redo is written. Default `applyStates`. */\n write?: StateWriter;\n /** A restorer for a file the app restores its own way, by path; undefined for every other file. */\n restorer?: (path: string) => FileRestorer | undefined;\n /** How each file's current bytes are read for the drift check. Default from disk (`captureBefore`). */\n read?: (paths: string[]) => FileState[];\n}\n\n/** What an undo or redo came to. `drift` names the files that changed since the step, when that refused it. */\nexport type StepOutcome =\n | { ok: true; label: string }\n | { ok: false; label: string; error: string; drift?: string[] };\n\ninterface Entry {\n label: string;\n key: string;\n before: FileState[];\n after: FileState[];\n}\n\n/** Merge state lists by path, `override` winning. */\nfunction mergeStates(base: FileState[], override: FileState[]): FileState[] {\n const byPath = new Map<string, FileState>();\n for (const s of base) byPath.set(s.path, s);\n for (const s of override) byPath.set(s.path, s);\n return [...byPath.values()];\n}\n\n/** File names, as a refusal lists them. */\nconst listNames = (paths: string[]): string => paths.map((p) => basename(p)).join(\", \");\n\nexport class History {\n private undoStack: Entry[] = [];\n private redoStack: Entry[] = [];\n private readonly opts: HistoryOptions;\n\n constructor(opts: HistoryOptions = {}) {\n this.opts = opts;\n }\n\n reset(): void {\n this.undoStack = [];\n this.redoStack = [];\n }\n\n get canUndo(): boolean { return this.undoStack.length > 0; }\n get canRedo(): boolean { return this.redoStack.length > 0; }\n /** The labels of the next undo and redo, for a menu (\"Undo Rename Card\"). */\n get undoLabel(): string | undefined { return this.undoStack[this.undoStack.length - 1]?.label; }\n get redoLabel(): string | undefined { return this.redoStack[this.redoStack.length - 1]?.label; }\n\n /** Record a committed change; coalesce with the top entry when the key matches (the same logical edit\n * continuing, as typing into one field). A new distinct edit clears redo. */\n record(label: string, key: string, before: FileState[], after: FileState[]): void {\n const top = this.undoStack[this.undoStack.length - 1];\n if (top && top.key === key) {\n top.before = mergeStates(before, top.before); // keep the earliest before per path\n top.after = mergeStates(top.after, after); // keep the latest after per path\n } else {\n this.undoStack.push({ label, key, before, after });\n if (this.undoStack.length > UNDO_LIMIT) this.undoStack.shift();\n }\n this.redoStack = [];\n }\n\n /**\n * Fold a write the app made on its own into the step the author took last, rather than recording a step of\n * its own: for a write nobody asked for (a value stored the first time a view is drawn), which a step of its\n * own would have the author's next Undo undo instead of what they meant. With nothing to undo yet, there is\n * nothing to fold into, and the write simply stands.\n */\n fold(before: FileState[], after: FileState[]): void {\n const top = this.undoStack[this.undoStack.length - 1];\n if (!top) return;\n const known = new Set(top.before.map((s) => s.path));\n top.before = [...top.before, ...before.filter((s) => !known.has(s.path))];\n top.after = mergeStates(top.after, after);\n }\n\n /** Move the top undo entry to redo and return its before-states, for a caller that writes them itself (and\n * checks drift itself). Most callers want `applyUndo`. */\n undo(): FileState[] | undefined {\n const entry = this.undoStack.pop();\n if (!entry) return undefined;\n this.redoStack.push(entry);\n return entry.before;\n }\n\n /** Move the top redo entry to undo and return its after-states (see `undo`). */\n redo(): FileState[] | undefined {\n const entry = this.redoStack.pop();\n if (!entry) return undefined;\n this.undoStack.push(entry);\n return entry.after;\n }\n\n /** An undo whose states could not be written: the step goes back where it was, so the author can try again\n * once the file can be written. */\n undoFailed(): void {\n const entry = this.redoStack.pop();\n if (entry) this.undoStack.push(entry);\n }\n\n /** The same for a redo that could not be written. */\n redoFailed(): void {\n const entry = this.undoStack.pop();\n if (entry) this.redoStack.push(entry);\n }\n\n /**\n * Undo the top step: check every file still says what the step left it saying, then write its befores. A\n * file that has changed since refuses the undo, and the step is dropped with every step below it (they were\n * taken on top of what has now been overwritten). A write that fails puts the step back. Undefined when there\n * is nothing to undo.\n */\n async applyUndo(): Promise<StepOutcome | undefined> {\n return this.apply(\"undo\");\n }\n\n /** Redo the top undone step, by the same rules: drift drops it and every step above it on the redo stack. */\n async applyRedo(): Promise<StepOutcome | undefined> {\n return this.apply(\"redo\");\n }\n\n private async apply(way: \"undo\" | \"redo\"): Promise<StepOutcome | undefined> {\n const from = way === \"undo\" ? this.undoStack : this.redoStack;\n const entry = from[from.length - 1];\n if (!entry) return undefined;\n const expected = way === \"undo\" ? entry.after : entry.before;\n const target = way === \"undo\" ? entry.before : entry.after;\n const restorer = this.opts.restorer;\n const current = new Map((this.opts.read ?? captureBefore)(target.map((s) => s.path)).map((s) => [s.path, s.content]));\n\n // The drift check, over every file the step touches.\n const drifted = expected.filter((s) => {\n if (s.local === true) return false; // bookkeeping outside version control is the app's own\n const now = current.get(s.path) ?? null;\n const r = restorer?.(s.path);\n return r ? !r.same(s.content, now) : now !== s.content;\n }).map((s) => s.path);\n if (drifted.length > 0) {\n // Every step from here down was taken on top of what has changed: none can be applied safely.\n from.length = 0;\n const name = drifted.length === 1 ? `${basename(drifted[0]!)} has` : `${listNames(drifted)} have`;\n return {\n ok: false, label: entry.label, drift: drifted,\n error: `couldn't ${way} \"${entry.label}\": ${name} changed since, so ${way === \"undo\" ? \"undoing\" : \"redoing\"} it would overwrite that. `\n + `Nothing from that step ${way === \"undo\" ? \"back\" : \"on\"} can be ${way === \"undo\" ? \"undone\" : \"redone\"} now.`,\n };\n }\n\n const states = target.map((s) => {\n const r = restorer?.(s.path);\n return r ? { ...s, content: r.restore(s.content, current.get(s.path) ?? null) } : s;\n });\n from.pop();\n (way === \"undo\" ? this.redoStack : this.undoStack).push(entry);\n const applied = await (this.opts.write ?? applyStates)(states);\n if (!applied.ok) {\n if (way === \"undo\") this.undoFailed(); else this.redoFailed();\n return { ok: false, label: entry.label, error: applied.error };\n }\n return { ok: true, label: entry.label };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/history.ts"],"names":["existsSync","readFileSync","writeTextFilesAsync","deleteFileAsync","basename","rmSync","writeFileSync"],"mappings":"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","file":"history.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// The file-byte history: undo and redo for writes an app makes to files, the\n// files-are-truth way. A step is a set of file-state transitions (a path's\n// content before and after, null meaning absent); undo writes the befores\n// back, redo the afters. No command log: the bytes on disk are the model, so\n// restoring bytes is undo.\n//\n// Storyletter's, lifted into the shell (patterkit design/patterpad-undo.md,\n// ruling 2) so Patterpad's project-level undo is the same code. The two apps'\n// DOCUMENT undo still shares nothing (Patterpad's open scene is ProseMirror's);\n// this is the file half only.\n//\n// Three things beyond Storyletter's first version:\n//\n// - DRIFT IS REFUSED (ruling 5). Before an undo or redo, each file must still\n// say what the step left it saying (its after, for an undo; its before, for a\n// redo). A file changed since, by a pull, a merge or another app, would have\n// that work overwritten, so the step is refused, naming the file, and dropped\n// with every step below it: none of them can now be applied safely.\n// - A PER-FILE RESTORE HOOK, for a file an app restores its own way (Patterpad\n// restores a `.patterx` block by block but stamps its edit trail afresh, which\n// moves on every save and so must not count as drift).\n// - THE WRITER IS THE APP'S. Patterpad writes through its own ordered queue;\n// `applyStates` (all or nothing through the VC layer) is the default.\n//\n// Main-process code: it reads and writes files.\n// ---------------------------------------------------------------------------\n\nimport { existsSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { deleteFileAsync, writeTextFilesAsync } from \"@wildwinter/simple-vc-lib\";\nimport type { VCWriteOutcome } from \"@wildwinter/simple-vc-lib\";\n\n/** A file's content, or null when the file is absent (created / deleted). */\nexport interface FileState {\n path: string;\n content: string | null;\n /** Bookkeeping that lives beside the project but outside version control: restored with plain writes,\n * never checked out or added, once the versioned files have landed. */\n local?: boolean;\n}\n\n/** Read the current on-disk state of a set of paths. */\nexport function captureBefore(paths: string[]): FileState[] {\n return paths.map((path) => ({ path, content: existsSync(path) ? readFileSync(path, \"utf8\") : null }));\n}\n\n/** What applying a set of states came to. `wrote` says whether the writes landed before something else (a\n * delete) failed, which decides whether there is now a change on disk for the history to know about. */\nexport type Applied = { ok: true } | { ok: false; error: string; wrote: boolean };\n\n/** How a set of states is written: `applyStates`, or the app's own write path. */\nexport type StateWriter = (states: FileState[]) => Promise<Applied>;\n\n/**\n * Apply file states through the VC layer: write content, or delete when null.\n *\n * ALL OR NOTHING, and off the main thread (simple-vc-lib 0.5.0): every file is checked out first, and if any\n * one is refused nothing is written. The deletes run only once the whole batch has landed, so a rename whose\n * new path was refused never loses the old file.\n */\nexport async function applyStates(all: FileState[]): Promise<Applied> {\n const states = all.filter((s) => s.local !== true);\n const writes = states.filter((s) => s.content !== null).map((s) => ({ filePath: s.path, content: s.content! }));\n if (writes.length > 0) {\n try {\n const batch = await writeTextFilesAsync(writes, \"utf8\", { allOrNothing: true });\n if (!batch.success) return { ok: false, error: writeFailure(batch.results), wrote: false };\n } catch (e) {\n return { ok: false, error: `couldn't save: ${e instanceof Error ? e.message : String(e)}`, wrote: false };\n }\n }\n const undeleted: string[] = [];\n for (const s of states) {\n if (s.content !== null || !existsSync(s.path)) continue;\n try {\n const gone = await deleteFileAsync(s.path);\n if (!gone.success) undeleted.push(`${basename(s.path)} (${gone.message || gone.status})`);\n } catch (e) {\n undeleted.push(`${basename(s.path)} (${e instanceof Error ? e.message : String(e)})`);\n }\n }\n if (undeleted.length > 0) return { ok: false, error: `couldn't delete ${undeleted.join(\"; \")}`, wrote: writes.length > 0 };\n // The bookkeeping last, and only once the project's own files have landed, so it never says the project is\n // at a revision its files are not.\n for (const s of all) {\n if (s.local !== true) continue;\n if (s.content === null) rmSync(s.path, { force: true });\n else writeFileSync(s.path, s.content, \"utf8\");\n }\n return { ok: true };\n}\n\n/**\n * What a refused batch says: the file, and why. A refusal names who holds it (\"'docks.storyletdeck' is locked\n * by bob@bob-ws\"); every other file in an all-or-nothing batch reports only that it was not prepared because of\n * that one, which is not worth repeating.\n */\nexport function writeFailure(results: VCWriteOutcome[]): string {\n const failed = results.filter((r) => !r.success);\n const refused = failed.filter((r) => r.status === \"locked\" || r.status === \"outOfDate\");\n const say = (r: VCWriteOutcome): string => {\n const name = basename(r.filePath);\n const why = r.message || r.status;\n return why.includes(name) ? why : `${name} (${why})`;\n };\n if (refused.length > 0) return `nothing was saved: ${refused.map(say).join(\"; \")}`;\n return `couldn't save ${failed.map(say).join(\"; \") || \"the project\"}`;\n}\n\n/** How many steps Undo keeps: ProseMirror's default depth, the same in both apps. A step holds whole files\n * before and after, so an uncapped stack grew for as long as the window stayed open. */\nexport const UNDO_LIMIT = 100;\n\n/**\n * A file an app restores its own way. `same` says whether the file on disk still says what the step expects\n * (the drift check), and `restore` gives the content to write for the target, from what is on disk now. A\n * `.patterx` compares and restores every block but its edit trail, which the undo stamps afresh.\n */\nexport interface FileRestorer {\n same(expected: string | null, current: string | null): boolean;\n restore(target: string | null, current: string | null): string | null;\n}\n\nexport interface HistoryOptions {\n /** How an undo or redo is written. Default `applyStates`. */\n write?: StateWriter;\n /** A restorer for a file the app restores its own way, by path; undefined for every other file. */\n restorer?: (path: string) => FileRestorer | undefined;\n /** How each file's current bytes are read for the drift check. Default from disk (`captureBefore`). */\n read?: (paths: string[]) => FileState[];\n}\n\n/** What an undo or redo came to. `drift` names the files that changed since the step, when that refused it. */\nexport type StepOutcome =\n | { ok: true; label: string }\n | { ok: false; label: string; error: string; drift?: string[] };\n\ninterface Entry {\n label: string;\n key: string;\n before: FileState[];\n after: FileState[];\n}\n\n/** Merge state lists by path, `override` winning. */\nfunction mergeStates(base: FileState[], override: FileState[]): FileState[] {\n const byPath = new Map<string, FileState>();\n for (const s of base) byPath.set(s.path, s);\n for (const s of override) byPath.set(s.path, s);\n return [...byPath.values()];\n}\n\n/** File names, as a refusal lists them. */\nconst listNames = (paths: string[]): string => paths.map((p) => basename(p)).join(\", \");\n\nexport class History {\n private undoStack: Entry[] = [];\n private redoStack: Entry[] = [];\n private readonly opts: HistoryOptions;\n\n constructor(opts: HistoryOptions = {}) {\n this.opts = opts;\n }\n\n reset(): void {\n this.undoStack = [];\n this.redoStack = [];\n }\n\n get canUndo(): boolean { return this.undoStack.length > 0; }\n get canRedo(): boolean { return this.redoStack.length > 0; }\n /** The labels of the next undo and redo, for a menu (\"Undo Rename Card\"). */\n get undoLabel(): string | undefined { return this.undoStack[this.undoStack.length - 1]?.label; }\n get redoLabel(): string | undefined { return this.redoStack[this.redoStack.length - 1]?.label; }\n\n /** Record a committed change; coalesce with the top entry when the key matches (the same logical edit\n * continuing, as typing into one field). A new distinct edit clears redo. */\n record(label: string, key: string, before: FileState[], after: FileState[]): void {\n const top = this.undoStack[this.undoStack.length - 1];\n if (top && top.key === key) {\n top.before = mergeStates(before, top.before); // keep the earliest before per path\n top.after = mergeStates(top.after, after); // keep the latest after per path\n } else {\n this.undoStack.push({ label, key, before, after });\n if (this.undoStack.length > UNDO_LIMIT) this.undoStack.shift();\n }\n this.redoStack = [];\n }\n\n /**\n * Fold a write the app made on its own into the step the author took last, rather than recording a step of\n * its own: for a write nobody asked for (a value stored the first time a view is drawn), which a step of its\n * own would have the author's next Undo undo instead of what they meant. With nothing to undo yet, there is\n * nothing to fold into, and the write simply stands.\n */\n fold(before: FileState[], after: FileState[]): void {\n const top = this.undoStack[this.undoStack.length - 1];\n if (!top) return;\n const known = new Set(top.before.map((s) => s.path));\n top.before = [...top.before, ...before.filter((s) => !known.has(s.path))];\n top.after = mergeStates(top.after, after);\n }\n\n /** Move the top undo entry to redo and return its before-states, for a caller that writes them itself (and\n * checks drift itself). Most callers want `applyUndo`. */\n undo(): FileState[] | undefined {\n const entry = this.undoStack.pop();\n if (!entry) return undefined;\n this.redoStack.push(entry);\n return entry.before;\n }\n\n /** Move the top redo entry to undo and return its after-states (see `undo`). */\n redo(): FileState[] | undefined {\n const entry = this.redoStack.pop();\n if (!entry) return undefined;\n this.undoStack.push(entry);\n return entry.after;\n }\n\n /** Forget every undone step: for a host that changes the files on its own (a step merged into the last one,\n * a reload), after which redoing what was undone would put back the wrong bytes. */\n clearRedo(): void {\n this.redoStack = [];\n }\n\n /** An undo whose states could not be written: the step goes back where it was, so the author can try again\n * once the file can be written. */\n undoFailed(): void {\n const entry = this.redoStack.pop();\n if (entry) this.undoStack.push(entry);\n }\n\n /** The same for a redo that could not be written. */\n redoFailed(): void {\n const entry = this.undoStack.pop();\n if (entry) this.redoStack.push(entry);\n }\n\n /**\n * Undo the top step: check every file still says what the step left it saying, then write its befores. A\n * file that has changed since refuses the undo, and the step is dropped with every step below it (they were\n * taken on top of what has now been overwritten). A write that fails puts the step back. Undefined when there\n * is nothing to undo.\n */\n async applyUndo(): Promise<StepOutcome | undefined> {\n return this.apply(\"undo\");\n }\n\n /** Redo the top undone step, by the same rules: drift drops it and every step above it on the redo stack. */\n async applyRedo(): Promise<StepOutcome | undefined> {\n return this.apply(\"redo\");\n }\n\n private async apply(way: \"undo\" | \"redo\"): Promise<StepOutcome | undefined> {\n const from = way === \"undo\" ? this.undoStack : this.redoStack;\n const entry = from[from.length - 1];\n if (!entry) return undefined;\n const expected = way === \"undo\" ? entry.after : entry.before;\n const target = way === \"undo\" ? entry.before : entry.after;\n const restorer = this.opts.restorer;\n const current = new Map((this.opts.read ?? captureBefore)(target.map((s) => s.path)).map((s) => [s.path, s.content]));\n\n // The drift check, over every file the step touches.\n const drifted = expected.filter((s) => {\n if (s.local === true) return false; // bookkeeping outside version control is the app's own\n const now = current.get(s.path) ?? null;\n const r = restorer?.(s.path);\n return r ? !r.same(s.content, now) : now !== s.content;\n }).map((s) => s.path);\n if (drifted.length > 0) {\n // Every step from here down was taken on top of what has changed: none can be applied safely.\n from.length = 0;\n const name = drifted.length === 1 ? `${basename(drifted[0]!)} has` : `${listNames(drifted)} have`;\n return {\n ok: false, label: entry.label, drift: drifted,\n error: `couldn't ${way} \"${entry.label}\": ${name} changed since, so ${way === \"undo\" ? \"undoing\" : \"redoing\"} it would overwrite that. `\n + `Nothing from that step ${way === \"undo\" ? \"back\" : \"on\"} can be ${way === \"undo\" ? \"undone\" : \"redone\"} now.`,\n };\n }\n\n const states = target.map((s) => {\n const r = restorer?.(s.path);\n return r ? { ...s, content: r.restore(s.content, current.get(s.path) ?? null) } : s;\n });\n from.pop();\n (way === \"undo\" ? this.redoStack : this.undoStack).push(entry);\n const applied = await (this.opts.write ?? applyStates)(states);\n if (!applied.ok) {\n if (way === \"undo\") this.undoFailed(); else this.redoFailed();\n return { ok: false, label: entry.label, error: applied.error };\n }\n return { ok: true, label: entry.label };\n }\n}\n"]}
|
package/dist/history.d.cts
CHANGED
|
@@ -91,6 +91,9 @@ declare class History {
|
|
|
91
91
|
undo(): FileState[] | undefined;
|
|
92
92
|
/** Move the top redo entry to undo and return its after-states (see `undo`). */
|
|
93
93
|
redo(): FileState[] | undefined;
|
|
94
|
+
/** Forget every undone step: for a host that changes the files on its own (a step merged into the last one,
|
|
95
|
+
* a reload), after which redoing what was undone would put back the wrong bytes. */
|
|
96
|
+
clearRedo(): void;
|
|
94
97
|
/** An undo whose states could not be written: the step goes back where it was, so the author can try again
|
|
95
98
|
* once the file can be written. */
|
|
96
99
|
undoFailed(): void;
|
package/dist/history.d.ts
CHANGED
|
@@ -91,6 +91,9 @@ declare class History {
|
|
|
91
91
|
undo(): FileState[] | undefined;
|
|
92
92
|
/** Move the top redo entry to undo and return its after-states (see `undo`). */
|
|
93
93
|
redo(): FileState[] | undefined;
|
|
94
|
+
/** Forget every undone step: for a host that changes the files on its own (a step merged into the last one,
|
|
95
|
+
* a reload), after which redoing what was undone would put back the wrong bytes. */
|
|
96
|
+
clearRedo(): void;
|
|
94
97
|
/** An undo whose states could not be written: the step goes back where it was, so the author can try again
|
|
95
98
|
* once the file can be written. */
|
|
96
99
|
undoFailed(): void;
|
package/dist/history.js
CHANGED
|
@@ -119,6 +119,11 @@ var History = class {
|
|
|
119
119
|
this.undoStack.push(entry);
|
|
120
120
|
return entry.after;
|
|
121
121
|
}
|
|
122
|
+
/** Forget every undone step: for a host that changes the files on its own (a step merged into the last one,
|
|
123
|
+
* a reload), after which redoing what was undone would put back the wrong bytes. */
|
|
124
|
+
clearRedo() {
|
|
125
|
+
this.redoStack = [];
|
|
126
|
+
}
|
|
122
127
|
/** An undo whose states could not be written: the step goes back where it was, so the author can try again
|
|
123
128
|
* once the file can be written. */
|
|
124
129
|
undoFailed() {
|