UNPKG

@blocknote/core

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

138 lines (128 loc) 4.88 kB
import { BlockNoteSchema } from "../blocks/BlockNoteSchema.js"; import { COLORS_DEFAULT } from "../editor/defaultColors.js"; import type { Dictionary } from "../i18n/dictionary.js"; import { en } from "../i18n/locales/index.js"; import { BlockFromConfig, BlockSchema, InlineContent, InlineContentSchema, StyleSchema, StyledText, Styles, } from "../schema/index.js"; import type { BlockMapping, InlineContentMapping, StyleMapping, } from "./mapping.js"; export type ExporterOptions = { /** * A function that can be used to resolve files, images, etc. * Exporters might need the binary contents of files like images, * which might not always be available from the same origin as the main page. * You can use this option to proxy requests through a server you control * to avoid cross-origin (CORS) issues. * * @default uses a BlockNote hosted proxy (https://corsproxy.api.blocknotejs.org/) * @param url - The URL of the file to resolve * @returns A Promise that resolves to a string (the URL to use instead of the original) * or a Blob (you can return the Blob directly if you have already fetched it) */ resolveFileUrl?: (url: string) => Promise<string | Blob>; /** * Colors to use for background of blocks, font colors, and highlight colors */ colors: typeof COLORS_DEFAULT; /** * The strings an exporter renders into the produced document (file link * texts, error placeholders). Accepts a locale from * `@blocknote/core/locales` or an editor dictionary; block packages that * ship their own exporter strings (e.g. math, diagram) read their sections * from this same object, exactly as they do from an editor dictionary. * * @default the English strings */ dictionary?: { exporter: Dictionary["exporter"] } & { // Block packages read their own sections (e.g. `math`, `diagram`) from // the same object; their types live with those packages. [blockDictionary: string]: unknown; }; }; export abstract class Exporter< B extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, RB, RI, RS, TS, > { public constructor( _schema: BlockNoteSchema<B, I, S>, // only used for type inference protected readonly mappings: { blockMapping: BlockMapping<B, I, S, RB, RI>; inlineContentMapping: InlineContentMapping<I, S, RI, TS>; styleMapping: StyleMapping<S, RS>; }, public readonly options: ExporterOptions, ) {} /** * The strings this exporter renders into the produced document - the * `exporter` section of the configured dictionary (the `dictionary` * option of {@link ExporterOptions}), or the English defaults. */ public get dictionary(): Dictionary["exporter"] { return this.options.dictionary?.exporter ?? en.exporter; } public async resolveFile(url: string) { if (!this.options?.resolveFileUrl) { return (await fetch(url)).blob(); } const ret = await this.options.resolveFileUrl(url); if (ret instanceof Blob) { return ret; } return (await fetch(ret)).blob(); } public mapStyles(styles: Styles<S>) { const stylesArray = Object.entries(styles).map(([key, value]) => { const mapping = this.mappings.styleMapping[key]; if (!mapping) { throw new Error( `Exporter is missing a style mapping for style "${key}". If this style comes from a separate package, spread that package's exporter mappings into your styleMapping.`, ); } const mappedStyle = mapping(value, this); return mappedStyle; }); return stylesArray; } public mapInlineContent(inlineContent: InlineContent<I, S>) { const mapping = this.mappings.inlineContentMapping[inlineContent.type]; if (!mapping) { throw new Error( `Exporter is missing an inline content mapping for inline content type "${inlineContent.type}". If this inline content comes from a separate package, spread that package's exporter mappings into your inlineContentMapping.`, ); } return mapping(inlineContent, this); } public transformInlineContent(inlineContentArray: InlineContent<I, S>[]) { return inlineContentArray.map((ic) => this.mapInlineContent(ic)); } public abstract transformStyledText(styledText: StyledText<S>): TS; public async mapBlock( block: BlockFromConfig<B[keyof B], I, S>, nestingLevel: number, numberedListIndex: number, children?: Array<Awaited<RB>>, ) { const mapping = this.mappings.blockMapping[block.type]; if (!mapping) { throw new Error( `Exporter is missing a block mapping for block type "${block.type}". If this block comes from a separate package, spread that package's exporter mappings into your blockMapping.`, ); } return mapping(block, this, nestingLevel, numberedListIndex, children); } }