From eb47fc51af7731387f6f03d1936d8a15460087fc Mon Sep 17 00:00:00 2001 From: gggpound Date: Thu, 21 Nov 2024 15:20:52 +0800 Subject: [PATCH] feat(doc): support paste img by right context --- .../doc-drawing-update.render-controller.ts | 20 +------ .../doc-clipboard.controller.ts | 19 ++++++- .../services/clipboard/clipboard.service.ts | 54 +++++++++++++------ .../clipboard/html-to-udm/converter.ts | 4 -- packages/ui/src/index.ts | 6 ++- .../clipboard/clipboard-interface.service.ts | 6 ++- 6 files changed, 67 insertions(+), 42 deletions(-) diff --git a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts index 5dbac50bf47..022532af5b7 100644 --- a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts +++ b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts @@ -255,25 +255,7 @@ export class DocDrawingUpdateRenderController extends Disposable implements IRen const res = this._getCurrentSceneAndTransformer(); if (res && res.transformer) { this.disposeWithMe(res.transformer.changeEnd$.pipe(debounceTime(30)).subscribe((params) => { - const activeTextRange = this._docSelectionManagerService.getActiveTextRange(); - if (activeTextRange) { - const { startOffset, endOffset } = activeTextRange; - if (startOffset + 1 !== endOffset) { - return; - } - const customBlocks = this._context.unit.getBody()?.customBlocks ?? []; - const block = customBlocks.find((b) => b.startIndex === startOffset); - if (block) { - this._setDrawingSelections([{ - drawingId: block.blockId, - drawingType: 5, - unitId: this._context.unit.getUnitId(), - subUnitId: this._context.unit.getUnitId(), - }]); - } - } - - // this._setDrawingSelections(params); + this._docSelectionManagerService.refreshSelection(); })); } else { throw new Error('transformer is not init'); diff --git a/packages/docs-ui/src/controllers/render-controllers/doc-clipboard.controller.ts b/packages/docs-ui/src/controllers/render-controllers/doc-clipboard.controller.ts index f2f3932784c..c6bdd4c14e9 100644 --- a/packages/docs-ui/src/controllers/render-controllers/doc-clipboard.controller.ts +++ b/packages/docs-ui/src/controllers/render-controllers/doc-clipboard.controller.ts @@ -23,7 +23,14 @@ import { Inject, RxDisposable, } from '@univerjs/core'; -import { FILES_CLIPBOARD_MIME_TYPE, HTML_CLIPBOARD_MIME_TYPE, PLAIN_TEXT_CLIPBOARD_MIME_TYPE } from '@univerjs/ui'; +import { + FILE__BMP_CLIPBOARD_MIME_TYPE, + FILE__JPEG_CLIPBOARD_MIME_TYPE, + FILE__WEBP_CLIPBOARD_MIME_TYPE, + FILE_PNG_CLIPBOARD_MIME_TYPE, + HTML_CLIPBOARD_MIME_TYPE, + PLAIN_TEXT_CLIPBOARD_MIME_TYPE, +} from '@univerjs/ui'; import { takeUntil } from 'rxjs'; import { whenDocOrEditor } from '../../commands/commands/clipboard.command'; import { IDocClipboardService } from '../../services/clipboard/clipboard.service'; @@ -59,8 +66,16 @@ export class DocClipboardController extends RxDisposable implements IRenderModul const clipboardEvent = config!.event as ClipboardEvent; let htmlContent = clipboardEvent.clipboardData?.getData(HTML_CLIPBOARD_MIME_TYPE); const textContent = clipboardEvent.clipboardData?.getData(PLAIN_TEXT_CLIPBOARD_MIME_TYPE); + + const imageTypes = [ + FILE__BMP_CLIPBOARD_MIME_TYPE, + FILE__JPEG_CLIPBOARD_MIME_TYPE, + FILE__WEBP_CLIPBOARD_MIME_TYPE, + FILE_PNG_CLIPBOARD_MIME_TYPE, + ]; + const files = [...(clipboardEvent.clipboardData?.items || [])] - .filter((item) => item.kind === FILES_CLIPBOARD_MIME_TYPE && item.type === 'image/png') + .filter((item) => imageTypes.includes(item.type)) .map((item) => item.getAsFile()!) .filter((e) => !!e); diff --git a/packages/docs-ui/src/services/clipboard/clipboard.service.ts b/packages/docs-ui/src/services/clipboard/clipboard.service.ts index bfa5cde7a88..db82b1103e9 100644 --- a/packages/docs-ui/src/services/clipboard/clipboard.service.ts +++ b/packages/docs-ui/src/services/clipboard/clipboard.service.ts @@ -21,7 +21,15 @@ import type { IRectRangeWithStyle, ITextRangeWithStyle } from '@univerjs/engine- import { BuildTextUtils, createIdentifier, DataStreamTreeTokenType, Disposable, DOC_RANGE_TYPE, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, getBodySlice, ICommandService, ILogService, Inject, IUniverInstanceService, normalizeBody, ObjectRelativeFromH, ObjectRelativeFromV, PositionedObjectLayoutType, SliceBodyType, toDisposable, Tools, UniverInstanceType } from '@univerjs/core'; import { DocSelectionManagerService } from '@univerjs/docs'; import { DrawingTypeEnum, ImageSourceType } from '@univerjs/drawing'; -import { HTML_CLIPBOARD_MIME_TYPE, IClipboardInterfaceService, PLAIN_TEXT_CLIPBOARD_MIME_TYPE } from '@univerjs/ui'; +import { + FILE__BMP_CLIPBOARD_MIME_TYPE, + FILE__JPEG_CLIPBOARD_MIME_TYPE, + FILE__WEBP_CLIPBOARD_MIME_TYPE, + FILE_PNG_CLIPBOARD_MIME_TYPE, + HTML_CLIPBOARD_MIME_TYPE, + IClipboardInterfaceService, + PLAIN_TEXT_CLIPBOARD_MIME_TYPE, +} from '@univerjs/ui'; import { CutContentCommand, InnerPasteCommand } from '../../commands/commands/clipboard.inner.command'; import { getCursorWhenDelete } from '../../commands/commands/doc-delete.command'; import { copyContentCache, extractId, genId } from './copy-content-cache'; @@ -41,6 +49,7 @@ export interface IDocClipboardHook { onCopyProperty?(start: number, end: number): IClipboardPropertyItem; onCopyContent?(start: number, end: number): string; onBeforePaste?: (body: IDocumentBody) => IDocumentBody; + onBeforePasteImage?: (file: File) => Promise<{ source: string; imageSourceType: ImageSourceType }>; } export interface IDocClipboardService { @@ -138,9 +147,7 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ const currentDocInstance = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_DOC); const docUnitId = currentDocInstance?.getUnitId() || ''; if (!html && !text && files.length) { - // 粘贴图片 - const imageHtml = await this._createImagePasteHtml(files); - html = imageHtml; + html = await this._createImagePasteHtml(files); } const partDocData = this._genDocDataFromHtmlAndText(html, text, docUnitId); // Paste in sheet editing mode without paste style, so we give textRuns empty array; @@ -411,10 +418,9 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ private async _genDocDataFromClipboardItems(items: ClipboardItem[]): Promise> { try { - // TODO: support paste image. - let html = ''; let text = ''; + const files: File[] = []; for (const clipboardItem of items) { for (const type of clipboardItem.types) { switch (type) { @@ -426,9 +432,21 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ html = await clipboardItem.getType(type).then((blob) => blob && blob.text()); break; } + case FILE__BMP_CLIPBOARD_MIME_TYPE: + case FILE__JPEG_CLIPBOARD_MIME_TYPE: + case FILE__WEBP_CLIPBOARD_MIME_TYPE: + case FILE_PNG_CLIPBOARD_MIME_TYPE: { + const blob = await clipboardItem.getType(type); + const file = new File([blob], `pasted_image.${type.split('/')[1]}`, { type }); + files.push(file); + break; + } } } } + if (!html && !text && files.length) { + html = await this._createImagePasteHtml(files); + } return this._genDocDataFromHtmlAndText(html, text); } catch (e) { @@ -479,20 +497,23 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ }, drawings: {}, }; - const fileToBase64 = async (file: File): Promise => { + const fileToBase64 = async (file: File): Promise<{ source: string; imageSourceType: ImageSourceType }> => { const reader = new FileReader(); return new Promise((res) => { reader.onloadend = function () { - res(reader.result as string); + res({ + source: reader.result as string, + imageSourceType: ImageSourceType.BASE64, + }); }; reader.readAsDataURL(file); }); }; - const getImageSize = (base64: string): Promise<{ width: number; height: number }> => { + const getImageSize = (base64: string | File): Promise<{ width: number; height: number }> => { const img = new Image(); const maxWidth = 500; return new Promise((resolve) => { - img.src = base64; + img.src = typeof base64 === 'string' ? base64 : URL.createObjectURL(base64); img.onload = () => { const width = Math.min(maxWidth, img.naturalWidth); const scale = img.naturalHeight / img.naturalWidth; @@ -500,9 +521,13 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ }; }); }; + // clipboardHooks 应该被重新设计,用来处理多个 hook 处理同一个节点的能力 + // 参考 interceptor + const onBeforePasteImage = (this._clipboardHooks.find((e) => e.onBeforePasteImage)?.onBeforePasteImage!) ?? fileToBase64; + await Promise.all(files.map(async (file, index) => { - const base64 = await fileToBase64(file); - const { width = 100, height = 100 } = await getImageSize(base64); + const image = await onBeforePasteImage(file); + const { width = 100, height = 100 } = await getImageSize(file); const itemId = `paste_image_id_${index}`; const body = doc.body!; const drawings = doc.drawings!; @@ -512,9 +537,9 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ drawingId: itemId, unitId: '', subUnitId: '', - imageSourceType: ImageSourceType.BASE64, + imageSourceType: image.imageSourceType, title: '', - source: base64, + source: image.source, description: '', layoutType: PositionedObjectLayoutType.INLINE, drawingType: DrawingTypeEnum.DRAWING_IMAGE, @@ -522,7 +547,6 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ width, height, angle: 0, - }, docTransform: { angle: 0, diff --git a/packages/docs-ui/src/services/clipboard/html-to-udm/converter.ts b/packages/docs-ui/src/services/clipboard/html-to-udm/converter.ts index 4b20ec9cc19..eb8b35ba7d3 100644 --- a/packages/docs-ui/src/services/clipboard/html-to-udm/converter.ts +++ b/packages/docs-ui/src/services/clipboard/html-to-udm/converter.ts @@ -140,10 +140,6 @@ export class HtmlToUDMService { const docTransformHeight = Number(element.dataset.docTransformHeight || height); // 外部会进行替换. const id = Tools.generateRandomId(6); - body.textRuns!.push({ - st: body.dataStream.length, - ed: body.dataStream.length, - }); doc.body?.customBlocks?.push({ startIndex: body.dataStream.length, blockId: id }); body.dataStream += '\b'; if (!doc.drawings) { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 66e304a628a..e5a3fd8a70c 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -45,7 +45,11 @@ export { DesktopBeforeCloseService, IBeforeCloseService } from './services/befor export { CopyCommand, CutCommand, PasteCommand } from './services/clipboard/clipboard.command'; export { BrowserClipboardService, - FILES_CLIPBOARD_MIME_TYPE, + FILE__BMP_CLIPBOARD_MIME_TYPE, + FILE__JPEG_CLIPBOARD_MIME_TYPE, + FILE__WEBP_CLIPBOARD_MIME_TYPE, + FILE_PNG_CLIPBOARD_MIME_TYPE, + FILE_SVG_XML_CLIPBOARD_MIME_TYPE, HTML_CLIPBOARD_MIME_TYPE, IClipboardInterfaceService, PLAIN_TEXT_CLIPBOARD_MIME_TYPE, diff --git a/packages/ui/src/services/clipboard/clipboard-interface.service.ts b/packages/ui/src/services/clipboard/clipboard-interface.service.ts index c7065adb7a1..3716c09ed83 100644 --- a/packages/ui/src/services/clipboard/clipboard-interface.service.ts +++ b/packages/ui/src/services/clipboard/clipboard-interface.service.ts @@ -21,7 +21,11 @@ import { supportClipboardAPI } from './clipboard-utils'; export const PLAIN_TEXT_CLIPBOARD_MIME_TYPE = 'text/plain'; export const HTML_CLIPBOARD_MIME_TYPE = 'text/html'; -export const FILES_CLIPBOARD_MIME_TYPE = 'file'; +export const FILE_PNG_CLIPBOARD_MIME_TYPE = 'image/png'; +export const FILE__JPEG_CLIPBOARD_MIME_TYPE = 'image/jpeg'; +export const FILE__BMP_CLIPBOARD_MIME_TYPE = 'image/bmp'; +export const FILE__WEBP_CLIPBOARD_MIME_TYPE = 'image/webp'; +export const FILE_SVG_XML_CLIPBOARD_MIME_TYPE = 'image/svg+xml'; /** * This interface provides an interface to access system's clipboard.