From 69e256ab8604d3c18fc455a288b04fbca3c823e6 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 12 Jul 2021 12:38:41 +0200 Subject: [PATCH 1/4] :sparkles: Moves cursor to position when clicking in the text box --- .../src/app/main/data/workspace/texts.cljs | 7 ++++++ .../main/ui/workspace/shapes/text/editor.cljs | 10 ++++++-- frontend/src/app/util/text_editor.cljs | 4 +++ frontend/src/app/util/text_editor_impl.js | 25 +++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index f928c01ce..289800da8 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -101,6 +101,13 @@ (update [_ state] (d/update-in-when state [:workspace-editor-state id] ted/editor-select-all)))) +(defn cursor-to-end + [{:keys [id] :as shape}] + (ptk/reify ::cursor-to-end + ptk/UpdateEvent + (update [_ state] + (d/update-in-when state [:workspace-editor-state id] ted/cursor-to-end)))) + ;; --- Helpers (defn- shape-current-values diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 285934422..0ba3a332f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -126,7 +126,13 @@ (fn [_ state] (st/emit! (dwt/update-editor-state shape (ted/editor-split-block state))) "handled")) - ] + + on-click + (mf/use-callback + (fn [event] + (when (dom/class? (dom/get-target event) "DraftEditor-root") + (st/emit! (dwt/cursor-to-end shape))) + (st/emit! (dwt/focus-editor))))] (mf/use-layout-effect on-mount) @@ -135,7 +141,7 @@ :style {:cursor cur/text :width (:width shape) :height (:height shape)} - :on-click (st/emitf (dwt/focus-editor)) + :on-click on-click :class (dom/classnames :align-top (= (:vertical-align content "top") "top") :align-center (= (:vertical-align content) "center") diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index 370f17f2d..c5336cf68 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -92,3 +92,7 @@ (defn remove-editor-blur-selection [state] (impl/removeBlurSelectionEntity state)) + +(defn cursor-to-end + [state] + (impl/cursorToEnd state)) diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index 9255ec621..c966f0527 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -56,6 +56,18 @@ function getSelectAllSelection(state) { }); } +function getCursorInEndPosition(state) { + const content = state.getCurrentContent(); + const lastBlock = content.getBlockMap().last(); + + return new SelectionState({ + "anchorKey": lastBlock.getKey(), + "anchorOffset": lastBlock.getLength(), + "focusKey": lastBlock.getKey(), + "focusOffset": lastBlock.getLength() + }); +} + export function selectAll(state) { return EditorState.forceSelection(state, getSelectAllSelection(state)); } @@ -209,3 +221,16 @@ export function removeInlineStylePrefix(contentState, selectionState, stylePrefi return block.set("characterList", chars); }); } + +export function cursorToEnd(state) { + const newSelection = getCursorInEndPosition(state); + const selection = state.getSelection(); + + let content = state.getCurrentContent(); + content = Modifier.applyEntity(content, newSelection, null); + + state = EditorState.forceSelection(state, newSelection); + state = EditorState.push(state, content, "apply-entity"); + + return state; +} From 26467187c48687e4eae1f01dab62e5b577838587 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Jul 2021 14:45:03 +0200 Subject: [PATCH 2/4] :sparkles: Fix text editor issues --- .../src/app/main/data/workspace/texts.cljs | 10 ++ .../main/ui/workspace/shapes/text/editor.cljs | 67 +++++++-- .../workspace/sidebar/options/menus/text.cljs | 5 +- frontend/src/app/util/text_editor.cljs | 36 ++++- frontend/src/app/util/text_editor_impl.js | 127 ++++++++++++++++-- 5 files changed, 218 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 289800da8..11dad4b70 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -316,3 +316,13 @@ (rx/race resize-batch change-page) (rx/of #(dissoc % ::handling-texts)))) (rx/empty)))))) + +(defn save-font + [data] + (ptk/reify ::save-font + ptk/UpdateEvent + (update [_ state] + ;; Check if the data has any multiple + (assoc-in state + [:workspace-local :defaults :font] + data)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 0ba3a332f..26617eb3e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.shapes.text.editor (:require ["draft-js" :as draft] + [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.text :as txt] [app.main.data.workspace :as dw] @@ -32,7 +33,7 @@ (let [bprops (obj/get props "blockProps") data (obj/get bprops "data") style (sts/generate-paragraph-styles (obj/get bprops "shape") - (obj/get bprops "data")) + (obj/get bprops "data")) dir (:text-direction data "auto")] @@ -56,12 +57,35 @@ :shape shape}} nil))) +(defn styles-fn [styles content] + (if (= (.getText content) "") + (-> (.getData content) + (.toJS) + (js->clj :keywordize-keys true) + (sts/generate-text-styles)) + (-> (txt/styles-to-attrs styles) + (sts/generate-text-styles)))) + (def default-decorator (ted/create-decorator "PENPOT_SELECTION" selection-component)) (def empty-editor-state (ted/create-editor-state nil default-decorator)) +(defn get-content-changes + [old-state state] + (let [old-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js old-state))) + :keywordize-keys false) + new-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js state))) + :keywordize-keys false)] + (->> old-blocks + (d/mapm + (fn [bkey bstate] + {:old (get bstate "text") + :new (get-in new-blocks [bkey "text"])})) + (filter #(contains? new-blocks (first %))) + (into {})))) + (mf/defc text-shape-edit-html {::mf/wrap [mf/memo] ::mf/wrap-props false @@ -106,13 +130,38 @@ (fn [_] (reset! blured false))) + prev-value (mf/use-ref state) + + ;; Effect that keeps updated the `prev-value` reference + _ (mf/use-effect + (mf/deps state) + #(mf/set-ref-val! prev-value state)) + + handle-change + (mf/use-callback + (fn [state] + (let [old-state (mf/ref-val prev-value)] + (if (and (some? state) (some? old-state)) + (let [block-states (get-content-changes old-state state) + + block-to-add-styles + (->> block-states + (filter + (fn [[_ v]] + (and (not= (:old v) (:new v)) + (= (:old v) "")))) + (mapv first))] + (ted/apply-block-styles-to-content state block-to-add-styles)) + state)))) + on-change (mf/use-callback (fn [val] - (let [val (if (true? @blured) - (ted/add-editor-blur-selection val) - (ted/remove-editor-blur-selection val))] - (st/emit! (dwt/update-editor-state shape val))))) + (let [val (handle-change val)] + (let [val (if (true? @blured) + (ted/add-editor-blur-selection val) + (ted/remove-editor-blur-selection val))] + (st/emit! (dwt/update-editor-state shape val)))))) on-editor (mf/use-callback @@ -124,7 +173,9 @@ handle-return (mf/use-callback (fn [_ state] - (st/emit! (dwt/update-editor-state shape (ted/editor-split-block state))) + (let [state (ted/editor-split-block state) + state (handle-change state)] + (st/emit! (dwt/update-editor-state shape state))) "handled")) on-click @@ -152,9 +203,7 @@ :on-focus on-focus :handle-return handle-return :strip-pasted-styles true - :custom-style-fn (fn [styles _] - (-> (txt/styles-to-attrs styles) - (sts/generate-text-styles))) + :custom-style-fn styles-fn :block-renderer-fn #(render-block % shape) :ref on-editor :editor-state state}]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index d9f9ec455..6bd83ade7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -220,7 +220,10 @@ emit-update! (mf/use-callback + (mf/deps values) (fn [id attrs] + (st/emit! (dwt/save-font (merge values attrs))) + (let [attrs (select-keys attrs root-attrs)] (when-not (empty? attrs) (st/emit! (dwt/update-root-attrs {:id id :attrs attrs})))) @@ -235,7 +238,7 @@ on-change (mf/use-callback - (mf/deps ids) + (mf/deps ids emit-update!) (fn [attrs] (run! #(emit-update! % attrs) ids))) diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index c5336cf68..790609f6f 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -70,8 +70,11 @@ (defn get-editor-current-inline-styles [state] - (-> (.getCurrentInlineStyle ^js state) - (txt/styles-to-attrs))) + (if (impl/isCurrentEmpty state) + (let [block (impl/getCurrentBlock state)] + (get-editor-block-data block)) + (-> (.getCurrentInlineStyle ^js state) + (txt/styles-to-attrs)))) (defn update-editor-current-block-data [state attrs] @@ -79,7 +82,18 @@ (defn update-editor-current-inline-styles [state attrs] - (impl/applyInlineStyle state (txt/attrs-to-styles attrs))) + (let [update-blocks + (fn [state block-key] + (if (empty? (impl/getBlockContent state block-key)) + (impl/updateBlockData state block-key (clj->js attrs)) + + (let [attrs (-> (impl/getInlineStyle state block-key 0) + (txt/styles-to-attrs))] + (impl/updateBlockData state block-key (clj->js attrs))))) + + state (impl/applyInlineStyle state (txt/attrs-to-styles attrs)) + selected (impl/getSelectedBlocks state)] + (reduce update-blocks state selected))) (defn editor-split-block [state] @@ -96,3 +110,19 @@ (defn cursor-to-end [state] (impl/cursorToEnd state)) + +(defn apply-block-styles-to-content + [state blocks] + (if (empty? blocks) + state + (let [selection (impl/getSelection state) + redfn + (fn [state bkey] + (let [attrs (-> (impl/getBlockData state bkey) + (js->clj :keywordize-keys true))] + (-> state + (impl/selectBlock bkey) + (impl/applyInlineStyle (txt/attrs-to-styles attrs)))))] + (as-> state $ + (reduce redfn $ blocks) + (impl/setSelection $ selection))))) diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index c966f0527..6eb1983cb 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -22,6 +22,23 @@ function isDefined(v) { return v !== undefined && v !== null; } +function mergeBlockData(block, newData) { + let data = block.getData(); + + for (let key of Object.keys(newData)) { + const oldVal = data.get(key); + if (oldVal === newData[key]) { + data = data.delete(key); + } else { + data = data.set(key, newData[key]); + } + } + + return block.merge({ + data: data + }); +} + export function createEditorState(content, decorator) { if (content === null) { return EditorState.createEmpty(decorator); @@ -95,26 +112,19 @@ export function updateCurrentBlockData(state, attrs) { let content = state.getCurrentContent(); content = modifySelectedBlocks(content, selection, (block) => { - let data = block.getData(); - for (let key of Object.keys(attrs)) { - const oldVal = data.get(key); - if (oldVal === attrs[key]) { - data = data.delete(key); - } else { - data = data.set(key, attrs[key]); - } - } - - return block.merge({ - data: data - }); + return mergeBlockData(block, attrs); }); return EditorState.push(state, content, "change-block-data"); } export function applyInlineStyle(state, styles) { - const selection = state.getSelection(); + let selection = state.getSelection(); + + if (selection.isCollapsed()) { + selection = selection.set("anchorOffset", 0); + } + let content = null; for (let style of styles) { @@ -234,3 +244,92 @@ export function cursorToEnd(state) { return state; } + +export function isCurrentEmpty(state) { + const selection = state.getSelection(); + + if (!selection.isCollapsed()) { + return false; + } + + const blockKey = selection.getStartKey(); + const content = state.getCurrentContent(); + + const block = content.getBlockForKey(blockKey); + + return block.getText() === ""; +} + +/* + Returns the block keys between a selection +*/ +export function getSelectedBlocks(state) { + const selection = state.getSelection(); + const startKey = selection.getStartKey(); + const endKey = selection.getEndKey(); + const content = state.getCurrentContent(); + const result = [ startKey ]; + + let currentKey = startKey; + + while (currentKey !== endKey) { + const currentBlock = content.getBlockAfter(currentKey); + currentKey = currentBlock.getKey(); + result.push(currentKey); + } + + return result; +} + +export function getBlockContent(state, blockKey) { + const content = state.getCurrentContent(); + const block = content.getBlockForKey(blockKey); + return block.getText(); +} + +export function getBlockData(state, blockKey) { + const content = state.getCurrentContent(); + const block = content.getBlockForKey(blockKey); + return block && block.getData().toJS(); +} + +export function updateBlockData(state, blockKey, data) { + const userSelection = state.getSelection(); + const content = state.getCurrentContent(); + const block = content.getBlockForKey(blockKey); + const newBlock = mergeBlockData(block, data); + + const blockData = newBlock.getData(); + + const newContent = Modifier.setBlockData( + state.getCurrentContent(), + SelectionState.createEmpty(blockKey), + blockData + ); + + const result = EditorState.push(state, newContent, 'change-block-data'); + return EditorState.acceptSelection(result, userSelection) +} + +export function getSelection(state) { + return state.getSelection(); +} + +export function setSelection(state, selection) { + return EditorState.acceptSelection(state, selection); +} + +export function selectBlock(state, blockKey) { + const block = state.getCurrentContent().getBlockForKey(blockKey); + const length = block.getText().length; + const selection = SelectionState.createEmpty(blockKey).merge({ + focusOffset: length + }); + return EditorState.acceptSelection(state, selection); +} + +export function getInlineStyle(state, blockKey, offset) { + const content = state.getCurrentContent(); + const block = content.getBlockForKey(blockKey); + return block.getInlineStyleAt(offset).toJS(); +} From 9ebafddac21d99f6772d38cdce97d8939317d376 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Jul 2021 21:51:30 +0200 Subject: [PATCH 3/4] :sparkles: Make last font used the default for next text box --- common/src/app/common/text.cljc | 3 ++- frontend/src/app/main/data/workspace.cljs | 18 ++++++++++++++---- .../src/app/main/data/workspace/texts.cljs | 14 +++++++++----- .../src/app/main/ui/shapes/text/styles.cljs | 4 ++-- .../workspace/sidebar/options/menus/text.cljs | 2 +- .../workspace/sidebar/options/shapes/text.cljs | 2 ++ frontend/src/app/util/text_editor_impl.js | 2 +- 7 files changed, 31 insertions(+), 14 deletions(-) diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index 9f184e7e9..f4c450f1a 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -64,7 +64,8 @@ (defn ^boolean is-text-node? [node] - (string? (:text node))) + (and (string? (:text node)) + (not= (:text node) ""))) (defn ^boolean is-paragraph-node? [node] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 7c0a57c7d..8ec75ebf3 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -207,7 +207,16 @@ :workspace-file :workspace-project :workspace-media-objects - :workspace-persistence)) + :workspace-persistence + :workspace-local + :workspace-data + :workspace-editor-state + :workspace-undo + :current-file-id + :current-project-id + :workspace-layout + :workspace-libraries + :workspace-presence)) ptk/WatchEvent (watch [_ _ _] @@ -242,9 +251,10 @@ (update [_ state] (let [page-id (or page-id (get-in state [:workspace-data :pages 0])) local (-> (:workspace-local state) - (dissoc :edition) - (dissoc :edit-path) - (dissoc :selected))] + (dissoc + :edition + :edit-path + :selected))] (-> state (assoc-in [:workspace-cache page-id] local) (dissoc :current-page-id :workspace-local :trimmed-page :workspace-drawing)))))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 11dad4b70..390e415db 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -75,11 +75,14 @@ (ptk/reify ::initialize-editor-state ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-editor-state id] - (fn [_] - (ted/create-editor-state - (some->> content ted/import-content) - decorator)))) + (let [text-state (some->> content ted/import-content) + attrs (get-in state [:workspace-local :defaults :font]) + + editor (cond-> (ted/create-editor-state text-state decorator) + (and (nil? content) (some? attrs)) + (ted/update-editor-current-block-data attrs))] + (-> state + (assoc-in [:workspace-editor-state id] editor)))) ptk/WatchEvent (watch [_ _ stream] @@ -326,3 +329,4 @@ (assoc-in state [:workspace-local :defaults :font] data)))) + diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 9d247e13f..ec76c8f61 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -47,8 +47,8 @@ text-align (:text-align data "start") grow-type (:grow-type shape) - base #js {:fontSize (str (:font-size txt/default-text-attrs) "px") - :lineHeight (:line-height txt/default-text-attrs) + base #js {:fontSize (str (:font-size data (:font-size txt/default-text-attrs)) "px") + :lineHeight (:line-height data (:line-height txt/default-text-attrs)) :margin "inherit"}] (cond-> base (some? line-height) (obj/set! "lineHeight" line-height) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index 6bd83ade7..ac2dc026f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -222,7 +222,7 @@ (mf/use-callback (mf/deps values) (fn [id attrs] - (st/emit! (dwt/save-font (merge values attrs))) + (st/emit! (dwt/save-font (merge txt/default-text-attrs values attrs))) (let [attrs (select-keys attrs root-attrs)] (when-not (empty? attrs) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 298983432..a15c66e02 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.main.data.workspace.texts :as dwt] [app.main.refs :as refs] + [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] @@ -42,6 +43,7 @@ text-values (d/merge (select-keys shape [:grow-type]) + (select-keys shape fill-attrs) (dwt/current-root-values {:shape shape :attrs root-attrs}) diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index 6eb1983cb..3c735b97c 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -122,7 +122,7 @@ export function applyInlineStyle(state, styles) { let selection = state.getSelection(); if (selection.isCollapsed()) { - selection = selection.set("anchorOffset", 0); + selection = getSelectAllSelection(state); } let content = null; From 1fc518297926f1051c3b959f18299b4ffc24df6c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 16 Jul 2021 13:09:57 +0200 Subject: [PATCH 4/4] :bug: Fix text focus issues --- backend/src/app/loggers/mattermost.clj | 2 +- common/src/app/common/pages/migrations.cljc | 2 +- .../src/app/main/data/workspace/texts.cljs | 13 +++--- .../main/ui/components/editable_select.cljs | 37 ++++++++++++---- .../main/ui/workspace/shapes/text/editor.cljs | 10 ++--- .../workspace/sidebar/options/menus/text.cljs | 42 +++++++++++++------ .../sidebar/options/menus/typography.cljs | 41 ++++++++++++------ .../sidebar/options/shapes/text.cljs | 3 +- frontend/src/app/util/dom.cljs | 11 +++-- frontend/src/app/util/text_editor_impl.js | 14 ++++--- 10 files changed, 118 insertions(+), 57 deletions(-) diff --git a/backend/src/app/loggers/mattermost.clj b/backend/src/app/loggers/mattermost.clj index fc0109bdd..f4a60e5c1 100644 --- a/backend/src/app/loggers/mattermost.clj +++ b/backend/src/app/loggers/mattermost.clj @@ -60,7 +60,7 @@ (a/close! output))) (defn- send-mattermost-notification! - [cfg {:keys [host version id] :as cdata}] + [cfg {:keys [host id] :as cdata}] (try (let [uri (:uri cfg) text (str "Unhandled exception (host: " host ", url: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n" diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index f06c79563..104e1fe4c 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -258,7 +258,7 @@ (defmethod migrate 11 [data] - (letfn [(update-object [objects id shape] + (letfn [(update-object [objects _id shape] (if (= :frame (:type shape)) (d/update-when shape :shapes (fn [shapes] (filterv (fn [id] (contains? objects id)) shapes))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 390e415db..1163a5687 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -203,8 +203,11 @@ (when-not (some? (get-in state [:workspace-editor-state id])) (let [objects (wsh/lookup-page-objects state) shape (get objects id) + update-node? (fn [node] + (or (txt/is-text-node? node) + (txt/is-paragraph-node? node))) - update-fn #(update-shape % txt/is-text-node? attrs/merge attrs) + update-fn #(update-shape % update-node? attrs/merge attrs) shape-ids (cond (= (:type shape) :text) [id] (= (:type shape) :group) (cp/get-children id objects))] (rx/of (dch/update-shapes shape-ids update-fn))))))) @@ -325,8 +328,8 @@ (ptk/reify ::save-font ptk/UpdateEvent (update [_ state] - ;; Check if the data has any multiple - (assoc-in state - [:workspace-local :defaults :font] - data)))) + (let [multiple? (->> data vals (d/seek #(= % :multiple)))] + (cond-> state + (not multiple?) + (assoc-in [:workspace-local :defaults :font] data)))))) diff --git a/frontend/src/app/main/ui/components/editable_select.cljs b/frontend/src/app/main/ui/components/editable_select.cljs index 40c13e770..127e0e403 100644 --- a/frontend/src/app/main/ui/components/editable_select.cljs +++ b/frontend/src/app/main/ui/components/editable_select.cljs @@ -14,19 +14,23 @@ [app.util.timers :as timers] [rumext.alpha :as mf])) -(mf/defc editable-select [{:keys [value type options class on-change placeholder]}] +(mf/defc editable-select [{:keys [value type options class on-change placeholder on-blur]}] (let [state (mf/use-state {:id (uuid/next) :is-open? false :current-value value :top nil :left nil :bottom nil}) + + emit-blur? (mf/use-ref nil) + open-dropdown #(swap! state assoc :is-open? true) close-dropdown #(swap! state assoc :is-open? false) select-item (fn [value] (fn [_] (swap! state assoc :current-value value) - (when on-change (on-change value)))) + (when on-change (on-change value)) + (when on-blur (on-blur)))) as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item])) @@ -55,21 +59,38 @@ assoc :left left :top top - :bottom bottom))))))] + :bottom bottom)))))) + + handle-focus + (mf/use-callback + (fn [] + (mf/set-ref-val! emit-blur? false))) + + handle-blur + (mf/use-callback + (fn [] + (mf/set-ref-val! emit-blur? true) + (timers/schedule + 200 + (fn [] + (when (and on-blur (mf/ref-val emit-blur?)) (on-blur))))))] (mf/use-effect - (mf/deps value) - #(reset! state {:current-value value})) + (mf/deps value (:current-value @state)) + #(when (not= value (:current-value @state)) + (reset! state {:current-value value}))) (mf/use-effect - (mf/deps options) - #(reset! state {:is-open? false - :current-value value})) + (mf/deps (:is-open? @state)) + (fn [] + (mf/set-ref-val! emit-blur? (not (:is-open? @state))))) [:div.editable-select {:class class :ref on-node-load} [:input.input-text {:value (or (-> @state :current-value value->label) "") :on-change handle-change-input + :on-focus handle-focus + :on-blur handle-blur :placeholder placeholder :type type}] [:span.dropdown-button {:on-click open-dropdown} i/arrow-down] diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 26617eb3e..e2321b4de 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -157,11 +157,11 @@ on-change (mf/use-callback (fn [val] - (let [val (handle-change val)] - (let [val (if (true? @blured) - (ted/add-editor-blur-selection val) - (ted/remove-editor-blur-selection val))] - (st/emit! (dwt/update-editor-state shape val)))))) + (let [val (handle-change val) + val (if (true? @blured) + (ted/add-editor-blur-selection val) + (ted/remove-editor-blur-selection val))] + (st/emit! (dwt/update-editor-state shape val))))) on-editor (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index ac2dc026f..5f55a9ab2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -20,6 +20,7 @@ [app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry typography-options]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] + [app.util.timers :as tm] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -80,11 +81,12 @@ (def attrs (d/concat #{} shape-attrs root-attrs paragraph-attrs text-attrs)) (mf/defc text-align-options - [{:keys [values on-change] :as props}] + [{:keys [values on-change on-blur] :as props}] (let [{:keys [text-align]} values handle-change (fn [_ new-align] - (on-change {:text-align new-align}))] + (on-change {:text-align new-align}) + (when (some? on-blur) (on-blur)))] ;; --- Align [:div.align-icons @@ -110,10 +112,12 @@ i/text-align-justify]])) (mf/defc text-direction-options - [{:keys [values on-change] :as props}] + [{:keys [values on-change on-blur] :as props}] (let [direction (:text-direction values) - handle-change (fn [_ val] - (on-change {:text-direction val}))] + handle-change + (fn [_ val] + (on-change {:text-direction val}) + (when (some? on-blur) (on-blur)))] ;; --- Align [:div.align-icons [:span.tooltip.tooltip-bottom-left @@ -128,12 +132,13 @@ i/text-direction-rtl]])) (mf/defc vertical-align - [{:keys [values on-change] :as props}] + [{:keys [values on-change on-blur] :as props}] (let [{:keys [vertical-align]} values vertical-align (or vertical-align "top") handle-change (fn [_ new-align] - (on-change {:vertical-align new-align}))] + (on-change {:vertical-align new-align}) + (when (some? on-blur) (on-blur)))] [:div.align-icons [:span.tooltip.tooltip-bottom-left @@ -153,11 +158,12 @@ i/align-bottom]])) (mf/defc grow-options - [{:keys [ids values] :as props}] + [{:keys [ids values on-blur] :as props}] (let [grow-type (:grow-type values) handle-change-grow (fn [_ grow-type] - (st/emit! (dch/update-shapes ids #(assoc % :grow-type grow-type))))] + (st/emit! (dch/update-shapes ids #(assoc % :grow-type grow-type))) + (when (some? on-blur) (on-blur)))] [:div.align-icons [:span.tooltip.tooltip-bottom @@ -177,11 +183,12 @@ i/auto-height]])) (mf/defc text-decoration-options - [{:keys [values on-change] :as props}] + [{:keys [values on-change on-blur] :as props}] (let [text-decoration (or (:text-decoration values) "none") handle-change (fn [_ type] - (on-change {:text-decoration type}))] + (on-change {:text-decoration type}) + (when (some? on-blur) (on-blur)))] [:div.align-icons [:span.tooltip.tooltip-bottom {:alt (tr "workspace.options.text-options.none") @@ -222,7 +229,8 @@ (mf/use-callback (mf/deps values) (fn [id attrs] - (st/emit! (dwt/save-font (merge txt/default-text-attrs values attrs))) + (st/emit! (dwt/save-font (-> (merge txt/default-text-attrs values attrs) + (select-keys text-attrs)))) (let [attrs (select-keys attrs root-attrs)] (when-not (empty? attrs) @@ -290,7 +298,15 @@ opts #js {:ids ids :values values - :on-change on-change}] + :on-change on-change + :on-blur + (fn [] + (tm/schedule + 100 + (fn [] + (when (not= "INPUT" (-> (dom/get-active) (dom/get-tag-name))) + (let [node (dom/get-element-by-class "public-DraftEditor-content")] + (dom/focus! node))))))}] [:div.element-set [:div.element-set-title diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index ca5e77631..68c503fcc 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -230,7 +230,7 @@ :current? (= (:id font) (:id selected))}]))) (mf/defc font-options - [{:keys [values on-change] :as props}] + [{:keys [values on-change on-blur] :as props}] (let [{:keys [font-id font-size font-variant-id]} values font-id (or font-id (:font-id txt/default-text-attrs)) @@ -271,18 +271,26 @@ :font-family (:family font) :font-variant-id new-variant-id :font-weight (:weight variant) - :font-style (:style variant)})))) + :font-style (:style variant)}) + (dom/blur! (dom/get-target event))))) on-font-select (mf/use-callback (mf/deps change-font) (fn [font*] (when (not= font font*) - (change-font (:id font*))))) + (change-font (:id font*))) + + (when (some? on-blur) + (on-blur)))) on-font-selector-close (mf/use-callback - #(reset! open-selector? false))] + (fn [] + (reset! open-selector? false) + (when (some? on-blur) + (on-blur)) + ))] [:* (when @open-selector? @@ -314,12 +322,14 @@ :options size-options :type "number" :placeholder "--" - :on-change on-font-size-change}]) + :on-change on-font-size-change + :on-blur on-blur}]) [:select.input-select.variant-option {:disabled (= font-id :multiple) :value (attr->string font-variant-id) - :on-change on-font-variant-change} + :on-change on-font-variant-change + :on-blur on-blur} (when (or (= font-id :multiple) (= font-variant-id :multiple)) [:option {:value ""} "--"]) (for [variant (:variants font)] @@ -329,7 +339,7 @@ (mf/defc spacing-options - [{:keys [values on-change] :as props}] + [{:keys [values on-change on-blur] :as props}] (let [{:keys [line-height letter-spacing]} values @@ -353,7 +363,8 @@ :max "200" :value (attr->string line-height) :placeholder (tr "settings.multiple") - :on-change #(handle-change % :line-height)}]] + :on-change #(handle-change % :line-height) + :on-blur on-blur}]] [:div.input-icon [:span.icon-before.tooltip.tooltip-bottom @@ -366,18 +377,21 @@ :max "200" :value (attr->string letter-spacing) :placeholder (tr "settings.multiple") - :on-change #(handle-change % :letter-spacing)}]]])) + :on-change #(handle-change % :letter-spacing) + :on-blur on-blur}]]])) (mf/defc text-transform-options - [{:keys [values on-change] :as props}] + [{:keys [values on-change on-blur] :as props}] (let [text-transform (or (:text-transform values) "none") handle-change (fn [_ type] - (on-change {:text-transform type}))] + (on-change {:text-transform type}) + (when (some? on-blur) (on-blur)))] [:div.align-icons [:span.tooltip.tooltip-bottom {:alt (tr "workspace.options.text-options.none") :class (dom/classnames :current (= "none" text-transform)) + :focus #(dom/prevent-default %) :on-click #(handle-change % "none")} i/minus] [:span.tooltip.tooltip-bottom @@ -397,11 +411,12 @@ i/titlecase]])) (mf/defc typography-options - [{:keys [ids editor values on-change]}] + [{:keys [ids editor values on-change on-blur]}] (let [opts #js {:editor editor :ids ids :values values - :on-change on-change}] + :on-change on-change + :on-blur on-blur}] [:div.element-set-content [:> font-options opts] [:div.row-flex diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index a15c66e02..a8eccfa4d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -9,10 +9,9 @@ [app.common.data :as d] [app.main.data.workspace.texts :as dwt] [app.main.refs :as refs] - [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] - [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu fill-attrs]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index f05f2a1a5..dfa4f4467 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -223,11 +223,13 @@ (defn focus! [node] - (.focus node)) + (when (some? node) + (.focus node))) (defn blur! [node] - (.blur node)) + (when (some? node) + (.blur node))) (defn fullscreen? [] @@ -291,8 +293,11 @@ (defn get-user-agent [] (.-userAgent globals/navigator)) +(defn get-active [] + (.-activeElement globals/document)) + (defn active? [node] - (= (.-activeElement globals/document) node)) + (= (get-active) node)) (defn get-data [^js node ^string attr] (.getAttribute node (str "data-" attr))) diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index 3c735b97c..18de4ec8f 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -34,7 +34,7 @@ function mergeBlockData(block, newData) { } } - return block.merge({ + return block.mergeDeep({ data: data }); } @@ -119,29 +119,31 @@ export function updateCurrentBlockData(state, attrs) { } export function applyInlineStyle(state, styles) { - let selection = state.getSelection(); + const userSelection = state.getSelection(); + let selection = userSelection; if (selection.isCollapsed()) { selection = getSelectAllSelection(state); } + let result = state; let content = null; for (let style of styles) { const [p, k, v] = style.split("$$$"); const prefix = [p, k, ""].join("$$$"); - content = state.getCurrentContent(); + content = result.getCurrentContent(); content = removeInlineStylePrefix(content, selection, prefix); if (v !== "z:null") { content = Modifier.applyInlineStyle(content, selection, style); } - state = EditorState.push(state, content, "change-inline-style"); + result = EditorState.push(result, content, "change-inline-style"); } - return state; + return EditorState.acceptSelection(result, userSelection); } export function splitBlockPreservingData(state) { @@ -308,7 +310,7 @@ export function updateBlockData(state, blockKey, data) { ); const result = EditorState.push(state, newContent, 'change-block-data'); - return EditorState.acceptSelection(result, userSelection) + return EditorState.acceptSelection(result, userSelection); } export function getSelection(state) {