@adminforth/markdown 1.10.12 → 1.11.1

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/build.log CHANGED
@@ -14,5 +14,5 @@ custom/tsconfig.json
14
14
  custom/utils/
15
15
  custom/utils/monacoMarkdownToggle.ts
16
16
 
17
- sent 52,957 bytes received 180 bytes 106,274.00 bytes/sec
17
+ sent 52,947 bytes received 180 bytes 106,254.00 bytes/sec
18
18
  total size is 52,265 speedup is 0.98
package/dist/index.js CHANGED
@@ -19,11 +19,24 @@ export default class MarkdownPlugin extends AdminForthPlugin {
19
19
  }
20
20
  // Placeholder for future Upload Plugin API integration.
21
21
  // For now, treat all extracted URLs as plugin-owned public URLs.
22
- isPluginPublicUrl(_url) {
23
- // todo: here we need to check that host name is same as upload plugin, probably create upload plugin endpoint
24
- // should handle cases that user might define custom preview url
25
- // and that local storage has no host name, here, the fact of luck of hostname might be used as
26
- return true;
22
+ isUrlFromPlugin(url) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ if (!this.uploadPlugin)
25
+ return false;
26
+ try {
27
+ const uploadPlugin = this.uploadPlugin;
28
+ if (typeof uploadPlugin.isInternalUrl === 'function') {
29
+ return yield uploadPlugin.isInternalUrl(url);
30
+ }
31
+ else {
32
+ throw new Error('Please update upload plugin and storage adapter');
33
+ }
34
+ }
35
+ catch (err) {
36
+ console.error(`[MarkdownPlugin] Error checking URL ${url}:`, err);
37
+ }
38
+ return false;
39
+ });
27
40
  }
28
41
  validateConfigAfterDiscover(adminforth, resourceConfig) {
29
42
  this.adminforth = adminforth;
@@ -145,23 +158,33 @@ export default class MarkdownPlugin extends AdminForthPlugin {
145
158
  }
146
159
  catch (_a) {
147
160
  // Fallback: strip scheme/host if it looks like a URL, otherwise treat as a path.
148
- return stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
161
+ const key = stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
162
+ // since only local storage adapter returns non-URL paths
163
+ // we need to cut-off express-base
164
+ // e.g. /uploaded-static/cars_description_images-74bbe25a154500c4a2fcb91fb91cea75/sqlite/images_1775114133312.webp
165
+ // should become /sqlite/images_1775114133312.webp
166
+ const cutted = key.split("/").filter(Boolean).slice(2).join("/");
167
+ return cutted;
149
168
  }
150
169
  };
151
170
  const shouldTrackUrl = (url) => {
152
171
  try {
153
- return this.isPluginPublicUrl(url);
172
+ return this.isUrlFromPlugin(url);
154
173
  }
155
174
  catch (err) {
156
175
  console.error('Error checking URL ownership', url, err);
157
176
  return false;
158
177
  }
159
178
  };
