@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.
- package/dist/{CollectionEditorDialog-DLHCka00.js → CollectionEditorDialog-p5-I-Apu.js} +81 -42
- package/dist/CollectionEditorDialog-p5-I-Apu.js.map +1 -0
- package/dist/{PropertyEditView-_pu5hzZn.js → PropertyEditView-DDB_X-00.js} +4 -4
- package/dist/PropertyEditView-DDB_X-00.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-ChPNl_uU.js → RouterCollectionsStudioView-SGBV4CMg.js} +4 -4
- package/dist/{RouterCollectionsStudioView-ChPNl_uU.js.map → RouterCollectionsStudioView-SGBV4CMg.js.map} +1 -1
- package/dist/{SchemaChangeDialog-BeP0A4MJ.js → SchemaChangeDialog-CcFSXvkK.js} +33 -12
- package/dist/SchemaChangeDialog-CcFSXvkK.js.map +1 -0
- package/dist/collection_editor/_cms_internals.d.ts +2 -1
- package/dist/collection_editor/liveSchemaClient.d.ts +38 -18
- package/dist/collection_editor/serializable_types.d.ts +2 -0
- package/dist/collection_editor/types/config_controller.d.ts +11 -0
- package/dist/collection_editor/useLiveSchemaEditing.d.ts +5 -5
- package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +7 -0
- package/dist/collection_editor_ui.js +5 -5
- package/dist/components/ClearFilterSortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/CollectionViewActions.d.ts +6 -1
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +5 -2
- package/dist/components/CollectionViewBinding/FiltersDialog.d.ts +5 -0
- package/dist/components/EditViewBinding.d.ts +7 -1
- package/dist/components/EntityIdentityBar.d.ts +29 -4
- package/dist/components/NavigationLoadingErrorView.d.ts +16 -0
- package/dist/components/common/ActionMenuLabel.d.ts +15 -0
- package/dist/components/common/default_entity_actions.d.ts +37 -0
- package/dist/data_import/components/ImportConversionProblems.d.ts +11 -0
- package/dist/data_import/components/index.d.ts +1 -0
- package/dist/data_import/utils/data.d.ts +81 -4
- package/dist/data_import/utils/import_columns.d.ts +48 -0
- package/dist/editor/editor.d.ts +7 -1
- package/dist/editor/extensions/Image.d.ts +22 -1
- package/dist/editor/hooks/useProseMirror.d.ts +4 -1
- package/dist/editor.js +69 -7
- package/dist/editor.js.map +1 -1
- package/dist/{export-4bJWdlDK.js → export-DCHuPD1a.js} +4 -4
- package/dist/export-DCHuPD1a.js.map +1 -0
- package/dist/form/form_utils.d.ts +24 -0
- package/dist/form/resolve_form_properties.d.ts +26 -0
- package/dist/{history-BElrZ5Rw.js → history-CBd6SBRs.js} +3 -2
- package/dist/{history-BElrZ5Rw.js.map → history-CBd6SBRs.js.map} +1 -1
- package/dist/hooks/useRecordActions.d.ts +28 -6
- package/dist/hooks/useReferencePaths.d.ts +21 -0
- package/dist/hooks/useStorageReferenceResolver.d.ts +23 -0
- package/dist/{import-TCgkTVlS.js → import-DjQoRrIU.js} +34 -52
- package/dist/import-DjQoRrIU.js.map +1 -0
- package/dist/index.js +189 -115
- package/dist/index.js.map +1 -1
- package/dist/preview/components/StorageMarkdown.d.ts +10 -0
- package/dist/preview/util.d.ts +4 -0
- package/dist/util/caller_scopes.d.ts +15 -0
- package/dist/util/entity_actions.d.ts +37 -1
- package/dist/util/fit_inline_actions.d.ts +20 -0
- package/dist/{util-DybQBkug.js → util-BRvKFMLa.js} +4546 -3163
- package/dist/util-BRvKFMLa.js.map +1 -0
- package/dist/{util-CC0ozHoD.js → util-D7ebttKT.js} +6 -2
- package/dist/{util-CC0ozHoD.js.map → util-D7ebttKT.js.map} +1 -1
- package/package.json +9 -9
- package/dist/CollectionEditorDialog-DLHCka00.js.map +0 -1
- package/dist/PropertyEditView-_pu5hzZn.js.map +0 -1
- package/dist/SchemaChangeDialog-BeP0A4MJ.js.map +0 -1
- package/dist/export-4bJWdlDK.js.map +0 -1
- package/dist/import-TCgkTVlS.js.map +0 -1
- 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 —
|
|
72
|
-
*
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
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>;
|
package/dist/editor/editor.d.ts
CHANGED
|
@@ -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-
|
|
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 =
|
|
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:
|
|
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(() => {
|