@rebasepro/cms 0.23.0 → 0.24.0

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 (62) hide show
  1. package/dist/{CollectionEditorDialog-DLHCka00.js → CollectionEditorDialog-p5-I-Apu.js} +81 -42
  2. package/dist/CollectionEditorDialog-p5-I-Apu.js.map +1 -0
  3. package/dist/{PropertyEditView-_pu5hzZn.js → PropertyEditView-DDB_X-00.js} +4 -4
  4. package/dist/PropertyEditView-DDB_X-00.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-ChPNl_uU.js → RouterCollectionsStudioView-SGBV4CMg.js} +4 -4
  6. package/dist/{RouterCollectionsStudioView-ChPNl_uU.js.map → RouterCollectionsStudioView-SGBV4CMg.js.map} +1 -1
  7. package/dist/{SchemaChangeDialog-BeP0A4MJ.js → SchemaChangeDialog-CcFSXvkK.js} +33 -12
  8. package/dist/SchemaChangeDialog-CcFSXvkK.js.map +1 -0
  9. package/dist/collection_editor/_cms_internals.d.ts +2 -1
  10. package/dist/collection_editor/liveSchemaClient.d.ts +38 -18
  11. package/dist/collection_editor/serializable_types.d.ts +2 -0
  12. package/dist/collection_editor/types/config_controller.d.ts +11 -0
  13. package/dist/collection_editor/useLiveSchemaEditing.d.ts +5 -5
  14. package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +7 -0
  15. package/dist/collection_editor_ui.js +5 -5
  16. package/dist/components/ClearFilterSortButton.d.ts +7 -1
  17. package/dist/components/CollectionViewBinding/CollectionViewActions.d.ts +6 -1
  18. package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +5 -2
  19. package/dist/components/CollectionViewBinding/FiltersDialog.d.ts +5 -0
  20. package/dist/components/EditViewBinding.d.ts +7 -1
  21. package/dist/components/EntityIdentityBar.d.ts +29 -4
  22. package/dist/components/NavigationLoadingErrorView.d.ts +16 -0
  23. package/dist/components/common/ActionMenuLabel.d.ts +15 -0
  24. package/dist/components/common/default_entity_actions.d.ts +37 -0
  25. package/dist/data_import/components/ImportConversionProblems.d.ts +11 -0
  26. package/dist/data_import/components/index.d.ts +1 -0
  27. package/dist/data_import/utils/data.d.ts +81 -4
  28. package/dist/data_import/utils/import_columns.d.ts +48 -0
  29. package/dist/editor/editor.d.ts +7 -1
  30. package/dist/editor/extensions/Image.d.ts +22 -1
  31. package/dist/editor/hooks/useProseMirror.d.ts +4 -1
  32. package/dist/editor.js +69 -7
  33. package/dist/editor.js.map +1 -1
  34. package/dist/{export-4bJWdlDK.js → export-DCHuPD1a.js} +4 -4
  35. package/dist/export-DCHuPD1a.js.map +1 -0
  36. package/dist/form/form_utils.d.ts +24 -0
  37. package/dist/form/resolve_form_properties.d.ts +26 -0
  38. package/dist/{history-BElrZ5Rw.js → history-CBd6SBRs.js} +3 -2
  39. package/dist/{history-BElrZ5Rw.js.map → history-CBd6SBRs.js.map} +1 -1
  40. package/dist/hooks/useRecordActions.d.ts +28 -6
  41. package/dist/hooks/useReferencePaths.d.ts +21 -0
  42. package/dist/hooks/useStorageReferenceResolver.d.ts +23 -0
  43. package/dist/{import-TCgkTVlS.js → import-DjQoRrIU.js} +34 -52
  44. package/dist/import-DjQoRrIU.js.map +1 -0
  45. package/dist/index.js +189 -115
  46. package/dist/index.js.map +1 -1
  47. package/dist/preview/components/StorageMarkdown.d.ts +10 -0
  48. package/dist/preview/util.d.ts +4 -0
  49. package/dist/util/caller_scopes.d.ts +15 -0
  50. package/dist/util/entity_actions.d.ts +37 -1
  51. package/dist/util/fit_inline_actions.d.ts +20 -0
  52. package/dist/{util-DybQBkug.js → util-BRvKFMLa.js} +4546 -3163
  53. package/dist/util-BRvKFMLa.js.map +1 -0
  54. package/dist/{util-CC0ozHoD.js → util-D7ebttKT.js} +6 -2
  55. package/dist/{util-CC0ozHoD.js.map → util-D7ebttKT.js.map} +1 -1
  56. package/package.json +9 -9
  57. package/dist/CollectionEditorDialog-DLHCka00.js.map +0 -1
  58. package/dist/PropertyEditView-_pu5hzZn.js.map +0 -1
  59. package/dist/SchemaChangeDialog-BeP0A4MJ.js.map +0 -1
  60. package/dist/export-4bJWdlDK.js.map +0 -1
  61. package/dist/import-TCgkTVlS.js.map +0 -1
  62. package/dist/util-DybQBkug.js.map +0 -1
