From 451bf949c71e900be31aeb0a5cb8c147840346f7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 22 Oct 2026 12:25:48 +0200 Subject: [PATCH] :bug: Keep the workspace open when closing the v2 text editor Closing the v2 text editor with Escape could crash the workspace. The text options panel renders once more with the editor it saw last, after closing has disposed it, and reading the style of a disposed editor throws. The panel now takes the values from the shape when the editor is gone. --- .../src/app/main/data/workspace/texts.cljs | 15 +++++++++-- .../data/workspace_texts_test.cljs | 27 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index c464d4f1e1..cda89b4c87 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -443,6 +443,17 @@ result (if (empty? result) txt/default-text-attrs result)] result)) +(defn- v2-editor-live? + "False for a missing or disposed v2 editor. A component can still hold + the instance for one render after closing the editor disposed it, and + reading its style then throws." + [editor] + (and (some? editor) + (try + (.-currentStyle ^js editor) + true + (catch :default _ false)))) + (defn- styled-runs "The text runs of `content` with their styles, without node ids or the style keys set to nil." @@ -475,7 +486,7 @@ [{:keys [editor-styles editor-state editor-instance attrs shape] :as options}] (cond (some? editor-styles) (v3-current-text-values options) - (some? editor-instance) (v2-current-text-values options) + (v2-editor-live? editor-instance) (v2-current-text-values options) (some? editor-state) (v1-current-paragraph-values options) :else (shape-current-values shape txt/is-paragraph-node? attrs))) @@ -492,7 +503,7 @@ [{:keys [editor-styles editor-state editor-instance attrs shape] :as options}] (cond (some? editor-styles) (v3-current-text-values options) - (some? editor-instance) (v2-current-text-values options) + (v2-editor-live? editor-instance) (v2-current-text-values options) (some? editor-state) (v1-current-text-values options) :else (shape-current-values shape txt/is-text-node? attrs))) diff --git a/frontend/test/frontend_tests/data/workspace_texts_test.cljs b/frontend/test/frontend_tests/data/workspace_texts_test.cljs index 2280456eb4..52280e43e1 100644 --- a/frontend/test/frontend_tests/data/workspace_texts_test.cljs +++ b/frontend/test/frontend_tests/data/workspace_texts_test.cljs @@ -643,6 +643,33 @@ (t/is (= 50 (:width result))) (t/is (= 25 (:height result)))))) +;; --------------------------------------------------------------------------- +;; Tests: values read from a disposed v2 editor +;; --------------------------------------------------------------------------- + +(defn- disposed-v2-editor + "A v2 editor after `dispose!`: reading its style throws." + [] + (let [editor #js {}] + (js/Object.defineProperty editor "currentStyle" + #js {:get (fn [] (throw (js/TypeError. "Cannot read properties of null (reading 'currentStyle')")))}) + editor)) + +;; Scenario: the sidebar renders once more with the editor it saw last, +;; after closing the text editor disposed it. Proves: the values come from +;; the shape instead of crashing the workspace. +(t/deftest current-values-of-a-disposed-v2-editor-come-from-the-shape + (let [shape (make-text-shape) + editor (disposed-v2-editor)] + (t/is (= (dwt/current-text-values {:shape shape :attrs txt/text-node-attrs}) + (dwt/current-text-values {:editor-instance editor + :shape shape + :attrs txt/text-node-attrs}))) + (t/is (= (dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs}) + (dwt/current-paragraph-values {:editor-instance editor + :shape shape + :attrs txt/paragraph-attrs}))))) + ;; --------------------------------------------------------------------------- ;; same-styled-text? ;; ---------------------------------------------------------------------------