@protobase/ui 0.19.8 → 0.19.10

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 (85) hide show
  1. package/dist/app/app.js +4 -4
  2. package/dist/app/create/create-offered.js +9 -0
  3. package/dist/app/create/create-page.js +4 -4
  4. package/dist/app/create/create-record-form.js +26 -62
  5. package/dist/app/create/create-route.js +29 -0
  6. package/dist/app/create/form.d.ts +30 -0
  7. package/dist/app/create/form.js +72 -0
  8. package/dist/app/custom-section.js +1 -1
  9. package/dist/app/displays/map-display.js +1 -1
  10. package/dist/app/displays/record-card.js +18 -4
  11. package/dist/app/displays/record-link.js +1 -1
  12. package/dist/app/displays/use-map-records.js +2 -2
  13. package/dist/app/embedded-list/embedded-columns.js +2 -2
  14. package/dist/app/embedded-list/embedded-list.js +5 -5
  15. package/dist/app/embedded-list/use-embedded-rows.js +2 -2
  16. package/dist/app/field-editor.js +21 -5
  17. package/dist/app/gallery/gallery-section.js +1 -1
  18. package/dist/app/gallery/photo-details.js +2 -2
  19. package/dist/app/list-page.js +18 -17
  20. package/dist/app/pages/blocks/card-row.js +2 -2
  21. package/dist/app/pages/blocks/field.js +2 -2
  22. package/dist/app/pages/blocks/link.js +1 -1
  23. package/dist/app/pages/blocks/modal-form.js +6 -6
  24. package/dist/app/pages/blocks/progress.js +1 -1
  25. package/dist/app/pages/blocks/record-card.js +2 -2
  26. package/dist/app/pages/blocks/record-row.js +1 -1
  27. package/dist/app/pages/blocks/show.js +2 -2
  28. package/dist/app/pages/blocks/stat.js +3 -3
  29. package/dist/app/pages/blocks/table.js +1 -1
  30. package/dist/app/pages/field-value.js +5 -2
  31. package/dist/app/pages/record-actions.js +2 -2
  32. package/dist/app/pages/use-run-action.js +2 -2
  33. package/dist/app/record-page.js +15 -11
  34. package/dist/app/record-row/record-row-body.js +2 -2
  35. package/dist/app/record-row/saved-record-row.js +1 -1
  36. package/dist/app/related-section.js +1 -1
  37. package/dist/app/searches/saved-search-rows.js +1 -1
  38. package/dist/app/searches/saved-search-section.js +2 -2
  39. package/dist/app/searches/saved-searches.js +2 -2
  40. package/dist/app/searches/search-controls.js +1 -1
  41. package/dist/app/searches/search-name-dialog.js +1 -1
  42. package/dist/app/searches/update-search-dialog.js +2 -2
  43. package/dist/app/searches/use-filter-chips.js +1 -1
  44. package/dist/app/searches/use-save-search.js +2 -2
  45. package/dist/app/searches/use-saved-searches.js +1 -1
  46. package/dist/app/searches/use-search-row.js +1 -1
  47. package/dist/app/sensitive-field.js +1 -1
  48. package/dist/app/use-app-address.js +1 -1
  49. package/dist/app/value-badge.js +1 -1
  50. package/dist/auth/sign-in-prompt.js +1 -1
  51. package/dist/data-grid/cells.js +1 -1
  52. package/dist/index.d.ts +9 -1
  53. package/dist/index.js +10 -2
  54. package/dist/live/columns-from-model.js +3 -0
  55. package/dist/live/grid-loader.js +1 -1
  56. package/dist/live/use-filter-config.js +1 -1
  57. package/dist/markdown/markdown-content.js +18 -0
  58. package/dist/markdown/markdown-editor.js +46 -0
  59. package/dist/markdown/markdown-excerpt.js +36 -0
  60. package/dist/markdown/markdown-hint.js +5 -0
  61. package/dist/markdown/markdown-html.js +59 -0
  62. package/dist/markdown/markdown.js +15 -0
  63. package/dist/package.js +1 -1
  64. package/dist/primitives/choices.d.ts +22 -0
  65. package/dist/primitives/choices.js +33 -0
  66. package/dist/primitives/dialog.d.ts +15 -0
  67. package/dist/primitives/menu-select.d.ts +14 -0
  68. package/dist/primitives/segmented.d.ts +23 -0
  69. package/dist/primitives/textarea.d.ts +9 -0
  70. package/dist/record-view/field.d.ts +27 -0
  71. package/dist/record-view/section.d.ts +10 -0
  72. package/dist/shell/assets/app-GKl91FLS.js +69 -0
  73. package/dist/shell/assets/{index-cMmFPuUQ.js → index-BB2BN-6s.js} +1 -1
  74. package/dist/shell/assets/index-DxV5Rp_F.css +2 -0
  75. package/dist/shell/assets/{maplibre-9XYrB9k6.js → maplibre-DHotMteL.js} +2 -2
  76. package/dist/shell/assets/markdown-content-C4zE1qZp.js +67 -0
  77. package/dist/shell/assets/{record-map-OBtKCiUB.js → record-map-BBbVY6vn.js} +1 -1
  78. package/dist/shell/assets/{records-map-C4oMPL5C.js → records-map-ur6HR-I5.js} +1 -1
  79. package/dist/shell/assets/shared-protobase-ui-D7kkYiTL.js +1 -0
  80. package/dist/shell/index.html +4 -4
  81. package/dist/shell/protobase.shell.json +1 -1
  82. package/package.json +6 -4
  83. package/dist/shell/assets/app-D8i5r_6U.js +0 -68
  84. package/dist/shell/assets/index-Cuulz26c.css +0 -2
  85. package/dist/shell/assets/shared-protobase-ui-CN1HmyDa.js +0 -1
