@graview/core 0.1.59 → 0.1.61
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 +10 -0
- package/dist/arrangement.d.ts +2 -0
- package/dist/arrangement.d.ts.map +1 -1
- package/dist/arrangement.js +14 -0
- package/dist/arrangement.js.map +1 -1
- package/dist/capabilities.js +1 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +2 -1
- package/dist/catalog.js.map +1 -1
- package/dist/cli/check/context.d.ts +5 -0
- package/dist/cli/check/context.d.ts.map +1 -1
- package/dist/cli/check/context.js.map +1 -1
- package/dist/cli/check/face.d.ts +14 -0
- package/dist/cli/check/face.d.ts.map +1 -0
- package/dist/cli/check/face.js +161 -0
- package/dist/cli/check/face.js.map +1 -0
- package/dist/cli/check/index.d.ts +1 -0
- package/dist/cli/check/index.d.ts.map +1 -1
- package/dist/cli/check/index.js +1 -0
- package/dist/cli/check/index.js.map +1 -1
- package/dist/cli/check/typing.d.ts +4 -0
- package/dist/cli/check/typing.d.ts.map +1 -0
- package/dist/cli/check/typing.js +56 -0
- package/dist/cli/check/typing.js.map +1 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +4 -4
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/docs.d.ts.map +1 -1
- package/dist/cli/docs.js +5 -3
- package/dist/cli/docs.js.map +1 -1
- package/dist/cli/document-schema.d.ts.map +1 -1
- package/dist/cli/document-schema.js +2 -0
- package/dist/cli/document-schema.js.map +1 -1
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +14 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +2 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/compose.d.ts +6 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +6 -1
- package/dist/compose.js.map +1 -1
- package/dist/document/blocks.d.ts.map +1 -1
- package/dist/document/blocks.js +2 -1
- package/dist/document/blocks.js.map +1 -1
- package/dist/document/compile-checked.js +1 -1
- package/dist/document/compile-checked.js.map +1 -1
- package/dist/document/compile.d.ts.map +1 -1
- package/dist/document/compile.js +6 -2
- package/dist/document/compile.js.map +1 -1
- package/dist/document/compiled.d.ts.map +1 -1
- package/dist/document/compiled.js +12 -5
- package/dist/document/compiled.js.map +1 -1
- package/dist/document/connection-findings.d.ts.map +1 -1
- package/dist/document/connection-findings.js +134 -12
- package/dist/document/connection-findings.js.map +1 -1
- package/dist/document/connection-spec.d.ts +1 -0
- package/dist/document/connection-spec.d.ts.map +1 -1
- package/dist/document/connection-spec.js +8 -2
- package/dist/document/connection-spec.js.map +1 -1
- package/dist/document/edit.d.ts +1 -1
- package/dist/document/edit.d.ts.map +1 -1
- package/dist/document/edit.js +83 -11
- package/dist/document/edit.js.map +1 -1
- package/dist/document/field-spec.d.ts.map +1 -1
- package/dist/document/field-spec.js +5 -0
- package/dist/document/field-spec.js.map +1 -1
- package/dist/document/findings.d.ts +33 -2
- package/dist/document/findings.d.ts.map +1 -1
- package/dist/document/findings.js +14 -17
- package/dist/document/findings.js.map +1 -1
- package/dist/document/migrate.d.ts.map +1 -1
- package/dist/document/migrate.js +10 -4
- package/dist/document/migrate.js.map +1 -1
- package/dist/document/schema.d.ts +5 -0
- package/dist/document/schema.d.ts.map +1 -1
- package/dist/document/schema.js +10 -1
- package/dist/document/schema.js.map +1 -1
- package/dist/document.schema.json +84 -4
- package/dist/format-error.d.ts +1 -1
- package/dist/format-error.js +1 -1
- package/dist/formats.d.ts +30 -4
- package/dist/formats.d.ts.map +1 -1
- package/dist/formats.js +36 -9
- package/dist/formats.js.map +1 -1
- package/dist/import/coerce.d.ts.map +1 -1
- package/dist/import/coerce.js +16 -4
- package/dist/import/coerce.js.map +1 -1
- package/dist/import/rows.js +2 -2
- package/dist/import/rows.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/kind-list.d.ts +1 -0
- package/dist/kind-list.d.ts.map +1 -1
- package/dist/kind-list.js +2 -1
- package/dist/kind-list.js.map +1 -1
- package/dist/mutations/define-mutation.d.ts.map +1 -1
- package/dist/mutations/define-mutation.js +4 -3
- package/dist/mutations/define-mutation.js.map +1 -1
- package/dist/mutations/derive-repeats.d.ts.map +1 -1
- package/dist/mutations/derive-repeats.js +2 -1
- package/dist/mutations/derive-repeats.js.map +1 -1
- package/dist/mutations/form.d.ts +2 -0
- package/dist/mutations/form.d.ts.map +1 -1
- package/dist/mutations/form.js +6 -3
- package/dist/mutations/form.js.map +1 -1
- package/dist/mutations/type-text.d.ts +1 -1
- package/dist/mutations/type-text.js +1 -1
- package/dist/mutations/types.d.ts +13 -4
- package/dist/mutations/types.d.ts.map +1 -1
- package/dist/mutations/would-run.d.ts +26 -0
- package/dist/mutations/would-run.d.ts.map +1 -0
- package/dist/mutations/would-run.js +58 -0
- package/dist/mutations/would-run.js.map +1 -0
- package/dist/ops/log.d.ts +15 -0
- package/dist/ops/log.d.ts.map +1 -1
- package/dist/ops/log.js.map +1 -1
- package/dist/past.d.ts +3 -1
- package/dist/past.d.ts.map +1 -1
- package/dist/past.js +9 -1
- package/dist/past.js.map +1 -1
- package/dist/permissions/sight.d.ts +8 -0
- package/dist/permissions/sight.d.ts.map +1 -1
- package/dist/permissions/sight.js +8 -0
- package/dist/permissions/sight.js.map +1 -1
- package/dist/publish.d.ts +2 -2
- package/dist/publish.d.ts.map +1 -1
- package/dist/publishing.js +20 -5
- package/dist/publishing.js.map +1 -1
- package/dist/scaffold/agents.d.ts +9 -1
- package/dist/scaffold/agents.d.ts.map +1 -1
- package/dist/scaffold/agents.js +34 -3
- package/dist/scaffold/agents.js.map +1 -1
- package/dist/scaffold/project.js +1 -1
- package/dist/schema/choice-words.d.ts.map +1 -1
- package/dist/schema/choice-words.js +3 -0
- package/dist/schema/choice-words.js.map +1 -1
- package/dist/seen.d.ts.map +1 -1
- package/dist/seen.js +4 -1
- package/dist/seen.js.map +1 -1
- package/dist/starting-document.d.ts +41 -0
- package/dist/starting-document.d.ts.map +1 -0
- package/dist/starting-document.js +614 -0
- package/dist/starting-document.js.map +1 -0
- package/dist/store.d.ts +23 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +49 -11
- package/dist/store.js.map +1 -1
- package/dist/sync/connection.d.ts +10 -1
- package/dist/sync/connection.d.ts.map +1 -1
- package/dist/sync/connection.js +109 -17
- package/dist/sync/connection.js.map +1 -1
- package/dist/sync/engine.d.ts +13 -4
- package/dist/sync/engine.d.ts.map +1 -1
- package/dist/sync/engine.js +5 -3
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/google-calendar.d.ts.map +1 -1
- package/dist/sync/google-calendar.js +4 -2
- package/dist/sync/google-calendar.js.map +1 -1
- package/dist/sync/notion-types.d.ts +7 -0
- package/dist/sync/notion-types.d.ts.map +1 -1
- package/dist/sync/notion-types.js +6 -1
- package/dist/sync/notion-types.js.map +1 -1
- package/dist/sync/recurrence.d.ts +41 -0
- package/dist/sync/recurrence.d.ts.map +1 -0
- package/dist/sync/recurrence.js +192 -0
- package/dist/sync/recurrence.js.map +1 -0
- package/dist/traits.d.ts +72 -0
- package/dist/traits.d.ts.map +1 -0
- package/dist/traits.js +447 -0
- package/dist/traits.js.map +1 -0
- package/dist/typed.d.ts +4 -0
- package/dist/typed.d.ts.map +1 -1
- package/dist/typed.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/document/edit.js
CHANGED
|
@@ -18,6 +18,7 @@ import { jobFindings, jobWorldOfDocument } from "../job-findings.js";
|
|
|
18
18
|
import { JobSpec } from "./purpose-spec.js";
|
|
19
19
|
import { aliasWord, documentAliasFindings } from "../alias-findings.js";
|
|
20
20
|
import { humanizeField, withArticle } from "../schema/define-node.js";
|
|
21
|
+
import { ConnectionSpec } from "./connection-spec.js";
|
|
21
22
|
import { linkedRelationFindings } from "./linked-relation-findings.js";
|
|
22
23
|
import { linkedRelationOfSpec, reachesAnotherApp } from "../schema/linked.js";
|
|
23
24
|
import { ActSpec, ALIAS, MOST_ALIASES, EDGE_NAME, EdgeSpec, FIELD_NAME, FIELD_TYPES, FieldSpec, GrantSpec, KindSpec, NAME, RuleSpec, SightSpec, } from "./schema.js";
|
|
@@ -93,6 +94,7 @@ export const EDIT_OPS = [
|
|
|
93
94
|
"remove-job",
|
|
94
95
|
"add-alias",
|
|
95
96
|
"set-aliases",
|
|
97
|
+
"set-connection",
|
|
96
98
|
];
|
|
97
99
|
/** Words the rule language keeps: a field or relation renamed to one could not be named in a rule. */
|
|
98
100
|
const KEYWORDS = new Set(["and", "or", "not", "in", "where", "true", "false", "null"]);
|
|
@@ -122,7 +124,7 @@ function buildShapes() {
|
|
|
122
124
|
"remove-kind": z.object({ op: z.literal("remove-kind"), kind: kindName }).strict(),
|
|
123
125
|
"add-field": z.object({ op: z.literal("add-field"), kind: kindName, field: fieldName, fill: z.unknown().optional(), spec: z.record(z.string(), z.unknown()).optional() }).passthrough(),
|
|
124
126
|
"rename-field": z.object({ op: z.literal("rename-field"), kind: kindName, field: fieldName, to: fieldName }).strict(),
|
|
125
|
-
"retype-field": z.object({ op: z.literal("retype-field"), kind: kindName, field: fieldName, type: z.enum(FIELD_TYPES), options: z.array(z.string().min(1).max(80)).min(1).max(100).optional(), of: z.enum(["string", "number", "date", "image", "video", "file"]).optional(), format: z.enum(["money", "percent", "duration", "markdown"]).optional() }).strict(),
|
|
127
|
+
"retype-field": z.object({ op: z.literal("retype-field"), kind: kindName, field: fieldName, type: z.enum(FIELD_TYPES), options: z.array(z.string().min(1).max(80)).min(1).max(100).optional(), multiple: z.boolean().optional(), of: z.enum(["string", "number", "date", "image", "video", "file"]).optional(), format: z.enum(["money", "percent", "duration", "markdown"]).optional() }).strict(),
|
|
126
128
|
"set-options": z.object({ op: z.literal("set-options"), kind: kindName, field: fieldName, add: z.array(z.string().min(1).max(80)).optional(), remove: z.array(z.string().min(1).max(80)).optional(), labels: z.record(z.string().min(1).max(80), z.union([z.string().min(1).max(80), z.null()])).optional() }).strict(),
|
|
127
129
|
"set-format": z.object({ op: z.literal("set-format"), kind: kindName, field: fieldName, format: z.union([z.enum(["money", "percent", "duration", "markdown"]), z.null()]).optional(), unit: z.union([z.string().min(1).max(12), z.null()]).optional() }).strict(),
|
|
128
130
|
"set-range": z.object({ op: z.literal("set-range"), kind: kindName, field: fieldName, min: z.union([z.number(), z.null()]).optional(), max: z.union([z.number(), z.null()]).optional(), step: z.union([z.number().positive(), z.null()]).optional() }).strict(),
|
|
@@ -269,6 +271,8 @@ function buildShapes() {
|
|
|
269
271
|
"add-alias": z.object({ op: z.literal("add-alias"), kind: kindName, field: fieldName.optional(), alias: z.string().min(1).max(40) }).strict(),
|
|
270
272
|
// All of a kind's or a field's other words at once; null or [] takes them away.
|
|
271
273
|
"set-aliases": z.object({ op: z.literal("set-aliases"), kind: kindName, field: fieldName.optional(), aliases: z.union([z.array(z.string().min(1).max(40)).max(MOST_ALIASES), z.null()]) }).strict(),
|
|
274
|
+
// A connection declared whole, by its name: set where there was none, or replaced (FR-229); null takes it away.
|
|
275
|
+
"set-connection": z.object({ op: z.literal("set-connection"), name: kindName, connection: z.union([z.record(z.string(), z.unknown()), z.null()]) }).strict(),
|
|
272
276
|
};
|
|
273
277
|
}
|
|
274
278
|
let shapes;
|
|
@@ -276,6 +280,13 @@ const opShapes = () => (shapes ??= buildShapes());
|
|
|
276
280
|
const NONE = new Set();
|
|
277
281
|
const clone = (v) => structuredClone(v);
|
|
278
282
|
const asArray = (v) => [v ?? []].flat();
|
|
283
|
+
/** The first of a choice's default that is none of its options — a list of them where the choice takes several (FR-233) — or undefined when every one is. */
|
|
284
|
+
function defaultOutsideOptions(f, given) {
|
|
285
|
+
const options = f.options ?? [];
|
|
286
|
+
if (!f.multiple)
|
|
287
|
+
return Array.isArray(given) || !options.includes(String(given)) ? given : undefined;
|
|
288
|
+
return (Array.isArray(given) ? given : [given]).find((one) => !options.includes(String(one)));
|
|
289
|
+
}
|
|
279
290
|
const kebab = (camel) => camel.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
280
291
|
const words = (name) => kebab(name).replace(/-/g, " ");
|
|
281
292
|
/** Another app's kinds as people say them: "one house in the app @nick/house", "houses or flats in the app @nick/house". Their plural is that app's to say; this one says the plain one. */
|
|
@@ -860,8 +871,8 @@ class Editor {
|
|
|
860
871
|
this.said.push(`${e.kind}'s ${e.field} has no default now.`);
|
|
861
872
|
}
|
|
862
873
|
else {
|
|
863
|
-
if (f.type === "enum" &&
|
|
864
|
-
return this.fail(i, "default", `"${String(e.default)}" is not one of ${e.field}'s options (${(f.options ?? []).join(", ")})`);
|
|
874
|
+
if (f.type === "enum" && defaultOutsideOptions(f, e.default) !== undefined)
|
|
875
|
+
return this.fail(i, "default", `"${String(defaultOutsideOptions(f, e.default))}" is not one of ${e.field}'s options (${(f.options ?? []).join(", ")})`);
|
|
865
876
|
const outside = outsideRange(e.default, f, e.field);
|
|
866
877
|
if (outside)
|
|
867
878
|
return this.fail(i, "default", outside);
|
|
@@ -1030,7 +1041,36 @@ class Editor {
|
|
|
1030
1041
|
return this.setAliases(i, e.kind, e.field, [...this.aliasesOf(e.kind, e.field), e.alias], e.alias);
|
|
1031
1042
|
case "set-aliases":
|
|
1032
1043
|
return this.setAliases(i, e.kind, e.field, e.aliases ?? []);
|
|
1044
|
+
case "set-connection":
|
|
1045
|
+
return this.setConnection(i, e.name, e.connection);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
// ── a connection, declared whole (FR-229) ──
|
|
1049
|
+
/**
|
|
1050
|
+
* A CONNECTION SET BY NAME: its shape is judged here, as the document's
|
|
1051
|
+
* is; what it says of the kind and the fields it maps, and the acts an
|
|
1052
|
+
* inbound change lands through, the checker judges of the document it
|
|
1053
|
+
* leaves (`connection-findings.ts`). Null takes the connection away.
|
|
1054
|
+
*/
|
|
1055
|
+
setConnection(i, name, connection) {
|
|
1056
|
+
if (connection === null) {
|
|
1057
|
+
if (!this.doc.connections?.[name])
|
|
1058
|
+
return this.fail(i, "name", `there is no connection called "${name}"${this.doc.connections ? `; there ${Object.keys(this.doc.connections).length === 1 ? "is" : "are"} ${Object.keys(this.doc.connections).join(", ")}` : ""}`);
|
|
1059
|
+
delete this.doc.connections[name];
|
|
1060
|
+
if (Object.keys(this.doc.connections).length === 0)
|
|
1061
|
+
delete this.doc.connections;
|
|
1062
|
+
this.said.push(`The connection ${name} is gone: nothing keeps its records in step now.`);
|
|
1063
|
+
return;
|
|
1033
1064
|
}
|
|
1065
|
+
const parsed = ConnectionSpec.safeParse(connection);
|
|
1066
|
+
if (!parsed.success) {
|
|
1067
|
+
this.refuse(i, parsed.error.issues.map((issue) => ({ ...issue, path: ["connection", ...issue.path] })), "a connection");
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const was = this.doc.connections?.[name] !== undefined;
|
|
1071
|
+
(this.doc.connections ??= {})[name] = parsed.data;
|
|
1072
|
+
const system = parsed.data.system === "notion" ? "a Notion database" : "a Google calendar";
|
|
1073
|
+
this.said.push(`The connection ${name} ${was ? "now keeps" : "keeps"} ${pluralOf(this.doc.kinds, parsed.data.kind)} in step with ${system}${parsed.data.direction === "read" ? ", which owns them" : ", both ways"}.`);
|
|
1034
1074
|
}
|
|
1035
1075
|
// ── other words for a kind or a field (Roadmap f-learned-suggestions) ──
|
|
1036
1076
|
aliasesOf(kind, field) {
|
|
@@ -1361,9 +1401,10 @@ class Editor {
|
|
|
1361
1401
|
return (this.fail(i, `bindings.${kind}.column`, `the board does not say which field of ${kind} its columns are`, example), false);
|
|
1362
1402
|
if (!(field in fields))
|
|
1363
1403
|
return (this.fail(i, `bindings.${kind}.column`, `${kind} has no field "${field}"; it has ${Object.keys(fields).join(", ")}`), false);
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1404
|
+
// A choice that takes several of its options (FR-233) has no columns: a record would stand in several.
|
|
1405
|
+
if (fields[field]["type"] !== "enum" || fields[field]["multiple"] === true) {
|
|
1406
|
+
const choices = Object.keys(fields).filter((one) => fields[one]["type"] === "enum" && fields[one]["multiple"] !== true);
|
|
1407
|
+
return (this.fail(i, `bindings.${kind}.column`, `${kind}'s ${field} is ${fields[field]["multiple"] === true ? "a choice of several, so a record would stand in more than one column" : "not a choice, so it has no columns"}`, choices.length > 0 ? `bind one of ${choices.join(", ")}` : `give ${kind} a choice field first: add-field with "type": "enum" and its "options", in the order the columns go`), false);
|
|
1367
1408
|
}
|
|
1368
1409
|
const others = Object.keys(roles).filter((role) => role !== "column");
|
|
1369
1410
|
if (others.length > 0)
|
|
@@ -2237,6 +2278,12 @@ class Editor {
|
|
|
2237
2278
|
spec.plural = e.plural;
|
|
2238
2279
|
else if (spec.plural && (spec.plural === `${words(from)}s` || spec.plural === `${from}s`))
|
|
2239
2280
|
spec.plural = `${words(to)}s`;
|
|
2281
|
+
// Its hue follows it.
|
|
2282
|
+
if (isObject(this.doc.brand?.accents) && from in this.doc.brand.accents && !(to in this.doc.brand.accents)) {
|
|
2283
|
+
this.doc.brand.accents[to] = this.doc.brand.accents[from];
|
|
2284
|
+
delete this.doc.brand.accents[from];
|
|
2285
|
+
touched.push("its hue");
|
|
2286
|
+
}
|
|
2240
2287
|
// Its list's filters, order and grouping follow it.
|
|
2241
2288
|
const lists = this.doc.pages?.lists;
|
|
2242
2289
|
if (lists?.[from] !== undefined && lists[to] === undefined) {
|
|
@@ -2288,6 +2335,7 @@ class Editor {
|
|
|
2288
2335
|
gone.push(what);
|
|
2289
2336
|
}
|
|
2290
2337
|
const removedPlural = this.doc.kinds[kind].plural ?? `${words(kind)}s`;
|
|
2338
|
+
const people = this.doc.kinds[kind].people === true;
|
|
2291
2339
|
// Its own relations' connect and disconnect go with it.
|
|
2292
2340
|
const ownRelationActs = relationActsOf(documentRelations({ [kind]: this.doc.kinds[kind] }));
|
|
2293
2341
|
delete this.doc.kinds[kind];
|
|
@@ -2306,15 +2354,25 @@ class Editor {
|
|
|
2306
2354
|
}
|
|
2307
2355
|
}
|
|
2308
2356
|
}
|
|
2357
|
+
// With the people kind gone, an act on the acting person's own record (`$me`) has no record to reach: it goes too.
|
|
2309
2358
|
for (const [name, act] of Object.entries(this.doc.acts ?? {})) {
|
|
2310
2359
|
const on = asArray(act.on);
|
|
2311
|
-
if (act.creates === kind || (act.effects ?? []).some((x) => x.create === kind) || (on.includes(kind) && on.length === 1)) {
|
|
2360
|
+
if (act.creates === kind || (act.effects ?? []).some((x) => x.create === kind) || (on.includes(kind) && on.length === 1) || (people && JSON.stringify(act).includes('"$me"'))) {
|
|
2312
2361
|
gone.push(titled("act", name, act));
|
|
2313
2362
|
this.removeActQuietly(name);
|
|
2314
2363
|
}
|
|
2315
2364
|
else if (on.includes(kind))
|
|
2316
2365
|
act.on = on.filter((k) => k !== kind);
|
|
2317
2366
|
}
|
|
2367
|
+
// Its hue goes with it: an accent for a kind nobody declares is refused by the checker.
|
|
2368
|
+
if (isObject(this.doc.brand?.accents) && kind in this.doc.brand.accents) {
|
|
2369
|
+
delete this.doc.brand.accents[kind];
|
|
2370
|
+
if (Object.keys(this.doc.brand.accents).length === 0)
|
|
2371
|
+
delete this.doc.brand.accents;
|
|
2372
|
+
if (Object.keys(this.doc.brand).length === 0)
|
|
2373
|
+
delete this.doc.brand;
|
|
2374
|
+
gone.push("its hue");
|
|
2375
|
+
}
|
|
2318
2376
|
for (const [name, rule] of Object.entries(this.doc.rules ?? {})) {
|
|
2319
2377
|
if (rule.over === kind || exprMentions(this.doc, rule.when, rule.over === "graph" ? NONE : new Set([rule.over]), { t: "kind", from: kind, to: "\u0000" }) || exprMentions(this.doc, rule.require, rule.over === "graph" ? NONE : new Set([rule.over]), { t: "kind", from: kind, to: "\u0000" })) {
|
|
2320
2378
|
delete this.doc.rules[name];
|
|
@@ -2342,7 +2400,12 @@ class Editor {
|
|
|
2342
2400
|
});
|
|
2343
2401
|
}
|
|
2344
2402
|
if (this.doc.policy?.sees) {
|
|
2345
|
-
|
|
2403
|
+
// A sight of other kinds through a relation that went with the kind would show nothing: it goes too.
|
|
2404
|
+
const declared = new Set(Object.values(this.doc.kinds).flatMap((spec) => Object.keys(spec.edges ?? {})));
|
|
2405
|
+
const through = this.doc.policy.sees.filter((s) => typeof s.via === "string" && !declared.has(s.via) && s.kinds.some((k) => k !== kind));
|
|
2406
|
+
if (through.length > 0)
|
|
2407
|
+
gone.push(`${through.length === 1 ? "the sight" : "the sights"} through ${listOf([...new Set(through.map((s) => `"${String(s.via)}"`))])}`);
|
|
2408
|
+
this.doc.policy.sees = this.doc.policy.sees.filter((s) => !through.includes(s)).map((s) => ({ ...s, kinds: s.kinds.filter((k) => k !== kind) })).filter((s) => s.kinds.length > 0);
|
|
2346
2409
|
if (this.doc.policy.sees.length === 0)
|
|
2347
2410
|
delete this.doc.policy.sees;
|
|
2348
2411
|
}
|
|
@@ -2397,8 +2460,8 @@ class Editor {
|
|
|
2397
2460
|
}
|
|
2398
2461
|
const f = { ...parsed.data };
|
|
2399
2462
|
delete f.renamedFrom;
|
|
2400
|
-
if (f.type === "enum" && f.default !== undefined &&
|
|
2401
|
-
return this.fail(i, "default", `"${String(f.default)}" is not one of the options`);
|
|
2463
|
+
if (f.type === "enum" && f.default !== undefined && defaultOutsideOptions(f, f.default) !== undefined)
|
|
2464
|
+
return this.fail(i, "default", `"${String(defaultOutsideOptions(f, f.default))}" is not one of the options`);
|
|
2402
2465
|
if (f.format && !formatFits(f.format, f.type))
|
|
2403
2466
|
return this.fail(i, "format", refusedFormat(f.format, f.type));
|
|
2404
2467
|
spec.fields[field] = f;
|
|
@@ -2489,11 +2552,18 @@ class Editor {
|
|
|
2489
2552
|
if (e.format && !formatFits(e.format, e.type))
|
|
2490
2553
|
return this.fail(i, "format", refusedFormat(e.format, e.type));
|
|
2491
2554
|
const was = f.type;
|
|
2555
|
+
const wasSeveral = f.multiple === true;
|
|
2492
2556
|
f.type = e.type;
|
|
2493
2557
|
if (e.type === "enum")
|
|
2494
2558
|
f.options = e.options ?? f.options;
|
|
2495
2559
|
else
|
|
2496
2560
|
delete f.options;
|
|
2561
|
+
// A choice that takes several of its options (FR-233), or one again: only an enum says it, and a retype says it anew.
|
|
2562
|
+
if (e.type === "enum" && e.multiple)
|
|
2563
|
+
f.multiple = true;
|
|
2564
|
+
else
|
|
2565
|
+
delete f.multiple;
|
|
2566
|
+
const several = f.multiple === true;
|
|
2497
2567
|
// An option's label goes with the option.
|
|
2498
2568
|
if (f.optionLabels) {
|
|
2499
2569
|
f.optionLabels = Object.fromEntries(Object.entries(f.optionLabels).filter(([o]) => (f.options ?? []).includes(o)));
|
|
@@ -2523,9 +2593,11 @@ class Editor {
|
|
|
2523
2593
|
// A list grouped by a choice that is no longer one opens ungrouped, and says so.
|
|
2524
2594
|
const held = this.listsHold(e.kind);
|
|
2525
2595
|
if (held.length > 0)
|
|
2526
|
-
this.said.push(`${capitalize(listOf(held))} ${held.length === 1 ? "goes" : "go"} back to the list's own, since ${e.field} is no longer ${was === "enum" ? "a choice" : `a ${was}`}.`);
|
|
2596
|
+
this.said.push(`${capitalize(listOf(held))} ${held.length === 1 ? "goes" : "go"} back to the list's own, since ${e.field} is no longer ${was === "enum" ? (several ? "a choice of one" : "a choice") : `a ${was}`}.`);
|
|
2527
2597
|
if (was === e.type && e.type === "text")
|
|
2528
2598
|
this.said.push(`${e.kind}'s ${e.field} ${e.format === "markdown" ? "is now written in Markdown, drawn as prose on the record's page" : "is drawn as written, not as Markdown"}; its values are kept.`);
|
|
2599
|
+
else if (was === e.type && e.type === "enum" && wasSeveral !== several)
|
|
2600
|
+
this.said.push(several ? `${e.kind}'s ${e.field} now takes several of its options at once; the one each record held is kept as a list of one.` : `${e.kind}'s ${e.field} takes one of its options again; the first each record held is kept.`);
|
|
2529
2601
|
else
|
|
2530
2602
|
this.said.push(`${e.kind}'s ${e.field} changes from ${was} to ${e.type}${said ? `, said as ${said}` : ""}; values that convert are kept.`);
|
|
2531
2603
|
}
|