From 4ef631fd6a774569f999ef9d4ae6cab404a64dcf Mon Sep 17 00:00:00 2001 From: AzazelN28 Date: Mon, 9 Dec 2024 16:07:44 +0100 Subject: [PATCH] :bug: Fix copy/paste issues --- frontend/src/app/main/data/workspace.cljs | 35 +++++++++++++++++++ .../src/app/main/data/workspace/texts.cljs | 2 ++ frontend/src/app/util/webapi.cljs | 4 +++ frontend/text-editor/src/editor/TextEditor.js | 15 ++++++++ .../src/editor/content/dom/Style.js | 30 +++++++++++++--- .../controllers/SelectionController.test.js | 1 - 6 files changed, 82 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index cf6092e03..54ad7c530 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -65,6 +65,7 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] @@ -83,6 +84,7 @@ [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.storage :as storage] + [app.util.text.content :as tc] [app.util.timers :as tm] [app.util.webapi :as wapi] [beicon.v2.core :as rx] @@ -1639,6 +1641,7 @@ (rx/ignore)))))))))) (declare ^:private paste-transit) +(declare ^:private paste-html-text) (declare ^:private paste-text) (declare ^:private paste-image) (declare ^:private paste-svg-text) @@ -1706,6 +1709,7 @@ (let [pdata (wapi/read-from-paste-event event) image-data (some-> pdata wapi/extract-images) text-data (some-> pdata wapi/extract-text) + html-data (some-> pdata wapi/extract-html-text) transit-data (ex/ignoring (some-> text-data t/decode-str))] (cond (and (string? text-data) (re-find #"cljs root) + + id (uuid/next) + width (max 8 (min (* 7 (count text)) 700)) + height 16 + {:keys [x y]} (calculate-paste-position state) + + shape {:id id + :type :text + :name (txt/generate-shape-name text) + :x x + :y y + :width width + :height height + :grow-type (if (> (count text) 100) :auto-height :auto-width) + :content content} + undo-id (js/Symbol)] + (rx/of (dwu/start-undo-transaction undo-id) + (dwsh/create-and-add-shape :text x y shape) + (dwu/commit-undo-transaction undo-id)))))) + (defn- paste-text [text] (dm/assert! (string? text)) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 20e24611b..1f7db4b1b 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -37,6 +37,8 @@ ;; -- V2 Editor Helpers +(def ^function create-root-from-string editor.v2/createRootFromString) +(def ^function create-root-from-html editor.v2/createRootFromHTML) (def ^function create-editor editor.v2/create) (def ^function set-editor-root! editor.v2/setRoot) (def ^function get-editor-root editor.v2/getRoot) diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index 2225a96db..722067fe7 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -145,6 +145,10 @@ (not= (.-tagName ^js target) "INPUT")) ;; an editable control (.. ^js event getBrowserEvent -clipboardData)))) +(defn extract-html-text + [clipboard-data] + (.getData clipboard-data "text/html")) + (defn extract-text [clipboard-data] (.getData clipboard-data "text")) diff --git a/frontend/text-editor/src/editor/TextEditor.js b/frontend/text-editor/src/editor/TextEditor.js index 357496312..795731b9a 100644 --- a/frontend/text-editor/src/editor/TextEditor.js +++ b/frontend/text-editor/src/editor/TextEditor.js @@ -12,6 +12,7 @@ import ChangeController from "./controllers/ChangeController.js"; import SelectionController from "./controllers/SelectionController.js"; import { createSelectionImposterFromClientRects } from "./selection/Imposter.js"; import { addEventListeners, removeEventListeners } from "./Event.js"; +import { mapContentFragmentFromHTML, mapContentFragmentFromString } from "./content/dom/Content.js"; import { createRoot, createEmptyRoot } from "./content/dom/Root.js"; import { createParagraph } from "./content/dom/Paragraph.js"; import { createEmptyInline, createInline } from "./content/dom/Inline.js"; @@ -501,6 +502,20 @@ export class TextEditor extends EventTarget { } } +export function createRootFromHTML(html) { + const fragment = mapContentFragmentFromHTML(html); + const root = createRoot([]); + root.replaceChildren(fragment); + return root; +} + +export function createRootFromString(string) { + const fragment = mapContentFragmentFromString(string); + const root = createRoot([]); + root.replaceChild(fragment); + return root; +} + export function isEditor(instance) { return (instance instanceof TextEditor); } diff --git a/frontend/text-editor/src/editor/content/dom/Style.js b/frontend/text-editor/src/editor/content/dom/Style.js index dde094472..a4a883770 100644 --- a/frontend/text-editor/src/editor/content/dom/Style.js +++ b/frontend/text-editor/src/editor/content/dom/Style.js @@ -75,6 +75,17 @@ function getInertElement() { return inertElement; } +/** + * Returns a default declaration. + * + * @returns {CSSStyleDeclaration} + */ +function getStyleDefaultsDeclaration() { + const element = getInertElement(); + resetInertElement(); + return element.style; +} + /** * Computes the styles of an element the same way `window.getComputedStyle` does. * @@ -115,22 +126,26 @@ export function getComputedStyle(element) { * CSS properties like `font-family` or some CSS variables. * * @param {Node} node - * @param {CSSStyleDeclaration} styleDefaults + * @param {CSSStyleDeclaration} [styleDefaults] * @returns {CSSStyleDeclaration} */ -export function normalizeStyles(node, styleDefaults) { +export function normalizeStyles(node, styleDefaults = getStyleDefaultsDeclaration()) { const styleDeclaration = mergeStyleDeclarations( styleDefaults, getComputedStyle(node.parentElement) ); + // If there's a color property, we should convert it to // a --fills CSS variable property. const fills = styleDeclaration.getPropertyValue("--fills"); const color = styleDeclaration.getPropertyValue("color"); - if (color && !fills) { + if (color) { styleDeclaration.removeProperty("color"); styleDeclaration.setProperty("--fills", getFills(color)); + } else { + styleDeclaration.setProperty("--fills", fills); } + // If there's a font-family property and not a --font-id, then // we remove the font-family because it will not work. const fontFamily = styleDeclaration.getPropertyValue("font-family"); @@ -145,8 +160,15 @@ export function normalizeStyles(node, styleDefaults) { } const lineHeight = styleDeclaration.getPropertyValue("line-height"); - if (!lineHeight || lineHeight === "") { + if (!lineHeight || lineHeight === "" || !lineHeight.endsWith("px")) { + // TODO: PodrĂ­amos convertir unidades en decimales. styleDeclaration.setProperty("line-height", DEFAULT_LINE_HEIGHT); + } else if (lineHeight.endsWith("px")) { + const fontSize = styleDeclaration.getPropertyValue("font-size"); + styleDeclaration.setProperty( + "line-height", + parseFloat(lineHeight) / parseFloat(fontSize), + ); } return styleDeclaration } diff --git a/frontend/text-editor/src/editor/controllers/SelectionController.test.js b/frontend/text-editor/src/editor/controllers/SelectionController.test.js index 070475e44..786c9a18d 100644 --- a/frontend/text-editor/src/editor/controllers/SelectionController.test.js +++ b/frontend/text-editor/src/editor/controllers/SelectionController.test.js @@ -1,5 +1,4 @@ import { expect, describe, test } from "vitest"; -import TextEditor from "../TextEditor.js"; import { createEmptyParagraph, createParagraph,