package/dist/app/app.js CHANGED
@@ -24,7 +24,8 @@ import { breadcrumbFor } from "./breadcrumb-for.js";
24
24
  import { sourceStatusOf } from "../data/with-sources.js";
25
25
  import { MetaGate, Notice, useAdminMeta } from "./meta-gate.js";
26
26
  import { useAppAddress } from "./use-app-address.js";
27
- import { CreatePage } from "./create/create-page.js";
27
+ import { ComposedPage } from "./pages/composed-page.js";
28
+ import { CreateRoute } from "./create/create-route.js";
28
29
  import { ListPage } from "./list-page.js";
29
30
  import { MembersPage } from "../organizations/members-page.js";
30
31
  import { InvitationsPage } from "../organizations/invitations-page.js";
@@ -35,7 +36,6 @@ import { actingUser } from "../roles/acting-user.js";
35
36
  import { useActiveRole } from "../roles/use-active-role.js";
36
37
  import { userMenuFromMeta } from "./user-menu-from-meta.js";
37
38
  import { navFromMeta } from "./nav-from-meta.js";
38
- import { ComposedPage } from "./pages/composed-page.js";
39
39
  import { ProjectLogo } from "./project-logo.js";
40
40
  import { PluginGate, usePluginsOff } from "../plugins/plugin-gate.js";
41
41
  import { PluginNotice } from "../plugins/plugin-notice.js";
@@ -163,10 +163,10 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
163
163
  address.page,
164
164
  "."
165
165
  ]
166
- }) : drawnFrom && drawnFrom.state !== "ready" ? /* @__PURE__ */ jsx(SourceNotice, { status: drawnFrom }) : address.kind === "create" ? /* @__PURE__ */ jsx(CreatePage, {
166
+ }) : drawnFrom && drawnFrom.state !== "ready" ? /* @__PURE__ */ jsx(SourceNotice, { status: drawnFrom }) : address.kind === "create" ? /* @__PURE__ */ jsx(CreateRoute, {
167
167
  resource: address.resource,
168
168
  values: address.values
169
- }, `${address.resource}/new?${new URLSearchParams(address.values).toString()}`) : address.kind === "record" ? /* @__PURE__ */ jsx(RecordPage, {
169
+ }, address.resource) : address.kind === "record" ? /* @__PURE__ */ jsx(RecordPage, {
170
170
  resource: address.resource,
171
171
  recordKey: address.key
172
172
  }, `${address.resource}/${address.key}`) : /* @__PURE__ */ jsx(ListPage, { address }, address.resource);
@@ -0,0 +1,9 @@
1
+ import { can } from "../permissions.js";
2
+ //#region src/app/create/create-offered.ts
3
+ /**
4
+ * Whether the list offers New: a view's create page to whoever `/meta` gives that page, whatever the resource's access
5
+ * says; the create form to whoever may create records.
6
+ */
7
+ const createOffered = (view, pages, permissions) => view?.create ? Object.hasOwn(pages, view.create.page) : can(permissions, "create");
8
+ //#endregion
9
+ export { createOffered };
@@ -1,14 +1,14 @@
1
1
  import { useClient } from "../../data/api-provider.js";
2
2
  import { recordKey } from "../../live/model-helpers.js";
3
3
  import { humanize } from "../../live/naming.js";
4
- import { fetchLabels } from "../../live/relation-labels.js";
5
4
  import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
5
+ import { can } from "../permissions.js";
6
6
  import { useAppAddress } from "../use-app-address.js";
7
+ import { fetchLabels } from "../../live/relation-labels.js";
8
+ import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired } from "./create-values.js";
7
9
  import { FieldEditor } from "../field-editor.js";
8
- import { can } from "../permissions.js";
9
10
  import { useLiveSave } from "../use-live-save.js";
10
11
  import { CreateRecordForm } from "./create-record-form.js";
11
- import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired } from "./create-values.js";
12
12
  import { useEffect, useState } from "react";
13
13
  import { isApiError } from "@protobase/client";
