@qkix/strapi-plugin-better-blocks 0.23.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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +795 -0
  3. package/dist/_chunks/en-BcPniuRQ.js +1 -0
  4. package/dist/_chunks/en-DYxpcVy9.mjs +4 -0
  5. package/dist/admin/index.js +1933 -0
  6. package/dist/admin/index.mjs +17091 -0
  7. package/dist/admin/src/components/BlocksInput/Blocks/Audio.d.ts +6 -0
  8. package/dist/admin/src/components/BlocksInput/Blocks/AudioEditorModal.d.ts +21 -0
  9. package/dist/admin/src/components/BlocksInput/Blocks/Button.d.ts +11 -0
  10. package/dist/admin/src/components/BlocksInput/Blocks/ButtonEditorModal.d.ts +17 -0
  11. package/dist/admin/src/components/BlocksInput/Blocks/Callout.d.ts +21 -0
  12. package/dist/admin/src/components/BlocksInput/Blocks/Code.d.ts +18 -0
  13. package/dist/admin/src/components/BlocksInput/Blocks/Details.d.ts +12 -0
  14. package/dist/admin/src/components/BlocksInput/Blocks/Diagram.d.ts +15 -0
  15. package/dist/admin/src/components/BlocksInput/Blocks/Embed.d.ts +22 -0
  16. package/dist/admin/src/components/BlocksInput/Blocks/EmbedEditorModal.d.ts +27 -0
  17. package/dist/admin/src/components/BlocksInput/Blocks/Heading.d.ts +14 -0
  18. package/dist/admin/src/components/BlocksInput/Blocks/HorizontalLine.d.ts +10 -0
  19. package/dist/admin/src/components/BlocksInput/Blocks/Image.d.ts +5 -0
  20. package/dist/admin/src/components/BlocksInput/Blocks/Link.d.ts +3 -0
  21. package/dist/admin/src/components/BlocksInput/Blocks/List.d.ts +3 -0
  22. package/dist/admin/src/components/BlocksInput/Blocks/Math.d.ts +17 -0
  23. package/dist/admin/src/components/BlocksInput/Blocks/MediaEmbed.d.ts +7 -0
  24. package/dist/admin/src/components/BlocksInput/Blocks/Paragraph.d.ts +3 -0
  25. package/dist/admin/src/components/BlocksInput/Blocks/Quote.d.ts +3 -0
  26. package/dist/admin/src/components/BlocksInput/Blocks/SocialEmbed.d.ts +8 -0
  27. package/dist/admin/src/components/BlocksInput/Blocks/SocialEmbedEditorModal.d.ts +41 -0
  28. package/dist/admin/src/components/BlocksInput/Blocks/SourceEditorModal.d.ts +19 -0
  29. package/dist/admin/src/components/BlocksInput/Blocks/Table.d.ts +14 -0
  30. package/dist/admin/src/components/BlocksInput/Blocks/TableGridPicker.d.ts +20 -0
  31. package/dist/admin/src/components/BlocksInput/Blocks/TableIcons.d.ts +31 -0
  32. package/dist/admin/src/components/BlocksInput/Blocks/TableToolbar.d.ts +14 -0
  33. package/dist/admin/src/components/BlocksInput/Blocks/Video.d.ts +6 -0
  34. package/dist/admin/src/components/BlocksInput/Blocks/VideoEditorModal.d.ts +29 -0
  35. package/dist/admin/src/components/BlocksInput/Blocks/embedProviders.d.ts +43 -0
  36. package/dist/admin/src/components/BlocksInput/Blocks/muxClient.d.ts +40 -0
  37. package/dist/admin/src/components/BlocksInput/Blocks/socialFrame.d.ts +13 -0
  38. package/dist/admin/src/components/BlocksInput/Blocks/tableGrid.d.ts +90 -0
  39. package/dist/admin/src/components/BlocksInput/Blocks/tableOperations.d.ts +124 -0
  40. package/dist/admin/src/components/BlocksInput/Blocks/videoProviders.d.ts +37 -0
  41. package/dist/admin/src/components/BlocksInput/BlocksContent.d.ts +7 -0
  42. package/dist/admin/src/components/BlocksInput/BlocksEditor.d.ts +71 -0
  43. package/dist/admin/src/components/BlocksInput/BlocksToolbar.d.ts +27 -0
  44. package/dist/admin/src/components/BlocksInput/EditorLayout.d.ts +10 -0
  45. package/dist/admin/src/components/BlocksInput/EmojiPicker.d.ts +5 -0
  46. package/dist/admin/src/components/BlocksInput/FindReplace.d.ts +11 -0
  47. package/dist/admin/src/components/BlocksInput/FloatingSelectionToolbar.d.ts +11 -0
  48. package/dist/admin/src/components/BlocksInput/FontModifiersIcons.d.ts +32 -0
  49. package/dist/admin/src/components/BlocksInput/InlineColorPicker.d.ts +3 -0
  50. package/dist/admin/src/components/BlocksInput/MenuScrollbar.d.ts +30 -0
  51. package/dist/admin/src/components/BlocksInput/Modifiers.d.ts +16 -0
  52. package/dist/admin/src/components/BlocksInput/SlashCommands.d.ts +3 -0
  53. package/dist/admin/src/components/BlocksInput/SpecialCharPicker.d.ts +5 -0
  54. package/dist/admin/src/components/BlocksInput/WordCount.d.ts +3 -0
  55. package/dist/admin/src/components/BlocksInput/plugins/withAutoTransform.d.ts +3 -0
  56. package/dist/admin/src/components/BlocksInput/plugins/withLinks.d.ts +8 -0
  57. package/dist/admin/src/components/BlocksInput/plugins/withMarkdownPaste.d.ts +3 -0
  58. package/dist/admin/src/components/BlocksInput/plugins/withStrapiSchema.d.ts +6 -0
  59. package/dist/admin/src/components/BlocksInput/utils/constants.d.ts +13 -0
  60. package/dist/admin/src/components/BlocksInput/utils/conversions.d.ts +14 -0
  61. package/dist/admin/src/components/BlocksInput/utils/enterKey.d.ts +6 -0
  62. package/dist/admin/src/components/BlocksInput/utils/links.d.ts +12 -0
  63. package/dist/admin/src/components/BlocksInput/utils/optionsDefaults.d.ts +28 -0
  64. package/dist/admin/src/components/BlocksInput/utils/optionsParser.d.ts +34 -0
  65. package/dist/admin/src/components/BlocksInput/utils/parseMarkdownToSlate.d.ts +2 -0
  66. package/dist/admin/src/components/BlocksInput/utils/prefixFileUrlWithBackendUrl.d.ts +8 -0
  67. package/dist/admin/src/components/BlocksInput/utils/shiki.d.ts +29 -0
  68. package/dist/admin/src/components/BlocksInput/utils/shortcuts.d.ts +25 -0
  69. package/dist/admin/src/components/BlocksInput/utils/types.d.ts +404 -0
  70. package/dist/admin/src/components/Initializer.d.ts +5 -0
  71. package/dist/admin/src/components/Input.d.ts +45 -0
  72. package/dist/admin/src/components/PluginIcon.d.ts +3 -0
  73. package/dist/admin/src/hooks/useDragAndDrop.d.ts +51 -0
  74. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +14 -0
  75. package/dist/admin/src/index.d.ts +10 -0
  76. package/dist/admin/src/pages/App.d.ts +3 -0
  77. package/dist/admin/src/pages/HomePage.d.ts +3 -0
  78. package/dist/admin/src/pluginId.d.ts +1 -0
  79. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  80. package/dist/server/index.js +1 -0
  81. package/dist/server/index.mjs +393 -0
  82. package/dist/server/src/bootstrap.d.ts +5 -0
  83. package/dist/server/src/config/index.d.ts +94 -0
  84. package/dist/server/src/content-types/index.d.ts +2 -0
  85. package/dist/server/src/controllers/controller.d.ts +12 -0
  86. package/dist/server/src/controllers/index.d.ts +14 -0
  87. package/dist/server/src/controllers/oembed.d.ts +12 -0
  88. package/dist/server/src/destroy.d.ts +5 -0
  89. package/dist/server/src/index.d.ts +125 -0
  90. package/dist/server/src/middlewares/index.d.ts +2 -0
  91. package/dist/server/src/policies/index.d.ts +2 -0
  92. package/dist/server/src/register.d.ts +5 -0
  93. package/dist/server/src/routes/admin.d.ts +9 -0
  94. package/dist/server/src/routes/content-api.d.ts +9 -0
  95. package/dist/server/src/routes/index.d.ts +25 -0
  96. package/dist/server/src/services/index.d.ts +14 -0
  97. package/dist/server/src/services/oembed.d.ts +27 -0
  98. package/dist/server/src/services/service.d.ts +7 -0
  99. package/package.json +131 -0
