tsondb 0.5.15 → 0.5.17
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/declarations/Declaration.d.ts +1 -0
- package/dist/src/node/schema/types/references/IncludeIdentifierType.d.ts +3 -2
- package/dist/src/node/schema/types/references/IncludeIdentifierType.js +3 -2
- package/dist/src/node/schema/types/references/NestedEntityMapType.d.ts +7 -4
- package/dist/src/node/schema/types/references/NestedEntityMapType.js +20 -10
- 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) => {
|
|
@@ -17,6 +17,7 @@ export declare const getTypeArgumentsRecord: <Params extends TypeParameter[]>(de
|
|
|
17
17
|
export type Decl = EntityDecl | EnumDecl | TypeAliasDecl;
|
|
18
18
|
export type SerializedDecl = SerializedEntityDecl | SerializedEnumDecl | SerializedTypeAliasDecl;
|
|
19
19
|
export type DeclP<Params extends TypeParameter[] = TypeParameter[]> = EntityDecl | EnumDecl<string, Record<string, EnumCaseDecl>, Params> | TypeAliasDecl<string, Type, Params>;
|
|
20
|
+
export type IncludableDeclP<Params extends TypeParameter[] = TypeParameter[]> = EnumDecl<string, Record<string, EnumCaseDecl>, Params> | TypeAliasDecl<string, Type, Params>;
|
|
20
21
|
export type SerializedDeclP<Params extends SerializedTypeParameter[] = SerializedTypeParameter[]> = SerializedEntityDecl | SerializedEnumDecl<string, Record<string, SerializedEnumCaseDecl>, Params> | SerializedTypeAliasDecl<string, SerializedType, Params>;
|
|
21
22
|
export type SecondaryDecl = EnumDecl | TypeAliasDecl;
|
|
22
23
|
export type SerializedSecondaryDecl = SerializedEnumDecl | SerializedTypeAliasDecl;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GetNestedDeclarations, SerializedTypeArguments, TypeArguments } from "../../declarations/Declaration.ts";
|
|
1
|
+
import type { GetNestedDeclarations, IncludableDeclP, SerializedTypeArguments, TypeArguments } from "../../declarations/Declaration.ts";
|
|
2
2
|
import type { EnumDecl } from "../../declarations/EnumDecl.ts";
|
|
3
3
|
import type { TypeAliasDecl } from "../../declarations/TypeAliasDecl.ts";
|
|
4
4
|
import type { GetReferences, Node, Serializer } from "../../Node.ts";
|
|
@@ -23,9 +23,10 @@ export { GenIncludeIdentifierType as GenIncludeIdentifier };
|
|
|
23
23
|
export declare const IncludeIdentifierType: <T extends TConstraint<[]>>(reference: T) => IncludeIdentifierType<[], T>;
|
|
24
24
|
export { IncludeIdentifierType as IncludeIdentifier };
|
|
25
25
|
export declare const isIncludeIdentifierType: (node: Node) => node is IncludeIdentifierType;
|
|
26
|
+
export declare const isNoGenericIncludeIdentifierType: (node: IncludeIdentifierType) => node is IncludeIdentifierType<[], IncludableDeclP<[]>>;
|
|
26
27
|
export declare const getNestedDeclarationsInIncludeIdentifierType: GetNestedDeclarations<IncludeIdentifierType>;
|
|
27
28
|
export declare const validateIncludeIdentifierType: Validator<IncludeIdentifierType>;
|
|
28
|
-
export declare const resolveTypeArgumentsInIncludeIdentifierType: (args: Record<string, Type>, type:
|
|
29
|
+
export declare const resolveTypeArgumentsInIncludeIdentifierType: <T extends IncludeIdentifierType>(args: Record<string, Type>, type: T) => T extends IncludeIdentifierType<[], IncludableDeclP<[]>> ? T : Type;
|
|
29
30
|
export declare const serializeIncludeIdentifierType: Serializer<IncludeIdentifierType, SerializedIncludeIdentifierType>;
|
|
30
31
|
export declare const getReferencesForIncludeIdentifierType: GetReferences<IncludeIdentifierType>;
|
|
31
32
|
export declare const formatIncludeIdentifierValue: StructureFormatter<IncludeIdentifierType>;
|
|
@@ -15,13 +15,14 @@ export const IncludeIdentifierType = (reference) => ({
|
|
|
15
15
|
});
|
|
16
16
|
export { IncludeIdentifierType as IncludeIdentifier };
|
|
17
17
|
export const isIncludeIdentifierType = (node) => node.kind === NodeKind.IncludeIdentifierType;
|
|
18
|
+
export const isNoGenericIncludeIdentifierType = (node) => node.args.length === 0 && node.reference.parameters.length === 0;
|
|
18
19
|
export const getNestedDeclarationsInIncludeIdentifierType = (addedDecls, type) => type.args.reduce((accAddedDecls, arg) => getNestedDeclarations(accAddedDecls, arg), addedDecls.includes(type.reference)
|
|
19
20
|
? addedDecls
|
|
20
21
|
: getNestedDeclarations([type.reference, ...addedDecls], type.reference));
|
|
21
22
|
export const validateIncludeIdentifierType = (helpers, type, value) => validateDecl(helpers, type.reference, type.args, value);
|
|
22
|
-
export const resolveTypeArgumentsInIncludeIdentifierType = (args, type) => type
|
|
23
|
+
export const resolveTypeArgumentsInIncludeIdentifierType = (args, type) => (isNoGenericIncludeIdentifierType(type)
|
|
23
24
|
? type
|
|
24
|
-
: resolveTypeArgumentsInDecl(type.reference, type.args.map(arg => resolveTypeArgumentsInType(args, arg))).type.value;
|
|
25
|
+
: resolveTypeArgumentsInDecl(type.reference, type.args.map(arg => resolveTypeArgumentsInType(args, arg))).type.value);
|
|
25
26
|
export const serializeIncludeIdentifierType = type => ({
|
|
26
27
|
...removeParentKey(type),
|
|
27
28
|
reference: type.reference.name,
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { Lazy } from "../../../../shared/utils/lazy.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type GetNestedDeclarations } from "../../declarations/Declaration.ts";
|
|
3
3
|
import type { EntityDecl } from "../../declarations/EntityDecl.ts";
|
|
4
|
+
import type { TypeAliasDecl } from "../../declarations/TypeAliasDecl.ts";
|
|
4
5
|
import type { GetReferences, Node, Serializer } from "../../Node.ts";
|
|
5
6
|
import { NodeKind } from "../../Node.ts";
|
|
6
7
|
import type { Validator } from "../../validation/type.ts";
|
|
7
8
|
import type { MemberDecl, ObjectType, SerializedMemberDecl, SerializedObjectType } from "../generic/ObjectType.ts";
|
|
8
9
|
import type { BaseType, SerializedBaseType, StructureFormatter, Type } from "../Type.ts";
|
|
10
|
+
import { type IncludeIdentifier, type SerializedIncludeIdentifierType } from "./IncludeIdentifierType.ts";
|
|
9
11
|
type TConstraint = Record<string, MemberDecl>;
|
|
12
|
+
type PossibleType<T extends TConstraint> = ObjectType<T> | IncludeIdentifier<[], TypeAliasDecl<string, ObjectType<T>, []>>;
|
|
10
13
|
export interface NestedEntityMapType<Name extends string = string, T extends TConstraint = TConstraint> extends BaseType {
|
|
11
14
|
kind: NodeKind["NestedEntityMapType"];
|
|
12
15
|
name: Name;
|
|
13
16
|
comment?: string;
|
|
14
17
|
secondaryEntity: EntityDecl;
|
|
15
|
-
type: Lazy<
|
|
18
|
+
type: Lazy<PossibleType<T>>;
|
|
16
19
|
}
|
|
17
20
|
type TSerializedConstraint = Record<string, SerializedMemberDecl>;
|
|
18
21
|
export interface SerializedNestedEntityMapType<Name extends string = string, T extends TSerializedConstraint = TSerializedConstraint> extends SerializedBaseType {
|
|
@@ -20,13 +23,13 @@ export interface SerializedNestedEntityMapType<Name extends string = string, T e
|
|
|
20
23
|
name: Name;
|
|
21
24
|
comment?: string;
|
|
22
25
|
secondaryEntity: string;
|
|
23
|
-
type: SerializedObjectType<T
|
|
26
|
+
type: SerializedObjectType<T> | SerializedIncludeIdentifierType;
|
|
24
27
|
}
|
|
25
28
|
export declare const NestedEntityMapType: <Name extends string, T extends TConstraint>(options: {
|
|
26
29
|
name: Name;
|
|
27
30
|
comment?: string;
|
|
28
31
|
secondaryEntity: EntityDecl;
|
|
29
|
-
type:
|
|
32
|
+
type: PossibleType<T>;
|
|
30
33
|
}) => NestedEntityMapType<Name, T>;
|
|
31
34
|
export { NestedEntityMapType as NestedEntityMap };
|
|
32
35
|
export declare const isNestedEntityMapType: (node: Node) => node is NestedEntityMapType;
|
|
@@ -3,9 +3,11 @@ import { sortObjectKeysAlphabetically } from "../../../../shared/utils/object.js
|
|
|
3
3
|
import { parallelizeErrors } from "../../../../shared/utils/validation.js";
|
|
4
4
|
import { wrapErrorsIfAny } from "../../../utils/error.js";
|
|
5
5
|
import { entity, json, key as keyColor } from "../../../utils/errorFormatting.js";
|
|
6
|
+
import { getNestedDeclarations, } from "../../declarations/Declaration.js";
|
|
6
7
|
import { NodeKind } from "../../Node.js";
|
|
7
|
-
import {
|
|
8
|
-
import { formatValue, removeParentKey, setParent } from "../Type.js";
|
|
8
|
+
import { getReferencesForObjectType, isObjectType, resolveTypeArgumentsInObjectType, serializeObjectType, } from "../generic/ObjectType.js";
|
|
9
|
+
import { formatValue, removeParentKey, setParent, validate } from "../Type.js";
|
|
10
|
+
import { formatIncludeIdentifierValue, getReferencesForIncludeIdentifierType, resolveTypeArgumentsInIncludeIdentifierType, serializeIncludeIdentifierType, } from "./IncludeIdentifierType.js";
|
|
9
11
|
export const NestedEntityMapType = (options) => {
|
|
10
12
|
const nestedEntityMapType = {
|
|
11
13
|
...options,
|
|
@@ -24,30 +26,38 @@ const _NestedEntityMapType = (options) => {
|
|
|
24
26
|
return nestedEntityMapType;
|
|
25
27
|
};
|
|
26
28
|
export const isNestedEntityMapType = (node) => node.kind === NodeKind.NestedEntityMapType;
|
|
27
|
-
export const getNestedDeclarationsInNestedEntityMapType = (addedDecls, type) =>
|
|
29
|
+
export const getNestedDeclarationsInNestedEntityMapType = (addedDecls, type) => getNestedDeclarations(addedDecls.includes(type.secondaryEntity) ? addedDecls : [type.secondaryEntity, ...addedDecls], type.type.value);
|
|
28
30
|
export const validateNestedEntityMapType = (helpers, type, value) => {
|
|
29
31
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
30
32
|
return [TypeError(`expected an object, but got ${json(value, helpers.useStyling)}`)];
|
|
31
33
|
}
|
|
32
|
-
return parallelizeErrors(Object.keys(value).map(key => wrapErrorsIfAny(`at nested entity map ${entity(`"${type.name}"`, helpers.useStyling)} at key ${keyColor(`"${key}"`, helpers.useStyling)}`,
|
|
34
|
+
return parallelizeErrors(Object.keys(value).map(key => wrapErrorsIfAny(`at nested entity map ${entity(`"${type.name}"`, helpers.useStyling)} at key ${keyColor(`"${key}"`, helpers.useStyling)}`, validate(helpers, type.type.value, value[key]).concat(helpers.checkReferentialIntegrity({
|
|
33
35
|
name: type.secondaryEntity.name,
|
|
34
36
|
value: key,
|
|
35
37
|
})))));
|
|
36
38
|
};
|
|
37
39
|
export const resolveTypeArgumentsInNestedEntityMapType = (args, type) => _NestedEntityMapType({
|
|
38
40
|
...type,
|
|
39
|
-
type: () =>
|
|
41
|
+
type: () => isObjectType(type.type.value)
|
|
42
|
+
? resolveTypeArgumentsInObjectType(args, type.type.value)
|
|
43
|
+
: resolveTypeArgumentsInIncludeIdentifierType(args, type.type.value),
|
|
40
44
|
});
|
|
41
45
|
export const serializeNestedEntityMapType = type => ({
|
|
42
46
|
...removeParentKey(type),
|
|
43
47
|
secondaryEntity: type.secondaryEntity.name,
|
|
44
|
-
type:
|
|
48
|
+
type: isObjectType(type.type.value)
|
|
49
|
+
? serializeObjectType(type.type.value)
|
|
50
|
+
: serializeIncludeIdentifierType(type.type.value),
|
|
45
51
|
});
|
|
46
52
|
export const getReferencesForNestedEntityMapType = (type, value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
47
53
|
? Object.values(value)
|
|
48
|
-
.flatMap(item =>
|
|
54
|
+
.flatMap(item => isObjectType(type.type.value)
|
|
55
|
+
? getReferencesForObjectType(type.type.value, item)
|
|
56
|
+
: getReferencesForIncludeIdentifierType(type.type.value, item))
|
|
49
57
|
.concat(Object.keys(value))
|
|
50
58
|
: [];
|
|
51
|
-
export const formatNestedEntityMapValue = (type, value) =>
|
|
52
|
-
?
|
|
53
|
-
|
|
59
|
+
export const formatNestedEntityMapValue = (type, value) => isObjectType(type.type.value)
|
|
60
|
+
? typeof value === "object" && value !== null && !Array.isArray(value)
|
|
61
|
+
? sortObjectKeysAlphabetically(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, formatValue(type.type.value, item)])))
|
|
62
|
+
: value
|
|
63
|
+
: formatIncludeIdentifierValue(type.type.value, value);
|
|
@@ -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);
|