mirror of https://github.com/logseq/logseq
feat: switch to use Excalidraw api
parent
76b33f2d8f
commit
92ad0296e6
|
@ -789,3 +789,12 @@ a {
|
|||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* excalidraw */
|
||||
.Island > div > div > div {
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.excalidraw hr {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,13 @@
|
|||
/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
|
||||
@font-face {
|
||||
font-family: "Virgil";
|
||||
src: url("../fonts/Virgil.woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* https://github.com/microsoft/cascadia-code */
|
||||
@font-face {
|
||||
font-family: "Cascadia";
|
||||
src: url("../fonts/Cascadia.woff2");
|
||||
font-display: swap;
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
@import "./inter.css";
|
||||
@import "./reveal.min.css";
|
||||
@import "./reveal_black.min.css";
|
||||
@import "./fonts.css";
|
||||
@import "./excalidraw.min.css";
|
||||
@import "./katex.min.css";
|
||||
@import "./codemirror.min.css";
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@import "./inter.css";
|
||||
@import "./reveal.min.css";
|
||||
@import "./reveal_black.min.css";
|
||||
@import "./fonts.css";
|
||||
@import "./excalidraw.min.css";
|
||||
@import "./katex.min.css";
|
||||
@import "./codemirror.min.css";
|
||||
|
@ -10,4 +11,4 @@
|
|||
@import "./datepicker.css";
|
||||
@import "./highlight.css";
|
||||
@import "./tailwind.core.css"; /* Build by gulp. Check `_buildTailwind` for more detail */
|
||||
@import "./common.css";
|
||||
@import "./common.css";
|
||||
|
|
Binary file not shown.
|
@ -12,6 +12,9 @@
|
|||
:depends-on #{:main}}
|
||||
:age-encryption
|
||||
{:entries [frontend.extensions.age-encryption]
|
||||
:depends-on #{:main}}
|
||||
:excalidraw
|
||||
{:entries [frontend.extensions.excalidraw]
|
||||
:depends-on #{:main}}}
|
||||
|
||||
:output-dir "./static/js"
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
[goog.dom :as gdom]
|
||||
[goog.object :as gobj]
|
||||
[frontend.format :as format]
|
||||
[frontend.handler.common :as common-handler]))
|
||||
[frontend.handler.common :as common-handler]
|
||||
[frontend.handler.draw :as draw]
|
||||
[promesa.core :as p]))
|
||||
|
||||
;; TODO: move to frontend.handler.editor.commands
|
||||
|
||||
|
@ -102,10 +104,13 @@
|
|||
["Scheduled" [[:editor/clear-current-slash]
|
||||
[:editor/show-date-picker]]]
|
||||
["Query" [[:editor/input "{{query }}" {:backward-pos 2}]]]
|
||||
["Draw" [[:editor/input "/draw "]
|
||||
[:editor/show-input [{:command :draw
|
||||
:id :title
|
||||
:placeholder "Draw title"}]]]]
|
||||
["Draw" (fn []
|
||||
(let [file (draw/file-name)
|
||||
path (str config/default-draw-directory "/" file)
|
||||
text (util/format "[[%s]]" path)]
|
||||
(p/let [_ (draw/create-draw-with-default-content path)]
|
||||
(println "draw file created, " path))
|
||||
text))]
|
||||
["WAITING" (->marker "WAITING")]
|
||||
["CANCELED" (->marker "CANCELED")]
|
||||
["Tomorrow" #(get-page-ref-text (date/tomorrow))]
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
[goog.dom :as gdom]
|
||||
[frontend.handler.expand :as expand]
|
||||
[frontend.components.svg :as svg]
|
||||
[frontend.components.draw :as draw]
|
||||
[frontend.components.datetime :as datetime-comp]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
|
@ -48,7 +47,8 @@
|
|||
[frontend.commands :as commands]
|
||||
[lambdaisland.glogi :as log]
|
||||
[frontend.context.i18n :as i18n]
|
||||
[frontend.template :as template]))
|
||||
[frontend.template :as template]
|
||||
[shadow.loader :as loader]))
|
||||
|
||||
;; TODO: remove rum/with-context because it'll make reactive queries not working
|
||||
|
||||
|
@ -385,32 +385,41 @@
|
|||
full-path (.. util/node-path (join repo-path (config/get-local-asset-absolute-path path)))]
|
||||
[:a.asset-ref {:target "_blank" :href full-path} (or title path)]))
|
||||
|
||||
(defonce excalidraw-loaded? (atom false))
|
||||
(rum/defc excalidraw < rum/reactive
|
||||
{:init (fn [state]
|
||||
(p/let [_ (loader/load :excalidraw)]
|
||||
(reset! excalidraw-loaded? true))
|
||||
state)}
|
||||
[file]
|
||||
(let [loaded? (rum/react excalidraw-loaded?)
|
||||
draw-component (if loaded?
|
||||
(resolve 'frontend.extensions.excalidraw/draw))]
|
||||
(when draw-component
|
||||
(draw-component {:file file}))))
|
||||
|
||||
(rum/defc page-reference < rum/reactive
|
||||
[html-export? s config label]
|
||||
(let [show-brackets? (state/show-brackets?)
|
||||
nested-link? (:nested-link? config)
|
||||
contents-page? (= "contents" (string/lower-case (str (:id config))))]
|
||||
[:span.page-reference
|
||||
(when (and (or show-brackets? nested-link?)
|
||||
(not html-export?)
|
||||
(not contents-page?))
|
||||
[:span.text-gray-500.bracket "[["])
|
||||
(if (string/ends-with? s ".excalidraw")
|
||||
[:a.page-ref
|
||||
{:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(set! (.-href js/window.location)
|
||||
(rfe/href :draw nil {:file (string/replace s (str config/default-draw-directory "/") "")})))}
|
||||
[:span
|
||||
(svg/excalidraw-logo)
|
||||
(string/capitalize (draw/get-file-title s))]]
|
||||
contents-page? (= "contents" (string/lower-case (str (:id config))))
|
||||
draw? (string/ends-with? s ".excalidraw")]
|
||||
(if (string/ends-with? s ".excalidraw")
|
||||
[:div.draw {:on-click (fn [e]
|
||||
(.stopPropagation e))}
|
||||
(excalidraw s)]
|
||||
[:span.page-reference
|
||||
(when (and (or show-brackets? nested-link?)
|
||||
(not html-export?)
|
||||
(not contents-page?))
|
||||
[:span.text-gray-500.bracket "[["])
|
||||
(page-cp (assoc config
|
||||
:label (mldoc/plain->text label)
|
||||
:contents-page? contents-page?) {:page/name s}))
|
||||
(when (and (or show-brackets? nested-link?)
|
||||
(not html-export?)
|
||||
(not contents-page?))
|
||||
[:span.text-gray-500.bracket "]]"])]))
|
||||
:contents-page? contents-page?) {:page/name s})
|
||||
(when (and (or show-brackets? nested-link?)
|
||||
(not html-export?)
|
||||
(not contents-page?))
|
||||
[:span.text-gray-500.bracket "]]"])])))
|
||||
|
||||
(defn- latex-environment-content
|
||||
[name option content]
|
||||
|
|
|
@ -1,497 +0,0 @@
|
|||
(ns frontend.components.draw
|
||||
(:require [rum.core :as rum]
|
||||
[goog.object :as gobj]
|
||||
[frontend.rum :as r]
|
||||
[frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.mixins :as mixins]
|
||||
[frontend.storage :as storage]
|
||||
[frontend.components.svg :as svg]
|
||||
[cljs-bean.core :as bean]
|
||||
[dommy.core :as d]
|
||||
[clojure.string :as string]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.draw :as draw :refer
|
||||
[*files
|
||||
*current-file
|
||||
*current-title
|
||||
*file-loading?
|
||||
*elements
|
||||
*unsaved?
|
||||
*search-files
|
||||
*saving-title
|
||||
*excalidraw]]
|
||||
[frontend.handler.file :as file]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.loader :as loader]
|
||||
[frontend.config :as config]
|
||||
[frontend.state :as state]
|
||||
[frontend.search :as search]
|
||||
[frontend.components.repo :as repo]
|
||||
[promesa.core :as p]
|
||||
[reitit.frontend.easy :as rfe]))
|
||||
|
||||
(defn loaded? []
|
||||
js/window.Excalidraw)
|
||||
|
||||
(defonce *loaded? (atom false))
|
||||
|
||||
(defonce draw-state :draw-state)
|
||||
|
||||
(defn get-draw-state []
|
||||
(storage/get draw-state))
|
||||
(defn set-draw-state! [value]
|
||||
(storage/set draw-state value))
|
||||
(defn get-k
|
||||
([k]
|
||||
(get-k k (state/get-current-repo)))
|
||||
([repo k]
|
||||
(when repo
|
||||
(get-in (get-draw-state) [repo k]))))
|
||||
|
||||
(defn set-k
|
||||
[k v]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [state (get-draw-state)]
|
||||
(let [new-state (assoc-in state [repo k] v)]
|
||||
(set-draw-state! new-state)))))
|
||||
|
||||
(defn get-last-file
|
||||
([]
|
||||
(get-k :last-file))
|
||||
([repo]
|
||||
(get-k repo :last-file)))
|
||||
|
||||
(defn get-last-title
|
||||
([]
|
||||
(get-k :last-title))
|
||||
([repo]
|
||||
(get-k repo :last-title)))
|
||||
|
||||
(defn set-last-file!
|
||||
[value]
|
||||
(set-k :last-file value))
|
||||
(defn set-last-title!
|
||||
[value]
|
||||
(set-k :last-title value))
|
||||
|
||||
(defn get-last-elements
|
||||
[]
|
||||
(storage/get-json (str (state/get-current-repo) "-" "last-elements")))
|
||||
(defn get-last-app-state
|
||||
[]
|
||||
(storage/get-json (str (state/get-current-repo) "-" "last-app-state")))
|
||||
|
||||
(defn set-last-elements!
|
||||
[value]
|
||||
(storage/set-json (str (state/get-current-repo) "-" "last-elements") value))
|
||||
(defn set-last-app-state!
|
||||
[value]
|
||||
(storage/set-json (str (state/get-current-repo) "-" "last-app-state") value))
|
||||
|
||||
(defn set-excalidraw-component!
|
||||
[]
|
||||
(reset! *excalidraw (r/adapt-class
|
||||
(gobj/get js/window.Excalidraw "default"))))
|
||||
|
||||
(defn serialize-as-json
|
||||
[elements app-state]
|
||||
(when (loaded?)
|
||||
(when-let [f (gobj/get js/window.Excalidraw "serializeAsJSON")]
|
||||
(f elements app-state))))
|
||||
|
||||
;; api restore
|
||||
|
||||
(defn from-json
|
||||
[text]
|
||||
(when-not (string/blank? text)
|
||||
(try
|
||||
(when-let [data (js/JSON.parse text)]
|
||||
(if (not= "excalidraw" (gobj/get data "type"))
|
||||
(notification/show!
|
||||
(util/format "Could not load this invalid excalidraw file")
|
||||
:error)
|
||||
{:elements (gobj/get data "elements")
|
||||
:app-state (gobj/get data "appState")}))
|
||||
(catch js/Error e
|
||||
(prn "from json error:")
|
||||
(js/console.dir e)
|
||||
(notification/show!
|
||||
(util/format "Could not load this invalid excalidraw file")
|
||||
:error)))))
|
||||
|
||||
(defn get-file-title
|
||||
[file]
|
||||
(when file
|
||||
(let [s (subs file 20)
|
||||
title (string/replace s ".excalidraw" "")]
|
||||
(string/replace title "-" " "))))
|
||||
|
||||
(defn save-excalidraw!
|
||||
[state _event file ok-handler]
|
||||
(let [title @*current-title]
|
||||
(cond
|
||||
(string/blank? title)
|
||||
(do
|
||||
(reset! *saving-title nil)
|
||||
(notification/show!
|
||||
"Please specify a title first!"
|
||||
:error)
|
||||
;; TODO: focus the title input
|
||||
)
|
||||
|
||||
(= title @*saving-title)
|
||||
nil
|
||||
|
||||
:else
|
||||
(when-let [elements (get-last-elements)]
|
||||
(reset! *saving-title title)
|
||||
(let [app-state (get-last-app-state)
|
||||
[option] (:rum/args state)
|
||||
file (util/trim-safe
|
||||
(or
|
||||
file
|
||||
@*current-file
|
||||
(:file option)
|
||||
(draw/title->file-name title)))
|
||||
data (serialize-as-json elements app-state)]
|
||||
(when file
|
||||
(draw/save-excalidraw! file data
|
||||
(fn [file]
|
||||
(reset! *files
|
||||
(distinct (conj @*files file)))
|
||||
(reset! *current-file file)
|
||||
(reset! *unsaved? false)
|
||||
(set-last-file! file)
|
||||
(when ok-handler (ok-handler file))
|
||||
(reset! *saving-title nil)))))))))
|
||||
|
||||
(defn- clear-canvas!
|
||||
[]
|
||||
(when-let [canvas (d/by-id "canvas")]
|
||||
(let [context (.getContext canvas "2d")]
|
||||
(.clearRect context 0 0 (gobj/get canvas "width") (gobj/get canvas "height"))
|
||||
(set! (.-fillStyle context) "#FFF")
|
||||
(.fillRect context 0 0 (gobj/get canvas "width") (gobj/get canvas "height")))))
|
||||
|
||||
(defn- new-file!
|
||||
[]
|
||||
;; TODO: save current firstly
|
||||
(clear-canvas!)
|
||||
(reset! *current-title "")
|
||||
(reset! *current-file nil)
|
||||
(reset! *elements nil)
|
||||
(set-last-elements! nil)
|
||||
(set-last-title! nil)
|
||||
(set-last-file! nil)
|
||||
(set-last-app-state! nil))
|
||||
|
||||
(defn- rename-file!
|
||||
[file new-title]
|
||||
(when-not (string/blank? new-title)
|
||||
(let [new-file (draw/title->file-name new-title)]
|
||||
(when-not (= (string/trim file) (string/trim new-file))
|
||||
(save-excalidraw!
|
||||
{} {} new-file
|
||||
(fn []
|
||||
(set-last-file! new-file)
|
||||
(util/p-handle
|
||||
(file/remove-file!
|
||||
(state/get-current-repo)
|
||||
(str config/default-draw-directory "/" file))
|
||||
(fn [_]
|
||||
(reset! *files (->> (conj @*files new-file)
|
||||
(remove #(= file %))
|
||||
distinct
|
||||
(vec)))
|
||||
(reset! *current-file new-file)
|
||||
(notification/show!
|
||||
"File was renamed successfully!"
|
||||
:success))
|
||||
(fn [error]
|
||||
(println "Rename file failed, reason: ")
|
||||
(js/console.dir error)))))))))
|
||||
|
||||
(rum/defc draw-title < rum/reactive
|
||||
(mixins/event-mixin
|
||||
(fn [state]
|
||||
(let [old-title @*current-title]
|
||||
(mixins/hide-when-esc-or-outside
|
||||
state
|
||||
:on-hide (fn [state e event]
|
||||
(let [title (and @*current-title (string/trim @*current-title))
|
||||
file @*current-file]
|
||||
(when (or
|
||||
(string/blank? old-title)
|
||||
(not= (string/trim old-title) title))
|
||||
(cond
|
||||
(and file (not (string/blank? title)))
|
||||
(rename-file! file title)
|
||||
|
||||
(and (not file)
|
||||
(not (string/blank? title))
|
||||
(seq @*elements)) ; new file
|
||||
(save-excalidraw! {} {} nil nil)
|
||||
|
||||
:else
|
||||
nil))))))
|
||||
state))
|
||||
[]
|
||||
(let [current-title (rum/react *current-title)]
|
||||
[:input#draw-title.font-medium.w-48.px-2.py-1.ml-2
|
||||
{:on-click (fn [e]
|
||||
(util/stop e))
|
||||
:placeholder "Untitled"
|
||||
:auto-complete "off"
|
||||
:default-value (or (and current-title (string/capitalize current-title)) "")
|
||||
:on-change (fn [e]
|
||||
(when-let [value (util/evalue e)]
|
||||
(set-last-title! value)
|
||||
(reset! *current-title value)))}]))
|
||||
|
||||
(rum/defc files-search < rum/reactive
|
||||
[state]
|
||||
[:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
|
||||
[:div.absolute.inset-y-0.flex.items-center.pointer-events-none.left-3
|
||||
[:svg.h-4.w-4
|
||||
{:view-box "0 0 20 20", :fill "currentColor"}
|
||||
[:path
|
||||
{:d
|
||||
"M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
|
||||
:clip-rule "evenodd",
|
||||
:fill-rule "evenodd"}]]]
|
||||
[:input.block.w-full.pl-2.sm:text-sm.sm:leading-3.mb-2.mt-2.border-none.outline-none.focus:outline-none
|
||||
{:style {:padding-left "2rem"
|
||||
:border-radius 0}
|
||||
:placeholder "Search"
|
||||
:auto-complete "off"
|
||||
:on-change (fn [e]
|
||||
(let [value (util/evalue e)
|
||||
files @*files]
|
||||
(reset! *search-files
|
||||
(if (string/blank? value)
|
||||
files
|
||||
(search/fuzzy-search files value :limit 10)))))}]])
|
||||
|
||||
(rum/defcs save-button < rum/reactive
|
||||
[state]
|
||||
(let [unsaved? (rum/react *unsaved?)]
|
||||
[:a.ml-2 {:title (if unsaved? "Save changes" "Save")
|
||||
:on-click (fn [e]
|
||||
(save-excalidraw! state e nil nil))}
|
||||
[:div.ToolIcon__icon {:class (if unsaved? "bg-orange-400" "bg-gray-200")
|
||||
:style {:width "2rem"
|
||||
:height "2rem"}}
|
||||
svg/save]]))
|
||||
|
||||
(rum/defcs files < rum/reactive
|
||||
[state]
|
||||
(let [all-files (rum/react *files)
|
||||
search-files (rum/react *search-files)
|
||||
files (if (seq search-files) search-files all-files)
|
||||
current-file (rum/react *current-file)
|
||||
unsaved? (rum/react *unsaved?)]
|
||||
[:div.flex-row.flex.items-center
|
||||
[:a.ml-2 {:title "New file"
|
||||
:on-click new-file!}
|
||||
[:div.ToolIcon__icon.bg-gray-200 {:style {:width "2rem"
|
||||
:height "2rem"}}
|
||||
svg/plus]]
|
||||
|
||||
(ui/dropdown-with-links
|
||||
(fn [{:keys [toggle-fn]}]
|
||||
[:div.ToolIcon__icon.ml-2.cursor.bg-gray-200 {:title "List files"
|
||||
:on-click toggle-fn
|
||||
:style {:width "2rem"
|
||||
:height "2rem"}}
|
||||
svg/folder])
|
||||
(mapv
|
||||
(fn [file]
|
||||
{:title (get-file-title file)
|
||||
:options {:title file
|
||||
:on-click
|
||||
(fn [e]
|
||||
(util/stop e)
|
||||
(set-last-file! file)
|
||||
(reset! *current-file file)
|
||||
(reset! *current-title (get-file-title file))
|
||||
(reset! *search-files []))}})
|
||||
files)
|
||||
{:modal-class (util/hiccup->class
|
||||
"origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.bg-white.w-48.dropdown-overflow-auto")
|
||||
:links-header (when (>= (count all-files) 5)
|
||||
(files-search))})
|
||||
|
||||
(save-button)
|
||||
|
||||
(let [links (->> [(when @*current-file
|
||||
{:title "Delete"
|
||||
:options {:style {:color "#db1111"}
|
||||
:on-click (fn [e]
|
||||
(util/stop e)
|
||||
(when-let [current-file @*current-file]
|
||||
(p/let [_ (file/remove-file! (state/get-current-repo)
|
||||
(str config/default-draw-directory "/" current-file))]
|
||||
(reset! *files (remove #(= current-file %) @*files))
|
||||
(new-file!))))}})]
|
||||
(remove nil?))]
|
||||
(when (seq links)
|
||||
(ui/dropdown-with-links
|
||||
(fn [{:keys [toggle-fn]}]
|
||||
[:div.ToolIcon__icon.ml-2.cursor.bg-gray-200
|
||||
{:title "More options"
|
||||
:on-click toggle-fn
|
||||
:style {:width "2rem"
|
||||
:height "2rem"}}
|
||||
(svg/vertical-dots nil)])
|
||||
links
|
||||
{:modal-class (util/hiccup->class
|
||||
"origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.bg-white.w-48.dropdown-overflow-auto")})))
|
||||
|
||||
(draw-title)]))
|
||||
|
||||
(defn- set-canvas-actions-style!
|
||||
[state]
|
||||
(when-let [section (first (d/by-tag "section"))]
|
||||
(when (= "canvasActions-title" (d/attr section "aria-labelledby"))
|
||||
(d/set-style! section "margin-top" "48px")))
|
||||
state)
|
||||
|
||||
(rum/defcs draw-inner < rum/reactive
|
||||
(mixins/keyboard-mixin (util/->system-modifier "ctrl+s")
|
||||
(fn [state e]
|
||||
(save-excalidraw! state e nil nil)))
|
||||
(mixins/keyboard-mixin "alt+z" set-canvas-actions-style!)
|
||||
{:init (fn [state]
|
||||
(reset! *elements nil)
|
||||
(let [[option] (:rum/args state)
|
||||
file (or @*current-file
|
||||
(:file option))]
|
||||
(do
|
||||
(reset! *current-title (get-file-title file))
|
||||
(set-last-file! file))
|
||||
(cond
|
||||
file
|
||||
(do
|
||||
(reset! *file-loading? true)
|
||||
(draw/load-excalidraw-file
|
||||
file
|
||||
(fn [data]
|
||||
(let [{:keys [elements app-state]} (from-json data)]
|
||||
(reset! *elements elements)
|
||||
(reset! *file-loading? false)))))
|
||||
|
||||
:else
|
||||
(when-let [elements (get-last-elements)]
|
||||
;; TODO: keep this for history undo
|
||||
(reset! *elements (remove #(gobj/get % "isDeleted") elements))))
|
||||
(assoc state
|
||||
::layout (atom [js/window.innerWidth js/window.innerHeight]))))
|
||||
:did-mount set-canvas-actions-style!
|
||||
:did-update set-canvas-actions-style!}
|
||||
[state option]
|
||||
(let [current-repo (state/sub :git/current-repo)
|
||||
elements (rum/react *elements)
|
||||
loading? (rum/react *file-loading?)
|
||||
file (rum/react *current-file)
|
||||
layout (get state ::layout)
|
||||
[width height] (rum/react layout)
|
||||
options (bean/->js {:zenModeEnabled true
|
||||
:viewBackgroundColor "#FFF"})
|
||||
excalidraw-component @*excalidraw]
|
||||
[:div.draw.white-theme {:style {:background "#FFF"}}
|
||||
(when (and (or (and file elements)
|
||||
(nil? file))
|
||||
excalidraw-component)
|
||||
(excalidraw-component
|
||||
{:width (get option :width width)
|
||||
:height (get option :height height)
|
||||
:on-resize (fn []
|
||||
(reset! layout [js/window.innerWidth js/window.innerHeight]))
|
||||
|
||||
:on-change (or (:on-change option)
|
||||
(fn [elements state]
|
||||
(when (not= (bean/->clj elements)
|
||||
(bean/->clj @*elements))
|
||||
(reset! *unsaved? true))
|
||||
(set-last-elements! elements)
|
||||
(set-last-app-state! state)
|
||||
(reset! *elements elements)))
|
||||
:options options
|
||||
:user (bean/->js {:name (or (:user-name option)
|
||||
(:name (state/get-me))
|
||||
(util/unique-id))})
|
||||
:on-username-change (fn [])
|
||||
:initial-data (or elements #js [])}))
|
||||
[:div.absolute.top-4.left-4.hidden.md:block
|
||||
[:div.flex.flex-row.items-center
|
||||
[:a.mr-3.opacity-70.hover:opacity-100 {:href (rfe/href :home)
|
||||
:title "Back to logseq"}
|
||||
(svg/logo false)]
|
||||
(files)
|
||||
(when loading?
|
||||
svg/loading)]]
|
||||
(ui/notification)
|
||||
|
||||
(when current-repo
|
||||
[:div.absolute.top-4.right-4.hidden.md:block
|
||||
[:div.flex.flex-row.items-center
|
||||
(repo/sync-status current-repo)
|
||||
(repo/repos-dropdown true
|
||||
(fn [repo]
|
||||
(reset! *current-file (get-last-file repo))))]])]))
|
||||
|
||||
(rum/defcs draw-2 < rum/reactive
|
||||
{:init (fn [state]
|
||||
(let [repo (storage/get :git/current-repo)]
|
||||
|
||||
(let [current-title (get-last-title repo)]
|
||||
(reset! *current-title current-title))
|
||||
(let [current-file (or
|
||||
(get-in (first (:rum/args state))
|
||||
[:query-params :file])
|
||||
(get-last-file repo))]
|
||||
(reset! *current-file current-file)
|
||||
(reset! *current-title (get-file-title current-file))))
|
||||
|
||||
(if (loaded?)
|
||||
(set-excalidraw-component!)
|
||||
(loader/load
|
||||
(config/asset-uri "/static/js/excalidraw.min.js")
|
||||
(fn []
|
||||
(reset! *loaded? true)
|
||||
(set-excalidraw-component!))))
|
||||
|
||||
(draw/get-all-excalidraw-files
|
||||
(fn [files]
|
||||
(reset! *files (distinct files))))
|
||||
|
||||
(state/set-draw! true)
|
||||
state)
|
||||
:will-unmount (fn [state]
|
||||
(state/set-draw! false)
|
||||
state)}
|
||||
[state option]
|
||||
(let [loaded? (or (loaded?)
|
||||
(rum/react *loaded?))
|
||||
current-repo (state/sub :git/current-repo)
|
||||
component (rum/react *excalidraw)]
|
||||
(if component
|
||||
(let [current-file (rum/react *current-file)
|
||||
current-file (or current-file
|
||||
(and current-repo
|
||||
(get-last-file current-repo)))]
|
||||
(let [key (if current-repo
|
||||
(str current-repo "-"
|
||||
(or (and current-file (str "draw-" current-file))
|
||||
"draw-with-no-file"))
|
||||
"draw-with-no-file")]
|
||||
(rum/with-key (draw-inner option) key)))
|
||||
[:div.center svg/loading])))
|
||||
|
||||
(rum/defc draw < rum/reactive
|
||||
[option]
|
||||
(let [db-restoring? (state/sub :db/restoring?)]
|
||||
(if db-restoring?
|
||||
[:div.ls-center
|
||||
(ui/loading "Loading")]
|
||||
(draw-2 option))))
|
|
@ -1,23 +0,0 @@
|
|||
#draw {
|
||||
-webkit-app-region: no-drag;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#draw iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.draw {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.excalidraw-embed .draw {
|
||||
position: relative;
|
||||
}
|
|
@ -131,7 +131,7 @@
|
|||
(ui/loading (t :loading))]]
|
||||
|
||||
:else
|
||||
[:div {:style {:margin-bottom (if global-graph-pages? 0 120)}}
|
||||
[:div.max-w-7xl.mx-auto {:style {:margin-bottom (if global-graph-pages? 0 120)}}
|
||||
main-content])]]
|
||||
(right-sidebar/sidebar)]))
|
||||
|
||||
|
@ -176,7 +176,7 @@
|
|||
preferred-format (state/sub [:me :preferred_format])
|
||||
logged? (:name me)]
|
||||
(rum/with-context [[t] i18n/*tongue-context*]
|
||||
[:div.max-w-7xl.mx-auto
|
||||
[:div
|
||||
(cond
|
||||
(and default-home
|
||||
(= :home (state/get-current-route))
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
|
||||
(defn content-encrypted?
|
||||
[content]
|
||||
(or (str/starts-with? content age-pem-header-line)
|
||||
(str/starts-with? content age-version-line)))
|
||||
(when content
|
||||
(or (str/starts-with? content age-pem-header-line)
|
||||
(str/starts-with? content age-version-line))))
|
||||
|
||||
(defn encrypted-db?
|
||||
[repo-url]
|
||||
|
@ -100,4 +101,4 @@
|
|||
lazy-decrypt-with-user-passphrase (resolve 'frontend.extensions.age-encryption/decrypt-with-user-passphrase)
|
||||
content (utf8/encode content)
|
||||
decrypted (lazy-decrypt-with-user-passphrase passphrase content)]
|
||||
(utf8/decode decrypted)))
|
||||
(utf8/decode decrypted)))
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
(ns frontend.extensions.excalidraw
|
||||
(:require [rum.core :as rum]
|
||||
[goog.object :as gobj]
|
||||
[frontend.rum :as r]
|
||||
[frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.mixins :as mixins]
|
||||
[frontend.storage :as storage]
|
||||
[frontend.components.svg :as svg]
|
||||
[cljs-bean.core :as bean]
|
||||
[dommy.core :as d]
|
||||
[clojure.string :as string]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.draw :as draw]
|
||||
[frontend.handler.file :as file]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.loader :as loader]
|
||||
[frontend.config :as config]
|
||||
[frontend.state :as state]
|
||||
[frontend.search :as search]
|
||||
[frontend.components.repo :as repo]
|
||||
[promesa.core :as p]
|
||||
[reitit.frontend.easy :as rfe]
|
||||
["@excalidraw/excalidraw" :as Excalidraw]))
|
||||
|
||||
(def excalidraw (r/adapt-class (gobj/get Excalidraw "default")))
|
||||
|
||||
(defn from-json
|
||||
[text]
|
||||
(when-not (string/blank? text)
|
||||
(try
|
||||
(js/JSON.parse text)
|
||||
(catch js/Error e
|
||||
(println "from json error:")
|
||||
(js/console.dir e)
|
||||
(notification/show!
|
||||
(util/format "Could not load this invalid excalidraw file")
|
||||
:error)))))
|
||||
|
||||
(defonce *bounding-width (atom nil))
|
||||
(defn- get-bounding-width
|
||||
[ref]
|
||||
(when ref
|
||||
(when-let [current (gobj/get ref "current")]
|
||||
(-> current
|
||||
(.getBoundingClientRect)
|
||||
(gobj/get "width")))))
|
||||
|
||||
(rum/defcs draw-inner < rum/reactive
|
||||
(rum/local true ::zen-mode?)
|
||||
(rum/local false ::view-mode?)
|
||||
(rum/local nil ::elements)
|
||||
[state data option]
|
||||
(let [current-repo (state/sub :git/current-repo)
|
||||
bounding-width (rum/react *bounding-width)
|
||||
*zen-mode? (get state ::zen-mode?)
|
||||
*view-mode? (get state ::view-mode?)
|
||||
wide-mode? (state/sub :ui/wide-mode?)
|
||||
*elements (get state ::elements)
|
||||
file (:file option)]
|
||||
(when data
|
||||
[:div.overflow-hidden
|
||||
[:div.my-1 {:style {:font-size 10}}
|
||||
[:a.mr-2 {:on-click ui-handler/toggle-wide-mode!}
|
||||
(util/format "Wide Mode (%s)" (if wide-mode? "ON" "OFF"))]
|
||||
[:a.mr-2 {:on-click #(swap! *zen-mode? not)}
|
||||
(util/format "Zen Mode (%s)" (if @*zen-mode? "ON" "OFF"))]
|
||||
[:a.mr-2 {:on-click #(swap! *view-mode? not)}
|
||||
(util/format "View Mode (%s)" (if @*view-mode? "ON" "OFF"))]]
|
||||
[:div
|
||||
(excalidraw
|
||||
(merge
|
||||
{:on-change (fn [elements state]
|
||||
(let [elements->clj (bean/->clj elements)]
|
||||
(when (and (seq elements->clj)
|
||||
(not= elements @*elements))
|
||||
(let [state (bean/->clj state)]
|
||||
(draw/save-excalidraw!
|
||||
file
|
||||
(-> {:type "excalidraw"
|
||||
:version 2
|
||||
:source config/website
|
||||
:elements elements
|
||||
:appState (select-keys state [:gridSize :viewBackgroundColor])}
|
||||
bean/->js
|
||||
(js/JSON.stringify)))
|
||||
(reset! *elements elements)))))
|
||||
:zen-mode-enabled @*zen-mode?
|
||||
:view-mode-enabled @*view-mode?
|
||||
:grid-mode-enabled false
|
||||
:initial-data data}
|
||||
(if wide-mode?
|
||||
{:height 650}
|
||||
{:width 800
|
||||
:height 500})))]])))
|
||||
|
||||
(rum/defcs draw-container < rum/reactive
|
||||
{:init (fn [state]
|
||||
(let [[option] (:rum/args state)
|
||||
file (:file option)
|
||||
*data (atom nil)
|
||||
*loading? (atom true)]
|
||||
(when file
|
||||
(draw/load-excalidraw-file
|
||||
file
|
||||
(fn [data]
|
||||
(let [data (from-json data)]
|
||||
(reset! *data data)
|
||||
(reset! *loading? false)))))
|
||||
(assoc state
|
||||
::data *data
|
||||
::loading? *loading?)))}
|
||||
[state option]
|
||||
(let [*data (get state ::data)
|
||||
*loading? (get state ::loading?)
|
||||
loading? (rum/react *loading?)
|
||||
data (rum/react *data)
|
||||
db-restoring? (state/sub :db/restoring?)]
|
||||
(when (:file option)
|
||||
(cond
|
||||
db-restoring?
|
||||
[:div.ls-center
|
||||
(ui/loading "Loading")]
|
||||
|
||||
(false? loading?)
|
||||
(draw-inner data option)
|
||||
|
||||
:else ; loading
|
||||
nil))))
|
||||
|
||||
(rum/defc draw < rum/reactive
|
||||
[option]
|
||||
(let [repo (state/get-current-repo)
|
||||
granted? (state/sub [:nfs/user-granted? repo])]
|
||||
(when-not (and (config/local-db? repo) (not granted?))
|
||||
(draw-container option))))
|
|
@ -133,11 +133,13 @@
|
|||
not-changed? (= last-modified-at local-last-modified-at)
|
||||
format (-> (util/get-file-ext path)
|
||||
(config/get-file-format))
|
||||
pending-writes (state/get-write-chan-length)]
|
||||
pending-writes (state/get-write-chan-length)
|
||||
draw? (and path (string/ends-with? path ".excalidraw"))]
|
||||
(if (and local-content (or old-content
|
||||
;; temporally fix
|
||||
(and path (string/ends-with? path ".excalidraw"))) new?
|
||||
draw?) new?
|
||||
(or
|
||||
draw?
|
||||
;; Writing not finished
|
||||
(> pending-writes 0)
|
||||
;; not changed by other editors
|
||||
|
|
|
@ -14,37 +14,6 @@
|
|||
[cljs-time.core :as t]
|
||||
[cljs-time.coerce :as tc]))
|
||||
|
||||
;; state
|
||||
(defonce *files (atom nil))
|
||||
(defonce *current-file (atom nil))
|
||||
(defonce *current-title (atom ""))
|
||||
(defonce *file-loading? (atom nil))
|
||||
(defonce *elements (atom nil))
|
||||
(defonce *unsaved? (atom false))
|
||||
(defonce *search-files (atom []))
|
||||
(defonce *saving-title (atom nil))
|
||||
(defonce *excalidraw (atom nil))
|
||||
|
||||
;; TODO: refactor
|
||||
(defonce draw-state :draw-state)
|
||||
|
||||
(defn get-draw-state []
|
||||
(storage/get draw-state))
|
||||
(defn set-draw-state! [value]
|
||||
(storage/set draw-state value))
|
||||
|
||||
(defn set-k
|
||||
[k v]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [state (get-draw-state)]
|
||||
(let [new-state (assoc-in state [repo k] v)]
|
||||
(set-draw-state! new-state)))))
|
||||
|
||||
(defn set-last-file!
|
||||
[value]
|
||||
(set-k :last-file value))
|
||||
|
||||
;; excalidraw
|
||||
(defn create-draws-directory!
|
||||
[repo]
|
||||
(when repo
|
||||
|
@ -55,8 +24,8 @@
|
|||
(fn [_error] nil)))))
|
||||
|
||||
(defn save-excalidraw!
|
||||
[file data ok-handler]
|
||||
(let [path (str config/default-draw-directory "/" file)
|
||||
[file data]
|
||||
(let [path file
|
||||
repo (state/get-current-repo)]
|
||||
(when repo
|
||||
(let [repo-dir (config/get-repo-dir repo)]
|
||||
|
@ -65,7 +34,6 @@
|
|||
(create-draws-directory! repo)
|
||||
(fs/write-file! repo repo-dir path data nil)
|
||||
(git-handler/git-add repo path)
|
||||
(ok-handler file)
|
||||
(db/transact! repo
|
||||
[{:file/path path
|
||||
:page/name file
|
||||
|
@ -75,58 +43,30 @@
|
|||
(prn "Write file failed, path: " path ", data: " data)
|
||||
(js/console.dir error))))))))
|
||||
|
||||
(defn get-all-excalidraw-files
|
||||
[ok-handler]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(p/let [_ (create-draws-directory! repo)]
|
||||
(let [dir (str (config/get-repo-dir repo)
|
||||
"/"
|
||||
config/default-draw-directory)]
|
||||
(util/p-handle
|
||||
(fs/readdir dir)
|
||||
(fn [files]
|
||||
(let [files (-> (filter #(string/ends-with? % ".excalidraw") files)
|
||||
(distinct)
|
||||
(sort)
|
||||
(reverse))]
|
||||
(ok-handler files)))
|
||||
(fn [error]
|
||||
(js/console.dir error)))))))
|
||||
|
||||
(defn load-excalidraw-file
|
||||
[file ok-handler]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(util/p-handle
|
||||
(file-handler/load-file repo (str config/default-draw-directory "/" file))
|
||||
(file-handler/load-file repo file)
|
||||
(fn [content]
|
||||
(ok-handler content))
|
||||
(fn [error]
|
||||
(prn "Error loading " file ": "
|
||||
error)))))
|
||||
(println "Error loading " file ": "
|
||||
error)))))
|
||||
|
||||
(defonce default-content
|
||||
(util/format
|
||||
"{\n \"type\": \"excalidraw\",\n \"version\": 2,\n \"source\": \"%s\",\n \"elements\": [],\n \"appState\": {\n \"viewBackgroundColor\": \"#FFF\",\n \"gridSize\": null\n }\n}"
|
||||
config/website))
|
||||
|
||||
(defn title->file-name
|
||||
[title]
|
||||
(when (not (string/blank? title))
|
||||
(let [title (string/lower-case (string/replace title " " "-"))]
|
||||
(str (date/get-date-time-string-2) "-" title ".excalidraw"))))
|
||||
(defn file-name
|
||||
[]
|
||||
(str (date/get-date-time-string-2) ".excalidraw"))
|
||||
|
||||
(defn create-draw-with-default-content
|
||||
[current-file ok-handler]
|
||||
[current-file]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(p/let [exists? (fs/file-exists? (config/get-repo-dir repo)
|
||||
(str config/default-draw-directory current-file))]
|
||||
(when-not exists?
|
||||
(save-excalidraw! current-file default-content
|
||||
(fn [file]
|
||||
(reset! *files
|
||||
(distinct (conj @*files file)))
|
||||
(reset! *current-file file)
|
||||
(reset! *unsaved? false)
|
||||
(set-last-file! file)
|
||||
(reset! *saving-title nil)
|
||||
(ok-handler)))))))
|
||||
(save-excalidraw! current-file default-content)))))
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
[frontend.handler.repo :as repo-handler]
|
||||
[frontend.handler.file :as file-handler]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.draw :as draw]
|
||||
[frontend.handler.expand :as expand]
|
||||
[frontend.handler.block :as block-handler]
|
||||
[frontend.format.mldoc :as mldoc]
|
||||
|
@ -2112,22 +2111,6 @@
|
|||
(get-link format link label)
|
||||
format
|
||||
{:last-pattern (str commands/slash "link")})))
|
||||
:draw
|
||||
(when-not (string/blank? (:title m))
|
||||
(let [file (draw/title->file-name (:title m))
|
||||
value (util/format
|
||||
"[[%s]]\n<iframe class=\"draw-iframe\" src=\"/#/draw?file=%s\" width=\"100%\" height=\"400\" frameborder=\"0\" allowfullscreen></iframe>"
|
||||
file
|
||||
file)]
|
||||
(insert-command! id
|
||||
value
|
||||
format
|
||||
{:last-pattern (str commands/slash "draw ")})
|
||||
(draw/create-draw-with-default-content
|
||||
file
|
||||
(fn []
|
||||
(let [input (gdom/getElement "download")]
|
||||
(.click input))))))
|
||||
nil)
|
||||
|
||||
(state/set-editor-show-input! nil)
|
||||
|
|
|
@ -92,3 +92,12 @@
|
|||
;; (state/get-custom-css-link)
|
||||
)]
|
||||
(util/add-style! style)))
|
||||
|
||||
(defn toggle-wide-mode!
|
||||
[]
|
||||
(let [wide? (state/get-wide-mode?)
|
||||
elements (array-seq (js/document.getElementsByClassName "cp__sidebar-main-content"))
|
||||
max-width (if wide? "var(--ls-main-content-max-width)" "100%")]
|
||||
(when-let [element (first elements)]
|
||||
(dom/set-style! element :max-width max-width))
|
||||
(state/toggle-wide-mode!)))
|
||||
|
|
|
@ -51,6 +51,8 @@
|
|||
(enable-when-not-editing-mode! ui-handler/toggle-contents!)
|
||||
(or (shortcut :editor/toggle-settings) "t s")
|
||||
(enable-when-not-editing-mode! ui-handler/toggle-settings-modal!)
|
||||
(or (shortcut :ui/toggle-wide-mode) "t w")
|
||||
(enable-when-not-editing-mode! ui-handler/toggle-wide-mode!)
|
||||
(or (shortcut :ui/toggle-between-page-and-file) "s")
|
||||
(enable-when-not-editing-mode! route-handler/toggle-between-page-and-file!)
|
||||
(or (shortcut :git/commit) "c")
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
[frontend.components.file :as file]
|
||||
[frontend.components.page :as page]
|
||||
[frontend.components.diff :as diff]
|
||||
[frontend.components.draw :as draw]
|
||||
[frontend.components.journal :as journal]
|
||||
[frontend.components.settings :as settings]
|
||||
[frontend.components.external :as external]
|
||||
|
@ -52,10 +51,6 @@
|
|||
{:name :diff
|
||||
:view diff/diff}]
|
||||
|
||||
["/draw"
|
||||
{:name :draw
|
||||
:view draw/draw}]
|
||||
|
||||
["/settings"
|
||||
{:name :settings
|
||||
:view settings/settings}]
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
:ui/sidebar-open? false
|
||||
:ui/left-sidebar-open? false
|
||||
:ui/theme (or (storage/get :ui/theme) "dark")
|
||||
:ui/wide-mode? false
|
||||
;; :show-all, :hide-block-body, :hide-block-children
|
||||
:ui/cycle-collapse :show-all
|
||||
:ui/collapsed-blocks {}
|
||||
|
@ -899,10 +900,13 @@
|
|||
(set-state! :indexeddb/support? value))
|
||||
|
||||
(defn set-modal!
|
||||
[modal-panel-content]
|
||||
(swap! state assoc
|
||||
:modal/show? (boolean modal-panel-content)
|
||||
:modal/panel-content modal-panel-content))
|
||||
([modal-panel-content]
|
||||
(set-modal! modal-panel-content false))
|
||||
([modal-panel-content fullscreen?]
|
||||
(swap! state assoc
|
||||
:modal/show? (boolean modal-panel-content)
|
||||
:modal/panel-content modal-panel-content
|
||||
:modal/fullscreen? fullscreen?)))
|
||||
|
||||
(defn close-modal!
|
||||
[]
|
||||
|
@ -992,6 +996,14 @@
|
|||
[]
|
||||
(get-in @state [:repo/changed-files (get-current-repo)]))
|
||||
|
||||
(defn get-wide-mode?
|
||||
[]
|
||||
(:ui/wide-mode? @state))
|
||||
|
||||
(defn toggle-wide-mode!
|
||||
[]
|
||||
(update-state! :ui/wide-mode? not))
|
||||
|
||||
(defn set-online!
|
||||
[value]
|
||||
(set-state! :network/online? value))
|
||||
|
|
|
@ -424,7 +424,7 @@
|
|||
[:div.absolute.inset-0.opacity-75]])
|
||||
|
||||
(rum/defc modal-panel
|
||||
[panel-content transition-state close-fn]
|
||||
[panel-content transition-state close-fn fullscreen?]
|
||||
[:div.ui__modal-panel.transform.transition-all.sm:min-w-lg.sm
|
||||
{:class (case transition-state
|
||||
"entering" "ease-out duration-300 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
|
@ -444,7 +444,7 @@
|
|||
:stroke-linejoin "round"
|
||||
:stroke-linecap "round"}]]]]
|
||||
|
||||
[:div.panel-content
|
||||
[:div {:class (if fullscreen? "" "panel-content")}
|
||||
(panel-content close-fn)]])
|
||||
|
||||
(rum/defc modal < rum/reactive
|
||||
|
@ -458,6 +458,7 @@
|
|||
:outside? false)))
|
||||
[]
|
||||
(let [modal-panel-content (state/sub :modal/panel-content)
|
||||
fullscreen? (state/sub :modal/fullscreen?)
|
||||
show? (boolean modal-panel-content)
|
||||
close-fn #(state/close-modal!)
|
||||
modal-panel-content (or modal-panel-content (fn [close] [:div]))]
|
||||
|
@ -470,7 +471,7 @@
|
|||
(css-transition
|
||||
{:in show? :timeout 0}
|
||||
(fn [state]
|
||||
(modal-panel modal-panel-content state close-fn)))]))
|
||||
(modal-panel modal-panel-content state close-fn fullscreen?)))]))
|
||||
|
||||
(defn make-confirm-modal
|
||||
[{:keys [tag title sub-title sub-checkbox? on-cancel on-confirm] :as opts}]
|
||||
|
|
Loading…
Reference in New Issue