Skip to content

Commit

Permalink
feat: paste content from word
Browse files Browse the repository at this point in the history
  • Loading branch information
Jocs committed Nov 13, 2023
1 parent 6bf4d2c commit dd382d6
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 59 deletions.
4 changes: 1 addition & 3 deletions packages/base-docs/src/basics/memory-cursor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class MemoryCursor {
export class MemoryCursor {
cursor: number = 0;

reset() {
Expand All @@ -10,5 +10,3 @@ class MemoryCursor {
this.cursor += pos;
}
}

export default MemoryCursor;
Original file line number Diff line number Diff line change
Expand Up @@ -301,15 +301,16 @@ export const CoverCommand: ICommand<ICoverCommandParams> = {
},
};

function getRetainAndDeleteFromReplace(
export function getRetainAndDeleteFromReplace(
range: ITextRange,
segmentId?: string
segmentId: string = '',
memoryCursor: number = 0
): Array<IRetainMutationParams | IDeleteMutationParams> {
const { startOffset, endOffset, collapsed } = range;
const dos: Array<IRetainMutationParams | IDeleteMutationParams> = [];

const textStart = startOffset + (collapsed ? -1 : 0);
const textEnd = endOffset - 1;
const textStart = startOffset + (collapsed ? -1 : 0) - memoryCursor;
const textEnd = endOffset - 1 - memoryCursor;

if (textStart > 0) {
dos.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
IUniverInstanceService,
} from '@univerjs/core';

import MemoryCursor from '../../basics/memory-cursor';
import { MemoryCursor } from '../../basics/memory-cursor';
import { TextSelectionManagerService } from '../../services/text-selection-manager.service';
import { IRichTextEditingMutationParams, RichTextEditingMutation } from '../mutations/core-editing.mutation';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UpdateDocsAttributeType,
} from '@univerjs/core';

import MemoryCursor from '../../basics/memory-cursor';
import { MemoryCursor } from '../../basics/memory-cursor';
import { DeleteApply } from './functions/delete-apply';
import { InsertApply } from './functions/insert-apply';
import { UpdateAttributeApply } from './functions/update-apply';
Expand Down Expand Up @@ -71,6 +71,8 @@ export const RichTextEditingMutation: IMutation<IRichTextEditingMutationParams,

memoryCursor.reset();

console.log(JSON.stringify(mutations, null, 2));

mutations.forEach((mutation) => {
// FIXME: @jocs Since UpdateAttributeApply modifies the mutation(used in undo/redo),
// so make a deep copy here, does UpdateAttributeApply need to
Expand Down
2 changes: 2 additions & 0 deletions packages/base-docs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export * from './basics/component-tools';
export * from './basics/docs-view-key';
export { MemoryCursor } from './basics/memory-cursor';
export {
CoverCommand,
DeleteCommand,
getRetainAndDeleteFromReplace,
type ICoverCommandParams,
type IDeleteCommandParams,
type IIMEInputCommandParams,
Expand Down
123 changes: 94 additions & 29 deletions packages/ui-plugin-docs/src/commands/commands/clipboard.command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
import { CopyCommand, CutCommand, IClipboardInterfaceService, PasteCommand } from '@univerjs/base-ui';
import { CommandType, FOCUSING_DOC, ILogService, IMultiCommand } from '@univerjs/core';
import {
getRetainAndDeleteFromReplace,
IRichTextEditingMutationParams,
MemoryCursor,
RichTextEditingMutation,
TextSelectionManagerService,
} from '@univerjs/base-docs';
import { CopyCommand, CutCommand, PasteCommand } from '@univerjs/base-ui';
import {
CommandType,
FOCUSING_DOC,
ICommand,
ICommandInfo,
ICommandService,
IDocumentBody,
IMultiCommand,
IUndoRedoService,
IUniverInstanceService,
} from '@univerjs/core';

import { IDocClipboardService } from '../../services/clipboard/clipboard.service';
interface IInnerPasteCommandParams {
segmentId: string;
body: IDocumentBody;
}

export const DocCopyCommand: IMultiCommand = {
id: CopyCommand.id,
Expand All @@ -10,10 +30,7 @@ export const DocCopyCommand: IMultiCommand = {
multi: true,
priority: 1100,
preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC),
handler: async (accessor, params) => {
const docClipboardService = accessor.get(IDocClipboardService);
return docClipboardService.copy();
},
handler: async () => true,
};

export const DocCutCommand: IMultiCommand = {
Expand All @@ -23,10 +40,7 @@ export const DocCutCommand: IMultiCommand = {
multi: true,
priority: 1100,
preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC),
handler: async (accessor, params) => {
const docClipboardService = accessor.get(IDocClipboardService);
return docClipboardService.cut();
},
handler: async () => true,
};

export const DocPasteCommand: IMultiCommand = {
Expand All @@ -36,31 +50,82 @@ export const DocPasteCommand: IMultiCommand = {
multi: true,
priority: 1100,
preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC),
handler: async (accessor, params) => {
const logService = accessor.get(ILogService);
handler: async () => true,
};

export const InnerPasteCommand: ICommand<IInnerPasteCommandParams> = {
id: 'doc.command.inner-paste',
type: CommandType.COMMAND,
handler: async (accessor, params: IInnerPasteCommandParams) => {
const { segmentId, body } = params;
const undoRedoService = accessor.get(IUndoRedoService);
const commandService = accessor.get(ICommandService);
const textSelectionManagerService = accessor.get(TextSelectionManagerService);
const currentUniverService = accessor.get(IUniverInstanceService);

const selections = textSelectionManagerService.getSelections();

if (!Array.isArray(selections) || selections.length === 0) {
return false;
}

// use cell editor to get ClipboardData first
// if that doesn't work, use the browser's clipboard API
// this clipboard API would ask user for permission, so we may need to notify user (and retry perhaps)
logService.log('[DocPasteCommand]', 'the focusing element is', document.activeElement);
const docsModel = currentUniverService.getCurrentUniverDocInstance();
const unitId = docsModel.getUnitId();

const result = document.execCommand('paste');
const doMutation: ICommandInfo<IRichTextEditingMutationParams> = {
id: RichTextEditingMutation.id,
params: {
unitId,
mutations: [],
},
};

if (!result) {
logService.log(
'[DocPasteCommand]',
'failed to execute paste command on the activeElement, trying to use clipboard API.'
);
const memoryCursor = new MemoryCursor();

const clipboardInterfaceService = accessor.get(IClipboardInterfaceService);
const clipboardItems = await clipboardInterfaceService.read();
const sheetClipboardService = accessor.get(IDocClipboardService);
memoryCursor.reset();

if (clipboardItems.length !== 0) {
return sheetClipboardService.paste(clipboardItems[0]);
for (const selection of selections) {
const { startOffset, endOffset, collapsed } = selection;

const len = startOffset - memoryCursor.cursor;

if (collapsed) {
doMutation.params!.mutations.push({
t: 'r',
len,
segmentId,
});
} else {
doMutation.params!.mutations.push(
...getRetainAndDeleteFromReplace(selection, segmentId, memoryCursor.cursor)
);
}

return false;
doMutation.params!.mutations.push({
t: 'i',
body,
len: body.dataStream.length,
line: 0,
segmentId,
});

memoryCursor.reset();
memoryCursor.moveCursor(endOffset);
}

const result = commandService.syncExecuteCommand<
IRichTextEditingMutationParams,
IRichTextEditingMutationParams
>(doMutation.id, doMutation.params);

if (result) {
undoRedoService.pushUndoRedo({
unitID: unitId,
undoMutations: [{ id: RichTextEditingMutation.id, params: result }],
redoMutations: [{ id: RichTextEditingMutation.id, params: doMutation.params }],
});

return true;
}

return false;
Expand Down
49 changes: 46 additions & 3 deletions packages/ui-plugin-docs/src/controllers/clipboard.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,63 @@
import { Disposable, ICommandService, IUniverInstanceService } from '@univerjs/core';
import { ITextSelectionRenderManager } from '@univerjs/base-render';
import { Disposable, ICommandInfo, ICommandService, ILogService, IUniverInstanceService } from '@univerjs/core';

import { DocCopyCommand, DocCutCommand, DocPasteCommand } from '../commands/commands/clipboard.command';
import {
DocCopyCommand,
DocCutCommand,
DocPasteCommand,
InnerPasteCommand,
} from '../commands/commands/clipboard.command';
import { IDocClipboardService } from '../services/clipboard/clipboard.service';

export class DocClipboardController extends Disposable {
constructor(
@ILogService private readonly _logService: ILogService,
@ICommandService private readonly _commandService: ICommandService,
@IUniverInstanceService private readonly _currentUniverService: IUniverInstanceService,
@IDocClipboardService private readonly _docClipboardService: IDocClipboardService
@IDocClipboardService private readonly _docClipboardService: IDocClipboardService,
@ITextSelectionRenderManager private _textSelectionRenderManager: ITextSelectionRenderManager
) {
super();
this.commandExecutedListener();
}

initialize() {
[DocCopyCommand, DocCutCommand, DocPasteCommand].forEach((command) =>
this.disposeWithMe(this._commandService.registerAsMultipleCommand(command))
);
[InnerPasteCommand].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command)));
}

private commandExecutedListener() {
const updateCommandList = [DocPasteCommand.id];

this.disposeWithMe(
this._commandService.onCommandExecuted((command: ICommandInfo) => {
if (!updateCommandList.includes(command.id)) {
return;
}

this.handlePaste();
})
);
}

private async handlePaste() {
const { _docClipboardService: docClipboardService } = this;
const { segmentId } = this._textSelectionRenderManager.getActiveRange() ?? {};

if (!segmentId) {
this._logService.error('[DocClipboardController] segmentId is not existed');
}

try {
const body = await docClipboardService.queryClipboardData();

this._commandService.executeCommand(InnerPasteCommand.id, { body, segmentId });

// TODO: @jocs, reset selections.
} catch (_e) {
this._logService.error('[DocClipboardController] clipboard is empty');
}
}
}
38 changes: 20 additions & 18 deletions packages/ui-plugin-docs/src/services/clipboard/clipboard.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
IClipboardInterfaceService,
PLAIN_TEXT_CLIPBOARD_MIME_TYPE,
} from '@univerjs/base-ui';
import { Disposable, IUniverInstanceService, toDisposable } from '@univerjs/core';
import { Disposable, IDocumentBody, IUniverInstanceService, toDisposable } from '@univerjs/core';
import { createIdentifier, IDisposable } from '@wendellhu/redi';

import HtmlToUDMService from './html-to-udm/converter';

export interface IClipboardPropertyItem {}

export interface IDocClipboardHook {
Expand All @@ -14,9 +16,7 @@ export interface IDocClipboardHook {
}

export interface IDocClipboardService {
copy(): Promise<boolean>;
cut(): Promise<boolean>;
paste(item: ClipboardItem): Promise<boolean>;
queryClipboardData(): Promise<IDocumentBody>;

addClipboardHook(hook: IDocClipboardHook): IDisposable;
}
Expand All @@ -25,6 +25,7 @@ export const IDocClipboardService = createIdentifier<IDocClipboardService>('doc.

export class DocClipboardService extends Disposable implements IDocClipboardService {
private _clipboardHooks: IDocClipboardHook[] = [];
private htmlToUDM = new HtmlToUDMService();

constructor(
@IUniverInstanceService private readonly _currentUniverService: IUniverInstanceService,
Expand All @@ -33,23 +34,24 @@ export class DocClipboardService extends Disposable implements IDocClipboardServ
super();
}

copy(): Promise<boolean> {
throw new Error('Method not implemented.');
}

cut(): Promise<boolean> {
throw new Error('Method not implemented.');
}
async queryClipboardData(): Promise<IDocumentBody> {
const clipboardItems = await this._clipboardInterfaceService.read();

async paste(item: ClipboardItem): Promise<boolean> {
const text = await item.getType(PLAIN_TEXT_CLIPBOARD_MIME_TYPE).then((blob) => blob && blob.text());
const html = await item.getType(HTML_CLIPBOARD_MIME_TYPE).then((blob) => blob && blob.text());
if (clipboardItems.length === 0) {
return Promise.reject();
}
const clipboardItem = clipboardItems[0];
const text = await clipboardItem.getType(PLAIN_TEXT_CLIPBOARD_MIME_TYPE).then((blob) => blob && blob.text());
const html = await clipboardItem.getType(HTML_CLIPBOARD_MIME_TYPE).then((blob) => blob && blob.text());

console.log(text);
console.log(html);
if (!html) {
// TODO: @JOCS, Parsing paragraphs and sections
return {
dataStream: text,
};
}

// this._logService.error('[SheetClipboardService]', 'No valid data on clipboard');
throw new Error('Method not implemented.');
return this.htmlToUDM.convert(html);
}

addClipboardHook(hook: IDocClipboardHook): IDisposable {
Expand Down
Loading

0 comments on commit dd382d6

Please sign in to comment.