@@ -1,6 +1,24 @@
1
1
  import type { AdminCollection } from "@rebasepro/cms-types";
2
2
  import type { EntityStatus } from "@rebasepro/types";
3
3
  import React from "react";
4
+ import type { PlacedEntityActions } from "../util/entity_actions.js";
5
+ /**
6
+ * One of the record's own actions, resolved and ready to run — what the bar
7
+ * needs to draw it as a button or as a menu item.
8
+ */
9
+ export interface RecordActionItem {
10
+ key: string;
11
+ name: string;
12
+ icon?: React.ReactNode;
13
+ enabled: boolean;
14
+ /** Why it is disabled, when it is and its action says. */
15
+ disabledReason?: string;
16
+ /**
17
+ * Runs the action, reporting its own failure. Returns a promise only when
18
+ * the action is asynchronous, which is what keeps a button's spinner up.
19
+ */
20
+ run: () => Promise<void> | undefined;
21
+ }
4
22
  export interface EntityIdentityBarProps {
5
23
  /** Plural collection name, shown as the breadcrumb ahead of the title. */
6
24
  collection: AdminCollection;
@@ -68,10 +86,17 @@ export interface EntityIdentityBarProps {
68
86
  externalLink?: string;
69
87
  /**
70
88
  * Actions performed *on* the record — copy, delete, whatever the collection
71
- * adds — as menu items under the overflow button. They live here rather
72
- * than in a footer, which is what let the footer go away entirely.
89
+ * adds — already placed: `inline` ones are buttons ahead of Save, folding
90
+ * into the overflow menu when the bar runs short of room, and the rest are
91
+ * menu groups. They live here rather than in a footer, which is what let
92
+ * the footer go away entirely.
93
+ */
94
+ recordActions?: PlacedEntityActions<RecordActionItem>;
95
+ /**
96
+ * The read-only view's Edit: its main button, drawn where Save stands on
97
+ * the edit view — after the record's own buttons, ahead of the menu.
73
98
  */
74
- recordActions?: React.ReactNode;
99
+ primaryAction?: React.ReactNode;
75
100
  /** Plugin-contributed actions, rendered inline before Save. */
76
101
  pluginActions?: React.ReactNode;
77
102
  /** Close / full-screen buttons supplied by the panel that owns this view. */
@@ -92,7 +117,7 @@ export interface EntityIdentityBarProps {
92
117
  * editing. They live here instead, and the left half of a bar that was
93
118
  * previously empty now does the work.
94
119
  */
95
- export declare function EntityIdentityBar({ collection, collectionUrl, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, saveAndClosePlacement, onClose, onBack, onInspect, onViewHistory, externalLink, recordActions, pluginActions, trailing, leading }: EntityIdentityBarProps): React.JSX.Element;
120
+ export declare function EntityIdentityBar({ collection, collectionUrl, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, saveAndClosePlacement, onClose, onBack, onInspect, onViewHistory, externalLink, recordActions, primaryAction, pluginActions, trailing, leading }: EntityIdentityBarProps): React.JSX.Element;
96
121
  export type EntitySaveActionsProps = Pick<EntityIdentityBarProps, "status" | "dirty" | "saving" | "onSave" | "onDiscard" | "saveDisabled" | "hasErrors" | "onSaveAndClose" | "saveAndClosePlacement">;
97
122
  /**
98
123
  * Save, "save and close", Discard, and the words saying where the edit stands.
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ /**
3
+ * The collections or views could not be resolved: a `collections` or `views`
4
+ * builder threw, or a plugin's `modifyCollections` did, or something either of
5
+ * them awaited failed.
6
+ *
7
+ * That failure used to be caught, logged and stored as
8
+ * `navigationLoadingError`, and then read by nothing: the home page rendered
9
+ * an empty search bar and a deep link to a collection spun for ever, or said
10
+ * the collection was not registered — the wrong cause. This says what failed
11
+ * and offers the retry the navigation controller already had.
12
+ */
13
+ export declare function NavigationLoadingErrorView({ error, onRetry }: {
14
+ error: Error;
15
+ onRetry?: () => void;
16
+ }): React.JSX.Element;
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ /**
3
+ * An action's name in a menu and, when it is unavailable and says why, the
4
+ * reason on a line beneath it.
5
+ *
6
+ * In the text rather than a tooltip: a disabled menu item cannot be focused or
7
+ * hovered into anything, and on a touch screen there is no hover at all — the
8
+ * reason has to be readable where the item is. It is drawn in the label's own
9
+ * ink, a size down, because the item dims everything it holds: a muted colour
10
+ * under that dimming fell below legible.
11
+ */
12
+ export declare function ActionMenuLabel({ name, disabledReason }: {
13
+ name: string;
14
+ disabledReason?: string;
15
+ }): React.JSX.Element;
@@ -15,3 +15,40 @@ export declare function ResetPasswordActionDialog({ user, open, onClose }: {
15
15
  onClose: () => void;
16
16
  }): React.JSX.Element;
17
17
  export declare const resetPasswordAction: EntityAction;
18
+ /** A user, as the two dialogs below need one. */
19
+ interface AdminActionUser {
20
+ uid: string;
21
+ email: string;
22
+ }
23
+ /**
24
+ * Remove every second factor and recovery code an account has, and end its
25
+ * sessions — for someone who lost their authenticator and their codes.
26
+ */
27
+ export declare function ResetMfaActionDialog({ user, open, onClose }: {
28
+ user: AdminActionUser;
29
+ open: boolean;
30
+ onClose: () => void;
31
+ }): React.JSX.Element;
32
+ /**
33
+ * Switch an account off, or back on. Off, it signs in nowhere and every session
34
+ * and token it holds ends; the switch reads the account's state first, because
35
+ * the users table does not carry it.
36
+ */
37
+ export declare function AccountAccessDialog({ user, open, onClose }: {
38
+ user: AdminActionUser;
39
+ open: boolean;
40
+ onClose: () => void;
41
+ }): React.JSX.Element;
42
+ /**
43
+ * Reset a user's two-step verification. Offered to whoever holds
44
+ * `users:write`, as `DELETE /admin/users/:uid/mfa` is; the route also refuses
45
+ * an account that outranks the caller, and the dialog says so.
46
+ */
47
+ export declare const resetMfaAction: EntityAction;
48
+ /**
49
+ * Switch an account off or on. Offered to whoever holds `users:write`, as
50
+ * `PUT /admin/users/:uid { disabled }` is, and never on the caller's own
51
+ * account, which the route refuses.
52
+ */
53
+ export declare const accountAccessAction: EntityAction;
54
+ export {};
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { ImportConversionProblem } from "../utils/data.js";
3
+ /**
4
+ * The cells of an import that do not convert to the type of the field they
5
+ * map to — per column, how many, and the first few with their row — shown in
6
+ * the preview, before anything is written. Those cells are left out of the
7
+ * rows; nothing is turned into `null`, `0` or `false` without saying so.
8
+ */
9
+ export declare function ImportConversionProblems({ problems }: {
10
+ problems: ImportConversionProblem[];
11
+ }): React.JSX.Element | null;
@@ -2,3 +2,4 @@ export * from "./DataNewPropertiesMapping.js";
2
2
  export * from "./ImportFileUpload.js";
3
3
  export * from "./ImportNewPropertyFieldPreview.js";
4
4
  export * from "./ImportSaveInProgress.js";
5
+ export * from "./ImportConversionProblems.js";
@@ -1,5 +1,82 @@
1
- import { Entity, CollectionRegistryController, Properties, Property } from "@rebasepro/types";
1
+ import { DataType, Entity, CollectionRegistryController, Properties, Property } from "@rebasepro/types";
2
2
  import { AuthController } from "@rebasepro/cms-types";
3
- export declare function convertDataToEntity(authController: AuthController, navigation: CollectionRegistryController, data: Record<any, any>, idColumn: string | undefined, headersMapping: Record<string, string | null>, properties: Properties, path: string, defaultValues: Record<string, any>): Entity<any>;
4
- export declare function flattenEntry(obj: Record<string, unknown>, parent?: string): Record<string, unknown>;
5
- export declare function processValueMapping(authController: AuthController, value: any, navigation: CollectionRegistryController, property?: Property): any;
3
+ import { ColumnTarget } from "./import_columns.js";
4
+ /** What the conversion asks of the collection registry: the collection a reference points into. */
5
+ export type ImportCollectionLookup = Pick<CollectionRegistryController, "getCollection">;
6
+ /**
7
+ * Why a cell could not be converted to its property's type. Each one is a
8
+ * translation key under `import_problem_`.
9
+ */
10
+ export type ImportProblemReason = "not_a_number" | "number_leading_zero" | "number_too_precise" | "not_a_boolean" | "not_a_date" | "ambiguous_date" | "not_a_vector" | "not_a_map" | "not_a_geopoint";
11
+ /**
12
+ * A cell the import could not convert to the type of the property it maps
13
+ * to. It is left out of the row — the property's default applies — and shown
14
+ * to the person importing before anything is written.
15
+ */
16
+ export type ImportConversionProblem = {
17
+ /** The row's index among the file's data rows, from 0. */
18
+ row: number;
19
+ /** The column, as the file names it. */
20
+ column: string;
21
+ /** The property the column maps to. */
22
+ property: string;
23
+ /** The type that property holds. */
24
+ expected: DataType;
25
+ /** The cell, as it was read from the file. */
26
+ value: unknown;
27
+ reason: ImportProblemReason;
28
+ };
29
+ /** Reports one cell that did not convert; the cell is then left out. */
30
+ export type ImportProblemListener = (problem: {
31
+ reason: ImportProblemReason;
32
+ }) => void;
33
+ /**
34
+ * How a column writes the dates whose day and month are both numbers
35
+ * (`05/01/2024`): day first, month first, or — when the column holds both —
36
+ * each date by the only order that can read it.
37
+ */
38
+ export type SlashDateOrder = "dmy" | "mdy" | "each";
39
+ /**
40
+ * Convert every row of an imported file, and list each cell that did not
41
+ * convert. The previews call this, so the list is in front of the person
42
+ * importing before the first row is written.
43
+ */
44
+ export declare function convertImportData(authController: AuthController, navigation: ImportCollectionLookup, data: object[], idColumn: string | undefined, headersMapping: Record<string, string | null>, properties: Properties, path: string, defaultValues: Record<string, any>): {
45
+ entities: Entity<any>[];
46
+ problems: ImportConversionProblem[];
47
+ };
48
+ export declare function convertDataToEntity(authController: AuthController, navigation: ImportCollectionLookup, data: Record<any, any>, idColumn: string | undefined, headersMapping: Record<string, string | null>, properties: Properties, path: string, defaultValues: Record<string, any>, onProblem?: (problem: Omit<ImportConversionProblem, "row">) => void, dateOrders?: Record<string, SlashDateOrder | undefined>): Entity<any>;
49
+ /**
50
+ * Where a column lands under the import's mapping: the property the mapping
51
+ * names for it, `null` for "Do not import this property", and the property
52
+ * with the column's own name when the mapping does not mention the column.
53
+ */
54
+ export declare function mappedColumnTarget(headersMapping: Record<string, string | null>): ColumnTarget;
55
+ /**
56
+ * The cells of one imported row by column name (`address.street`): each
57
+ * value that is not split into columns of its own (see `importColumns`).
58
+ * Without `properties`, every object is split.
59
+ */
60
+ export declare function flattenEntry(obj: Record<string, unknown>, properties?: Properties, targetOf?: ColumnTarget): Record<string, unknown>;
61
+ /**
62
+ * Convert one cell to the type of the property it lands in.
63
+ *
64
+ * A cell is converted only when the property's type can hold what it says:
65
+ * text becomes a number when it spells one exactly (`readNumberExactly`, the
66
+ * same answer the inference uses), a boolean when it says yes or no, a date
67
+ * when it names a day. Anything else is reported to `onProblem`, and the
68
+ * result is `null` — the caller leaves such a cell out rather than writing
69
+ * that `null`, a `0` or a `false` in its place.
70
+ *
71
+ * @param dateOrder how the cell's column writes day/month dates, when it
72
+ * writes them with numbers only (see `slashDateOrdersByColumn`).
73
+ */
74
+ export declare function processValueMapping(authController: AuthController, value: any, navigation: ImportCollectionLookup, property?: Property, onProblem?: ImportProblemListener, dateOrder?: SlashDateOrder): any;
75
+ /**
76
+ * For each column of the file, how it writes day/month dates with numbers
77
+ * only, when its values say: a first number past 12 is a day, so the column
78
+ * is day first; a second number past 12, month first; both, and each date
79
+ * is read by the only order that fits it. A column that never says is read
80
+ * in the order of the browser's locale.
81
+ */
82
+ export declare function slashDateOrdersByColumn(data: object[], properties?: Properties, targetOf?: ColumnTarget): Record<string, SlashDateOrder | undefined>;
@@ -0,0 +1,48 @@
1
+ import { Properties, Property } from "@rebasepro/types";
2
+ /**
3
+ * Whether a property's value is an object the import takes whole, from one
4
+ * column: a geopoint, a vector, or a map that declares no fields (a key-value
5
+ * map). These are the values the export writes as objects — in a JSON file
6
+ * as the object, in a CSV cell as its JSON — and splitting one into
7
+ * `location.latitude` columns left it in columns no property holds: it was
8
+ * dropped without a word.
9
+ *
10
+ * Any other object is read one column per key: a map with declared fields
11
+ * field by field, as the export writes it, and an object no property takes
12
+ * so that its keys can be mapped one by one.
13
+ */
14
+ export declare function takesObjectWhole(property: Property): boolean;
15
+ /** One column of an imported row, as the mapping step lists it. */
16
+ export type ImportColumn = {
17
+ /** The column's name: a key of the row, or `parent.key` below one. */
18
+ column: string;
19
+ value: unknown;
20
+ /** The property path it lands in, or `null` for none. */
21
+ target: string | null;
22
+ /** Its value is an object that is read as one column per key, listed after it. */
23
+ split: boolean;
24
+ };
25
+ /**
26
+ * Where a column lands: the property path for it, or `null` for none. A
27
+ * column below another is given that parent; a top-level one is not.
28
+ */
29
+ export type ColumnTarget = (column: string, key: string, parent?: Pick<ImportColumn, "column" | "target">) => string | null;
30
+ /**
31
+ * The columns of one imported row, parents before their children.
32
+ *
33
+ * Each key of the row is a column. A key whose value is an object is also
34
+ * read as one column per key of that object (`address.street`) — unless the
35
+ * property it lands in takes the object whole (see {@link takesObjectWhole}).
36
+ *
37
+ * The mapping step lists these columns and the conversion reads them, so both
38
+ * get them from here: a list built one way and rows read another is a mapping
39
+ * whose keys match nothing.
40
+ */
41
+ export declare function importColumns(row: Record<string, unknown>, properties: Properties | undefined, targetOf: ColumnTarget): ImportColumn[];
42
+ /**
43
+ * The mapping the import starts from: each column of the file onto the
44
+ * property with its name, or with its name slugged (`First Name` →
45
+ * `first_name`); a column below a map onto that map's field. A column that
46
+ * matches nothing keeps its own name, for the person importing to map.
47
+ */
48
+ export declare function buildHeadersMappingFromData(rows: object[], properties?: Properties): Record<string, string>;
@@ -12,6 +12,12 @@ export type RichTextEditorProps = {
12
12
  onJsonContentChange?: (content: JSONContent | null) => void;
13
13
  onHtmlContentChange?: (content: string) => void;
14
14
  handleImageUpload: (file: File) => Promise<string>;
15
+ /**
16
+ * Turns an image's stored `src` into a URL to show. `handleImageUpload`
17
+ * returns a `rebase-storage:` reference to keep in the text — a private
18
+ * object's URL expires — and the editor asks this for a URL to display it.
19
+ */
20
+ resolveImageSrc?: (src: string) => Promise<string | null | undefined>;
15
21
  version?: number;
16
22
  textSize?: RichTextEditorTextSize;
17
23
  highlight?: {
@@ -22,4 +28,4 @@ export type RichTextEditorProps = {
22
28
  customComponents?: CustomEditorComponent[];
23
29
  disabled?: boolean;
24
30
  };
25
- export declare const RichTextEditor: ({ content, onJsonContentChange, onHtmlContentChange, onMarkdownContentChange, version, textSize, highlight, handleImageUpload, aiController, disabled }: RichTextEditorProps) => React.JSX.Element;
31
+ export declare const RichTextEditor: ({ content, onJsonContentChange, onHtmlContentChange, onMarkdownContentChange, version, textSize, highlight, handleImageUpload, resolveImageSrc, aiController, disabled }: RichTextEditorProps) => React.JSX.Element;
@@ -1,6 +1,27 @@
1
1
  import { EditorView } from "prosemirror-view";
2
- import { Plugin, PluginKey } from "prosemirror-state";
2
+ import { EditorState, Plugin, PluginKey } from "prosemirror-state";
3
3
  export type UploadFn = (image: File) => Promise<string>;
4
+ /**
5
+ * Turns an image's stored `src` into one a browser can load.
6
+ *
7
+ * An uploaded image is stored as a `rebase-storage:` reference rather than a
8
+ * URL — a private object's URL carries a token that expires in minutes — so
9
+ * the editor asks for a URL each time it shows one.
10
+ */
11
+ export type ImageSrcResolver = (src: string) => Promise<string | null | undefined>;
12
+ /**
13
+ * Carries the resolver to the image node views. They render in their own React
14
+ * roots (see `ReactNodeView`), out of reach of any context, so the editor state
15
+ * is where they can find it.
16
+ */
17
+ export declare const createImageSrcResolverPlugin: (resolve: ImageSrcResolver) => Plugin;
18
+ /** The resolver this editor was given, if any. */
19
+ export declare function imageSrcResolverOf(state: EditorState): ImageSrcResolver | undefined;
20
+ /**
21
+ * The URL to load for a stored `src`: a reference resolved, anything else as
22
+ * it is, and `undefined` for a reference this editor cannot resolve.
23
+ */
24
+ export declare function loadableImageSrc(state: EditorState, src: string): Promise<string | undefined>;
4
25
  export declare function onFileRead(view: EditorView, readerEvent: ProgressEvent<FileReader>, pos: number, upload: UploadFn, image: File): Promise<void>;
5
26
  export declare const ImagePluginKey: PluginKey<any>;
6
27
  export declare const createDropImagePlugin: (upload: UploadFn) => Plugin;
@@ -1,11 +1,14 @@
1
1
  import { EditorState } from "prosemirror-state";
2
2
  import { EditorView } from "prosemirror-view";
3
+ import { type ImageSrcResolver } from "../extensions/Image.js";
3
4
  interface UseProseMirrorProps {
4
5
  initialContent?: string | any;
5
6
  editable?: boolean;
6
7
  handleImageUpload?: (file: File) => Promise<string>;
8
+ /** See {@link RichTextEditorProps.resolveImageSrc}. */
9
+ resolveImageSrc?: ImageSrcResolver;
7
10
  }
8
- export declare function useProseMirror({ initialContent, editable, handleImageUpload }: UseProseMirrorProps): {
11
+ export declare function useProseMirror({ initialContent, editable, handleImageUpload, resolveImageSrc }: UseProseMirrorProps): {
9
12
  state: EditorState;
10
13
  view: EditorView | null;
11
14
  editorRef: import("react").RefObject<HTMLDivElement | null>;
package/dist/editor.js CHANGED
@@ -1,9 +1,10 @@
1
- import { o as sanitizeUrl } from "./util-CC0ozHoD.js";
1
+ import { o as sanitizeUrl } from "./util-D7ebttKT.js";
2
2
  import { i as schema, n as parser, r as serializer } from "./markdown-CCqp8ibf.js";
3
3
  import React, { createContext, forwardRef, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { BoldIcon, Button, CheckIcon, CheckSquareIcon, Checkbox, ChevronDownIcon, CodeIcon, Heading1Icon, Heading2Icon, Heading3Icon, IconButton, ImageIcon, ItalicIcon, Label, ListIcon, ListOrderedIcon, Popover, QuoteIcon, Separator, Slot, StrikethroughIcon, TableIcon, TextField, TextareaAutosize, Tooltip, Trash2Icon, TypeIcon, UnderlineIcon, Wand2Icon, cls, defaultBorderMixin, focusedDisabled, iconSize, useInjectStyles } from "@rebasepro/ui";
5
5
  import { useTranslation } from "@rebasepro/app";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { isStorageReference } from "@rebasepro/types";
7
8
  import { EditorState, NodeSelection, Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state";
8
9
  import { autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
9
10
  import { DOMParser as DOMParser$1, DOMSerializer, Fragment as Fragment$1 } from "prosemirror-model";
@@ -167,6 +168,35 @@ function slashCommandPlugin() {
167
168
  }
168
169
  //#endregion
169
170
  //#region src/editor/extensions/Image.ts
171
+ var ImageSrcResolverKey = new PluginKey("imageSrcResolver");
172
+ /**
173
+ * Carries the resolver to the image node views. They render in their own React
174
+ * roots (see `ReactNodeView`), out of reach of any context, so the editor state
175
+ * is where they can find it.
176
+ */
177
+ var createImageSrcResolverPlugin = (resolve) => new Plugin({
178
+ key: ImageSrcResolverKey,
179
+ state: {
180
+ init: () => resolve,
181
+ apply: (_tr, value) => value
182
+ }
183
+ });
184
+ /** The resolver this editor was given, if any. */
185
+ function imageSrcResolverOf(state) {
186
+ return ImageSrcResolverKey.getState(state);
187
+ }
188
+ /**
189
+ * The URL to load for a stored `src`: a reference resolved, anything else as
190
+ * it is, and `undefined` for a reference this editor cannot resolve.
191
+ */
192
+ async function loadableImageSrc(state, src) {
193
+ if (!isStorageReference(src)) return src;
194
+ try {
195
+ return await imageSrcResolverOf(state)?.(src) ?? void 0;
196
+ } catch {
197
+ return;
198
+ }
199
+ }
170
200
  async function onFileRead(view, readerEvent, pos, upload, image) {
171
201
  const { schema } = view.state;
172
202
  const plugin = view.state.plugins.find((p) => p.spec.key === ImagePluginKey);
@@ -200,8 +230,13 @@ async function onFileRead(view, readerEvent, pos, upload, image) {
200
230
  tr.setMeta(plugin, { decorationSet: currentDecos });
201
231
  view.dispatch(tr);
202
232
  };
233
+ const preloadUrl = await loadableImageSrc(view.state, src);
234
+ if (!preloadUrl) {
235
+ replacePlaceholder();
236
+ return;
237
+ }
203
238
  const preloader = new Image();
204
- preloader.src = src;
239
+ preloader.src = preloadUrl;
205
240
  preloader.onload = replacePlaceholder;
206
241
  preloader.onerror = replacePlaceholder;
207
242
  }
@@ -1244,6 +1279,7 @@ var TableBubble = forwardRef(({ options, className }, ref) => {
1244
1279
  /* @__PURE__ */ jsx(Tooltip, {
1245
1280
  title: t("add_row_before"),
1246
1281
  children: /* @__PURE__ */ jsx(IconButton, {
1282
+ "aria-label": t("add_row_before"),
1247
1283
  size: "small",
1248
1284
  onClick: () => executeCommand(addRowBefore),
1249
1285
  children: /* @__PURE__ */ jsxs("svg", {
@@ -1289,6 +1325,7 @@ var TableBubble = forwardRef(({ options, className }, ref) => {
1289
1325
  /* @__PURE__ */ jsx(Tooltip, {
1290
1326
  title: t("add_row_after"),
1291
1327
  children: /* @__PURE__ */ jsx(IconButton, {
1328
+ "aria-label": t("add_row_after"),
1292
1329
  size: "small",
1293
1330
  onClick: () => executeCommand(addRowAfter),
1294
1331
  children: /* @__PURE__ */ jsxs("svg", {
@@ -1334,6 +1371,7 @@ var TableBubble = forwardRef(({ options, className }, ref) => {
1334
1371
  /* @__PURE__ */ jsx(Tooltip, {
1335
1372
  title: t("delete_row"),
1336
1373
  children: /* @__PURE__ */ jsx(IconButton, {
1374
+ "aria-label": t("delete_row"),
1337
1375
  size: "small",
1338
1376
  onClick: () => executeCommand(deleteRow),
1339
1377
  children: /* @__PURE__ */ jsxs("svg", {
@@ -1378,6 +1416,7 @@ var TableBubble = forwardRef(({ options, className }, ref) => {
1378
1416
  /* @__PURE__ */ jsx(Tooltip, {
1379
1417
  title: t("add_column_before"),
1380
1418
  children: /* @__PURE__ */ jsx(IconButton, {
1419
+ "aria-label": t("add_column_before"),
1381
1420
  size: "small",
1382
1421
  onClick: () => executeCommand(addColumnBefore),
1383
1422
  children: /* @__PURE__ */ jsxs("svg", {
@@ -1423,6 +1462,7 @@ var TableBubble = forwardRef(({ options, className }, ref) => {
1423
1462
  /* @__PURE__ */ jsx(Tooltip, {
1424
1463
  title: t("add_column_after"),
1425
1464
  children: /* @__PURE__ */ jsx(IconButton, {
1465
+ "aria-label": t("add_column_after"),
1426
1466
  size: "small",
1427
1467
  onClick: () => executeCommand(addColumnAfter),
1428
1468
  children: /* @__PURE__ */ jsxs("svg", {
@@ -1468,6 +1508,7 @@ var TableBubble = forwardRef(({ options, className }, ref) => {
1468
1508
  /* @__PURE__ */ jsx(Tooltip, {
1469
1509
  title: t("delete_column"),
1470
1510
  children: /* @__PURE__ */ jsx(IconButton, {
1511
+ "aria-label": t("delete_column"),
1471
1512
  size: "small",
1472
1513
  onClick: () => executeCommand(deleteColumn),
1473
1514
  children: /* @__PURE__ */ jsxs("svg", {
@@ -1509,6 +1550,7 @@ var TableBubble = forwardRef(({ options, className }, ref) => {
1509
1550
  /* @__PURE__ */ jsx(Tooltip, {
1510
1551
  title: t("delete_table"),
1511
1552
  children: /* @__PURE__ */ jsx(IconButton, {
1553
+ "aria-label": t("delete_table"),
1512
1554
  size: "small",
1513
1555
  onClick: () => executeCommand(deleteTable),
1514
1556
  children: /* @__PURE__ */ jsxs("svg", {
@@ -2573,8 +2615,24 @@ var TaskItemComponent = ({ node, view, getPos }) => {
2573
2615
  //#region src/editor/nodeViews/ImageComponent.tsx
2574
2616
  var ImageComponent = ({ node, view, getPos }) => {
2575
2617
  const selected = view.state.selection.from === getPos();
2618
+ const stored = node.attrs.src;
2619
+ const [shown, setShown] = useState(isStorageReference(stored) ? void 0 : stored);
2620
+ useEffect(() => {
2621
+ let live = true;
2622
+ if (!isStorageReference(stored)) {
2623
+ setShown(stored);
2624
+ return;
2625
+ }
2626
+ setShown(void 0);
2627
+ loadableImageSrc(view.state, stored).then((url) => {
2628
+ if (live) setShown(url);
2629
+ });
2630
+ return () => {
2631
+ live = false;
2632
+ };
2633
+ }, [stored, view]);
2576
2634
  return /* @__PURE__ */ jsx("img", {
2577
- src: node.attrs.src,
2635
+ src: shown,
2578
2636
  alt: node.attrs.alt || "",
2579
2637
  title: node.attrs.title || "",
2580
2638
  className: cls("rounded-lg max-w-full !m-0", selected ? "" : "")
@@ -2602,12 +2660,15 @@ var trailingNodePlugin = new Plugin({ appendTransaction: (_, oldState, newState)
2602
2660
  if (doc.lastChild && doc.lastChild.type.name !== "paragraph") return newState.tr.insert(doc.content.size, newState.schema.nodes.paragraph.create());
2603
2661
  return null;
2604
2662
  } });
2605
- function useProseMirror({ initialContent, editable = true, handleImageUpload }) {
2663
+ function useProseMirror({ initialContent, editable = true, handleImageUpload, resolveImageSrc }) {
2664
+ const resolveImageSrcRef = useRef(resolveImageSrc);
2665
+ resolveImageSrcRef.current = resolveImageSrc;
2606
2666
  const plugins = [
2607
2667
  ...getCorePlugins(),
2608
2668
  columnResizing(),
2609
2669
  tableEditing(),
2610
- trailingNodePlugin
2670
+ trailingNodePlugin,
2671
+ createImageSrcResolverPlugin(async (src) => resolveImageSrcRef.current ? resolveImageSrcRef.current(src) : void 0)
2611
2672
  ];
2612
2673
  if (handleImageUpload) plugins.push(createDropImagePlugin(handleImageUpload));
2613
2674
  const defaultState = EditorState.create({
@@ -2687,7 +2748,7 @@ var proseClasses = {
2687
2748
  "base": "prose-base",
2688
2749
  "lg": "prose-lg"
2689
2750
  };
2690
- var RichTextEditor = ({ content, onJsonContentChange, onHtmlContentChange, onMarkdownContentChange, version, textSize = "base", highlight, handleImageUpload, aiController, disabled }) => {
2751
+ var RichTextEditor = ({ content, onJsonContentChange, onHtmlContentChange, onMarkdownContentChange, version, textSize = "base", highlight, handleImageUpload, resolveImageSrc, aiController, disabled }) => {
2691
2752
  const { t } = useTranslation();
2692
2753
  const [openNode, setOpenNode] = useState(false);
2693
2754
  const [openLink, setOpenLink] = useState(false);
@@ -2751,7 +2812,8 @@ var RichTextEditor = ({ content, onJsonContentChange, onHtmlContentChange, onMar
2751
2812
  const { state, view, editorRef } = useProseMirror({
2752
2813
  initialContent: content,
2753
2814
  editable: !disabled,
2754
- handleImageUpload
2815
+ handleImageUpload,
2816
+ resolveImageSrc
2755
2817
  });
2756
2818
  const versionRef = useRef(version);
2757
2819
  useEffect(() => {