0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-13 15:31:26 -05:00

Fix text editor issues

This commit is contained in:
alonso.torres 2021-07-14 14:45:03 +02:00
parent 69e256ab86
commit 26467187c4
5 changed files with 218 additions and 27 deletions

View file

@ -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))))

View file

@ -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}]]))

View file

@ -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)))

View file

@ -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)))))

View file

@ -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();
}