From a242962113685990c37035cde8ce3e9a6b179a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 4 Jul 2025 15:52:13 +0200 Subject: [PATCH] :bug: Fix missing font when pasting text (editor v1) --- frontend/src/app/main/data/workspace.cljs | 7 +++ .../app/main/data/workspace/clipboard.cljs | 4 +- frontend/src/app/main/refs.cljs | 3 ++ .../ui/workspace/shapes/text/v2_editor.cljs | 9 +++- frontend/text-editor/src/editor/TextEditor.js | 41 ++++++++++++------ .../text-editor/src/editor/clipboard/copy.js | 2 +- .../text-editor/src/editor/clipboard/paste.js | 15 +++++-- .../src/editor/content/dom/Content.js | 14 +++--- .../src/editor/content/dom/Element.js | 5 ++- .../src/editor/content/dom/Style.js | 43 +++++++++++-------- 10 files changed, 95 insertions(+), 48 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f8a927684d..a59b27270e 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1295,6 +1295,13 @@ (js/console.log "Copies no ref" (count copies-no-ref) (clj->js copies-no-ref)) (js/console.log "Childs no ref" (count childs-no-ref) (clj->js childs-no-ref)))))) +(defn set-clipboard-style + [style] + (ptk/reify ::set-clipboard-style + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-global :clipboard-style] style)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 9cf3ff1ccb..9cc7a10ce1 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -42,6 +42,7 @@ [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.undo :as dwu] [app.main.errors] + [app.main.refs :as refs] [app.main.repo :as rp] [app.main.router :as rt] [app.main.streams :as ms] @@ -950,7 +951,8 @@ (ptk/reify ::paste-html-text ptk/WatchEvent (watch [_ state _] - (let [root (dwtxt/create-root-from-html html) + (let [style (deref refs/workspace-clipboard-style) + root (dwtxt/create-root-from-html html style) content (tc/dom->cljs root)] (when (types.text/valid-content? content) (let [id (uuid/next) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 9b27c73c11..73b41500e2 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -402,6 +402,9 @@ [frame-id] (l/derived #(get % frame-id) workspace-frame-modifiers =)) +(def workspace-clipboard-style + (l/derived :clipboard-style workspace-global)) + (defn select-bool-children [id] (l/derived #(dsh/select-bool-children % id) st/state =)) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs index 8a42dde614..e1dad90b26 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs @@ -116,7 +116,12 @@ on-change (fn [] (when-let [content (content/dom->cljs (dwt/get-editor-root instance))] - (st/emit! (dwt/v2-update-text-shape-content shape-id content :update-name? true))))] + (st/emit! (dwt/v2-update-text-shape-content shape-id content :update-name? true)))) + + on-clipboard-change + (fn [event] + (let [style (.-detail event)] + (st/emit! (dw/set-clipboard-style style))))] (.addEventListener ^js global/document "keyup" on-key-up) (.addEventListener ^js instance "blur" on-blur) @@ -124,6 +129,7 @@ (.addEventListener ^js instance "needslayout" on-needs-layout) (.addEventListener ^js instance "stylechange" on-style-change) (.addEventListener ^js instance "change" on-change) + (.addEventListener ^js instance "clipboardchange" on-clipboard-change) (st/emit! (dwt/update-editor instance)) (when (some? content) @@ -138,6 +144,7 @@ (.removeEventListener ^js instance "needslayout" on-needs-layout) (.removeEventListener ^js instance "stylechange" on-style-change) (.removeEventListener ^js instance "change" on-change) + (.removeEventListener ^js instance "clipboardchange" on-clipboard-change) (dwt/dispose! instance) (st/emit! (dwt/update-editor nil))))) diff --git a/frontend/text-editor/src/editor/TextEditor.js b/frontend/text-editor/src/editor/TextEditor.js index 795731b9a4..277ba0eced 100644 --- a/frontend/text-editor/src/editor/TextEditor.js +++ b/frontend/text-editor/src/editor/TextEditor.js @@ -12,7 +12,10 @@ 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 { + 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"; @@ -173,11 +176,11 @@ export class TextEditor extends EventTarget { this.#selectionController = new SelectionController( this, document.getSelection(), - options + options, ); this.#selectionController.addEventListener( "stylechange", - this.#onStyleChange + this.#onStyleChange, ); addEventListeners(this.#element, this.#events, { capture: true, @@ -199,7 +202,7 @@ export class TextEditor extends EventTarget { if (rects) { const rect = this.#selectionImposterElement.getBoundingClientRect(); this.#selectionImposterElement.replaceChildren( - createSelectionImposterFromClientRects(rect, rects) + createSelectionImposterFromClientRects(rect, rects), ); } } @@ -258,7 +261,15 @@ export class TextEditor extends EventTarget { * * @param {ClipboardEvent} e */ - #onCopy = (e) => clipboard.copy(e, this, this.#selectionController); + #onCopy = (e) => { + this.dispatchEvent( + new CustomEvent("clipboardchange", { + detail: this.#selectionController.currentStyle, + }), + ); + + clipboard.copy(e, this, this.#selectionController); + }; /** * Event called before the DOM is modified. @@ -304,7 +315,10 @@ export class TextEditor extends EventTarget { return; } - if (e.inputType === "insertCompositionText" && this.#fixInsertCompositionText) { + if ( + e.inputType === "insertCompositionText" && + this.#fixInsertCompositionText + ) { e.preventDefault(); this.#fixInsertCompositionText = false; if (e.data) { @@ -331,7 +345,7 @@ export class TextEditor extends EventTarget { type: type, mutations: mutations, }, - }) + }), ); } @@ -492,7 +506,7 @@ export class TextEditor extends EventTarget { this.#changeController = null; this.#selectionController.removeEventListener( "stylechange", - this.#onStyleChange + this.#onStyleChange, ); this.#selectionController.dispose(); this.#selectionController = null; @@ -502,10 +516,11 @@ export class TextEditor extends EventTarget { } } -export function createRootFromHTML(html) { - const fragment = mapContentFragmentFromHTML(html); - const root = createRoot([]); +export function createRootFromHTML(html, style) { + const fragment = mapContentFragmentFromHTML(html, style); + const root = createRoot([], style); root.replaceChildren(fragment); + console.log("ROOT", root); return root; } @@ -517,7 +532,7 @@ export function createRootFromString(string) { } export function isEditor(instance) { - return (instance instanceof TextEditor); + return instance instanceof TextEditor; } /* Convenience function based API for Text Editor */ @@ -538,7 +553,7 @@ export function setRoot(instance, root) { } export function create(element, options) { - return new TextEditor(element, {...options}); + return new TextEditor(element, { ...options }); } export function getCurrentStyle(instance) { diff --git a/frontend/text-editor/src/editor/clipboard/copy.js b/frontend/text-editor/src/editor/clipboard/copy.js index 49ed9f9ec3..374d301965 100644 --- a/frontend/text-editor/src/editor/clipboard/copy.js +++ b/frontend/text-editor/src/editor/clipboard/copy.js @@ -16,4 +16,4 @@ * @param {ClipboardEvent} event * @param {TextEditor} editor */ -export function copy(event, editor) {} +export function copy(event, editor, selectionController) {} diff --git a/frontend/text-editor/src/editor/clipboard/paste.js b/frontend/text-editor/src/editor/clipboard/paste.js index 2b3525afc0..af472888bd 100644 --- a/frontend/text-editor/src/editor/clipboard/paste.js +++ b/frontend/text-editor/src/editor/clipboard/paste.js @@ -6,7 +6,10 @@ * Copyright (c) KALEIDOS INC */ -import { mapContentFragmentFromHTML, mapContentFragmentFromString } from "../content/dom/Content.js"; +import { + mapContentFragmentFromHTML, + mapContentFragmentFromString, +} from "../content/dom/Content.js"; /** * When the user pastes some HTML, what we do is generate @@ -27,10 +30,16 @@ export function paste(event, editor, selectionController) { let fragment = null; if (event.clipboardData.types.includes("text/html")) { const html = event.clipboardData.getData("text/html"); - fragment = mapContentFragmentFromHTML(html, selectionController.currentStyle); + fragment = mapContentFragmentFromHTML( + html, + selectionController.currentStyle, + ); } else if (event.clipboardData.types.includes("text/plain")) { const plain = event.clipboardData.getData("text/plain"); - fragment = mapContentFragmentFromString(plain, selectionController.currentStyle); + fragment = mapContentFragmentFromString( + plain, + selectionController.currentStyle, + ); } if (!fragment) { diff --git a/frontend/text-editor/src/editor/content/dom/Content.js b/frontend/text-editor/src/editor/content/dom/Content.js index 9c6c8efccc..d95d9e2a71 100644 --- a/frontend/text-editor/src/editor/content/dom/Content.js +++ b/frontend/text-editor/src/editor/content/dom/Content.js @@ -48,10 +48,7 @@ function isContentFragmentFromDocumentInline(document) { * @returns {DocumentFragment} */ export function mapContentFragmentFromDocument(document, root, styleDefaults) { - const nodeIterator = document.createNodeIterator( - root, - NodeFilter.SHOW_TEXT - ); + const nodeIterator = document.createNodeIterator(root, NodeFilter.SHOW_TEXT); const fragment = document.createDocumentFragment(); let currentParagraph = null; @@ -110,7 +107,7 @@ export function mapContentFragmentFromHTML(html, styleDefaults) { return mapContentFragmentFromDocument( htmlDocument, htmlDocument.documentElement, - styleDefaults + styleDefaults, ); } @@ -129,9 +126,10 @@ export function mapContentFragmentFromString(string, styleDefaults) { fragment.appendChild(createEmptyParagraph(styleDefaults)); } else { fragment.appendChild( - createParagraph([ - createInline(new Text(line), styleDefaults) - ], styleDefaults) + createParagraph( + [createInline(new Text(line), styleDefaults)], + styleDefaults, + ), ); } } diff --git a/frontend/text-editor/src/editor/content/dom/Element.js b/frontend/text-editor/src/editor/content/dom/Element.js index d118801468..a270e076f8 100644 --- a/frontend/text-editor/src/editor/content/dom/Element.js +++ b/frontend/text-editor/src/editor/content/dom/Element.js @@ -34,15 +34,16 @@ export function createRandomId() { * @returns {HTMLElement} */ export function createElement(tag, options) { + console.log("createElement", options); const element = document.createElement(tag); if (options?.attributes) { Object.entries(options.attributes).forEach(([name, value]) => - element.setAttribute(name, value) + element.setAttribute(name, value), ); } if (options?.data) { Object.entries(options.data).forEach( - ([name, value]) => (element.dataset[name] = value) + ([name, value]) => (element.dataset[name] = value), ); } if (options?.styles && options?.allowedStyles) { diff --git a/frontend/text-editor/src/editor/content/dom/Style.js b/frontend/text-editor/src/editor/content/dom/Style.js index d194a13363..19a4a6a977 100644 --- a/frontend/text-editor/src/editor/content/dom/Style.js +++ b/frontend/text-editor/src/editor/content/dom/Style.js @@ -26,7 +26,7 @@ export function mergeStyleDeclarations(target, source) { const styleValue = source.getPropertyValue(styleName); target.setProperty(styleName, styleValue); } - return target + return target; } /** @@ -40,7 +40,7 @@ function resetStyleDeclaration(styleDeclaration) { const styleName = styleDeclaration.item(index); styleDeclaration.removeProperty(styleName); } - return styleDeclaration + return styleDeclaration; } /** @@ -49,14 +49,14 @@ function resetStyleDeclaration(styleDeclaration) { * * @type {HTMLDivElement|null} */ -let inertElement = null +let inertElement = null; /** * Resets the style declaration of the inert * element. */ function resetInertElement() { - if (!inertElement) throw new Error('Invalid inert element'); + if (!inertElement) throw new Error("Invalid inert element"); resetStyleDeclaration(inertElement.style); return inertElement; } @@ -110,10 +110,7 @@ export function getComputedStyle(element) { } } else { const newValue = currentElement.style.getPropertyValue(styleName); - inertElement.style.setProperty( - styleName, - newValue - ); + inertElement.style.setProperty(styleName, newValue); } } currentElement = currentElement.parentElement; @@ -131,12 +128,12 @@ export function getComputedStyle(element) { * @param {CSSStyleDeclaration} [styleDefaults] * @returns {CSSStyleDeclaration} */ -export function normalizeStyles(node, styleDefaults = getStyleDefaultsDeclaration()) { +export function normalizeStyles( + node, + styleDefaults = getStyleDefaultsDeclaration(), +) { const computedStyle = getComputedStyle(node.parentElement); - const styleDeclaration = mergeStyleDeclarations( - styleDefaults, - computedStyle - ); + const styleDeclaration = mergeStyleDeclarations(styleDefaults, computedStyle); // If there's a color property, we should convert it to // a --fills CSS variable property. @@ -173,7 +170,7 @@ export function normalizeStyles(node, styleDefaults = getStyleDefaultsDeclaratio parseFloat(lineHeight) / parseFloat(fontSize), ); } - return styleDeclaration + return styleDeclaration; } /** @@ -237,7 +234,7 @@ export function getStyleFromDeclaration(style, styleName, styleUnit) { if (styleName === "font-size") { return getStyleFontSize(styleValueAsNumber, styleValue); } else if (styleName === "line-height") { - return styleValue + return styleValue; } if (Number.isNaN(styleValueAsNumber)) { return styleValue; @@ -291,10 +288,14 @@ export function setStylesFromObject(element, allowedStyles, styleObject) { export function setStylesFromDeclaration( element, allowedStyles, - styleDeclaration + styleDeclaration, ) { for (const [styleName, styleUnit] of allowedStyles) { - const styleValue = getStyleFromDeclaration(styleDeclaration, styleName, styleUnit); + const styleValue = getStyleFromDeclaration( + styleDeclaration, + styleName, + styleUnit, + ); if (styleValue) { setStyle(element, styleName, styleValue, styleUnit); } @@ -316,7 +317,7 @@ export function setStyles(element, allowedStyles, styleObjectOrDeclaration) { return setStylesFromDeclaration( element, allowedStyles, - styleObjectOrDeclaration + styleObjectOrDeclaration, ); } return setStylesFromObject(element, allowedStyles, styleObjectOrDeclaration); @@ -354,7 +355,11 @@ export function mergeStyles(allowedStyles, styleDeclaration, newStyles) { if (styleName in newStyles) { mergedStyles[styleName] = newStyles[styleName]; } else { - mergedStyles[styleName] = getStyleFromDeclaration(styleDeclaration, styleName, styleUnit); + mergedStyles[styleName] = getStyleFromDeclaration( + styleDeclaration, + styleName, + styleUnit, + ); } } return mergedStyles;