From 4360c1fe4b4c599f941bf575a82f1d54a38305a3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 7 Jan 2022 11:31:08 +0100 Subject: [PATCH] :bug: Improved behaviour on text options when not text is selected --- CHANGES.md | 3 +- .../main/ui/workspace/shapes/text/editor.cljs | 63 ++++++++++--------- frontend/src/app/util/text_editor.cljs | 44 +++++++++++++ frontend/src/app/util/text_editor_impl.js | 41 ++++++++++-- 4 files changed, 117 insertions(+), 34 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1f9447cd3..169624d0a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,6 +38,7 @@ - Fix problem when resizing texts inside groups [Taiga #2310](https://tree.taiga.io/project/penpot/issue/2310) - Fix problem with multiple exports [Taiga #2468](https://tree.taiga.io/project/penpot/issue/2468) - Allow import to continue from recoverable failures [#1412](https://github.com/penpot/penpot/issues/1412) +- Improved behaviour on text options when not text is selected [Taiga #2390](https://tree.taiga.io/project/penpot/issue/2390) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) @@ -81,8 +82,6 @@ ### :arrow_up: Deps updates - Update log4j2 dependency. ->>>>>>> main - # 1.10.2-beta 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 47880bac0..fad657938 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -7,7 +7,6 @@ (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] @@ -72,20 +71,18 @@ (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))) +(defn get-blocks-to-setup [block-changes] + (->> block-changes + (filter (fn [[_ v]] + (nil? (:old v)))) + (mapv first))) - :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 {})))) +(defn get-blocks-to-add-styles + [block-changes] + (->> block-changes + (filter (fn [[_ v]] + (and (not= (:old v) (:new v)) (= (:old v) "")))) + (mapv first))) (mf/defc text-shape-edit-html {::mf/wrap [mf/memo] @@ -143,25 +140,35 @@ (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)) + (let [block-changes (ted/get-content-changes old-state state) + prev-data (ted/get-editor-current-inline-styles old-state) + block-to-setup (get-blocks-to-setup block-changes) + block-to-add-styles (get-blocks-to-add-styles block-changes)] + (-> state + (ted/setup-block-styles block-to-setup prev-data) + (ted/apply-block-styles-to-content block-to-add-styles))) state)))) on-change (mf/use-callback (fn [val] - (let [val (handle-change val) - val (if (true? @blurred) - (ted/add-editor-blur-selection val) - (ted/remove-editor-blur-selection val))] + (let [prev-val (mf/ref-val prev-value) + styleOverride (ted/get-style-override prev-val) + + ;; If the content and the selection are the same we keep the style override + keep-style? (and (some? styleOverride) + (ted/content-equals prev-val val) + (ted/selection-equals prev-val val)) + + val (cond-> (handle-change val) + @blurred + (ted/add-editor-blur-selection) + + (not @blurred) + (ted/remove-editor-blur-selection) + + keep-style? + (ted/set-style-override styleOverride))] (st/emit! (dwt/update-editor-state shape val))))) on-editor diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index 06738d20e..f1d0a7735 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -111,6 +111,16 @@ [state] (impl/cursorToEnd state)) +(defn setup-block-styles + [state blocks attrs] + (if (empty? blocks) + state + (->> blocks + (reduce + (fn [state block-key] + (impl/updateBlockData state block-key (clj->js attrs))) + state)))) + (defn apply-block-styles-to-content [state blocks] (if (empty? blocks) @@ -130,3 +140,37 @@ (defn insert-text [state text attrs] (let [style (txt/attrs-to-styles attrs)] (impl/insertText state text (clj->js attrs) (clj->js style)))) + +(defn get-style-override [state] + (.getInlineStyleOverride state)) + +(defn set-style-override [state inline-style] + (impl/setInlineStyleOverride state inline-style)) + +(defn content-equals [state other] + (.equals (.getCurrentContent state) (.getCurrentContent other))) + +(defn selection-equals [state other] + (impl/selectionEquals (.getSelection state) (.getSelection other))) + +(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)] + (merge + (into {} + (comp (filter #(contains? new-blocks (first %))) + (map (fn [[bkey bstate]] + [bkey + {:old (get bstate "text") + :new (get-in new-blocks [bkey "text"])}]))) + old-blocks) + (into {} + (comp (filter #(not (contains? old-blocks (first %)))) + (map (fn [[bkey bstate]] + [bkey + {:old nil + :new (get bstate "text")}]))) + new-blocks)))) diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index 92b01533b..6a8cbc178 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -121,15 +121,33 @@ export function updateCurrentBlockData(state, attrs) { return EditorState.push(state, content, "change-block-data"); } +function addStylesToOverride(styles, other) { + let result = styles; + + for (let style of other) { + const [p, k, v] = style.split("$$$"); + const prefix = [p, k, ""].join("$$$"); + + const curValue = result.find((it) => it.startsWith(prefix)) + if (curValue) { + result = result.remove(curValue); + } + result = result.add(style); + } + return result +} + export function applyInlineStyle(state, styles) { const userSelection = state.getSelection(); let selection = userSelection; + let result = state; if (selection.isCollapsed()) { - selection = getSelectAllSelection(state); + const currentOverride = state.getCurrentInlineStyle() || new OrderedSet(); + const styleOverride = addStylesToOverride(currentOverride, styles) + return EditorState.setInlineStyleOverride(state, styleOverride); } - let result = state; let content = null; for (let style of styles) { @@ -300,6 +318,7 @@ export function getBlockData(state, blockKey) { export function updateBlockData(state, blockKey, data) { const userSelection = state.getSelection(); + const inlineStyleOverride = state.getInlineStyleOverride(); const content = state.getCurrentContent(); const block = content.getBlockForKey(blockKey); const newBlock = mergeBlockData(block, data); @@ -312,8 +331,10 @@ export function updateBlockData(state, blockKey, data) { blockData ); - const result = EditorState.push(state, newContent, 'change-block-data'); - return EditorState.acceptSelection(result, userSelection); + let result = EditorState.push(state, newContent, 'change-block-data'); + result = EditorState.acceptSelection(result, userSelection); + result = EditorState.setInlineStyleOverride(result, inlineStyleOverride); + return result; } export function getSelection(state) { @@ -376,3 +397,15 @@ export function insertText(state, text, attrs, inlineStyles) { const resultSelection = SelectionState.createEmpty(selection.getStartKey()); return EditorState.push(state, newContent, 'insert-fragment'); } + +export function setInlineStyleOverride(state, inlineStyles) { + return EditorState.setInlineStyleOverride(state, inlineStyles); +} + +export function selectionEquals(selection, other) { + return selection.getAnchorKey() === other.getAnchorKey() && + selection.getAnchorOffset() === other.getAnchorOffset() && + selection.getFocusKey() === other.getFocusKey() && + selection.getFocusOffset() === other.getFocusOffset() && + selection.getIsBackward() === other.getIsBackward(); +}