mirror of
https://github.com/penpot/penpot.git
synced 2025-05-19 17:56:13 +02:00
Merge pull request #5436 from penpot/azazeln28-fix-text-editor-v2-copy-paste-issues
🐛 Fix copy/paste issues
This commit is contained in:
commit
4cfe33bc5c
6 changed files with 82 additions and 5 deletions
|
@ -65,6 +65,7 @@
|
||||||
[app.main.data.workspace.shape-layout :as dwsl]
|
[app.main.data.workspace.shape-layout :as dwsl]
|
||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
[app.main.data.workspace.state-helpers :as wsh]
|
[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.thumbnails :as dwth]
|
||||||
[app.main.data.workspace.transforms :as dwt]
|
[app.main.data.workspace.transforms :as dwt]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
|
@ -83,6 +84,7 @@
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
[app.util.storage :as storage]
|
[app.util.storage :as storage]
|
||||||
|
[app.util.text.content :as tc]
|
||||||
[app.util.timers :as tm]
|
[app.util.timers :as tm]
|
||||||
[app.util.webapi :as wapi]
|
[app.util.webapi :as wapi]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
|
@ -1639,6 +1641,7 @@
|
||||||
(rx/ignore))))))))))
|
(rx/ignore))))))))))
|
||||||
|
|
||||||
(declare ^:private paste-transit)
|
(declare ^:private paste-transit)
|
||||||
|
(declare ^:private paste-html-text)
|
||||||
(declare ^:private paste-text)
|
(declare ^:private paste-text)
|
||||||
(declare ^:private paste-image)
|
(declare ^:private paste-image)
|
||||||
(declare ^:private paste-svg-text)
|
(declare ^:private paste-svg-text)
|
||||||
|
@ -1706,6 +1709,7 @@
|
||||||
(let [pdata (wapi/read-from-paste-event event)
|
(let [pdata (wapi/read-from-paste-event event)
|
||||||
image-data (some-> pdata wapi/extract-images)
|
image-data (some-> pdata wapi/extract-images)
|
||||||
text-data (some-> pdata wapi/extract-text)
|
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))]
|
transit-data (ex/ignoring (some-> text-data t/decode-str))]
|
||||||
(cond
|
(cond
|
||||||
(and (string? text-data) (re-find #"<svg\s" text-data))
|
(and (string? text-data) (re-find #"<svg\s" text-data))
|
||||||
|
@ -1718,6 +1722,9 @@
|
||||||
(coll? transit-data)
|
(coll? transit-data)
|
||||||
(rx/of (paste-transit (assoc transit-data :in-viewport in-viewport?)))
|
(rx/of (paste-transit (assoc transit-data :in-viewport in-viewport?)))
|
||||||
|
|
||||||
|
(string? html-data)
|
||||||
|
(rx/of (paste-html-text html-data text-data))
|
||||||
|
|
||||||
(string? text-data)
|
(string? text-data)
|
||||||
(rx/of (paste-text text-data))
|
(rx/of (paste-text text-data))
|
||||||
|
|
||||||
|
@ -2070,6 +2077,34 @@
|
||||||
:else
|
:else
|
||||||
(deref ms/mouse-position)))
|
(deref ms/mouse-position)))
|
||||||
|
|
||||||
|
(defn- paste-html-text
|
||||||
|
[html text]
|
||||||
|
(dm/assert! (string? html))
|
||||||
|
(ptk/reify ::paste-html-text
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [root (dwtxt/create-root-from-html html)
|
||||||
|
content (tc/dom->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
|
(defn- paste-text
|
||||||
[text]
|
[text]
|
||||||
(dm/assert! (string? text))
|
(dm/assert! (string? text))
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
|
|
||||||
;; -- V2 Editor Helpers
|
;; -- 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 create-editor editor.v2/create)
|
||||||
(def ^function set-editor-root! editor.v2/setRoot)
|
(def ^function set-editor-root! editor.v2/setRoot)
|
||||||
(def ^function get-editor-root editor.v2/getRoot)
|
(def ^function get-editor-root editor.v2/getRoot)
|
||||||
|
|
|
@ -145,6 +145,10 @@
|
||||||
(not= (.-tagName ^js target) "INPUT")) ;; an editable control
|
(not= (.-tagName ^js target) "INPUT")) ;; an editable control
|
||||||
(.. ^js event getBrowserEvent -clipboardData))))
|
(.. ^js event getBrowserEvent -clipboardData))))
|
||||||
|
|
||||||
|
(defn extract-html-text
|
||||||
|
[clipboard-data]
|
||||||
|
(.getData clipboard-data "text/html"))
|
||||||
|
|
||||||
(defn extract-text
|
(defn extract-text
|
||||||
[clipboard-data]
|
[clipboard-data]
|
||||||
(.getData clipboard-data "text"))
|
(.getData clipboard-data "text"))
|
||||||
|
|
|
@ -12,6 +12,7 @@ import ChangeController from "./controllers/ChangeController.js";
|
||||||
import SelectionController from "./controllers/SelectionController.js";
|
import SelectionController from "./controllers/SelectionController.js";
|
||||||
import { createSelectionImposterFromClientRects } from "./selection/Imposter.js";
|
import { createSelectionImposterFromClientRects } from "./selection/Imposter.js";
|
||||||
import { addEventListeners, removeEventListeners } from "./Event.js";
|
import { addEventListeners, removeEventListeners } from "./Event.js";
|
||||||
|
import { mapContentFragmentFromHTML, mapContentFragmentFromString } from "./content/dom/Content.js";
|
||||||
import { createRoot, createEmptyRoot } from "./content/dom/Root.js";
|
import { createRoot, createEmptyRoot } from "./content/dom/Root.js";
|
||||||
import { createParagraph } from "./content/dom/Paragraph.js";
|
import { createParagraph } from "./content/dom/Paragraph.js";
|
||||||
import { createEmptyInline, createInline } from "./content/dom/Inline.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) {
|
export function isEditor(instance) {
|
||||||
return (instance instanceof TextEditor);
|
return (instance instanceof TextEditor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,17 @@ function getInertElement() {
|
||||||
return inertElement;
|
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.
|
* 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.
|
* CSS properties like `font-family` or some CSS variables.
|
||||||
*
|
*
|
||||||
* @param {Node} node
|
* @param {Node} node
|
||||||
* @param {CSSStyleDeclaration} styleDefaults
|
* @param {CSSStyleDeclaration} [styleDefaults]
|
||||||
* @returns {CSSStyleDeclaration}
|
* @returns {CSSStyleDeclaration}
|
||||||
*/
|
*/
|
||||||
export function normalizeStyles(node, styleDefaults) {
|
export function normalizeStyles(node, styleDefaults = getStyleDefaultsDeclaration()) {
|
||||||
const styleDeclaration = mergeStyleDeclarations(
|
const styleDeclaration = mergeStyleDeclarations(
|
||||||
styleDefaults,
|
styleDefaults,
|
||||||
getComputedStyle(node.parentElement)
|
getComputedStyle(node.parentElement)
|
||||||
);
|
);
|
||||||
|
|
||||||
// If there's a color property, we should convert it to
|
// If there's a color property, we should convert it to
|
||||||
// a --fills CSS variable property.
|
// a --fills CSS variable property.
|
||||||
const fills = styleDeclaration.getPropertyValue("--fills");
|
const fills = styleDeclaration.getPropertyValue("--fills");
|
||||||
const color = styleDeclaration.getPropertyValue("color");
|
const color = styleDeclaration.getPropertyValue("color");
|
||||||
if (color && !fills) {
|
if (color) {
|
||||||
styleDeclaration.removeProperty("color");
|
styleDeclaration.removeProperty("color");
|
||||||
styleDeclaration.setProperty("--fills", getFills(color));
|
styleDeclaration.setProperty("--fills", getFills(color));
|
||||||
|
} else {
|
||||||
|
styleDeclaration.setProperty("--fills", fills);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a font-family property and not a --font-id, then
|
// If there's a font-family property and not a --font-id, then
|
||||||
// we remove the font-family because it will not work.
|
// we remove the font-family because it will not work.
|
||||||
const fontFamily = styleDeclaration.getPropertyValue("font-family");
|
const fontFamily = styleDeclaration.getPropertyValue("font-family");
|
||||||
|
@ -145,8 +160,15 @@ export function normalizeStyles(node, styleDefaults) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineHeight = styleDeclaration.getPropertyValue("line-height");
|
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);
|
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
|
return styleDeclaration
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { expect, describe, test } from "vitest";
|
import { expect, describe, test } from "vitest";
|
||||||
import TextEditor from "../TextEditor.js";
|
|
||||||
import {
|
import {
|
||||||
createEmptyParagraph,
|
createEmptyParagraph,
|
||||||
createParagraph,
|
createParagraph,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue