feat(editor): import docs from docx (#11774)

Support importing .docx files, as mentioned in
https://github.com/toeverything/AFFiNE/issues/10154#issuecomment-2655744757

It essentially uses mammoth to convert the docx to html, and then
imports the html with the standard steps.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Import Microsoft Word (.docx) files directly via the import dialog
(creates new documents).
* .docx added as a selectable file type in the file picker and import
options.

* **Localization**
* Added localized labels and tooltips for DOCX import in English,
Simplified Chinese, and Traditional Chinese.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
Co-authored-by: DarkSky <darksky2048@gmail.com>
This commit is contained in:
Xun Sun
2025-11-15 15:51:23 +08:00
committed by GitHub
parent e5db0e66c1
commit 17ec76540b
14 changed files with 195 additions and 15 deletions

View File

@@ -22,6 +22,7 @@ import track from '@affine/track';
import { openFilesWith } from '@blocksuite/affine/shared/utils';
import type { Workspace } from '@blocksuite/affine/store';
import {
DocxTransformer,
HtmlTransformer,
MarkdownTransformer,
NotionHtmlTransformer,
@@ -30,6 +31,7 @@ import {
import {
ExportToHtmlIcon,
ExportToMarkdownIcon,
FileIcon,
HelpIcon,
NotionIcon,
PageIcon,
@@ -186,8 +188,9 @@ type ImportType =
| 'notion'
| 'snapshot'
| 'html'
| 'docx'
| 'dotaffinefile';
type AcceptType = 'Markdown' | 'Zip' | 'Html' | 'Skip'; // Skip is used for dotaffinefile
type AcceptType = 'Markdown' | 'Zip' | 'Html' | 'Docx' | 'Skip'; // Skip is used for dotaffinefile
type Status = 'idle' | 'importing' | 'success' | 'error';
type ImportResult = {
docIds: string[];
@@ -262,6 +265,17 @@ const importOptions = [
testId: 'editor-option-menu-import-notion',
type: 'notion' as ImportType,
},
{
key: 'docx',
label: 'com.affine.import.docx',
prefixIcon: <FileIcon color={cssVar('black')} width={20} height={20} />,
suffixIcon: (
<HelpIcon color={cssVarV2('icon/primary')} width={20} height={20} />
),
suffixTooltip: 'com.affine.import.docx.tooltip',
testId: 'editor-option-menu-import-docx',
type: 'docx' as ImportType,
},
{
key: 'snapshot',
label: 'com.affine.import.snapshot',
@@ -432,6 +446,23 @@ const importConfigs: Record<ImportType, ImportConfig> = {
};
},
},
docx: {
fileOptions: { acceptType: 'Docx', multiple: false },
importFunction: async (docCollection, file) => {
const files = Array.isArray(file) ? file : [file];
const docIds: string[] = [];
for (const file of files) {
const docId = await DocxTransformer.importDocx({
collection: docCollection,
schema: getAFFiNEWorkspaceSchema(),
imported: file,
extensions: getStoreManager().config.init().value.get('store'),
});
if (docId) docIds.push(docId);
}
return { docIds };
},
},
snapshot: {
fileOptions: { acceptType: 'Zip', multiple: false },
importFunction: async (