@jackuait/blok 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blok.cjs +1 -1
- package/dist/blok.iife.js +4 -4
- package/dist/blok.mjs +2 -2
- package/dist/blok.umd.js +41 -41
- package/dist/chunks/{blok-Cz71HTuP.cjs → blok-Blnd71Ww.cjs} +5 -5
- package/dist/chunks/{blok-x8D2aON2.mjs → blok-S6c68iVJ.mjs} +2570 -2269
- package/dist/chunks/{constants-BK6kLyZB.cjs → constants-BIqDMhRj.cjs} +1 -1
- package/dist/chunks/{constants-CLzXh7Tb.mjs → constants-BvxJIgSQ.mjs} +1 -1
- package/dist/chunks/{tools-Bw8_CkhE.mjs → tools-B89FUeNz.mjs} +451 -416
- package/dist/chunks/{tools-BKjP4TXy.cjs → tools-DTjC0C5o.cjs} +3 -3
- package/dist/full.cjs +1 -1
- package/dist/full.mjs +3 -3
- package/dist/react.cjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/tools.cjs +1 -1
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/components/modules/paste/index.ts +33 -6
- package/src/components/modules/paste/next-space-blocks.ts +520 -0
- package/src/components/modules/paste/notion-blocks-v3.ts +28 -4
- package/src/components/utils/media-empty-state.ts +80 -47
- package/src/tools/audio/empty-state.ts +4 -0
- package/src/tools/audio/index.ts +4 -0
- package/src/tools/file/empty-state.ts +4 -0
- package/src/tools/file/index.ts +2 -0
- package/src/tools/image/empty-state.ts +4 -0
- package/src/tools/image/index.ts +5 -0
- package/src/tools/video/empty-state.ts +4 -0
- package/src/tools/video/index.ts +4 -0
- package/types/tools/audio.d.ts +6 -0
- package/types/tools/file.d.ts +6 -0
- package/types/tools/image.d.ts +6 -0
- package/types/tools/media-source.d.ts +12 -0
- package/types/tools/video.d.ts +6 -0
- package/types/tools-entry.d.ts +1 -0
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* correctly-nested block tree.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { colorVarName } from '../../shared/color-presets';
|
|
23
|
+
import { COLOR_PRESETS, colorVarName } from '../../shared/color-presets';
|
|
24
24
|
import { DEFAULT_EMOJI } from '../../../tools/callout/constants';
|
|
25
25
|
import { DEFAULT_LANGUAGE, LANGUAGES } from '../../../tools/code/constants';
|
|
26
26
|
import { matchEmbedService } from '../../../tools/link/registry';
|
|
@@ -151,6 +151,17 @@ export function parseNotionBlocksV3(json: string): NotionParsedBlock[] | null {
|
|
|
151
151
|
|
|
152
152
|
result.push(block);
|
|
153
153
|
|
|
154
|
+
// Blok's callout keeps its body in CHILD blocks, so the callout's own inline
|
|
155
|
+
// title line becomes a child paragraph (emitted before its `content`
|
|
156
|
+
// children), matching what a native callout copy carries.
|
|
157
|
+
if (value.type === 'callout') {
|
|
158
|
+
const calloutTitle = richText(value.properties?.title, byId);
|
|
159
|
+
|
|
160
|
+
if (calloutTitle.length > 0) {
|
|
161
|
+
result.push({ id: `${id}:callout-body`, tool: 'paragraph', data: { text: calloutTitle }, parentId: id });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
154
165
|
children.forEach((childId) => walk(childId, id));
|
|
155
166
|
};
|
|
156
167
|
|
|
@@ -328,7 +339,10 @@ function mapValue(value: NotionValue, byId: Map<string, NotionValue>): Mapped |
|
|
|
328
339
|
const emoji = typeof format.page_icon === 'string' && format.page_icon.length > 0 ? format.page_icon : DEFAULT_EMOJI;
|
|
329
340
|
const { textColor, backgroundColor } = parseBlockColor(format.block_color);
|
|
330
341
|
|
|
331
|
-
|
|
342
|
+
// No inline `text`: Blok's callout stores its body in CHILD blocks, so the
|
|
343
|
+
// callout's title line is emitted as a child paragraph by `walk` (and a
|
|
344
|
+
// stray `data.text` would be silently discarded by the callout tool).
|
|
345
|
+
return { tool: 'callout', data: { emoji, textColor, backgroundColor } };
|
|
332
346
|
}
|
|
333
347
|
case 'page':
|
|
334
348
|
// A `page` block inside content is a SUB-PAGE reference: its body lives on
|
|
@@ -862,11 +876,21 @@ function buildColourStyle(colourFlags: unknown[]): string {
|
|
|
862
876
|
return parts.join('; ');
|
|
863
877
|
}
|
|
864
878
|
|
|
865
|
-
/**
|
|
879
|
+
/** The set of colour preset names Blok recognises (its callout/marker palette). */
|
|
880
|
+
const BLOK_COLOR_NAMES = new Set(COLOR_PRESETS.map((preset) => preset.name));
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Map a Notion `block_color` to callout `{ textColor, backgroundColor }` names.
|
|
884
|
+
* Names outside Blok's preset palette collapse to `null`: the callout tool
|
|
885
|
+
* builds `var(--blok-color-<name>-…)` from the name verbatim, so an unrecognised
|
|
886
|
+
* name yields an undefined custom property that silently drops the colour AND
|
|
887
|
+
* the callout's border. Notion's palette already aligns with Blok's, so this is
|
|
888
|
+
* a safety clamp rather than a remap.
|
|
889
|
+
*/
|
|
866
890
|
function parseBlockColor(token: unknown): { textColor: string | null; backgroundColor: string | null } {
|
|
867
891
|
const colour = parseNotionColour(token);
|
|
868
892
|
|
|
869
|
-
if (colour === null) {
|
|
893
|
+
if (colour === null || !BLOK_COLOR_NAMES.has(colour.name)) {
|
|
870
894
|
return { textColor: null, backgroundColor: null };
|
|
871
895
|
}
|
|
872
896
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MediaSource } from '../../../types/tools/media-source';
|
|
1
2
|
import {
|
|
2
3
|
IconArrowDownLine,
|
|
3
4
|
IconArrowUp,
|
|
@@ -48,6 +49,15 @@ export interface MediaEmptyStateOptions {
|
|
|
48
49
|
labels: MediaEmptyStateLabels;
|
|
49
50
|
onFile(file: File): void;
|
|
50
51
|
onUrl(url: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Which insert sources to expose:
|
|
54
|
+
* - `'both'` (default): show the Upload and Link tabs.
|
|
55
|
+
* - `'upload'`: file upload only — the Link tab and URL bar are hidden.
|
|
56
|
+
* - `'url'`: link/embed only — the Upload dropzone, file picker, and
|
|
57
|
+
* drag/paste-to-upload affordances are hidden.
|
|
58
|
+
* With a single source the tablist is omitted entirely.
|
|
59
|
+
*/
|
|
60
|
+
sources?: MediaSource;
|
|
51
61
|
/**
|
|
52
62
|
* How the panel transitions when switching Upload/Link tabs:
|
|
53
63
|
* - `'reflow'` (default): ease the panel height between the two layouts and
|
|
@@ -64,6 +74,9 @@ export interface MediaEmptyStateOptions {
|
|
|
64
74
|
|
|
65
75
|
export type MediaEmptyStateSwap = 'reflow' | 'slide' | 'none';
|
|
66
76
|
|
|
77
|
+
/** Re-exported from the public type so the renderer and tools share one source. */
|
|
78
|
+
export type { MediaSource };
|
|
79
|
+
|
|
67
80
|
const MIME_LABELS: Record<string, string> = {
|
|
68
81
|
'image/jpeg': 'JPG',
|
|
69
82
|
'image/jpg': 'JPG',
|
|
@@ -213,6 +226,10 @@ export function renderMediaEmptyState(opts: MediaEmptyStateOptions): MediaEmptyS
|
|
|
213
226
|
|
|
214
227
|
const swapMode: MediaEmptyStateSwap = opts.swap ?? 'reflow';
|
|
215
228
|
|
|
229
|
+
const source: MediaSource = opts.sources ?? 'both';
|
|
230
|
+
const showUpload = source !== 'url';
|
|
231
|
+
const showEmbed = source !== 'upload';
|
|
232
|
+
|
|
216
233
|
const root = document.createElement('div') as unknown as MediaEmptyStateElement;
|
|
217
234
|
root.className = 'blok-media-empty';
|
|
218
235
|
root.setAttribute('data-blok-media-empty-state', '');
|
|
@@ -233,17 +250,27 @@ export function renderMediaEmptyState(opts: MediaEmptyStateOptions): MediaEmptyS
|
|
|
233
250
|
panel.setAttribute('data-panel-host', '');
|
|
234
251
|
panel.setAttribute('role', 'tabpanel');
|
|
235
252
|
|
|
253
|
+
const initialKind: SourceKind = showUpload ? 'upload' : 'embed';
|
|
254
|
+
|
|
236
255
|
const tabs = document.createElement('div');
|
|
237
256
|
tabs.className = 'blok-media-empty__tabs';
|
|
238
257
|
tabs.setAttribute('role', 'tablist');
|
|
239
258
|
tabs.setAttribute('aria-label', labels.sourceAria);
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
259
|
+
const tabList: HTMLButtonElement[] = [];
|
|
260
|
+
const tabKinds: SourceKind[] = [];
|
|
261
|
+
if (showUpload) {
|
|
262
|
+
const uploadTab = makeTab('upload', labels.upload, panel.id, true);
|
|
263
|
+
tabs.append(uploadTab);
|
|
264
|
+
tabList.push(uploadTab);
|
|
265
|
+
tabKinds.push('upload');
|
|
266
|
+
}
|
|
267
|
+
if (showEmbed) {
|
|
268
|
+
const embedTab = makeTab('embed', labels.embed, panel.id, !showUpload);
|
|
269
|
+
tabs.append(embedTab);
|
|
270
|
+
tabList.push(embedTab);
|
|
271
|
+
tabKinds.push('embed');
|
|
272
|
+
}
|
|
273
|
+
panel.setAttribute('aria-labelledby', tabList[0].id);
|
|
247
274
|
|
|
248
275
|
const dropOverlay = document.createElement('div');
|
|
249
276
|
dropOverlay.className = 'blok-media-empty__drop-overlay';
|
|
@@ -456,53 +483,59 @@ export function renderMediaEmptyState(opts: MediaEmptyStateOptions): MediaEmptyS
|
|
|
456
483
|
}
|
|
457
484
|
});
|
|
458
485
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
486
|
+
// File-based affordances (paste/drag/drop) only apply when upload is enabled.
|
|
487
|
+
if (showUpload) {
|
|
488
|
+
card.addEventListener('paste', (ev) => {
|
|
489
|
+
if (card.getAttribute('data-active-tab') !== 'upload') return;
|
|
490
|
+
const clip = ev.clipboardData;
|
|
491
|
+
if (!clip) return;
|
|
492
|
+
for (const item of Array.from(clip.items)) {
|
|
493
|
+
if (item.kind !== 'file') continue;
|
|
494
|
+
const file = item.getAsFile();
|
|
495
|
+
if (file && (!types.length || matchesMime(file.type, types))) {
|
|
496
|
+
ev.preventDefault();
|
|
497
|
+
opts.onFile(file);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
470
500
|
}
|
|
471
|
-
}
|
|
472
|
-
});
|
|
501
|
+
});
|
|
473
502
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
503
|
+
const drag = { depth: 0 };
|
|
504
|
+
card.addEventListener('dragenter', (ev) => {
|
|
505
|
+
if (!ev.dataTransfer?.types.includes('Files')) return;
|
|
506
|
+
ev.preventDefault();
|
|
507
|
+
drag.depth += 1;
|
|
508
|
+
card.classList.add('is-dragover');
|
|
509
|
+
});
|
|
510
|
+
card.addEventListener('dragover', (ev) => {
|
|
511
|
+
const dt = ev.dataTransfer;
|
|
512
|
+
if (!dt?.types.includes('Files')) return;
|
|
513
|
+
ev.preventDefault();
|
|
514
|
+
dt.dropEffect = 'copy';
|
|
515
|
+
});
|
|
516
|
+
card.addEventListener('dragleave', () => {
|
|
517
|
+
drag.depth = Math.max(0, drag.depth - 1);
|
|
518
|
+
if (drag.depth === 0) card.classList.remove('is-dragover');
|
|
519
|
+
});
|
|
520
|
+
card.addEventListener('drop', (ev) => {
|
|
521
|
+
ev.preventDefault();
|
|
522
|
+
drag.depth = 0;
|
|
523
|
+
card.classList.remove('is-dragover');
|
|
524
|
+
const file = ev.dataTransfer?.files?.[0];
|
|
525
|
+
if (file) opts.onFile(file);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
498
528
|
|
|
499
529
|
const header = document.createElement('div');
|
|
500
530
|
header.className = 'blok-media-empty__header';
|
|
501
|
-
|
|
531
|
+
// A single source needs no tablist — show just the caption.
|
|
532
|
+
if (tabList.length > 1) header.append(label, tabs);
|
|
533
|
+
else header.append(label);
|
|
502
534
|
|
|
503
|
-
card.append(header, panel
|
|
535
|
+
card.append(header, panel);
|
|
536
|
+
if (showUpload) card.append(dropOverlay);
|
|
504
537
|
root.append(card, error);
|
|
505
|
-
activate(
|
|
538
|
+
activate(initialKind);
|
|
506
539
|
|
|
507
540
|
root.setError = (message: string | null): void => {
|
|
508
541
|
if (!message) {
|
|
@@ -3,6 +3,7 @@ import { tr } from './i18n';
|
|
|
3
3
|
import {
|
|
4
4
|
renderMediaEmptyState,
|
|
5
5
|
type MediaEmptyStateElement,
|
|
6
|
+
type MediaSource,
|
|
6
7
|
} from '../../components/utils/media-empty-state';
|
|
7
8
|
import type { I18nInstance } from '../../components/utils/tools';
|
|
8
9
|
|
|
@@ -13,6 +14,8 @@ export interface EmptyStateOptions {
|
|
|
13
14
|
acceptTypes?: string[];
|
|
14
15
|
/** Max file size in bytes — surfaced in the formats hint when set. */
|
|
15
16
|
maxSize?: number;
|
|
17
|
+
/** Which insert sources to expose. Default `'both'`. */
|
|
18
|
+
sources?: MediaSource;
|
|
16
19
|
i18n?: I18nInstance;
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -23,6 +26,7 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
|
|
|
23
26
|
return renderMediaEmptyState({
|
|
24
27
|
acceptTypes: opts.acceptTypes ?? [...DEFAULT_MIME_TYPES],
|
|
25
28
|
maxSize: opts.maxSize,
|
|
29
|
+
sources: opts.sources,
|
|
26
30
|
onFile: opts.onFile,
|
|
27
31
|
onUrl: opts.onUrl,
|
|
28
32
|
labels: {
|
package/src/tools/audio/index.ts
CHANGED
|
@@ -118,11 +118,14 @@ export class AudioTool implements BlockTool {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
public onPaste(event: PasteEvent): void {
|
|
121
|
+
const sources = this.config.sources;
|
|
121
122
|
if (event.type === 'pattern') {
|
|
123
|
+
if (sources === 'upload') return;
|
|
122
124
|
this.applyResult({ url: (event as PatternPasteEvent).detail.data });
|
|
123
125
|
return;
|
|
124
126
|
}
|
|
125
127
|
if (event.type === 'file') {
|
|
128
|
+
if (sources === 'url') return;
|
|
126
129
|
this.startUpload((event as FilePasteEvent).detail.file);
|
|
127
130
|
}
|
|
128
131
|
}
|
|
@@ -364,6 +367,7 @@ export class AudioTool implements BlockTool {
|
|
|
364
367
|
onUrl: (url) => this.startUrl(url),
|
|
365
368
|
acceptTypes: this.config.types,
|
|
366
369
|
maxSize: pickDisplayMaxSize(this.config.maxSize),
|
|
370
|
+
sources: this.config.sources,
|
|
367
371
|
i18n: this.api.i18n,
|
|
368
372
|
});
|
|
369
373
|
this.root.appendChild(el);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
renderMediaEmptyState,
|
|
3
3
|
type MediaEmptyStateElement,
|
|
4
|
+
type MediaSource,
|
|
4
5
|
} from '../../components/utils/media-empty-state';
|
|
5
6
|
import type { I18nInstance } from '../../components/utils/tools';
|
|
6
7
|
|
|
@@ -11,6 +12,8 @@ export interface EmptyStateOptions {
|
|
|
11
12
|
acceptTypes: string[];
|
|
12
13
|
/** Max file size in bytes — surfaced in the formats hint when set. */
|
|
13
14
|
maxSize?: number;
|
|
15
|
+
/** Which insert sources to expose. Default `'both'`. */
|
|
16
|
+
sources?: MediaSource;
|
|
14
17
|
i18n: I18nInstance;
|
|
15
18
|
onFile(file: File): void;
|
|
16
19
|
onUrl(url: string): void;
|
|
@@ -21,6 +24,7 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
|
|
|
21
24
|
return renderMediaEmptyState({
|
|
22
25
|
acceptTypes: opts.acceptTypes,
|
|
23
26
|
maxSize: opts.maxSize,
|
|
27
|
+
sources: opts.sources,
|
|
24
28
|
onFile: opts.onFile,
|
|
25
29
|
onUrl: opts.onUrl,
|
|
26
30
|
labels: {
|
package/src/tools/file/index.ts
CHANGED
|
@@ -98,6 +98,7 @@ export class FileTool implements BlockTool {
|
|
|
98
98
|
|
|
99
99
|
public onPaste(event: PasteEvent): void {
|
|
100
100
|
if (event.type === 'file') {
|
|
101
|
+
if (this.config.sources === 'url') return;
|
|
101
102
|
this.startUpload((event as FilePasteEvent).detail.file);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -346,6 +347,7 @@ export class FileTool implements BlockTool {
|
|
|
346
347
|
private buildEmpty(): EmptyStateElement {
|
|
347
348
|
return renderEmptyState({
|
|
348
349
|
acceptTypes: this.config.types ?? [],
|
|
350
|
+
sources: this.config.sources,
|
|
349
351
|
i18n: this.api.i18n,
|
|
350
352
|
onFile: (file) => this.startUpload(file),
|
|
351
353
|
onUrl: (url) => this.startUrl(url),
|
|
@@ -3,6 +3,7 @@ import { tr } from './i18n';
|
|
|
3
3
|
import {
|
|
4
4
|
renderMediaEmptyState,
|
|
5
5
|
type MediaEmptyStateElement,
|
|
6
|
+
type MediaSource,
|
|
6
7
|
} from '../../components/utils/media-empty-state';
|
|
7
8
|
import type { I18nInstance } from '../../components/utils/tools';
|
|
8
9
|
|
|
@@ -13,6 +14,8 @@ export interface EmptyStateOptions {
|
|
|
13
14
|
acceptTypes?: string[];
|
|
14
15
|
/** Max file size in bytes — surfaced in the formats hint when set. */
|
|
15
16
|
maxSize?: number;
|
|
17
|
+
/** Which insert sources to expose. Default `'both'`. */
|
|
18
|
+
sources?: MediaSource;
|
|
16
19
|
uploadLabel?: string;
|
|
17
20
|
embedLabel?: string;
|
|
18
21
|
embedPlaceholder?: string;
|
|
@@ -27,6 +30,7 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
|
|
|
27
30
|
return renderMediaEmptyState({
|
|
28
31
|
acceptTypes: opts.acceptTypes ?? [...DEFAULT_MIME_TYPES],
|
|
29
32
|
maxSize: opts.maxSize,
|
|
33
|
+
sources: opts.sources,
|
|
30
34
|
onFile: opts.onFile,
|
|
31
35
|
onUrl: opts.onUrl,
|
|
32
36
|
labels: {
|
package/src/tools/image/index.ts
CHANGED
|
@@ -156,7 +156,9 @@ export class ImageTool implements BlockTool {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
public onPaste(event: PasteEvent): void {
|
|
159
|
+
const sources = this.config.sources;
|
|
159
160
|
if (event.type === 'pattern') {
|
|
161
|
+
if (sources === 'upload') return;
|
|
160
162
|
const detail = (event as PatternPasteEvent).detail;
|
|
161
163
|
if (this.shouldConvertGifUrl(detail.data)) {
|
|
162
164
|
this.startUrl(detail.data);
|
|
@@ -166,6 +168,7 @@ export class ImageTool implements BlockTool {
|
|
|
166
168
|
return;
|
|
167
169
|
}
|
|
168
170
|
if (event.type === 'tag') {
|
|
171
|
+
if (sources === 'upload') return;
|
|
169
172
|
const node = (event as HTMLPasteEvent).detail.data;
|
|
170
173
|
const src = node?.getAttribute?.('src') ?? '';
|
|
171
174
|
if (!src) return;
|
|
@@ -173,6 +176,7 @@ export class ImageTool implements BlockTool {
|
|
|
173
176
|
return;
|
|
174
177
|
}
|
|
175
178
|
if (event.type === 'file') {
|
|
179
|
+
if (sources === 'url') return;
|
|
176
180
|
const detail = (event as FilePasteEvent).detail;
|
|
177
181
|
this.startUpload(detail.file);
|
|
178
182
|
}
|
|
@@ -687,6 +691,7 @@ export class ImageTool implements BlockTool {
|
|
|
687
691
|
onUrl: (url) => this.startUrl(url),
|
|
688
692
|
acceptTypes: this.config.types,
|
|
689
693
|
maxSize: pickDisplayMaxSize(this.config.maxSize),
|
|
694
|
+
sources: this.config.sources,
|
|
690
695
|
i18n: this.api.i18n,
|
|
691
696
|
});
|
|
692
697
|
this.emptyStateEl = el;
|
|
@@ -3,6 +3,7 @@ import { tr } from './i18n';
|
|
|
3
3
|
import {
|
|
4
4
|
renderMediaEmptyState,
|
|
5
5
|
type MediaEmptyStateElement,
|
|
6
|
+
type MediaSource,
|
|
6
7
|
} from '../../components/utils/media-empty-state';
|
|
7
8
|
import type { I18nInstance } from '../../components/utils/tools';
|
|
8
9
|
|
|
@@ -13,6 +14,8 @@ export interface EmptyStateOptions {
|
|
|
13
14
|
acceptTypes?: string[];
|
|
14
15
|
/** Max file size in bytes — surfaced in the formats hint when set. */
|
|
15
16
|
maxSize?: number;
|
|
17
|
+
/** Which insert sources to expose. Default `'both'`. */
|
|
18
|
+
sources?: MediaSource;
|
|
16
19
|
i18n?: I18nInstance;
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -23,6 +26,7 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
|
|
|
23
26
|
return renderMediaEmptyState({
|
|
24
27
|
acceptTypes: opts.acceptTypes ?? [...DEFAULT_MIME_TYPES],
|
|
25
28
|
maxSize: opts.maxSize,
|
|
29
|
+
sources: opts.sources,
|
|
26
30
|
onFile: opts.onFile,
|
|
27
31
|
onUrl: opts.onUrl,
|
|
28
32
|
labels: {
|
package/src/tools/video/index.ts
CHANGED
|
@@ -112,11 +112,14 @@ export class VideoTool implements BlockTool {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
public onPaste(event: PasteEvent): void {
|
|
115
|
+
const sources = this.config.sources;
|
|
115
116
|
if (event.type === 'pattern') {
|
|
117
|
+
if (sources === 'upload') return;
|
|
116
118
|
this.applyResult({ url: (event as PatternPasteEvent).detail.data });
|
|
117
119
|
return;
|
|
118
120
|
}
|
|
119
121
|
if (event.type === 'file') {
|
|
122
|
+
if (sources === 'url') return;
|
|
120
123
|
this.startUpload((event as FilePasteEvent).detail.file);
|
|
121
124
|
}
|
|
122
125
|
}
|
|
@@ -322,6 +325,7 @@ export class VideoTool implements BlockTool {
|
|
|
322
325
|
onUrl: (url) => this.startUrl(url),
|
|
323
326
|
acceptTypes: this.config.types,
|
|
324
327
|
maxSize: pickDisplayMaxSize(this.config.maxSize),
|
|
328
|
+
sources: this.config.sources,
|
|
325
329
|
i18n: this.api.i18n,
|
|
326
330
|
});
|
|
327
331
|
this.root.appendChild(el);
|
package/types/tools/audio.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BlockToolData } from './block-tool-data';
|
|
2
2
|
import { MaxSizeConfig } from './max-size';
|
|
3
|
+
import { MediaSource } from './media-source';
|
|
3
4
|
|
|
4
5
|
export type AudioAlignment = 'left' | 'center' | 'right';
|
|
5
6
|
|
|
@@ -36,5 +37,10 @@ export interface AudioConfig {
|
|
|
36
37
|
*/
|
|
37
38
|
types?: string[];
|
|
38
39
|
maxSize?: MaxSizeConfig;
|
|
40
|
+
/**
|
|
41
|
+
* Restrict how audio may be added. Default `'both'` (Upload + Link).
|
|
42
|
+
* Use `'upload'` for file-only or `'url'` for link-only. See {@link MediaSource}.
|
|
43
|
+
*/
|
|
44
|
+
sources?: MediaSource;
|
|
39
45
|
captionPlaceholder?: string;
|
|
40
46
|
}
|
package/types/tools/file.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BlockToolData } from './block-tool-data';
|
|
2
2
|
import { MaxSizeConfig } from './max-size';
|
|
3
|
+
import { MediaSource } from './media-source';
|
|
3
4
|
|
|
4
5
|
/** Persisted data shape for the File block tool. */
|
|
5
6
|
export interface FileData extends BlockToolData {
|
|
@@ -69,6 +70,11 @@ export interface FileConfig {
|
|
|
69
70
|
* unlimited. See {@link MaxSizeConfig}.
|
|
70
71
|
*/
|
|
71
72
|
maxSize?: MaxSizeConfig;
|
|
73
|
+
/**
|
|
74
|
+
* Restrict how a file may be added. Default `'both'` (Upload + Link).
|
|
75
|
+
* Use `'upload'` for file-only or `'url'` for link-only. See {@link MediaSource}.
|
|
76
|
+
*/
|
|
77
|
+
sources?: MediaSource;
|
|
72
78
|
/** Caption placeholder. */
|
|
73
79
|
captionPlaceholder?: string;
|
|
74
80
|
}
|
package/types/tools/image.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BlockToolData } from './block-tool-data';
|
|
2
2
|
import { MaxSizeConfig } from './max-size';
|
|
3
|
+
import { MediaSource } from './media-source';
|
|
3
4
|
|
|
4
5
|
/** Horizontal alignment of the image within its container. */
|
|
5
6
|
export type ImageAlignment = 'left' | 'center' | 'right';
|
|
@@ -88,6 +89,11 @@ export interface ImageConfig {
|
|
|
88
89
|
* type with `'*'` as the fallback. Default 30 MiB. See {@link MaxSizeConfig}.
|
|
89
90
|
*/
|
|
90
91
|
maxSize?: MaxSizeConfig;
|
|
92
|
+
/**
|
|
93
|
+
* Restrict how an image may be added. Default `'both'` (Upload + Link).
|
|
94
|
+
* Use `'upload'` for file-only or `'url'` for link-only. See {@link MediaSource}.
|
|
95
|
+
*/
|
|
96
|
+
sources?: MediaSource;
|
|
91
97
|
/**
|
|
92
98
|
* Auto-convert animated GIFs to a looping WebM video block on insert.
|
|
93
99
|
* Default true. Set false to keep GIFs as image blocks.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which insert sources a media tool (image, video, audio, file) exposes in its
|
|
3
|
+
* empty state.
|
|
4
|
+
*
|
|
5
|
+
* - `'both'` (default): offer both file Upload and Link (URL/embed) tabs.
|
|
6
|
+
* - `'upload'`: file upload only — the Link tab and URL bar are hidden.
|
|
7
|
+
* - `'url'`: link/embed only — the Upload dropzone, file picker, and
|
|
8
|
+
* drag/paste-to-upload affordances are hidden.
|
|
9
|
+
*
|
|
10
|
+
* With a single source the tab switcher is omitted entirely.
|
|
11
|
+
*/
|
|
12
|
+
export type MediaSource = 'upload' | 'url' | 'both';
|
package/types/tools/video.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BlockToolData } from './block-tool-data';
|
|
2
2
|
import { MaxSizeConfig } from './max-size';
|
|
3
|
+
import { MediaSource } from './media-source';
|
|
3
4
|
|
|
4
5
|
/** Horizontal alignment of the video within its container. */
|
|
5
6
|
export type VideoAlignment = 'left' | 'center' | 'right';
|
|
@@ -68,6 +69,11 @@ export interface VideoConfig {
|
|
|
68
69
|
* type with `'*'` as the fallback. Default 100 MiB. See {@link MaxSizeConfig}.
|
|
69
70
|
*/
|
|
70
71
|
maxSize?: MaxSizeConfig;
|
|
72
|
+
/**
|
|
73
|
+
* Restrict how a video may be added. Default `'both'` (Upload + Link).
|
|
74
|
+
* Use `'upload'` for file-only or `'url'` for link-only. See {@link MediaSource}.
|
|
75
|
+
*/
|
|
76
|
+
sources?: MediaSource;
|
|
71
77
|
/** Caption placeholder. Default "Write a caption…" */
|
|
72
78
|
captionPlaceholder?: string;
|
|
73
79
|
/** Ambient glow intensity behind every player. Default 'minimal'. */
|
package/types/tools-entry.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export { ImageData, ImageConfig, ImageUploader, ImageAlignment, ImageSize, Image
|
|
|
61
61
|
export { FileData, FileConfig, FileUploader, FileUploadContext, FileUploadResult } from './tools/file';
|
|
62
62
|
export { AudioData, AudioConfig, AudioUploader, AudioUploadContext, AudioAlignment } from './tools/audio';
|
|
63
63
|
export { VideoData, VideoConfig, VideoUploader, VideoUploadContext, VideoAlignment, VideoGlow } from './tools/video';
|
|
64
|
+
export { MediaSource } from './tools/media-source';
|
|
64
65
|
export { ColumnListData } from './tools/column-list';
|
|
65
66
|
export { ColumnData } from './tools/column';
|
|
66
67
|
|