tsondb 0.5.15 → 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.
Files changed (30) hide show
  1. package/dist/src/node/index.js +3 -1
  2. package/dist/src/node/server/init.js +6 -0
  3. package/dist/src/node/utils/instances.js +2 -2
  4. package/dist/src/node/utils/references.d.ts +3 -0
  5. package/dist/src/node/utils/references.js +17 -10
  6. package/dist/src/shared/utils/markdown.d.ts +6 -1
  7. package/dist/src/shared/utils/markdown.js +96 -15
  8. package/dist/src/web/components/Git.js +3 -1
  9. package/dist/src/web/components/Layout.js +1 -2
  10. package/dist/src/web/components/typeInputs/StringTypeInput.js +4 -3
  11. package/dist/src/web/context/entities.d.ts +6 -0
  12. package/dist/src/web/context/entities.js +2 -0
  13. package/dist/src/web/hooks/useEntityFromRoute.d.ts +1 -1
  14. package/dist/src/web/hooks/useEntityFromRoute.js +4 -15
  15. package/dist/src/web/index.js +19 -2
  16. package/dist/src/web/routes/CreateInstance.js +5 -5
  17. package/dist/src/web/routes/Entity.js +10 -9
  18. package/dist/src/web/routes/Home.js +6 -8
  19. package/dist/src/web/routes/Instance.js +14 -14
  20. package/dist/src/web/utils/BlockMarkdown.d.ts +7 -0
  21. package/dist/src/web/utils/BlockMarkdown.js +17 -0
  22. package/dist/src/web/utils/BlockMarkdownHighlighting.d.ts +7 -0
  23. package/dist/src/web/utils/BlockMarkdownHighlighting.js +10 -0
  24. package/dist/src/web/utils/InlineMarkdown.d.ts +7 -0
  25. package/dist/src/web/utils/InlineMarkdown.js +13 -0
  26. package/dist/src/web/utils/Markdown.js +1 -29
  27. package/dist/src/web/utils/MarkdownHighlighting.d.ts +7 -0
  28. package/dist/src/web/utils/MarkdownHighlighting.js +11 -0
  29. package/package.json +1 -1
  30. package/public/css/styles.css +40 -5
@@ -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:schema");
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) => {
@@ -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) => Object.entries(instancesByEntityName).reduce((acc, [entityName, instances]) => {
16
- const entity = entitiesByName[entityName];
17
- if (entity) {
18
- return instances.reduce((acc1, instance) => {
19
- const references = getReferencesForEntityDecl(entity, instance.content);
20
- return addReferences(acc1, references, instance.id);
21
- }, acc);
22
- }
23
- return acc;
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
- } | TextNode;
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 => ({ kind: "code", content: result[1] ?? "" }),
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) => ({ kind: "bold", content: parseInside(result[1] ?? "") }),
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) => ({ kind: "italic", content: parseInside(result[1] ?? "") }),
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) => ({ kind: "bold", content: parseInside(result[1] ?? "") }),
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) => ({ kind: "italic", content: parseInside(result[1] ?? "") }),
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 ? parseForInlineRules(rules.slice(1), before) : []),
43
- activeRule.map(res, text => parseForInlineRules(rules.slice(1), text)),
44
- ...(after.length > 0 ? parseForInlineRules(rules, after) : []),
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]+?)*)(?:\n{2,}|$)/,
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]+?)*)(?:\n{2,}|\s*$)/,
65
- map: result => ({ kind: "paragraph", content: parseInlineMarkdown(result[1] ?? "") }),
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(Git, {}), _jsx("main", { children: children })] }));
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", { className: "editor", children: [_jsx("div", { class: "textarea-grow-wrap", "data-value": value, children: _jsx("textarea", { value: value, minLength: minLength, maxLength: maxLength, onInput: event => {
13
- onChange(event.currentTarget.value);
14
- }, "aria-invalid": errors.length > 0 }) }), _jsx(ValidationErrors, { errors: errors })] }), _jsx("div", { className: "preview", children: _jsx(Markdown, { string: value }) })] })) : (_jsxs("div", { className: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern === undefined
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)
@@ -0,0 +1,6 @@
1
+ import type { SerializedEntityDecl } from "../../node/schema/index.ts";
2
+ export declare const EntitiesContext: import("preact").Context<{
3
+ declaration: SerializedEntityDecl;
4
+ instanceCount: number;
5
+ isLocaleEntity: boolean;
6
+ }[]>;
@@ -0,0 +1,2 @@
1
+ import { createContext } from "preact";
2
+ export const EntitiesContext = createContext([]);
@@ -1,5 +1,5 @@
1
1
  import type { SerializedEntityDecl } from "../../node/schema/declarations/EntityDecl.ts";
2
2
  export declare const useEntityFromRoute: () => {
3
- entity: SerializedEntityDecl;
3
+ declaration: SerializedEntityDecl;
4
4
  isLocaleEntity: boolean;
5
5
  } | undefined;
@@ -1,20 +1,9 @@
1
1
  import { useRoute } from "preact-iso";
2
- import { useEffect, useMemo, useState } from "preact/hooks";
3
- import { getEntityByName } from "../api.js";
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 [entityData, setEntityData] = useState();
7
- useEffect(() => {
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
  };
@@ -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 App = () => (_jsx(LocationProvider, { 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 })] }) }));
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.entity.type));
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?.entity.name ?? name;
31
+ const entityName = entityFromRoute?.declaration.name ?? name;
32
32
  document.title =
