fix(editor): support relative image reference path when importing zip with images (#12264)

Closes: [BS-3385](https://linear.app/affine-design/issue/BS-3385/markdown类型的导入,支持media文件和md文件不在同目录的情况)

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

- **New Features**
  - Added utility functions to resolve and normalize image file paths in markdown and HTML imports.
  - Introduced middleware to provide full file path context during file import and transformation.
  - Added new types for improved asset and file management in zip imports.

- **Refactor**
  - Centralized and simplified image processing logic across HTML, Markdown, and Notion HTML adapters for improved maintainability.
  - Enhanced type safety and clarity in file and asset handling during zip imports.

- **Tests**
  - Added comprehensive tests for image file path resolution utility.

- **Documentation**
  - Improved inline code comments explaining file path resolution logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
donteatfriedrice
2025-05-14 02:30:30 +00:00
parent 2f8d8dbc1e
commit 26ece014f1
13 changed files with 320 additions and 215 deletions
@@ -2,6 +2,7 @@ import {
defaultImageProxyMiddleware,
docLinkBaseURLMiddleware,
fileNameMiddleware,
filePathMiddleware,
HtmlAdapter,
titleMiddleware,
} from '@blocksuite/affine-shared/adapters';
@@ -15,6 +16,7 @@ import type {
} from '@blocksuite/store';
import { extMimeMap, Transformer } from '@blocksuite/store';
import type { AssetMap, ImportedFileEntry, PathBlobIdMap } from './type.js';
import { createAssetsArchive, download, Unzip } from './utils.js';
type ImportHTMLToDocOptions = {
@@ -143,9 +145,9 @@ async function importHTMLZip({
await unzip.load(imported);
const docIds: string[] = [];
const pendingAssets = new Map<string, File>();
const pendingPathBlobIdMap = new Map<string, string>();
const htmlBlobs: [string, Blob][] = [];
const pendingAssets: AssetMap = new Map();
const pendingPathBlobIdMap: PathBlobIdMap = new Map();
const htmlBlobs: ImportedFileEntry[] = [];
for (const { path, content: blob } of unzip) {
if (path.includes('__MACOSX') || path.includes('.DS_Store')) {
@@ -154,7 +156,11 @@ async function importHTMLZip({
const fileName = path.split('/').pop() ?? '';
if (fileName.endsWith('.html')) {
htmlBlobs.push([fileName, blob]);
htmlBlobs.push({
filename: fileName,
contentBlob: blob,
fullPath: path,
});
} else {
const ext = path.split('.').at(-1) ?? '';
const mime = extMimeMap.get(ext) ?? '';
@@ -165,8 +171,9 @@ async function importHTMLZip({
}
await Promise.all(
htmlBlobs.map(async ([fileName, blob]) => {
const fileNameWithoutExt = fileName.replace(/\.[^/.]+$/, '');
htmlBlobs.map(async htmlFile => {
const { filename, contentBlob, fullPath } = htmlFile;
const fileNameWithoutExt = filename.replace(/\.[^/.]+$/, '');
const job = new Transformer({
schema,
blobCRUD: collection.blobSync,
@@ -179,18 +186,19 @@ async function importHTMLZip({
defaultImageProxyMiddleware,
fileNameMiddleware(fileNameWithoutExt),
docLinkBaseURLMiddleware(collection.id),
filePathMiddleware(fullPath),
],
});
const assets = job.assets;
const pathBlobIdMap = job.assetsManager.getPathBlobIdMap();
for (const [key, value] of pendingAssets.entries()) {
assets.set(key, value);
}
for (const [key, value] of pendingPathBlobIdMap.entries()) {
pathBlobIdMap.set(key, value);
for (const [assetPath, key] of pendingPathBlobIdMap.entries()) {
pathBlobIdMap.set(assetPath, key);
if (pendingAssets.get(key)) {
assets.set(key, pendingAssets.get(key)!);
}
}
const htmlAdapter = new HtmlAdapter(job, provider);
const html = await blob.text();
const html = await contentBlob.text();
const doc = await htmlAdapter.toDoc({
file: html,
assets: job.assetsManager,