@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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How another Strapi plugin adds a block type to the Better Blocks editor.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the renderers, which take their registrations as a prop, the editor
|
|
5
|
+
* keeps a module-level registry. That is not an oversight: the Strapi admin is
|
|
6
|
+
* a single browser application whose plugins register once at boot, exactly
|
|
7
|
+
* like `app.customFields.register`. There is no second document being edited in
|
|
8
|
+
* another request to leak into.
|
|
9
|
+
*
|
|
10
|
+
* Registering is the *only* supported way in. Adding a block by editing this
|
|
11
|
+
* package is how the editor, both renderers and the validator ended up with
|
|
12
|
+
* four separate hardcoded lists of block types in the first place.
|
|
13
|
+
*/
|
|
14
|
+
/// <reference types="react" />
|
|
15
|
+
import type { BlockDefinition } from '@qkix/better-blocks-core';
|
|
16
|
+
import type { Editor } from 'slate';
|
|
17
|
+
import type { MessageDescriptor } from 'react-intl';
|
|
18
|
+
import type { RenderElementProps } from 'slate-react';
|
|
19
|
+
import type { CSSProperties } from 'styled-components';
|
|
20
|
+
/**
|
|
21
|
+
* A block type contributed by another package.
|
|
22
|
+
*
|
|
23
|
+
* Extends the core {@link BlockDefinition}, so a package writes `type` and
|
|
24
|
+
* `content` once and the same object teaches the validator, the migrator and
|
|
25
|
+
* both renderers about the block.
|
|
26
|
+
*/
|
|
27
|
+
export type EditorBlockDefinition = BlockDefinition & {
|
|
28
|
+
/** Draws the block inside the editor. Slate calls this with its element props. */
|
|
29
|
+
renderElement: (props: RenderElementProps) => React.JSX.Element;
|
|
30
|
+
/**
|
|
31
|
+
* Icon for the Insert menu and the slash command. Required to appear in
|
|
32
|
+
* either — a block with no icon is still rendered, just not offered.
|
|
33
|
+
*/
|
|
34
|
+
icon?: React.ComponentType;
|
|
35
|
+
/** Menu label. A plain string is fine; a descriptor gets translated. */
|
|
36
|
+
label?: MessageDescriptor | string;
|
|
37
|
+
/**
|
|
38
|
+
* Inserts an empty block of this type at the selection. Required for the
|
|
39
|
+
* block to appear in the Insert menu or the slash command, since without it
|
|
40
|
+
* there is nothing for those to do.
|
|
41
|
+
*/
|
|
42
|
+
insert?: (editor: Editor) => void;
|
|
43
|
+
/** Text that converts into this block as you type, e.g. '```mermaid'. */
|
|
44
|
+
snippets?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Anything `isVoid` cannot express — custom normalization, paste handling,
|
|
47
|
+
* key bindings. Applied when the editor is created.
|
|
48
|
+
*/
|
|
49
|
+
withEditor?: (editor: Editor) => Editor;
|
|
50
|
+
/** Nudges the drag handle down, for blocks whose first line is not text. */
|
|
51
|
+
dragHandleTopMargin?: CSSProperties['marginTop'];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Adds a block type to the editor. Call from a Strapi plugin's `register()`.
|
|
55
|
+
*
|
|
56
|
+
* Throws on the two mistakes that would otherwise surface much later as a block
|
|
57
|
+
* that mysteriously does not appear: shadowing a built-in type, and registering
|
|
58
|
+
* the same type twice.
|
|
59
|
+
*/
|
|
60
|
+
export declare function registerBlock(definition: EditorBlockDefinition): void;
|
|
61
|
+
/** Every registered block, in registration order. */
|
|
62
|
+
export declare function getRegisteredBlocks(): EditorBlockDefinition[];
|
|
63
|
+
/**
|
|
64
|
+
* Whether the block can be offered in the Insert menu and the slash command.
|
|
65
|
+
*
|
|
66
|
+
* All three are needed: something to draw, something to name it, and something
|
|
67
|
+
* to do when it is picked. A block missing any of them still renders in a
|
|
68
|
+
* document, it just is not offered as a thing to add.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isOfferedInMenus(definition: EditorBlockDefinition): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* The block's menu label as react-intl wants it. A plain string becomes the
|
|
73
|
+
* default message under a generated id, so it shows as written until someone
|
|
74
|
+
* ships a translation for it.
|
|
75
|
+
*/
|
|
76
|
+
export declare function blockLabelDescriptor(definition: EditorBlockDefinition): MessageDescriptor;
|
|
77
|
+
/** Empties the registry. Exists for tests; the admin never needs it. */
|
|
78
|
+
export declare function clearRegisteredBlocks(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Teaches Slate about the registered blocks.
|
|
81
|
+
*
|
|
82
|
+
* Void-ness is derived from the declared content model rather than asked for
|
|
83
|
+
* separately: a block that renders from its own attributes is exactly a Slate
|
|
84
|
+
* void, and getting that wrong is the difference between a working block and an
|
|
85
|
+
* uneditable one.
|
|
86
|
+
*/
|
|
87
|
+
export declare function withRegisteredBlocks(editor: Editor): Editor;
|
|
@@ -2,7 +2,7 @@ import { Editor } from 'slate';
|
|
|
2
2
|
import { type BlocksStore } from '../BlocksEditor';
|
|
3
3
|
import { createTableCell, createTableRow, insertTable } from './tableOperations';
|
|
4
4
|
/**
|
|
5
|
-
* Protects the table's shape from Slate's generic editing
|
|
5
|
+
* Protects the table's shape from Slate's generic editing behaviors (deleting
|
|
6
6
|
* across cells, normalizing rows away, converting a cell to a paragraph, …).
|
|
7
7
|
*
|
|
8
8
|
* Structural edits are performed by tableOperations, which lifts the guard via
|
|
@@ -3,7 +3,7 @@ import { type AspectRatio, type EmbedProvider } from '../utils/types';
|
|
|
3
3
|
export declare const EMBED_FRAME_HOSTS: string[];
|
|
4
4
|
/** Human-readable provider name for a URL, e.g. "YouTube". */
|
|
5
5
|
export declare const getProviderLabel: (provider?: EmbedProvider) => string;
|
|
6
|
-
/** Detected provider for a share URL, or `generic` when
|
|
6
|
+
/** Detected provider for a share URL, or `generic` when unrecognized. */
|
|
7
7
|
export declare const detectProvider: (url: string) => EmbedProvider;
|
|
8
8
|
/** Iframe `src` derived from a share URL, or null when not derivable. */
|
|
9
9
|
export declare const getEmbedSrc: (url: string) => string | null;
|
|
@@ -32,7 +32,7 @@ export interface IframeError {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare const sanitizeIframe: (input: string) => SanitizedIframe | IframeError;
|
|
34
34
|
/**
|
|
35
|
-
* Build the iframe markup for a
|
|
35
|
+
* Build the iframe markup for a recognized share URL. Returns null when the URL
|
|
36
36
|
* isn't one of the known providers — the author then has to use embed-code mode.
|
|
37
37
|
*/
|
|
38
38
|
export declare const buildUrlEmbed: (url: string, title?: string) => SanitizedIframe | null;
|
|
@@ -85,6 +85,6 @@ export declare const cellsInRange: (grid: TableGrid, range: GridRange) => GridCe
|
|
|
85
85
|
* linear, so everything between r2c1 and r3c2 includes the whole tail of row 2
|
|
86
86
|
* — taking the bounding box of every touched cell would turn a 2x2 pick into a
|
|
87
87
|
* full-width 2x5 one. Treating anchor and focus as opposite corners gives the
|
|
88
|
-
* 2D
|
|
88
|
+
* 2D behavior authors expect from a table.
|
|
89
89
|
*/
|
|
90
90
|
export declare const getSelectedRange: (editor: Editor, grid: TableGrid) => GridRange | null;
|
|
@@ -29,10 +29,18 @@ type NonSelectorBlockKey = 'list-item' | 'link' | 'horizontal-line' | 'table' |
|
|
|
29
29
|
declare const selectorBlockKeys: readonly ["paragraph", "heading-one", "heading-two", "heading-three", "heading-four", "heading-five", "heading-six", "list-ordered", "list-unordered", "list-todo", "image", "audio", "video", "embed", "quote", "code", "math", "diagram", "callout", "details", "button", "social-embed"];
|
|
30
30
|
type SelectorBlockKey = (typeof selectorBlockKeys)[number];
|
|
31
31
|
declare const isSelectorBlockKey: (key: unknown) => key is "image" | "audio" | "math" | "diagram" | "callout" | "details" | "button" | "social-embed" | "embed" | "video" | "code" | "paragraph" | "heading-one" | "heading-two" | "heading-three" | "heading-four" | "heading-five" | "heading-six" | "list-ordered" | "list-unordered" | "list-todo" | "quote";
|
|
32
|
+
/**
|
|
33
|
+
* The built-in keys are spelled out so `blocks.paragraph` stays typed, while
|
|
34
|
+
* the index signature leaves room for the types other packages register — see
|
|
35
|
+
* `registerBlock`. Without it, every consumer that walks the store would have
|
|
36
|
+
* to widen at the call site.
|
|
37
|
+
*/
|
|
32
38
|
type BlocksStore = {
|
|
33
39
|
[K in SelectorBlockKey]: SelectorBlock;
|
|
34
40
|
} & {
|
|
35
41
|
[K in NonSelectorBlockKey]: NonSelectorBlock;
|
|
42
|
+
} & {
|
|
43
|
+
[key: string]: SelectorBlock | NonSelectorBlock;
|
|
36
44
|
};
|
|
37
45
|
interface BlocksEditorContextValue {
|
|
38
46
|
blocks: BlocksStore;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* scroll (defeating the purpose on macOS). We therefore do NOT set
|
|
21
21
|
* `scrollbar-color`; Firefox falls back to its own always-visible bar.
|
|
22
22
|
* - The track gets a real background so the channel stays visible even when the
|
|
23
|
-
* content fits and the thumb spans the full height; the
|
|
23
|
+
* content fits and the thumb spans the full height; the colors are
|
|
24
24
|
* mid-range neutrals that contrast with the menu background (`neutral0`) in
|
|
25
25
|
* both the light and dark themes.
|
|
26
26
|
* - `!important` / `display: block` are required to beat the design-system
|
|
@@ -85,7 +85,7 @@ export interface ImageElement extends CustomElement {
|
|
|
85
85
|
}
|
|
86
86
|
export type AudioAlignment = 'left' | 'center' | 'right' | 'none';
|
|
87
87
|
export type AudioPreload = 'none' | 'metadata' | 'auto';
|
|
88
|
-
/** Player
|
|
88
|
+
/** Player behavior flags, mirrored 1:1 onto the HTML5 `<audio>` element. */
|
|
89
89
|
export interface AudioPlayerSettings {
|
|
90
90
|
autoplay: boolean;
|
|
91
91
|
loop: boolean;
|
|
@@ -95,7 +95,7 @@ export interface AudioPlayerSettings {
|
|
|
95
95
|
/**
|
|
96
96
|
* Audio block. A void block referencing a Strapi Media Library asset (or an
|
|
97
97
|
* external URL). Stores the file metadata plus a display title, caption, player
|
|
98
|
-
*
|
|
98
|
+
* behavior flags and alignment. The frontend renderer turns this into a native
|
|
99
99
|
* HTML5 `<audio>` player — see issue #43 for the renderer contract.
|
|
100
100
|
*/
|
|
101
101
|
export interface AudioElement extends CustomElement {
|
|
@@ -271,7 +271,7 @@ export declare const isButtonNode: (element: CustomElement) => element is Button
|
|
|
271
271
|
export type SocialPlatform = 'twitter' | 'instagram' | 'facebook' | 'tiktok' | 'linkedin' | 'pinterest';
|
|
272
272
|
export type SocialAlignment = 'left' | 'center' | 'right';
|
|
273
273
|
/**
|
|
274
|
-
*
|
|
274
|
+
* Normalized oEmbed payload fetched from the platform via the plugin's server
|
|
275
275
|
* oEmbed proxy (`GET /better-blocks/oembed`). Stored on the node so the frontend
|
|
276
276
|
* renderer can render the embed without itself calling the platform at runtime.
|
|
277
277
|
*/
|
|
@@ -353,7 +353,7 @@ export interface EmbedElement extends CustomElement {
|
|
|
353
353
|
export declare const isEmbedNode: (element: CustomElement) => element is EmbedElement;
|
|
354
354
|
/** Video hosting providers the video block understands. */
|
|
355
355
|
export type VideoProvider = 'local' | 'mux' | 'api-video' | 'cloudinary' | 'custom';
|
|
356
|
-
/** Player
|
|
356
|
+
/** Player behavior flags, mirrored onto the HTML5 `<video>` element. */
|
|
357
357
|
export interface VideoPlayerSettings {
|
|
358
358
|
autoplay: boolean;
|
|
359
359
|
loop: boolean;
|
|
@@ -38,7 +38,7 @@ type UseDragAndDropReturn<E extends Element = HTMLElement> = [
|
|
|
38
38
|
];
|
|
39
39
|
/**
|
|
40
40
|
* A utility hook abstracting the general drag and drop hooks from react-dnd.
|
|
41
|
-
* Centralising the same
|
|
41
|
+
* Centralising the same behaviors and by default offering keyboard support.
|
|
42
42
|
*/
|
|
43
43
|
declare const useDragAndDrop: <TIndex extends number | number[], TItem extends {
|
|
44
44
|
[key: string]: unknown;
|
|
@@ -8,3 +8,10 @@ declare const _default: {
|
|
|
8
8
|
}[]>;
|
|
9
9
|
};
|
|
10
10
|
export default _default;
|
|
11
|
+
/**
|
|
12
|
+
* The extension surface. Another Strapi plugin adds a block type by calling
|
|
13
|
+
* `registerBlock` from its own `register()`, which runs before any editor is
|
|
14
|
+
* mounted.
|
|
15
|
+
*/
|
|
16
|
+
export { registerBlock, getRegisteredBlocks, clearRegisteredBlocks, } from './blockRegistry';
|
|
17
|
+
export type { EditorBlockDefinition } from './blockRegistry';
|
package/dist/server/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const
|
|
1
|
+
"use strict";const k=({strapi:o})=>{},w=({strapi:o})=>{},y=({strapi:o})=>{o.customFields.register({name:"better-blocks",plugin:"better-blocks",type:"json",inputSize:{default:12,isResizable:!1}})},C={default:{details:{defaultSummary:"Click to expand",style:"github"},button:{defaultStyle:{backgroundColor:"#4945ff",textColor:"#ffffff",borderRadius:"4px",padding:"12px 24px",fontSize:"16px",fontWeight:"600"},presets:{primary:{backgroundColor:"#4945ff",textColor:"#ffffff",border:"none"},secondary:{backgroundColor:"#dcdce4",textColor:"#32324d",border:"none"},outline:{backgroundColor:"transparent",textColor:"#4945ff",border:"2px solid #4945ff"},filled:{backgroundColor:"#32324d",textColor:"#ffffff",border:"none"}}},social:{enabled:!0,platforms:["twitter","instagram","facebook","tiktok","linkedin","pinterest"],cache:!0,cacheTTL:86400,cacheMaxEntries:500,instagram:{accessToken:void 0},facebook:{accessToken:void 0}}},validator(o={}){const t=o?.details?.style;if(t&&t!=="github"&&t!=="custom")throw new Error(`[better-blocks] config.details.style must be "github" or "custom" (received "${t}")`)}},v={},x=({strapi:o})=>({index(t){t.body=o.plugin("better-blocks").service("service").getWelcomeMessage()},getConfig(t){t.body={details:o.plugin("better-blocks").config("details",{defaultSummary:"Click to expand",style:"github"}),button:o.plugin("better-blocks").config("button",{defaultStyle:{backgroundColor:"#4945ff",textColor:"#ffffff",borderRadius:"4px",padding:"12px 24px",fontSize:"16px",fontWeight:"600"},presets:{primary:{backgroundColor:"#4945ff",textColor:"#ffffff",border:"none"},secondary:{backgroundColor:"#dcdce4",textColor:"#32324d",border:"none"},outline:{backgroundColor:"transparent",textColor:"#4945ff",border:"2px solid #4945ff"},filled:{backgroundColor:"#32324d",textColor:"#ffffff",border:"none"}}}),social:(()=>{const e=o.plugin("better-blocks").config("social",{enabled:!0,platforms:["twitter","instagram","facebook","tiktok","linkedin","pinterest"]});return{enabled:e.enabled!==!1,platforms:e.platforms??[],instagramConfigured:!!e.instagram?.accessToken,facebookConfigured:!!e.facebook?.accessToken}})()}}}),E=["twitter","instagram","facebook","tiktok","linkedin","pinterest"],$=({strapi:o})=>({async fetch(t){const{url:e,platform:r}=t.query;if(!e||typeof e!="string")return t.badRequest('Missing required "url" query parameter.');const n=r&&E.includes(r)?r:void 0;try{t.body=await o.plugin("better-blocks").service("oembed").fetch(e,n)}catch(s){const c=s instanceof Error?s.message:"oEmbed request failed.";return t.badRequest(c)}}}),T={controller:x,oembed:$},R={},_={},L=[{method:"GET",path:"/",handler:"controller.index",config:{policies:[]}}],S=[{method:"GET",path:"/config",handler:"controller.getConfig",config:{policies:[]}},{method:"GET",path:"/oembed",handler:"oembed.fetch",config:{policies:["admin::isAuthenticatedAdmin"]}}],z={"content-api":{type:"content-api",routes:L},admin:{type:"admin",routes:S}},A=({strapi:o})=>({getWelcomeMessage(){return"Welcome to Strapi 🚀"}}),U=({max:o})=>{if(o<1)throw new Error(`ttlCache: max must be at least 1 (received ${o})`);const t=new Map;return{get(e){const r=t.get(e);if(r){if(r.expires<=Date.now()){t.delete(e);return}return t.delete(e),t.set(e,r),r.value}},set(e,r,n){if(t.delete(e),t.size>=o){const s=Date.now();for(const[c,i]of t)i.expires<=s&&t.delete(c)}for(;t.size>=o;){const s=t.keys().next();if(s.done)break;t.delete(s.value)}t.set(e,{expires:Date.now()+n,value:r})},delete:e=>t.delete(e),clear:()=>t.clear(),get size(){return t.size}}},I=[{platform:"twitter",re:/(?:twitter\.com|x\.com)\/[^/]+\/status(?:es)?\/\d+/i},{platform:"instagram",re:/instagram\.com\/(?:p|reel|tv)\//i},{platform:"tiktok",re:/tiktok\.com\/@[^/]+\/video\/\d+/i},{platform:"facebook",re:/facebook\.com\//i},{platform:"linkedin",re:/linkedin\.com\//i},{platform:"pinterest",re:/(?:pinterest\.[a-z.]+|pin\.it)\//i}],g=o=>{const t=I.find(({re:e})=>e.test(o));return t?t.platform:null},M=(o,t,e)=>{const r=encodeURIComponent(t);switch(o){case"twitter":return`https://publish.twitter.com/oembed?url=${r}&omit_script=true&dnt=true`;case"tiktok":return`https://www.tiktok.com/oembed?url=${r}`;case"pinterest":return`https://www.pinterest.com/oembed.json?url=${r}`;case"instagram":{const n=e.instagram?.accessToken;return n?`https://graph.facebook.com/v17.0/instagram_oembed?url=${r}&access_token=${encodeURIComponent(n)}&omitscript=true`:null}case"facebook":{const n=e.facebook?.accessToken;return n?`https://graph.facebook.com/v17.0/oembed_post?url=${r}&access_token=${encodeURIComponent(n)}&omitscript=true`:null}case"linkedin":return null;default:return null}},P=async o=>{if(!/^https?:\/\/pin\.it\//i.test(o))return o;try{const e=(await fetch(o,{redirect:"follow"})).url||o;return/pinterest\.[a-z.]+\/pin\//i.test(e)?e:o}catch{return o}},q=o=>{const t=o.match(/(?:urn:li:)?(ugcPost|activity|share)[:-](\d{10,})/i);if(!t)return null;const r=`urn:li:${t[1].toLowerCase()==="ugcpost"?"ugcPost":t[1].toLowerCase()}:${t[2]}`,n=`https://www.linkedin.com/embed/feed/update/${encodeURIComponent(r)}`;return{platform:"linkedin",url:o,html:`<iframe src="${n}" height="600" width="100%" frameborder="0" allowfullscreen title="Embedded LinkedIn post" loading="lazy"></iframe>`,providerName:"LinkedIn",width:504,height:600}},D=o=>o.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi,"").trim(),W=(o,t,e)=>({platform:o,url:t,html:typeof e.html=="string"&&D(e.html)||null,title:typeof e.title=="string"?e.title:void 0,author:typeof e.author_name=="string"?e.author_name:void 0,authorUrl:typeof e.author_url=="string"?e.author_url:void 0,thumbnailUrl:typeof e.thumbnail_url=="string"?e.thumbnail_url:void 0,providerName:typeof e.provider_name=="string"?e.provider_name:void 0,width:typeof e.width=="number"?e.width:void 0,height:typeof e.height=="number"?e.height:void 0}),j=500,F=({strapi:o})=>{const t=()=>o.plugin("better-blocks").config("social",{});let e=null;const r=()=>(e||(e=U({max:t().cacheMaxEntries??j})),e);return{detectPlatform:g,clearCache:()=>e?.clear(),async fetch(n,s){const c=t();if(c.enabled===!1)throw new Error("Social embeds are disabled in plugin config.");const i=s??g(n);if(!i)throw new Error(`Unsupported or unrecognized social URL: ${n}`);const u=c.platforms;if(Array.isArray(u)&&!u.includes(i))throw new Error(`Platform "${i}" is not enabled in plugin config.`);const p=c.cache!==!1,m=(c.cacheTTL??86400)*1e3,b=`${i}:${n}`;if(p){const l=r().get(b);if(l)return l}let a=null;if(i==="linkedin"){if(a=q(n),!a)throw new Error("Could not derive a LinkedIn embed from this URL. Paste the post embed code instead.")}else{const l=await P(n),h=M(i,l,c);if(!h)throw new Error(`oEmbed for "${i}" requires an access token. Configure config.social.${i}.accessToken, or paste the embed code instead.`);const d=await fetch(h,{headers:{Accept:"application/json"}});if(!d.ok)throw new Error(`oEmbed request to ${i} failed (HTTP ${d.status}).`);const f=await d.json();if(typeof f.error=="string")throw new Error(`${i} could not embed this URL: ${f.error}`);if(a=W(i,l,f),!a.html)throw new Error(`${i} returned no embed markup for this URL. Check the post is public, or paste the embed code instead.`)}return p&&m>0&&r().set(b,a,m),a}}},N={service:A,oembed:F},G={register:y,bootstrap:k,destroy:w,config:C,controllers:T,routes:z,services:N,contentTypes:v,policies:_,middlewares:R};module.exports=G;
|