tsondb 0.20.5 → 0.20.6

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.
@@ -120,7 +120,7 @@ else {
120
120
  debug(`running command: serve`);
121
121
  validateConfigForServer(config);
122
122
  const db = await createDB(config, config.validationOptions, false);
123
- createServer(db, config.homeLayoutSections, config.serverOptions, config.validationOptions, config.customStylesheetPath, config.customAssetsPath);
123
+ createServer(db, config);
124
124
  break;
125
125
  }
126
126
  case "validate": {
@@ -1,3 +1,4 @@
1
+ import type { FormatterOptions as MarkdownFormatterOptions } from "@elyukai/markdown/format";
1
2
  import type { DefaultTSONDBTypes, ValidationOptions } from "./index.ts";
2
3
  import type { Output } from "./output.ts";
3
4
  import type { EntityDecl } from "./schema/dsl/index.ts";
@@ -18,6 +19,13 @@ export type Config<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = {
18
19
  customStylesheetPath?: string;
19
20
  customAssetsPath?: string;
20
21
  validationOptions?: Partial<ValidationOptions>;
22
+ formattingOptions?: Partial<FormatterOptions>;
23
+ };
24
+ export type FormatterOptions = {
25
+ /**
26
+ * If set to false, Markdown formatting will be disabled. Otherwise, the default or, if provided, provided Markdown formatting options will be used to format Markdown strings.
27
+ */
28
+ markdown?: Partial<MarkdownFormatterOptions> | false;
21
29
  };
22
30
  export type ServerOptions = {
23
31
  port: number;
@@ -41,6 +49,7 @@ export declare const validateConfigForGeneration: (config: Config) => asserts co
41
49
  export type DataConfig<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = Config<T> & {
42
50
  schema: Schema<T>;
43
51
  dataRootPath: string;
52
+ formattingOptions?: Partial<FormatterOptions>;
44
53
  };
45
54
  export declare const validateConfigForData: (config: Config) => asserts config is DataConfig;
46
55
  /**
@@ -53,6 +62,7 @@ export type ServerConfig<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> = Da
53
62
  validationOptions?: Partial<ValidationOptions>;
54
63
  customStylesheetPath?: string;
55
64
  customAssetsPath?: string;
65
+ formattingOptions?: Partial<FormatterOptions>;
56
66
  };
57
67
  /**
58
68
  * The configuration type required for validating the contents of the database.
@@ -1,4 +1,5 @@
1
1
  import type { InstanceContainer, InstanceContainerOverview } from "../shared/utils/instances.ts";
2
+ import type { FormatterOptions } from "./config.ts";
2
3
  import { Git } from "./git.js";
3
4
  import type { Output } from "./output.ts";
4
5
  import { type EntityDecl } from "./schema/dsl/index.ts";
@@ -42,6 +43,10 @@ export interface TSONDBOptions<T extends DefaultTSONDBTypes = DefaultTSONDBTypes
42
43
  * Options for validating the data in the database.
43
44
  */
44
45
  validationOptions?: Partial<ValidationOptions>;
46
+ /**
47
+ * Options for formatting the data in the database.
48
+ */
49
+ formatterOptions?: Partial<FormatterOptions>;
45
50
  }
46
51
  /**
47
52
  * Options for validating the data in the database.
@@ -96,6 +96,7 @@ export class TSONDB {
96
96
  #git;
97
97
  #referencesToInstances;
98
98
  #validationOptions;
99
+ #formatterOptions;
99
100
  #gitWrapper;
100
101
  #locked = false;
101
102
  constructor(options) {
@@ -106,6 +107,7 @@ export class TSONDB {
106
107
  this.#git = options.git;
107
108
  this.#referencesToInstances = options.referencesToInstances;
108
109
  this.#validationOptions = options.validationOptions;
110
+ this.#formatterOptions = options.formatterOptions;
109
111
  this.#gitWrapper = Lazy.of(() => this.#git
110
112
  ? new Git(this, this.#git.client, this.#git.root, this.#dataRootPath, () => this.#data, (data) => {
111
113
  this.#data = data;
@@ -281,7 +283,7 @@ export class TSONDB {
281
283
  await this.#data.forEachInstance(async (entityName, instance) => {
282
284
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
283
285
  const entity = this.#schema.getEntity(entityName);
284
- await writeInstance(this.#dataRootPath, entity, instance.id, instance.content);
286
+ await writeInstance(this.#dataRootPath, entity, instance.id, instance.content, this.#formatterOptions);
285
287
  }, true);
286
288
  debug("All data is formatted");
287
289
  }
@@ -331,7 +333,7 @@ export class TSONDB {
331
333
  throw new AggregateError(errors, "Validation errors occurred");
332
334
  }
333
335
  debug("Applying changes to disk ...");
334
- const diskResult = await applyStepsToDisk(this.#dataRootPath, steps);
336
+ const diskResult = await applyStepsToDisk(this.#dataRootPath, steps, this.#formatterOptions);
335
337
  if (isError(diskResult)) {
336
338
  debug("Error applying changes to disk: %s", diskResult.error.message);
337
339
  throw diskResult.error;
@@ -1,5 +1,6 @@
1
+ import type { FormatterOptions } from "../../config.ts";
1
2
  import type { Type } from "../dsl/index.ts";
2
3
  /**
3
4
  * Format the structure of a value to always look the same when serialized as JSON.
4
5
  */
5
- export declare const formatValue: (type: Type, value: unknown) => unknown;
6
+ export declare const formatValue: (type: Type, value: unknown, options: Partial<FormatterOptions> | undefined) => unknown;
@@ -1,3 +1,4 @@
1
+ import { format as formatMarkdown, formatInline as formatMarkdownInline, } from "@elyukai/markdown/format";
1
2
  import { mapObject, sortObjectKeys, sortObjectKeysByIndex } from "@elyukai/utils/object";
2
3
  import { assertExhaustive } from "@elyukai/utils/typeSafety";
3
4
  import { ENUM_DISCRIMINATOR_KEY } from "../../../shared/schema/declarations/EnumDecl.js";
@@ -11,19 +12,21 @@ const formatTranslationObjectValue = (type, value) => typeof value === "object"
11
12
  /**
12
13
  * Format the structure of a value to always look the same when serialized as JSON.
13
14
  */
14
- export const formatValue = (type, value) => {
15
+ export const formatValue = (type, value, options) => {
15
16
  switch (type.kind) {
16
17
  case NodeKind.ArrayType:
17
- return Array.isArray(value) ? value.map(item => formatValue(type.items, item)) : value;
18
+ return Array.isArray(value)
19
+ ? value.map(item => formatValue(type.items, item, options))
20
+ : value;
18
21
  case NodeKind.ObjectType:
19
22
  return typeof value === "object" && value !== null && !Array.isArray(value)
20
- ? sortObjectKeysByIndex(mapObject(value, (item, key) => type.properties[key] ? formatValue(type.properties[key].type, item) : item), Object.keys(type.properties))
23
+ ? sortObjectKeysByIndex(mapObject(value, (item, key) => type.properties[key] ? formatValue(type.properties[key].type, item, options) : item), Object.keys(type.properties))
21
24
  : value;
22
25
  case NodeKind.IncludeIdentifierType:
23
- return formatValue(type.reference.type.value, value);
26
+ return formatValue(type.reference.type.value, value, options);
24
27
  case NodeKind.NestedEntityMapType:
25
28
  return typeof value === "object" && value !== null && !Array.isArray(value)
26
- ? sortObjectKeys(mapObject(value, item => formatValue(type.type.value, item)))
29
+ ? sortObjectKeys(mapObject(value, item => formatValue(type.type.value, item, options)))
27
30
  : value;
28
31
  case NodeKind.EnumType: {
29
32
  if (typeof value === "object" &&
@@ -38,7 +41,7 @@ export const formatValue = (type, value) => {
38
41
  [ENUM_DISCRIMINATOR_KEY]: caseName,
39
42
  ...(caseValue == null || caseType == null
40
43
  ? {}
41
- : { [caseName]: formatValue(caseType, caseValue) }),
44
+ : { [caseName]: formatValue(caseType, caseValue, options) }),
42
45
  };
43
46
  }
44
47
  return value;
@@ -47,11 +50,25 @@ export const formatValue = (type, value) => {
47
50
  return Array.isArray(value) ? value.toSorted() : value;
48
51
  case NodeKind.TranslationObjectType:
49
52
  return formatTranslationObjectValue(type.properties, value);
53
+ case NodeKind.StringType: {
54
+ if (options?.markdown === false) {
55
+ return value;
56
+ }
57
+ switch (type.markdown) {
58
+ case "inline":
59
+ return typeof value === "string" ? formatMarkdownInline(value.trim()) : value;
60
+ case "block":
61
+ return typeof value === "string"
62
+ ? formatMarkdown(value.trimEnd(), options?.markdown)
63
+ : value;
64
+ default:
65
+ return value;
66
+ }
67
+ }
50
68
  case NodeKind.BooleanType:
51
69
  case NodeKind.DateType:
52
70
  case NodeKind.FloatType:
53
71
  case NodeKind.IntegerType:
54
- case NodeKind.StringType:
55
72
  case NodeKind.TypeArgumentType:
56
73
  case NodeKind.ReferenceIdentifierType:
57
74
  return value;
@@ -1,4 +1,4 @@
1
- import type { HomeLayoutSection } from "../config.ts";
1
+ import type { HomeLayoutSection, ServerConfig } from "../config.ts";
2
2
  import type { TSONDB, ValidationOptions } from "../index.ts";
3
3
  export type ServerOptions = {
4
4
  port: number;
@@ -16,4 +16,4 @@ declare global {
16
16
  }
17
17
  }
18
18
  }
19
- export declare const createServer: (db: TSONDB, homeLayoutSections?: HomeLayoutSection[], options?: Partial<ServerOptions>, validationOptions?: Partial<ValidationOptions>, customStylesheetPath?: string, customAssetsPath?: string) => void;
19
+ export declare const createServer: (db: TSONDB, config: Omit<ServerConfig, "locales">) => void;
@@ -15,7 +15,8 @@ const staticNodeModule = (moduleName) => {
15
15
  }
16
16
  return express.static(dirname(pathToPackageJson));
17
17
  };
18
- export const createServer = (db, homeLayoutSections, options, validationOptions, customStylesheetPath, customAssetsPath) => {
18
+ export const createServer = (db, config) => {
19
+ const { serverOptions: options, homeLayoutSections, validationOptions, customAssetsPath, customStylesheetPath, } = config;
19
20
  const { port } = { ...defaultOptions, ...options };
20
21
  const app = express();
21
22
  app.use(express.static(join(import.meta.dirname, "../../../../public")));
@@ -1,2 +1,3 @@
1
+ import type { FormatterOptions } from "../config.ts";
1
2
  import type { TransactionStep } from "../transaction.ts";
2
- export declare const applyStepsToDisk: (root: string, steps: TransactionStep[]) => Promise<import("@elyukai/utils/result").Error<Error> | import("@elyukai/utils/result").Ok<void>>;
3
+ export declare const applyStepsToDisk: (root: string, steps: TransactionStep[], formatterOptions: Partial<FormatterOptions> | undefined) => Promise<import("@elyukai/utils/result").Error<Error> | import("@elyukai/utils/result").Ok<void>>;
@@ -1,8 +1,8 @@
1
1
  import { error, isError, ok } from "@elyukai/utils/result";
2
2
  import * as DatabaseFilesystem from "./files.js";
3
- const setInstanceOnDisk = async (root, entity, instanceId, instanceContent) => {
3
+ const setInstanceOnDisk = async (root, entity, instanceId, instanceContent, formatterOptions) => {
4
4
  try {
5
- await DatabaseFilesystem.writeInstance(root, entity, instanceId, instanceContent);
5
+ await DatabaseFilesystem.writeInstance(root, entity, instanceId, instanceContent, formatterOptions);
6
6
  return ok();
7
7
  }
8
8
  catch (e) {
@@ -18,41 +18,41 @@ const deleteInstanceOnDisk = async (root, entityName, instanceId) => {
18
18
  return error(e);
19
19
  }
20
20
  };
21
- const rollbackChanges = async (root, steps) => {
21
+ const rollbackChanges = async (root, steps, formatterOptions) => {
22
22
  for (const step of steps) {
23
- const res = await runReverseStepAction(root, step);
23
+ const res = await runReverseStepAction(root, step, formatterOptions);
24
24
  if (isError(res)) {
25
25
  return res;
26
26
  }
27
27
  }
28
28
  return ok();
29
29
  };
30
- const runStepAction = (root, step) => {
30
+ const runStepAction = (root, step, formatterOptions) => {
31
31
  switch (step.kind) {
32
32
  case "create":
33
33
  case "update":
34
- return setInstanceOnDisk(root, step.entity, step.instanceId, step.instanceContent);
34
+ return setInstanceOnDisk(root, step.entity, step.instanceId, step.instanceContent, formatterOptions);
35
35
  case "delete":
36
36
  return deleteInstanceOnDisk(root, step.entity.name, step.instanceId);
37
37
  }
38
38
  };
39
- const runReverseStepAction = (root, step) => {
39
+ const runReverseStepAction = (root, step, formatterOptions) => {
40
40
  switch (step.kind) {
41
41
  case "create":
42
42
  return deleteInstanceOnDisk(root, step.entity.name, step.instanceId);
43
43
  case "update":
44
- return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance);
44
+ return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance, formatterOptions);
45
45
  case "delete":
46
- return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance);
46
+ return setInstanceOnDisk(root, step.entity, step.instanceId, step.oldInstance, formatterOptions);
47
47
  }
48
48
  };
49
- export const applyStepsToDisk = async (root, steps) => {
49
+ export const applyStepsToDisk = async (root, steps, formatterOptions) => {
50
50
  for (let i = 0; i < steps.length; i++) {
51
51
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
52
52
  const step = steps[i];
53
- const res = await runStepAction(root, step);
53
+ const res = await runStepAction(root, step, formatterOptions);
54
54
  if (isError(res)) {
55
- await rollbackChanges(root, steps.slice(0, i));
55
+ await rollbackChanges(root, steps.slice(0, i), formatterOptions);
56
56
  return res;
57
57
  }
58
58
  }
@@ -1,7 +1,8 @@
1
1
  import type { InstanceContent } from "../../shared/utils/instances.ts";
2
+ import type { FormatterOptions } from "../config.ts";
2
3
  import { type EntityDecl } from "../schema/dsl/index.ts";
3
4
  export declare const getFileNameForId: (id: string) => string;
4
5
  export declare const getPathToInstance: (dataRoot: string, entityName: string, id: string) => string;
5
- export declare const writeInstance: (dataRoot: string, entity: EntityDecl, id: string, instance: InstanceContent) => Promise<void>;
6
+ export declare const writeInstance: (dataRoot: string, entity: EntityDecl, id: string, instance: InstanceContent, formatterOptions: Partial<FormatterOptions> | undefined) => Promise<void>;
6
7
  export declare const deleteInstance: (dataRoot: string, entityName: string, id: string) => Promise<void>;
7
- export declare const formatInstance: (entity: EntityDecl, instanceContent: InstanceContent) => string;
8
+ export declare const formatInstance: (entity: EntityDecl, instanceContent: InstanceContent, options: Partial<FormatterOptions> | undefined) => string;
@@ -4,8 +4,8 @@ import {} from "../schema/dsl/index.js";
4
4
  import { formatValue } from "../schema/treeOperations/format.js";
5
5
  export const getFileNameForId = (id) => `${id}.json`;
6
6
  export const getPathToInstance = (dataRoot, entityName, id) => join(dataRoot, entityName, getFileNameForId(id));
7
- export const writeInstance = (dataRoot, entity, id, instance) => writeFile(getPathToInstance(dataRoot, entity.name, id), formatInstance(entity, instance), {
7
+ export const writeInstance = (dataRoot, entity, id, instance, formatterOptions) => writeFile(getPathToInstance(dataRoot, entity.name, id), formatInstance(entity, instance, formatterOptions), {
8
8
  encoding: "utf-8",
9
9
  });
10
10
  export const deleteInstance = (dataRoot, entityName, id) => rm(getPathToInstance(dataRoot, entityName, id));
11
- export const formatInstance = (entity, instanceContent) => JSON.stringify(formatValue(entity.type.value, instanceContent), undefined, 2) + "\n";
11
+ export const formatInstance = (entity, instanceContent, options) => JSON.stringify(formatValue(entity.type.value, instanceContent, options), undefined, 2) + "\n";
@@ -1,6 +1,8 @@
1
+ import type { MarkdownStringOption } from "../schema/types/StringType.ts";
1
2
  export interface StringConstraints {
2
3
  minLength?: number;
3
4
  maxLength?: number;
4
5
  pattern?: string | RegExp;
6
+ markdown?: MarkdownStringOption;
5
7
  }
6
8
  export declare const validateStringConstraints: (constraints: StringConstraints, value: string) => Error[];
@@ -1,3 +1,5 @@
1
+ import { validate, validateInline } from "@elyukai/markdown/validate";
2
+ import { assertExhaustive } from "@elyukai/utils/typeSafety";
1
3
  import { parallelizeErrors } from "../utils/validation.js";
2
4
  export const validateStringConstraints = (constraints, value) => parallelizeErrors([
3
5
  constraints.minLength !== undefined && value.length < constraints.minLength
@@ -17,4 +19,17 @@ export const validateStringConstraints = (constraints, value) => parallelizeErro
17
19
  ? TypeError(`string does not match the pattern ${pattern.toString()}`)
18
20
  : undefined;
19
21
  })(),
22
+ (() => {
23
+ if (constraints.markdown === undefined) {
24
+ return undefined;
25
+ }
26
+ switch (constraints.markdown) {
27
+ case "inline":
28
+ return validateInline(value);
29
+ case "block":
30
+ return validate(value);
31
+ default:
32
+ return assertExhaustive(constraints.markdown);
33
+ }
34
+ })(),
20
35
  ]);
@@ -5440,6 +5440,57 @@ const ReferenceIdentifierTypeInput = ({ type, value, instanceNamesByEntity, disa
5440
5440
  ]
5441
5441
  });
5442
5442
  };
5443
+ class InternalMarkdownError extends Error {
5444
+ constructor(message){
5445
+ super(message);
5446
+ this.name = "InternalMarkdownError";
5447
+ }
5448
+ }
5449
+ class MarkdownSyntaxError extends SyntaxError {
5450
+ row;
5451
+ column;
5452
+ remainingText;
5453
+ constructor(row, column, remainingText){
5454
+ super(`Unexpected Markdown syntax at row ${row.toFixed()}, column ${column.toFixed()}: ${remainingText}`);
5455
+ this.name = "MarkdownSyntaxError";
5456
+ this.row = row;
5457
+ this.column = column;
5458
+ this.remainingText = remainingText;
5459
+ }
5460
+ }
5461
+ const positionInStringToColumnAndRow = (position, string)=>{
5462
+ const lines = string.slice(0, position).split("\n");
5463
+ const row = lines.length;
5464
+ const column = (lines.at(-1)?.length ?? 0) + 1;
5465
+ return {
5466
+ row,
5467
+ column
5468
+ };
5469
+ };
5470
+ const validateInline = (markdown)=>{
5471
+ const results = inlineMarkdown.evalT({
5472
+ indentation: 0,
5473
+ keepSyntax: false
5474
+ }).parse(markdown);
5475
+ if (!isNotEmpty(results)) return new InternalMarkdownError("Failed to parse inline Markdown");
5476
+ const [_, remaining] = results[0];
5477
+ if (remaining.length > 0) {
5478
+ const { row, column } = positionInStringToColumnAndRow(markdown.length - remaining.length, markdown);
5479
+ return new MarkdownSyntaxError(row, column, remaining);
5480
+ }
5481
+ };
5482
+ const validate = (markdown)=>{
5483
+ const results = finalBlockMarkdown.evalT({
5484
+ indentation: 0,
5485
+ keepSyntax: false
5486
+ }).parse(markdown);
5487
+ if (!isNotEmpty(results)) return new InternalMarkdownError("Failed to parse Markdown");
5488
+ const [_, remaining] = results[0];
5489
+ if (remaining.length > 0) {
5490
+ const { row, column } = positionInStringToColumnAndRow(markdown.length - remaining.length, markdown);
5491
+ return new MarkdownSyntaxError(row, column, remaining);
5492
+ }
5493
+ };
5443
5494
  const validateStringConstraints = (constraints, value)=>parallelizeErrors([
5444
5495
  void 0 !== constraints.minLength && value.length < constraints.minLength ? RangeError(`expected a string with at least ${constraints.minLength.toString()} character${1 === constraints.minLength ? "" : "s"}, but got ${value.length.toString()} character${1 === value.length ? "" : "s"}`) : void 0,
5445
5496
  void 0 !== constraints.maxLength && value.length > constraints.maxLength ? RangeError(`expected a string with at most ${constraints.maxLength.toString()} character${1 === constraints.maxLength ? "" : "s"}, but got ${value.length.toString()} character${1 === value.length ? "" : "s"}`) : void 0,
@@ -5447,6 +5498,17 @@ const validateStringConstraints = (constraints, value)=>parallelizeErrors([
5447
5498
  if (void 0 === constraints.pattern) return;
5448
5499
  const pattern = "string" == typeof constraints.pattern ? new RegExp(constraints.pattern) : constraints.pattern;
5449
5500
  return pattern.test(value) ? void 0 : TypeError(`string does not match the pattern ${pattern.toString()}`);
5501
+ })(),
5502
+ (()=>{
5503
+ if (void 0 === constraints.markdown) return;
5504
+ switch(constraints.markdown){
5505
+ case "inline":
5506
+ return validateInline(value);
5507
+ case "block":
5508
+ return validate(value);
5509
+ default:
5510
+ return typeSafety_assertExhaustive(constraints.markdown);
5511
+ }
5450
5512
  })()
5451
5513
  ]);
5452
5514
  const BlockMarkdownHighlighting = ({ node })=>{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsondb",
3
- "version": "0.20.5",
3
+ "version": "0.20.6",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Lukas Obermann",
@@ -53,7 +53,7 @@
53
53
  "typescript-eslint": "^8.70.0"
54
54
  },
55
55
  "dependencies": {
56
- "@elyukai/markdown": "^0.3.2",
56
+ "@elyukai/markdown": "^0.3.3",
57
57
  "@elyukai/utils": "^0.3.6",
58
58
  "@preact/signals": "^2.11.2",
59
59
  "debug": "^4.4.3",
@@ -244,6 +244,14 @@ button {
244
244
  cursor: pointer;
245
245
  }
246
246
 
247
+ @media (any-pointer: fine) {
248
+ a.btn,
249
+ button {
250
+ padding: 0.25rem 0.5rem;
251
+ font-size: 0.9rem;
252
+ }
253
+ }
254
+
247
255
  a.btn:hover,
248
256
  button:hover {
249
257
  background: var(--color-button-background-hover);
@@ -255,7 +263,6 @@ a.btn--primary,
255
263
  button.primary {
256
264
  background: var(--color-button-background-primary);
257
265
  color: var(--color-button-foreground-primary);
258
- padding: 0.5rem 1rem;
259
266
  border: none;
260
267
  }
261
268
 
@@ -276,7 +283,6 @@ a.btn--destructive,
276
283
  button.destructive {
277
284
  background: var(--color-button-background-destructive);
278
285
  color: var(--color-button-foreground-destructive);
279
- padding: 0.5rem 1rem;
280
286
  border: none;
281
287
  }
282
288
 
@@ -316,6 +322,16 @@ select,
316
322
  border-radius: 0;
317
323
  }
318
324
 
325
+ @media (any-pointer: fine) {
326
+ input,
327
+ textarea,
328
+ select,
329
+ .textarea-grow-wrap__mirror {
330
+ padding: 0.25rem 0.375rem;
331
+ font-size: 0.9rem;
332
+ }
333
+ }
334
+
319
335
  input::placeholder,
320
336
  textarea::placeholder,
321
337
  select::placeholder {
@@ -396,7 +412,7 @@ input[type="checkbox"]:checked::before {
396
412
  height: 0.4rem;
397
413
  border-color: var(--color-background-primary);
398
414
  transform: rotate(-45deg);
399
- margin: calc(-0.325rem) 0 0 calc(-0.4rem);
415
+ margin: -0.15rem 0 0 -0.325rem;
400
416
  }
401
417
 
402
418
  input[type="radio"]:checked::before {
@@ -515,6 +531,13 @@ select {
515
531
  container-type: inline-size;
516
532
  }
517
533
 
534
+ @supports (grid-template-rows: subgrid) {
535
+ .entries {
536
+ display: grid;
537
+ grid-template-columns: 1fr auto auto;
538
+ }
539
+ }
540
+
518
541
  .entries-item {
519
542
  display: grid;
520
543
  gap: 0.125rem 1.5rem;
@@ -527,6 +550,20 @@ select {
527
550
  --text-padding: 0.5rem;
528
551
  }
529
552
 
553
+ @supports (grid-template-rows: subgrid) {
554
+ .entries-item {
555
+ grid-template-columns: subgrid;
556
+ grid-column: 1 / -1;
557
+ grid-template-areas: "title side side" "subtitle side side" ". side side";
558
+ }
559
+ }
560
+
561
+ @media (any-pointer: fine) {
562
+ .entries-item {
563
+ padding: 0.0625rem 0;
564
+ }
565
+ }
566
+
530
567
  @container (min-width: 40rem) {
531
568
  .entries-item {
532
569
  grid-template-columns: 1fr auto auto;
@@ -534,6 +571,12 @@ select {
534
571
  grid-template-areas: "title subtitle side";
535
572
  align-items: center;
536
573
  }
574
+
575
+ @supports (grid-template-rows: subgrid) {
576
+ .entries-item {
577
+ grid-template-columns: subgrid;
578
+ }
579
+ }
537
580
  }
538
581
 
539
582
  .entries-item:first-child {
@@ -607,6 +650,7 @@ select {
607
650
  grid-area: side;
608
651
  display: flex;
609
652
  align-items: center;
653
+ justify-content: end;
610
654
  gap: 1rem;
611
655
  }
612
656
 
@@ -673,6 +717,11 @@ form > .field--container {
673
717
  border: none;
674
718
  }
675
719
 
720
+ .field--container > .empty {
721
+ font-size: 0.8rem;
722
+ color: var(--color-foreground-tertiary);
723
+ }
724
+
676
725
  .field--container > ul,
677
726
  .field--container > ol {
678
727
  margin: 0;
@@ -690,6 +739,13 @@ form > .field--container {
690
739
  grid-column: 1 / 4;
691
740
  }
692
741
 
742
+ @media (any-pointer: fine) {
743
+ .field--container > ul > li,
744
+ .field--container > ol > li {
745
+ padding-block: 0.25rem;
746
+ }
747
+ }
748
+
693
749
  .field--child-entities,
694
750
  .field--child-entities > ul > li,
695
751
  .field--child-entities > ol > li {
@@ -937,6 +993,13 @@ form > .field--container {
937
993
  align-self: center;
938
994
  }
939
995
 
996
+ @media (any-pointer: fine) {
997
+ .container-item-title {
998
+ font-size: 0.9rem;
999
+ padding-block: 0.25rem;
1000
+ }
1001
+ }
1002
+
940
1003
  .container-item-title .id {
941
1004
  color: var(--color-foreground-tertiary);
942
1005
  font-size: 0.8rem;
@@ -947,6 +1010,12 @@ form > .field--container {
947
1010
  margin: 0.125rem 0 0;
948
1011
  }
949
1012
 
1013
+ @media (any-pointer: fine) {
1014
+ .container-item-title .comment {
1015
+ margin: 0.25rem 0 0;
1016
+ }
1017
+ }
1018
+
950
1019
  .container-item-actions {
951
1020
  flex: none;
952
1021
  display: flex;
@@ -1002,6 +1071,21 @@ button[type="submit"] {
1002
1071
  margin: 0.35rem 0 0;
1003
1072
  }
1004
1073
 
1074
+ @media (any-pointer: fine) {
1075
+ .comment {
1076
+ font-size: 0.75rem;
1077
+ }
1078
+
1079
+ .comment p {
1080
+ margin: 0.25rem 0 0;
1081
+ }
1082
+
1083
+ .comment ul {
1084
+ padding-left: 1.25rem;
1085
+ margin: 0.25rem 0 0;
1086
+ }
1087
+ }
1088
+
1005
1089
  main:has(.form-footer) {
1006
1090
  margin-bottom: -1.5rem;
1007
1091
  }