{{roam/render:
(ns excalidraw.app.beta.v09 (:require [clojure.set :as s] [reagent.core :as r] [roam.datascript :as rd] [roam.block :as block] [clojure.string :as str] [clojure.edn :as edn] [roam.util :as util] [roam.datascript.reactive :as dr] [clojure.pprint :as pp])) (def plugin-version 1) (def app-page "roam/excalidraw") (def app-settings-block "Settings") (def app-setting-uid "Excal_SET") (def default-app-settings {:mode "light" :img "SVG" :full-screen-margin 0.015 :max-embed-width 600 :max-embed-height 400 :nested-text-rows 20 :nested-text-row-height 40 :nested-text-col-width 400 :nested-text-start-top 40 :nested-text-start-left 320 :nested-text-font-size 20 :nested-text-font-family 1}) (def app-settings (r/atom default-app-settings)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; util functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def silent (r/atom false)) (defn debug [x] (if-not @silent (apply (.-log js/console) "<<< Roam-Excalidraw Main cljs >>>" x))) (def embedded-view "ev") (def full-screen-view "fs") (defn is-full-screen [cs] ;;component-state (not= (:position @cs) embedded-view)) (defn create-block [parent-uid order block-string] (.createBlock js/window.ExcalidrawWrapper parent-uid order block-string)) (defn block-update [x] (.updateBlock js/window.ExcalidrawWrapper x)) (defn pretty-settings [x] (let [y (into (sorted-map) (sort-by first (seq x)))] (-> (str y) (str/replace "{" "{\n") (str/replace ", " "\n") (str/replace "}" "\n}")))) (defn get-next-block-order [x] (let [o (rd/q '[:find (max ?o) . :in $ ?uid :where [?b :block/uid ?uid] [?b :block/children ?c] [?c :block/order ?o]] x)] (if (nil? o) 0 (+ 1 o)) )) (defn save-settings [] ;;(debug ["(save-settings) Enter"]) (let [settings-host (r/atom (rd/q '[:find ?uid . :in $ ?page ?block :where [?p :node/title ?page] [?p :block/children ?b] [?b :block/string ?block] [?b :block/uid ?uid]] app-page app-settings-block))] (if (nil? @settings-host) (do ;;(debug ["(save-settings) settings host does not exist"]) (reset! settings-host (create-block (rd/q '[:find ?uid . :in $ ?page :where [?p :node/title ?page] [?p :block/uid ?uid]] app-page) 3 app-settings-block)))) (let [settings-block (r/atom (rd/q '[:find ?uid . :in $ ?settings-host :where [?b :block/uid ?settings-host] [?b :block/children ?c] [?c :block/order 0] [?c :block/uid ?uid]] @settings-host))] (if (nil? @settings-block) (do ;;(debug ["(save-settings) settings-block does not exist"]) (create-block @settings-host 0 (pretty-settings @app-settings))) (do ;;(debug ["(save-settings) settings-block exists, updating"]) (block-update {:block {:uid @settings-block :string (pretty-settings @app-settings)}})))))) (defn js-to-clj-str [& x] ;;(debug ["(js-to-clj-str): x: " x (str x)]) (let [res (-> x (str) (str/replace #"\(#js" "") (str/replace #"#js" "") (str/replace #"\}\}\)" "}}"))] res)) (defn fix-double-bracket [x] (str/replace x #"\[{2}" "[ [")) (defn get-data-block-uid [x] (rd/q '[:find ?drawing-uid . :in $ ?uid :where [?e :block/uid ?uid] [?e :block/children ?c] [?c :block/order 0] [?c :block/string ?s] [(clojure.string/includes? ?s "((ExcalDATA))")] [?c :block/uid ?drawing-uid]] x)) (defn pull-children [block-uid order] (rd/q '[:find (pull ?b [:block/uid :block/string {:block/children [:block/string :block/order :block/uid {:block/children ...}]}]) :in $ ?block-uid ?order :where [?e :block/uid ?block-uid] [?e :block/children ?b] [?b :block/order ?order]] block-uid order)) (defn flatten-nested-text [nested-text level] (let [result (atom nil)] (doseq [y nested-text] (let [order (str/join [level (pp/cl-format nil "~2,'0d" (:block/order y))])] (reset! result (conj @result {:block/string (:block/string y) :block/order order :block/uid (:block/uid y)})) (if-not (nil? (:block/children y)) (reset! result (concat @result (flatten-nested-text (:block/children y) order)))) )) ;;(debug ["flat-nest " (str @result)]) (into [] (sort-by :block/order @result)) )) (defn get-text-blocks [x] (-> x (pull-children 1) (first) (get-in [0 :block/children]) (flatten-nested-text ""))) (defn get-text-elements [x] (filter (comp #{"text"} :type) x) ) (defn get-block-uid-from-text-element [x] (second (re-find #"ROAM_(.*)_ROAM" (:id x))) ) ;;updates the :elements value of the drawing with nested text and updated object groups (defn update-elements-with-parts [x] {:raw-elements [] :text-elements [] :groups []} (into [] (concat (into [] (remove (comp #{"text"} :type) (:raw-elements x))) (:text-elements x))) ) ;;{:block-uid "BlockUID" :map-string "String" :cs atom :drawing atom} (defn save-component [x] ;;Disable the pullWatch while blocks are edited (reset! (:saving-flag x) true) ;;(debug ["(save-component) Enter"]) (let [data-block-uid (get-data-block-uid (:block-uid x)) edn-map (edn/read-string (:map-string x)) text-elements (r/atom nil) ;;get text blocks nested under title nestedtext-parent-block-uid (get-in @(:drawing x) [:nestedtext-parent :block-uid]) nested-text-blocks (get-text-blocks (:block-uid x)) app-state (into {} (filter (comp some? val) (:appState edn-map)))] ;;remove nil elements from appState ;;process text on drawing ;;(debug ["(save-component) start processing text"]) (doseq [y (get-text-elements (:elements edn-map))] (if (:isDeleted y) (if (str/starts-with? (:id y) "ROAM_") (block/delete {:block {:uid (get-block-uid-from-text-element y)}}) ) (if (str/starts-with? (:id y) "ROAM_") (do ;;block with text should already exist, update text, but double check that the block is there... ;;(debug ["(save-component) nested block should exist text:" (:text y) "block-id" (get-block-uid-from-text-element y)]) (let [text-block-uid (get-block-uid-from-text-element y) nested-block (filter (comp #{text-block-uid} :block/uid) nested-text-blocks)] (if-not (= 0 (count nested-block)) (do ;;block exists ;;(debug ["(save-component) block exists, updateing"]) (if-not (= (:block/string (first nested-block)) (:text y)) (block-update {:block {:uid text-block-uid :string (:text y)}})) (reset! text-elements (conj @text-elements y)) ) (do ;block no-longer exists, create new one ;;(debug ["(save-component) block should, but does not exist, creating..."]) (let [new-block-uid (.createBlock js/ExcalidrawWrapper nestedtext-parent-block-uid (get-next-block-order nestedtext-parent-block-uid) (:text y))] (reset! text-elements (conj @text-elements (assoc-in y [:id] (str/join ["ROAM_" new-block-uid "_ROAM"])))) ))))) (do ;;block with text does not exist as nested block, create new ;;(debug ["(save-component) block does not exists, creating"]) (let [new-block-uid (.createBlock js/ExcalidrawWrapper nestedtext-parent-block-uid (get-next-block-order nestedtext-parent-block-uid) (:text y))] (reset! text-elements (conj @text-elements (assoc-in y [:id] (str/join ["ROAM_" new-block-uid "_ROAM"])))) (reset! text-elements (conj @text-elements (assoc-in y [:isDeleted] true))) ))))) ;;(debug ["(save-component) text-blocks with updated IDs" (str @text-elements)]) ;;updating the data block is the final piece in saving the component (let [elements (update-elements-with-parts {:raw-elements (:elements edn-map) :text-elements @text-elements}) out-string (fix-double-bracket (str {:elements elements :appState app-state :roamExcalidraw {:version plugin-version}})) render-string (str/join ["{{roam/render: ((ExcalDATA)) " out-string " }}"])] (block-update {:block {:uid data-block-uid :string render-string}}) (swap! app-settings assoc-in [:mode] (get-in app-state [:theme])) (save-settings) (reset! (:saving-flag x) false) {:elements elements :appState app-state :roamExcalidraw {:version plugin-version}} ))) (defn load-settings [] ;;(debug ["(load-settings) Enter"]) (let [settings-block (rd/q '[:find ?settings . :in $ ?page ?block :where [?p :node/title ?page] [?p :block/children ?b] [?b :block/string ?block] [?b :block/children ?c] [?c :block/order 0] [?c :block/string ?settings]] app-page app-settings-block)] (if-not (nil? settings-block) (do ;;(debug ["(load-settings) settings: " settings-block]) (reset! app-settings (edn/read-string settings-block)) (if (nil? @app-settings) (reset! app-settings default-app-settings)) (doseq [key (keys default-app-settings)] (if (nil? (key @app-settings)) (swap! app-settings assoc-in [key] (key default-app-settings)))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Load data from nested block(s) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;finds the json enclosed in double parentheses and returns it (defn get-data-from-block-string [x] ;;(debug ["(get-data-from-block-string)" (count x)]) (if (= (count x) 0) (do ;;(debug ["(get-data-from-block-string) returning nil"]) nil) (do (let [data-string (get-in (first x) [0 :block/string]) return-string (second (re-find #"ExcalDATA\){2}\s*(\{.*\})\s*\}{2}" data-string))] ;;;(debug ["(get-data-from-block-string) returning: " retrun-string]) (edn/read-string return-string))))) (defn create-nested-blocks [x]; {:block-uid "BlockUID" :drawing atom :empty-block-uid "BlockUID"} ;;block uid is the block of the roam/render component ;;empty block is the block created by the user by trying to nest text under ;;a new drawing that hasn't been edited yet (i.e. the data and title children ;;are missing) ;;(debug ["(create-nested-blocks)"]) (let [default-data {:appState {:theme (:mode @app-settings)} :roamExcalidraw {:version 1}}] (create-block (:block-uid x) 0 (str/join ["{{roam/render: ((ExcalDATA)) " (str default-data) " }}"])) (reset! (:drawing x) {:drawing default-data :nestedtext-parent {:block-uid (create-block (:block-uid x) 1 "**Text nested here will appear on your drawing:**")}}) (if (nil? (:empty-block-uid x)) (block-update {:block {:uid (:block-uid x) :open false}}) ;;fold up the drawing block to hide children (block/move {:location {:parent-uid (get-in @(:drawing x) [:nestedtext-parent :block-uid]) ;;move new block under nested text :order 0} :block {:uid (:empty-block-uid x)}})) )) (defn load-drawing [x] ;{:block-uid "BlockUID" :drawing atom :data objects :text "text"} ;drawing is the atom holding the drawing map ;block uid is the block with the roam/render component ;data are the drawing objects ;text are the nested text blocks ;;(debug ["(load-drawing) enter"]) (if (= (count (:data x)) 0) (do ;;(debug ["(load-drawing) no children - creating dummy data"]) (let [default-data {:appState {:theme (:mode @app-settings)} :roamExcalidraw {:version 1}}] (reset! (:drawing x) {:drawing default-data :nestedtext-parent {:block-uid nil}}) )) (if (= (count (:text x)) 0) (do ;;(debug ["(load-drawing) create title only"]) (reset! (:drawing x) {:drawing (:data x) :nestedtext-parent {:block-uid (create-block (:block-uid x) 1 "**Text nested here will appear on your drawing:**")}}) (block-update {:block {:uid (:block-uid x) :open false}}) ) (do ;;(debug ["(load-drawing) ExcalDATA & title already exist"]) (reset! (:drawing x) {:drawing (:data x) :nestedtext-parent {:block-uid (get-in (:text x) [0 :block/uid])} :text (get-in (:text x) [0 :block/children])}) ))) ;;(debug ["(load-drawing) drawing: " @(:drawing x) " data: " (:data x) " text: " (str (:text x)) "theme " (get-in (:data x) [:appState :theme])]) ) ;;check if text in nested block has changed compared to drawing and updated text in drawing element including size (defn update-drawing-based-on-nested-blocks [x] ;{:elements [] :appState {} :nested-text [:block/uid "BlockUID" :block/string "text"]} ;;(debug ["(update-drawing-based-on-nested-blocks) Enter x:" x]) (if-not (nil? (:nested-text x)) (do (let [text-elements (r/atom nil) nested-text (flatten-nested-text (:nested-text x) "")] ;;(debug ["(update-drawing-based-on-nested-blocks) processing nested text - apply changes to existing text elements, omit deleted ones"]) ;;update elements on drawing based on changes to nested text (doseq [y (get-text-elements (:elements x))] (let [block-uid (get-block-uid-from-text-element y) block-text (:block/string (first (filter (comp #{block-uid} :block/uid) nested-text))) text-element-has-nested-block-pair (= 0 (count (filter (comp #{block-uid} :block/uid) nested-text)))] ;;add text to drawing if text element has a nested block pair, (if (not text-element-has-nested-block-pair) (do ;if text has changed, update measures (if-not (= block-text (:text y)) (let [text-measures (js->clj (.measureText js/ExcalidrawWrapper block-text y))] (reset! text-elements (conj @text-elements (-> y (assoc-in [:text] block-text) (assoc-in [:baseline] (get text-measures "baseline")) (assoc-in [:width] (get text-measures "width")) (assoc-in [:height] (get text-measures "height")) )))) ;;else add to list as-is (reset! text-elements (conj @text-elements y)))) ;;else it should be removed from the drawing ;;unless the drawing is from an old version of the plugin when nested blocks weren't handled (if (< (get-in x [:roamExcalidraw :version]) 1) (reset! text-elements (conj @text-elements y))) ))) ;;(debug ["(update-drawing-based-on-nested-blocks) processing nested text - add new nested blocks"]) ;;add text for newly nested blocks (let [counter (atom -1)] (doseq [nt nested-text] (swap! counter inc) (let [text (:block/string nt) dummy {:fontFamily (:nested-text-font-family @app-settings) :fontSize (:nested-text-font-size @app-settings)} order (:block/order nt) id (:block/uid nt)] (if (= 0 (count (filter (comp #{(str/join ["ROAM_" (:block/uid nt) "_ROAM"])} :id) @text-elements))) (let [col (int (/ @counter (:nested-text-rows @app-settings))) row (mod @counter (:nested-text-rows @app-settings)) x (+ (:nested-text-start-left @app-settings) (* col (:nested-text-col-width @app-settings))) y (+ (:nested-text-start-top @app-settings) (* row (:nested-text-row-height @app-settings))) text-measures (js->clj (.measureText js/ExcalidrawWrapper text dummy))] ;;(debug ["(update-drawing-based-on-nested-blocks) add new: text" text "id" id]) (reset! text-elements (conj @text-elements {:y y :baseline (get text-measures "baseline") :isDeleted false :strokeStyle "solid":roughness 1 :width (get text-measures "width") :type "text" :strokeSharpness "sharp" :fillStyle "hachure" :angle 0 :groupIds [] :seed 1 :fontFamily (:nested-text-font-family @app-settings) :boundElementIds [] :strokeWidth 1 :opacity 100 :id (str/join ["ROAM_" id "_ROAM"]) :verticalAlign "top" :strokeColor "#000000" :textAlign "left" :x (+ x (* 5 (count order))) :fontSize (:nested-text-font-size @app-settings) :version 1 :backgroundColor "transparent" :versionNonce 1 :height (get text-measures "height") :text text})) ))))) {:elements (update-elements-with-parts {:raw-elements (:elements x) :text-elements @text-elements}) :appState (:appState x)} )) {:elements (:elements x) :appState (:appState x)} )) (defn generate-scene [x] ;{:drawing atom}] ;;(debug ["(generate-scene) enter" x]) (update-drawing-based-on-nested-blocks {:elements (:elements (:drawing @(:drawing x))) :appState (:appState (:drawing @(:drawing x))) :nested-text (:text @(:drawing x)) :roamExcalidraw (:roamExcalidraw (:drawing @(:drawing x)))})) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Main Function Form-3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn resize [ew] ;;(debug ["(resize)"]) (if-not (nil? @ew) (.onResize @ew))) (defn update-scene [ew scene] ;;(debug ["(update-scene) scene: " scene]) (if-not (nil? @ew) (.updateScene @ew scene))) (defn get-drawing [ew] ;;(debug ["(get-drawing): " (.getDrawing js/window.ExcalidrawWrapper @ew)]) (.getDrawing js/window.ExcalidrawWrapper @ew)) (defn host-div-style [cs] (let [width (.-innerWidth js/window) height (.-innerHeight js/window) top (int (* height (:full-screen-margin @app-settings))) left (int (* width (:full-screen-margin @app-settings))) host-div-width (if (nil? (:this-dom-node @cs)) (:max-embed-width @app-settings) (.getHostDIVWidth js/ExcalidrawWrapper (:this-dom-node @cs))) embed-width (if (> host-div-width (:max-embed-width @app-settings)) (:max-embed-width @app-settings) host-div-width) embed-height (* (:max-embed-height @app-settings) (/ embed-width (:max-embed-width @app-settings))) ar (:aspect-ratio @cs) w (if (nil? ar) embed-width (if (> ar 1.0) embed-width (* ar embed-height))) h (if (nil? ar @cs) "100%" (if (> ar 1.0) "100%" embed-height ))] (if (is-full-screen cs) {:position "fixed" :z-index 1000 :top top :left left :width (str/join ["calc(100% - " (* left 2) "px)"]) :height (- height (* top 2)) :resize "none"} {:position "relative" :width w :height h :resize "both" :overflow "hidden"} ))) (defn going-full-screen? [x cs style] (if (= x true) (do (load-settings) (.fullScreenKeyboardEventRedirect js/window.ExcalidrawWrapper true) (swap! cs assoc-in [:position] full-screen-view) (swap! style assoc-in [:host-div] (host-div-style cs)) (swap! cs assoc-in [:mouseover] false)) (do (.fullScreenKeyboardEventRedirect js/window.ExcalidrawWrapper false) (swap! cs assoc-in [:position] embedded-view) (swap! style assoc-in [:host-div] (host-div-style cs))))) ;;state to capture when callback confirms React libraries have loaded (def deps-available (r/atom false)) (defn check-js-dependencies [] (if (and (not= (str (type js/Excalidraw)) "") (not= (str (type js/ReactDOM)) "") (not= (str (type js/React)) "") (not= (str (type js/ExcalidrawConfig)) "") (not= (str (type js/ExcalidrawWrapper)) "")) (do (reset! silent (not (.-DEBUG js/ExcalidrawConfig))) (reset! deps-available true)) (js/setTimeout check-js-dependencies 1000) )) (defn get-embed-image [drawing dom-node app-name] (if (= (:img @app-settings) "PNG") (.getPNG js/window.ExcalidrawWrapper drawing dom-node app-name) (.getSVG js/window.ExcalidrawWrapper drawing dom-node app-name) )) (defn main [{:keys [block-uid]} & args] ;;(debug ["(main) component starting..."]) (check-js-dependencies) (let [drawing (r/atom nil) cs (r/atom {:position embedded-view ;;component-state :this-dom-node nil :aspect-ratio nil :mouseover false :prev-empty-block nil}) ;; this is a semaphore system to avoid creating double nested blocks when manually creating the first nested element saving-flag (atom false) pull-watch-active (atom false) ew (r/atom nil) ;;excalidraw-wrapper app-name (str/join ["excalidraw-app-" block-uid]) style (r/atom {:host-div (host-div-style cs)}) resize-handler (fn [] (if (is-full-screen cs) (swap! style assoc-in [:host-div] (host-div-style cs)) (if-not (nil? (:this-dom-node @cs)) (swap! style assoc-in [:host-div] (host-div-style cs))))) ;changed-drawing (atom nil) drawing-on-change-callback (fn [x] (if-not @saving-flag (.updateScene @ew (save-component {:block-uid block-uid :map-string (js-to-clj-str x) :cs cs :drawing drawing :saving-flag saving-flag})))) pull-watch-callback (fn [before after] ;;(debug ["(pull-watch-callback) after:" (js-to-clj-str after)]) (if-not (or @saving-flag (is-full-screen cs) @pull-watch-active) (do (reset! pull-watch-active true) (let [drawing-data (pull-children block-uid 0) drawing-text (pull-children block-uid 1) empty-block-uid (re-find #":block/uid \"(.*)\", (:block/string \"\")" (str drawing-data))] ;check if user has nested a block under a new drawing (if-not (nil? empty-block-uid) (if-not (= (second empty-block-uid) (:prev-empty-block @cs)) (do (swap! cs assoc-in [:prev-empty-block] (second empty-block-uid)) ;;semaphore to avoid double creation of blocks (create-nested-blocks {:block-uid block-uid :drawing drawing :empty-block-uid (second empty-block-uid)}) ))) (load-drawing {:block-uid block-uid :drawing drawing :data (get-data-from-block-string drawing-data) :text (first drawing-text)}) (if-not (is-full-screen cs) (do (swap! cs assoc-in [:aspect-ratio] (get-embed-image (generate-scene {:drawing drawing}) (:this-dom-node @cs) app-name)) (swap! style assoc-in [:host-div] (host-div-style cs))))) (reset! pull-watch-active false) )))] (if (= @deps-available false) [:div "Libraries have not yet loaded. Please refresh the block in a moment."] (fn [] ;;(debug ["(main) fn[] starting..."]) (r/create-class { :display-name "Excalidraw Roam Beta" ;; Constructor ; :constructor (fn [this props]) ; :get-initial-state (fn [this] ) ;; Static methods ; :get-derived-state-from-props (fn [props state] ) ; :get-derived-state-from-error (fn [error] ) ;; Methods ; :get-snapshot-before-update (fn [this old-argv new-argv] ) ; :should-component-update (fn [this old-argv new-argv]) :component-did-mount (fn [this] ;;(debug ["(main) :component-did-mount"]) (load-settings) (swap! cs assoc-in [:this-dom-node] (r/dom-node this)) ;;(debug ["(main) :component-did-mount addPullWatch"]) (.addPullWatch js/ExcalidrawWrapper block-uid pull-watch-callback) (pull-watch-callback nil nil) (swap! style assoc-in [:host-div] (host-div-style cs)) (.addEventListener js/window "resize" resize-handler) ;;(debug ["(main) :component-did-mount Exalidraw mount initiated"]) ) :component-did-update (fn [this old-argv old-state snapshot] ;;(debug ["(main) :component-did-update"]) (if (is-full-screen cs) (resize ew))) :component-will-unmount (fn [this] ;;(debug ["(main) :component-will-unmount"]) (.removePullWatch js/ExcalidrawWrapper block-uid pull-watch-callback) (.removeEventListener js/window "resize" resize-handler)) ; :component-did-catch (fn [this error info]) :reagent-render (fn [{:keys [block-uid]} & args] ;;(debug ["(main) :reagent-render"]) [:div {:class "excalidraw-host" :style (:host-div @style) :on-mouse-over (fn[e] (swap! cs assoc-in [:mouseover] true)) :on-mouse-leave (fn[e] (swap! cs assoc-in [:mouseover] false)) } (if-not (is-full-screen cs) [:button {:class "ex-embed-button" :style {:display (if (:mouseover @cs) "block" "none") :left (if-not (nil? (:this-dom-node @cs)) (- (.-clientWidth (:this-dom-node @cs)) 32) 0)} :draggable true :on-click (fn [e] (load-settings) (going-full-screen? true cs style) (if (nil? (get-in @drawing [:nestedtext-parent :block-uid])) (create-nested-blocks {:block-uid block-uid :drawing drawing :empty-block-uid nil})) (reset! ew (js/ExcalidrawWrapper. app-name (generate-scene {:drawing drawing}) (:this-dom-node @cs) drawing-on-change-callback )) ;(js/setTimeout autosave 10000) )} "🖋"] [:button {:class "ex-fullscreen-button" :style {:left (- (.-clientWidth (:this-dom-node @cs)) 32)} :draggable true :on-click (fn [e] (.svgClipboard js/ExcalidrawWrapper) (going-full-screen? false cs style) (save-component {:block-uid block-uid :map-string (js-to-clj-str (get-drawing ew)) :cs cs :drawing drawing :saving-flag saving-flag}) (swap! cs assoc-in [:aspect-ratio] (get-embed-image (get-drawing ew) (:this-dom-node @cs) app-name)) )} "❌"]) [:div {:id app-name :style {:position "relative" :width "100%" :height "100%"}} ]])})))))
(ns excalidraw.data.alpha.v01) (def silent true) (defn debug [x] (if-not silent (apply (.-log js/console) x))) (defn main2 [{:keys [block-uid]} args] (debug ["data: " args]) [:div [:span.excalidraw-data "Excalidraw DATA"]])
Text nested here will appear on your drawing:
第二章自然人
第一节民事权利能力和民事行为能力
第二节监护
第三节宣告失踪和宣告死亡
第四节个体工商户和农村承包经营户
民法典第一编总则第二章自然人第二节监护框架图