@adminforth/markdown 1.2.10 → 1.4.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/build.log +2 -2
- package/custom/MarkdownEditor.vue +553 -223
- package/custom/package-lock.json +39 -2780
- package/custom/package.json +4 -4
- package/dist/custom/MarkdownEditor.vue +553 -223
- package/dist/custom/package-lock.json +39 -2780
- package/dist/custom/package.json +4 -4
- package/dist/index.js +126 -40
- package/index.ts +169 -58
- package/package.json +1 -1
- package/types.ts +17 -0
package/dist/custom/package.json
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"author": "",
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@milkdown/kit": "^7.18.0",
|
|
14
|
-
"@milkdown/theme-nord": "^7.18.0",
|
|
15
|
-
"@milkdown/vue": "^7.18.0",
|
|
16
13
|
"dompurify": "^3.2.4",
|
|
17
|
-
"
|
|
14
|
+
"monaco-editor": "^0.45.0",
|
|
15
|
+
"marked": "^15.0.7",
|
|
16
|
+
"turndown": "^7.2.2",
|
|
17
|
+
"turndown-plugin-gfm": "^1.0.2"
|
|
18
18
|
}
|
|
19
19
|
}
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,18 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
54
54
|
if (!field) {
|
|
55
55
|
throw new Error(`Field '${this.options.attachments.attachmentFieldName}' not found in resource '${this.options.attachments.attachmentResource}'`);
|
|
56
56
|
}
|
|
57
|
+
if (this.options.attachments.attachmentTitleFieldName) {
|
|
58
|
+
const titleField = yield resource.columns.find(c => c.name === this.options.attachments.attachmentTitleFieldName);
|
|
59
|
+
if (!titleField) {
|
|
60
|
+
throw new Error(`Field '${this.options.attachments.attachmentTitleFieldName}' not found in resource '${this.options.attachments.attachmentResource}'`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (this.options.attachments.attachmentAltFieldName) {
|
|
64
|
+
const altField = yield resource.columns.find(c => c.name === this.options.attachments.attachmentAltFieldName);
|
|
65
|
+
if (!altField) {
|
|
66
|
+
throw new Error(`Field '${this.options.attachments.attachmentAltFieldName}' not found in resource '${this.options.attachments.attachmentResource}'`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
57
69
|
const plugin = yield adminforth.activatedPlugins.find(p => p.resourceConfig.resourceId === this.options.attachments.attachmentResource &&
|
|
58
70
|
p.pluginOptions.pathColumnName === this.options.attachments.attachmentFieldName);
|
|
59
71
|
if (!plugin) {
|
|
@@ -86,7 +98,6 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
86
98
|
meta: {
|
|
87
99
|
pluginInstanceId: this.pluginInstanceId,
|
|
88
100
|
columnName: fieldName,
|
|
89
|
-
pluginType: 'crepe',
|
|
90
101
|
uploadPluginInstanceId: (_a = this.uploadPlugin) === null || _a === void 0 ? void 0 : _a.pluginInstanceId,
|
|
91
102
|
},
|
|
92
103
|
};
|
|
@@ -95,43 +106,65 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
95
106
|
meta: {
|
|
96
107
|
pluginInstanceId: this.pluginInstanceId,
|
|
97
108
|
columnName: fieldName,
|
|
98
|
-
pluginType: 'crepe',
|
|
99
109
|
uploadPluginInstanceId: (_b = this.uploadPlugin) === null || _b === void 0 ? void 0 : _b.pluginInstanceId,
|
|
100
110
|
},
|
|
101
111
|
};
|
|
102
112
|
const editorRecordPkField = resourceConfig.columns.find(c => c.primaryKey);
|
|
103
113
|
if (this.options.attachments) {
|
|
104
|
-
|
|
114
|
+
const extractKeyFromUrl = (url) => url.replace(/^https:\/\/[^\/]+\/+/, '');
|
|
115
|
+
function getAttachmentMetas(markdown) {
|
|
116
|
+
var _a, _b, _c;
|
|
105
117
|
if (!markdown) {
|
|
106
118
|
return [];
|
|
107
119
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
// Minimal image syntax:  or  or 
|
|
121
|
+
// We only track https URLs and only those that look like S3/AWS public URLs.
|
|
122
|
+
const imageRegex = /!\[([^\]]*)\]\(\s*(https:\/\/[^\s)]+)\s*(?:\s+(?:"([^"]*)"|'([^']*)'))?\s*\)/g;
|
|
123
|
+
const byKey = new Map();
|
|
124
|
+
for (const match of markdown.matchAll(imageRegex)) {
|
|
125
|
+
const altRaw = (_a = match[1]) !== null && _a !== void 0 ? _a : '';
|
|
126
|
+
const srcRaw = match[2];
|
|
127
|
+
const titleRaw = (_c = ((_b = match[3]) !== null && _b !== void 0 ? _b : match[4])) !== null && _c !== void 0 ? _c : null;
|
|
128
|
+
const srcNoQuery = srcRaw.split('?')[0];
|
|
129
|
+
if (!srcNoQuery.includes('s3') && !srcNoQuery.includes('amazonaws')) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const key = extractKeyFromUrl(srcNoQuery);
|
|
133
|
+
byKey.set(key, {
|
|
134
|
+
key,
|
|
135
|
+
alt: altRaw,
|
|
136
|
+
title: titleRaw,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return [...byKey.values()];
|
|
113
140
|
}
|
|
114
|
-
const createAttachmentRecords = (adminforth, options, recordId,
|
|
115
|
-
|
|
141
|
+
const createAttachmentRecords = (adminforth, options, recordId, metas, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
142
|
+
if (!metas.length) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
116
145
|
process.env.HEAVY_DEBUG && console.log('📸 Creating attachment records', JSON.stringify(recordId));
|
|
117
146
|
try {
|
|
118
|
-
yield Promise.all(
|
|
119
|
-
console.log('Processing path:', s3Path);
|
|
147
|
+
yield Promise.all(metas.map((meta) => __awaiter(this, void 0, void 0, function* () {
|
|
120
148
|
try {
|
|
149
|
+
const recordToCreate = {
|
|
150
|
+
[options.attachments.attachmentFieldName]: meta.key,
|
|
151
|
+
[options.attachments.attachmentRecordIdFieldName]: recordId,
|
|
152
|
+
[options.attachments.attachmentResourceIdFieldName]: resourceConfig.resourceId,
|
|
153
|
+
};
|
|
154
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
155
|
+
recordToCreate[options.attachments.attachmentTitleFieldName] = meta.title;
|
|
156
|
+
}
|
|
157
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
158
|
+
recordToCreate[options.attachments.attachmentAltFieldName] = meta.alt;
|
|
159
|
+
}
|
|
121
160
|
yield adminforth.createResourceRecord({
|
|
122
161
|
resource: this.attachmentResource,
|
|
123
|
-
record:
|
|
124
|
-
[options.attachments.attachmentFieldName]: extractKey(s3Path),
|
|
125
|
-
[options.attachments.attachmentRecordIdFieldName]: recordId,
|
|
126
|
-
[options.attachments.attachmentResourceIdFieldName]: resourceConfig.resourceId,
|
|
127
|
-
},
|
|
162
|
+
record: recordToCreate,
|
|
128
163
|
adminUser,
|
|
129
|
-
response: {}
|
|
130
164
|
});
|
|
131
|
-
console.log('Successfully created record for:', s3Path);
|
|
132
165
|
}
|
|
133
166
|
catch (err) {
|
|
134
|
-
console.error('Error creating record for',
|
|
167
|
+
console.error('Error creating record for', meta.key, err);
|
|
135
168
|
}
|
|
136
169
|
})));
|
|
137
170
|
}
|
|
@@ -139,28 +172,77 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
139
172
|
console.error('Error in Promise.all', err);
|
|
140
173
|
}
|
|
141
174
|
});
|
|
142
|
-
const deleteAttachmentRecords = (adminforth, options,
|
|
143
|
-
if (!
|
|
175
|
+
const deleteAttachmentRecords = (adminforth, options, recordId, keys, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
176
|
+
if (!keys.length) {
|
|
144
177
|
return;
|
|
145
178
|
}
|
|
146
179
|
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
147
|
-
const attachments = yield adminforth.resource(options.attachments.attachmentResource).list(
|
|
180
|
+
const attachments = yield adminforth.resource(options.attachments.attachmentResource).list([
|
|
181
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
182
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId),
|
|
183
|
+
Filters.IN(options.attachments.attachmentFieldName, keys),
|
|
184
|
+
]);
|
|
148
185
|
yield Promise.all(attachments.map((a) => __awaiter(this, void 0, void 0, function* () {
|
|
149
186
|
yield adminforth.deleteResourceRecord({
|
|
150
187
|
resource: this.attachmentResource,
|
|
151
188
|
recordId: a[attachmentPrimaryKeyField.name],
|
|
152
189
|
adminUser,
|
|
153
190
|
record: a,
|
|
154
|
-
|
|
191
|
+
});
|
|
192
|
+
})));
|
|
193
|
+
});
|
|
194
|
+
const updateAttachmentRecordsMetadata = (adminforth, options, recordId, metas, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
195
|
+
if (!metas.length) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (!options.attachments.attachmentTitleFieldName && !options.attachments.attachmentAltFieldName) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
202
|
+
const metaByKey = new Map(metas.map(m => [m.key, m]));
|
|
203
|
+
const existingAparts = yield adminforth.resource(options.attachments.attachmentResource).list([
|
|
204
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
205
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
206
|
+
]);
|
|
207
|
+
yield Promise.all(existingAparts.map((a) => __awaiter(this, void 0, void 0, function* () {
|
|
208
|
+
var _a, _b, _c, _d;
|
|
209
|
+
const key = a[options.attachments.attachmentFieldName];
|
|
210
|
+
const meta = metaByKey.get(key);
|
|
211
|
+
if (!meta) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const patch = {};
|
|
215
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
216
|
+
const field = options.attachments.attachmentTitleFieldName;
|
|
217
|
+
if (((_a = a[field]) !== null && _a !== void 0 ? _a : null) !== ((_b = meta.title) !== null && _b !== void 0 ? _b : null)) {
|
|
218
|
+
patch[field] = meta.title;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
222
|
+
const field = options.attachments.attachmentAltFieldName;
|
|
223
|
+
if (((_c = a[field]) !== null && _c !== void 0 ? _c : null) !== ((_d = meta.alt) !== null && _d !== void 0 ? _d : null)) {
|
|
224
|
+
patch[field] = meta.alt;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!Object.keys(patch).length) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
yield adminforth.updateResourceRecord({
|
|
231
|
+
resource: this.attachmentResource,
|
|
232
|
+
recordId: a[attachmentPrimaryKeyField.name],
|
|
233
|
+
record: patch,
|
|
234
|
+
oldRecord: a,
|
|
235
|
+
adminUser,
|
|
155
236
|
});
|
|
156
237
|
})));
|
|
157
238
|
});
|
|
158
239
|
(resourceConfig.hooks.create.afterSave).push((_a) => __awaiter(this, [_a], void 0, function* ({ record, adminUser }) {
|
|
159
240
|
// find all s3Paths in the html
|
|
160
|
-
const
|
|
161
|
-
|
|
241
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
242
|
+
const keys = metas.map(m => m.key);
|
|
243
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
|
|
162
244
|
// create attachment records
|
|
163
|
-
yield createAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name],
|
|
245
|
+
yield createAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name], metas, adminUser);
|
|
164
246
|
return { ok: true };
|
|
165
247
|
}));
|
|
166
248
|
// after edit we need to delete attachments that are not in the html anymore
|
|
@@ -176,18 +258,22 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
176
258
|
Filters.EQ(this.options.attachments.attachmentRecordIdFieldName, recordId),
|
|
177
259
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
178
260
|
]);
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
process.env.HEAVY_DEBUG && console.log('📸
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
261
|
+
const existingKeys = existingAparts.map((a) => a[this.options.attachments.attachmentFieldName]);
|
|
262
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
263
|
+
const newKeys = metas.map(m => m.key);
|
|
264
|
+
process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys);
|
|
265
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found new keys (from text)', newKeys);
|
|
266
|
+
const toDelete = existingKeys.filter(key => !newKeys.includes(key));
|
|
267
|
+
const toAdd = newKeys.filter(key => !existingKeys.includes(key));
|
|
268
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', toDelete);
|
|
269
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to add', toAdd);
|
|
270
|
+
const metasToAdd = metas.filter(m => toAdd.includes(m.key));
|
|
187
271
|
yield Promise.all([
|
|
188
|
-
deleteAttachmentRecords(adminforth, this.options, toDelete, adminUser),
|
|
189
|
-
createAttachmentRecords(adminforth, this.options, recordId,
|
|
272
|
+
deleteAttachmentRecords(adminforth, this.options, recordId, toDelete, adminUser),
|
|
273
|
+
createAttachmentRecords(adminforth, this.options, recordId, metasToAdd, adminUser)
|
|
190
274
|
]);
|
|
275
|
+
// Keep alt/title in sync for existing attachments too
|
|
276
|
+
yield updateAttachmentRecordsMetadata(adminforth, this.options, recordId, metas, adminUser);
|
|
191
277
|
return { ok: true };
|
|
192
278
|
}));
|
|
193
279
|
// after delete we need to delete all attachments
|
|
@@ -196,9 +282,9 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
196
282
|
Filters.EQ(this.options.attachments.attachmentRecordIdFieldName, record[editorRecordPkField.name]),
|
|
197
283
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
198
284
|
]);
|
|
199
|
-
const
|
|
200
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
201
|
-
yield deleteAttachmentRecords(adminforth, this.options,
|
|
285
|
+
const existingKeys = existingAparts.map((a) => a[this.options.attachments.attachmentFieldName]);
|
|
286
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', existingKeys);
|
|
287
|
+
yield deleteAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name], existingKeys, adminUser);
|
|
202
288
|
return { ok: true };
|
|
203
289
|
}));
|
|
204
290
|
}
|
package/index.ts
CHANGED
|
@@ -52,6 +52,20 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
52
52
|
if (!field) {
|
|
53
53
|
throw new Error(`Field '${this.options.attachments!.attachmentFieldName}' not found in resource '${this.options.attachments!.attachmentResource}'`);
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
if (this.options.attachments.attachmentTitleFieldName) {
|
|
57
|
+
const titleField = await resource.columns.find(c => c.name === this.options.attachments!.attachmentTitleFieldName);
|
|
58
|
+
if (!titleField) {
|
|
59
|
+
throw new Error(`Field '${this.options.attachments!.attachmentTitleFieldName}' not found in resource '${this.options.attachments!.attachmentResource}'`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (this.options.attachments.attachmentAltFieldName) {
|
|
64
|
+
const altField = await resource.columns.find(c => c.name === this.options.attachments!.attachmentAltFieldName);
|
|
65
|
+
if (!altField) {
|
|
66
|
+
throw new Error(`Field '${this.options.attachments!.attachmentAltFieldName}' not found in resource '${this.options.attachments!.attachmentResource}'`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
55
69
|
|
|
56
70
|
const plugin = await adminforth.activatedPlugins.find(p =>
|
|
57
71
|
p.resourceConfig!.resourceId === this.options.attachments!.attachmentResource &&
|
|
@@ -91,7 +105,6 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
91
105
|
meta: {
|
|
92
106
|
pluginInstanceId: this.pluginInstanceId,
|
|
93
107
|
columnName: fieldName,
|
|
94
|
-
pluginType: 'crepe',
|
|
95
108
|
uploadPluginInstanceId: this.uploadPlugin?.pluginInstanceId,
|
|
96
109
|
},
|
|
97
110
|
};
|
|
@@ -101,51 +114,80 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
101
114
|
meta: {
|
|
102
115
|
pluginInstanceId: this.pluginInstanceId,
|
|
103
116
|
columnName: fieldName,
|
|
104
|
-
pluginType: 'crepe',
|
|
105
117
|
uploadPluginInstanceId: this.uploadPlugin?.pluginInstanceId,
|
|
106
118
|
},
|
|
107
119
|
};
|
|
108
120
|
const editorRecordPkField = resourceConfig.columns.find(c => c.primaryKey);
|
|
109
121
|
if (this.options.attachments) {
|
|
110
122
|
|
|
111
|
-
|
|
123
|
+
type AttachmentMeta = { key: string; alt: string | null; title: string | null };
|
|
124
|
+
|
|
125
|
+
const extractKeyFromUrl = (url: string) => url.replace(/^https:\/\/[^\/]+\/+/, '');
|
|
126
|
+
|
|
127
|
+
function getAttachmentMetas(markdown: string): AttachmentMeta[] {
|
|
112
128
|
if (!markdown) {
|
|
113
129
|
return [];
|
|
114
130
|
}
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
132
|
+
// Minimal image syntax:  or  or 
|
|
133
|
+
// We only track https URLs and only those that look like S3/AWS public URLs.
|
|
134
|
+
const imageRegex = /!\[([^\]]*)\]\(\s*(https:\/\/[^\s)]+)\s*(?:\s+(?:"([^"]*)"|'([^']*)'))?\s*\)/g;
|
|
135
|
+
|
|
136
|
+
const byKey = new Map<string, AttachmentMeta>();
|
|
137
|
+
for (const match of markdown.matchAll(imageRegex)) {
|
|
138
|
+
const altRaw = match[1] ?? '';
|
|
139
|
+
const srcRaw = match[2];
|
|
140
|
+
const titleRaw = (match[3] ?? match[4]) ?? null;
|
|
141
|
+
|
|
142
|
+
const srcNoQuery = srcRaw.split('?')[0];
|
|
143
|
+
if (!srcNoQuery.includes('s3') && !srcNoQuery.includes('amazonaws')) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
119
146
|
|
|
120
|
-
|
|
121
|
-
.
|
|
122
|
-
|
|
147
|
+
const key = extractKeyFromUrl(srcNoQuery);
|
|
148
|
+
byKey.set(key, {
|
|
149
|
+
key,
|
|
150
|
+
alt: altRaw,
|
|
151
|
+
title: titleRaw,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return [...byKey.values()];
|
|
123
155
|
}
|
|
124
156
|
|
|
125
157
|
const createAttachmentRecords = async (
|
|
126
|
-
adminforth: IAdminForth,
|
|
158
|
+
adminforth: IAdminForth,
|
|
159
|
+
options: PluginOptions,
|
|
160
|
+
recordId: any,
|
|
161
|
+
metas: AttachmentMeta[],
|
|
162
|
+
adminUser: AdminUser
|
|
127
163
|
) => {
|
|
128
|
-
|
|
164
|
+
if (!metas.length) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
129
167
|
process.env.HEAVY_DEBUG && console.log('📸 Creating attachment records', JSON.stringify(recordId))
|
|
130
168
|
try {
|
|
131
|
-
await Promise.all(
|
|
132
|
-
console.log('Processing path:', s3Path);
|
|
169
|
+
await Promise.all(metas.map(async (meta) => {
|
|
133
170
|
try {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
171
|
+
const recordToCreate: any = {
|
|
172
|
+
[options.attachments.attachmentFieldName]: meta.key,
|
|
173
|
+
[options.attachments.attachmentRecordIdFieldName]: recordId,
|
|
174
|
+
[options.attachments.attachmentResourceIdFieldName]: resourceConfig.resourceId,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
178
|
+
recordToCreate[options.attachments.attachmentTitleFieldName] = meta.title;
|
|
179
|
+
}
|
|
180
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
181
|
+
recordToCreate[options.attachments.attachmentAltFieldName] = meta.alt;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await adminforth.createResourceRecord({
|
|
185
|
+
resource: this.attachmentResource,
|
|
186
|
+
record: recordToCreate,
|
|
187
|
+
adminUser,
|
|
188
|
+
});
|
|
147
189
|
} catch (err) {
|
|
148
|
-
console.error('Error creating record for',
|
|
190
|
+
console.error('Error creating record for', meta.key, err);
|
|
149
191
|
}
|
|
150
192
|
}));
|
|
151
193
|
} catch (err) {
|
|
@@ -154,36 +196,95 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
154
196
|
}
|
|
155
197
|
|
|
156
198
|
const deleteAttachmentRecords = async (
|
|
157
|
-
adminforth: IAdminForth,
|
|
199
|
+
adminforth: IAdminForth,
|
|
200
|
+
options: PluginOptions,
|
|
201
|
+
recordId: any,
|
|
202
|
+
keys: string[],
|
|
203
|
+
adminUser: AdminUser
|
|
158
204
|
) => {
|
|
159
|
-
if (!
|
|
205
|
+
if (!keys.length) {
|
|
160
206
|
return;
|
|
161
207
|
}
|
|
162
208
|
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
163
|
-
const attachments = await adminforth.resource(options.attachments.attachmentResource).list(
|
|
164
|
-
Filters.
|
|
165
|
-
|
|
209
|
+
const attachments = await adminforth.resource(options.attachments.attachmentResource).list([
|
|
210
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
211
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId),
|
|
212
|
+
Filters.IN(options.attachments.attachmentFieldName, keys),
|
|
213
|
+
]);
|
|
214
|
+
|
|
166
215
|
await Promise.all(attachments.map(async (a: any) => {
|
|
167
|
-
await adminforth.deleteResourceRecord(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
response: {} as any
|
|
174
|
-
}
|
|
175
|
-
)
|
|
216
|
+
await adminforth.deleteResourceRecord({
|
|
217
|
+
resource: this.attachmentResource,
|
|
218
|
+
recordId: a[attachmentPrimaryKeyField.name],
|
|
219
|
+
adminUser,
|
|
220
|
+
record: a,
|
|
221
|
+
})
|
|
176
222
|
}))
|
|
177
223
|
}
|
|
224
|
+
|
|
225
|
+
const updateAttachmentRecordsMetadata = async (
|
|
226
|
+
adminforth: IAdminForth,
|
|
227
|
+
options: PluginOptions,
|
|
228
|
+
recordId: any,
|
|
229
|
+
metas: AttachmentMeta[],
|
|
230
|
+
adminUser: AdminUser
|
|
231
|
+
) => {
|
|
232
|
+
if (!metas.length) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (!options.attachments.attachmentTitleFieldName && !options.attachments.attachmentAltFieldName) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
239
|
+
const metaByKey = new Map(metas.map(m => [m.key, m] as const));
|
|
240
|
+
|
|
241
|
+
const existingAparts = await adminforth.resource(options.attachments.attachmentResource).list([
|
|
242
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
243
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
await Promise.all(existingAparts.map(async (a: any) => {
|
|
247
|
+
const key = a[options.attachments.attachmentFieldName];
|
|
248
|
+
const meta = metaByKey.get(key);
|
|
249
|
+
if (!meta) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const patch: any = {};
|
|
254
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
255
|
+
const field = options.attachments.attachmentTitleFieldName;
|
|
256
|
+
if ((a[field] ?? null) !== (meta.title ?? null)) {
|
|
257
|
+
patch[field] = meta.title;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
261
|
+
const field = options.attachments.attachmentAltFieldName;
|
|
262
|
+
if ((a[field] ?? null) !== (meta.alt ?? null)) {
|
|
263
|
+
patch[field] = meta.alt;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!Object.keys(patch).length) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
await adminforth.updateResourceRecord({
|
|
271
|
+
resource: this.attachmentResource,
|
|
272
|
+
recordId: a[attachmentPrimaryKeyField.name],
|
|
273
|
+
record: patch,
|
|
274
|
+
oldRecord: a,
|
|
275
|
+
adminUser,
|
|
276
|
+
});
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
178
279
|
|
|
179
280
|
(resourceConfig.hooks.create.afterSave).push(async ({ record, adminUser }: { record: any, adminUser: AdminUser }) => {
|
|
180
281
|
// find all s3Paths in the html
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
282
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
283
|
+
const keys = metas.map(m => m.key);
|
|
284
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
|
|
184
285
|
// create attachment records
|
|
185
286
|
await createAttachmentRecords(
|
|
186
|
-
adminforth, this.options, record[editorRecordPkField.name],
|
|
287
|
+
adminforth, this.options, record[editorRecordPkField.name], metas, adminUser);
|
|
187
288
|
|
|
188
289
|
return { ok: true };
|
|
189
290
|
});
|
|
@@ -202,19 +303,29 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
202
303
|
Filters.EQ(this.options.attachments.attachmentRecordIdFieldName, recordId),
|
|
203
304
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
204
305
|
]);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
212
|
-
|
|
306
|
+
const existingKeys = existingAparts.map((a: any) => a[this.options.attachments.attachmentFieldName]);
|
|
307
|
+
|
|
308
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
309
|
+
const newKeys = metas.map(m => m.key);
|
|
310
|
+
|
|
311
|
+
process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys)
|
|
312
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found new keys (from text)', newKeys);
|
|
313
|
+
|
|
314
|
+
const toDelete = existingKeys.filter(key => !newKeys.includes(key));
|
|
315
|
+
const toAdd = newKeys.filter(key => !existingKeys.includes(key));
|
|
316
|
+
|
|
317
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', toDelete)
|
|
318
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to add', toAdd);
|
|
319
|
+
|
|
320
|
+
const metasToAdd = metas.filter(m => toAdd.includes(m.key));
|
|
213
321
|
await Promise.all([
|
|
214
|
-
deleteAttachmentRecords(adminforth, this.options, toDelete, adminUser),
|
|
215
|
-
createAttachmentRecords(adminforth, this.options, recordId,
|
|
322
|
+
deleteAttachmentRecords(adminforth, this.options, recordId, toDelete, adminUser),
|
|
323
|
+
createAttachmentRecords(adminforth, this.options, recordId, metasToAdd, adminUser)
|
|
216
324
|
]);
|
|
217
325
|
|
|
326
|
+
// Keep alt/title in sync for existing attachments too
|
|
327
|
+
await updateAttachmentRecordsMetadata(adminforth, this.options, recordId, metas, adminUser);
|
|
328
|
+
|
|
218
329
|
return { ok: true };
|
|
219
330
|
|
|
220
331
|
}
|
|
@@ -229,9 +340,9 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
229
340
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
230
341
|
]
|
|
231
342
|
);
|
|
232
|
-
const
|
|
233
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
234
|
-
await deleteAttachmentRecords(adminforth, this.options,
|
|
343
|
+
const existingKeys = existingAparts.map((a: any) => a[this.options.attachments.attachmentFieldName]);
|
|
344
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', existingKeys);
|
|
345
|
+
await deleteAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name], existingKeys, adminUser);
|
|
235
346
|
|
|
236
347
|
return { ok: true };
|
|
237
348
|
}
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -32,8 +32,25 @@ export interface PluginOptions {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* When attachment is created, it will be linked to the resource, by storing id of the resource with editor in attachment resource.
|
|
35
|
+
* Here you define the field name where this id will be stored.
|
|
35
36
|
* For example when RichEditor installed on description field of apartment resource, it will store id of apartment resource.
|
|
37
|
+
*
|
|
38
|
+
* Why we force to store and ask for resource id if we already have record id? Because in amny use cases attachments resource is shared between multiple resources, and record id might be not be unique across resources, but resource id + record id will be always unique.
|
|
36
39
|
*/
|
|
37
40
|
attachmentResourceIdFieldName: string; // e.g. 'apartment_resource_id',
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Optional: field name in attachment resource where title of image will be stored.
|
|
44
|
+
* When in markdown title of image is mentioned e.g. , it will be parsed and stored in attachment resource.
|
|
45
|
+
* If you will update title in markdown, it will be updated in attachment resource as well.
|
|
46
|
+
*/
|
|
47
|
+
attachmentTitleFieldName?: string; // e.g. 'title',
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Optional: field name in attachment resource where alt of image will be stored.
|
|
51
|
+
* When in markdown alt of image is mentioned e.g. , it will be parsed and stored in attachment resource.
|
|
52
|
+
* If you will update alt in markdown, it will be updated in attachment resource as well.
|
|
53
|
+
*/
|
|
54
|
+
attachmentAltFieldName?: string; // e.g. 'alt',
|
|
38
55
|
},
|
|
39
56
|
}
|