@nextgensoftwares/folio-plugin-media
Images, video, audio and file attachments: uploads, layout, painters. Guide: Media plugin.
Plugin
| Export | Signature | Description |
|---|---|---|
mediaPlugin | (options: MediaPluginOptions) => MediaPlugin | throws TypeError if uploader isn't a function |
type MediaPlugin | FolioPlugin with name: 'media', typed commands: MediaCommands, painters, input: MediaInput, uploads: UploadController, mediaRules: MediaRules, resolveSrc |
ts
interface MediaPluginOptions {
uploader: MediaUploader;
rules?: Partial<Record<MediaKind, Partial<KindRule>>>;
resolveSrc?: SrcResolver; // default: uploader.resolve, else identity
probe?: boolean; // read size/duration/poster/PDF pages in the browser (default true)
onError?: (message: string, file?: File) => void;
prompt?: (label: string, value: string) => string | null | Promise<string | null>; // default window.prompt
sizePresets?: SizePreset[]; // default DEFAULT_SIZE_PRESETS
lostGraceMs?: number; // default 4000
}Types
ts
type MediaKind = 'image' | 'video' | 'audio' | 'file';
interface UploadOptions { onProgress(fraction: number): void; signal: AbortSignal }
interface UploadResult { src: string; mediaId?: string; width?: number; height?: number; duration?: number; poster?: string }
type SrcResolver = (src: string) => string | Promise<string>;
type MediaUploader = ((file: File, opts: UploadOptions) => Promise<UploadResult>) & { resolve?: SrcResolver };
interface KindRule { mimeTypes: string[]; extensions: string[]; maxBytes: number }
type MediaRules = Record<MediaKind, KindRule>;
interface SizePreset { id: string; label: string; width: string | 'natural' }| Export | Description |
|---|---|
MEDIA_KINDS | ['image', 'video', 'audio', 'file'] |
DEFAULT_SIZE_PRESETS | Small 25%, Medium 50%, Large 75%, Full width, Original size |
MediaKind, UploadOptions, UploadResult, SrcResolver, MediaUploader, KindRule, MediaRules, SizePreset, MediaPluginOptions | types above |
Validation
| Export | Signature | Description |
|---|---|---|
DEFAULT_RULES | MediaRules | per-kind MIME types, extensions and size caps (no SVG) |
resolveRules | (overrides?) => MediaRules | defaults with per-kind overrides; garbage ignored |
validateFile | (file: FileLike | null | undefined, rules, expected?) => ValidationResult | { ok: true, kind, mime } or { ok: false, code: 'empty' | 'type' | 'kind' | 'size' | 'invalid', message } |
detectKind | (file: FileLike, rules) => MediaKind | null | by MIME type, else extension |
acceptFor | (rules, kind?) => string | accept attribute for a file input |
isSafeSrc | (src: unknown) => src is string | only schemes that can't run script |
formatBytes | (bytes) => string | "1.2 MB" |
type FileLike | { name, type, size } |
Uploaders
| Export | Description |
|---|---|
objectUrlUploader({ delayMs? }) | blob: URLs (die on reload); simulated progress |
indexedDbUploader(opts?), IndexedDbUploader, IndexedDbUploaderOptions | blobs in IndexedDB, src = "idb:<key>"; resolve, remove(src), blob(src); options dbName, storeName, indexedDB, createObjectURL |
IDB_SCHEME | 'idb:' |
serverUploader(opts), ServerUploaderOptions | multipart POST via XHR with progress; url, field, headers (object or function), fields, withCredentials, parse |
Schema and layout
| Export | Description |
|---|---|
MEDIA_NODE, FILE_NODE | 'resizableMedia', 'fileAttachment' |
mediaSchema | Partial<SchemaSpec>: the fileAttachment node + global attrs on resizableMedia |
mediaGlobalAttrs | naturalWidth, naturalHeight, duration, poster, name, mime, uploadId |
fileAttachmentSpec | src, mediaId, name, size, mime, pages, uploadId |
mediaSizer | MediaSizer reading naturalWidth / naturalHeight |
fileAttachmentRenderer, mediaRenderers, FILE_CARD | file card layout (one custom fragment; { height: 56, maxWidth: 400, minWidth: 120 }) |
cardFlow(attrs, ctx, { height, maxWidth, minWidth? }) | a card's flow: width from width (px/%), placement from the attrs (start edge by default); for plugins drawing bigger cards |
placementAttrs, cardAttrs | placement attr specs (wrap, float, distT/B/L/R, offsetX/Y, anchor); cardAttrs adds width and alignment (on fileAttachment) |
Placement (size, wrap, alignment)
| Export | Description |
|---|---|
createPlacementCommands(), PlacementCommands | media.setPlacement(patch), media.setWrap(wrap, side?), media.setSize({ width, height? }) (merged into plugin.commands) |
PlacementPatch, SizeInput | { wrap?, side?, alignment?, distT?..distR?, offsetX?, offsetY?, anchor?, borderRadius?, objectFit? }; { width: number | string, height?: number | null } |
placementAttrsOf(patch) | validated node attrs for a patch (unknown/malformed fields dropped, numbers clamped) |
createPlacementItems(commands, presets) | the surface-neutral UI items media.size, media.layout, media.distance, media.style (React render controls + menu children) |
PLACEMENT_GROUP, MEDIA_SURFACES | 'media.placement' (route it to move all four); ['bubble', 'contextMenu'] (their default surfaces) |
LAYOUT_OPTIONS, layoutOptionOf(attrs) | the six layout options (in line, square left/right, top & bottom, behind, in front) and which one attrs select |
CARD_PRESETS, Preset | file card width presets (Compact 240px, Standard 400px, Wide 100%) |
PLACEMENT_ICONS | layout-option icons as SVG path strings |
Print fallbacks
ts
interface PrintFallback { image?: string; title: string; detail: string; link?: string }| Export | Description |
|---|---|
mediaPrintFallback(attrs) | video/audio → poster + title + "Video · 3:25"; images → null (drawn as-is); unfinished uploads → a note |
filePrintFallback(attrs) | file card → name, "PDF · 1.2 MB · 12 pages", link |
formatDuration(seconds), fileDetail(attrs), fileTypeLabel(attrs) | helpers |
Probing
| Export | Description |
|---|---|
probeFile(file, kind) => Promise<ProbeResult> | intrinsic size, duration, JPEG poster frame (video), PDF page count; best-effort, never rejects |
ProbeResult | { width?, height?, duration?, posterBlob?, pages? } |
pdfPageCount(blob), pdfPageCountFromText(text) | PDF page count (files over 15 MB aren't scanned) |
Commands and input
| Export | Description |
|---|---|
createMediaCommands(ctrl), MediaCommands | media.insertFiles, media.insertUrl, media.setAlt, media.setTitle, media.setAlignment, media.setWidth, media.resize, media.delete, media.cancelUpload, media.retryUpload |
selectedMedia(state), SelectedMedia | { node, pos, kind } when a media/file node is selected |
normalizeWidth(width, natural?) | CSS width the layout understands, or null |
createMediaInput(ctrl, accept), MediaInput | paste(editor, data), drop(editor, data, at?), pick(editor, kind?), install(editor, el, locate?) |
pickFiles(accept, multiple?), filesFrom(data), DropPoint | file picker; files in a paste/drop |
Upload state
| Export | Description |
|---|---|
UploadController | validate → placeholder → upload (3 at a time) → final attrs / error / abort; insert(host, files, opts?), cancel(id), retry(id), store, pmPlugin |
ControllerOptions, InsertOptions, InsertReport | { uploader, rules, probe?, onError?, lostGraceMs?, concurrency? }; { pos?, expected?, replace? }; { ids, rejected } |
UploadStore, UploadEntry, UploadStatus | transient progress outside the document, subscribable per upload id |
trackerKey, EditorHost | ProseMirror plugin key of the placeholder tracker; { getState(), dispatch(tr) } |
Painters (React)
| Export | Description |
|---|---|
createPainters({ ctrl, resolveSrc }) | media:image, media:video, media:audio, fileAttachment |
FragmentPainter, PainterProps | the @nextgensoftwares/folio-react painter contract |
useResolvedSrc(src, resolve), useSelected(editor, path), useUpload(store, id) | hooks for custom painters |
ResizeHandles | selection overlay with resize handles ({ fragment, page, editor }), for painters that replace the media painter (e.g. @nextgensoftwares/folio-plugin-player) |