@rebasepro/admin 0.11.1-canary.gfd39654 → 0.12.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/README.md +1 -1
- package/dist/{CollectionEditorDialog-o7F_zbGY.js → CollectionEditorDialog-Dpc75wIZ.js} +56 -260
- package/dist/CollectionEditorDialog-Dpc75wIZ.js.map +1 -0
- package/dist/{PropertyEditView-DgB2b22q.js → PropertyEditView-DH3XZC2x.js} +2 -2
- package/dist/{PropertyEditView-DgB2b22q.js.map → PropertyEditView-DH3XZC2x.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-DaLRgPkj.js → RouterCollectionsStudioView-BZLq4MGp.js} +4 -4
- package/dist/{RouterCollectionsStudioView-DaLRgPkj.js.map → RouterCollectionsStudioView-BZLq4MGp.js.map} +1 -1
- package/dist/collection_editor/CollectionEditorDialogsContext.d.ts +1 -1
- package/dist/collection_editor/_cms_internals.d.ts +1 -1
- package/dist/collection_editor/extensibility_types.d.ts +1 -1
- package/dist/collection_editor/serializable_types.d.ts +40 -12
- package/dist/collection_editor/serializable_utils.d.ts +1 -1
- package/dist/collection_editor/ui/collection_editor/CollectionRLSTab.d.ts +1 -11
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/CollectionEditorDialogs.d.ts +2 -2
- package/dist/components/CollectionTableBinding/column_utils.d.ts +7 -1
- package/dist/components/CollectionViewBinding/CollectionListViewBinding.d.ts +2 -2
- package/dist/components/DefaultDrawer.d.ts +3 -3
- package/dist/components/DrawerNavigationGroup.d.ts +8 -1
- package/dist/components/DrawerNavigationItem.d.ts +7 -1
- package/dist/components/EditViewBinding.d.ts +1 -1
- package/dist/components/RebaseAdmin.d.ts +1 -1
- package/dist/components/RebaseAuthGate.d.ts +1 -1
- package/dist/components/RebaseLayout.d.ts +5 -1
- package/dist/components/RebaseNavigation.d.ts +2 -2
- package/dist/components/RebaseRouteDefs.d.ts +1 -1
- package/dist/components/RebaseShell.d.ts +6 -1
- package/dist/components/RelationSelector.d.ts +14 -0
- package/dist/components/SelectableTable/filters/RelationFilterField.d.ts +27 -0
- package/dist/components/SelectableTable/filters/null_filter.d.ts +46 -0
- package/dist/components/SidePanelProvider.d.ts +2 -2
- package/dist/components/app/Scaffold.d.ts +11 -0
- package/dist/components/field_configs.d.ts +1 -1
- package/dist/{export-BvqhjdEb.js → export-BzIpUdSK.js} +3 -3
- package/dist/export-BzIpUdSK.js.map +1 -0
- package/dist/form/EntityForm.d.ts +2 -2
- package/dist/form/EntityFormBinding.d.ts +5 -5
- package/dist/{history-Dz20hH38.js → history-g9688IaK.js} +2 -2
- package/dist/{history-Dz20hH38.js.map → history-g9688IaK.js.map} +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/useAdminContext.d.ts +29 -0
- package/dist/hooks/useHistory.d.ts +7 -11
- package/dist/{import-DINSB-Vu.js → import-BjUG-cIY.js} +2 -2
- package/dist/{import-DINSB-Vu.js.map → import-BjUG-cIY.js.map} +1 -1
- package/dist/index.js +61 -38
- package/dist/index.js.map +1 -1
- package/dist/types/components/EntityFormProps.d.ts +1 -1
- package/dist/types/fields.d.ts +1 -1
- package/dist/{util-BhhfHY1M.js → util-B5fJm1FA.js} +296 -169
- package/dist/util-B5fJm1FA.js.map +1 -0
- package/package.json +9 -9
- package/src/collection_editor/CollectionEditorDialogsContext.tsx +1 -1
- package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
- package/src/collection_editor/_cms_internals.ts +1 -1
- package/src/collection_editor/extensibility_types.ts +1 -1
- package/src/collection_editor/index.ts +2 -2
- package/src/collection_editor/serializable_types.ts +44 -13
- package/src/collection_editor/serializable_utils.ts +68 -59
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +66 -41
- package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +2 -2
- package/src/components/CollectionEditorDialogs.tsx +2 -2
- package/src/components/CollectionTableBinding/CollectionRowActions.tsx +5 -4
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +8 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +19 -5
- package/src/components/CollectionTableBinding/table_bindings.tsx +10 -10
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +8 -7
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +2 -2
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +2 -2
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +2 -2
- package/src/components/DefaultDrawer.tsx +19 -8
- package/src/components/DeleteEntityDialog.tsx +2 -2
- package/src/components/DetailViewBinding.tsx +21 -1
- package/src/components/DrawerNavigationGroup.tsx +47 -4
- package/src/components/DrawerNavigationItem.tsx +19 -5
- package/src/components/EditFormActions.tsx +4 -3
- package/src/components/EditViewBinding.tsx +1 -1
- package/src/components/HomePage/ContentHomePage.tsx +4 -4
- package/src/components/RebaseAdmin.tsx +3 -3
- package/src/components/RebaseAuthGate.tsx +1 -1
- package/src/components/RebaseLayout.tsx +8 -2
- package/src/components/RebaseNavigation.tsx +9 -9
- package/src/components/RebaseRouteDefs.tsx +2 -2
- package/src/components/RebaseShell.tsx +10 -1
- package/src/components/RelationSelector.tsx +18 -3
- package/src/components/SelectableTable/filters/FilterFieldBinding.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +36 -9
- package/src/components/SelectableTable/filters/RelationFilterField.tsx +93 -17
- package/src/components/SelectableTable/filters/null_filter.ts +63 -0
- package/src/components/SidePanelProvider.tsx +2 -2
- package/src/components/app/Scaffold.tsx +16 -1
- package/src/data_export/export/ExportCollectionAction.tsx +2 -2
- package/src/form/EntityForm.tsx +2 -2
- package/src/form/EntityFormActions.tsx +2 -1
- package/src/form/EntityFormBinding.tsx +7 -7
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +3 -3
- package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +16 -5
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +3 -3
- package/src/form/field_bindings/RelationFieldBinding.tsx +17 -6
- package/src/form/field_bindings/RepeatFieldBinding.tsx +2 -2
- package/src/hooks/index.ts +1 -1
- package/src/hooks/navigation/useNavigationResolution.ts +10 -4
- package/src/hooks/{useCMSContext.tsx → useAdminContext.tsx} +10 -10
- package/src/hooks/useHistory.ts +7 -11
- package/src/hooks/useResolvedNavigationFrom.tsx +2 -2
- package/src/index.ts +4 -4
- package/src/preview/PropertyPreview.tsx +6 -6
- package/src/preview/components/RelationPreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +2 -2
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +2 -2
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +2 -2
- package/src/types/components/EntityFormProps.tsx +2 -2
- package/src/types/fields.tsx +1 -1
- package/dist/CollectionEditorDialog-o7F_zbGY.js.map +0 -1
- package/dist/collection_editor/pgColumnToProperty.d.ts +0 -7
- package/dist/editor/extensions/Image/index.d.ts +0 -6
- package/dist/export-BvqhjdEb.js.map +0 -1
- package/dist/hooks/useCMSContext.d.ts +0 -29
- package/dist/util-BhhfHY1M.js.map +0 -1
- package/src/collection_editor/pgColumnToProperty.ts +0 -291
- package/src/editor/extensions/Image/index.ts +0 -133
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { Property, StringProperty, NumberProperty, ArrayProperty, TableColumnInfo, TableMetadata, PostgresProperties } from "@rebasepro/types";
|
|
2
|
-
import { prettifyIdentifier } from "@rebasepro/utils";
|
|
3
|
-
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Maps a PostgreSQL column data type to a Rebase property type.
|
|
7
|
-
*/
|
|
8
|
-
function pgTypeToRebaseProperty(column: TableColumnInfo): Property | null {
|
|
9
|
-
const {
|
|
10
|
-
column_name,
|
|
11
|
-
data_type,
|
|
12
|
-
udt_name,
|
|
13
|
-
is_nullable,
|
|
14
|
-
column_default,
|
|
15
|
-
enum_values
|
|
16
|
-
} = column;
|
|
17
|
-
|
|
18
|
-
const required = is_nullable === "NO";
|
|
19
|
-
const prettifiedName = prettifyIdentifier(column_name);
|
|
20
|
-
|
|
21
|
-
// Detect if this column is a primary key (auto-generated id)
|
|
22
|
-
const isAutoId = column_default != null && (
|
|
23
|
-
column_default.includes("nextval") ||
|
|
24
|
-
column_default.includes("gen_random_uuid") ||
|
|
25
|
-
column_default.includes("uuid_generate") ||
|
|
26
|
-
column_default.includes("identity")
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// USER-DEFINED = PostgreSQL enums
|
|
30
|
-
if (data_type === "USER-DEFINED" && enum_values && enum_values.length > 0) {
|
|
31
|
-
return {
|
|
32
|
-
type: "string",
|
|
33
|
-
name: prettifiedName,
|
|
34
|
-
enum: enum_values.map((v: string) => ({ id: v,
|
|
35
|
-
label: prettifyIdentifier(v) })),
|
|
36
|
-
validation: required ? { required: true } : undefined
|
|
37
|
-
} as StringProperty;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const dt = data_type.toLowerCase();
|
|
41
|
-
switch (dt) {
|
|
42
|
-
case "character varying":
|
|
43
|
-
case "varchar":
|
|
44
|
-
case "text":
|
|
45
|
-
case "char":
|
|
46
|
-
case "character":
|
|
47
|
-
case "citext": {
|
|
48
|
-
let colType: "varchar" | "text" | "char" = "varchar";
|
|
49
|
-
if (dt === "text" || dt === "citext") colType = "text";
|
|
50
|
-
if (dt === "char" || dt === "character") colType = "char";
|
|
51
|
-
const prop: StringProperty = {
|
|
52
|
-
type: "string",
|
|
53
|
-
name: prettifiedName,
|
|
54
|
-
columnType: colType,
|
|
55
|
-
validation: required ? { required: true } : undefined
|
|
56
|
-
};
|
|
57
|
-
if (isAutoId) {
|
|
58
|
-
prop.isId = "manual";
|
|
59
|
-
}
|
|
60
|
-
return prop;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
case "uuid": {
|
|
64
|
-
const prop: StringProperty = {
|
|
65
|
-
type: "string",
|
|
66
|
-
name: prettifiedName,
|
|
67
|
-
validation: required ? { required: true } : undefined
|
|
68
|
-
};
|
|
69
|
-
if (isAutoId) {
|
|
70
|
-
prop.isId = "uuid";
|
|
71
|
-
}
|
|
72
|
-
return prop;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
case "integer":
|
|
76
|
-
case "bigint":
|
|
77
|
-
case "smallint": {
|
|
78
|
-
const colType = dt === "bigint" ? "bigint" : "integer";
|
|
79
|
-
const prop: NumberProperty = {
|
|
80
|
-
type: "number",
|
|
81
|
-
name: prettifiedName,
|
|
82
|
-
columnType: colType,
|
|
83
|
-
validation: {
|
|
84
|
-
...(required ? { required: true } : {}),
|
|
85
|
-
integer: true
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
if (isAutoId) {
|
|
89
|
-
prop.isId = "increment";
|
|
90
|
-
}
|
|
91
|
-
return prop;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
case "serial":
|
|
95
|
-
case "bigserial":
|
|
96
|
-
case "smallserial": {
|
|
97
|
-
const colType = dt === "bigserial" ? "bigserial" : "serial";
|
|
98
|
-
return {
|
|
99
|
-
type: "number",
|
|
100
|
-
name: prettifiedName,
|
|
101
|
-
columnType: colType,
|
|
102
|
-
isId: "increment",
|
|
103
|
-
validation: {
|
|
104
|
-
...(required ? { required: true } : {}),
|
|
105
|
-
integer: true
|
|
106
|
-
}
|
|
107
|
-
} as NumberProperty;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
case "numeric":
|
|
111
|
-
case "decimal":
|
|
112
|
-
case "real":
|
|
113
|
-
case "double precision": {
|
|
114
|
-
let colType: "numeric" | "real" | "double precision" = "numeric";
|
|
115
|
-
if (dt === "real") colType = "real";
|
|
116
|
-
if (dt === "double precision") colType = "double precision";
|
|
117
|
-
return {
|
|
118
|
-
type: "number",
|
|
119
|
-
name: prettifiedName,
|
|
120
|
-
columnType: colType,
|
|
121
|
-
validation: required ? { required: true } : undefined
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
case "boolean":
|
|
126
|
-
return {
|
|
127
|
-
type: "boolean",
|
|
128
|
-
name: prettifiedName,
|
|
129
|
-
validation: required ? { required: true } : undefined
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
case "timestamp with time zone":
|
|
133
|
-
case "timestamp without time zone":
|
|
134
|
-
case "timestamp":
|
|
135
|
-
case "timestamptz":
|
|
136
|
-
case "date":
|
|
137
|
-
case "time with time zone":
|
|
138
|
-
case "time without time zone":
|
|
139
|
-
case "time": {
|
|
140
|
-
let colType: "timestamp" | "date" | "time" = "timestamp";
|
|
141
|
-
if (dt.startsWith("date")) colType = "date";
|
|
142
|
-
if (dt.startsWith("time ") || dt === "time") colType = "time";
|
|
143
|
-
return {
|
|
144
|
-
type: "date",
|
|
145
|
-
name: prettifiedName,
|
|
146
|
-
columnType: colType,
|
|
147
|
-
validation: required ? { required: true } : undefined
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
case "jsonb":
|
|
152
|
-
case "json":
|
|
153
|
-
return {
|
|
154
|
-
type: "map",
|
|
155
|
-
name: prettifiedName,
|
|
156
|
-
columnType: dt === "jsonb" ? "jsonb" : "json",
|
|
157
|
-
keyValue: true,
|
|
158
|
-
properties: {}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
case "array":
|
|
162
|
-
case "ARRAY": {
|
|
163
|
-
let innerType = "string";
|
|
164
|
-
let colType: ArrayProperty["columnType"] = undefined;
|
|
165
|
-
if (udt_name === "_text" || udt_name === "_varchar") {
|
|
166
|
-
innerType = "string";
|
|
167
|
-
colType = "text[]";
|
|
168
|
-
} else if (udt_name === "_int4" || udt_name === "_int2" || udt_name === "_int8") {
|
|
169
|
-
innerType = "number";
|
|
170
|
-
colType = "integer[]";
|
|
171
|
-
} else if (udt_name === "_bool") {
|
|
172
|
-
innerType = "boolean";
|
|
173
|
-
colType = "boolean[]";
|
|
174
|
-
} else if (udt_name === "_numeric") {
|
|
175
|
-
innerType = "number";
|
|
176
|
-
colType = "numeric[]";
|
|
177
|
-
}
|
|
178
|
-
return {
|
|
179
|
-
type: "array",
|
|
180
|
-
name: prettifiedName,
|
|
181
|
-
columnType: colType,
|
|
182
|
-
of: { type: innerType }
|
|
183
|
-
} as ArrayProperty;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
default:
|
|
187
|
-
// Fallback: treat unknown types as string
|
|
188
|
-
return {
|
|
189
|
-
type: "string",
|
|
190
|
-
name: prettifiedName,
|
|
191
|
-
validation: required ? { required: true } : undefined
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Builds a partial AdminCollection from PostgreSQL table metadata.
|
|
198
|
-
* This is used when creating a new collection from an existing database table.
|
|
199
|
-
*/
|
|
200
|
-
export function buildCollectionFromTableMetadata(
|
|
201
|
-
tableName: string,
|
|
202
|
-
metadata: TableMetadata
|
|
203
|
-
): Partial<AdminCollection> {
|
|
204
|
-
const properties: Record<string, Property> = {};
|
|
205
|
-
const propertiesOrder: string[] = [];
|
|
206
|
-
const relations: any[] = []; // In the builder/editor, target can be a string path before hydration
|
|
207
|
-
const securityRules: any[] = [];
|
|
208
|
-
|
|
209
|
-
// Parse columns
|
|
210
|
-
for (const column of metadata.columns) {
|
|
211
|
-
const property = pgTypeToRebaseProperty(column);
|
|
212
|
-
if (property) {
|
|
213
|
-
// Remove undefined keys so we don't output "validation: undefined"
|
|
214
|
-
const propRecord = property as unknown as Record<string, unknown>;
|
|
215
|
-
Object.keys(propRecord).forEach(key => propRecord[key] === undefined && delete propRecord[key]);
|
|
216
|
-
|
|
217
|
-
properties[column.column_name] = property;
|
|
218
|
-
propertiesOrder.push(column.column_name);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Parse Outgoing Foreign Keys -> Many-to-One / One-to-One
|
|
223
|
-
if (metadata.foreignKeys) {
|
|
224
|
-
for (const fk of metadata.foreignKeys) {
|
|
225
|
-
const relName = fk.column_name.endsWith("_id") ? fk.column_name.substring(0, fk.column_name.length - 3) : fk.column_name;
|
|
226
|
-
relations.push({
|
|
227
|
-
id: fk.column_name,
|
|
228
|
-
relationName: relName,
|
|
229
|
-
target: fk.foreign_table_name, // Will be hydrated later
|
|
230
|
-
cardinality: "one",
|
|
231
|
-
direction: "owning",
|
|
232
|
-
localKey: fk.column_name
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Parse Incoming Junctions -> Many-to-Many
|
|
238
|
-
if (metadata.junctions) {
|
|
239
|
-
for (const junction of metadata.junctions) {
|
|
240
|
-
const relName = junction.target_table_name; // E.g., 'roles'
|
|
241
|
-
relations.push({
|
|
242
|
-
id: junction.target_table_name + "_relation",
|
|
243
|
-
relationName: relName,
|
|
244
|
-
target: junction.target_table_name, // Will be hydrated later
|
|
245
|
-
cardinality: "many",
|
|
246
|
-
direction: "owning",
|
|
247
|
-
through: {
|
|
248
|
-
table: junction.junction_table_name,
|
|
249
|
-
sourceColumn: junction.source_column_name,
|
|
250
|
-
targetColumn: junction.target_column_name
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Parse RLS Policies
|
|
257
|
-
if (metadata.policies) {
|
|
258
|
-
for (const policy of metadata.policies) {
|
|
259
|
-
// Attempt to map typical cmds to operations.
|
|
260
|
-
// Postgres cmd: SELECT, INSERT, UPDATE, DELETE, ALL
|
|
261
|
-
let operations: string[] = [];
|
|
262
|
-
switch (policy.cmd) {
|
|
263
|
-
case "ALL": operations = ["read", "create", "update", "delete"]; break;
|
|
264
|
-
case "SELECT": operations = ["read"]; break;
|
|
265
|
-
case "INSERT": operations = ["create"]; break;
|
|
266
|
-
case "UPDATE": operations = ["update"]; break;
|
|
267
|
-
case "DELETE": operations = ["delete"]; break;
|
|
268
|
-
}
|
|
269
|
-
securityRules.push({
|
|
270
|
-
name: policy.policy_name,
|
|
271
|
-
operations,
|
|
272
|
-
// roles is string[] e.g., ["public", "authenticated"]
|
|
273
|
-
roles: policy.roles ?? [],
|
|
274
|
-
qual: policy.qual,
|
|
275
|
-
with_check: policy.with_check
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const prettifiedName = prettifyIdentifier(tableName);
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
name: prettifiedName,
|
|
284
|
-
slug: tableName,
|
|
285
|
-
table: tableName,
|
|
286
|
-
properties: properties as PostgresProperties,
|
|
287
|
-
propertiesOrder,
|
|
288
|
-
...(relations.length > 0 ? { relations } : {}),
|
|
289
|
-
...(securityRules.length > 0 ? { securityRules } : {})
|
|
290
|
-
};
|
|
291
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
|
|
2
|
-
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
|
-
import { schema } from "../../schema";
|
|
4
|
-
|
|
5
|
-
export type UploadFn = (image: File) => Promise<string>;
|
|
6
|
-
|
|
7
|
-
export async function onFileRead(view: EditorView, readerEvent: ProgressEvent<FileReader>, pos: number, upload: UploadFn, image: File) {
|
|
8
|
-
const state = view.state as typeof view.state & { plugins: Plugin[] };
|
|
9
|
-
const plugin = state.plugins.find((p: Plugin) => p.spec.key === ImagePluginKey);
|
|
10
|
-
if (!plugin) {
|
|
11
|
-
console.error("Image plugin not found");
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
let decorationSet = plugin.getState(view.state);
|
|
15
|
-
|
|
16
|
-
const placeholder = document.createElement("div");
|
|
17
|
-
const imageElement = document.createElement("img");
|
|
18
|
-
// basic styling for loading state
|
|
19
|
-
imageElement.setAttribute("class", "opacity-40 rounded-lg border");
|
|
20
|
-
imageElement.src = readerEvent.target?.result as string;
|
|
21
|
-
placeholder.appendChild(imageElement);
|
|
22
|
-
|
|
23
|
-
const deco = Decoration.widget(pos, placeholder);
|
|
24
|
-
decorationSet = decorationSet?.add(view.state.doc, [deco]);
|
|
25
|
-
view.dispatch(view.state.tr.setMeta(plugin, { decorationSet }));
|
|
26
|
-
|
|
27
|
-
// Image Upload Logic
|
|
28
|
-
const src = await upload(image);
|
|
29
|
-
console.debug("Uploaded image", src);
|
|
30
|
-
|
|
31
|
-
// Replace placeholder with actual image
|
|
32
|
-
const imageNode = schema.nodes.image.create({ src });
|
|
33
|
-
const tr = view.state.tr.replaceWith(pos, pos, imageNode);
|
|
34
|
-
|
|
35
|
-
// Remove placeholder decoration
|
|
36
|
-
decorationSet = decorationSet?.remove([deco]);
|
|
37
|
-
tr.setMeta(plugin, { decorationSet });
|
|
38
|
-
view.dispatch(tr);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const ImagePluginKey = new PluginKey("imagePlugin");
|
|
42
|
-
|
|
43
|
-
export const createDropImagePlugin = (upload: UploadFn): Plugin => {
|
|
44
|
-
const plugin: Plugin = new Plugin({
|
|
45
|
-
key: ImagePluginKey,
|
|
46
|
-
state: {
|
|
47
|
-
// Initialize the plugin state with an empty DecorationSet
|
|
48
|
-
init: () => DecorationSet.empty,
|
|
49
|
-
// Apply transactions to update the state
|
|
50
|
-
apply: (tr, old) => {
|
|
51
|
-
// Handle custom transaction steps that update decorations
|
|
52
|
-
const meta = tr.getMeta(plugin);
|
|
53
|
-
if (meta && meta.decorationSet) {
|
|
54
|
-
return meta.decorationSet;
|
|
55
|
-
}
|
|
56
|
-
// Map decorations to the new document structure
|
|
57
|
-
return old.map(tr.mapping, tr.doc);
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
props: {
|
|
61
|
-
handleDOMEvents: {
|
|
62
|
-
drop: (view, event) => {
|
|
63
|
-
if (!event.dataTransfer?.files || event.dataTransfer?.files.length === 0) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
event.preventDefault();
|
|
67
|
-
|
|
68
|
-
const files = Array.from(event.dataTransfer.files);
|
|
69
|
-
const images = files.filter(file => /image/i.test(file.type));
|
|
70
|
-
|
|
71
|
-
if (images.length === 0) {
|
|
72
|
-
console.debug("No images found in dropped files");
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
images.forEach(image => {
|
|
77
|
-
const position = view.posAtCoords({
|
|
78
|
-
left: event.clientX,
|
|
79
|
-
top: event.clientY
|
|
80
|
-
});
|
|
81
|
-
if (!position) return;
|
|
82
|
-
|
|
83
|
-
const reader = new FileReader();
|
|
84
|
-
reader.onload = async (readerEvent) => {
|
|
85
|
-
await onFileRead(view, readerEvent, position.pos, upload, image);
|
|
86
|
-
};
|
|
87
|
-
reader.readAsDataURL(image);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
handlePaste(view, event, slice) {
|
|
94
|
-
const items = Array.from(event.clipboardData?.items || []);
|
|
95
|
-
const pos = view.state.selection.from;
|
|
96
|
-
let anyImageFound = false;
|
|
97
|
-
|
|
98
|
-
items.forEach((item) => {
|
|
99
|
-
const image = item.getAsFile();
|
|
100
|
-
if (image && /image/i.test(item.type)) {
|
|
101
|
-
anyImageFound = true;
|
|
102
|
-
const reader = new FileReader();
|
|
103
|
-
|
|
104
|
-
reader.onload = async (readerEvent) => {
|
|
105
|
-
await onFileRead(view, readerEvent, pos, upload, image);
|
|
106
|
-
};
|
|
107
|
-
reader.readAsDataURL(image);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return anyImageFound;
|
|
112
|
-
},
|
|
113
|
-
decorations(state) {
|
|
114
|
-
return plugin.getState(state);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
view(editorView) {
|
|
118
|
-
// This is needed to immediately apply the decoration updates
|
|
119
|
-
return {
|
|
120
|
-
update(view, prevState) {
|
|
121
|
-
const prevDecos = plugin.getState(prevState);
|
|
122
|
-
const newDecos = plugin.getState(view.state);
|
|
123
|
-
|
|
124
|
-
if (prevDecos !== newDecos) {
|
|
125
|
-
view.updateState(view.state);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
return plugin;
|
|
133
|
-
};
|