33
33
  entityName === undefined ? "Not found" : "New " + toTitleCase(entityName) + " — TSONDB";
34
- }, [entityFromRoute?.entity.name, name]);
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.name },
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, getEntityByName, getInstancesByEntityName, } from "../api.js";
6
+ import { deleteInstanceByEntityNameAndId, getInstancesByEntityName } from "../api.js";
7
7
  import { Layout } from "../components/Layout.js";
8
- import { useAPIResource } from "../hooks/useAPIResource.js";
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 [entity] = useAPIResource(getEntityByName, name ?? "");
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?.declaration.namePlural ?? name ?? "") + " — TSONDB";
21
- }, [entity?.declaration.namePlural, name]);
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?.declaration.namePlural ?? name) }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
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.declaration.namePlural) }), _jsx("a", { class: "btn btn--primary", href: `/entities/${entity.declaration.name}/instances/create`, children: "Add" })] }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.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
+ 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.declaration.name}/instances/${instance.id}`, class: "btn", children: "Edit" }), _jsx("button", { class: "destructive", onClick: () => {
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.declaration.name, instance.id)
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 { useMappedAPIResource } from "../hooks/useMappedAPIResource.js";
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 [entities] = useMappedAPIResource(getAllEntities, mapEntities);
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?.filter(entity => entity.declaration.name.toLowerCase().includes(lowerSearchText) ||
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 === "" ? "" : `${(filteredEntities?.length ?? 0).toString()} of `, entities?.length ?? 0, " entit", entities?.length === 1 ? "y" : "ies"] }), _jsxs("form", { action: "", rel: "search", onSubmit: e => {
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: (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))) })] }));
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 (entityFromRoute?.entity && instance?.content && id) {
24
+ if (entity && instance?.content && id) {
24
25
  const defaultName = id;
25
- const instanceName = getSerializedDisplayNameFromEntityInstance(entityFromRoute.entity, instance.content, defaultName);
26
- const entityName = entityFromRoute.entity.name;
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
- }, [entityFromRoute?.entity, id, instance?.content]);
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 (!entityFromRoute ||
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
- { url: `/entities/${name}`, label: name },
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(entityFromRoute.entity, instance.content, defaultName);
77
+ const instanceName = getSerializedDisplayNameFromEntityInstance(entity, instance.content, defaultName);
78
78
  return (_jsxs(Layout, { breadcrumbs: [
79
79
  { url: "/", label: homeTitle },
80
- { url: `/entities/${name}`, label: entityFromRoute.entity.name },
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(entityFromRoute.entity.name, instance.id)
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: entityFromRoute.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" }) })] })] }));
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,7 @@
1
+ import type { FunctionalComponent } from "preact";
2
+ import type { BlockMarkdownNode } from "../../shared/utils/markdown.ts";
3
+ type Props = {
4
+ node: BlockMarkdownNode;
5
+ };
6
+ export declare const BlockMarkdown: FunctionalComponent<Props>;
7
+ export {};
@@ -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,7 @@
1
+ import type { FunctionalComponent } from "preact";
2
+ import type { InlineMarkdownNode } from "../../shared/utils/markdown.ts";
3
+ type Props = {
4
+ node: InlineMarkdownNode;
5
+ };
6
+ export declare const InlineMarkdown: FunctionalComponent<Props>;
7
+ export {};
@@ -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,7 @@
1
+ import type { FunctionalComponent } from "preact";
2
+ type Props = {
3
+ class?: string;
4
+ string: string;
5
+ };
6
+ export declare const MarkdownHighlighting: FunctionalComponent<Props>;
7
+ export {};
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsondb",
3
- "version": "0.5.15",
3
+ "version": "0.5.16",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Lukas Obermann",
@@ -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-wrap::after {
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::after {
266
- content: attr(data-value) " ";
271
+ .textarea-grow-wrap .textarea-grow-wrap__mirror {
267
272
  white-space: pre-wrap;
268
- visibility: hidden;
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-wrap::after {
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);