160
- const getKeyFromTrackedUrl = (rawUrl) => {
179
+ const getKeyFromTrackedUrl = (rawUrl) => __awaiter(this, void 0, void 0, function* () {
161
180
  const srcTrimmed = rawUrl.trim().replace(/^<|>$/g, '');
162
181
  if (!srcTrimmed || srcTrimmed.startsWith('data:') || srcTrimmed.startsWith('javascript:')) {
163
182
  return null;
164
183
  }
184
+ const isInternal = yield this.isUrlFromPlugin(srcTrimmed);
185
+ if (!isInternal) {
186
+ return null;
187
+ }
165
188
  if (!shouldTrackUrl(srcTrimmed)) {
166
189
  return null;
167
190
  }
@@ -171,7 +194,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
171
194
  return null;
172
195
  }
173
196
  return decodeURIComponent(key);
174
- };
197
+ });
175
198
  const upsertMeta = (byKey, key, next) => {
176
199
  var _a, _b;
177
200
  const existing = byKey.get(key);
@@ -201,38 +224,40 @@ export default class MarkdownPlugin extends AdminForthPlugin {
201
224
  return cleaned || null;
202
225
  };
203
226
  function getAttachmentMetas(markdown) {
204
- var _a, _b, _c, _d, _e, _f;
205
- if (!markdown) {
206
- return [];
207
- }
208
- // Markdown image syntax: ![alt](src) or ![alt](src "title") or ![alt](src 'title')
209
- const imageRegex = /!\[([^\]]*)\]\(\s*([^\s)]+)\s*(?:\s+(?:\"([^\"]*)\"|'([^']*)'))?\s*\)/g;
210
- // HTML embedded media links.
211
- const htmlSrcRegex = /<(?:source|video)\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s"'=<>`]+))[^>]*>/gi;
212
- const byKey = new Map();
213
- for (const match of markdown.matchAll(imageRegex)) {
214
- const altRaw = (_a = match[1]) !== null && _a !== void 0 ? _a : '';
215
- const srcRaw = match[2];
216
- const titleRaw = normalizeAttachmentTitleForDb((_c = ((_b = match[3]) !== null && _b !== void 0 ? _b : match[4])) !== null && _c !== void 0 ? _c : null);
217
- const key = getKeyFromTrackedUrl(srcRaw);
218
- if (!key) {
219
- continue;
227
+ return __awaiter(this, void 0, void 0, function* () {
228
+ var _a, _b, _c, _d, _e, _f;
229
+ if (!markdown) {
230
+ return [];
220
231
  }
221
- upsertMeta(byKey, key, {
222
- alt: altRaw,
223
- title: titleRaw,
224
- });
225
- }
226
- let srcMatch;
227
- while ((srcMatch = htmlSrcRegex.exec(markdown)) !== null) {
228
- const srcRaw = (_f = (_e = (_d = srcMatch[1]) !== null && _d !== void 0 ? _d : srcMatch[2]) !== null && _e !== void 0 ? _e : srcMatch[3]) !== null && _f !== void 0 ? _f : '';
229
- const key = getKeyFromTrackedUrl(srcRaw);
230
- if (!key) {
231
- continue;
232
+ // Markdown image syntax: ![alt](src) or ![alt](src "title") or ![alt](src 'title')
233
+ const imageRegex = /!\[([^\]]*)\]\(\s*([^\s)]+)\s*(?:\s+(?:\"([^\"]*)\"|'([^']*)'))?\s*\)/g;
234
+ // HTML embedded media links.
235
+ const htmlSrcRegex = /<(?:source|video)\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s"'=<>`]+))[^>]*>/gi;
236
+ const byKey = new Map();
237
+ for (const match of markdown.matchAll(imageRegex)) {
238
+ const altRaw = (_a = match[1]) !== null && _a !== void 0 ? _a : '';
239
+ const srcRaw = match[2];
240
+ const titleRaw = normalizeAttachmentTitleForDb((_c = ((_b = match[3]) !== null && _b !== void 0 ? _b : match[4])) !== null && _c !== void 0 ? _c : null);
241
+ const key = yield getKeyFromTrackedUrl(srcRaw);
242
+ if (!key) {
243
+ continue;
244
+ }
245
+ upsertMeta(byKey, key, {
246
+ alt: altRaw,
247
+ title: titleRaw,
248
+ });
232
249
  }
233
- upsertMeta(byKey, key, {});
234
- }
235
- return [...byKey.values()];
250
+ let srcMatch;
251
+ while ((srcMatch = htmlSrcRegex.exec(markdown)) !== null) {
252
+ const srcRaw = (_f = (_e = (_d = srcMatch[1]) !== null && _d !== void 0 ? _d : srcMatch[2]) !== null && _e !== void 0 ? _e : srcMatch[3]) !== null && _f !== void 0 ? _f : '';
253
+ const key = yield getKeyFromTrackedUrl(srcRaw);
254
+ if (!key) {
255
+ continue;
256
+ }
257
+ upsertMeta(byKey, key, {});
258
+ }
259
+ return [...byKey.values()];
260
+ });
236
261
  }
237
262
  const createAttachmentRecords = (adminforth, options, recordId, metas, adminUser) => __awaiter(this, void 0, void 0, function* () {
238
263
  if (!metas.length) {
@@ -334,7 +359,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
334
359
  });
335
360
  (resourceConfig.hooks.create.afterSave).push((_a) => __awaiter(this, [_a], void 0, function* ({ record, adminUser }) {
336
361
  // find all s3Paths in the html
337
- const metas = getAttachmentMetas(record[this.options.fieldName]);
362
+ const metas = yield getAttachmentMetas(record[this.options.fieldName]);
338
363
  const keys = metas.map(m => m.key);
339
364
  process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
340
365
  // create attachment records
@@ -355,7 +380,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
355
380
  Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
356
381
  ]);
357
382
  const existingKeys = existingAparts.map((a) => a[this.options.attachments.attachmentFieldName]);
358
- const metas = getAttachmentMetas(record[this.options.fieldName]);
383
+ const metas = yield getAttachmentMetas(record[this.options.fieldName]);
359
384
  const newKeys = metas.map(m => m.key);
360
385
  process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys);
361
386
  process.env.HEAVY_DEBUG && console.log('📸 Found new keys (from text)', newKeys);
package/index.ts CHANGED
@@ -19,11 +19,19 @@ export default class MarkdownPlugin extends AdminForthPlugin {
19
19
 
20
20
  // Placeholder for future Upload Plugin API integration.
21
21
  // For now, treat all extracted URLs as plugin-owned public URLs.
22
- isPluginPublicUrl(_url: string): boolean {
23
- // todo: here we need to check that host name is same as upload plugin, probably create upload plugin endpoint
24
- // should handle cases that user might define custom preview url
25
- // and that local storage has no host name, here, the fact of luck of hostname might be used as
26
- return true;
22
+ async isUrlFromPlugin(url: string): Promise<boolean> {
23
+ if (!this.uploadPlugin) return false;
24
+ try {
25
+ const uploadPlugin = this.uploadPlugin as any;
26
+ if (typeof uploadPlugin.isInternalUrl === 'function') {
27
+ return await uploadPlugin.isInternalUrl(url);
28
+ } else {
29
+ throw new Error ('Please update upload plugin and storage adapter')
30
+ }
31
+ } catch (err) {
32
+ console.error(`[MarkdownPlugin] Error checking URL ${url}:`, err);
33
+ }
34
+ return false;
27
35
  }
28
36
 
29
37
  validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
@@ -162,24 +170,35 @@ export default class MarkdownPlugin extends AdminForthPlugin {
162
170
  return u.pathname.replace(/^\/+/, '');
163
171
  } catch {
164
172
  // Fallback: strip scheme/host if it looks like a URL, otherwise treat as a path.
165
- return stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
173
+ const key = stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
174
+
175
+ // since only local storage adapter returns non-URL paths
176
+ // we need to cut-off express-base
177
+ // e.g. /uploaded-static/cars_description_images-74bbe25a154500c4a2fcb91fb91cea75/sqlite/images_1775114133312.webp
178
+ // should become /sqlite/images_1775114133312.webp
179
+ const cutted = key.split("/").filter(Boolean).slice(2).join("/");
180
+ return cutted;
166
181
  }
167
182
  };
168
183
 
169
184
  const shouldTrackUrl = (url: string) => {
170
185
  try {
171
- return this.isPluginPublicUrl(url);
186
+ return this.isUrlFromPlugin(url);
172
187
  } catch (err) {
173
188
  console.error('Error checking URL ownership', url, err);
174
189
  return false;
175
190
  }
176
191
  };
177
192
 
178
- const getKeyFromTrackedUrl = (rawUrl: string): string | null => {
193
+ const getKeyFromTrackedUrl = async (rawUrl: string): Promise<string | null> => {
179
194
  const srcTrimmed = rawUrl.trim().replace(/^<|>$/g, '');
180
195
  if (!srcTrimmed || srcTrimmed.startsWith('data:') || srcTrimmed.startsWith('javascript:')) {
181
196
  return null;
182
197
  }
198
+ const isInternal = await this.isUrlFromPlugin(srcTrimmed);
199
+ if (!isInternal) {
200
+ return null;
201
+ }
183
202
  if (!shouldTrackUrl(srcTrimmed)) {
184
203
  return null;
185
204
  }
@@ -225,7 +244,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
225
244
  return cleaned || null;
226
245
  };
227
246
 
228
- function getAttachmentMetas(markdown: string): AttachmentMeta[] {
247
+ async function getAttachmentMetas(markdown: string): Promise<AttachmentMeta[]> {
229
248
  if (!markdown) {
230
249
  return [];
231
250
  }
@@ -242,7 +261,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
242
261
  const srcRaw = match[2];
243
262
  const titleRaw = normalizeAttachmentTitleForDb((match[3] ?? match[4]) ?? null);
244
263
 
245
- const key = getKeyFromTrackedUrl(srcRaw);
264
+ const key = await getKeyFromTrackedUrl(srcRaw);
246
265
  if (!key) {
247
266
  continue;
248
267
  }
@@ -255,7 +274,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
255
274
  let srcMatch: RegExpExecArray | null;
256
275
  while ((srcMatch = htmlSrcRegex.exec(markdown)) !== null) {
257
276
  const srcRaw = srcMatch[1] ?? srcMatch[2] ?? srcMatch[3] ?? '';
258
- const key = getKeyFromTrackedUrl(srcRaw);
277
+ const key = await getKeyFromTrackedUrl(srcRaw);
259
278
  if (!key) {
260
279
  continue;
261
280
  }
@@ -390,7 +409,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
390
409
 
391
410
  (resourceConfig.hooks.create.afterSave).push(async ({ record, adminUser }: { record: any, adminUser: AdminUser }) => {
392
411
  // find all s3Paths in the html
393
- const metas = getAttachmentMetas(record[this.options.fieldName]);
412
+ const metas = await getAttachmentMetas(record[this.options.fieldName]);
394
413
  const keys = metas.map(m => m.key);
395
414
  process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
396
415
  // create attachment records
@@ -416,7 +435,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
416
435
  ]);
417
436
  const existingKeys = existingAparts.map((a: any) => a[this.options.attachments.attachmentFieldName]);
418
437
 
419
- const metas = getAttachmentMetas(record[this.options.fieldName]);
438
+ const metas = await getAttachmentMetas(record[this.options.fieldName]);
420
439
  const newKeys = metas.map(m => m.key);
421
440
 
422
441
  process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/markdown",
3
- "version": "1.10.12",
3
+ "version": "1.11.1",
4
4
  "description": "Markdown plugin for adminforth",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",