@@ -0,0 +1,2 @@
1
+ import type { CustomElement } from './types';
2
+ export declare const parseMarkdownToSlate: (markdownText: string) => CustomElement[] | null;
@@ -0,0 +1,8 @@
1
+ declare global {
2
+ interface Window {
3
+ strapi: {
4
+ backendURL: string;
5
+ };
6
+ }
7
+ }
8
+ export declare const prefixFileUrlWithBackendUrl: (fileURL?: string) => string | undefined;
@@ -0,0 +1,29 @@
1
+ import { type Highlighter } from 'shiki';
2
+ export declare const LIGHT_THEME = "github-light";
3
+ export declare const DARK_THEME = "github-dark";
4
+ /**
5
+ * Subscribe to highlighter/language load events. Returns an unsubscribe fn.
6
+ */
7
+ export declare const subscribeShiki: (cb: () => void) => (() => void);
8
+ /**
9
+ * Map a plugin code language value to a Shiki grammar id, or `null` when there
10
+ * is no highlighting to apply (plaintext / unknown).
11
+ */
12
+ export declare const normalizeLang: (value?: string) => string | null;
13
+ /**
14
+ * Return the highlighter instance, kicking off async init on first call.
15
+ * Returns `null` until the highlighter is ready.
16
+ */
17
+ export declare const getShiki: () => Highlighter | null;
18
+ /**
19
+ * Ensure a Shiki grammar is loaded. Returns `true` when ready to tokenize,
20
+ * `false` while loading (a `notify()` fires once it resolves).
21
+ */
22
+ export declare const ensureLanguage: (lang: string) => boolean;
23
+ type CachedTokens = ReturnType<Highlighter['codeToTokens']>;
24
+ /**
25
+ * Tokenize `code` with caching. Caller must have confirmed the highlighter and
26
+ * language are ready (getShiki()/ensureLanguage()).
27
+ */
28
+ export declare const tokenizeCode: (code: string, lang: string, theme: string) => CachedTokens | null;
29
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Keyboard shortcuts for block-type conversion.
3
+ *
4
+ * These are matched on `event.code` rather than `event.key`: on macOS, holding
5
+ * Option rewrites `key` ("1" becomes "¡"), while `code` stays "Digit1".
6
+ */
7
+ /** Block keys that can be reached with a shortcut, mapped to their key code. */
8
+ declare const BLOCK_SHORTCUTS: Record<string, string>;
9
+ /**
10
+ * Renders a shortcut for display next to a menu item, e.g. "⌘⌥2" on macOS and
11
+ * "Ctrl+Alt+2" elsewhere.
12
+ */
13
+ declare const getShortcutLabel: (blockKey: string) => string | null;
14
+ /**
15
+ * Resolves a keydown event to the block key it should convert to, or null when
16
+ * the event isn't a block shortcut. Callers still own preventDefault.
17
+ */
18
+ declare const matchBlockShortcut: (event: {
19
+ code: string;
20
+ altKey: boolean;
21
+ metaKey: boolean;
22
+ ctrlKey: boolean;
23
+ shiftKey: boolean;
24
+ }) => string | null;
25
+ export { BLOCK_SHORTCUTS, getShortcutLabel, matchBlockShortcut };
@@ -0,0 +1,404 @@
1
+ import type { BaseElement, BaseText, Node, Descendant } from 'slate';
2
+ export interface Option {
3
+ label: string;
4
+ value: string;
5
+ isDefault?: boolean;
6
+ }
7
+ export interface FontSetting {
8
+ breakpoint: string;
9
+ fontSize: string | null;
10
+ fontLeading: string | null;
11
+ fontAlignment: string | null;
12
+ fontTracking: string | null;
13
+ }
14
+ export interface SeparatorSetting {
15
+ breakpoint: string;
16
+ separatorSize: number | null;
17
+ separatorOrientation: 'horizontal' | 'vertical' | null;
18
+ separatorLength: number | null;
19
+ }
20
+ export interface ImageSetting {
21
+ breakpoint: string;
22
+ imageWidth: string | null;
23
+ imageHeight: string | null;
24
+ imageAspectRatioLocked?: boolean;
25
+ }
26
+ export type TextAlign = 'left' | 'center' | 'right' | 'justify';
27
+ export interface CustomElement extends BaseElement {
28
+ type: string;
29
+ fontFamily?: string;
30
+ fontColor?: string;
31
+ fontSettings?: FontSetting[];
32
+ textAlign?: TextAlign;
33
+ separatorStyle?: string;
34
+ separatorColor?: string;
35
+ separatorSettings?: SeparatorSetting[];
36
+ imageSettings?: ImageSetting[];
37
+ [key: string]: unknown;
38
+ }
39
+ export interface CustomText extends BaseText {
40
+ type: 'text';
41
+ text: string;
42
+ color?: string;
43
+ backgroundColor?: string;
44
+ }
45
+ export interface LinkNode extends CustomElement {
46
+ type: 'link';
47
+ url: string;
48
+ target?: '_blank' | '_self';
49
+ rel?: string;
50
+ children: Descendant[];
51
+ }
52
+ export interface ListNode extends CustomElement {
53
+ type: 'list';
54
+ format: 'ordered' | 'unordered' | 'todo';
55
+ indentLevel: number;
56
+ /**
57
+ * First number of an ordered list, mirroring the `start` attribute on `<ol>`.
58
+ * Only set when the list does not begin at 1 (e.g. Markdown `3.` pasted in),
59
+ * so existing content and consumers that ignore it keep numbering from 1.
60
+ */
61
+ start?: number;
62
+ children: Descendant[];
63
+ }
64
+ export interface HeadingElement extends CustomElement {
65
+ type: 'heading';
66
+ level: 1 | 2 | 3 | 4 | 5 | 6;
67
+ children: CustomText[];
68
+ }
69
+ export interface ImageElement extends CustomElement {
70
+ type: 'image';
71
+ image: {
72
+ url: string;
73
+ alternativeText?: string;
74
+ width?: number;
75
+ height?: number;
76
+ formats?: Record<string, any>;
77
+ hash?: string;
78
+ ext?: string;
79
+ mime?: string;
80
+ size?: number;
81
+ previewUrl?: string;
82
+ };
83
+ imageSettings?: ImageSetting[];
84
+ children: CustomText[];
85
+ }
86
+ export type AudioAlignment = 'left' | 'center' | 'right' | 'none';
87
+ export type AudioPreload = 'none' | 'metadata' | 'auto';
88
+ /** Player behaviour flags, mirrored 1:1 onto the HTML5 `<audio>` element. */
89
+ export interface AudioPlayerSettings {
90
+ autoplay: boolean;
91
+ loop: boolean;
92
+ controls: boolean;
93
+ preload: AudioPreload;
94
+ }
95
+ /**
96
+ * Audio block. A void block referencing a Strapi Media Library asset (or an
97
+ * external URL). Stores the file metadata plus a display title, caption, player
98
+ * behaviour flags and alignment. The frontend renderer turns this into a native
99
+ * HTML5 `<audio>` player — see issue #43 for the renderer contract.
100
+ */
101
+ export interface AudioElement extends CustomElement {
102
+ type: 'audio';
103
+ file: {
104
+ /** Strapi Media Library file id (absent when inserted from a raw URL). */
105
+ id?: number;
106
+ /** Direct URL to the audio file (absolute, backend-prefixed when local). */
107
+ url: string;
108
+ name?: string;
109
+ ext?: string;
110
+ hash?: string;
111
+ mime?: string;
112
+ /** File size in bytes. */
113
+ size?: number;
114
+ /** Duration in seconds (optional; read from the player when available). */
115
+ duration?: number;
116
+ /** Upload provider: local | cloudinary | etc. */
117
+ provider?: string;
118
+ };
119
+ title?: string;
120
+ caption?: string;
121
+ player: AudioPlayerSettings;
122
+ alignment: AudioAlignment;
123
+ children: CustomText[];
124
+ }
125
+ export declare const isAudioNode: (element: CustomElement) => element is AudioElement;
126
+ export interface MathElement extends CustomElement {
127
+ type: 'math';
128
+ format: 'inline' | 'block';
129
+ value: string;
130
+ children: CustomText[];
131
+ }
132
+ export interface DiagramElement extends CustomElement {
133
+ type: 'diagram';
134
+ format: 'mermaid';
135
+ value: string;
136
+ children: CustomText[];
137
+ }
138
+ export type CalloutVariant = 'note' | 'tip' | 'important' | 'warning' | 'caution';
139
+ export interface CalloutElement extends CustomElement {
140
+ type: 'callout';
141
+ variant: CalloutVariant;
142
+ title?: string;
143
+ children: Descendant[];
144
+ }
145
+ export interface DetailsElement extends CustomElement {
146
+ type: 'details';
147
+ /** Plain-text label shown in the disclosure header (the <summary>) */
148
+ summary: string;
149
+ /** When true the section is expanded by default (maps to the HTML `open` attribute) */
150
+ defaultOpen?: boolean;
151
+ /** Per-block style override; falls back to the field/global config when unset */
152
+ style?: 'github' | 'custom';
153
+ /** Block-level content shown when the section is expanded */
154
+ children: Descendant[];
155
+ }
156
+ export type ButtonAlignment = 'left' | 'center' | 'right';
157
+ export type ButtonMode = 'link' | 'file';
158
+ export type ButtonLinkTarget = '_self' | '_blank' | '_parent' | '_top';
159
+ export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'filled' | 'custom';
160
+ export interface ButtonStyle {
161
+ /**
162
+ * Selected style preset. Switches to "custom" once the author overrides the
163
+ * preset's colors or border. Frontend renderers can ignore this.
164
+ */
165
+ variant?: ButtonVariant;
166
+ backgroundColor?: string;
167
+ textColor?: string;
168
+ borderRadius?: string;
169
+ fontSize?: string;
170
+ fontWeight?: string;
171
+ padding?: string;
172
+ border?: string;
173
+ hoverBackgroundColor?: string;
174
+ hoverTextColor?: string;
175
+ }
176
+ /** Color/border values a preset applies (the rest of the styling stays as-is). */
177
+ export interface ButtonPreset {
178
+ backgroundColor?: string;
179
+ textColor?: string;
180
+ border?: string;
181
+ }
182
+ export type ButtonPresets = Partial<Record<Exclude<ButtonVariant, 'custom'>, ButtonPreset>>;
183
+ export interface ButtonLink {
184
+ url: string;
185
+ target?: ButtonLinkTarget;
186
+ /** Auto-set to "noopener noreferrer" when target is "_blank". */
187
+ rel?: string;
188
+ ariaLabel?: string;
189
+ }
190
+ export interface ButtonFile {
191
+ /** Strapi Media Library file id (when picked from the library). */
192
+ id?: number;
193
+ url: string;
194
+ name: string;
195
+ /** File size in bytes. */
196
+ size?: number;
197
+ /** File extension, e.g. ".pdf". */
198
+ ext?: string;
199
+ mime?: string;
200
+ }
201
+ /**
202
+ * WordPress-style call-to-action button. A void block: its visual form is the
203
+ * stored data (text + link/file + inline style), not editable rich text.
204
+ */
205
+ export interface ButtonElement extends CustomElement {
206
+ type: 'button';
207
+ /** "link" → hyperlink, "file" → Media Library download. */
208
+ buttonType: ButtonMode;
209
+ /**
210
+ * Visible button label. NOTE: must not be named `text` — Slate treats any node
211
+ * with a string `text` property as a Text leaf, which would break this element.
212
+ */
213
+ label: string;
214
+ alignment: ButtonAlignment;
215
+ link?: ButtonLink;
216
+ file?: ButtonFile;
217
+ /** File mode: show the human-readable file size next to the label. */
218
+ showFileSize?: boolean;
219
+ /** File mode: show a file-type icon next to the label. */
220
+ showFileIcon?: boolean;
221
+ /**
222
+ * File mode: when true, open the file in a new tab (preview) instead of
223
+ * forcing a download. Renderers map this to `target="_blank"` vs `download`.
224
+ */
225
+ filePreview?: boolean;
226
+ style?: ButtonStyle;
227
+ /** Optional custom CSS class for frontend theming. */
228
+ cssClass?: string;
229
+ children: CustomText[];
230
+ }
231
+ /**
232
+ * Horizontal alignment of a table cell's content. Absent means `left`, so
233
+ * documents authored before alignment existed need no migration.
234
+ */
235
+ export type TableCellAlign = 'left' | 'center' | 'right';
236
+ export interface TableCellElement extends CustomElement {
237
+ type: 'table-cell' | 'table-header-cell';
238
+ align?: TableCellAlign;
239
+ /**
240
+ * Columns this cell covers, from merging. Absent means 1, so unmerged cells
241
+ * — the overwhelming majority — store nothing and existing tables stay valid.
242
+ */
243
+ colSpan?: number;
244
+ /** Rows this cell covers. Absent means 1. */
245
+ rowSpan?: number;
246
+ /** Inline content: text leaves, links, inline math, … */
247
+ children: Descendant[];
248
+ }
249
+ export interface TableRowElement extends CustomElement {
250
+ type: 'table-row';
251
+ children: TableCellElement[];
252
+ }
253
+ export interface TableElement extends CustomElement {
254
+ type: 'table';
255
+ children: TableRowElement[];
256
+ }
257
+ export declare const isTableCellNode: (element: CustomElement) => element is TableCellElement;
258
+ export type Block<T extends string> = Extract<Node, {
259
+ type: T;
260
+ }>;
261
+ export declare const getEntries: <T extends object>(object: T) => [keyof T, T[keyof T]][];
262
+ export declare const getKeys: <T extends object>(object: T) => (keyof T)[];
263
+ export declare const isLinkNode: (element: CustomElement) => element is LinkNode;
264
+ export declare const isListNode: (element: CustomElement) => element is ListNode;
265
+ export declare const isMathNode: (element: CustomElement) => element is MathElement;
266
+ export declare const isDiagramNode: (element: CustomElement) => element is DiagramElement;
267
+ export declare const isCalloutNode: (element: CustomElement) => element is CalloutElement;
268
+ export declare const isDetailsNode: (element: CustomElement) => element is DetailsElement;
269
+ export declare const isButtonNode: (element: CustomElement) => element is ButtonElement;
270
+ /** Social platforms supported by the social-embed block. */
271
+ export type SocialPlatform = 'twitter' | 'instagram' | 'facebook' | 'tiktok' | 'linkedin' | 'pinterest';
272
+ export type SocialAlignment = 'left' | 'center' | 'right';
273
+ /**
274
+ * Normalised oEmbed payload fetched from the platform via the plugin's server
275
+ * oEmbed proxy (`GET /better-blocks/oembed`). Stored on the node so the frontend
276
+ * renderer can render the embed without itself calling the platform at runtime.
277
+ */
278
+ export interface SocialOEmbed {
279
+ /** Platform-provided embed HTML (blockquote / iframe). */
280
+ html?: string;
281
+ title?: string;
282
+ author?: string;
283
+ authorUrl?: string;
284
+ thumbnailUrl?: string;
285
+ providerName?: string;
286
+ width?: number;
287
+ height?: number;
288
+ }
289
+ /**
290
+ * Social media embed. A void block: it stores the post URL + platform plus the
291
+ * oEmbed payload fetched server-side at author time. The frontend renderer turns
292
+ * that into the platform's official embed widget. An optional `embedCode` lets
293
+ * authors paste a platform embed snippet to override URL-based rendering.
294
+ */
295
+ export interface SocialEmbedElement extends CustomElement {
296
+ type: 'social-embed';
297
+ platform: SocialPlatform;
298
+ /** Canonical URL of the social post. */
299
+ url: string;
300
+ /** Optional pasted embed snippet (overrides URL-based rendering). */
301
+ embedCode?: string;
302
+ /** oEmbed metadata fetched from the platform via the server proxy. */
303
+ oembed?: SocialOEmbed;
304
+ alignment: SocialAlignment;
305
+ caption?: string;
306
+ children: CustomText[];
307
+ }
308
+ export declare const isSocialEmbedNode: (element: CustomElement) => element is SocialEmbedElement;
309
+ /** Shared by the embed and video blocks; maps to CSS `aspect-ratio`. */
310
+ export type AspectRatio = '16:9' | '21:9' | '4:3' | '1:1' | 'custom';
311
+ export type MediaAlignment = 'left' | 'center' | 'right' | 'none';
312
+ /**
313
+ * Providers the embed block can derive an iframe for from a share URL.
314
+ * `generic` covers anything pasted as raw embed code.
315
+ */
316
+ export type EmbedProvider = 'youtube' | 'vimeo' | 'loom' | 'wistia' | 'dailymotion' | 'api-video' | 'generic';
317
+ /**
318
+ * Generic embed. A void block holding either a share URL the plugin knows how
319
+ * to turn into an iframe, or a raw embed snippet the author pasted. In both
320
+ * cases `embedHtml` is the sanitized, ready-to-render iframe markup, so a
321
+ * frontend renderer never has to re-derive or re-sanitize anything.
322
+ *
323
+ * Supersedes the legacy `media-embed` block (YouTube/Vimeo only).
324
+ */
325
+ export interface EmbedElement extends CustomElement {
326
+ type: 'embed';
327
+ /** Which input the author used. Drives which of `url`/`iframe` is set. */
328
+ source: 'url' | 'iframe';
329
+ /** Original share URL (source: 'url'). */
330
+ url?: string;
331
+ /** Raw snippet exactly as pasted, kept for round-tripping the editor UI. */
332
+ iframe?: string;
333
+ /**
334
+ * Sanitized iframe markup to render. Always present once the block has a
335
+ * source — renderers should output this and ignore `url`/`iframe`.
336
+ */
337
+ embedHtml?: string;
338
+ /** The `src` of `embedHtml`, hoisted so renderers can check the host. */
339
+ embedSrc?: string;
340
+ /** Auto-detected from the URL; `generic` for pasted embed codes. */
341
+ provider?: EmbedProvider;
342
+ /** Poster image, when the provider exposes one without an API call. */
343
+ thumbnail?: string;
344
+ aspectRatio: AspectRatio;
345
+ /** Free-form `width / height` used when `aspectRatio` is 'custom'. */
346
+ customAspectRatio?: string;
347
+ alignment: MediaAlignment;
348
+ caption?: string;
349
+ /** Accessible name applied to the iframe's `title` attribute. */
350
+ title?: string;
351
+ children: CustomText[];
352
+ }
353
+ export declare const isEmbedNode: (element: CustomElement) => element is EmbedElement;
354
+ /** Video hosting providers the video block understands. */
355
+ export type VideoProvider = 'local' | 'mux' | 'api-video' | 'cloudinary' | 'custom';
356
+ /** Player behaviour flags, mirrored onto the HTML5 `<video>` element. */
357
+ export interface VideoPlayerSettings {
358
+ autoplay: boolean;
359
+ loop: boolean;
360
+ muted: boolean;
361
+ controls: boolean;
362
+ }
363
+ /**
364
+ * Provider-aware video block. Holds a playable source (a Media Library asset,
365
+ * a provider playback id, or a direct URL) plus display and player settings.
366
+ *
367
+ * NOTE: player flags are nested under `player` rather than sitting at the top
368
+ * level as issue #44 proposed, to match the sibling `audio` block — one shape
369
+ * for renderers to learn, not two.
370
+ */
371
+ export interface VideoElement extends CustomElement {
372
+ type: 'video';
373
+ provider: VideoProvider;
374
+ /** Provider's asset identifier (Mux asset id, api.video video id, …). */
375
+ assetId?: string;
376
+ /** Provider's playback identifier (Mux playback id). */
377
+ playbackId?: string;
378
+ /** Playback URL: HLS/DASH manifest, or a direct file URL for `local`. */
379
+ url: string;
380
+ /** Media Library metadata, present when the source is an uploaded asset. */
381
+ file?: {
382
+ id?: number;
383
+ name?: string;
384
+ ext?: string;
385
+ mime?: string;
386
+ /** File size in bytes. */
387
+ size?: number;
388
+ /** Duration in seconds, when the provider reports it. */
389
+ duration?: number;
390
+ provider?: string;
391
+ };
392
+ /** Thumbnail shown before playback. */
393
+ poster?: string;
394
+ title?: string;
395
+ caption?: string;
396
+ /** URL of a WebVTT track for captions/transcript. */
397
+ transcript?: string;
398
+ player: VideoPlayerSettings;
399
+ alignment: MediaAlignment;
400
+ aspectRatio: AspectRatio;
401
+ customAspectRatio?: string;
402
+ children: CustomText[];
403
+ }
404
+ export declare const isVideoNode: (element: CustomElement) => element is VideoElement;
@@ -0,0 +1,5 @@
1
+ type InitializerProps = {
2
+ setPlugin: (id: string) => void;
3
+ };
4
+ declare const Initializer: ({ setPlugin }: InitializerProps) => null;
5
+ export { Initializer };
@@ -0,0 +1,45 @@
1
+ import * as React from 'react';
2
+ interface InputProps {
3
+ attribute: {
4
+ type: string;
5
+ customField: string;
6
+ options?: {
7
+ required?: boolean;
8
+ regex?: string;
9
+ minLength?: number;
10
+ unique?: boolean;
11
+ disableDefaultColors?: boolean;
12
+ customColorsPresets?: string;
13
+ disableDefaultBgColors?: boolean;
14
+ customBgColorsPresets?: string;
15
+ detailsDefaultSummary?: string;
16
+ detailsStyle?: 'github' | 'custom';
17
+ buttonDefaultBackgroundColor?: string;
18
+ buttonDefaultTextColor?: string;
19
+ };
20
+ };
21
+ description?: {
22
+ id: string;
23
+ defaultMessage: string;
24
+ };
25
+ hint?: string;
26
+ disabled?: boolean;
27
+ intlLabel?: {
28
+ id: string;
29
+ defaultMessage: string;
30
+ };
31
+ name: string;
32
+ label: string;
33
+ onChange: (e: {
34
+ target: {
35
+ name: string;
36
+ type: string;
37
+ value: string;
38
+ };
39
+ }) => void;
40
+ required?: boolean;
41
+ value?: string;
42
+ error?: string;
43
+ }
44
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
45
+ export default Input;
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ declare const PluginIcon: () => import("react").JSX.Element;
3
+ export { PluginIcon };
@@ -0,0 +1,51 @@
1
+ import * as React from 'react';
2
+ import { type HandlerManager, type ConnectDragSource, type ConnectDropTarget, type ConnectDragPreview } from 'react-dnd';
3
+ import { type UseKeyboardDragAndDropCallbacks } from './useKeyboardDragAndDrop';
4
+ import type { Data } from '@strapi/types';
5
+ declare const DIRECTIONS: {
6
+ readonly UPWARD: "upward";
7
+ readonly DOWNWARD: "downward";
8
+ };
9
+ declare const DROP_SENSITIVITY: {
10
+ readonly REGULAR: "regular";
11
+ readonly IMMEDIATE: "immediate";
12
+ };
13
+ interface UseDragAndDropOptions<TIndex extends number | Array<number> = number, TItem extends {
14
+ index: TIndex;
15
+ } = {
16
+ index: TIndex;
17
+ }> extends UseKeyboardDragAndDropCallbacks<TIndex> {
18
+ type?: string;
19
+ index: TIndex;
20
+ item?: TItem;
21
+ onStart?: () => void;
22
+ onEnd?: () => void;
23
+ dropSensitivity?: (typeof DROP_SENSITIVITY)[keyof typeof DROP_SENSITIVITY];
24
+ }
25
+ type Identifier = ReturnType<HandlerManager['getHandlerId']>;
26
+ type UseDragAndDropReturn<E extends Element = HTMLElement> = [
27
+ props: {
28
+ handlerId: Identifier;
29
+ isDragging: boolean;
30
+ handleKeyDown: <E extends Element>(event: React.KeyboardEvent<E>) => void;
31
+ isOverDropTarget: boolean;
32
+ direction: (typeof DIRECTIONS)[keyof typeof DIRECTIONS] | null;
33
+ },
34
+ objectRef: React.RefObject<E>,
35
+ dropRef: ConnectDropTarget,
36
+ dragRef: ConnectDragSource,
37
+ dragPreviewRef: ConnectDragPreview
38
+ ];
39
+ /**
40
+ * A utility hook abstracting the general drag and drop hooks from react-dnd.
41
+ * Centralising the same behaviours and by default offering keyboard support.
42
+ */
43
+ declare const useDragAndDrop: <TIndex extends number | number[], TItem extends {
44
+ [key: string]: unknown;
45
+ index: TIndex;
46
+ id?: Data.ID | undefined;
47
+ } = {
48
+ [key: string]: unknown;
49
+ index: TIndex;
50
+ }, E extends Element = HTMLElement>(active: boolean, { type, index, item, onStart, onEnd, onGrabItem, onDropItem, onCancel, onMoveItem, dropSensitivity, }: UseDragAndDropOptions<TIndex, TItem>) => UseDragAndDropReturn<E>;
51
+ export { useDragAndDrop, UseDragAndDropReturn, UseDragAndDropOptions, DIRECTIONS, DROP_SENSITIVITY, };
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ export type UseKeyboardDragAndDropCallbacks<TIndex extends number | Array<number> = number> = {
3
+ onCancel?: (index: TIndex) => void;
4
+ onDropItem?: (currentIndex: TIndex, newIndex?: TIndex) => void;
5
+ onGrabItem?: (index: TIndex) => void;
6
+ onMoveItem?: (newIndex: TIndex, currentIndex: TIndex) => void;
7
+ };
8
+ /**
9
+ * Utility hook designed to implement keyboard accessibile drag and drop by
10
+ * returning an onKeyDown handler to be passed to the drag icon button.
11
+ *
12
+ * @internal - You should use `useDragAndDrop` instead.
13
+ */
14
+ export declare const useKeyboardDragAndDrop: <TIndex extends number | number[] = number>(active: boolean, index: TIndex, { onCancel, onDropItem, onGrabItem, onMoveItem, }: UseKeyboardDragAndDropCallbacks<TIndex>) => <E extends Element>(e: React.KeyboardEvent<E>) => void;
@@ -0,0 +1,10 @@
1
+ declare const _default: {
2
+ register(app: any): void;
3
+ registerTrads({ locales }: {
4
+ locales: string[];
5
+ }): Promise<{
6
+ data: any;
7
+ locale: string;
8
+ }[]>;
9
+ };
10
+ export default _default;
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ declare const App: () => import("react").JSX.Element;
3
+ export { App };
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ declare const HomePage: () => import("react").JSX.Element;
3
+ export { HomePage };
@@ -0,0 +1 @@
1
+ export declare const PLUGIN_ID = "better-blocks";
@@ -0,0 +1,2 @@
1
+ declare const getTranslation: (id: string) => string;
2
+ export { getTranslation };
@@ -0,0 +1 @@
1
+ "use strict";const g=({strapi:t})=>{},k=({strapi:t})=>{},y=({strapi:t})=>{t.customFields.register({name:"better-blocks",plugin:"better-blocks",type:"json",inputSize:{default:12,isResizable:!1}})},w={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,instagram:{accessToken:void 0},facebook:{accessToken:void 0}}},validator(t={}){const o=t?.details?.style;if(o&&o!=="github"&&o!=="custom")throw new Error(`[better-blocks] config.details.style must be "github" or "custom" (received "${o}")`)}},C={},v=({strapi:t})=>({index(o){o.body=t.plugin("better-blocks").service("service").getWelcomeMessage()},getConfig(o){o.body={details:t.plugin("better-blocks").config("details",{defaultSummary:"Click to expand",style:"github"}),button:t.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=t.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}})()}}}),x=["twitter","instagram","facebook","tiktok","linkedin","pinterest"],$=({strapi:t})=>({async fetch(o){const{url:e,platform:i}=o.query;if(!e||typeof e!="string")return o.badRequest('Missing required "url" query parameter.');const r=i&&x.includes(i)?i:void 0;try{o.body=await t.plugin("better-blocks").service("oembed").fetch(e,r)}catch(n){const a=n instanceof Error?n.message:"oEmbed request failed.";return o.badRequest(a)}}}),T={controller:v,oembed:$},E={},R={},L=[{method:"GET",path:"/",handler:"controller.index",config:{policies:[]}}],_=[{method:"GET",path:"/config",handler:"controller.getConfig",config:{policies:[]}},{method:"GET",path:"/oembed",handler:"oembed.fetch",config:{policies:["admin::isAuthenticatedAdmin"]}}],S={"content-api":{type:"content-api",routes:L},admin:{type:"admin",routes:_}},U=({strapi:t})=>({getWelcomeMessage(){return"Welcome to Strapi 🚀"}}),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}],b=t=>{const o=I.find(({re:e})=>e.test(t));return o?o.platform:null},A=(t,o,e)=>{const i=encodeURIComponent(o);switch(t){case"twitter":return`https://publish.twitter.com/oembed?url=${i}&omit_script=true&dnt=true`;case"tiktok":return`https://www.tiktok.com/oembed?url=${i}`;case"pinterest":return`https://www.pinterest.com/oembed.json?url=${i}`;case"instagram":{const r=e.instagram?.accessToken;return r?`https://graph.facebook.com/v17.0/instagram_oembed?url=${i}&access_token=${encodeURIComponent(r)}&omitscript=true`:null}case"facebook":{const r=e.facebook?.accessToken;return r?`https://graph.facebook.com/v17.0/oembed_post?url=${i}&access_token=${encodeURIComponent(r)}&omitscript=true`:null}case"linkedin":return null;default:return null}},P=async t=>{if(!/^https?:\/\/pin\.it\//i.test(t))return t;try{const e=(await fetch(t,{redirect:"follow"})).url||t;return/pinterest\.[a-z.]+\/pin\//i.test(e)?e:t}catch{return t}},q=t=>{const o=t.match(/(?:urn:li:)?(ugcPost|activity|share)[:-](\d{10,})/i);if(!o)return null;const i=`urn:li:${o[1].toLowerCase()==="ugcpost"?"ugcPost":o[1].toLowerCase()}:${o[2]}`,r=`https://www.linkedin.com/embed/feed/update/${encodeURIComponent(i)}`;return{platform:"linkedin",url:t,html:`<iframe src="${r}" height="600" width="100%" frameborder="0" allowfullscreen title="Embedded LinkedIn post" loading="lazy"></iframe>`,providerName:"LinkedIn",width:504,height:600}},z=t=>t.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi,"").trim(),M=(t,o,e)=>({platform:t,url:o,html:typeof e.html=="string"&&z(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}),h=new Map,W=({strapi:t})=>{const o=()=>t.plugin("better-blocks").config("social",{});return{detectPlatform:b,async fetch(e,i){const r=o();if(r.enabled===!1)throw new Error("Social embeds are disabled in plugin config.");const n=i??b(e);if(!n)throw new Error(`Unsupported or unrecognised social URL: ${e}`);const a=r.platforms;if(Array.isArray(a)&&!a.includes(n))throw new Error(`Platform "${n}" is not enabled in plugin config.`);const f=r.cache!==!1,u=(r.cacheTTL??86400)*1e3,p=`${n}:${e}`;if(f){const c=h.get(p);if(c&&c.expires>Date.now())return c.data}let s=null;if(n==="linkedin"){if(s=q(e),!s)throw new Error("Could not derive a LinkedIn embed from this URL. Paste the post embed code instead.")}else{const c=await P(e),m=A(n,c,r);if(!m)throw new Error(`oEmbed for "${n}" requires an access token. Configure config.social.${n}.accessToken, or paste the embed code instead.`);const l=await fetch(m,{headers:{Accept:"application/json"}});if(!l.ok)throw new Error(`oEmbed request to ${n} failed (HTTP ${l.status}).`);const d=await l.json();if(typeof d.error=="string")throw new Error(`${n} could not embed this URL: ${d.error}`);if(s=M(n,c,d),!s.html)throw new Error(`${n} returned no embed markup for this URL. Check the post is public, or paste the embed code instead.`)}return f&&u>0&&h.set(p,{expires:Date.now()+u,data:s}),s}}},j={service:U,oembed:W},D={register:y,bootstrap:g,destroy:k,config:w,controllers:T,routes:S,services:j,contentTypes:C,policies:R,middlewares:E};module.exports=D;