@qkix/strapi-plugin-better-blocks 0.23.0 → 0.23.2
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 +85 -12
- package/dist/admin/index.js +220 -220
- package/dist/admin/index.mjs +3999 -3905
- package/dist/admin/src/blockRegistry.d.ts +87 -0
- package/dist/admin/src/components/BlocksInput/Blocks/Table.d.ts +1 -1
- package/dist/admin/src/components/BlocksInput/Blocks/embedProviders.d.ts +2 -2
- package/dist/admin/src/components/BlocksInput/Blocks/tableGrid.d.ts +1 -1
- package/dist/admin/src/components/BlocksInput/BlocksEditor.d.ts +8 -0
- package/dist/admin/src/components/BlocksInput/MenuScrollbar.d.ts +1 -1
- package/dist/admin/src/components/BlocksInput/utils/types.d.ts +4 -4
- package/dist/admin/src/hooks/useDragAndDrop.d.ts +1 -1
- package/dist/admin/src/index.d.ts +7 -0
- package/dist/server/index.js +1 -1
- package/dist/server/index.mjs +154 -110
- package/dist/server/src/config/index.d.ts +1 -0
- package/dist/server/src/controllers/oembed.d.ts +1 -1
- package/dist/server/src/index.d.ts +2 -0
- package/dist/server/src/services/index.d.ts +1 -0
- package/dist/server/src/services/oembed.d.ts +44 -1
- package/dist/server/src/services/ttlCache.d.ts +23 -0
- package/package.json +17 -7
package/dist/server/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const
|
|
2
|
-
},
|
|
3
|
-
}, y = ({ strapi:
|
|
4
|
-
|
|
1
|
+
const k = ({ strapi: o }) => {
|
|
2
|
+
}, w = ({ strapi: o }) => {
|
|
3
|
+
}, y = ({ strapi: o }) => {
|
|
4
|
+
o.customFields.register({
|
|
5
5
|
name: "better-blocks",
|
|
6
6
|
plugin: "better-blocks",
|
|
7
7
|
type: "json",
|
|
@@ -10,7 +10,7 @@ const g = ({ strapi: t }) => {
|
|
|
10
10
|
isResizable: !1
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
|
-
},
|
|
13
|
+
}, C = {
|
|
14
14
|
default: {
|
|
15
15
|
/**
|
|
16
16
|
* Defaults for the collapsible Details / Summary block.
|
|
@@ -97,32 +97,36 @@ const g = ({ strapi: t }) => {
|
|
|
97
97
|
cache: !0,
|
|
98
98
|
cacheTTL: 86400,
|
|
99
99
|
// seconds (24h)
|
|
100
|
+
// Upper bound on cached embeds, evicted least-recently-used. Without one
|
|
101
|
+
// the cache keeps an entry for every post ever embedded, for the life of
|
|
102
|
+
// the process — expiry alone never frees anything nobody asks for again.
|
|
103
|
+
cacheMaxEntries: 500,
|
|
100
104
|
instagram: { accessToken: void 0 },
|
|
101
105
|
facebook: { accessToken: void 0 }
|
|
102
106
|
}
|
|
103
107
|
},
|
|
104
|
-
validator(
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
108
|
+
validator(o = {}) {
|
|
109
|
+
const t = o?.details?.style;
|
|
110
|
+
if (t && t !== "github" && t !== "custom")
|
|
107
111
|
throw new Error(
|
|
108
|
-
`[better-blocks] config.details.style must be "github" or "custom" (received "${
|
|
112
|
+
`[better-blocks] config.details.style must be "github" or "custom" (received "${t}")`
|
|
109
113
|
);
|
|
110
114
|
}
|
|
111
|
-
},
|
|
112
|
-
index(
|
|
113
|
-
|
|
115
|
+
}, v = {}, x = ({ strapi: o }) => ({
|
|
116
|
+
index(t) {
|
|
117
|
+
t.body = o.plugin("better-blocks").service("service").getWelcomeMessage();
|
|
114
118
|
},
|
|
115
119
|
/**
|
|
116
120
|
* Exposes the plugin config (config/plugins.js merged with server defaults) to
|
|
117
121
|
* the admin panel, so blocks like Details can honour admin-level settings.
|
|
118
122
|
*/
|
|
119
|
-
getConfig(
|
|
120
|
-
|
|
121
|
-
details:
|
|
123
|
+
getConfig(t) {
|
|
124
|
+
t.body = {
|
|
125
|
+
details: o.plugin("better-blocks").config("details", {
|
|
122
126
|
defaultSummary: "Click to expand",
|
|
123
127
|
style: "github"
|
|
124
128
|
}),
|
|
125
|
-
button:
|
|
129
|
+
button: o.plugin("better-blocks").config("button", {
|
|
126
130
|
defaultStyle: {
|
|
127
131
|
backgroundColor: "#4945ff",
|
|
128
132
|
textColor: "#ffffff",
|
|
@@ -155,7 +159,7 @@ const g = ({ strapi: t }) => {
|
|
|
155
159
|
}
|
|
156
160
|
}),
|
|
157
161
|
social: (() => {
|
|
158
|
-
const e =
|
|
162
|
+
const e = o.plugin("better-blocks").config("social", {
|
|
159
163
|
enabled: !0,
|
|
160
164
|
platforms: [
|
|
161
165
|
"twitter",
|
|
@@ -175,35 +179,35 @@ const g = ({ strapi: t }) => {
|
|
|
175
179
|
})()
|
|
176
180
|
};
|
|
177
181
|
}
|
|
178
|
-
}),
|
|
182
|
+
}), E = [
|
|
179
183
|
"twitter",
|
|
180
184
|
"instagram",
|
|
181
185
|
"facebook",
|
|
182
186
|
"tiktok",
|
|
183
187
|
"linkedin",
|
|
184
188
|
"pinterest"
|
|
185
|
-
], $ = ({ strapi:
|
|
189
|
+
], $ = ({ strapi: o }) => ({
|
|
186
190
|
/**
|
|
187
191
|
* GET /better-blocks/oembed?url=...&platform=...
|
|
188
|
-
* Proxies the platform oEmbed request server-side and returns the
|
|
192
|
+
* Proxies the platform oEmbed request server-side and returns the normalized
|
|
189
193
|
* payload used by the social-embed block.
|
|
190
194
|
*/
|
|
191
|
-
async fetch(
|
|
192
|
-
const { url: e, platform:
|
|
195
|
+
async fetch(t) {
|
|
196
|
+
const { url: e, platform: r } = t.query;
|
|
193
197
|
if (!e || typeof e != "string")
|
|
194
|
-
return
|
|
195
|
-
const
|
|
198
|
+
return t.badRequest('Missing required "url" query parameter.');
|
|
199
|
+
const n = r && E.includes(r) ? r : void 0;
|
|
196
200
|
try {
|
|
197
|
-
|
|
198
|
-
} catch (
|
|
199
|
-
const
|
|
200
|
-
return
|
|
201
|
+
t.body = await o.plugin("better-blocks").service("oembed").fetch(e, n);
|
|
202
|
+
} catch (s) {
|
|
203
|
+
const c = s instanceof Error ? s.message : "oEmbed request failed.";
|
|
204
|
+
return t.badRequest(c);
|
|
201
205
|
}
|
|
202
206
|
}
|
|
203
207
|
}), T = {
|
|
204
|
-
controller:
|
|
208
|
+
controller: x,
|
|
205
209
|
oembed: $
|
|
206
|
-
},
|
|
210
|
+
}, R = {}, _ = {}, L = [
|
|
207
211
|
{
|
|
208
212
|
method: "GET",
|
|
209
213
|
path: "/",
|
|
@@ -213,7 +217,7 @@ const g = ({ strapi: t }) => {
|
|
|
213
217
|
policies: []
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
|
-
],
|
|
220
|
+
], S = [
|
|
217
221
|
{
|
|
218
222
|
method: "GET",
|
|
219
223
|
path: "/config",
|
|
@@ -232,20 +236,54 @@ const g = ({ strapi: t }) => {
|
|
|
232
236
|
policies: ["admin::isAuthenticatedAdmin"]
|
|
233
237
|
}
|
|
234
238
|
}
|
|
235
|
-
],
|
|
239
|
+
], z = {
|
|
236
240
|
"content-api": {
|
|
237
241
|
type: "content-api",
|
|
238
242
|
routes: L
|
|
239
243
|
},
|
|
240
244
|
admin: {
|
|
241
245
|
type: "admin",
|
|
242
|
-
routes:
|
|
246
|
+
routes: S
|
|
243
247
|
}
|
|
244
|
-
},
|
|
248
|
+
}, A = ({ strapi: o }) => ({
|
|
245
249
|
getWelcomeMessage() {
|
|
246
250
|
return "Welcome to Strapi 🚀";
|
|
247
251
|
}
|
|
248
|
-
}),
|
|
252
|
+
}), U = ({ max: o }) => {
|
|
253
|
+
if (o < 1)
|
|
254
|
+
throw new Error(`ttlCache: max must be at least 1 (received ${o})`);
|
|
255
|
+
const t = /* @__PURE__ */ new Map();
|
|
256
|
+
return {
|
|
257
|
+
get(e) {
|
|
258
|
+
const r = t.get(e);
|
|
259
|
+
if (r) {
|
|
260
|
+
if (r.expires <= Date.now()) {
|
|
261
|
+
t.delete(e);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
return t.delete(e), t.set(e, r), r.value;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
set(e, r, n) {
|
|
268
|
+
if (t.delete(e), t.size >= o) {
|
|
269
|
+
const s = Date.now();
|
|
270
|
+
for (const [c, i] of t)
|
|
271
|
+
i.expires <= s && t.delete(c);
|
|
272
|
+
}
|
|
273
|
+
for (; t.size >= o; ) {
|
|
274
|
+
const s = t.keys().next();
|
|
275
|
+
if (s.done) break;
|
|
276
|
+
t.delete(s.value);
|
|
277
|
+
}
|
|
278
|
+
t.set(e, { expires: Date.now() + n, value: r });
|
|
279
|
+
},
|
|
280
|
+
delete: (e) => t.delete(e),
|
|
281
|
+
clear: () => t.clear(),
|
|
282
|
+
get size() {
|
|
283
|
+
return t.size;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}, I = [
|
|
249
287
|
{
|
|
250
288
|
platform: "twitter",
|
|
251
289
|
re: /(?:twitter\.com|x\.com)\/[^/]+\/status(?:es)?\/\d+/i
|
|
@@ -255,28 +293,28 @@ const g = ({ strapi: t }) => {
|
|
|
255
293
|
{ platform: "facebook", re: /facebook\.com\//i },
|
|
256
294
|
{ platform: "linkedin", re: /linkedin\.com\//i },
|
|
257
295
|
{ platform: "pinterest", re: /(?:pinterest\.[a-z.]+|pin\.it)\//i }
|
|
258
|
-
],
|
|
259
|
-
const
|
|
260
|
-
return
|
|
261
|
-
},
|
|
262
|
-
const
|
|
263
|
-
switch (
|
|
296
|
+
], g = (o) => {
|
|
297
|
+
const t = I.find(({ re: e }) => e.test(o));
|
|
298
|
+
return t ? t.platform : null;
|
|
299
|
+
}, M = (o, t, e) => {
|
|
300
|
+
const r = encodeURIComponent(t);
|
|
301
|
+
switch (o) {
|
|
264
302
|
case "twitter":
|
|
265
|
-
return `https://publish.twitter.com/oembed?url=${
|
|
303
|
+
return `https://publish.twitter.com/oembed?url=${r}&omit_script=true&dnt=true`;
|
|
266
304
|
case "tiktok":
|
|
267
|
-
return `https://www.tiktok.com/oembed?url=${
|
|
305
|
+
return `https://www.tiktok.com/oembed?url=${r}`;
|
|
268
306
|
case "pinterest":
|
|
269
|
-
return `https://www.pinterest.com/oembed.json?url=${
|
|
307
|
+
return `https://www.pinterest.com/oembed.json?url=${r}`;
|
|
270
308
|
case "instagram": {
|
|
271
|
-
const
|
|
272
|
-
return
|
|
273
|
-
|
|
309
|
+
const n = e.instagram?.accessToken;
|
|
310
|
+
return n ? `https://graph.facebook.com/v17.0/instagram_oembed?url=${r}&access_token=${encodeURIComponent(
|
|
311
|
+
n
|
|
274
312
|
)}&omitscript=true` : null;
|
|
275
313
|
}
|
|
276
314
|
case "facebook": {
|
|
277
|
-
const
|
|
278
|
-
return
|
|
279
|
-
|
|
315
|
+
const n = e.facebook?.accessToken;
|
|
316
|
+
return n ? `https://graph.facebook.com/v17.0/oembed_post?url=${r}&access_token=${encodeURIComponent(
|
|
317
|
+
n
|
|
280
318
|
)}&omitscript=true` : null;
|
|
281
319
|
}
|
|
282
320
|
case "linkedin":
|
|
@@ -284,32 +322,32 @@ const g = ({ strapi: t }) => {
|
|
|
284
322
|
default:
|
|
285
323
|
return null;
|
|
286
324
|
}
|
|
287
|
-
}, P = async (
|
|
288
|
-
if (!/^https?:\/\/pin\.it\//i.test(
|
|
325
|
+
}, P = async (o) => {
|
|
326
|
+
if (!/^https?:\/\/pin\.it\//i.test(o)) return o;
|
|
289
327
|
try {
|
|
290
|
-
const e = (await fetch(
|
|
291
|
-
return /pinterest\.[a-z.]+\/pin\//i.test(e) ? e :
|
|
328
|
+
const e = (await fetch(o, { redirect: "follow" })).url || o;
|
|
329
|
+
return /pinterest\.[a-z.]+\/pin\//i.test(e) ? e : o;
|
|
292
330
|
} catch {
|
|
293
|
-
return
|
|
331
|
+
return o;
|
|
294
332
|
}
|
|
295
|
-
}, q = (
|
|
296
|
-
const
|
|
333
|
+
}, q = (o) => {
|
|
334
|
+
const t = o.match(
|
|
297
335
|
/(?:urn:li:)?(ugcPost|activity|share)[:-](\d{10,})/i
|
|
298
336
|
);
|
|
299
|
-
if (!
|
|
300
|
-
const
|
|
337
|
+
if (!t) return null;
|
|
338
|
+
const r = `urn:li:${t[1].toLowerCase() === "ugcpost" ? "ugcPost" : t[1].toLowerCase()}:${t[2]}`, n = `https://www.linkedin.com/embed/feed/update/${encodeURIComponent(r)}`;
|
|
301
339
|
return {
|
|
302
340
|
platform: "linkedin",
|
|
303
|
-
url:
|
|
304
|
-
html: `<iframe src="${
|
|
341
|
+
url: o,
|
|
342
|
+
html: `<iframe src="${n}" height="600" width="100%" frameborder="0" allowfullscreen title="Embedded LinkedIn post" loading="lazy"></iframe>`,
|
|
305
343
|
providerName: "LinkedIn",
|
|
306
344
|
width: 504,
|
|
307
345
|
height: 600
|
|
308
346
|
};
|
|
309
|
-
},
|
|
310
|
-
platform:
|
|
311
|
-
url:
|
|
312
|
-
html: typeof e.html == "string" &&
|
|
347
|
+
}, D = (o) => o.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").trim(), W = (o, t, e) => ({
|
|
348
|
+
platform: o,
|
|
349
|
+
url: t,
|
|
350
|
+
html: typeof e.html == "string" && D(e.html) || null,
|
|
313
351
|
title: typeof e.title == "string" ? e.title : void 0,
|
|
314
352
|
author: typeof e.author_name == "string" ? e.author_name : void 0,
|
|
315
353
|
authorUrl: typeof e.author_url == "string" ? e.author_url : void 0,
|
|
@@ -317,77 +355,83 @@ const g = ({ strapi: t }) => {
|
|
|
317
355
|
providerName: typeof e.provider_name == "string" ? e.provider_name : void 0,
|
|
318
356
|
width: typeof e.width == "number" ? e.width : void 0,
|
|
319
357
|
height: typeof e.height == "number" ? e.height : void 0
|
|
320
|
-
}),
|
|
321
|
-
const
|
|
358
|
+
}), j = 500, F = ({ strapi: o }) => {
|
|
359
|
+
const t = () => o.plugin("better-blocks").config("social", {});
|
|
360
|
+
let e = null;
|
|
361
|
+
const r = () => (e || (e = U({
|
|
362
|
+
max: t().cacheMaxEntries ?? j
|
|
363
|
+
})), e);
|
|
322
364
|
return {
|
|
323
|
-
detectPlatform:
|
|
365
|
+
detectPlatform: g,
|
|
366
|
+
/** Exposed for tests and for anyone who needs to drop cached embeds. */
|
|
367
|
+
clearCache: () => e?.clear(),
|
|
324
368
|
/**
|
|
325
|
-
* Fetch (and cache) the
|
|
369
|
+
* Fetch (and cache) the normalized oEmbed for `url`. `platform` may be
|
|
326
370
|
* passed to skip auto-detection. Throws on an unsupported/unconfigured
|
|
327
371
|
* platform or a failed upstream request.
|
|
328
372
|
*/
|
|
329
|
-
async fetch(
|
|
330
|
-
const
|
|
331
|
-
if (
|
|
373
|
+
async fetch(n, s) {
|
|
374
|
+
const c = t();
|
|
375
|
+
if (c.enabled === !1)
|
|
332
376
|
throw new Error("Social embeds are disabled in plugin config.");
|
|
333
|
-
const
|
|
334
|
-
if (!
|
|
335
|
-
throw new Error(`Unsupported or
|
|
336
|
-
const
|
|
337
|
-
if (Array.isArray(
|
|
377
|
+
const i = s ?? g(n);
|
|
378
|
+
if (!i)
|
|
379
|
+
throw new Error(`Unsupported or unrecognized social URL: ${n}`);
|
|
380
|
+
const u = c.platforms;
|
|
381
|
+
if (Array.isArray(u) && !u.includes(i))
|
|
338
382
|
throw new Error(
|
|
339
|
-
`Platform "${
|
|
383
|
+
`Platform "${i}" is not enabled in plugin config.`
|
|
340
384
|
);
|
|
341
|
-
const
|
|
342
|
-
if (
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
385
|
+
const p = c.cache !== !1, m = (c.cacheTTL ?? 86400) * 1e3, b = `${i}:${n}`;
|
|
386
|
+
if (p) {
|
|
387
|
+
const l = r().get(b);
|
|
388
|
+
if (l) return l;
|
|
345
389
|
}
|
|
346
|
-
let
|
|
347
|
-
if (
|
|
348
|
-
if (
|
|
390
|
+
let a = null;
|
|
391
|
+
if (i === "linkedin") {
|
|
392
|
+
if (a = q(n), !a)
|
|
349
393
|
throw new Error(
|
|
350
394
|
"Could not derive a LinkedIn embed from this URL. Paste the post embed code instead."
|
|
351
395
|
);
|
|
352
396
|
} else {
|
|
353
|
-
const
|
|
354
|
-
if (!
|
|
397
|
+
const l = await P(n), h = M(i, l, c);
|
|
398
|
+
if (!h)
|
|
355
399
|
throw new Error(
|
|
356
|
-
`oEmbed for "${
|
|
400
|
+
`oEmbed for "${i}" requires an access token. Configure config.social.${i}.accessToken, or paste the embed code instead.`
|
|
357
401
|
);
|
|
358
|
-
const
|
|
402
|
+
const d = await fetch(h, {
|
|
359
403
|
headers: { Accept: "application/json" }
|
|
360
404
|
});
|
|
361
|
-
if (!
|
|
405
|
+
if (!d.ok)
|
|
362
406
|
throw new Error(
|
|
363
|
-
`oEmbed request to ${
|
|
407
|
+
`oEmbed request to ${i} failed (HTTP ${d.status}).`
|
|
364
408
|
);
|
|
365
|
-
const
|
|
366
|
-
if (typeof
|
|
367
|
-
throw new Error(`${
|
|
368
|
-
if (
|
|
409
|
+
const f = await d.json();
|
|
410
|
+
if (typeof f.error == "string")
|
|
411
|
+
throw new Error(`${i} could not embed this URL: ${f.error}`);
|
|
412
|
+
if (a = W(i, l, f), !a.html)
|
|
369
413
|
throw new Error(
|
|
370
|
-
`${
|
|
414
|
+
`${i} returned no embed markup for this URL. Check the post is public, or paste the embed code instead.`
|
|
371
415
|
);
|
|
372
416
|
}
|
|
373
|
-
return
|
|
417
|
+
return p && m > 0 && r().set(b, a, m), a;
|
|
374
418
|
}
|
|
375
419
|
};
|
|
376
|
-
},
|
|
377
|
-
service:
|
|
378
|
-
oembed:
|
|
379
|
-
},
|
|
420
|
+
}, N = {
|
|
421
|
+
service: A,
|
|
422
|
+
oembed: F
|
|
423
|
+
}, G = {
|
|
380
424
|
register: y,
|
|
381
|
-
bootstrap:
|
|
382
|
-
destroy:
|
|
383
|
-
config:
|
|
425
|
+
bootstrap: k,
|
|
426
|
+
destroy: w,
|
|
427
|
+
config: C,
|
|
384
428
|
controllers: T,
|
|
385
|
-
routes:
|
|
386
|
-
services:
|
|
387
|
-
contentTypes:
|
|
388
|
-
policies:
|
|
389
|
-
middlewares:
|
|
429
|
+
routes: z,
|
|
430
|
+
services: N,
|
|
431
|
+
contentTypes: v,
|
|
432
|
+
policies: _,
|
|
433
|
+
middlewares: R
|
|
390
434
|
};
|
|
391
435
|
export {
|
|
392
|
-
|
|
436
|
+
G as default
|
|
393
437
|
};
|
|
@@ -4,7 +4,7 @@ declare const oembed: ({ strapi }: {
|
|
|
4
4
|
}) => {
|
|
5
5
|
/**
|
|
6
6
|
* GET /better-blocks/oembed?url=...&platform=...
|
|
7
|
-
* Proxies the platform oEmbed request server-side and returns the
|
|
7
|
+
* Proxies the platform oEmbed request server-side and returns the normalized
|
|
8
8
|
* payload used by the social-embed block.
|
|
9
9
|
*/
|
|
10
10
|
fetch(ctx: any): Promise<any>;
|
|
@@ -51,6 +51,7 @@ declare const _default: {
|
|
|
51
51
|
platforms: string[];
|
|
52
52
|
cache: boolean;
|
|
53
53
|
cacheTTL: number;
|
|
54
|
+
cacheMaxEntries: number;
|
|
54
55
|
instagram: {
|
|
55
56
|
accessToken: any;
|
|
56
57
|
};
|
|
@@ -115,6 +116,7 @@ declare const _default: {
|
|
|
115
116
|
strapi: import("@strapi/types/dist/core").Strapi;
|
|
116
117
|
}) => {
|
|
117
118
|
detectPlatform: (url: string) => import("./services/oembed").SocialPlatform;
|
|
119
|
+
clearCache: () => void;
|
|
118
120
|
fetch(url: string, platform?: import("./services/oembed").SocialPlatform): Promise<import("./services/oembed").NormalisedOEmbed>;
|
|
119
121
|
};
|
|
120
122
|
};
|
|
@@ -8,6 +8,7 @@ declare const _default: {
|
|
|
8
8
|
strapi: import("@strapi/types/dist/core").Strapi;
|
|
9
9
|
}) => {
|
|
10
10
|
detectPlatform: (url: string) => import("./oembed").SocialPlatform;
|
|
11
|
+
clearCache: () => void;
|
|
11
12
|
fetch(url: string, platform?: import("./oembed").SocialPlatform): Promise<import("./oembed").NormalisedOEmbed>;
|
|
12
13
|
};
|
|
13
14
|
};
|
|
@@ -12,13 +12,56 @@ export interface NormalisedOEmbed {
|
|
|
12
12
|
width?: number;
|
|
13
13
|
height?: number;
|
|
14
14
|
}
|
|
15
|
+
export interface SocialConfig {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
platforms?: SocialPlatform[];
|
|
18
|
+
cache?: boolean;
|
|
19
|
+
cacheTTL?: number;
|
|
20
|
+
cacheMaxEntries?: number;
|
|
21
|
+
instagram?: {
|
|
22
|
+
accessToken?: string;
|
|
23
|
+
};
|
|
24
|
+
facebook?: {
|
|
25
|
+
accessToken?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
15
28
|
export declare const detectPlatform: (url: string) => SocialPlatform | null;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the platform oEmbed endpoint to call, or null when the platform has
|
|
31
|
+
* no public oEmbed API (e.g. LinkedIn, which we render via a constructed iframe).
|
|
32
|
+
*/
|
|
33
|
+
export declare const buildEndpoint: (platform: SocialPlatform, url: string, config: SocialConfig) => string | null;
|
|
34
|
+
/**
|
|
35
|
+
* `pin.it` short links are 30x redirects that Pinterest's oEmbed endpoint
|
|
36
|
+
* rejects outright, so expand them to the canonical `/pin/<id>/` URL first.
|
|
37
|
+
* Returns the original URL when it isn't a short link or can't be resolved.
|
|
38
|
+
*/
|
|
39
|
+
export declare const resolveShortUrl: (url: string) => Promise<string>;
|
|
40
|
+
/** LinkedIn has no oEmbed API but exposes a stable iframe embed by URN. */
|
|
41
|
+
export declare const buildLinkedInIframe: (url: string) => NormalisedOEmbed | null;
|
|
42
|
+
/**
|
|
43
|
+
* Platform oEmbed markup often ships the widget script inline (TikTok always
|
|
44
|
+
* does; Twitter/Instagram only when `omit_script` isn't honoured). Renderers
|
|
45
|
+
* inject that script themselves, and a `<script>` added through `innerHTML`
|
|
46
|
+
* never executes anyway — worse, its inert tag makes a renderer believe the
|
|
47
|
+
* widget is already loaded. Strip it so the stored html is markup only.
|
|
48
|
+
*/
|
|
49
|
+
export declare const stripScripts: (html: string) => string;
|
|
50
|
+
export declare const normalize: (platform: SocialPlatform, url: string, raw: Record<string, unknown>) => NormalisedOEmbed;
|
|
51
|
+
/**
|
|
52
|
+
* Bounded so a long-running instance cannot accumulate one entry per social post
|
|
53
|
+
* ever embedded. 500 posts is far more than an editor touches in a TTL window,
|
|
54
|
+
* and `cacheMaxEntries` raises it for anyone who disagrees.
|
|
55
|
+
*/
|
|
56
|
+
export declare const DEFAULT_CACHE_MAX_ENTRIES = 500;
|
|
16
57
|
declare const service: ({ strapi }: {
|
|
17
58
|
strapi: Core.Strapi;
|
|
18
59
|
}) => {
|
|
19
60
|
detectPlatform: (url: string) => SocialPlatform | null;
|
|
61
|
+
/** Exposed for tests and for anyone who needs to drop cached embeds. */
|
|
62
|
+
clearCache: () => void;
|
|
20
63
|
/**
|
|
21
|
-
* Fetch (and cache) the
|
|
64
|
+
* Fetch (and cache) the normalized oEmbed for `url`. `platform` may be
|
|
22
65
|
* passed to skip auto-detection. Throws on an unsupported/unconfigured
|
|
23
66
|
* platform or a failed upstream request.
|
|
24
67
|
*/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bounded cache with per-entry expiry.
|
|
3
|
+
*
|
|
4
|
+
* The oEmbed service used a bare `Map` keyed by URL, which meant one entry for
|
|
5
|
+
* every social post ever embedded, for the life of the process. Entries expired
|
|
6
|
+
* logically — a stale hit was discarded on read — but nothing ever removed them,
|
|
7
|
+
* so a long-running instance kept the memory whether or not anyone asked for
|
|
8
|
+
* that post again.
|
|
9
|
+
*
|
|
10
|
+
* Eviction is least-recently-used: a hit moves its entry to the end, and a write
|
|
11
|
+
* past `max` drops the entry at the front. `Map` iterates in insertion order and
|
|
12
|
+
* re-inserting moves a key to the end, which is the whole implementation.
|
|
13
|
+
*/
|
|
14
|
+
export interface TtlCache<T> {
|
|
15
|
+
get(key: string): T | undefined;
|
|
16
|
+
set(key: string, value: T, ttlMs: number): void;
|
|
17
|
+
delete(key: string): boolean;
|
|
18
|
+
clear(): void;
|
|
19
|
+
readonly size: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const createTtlCache: <T>({ max }: {
|
|
22
|
+
max: number;
|
|
23
|
+
}) => TtlCache<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qkix/strapi-plugin-better-blocks",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.2",
|
|
4
4
|
"description": "An enhanced Rich Text (Blocks) field for Strapi with inline color picker and more",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"kind": "plugin",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"remark-math": "^6.0.0",
|
|
55
55
|
"remark-parse": "^11.0.0",
|
|
56
56
|
"shiki": "^4.3.0",
|
|
57
|
-
"unified": "^11.0.5"
|
|
57
|
+
"unified": "^11.0.5",
|
|
58
|
+
"@qkix/better-blocks-core": "^0.2.2"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@strapi/sdk-plugin": "^5.3.2",
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
"typescript": "^5.8.3",
|
|
74
75
|
"vitest": "^3.2.4",
|
|
75
76
|
"@qkix/eslint-config": "0.0.0",
|
|
77
|
+
"@qkix/strapi-test-harness": "0.0.0",
|
|
76
78
|
"@qkix/tsconfig": "0.0.0"
|
|
77
79
|
},
|
|
78
80
|
"peerDependencies": {
|
|
@@ -99,17 +101,25 @@
|
|
|
99
101
|
"author": "qkix",
|
|
100
102
|
"repository": {
|
|
101
103
|
"type": "git",
|
|
102
|
-
"url": "git+https://github.com/qkix/strapi-
|
|
104
|
+
"url": "git+https://github.com/qkix/strapi-plugins.git",
|
|
103
105
|
"directory": "packages/strapi-plugin-better-blocks"
|
|
104
106
|
},
|
|
105
107
|
"bugs": {
|
|
106
|
-
"url": "https://github.com/qkix/strapi-
|
|
108
|
+
"url": "https://github.com/qkix/strapi-plugins/issues"
|
|
107
109
|
},
|
|
108
|
-
"homepage": "https://github.com/qkix/strapi-
|
|
110
|
+
"homepage": "https://github.com/qkix/strapi-plugins#readme",
|
|
109
111
|
"nx": {
|
|
110
112
|
"tags": [
|
|
111
113
|
"scope:plugin"
|
|
112
|
-
]
|
|
114
|
+
],
|
|
115
|
+
"targets": {
|
|
116
|
+
"test": {
|
|
117
|
+
"dependsOn": [
|
|
118
|
+
"build",
|
|
119
|
+
"^build"
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
113
123
|
},
|
|
114
124
|
"publishConfig": {
|
|
115
125
|
"access": "public"
|
|
@@ -126,6 +136,6 @@
|
|
|
126
136
|
"verify": "strapi-plugin verify",
|
|
127
137
|
"watch": "strapi-plugin watch",
|
|
128
138
|
"watch:link": "strapi-plugin watch:link",
|
|
129
|
-
"typecheck": "tsc -p admin/tsconfig.json && tsc -p server/tsconfig.json"
|
|
139
|
+
"typecheck": "tsc -p admin/tsconfig.json --noEmit && tsc -p server/tsconfig.json --noEmit"
|
|
130
140
|
}
|
|
131
141
|
}
|