penpot/frontend/src/app/util/text_editor.cljs
2022-08-19 15:15:29 +02:00

185 lines
5.3 KiB
Clojure

;; 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/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.util.text-editor
"Draft related abstraction functions."
(:require
["./text_editor_impl.js" :as impl]
["draft-js" :as draft]
[app.common.text :as txt]))
;; --- CONVERSION
(defn immutable-map->map
[obj]
(let [data (into {} (map (fn [[k v]] [(keyword k) v])) (seq obj))]
(assoc data :fills (js->clj (:fills data) :keywordize-keys true))))
;; --- DRAFT-JS HELPERS
(defn create-editor-state
([]
(impl/createEditorState nil nil))
([content]
(impl/createEditorState content nil))
([content decorator]
(impl/createEditorState content decorator)))
(defn create-decorator
[type component]
(impl/createDecorator type component))
(defn import-content
[content]
(-> content txt/convert-to-draft clj->js draft/convertFromRaw))
(defn export-content
[content]
(-> content
(draft/convertToRaw)
(js->clj :keywordize-keys true)
(txt/convert-from-draft)))
(defn get-editor-current-content
[state]
(.getCurrentContent ^js state))
(defn ^boolean content-has-text?
[content]
(.hasText ^js content))
(defn editor-select-all
[state]
(impl/selectAll state))
(defn get-editor-block-data
[block]
(-> (.getData ^js block)
(immutable-map->map)))
(defn get-editor-block-type
[block]
(.getType ^js block))
(defn get-editor-current-block-data
[state]
(let [block (impl/getCurrentBlock state)]
(get-editor-block-data block)))
(defn get-editor-current-inline-styles
[state]
(if (impl/isCurrentEmpty state)
(get-editor-current-block-data state)
(-> (.getCurrentInlineStyle ^js state)
(txt/styles-to-attrs)
(dissoc :text-align :text-direction))))
(defn update-editor-current-block-data
[state attrs]
(impl/updateCurrentBlockData state (clj->js attrs)))
(defn update-editor-current-inline-styles
[state 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)
(dissoc :text-align :text-direction))]
(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 update-editor-current-inline-styles-fn
[state update-fn]
(let [attrs (-> (.getCurrentInlineStyle ^js state)
(txt/styles-to-attrs)
(update-fn))]
(impl/applyInlineStyle state (txt/attrs-to-styles attrs))))
(defn editor-split-block
[state]
(impl/splitBlockPreservingData state))
(defn add-editor-blur-selection
[state]
(impl/addBlurSelectionEntity state))
(defn remove-editor-blur-selection
[state]
(impl/removeBlurSelectionEntity state))
(defn cursor-to-end
[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)
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)))))
(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))))