14
14
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -117,7 +117,7 @@ const LoadedCreate = ({ resource, values }) => {
117
117
  help: view?.fields[name]?.help,
118
118
  required: isRequired(field),
119
119
  error: errors[name],
120
- wide: Boolean(view?.fields[name]?.multiline),
120
+ wide: Boolean(view?.fields[name]?.multiline || view?.fields[name]?.markdown),
121
121
  editor: /* @__PURE__ */ jsx(FieldEditor, {
122
122
  field,
123
123
  hints: view?.fields[name],
@@ -1,69 +1,33 @@
1
- import { Button } from "../../primitives/button.js";
2
- import { ErrorBanner } from "../error-banner.js";
3
1
  import { Field } from "../../record-view/field.js";
4
2
  import { Section } from "../../record-view/section.js";
5
- import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Form } from "./form.js";
4
+ import { jsx } from "react/jsx-runtime";
6
5
  //#region src/app/create/create-record-form.tsx
7
6
  /** The presentation of "New <thing>": the view's sections, each field with its editor, and Create or Cancel. */
8
- const CreateRecordForm = ({ noun, collection, sections, saving, message, onSubmit, onCancel }) => /* @__PURE__ */ jsx("form", {
9
- className: "flex-1",
10
- onSubmit: (event) => {
11
- event.preventDefault();
12
- onSubmit();
13
- },
14
- children: /* @__PURE__ */ jsxs("div", {
15
- className: "mx-auto max-w-[760px] px-4 py-6 md:px-10 md:py-8",
16
- children: [
17
- /* @__PURE__ */ jsxs("header", {
18
- className: "flex flex-col gap-4 border-b pb-6 sm:flex-row sm:items-end sm:justify-between sm:gap-6",
19
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
20
- className: "text-xs text-muted-foreground",
21
- children: collection
22
- }), /* @__PURE__ */ jsxs("h1", {
23
- className: "mt-1 text-2xl font-semibold tracking-tight",
24
- children: ["New ", noun.toLowerCase()]
25
- })] }), /* @__PURE__ */ jsxs("div", {
26
- className: "flex items-center gap-2",
27
- children: [/* @__PURE__ */ jsx(Button, {
28
- className: "max-sm:min-h-11",
29
- onClick: onCancel,
30
- children: "Cancel"
31
- }), /* @__PURE__ */ jsxs(Button, {
32
- variant: "primary",
33
- type: "submit",
34
- className: "max-sm:min-h-11 max-sm:flex-1",
35
- loading: saving,
36
- children: ["Create ", noun.toLowerCase()]
37
- })]
38
- })]
39
- }),
40
- message && /* @__PURE__ */ jsx("div", {
41
- className: "mt-4",
42
- children: /* @__PURE__ */ jsx(ErrorBanner, {
43
- title: "Not created",
44
- error: new Error(message)
45
- })
46
- }),
47
- /* @__PURE__ */ jsx("div", {
48
- className: "divide-y divide-border",
49
- children: sections.map((section) => /* @__PURE__ */ jsx(Section, {
50
- title: section.title,
51
- help: section.help,
52
- children: /* @__PURE__ */ jsx("div", {
53
- className: "grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2",
54
- children: section.fields.map((field) => /* @__PURE__ */ jsx(Field, {
55
- label: field.label,
56
- required: field.required,
57
- help: field.help,
58
- error: field.error,
59
- wide: field.wide,
60
- children: field.editor
61
- }, field.name))
62
- })
63
- }, section.title))
64
- })
65
- ]
66
- })
7
+ const CreateRecordForm = ({ noun, collection, sections, saving, message, onSubmit, onCancel }) => /* @__PURE__ */ jsx(Form, {
8
+ title: `New ${noun.toLowerCase()}`,
9
+ eyebrow: collection,
10
+ submit: `Create ${noun.toLowerCase()}`,
11
+ saving,
12
+ error: message ? new Error(message) : void 0,
13
+ errorTitle: "Not created",
14
+ onSubmit,
15
+ onCancel,
16
+ children: sections.map((section) => /* @__PURE__ */ jsx(Section, {
17
+ title: section.title,
18
+ help: section.help,
19
+ children: /* @__PURE__ */ jsx("div", {
20
+ className: "grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2",
21
+ children: section.fields.map((field) => /* @__PURE__ */ jsx(Field, {
22
+ label: field.label,
23
+ required: field.required,
24
+ help: field.help,
25
+ error: field.error,
26
+ wide: field.wide,
27
+ children: field.editor
28
+ }, field.name))
29
+ })
30
+ }, section.title))
67
31
  });
68
32
  //#endregion
69
33
  export { CreateRecordForm };
@@ -0,0 +1,29 @@
1
+ import { humanize } from "../../live/naming.js";
2
+ import { Notice, useAdminMeta } from "../meta-gate.js";
3
+ import { ComposedPage } from "../pages/composed-page.js";
4
+ import { CreatePage } from "./create-page.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/app/create/create-route.tsx
7
+ /**
8
+ * `/:resource/new`: the view's create page when it names one, which reads the address's `values` itself and may change
9
+ * them in place, else the create form, started again when the address's `values` change.
10
+ */
11
+ const CreateRoute = ({ resource, values }) => {
12
+ const meta = useAdminMeta();
13
+ const view = meta.views[resource];
14
+ if (!view?.create) return /* @__PURE__ */ jsx(CreatePage, {
15
+ resource,
16
+ values
17
+ }, new URLSearchParams(values).toString());
18
+ if (!meta.pages[view.create.page]) return /* @__PURE__ */ jsxs(Notice, {
19
+ title: "Not allowed",
20
+ children: [
21
+ "You cannot create ",
22
+ view.names?.plural.toLowerCase() ?? humanize(resource),
23
+ "."
24
+ ]
25
+ });
26
+ return /* @__PURE__ */ jsx(ComposedPage, { name: view.create.page });
27
+ };
28
+ //#endregion
29
+ export { CreateRoute };
@@ -0,0 +1,30 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/app/create/form.d.ts
3
+ export type FormProps = {
4
+ /**
5
+ * The heading, such as "New repair", when the form is the whole page, as the create form is: the heading and its
6
+ * buttons on top. Without it, the form sits in a page with a heading of its own, a composed page's, and its buttons
7
+ * follow the sections.
8
+ */
9
+ title?: string;
10
+ /** The line above the heading, such as the records' name. */
11
+ eyebrow?: string;
12
+ /** The submit button's label, such as "Create repair". */
13
+ submit: string;
14
+ /** The submit button spins and cannot be pressed again. */
15
+ saving?: boolean;
16
+ /** The submit button cannot be pressed, while the answers are not complete. */
17
+ disabled?: boolean;
18
+ /** A failure that belongs to no field, shown above the sections; an API error shows its messages and hints. */
19
+ error?: unknown;
20
+ /** The failure's title. */
21
+ errorTitle?: string;
22
+ onSubmit: () => void;
23
+ /** Shows Cancel beside the submit button. */
24
+ onCancel?: () => void;
25
+ /** The form's sections (`Section`), drawn one under the other with a line between them. */
26
+ children: ReactNode;
27
+ };
28
+ /** The frame of the app's forms: Cancel and the submit button, a failure that belongs to no field, and the sections. */
29
+ export declare const Form: ({ title, eyebrow, submit, saving, disabled, error, errorTitle, onSubmit, onCancel, children }: FormProps) => import("react").JSX.Element;
30
+ //#endregion
@@ -0,0 +1,72 @@
1
+ import { Button } from "../../primitives/button.js";
2
+ import { ErrorBanner } from "../error-banner.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/app/create/form.tsx
5
+ /** The frame of the app's forms: Cancel and the submit button, a failure that belongs to no field, and the sections. */
6
+ const Form = ({ title, eyebrow, submit, saving, disabled, error, errorTitle = "Not saved", onSubmit, onCancel, children }) => {
7
+ const buttons = /* @__PURE__ */ jsxs("div", {
8
+ className: "flex items-center gap-2",
9
+ children: [onCancel && /* @__PURE__ */ jsx(Button, {
10
+ className: "max-sm:min-h-11",
11
+ onClick: onCancel,
12
+ children: "Cancel"
13
+ }), /* @__PURE__ */ jsx(Button, {
14
+ variant: "primary",
15
+ type: "submit",
16
+ className: "max-sm:min-h-11 max-sm:flex-1",
17
+ loading: saving,
18
+ disabled,
19
+ children: submit
20
+ })]
21
+ });
22
+ const failure = error !== void 0 && /* @__PURE__ */ jsx("div", {
23
+ className: "mt-4",
24
+ children: /* @__PURE__ */ jsx(ErrorBanner, {
25
+ title: errorTitle,
26
+ error
27
+ })
28
+ });
29
+ const sections = /* @__PURE__ */ jsx("div", {
30
+ className: "divide-y divide-border",
31
+ children
32
+ });
33
+ const send = (event) => {
34
+ event.preventDefault();
35
+ onSubmit();
36
+ };
37
+ if (title === void 0) return /* @__PURE__ */ jsxs("form", {
38
+ className: "max-w-[760px]",
39
+ onSubmit: send,
40
+ children: [
41
+ failure,
42
+ sections,
43
+ /* @__PURE__ */ jsx("footer", {
44
+ className: "border-t pt-6 sm:flex sm:justify-end",
45
+ children: buttons
46
+ })
47
+ ]
48
+ });
49
+ return /* @__PURE__ */ jsx("form", {
50
+ className: "flex-1",
51
+ onSubmit: send,
52
+ children: /* @__PURE__ */ jsxs("div", {
53
+ className: "mx-auto max-w-[760px] px-4 py-6 md:px-10 md:py-8",
54
+ children: [
55
+ /* @__PURE__ */ jsxs("header", {
56
+ className: "flex flex-col gap-4 border-b pb-6 sm:flex-row sm:items-end sm:justify-between sm:gap-6",
57
+ children: [/* @__PURE__ */ jsxs("div", { children: [eyebrow && /* @__PURE__ */ jsx("p", {
58
+ className: "text-xs text-muted-foreground",
59
+ children: eyebrow
60
+ }), /* @__PURE__ */ jsx("h1", {
61
+ className: "mt-1 text-2xl font-semibold tracking-tight",
62
+ children: title
63
+ })] }), buttons]
64
+ }),
65
+ failure,
66
+ sections
67
+ ]
68
+ })
69
+ });
70
+ };
71
+ //#endregion
72
+ export { Form };
@@ -1,9 +1,9 @@
1
1
  import { Problem } from "./pages/problem.js";
2
2
  import { CustomBoundary } from "./pages/custom-boundary.js";
3
3
  import { useProjectUi } from "./pages/project-ui.js";
4
+ import { RecordScope } from "./pages/record-scope.js";
4
5
  import { Section } from "../record-view/section.js";
5
6
  import { SidebarCard } from "../record-view/record-sidebar.js";
6
- import { RecordScope } from "./pages/record-scope.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { unmarkFieldRefs } from "@protobase/schema/internal";
9
9
  //#region src/app/custom-section.tsx
@@ -2,8 +2,8 @@ import { cn } from "../../lib/cn.js";
2
2
  import { Button } from "../../primitives/button.js";
3
3
  import { Problem } from "../pages/problem.js";
4
4
  import { humanize } from "../../live/naming.js";
5
- import { mapCards } from "./list-displays.js";
6
5
  import { useAdminMeta } from "../meta-gate.js";
6
+ import { mapCards } from "./list-displays.js";
7
7
  import { ErrorBanner } from "../error-banner.js";
8
8
  import { useDebounced } from "../../lib/use-debounced.js";
9
9
  import { RecordCard } from "./record-card.js";
@@ -8,9 +8,12 @@ import { amountParts, cardFields, filledAmount, filledMeta } from "./list-displa
8
8
  import { AppLink } from "../use-app-address.js";
9
9
  import { shownWidget } from "../field-types/shown-widget.js";
10
10
  import { WidgetPart } from "../field-types/widget-part.js";
11
+ import { markdownExcerpt } from "../../markdown/markdown-excerpt.js";
12
+ import { isMarkdown } from "../../markdown/markdown-hint.js";
13
+ import { ValueBadge } from "../value-badge.js";
14
+ import { Markdown } from "../../markdown/markdown.js";
11
15
  import { namedIcon } from "../named-icon.js";
12
16
  import { FavoriteButton } from "../saved/favorite-button.js";
13
- import { ValueBadge } from "../value-badge.js";
14
17
  import { CardImage } from "./card-image.js";
15
18
  import { CardCarousel } from "./card-carousel.js";
16
19
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -38,7 +41,9 @@ const RecordCard = ({ model, view, display, row, swipe = true, mark }) => {
38
41
  const text = (name) => {
39
42
  const field = model.fields[name];
40
43
  if (!field) return "";
41
- return row.labels[name] ?? displayValue(field, row.record[name], view?.fields[name]);
44
+ const value = row.record[name];
45
+ if (row.labels[name] === void 0 && isMarkdown(field, view?.fields[name]) && typeof value === "string" && value !== "") return markdownExcerpt(value);
46
+ return row.labels[name] ?? displayValue(field, value, view?.fields[name]);
42
47
  };
43
48
  const { fields: fieldTypes, components } = useProjectUi();
44
49
  const Card = display.card === void 0 ? void 0 : components?.[display.card];
@@ -53,6 +58,15 @@ const RecordCard = ({ model, view, display, row, swipe = true, mark }) => {
53
58
  fallback: text(name)
54
59
  }) : text(name);
55
60
  };
61
+ const subtitleValue = (name) => {
62
+ const field = model.fields[name];
63
+ const value = row.record[name];
64
+ if (!field || !isMarkdown(field, view?.fields[name]) || shownWidget(fieldTypes, field, view?.fields[name]) || typeof value !== "string" || value === "") return shownValue(name);
65
+ return /* @__PURE__ */ jsx(Markdown, {
66
+ source: value,
67
+ inLink: true
68
+ });
69
+ };
56
70
  const metaValue = (name) => {
57
71
  const field = model.fields[name];
58
72
  const hints = view?.fields[name];
@@ -99,8 +113,8 @@ const RecordCard = ({ model, view, display, row, swipe = true, mark }) => {
99
113
  })]
100
114
  }),
101
115
  subtitle !== void 0 && /* @__PURE__ */ jsx("span", {
102
- className: view?.fields[subtitle]?.multiline ? "line-clamp-3 whitespace-pre-line break-words text-muted-foreground" : "truncate text-muted-foreground",
103
- children: shownValue(subtitle)
116
+ className: view?.fields[subtitle]?.markdown ? "line-clamp-3 break-words text-muted-foreground" : view?.fields[subtitle]?.multiline ? "line-clamp-3 whitespace-pre-line break-words text-muted-foreground" : "truncate text-muted-foreground",
117
+ children: subtitleValue(subtitle)
104
118
  }),
105
119
  shownAmount !== void 0 && /* @__PURE__ */ jsx("span", {
106
120
  className: "truncate tabular-nums text-muted-foreground",
@@ -1,6 +1,6 @@
1
+ import { useLayoutRecord } from "../pages/record-scope.js";
1
2
  import { AppLink } from "../use-app-address.js";
2
3
  import { useOptionalListDisplay } from "./display-context.js";
3
- import { useLayoutRecord } from "../pages/record-scope.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region src/app/displays/record-link.tsx
6
6
  /**
@@ -1,9 +1,9 @@
1
1
  import { keys } from "../../data/query-keys.js";
2
2
  import { useClient } from "../../data/api-provider.js";
3
- import { listLabels, withLabels } from "../../live/relation-labels.js";
3
+ import { useAdminMeta } from "../meta-gate.js";
4
4
  import { listPhotos, withPhotos } from "../../live/row-photos.js";
5
5
  import { displayRow } from "./list-displays.js";
6
- import { useAdminMeta } from "../meta-gate.js";
6
+ import { listLabels, withLabels } from "../../live/relation-labels.js";
7
7
  import { areaFilter, requestZoom } from "../map/map-area.js";
8
8
  import { keepPreviousData, useQuery } from "@tanstack/react-query";
9
9
  //#region src/app/displays/use-map-records.ts
@@ -1,10 +1,10 @@
1
1
  import { humanize } from "../../live/naming.js";
2
2
  import { displayRow } from "../displays/list-displays.js";
3
- import { columnsFromModel } from "../../live/columns-from-model.js";
4
3
  import { scopeOf } from "../pages/use-layout-data.js";
5
- import { withFieldTypeCells } from "../field-types/field-type-cells.js";
4
+ import { columnsFromModel } from "../../live/columns-from-model.js";
6
5
  import { FieldValue } from "../pages/field-value.js";
7
6
  import { usePathValue } from "../pages/use-path-value.js";
7
+ import { withFieldTypeCells } from "../field-types/field-type-cells.js";
8
8
  import { jsx } from "react/jsx-runtime";
9
9
  import { resolveFieldPath } from "@protobase/layout";
10
10
  //#region src/app/embedded-list/embedded-columns.tsx
@@ -3,19 +3,19 @@ import { Skeleton } from "../../primitives/skeleton.js";
3
3
  import { recordKey } from "../../live/model-helpers.js";
4
4
  import { humanize } from "../../live/naming.js";
5
5
  import { useProjectUi } from "../pages/project-ui.js";
6
+ import { useAdminMeta } from "../meta-gate.js";
6
7
  import { displayRow, loadedFields, shownDisplay } from "../displays/list-displays.js";
7
8
  import { defaultOrderBy, parseSort, sortToOrderBy } from "../list-order.js";
8
- import { useAdminMeta } from "../meta-gate.js";
9
9
  import { AppLink, useAppAddress } from "../use-app-address.js";
10
10
  import { ErrorBanner } from "../error-banner.js";
11
- import { DataGrid } from "../../data-grid/data-grid.js";
12
11
  import { listColumnNames } from "../../live/columns-from-model.js";
13
- import { MapDisplay } from "../displays/map-display.js";
12
+ import { DataGrid } from "../../data-grid/data-grid.js";
13
+ import { viewAllAddress } from "./embedded-display.js";
14
+ import { CardsDisplay } from "../displays/cards-display.js";
14
15
  import { CustomDisplay } from "../displays/custom-display.js";
16
+ import { MapDisplay } from "../displays/map-display.js";
15
17
  import { DisplaySwitch } from "../displays/display-controls.js";
16
- import { CardsDisplay } from "../displays/cards-display.js";
17
18
  import { embeddedColumns } from "./embedded-columns.js";
18
- import { viewAllAddress } from "./embedded-display.js";
19
19
  import { useEmbeddedRows } from "./use-embedded-rows.js";
20
20
  import { useMemo, useState } from "react";
21
21
  import { ArrowRight } from "lucide-react";
@@ -1,9 +1,9 @@
1
1
  import { keys } from "../../data/query-keys.js";
2
2
  import { useClient } from "../../data/api-provider.js";
3
3
  import { recordId } from "../../live/model-helpers.js";
4
- import { listLabels, withLabels } from "../../live/relation-labels.js";
5
- import { listPhotos, withPhotos } from "../../live/row-photos.js";
6
4
  import { useAdminMeta } from "../meta-gate.js";
5
+ import { listPhotos, withPhotos } from "../../live/row-photos.js";
6
+ import { listLabels, withLabels } from "../../live/relation-labels.js";
7
7
  import { keepPreviousData, useQuery } from "@tanstack/react-query";
8
8
  //#region src/app/embedded-list/use-embedded-rows.ts
9
9
  /**
@@ -1,15 +1,19 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { Input } from "../primitives/input.js";
3
- import { Switch } from "../primitives/switch.js";
4
- import { Textarea } from "../primitives/textarea.js";
3
+ import { humanize } from "../live/naming.js";
5
4
  import { displayValue } from "../live/display-value.js";
6
5
  import { AppLink } from "./use-app-address.js";
7
- import { RelationPicker } from "./relation-picker.js";
6
+ import { useShownWidget } from "./field-types/shown-widget.js";
7
+ import { WidgetPart } from "./field-types/widget-part.js";
8
+ import { isMarkdown } from "../markdown/markdown-hint.js";
9
+ import { Markdown } from "../markdown/markdown.js";
8
10
  import { fromDraft, isEditable, toFieldDraft } from "../live/field-values.js";
11
+ import { Switch } from "../primitives/switch.js";
12
+ import { Textarea } from "../primitives/textarea.js";
13
+ import { RelationPicker } from "./relation-picker.js";
9
14
  import { SensitiveField } from "./sensitive-field.js";
10
15
  import { FileField } from "./file-field.js";
11
- import { useShownWidget } from "./field-types/shown-widget.js";
12
- import { WidgetPart } from "./field-types/widget-part.js";
16
+ import { MarkdownEditor } from "../markdown/markdown-editor.js";
13
17
  import { jsx, jsxs } from "react/jsx-runtime";
14
18
  import { enumLabel } from "@protobase/schema";
15
19
  //#region src/app/field-editor.tsx
@@ -70,6 +74,11 @@ const BuiltInEditor = ({ field, hints, value, stored, relationLabel, relationTo,
70
74
  });
71
75
  }
72
76
  const multiline = Boolean(hints?.multiline) && field.type === "text";
77
+ const markdown = isMarkdown(field, hints);
78
+ if ((!isEditable(field) || locked) && markdown && typeof stored === "string" && stored !== "") return /* @__PURE__ */ jsx(Markdown, {
79
+ source: stored,
80
+ className: "py-1.5"
81
+ });
73
82
  if (!isEditable(field) || locked) return /* @__PURE__ */ jsx("div", {
74
83
  className: cn("py-1.5 tabular-nums", field.type === "decimal" || field.type === "integer" ? "font-medium" : "text-muted-foreground", multiline && "whitespace-pre-line break-words"),
75
84
  children: displayValue(field, stored, hints)
@@ -86,6 +95,13 @@ const BuiltInEditor = ({ field, hints, value, stored, relationLabel, relationTo,
86
95
  children: enumLabel(option, hints?.valueLabels)
87
96
  }, option))]
88
97
  });
98
+ if (markdown) return /* @__PURE__ */ jsx(MarkdownEditor, {
99
+ label: hints?.label ?? humanize(field.name),
100
+ invalid,
101
+ placeholder,
102
+ value: String(value),
103
+ onChange
104
+ });
89
105
  if (multiline) return /* @__PURE__ */ jsx(Textarea, {
90
106
  invalid,
91
107
  placeholder,
@@ -1,8 +1,8 @@
1
1
  import { cn } from "../../lib/cn.js";
2
2
  import { Skeleton } from "../../primitives/skeleton.js";
3
3
  import { humanize } from "../../live/naming.js";
4
- import { shownImage } from "../../live/file-values.js";
5
4
  import { useAdminMeta, usePermissions } from "../meta-gate.js";
5
+ import { shownImage } from "../../live/file-values.js";
6
6
  import { ErrorBanner } from "../error-banner.js";
7
7
  import { FileImage } from "../../live/file-image.js";
8
8
  import { Section } from "../../record-view/section.js";
@@ -1,11 +1,11 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
2
  import { useRecord } from "../../data/use-record.js";
3
3
  import { humanize } from "../../live/naming.js";
4
+ import { fieldAccess } from "../permissions.js";
4
5
  import { AppLink } from "../use-app-address.js";
5
6
  import { ErrorBanner } from "../error-banner.js";
6
- import { fieldAccess } from "../permissions.js";
7
- import { SpecTable } from "../../record-view/spec-table.js";
8
7
  import { FieldValue } from "../pages/field-value.js";
8
+ import { SpecTable } from "../../record-view/spec-table.js";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/app/gallery/photo-details.tsx
11
11
  /**
@@ -2,47 +2,48 @@ import { useIsDesktop } from "../lib/use-media-query.js";
2
2
  import { usePageScroll } from "../app-shell/page-scroll.js";
3
3
  import { PageHeader } from "../app-shell/page-header.js";
4
4
  import { useAuth } from "../auth/auth-provider.js";
5
- import { Button } from "../primitives/button.js";
5
+ import { Button, buttonClass } from "../primitives/button.js";
6
6
  import { recordKey, recordTitleField } from "../live/model-helpers.js";
7
7
  import { humanize } from "../live/naming.js";
8
8
  import { useProjectUi } from "./pages/project-ui.js";
9
+ import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
10
+ import { can, rowAllows } from "./permissions.js";
9
11
  import { loadedFields, shownDisplay } from "./displays/list-displays.js";
10
12
  import { parseSort, sortToOrderBy } from "./list-order.js";
11
13
  import { listedResource } from "./current-address.js";
12
- import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
13
- import { useAppAddress } from "./use-app-address.js";
14
+ import { AppLink, useAppAddress } from "./use-app-address.js";
14
15
  import { ErrorBanner } from "./error-banner.js";
15
- import { can, rowAllows } from "./permissions.js";
16
+ import { columnsFromModel } from "../live/columns-from-model.js";
16
17
  import { pinnedStorageKey } from "../data-grid/pinned-storage.js";
17
18
  import { DataGrid } from "../data-grid/data-grid.js";
19
+ import { useLiveRows } from "../live/use-live-rows.js";
18
20
  import { ActiveFilterChips } from "../filter-panel/active-filter-chips.js";
19
21
  import { activeChips, emptyFilterState, removeChip } from "../filter-panel/filter-state.js";
20
22
  import { FilterSection } from "../filter-panel/filter-section.js";
21
23
  import { ToolbarRow } from "../filter-panel/toolbar-row.js";
22
24
  import { FilterPanel } from "../filter-panel/filter-panel.js";
23
- import { columnsFromModel } from "../live/columns-from-model.js";
24
25
  import { stateToText, textToState } from "../live/filter-string.js";
25
26
  import { widgetFields } from "../live/view-fields.js";
26
- import { useFilterConfig } from "../live/use-filter-config.js";
27
- import { useLiveRows } from "../live/use-live-rows.js";
27
+ import { facetNames, filterChips } from "./searches/saved-search.js";
28
28
  import { Sheet } from "../primitives/sheet.js";
29
+ import { SignInPrompt } from "../auth/sign-in-prompt.js";
30
+ import { CustomDisplay } from "./displays/custom-display.js";
31
+ import { MapDisplay } from "./displays/map-display.js";
32
+ import { DisplaySwitch, SortMenu } from "./displays/display-controls.js";
33
+ import { withFieldTypeCells } from "./field-types/field-type-cells.js";
34
+ import { useFilterConfig } from "../live/use-filter-config.js";
29
35
  import { useDeleteFlow } from "./delete/use-delete-flow.js";
30
36
  import { useRestore } from "./delete/use-restore.js";
37
+ import { createOffered } from "./create/create-offered.js";
31
38
  import { ImportButton } from "./import/import-button.js";
32
39
  import { exportOffered, exportsSelection } from "./export/export-options.js";
33
40
  import { useExportFlow } from "./export/use-export-flow.js";
34
- import { SignInPrompt } from "../auth/sign-in-prompt.js";
35
41
  import { ListCards } from "./displays/list-cards.js";
36
- import { MapDisplay } from "./displays/map-display.js";
37
- import { CustomDisplay } from "./displays/custom-display.js";
38
- import { DisplaySwitch, SortMenu } from "./displays/display-controls.js";
39
42
  import { useDisplayRows } from "./displays/use-display-rows.js";
40
43
  import { useListScroll } from "./list-scroll.js";
41
44
  import { ListChart } from "./list-chart.js";
42
45
  import { ListSearch } from "./list-search.js";
43
- import { withFieldTypeCells } from "./field-types/field-type-cells.js";
44
46
  import { SavedList } from "./saved/saved-list.js";
45
- import { facetNames, filterChips } from "./searches/saved-search.js";
46
47
  import { SaveSearchButton } from "./searches/save-search-button.js";
47
48
  import { SavedSearches } from "./searches/saved-searches.js";
48
49
  import { useMemo, useState } from "react";
@@ -254,12 +255,12 @@ const LoadedList = ({ model, address, scope, heading }) => {
254
255
  })
255
256
  ]
256
257
  });
257
- const createButton = !scope && can(permissions, "create") && /* @__PURE__ */ jsxs(Button, {
258
- variant: "primary",
259
- onClick: () => go({
258
+ const createButton = !scope && createOffered(view, meta.pages, permissions) && /* @__PURE__ */ jsxs(AppLink, {
259
+ to: {
260
260
  kind: "create",
261
261
  resource
262
- }),
262
+ },
263
+ className: buttonClass({ variant: "primary" }),
263
264
  children: [
264
265
  /* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
265
266
  "New ",
@@ -1,10 +1,10 @@
1
1
  import { Skeleton } from "../../../primitives/skeleton.js";
2
2
  import { useAdminMeta } from "../../meta-gate.js";
3
- import { ErrorBanner } from "../../error-banner.js";
4
- import { scopeOf, useListPage } from "../use-layout-data.js";
5
3
  import { RecordScope } from "../record-scope.js";
4
+ import { scopeOf, useListPage } from "../use-layout-data.js";
6
5
  import { propsOf } from "./block-props.js";
7
6
  import { CardFrame, Quiet } from "./card-frame.js";
7
+ import { ErrorBanner } from "../../error-banner.js";
8
8
  import { jsx } from "react/jsx-runtime";
9
9
  //#region src/app/pages/blocks/card-row.tsx
10
10
  const LoadedCardRow = ({ model, filter, sort, limit = 12, title, description, empty, actions, children }) => {
@@ -1,10 +1,10 @@
1
1
  import { Skeleton } from "../../../primitives/skeleton.js";
2
2
  import { humanize } from "../../../live/naming.js";
3
3
  import { useAdminMeta } from "../../meta-gate.js";
4
- import { FieldValue } from "../field-value.js";
5
- import { usePathValue } from "../use-path-value.js";
6
4
  import { useLayoutRecord } from "../record-scope.js";
7
5
  import { propsOf } from "./block-props.js";
6
+ import { FieldValue } from "../field-value.js";
7
+ import { usePathValue } from "../use-path-value.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/app/pages/blocks/field.tsx
10
10
  /** The view's label for the field at the end of the path, else the path in words: `companyId.city` is "Company city". */