@adminforth/markdown 1.3.0 → 1.5.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/index.js +143 -36
- package/index.ts +186 -54
- package/package.json +1 -1
- package/types.ts +14 -0
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) {
|
|
@@ -99,36 +111,77 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
99
111
|
};
|
|
100
112
|
const editorRecordPkField = resourceConfig.columns.find(c => c.primaryKey);
|
|
101
113
|
if (this.options.attachments) {
|
|
102
|
-
|
|
114
|
+
const stripQueryAndHash = (value) => value.split('#')[0].split('?')[0];
|
|
115
|
+
const extractKeyFromUrl = (url) => {
|
|
116
|
+
// Supports absolute https/http URLs and protocol-relative URLs.
|
|
117
|
+
// Returns the object key as a path without leading slashes.
|
|
118
|
+
try {
|
|
119
|
+
const normalized = url.startsWith('//') ? `https:${url}` : url;
|
|
120
|
+
const u = new URL(normalized);
|
|
121
|
+
return u.pathname.replace(/^\/+/, '');
|
|
122
|
+
}
|
|
123
|
+
catch (_a) {
|
|
124
|
+
// Fallback: strip scheme/host if it looks like a URL, otherwise treat as a path.
|
|
125
|
+
return stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
function getAttachmentMetas(markdown) {
|
|
129
|
+
var _a, _b, _c;
|
|
103
130
|
if (!markdown) {
|
|
104
131
|
return [];
|
|
105
132
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
// Minimal image syntax:  or  or 
|
|
134
|
+
// We track external (http/https) and relative sources, but skip data: URLs.
|
|
135
|
+
const imageRegex = /!\[([^\]]*)\]\(\s*([^\s)]+)\s*(?:\s+(?:\"([^\"]*)\"|'([^']*)'))?\s*\)/g;
|
|
136
|
+
const byKey = new Map();
|
|
137
|
+
for (const match of markdown.matchAll(imageRegex)) {
|
|
138
|
+
const altRaw = (_a = match[1]) !== null && _a !== void 0 ? _a : '';
|
|
139
|
+
const srcRaw = match[2];
|
|
140
|
+
const titleRaw = (_c = ((_b = match[3]) !== null && _b !== void 0 ? _b : match[4])) !== null && _c !== void 0 ? _c : null;
|
|
141
|
+
const srcTrimmed = srcRaw.trim().replace(/^<|>$/g, '');
|
|
142
|
+
if (!srcTrimmed || srcTrimmed.startsWith('data:')) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const srcNoQuery = stripQueryAndHash(srcTrimmed);
|
|
146
|
+
const key = extractKeyFromUrl(srcNoQuery);
|
|
147
|
+
if (!key) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
byKey.set(key, {
|
|
151
|
+
key,
|
|
152
|
+
alt: altRaw,
|
|
153
|
+
title: titleRaw,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return [...byKey.values()];
|
|
111
157
|
}
|
|
112
|
-
const createAttachmentRecords = (adminforth, options, recordId,
|
|
113
|
-
|
|
158
|
+
const createAttachmentRecords = (adminforth, options, recordId, metas, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
159
|
+
if (!metas.length) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
114
162
|
process.env.HEAVY_DEBUG && console.log('📸 Creating attachment records', JSON.stringify(recordId));
|
|
115
163
|
try {
|
|
116
|
-
yield Promise.all(
|
|
117
|
-
console.log('Processing path:', s3Path);
|
|
164
|
+
yield Promise.all(metas.map((meta) => __awaiter(this, void 0, void 0, function* () {
|
|
118
165
|
try {
|
|
166
|
+
const recordToCreate = {
|
|
167
|
+
[options.attachments.attachmentFieldName]: meta.key,
|
|
168
|
+
[options.attachments.attachmentRecordIdFieldName]: recordId,
|
|
169
|
+
[options.attachments.attachmentResourceIdFieldName]: resourceConfig.resourceId,
|
|
170
|
+
};
|
|
171
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
172
|
+
recordToCreate[options.attachments.attachmentTitleFieldName] = meta.title;
|
|
173
|
+
}
|
|
174
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
175
|
+
recordToCreate[options.attachments.attachmentAltFieldName] = meta.alt;
|
|
176
|
+
}
|
|
119
177
|
yield adminforth.createResourceRecord({
|
|
120
178
|
resource: this.attachmentResource,
|
|
121
|
-
record:
|
|
122
|
-
[options.attachments.attachmentFieldName]: extractKey(s3Path),
|
|
123
|
-
[options.attachments.attachmentRecordIdFieldName]: recordId,
|
|
124
|
-
[options.attachments.attachmentResourceIdFieldName]: resourceConfig.resourceId,
|
|
125
|
-
},
|
|
179
|
+
record: recordToCreate,
|
|
126
180
|
adminUser,
|
|
127
181
|
});
|
|
128
|
-
console.log('Successfully created record for:', s3Path);
|
|
129
182
|
}
|
|
130
183
|
catch (err) {
|
|
131
|
-
console.error('Error creating record for',
|
|
184
|
+
console.error('Error creating record for', meta.key, err);
|
|
132
185
|
}
|
|
133
186
|
})));
|
|
134
187
|
}
|
|
@@ -136,12 +189,16 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
136
189
|
console.error('Error in Promise.all', err);
|
|
137
190
|
}
|
|
138
191
|
});
|
|
139
|
-
const deleteAttachmentRecords = (adminforth, options,
|
|
140
|
-
if (!
|
|
192
|
+
const deleteAttachmentRecords = (adminforth, options, recordId, keys, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
if (!keys.length) {
|
|
141
194
|
return;
|
|
142
195
|
}
|
|
143
196
|
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
144
|
-
const attachments = yield adminforth.resource(options.attachments.attachmentResource).list(
|
|
197
|
+
const attachments = yield adminforth.resource(options.attachments.attachmentResource).list([
|
|
198
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
199
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId),
|
|
200
|
+
Filters.IN(options.attachments.attachmentFieldName, keys),
|
|
201
|
+
]);
|
|
145
202
|
yield Promise.all(attachments.map((a) => __awaiter(this, void 0, void 0, function* () {
|
|
146
203
|
yield adminforth.deleteResourceRecord({
|
|
147
204
|
resource: this.attachmentResource,
|
|
@@ -151,12 +208,58 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
151
208
|
});
|
|
152
209
|
})));
|
|
153
210
|
});
|
|
211
|
+
const updateAttachmentRecordsMetadata = (adminforth, options, recordId, metas, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
212
|
+
if (!metas.length) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (!options.attachments.attachmentTitleFieldName && !options.attachments.attachmentAltFieldName) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
219
|
+
const metaByKey = new Map(metas.map(m => [m.key, m]));
|
|
220
|
+
const existingAparts = yield adminforth.resource(options.attachments.attachmentResource).list([
|
|
221
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
222
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
223
|
+
]);
|
|
224
|
+
yield Promise.all(existingAparts.map((a) => __awaiter(this, void 0, void 0, function* () {
|
|
225
|
+
var _a, _b, _c, _d;
|
|
226
|
+
const key = a[options.attachments.attachmentFieldName];
|
|
227
|
+
const meta = metaByKey.get(key);
|
|
228
|
+
if (!meta) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const patch = {};
|
|
232
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
233
|
+
const field = options.attachments.attachmentTitleFieldName;
|
|
234
|
+
if (((_a = a[field]) !== null && _a !== void 0 ? _a : null) !== ((_b = meta.title) !== null && _b !== void 0 ? _b : null)) {
|
|
235
|
+
patch[field] = meta.title;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
239
|
+
const field = options.attachments.attachmentAltFieldName;
|
|
240
|
+
if (((_c = a[field]) !== null && _c !== void 0 ? _c : null) !== ((_d = meta.alt) !== null && _d !== void 0 ? _d : null)) {
|
|
241
|
+
patch[field] = meta.alt;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!Object.keys(patch).length) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
yield adminforth.updateResourceRecord({
|
|
248
|
+
resource: this.attachmentResource,
|
|
249
|
+
recordId: a[attachmentPrimaryKeyField.name],
|
|
250
|
+
record: patch,
|
|
251
|
+
oldRecord: a,
|
|
252
|
+
adminUser,
|
|
253
|
+
});
|
|
254
|
+
})));
|
|
255
|
+
});
|
|
154
256
|
(resourceConfig.hooks.create.afterSave).push((_a) => __awaiter(this, [_a], void 0, function* ({ record, adminUser }) {
|
|
155
257
|
// find all s3Paths in the html
|
|
156
|
-
const
|
|
157
|
-
|
|
258
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
259
|
+
const keys = metas.map(m => m.key);
|
|
260
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
|
|
158
261
|
// create attachment records
|
|
159
|
-
yield createAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name],
|
|
262
|
+
yield createAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name], metas, adminUser);
|
|
160
263
|
return { ok: true };
|
|
161
264
|
}));
|
|
162
265
|
// after edit we need to delete attachments that are not in the html anymore
|
|
@@ -172,18 +275,22 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
172
275
|
Filters.EQ(this.options.attachments.attachmentRecordIdFieldName, recordId),
|
|
173
276
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
174
277
|
]);
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
process.env.HEAVY_DEBUG && console.log('📸
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
278
|
+
const existingKeys = existingAparts.map((a) => a[this.options.attachments.attachmentFieldName]);
|
|
279
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
280
|
+
const newKeys = metas.map(m => m.key);
|
|
281
|
+
process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys);
|
|
282
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found new keys (from text)', newKeys);
|
|
283
|
+
const toDelete = existingKeys.filter(key => !newKeys.includes(key));
|
|
284
|
+
const toAdd = newKeys.filter(key => !existingKeys.includes(key));
|
|
285
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', toDelete);
|
|
286
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to add', toAdd);
|
|
287
|
+
const metasToAdd = metas.filter(m => toAdd.includes(m.key));
|
|
183
288
|
yield Promise.all([
|
|
184
|
-
deleteAttachmentRecords(adminforth, this.options, toDelete, adminUser),
|
|
185
|
-
createAttachmentRecords(adminforth, this.options, recordId,
|
|
289
|
+
deleteAttachmentRecords(adminforth, this.options, recordId, toDelete, adminUser),
|
|
290
|
+
createAttachmentRecords(adminforth, this.options, recordId, metasToAdd, adminUser)
|
|
186
291
|
]);
|
|
292
|
+
// Keep alt/title in sync for existing attachments too
|
|
293
|
+
yield updateAttachmentRecordsMetadata(adminforth, this.options, recordId, metas, adminUser);
|
|
187
294
|
return { ok: true };
|
|
188
295
|
}));
|
|
189
296
|
// after delete we need to delete all attachments
|
|
@@ -192,9 +299,9 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
192
299
|
Filters.EQ(this.options.attachments.attachmentRecordIdFieldName, record[editorRecordPkField.name]),
|
|
193
300
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
194
301
|
]);
|
|
195
|
-
const
|
|
196
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
197
|
-
yield deleteAttachmentRecords(adminforth, this.options,
|
|
302
|
+
const existingKeys = existingAparts.map((a) => a[this.options.attachments.attachmentFieldName]);
|
|
303
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', existingKeys);
|
|
304
|
+
yield deleteAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name], existingKeys, adminUser);
|
|
198
305
|
return { ok: true };
|
|
199
306
|
}));
|
|
200
307
|
}
|
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 &&
|
|
@@ -106,43 +120,91 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
106
120
|
const editorRecordPkField = resourceConfig.columns.find(c => c.primaryKey);
|
|
107
121
|
if (this.options.attachments) {
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
type AttachmentMeta = { key: string; alt: string | null; title: string | null };
|
|
124
|
+
|
|
125
|
+
const stripQueryAndHash = (value: string) => value.split('#')[0].split('?')[0];
|
|
126
|
+
|
|
127
|
+
const extractKeyFromUrl = (url: string) => {
|
|
128
|
+
// Supports absolute https/http URLs and protocol-relative URLs.
|
|
129
|
+
// Returns the object key as a path without leading slashes.
|
|
130
|
+
try {
|
|
131
|
+
const normalized = url.startsWith('//') ? `https:${url}` : url;
|
|
132
|
+
const u = new URL(normalized);
|
|
133
|
+
return u.pathname.replace(/^\/+/, '');
|
|
134
|
+
} catch {
|
|
135
|
+
// Fallback: strip scheme/host if it looks like a URL, otherwise treat as a path.
|
|
136
|
+
return stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function getAttachmentMetas(markdown: string): AttachmentMeta[] {
|
|
110
141
|
if (!markdown) {
|
|
111
142
|
return [];
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
145
|
+
// Minimal image syntax:  or  or 
|
|
146
|
+
// We track external (http/https) and relative sources, but skip data: URLs.
|
|
147
|
+
const imageRegex = /!\[([^\]]*)\]\(\s*([^\s)]+)\s*(?:\s+(?:\"([^\"]*)\"|'([^']*)'))?\s*\)/g;
|
|
117
148
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
149
|
+
const byKey = new Map<string, AttachmentMeta>();
|
|
150
|
+
for (const match of markdown.matchAll(imageRegex)) {
|
|
151
|
+
const altRaw = match[1] ?? '';
|
|
152
|
+
const srcRaw = match[2];
|
|
153
|
+
const titleRaw = (match[3] ?? match[4]) ?? null;
|
|
154
|
+
|
|
155
|
+
const srcTrimmed = srcRaw.trim().replace(/^<|>$/g, '');
|
|
156
|
+
if (!srcTrimmed || srcTrimmed.startsWith('data:')) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const srcNoQuery = stripQueryAndHash(srcTrimmed);
|
|
161
|
+
const key = extractKeyFromUrl(srcNoQuery);
|
|
162
|
+
if (!key) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
byKey.set(key, {
|
|
166
|
+
key,
|
|
167
|
+
alt: altRaw,
|
|
168
|
+
title: titleRaw,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return [...byKey.values()];
|
|
121
172
|
}
|
|
122
173
|
|
|
123
174
|
const createAttachmentRecords = async (
|
|
124
|
-
adminforth: IAdminForth,
|
|
175
|
+
adminforth: IAdminForth,
|
|
176
|
+
options: PluginOptions,
|
|
177
|
+
recordId: any,
|
|
178
|
+
metas: AttachmentMeta[],
|
|
179
|
+
adminUser: AdminUser
|
|
125
180
|
) => {
|
|
126
|
-
|
|
181
|
+
if (!metas.length) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
127
184
|
process.env.HEAVY_DEBUG && console.log('📸 Creating attachment records', JSON.stringify(recordId))
|
|
128
185
|
try {
|
|
129
|
-
await Promise.all(
|
|
130
|
-
console.log('Processing path:', s3Path);
|
|
186
|
+
await Promise.all(metas.map(async (meta) => {
|
|
131
187
|
try {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
188
|
+
const recordToCreate: any = {
|
|
189
|
+
[options.attachments.attachmentFieldName]: meta.key,
|
|
190
|
+
[options.attachments.attachmentRecordIdFieldName]: recordId,
|
|
191
|
+
[options.attachments.attachmentResourceIdFieldName]: resourceConfig.resourceId,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
195
|
+
recordToCreate[options.attachments.attachmentTitleFieldName] = meta.title;
|
|
196
|
+
}
|
|
197
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
198
|
+
recordToCreate[options.attachments.attachmentAltFieldName] = meta.alt;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await adminforth.createResourceRecord({
|
|
202
|
+
resource: this.attachmentResource,
|
|
203
|
+
record: recordToCreate,
|
|
204
|
+
adminUser,
|
|
205
|
+
});
|
|
144
206
|
} catch (err) {
|
|
145
|
-
console.error('Error creating record for',
|
|
207
|
+
console.error('Error creating record for', meta.key, err);
|
|
146
208
|
}
|
|
147
209
|
}));
|
|
148
210
|
} catch (err) {
|
|
@@ -151,35 +213,95 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
151
213
|
}
|
|
152
214
|
|
|
153
215
|
const deleteAttachmentRecords = async (
|
|
154
|
-
adminforth: IAdminForth,
|
|
216
|
+
adminforth: IAdminForth,
|
|
217
|
+
options: PluginOptions,
|
|
218
|
+
recordId: any,
|
|
219
|
+
keys: string[],
|
|
220
|
+
adminUser: AdminUser
|
|
155
221
|
) => {
|
|
156
|
-
if (!
|
|
222
|
+
if (!keys.length) {
|
|
157
223
|
return;
|
|
158
224
|
}
|
|
159
225
|
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
160
|
-
const attachments = await adminforth.resource(options.attachments.attachmentResource).list(
|
|
161
|
-
Filters.
|
|
162
|
-
|
|
226
|
+
const attachments = await adminforth.resource(options.attachments.attachmentResource).list([
|
|
227
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
228
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId),
|
|
229
|
+
Filters.IN(options.attachments.attachmentFieldName, keys),
|
|
230
|
+
]);
|
|
231
|
+
|
|
163
232
|
await Promise.all(attachments.map(async (a: any) => {
|
|
164
|
-
await adminforth.deleteResourceRecord(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
)
|
|
233
|
+
await adminforth.deleteResourceRecord({
|
|
234
|
+
resource: this.attachmentResource,
|
|
235
|
+
recordId: a[attachmentPrimaryKeyField.name],
|
|
236
|
+
adminUser,
|
|
237
|
+
record: a,
|
|
238
|
+
})
|
|
172
239
|
}))
|
|
173
240
|
}
|
|
241
|
+
|
|
242
|
+
const updateAttachmentRecordsMetadata = async (
|
|
243
|
+
adminforth: IAdminForth,
|
|
244
|
+
options: PluginOptions,
|
|
245
|
+
recordId: any,
|
|
246
|
+
metas: AttachmentMeta[],
|
|
247
|
+
adminUser: AdminUser
|
|
248
|
+
) => {
|
|
249
|
+
if (!metas.length) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!options.attachments.attachmentTitleFieldName && !options.attachments.attachmentAltFieldName) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const attachmentPrimaryKeyField = this.attachmentResource.columns.find(c => c.primaryKey);
|
|
256
|
+
const metaByKey = new Map(metas.map(m => [m.key, m] as const));
|
|
257
|
+
|
|
258
|
+
const existingAparts = await adminforth.resource(options.attachments.attachmentResource).list([
|
|
259
|
+
Filters.EQ(options.attachments.attachmentRecordIdFieldName, recordId),
|
|
260
|
+
Filters.EQ(options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
await Promise.all(existingAparts.map(async (a: any) => {
|
|
264
|
+
const key = a[options.attachments.attachmentFieldName];
|
|
265
|
+
const meta = metaByKey.get(key);
|
|
266
|
+
if (!meta) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const patch: any = {};
|
|
271
|
+
if (options.attachments.attachmentTitleFieldName) {
|
|
272
|
+
const field = options.attachments.attachmentTitleFieldName;
|
|
273
|
+
if ((a[field] ?? null) !== (meta.title ?? null)) {
|
|
274
|
+
patch[field] = meta.title;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (options.attachments.attachmentAltFieldName) {
|
|
278
|
+
const field = options.attachments.attachmentAltFieldName;
|
|
279
|
+
if ((a[field] ?? null) !== (meta.alt ?? null)) {
|
|
280
|
+
patch[field] = meta.alt;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!Object.keys(patch).length) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await adminforth.updateResourceRecord({
|
|
288
|
+
resource: this.attachmentResource,
|
|
289
|
+
recordId: a[attachmentPrimaryKeyField.name],
|
|
290
|
+
record: patch,
|
|
291
|
+
oldRecord: a,
|
|
292
|
+
adminUser,
|
|
293
|
+
});
|
|
294
|
+
}));
|
|
295
|
+
}
|
|
174
296
|
|
|
175
297
|
(resourceConfig.hooks.create.afterSave).push(async ({ record, adminUser }: { record: any, adminUser: AdminUser }) => {
|
|
176
298
|
// find all s3Paths in the html
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
299
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
300
|
+
const keys = metas.map(m => m.key);
|
|
301
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
|
|
180
302
|
// create attachment records
|
|
181
303
|
await createAttachmentRecords(
|
|
182
|
-
adminforth, this.options, record[editorRecordPkField.name],
|
|
304
|
+
adminforth, this.options, record[editorRecordPkField.name], metas, adminUser);
|
|
183
305
|
|
|
184
306
|
return { ok: true };
|
|
185
307
|
});
|
|
@@ -198,19 +320,29 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
198
320
|
Filters.EQ(this.options.attachments.attachmentRecordIdFieldName, recordId),
|
|
199
321
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
200
322
|
]);
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
208
|
-
|
|
323
|
+
const existingKeys = existingAparts.map((a: any) => a[this.options.attachments.attachmentFieldName]);
|
|
324
|
+
|
|
325
|
+
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
326
|
+
const newKeys = metas.map(m => m.key);
|
|
327
|
+
|
|
328
|
+
process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys)
|
|
329
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found new keys (from text)', newKeys);
|
|
330
|
+
|
|
331
|
+
const toDelete = existingKeys.filter(key => !newKeys.includes(key));
|
|
332
|
+
const toAdd = newKeys.filter(key => !existingKeys.includes(key));
|
|
333
|
+
|
|
334
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', toDelete)
|
|
335
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to add', toAdd);
|
|
336
|
+
|
|
337
|
+
const metasToAdd = metas.filter(m => toAdd.includes(m.key));
|
|
209
338
|
await Promise.all([
|
|
210
|
-
deleteAttachmentRecords(adminforth, this.options, toDelete, adminUser),
|
|
211
|
-
createAttachmentRecords(adminforth, this.options, recordId,
|
|
339
|
+
deleteAttachmentRecords(adminforth, this.options, recordId, toDelete, adminUser),
|
|
340
|
+
createAttachmentRecords(adminforth, this.options, recordId, metasToAdd, adminUser)
|
|
212
341
|
]);
|
|
213
342
|
|
|
343
|
+
// Keep alt/title in sync for existing attachments too
|
|
344
|
+
await updateAttachmentRecordsMetadata(adminforth, this.options, recordId, metas, adminUser);
|
|
345
|
+
|
|
214
346
|
return { ok: true };
|
|
215
347
|
|
|
216
348
|
}
|
|
@@ -225,9 +357,9 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
225
357
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
226
358
|
]
|
|
227
359
|
);
|
|
228
|
-
const
|
|
229
|
-
process.env.HEAVY_DEBUG && console.log('📸 Found
|
|
230
|
-
await deleteAttachmentRecords(adminforth, this.options,
|
|
360
|
+
const existingKeys = existingAparts.map((a: any) => a[this.options.attachments.attachmentFieldName]);
|
|
361
|
+
process.env.HEAVY_DEBUG && console.log('📸 Found keys to delete', existingKeys);
|
|
362
|
+
await deleteAttachmentRecords(adminforth, this.options, record[editorRecordPkField.name], existingKeys, adminUser);
|
|
231
363
|
|
|
232
364
|
return { ok: true };
|
|
233
365
|
}
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -38,5 +38,19 @@ export interface PluginOptions {
|
|
|
38
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.
|
|
39
39
|
*/
|
|
40
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',
|
|
41
55
|
},
|
|
42
56
|
}
|