Merge pull request #8302 from penpot/azazeln28-issue-13124-text-not-restored-undoing

🐛 Fix text not restored on ctrl+z
This commit is contained in:
Elena Torró
2026-02-09 13:41:43 +01:00
committed by GitHub
5 changed files with 69 additions and 20 deletions

View File

@@ -46,6 +46,7 @@
(def ^function create-editor editor.v2/create)
(def ^function set-editor-root! editor.v2/setRoot)
(def ^function get-editor-root editor.v2/getRoot)
(def ^function is-empty? editor.v2/isEmpty)
(def ^function dispose! editor.v2/dispose)
(declare v2-update-text-shape-content)
@@ -901,15 +902,22 @@
(update-in state [:workspace-text-modifier shape-id] {:position-data position-data}))))
(defn v2-update-text-shape-content
[id content & {:keys [update-name? name finalize?]
:or {update-name? false name nil finalize? false}}]
[id content & {:keys [update-name? name finalize? save-undo?]
:or {update-name? false name nil finalize? false save-undo? true}}]
(ptk/reify ::v2-update-text-shape-content
ptk/WatchEvent
(watch [_ state _]
(if (features/active-feature? state "render-wasm/v1")
(let [objects (dsh/lookup-page-objects state)
shape (get objects id)
new-shape? (nil? (:content shape))]
new-shape? (nil? (:content shape))
prev-content (:content shape)
has-prev-content? (not (nil? (:prev-content shape)))
has-content? (when-not new-shape?
(v2-content-has-text? content))
did-has-content? (when-not new-shape?
(v2-content-has-text? prev-content))]
(rx/concat
(rx/of
(dwsh/update-shapes
@@ -917,10 +925,16 @@
(fn [shape]
(let [new-shape (-> shape
(assoc :content content)
(cond-> (and has-content?
has-prev-content?)
(dissoc :prev-content))
(cond-> (and did-has-content?
(not has-content?))
(assoc :prev-content prev-content))
(cond-> (and update-name? (some? name))
(assoc :name name)))]
new-shape))
{:undo-group (when new-shape? id)})
{:save-undo? save-undo? :undo-group (when new-shape? id)})
(if (and (not= :fixed (:grow-type shape)) finalize?)
(dwm/apply-wasm-modifiers
@@ -933,8 +947,16 @@
(when finalize?
(rx/concat
(when (and (not (v2-content-has-text? content)) (some? id))
(when (and (not has-content?) (some? id))
(rx/of
(when has-prev-content?
(dwsh/update-shapes
[id]
(fn [shape]
(let [new-shape (-> shape
(assoc :content (:prev-content shape)))]
new-shape))
{:save-undo? false}))
(dws/deselect-shape id)
(dwsh/delete-shapes #{id})))
(rx/of (dwt/finish-transform))))))

View File

@@ -117,7 +117,8 @@
(st/emit! (dwt/v2-update-text-shape-content shape-id content
:update-name? update-name?
:name generated-name
:finalize? true))))
:finalize? true
:save-undo? false))))
(let [container-node (mf/ref-val container-ref)]
(dom/set-style! container-node "opacity" 0)))
@@ -135,15 +136,21 @@
on-needs-layout
(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
:save-undo? false)))
;; FIXME: We need to find a better way to trigger layout changes.
#_(st/emit!
(dwt/v2-update-text-shape-position-data shape-id [])))
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))))
(let [is-empty? (dwt/is-empty? instance)
save-undo? (not is-empty?)]
(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
:save-undo? save-undo?)))))
on-clipboard-change
(fn [event]
@@ -247,7 +254,7 @@
:ref container-ref
:data-testid "text-editor-container"
:style {:width "var(--editor-container-width)"
:height "var(--editor-container-height)"}
:height "var(--editor-container-height)"}}
;; We hide the editor when is blurred because otherwise the
;; selection won't let us see the underlying text. Use opacity
;; because display or visibility won't allow to recover focus
@@ -256,7 +263,7 @@
;; IMPORTANT! This is now done through DOM mutations (see
;; on-blur and on-focus) but I keep this for future references.
;; :opacity (when @blurred 0)}}
}
[:div
{:class (dm/str
"mousetrap "

View File

@@ -32,7 +32,8 @@
"This function adds units to style values"
[k v]
(cond
(and (or (= k :font-size)
(and (keyword? k)
(or (= k :font-size)
(= k :letter-spacing))
(not= (str/slice v -2) "px"))
(str v "px")

View File

@@ -326,7 +326,9 @@ export class TextEditor extends EventTarget {
* @param {FocusEvent} e
*/
#onBlur = (e) => {
this.#changeController.notifyImmediately();
if (!this.isEmpty) {
this.#changeController.notifyImmediately();
}
this.#selectionController.saveSelection();
this.dispatchEvent(new FocusEvent(e.type, e));
};
@@ -683,13 +685,26 @@ export function createRootFromString(string) {
* Returns true if the passed object is a TextEditor
* instance.
*
* @param {*} instance
* @param {TextEditor} instance
* @returns {boolean}
*/
export function isTextEditor(instance) {
return instance instanceof TextEditor;
}
/**
* Returns true if the TextEditor is empty.
*
* @param {TextEditor} instance
* @returns {boolean}
*/
export function isEmpty(instance) {
if (isTextEditor(instance)) {
return instance.isEmpty;
}
throw new TypeError('Instance is not a TextEditor');
}
/**
* Returns the root element of a TextEditor
* instance.
@@ -701,7 +716,7 @@ export function getRoot(instance) {
if (isTextEditor(instance)) {
return instance.root;
}
return null;
throw new TypeError("Instance is not a TextEditor");
}
/**
@@ -714,9 +729,9 @@ export function getRoot(instance) {
export function setRoot(instance, root) {
if (isTextEditor(instance)) {
instance.root = root;
return instance;
}
return instance;
throw new TypeError("Instance is not a TextEditor");
}
/**
@@ -741,7 +756,7 @@ export function getCurrentStyle(instance) {
if (isTextEditor(instance)) {
return instance.currentStyle;
}
return null;
throw new TypeError("Instance is not a TextEditor");
}
/**
@@ -756,7 +771,7 @@ export function applyStylesToSelection(instance, styles) {
if (isTextEditor(instance)) {
return instance.applyStylesToSelection(styles);
}
return null;
throw new TypeError("Instance is not a TextEditor");
}
/**
@@ -770,7 +785,7 @@ export function dispose(instance) {
if (isTextEditor(instance)) {
return instance.dispose();
}
return null;
throw new TypeError("Instance is not a TextEditor");
}
export default TextEditor;

View File

@@ -54,8 +54,12 @@ export class ChangeController extends EventTarget {
return this.#hasPendingChanges;
}
/**
* Handles timeout.
*/
#onTimeout = () => {
this.dispatchEvent(new Event("change"));
this.#hasPendingChanges = false;
};
/**