mirror of
https://github.com/penpot/penpot.git
synced 2025-05-26 05:16:10 +02:00
✨ Allow to unselect the text alignment.
Defaulting to 'start' (rtl friendly).
This commit is contained in:
parent
ca52f4f8ea
commit
7bc91e7224
7 changed files with 298 additions and 216 deletions
|
@ -454,7 +454,6 @@
|
||||||
(if (empty? rch-operations) rch (conj rch rchg))
|
(if (empty? rch-operations) rch (conj rch rchg))
|
||||||
(if (empty? uch-operations) uch (conj uch uchg)))))))))))
|
(if (empty? uch-operations) uch (conj uch uchg)))))))))))
|
||||||
|
|
||||||
|
|
||||||
(defn update-shapes-recursive
|
(defn update-shapes-recursive
|
||||||
[ids f]
|
[ids f]
|
||||||
(us/assert ::coll-of-uuid ids)
|
(us/assert ::coll-of-uuid ids)
|
||||||
|
|
|
@ -121,8 +121,8 @@
|
||||||
;; --- TEXT EDITION IMPL
|
;; --- TEXT EDITION IMPL
|
||||||
|
|
||||||
(defn- update-shape
|
(defn- update-shape
|
||||||
[shape pred-fn attrs]
|
[shape pred-fn merge-fn attrs]
|
||||||
(let [merge-attrs #(attrs/merge % attrs)
|
(let [merge-attrs #(merge-fn % attrs)
|
||||||
transform #(txt/transform-nodes pred-fn merge-attrs %)]
|
transform #(txt/transform-nodes pred-fn merge-attrs %)]
|
||||||
(update shape :content transform)))
|
(update shape :content transform)))
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@
|
||||||
(let [objects (dwc/lookup-page-objects state)
|
(let [objects (dwc/lookup-page-objects state)
|
||||||
shape (get objects id)
|
shape (get objects id)
|
||||||
|
|
||||||
update-fn #(update-shape % txt/is-root-node? attrs)
|
update-fn #(update-shape % txt/is-root-node? attrs/merge attrs)
|
||||||
shape-ids (cond (= (:type shape) :text) [id]
|
shape-ids (cond (= (:type shape) :text) [id]
|
||||||
(= (:type shape) :group) (cp/get-children id objects))]
|
(= (:type shape) :group) (cp/get-children id objects))]
|
||||||
|
|
||||||
|
@ -154,7 +154,15 @@
|
||||||
(let [objects (dwc/lookup-page-objects state)
|
(let [objects (dwc/lookup-page-objects state)
|
||||||
shape (get objects id)
|
shape (get objects id)
|
||||||
|
|
||||||
update-fn #(update-shape % txt/is-paragraph-node? attrs)
|
merge-fn (fn [node attrs]
|
||||||
|
(reduce-kv (fn [node k v]
|
||||||
|
(if (= (get node k) v)
|
||||||
|
(dissoc node k)
|
||||||
|
(assoc node k v)))
|
||||||
|
node
|
||||||
|
attrs))
|
||||||
|
|
||||||
|
update-fn #(update-shape % txt/is-paragraph-node? merge-fn attrs)
|
||||||
shape-ids (cond (= (:type shape) :text) [id]
|
shape-ids (cond (= (:type shape) :text) [id]
|
||||||
(= (:type shape) :group) (cp/get-children id objects))]
|
(= (:type shape) :group) (cp/get-children id objects))]
|
||||||
|
|
||||||
|
@ -174,7 +182,7 @@
|
||||||
(let [objects (dwc/lookup-page-objects state)
|
(let [objects (dwc/lookup-page-objects state)
|
||||||
shape (get objects id)
|
shape (get objects id)
|
||||||
|
|
||||||
update-fn #(update-shape % txt/is-text-node? attrs)
|
update-fn #(update-shape % txt/is-text-node? attrs/merge attrs)
|
||||||
shape-ids (cond (= (:type shape) :text) [id]
|
shape-ids (cond (= (:type shape) :text) [id]
|
||||||
(= (:type shape) :group) (cp/get-children id objects))]
|
(= (:type shape) :group) (cp/get-children id objects))]
|
||||||
(rx/of (dwc/update-shapes shape-ids update-fn))))))))
|
(rx/of (dwc/update-shapes shape-ids update-fn))))))))
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
(defn generate-paragraph-styles
|
(defn generate-paragraph-styles
|
||||||
[shape data]
|
[shape data]
|
||||||
(let [line-height (:line-height data)
|
(let [line-height (:line-height data)
|
||||||
text-align (:text-align data)
|
text-align (:text-align data "start")
|
||||||
grow-type (:grow-type shape)
|
grow-type (:grow-type shape)
|
||||||
|
|
||||||
base #js {:fontSize (str (:font-size txt/default-text-attrs) "px")
|
base #js {:fontSize (str (:font-size txt/default-text-attrs) "px")
|
||||||
|
|
|
@ -26,22 +26,53 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(def text-typography-attrs [:typography-ref-id :typography-ref-file])
|
(def text-typography-attrs
|
||||||
(def text-fill-attrs [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient :fill :opacity ])
|
[:typography-ref-id
|
||||||
(def text-font-attrs [:font-id :font-family :font-variant-id :font-size :font-weight :font-style])
|
:typography-ref-file])
|
||||||
(def text-align-attrs [:text-align])
|
|
||||||
(def text-spacing-attrs [:line-height :letter-spacing])
|
|
||||||
(def text-valign-attrs [:vertical-align])
|
|
||||||
(def text-decoration-attrs [:text-decoration])
|
|
||||||
(def text-transform-attrs [:text-transform])
|
|
||||||
|
|
||||||
(def shape-attrs [:grow-type])
|
(def text-fill-attrs
|
||||||
(def root-attrs (d/concat text-valign-attrs
|
[:fill-color
|
||||||
text-align-attrs))
|
:fill-opacity
|
||||||
(def paragraph-attrs text-align-attrs)
|
:fill-color-ref-id
|
||||||
(def text-attrs (d/concat text-typography-attrs
|
:fill-color-ref-file
|
||||||
|
:fill-color-gradient])
|
||||||
|
|
||||||
|
(def text-font-attrs
|
||||||
|
[:font-id
|
||||||
|
:font-family
|
||||||
|
:font-variant-id
|
||||||
|
:font-size
|
||||||
|
:font-weight
|
||||||
|
:font-style])
|
||||||
|
|
||||||
|
(def text-align-attrs
|
||||||
|
[:text-align])
|
||||||
|
|
||||||
|
(def text-spacing-attrs
|
||||||
|
[:line-height
|
||||||
|
:letter-spacing])
|
||||||
|
|
||||||
|
(def text-valign-attrs
|
||||||
|
[:vertical-align])
|
||||||
|
|
||||||
|
(def text-decoration-attrs
|
||||||
|
[:text-decoration])
|
||||||
|
|
||||||
|
(def text-transform-attrs
|
||||||
|
[:text-transform])
|
||||||
|
|
||||||
|
(def shape-attrs
|
||||||
|
[:grow-type])
|
||||||
|
|
||||||
|
(def root-attrs
|
||||||
|
(d/concat text-valign-attrs text-align-attrs))
|
||||||
|
|
||||||
|
(def paragraph-attrs
|
||||||
|
text-align-attrs)
|
||||||
|
|
||||||
|
(def text-attrs
|
||||||
|
(d/concat text-typography-attrs
|
||||||
text-font-attrs
|
text-font-attrs
|
||||||
text-align-attrs
|
|
||||||
text-spacing-attrs
|
text-spacing-attrs
|
||||||
text-decoration-attrs
|
text-decoration-attrs
|
||||||
text-transform-attrs))
|
text-transform-attrs))
|
||||||
|
@ -51,9 +82,6 @@
|
||||||
(mf/defc text-align-options
|
(mf/defc text-align-options
|
||||||
[{:keys [ids values on-change] :as props}]
|
[{:keys [ids values on-change] :as props}]
|
||||||
(let [{:keys [text-align]} values
|
(let [{:keys [text-align]} values
|
||||||
|
|
||||||
text-align (or text-align "left")
|
|
||||||
|
|
||||||
handle-change
|
handle-change
|
||||||
(fn [event new-align]
|
(fn [event new-align]
|
||||||
(on-change {:text-align new-align}))]
|
(on-change {:text-align new-align}))]
|
||||||
|
@ -169,7 +197,7 @@
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [ids type values] :as props}]
|
[{:keys [ids type values] :as props}]
|
||||||
|
|
||||||
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
(let [file-id (mf/use-ctx ctx/current-file-id)
|
||||||
typographies (mf/deref refs/workspace-file-typography)
|
typographies (mf/deref refs/workspace-file-typography)
|
||||||
shared-libs (mf/deref refs/workspace-libraries)
|
shared-libs (mf/deref refs/workspace-libraries)
|
||||||
label (case type
|
label (case type
|
||||||
|
@ -194,14 +222,14 @@
|
||||||
typography (cond
|
typography (cond
|
||||||
(and (:typography-ref-id values)
|
(and (:typography-ref-id values)
|
||||||
(not= (:typography-ref-id values) :multiple)
|
(not= (:typography-ref-id values) :multiple)
|
||||||
(not= (:typography-ref-file values) current-file-id))
|
(not= (:typography-ref-file values) file-id))
|
||||||
(-> shared-libs
|
(-> shared-libs
|
||||||
(get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)])
|
(get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)])
|
||||||
(assoc :file-id (:typography-ref-file values)))
|
(assoc :file-id (:typography-ref-file values)))
|
||||||
|
|
||||||
(and (:typography-ref-id values)
|
(and (:typography-ref-id values)
|
||||||
(not= (:typography-ref-id values) :multiple)
|
(not= (:typography-ref-id values) :multiple)
|
||||||
(= (:typography-ref-file values) current-file-id))
|
(= (:typography-ref-file values) file-id))
|
||||||
(get typographies (:typography-ref-id values)))
|
(get typographies (:typography-ref-id values)))
|
||||||
|
|
||||||
on-convert-to-typography
|
on-convert-to-typography
|
||||||
|
@ -218,7 +246,7 @@
|
||||||
(let [id (uuid/next)]
|
(let [id (uuid/next)]
|
||||||
(st/emit! (dwl/add-typography (assoc typography :id id) false))
|
(st/emit! (dwl/add-typography (assoc typography :id id) false))
|
||||||
(run! #(emit-update! % {:typography-ref-id id
|
(run! #(emit-update! % {:typography-ref-id id
|
||||||
:typography-ref-file current-file-id}) ids)))))
|
:typography-ref-file file-id}) ids)))))
|
||||||
|
|
||||||
handle-detach-typography
|
handle-detach-typography
|
||||||
(fn []
|
(fn []
|
||||||
|
@ -228,7 +256,7 @@
|
||||||
|
|
||||||
handle-change-typography
|
handle-change-typography
|
||||||
(fn [changes]
|
(fn [changes]
|
||||||
(st/emit! (dwl/update-typography (merge typography changes) current-file-id)))
|
(st/emit! (dwl/update-typography (merge typography changes) file-id)))
|
||||||
|
|
||||||
opts #js {:ids ids
|
opts #js {:ids ids
|
||||||
:values values
|
:values values
|
||||||
|
@ -244,7 +272,7 @@
|
||||||
(cond
|
(cond
|
||||||
typography
|
typography
|
||||||
[:& typography-entry {:typography typography
|
[:& typography-entry {:typography typography
|
||||||
:read-only? (not= (:typography-ref-file values) current-file-id)
|
:read-only? (not= (:typography-ref-file values) file-id)
|
||||||
:file (get shared-libs (:typography-ref-file values))
|
:file (get shared-libs (:typography-ref-file values))
|
||||||
:on-detach handle-detach-typography
|
:on-detach handle-detach-typography
|
||||||
:on-change handle-change-typography}]
|
:on-change handle-change-typography}]
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) UXBOX Labs SL
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import {CharacterMetadata} from "draft-js";
|
|
||||||
import {Map} from "immutable";
|
|
||||||
|
|
||||||
function removeStylePrefix(chmeta, stylePrefix) {
|
|
||||||
var withoutStyle = chmeta.set('style', chmeta.getStyle().filter((s) => !s.startsWith(stylePrefix)))
|
|
||||||
return CharacterMetadata.create(withoutStyle);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function removeInlineStylePrefix(contentState, selectionState, stylePrefix) {
|
|
||||||
var blockMap = contentState.getBlockMap();
|
|
||||||
var startKey = selectionState.getStartKey();
|
|
||||||
var startOffset = selectionState.getStartOffset();
|
|
||||||
var endKey = selectionState.getEndKey();
|
|
||||||
var endOffset = selectionState.getEndOffset();
|
|
||||||
var newBlocks = blockMap.skipUntil(function (_, k) {
|
|
||||||
return k === startKey;
|
|
||||||
}).takeUntil(function (_, k) {
|
|
||||||
return k === endKey;
|
|
||||||
}).concat(Map([[endKey, blockMap.get(endKey)]])).map(function (block, blockKey) {
|
|
||||||
var sliceStart;
|
|
||||||
var sliceEnd;
|
|
||||||
|
|
||||||
if (startKey === endKey) {
|
|
||||||
sliceStart = startOffset;
|
|
||||||
sliceEnd = endOffset;
|
|
||||||
} else {
|
|
||||||
sliceStart = blockKey === startKey ? startOffset : 0;
|
|
||||||
sliceEnd = blockKey === endKey ? endOffset : block.getLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
var chars = block.getCharacterList();
|
|
||||||
var current;
|
|
||||||
|
|
||||||
while (sliceStart < sliceEnd) {
|
|
||||||
current = chars.get(sliceStart);
|
|
||||||
chars = chars.set(sliceStart, removeStylePrefix(current, stylePrefix));
|
|
||||||
sliceStart++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return block.set('characterList', chars);
|
|
||||||
});
|
|
||||||
|
|
||||||
return contentState.merge({
|
|
||||||
blockMap: blockMap.merge(newBlocks),
|
|
||||||
selectionBefore: selectionState,
|
|
||||||
selectionAfter: selectionState
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -11,7 +11,7 @@
|
||||||
"Draft related abstraction functions."
|
"Draft related abstraction functions."
|
||||||
(:require
|
(:require
|
||||||
["draft-js" :as draft]
|
["draft-js" :as draft]
|
||||||
["./draft_helpers.js" :as helpers]
|
["./text_editor_impl.js" :as impl]
|
||||||
[app.common.attrs :as attrs]
|
[app.common.attrs :as attrs]
|
||||||
[app.common.text :as txt]
|
[app.common.text :as txt]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
@ -206,27 +206,15 @@
|
||||||
|
|
||||||
(defn create-editor-state
|
(defn create-editor-state
|
||||||
([]
|
([]
|
||||||
(.createEmpty ^js draft/EditorState))
|
(impl/createEditorState nil nil))
|
||||||
([content]
|
([content]
|
||||||
(.createWithContent ^js draft/EditorState content))
|
(impl/createEditorState content nil))
|
||||||
([content decorator]
|
([content decorator]
|
||||||
(if (some? content)
|
(impl/createEditorState content decorator)))
|
||||||
(.createWithContent ^js draft/EditorState content decorator)
|
|
||||||
(.createEmpty ^js draft/EditorState decorator))))
|
|
||||||
|
|
||||||
(defn create-decorator
|
(defn create-decorator
|
||||||
[type component]
|
[type component]
|
||||||
(letfn [(find-entity [block callback content]
|
(impl/createDecorator type component))
|
||||||
(.findEntityRanges ^js block
|
|
||||||
(fn [cmeta]
|
|
||||||
(let [ekey (.getEntity ^js cmeta)]
|
|
||||||
(boolean
|
|
||||||
(and (some? ekey)
|
|
||||||
(= type (.. ^js content (getEntity ekey) (getType)))))))
|
|
||||||
callback))]
|
|
||||||
(draft/CompositeDecorator.
|
|
||||||
#js [#js {:strategy find-entity
|
|
||||||
:component component}])))
|
|
||||||
|
|
||||||
(defn import-content
|
(defn import-content
|
||||||
[content]
|
[content]
|
||||||
|
@ -248,18 +236,7 @@
|
||||||
|
|
||||||
(defn editor-select-all
|
(defn editor-select-all
|
||||||
[state]
|
[state]
|
||||||
(let [content (get-editor-current-content state)
|
(impl/selectAll state))
|
||||||
fblock (.. ^js content getBlockMap first)
|
|
||||||
lblock (.. ^js content getBlockMap last)
|
|
||||||
fbk (.getKey ^js fblock)
|
|
||||||
lbk (.getKey ^js lblock)
|
|
||||||
lbl (.getLength ^js lblock)
|
|
||||||
params #js {:anchorKey fbk
|
|
||||||
:anchorOffset 0
|
|
||||||
:focusKey lbk
|
|
||||||
:focusOffset lbl}
|
|
||||||
selection (draft/SelectionState. params)]
|
|
||||||
(.forceSelection ^js draft/EditorState state selection)))
|
|
||||||
|
|
||||||
(defn get-editor-block-data
|
(defn get-editor-block-data
|
||||||
[block]
|
[block]
|
||||||
|
@ -272,9 +249,7 @@
|
||||||
|
|
||||||
(defn get-editor-current-block-data
|
(defn get-editor-current-block-data
|
||||||
[state]
|
[state]
|
||||||
(let [content (.getCurrentContent ^js state)
|
(let [block (impl/getCurrentBlock state)]
|
||||||
key (.. ^js state getSelection getStartKey)
|
|
||||||
block (.getBlockForKey ^js content key)]
|
|
||||||
(get-editor-block-data block)))
|
(get-editor-block-data block)))
|
||||||
|
|
||||||
(defn get-editor-current-inline-styles
|
(defn get-editor-current-inline-styles
|
||||||
|
@ -284,103 +259,20 @@
|
||||||
|
|
||||||
(defn update-editor-current-block-data
|
(defn update-editor-current-block-data
|
||||||
[state attrs]
|
[state attrs]
|
||||||
(loop [selection (.getSelection ^js state)
|
(impl/updateCurrentBlockData state (clj->js attrs)))
|
||||||
start-key (.getStartKey ^js selection)
|
|
||||||
end-key (.getEndKey ^js selection)
|
|
||||||
content (.getCurrentContent ^js state)
|
|
||||||
target selection]
|
|
||||||
(if (and (not= start-key end-key)
|
|
||||||
(zero? (.getEndOffset ^js selection)))
|
|
||||||
(let [before-block (.getBlockBefore ^js content end-key)]
|
|
||||||
(recur selection
|
|
||||||
start-key
|
|
||||||
(.getKey ^js before-block)
|
|
||||||
content
|
|
||||||
(.merge ^js target
|
|
||||||
#js {:anchorKey start-key
|
|
||||||
:anchorOffset (.getStartOffset ^js selection)
|
|
||||||
:focusKey end-key
|
|
||||||
:focusOffset (.getLength ^js before-block)
|
|
||||||
:isBackward false})))
|
|
||||||
(.push ^js draft/EditorState
|
|
||||||
state
|
|
||||||
(.mergeBlockData ^js draft/Modifier content target (clj->js attrs))
|
|
||||||
"change-block-data"))))
|
|
||||||
|
|
||||||
(defn get-editor-current-entity-key
|
|
||||||
[state]
|
|
||||||
(let [content (.getCurrentContent ^js state)
|
|
||||||
selection (.getSelection ^js state)
|
|
||||||
start-key (.getStartKey ^js selection)
|
|
||||||
start-offset (.getStartOffset ^js selection)
|
|
||||||
block (.getBlockForKey ^js content start-key)]
|
|
||||||
(.getEntityAt ^js block start-offset)))
|
|
||||||
|
|
||||||
(defn update-editor-current-inline-styles
|
(defn update-editor-current-inline-styles
|
||||||
[state attrs]
|
[state attrs]
|
||||||
(let [selection (.getSelection ^js state)
|
(impl/applyInlineStyle state (attrs-to-styles attrs)))
|
||||||
styles (attrs-to-styles attrs)]
|
|
||||||
(reduce (fn [state style]
|
|
||||||
(let [[sk sv] (decode-style style)
|
|
||||||
prefix (encode-style-prefix sk)
|
|
||||||
|
|
||||||
content (.getCurrentContent ^js state)
|
|
||||||
content (helpers/removeInlineStylePrefix content
|
|
||||||
selection
|
|
||||||
prefix)
|
|
||||||
|
|
||||||
content (.applyInlineStyle ^js draft/Modifier
|
|
||||||
content
|
|
||||||
selection
|
|
||||||
style)]
|
|
||||||
(.push ^js draft/EditorState state content "change-inline-style")))
|
|
||||||
state
|
|
||||||
styles)))
|
|
||||||
|
|
||||||
(defn editor-split-block
|
(defn editor-split-block
|
||||||
[state]
|
[state]
|
||||||
(let [content (.getCurrentContent ^js state)
|
(impl/splitBlockPreservingData state))
|
||||||
selection (.getSelection ^js state)
|
|
||||||
content (.splitBlock ^js draft/Modifier content selection)
|
|
||||||
block-data (.. ^js content -blockMap (get (.. content -selectionBefore getStartKey)) getData)
|
|
||||||
block-key (.. ^js content -selectionAfter getStartKey)
|
|
||||||
block-map (.. ^js content -blockMap (update block-key (fn [block] (.set ^js block "data" block-data))))]
|
|
||||||
(.push ^js draft/EditorState state (.set ^js content "blockMap" block-map) "split-block")))
|
|
||||||
|
|
||||||
(defn add-editor-blur-selection
|
(defn add-editor-blur-selection
|
||||||
[state]
|
[state]
|
||||||
(let [content (.getCurrentContent ^js state)
|
(impl/addBlurSelectionEntity state))
|
||||||
selection (.getSelection ^js state)
|
|
||||||
content (.createEntity ^js content "PENPOT_SELECTION" "MUTABLE")
|
|
||||||
ekey (.getLastCreatedEntityKey ^js content)
|
|
||||||
content (.applyEntity draft/Modifier
|
|
||||||
content
|
|
||||||
selection
|
|
||||||
ekey)]
|
|
||||||
(.push draft/EditorState state content "apply-entity")))
|
|
||||||
|
|
||||||
|
|
||||||
(defn remove-editor-blur-selection
|
(defn remove-editor-blur-selection
|
||||||
[state]
|
[state]
|
||||||
(let [content (get-editor-current-content state)
|
(impl/removeBlurSelectionEntity state))
|
||||||
fblock (.. ^js content getBlockMap first)
|
|
||||||
lblock (.. ^js content getBlockMap last)
|
|
||||||
fbk (.getKey ^js fblock)
|
|
||||||
lbk (.getKey ^js lblock)
|
|
||||||
lbl (.getLength ^js lblock)
|
|
||||||
params #js {:anchorKey fbk
|
|
||||||
:anchorOffset 0
|
|
||||||
:focusKey lbk
|
|
||||||
:focusOffset lbl}
|
|
||||||
|
|
||||||
prev-selection (.getSelection state)
|
|
||||||
|
|
||||||
selection (draft/SelectionState. params)
|
|
||||||
content (.applyEntity draft/Modifier
|
|
||||||
content
|
|
||||||
selection
|
|
||||||
nil)]
|
|
||||||
(as-> state $
|
|
||||||
(.push draft/EditorState $ content "apply-entity")
|
|
||||||
(.forceSelection ^js draft/EditorState $ prev-selection))))
|
|
||||||
|
|
||||||
|
|
212
frontend/src/app/util/text_editor_impl.js
Normal file
212
frontend/src/app/util/text_editor_impl.js
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
/**
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
* defined by the Mozilla Public License, v. 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) UXBOX Labs SL
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CharacterMetadata,
|
||||||
|
EditorState,
|
||||||
|
CompositeDecorator,
|
||||||
|
SelectionState,
|
||||||
|
Modifier
|
||||||
|
} from "draft-js";
|
||||||
|
|
||||||
|
import {Map} from "immutable";
|
||||||
|
|
||||||
|
function isDefined(v) {
|
||||||
|
return v !== undefined && v !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEditorState(content, decorator) {
|
||||||
|
if (content === null) {
|
||||||
|
return EditorState.createEmpty(decorator);
|
||||||
|
} else {
|
||||||
|
return EditorState.createWithContent(content, decorator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDecorator(type, component) {
|
||||||
|
const strategy = (block, callback, content) => {
|
||||||
|
return block.findEntityRanges((cmeta) => {
|
||||||
|
const entityKey = cmeta.getEntity();
|
||||||
|
return isDefined(entityKey) && (type === content.getEntity(entityKey).getType());
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
return new CompositeDecorator([
|
||||||
|
{strategy, component}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectAllSelection(state) {
|
||||||
|
const content = state.getCurrentContent();
|
||||||
|
const firstBlock = content.getBlockMap().first();
|
||||||
|
const lastBlock = content.getBlockMap().last();
|
||||||
|
|
||||||
|
return new SelectionState({
|
||||||
|
"anchorKey": firstBlock.getKey(),
|
||||||
|
"anchorOffset": 0,
|
||||||
|
"focusKey": lastBlock.getKey(),
|
||||||
|
"focusOffset": lastBlock.getLength()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectAll(state) {
|
||||||
|
return EditorState.forceSelection(state, getSelectAllSelection(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
function modifySelectedBlocks(contentState, selectionState, operation) {
|
||||||
|
var startKey = selectionState.getStartKey();
|
||||||
|
var endKey = selectionState.getEndKey();
|
||||||
|
var blockMap = contentState.getBlockMap();
|
||||||
|
|
||||||
|
var newBlocks = blockMap.toSeq().skipUntil(function (_, k) {
|
||||||
|
return k === startKey;
|
||||||
|
}).takeUntil(function (_, k) {
|
||||||
|
return k === endKey;
|
||||||
|
}).concat(Map([[endKey, blockMap.get(endKey)]])).map(operation);
|
||||||
|
|
||||||
|
return contentState.merge({
|
||||||
|
blockMap: blockMap.merge(newBlocks),
|
||||||
|
selectionBefore: selectionState,
|
||||||
|
selectionAfter: selectionState
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCurrentBlockData(state, attrs) {
|
||||||
|
const selection = state.getSelection();
|
||||||
|
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 EditorState.push(state, content, "change-block-data");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyInlineStyle(state, styles) {
|
||||||
|
const selection = state.getSelection();
|
||||||
|
|
||||||
|
let state = state;
|
||||||
|
let content = null;
|
||||||
|
|
||||||
|
for (let style of styles) {
|
||||||
|
const [p, k, _] = style.split("$$$");
|
||||||
|
const prefix = [p, k, ""].join("$$$");
|
||||||
|
|
||||||
|
content = state.getCurrentContent();
|
||||||
|
content = removeInlineStylePrefix(content, selection, prefix);
|
||||||
|
content = Modifier.applyInlineStyle(content, selection, style);
|
||||||
|
state = EditorState.push(state, content, "change-inline-style");
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function splitBlockPreservingData(state) {
|
||||||
|
let content = state.getCurrentContent();
|
||||||
|
const selection = state.getSelection();
|
||||||
|
|
||||||
|
content = Modifier.splitBlock(content, selection);
|
||||||
|
|
||||||
|
const blockData = content.blockMap.get(content.selectionBefore.getStartKey()).getData();
|
||||||
|
const blockKey = content.selectionAfter.getStartKey();
|
||||||
|
const blockMap = content.blockMap.update(blockKey, (block) => {
|
||||||
|
return block.set("data", blockData);
|
||||||
|
});
|
||||||
|
|
||||||
|
content = content.set("blockMap", blockMap);
|
||||||
|
|
||||||
|
return EditorState.push(state, content, "split-block");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addBlurSelectionEntity(state) {
|
||||||
|
let content = state.getCurrentContent(state);
|
||||||
|
const selection = state.getSelection();
|
||||||
|
|
||||||
|
content = content.createEntity("PENPOT_SELECTION", "MUTABLE");
|
||||||
|
const entityKey = content.getLastCreatedEntityKey();
|
||||||
|
|
||||||
|
content = Modifier.applyEntity(content, selection, entityKey);
|
||||||
|
return EditorState.push(state, content, "apply-entity");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeBlurSelectionEntity(state) {
|
||||||
|
const selectionAll = getSelectAllSelection(state);
|
||||||
|
const selection = state.getSelection();
|
||||||
|
|
||||||
|
let content = state.getCurrentContent();
|
||||||
|
content = Modifier.applyEntity(content, selectionAll, null);
|
||||||
|
|
||||||
|
state = EditorState.push(state, content, "apply-entity");
|
||||||
|
state = EditorState.forceSelection(state, selection);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentBlock(state) {
|
||||||
|
const content = state.getCurrentContent();
|
||||||
|
const selection = state.getSelection();
|
||||||
|
const startKey = selection.getStartKey();
|
||||||
|
return content.getBlockForKey(startKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentEntityKey(state) {
|
||||||
|
const block = getCurrentBlock(state);
|
||||||
|
const selection = state.getSelection();
|
||||||
|
const startOffset = selection.getStartOffset();
|
||||||
|
return block.getEntityAt(startOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeInlineStylePrefix(contentState, selectionState, stylePrefix) {
|
||||||
|
const startKey = selectionState.getStartKey();
|
||||||
|
const startOffset = selectionState.getStartOffset();
|
||||||
|
const endKey = selectionState.getEndKey();
|
||||||
|
const endOffset = selectionState.getEndOffset();
|
||||||
|
|
||||||
|
return modifySelectedBlocks(contentState, selectionState, (block, blockKey) => {
|
||||||
|
let sliceStart;
|
||||||
|
let sliceEnd;
|
||||||
|
|
||||||
|
if (startKey === endKey) {
|
||||||
|
sliceStart = startOffset;
|
||||||
|
sliceEnd = endOffset;
|
||||||
|
} else {
|
||||||
|
sliceStart = blockKey === startKey ? startOffset : 0;
|
||||||
|
sliceEnd = blockKey === endKey ? endOffset : block.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
let chars = block.getCharacterList();
|
||||||
|
let current;
|
||||||
|
|
||||||
|
while (sliceStart < sliceEnd) {
|
||||||
|
current = chars.get(sliceStart);
|
||||||
|
current = current.set("style", current.getStyle().filter((s) => !s.startsWith(stylePrefix)))
|
||||||
|
chars = chars.set(sliceStart, CharacterMetadata.create(current));
|
||||||
|
|
||||||
|
sliceStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block.set('characterList', chars);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue