From 610bcb898a3e46435ad8ada11351463f3d80e09e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sat, 15 Jul 2023 09:26:27 +0530 Subject: [PATCH] d --- src/env/types.ts | 2 +- src/shapes/IText/ITextKeyBehavior.ts | 219 +++++++++++++++------------ src/shapes/Text/StyledText.ts | 6 +- 3 files changed, 129 insertions(+), 98 deletions(-) diff --git a/src/env/types.ts b/src/env/types.ts index d86d08fe5aa..16b7cecb1b4 100644 --- a/src/env/types.ts +++ b/src/env/types.ts @@ -3,7 +3,7 @@ import type { DOMWindow } from 'jsdom'; export type TCopyPasteData = { copiedText?: string; - copiedStyle?: Record; + copiedTextStyle?: Record; }; export type TFabricEnv = { document: Document; diff --git a/src/shapes/IText/ITextKeyBehavior.ts b/src/shapes/IText/ITextKeyBehavior.ts index 6c941d04897..a02b9f94ff5 100644 --- a/src/shapes/IText/ITextKeyBehavior.ts +++ b/src/shapes/IText/ITextKeyBehavior.ts @@ -157,112 +157,145 @@ export abstract class ITextKeyBehavior< this.canvas && this.canvas.requestRenderAll(); } + getDiffFromInputEvent({ inputType }: InputEvent) { + // selection diff + const { + selectionStart: prevSelectionStart, + selectionEnd: prevSelectionEnd, + } = this; + const { + selectionStart: nextSelectionStart, + selectionEnd: nextSelectionEnd, + } = this.fromStringToGraphemeSelection( + this.hiddenTextarea.selectionStart, + this.hiddenTextarea.selectionEnd, + this.hiddenTextarea.value + ); + let removedFrom: number, removedTo: number; + if (prevSelectionStart === prevSelectionEnd) { + const delta = + inputType === 'deleteContentBackward' + ? -1 + : inputType === 'deleteContentForward' + ? 1 + : 0; + removedFrom = Math.min(prevSelectionStart, prevSelectionStart + delta); + removedTo = Math.max(prevSelectionStart, prevSelectionStart + delta); + } else { + removedFrom = prevSelectionStart; + removedTo = prevSelectionEnd; + } + // text diff + const prevText = this._text.slice(), + { graphemeText: nextText } = this._splitTextIntoLines( + this.hiddenTextarea.value + ); + const charDiff = + nextText.length - + prevText.length + + (prevSelectionEnd - prevSelectionStart); + const removedText = prevText.slice(removedFrom, removedTo); + const insertedText = nextText.slice( + nextSelectionEnd - charDiff, + nextSelectionEnd + ); + // get styles from event + let stylesToAdd: TextStyleDeclaration[] = []; + const { copyPasteData } = getEnv(); + if ( + this.fromPaste && + !config.disableStyleCopyPaste && + copyPasteData.copiedTextStyle && + insertedText.join('') === copyPasteData.copiedText + ) { + stylesToAdd = copyPasteData.copiedTextStyle; + } else if ( + inputType !== 'deleteContentBackward' && + inputType !== 'deleteContentForward' + ) { + const selectionStartStyle = this.getStyleAtPosition( + Math.max(0, prevSelectionStart - 1) + ); + stylesToAdd = new Array(insertedText.length).fill().map(() => ({ + ...selectionStartStyle, + })); + } + // diff + return { + before: { + selectionStart: prevSelectionStart, + selectionEnd: prevSelectionEnd, + value: prevText, + }, + after: { + selectionStart: nextSelectionStart, + selectionEnd: nextSelectionEnd, + value: nextText, + }, + diff: { + index: removedFrom, + removed: removedText, + added: insertedText, + styles: stylesToAdd, + }, + }; + } + /** * Handles onInput event * @param {Event} e Event object */ - onInput(e: Event) { - const fromPaste = this.fromPaste; - this.fromPaste = false; + onInput(e: InputEvent) { e && e.stopPropagation(); if (!this.isEditing) { + this.fromPaste = false; return; } - const updateAndFire = () => { - this.updateFromTextArea(); - this.fire('changed'); - if (this.canvas) { - this.canvas.fire('text:changed', { target: this }); - this.canvas.requestRenderAll(); - } - }; if (this.hiddenTextarea.value === '') { this.styles = {}; - updateAndFire(); - return; - } - // decisions about style changes. - const nextText = this._splitTextIntoLines( - this.hiddenTextarea.value - ).graphemeText, - charCount = this._text.length, - nextCharCount = nextText.length, - selectionStart = this.selectionStart, - selectionEnd = this.selectionEnd, - selection = selectionStart !== selectionEnd; - let copiedStyle, - removedText, - charDiff = nextCharCount - charCount, - removeFrom, - removeTo; - - const textareaSelection = this.fromStringToGraphemeSelection( - this.hiddenTextarea.selectionStart, - this.hiddenTextarea.selectionEnd, - this.hiddenTextarea.value - ); - const backDelete = selectionStart > textareaSelection.selectionStart; - - if (selection) { - removedText = this._text.slice(selectionStart, selectionEnd); - charDiff += selectionEnd - selectionStart; - } else if (nextCharCount < charCount) { - if (backDelete) { - removedText = this._text.slice(selectionEnd + charDiff, selectionEnd); - } else { - removedText = this._text.slice( - selectionStart, - selectionStart - charDiff - ); - } - } - const insertedText = nextText.slice( - textareaSelection.selectionEnd - charDiff, - textareaSelection.selectionEnd - ); - if (removedText && removedText.length) { - if (insertedText.length) { - // let's copy some style before deleting. - // we want to copy the style before the cursor OR the style at the cursor if selection - // is bigger than 0. - copiedStyle = this.getSelectionStyles( - selectionStart, - selectionStart + 1, - false - ); - // now duplicate the style one for each inserted text. - copiedStyle = insertedText.map( - () => - // this return an array of references, but that is fine since we are - // copying the style later. - copiedStyle[0] - ); - } - if (selection) { - removeFrom = selectionStart; - removeTo = selectionEnd; - } else if (backDelete) { - // detect differences between forwardDelete and backDelete - removeFrom = selectionEnd - removedText.length; - removeTo = selectionEnd; - } else { - removeFrom = selectionEnd; - removeTo = selectionEnd + removedText.length; - } - this.removeStyleFromTo(removeFrom, removeTo); - } - if (insertedText.length) { - const { copyPasteData } = getEnv(); - if ( - fromPaste && - insertedText.join('') === copyPasteData.copiedText && - !config.disableStyleCopyPaste + } else { + const { + diff: { index, removed, added, styles }, + } = this.getDiffFromInputEvent(e); + console.log(this.text.slice(0, index), index, index + removed.length, { + index, + removed, + added, + styles, + }); + // this.removeStyleFromTo(index, index + removed.length); + + const removeStyleFrom = this.getStyleCursorPosition(index); + const styleCursor = this.getStyleCursorPosition( + Math.max(index, index + removed.length - 1) + ); + while ( + removed.length && + (styleCursor.lineIndex > removeStyleFrom.lineIndex || + (styleCursor.lineIndex === removeStyleFrom.lineIndex && + styleCursor.charIndex >= removeStyleFrom.charIndex)) ) { - copiedStyle = copyPasteData.copiedTextStyle; + console.log(styleCursor); + delete this.styles[styleCursor.lineIndex]?.[styleCursor.charIndex]; + if (styleCursor.charIndex > 0) { + styleCursor.charIndex--; + } else if (styleCursor.lineIndex > 0) { + styleCursor.lineIndex--; + styleCursor.charIndex = + this._unwrappedTextLines[styleCursor.lineIndex].length; + } else { + break; + } } - this.insertNewStyleBlock(insertedText, selectionStart, copiedStyle); + this.insertNewStyleBlock(added, index, styles); } - updateAndFire(); + this.updateFromTextArea(); + this.fire('changed'); + if (this.canvas) { + this.canvas.fire('text:changed', { target: this }); + this.canvas.requestRenderAll(); + } + this.fromPaste = false; } /** diff --git a/src/shapes/Text/StyledText.ts b/src/shapes/Text/StyledText.ts index 7e4d21086da..b3df54600b5 100644 --- a/src/shapes/Text/StyledText.ts +++ b/src/shapes/Text/StyledText.ts @@ -225,10 +225,8 @@ export abstract class StyledText< * @private */ getStyleAtPosition< - T extends boolean, - R extends T extends true - ? CompleteTextStyleDeclaration - : TextStyleDeclaration + T extends boolean = false, + R = T extends true ? CompleteTextStyleDeclaration : TextStyleDeclaration >(position: number, complete?: T): R { const { lineIndex, charIndex } = this.getStyleCursorPosition(position); return (