🐛 Fix missing font when pasting text (editor v1)

This commit is contained in:
Belén Albeza 2025-07-04 15:52:13 +02:00
parent 2b95e6b7a9
commit a242962113
10 changed files with 95 additions and 48 deletions

View File

@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -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)

View File

@ -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 =))

View File

@ -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)))))

View File

@ -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) {

View File

@ -16,4 +16,4 @@
* @param {ClipboardEvent} event
* @param {TextEditor} editor
*/
export function copy(event, editor) {}
export function copy(event, editor, selectionController) {}

View File

@ -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) {

View File

@ -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,
),
);
}
}

View File

@ -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) {

View File

@ -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;