feat(core): export doc with adapter extension (#9226)
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
|||||||
import { AIChatBlockSpec } from '@affine/core/blocksuite/presets/blocks/ai-chat-block';
|
import { AIChatBlockSpec } from '@affine/core/blocksuite/presets/blocks/ai-chat-block';
|
||||||
import type { ExtensionType } from '@blocksuite/affine/block-std';
|
import type { ExtensionType } from '@blocksuite/affine/block-std';
|
||||||
import {
|
import {
|
||||||
|
AdapterFactoryExtensions,
|
||||||
BookmarkBlockSpec,
|
BookmarkBlockSpec,
|
||||||
CodeBlockSpec,
|
CodeBlockSpec,
|
||||||
DatabaseBlockSpec,
|
DatabaseBlockSpec,
|
||||||
@@ -48,6 +49,7 @@ const CommonBlockSpecs: ExtensionType[] = [
|
|||||||
EmbedLinkedDocBlockSpec,
|
EmbedLinkedDocBlockSpec,
|
||||||
// special
|
// special
|
||||||
CustomAttachmentBlockSpec,
|
CustomAttachmentBlockSpec,
|
||||||
|
AdapterFactoryExtensions,
|
||||||
].flat();
|
].flat();
|
||||||
|
|
||||||
export const DefaultBlockSpecs: ExtensionType[] = [
|
export const DefaultBlockSpecs: ExtensionType[] = [
|
||||||
|
|||||||
@@ -6,15 +6,23 @@ import {
|
|||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
|
import type { BlockStdScope } from '@blocksuite/affine/block-std';
|
||||||
import {
|
import {
|
||||||
|
createAssetsArchive,
|
||||||
|
docLinkBaseURLMiddleware,
|
||||||
|
download,
|
||||||
|
embedSyncedDocMiddleware,
|
||||||
ExportManager,
|
ExportManager,
|
||||||
|
HtmlAdapterFactoryIdentifier,
|
||||||
HtmlTransformer,
|
HtmlTransformer,
|
||||||
|
MarkdownAdapterFactoryIdentifier,
|
||||||
MarkdownTransformer,
|
MarkdownTransformer,
|
||||||
printToPdf,
|
printToPdf,
|
||||||
|
titleMiddleware,
|
||||||
ZipTransformer,
|
ZipTransformer,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||||
import type { Doc } from '@blocksuite/affine/store';
|
import { type Doc, Job } from '@blocksuite/affine/store';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@@ -29,6 +37,90 @@ interface ExportHandlerOptions {
|
|||||||
type: ExportType;
|
type: ExportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AdapterResult {
|
||||||
|
file: string;
|
||||||
|
assetsIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdapterFactoryIdentifier =
|
||||||
|
| typeof HtmlAdapterFactoryIdentifier
|
||||||
|
| typeof MarkdownAdapterFactoryIdentifier;
|
||||||
|
|
||||||
|
interface AdapterConfig {
|
||||||
|
identifier: AdapterFactoryIdentifier;
|
||||||
|
fileExtension: string; // file extension need to be lower case with dot prefix, e.g. '.md', '.txt', '.html'
|
||||||
|
contentType: string;
|
||||||
|
indexFileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportDoc(doc: Doc, std: BlockStdScope, config: AdapterConfig) {
|
||||||
|
const job = new Job({
|
||||||
|
collection: doc.collection,
|
||||||
|
middlewares: [
|
||||||
|
docLinkBaseURLMiddleware,
|
||||||
|
titleMiddleware,
|
||||||
|
embedSyncedDocMiddleware('content'),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const adapterFactory = std.provider.get(config.identifier);
|
||||||
|
const adapter = adapterFactory.get(job);
|
||||||
|
const result = (await adapter.fromDoc(doc)) as AdapterResult;
|
||||||
|
|
||||||
|
if (!result || (!result.file && !result.assetsIds.length)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const docTitle = doc.meta?.title || 'Untitled';
|
||||||
|
const contentBlob = new Blob([result.file], { type: config.contentType });
|
||||||
|
|
||||||
|
let downloadBlob: Blob;
|
||||||
|
let name: string;
|
||||||
|
|
||||||
|
if (result.assetsIds.length > 0) {
|
||||||
|
if (!job.assets) {
|
||||||
|
throw new Error('No assets found');
|
||||||
|
}
|
||||||
|
const zip = await createAssetsArchive(job.assets, result.assetsIds);
|
||||||
|
await zip.file(config.indexFileName, contentBlob);
|
||||||
|
downloadBlob = await zip.generate();
|
||||||
|
name = `${docTitle}.zip`;
|
||||||
|
} else {
|
||||||
|
downloadBlob = contentBlob;
|
||||||
|
name = `${docTitle}${config.fileExtension}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
download(downloadBlob, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportToHtml(doc: Doc, std?: BlockStdScope) {
|
||||||
|
if (!std) {
|
||||||
|
// If std is not provided, we use the default export method
|
||||||
|
await HtmlTransformer.exportDoc(doc);
|
||||||
|
} else {
|
||||||
|
await exportDoc(doc, std, {
|
||||||
|
identifier: HtmlAdapterFactoryIdentifier,
|
||||||
|
fileExtension: '.html',
|
||||||
|
contentType: 'text/html',
|
||||||
|
indexFileName: 'index.html',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportToMarkdown(doc: Doc, std?: BlockStdScope) {
|
||||||
|
if (!std) {
|
||||||
|
// If std is not provided, we use the default export method
|
||||||
|
await MarkdownTransformer.exportDoc(doc);
|
||||||
|
} else {
|
||||||
|
await exportDoc(doc, std, {
|
||||||
|
identifier: MarkdownAdapterFactoryIdentifier,
|
||||||
|
fileExtension: '.md',
|
||||||
|
contentType: 'text/plain',
|
||||||
|
indexFileName: 'index.md',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function exportHandler({
|
async function exportHandler({
|
||||||
page,
|
page,
|
||||||
type,
|
type,
|
||||||
@@ -40,10 +132,10 @@ async function exportHandler({
|
|||||||
});
|
});
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'html':
|
case 'html':
|
||||||
await HtmlTransformer.exportDoc(page);
|
await exportToHtml(page, editorRoot?.std);
|
||||||
return;
|
return;
|
||||||
case 'markdown':
|
case 'markdown':
|
||||||
await MarkdownTransformer.exportDoc(page);
|
await exportToMarkdown(page, editorRoot?.std);
|
||||||
return;
|
return;
|
||||||
case 'snapshot':
|
case 'snapshot':
|
||||||
await ZipTransformer.exportDocs(page.collection, [page]);
|
await ZipTransformer.exportDocs(page.collection, [page]);
|
||||||
|
|||||||
Reference in New Issue
Block a user