tsondb 0.5.14 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/node/index.js +3 -1
- package/dist/src/node/schema/types/generic/EnumType.js +17 -13
- package/dist/src/node/server/init.js +6 -0
- package/dist/src/node/utils/instances.js +2 -2
- package/dist/src/node/utils/references.d.ts +3 -0
- package/dist/src/node/utils/references.js +17 -10
- package/dist/src/shared/utils/markdown.d.ts +6 -1
- package/dist/src/shared/utils/markdown.js +96 -15
- package/dist/src/web/components/Git.js +3 -1
- package/dist/src/web/components/Layout.js +1 -2
- package/dist/src/web/components/typeInputs/StringTypeInput.js +4 -3
- package/dist/src/web/context/entities.d.ts +6 -0
- package/dist/src/web/context/entities.js +2 -0
- package/dist/src/web/hooks/useEntityFromRoute.d.ts +1 -1
- package/dist/src/web/hooks/useEntityFromRoute.js +4 -15
- package/dist/src/web/index.js +19 -2
- package/dist/src/web/routes/CreateInstance.js +5 -5
- package/dist/src/web/routes/Entity.js +10 -9
- package/dist/src/web/routes/Home.js +6 -8
- package/dist/src/web/routes/Instance.js +14 -14
- package/dist/src/web/utils/BlockMarkdown.d.ts +7 -0
- package/dist/src/web/utils/BlockMarkdown.js +17 -0
- package/dist/src/web/utils/BlockMarkdownHighlighting.d.ts +7 -0
- package/dist/src/web/utils/BlockMarkdownHighlighting.js +10 -0
- package/dist/src/web/utils/InlineMarkdown.d.ts +7 -0
- package/dist/src/web/utils/InlineMarkdown.js +13 -0
- package/dist/src/web/utils/Markdown.js +1 -29
- package/dist/src/web/utils/MarkdownHighlighting.d.ts +7 -0
- package/dist/src/web/utils/MarkdownHighlighting.js +11 -0
- package/package.json +1 -1
- package/public/css/styles.css +40 -5
package/dist/src/node/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { getEntities } from "./schema/Schema.js";
|
|
|
9
9
|
import { createServer } from "./server/index.js";
|
|
10
10
|
import { countErrors, getErrorMessageForDisplay, wrapErrorsIfAny } from "./utils/error.js";
|
|
11
11
|
import { formatInstance, getInstancesByEntityName } from "./utils/instances.js";
|
|
12
|
-
const debug = Debug("tsondb:
|
|
12
|
+
const debug = Debug("tsondb:jsapi");
|
|
13
13
|
const prepareFolders = async (dataRootPath, entities) => {
|
|
14
14
|
await mkdir(dataRootPath, { recursive: true });
|
|
15
15
|
for (const entity of entities) {
|
|
@@ -58,7 +58,9 @@ export const generateAndValidate = async (schema, outputs, dataRootPath, validat
|
|
|
58
58
|
export const serve = async (schema, dataRootPath, serverOptions) => {
|
|
59
59
|
const entities = getEntities(schema);
|
|
60
60
|
await prepareFolders(dataRootPath, entities);
|
|
61
|
+
debug("prepared folders");
|
|
61
62
|
const instancesByEntityName = await getInstancesByEntityName(dataRootPath, entities);
|
|
63
|
+
debug("loaded instances");
|
|
62
64
|
await createServer(schema, dataRootPath, instancesByEntityName, serverOptions);
|
|
63
65
|
};
|
|
64
66
|
export const generateValidateAndServe = async (schema, outputs, dataRootPath, serverOptions, validationOptions) => {
|
|
@@ -50,7 +50,7 @@ export const validateEnumType = (helpers, type, value) => {
|
|
|
50
50
|
];
|
|
51
51
|
}
|
|
52
52
|
return parallelizeErrors([
|
|
53
|
-
wrapErrorsIfAny(`at enum
|
|
53
|
+
wrapErrorsIfAny(`at enum case ${key(`"${caseName}"`, helpers.useStyling)}`, validate(helpers, associatedType, value[caseName])),
|
|
54
54
|
]);
|
|
55
55
|
}
|
|
56
56
|
return [];
|
|
@@ -77,18 +77,22 @@ export const serializeEnumType = type => ({
|
|
|
77
77
|
},
|
|
78
78
|
])),
|
|
79
79
|
});
|
|
80
|
-
export const getReferencesForEnumType = (type, value) =>
|
|
81
|
-
value !==
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
value[discriminatorKey]
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
export const getReferencesForEnumType = (type, value) => {
|
|
81
|
+
if (typeof value !== "object" ||
|
|
82
|
+
value === null ||
|
|
83
|
+
Array.isArray(value) ||
|
|
84
|
+
!(discriminatorKey in value)) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const enumCase = value[discriminatorKey];
|
|
88
|
+
return typeof enumCase === "string" &&
|
|
89
|
+
enumCase in type.values &&
|
|
90
|
+
type.values[enumCase] !== undefined &&
|
|
91
|
+
type.values[enumCase].type !== null &&
|
|
92
|
+
enumCase in value
|
|
93
|
+
? getReferencesForType(type.values[enumCase].type, value[enumCase])
|
|
94
|
+
: [];
|
|
95
|
+
};
|
|
92
96
|
export const formatEnumType = (type, value) => {
|
|
93
97
|
if (typeof value === "object" &&
|
|
94
98
|
value !== null &&
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
1
2
|
import { simpleGit } from "simple-git";
|
|
2
3
|
import { isEntityDecl } from "../schema/declarations/EntityDecl.js";
|
|
3
4
|
import { resolveTypeArgumentsInDecls } from "../schema/index.js";
|
|
4
5
|
import { attachGitStatusToInstancesByEntityName, getInstancesByEntityName, } from "../utils/instances.js";
|
|
5
6
|
import { getReferencesToInstances } from "../utils/references.js";
|
|
7
|
+
const debug = Debug("tsondb:server:init");
|
|
6
8
|
const getGit = async (dataRootPath) => {
|
|
7
9
|
const git = simpleGit({ baseDir: dataRootPath });
|
|
8
10
|
if (await git.checkIsRepo()) {
|
|
@@ -22,12 +24,16 @@ const getGit = async (dataRootPath) => {
|
|
|
22
24
|
export const init = async (schema, dataRootPath, instancesByEntityName) => {
|
|
23
25
|
const { git, root: gitRoot, status: gitStatus } = await getGit(dataRootPath);
|
|
24
26
|
const declarations = resolveTypeArgumentsInDecls(schema.declarations);
|
|
27
|
+
debug("resolved type arguments in declarations");
|
|
25
28
|
const entities = declarations.filter(isEntityDecl);
|
|
29
|
+
debug("found %d entities in declarations", entities.length);
|
|
26
30
|
const entitiesByName = Object.fromEntries(entities.map(entity => [entity.name, entity]));
|
|
27
31
|
const instancesByEntityNameInMemory = Object.assign({}, instancesByEntityName);
|
|
28
32
|
const referencesToInstances = getReferencesToInstances(instancesByEntityName, entitiesByName);
|
|
33
|
+
debug("created references cache");
|
|
29
34
|
if (gitStatus) {
|
|
30
35
|
attachGitStatusToInstancesByEntityName(instancesByEntityName, dataRootPath, gitRoot, gitStatus);
|
|
36
|
+
debug("retrieved git status to instances");
|
|
31
37
|
}
|
|
32
38
|
const getInstanceById = id => {
|
|
33
39
|
for (const entityName in instancesByEntityNameInMemory) {
|
|
@@ -2,7 +2,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
2
2
|
import { basename, extname, join } from "node:path";
|
|
3
3
|
import { formatValue } from "../schema/index.js";
|
|
4
4
|
import { getGitFileStatusFromStatusResult } from "./git.js";
|
|
5
|
-
export const getInstancesByEntityName = async (dataRoot, entities) => Object.fromEntries(await Promise.all(entities.map(async (entity) => {
|
|
5
|
+
export const getInstancesByEntityName = async (dataRoot, entities) => Object.fromEntries((await Promise.all(entities.map(async (entity) => {
|
|
6
6
|
const entityDir = join(dataRoot, entity.name);
|
|
7
7
|
const instanceFileNames = await readdir(entityDir);
|
|
8
8
|
const instances = await Promise.all(instanceFileNames.map(async (instanceFileName) => ({
|
|
@@ -11,7 +11,7 @@ export const getInstancesByEntityName = async (dataRoot, entities) => Object.fro
|
|
|
11
11
|
content: JSON.parse(await readFile(join(entityDir, instanceFileName), "utf-8")),
|
|
12
12
|
})));
|
|
13
13
|
return [entity.name, instances];
|
|
14
|
-
})));
|
|
14
|
+
}))).toSorted(([a], [b]) => a.localeCompare(b)));
|
|
15
15
|
export const attachGitStatusToInstancesByEntityName = (instancesByEntityName, dataRoot, gitRoot, gitStatus) => {
|
|
16
16
|
Object.entries(instancesByEntityName).forEach(([entityName, instances]) => {
|
|
17
17
|
instancesByEntityName[entityName] = instances.map(instanceContainer => ({
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { InstanceContainer } from "../../shared/utils/instances.ts";
|
|
2
2
|
import type { EntityDecl } from "../schema/declarations/EntityDecl.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A mapping from instance IDs to the list of instance IDs that reference them.
|
|
5
|
+
*/
|
|
3
6
|
export type ReferencesToInstances = {
|
|
4
7
|
[instanceId: string]: string[];
|
|
5
8
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
1
2
|
import { difference, removeAt } from "../../shared/utils/array.js";
|
|
2
3
|
import { getReferencesForEntityDecl } from "../schema/declarations/EntityDecl.js";
|
|
4
|
+
const debug = Debug("tsondb:utils:references");
|
|
3
5
|
const addReference = (acc, reference, instanceId) => ({
|
|
4
6
|
...acc,
|
|
5
7
|
[reference]: [...(acc[reference] ?? []), instanceId],
|
|
@@ -12,16 +14,21 @@ const removeReference = (acc, reference, instanceId) => acc[reference]
|
|
|
12
14
|
}
|
|
13
15
|
: acc;
|
|
14
16
|
const removeReferences = (acc, references, instanceId) => references.reduce((acc1, reference) => removeReference(acc1, reference, instanceId), acc);
|
|
15
|
-
export const getReferencesToInstances = (instancesByEntityName, entitiesByName) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
export const getReferencesToInstances = (instancesByEntityName, entitiesByName) => {
|
|
18
|
+
debug("collecting references ...");
|
|
19
|
+
return Object.entries(instancesByEntityName).reduce((acc, [entityName, instances]) => {
|
|
20
|
+
const entity = entitiesByName[entityName];
|
|
21
|
+
if (entity) {
|
|
22
|
+
const refs = instances.reduce((acc1, instance) => {
|
|
23
|
+
const references = getReferencesForEntityDecl(entity, instance.content);
|
|
24
|
+
return addReferences(acc1, references, instance.id);
|
|
25
|
+
}, acc);
|
|
26
|
+
debug("collected references for entity %s in %d instances", entityName, instances.length);
|
|
27
|
+
return refs;
|
|
28
|
+
}
|
|
29
|
+
return acc;
|
|
30
|
+
}, {});
|
|
31
|
+
};
|
|
25
32
|
export const updateReferencesToInstances = (entitiesByName, referencesToInstances, entityName, instanceId, oldInstance, newInstance) => {
|
|
26
33
|
const entity = entitiesByName[entityName];
|
|
27
34
|
if (entity) {
|
|
@@ -19,6 +19,11 @@ export type BlockMarkdownNode = {
|
|
|
19
19
|
kind: "listitem";
|
|
20
20
|
content: InlineMarkdownNode[];
|
|
21
21
|
}[];
|
|
22
|
-
}
|
|
22
|
+
};
|
|
23
|
+
export type BlockSyntaxMarkdownNode = InlineMarkdownNode | {
|
|
24
|
+
kind: "listitemmarker";
|
|
25
|
+
content: string;
|
|
26
|
+
};
|
|
23
27
|
export declare const parseBlockMarkdown: (text: string) => BlockMarkdownNode[];
|
|
28
|
+
export declare const parseBlockMarkdownForSyntaxHighlighting: (text: string) => BlockSyntaxMarkdownNode[];
|
|
24
29
|
export {};
|
|
@@ -1,22 +1,61 @@
|
|
|
1
1
|
const codeRule = {
|
|
2
2
|
pattern: /`(.*?)`/,
|
|
3
|
-
map: result => ({
|
|
3
|
+
map: (result, _parseInside, forSyntaxHighlighting) => ({
|
|
4
|
+
kind: "code",
|
|
5
|
+
content: forSyntaxHighlighting ? `\`${result[1] ?? ""}\`` : (result[1] ?? ""),
|
|
6
|
+
}),
|
|
4
7
|
};
|
|
5
8
|
const boldWithItalicRule = {
|
|
6
9
|
pattern: /\*\*(.*?\*.+?\*.*?)\*\*/,
|
|
7
|
-
map: (result, parseInside) => ({
|
|
10
|
+
map: (result, parseInside, forSyntaxHighlighting) => ({
|
|
11
|
+
kind: "bold",
|
|
12
|
+
content: forSyntaxHighlighting
|
|
13
|
+
? [
|
|
14
|
+
{ kind: "text", content: "**" },
|
|
15
|
+
...parseInside(result[1] ?? ""),
|
|
16
|
+
{ kind: "text", content: "**" },
|
|
17
|
+
]
|
|
18
|
+
: parseInside(result[1] ?? ""),
|
|
19
|
+
}),
|
|
8
20
|
};
|
|
9
21
|
const italicWithBoldRule = {
|
|
10
22
|
pattern: /\*(.*?\*\*.+?\*\*.*?)\*/,
|
|
11
|
-
map: (result, parseInside) => ({
|
|
23
|
+
map: (result, parseInside, forSyntaxHighlighting) => ({
|
|
24
|
+
kind: "italic",
|
|
25
|
+
content: forSyntaxHighlighting
|
|
26
|
+
? [
|
|
27
|
+
{ kind: "text", content: "*" },
|
|
28
|
+
...parseInside(result[1] ?? ""),
|
|
29
|
+
{ kind: "text", content: "*" },
|
|
30
|
+
]
|
|
31
|
+
: parseInside(result[1] ?? ""),
|
|
32
|
+
}),
|
|
12
33
|
};
|
|
13
34
|
const boldRule = {
|
|
14
35
|
pattern: /\*\*(.+?)\*\*/,
|
|
15
|
-
map: (result, parseInside) => ({
|
|
36
|
+
map: (result, parseInside, forSyntaxHighlighting) => ({
|
|
37
|
+
kind: "bold",
|
|
38
|
+
content: forSyntaxHighlighting
|
|
39
|
+
? [
|
|
40
|
+
{ kind: "text", content: "**" },
|
|
41
|
+
...parseInside(result[1] ?? ""),
|
|
42
|
+
{ kind: "text", content: "**" },
|
|
43
|
+
]
|
|
44
|
+
: parseInside(result[1] ?? ""),
|
|
45
|
+
}),
|
|
16
46
|
};
|
|
17
47
|
const italicRule = {
|
|
18
48
|
pattern: /\*(.+?)\*/,
|
|
19
|
-
map: (result, parseInside) => ({
|
|
49
|
+
map: (result, parseInside, forSyntaxHighlighting) => ({
|
|
50
|
+
kind: "italic",
|
|
51
|
+
content: forSyntaxHighlighting
|
|
52
|
+
? [
|
|
53
|
+
{ kind: "text", content: "*" },
|
|
54
|
+
...parseInside(result[1] ?? ""),
|
|
55
|
+
{ kind: "text", content: "*" },
|
|
56
|
+
]
|
|
57
|
+
: parseInside(result[1] ?? ""),
|
|
58
|
+
}),
|
|
20
59
|
};
|
|
21
60
|
const inlineRules = [
|
|
22
61
|
codeRule,
|
|
@@ -25,7 +64,7 @@ const inlineRules = [
|
|
|
25
64
|
boldRule,
|
|
26
65
|
italicRule,
|
|
27
66
|
];
|
|
28
|
-
const parseForInlineRules = (rules, text) => {
|
|
67
|
+
const parseForInlineRules = (rules, text, forSyntaxHighlighting) => {
|
|
29
68
|
if (text.length === 0) {
|
|
30
69
|
return [];
|
|
31
70
|
}
|
|
@@ -39,30 +78,50 @@ const parseForInlineRules = (rules, text) => {
|
|
|
39
78
|
const before = text.slice(0, index);
|
|
40
79
|
const after = text.slice(index + res[0].length);
|
|
41
80
|
return [
|
|
42
|
-
...(before.length > 0
|
|
43
|
-
|
|
44
|
-
|
|
81
|
+
...(before.length > 0
|
|
82
|
+
? parseForInlineRules(rules.slice(1), before, forSyntaxHighlighting)
|
|
83
|
+
: []),
|
|
84
|
+
activeRule.map(res, text => parseForInlineRules(rules.slice(1), text, forSyntaxHighlighting), forSyntaxHighlighting),
|
|
85
|
+
...(after.length > 0 ? parseForInlineRules(rules, after, forSyntaxHighlighting) : []),
|
|
45
86
|
];
|
|
46
87
|
}
|
|
47
88
|
else {
|
|
48
|
-
return parseForInlineRules(rules.slice(1), text);
|
|
89
|
+
return parseForInlineRules(rules.slice(1), text, forSyntaxHighlighting);
|
|
49
90
|
}
|
|
50
91
|
};
|
|
51
|
-
const parseInlineMarkdown = (text) => parseForInlineRules(inlineRules, text);
|
|
92
|
+
const parseInlineMarkdown = (text, forSyntaxHighlighting) => parseForInlineRules(inlineRules, text, forSyntaxHighlighting);
|
|
52
93
|
const listRule = {
|
|
53
|
-
pattern: /^((?:(?:\d+\.|[-*]) [^\n]+?)(?:\n(?:\d+\.|[-*]) [^\n]+?)*)(
|
|
94
|
+
pattern: /^((?:(?:\d+\.|[-*]) [^\n]+?)(?:\n(?:\d+\.|[-*]) [^\n]+?)*)(\n{2,}|$)/,
|
|
54
95
|
map: result => ({
|
|
55
96
|
kind: "list",
|
|
56
97
|
ordered: /^\d+\. /.test(result[0]),
|
|
57
98
|
content: (result[1] ?? "").split("\n").map(item => ({
|
|
58
99
|
kind: "listitem",
|
|
59
|
-
content: parseInlineMarkdown(item.replace(/^\d+\. |[-*] /, "")),
|
|
100
|
+
content: parseInlineMarkdown(item.replace(/^\d+\. |[-*] /, ""), false),
|
|
60
101
|
})),
|
|
61
102
|
}),
|
|
103
|
+
mapHighlighting: result => [
|
|
104
|
+
...(result[1] ?? "").split("\n").flatMap((item, index, array) => [
|
|
105
|
+
{
|
|
106
|
+
kind: "listitemmarker",
|
|
107
|
+
content: /^(\d+\. |[-*] )/.exec(item)?.[1] ?? "",
|
|
108
|
+
},
|
|
109
|
+
...parseInlineMarkdown(item.replace(/^\d+\. |[-*] /, ""), true),
|
|
110
|
+
...(index < array.length - 1 ? [{ kind: "text", content: "\n" }] : []),
|
|
111
|
+
]),
|
|
112
|
+
{ kind: "text", content: result[2] ?? "" },
|
|
113
|
+
],
|
|
62
114
|
};
|
|
63
115
|
const paragraphRule = {
|
|
64
|
-
pattern: /^((?:[^\n]+?)(?:\n[^\n]+?)*)(
|
|
65
|
-
map: result => ({
|
|
116
|
+
pattern: /^((?:[^\n]+?)(?:\n[^\n]+?)*)(\n{2,}|\s*$)/,
|
|
117
|
+
map: result => ({
|
|
118
|
+
kind: "paragraph",
|
|
119
|
+
content: parseInlineMarkdown(result[1] ?? "", false),
|
|
120
|
+
}),
|
|
121
|
+
mapHighlighting: result => [
|
|
122
|
+
...parseInlineMarkdown(result[1] ?? "", true),
|
|
123
|
+
{ kind: "text", content: result[2] ?? "" },
|
|
124
|
+
],
|
|
66
125
|
};
|
|
67
126
|
const blockRules = [listRule, paragraphRule];
|
|
68
127
|
const parseForBlockRules = (rules, text, remainingRules = rules) => {
|
|
@@ -83,4 +142,26 @@ const parseForBlockRules = (rules, text, remainingRules = rules) => {
|
|
|
83
142
|
return parseForBlockRules(rules, text, remainingRules.slice(1));
|
|
84
143
|
}
|
|
85
144
|
};
|
|
145
|
+
const parseForBlockRulesSyntaxHighlighting = (rules, text, remainingRules = rules) => {
|
|
146
|
+
if (text.length === 0) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const activeRule = remainingRules[0];
|
|
150
|
+
if (activeRule === undefined) {
|
|
151
|
+
return [{ kind: "text", content: text }];
|
|
152
|
+
}
|
|
153
|
+
const res = activeRule.pattern.exec(text);
|
|
154
|
+
if (res && (activeRule.predicate?.(res) ?? true)) {
|
|
155
|
+
const { index } = res;
|
|
156
|
+
const after = text.slice(index + res[0].length);
|
|
157
|
+
return [
|
|
158
|
+
...activeRule.mapHighlighting(res),
|
|
159
|
+
...(after.length > 0 ? parseForBlockRulesSyntaxHighlighting(rules, after) : []),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return parseForBlockRulesSyntaxHighlighting(rules, text, remainingRules.slice(1));
|
|
164
|
+
}
|
|
165
|
+
};
|
|
86
166
|
export const parseBlockMarkdown = (text) => parseForBlockRules(blockRules, text);
|
|
167
|
+
export const parseBlockMarkdownForSyntaxHighlighting = (text) => parseForBlockRulesSyntaxHighlighting(blockRules, text);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { useLocation } from "preact-iso";
|
|
2
3
|
import { useEffect, useState } from "preact/hooks";
|
|
3
4
|
import { getGitStatusForDisplay, getLabelForGitStatus, isChangedInIndex, isChangedInWorkingDir, } from "../../shared/utils/git.js";
|
|
4
5
|
import { commitStagedFiles, createBranch, getAllEntities, getBranches, getGitStatus, pullCommits, pushCommits, stageAllFiles, stageFileOfEntity, switchBranch, unstageAllFiles, unstageFileOfEntity, } from "../api.js";
|
|
@@ -36,6 +37,7 @@ export const Git = () => {
|
|
|
36
37
|
setAllBranches(branchesData.allBranches);
|
|
37
38
|
setCurrentBranch(branchesData.currentBranch);
|
|
38
39
|
});
|
|
40
|
+
const location = useLocation();
|
|
39
41
|
useEffect(() => {
|
|
40
42
|
getAllEntities()
|
|
41
43
|
.then(async (data) => {
|
|
@@ -48,7 +50,7 @@ export const Git = () => {
|
|
|
48
50
|
console.error("Error fetching entities:", error.toString());
|
|
49
51
|
}
|
|
50
52
|
});
|
|
51
|
-
}, []);
|
|
53
|
+
}, [location.path]);
|
|
52
54
|
const stage = (entityName, instance) => {
|
|
53
55
|
stageFileOfEntity(entityName, instance.id)
|
|
54
56
|
.then(() => updateGitStatus(entities))
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
-
import { Git } from "./Git.js";
|
|
3
2
|
export const Layout = ({ breadcrumbs, children }) => {
|
|
4
|
-
return (_jsxs(_Fragment, { children: [_jsx("header", { children: _jsx("nav", { children: _jsx("ol", { children: breadcrumbs.map(({ url, label }) => (_jsx("li", { children: _jsx("a", { href: url, children: label }) }, url))) }) }) }), _jsx(
|
|
3
|
+
return (_jsxs(_Fragment, { children: [_jsx("header", { children: _jsx("nav", { children: _jsx("ol", { children: breadcrumbs.map(({ url, label }) => (_jsx("li", { children: _jsx("a", { href: url, children: label }) }, url))) }) }) }), _jsx("main", { children: children })] }));
|
|
5
4
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
2
|
import { validateStringConstraints } from "../../../shared/validation/string.js";
|
|
3
3
|
import { Markdown } from "../../utils/Markdown.js";
|
|
4
|
+
import { MarkdownHighlighting } from "../../utils/MarkdownHighlighting.js";
|
|
4
5
|
import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
|
|
5
6
|
import { ValidationErrors } from "./utils/ValidationErrors.js";
|
|
6
7
|
export const StringTypeInput = ({ type, value, onChange }) => {
|
|
@@ -9,9 +10,9 @@ export const StringTypeInput = ({ type, value, onChange }) => {
|
|
|
9
10
|
}
|
|
10
11
|
const { minLength, maxLength, pattern, isMarkdown } = type;
|
|
11
12
|
const errors = validateStringConstraints(type, value);
|
|
12
|
-
return (_jsx("div", { class: "field field--string", children: isMarkdown ? (_jsxs(_Fragment, { children: [_jsxs("div", {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
return (_jsx("div", { class: "field field--string", children: isMarkdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { class: "editor editor--markdown", children: [_jsxs("div", { class: "textarea-grow-wrap", children: [_jsx("textarea", { value: value, minLength: minLength, maxLength: maxLength, onInput: event => {
|
|
14
|
+
onChange(event.currentTarget.value);
|
|
15
|
+
}, "aria-invalid": errors.length > 0 }), _jsxs("p", { class: "help", children: ["This textarea supports", " ", _jsx("a", { href: "https://www.markdownguide.org/getting-started/", target: "_blank", rel: "noreferrer", children: "Markdown" }), "."] }), _jsx(MarkdownHighlighting, { class: "textarea-grow-wrap__mirror editor-highlighting", string: value })] }), _jsx(ValidationErrors, { errors: errors })] }), _jsx("div", { class: "preview", children: _jsx(Markdown, { string: value }) })] })) : (_jsxs("div", { class: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern === undefined
|
|
15
16
|
? undefined
|
|
16
17
|
: pattern.startsWith("^(?:") && pattern.endsWith(")$")
|
|
17
18
|
? pattern.slice(4, -2)
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { useRoute } from "preact-iso";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useContext, useMemo } from "preact/hooks";
|
|
3
|
+
import { EntitiesContext } from "../context/entities.js";
|
|
4
4
|
export const useEntityFromRoute = () => {
|
|
5
5
|
const { params: { name }, } = useRoute();
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
if (name) {
|
|
9
|
-
getEntityByName(name)
|
|
10
|
-
.then(data => {
|
|
11
|
-
setEntityData(data);
|
|
12
|
-
})
|
|
13
|
-
.catch((error) => {
|
|
14
|
-
console.error("Error fetching data:", error);
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}, [name]);
|
|
18
|
-
const entityObj = useMemo(() => entityData && { entity: entityData.declaration, isLocaleEntity: entityData.isLocaleEntity }, [entityData]);
|
|
6
|
+
const entities = useContext(EntitiesContext);
|
|
7
|
+
const entityObj = useMemo(() => entities.find(e => e.declaration.name === name), [entities, name]);
|
|
19
8
|
return entityObj;
|
|
20
9
|
};
|
package/dist/src/web/index.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { render } from "preact";
|
|
3
|
-
import { LocationProvider, Route, Router } from "preact-iso";
|
|
3
|
+
import { LocationProvider, Route, Router, useLocation } from "preact-iso";
|
|
4
|
+
import { useEffect } from "preact/hooks";
|
|
5
|
+
import { getAllEntities } from "./api.js";
|
|
6
|
+
import { Git } from "./components/Git.js";
|
|
7
|
+
import { EntitiesContext } from "./context/entities.js";
|
|
8
|
+
import { useMappedAPIResource } from "./hooks/useMappedAPIResource.js";
|
|
4
9
|
import { CreateInstance } from "./routes/CreateInstance.js";
|
|
5
10
|
import { Entity } from "./routes/Entity.js";
|
|
6
11
|
import { Home } from "./routes/Home.js";
|
|
7
12
|
import { Instance } from "./routes/Instance.js";
|
|
8
13
|
import { NotFound } from "./routes/NotFound.js";
|
|
9
|
-
const
|
|
14
|
+
const mapEntities = (data) => data.declarations
|
|
15
|
+
.map(decl => ({ ...decl, isLocaleEntity: decl.declaration.name === data.localeEntity }))
|
|
16
|
+
.sort((a, b) => a.declaration.name.localeCompare(b.declaration.name));
|
|
17
|
+
const App = () => {
|
|
18
|
+
const [entities, reloadEntities] = useMappedAPIResource(getAllEntities, mapEntities);
|
|
19
|
+
const location = useLocation();
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
reloadEntities().catch((error) => {
|
|
22
|
+
alert("Error reloading entities: " + String(error));
|
|
23
|
+
});
|
|
24
|
+
}, [location.path, reloadEntities]);
|
|
25
|
+
return (_jsx(LocationProvider, { children: _jsxs(EntitiesContext.Provider, { value: entities ?? [], children: [_jsxs(Router, { children: [_jsx(Route, { path: "/", component: Home }), _jsx(Route, { path: "/entities/:name", component: Entity }), _jsx(Route, { path: "/entities/:name/instances/create", component: CreateInstance }), _jsx(Route, { path: "/entities/:name/instances/:id", component: Instance }), _jsx(Route, { default: true, component: NotFound })] }), _jsx(Git, {})] }) }));
|
|
26
|
+
};
|
|
10
27
|
const root = document.getElementById("app");
|
|
11
28
|
render(_jsx(App, {}), root);
|
|
@@ -23,22 +23,22 @@ export const CreateInstance = () => {
|
|
|
23
23
|
const [customId, setCustomId] = useState("");
|
|
24
24
|
useEffect(() => {
|
|
25
25
|
if (entityFromRoute) {
|
|
26
|
-
setInstance(createTypeSkeleton(getDeclFromDeclName, entityFromRoute.
|
|
26
|
+
setInstance(createTypeSkeleton(getDeclFromDeclName, entityFromRoute.declaration.type));
|
|
27
27
|
}
|
|
28
28
|
}, [getDeclFromDeclName, entityFromRoute]);
|
|
29
29
|
const { route } = useLocation();
|
|
30
30
|
useEffect(() => {
|
|
31
|
-
const entityName = entityFromRoute?.
|
|
31
|
+
const entityName = entityFromRoute?.declaration.name ?? name;
|
|
32
32
|
document.title =
|
|
33
33
|
entityName === undefined ? "Not found" : "New " + toTitleCase(entityName) + " — TSONDB";
|
|
34
|
-
}, [entityFromRoute?.
|
|
34
|
+
}, [entityFromRoute?.declaration.name, name]);
|
|
35
35
|
if (!name) {
|
|
36
36
|
return _jsx(NotFound, {});
|
|
37
37
|
}
|
|
38
38
|
if (!entityFromRoute || !instanceNamesByEntity || !declsLoaded) {
|
|
39
39
|
return (_jsxs("div", { children: [_jsx("h1", { children: name }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
|
|
40
40
|
}
|
|
41
|
-
const { entity, isLocaleEntity } = entityFromRoute;
|
|
41
|
+
const { declaration: entity, isLocaleEntity } = entityFromRoute;
|
|
42
42
|
const handleSubmit = (event) => {
|
|
43
43
|
event.preventDefault();
|
|
44
44
|
const name = event.submitter?.getAttribute("name");
|
|
@@ -75,7 +75,7 @@ export const CreateInstance = () => {
|
|
|
75
75
|
const idErrors = isLocaleEntity ? validateLocaleIdentifier(customId) : [];
|
|
76
76
|
return (_jsxs(Layout, { breadcrumbs: [
|
|
77
77
|
{ url: "/", label: homeTitle },
|
|
78
|
-
{ url: `/entities/${entity.name}`, label: entity.
|
|
78
|
+
{ url: `/entities/${entity.name}`, label: toTitleCase(entity.namePlural) },
|
|
79
79
|
], children: [_jsx("div", { class: "header-with-btns", children: _jsx("h1", { class: instanceName.length === 0 ? "empty-name" : undefined, children: instanceName || defaultName }) }), isLocaleEntity && (_jsxs("div", { class: "field field--id", children: [_jsx("label", { htmlFor: "id", children: "ID" }), _jsx("p", { className: "comment", children: "The instance\u2019s identifier. An IETF language tag (BCP47)." }), _jsx("input", { type: "text", id: "id", value: customId, required: true, pattern: "[a-z]{2,3}(-[A-Z]{2,3})?", placeholder: "en-US, de-DE, \u2026", onInput: event => {
|
|
80
80
|
setCustomId(event.currentTarget.value);
|
|
81
81
|
}, "aria-invalid": idErrors.length > 0 }), _jsx(ValidationErrors, { errors: idErrors })] })), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entity.type, path: undefined, value: instance, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: value => {
|
|
@@ -3,9 +3,9 @@ import { useRoute } from "preact-iso";
|
|
|
3
3
|
import { useEffect, useState } from "preact/hooks";
|
|
4
4
|
import { getGitStatusForDisplay, getLabelForGitStatus } from "../../shared/utils/git.js";
|
|
5
5
|
import { toTitleCase } from "../../shared/utils/string.js";
|
|
6
|
-
import { deleteInstanceByEntityNameAndId,
|
|
6
|
+
import { deleteInstanceByEntityNameAndId, getInstancesByEntityName } from "../api.js";
|
|
7
7
|
import { Layout } from "../components/Layout.js";
|
|
8
|
-
import {
|
|
8
|
+
import { useEntityFromRoute } from "../hooks/useEntityFromRoute.js";
|
|
9
9
|
import { useMappedAPIResource } from "../hooks/useMappedAPIResource.js";
|
|
10
10
|
import { Markdown } from "../utils/Markdown.js";
|
|
11
11
|
import { homeTitle } from "./Home.js";
|
|
@@ -14,11 +14,12 @@ const mapInstances = (data) => data.instances;
|
|
|
14
14
|
export const Entity = () => {
|
|
15
15
|
const { params: { name }, query: { created }, } = useRoute();
|
|
16
16
|
const [searchText, setSearchText] = useState("");
|
|
17
|
-
const
|
|
17
|
+
const entityFromRoute = useEntityFromRoute();
|
|
18
|
+
const { declaration: entity } = entityFromRoute ?? {};
|
|
18
19
|
const [instances, reloadInstances] = useMappedAPIResource(getInstancesByEntityName, mapInstances, name ?? "");
|
|
19
20
|
useEffect(() => {
|
|
20
|
-
document.title = toTitleCase(entity?.
|
|
21
|
-
}, [entity?.
|
|
21
|
+
document.title = toTitleCase(entity?.namePlural ?? name ?? "") + " — TSONDB";
|
|
22
|
+
}, [entity?.namePlural, name]);
|
|
22
23
|
useEffect(() => {
|
|
23
24
|
if (created) {
|
|
24
25
|
const instanceElement = document.getElementById(`instance-${created}`);
|
|
@@ -31,22 +32,22 @@ export const Entity = () => {
|
|
|
31
32
|
return _jsx(NotFound, {});
|
|
32
33
|
}
|
|
33
34
|
if (!entity || !instances) {
|
|
34
|
-
return (_jsxs("div", { children: [_jsx("h1", { children: toTitleCase(entity?.
|
|
35
|
+
return (_jsxs("div", { children: [_jsx("h1", { children: toTitleCase(entity?.namePlural ?? name) }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
|
|
35
36
|
}
|
|
36
37
|
const lowerSearchText = searchText.toLowerCase();
|
|
37
38
|
const filteredInstances = searchText.length === 0
|
|
38
39
|
? instances
|
|
39
40
|
: instances.filter(instance => instance.id.includes(searchText) ||
|
|
40
41
|
instance.displayName.toLowerCase().includes(lowerSearchText));
|
|
41
|
-
return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: homeTitle }], children: [_jsxs("div", { class: "header-with-btns", children: [_jsx("h1", { children: toTitleCase(entity.
|
|
42
|
+
return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: homeTitle }], children: [_jsxs("div", { class: "header-with-btns", children: [_jsx("h1", { children: toTitleCase(entity.namePlural) }), _jsx("a", { class: "btn btn--primary", href: `/entities/${entity.name}/instances/create`, children: "Add" })] }), entity.comment && _jsx(Markdown, { class: "description", string: entity.comment }), _jsxs("div", { className: "list-header", children: [_jsxs("p", { class: "instance-count", children: [searchText === "" ? "" : `${filteredInstances.length.toString()} of `, instances.length, " instance", instances.length === 1 ? "" : "s"] }), _jsxs("form", { action: "", rel: "search", onSubmit: e => {
|
|
42
43
|
e.preventDefault();
|
|
43
44
|
}, children: [_jsx("label", { htmlFor: "instance-search", class: "visually-hidden", children: "Search" }), _jsx("input", { type: "text", id: "instance-search", value: searchText, onInput: event => {
|
|
44
45
|
setSearchText(event.currentTarget.value);
|
|
45
46
|
} })] })] }), _jsx("ul", { class: "entries entries--instances", children: filteredInstances.map(instance => {
|
|
46
47
|
const gitStatusForDisplay = getGitStatusForDisplay(instance.gitStatus);
|
|
47
|
-
return (_jsxs("li", { id: `instance-${instance.id}`, class: `entries-item ${created === instance.id ? "entries-item--created" : ""} ${gitStatusForDisplay === undefined ? "" : `git-status--${gitStatusForDisplay}`}`, children: [_jsx("h2", { class: "entries-item__title", children: instance.displayName }), _jsx("p", { "aria-hidden": true, class: "entries-item__subtitle entries-item__subtitle--id", children: instance.id }), _jsxs("div", { class: "entries-item__side", children: [gitStatusForDisplay !== undefined && (_jsx("p", { class: `git-status git-status--${gitStatusForDisplay}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay })), _jsxs("div", { class: "btns", children: [_jsx("a", { href: `/entities/${entity.
|
|
48
|
+
return (_jsxs("li", { id: `instance-${instance.id}`, class: `entries-item ${created === instance.id ? "entries-item--created" : ""} ${gitStatusForDisplay === undefined ? "" : `git-status--${gitStatusForDisplay}`}`, children: [_jsx("h2", { class: "entries-item__title", children: instance.displayName }), _jsx("p", { "aria-hidden": true, class: "entries-item__subtitle entries-item__subtitle--id", children: instance.id }), _jsxs("div", { class: "entries-item__side", children: [gitStatusForDisplay !== undefined && (_jsx("p", { class: `git-status git-status--${gitStatusForDisplay}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay })), _jsxs("div", { class: "btns", children: [_jsx("a", { href: `/entities/${entity.name}/instances/${instance.id}`, class: "btn", children: "Edit" }), _jsx("button", { class: "destructive", onClick: () => {
|
|
48
49
|
if (confirm("Are you sure you want to delete this instance?")) {
|
|
49
|
-
deleteInstanceByEntityNameAndId(entity.
|
|
50
|
+
deleteInstanceByEntityNameAndId(entity.name, instance.id)
|
|
50
51
|
.then(() => reloadInstances())
|
|
51
52
|
.catch((error) => {
|
|
52
53
|
if (error instanceof Error) {
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "preact/hooks";
|
|
2
|
+
import { useContext, useEffect, useState } from "preact/hooks";
|
|
3
3
|
import { toTitleCase } from "../../shared/utils/string.js";
|
|
4
|
-
import { getAllEntities } from "../api.js";
|
|
5
4
|
import { Layout } from "../components/Layout.js";
|
|
6
|
-
import {
|
|
5
|
+
import { EntitiesContext } from "../context/entities.js";
|
|
7
6
|
import { Markdown } from "../utils/Markdown.js";
|
|
8
|
-
const mapEntities = (data) => data.declarations.sort((a, b) => a.declaration.name.localeCompare(b.declaration.name));
|
|
9
7
|
export const homeTitle = "Entities";
|
|
10
8
|
export const Home = () => {
|
|
11
|
-
const
|
|
9
|
+
const entities = useContext(EntitiesContext);
|
|
12
10
|
useEffect(() => {
|
|
13
11
|
document.title = homeTitle + " — TSONDB";
|
|
14
12
|
}, []);
|
|
@@ -16,11 +14,11 @@ export const Home = () => {
|
|
|
16
14
|
const lowerSearchText = searchText.toLowerCase().replaceAll(" ", "");
|
|
17
15
|
const filteredEntities = searchText.length === 0
|
|
18
16
|
? entities
|
|
19
|
-
: entities
|
|
17
|
+
: entities.filter(entity => entity.declaration.name.toLowerCase().includes(lowerSearchText) ||
|
|
20
18
|
entity.declaration.namePlural.toLowerCase().includes(lowerSearchText));
|
|
21
|
-
return (_jsxs(Layout, { breadcrumbs: [], children: [_jsx("h1", { children: homeTitle }), _jsxs("div", { className: "list-header", children: [_jsxs("p", { class: "instance-count", children: [searchText === "" ? "" : `${
|
|
19
|
+
return (_jsxs(Layout, { breadcrumbs: [], children: [_jsx("h1", { children: homeTitle }), _jsxs("div", { className: "list-header", children: [_jsxs("p", { class: "instance-count", children: [searchText === "" ? "" : `${filteredEntities.length.toString()} of `, entities.length, " entit", entities.length === 1 ? "y" : "ies"] }), _jsxs("form", { action: "", rel: "search", onSubmit: e => {
|
|
22
20
|
e.preventDefault();
|
|
23
21
|
}, children: [_jsx("label", { htmlFor: "entity-search", class: "visually-hidden", children: "Search" }), _jsx("input", { type: "text", id: "entity-search", value: searchText, onInput: event => {
|
|
24
22
|
setSearchText(event.currentTarget.value);
|
|
25
|
-
} })] })] }), _jsx("ul", { class: "entries entries--entities", children:
|
|
23
|
+
} })] })] }), _jsx("ul", { class: "entries entries--entities", children: filteredEntities.map(entity => (_jsxs("li", { class: "entries-item", children: [_jsxs("div", { class: "entries-item__title", children: [_jsx("h2", { children: toTitleCase(entity.declaration.namePlural) }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment }))] }), _jsxs("p", { class: "entries-item__subtitle", children: [entity.instanceCount, " instance", entity.instanceCount === 1 ? "" : "s"] }), _jsx("div", { class: "entries-item__side", children: _jsx("div", { class: "btns", children: _jsx("a", { href: `/entities/${entity.declaration.name}`, class: "btn", children: "View" }) }) })] }, entity.declaration.name))) })] }));
|
|
26
24
|
};
|
|
@@ -15,21 +15,22 @@ export const Instance = () => {
|
|
|
15
15
|
const { params: { name, id }, } = useRoute();
|
|
16
16
|
const [getDeclFromDeclName, declsLoaded] = useGetDeclFromDeclName();
|
|
17
17
|
const entityFromRoute = useEntityFromRoute();
|
|
18
|
+
const { declaration: entity } = entityFromRoute ?? {};
|
|
18
19
|
const [instanceNamesByEntity] = useInstanceNamesByEntity();
|
|
19
20
|
const [instance, setInstance] = useState();
|
|
20
21
|
const [originalInstance, setOriginalInstance] = useState();
|
|
21
22
|
const { route } = useLocation();
|
|
22
23
|
useEffect(() => {
|
|
23
|
-
if (
|
|
24
|
+
if (entity && instance?.content && id) {
|
|
24
25
|
const defaultName = id;
|
|
25
|
-
const instanceName = getSerializedDisplayNameFromEntityInstance(
|
|
26
|
-
const entityName =
|
|
26
|
+
const instanceName = getSerializedDisplayNameFromEntityInstance(entity, instance.content, defaultName);
|
|
27
|
+
const entityName = entity.name;
|
|
27
28
|
document.title = instanceName + " — " + toTitleCase(entityName) + " — TSONDB";
|
|
28
29
|
}
|
|
29
30
|
else {
|
|
30
31
|
document.title = "Not found — TSONDB";
|
|
31
32
|
}
|
|
32
|
-
}, [
|
|
33
|
+
}, [entity, id, instance?.content]);
|
|
33
34
|
useEffect(() => {
|
|
34
35
|
if (name && id) {
|
|
35
36
|
getInstanceByEntityNameAndId(name, id)
|
|
@@ -63,24 +64,23 @@ export const Instance = () => {
|
|
|
63
64
|
if (!name || !id) {
|
|
64
65
|
return _jsx(NotFound, {});
|
|
65
66
|
}
|
|
66
|
-
if (!
|
|
67
|
-
!instance ||
|
|
68
|
-
!originalInstance ||
|
|
69
|
-
!instanceNamesByEntity ||
|
|
70
|
-
!declsLoaded) {
|
|
67
|
+
if (!entity || !instance || !originalInstance || !instanceNamesByEntity || !declsLoaded) {
|
|
71
68
|
return (_jsxs(Layout, { breadcrumbs: [
|
|
72
69
|
{ url: "/", label: homeTitle },
|
|
73
|
-
{
|
|
70
|
+
{
|
|
71
|
+
url: `/entities/${name}`,
|
|
72
|
+
label: entity ? toTitleCase(entity.namePlural) : name,
|
|
73
|
+
},
|
|
74
74
|
], children: [_jsx("h1", { children: id }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
|
|
75
75
|
}
|
|
76
76
|
const defaultName = id;
|
|
77
|
-
const instanceName = getSerializedDisplayNameFromEntityInstance(
|
|
77
|
+
const instanceName = getSerializedDisplayNameFromEntityInstance(entity, instance.content, defaultName);
|
|
78
78
|
return (_jsxs(Layout, { breadcrumbs: [
|
|
79
79
|
{ url: "/", label: homeTitle },
|
|
80
|
-
{ url: `/entities/${name}`, label:
|
|
80
|
+
{ url: `/entities/${name}`, label: toTitleCase(entity.namePlural) },
|
|
81
81
|
], children: [_jsxs("div", { class: "header-with-btns", children: [_jsxs("h1", { class: instanceName.length === 0 ? "empty-name" : undefined, children: [_jsx("span", { children: instanceName || defaultName }), " ", _jsx("span", { className: "id", "aria-hidden": true, children: instance.id })] }), _jsx("button", { class: "destructive", onClick: () => {
|
|
82
82
|
if (confirm("Are you sure you want to delete this instance?")) {
|
|
83
|
-
deleteInstanceByEntityNameAndId(
|
|
83
|
+
deleteInstanceByEntityNameAndId(entity.name, instance.id)
|
|
84
84
|
.then(() => {
|
|
85
85
|
route(`/entities/${name}`);
|
|
86
86
|
})
|
|
@@ -90,5 +90,5 @@ export const Instance = () => {
|
|
|
90
90
|
}
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
-
}, children: "Delete" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type:
|
|
93
|
+
}, children: "Delete" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entity.type, value: instance.content, path: undefined, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: handleOnChange }), _jsx("div", { class: "form-footer btns", children: _jsx("button", { type: "submit", name: "save", class: "primary", children: "Save" }) })] })] }));
|
|
94
94
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
+
import { InlineMarkdown } from "./InlineMarkdown.js";
|
|
3
|
+
export const BlockMarkdown = ({ node }) => {
|
|
4
|
+
switch (node.kind) {
|
|
5
|
+
case "paragraph":
|
|
6
|
+
return (_jsx("p", { children: node.content.map((inline, ii) => (_jsx(InlineMarkdown, { node: inline }, ii))) }));
|
|
7
|
+
case "list":
|
|
8
|
+
if (node.ordered) {
|
|
9
|
+
return (_jsx("ol", { children: node.content.map((item, ii) => (_jsx("li", { children: item.content.map((inline, iii) => (_jsx(InlineMarkdown, { node: inline }, iii))) }, ii))) }));
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
return (_jsx("ul", { children: node.content.map((item, ii) => (_jsx("li", { children: item.content.map((inline, iii) => (_jsx(InlineMarkdown, { node: inline }, iii))) }, ii))) }));
|
|
13
|
+
}
|
|
14
|
+
default:
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FunctionalComponent } from "preact";
|
|
2
|
+
import type { BlockSyntaxMarkdownNode } from "../../shared/utils/markdown.ts";
|
|
3
|
+
type Props = {
|
|
4
|
+
node: BlockSyntaxMarkdownNode;
|
|
5
|
+
};
|
|
6
|
+
export declare const BlockMarkdownHighlighting: FunctionalComponent<Props>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
+
import { InlineMarkdown } from "./InlineMarkdown.js";
|
|
3
|
+
export const BlockMarkdownHighlighting = ({ node }) => {
|
|
4
|
+
switch (node.kind) {
|
|
5
|
+
case "listitemmarker":
|
|
6
|
+
return _jsx("span", { class: "list-item-marker", children: node.content });
|
|
7
|
+
default:
|
|
8
|
+
return _jsx(InlineMarkdown, { node: node });
|
|
9
|
+
}
|
|
10
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "preact/jsx-runtime";
|
|
2
|
+
export const InlineMarkdown = ({ node }) => {
|
|
3
|
+
switch (node.kind) {
|
|
4
|
+
case "code":
|
|
5
|
+
return _jsx("code", { children: node.content });
|
|
6
|
+
case "bold":
|
|
7
|
+
return (_jsx("strong", { children: node.content.map((inline, i) => (_jsx(InlineMarkdown, { node: inline }, i))) }));
|
|
8
|
+
case "italic":
|
|
9
|
+
return (_jsx("em", { children: node.content.map((inline, i) => (_jsx(InlineMarkdown, { node: inline }, i))) }));
|
|
10
|
+
case "text":
|
|
11
|
+
return node.content;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
2
|
import { parseBlockMarkdown } from "../../shared/utils/markdown.js";
|
|
3
|
+
import { BlockMarkdown } from "./BlockMarkdown.js";
|
|
3
4
|
export const Markdown = ({ class: className, string }) => {
|
|
4
5
|
const blocks = parseBlockMarkdown(string);
|
|
5
6
|
const blockElements = blocks.map((block, i) => (_jsx(BlockMarkdown, { node: block }, `md-block-${i.toString()}`)));
|
|
@@ -8,32 +9,3 @@ export const Markdown = ({ class: className, string }) => {
|
|
|
8
9
|
}
|
|
9
10
|
return _jsx(_Fragment, { children: blockElements });
|
|
10
11
|
};
|
|
11
|
-
const BlockMarkdown = ({ node }) => {
|
|
12
|
-
switch (node.kind) {
|
|
13
|
-
case "paragraph":
|
|
14
|
-
return (_jsx("p", { children: node.content.map((inline, ii) => (_jsx(InlineMarkdown, { node: inline }, ii))) }));
|
|
15
|
-
case "list":
|
|
16
|
-
if (node.ordered) {
|
|
17
|
-
return (_jsx("ol", { children: node.content.map((item, ii) => (_jsx("li", { children: item.content.map((inline, iii) => (_jsx(InlineMarkdown, { node: inline }, iii))) }, ii))) }));
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
return (_jsx("ul", { children: node.content.map((item, ii) => (_jsx("li", { children: item.content.map((inline, iii) => (_jsx(InlineMarkdown, { node: inline }, iii))) }, ii))) }));
|
|
21
|
-
}
|
|
22
|
-
case "text":
|
|
23
|
-
return node.content;
|
|
24
|
-
default:
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
const InlineMarkdown = ({ node }) => {
|
|
29
|
-
switch (node.kind) {
|
|
30
|
-
case "code":
|
|
31
|
-
return _jsx("code", { children: node.content });
|
|
32
|
-
case "bold":
|
|
33
|
-
return (_jsx("strong", { children: node.content.map((inline, i) => (_jsx(InlineMarkdown, { node: inline }, i))) }));
|
|
34
|
-
case "italic":
|
|
35
|
-
return (_jsx("em", { children: node.content.map((inline, i) => (_jsx(InlineMarkdown, { node: inline }, i))) }));
|
|
36
|
-
case "text":
|
|
37
|
-
return node.content;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
+
import { parseBlockMarkdownForSyntaxHighlighting } from "../../shared/utils/markdown.js";
|
|
3
|
+
import { BlockMarkdownHighlighting } from "./BlockMarkdownHighlighting.js";
|
|
4
|
+
export const MarkdownHighlighting = ({ class: className, string }) => {
|
|
5
|
+
const blocks = parseBlockMarkdownForSyntaxHighlighting(string);
|
|
6
|
+
const blockElements = blocks.map((block, i) => (_jsx(BlockMarkdownHighlighting, { node: block }, `md-block-${i.toString()}`)));
|
|
7
|
+
if (className) {
|
|
8
|
+
return _jsx("div", { class: className, children: blockElements });
|
|
9
|
+
}
|
|
10
|
+
return _jsx(_Fragment, { children: blockElements });
|
|
11
|
+
};
|
package/package.json
CHANGED
package/public/css/styles.css
CHANGED
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
--git-status-renamed-color: rgb(20, 97, 148);
|
|
49
49
|
--git-status-renamed-background: rgba(20, 97, 148, 0.15);
|
|
50
50
|
--shadow-color: rgba(160, 163, 165, 0.5);
|
|
51
|
+
--highlight-color-bold: rgb(205, 123, 43);
|
|
52
|
+
--highlight-color-italic: rgb(41, 155, 96);
|
|
53
|
+
--highlight-color-list-item-marker: rgb(195, 58, 237);
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
@media (prefers-color-scheme: dark) {
|
|
@@ -76,6 +79,9 @@
|
|
|
76
79
|
--button-color-disabled: hsl(260, 6%, 54%);
|
|
77
80
|
--highlight-color: #f2d600;
|
|
78
81
|
--highlight-background: #333300;
|
|
82
|
+
--highlight-color-bold: rgb(236, 170, 105);
|
|
83
|
+
--highlight-color-italic: rgb(130, 230, 178);
|
|
84
|
+
--highlight-color-list-item-marker: rgb(220, 133, 245);
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -236,7 +242,7 @@ button.destructive:disabled {
|
|
|
236
242
|
input,
|
|
237
243
|
textarea,
|
|
238
244
|
select,
|
|
239
|
-
.textarea-grow-
|
|
245
|
+
.textarea-grow-wrap__mirror {
|
|
240
246
|
background: var(--background);
|
|
241
247
|
color: var(--color);
|
|
242
248
|
font-family: var(--font-sans);
|
|
@@ -262,10 +268,9 @@ select[aria-invalid="true"] {
|
|
|
262
268
|
display: grid;
|
|
263
269
|
}
|
|
264
270
|
|
|
265
|
-
.textarea-grow-wrap
|
|
266
|
-
content: attr(data-value) " ";
|
|
271
|
+
.textarea-grow-wrap .textarea-grow-wrap__mirror {
|
|
267
272
|
white-space: pre-wrap;
|
|
268
|
-
|
|
273
|
+
z-index: -1;
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
.textarea-grow-wrap > textarea {
|
|
@@ -274,7 +279,7 @@ select[aria-invalid="true"] {
|
|
|
274
279
|
}
|
|
275
280
|
|
|
276
281
|
.textarea-grow-wrap > textarea,
|
|
277
|
-
.textarea-grow-
|
|
282
|
+
.textarea-grow-wrap__mirror {
|
|
278
283
|
grid-area: 1 / 1 / 2 / 2;
|
|
279
284
|
}
|
|
280
285
|
|
|
@@ -555,6 +560,36 @@ form > .field--container {
|
|
|
555
560
|
flex: 1 1 0;
|
|
556
561
|
}
|
|
557
562
|
|
|
563
|
+
.editor--markdown textarea,
|
|
564
|
+
.editor--markdown input,
|
|
565
|
+
.editor--markdown .textarea-grow-wrap__mirror {
|
|
566
|
+
font-family: var(--font-mono);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.editor--markdown textarea {
|
|
570
|
+
color: transparent;
|
|
571
|
+
background: transparent;
|
|
572
|
+
caret-color: var(--color);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.editor-highlighting strong {
|
|
576
|
+
color: var(--highlight-color-bold);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.editor-highlighting em {
|
|
580
|
+
color: var(--highlight-color-italic);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.editor-highlighting .list-item-marker {
|
|
584
|
+
color: var(--highlight-color-list-item-marker);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.help {
|
|
588
|
+
font-size: 0.8rem;
|
|
589
|
+
color: var(--secondary-color);
|
|
590
|
+
margin: 0.25rem 0 0;
|
|
591
|
+
}
|
|
592
|
+
|
|
558
593
|
.field--string .preview {
|
|
559
594
|
padding: 1rem;
|
|
560
595
|
background: var(--markdown-background);
|