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:
parent
69e256ab86
commit
26467187c4
5 changed files with 218 additions and 27 deletions
|
@ -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))))
|
||||
|
|
|
@ -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}]]))
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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)))))
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue