mirror of
https://github.com/penpot/penpot.git
synced 2026-07-22 22:17:58 +00:00
🐛 Fix missing font when pasting text (editor v1)
This commit is contained in:
parent
2b95e6b7a9
commit
a242962113
@ -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
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 =))
|
||||
|
||||
|
||||
@ -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)))))
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -16,4 +16,4 @@
|
||||
* @param {ClipboardEvent} event
|
||||
* @param {TextEditor} editor
|
||||
*/
|
||||
export function copy(event, editor) {}
|
||||
export function copy(event, editor, selectionController) {}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user