diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 76b888e238..518d7f0b60 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -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)))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs index 98e82162e2..d23b376844 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs @@ -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 " diff --git a/frontend/src/app/util/text/content/styles.cljs b/frontend/src/app/util/text/content/styles.cljs index 4b801ee864..c67ca4d629 100644 --- a/frontend/src/app/util/text/content/styles.cljs +++ b/frontend/src/app/util/text/content/styles.cljs @@ -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") diff --git a/frontend/text-editor/src/editor/TextEditor.js b/frontend/text-editor/src/editor/TextEditor.js index 20828c1264..e7f82739b1 100644 --- a/frontend/text-editor/src/editor/TextEditor.js +++ b/frontend/text-editor/src/editor/TextEditor.js @@ -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; diff --git a/frontend/text-editor/src/editor/controllers/ChangeController.js b/frontend/text-editor/src/editor/controllers/ChangeController.js index 166f89a598..f83e697414 100644 --- a/frontend/text-editor/src/editor/controllers/ChangeController.js +++ b/frontend/text-editor/src/editor/controllers/ChangeController.js @@ -54,8 +54,12 @@ export class ChangeController extends EventTarget { return this.#hasPendingChanges; } + /** + * Handles timeout. + */ #onTimeout = () => { this.dispatchEvent(new Event("change")); + this.#hasPendingChanges = false; }; /**