mirror of
https://github.com/logseq/logseq.git
synced 2026-05-27 06:04:23 +00:00
refactor: remove web
This commit is contained in:
511
src/main/frontend/handler/dnd.cljs
Normal file
511
src/main/frontend/handler/dnd.cljs
Normal file
@@ -0,0 +1,511 @@
|
||||
(ns frontend.handler.dnd
|
||||
(:require [frontend.handler.notification :as notification]
|
||||
[frontend.handler.repo :as repo-handler]
|
||||
[frontend.config :as config]
|
||||
[frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.db :as db]
|
||||
[clojure.walk :as walk]
|
||||
[clojure.string :as string]
|
||||
[frontend.utf8 :as utf8]
|
||||
[cljs-time.coerce :as tc]
|
||||
[cljs-time.core :as t]))
|
||||
|
||||
(defn- remove-block-child!
|
||||
[target-block parent-block]
|
||||
(let [child-ids (set (db/get-block-ids target-block))]
|
||||
(db/get-block-content-rec
|
||||
parent-block
|
||||
(fn [{:block/keys [uuid level content]}]
|
||||
(if (contains? child-ids uuid)
|
||||
""
|
||||
content)))))
|
||||
|
||||
(defn- recompute-block-level
|
||||
[to-block nested?]
|
||||
(+ (:block/level to-block)
|
||||
(if nested? 1 0)))
|
||||
|
||||
(defn- recompute-block-content-and-changes
|
||||
[target-block to-block nested? same-repo? same-file?]
|
||||
(let [new-level (recompute-block-level to-block nested?)
|
||||
target-level (:block/level target-block)
|
||||
format (:block/format target-block)
|
||||
pattern (config/get-block-pattern format)
|
||||
block-changes (atom [])
|
||||
all-content (db/get-block-content-rec
|
||||
target-block
|
||||
(fn [{:block/keys [uuid level content]
|
||||
:as block}]
|
||||
(let [new-level (+ new-level (- level target-level))
|
||||
new-content (string/replace-first content
|
||||
(apply str (repeat level pattern))
|
||||
(apply str (repeat new-level pattern)))
|
||||
block (cond->
|
||||
{:block/uuid uuid
|
||||
:block/level new-level
|
||||
:block/content new-content
|
||||
:block/page (:block/page to-block)}
|
||||
|
||||
(not same-repo?)
|
||||
(merge (dissoc block [:block/level :block/content]))
|
||||
|
||||
(not same-file?)
|
||||
(merge {:block/page (:block/page to-block)
|
||||
:block/file (:block/file to-block)}))]
|
||||
(swap! block-changes conj block)
|
||||
new-content)))]
|
||||
[all-content @block-changes]))
|
||||
|
||||
(defn- move-parent-to-child?
|
||||
[target-block to-block]
|
||||
(let [to-block-id (:block/uuid to-block)
|
||||
result (atom false)
|
||||
_ (walk/postwalk
|
||||
(fn [form]
|
||||
(when (map? form)
|
||||
(when-let [id (:block/uuid form)]
|
||||
(when (= id to-block-id)
|
||||
(reset! result true))))
|
||||
form)
|
||||
target-block)]
|
||||
@result))
|
||||
|
||||
(defn- compute-target-child?
|
||||
[target-block to-block]
|
||||
(let [target-block-id (:block/uuid target-block)
|
||||
result (atom false)
|
||||
_ (walk/postwalk
|
||||
(fn [form]
|
||||
(when (map? form)
|
||||
(when-let [id (:block/uuid form)]
|
||||
(when (= id target-block-id)
|
||||
(reset! result true))))
|
||||
form)
|
||||
to-block)]
|
||||
@result))
|
||||
|
||||
(defn rebuild-dnd-blocks
|
||||
[repo file target-child? start-pos target-blocks offset-block-uuid {:keys [delete? same-file?]
|
||||
:or {delete? false
|
||||
same-file? true}}]
|
||||
(when (seq target-blocks)
|
||||
(let [file-id (:db/id file)
|
||||
target-block-ids (set (map :block/uuid target-blocks))
|
||||
after-blocks (->> (db/get-file-after-blocks repo file-id start-pos)
|
||||
(remove (fn [h] (contains? target-block-ids (:block/uuid h)))))
|
||||
|
||||
after-blocks (cond
|
||||
delete?
|
||||
after-blocks
|
||||
|
||||
(and offset-block-uuid
|
||||
(not (contains? (set (map :block/uuid after-blocks)) offset-block-uuid)))
|
||||
(concat target-blocks after-blocks)
|
||||
|
||||
offset-block-uuid
|
||||
(let [[before after] (split-with (fn [h] (not= (:block/uuid h)
|
||||
offset-block-uuid)) after-blocks)]
|
||||
(concat (conj (vec before) (first after))
|
||||
target-blocks
|
||||
(rest after)))
|
||||
:else
|
||||
(concat target-blocks after-blocks))
|
||||
after-blocks (remove nil? after-blocks)
|
||||
;; _ (prn {:start-pos start-pos
|
||||
;; :target-blocks target-blocks
|
||||
;; :after-blocks (map (fn [block]
|
||||
;; (:block/content block))
|
||||
;; after-blocks)})
|
||||
last-start-pos (atom start-pos)
|
||||
result (mapv
|
||||
(fn [{:block/keys [uuid meta content level page] :as block}]
|
||||
(let [content (str (util/trim-safe content) "\n")
|
||||
target-block? (contains? target-block-ids uuid)
|
||||
content-length (if target-block?
|
||||
(utf8/length (utf8/encode content))
|
||||
(- (:end-pos meta) (:start-pos meta)))
|
||||
new-end-pos (+ @last-start-pos content-length)
|
||||
new-meta {:start-pos @last-start-pos
|
||||
:end-pos new-end-pos}]
|
||||
(reset! last-start-pos new-end-pos)
|
||||
(let [data {:block/uuid uuid
|
||||
:block/meta new-meta}]
|
||||
(cond
|
||||
(and target-block? (not same-file?))
|
||||
(merge
|
||||
(dissoc block :block/idx :block/dummy?)
|
||||
data)
|
||||
|
||||
target-block?
|
||||
(merge
|
||||
data
|
||||
{:block/level level
|
||||
:block/content content
|
||||
:block/page page})
|
||||
|
||||
:else
|
||||
data))))
|
||||
after-blocks)]
|
||||
result)))
|
||||
|
||||
(defn- get-start-pos
|
||||
[block]
|
||||
(get-in block [:block/meta :start-pos]))
|
||||
|
||||
(defn- get-end-pos
|
||||
[block]
|
||||
(get-in block [:block/meta :end-pos]))
|
||||
|
||||
(defn- compute-direction
|
||||
[target-block top-block nested? top? target-child?]
|
||||
(cond
|
||||
(= top-block target-block)
|
||||
:down
|
||||
|
||||
(and target-child? nested?)
|
||||
:up
|
||||
|
||||
(and target-child? (not top?))
|
||||
:down
|
||||
|
||||
:else
|
||||
:up))
|
||||
|
||||
(defn- compute-after-blocks-in-same-file
|
||||
[repo target-block to-block direction top? nested? target-child? target-file original-top-block-start-pos block-changes]
|
||||
(cond
|
||||
top?
|
||||
(rebuild-dnd-blocks repo target-file target-child?
|
||||
original-top-block-start-pos
|
||||
block-changes
|
||||
nil
|
||||
{})
|
||||
|
||||
(= direction :up)
|
||||
(let [offset-block-id (if nested?
|
||||
(:block/uuid to-block)
|
||||
(last (db/get-block-ids to-block)))
|
||||
offset-end-pos (get-end-pos
|
||||
(db/entity repo [:block/uuid offset-block-id]))]
|
||||
(rebuild-dnd-blocks repo target-file target-child?
|
||||
offset-end-pos
|
||||
block-changes
|
||||
nil
|
||||
{}))
|
||||
|
||||
(= direction :down)
|
||||
(let [offset-block-id (if nested?
|
||||
(:block/uuid to-block)
|
||||
(last (db/get-block-ids to-block)))
|
||||
target-start-pos (get-start-pos target-block)]
|
||||
(rebuild-dnd-blocks repo target-file target-child?
|
||||
target-start-pos
|
||||
block-changes
|
||||
offset-block-id
|
||||
{}))))
|
||||
|
||||
;; TODO: still could be different pages, e.g. move a block from one journal to another journal
|
||||
(defn- move-block-in-same-file
|
||||
[repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes]
|
||||
(if (move-parent-to-child? target-block to-block)
|
||||
nil
|
||||
(let [old-file-content (db/get-file (:file/path (db/entity (:db/id (:block/file target-block)))))
|
||||
old-file-content (utf8/encode old-file-content)
|
||||
subs (fn [start-pos end-pos] (utf8/substring old-file-content start-pos end-pos))
|
||||
bottom-content (db/get-block-content-rec bottom-block)
|
||||
top-content (remove-block-child! bottom-block top-block)
|
||||
top-area (subs 0 (get-start-pos top-block))
|
||||
bottom-area (subs
|
||||
(cond
|
||||
(and nested? (= direction :down))
|
||||
(get-end-pos bottom-block)
|
||||
target-child?
|
||||
(db/get-block-end-pos-rec repo top-block)
|
||||
:else
|
||||
(db/get-block-end-pos-rec repo bottom-block))
|
||||
nil)
|
||||
between-area (if (= direction :down)
|
||||
(subs (db/get-block-end-pos-rec repo target-block) (get-start-pos to-block))
|
||||
(subs (db/get-block-end-pos-rec repo to-block) (get-start-pos target-block)))
|
||||
up-content (when (= direction :up)
|
||||
(cond
|
||||
nested?
|
||||
(util/join-newline (:block/content top-block)
|
||||
target-content
|
||||
(if target-child?
|
||||
(remove-block-child! target-block (:block/children to-block))
|
||||
(db/get-block-content-rec (:block/children top-block))))
|
||||
(and top? target-child?)
|
||||
(util/join-newline target-content (remove-block-child! target-block to-block))
|
||||
|
||||
top?
|
||||
(util/join-newline target-content top-content)
|
||||
|
||||
:else
|
||||
(let [top-content (if target-child?
|
||||
(remove-block-child! target-block to-block)
|
||||
top-content)]
|
||||
(util/join-newline top-content target-content))))
|
||||
down-content (when (= direction :down)
|
||||
(cond
|
||||
nested?
|
||||
(util/join-newline (:block/content bottom-block)
|
||||
target-content)
|
||||
target-child?
|
||||
(util/join-newline top-content target-content)
|
||||
|
||||
:else
|
||||
(util/join-newline bottom-content target-content)))
|
||||
;; _ (prn {:direction direction
|
||||
;; :nested? nested?
|
||||
;; :top? top?
|
||||
;; :target-child? target-child?
|
||||
;; :top-area top-area
|
||||
;; :up-content up-content
|
||||
;; :between-area between-area
|
||||
;; :down-content down-content
|
||||
;; :bottom-area bottom-area
|
||||
;; })
|
||||
new-file-content (string/trim
|
||||
(util/join-newline
|
||||
top-area
|
||||
up-content
|
||||
between-area
|
||||
down-content
|
||||
bottom-area))
|
||||
after-blocks (->> (compute-after-blocks-in-same-file repo target-block to-block direction top? nested? target-child? target-file original-top-block-start-pos block-changes)
|
||||
(remove nil?))
|
||||
path (:file/path (db/entity repo (:db/id (:block/file to-block))))
|
||||
modified-time (let [modified-at (tc/to-long (t/now))]
|
||||
(->
|
||||
[[:db/add (:db/id (:block/page target-block)) :page/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/page to-block)) :page/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/file target-block)) :file/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/file to-block)) :file/last-modified-at modified-at]]
|
||||
distinct
|
||||
vec))]
|
||||
(profile
|
||||
"Move block in the same file: "
|
||||
(repo-handler/transact-react-and-alter-file!
|
||||
repo
|
||||
(concat
|
||||
after-blocks
|
||||
modified-time)
|
||||
{:key :block/change
|
||||
:data block-changes}
|
||||
[[path new-file-content]]))
|
||||
;; (alter-file repo
|
||||
;; path
|
||||
;; new-file-content
|
||||
;; {:re-render-root? true})
|
||||
)))
|
||||
|
||||
(defn- move-block-in-different-files
|
||||
[repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes]
|
||||
(let [target-file (db/entity repo (:db/id (:block/file target-block)))
|
||||
target-file-path (:file/path target-file)
|
||||
target-file-content (db/get-file repo target-file-path)
|
||||
to-file (db/entity repo (:db/id (:block/file to-block)))
|
||||
to-file-path (:file/path to-file)
|
||||
target-block-end-pos (db/get-block-end-pos-rec repo target-block)
|
||||
to-block-start-pos (get-start-pos to-block)
|
||||
to-block-end-pos (db/get-block-end-pos-rec repo to-block)
|
||||
new-target-file-content (utf8/delete! target-file-content
|
||||
(get-start-pos target-block)
|
||||
target-block-end-pos)
|
||||
to-file-content (utf8/encode (db/get-file repo to-file-path))
|
||||
new-to-file-content (let [separate-pos (cond nested?
|
||||
(get-end-pos to-block)
|
||||
top?
|
||||
to-block-start-pos
|
||||
:else
|
||||
to-block-end-pos)]
|
||||
(string/trim
|
||||
(util/join-newline
|
||||
(utf8/substring to-file-content 0 separate-pos)
|
||||
target-content
|
||||
(utf8/substring to-file-content separate-pos))))
|
||||
modified-time (let [modified-at (tc/to-long (t/now))]
|
||||
(->
|
||||
[[:db/add (:db/id (:block/page target-block)) :page/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/page to-block)) :page/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/file target-block)) :file/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/file to-block)) :file/last-modified-at modified-at]]
|
||||
distinct
|
||||
vec))
|
||||
target-after-blocks (rebuild-dnd-blocks repo target-file target-child?
|
||||
(get-start-pos target-block)
|
||||
block-changes nil {:delete? true})
|
||||
to-after-blocks (cond
|
||||
top?
|
||||
(rebuild-dnd-blocks repo to-file target-child?
|
||||
(get-start-pos to-block)
|
||||
block-changes
|
||||
nil
|
||||
{:same-file? false})
|
||||
|
||||
:else
|
||||
(let [offset-block-id (if nested?
|
||||
(:block/uuid to-block)
|
||||
(last (db/get-block-ids to-block)))
|
||||
offset-end-pos (get-end-pos
|
||||
(db/entity repo [:block/uuid offset-block-id]))]
|
||||
(rebuild-dnd-blocks repo to-file target-child?
|
||||
offset-end-pos
|
||||
block-changes
|
||||
nil
|
||||
{:same-file? false})))]
|
||||
(profile
|
||||
"Move block between different files: "
|
||||
(repo-handler/transact-react-and-alter-file!
|
||||
repo
|
||||
(concat
|
||||
target-after-blocks
|
||||
to-after-blocks
|
||||
modified-time)
|
||||
{:key :block/change
|
||||
:data (conj block-changes target-block)}
|
||||
[[target-file-path new-target-file-content]
|
||||
[to-file-path new-to-file-content]]))))
|
||||
|
||||
(defn- move-block-in-different-repos
|
||||
[target-block-repo to-block-repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes]
|
||||
(let [target-file (db/entity target-block-repo (:db/id (:block/file target-block)))
|
||||
target-file-path (:file/path target-file)
|
||||
target-file-content (db/get-file target-block-repo target-file-path)
|
||||
to-file (db/entity to-block-repo (:db/id (:block/file to-block)))
|
||||
to-file-path (:file/path to-file)
|
||||
target-block-end-pos (db/get-block-end-pos-rec target-block-repo target-block)
|
||||
to-block-start-pos (get-start-pos to-block)
|
||||
to-block-end-pos (db/get-block-end-pos-rec to-block-repo to-block)
|
||||
new-target-file-content (utf8/delete! target-file-content
|
||||
(get-start-pos target-block)
|
||||
target-block-end-pos)
|
||||
to-file-content (utf8/encode (db/get-file to-block-repo to-file-path))
|
||||
new-to-file-content (let [separate-pos (cond nested?
|
||||
(get-end-pos to-block)
|
||||
top?
|
||||
to-block-start-pos
|
||||
:else
|
||||
to-block-end-pos)]
|
||||
(string/trim
|
||||
(util/join-newline
|
||||
(utf8/substring to-file-content 0 separate-pos)
|
||||
target-content
|
||||
(utf8/substring to-file-content separate-pos))))
|
||||
target-delete-tx (map (fn [id]
|
||||
[:db.fn/retractEntity [:block/uuid id]])
|
||||
(db/get-block-ids target-block))
|
||||
[target-modified-time to-modified-time]
|
||||
(let [modified-at (tc/to-long (t/now))]
|
||||
[[[:db/add (:db/id (:block/page target-block)) :page/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/file target-block)) :file/last-modified-at modified-at]]
|
||||
[[:db/add (:db/id (:block/page to-block)) :page/last-modified-at modified-at]
|
||||
[:db/add (:db/id (:block/file to-block)) :file/last-modified-at modified-at]]])
|
||||
target-after-blocks (rebuild-dnd-blocks target-block-repo target-file target-child?
|
||||
(get-start-pos target-block)
|
||||
block-changes nil {:delete? true})
|
||||
to-after-blocks (cond
|
||||
top?
|
||||
(rebuild-dnd-blocks to-block-repo to-file target-child?
|
||||
(get-start-pos to-block)
|
||||
block-changes
|
||||
nil
|
||||
{:same-file? false})
|
||||
|
||||
:else
|
||||
(let [offset-block-id (if nested?
|
||||
(:block/uuid to-block)
|
||||
(last (db/get-block-ids to-block)))
|
||||
offset-end-pos (get-end-pos
|
||||
(db/entity to-block-repo [:block/uuid offset-block-id]))]
|
||||
(rebuild-dnd-blocks to-block-repo to-file target-child?
|
||||
offset-end-pos
|
||||
block-changes
|
||||
nil
|
||||
{:same-file? false})))]
|
||||
(profile
|
||||
"[Target file] Move block between different files: "
|
||||
(repo-handler/transact-react-and-alter-file!
|
||||
target-block-repo
|
||||
(concat
|
||||
target-delete-tx
|
||||
target-after-blocks
|
||||
target-modified-time)
|
||||
{:key :block/change
|
||||
:data [(dissoc target-block :block/children)]}
|
||||
[[target-file-path new-target-file-content]]))
|
||||
|
||||
(profile
|
||||
"[Destination file] Move block between different files: "
|
||||
(repo-handler/transact-react-and-alter-file!
|
||||
to-block-repo
|
||||
(concat
|
||||
to-after-blocks
|
||||
to-modified-time)
|
||||
{:key :block/change
|
||||
:data [block-changes]}
|
||||
[[to-file-path new-to-file-content]]))))
|
||||
|
||||
(defn move-block
|
||||
"There can be at least 3 possible situations:
|
||||
1. Move a block in the same file (either top-to-bottom or bottom-to-top).
|
||||
2. Move a block between two different files.
|
||||
3. Move a block between two files in different repos.
|
||||
|
||||
Notes:
|
||||
1. Those two blocks might have different formats, e.g. one is `org` and another is `markdown`,
|
||||
we don't handle this now. TODO: transform between different formats in mldoc.
|
||||
2. Sometimes we might need to move a parent block to it's own child.
|
||||
"
|
||||
[target-block to-block target-dom-id top? nested?]
|
||||
(when (and target-block to-block (:block/format target-block) (:block/format to-block))
|
||||
(cond
|
||||
(not= (:block/format target-block)
|
||||
(:block/format to-block))
|
||||
(notification/show!
|
||||
(util/format "Sorry, you can't move a block of format %s to another file of format %s."
|
||||
(:block/format target-block)
|
||||
(:block/format to-block))
|
||||
:error)
|
||||
|
||||
(= (:block/uuid target-block) (:block/uuid to-block))
|
||||
nil
|
||||
|
||||
:else
|
||||
(let [pattern (config/get-block-pattern (:block/format to-block))
|
||||
target-block-repo (:block/repo target-block)
|
||||
to-block-repo (:block/repo to-block)
|
||||
target-block (assoc target-block
|
||||
:block/meta
|
||||
(:block/meta (db/entity target-block-repo [:block/uuid (:block/uuid target-block)])))
|
||||
to-block (assoc to-block
|
||||
:block/meta
|
||||
(:block/meta (db/entity [:block/uuid (:block/uuid to-block)])))
|
||||
same-repo? (= target-block-repo to-block-repo)
|
||||
target-file (:block/file target-block)
|
||||
same-file? (and
|
||||
same-repo?
|
||||
(= (:db/id target-file)
|
||||
(:db/id (:block/file to-block))))
|
||||
[top-block bottom-block] (if same-file?
|
||||
(if (< (get-start-pos target-block)
|
||||
(get-start-pos to-block))
|
||||
[target-block to-block]
|
||||
[to-block target-block])
|
||||
[nil nil])
|
||||
target-child? (compute-target-child? target-block to-block)
|
||||
direction (compute-direction target-block top-block nested? top? target-child?)
|
||||
original-top-block-start-pos (get-start-pos top-block)
|
||||
[target-content block-changes] (recompute-block-content-and-changes target-block to-block nested? same-repo? same-file?)]
|
||||
(cond
|
||||
same-file?
|
||||
(move-block-in-same-file target-block-repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes)
|
||||
|
||||
;; same repo but different files
|
||||
same-repo?
|
||||
(move-block-in-different-files target-block-repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes)
|
||||
|
||||
;; different repos
|
||||
:else
|
||||
(move-block-in-different-repos target-block-repo to-block-repo target-block to-block top-block bottom-block nested? top? target-child? direction target-content target-file original-top-block-start-pos block-changes))))))
|
||||
142
src/main/frontend/handler/draw.cljs
Normal file
142
src/main/frontend/handler/draw.cljs
Normal file
@@ -0,0 +1,142 @@
|
||||
(ns frontend.handler.draw
|
||||
(:refer-clojure :exclude [load-file])
|
||||
(:require [frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.fs :as fs]
|
||||
[promesa.core :as p]
|
||||
[frontend.state :as state]
|
||||
[frontend.db :as db]
|
||||
[frontend.git :as git]
|
||||
[frontend.github :as github]
|
||||
[frontend.handler.file :as file-handler]
|
||||
[frontend.handler.git :as git-handler]
|
||||
[cljs-bean.core :as bean]
|
||||
[frontend.date :as date]
|
||||
[frontend.config :as config]
|
||||
[frontend.format :as format]
|
||||
[frontend.format.protocol :as protocol]
|
||||
[frontend.storage :as storage]
|
||||
[clojure.string :as string]
|
||||
[cljs-time.local :as tl]
|
||||
[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]
|
||||
(let [repo-dir (util/get-repo-dir repo)]
|
||||
(util/p-handle
|
||||
(fs/mkdir (str repo-dir (str "/" config/default-draw-directory)))
|
||||
(fn [_result] nil)
|
||||
(fn [_error] nil))))
|
||||
|
||||
(defn save-excalidraw!
|
||||
[file data ok-handler]
|
||||
(let [path (str config/default-draw-directory "/" file)
|
||||
repo (state/get-current-repo)]
|
||||
(when repo
|
||||
(let [repo-dir (util/get-repo-dir repo)]
|
||||
(p/let [_ (create-draws-directory! repo)]
|
||||
(util/p-handle
|
||||
(fs/write-file repo-dir path data)
|
||||
(fn [_]
|
||||
(util/p-handle
|
||||
(git-handler/git-add repo path)
|
||||
(fn [_]
|
||||
(ok-handler file)
|
||||
(let [modified-at (tc/to-long (t/now))]
|
||||
(db/transact! repo
|
||||
[{:file/path path
|
||||
:file/last-modified-at modified-at}
|
||||
{:page/name file
|
||||
:page/file path
|
||||
:page/last-modified-at (tc/to-long (t/now))
|
||||
:page/journal? false}])))))
|
||||
(fn [error]
|
||||
(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 (util/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))
|
||||
(fn [content]
|
||||
(ok-handler content))
|
||||
(fn [error]
|
||||
(prn "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 create-draw-with-default-content
|
||||
[current-file ok-handler]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(p/let [exists? (fs/file-exists? (util/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)))))))
|
||||
1910
src/main/frontend/handler/editor.cljs
Normal file
1910
src/main/frontend/handler/editor.cljs
Normal file
File diff suppressed because it is too large
Load Diff
102
src/main/frontend/handler/expand.cljs
Normal file
102
src/main/frontend/handler/expand.cljs
Normal file
@@ -0,0 +1,102 @@
|
||||
(ns frontend.handler.expand
|
||||
(:require [dommy.core :as d]
|
||||
[goog.dom :as gdom]
|
||||
[goog.object :as gobj]
|
||||
[frontend.util :as util]
|
||||
[clojure.string :as string]
|
||||
[medley.core :as medley]
|
||||
[frontend.state :as state]
|
||||
[frontend.db :as db]))
|
||||
|
||||
(defn- hide!
|
||||
[element]
|
||||
(d/set-style! element :display "none"))
|
||||
|
||||
(defn- show!
|
||||
[element]
|
||||
(d/set-style! element :display ""))
|
||||
|
||||
(defn collapse!
|
||||
[block]
|
||||
(let [uuid (:block/uuid block)
|
||||
nodes (array-seq (js/document.getElementsByClassName (str uuid)))]
|
||||
(doseq [node nodes]
|
||||
(d/add-class! node "collapsed")
|
||||
(when-let [e (.querySelector node ".block-body")]
|
||||
(hide! e))
|
||||
(when-let [e (.querySelector node ".block-children")]
|
||||
(hide! e)
|
||||
(let [elements (d/by-class node "ls-block")]
|
||||
(doseq [element elements]
|
||||
(hide! element))))
|
||||
(db/collapse-block! block))))
|
||||
|
||||
(defn expand!
|
||||
[block]
|
||||
(let [uuid (:block/uuid block)
|
||||
nodes (array-seq (js/document.getElementsByClassName (str uuid)))]
|
||||
(doseq [node nodes]
|
||||
(when-let [e (.querySelector node ".block-body")]
|
||||
(show! e))
|
||||
(when-let [e (.querySelector node ".block-children")]
|
||||
(let [elements (d/by-class node "ls-block")]
|
||||
(doseq [element elements]
|
||||
(show! element)))
|
||||
(show! e))
|
||||
(db/expand-block! block))))
|
||||
|
||||
(defn set-bullet-closed!
|
||||
[element]
|
||||
(when element
|
||||
(when-let [node (.querySelector element ".bullet-container")]
|
||||
(d/add-class! node "bullet-closed"))))
|
||||
|
||||
;; Collapse acts like TOC
|
||||
;; There are three modes to cycle:
|
||||
;; 1. Collapse all blocks which levels are greater than 2
|
||||
;; 2. Hide all block's body (user can still see the block title)
|
||||
;; 3. Show everything
|
||||
(defn cycle!
|
||||
[]
|
||||
(let [mode (state/next-collapse-mode)
|
||||
get-blocks (fn []
|
||||
(let [elements (d/by-class "ls-block")
|
||||
result (group-by (fn [e]
|
||||
(let [level (d/attr e "level")]
|
||||
(and level
|
||||
(> (util/parse-int level) 2)))) elements)]
|
||||
[(get result true) (get result false)]))]
|
||||
(case mode
|
||||
:show-all
|
||||
(do
|
||||
(doseq [element (d/by-class "ls-block")]
|
||||
(show! element))
|
||||
(let [elements (d/by-class "block-body")]
|
||||
(doseq [element elements]
|
||||
(show! element)))
|
||||
(doseq [element (d/by-class "bullet-closed")]
|
||||
(d/remove-class! element "bullet-closed"))
|
||||
(doseq [element (d/by-class "block-children")]
|
||||
(show! element)))
|
||||
|
||||
:hide-block-body
|
||||
(let [elements (d/by-class "block-body")]
|
||||
(doseq [element elements]
|
||||
(d/set-style! element :display "none")
|
||||
(when-let [parent (util/rec-get-block-node element)]
|
||||
(set-bullet-closed! parent))))
|
||||
|
||||
:hide-block-children
|
||||
(let [[elements top-level-elements] (get-blocks)
|
||||
level-2-elements (filter (fn [e]
|
||||
(let [level (d/attr e "level")]
|
||||
(and level
|
||||
(= (util/parse-int level) 2)
|
||||
(not (d/has-class? e "pre-block")))))
|
||||
top-level-elements)]
|
||||
(doseq [element elements]
|
||||
(hide! element))
|
||||
(doseq [element level-2-elements]
|
||||
(when (= "true" (d/attr element "haschild"))
|
||||
(set-bullet-closed! element)))))
|
||||
(state/cycle-collapse!)))
|
||||
75
src/main/frontend/handler/export.cljs
Normal file
75
src/main/frontend/handler/export.cljs
Normal file
@@ -0,0 +1,75 @@
|
||||
(ns frontend.handler.export
|
||||
(:require [frontend.state :as state]
|
||||
[frontend.db :as db]
|
||||
[frontend.util :as util]
|
||||
[cljs-bean.core :as bean]
|
||||
[clojure.string :as string]
|
||||
[goog.dom :as gdom]
|
||||
[frontend.publishing.html :as html]))
|
||||
|
||||
(defn copy-block!
|
||||
[block-id]
|
||||
(when-let [block (db/pull [:block/uuid block-id])]
|
||||
(let [content (:block/content block)]
|
||||
(util/copy-to-clipboard! content))))
|
||||
|
||||
(defn copy-block-as-json!
|
||||
[block-id]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [block-children (db/get-block-and-children repo block-id)]
|
||||
(util/copy-to-clipboard! (js/JSON.stringify (bean/->js block-children))))))
|
||||
|
||||
(defn copy-page-as-json!
|
||||
[page-name]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [properties (db/get-page-properties page-name)
|
||||
blocks (db/get-page-blocks repo page-name)]
|
||||
(util/copy-to-clipboard!
|
||||
(js/JSON.stringify
|
||||
(bean/->js
|
||||
{:properties properties
|
||||
:blocks blocks}))))))
|
||||
|
||||
(defn export-repo-as-json!
|
||||
[repo]
|
||||
(when-let [db (db/get-conn repo)]
|
||||
(let [db-json (db/db->json db)
|
||||
data-str (str "data:text/json;charset=utf-8," (js/encodeURIComponent db-json))]
|
||||
(when-let [anchor (gdom/getElement "download-as-json")]
|
||||
(.setAttribute anchor "href" data-str)
|
||||
(.setAttribute anchor "download" (str (last (string/split repo #"/")) ".json"))
|
||||
(.click anchor)))))
|
||||
|
||||
(defn download-file!
|
||||
[file-path]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [content (db/get-file repo file-path)]
|
||||
(let [data (js/Blob. (array content)
|
||||
(clj->js {:type "text/plain"}))]
|
||||
(let [anchor (gdom/getElement "download")
|
||||
url (js/window.URL.createObjectURL data)]
|
||||
(.setAttribute anchor "href" url)
|
||||
(.setAttribute anchor "download" file-path)
|
||||
(.click anchor))))))
|
||||
|
||||
(defn export-repo-as-html!
|
||||
[repo]
|
||||
(when-let [db (db/get-conn repo)]
|
||||
(let [db (if (state/all-pages-public?)
|
||||
(db/clean-export! db)
|
||||
(db/filter-only-public-pages-and-blocks db))
|
||||
db-str (db/db->string db)
|
||||
state (select-keys @state/state
|
||||
[:ui/theme :ui/cycle-collapse
|
||||
:ui/collapsed-blocks
|
||||
:ui/sidebar-collapsed-blocks
|
||||
:ui/show-recent?
|
||||
:config])
|
||||
state (update state :config (fn [config]
|
||||
{"local" (get config repo)}))
|
||||
html-str (str "data:text/html;charset=UTF-8,"
|
||||
(js/encodeURIComponent (html/publishing-html db-str (pr-str state))))]
|
||||
(when-let [anchor (gdom/getElement "download-as-html")]
|
||||
(.setAttribute anchor "href" html-str)
|
||||
(.setAttribute anchor "download" "index.html")
|
||||
(.click anchor)))))
|
||||
45
src/main/frontend/handler/external.cljs
Normal file
45
src/main/frontend/handler/external.cljs
Normal file
@@ -0,0 +1,45 @@
|
||||
(ns frontend.handler.external
|
||||
(:require [frontend.external :as external]
|
||||
[frontend.handler.file :as file-handler]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.state :as state]
|
||||
[frontend.date :as date]
|
||||
[frontend.config :as config]
|
||||
[clojure.string :as string]
|
||||
[frontend.db :as db]))
|
||||
|
||||
(defn index-files!
|
||||
[repo files error-files]
|
||||
(doseq [file files]
|
||||
(let [title (:title file)
|
||||
journal? (date/valid-journal-title? title)]
|
||||
(try
|
||||
(when-let [text (:text file)]
|
||||
(let [path (str (if journal?
|
||||
config/default-journals-directory
|
||||
config/default-pages-directory)
|
||||
"/"
|
||||
(if journal?
|
||||
(date/journal-title->default title)
|
||||
(string/replace title "/" "-"))
|
||||
".md")]
|
||||
(file-handler/alter-file repo path text {})
|
||||
(when journal?
|
||||
(let [page-name (string/lower-case title)]
|
||||
(db/transact! repo
|
||||
[{:page/name page-name
|
||||
:page/journal? true
|
||||
:page/journal-day (date/journal-title->int title)}])))))
|
||||
(catch js/Error e
|
||||
(swap! error-files conj file))))))
|
||||
|
||||
;; TODO: compute the dependencies
|
||||
;; TODO: Should it merge the roam daily notes with the month journals
|
||||
(defn import-from-roam-json!
|
||||
[data]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [files (external/to-markdown-files :roam data {})
|
||||
error-files (atom #{})]
|
||||
(index-files! repo files error-files)
|
||||
(when (seq @error-files)
|
||||
(index-files! repo @error-files (atom nil))))))
|
||||
182
src/main/frontend/handler/file.cljs
Normal file
182
src/main/frontend/handler/file.cljs
Normal file
@@ -0,0 +1,182 @@
|
||||
(ns frontend.handler.file
|
||||
(:refer-clojure :exclude [load-file])
|
||||
(:require [frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.fs :as fs]
|
||||
[promesa.core :as p]
|
||||
[frontend.state :as state]
|
||||
[frontend.db :as db]
|
||||
[frontend.git :as git]
|
||||
[frontend.handler.git :as git-handler]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[datascript.core :as d]
|
||||
[frontend.github :as github]
|
||||
[cljs-bean.core :as bean]
|
||||
[frontend.date :as date]
|
||||
[frontend.config :as config]
|
||||
[frontend.format :as format]
|
||||
[frontend.format.protocol :as protocol]
|
||||
[clojure.string :as string]
|
||||
[frontend.history :as history]
|
||||
[frontend.handler.project :as project-handler]))
|
||||
|
||||
(defn load-file
|
||||
[repo-url path]
|
||||
(->
|
||||
(p/let [content (fs/read-file (util/get-repo-dir repo-url) path)]
|
||||
content)
|
||||
(p/catch
|
||||
(fn [e]
|
||||
(println "Load file failed: " path)
|
||||
(js/console.error e)))))
|
||||
|
||||
(defn load-multiple-files
|
||||
[repo-url paths]
|
||||
(doall
|
||||
(mapv #(load-file repo-url %) paths)))
|
||||
|
||||
(defn- keep-formats
|
||||
[files formats]
|
||||
(filter
|
||||
(fn [file]
|
||||
(let [format (format/get-format file)]
|
||||
(contains? formats format)))
|
||||
files))
|
||||
|
||||
(defn- only-supported-formats
|
||||
[files]
|
||||
(keep-formats files (config/supported-formats)))
|
||||
|
||||
(defn- only-text-formats
|
||||
[files]
|
||||
(keep-formats files (config/text-formats)))
|
||||
|
||||
(defn- only-image-formats
|
||||
[files]
|
||||
(keep-formats files (config/img-formats)))
|
||||
|
||||
(defn- hidden?
|
||||
[path patterns]
|
||||
(some (fn [pattern]
|
||||
(or
|
||||
(= path pattern)
|
||||
(and (util/starts-with? pattern "/")
|
||||
(= (str "/" (first (string/split path #"/")))
|
||||
pattern)))) patterns))
|
||||
|
||||
(defn restore-config!
|
||||
([repo-url project-changed-check?]
|
||||
(restore-config! repo-url nil project-changed-check?))
|
||||
([repo-url config-content project-changed-check?]
|
||||
(let [old-project (:project (state/get-config))
|
||||
new-config (db/reset-config! repo-url config-content)]
|
||||
(when project-changed-check?
|
||||
(let [new-project (:project new-config)
|
||||
project-name (:name old-project)]
|
||||
(when-not (= new-project old-project)
|
||||
(project-handler/sync-project-settings! project-name new-project)))))))
|
||||
|
||||
(defn load-files
|
||||
[repo-url]
|
||||
(state/set-cloning? false)
|
||||
(state/set-state! :repo/loading-files? true)
|
||||
(p/let [files (git/list-files repo-url)
|
||||
files (bean/->clj files)
|
||||
config-content (load-file repo-url (str config/app-name "/" config/config-file))
|
||||
files (if config-content
|
||||
(let [config (restore-config! repo-url config-content true)]
|
||||
(if-let [patterns (seq (:hidden config))]
|
||||
(remove (fn [path] (hidden? path patterns)) files)
|
||||
files))
|
||||
files)]
|
||||
(only-supported-formats files)))
|
||||
|
||||
(defn load-files-contents!
|
||||
[repo-url files ok-handler]
|
||||
(let [images (only-image-formats files)
|
||||
files (only-text-formats files)]
|
||||
(-> (p/all (load-multiple-files repo-url files))
|
||||
(p/then (fn [contents]
|
||||
(ok-handler
|
||||
(cond->
|
||||
(zipmap files contents)
|
||||
|
||||
(seq images)
|
||||
(merge (zipmap images (repeat (count images) "")))))))
|
||||
(p/catch (fn [error]
|
||||
(println "load files failed: ")
|
||||
(js/console.dir error))))))
|
||||
|
||||
(defn alter-file
|
||||
[repo path content {:keys [reset? re-render-root? add-history?]
|
||||
:or {reset? true
|
||||
re-render-root? false
|
||||
add-history? true}}]
|
||||
(let [original-content (db/get-file-no-sub repo path)]
|
||||
(if reset?
|
||||
(db/reset-file! repo path content)
|
||||
(db/set-file-content! repo path content))
|
||||
(util/p-handle
|
||||
(fs/write-file (util/get-repo-dir repo) path content)
|
||||
(fn [_]
|
||||
(git-handler/git-add repo path)
|
||||
(when (= path (str config/app-name "/" config/config-file))
|
||||
(restore-config! repo true))
|
||||
(when (= path (str config/app-name "/" config/custom-css-file))
|
||||
(ui-handler/add-style-if-exists!))
|
||||
(when re-render-root? (ui-handler/re-render-root!))
|
||||
(when add-history?
|
||||
(history/add-history! repo [[path original-content content]])))
|
||||
(fn [error]
|
||||
(println "Write file failed, path: " path ", content: " content)
|
||||
(js/console.error error)))))
|
||||
|
||||
(defn alter-files
|
||||
[repo files]
|
||||
(let [files-tx (mapv (fn [[path content]]
|
||||
(let [original-content (db/get-file-no-sub repo path)]
|
||||
[path original-content content])) files)]
|
||||
(-> (p/all
|
||||
(doall
|
||||
(map
|
||||
(fn [[path content]]
|
||||
(db/set-file-content! repo path content)
|
||||
(util/p-handle
|
||||
(fs/write-file (util/get-repo-dir repo) path content)
|
||||
(fn [_]
|
||||
(git-handler/git-add repo path))
|
||||
(fn [error]
|
||||
(println "Write file failed, path: " path ", content: " content)
|
||||
(js/console.error error))))
|
||||
files)))
|
||||
(p/then (fn [_result]
|
||||
(ui-handler/re-render-file!)
|
||||
(history/add-history! repo files-tx))))))
|
||||
|
||||
(defn remove-file!
|
||||
[repo file]
|
||||
(when-not (string/blank? file)
|
||||
(->
|
||||
(p/let [_ (git/remove-file repo file)
|
||||
result (fs/unlink (str (util/get-repo-dir repo)
|
||||
"/"
|
||||
file)
|
||||
nil)]
|
||||
(state/git-add! repo (str "- " file))
|
||||
(when-let [file (db/entity repo [:file/path file])]
|
||||
(let [file-id (:db/id file)
|
||||
page-id (db/get-file-page-id (:file/path file))
|
||||
tx-data (map
|
||||
(fn [db-id]
|
||||
[:db.fn/retractEntity db-id])
|
||||
(remove nil? [file-id page-id]))]
|
||||
(when (seq tx-data)
|
||||
(db/transact! repo tx-data)))))
|
||||
(p/catch (fn [err]
|
||||
(prn "error: " err))))))
|
||||
|
||||
(defn re-index!
|
||||
[file]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [path (:file/path file)
|
||||
content (db/get-file path)]
|
||||
(alter-file repo path content {:re-render-root? true}))))
|
||||
99
src/main/frontend/handler/git.cljs
Normal file
99
src/main/frontend/handler/git.cljs
Normal file
@@ -0,0 +1,99 @@
|
||||
(ns frontend.handler.git
|
||||
(:refer-clojure :exclude [clone load-file])
|
||||
(:require [frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.fs :as fs]
|
||||
[promesa.core :as p]
|
||||
[frontend.state :as state]
|
||||
[frontend.db :as db]
|
||||
[frontend.git :as git]
|
||||
[frontend.github :as github]
|
||||
[cljs-bean.core :as bean]
|
||||
[frontend.date :as date]
|
||||
[frontend.config :as config]
|
||||
[frontend.format :as format]
|
||||
[frontend.format.protocol :as protocol]
|
||||
[goog.object :as gobj]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.route :as route-handler]
|
||||
[clojure.string :as string]
|
||||
[cljs-time.local :as tl]
|
||||
[cljs-time.core :as t]
|
||||
[cljs-time.coerce :as tc]))
|
||||
|
||||
(defn- set-latest-commit!
|
||||
[repo-url hash]
|
||||
(db/set-key-value repo-url :git/latest-commit hash))
|
||||
|
||||
(defn- set-remote-latest-commit!
|
||||
[repo-url hash]
|
||||
(db/set-key-value repo-url :git/remote-latest-commit hash))
|
||||
|
||||
(defn- set-git-status!
|
||||
[repo-url value]
|
||||
(db/set-key-value repo-url :git/status value)
|
||||
(state/set-git-status! repo-url value))
|
||||
|
||||
(defn- set-git-last-pulled-at!
|
||||
[repo-url]
|
||||
(db/set-key-value repo-url :git/last-pulled-at
|
||||
(date/get-date-time-string (tl/local-now))))
|
||||
|
||||
(defn- set-git-error!
|
||||
[repo-url value]
|
||||
(db/set-key-value repo-url :git/error (if value (str value))))
|
||||
|
||||
(defn git-add
|
||||
[repo-url file]
|
||||
(p/let [result (git/add repo-url file)]
|
||||
(state/git-add! repo-url file)))
|
||||
|
||||
(defn get-latest-commit
|
||||
([repo-url handler]
|
||||
(get-latest-commit repo-url handler 1))
|
||||
([repo-url handler length]
|
||||
(-> (p/let [commits (git/log repo-url length)]
|
||||
(handler (if (= length 1)
|
||||
(first commits)
|
||||
commits)))
|
||||
(p/catch (fn [error]
|
||||
(println "get latest commit failed: " error)
|
||||
(js/console.log (.-stack error))
|
||||
;; TODO: safe check
|
||||
(println "It might be an empty repo"))))))
|
||||
|
||||
(defn set-latest-commit-if-exists! [repo-url]
|
||||
(get-latest-commit
|
||||
repo-url
|
||||
(fn [commit]
|
||||
(when-let [hash (gobj/get commit "oid")]
|
||||
(set-latest-commit! repo-url hash)))))
|
||||
|
||||
(defn set-remote-latest-commit-if-exists! [repo-url]
|
||||
(get-latest-commit
|
||||
repo-url
|
||||
(fn [commit]
|
||||
(when-let [hash (gobj/get commit "oid")]
|
||||
(set-remote-latest-commit! repo-url hash)))))
|
||||
|
||||
(defn commit-and-force-push!
|
||||
[commit-message pushing?]
|
||||
(when-let [repo (frontend.state/get-current-repo)]
|
||||
(let [remote-oid (db/get-key-value repo
|
||||
:git/remote-latest-commit)]
|
||||
(p/let [commit-oid (git/commit repo commit-message (array remote-oid))
|
||||
result (git/write-ref! repo commit-oid)
|
||||
push-result (git/push repo
|
||||
(state/get-github-token repo)
|
||||
true)]
|
||||
(reset! pushing? false)
|
||||
(state/clear-changed-files! repo)
|
||||
(notification/clear! nil)
|
||||
(route-handler/redirect! {:to :home})))))
|
||||
|
||||
(defn git-set-username-email!
|
||||
[repo-url {:keys [name email]}]
|
||||
(when (and name email)
|
||||
(git/set-username-email
|
||||
(util/get-repo-dir repo-url)
|
||||
name
|
||||
email)))
|
||||
33
src/main/frontend/handler/history.cljs
Normal file
33
src/main/frontend/handler/history.cljs
Normal file
@@ -0,0 +1,33 @@
|
||||
(ns frontend.handler.history
|
||||
(:require [frontend.state :as state]
|
||||
[frontend.history :as history]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.handler.file :as file]))
|
||||
|
||||
(defn- default-undo
|
||||
[]
|
||||
(js/document.execCommand "undo" false nil))
|
||||
|
||||
(defn- default-redo
|
||||
[]
|
||||
(js/document.execCommand "redo" false nil))
|
||||
|
||||
(defn undo!
|
||||
[]
|
||||
(let [route (get-in (:route-match @state/state) [:data :name])]
|
||||
(if (and (contains? #{:home :page :file} route)
|
||||
(not (state/get-edit-input-id))
|
||||
(state/get-current-repo))
|
||||
(let [repo (state/get-current-repo)]
|
||||
(history/undo! repo file/alter-file))
|
||||
(default-undo))))
|
||||
|
||||
(defn redo!
|
||||
[]
|
||||
(let [route (get-in (:route-match @state/state) [:data :name])]
|
||||
(if (and (contains? #{:home :page :file} route)
|
||||
(not (state/get-edit-input-id))
|
||||
(state/get-current-repo))
|
||||
(let [repo (state/get-current-repo)]
|
||||
(history/redo! repo file/alter-file))
|
||||
(default-redo))))
|
||||
92
src/main/frontend/handler/image.cljs
Normal file
92
src/main/frontend/handler/image.cljs
Normal file
@@ -0,0 +1,92 @@
|
||||
(ns frontend.handler.image
|
||||
(:require [goog.object :as gobj]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.config :as config]
|
||||
[frontend.image :as image]
|
||||
[frontend.state :as state]
|
||||
[frontend.fs :as fs]
|
||||
[clojure.string :as string]
|
||||
[goog.dom :as gdom]))
|
||||
|
||||
(defn render-local-images!
|
||||
[]
|
||||
(try
|
||||
(let [images (array-seq (gdom/getElementsByTagName "img"))
|
||||
get-src (fn [image] (.getAttribute image "src"))
|
||||
local-images (filter
|
||||
(fn [image]
|
||||
(let [src (get-src image)]
|
||||
(and src
|
||||
(not (or (util/starts-with? src "http://")
|
||||
(util/starts-with? src "https://"))))))
|
||||
images)]
|
||||
(doseq [img local-images]
|
||||
(gobj/set img
|
||||
"onerror"
|
||||
(fn []
|
||||
(gobj/set (gobj/get img "style")
|
||||
"display" "none")))
|
||||
(let [path (get-src img)
|
||||
path (string/replace-first path "file:" "")
|
||||
path (if (= (first path) \.)
|
||||
(subs path 1)
|
||||
path)]
|
||||
(util/p-handle
|
||||
(fs/read-file-2 (util/get-repo-dir (state/get-current-repo))
|
||||
path)
|
||||
(fn [blob]
|
||||
(let [blob (js/Blob. (array blob) (clj->js {:type "image"}))
|
||||
img-url (image/create-object-url blob)]
|
||||
(gobj/set img "src" img-url)
|
||||
(gobj/set (gobj/get img "style")
|
||||
"display" "initial")))
|
||||
(fn [error]
|
||||
(println "Can't read local image file: ")
|
||||
(js/console.dir error))))))
|
||||
(catch js/Error e
|
||||
nil)))
|
||||
|
||||
(defn request-presigned-url
|
||||
[file filename mime-type uploading? url-handler on-processing]
|
||||
(cond
|
||||
(> (gobj/get file "size") (* 12 1024 1024))
|
||||
(notification/show! [:p "Sorry, we don't support any file that's larger than 12MB."] :error)
|
||||
|
||||
:else
|
||||
(do
|
||||
(reset! uploading? true)
|
||||
;; start uploading?
|
||||
(util/post (str config/api "presigned_url")
|
||||
{:filename filename
|
||||
:mime-type mime-type}
|
||||
(fn [{:keys [presigned-url s3-object-key] :as resp}]
|
||||
(if presigned-url
|
||||
(util/upload presigned-url
|
||||
file
|
||||
(fn [_result]
|
||||
;; request cdn signed url
|
||||
(util/post (str config/api "signed_url")
|
||||
{:s3-object-key s3-object-key}
|
||||
(fn [{:keys [signed-url]}]
|
||||
(reset! uploading? false)
|
||||
(if signed-url
|
||||
(do
|
||||
(url-handler signed-url))
|
||||
(prn "Something error, can't get a valid signed url.")))
|
||||
(fn [error]
|
||||
(reset! uploading? false)
|
||||
(prn "Something error, can't get a valid signed url."))))
|
||||
(fn [error]
|
||||
(reset! uploading? false)
|
||||
(prn "upload failed.")
|
||||
(js/console.dir error))
|
||||
(fn [e]
|
||||
(on-processing e)))
|
||||
;; TODO: notification, or re-try
|
||||
(do
|
||||
(reset! uploading? false)
|
||||
(prn "failed to get any presigned url, resp: " resp))))
|
||||
(fn [_error]
|
||||
;; (prn "Get token failed, error: " error)
|
||||
(reset! uploading? false))))))
|
||||
75
src/main/frontend/handler/migration.cljs
Normal file
75
src/main/frontend/handler/migration.cljs
Normal file
@@ -0,0 +1,75 @@
|
||||
(ns frontend.handler.migration
|
||||
(:require [frontend.handler.notification :as notification]
|
||||
[frontend.db :as db]
|
||||
[frontend.ui :as ui]
|
||||
[promesa.core :as p]
|
||||
[frontend.util :as util]
|
||||
[frontend.git :as git]
|
||||
[clojure.string :as str]
|
||||
[frontend.date :as date]
|
||||
[frontend.config :as config]
|
||||
[frontend.state :as state]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.handler.file :as file-handler]
|
||||
[frontend.handler.git :as git-handler]
|
||||
[frontend.fs :as fs]))
|
||||
|
||||
(defn get-files-from-blocks
|
||||
[blocks]
|
||||
(if (<= (count (:page blocks)) 1)
|
||||
nil
|
||||
{:path (str config/default-journals-directory "/" (date/journal-title->default (:title blocks)) "." (config/get-file-extension (state/get-preferred-format)))
|
||||
:page (reduce #(if (not (str/blank? (:block/content %2))) (str %1 (:block/content %2)) %1) "" (:page blocks))}))
|
||||
|
||||
(defn handle-journal-migration-from-monthly-to-daily!
|
||||
[repo]
|
||||
(state/set-daily-migrating! true)
|
||||
(let [all-journals (->>
|
||||
(db/q repo [:journals] {:use-cache? false}
|
||||
'[:find ?page-name
|
||||
:where
|
||||
[?page :page/journal? true]
|
||||
[?page :page/original-name ?page-name]])
|
||||
(db/react)
|
||||
(map first)
|
||||
(distinct)
|
||||
(map (fn [el] {:title el :page (db/get-page-blocks repo el)}))
|
||||
(util/remove-nils)
|
||||
(map get-files-from-blocks)
|
||||
(remove nil?))
|
||||
all-files (map first (db/get-files repo))]
|
||||
(let [to-delete (filter #(re-find #"journals/[0-9]{4}_[0-9]{2}\.+" %) all-files)]
|
||||
(-> (p/all (doall (map (fn [{:keys [path page]}]
|
||||
(println "migrating" path)
|
||||
(p/let [file-exists? (fs/create-if-not-exists (util/get-repo-dir repo) path page)]
|
||||
(db/reset-file! repo path page)
|
||||
(git-handler/git-add repo path))) all-journals)))
|
||||
(p/then
|
||||
(fn [_result]
|
||||
(let [remove-files (doall (map (fn [path]
|
||||
(db/delete-file! repo path)
|
||||
(file-handler/remove-file! repo path)) to-delete))]
|
||||
(-> (p/all remove-files)
|
||||
(p/then (fn [result]
|
||||
(println "Migration successfully!")
|
||||
(state/set-daily-migrating! false)
|
||||
(ui-handler/re-render-root!)
|
||||
(notification/show!
|
||||
"Migration successfully! Please re-index your repository after the sync indicator turned green for a smooth experience."
|
||||
:success)))
|
||||
(p/catch (fn [error]
|
||||
(state/set-daily-migrating! false)
|
||||
(println "Migration failed: ")
|
||||
(js/console.dir error)))))))))))
|
||||
|
||||
(defn show!
|
||||
[]
|
||||
(when-let [current-repo (state/get-current-repo)]
|
||||
(when (db/monthly-journals-exists? current-repo)
|
||||
(notification/show!
|
||||
[:div
|
||||
[:p "Logseq is migrating to creating journal pages on a daily basis for better performance and data safety. In the future, the current method of storing journal files once a month would be removed. Please click the following button to migrate, and feel free to let us know if anything unexpected happened!"]
|
||||
(ui/button "Begin migration"
|
||||
:on-click #(handle-journal-migration-from-monthly-to-daily! current-repo))]
|
||||
:warning
|
||||
false))))
|
||||
25
src/main/frontend/handler/notification.cljs
Normal file
25
src/main/frontend/handler/notification.cljs
Normal file
@@ -0,0 +1,25 @@
|
||||
(ns frontend.handler.notification
|
||||
(:require [frontend.state :as state]
|
||||
[frontend.util :as util]))
|
||||
|
||||
(defn clear!
|
||||
[uid]
|
||||
(let [contents (state/get-notification-contents)]
|
||||
(state/set-state! :notification/contents (dissoc contents uid))))
|
||||
|
||||
(defn show!
|
||||
([content status]
|
||||
(show! content status true nil))
|
||||
([content status clear?]
|
||||
(show! content status clear? nil))
|
||||
([content status clear? uid]
|
||||
(let [contents (state/get-notification-contents)
|
||||
uid (or uid (keyword (util/unique-id)))]
|
||||
(state/set-state! :notification/contents (assoc contents
|
||||
uid {:content content
|
||||
:status status}))
|
||||
|
||||
(when clear?
|
||||
(js/setTimeout #(clear! uid) 3000))
|
||||
|
||||
uid)))
|
||||
341
src/main/frontend/handler/page.cljs
Normal file
341
src/main/frontend/handler/page.cljs
Normal file
@@ -0,0 +1,341 @@
|
||||
(ns frontend.handler.page
|
||||
(:require [clojure.string :as string]
|
||||
[frontend.db :as db]
|
||||
[datascript.core :as d]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.tools.html-export :as html-export]
|
||||
[frontend.config :as config]
|
||||
[frontend.handler.route :as route-handler]
|
||||
[frontend.handler.file :as file-handler]
|
||||
[frontend.handler.git :as git-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.project :as project-handler]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.date :as date]
|
||||
[clojure.walk :as walk]
|
||||
[frontend.git :as git]
|
||||
[frontend.fs :as fs]
|
||||
[promesa.core :as p]
|
||||
[goog.object :as gobj]
|
||||
[frontend.format.mldoc :as mldoc]))
|
||||
|
||||
(defn create!
|
||||
[title]
|
||||
(let [repo (state/get-current-repo)
|
||||
dir (util/get-repo-dir repo)
|
||||
journal-page? (date/valid-journal-title? title)
|
||||
directory (if journal-page?
|
||||
config/default-journals-directory
|
||||
config/default-pages-directory)]
|
||||
(when dir
|
||||
(p/let [_ (-> (fs/mkdir (str dir "/" directory))
|
||||
(p/catch (fn [_e])))]
|
||||
(let [format (name (state/get-preferred-format))
|
||||
page (string/lower-case title)
|
||||
path (str (if journal-page?
|
||||
(date/journal-title->default title)
|
||||
(util/page-name-sanity page))
|
||||
"."
|
||||
(if (= format "markdown") "md" format))
|
||||
path (str directory "/" path)
|
||||
file-path (str "/" path)]
|
||||
(p/let [exists? (fs/file-exists? dir file-path)]
|
||||
(if exists?
|
||||
(notification/show!
|
||||
[:p.content
|
||||
(util/format "File %s already exists!" file-path)]
|
||||
:error)
|
||||
;; create the file
|
||||
(let [content (util/default-content-with-title format title)]
|
||||
(p/let [_ (fs/create-if-not-exists dir file-path content)]
|
||||
(db/reset-file! repo path content)
|
||||
(git-handler/git-add repo path)
|
||||
(route-handler/redirect! {:to :page
|
||||
:path-params {:name page}})
|
||||
(let [blocks (db/get-page-blocks page)
|
||||
last-block (last blocks)]
|
||||
(when last-block
|
||||
(js/setTimeout
|
||||
#(editor-handler/edit-last-block-for-new-page! last-block 0)
|
||||
100))))))))))))
|
||||
|
||||
(defn page-add-properties!
|
||||
[page-name properties]
|
||||
(let [page (db/entity [:page/name page-name])
|
||||
page-format (db/get-page-format page-name)
|
||||
properties-content (db/get-page-properties-content page-name)
|
||||
properties-content (if properties-content
|
||||
(string/trim properties-content)
|
||||
(config/properties-wrapper page-format))]
|
||||
(let [file (db/entity (:db/id (:page/file page)))
|
||||
file-path (:file/path file)
|
||||
file-content (db/get-file file-path)
|
||||
after-content (subs file-content (inc (count properties-content)))
|
||||
new-properties-content (db/add-properties! page-format properties-content properties)
|
||||
full-content (str new-properties-content "\n\n" (string/trim after-content))]
|
||||
(file-handler/alter-file (state/get-current-repo)
|
||||
file-path
|
||||
full-content
|
||||
{:reset? true
|
||||
:re-render-root? true}))))
|
||||
|
||||
(defn page-remove-property!
|
||||
[page-name k]
|
||||
(when-let [properties-content (string/trim (db/get-page-properties-content page-name))]
|
||||
(let [page (db/entity [:page/name page-name])
|
||||
file (db/entity (:db/id (:page/file page)))
|
||||
file-path (:file/path file)
|
||||
file-content (db/get-file file-path)
|
||||
after-content (subs file-content (count properties-content))
|
||||
page-format (db/get-page-format page-name)
|
||||
new-properties-content (let [lines (string/split-lines properties-content)
|
||||
prefix (case page-format
|
||||
:org (str "#+" (string/upper-case k) ": ")
|
||||
:markdown (str (string/lower-case k) ": ")
|
||||
"")
|
||||
exists? (atom false)
|
||||
lines (remove #(util/starts-with? % prefix) lines)]
|
||||
(string/join "\n" lines))
|
||||
full-content (str new-properties-content "\n\n" (string/trim after-content))]
|
||||
(file-handler/alter-file (state/get-current-repo)
|
||||
file-path
|
||||
full-content
|
||||
{:reset? true
|
||||
:re-render-root? true}))))
|
||||
|
||||
(defn published-success-handler
|
||||
[page-name]
|
||||
(fn [result]
|
||||
(let [permalink (:permalink result)]
|
||||
(page-add-properties! page-name {"permalink" permalink})
|
||||
(let [win (js/window.open (str
|
||||
config/website
|
||||
"/"
|
||||
(state/get-current-project)
|
||||
"/"
|
||||
permalink))]
|
||||
(.focus win)))))
|
||||
|
||||
(defn published-failed-handler
|
||||
[error]
|
||||
(notification/show!
|
||||
"Publish failed, please give it another try."
|
||||
:error))
|
||||
|
||||
(defn get-plugins
|
||||
[blocks]
|
||||
(let [plugins (atom {})
|
||||
add-plugin #(swap! plugins assoc % true)]
|
||||
(walk/postwalk
|
||||
(fn [x]
|
||||
(if (and (vector? x)
|
||||
(>= (count x) 2))
|
||||
(let [[type option] x]
|
||||
(case type
|
||||
"Src" (when (:language option)
|
||||
(add-plugin "highlight"))
|
||||
"Export" (when (= option "latex")
|
||||
(add-plugin "latex"))
|
||||
"Latex_Fragment" (add-plugin "latex")
|
||||
"Math" (add-plugin "latex")
|
||||
"Latex_Environment" (add-plugin "latex")
|
||||
nil)
|
||||
x)
|
||||
x))
|
||||
(map :block/body blocks))
|
||||
@plugins))
|
||||
|
||||
(defn publish-page-as-slide!
|
||||
([page-name project-add-modal]
|
||||
(publish-page-as-slide! page-name (db/get-page-blocks page-name) project-add-modal))
|
||||
([page-name blocks project-add-modal]
|
||||
(project-handler/exists-or-create!
|
||||
(fn [project]
|
||||
(page-add-properties! page-name {"published" true
|
||||
"slide" true})
|
||||
(let [properties (db/get-page-properties page-name)
|
||||
plugins (get-plugins blocks)
|
||||
data {:project project
|
||||
:title page-name
|
||||
:permalink (:permalink properties)
|
||||
:html (html-export/export-page page-name blocks notification/show!)
|
||||
:tags (:tags properties)
|
||||
:settings (merge
|
||||
(assoc properties
|
||||
:slide true
|
||||
:published true)
|
||||
plugins)
|
||||
:repo (state/get-current-repo)}]
|
||||
(util/post (str config/api "pages")
|
||||
data
|
||||
(published-success-handler page-name)
|
||||
published-failed-handler)))
|
||||
project-add-modal)))
|
||||
|
||||
(defn publish-page!
|
||||
[page-name project-add-modal]
|
||||
(project-handler/exists-or-create!
|
||||
(fn [project]
|
||||
(let [properties (db/get-page-properties page-name)
|
||||
slide? (let [slide (:slide properties)]
|
||||
(or (true? slide)
|
||||
(= "true" slide)))
|
||||
blocks (db/get-page-blocks page-name)
|
||||
plugins (get-plugins blocks)]
|
||||
(if slide?
|
||||
(publish-page-as-slide! page-name blocks project-add-modal)
|
||||
(do
|
||||
(page-add-properties! page-name {"published" true})
|
||||
(let [data {:project project
|
||||
:title page-name
|
||||
:permalink (:permalink properties)
|
||||
:html (html-export/export-page page-name blocks notification/show!)
|
||||
:tags (:tags properties)
|
||||
:settings (merge properties plugins)
|
||||
:repo (state/get-current-repo)}]
|
||||
(util/post (str config/api "pages")
|
||||
data
|
||||
(published-success-handler page-name)
|
||||
published-failed-handler))))))
|
||||
project-add-modal))
|
||||
|
||||
(defn unpublished-success-handler
|
||||
[page-name]
|
||||
(fn [result]
|
||||
(notification/show!
|
||||
"Un-publish successfully!"
|
||||
:success)))
|
||||
|
||||
(defn unpublished-failed-handler
|
||||
[error]
|
||||
(notification/show!
|
||||
"Un-publish failed, please give it another try."
|
||||
:error))
|
||||
|
||||
(defn unpublish-page!
|
||||
[page-name]
|
||||
(page-add-properties! page-name {"published" false})
|
||||
(let [properties (db/get-page-properties page-name)
|
||||
permalink (:permalink properties)
|
||||
project (state/get-current-project)]
|
||||
(if (and project permalink)
|
||||
(util/delete (str config/api project "/" permalink)
|
||||
(unpublished-success-handler page-name)
|
||||
unpublished-failed-handler)
|
||||
(notification/show!
|
||||
"Can't find the permalink of this page!"
|
||||
:error))))
|
||||
|
||||
(defn delete!
|
||||
[page-name ok-handler]
|
||||
(when page-name
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [page-name (string/lower-case page-name)]
|
||||
(let [file (db/get-page-file page-name)
|
||||
file-path (:file/path file)]
|
||||
;; delete file
|
||||
(when file-path
|
||||
(db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
|
||||
(when-let [files-conn (db/get-files-conn repo)]
|
||||
(d/transact! files-conn [[:db.fn/retractEntity [:file/path file-path]]]))
|
||||
|
||||
(let [blocks (db/get-page-blocks page-name)
|
||||
tx-data (mapv
|
||||
(fn [block]
|
||||
[:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
|
||||
blocks)]
|
||||
(db/transact! tx-data)
|
||||
;; remove file
|
||||
(->
|
||||
(p/let [_ (git/remove-file repo file-path)
|
||||
_result (fs/unlink (str (util/get-repo-dir repo)
|
||||
"/"
|
||||
file-path)
|
||||
nil)]
|
||||
(state/git-add! repo (str "- " file-path)))
|
||||
(p/catch (fn [err]
|
||||
(prn "error: " err))))))
|
||||
|
||||
(db/transact! [[:db.fn/retractEntity [:page/name page-name]]])
|
||||
|
||||
(ok-handler))))))
|
||||
|
||||
(defn rename!
|
||||
[old-name new-name]
|
||||
(when (and old-name new-name
|
||||
(not= (string/lower-case old-name) (string/lower-case new-name)))
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [page (db/entity [:page/name (string/lower-case old-name)])]
|
||||
(let [old-original-name (:page/original-name page)
|
||||
file (:page/file page)]
|
||||
(d/transact! (db/get-conn repo false)
|
||||
[{:db/id (:db/id page)
|
||||
:page/name (string/lower-case new-name)
|
||||
:page/original-name new-name}])
|
||||
|
||||
(when file
|
||||
(page-add-properties! (string/lower-case new-name) {:title new-name}))
|
||||
|
||||
;; update all files which have references to this page
|
||||
(let [files (db/get-files-that-referenced-page (:db/id page))]
|
||||
(doseq [file-path files]
|
||||
(let [file-content (db/get-file file-path)
|
||||
;; FIXME: not safe
|
||||
new-content (string/replace file-content
|
||||
(util/format "[[%s]]" old-original-name)
|
||||
(util/format "[[%s]]" new-name))]
|
||||
(file-handler/alter-file repo
|
||||
file-path
|
||||
new-content
|
||||
{:reset? true
|
||||
:re-render-root? false})))))
|
||||
|
||||
;; TODO: update browser history, remove the current one
|
||||
|
||||
;; Redirect to the new page
|
||||
(route-handler/redirect! {:to :page
|
||||
:path-params {:name (util/encode-str (string/lower-case new-name))}})
|
||||
|
||||
(notification/show! "Page renamed successfully!" :success)
|
||||
|
||||
(ui-handler/re-render-root!)))))
|
||||
|
||||
(defn rename-when-alter-title-propertiy!
|
||||
[page path format original-content content]
|
||||
(when (and page (contains? config/mldoc-support-formats format))
|
||||
(let [old-name page
|
||||
new-name (let [ast (mldoc/->edn content (mldoc/default-config format))]
|
||||
(db/get-page-name path ast))]
|
||||
(when (not= old-name new-name)
|
||||
(rename! old-name new-name)))))
|
||||
|
||||
(defn handle-add-page-to-contents!
|
||||
[page-name]
|
||||
(let [last-block (last (db/get-page-blocks (state/get-current-repo) "contents"))
|
||||
last-empty? (>= 3 (count (:block/content last-block)))
|
||||
heading-pattern (config/get-block-pattern (state/get-preferred-format))
|
||||
pre-str (str heading-pattern heading-pattern)
|
||||
new-content (if last-empty? (str pre-str " [[" page-name "]]") (str (:block/content last-block) pre-str " [[" page-name "]]"))]
|
||||
(editor-handler/insert-new-block-aux!
|
||||
last-block
|
||||
new-content
|
||||
{:create-new-block? false
|
||||
:ok-handler
|
||||
(fn [[_first-block last-block _new-block-content]]
|
||||
(notification/show! "Added to contents!" :success)
|
||||
(editor-handler/clear-when-saved!))
|
||||
:with-level? true
|
||||
:new-level 2
|
||||
:current-page "Contents"})))
|
||||
|
||||
(defn load-more-journals!
|
||||
[]
|
||||
(let [current-length (:journals-length @state/state)]
|
||||
(when (< current-length (db/get-journals-length))
|
||||
(state/update-state! :journals-length inc))))
|
||||
|
||||
(defn update-public-attribute!
|
||||
[page-name value]
|
||||
(page-add-properties! page-name {:public value}))
|
||||
71
src/main/frontend/handler/project.cljs
Normal file
71
src/main/frontend/handler/project.cljs
Normal file
@@ -0,0 +1,71 @@
|
||||
(ns frontend.handler.project
|
||||
(:require [frontend.state :as state]
|
||||
[frontend.util :as util :refer-macros [profile]]
|
||||
[clojure.string :as string]
|
||||
[frontend.config :as config]
|
||||
[frontend.handler.notification :as notification]))
|
||||
|
||||
;; project exists and current user owns it
|
||||
;; if project not exists, the server will create it
|
||||
(defn project-exists?
|
||||
[project]
|
||||
(let [projects (set (map :name (:projects (state/get-me))))]
|
||||
(and (seq projects) (contains? projects project))))
|
||||
|
||||
(defn create-project!
|
||||
([ok-handler]
|
||||
(create-project! (state/get-current-project) ok-handler))
|
||||
([project ok-handler]
|
||||
(let [config (state/get-config)
|
||||
data {:name project
|
||||
:repo (state/get-current-repo)
|
||||
:settings (or (get config :project)
|
||||
{:name project})}]
|
||||
(util/post (str config/api "projects")
|
||||
data
|
||||
(fn [result]
|
||||
(swap! state/state
|
||||
update-in [:me :projects]
|
||||
(fn [projects]
|
||||
(util/distinct-by :name (conj projects result))))
|
||||
(ok-handler project))
|
||||
(fn [error]
|
||||
(js/console.dir error)
|
||||
(notification/show! (util/format "Project \"%s\" already taken, please change to another name." project) :error))))))
|
||||
|
||||
(defn exists-or-create!
|
||||
[ok-handler modal-content]
|
||||
(if-let [project (state/get-current-project)]
|
||||
(if (project-exists? project)
|
||||
(ok-handler project)
|
||||
(create-project! ok-handler))
|
||||
(state/set-modal! modal-content)))
|
||||
|
||||
(defn add-project!
|
||||
[project]
|
||||
(create-project! project
|
||||
(fn []
|
||||
(notification/show! (util/format "Project \"%s\" was created successfully." project) :success)
|
||||
(state/close-modal!))))
|
||||
|
||||
(defn sync-project-settings!
|
||||
([]
|
||||
(when-let [project-name (state/get-current-project)]
|
||||
(let [settings (:project (state/get-config))]
|
||||
(sync-project-settings! project-name settings))))
|
||||
([project-name settings]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(if (project-exists? project-name)
|
||||
(util/post (str config/api "projects/" project-name)
|
||||
{:name project-name
|
||||
:settings settings
|
||||
:repo repo}
|
||||
(fn [response]
|
||||
(notification/show! "Project settings changed successfully!" :success))
|
||||
(fn [error]
|
||||
(println "Project settings updated failed, reason: ")
|
||||
(js/console.dir error)))
|
||||
(when (and settings
|
||||
(not (string/blank? (:name settings)))
|
||||
(>= (count (string/trim (:name settings))) 2))
|
||||
(add-project! (:name settings)))))))
|
||||
137
src/main/frontend/handler/repeated.cljs
Normal file
137
src/main/frontend/handler/repeated.cljs
Normal file
@@ -0,0 +1,137 @@
|
||||
(ns frontend.handler.repeated
|
||||
(:require [cljs-time.core :as t]
|
||||
[cljs-time.local :as tl]
|
||||
[cljs-time.format :as tf]
|
||||
[frontend.date :as date]
|
||||
[clojure.string :as string]
|
||||
[frontend.util :as util]))
|
||||
|
||||
(def custom-formatter (tf/formatter "yyyy-MM-dd EEE"))
|
||||
|
||||
(defn repeated?
|
||||
[timestamp]
|
||||
(some? (:repetition timestamp)))
|
||||
|
||||
(defn- get-duration-f-and-text
|
||||
[duration]
|
||||
(case duration
|
||||
"Hour"
|
||||
[t/hours "h"]
|
||||
"Day"
|
||||
[t/days "d"]
|
||||
"Week"
|
||||
[t/weeks "w"]
|
||||
"Month"
|
||||
[t/months "m"]
|
||||
"Year"
|
||||
[t/years "y"]
|
||||
nil))
|
||||
|
||||
(defn get-repeater-symbol
|
||||
[kind]
|
||||
(case kind
|
||||
"Plus"
|
||||
"+"
|
||||
"Dotted"
|
||||
".+"
|
||||
"++"))
|
||||
|
||||
(defn timestamp->text
|
||||
([timestamp]
|
||||
(timestamp->text timestamp nil))
|
||||
([{:keys [date wday repetition time active]} start-time]
|
||||
(let [{:keys [year month day]} date
|
||||
{:keys [hour min]
|
||||
:or {hour 0 min 0}} time
|
||||
[hour min] (if start-time
|
||||
[(t/hour start-time)
|
||||
(t/minute start-time)]
|
||||
[hour min])
|
||||
[[kind] [duration] num] repetition
|
||||
start-time (or start-time (t/local-date-time year month day hour min))
|
||||
[duration-f d] (get-duration-f-and-text duration)
|
||||
kind (get-repeater-symbol kind)
|
||||
repeater (when (and kind num d)
|
||||
(str kind num d))
|
||||
time-repeater (if time
|
||||
(str (util/zero-pad hour) ":" (util/zero-pad min)
|
||||
(if (string/blank? repeater)
|
||||
""
|
||||
(str " " repeater)))
|
||||
repeater)]
|
||||
(util/format "<%s%s>"
|
||||
(tf/unparse custom-formatter start-time)
|
||||
(if (string/blank? time-repeater)
|
||||
""
|
||||
(str " " time-repeater))))))
|
||||
|
||||
(defn- repeat-until-future-timestamp
|
||||
[datetime now delta keep-week?]
|
||||
(let [result (loop [result datetime]
|
||||
(if (t/after? result now)
|
||||
result
|
||||
(recur (t/plus result delta))))
|
||||
w1 (t/day-of-week datetime)
|
||||
w2 (t/day-of-week result)]
|
||||
(if (and keep-week? (not= w1 w2))
|
||||
;; next week
|
||||
(if (> w2 w1)
|
||||
(t/plus result (t/days (- 7 (- w2 w1))))
|
||||
(t/plus result (t/days (- w1 w2))))
|
||||
result)))
|
||||
|
||||
;; Fro https://www.reddit.com/r/orgmode/comments/hr2ytg/difference_between_the_repeaters_orgzly/fy2izqx?utm_source=share&utm_medium=web2x&context=3
|
||||
;; I use these repeaters for habit tracking and it can get a little tricky to keep track. This is my short form understanding:
|
||||
;; ".+X" = repeat in X d/w/m from the last time I marked it done
|
||||
;; "++X" = repeat in at least X d/w/m from the last time I marked it done and keep it on the same day of the week move the due date into the future by increments of d/w/m. If the due date, after being moved forward X d/w/m is still in the past, adjust it by however many d/w/m needed to get it into the future. For the w, the day of the week is kept constant.
|
||||
;; "+X" = repeat in X d/w/m from when I originally scheduled it, regardless of when I marked it done. Rarely used (as described by u/serendependy). A relevant case would be "paying rent" from the link.
|
||||
(defn next-timestamp-text
|
||||
[{:keys [date wday repetition time active] :as timestamp}]
|
||||
(let [{:keys [year month day]} date
|
||||
{:keys [hour min]
|
||||
:or {hour 0 min 0}} time
|
||||
[[kind] [duration] num] repetition
|
||||
[duration-f _] (get-duration-f-and-text duration)
|
||||
delta (duration-f num)
|
||||
today (date/get-local-date)
|
||||
start-time (t/local-date-time year month day hour min)
|
||||
start-time' (if (or (= kind "Dotted")
|
||||
(= kind "DoublePlus"))
|
||||
(if (t/before? (tl/local-now) start-time)
|
||||
start-time
|
||||
|
||||
;; Repeatedly add delta to make it a future timestamp
|
||||
(repeat-until-future-timestamp start-time (tl/local-now) delta
|
||||
(= kind "DoublePlus")))
|
||||
(t/plus start-time delta))]
|
||||
(timestamp->text timestamp start-time')))
|
||||
|
||||
(defn timestamp-map->text
|
||||
[{:keys [date time repeater]}]
|
||||
(let [{:keys [kind duration num]} repeater
|
||||
repeater (when (and kind num duration)
|
||||
(str kind num duration))
|
||||
time-repeater (if-not (string/blank? time)
|
||||
(str time
|
||||
(if (string/blank? repeater)
|
||||
""
|
||||
(str " " repeater)))
|
||||
repeater)]
|
||||
(util/format "<%s%s>"
|
||||
(tf/unparse custom-formatter date)
|
||||
(if (string/blank? time-repeater)
|
||||
""
|
||||
(str " " time-repeater)))))
|
||||
|
||||
(defn timestamp->map
|
||||
[{:keys [date wday repetition time active]}]
|
||||
(let [{:keys [year month day]} date
|
||||
{:keys [hour min]} time
|
||||
[[kind] [duration] num] repetition]
|
||||
{:date (t/local-date year month day)
|
||||
:time (when (and hour min)
|
||||
(str (util/zero-pad hour) ":" (util/zero-pad min)))
|
||||
:repeater (when (and kind duration num)
|
||||
{:kind (get-repeater-symbol kind)
|
||||
:duration (last (get-duration-f-and-text duration))
|
||||
:num num})}))
|
||||
641
src/main/frontend/handler/repo.cljs
Normal file
641
src/main/frontend/handler/repo.cljs
Normal file
@@ -0,0 +1,641 @@
|
||||
(ns frontend.handler.repo
|
||||
(:refer-clojure :exclude [clone])
|
||||
(:require [frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.fs :as fs]
|
||||
[promesa.core :as p]
|
||||
[datascript.core :as d]
|
||||
[frontend.state :as state]
|
||||
[frontend.db :as db]
|
||||
[frontend.git :as git]
|
||||
[frontend.github :as github]
|
||||
[cljs-bean.core :as bean]
|
||||
[frontend.date :as date]
|
||||
[frontend.config :as config]
|
||||
[frontend.format :as format]
|
||||
[frontend.format.protocol :as protocol]
|
||||
[goog.object :as gobj]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.handler.git :as git-handler]
|
||||
[frontend.handler.file :as file-handler]
|
||||
[frontend.handler.migration :as migration-handler]
|
||||
[frontend.handler.project :as project-handler]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.route :as route-handler]
|
||||
[frontend.handler.user :as user-handler]
|
||||
[frontend.ui :as ui]
|
||||
[cljs-time.local :as tl]
|
||||
[cljs-time.core :as t]
|
||||
[cljs.reader :as reader]
|
||||
[clojure.string :as string]
|
||||
[frontend.dicts :as dicts]
|
||||
;; [clojure.set :as set]
|
||||
))
|
||||
|
||||
;; Project settings should be checked in two situations:
|
||||
;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
|
||||
;; 2. Git pulls the new change (fn: load-files)
|
||||
|
||||
(defn load-repo-to-db!
|
||||
[repo-url diffs first-clone?]
|
||||
(let [load-contents (fn [files delete-files delete-blocks re-render?]
|
||||
(file-handler/load-files-contents!
|
||||
repo-url
|
||||
files
|
||||
(fn [contents]
|
||||
(state/set-state! :repo/loading-files? false)
|
||||
(state/set-state! :repo/importing-to-db? true)
|
||||
(let [parsed-files (filter
|
||||
(fn [[file _]]
|
||||
(let [format (format/get-format file)]
|
||||
(contains? config/mldoc-support-formats format)))
|
||||
contents)
|
||||
blocks-pages (if (seq parsed-files)
|
||||
(db/extract-all-blocks-pages repo-url parsed-files)
|
||||
[])]
|
||||
(db/reset-contents-and-blocks! repo-url contents blocks-pages delete-files delete-blocks)
|
||||
(let [config-file (str config/app-name "/" config/config-file)]
|
||||
(when (contains? (set files) config-file)
|
||||
(when-let [content (get contents config-file)]
|
||||
(file-handler/restore-config! repo-url content true))))
|
||||
;; (let [metadata-file (str config/app-name "/" config/metadata-file)]
|
||||
;; (when (contains? (set files) metadata-file)
|
||||
;; (when-let [content (get contents metadata-file)]
|
||||
;; (let [{:keys [tx-data]} (reader/read-string content)]
|
||||
;; (db/transact! repo-url tx-data)))))
|
||||
(state/set-state! :repo/importing-to-db? false)
|
||||
(when re-render?
|
||||
(ui-handler/re-render-root!))))))]
|
||||
(if first-clone?
|
||||
(->
|
||||
(p/let [files (file-handler/load-files repo-url)]
|
||||
(load-contents files nil nil false))
|
||||
(p/catch (fn [error]
|
||||
(println "loading files failed: ")
|
||||
(js/console.dir error)
|
||||
(state/set-state! :repo/loading-files? false))))
|
||||
(when (seq diffs)
|
||||
(let [filter-diffs (fn [type] (->> (filter (fn [f] (= type (:type f))) diffs)
|
||||
(map :path)))
|
||||
remove-files (filter-diffs "remove")
|
||||
modify-files (filter-diffs "modify")
|
||||
add-files (filter-diffs "add")
|
||||
delete-files (if (seq remove-files)
|
||||
(db/delete-files remove-files))
|
||||
delete-blocks (db/delete-blocks repo-url (concat remove-files modify-files))
|
||||
delete-pages (if (seq remove-files)
|
||||
(db/delete-pages-by-files remove-files)
|
||||
[])
|
||||
add-or-modify-files (util/remove-nils (concat add-files modify-files))]
|
||||
(load-contents add-or-modify-files (concat delete-files delete-pages) delete-blocks true))))))
|
||||
|
||||
(defn show-install-error!
|
||||
[repo-url title]
|
||||
(notification/show!
|
||||
[:p.content
|
||||
title
|
||||
[:span.text-gray-700.mr-2
|
||||
(util/format
|
||||
"Please make sure that you've installed the logseq app for the repo %s on GitHub. "
|
||||
repo-url)
|
||||
(ui/button
|
||||
"Install Logseq on GitHub"
|
||||
:href (str "https://github.com/apps/" config/github-app-name "/installations/new"))]]
|
||||
:error
|
||||
false))
|
||||
|
||||
(defn show-diff-error!
|
||||
[_repo-url]
|
||||
(notification/show!
|
||||
[:p
|
||||
[:span.text-gray-700.font-bold.mr-2
|
||||
"Please resolve the diffs if any."]
|
||||
(ui/button
|
||||
"Go to diff"
|
||||
:href "/diff")]
|
||||
:error
|
||||
false))
|
||||
|
||||
(defn get-new-token
|
||||
[repo]
|
||||
(when-let [installation-id (-> (filter
|
||||
(fn [r]
|
||||
(= (:url r) repo))
|
||||
(:repos (state/get-me)))
|
||||
(first)
|
||||
:installation_id)]
|
||||
(util/post (str config/api "refresh_github_token")
|
||||
{:installation-ids [installation-id]}
|
||||
(fn [result]
|
||||
(let [token (:token (first result))]
|
||||
(state/set-github-token! repo token)))
|
||||
(fn [error]
|
||||
(println "Something wrong!")
|
||||
(js/console.dir error)))))
|
||||
|
||||
(defn request-app-tokens!
|
||||
[ok-handler error-handler]
|
||||
(let [repos (:repos (state/get-me))
|
||||
installation-ids (->> (map :installation_id repos)
|
||||
(remove nil?)
|
||||
(distinct))]
|
||||
(when (or (seq repos)
|
||||
(seq installation-ids))
|
||||
(util/post (str config/api "refresh_github_token")
|
||||
{:installation-ids installation-ids
|
||||
:repos repos}
|
||||
(fn [result]
|
||||
(state/set-github-installation-tokens! result)
|
||||
(when ok-handler (ok-handler)))
|
||||
(fn [error]
|
||||
(println "Something wrong!")
|
||||
(js/console.dir error)
|
||||
(when error-handler (error-handler)))))))
|
||||
|
||||
(defn journal-file-changed?
|
||||
[repo-url diffs]
|
||||
(contains? (set (map :path diffs))
|
||||
(db/get-current-journal-path)))
|
||||
|
||||
(defn create-config-file-if-not-exists
|
||||
[repo-url]
|
||||
(let [repo-dir (util/get-repo-dir repo-url)
|
||||
app-dir config/app-name
|
||||
dir (str repo-dir "/" app-dir)]
|
||||
(p/let [_ (-> (fs/mkdir dir)
|
||||
(p/catch (fn [_e])))]
|
||||
(let [default-content config/config-default-content]
|
||||
(p/let [file-exists? (fs/create-if-not-exists repo-dir (str app-dir "/" config/config-file) default-content)]
|
||||
(let [path (str app-dir "/" config/config-file)
|
||||
old-content (when file-exists?
|
||||
(db/get-file repo-url path))
|
||||
content (or
|
||||
(and old-content
|
||||
(string/replace old-content "heading" "block"))
|
||||
default-content)]
|
||||
(db/reset-file! repo-url path content)
|
||||
(db/reset-config! repo-url content)
|
||||
(when-not (= content old-content)
|
||||
(git-handler/git-add repo-url path))))
|
||||
;; (p/let [file-exists? (fs/create-if-not-exists repo-dir (str app-dir "/" config/metadata-file) default-content)]
|
||||
;; (let [path (str app-dir "/" config/metadata-file)]
|
||||
;; (when-not file-exists?
|
||||
;; (db/reset-file! repo-url path "{:tx-data []}")
|
||||
;; (git-handler/git-add repo-url path))))
|
||||
))))
|
||||
|
||||
(defn create-contents-file
|
||||
[repo-url]
|
||||
(let [repo-dir (util/get-repo-dir repo-url)
|
||||
format (state/get-preferred-format)
|
||||
path (str "pages/contents." (if (= (name format) "markdown")
|
||||
"md"
|
||||
(name format)))
|
||||
file-path (str "/" path)
|
||||
default-content (util/default-content-with-title format "contents")]
|
||||
(p/let [_ (-> (fs/mkdir (str repo-dir "/pages"))
|
||||
(p/catch (fn [_e])))
|
||||
file-exists? (fs/create-if-not-exists repo-dir file-path default-content)]
|
||||
(when-not file-exists?
|
||||
(db/reset-file! repo-url path default-content)
|
||||
(git-handler/git-add repo-url path)))))
|
||||
|
||||
(defn create-dummy-notes-page
|
||||
[repo-url content]
|
||||
(let [repo-dir (util/get-repo-dir repo-url)
|
||||
path (str config/default-pages-directory "/how_to_make_dummy_notes.md")
|
||||
file-path (str "/" path)]
|
||||
(p/let [_ (-> (fs/mkdir (str repo-dir "/" config/default-pages-directory))
|
||||
(p/catch (fn [_e])))
|
||||
_file-exists? (fs/create-if-not-exists repo-dir file-path content)]
|
||||
(db/reset-file! repo-url path content))))
|
||||
|
||||
(defn create-today-journal-if-not-exists
|
||||
([repo-url]
|
||||
(create-today-journal-if-not-exists repo-url nil))
|
||||
([repo-url content]
|
||||
(let [repo-dir (util/get-repo-dir repo-url)
|
||||
format (state/get-preferred-format)
|
||||
title (date/today)
|
||||
file-name (date/journal-title->default title)
|
||||
default-content (util/default-content-with-title format title false)
|
||||
template (state/get-journal-template)
|
||||
template (if (and template
|
||||
(not (string/blank? template)))
|
||||
template)
|
||||
content (cond
|
||||
content
|
||||
content
|
||||
template
|
||||
(str default-content template)
|
||||
|
||||
:else
|
||||
(util/default-content-with-title format title true))
|
||||
path (str config/default-journals-directory "/" file-name "."
|
||||
(config/get-file-extension format))
|
||||
file-path (str "/" path)
|
||||
page-exists? (db/entity repo-url [:page/name (string/lower-case title)])
|
||||
empty-blocks? (empty? (db/get-page-blocks-no-cache repo-url (string/lower-case title)))]
|
||||
(when (or empty-blocks?
|
||||
(not page-exists?))
|
||||
(p/let [_ (-> (fs/mkdir (str repo-dir "/" config/default-journals-directory))
|
||||
(p/catch (fn [_e])))
|
||||
file-exists? (fs/create-if-not-exists repo-dir file-path content)]
|
||||
;; TODO: why file exists but page not created
|
||||
(p/let [resolved-content (if file-exists?
|
||||
(file-handler/load-file repo-url path)
|
||||
(p/resolved content))]
|
||||
(let [content (if (string/blank? (string/trim resolved-content))
|
||||
content
|
||||
resolved-content)]
|
||||
(db/reset-file! repo-url path content)
|
||||
(ui-handler/re-render-root!)
|
||||
(git-handler/git-add repo-url path))))))))
|
||||
|
||||
(defn create-default-files!
|
||||
[repo-url]
|
||||
(when-let [name (get-in @state/state [:me :name])]
|
||||
(create-config-file-if-not-exists repo-url)
|
||||
(create-today-journal-if-not-exists repo-url)
|
||||
(create-contents-file repo-url)))
|
||||
|
||||
(defn persist-repo!
|
||||
[repo]
|
||||
(when-let [files-conn (db/get-files-conn repo)]
|
||||
(db/persist repo @files-conn true))
|
||||
(when-let [db (db/get-conn repo)]
|
||||
(db/persist repo db false)))
|
||||
|
||||
(defn load-db-and-journals!
|
||||
[repo-url diffs first-clone?]
|
||||
(when (or diffs first-clone?)
|
||||
(p/let [_ (load-repo-to-db! repo-url diffs first-clone?)]
|
||||
(when first-clone?
|
||||
(create-default-files! repo-url))
|
||||
|
||||
(when first-clone?
|
||||
(migration-handler/show!)))))
|
||||
|
||||
(defn transact-react-and-alter-file!
|
||||
[repo tx transact-option files]
|
||||
(let [files (remove nil? files)
|
||||
pages (->> (map db/get-file-page (map first files))
|
||||
(remove nil?))]
|
||||
(db/transact-react!
|
||||
repo
|
||||
tx
|
||||
transact-option)
|
||||
(when (seq pages)
|
||||
(let [children-tx (mapcat #(db/rebuild-page-blocks-children repo %) pages)]
|
||||
(when (seq children-tx)
|
||||
(db/transact! repo children-tx)))))
|
||||
(when (seq files)
|
||||
(file-handler/alter-files repo files)))
|
||||
|
||||
(defn persist-repo-metadata!
|
||||
[repo]
|
||||
(let [files (db/get-files repo)]
|
||||
(when (seq files)
|
||||
(let [data (db/get-sync-metadata repo)
|
||||
data-str (pr-str data)]
|
||||
(file-handler/alter-file repo
|
||||
(str config/app-name "/" config/metadata-file)
|
||||
data-str
|
||||
{:reset? false})))))
|
||||
|
||||
(defn periodically-persist-app-metadata
|
||||
[repo-url]
|
||||
(js/setInterval #(persist-repo-metadata! repo-url)
|
||||
(* 5 60 1000)))
|
||||
|
||||
(declare push)
|
||||
|
||||
(defn pull
|
||||
[repo-url token {:keys [fallback? force-pull?]
|
||||
:or {fallback? false
|
||||
force-pull? false}}]
|
||||
(when (and
|
||||
(db/get-conn repo-url true)
|
||||
(db/cloned? repo-url)
|
||||
token)
|
||||
(let [status (db/get-key-value repo-url :git/status)]
|
||||
(when (or
|
||||
force-pull?
|
||||
(and
|
||||
;; (not= status :push-failed)
|
||||
(not= status :pushing)
|
||||
(empty? (state/get-changed-files repo-url))
|
||||
(not (state/get-edit-input-id))
|
||||
(not (state/in-draw-mode?))))
|
||||
(git-handler/set-git-status! repo-url :pulling)
|
||||
(let [latest-commit (db/get-key-value repo-url :git/latest-commit)]
|
||||
(->
|
||||
(p/let [result (git/fetch repo-url token)]
|
||||
(let [{:keys [fetchHead]} (bean/->clj result)]
|
||||
(when fetchHead
|
||||
(git-handler/set-remote-latest-commit! repo-url fetchHead))
|
||||
(-> (git/merge repo-url)
|
||||
(p/then (fn [result]
|
||||
(-> (git/checkout repo-url)
|
||||
(p/then (fn [result]
|
||||
(git-handler/set-git-status! repo-url nil)
|
||||
(git-handler/set-git-last-pulled-at! repo-url)
|
||||
(when (and latest-commit fetchHead
|
||||
(not= latest-commit fetchHead))
|
||||
(p/let [diffs (git/get-diffs repo-url latest-commit fetchHead)]
|
||||
(when (seq diffs)
|
||||
(load-db-and-journals! repo-url diffs false)
|
||||
(git-handler/set-latest-commit! repo-url fetchHead)
|
||||
(when (seq (state/get-changed-files repo-url))
|
||||
;; FIXME: no need to create a new commit
|
||||
(push repo-url {:diff-push? true})))))))
|
||||
(p/catch (fn [error]
|
||||
(git-handler/set-git-status! repo-url :checkout-failed)
|
||||
(git-handler/set-git-error! repo-url error))))))
|
||||
(p/catch (fn [error]
|
||||
(git-handler/set-git-status! repo-url :merge-failed)
|
||||
(git-handler/set-git-error! repo-url error)
|
||||
(notification/show!
|
||||
[:p.content
|
||||
"Failed to merge, please "
|
||||
[:span.text-gray-700.font-bold
|
||||
"resolve any diffs first."]]
|
||||
:error)
|
||||
(route-handler/redirect! {:to :diff}))))))
|
||||
(p/catch (fn [error]
|
||||
(println "Pull error:" (str error))
|
||||
(js/console.error error)
|
||||
;; token might be expired, request new token
|
||||
|
||||
(cond
|
||||
(and (or (string/includes? (str error) "401")
|
||||
(string/includes? (str error) "404"))
|
||||
(not fallback?))
|
||||
(request-app-tokens!
|
||||
(fn []
|
||||
(pull repo-url (state/get-github-token repo-url) {:fallback? true}))
|
||||
nil)
|
||||
|
||||
(or (string/includes? (str error) "401")
|
||||
(string/includes? (str error) "404"))
|
||||
(show-install-error! repo-url (util/format "Failed to fetch %s." repo-url))
|
||||
|
||||
:else
|
||||
nil)))))))))
|
||||
|
||||
(defn check-changed-files-status
|
||||
[f]
|
||||
(when (gobj/get js/window.workerThread "getChangedFiles")
|
||||
(->
|
||||
(p/let [files (js/window.workerThread.getChangedFiles (util/get-repo-dir (state/get-current-repo)))]
|
||||
(let [files (bean/->clj files)]
|
||||
(when (empty? files)
|
||||
;; FIXME: getChangedFiles not return right result
|
||||
(state/reset-changed-files! files))))
|
||||
(p/catch (fn [error]
|
||||
(js/console.dir error))))))
|
||||
|
||||
(defn push
|
||||
[repo-url {:keys [commit-message fallback? diff-push? force?]
|
||||
:or {commit-message "Logseq auto save"
|
||||
fallback? false
|
||||
diff-push? false
|
||||
force? false}}]
|
||||
(let [status (db/get-key-value repo-url :git/status)]
|
||||
(when (and
|
||||
(db/cloned? repo-url)
|
||||
(not (state/get-edit-input-id)))
|
||||
(-> (p/let [files (js/window.workerThread.getChangedFiles (util/get-repo-dir (state/get-current-repo)))]
|
||||
(prn {:changed-files files})
|
||||
(when (or
|
||||
;; FIXME:
|
||||
force?
|
||||
(and
|
||||
(seq (state/get-changed-files repo-url))
|
||||
(seq files))
|
||||
fallback?
|
||||
diff-push?)
|
||||
;; auto commit if there are any un-committed changes
|
||||
(let [commit-message (if (string/blank? commit-message)
|
||||
"Logseq auto save"
|
||||
commit-message)]
|
||||
(p/let [_ (git/commit repo-url commit-message)]
|
||||
(git-handler/set-latest-commit-if-exists! repo-url)
|
||||
(git-handler/set-git-status! repo-url :pushing)
|
||||
(when-let [token (state/get-github-token repo-url)]
|
||||
(util/p-handle
|
||||
(git/push repo-url token)
|
||||
(fn [result]
|
||||
(git-handler/set-git-status! repo-url nil)
|
||||
(git-handler/set-git-error! repo-url nil)
|
||||
(state/clear-changed-files! repo-url))
|
||||
(fn [error]
|
||||
(js/console.error error)
|
||||
(let [permission? (or (string/includes? (str error) "401")
|
||||
(string/includes? (str error) "404"))]
|
||||
(cond
|
||||
(and permission? (not fallback?))
|
||||
(request-app-tokens!
|
||||
(fn []
|
||||
(push repo-url
|
||||
{:commit-message commit-message
|
||||
:fallback? true}))
|
||||
nil)
|
||||
|
||||
:else
|
||||
(do
|
||||
(git-handler/set-git-status! repo-url :push-failed)
|
||||
(git-handler/set-git-error! repo-url error)
|
||||
(if permission?
|
||||
(show-install-error! repo-url (util/format "Failed to push to %s. " repo-url))
|
||||
(pull repo-url token {:force-pull? true}))))))))))))
|
||||
(p/catch (fn [error]
|
||||
(println "Git push error: ")
|
||||
(js/console.dir error)))))))
|
||||
|
||||
(defn pull-current-repo
|
||||
[]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [token (state/get-github-token repo)]
|
||||
(pull repo token {:force-pull? true}))))
|
||||
|
||||
(defn clone
|
||||
([repo-url]
|
||||
(clone repo-url false))
|
||||
([repo-url fallback?]
|
||||
(when-let [token (state/get-github-token repo-url)]
|
||||
(util/p-handle
|
||||
(do
|
||||
(state/set-cloning? true)
|
||||
(git/clone repo-url token))
|
||||
(fn [result]
|
||||
(state/set-git-clone-repo! "")
|
||||
(state/set-current-repo! repo-url)
|
||||
(db/start-db-conn! (:me @state/state) repo-url)
|
||||
(db/mark-repo-as-cloned repo-url)
|
||||
(git-handler/set-latest-commit-if-exists! repo-url)
|
||||
(git-handler/set-remote-latest-commit-if-exists! repo-url))
|
||||
(fn [e]
|
||||
(if (and (not fallback?)
|
||||
(or (string/includes? (str e) "401")
|
||||
(string/includes? (str e) "404")))
|
||||
(request-app-tokens!
|
||||
(fn []
|
||||
(clone repo-url true))
|
||||
nil)
|
||||
(do
|
||||
(println "Clone failed, error: ")
|
||||
(js/console.error e)
|
||||
(state/set-cloning? false)
|
||||
(git-handler/set-git-status! repo-url :clone-failed)
|
||||
(git-handler/set-git-error! repo-url e)
|
||||
|
||||
(show-install-error! repo-url (util/format "Failed to clone %s." repo-url)))))))))
|
||||
|
||||
(defn set-config-content!
|
||||
[repo path new-config]
|
||||
(let [new-content (util/pp-str new-config)]
|
||||
(file-handler/alter-file repo path new-content {:reset? false
|
||||
:re-render-root? false})))
|
||||
|
||||
(defn set-config!
|
||||
[k v]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(let [path (str config/app-name "/" config/config-file)]
|
||||
(when-let [config (db/get-file-no-sub path)]
|
||||
(let [config (try
|
||||
(reader/read-string config)
|
||||
(catch js/Error e
|
||||
(println "Parsing config file failed: ")
|
||||
(js/console.dir e)
|
||||
{}))
|
||||
ks (if (vector? k) k [k])
|
||||
new-config (assoc-in config ks v)]
|
||||
(state/set-config! repo new-config)
|
||||
(set-config-content! repo path new-config))))))
|
||||
|
||||
(defn remove-repo!
|
||||
[{:keys [id url] :as repo}]
|
||||
(util/delete (str config/api "repos/" id)
|
||||
(fn []
|
||||
(db/remove-conn! url)
|
||||
(db/remove-db! url)
|
||||
(db/remove-files-db! url)
|
||||
(fs/rmdir (util/get-repo-dir url))
|
||||
(state/delete-repo! repo)
|
||||
(state/clear-changed-files! repo))
|
||||
(fn [error]
|
||||
(prn "Delete repo failed, error: " error))))
|
||||
|
||||
(defn setup-local-repo-if-not-exists!
|
||||
[]
|
||||
(if js/window.pfs
|
||||
(let [repo config/local-repo]
|
||||
(p/let [result (-> (fs/mkdir (str "/" repo))
|
||||
(p/catch (fn [_e] nil)))
|
||||
_ (state/set-current-repo! repo)
|
||||
_ (db/start-db-conn! nil repo)
|
||||
_ (when-not config/publishing?
|
||||
(let [dummy-notes (get-in dicts/dicts [:en :tutorial/dummy-notes])]
|
||||
(create-dummy-notes-page repo dummy-notes)))
|
||||
_ (when-not config/publishing?
|
||||
(let [tutorial (get-in dicts/dicts [:en :tutorial/text])
|
||||
tutorial (string/replace-first tutorial "$today" (date/today))]
|
||||
(create-today-journal-if-not-exists repo tutorial)))
|
||||
_ (create-config-file-if-not-exists repo)
|
||||
_ (create-contents-file repo)]
|
||||
(state/set-db-restoring! false)))
|
||||
(js/setTimeout setup-local-repo-if-not-exists! 100)))
|
||||
|
||||
(defn periodically-pull
|
||||
[repo-url pull-now?]
|
||||
(when-let [token (state/get-github-token repo-url)]
|
||||
(when pull-now? (pull repo-url token nil))
|
||||
(js/setInterval #(pull repo-url token nil)
|
||||
(* (config/git-pull-secs) 1000))))
|
||||
|
||||
(defn periodically-push-tasks
|
||||
[repo-url]
|
||||
(let [token (state/get-github-token repo-url)
|
||||
push (fn []
|
||||
(when (and (not (false? (:git-auto-push (state/get-config repo-url))))
|
||||
;; (not config/dev?)
|
||||
)
|
||||
(push repo-url nil)))]
|
||||
(js/setInterval push
|
||||
(* (config/git-push-secs) 1000))))
|
||||
|
||||
(defn periodically-pull-and-push
|
||||
[repo-url {:keys [pull-now?]
|
||||
:or {pull-now? true}}]
|
||||
(periodically-pull repo-url pull-now?)
|
||||
(periodically-push-tasks repo-url))
|
||||
|
||||
(defn create-repo!
|
||||
[repo-url branch]
|
||||
(util/post (str config/api "repos")
|
||||
{:url repo-url
|
||||
:branch branch}
|
||||
(fn [result]
|
||||
(if (:installation_id result)
|
||||
(set! (.-href js/window.location) config/website)
|
||||
(set! (.-href js/window.location) (str "https://github.com/apps/" config/github-app-name "/installations/new"))))
|
||||
(fn [error]
|
||||
(println "Something wrong!")
|
||||
(js/console.dir error))))
|
||||
|
||||
(defn clone-and-pull
|
||||
[repo-url]
|
||||
(->
|
||||
(p/let [_ (clone repo-url)
|
||||
_ (git-handler/git-set-username-email! repo-url (:me @state/state))]
|
||||
(load-db-and-journals! repo-url nil true)
|
||||
(periodically-pull-and-push repo-url {:pull-now? false})
|
||||
;; (periodically-persist-app-metadata repo-url)
|
||||
)
|
||||
(p/catch (fn [error]
|
||||
(js/console.error error)))))
|
||||
|
||||
(defn clone-and-pull-repos
|
||||
[me]
|
||||
(if (and js/window.git js/window.pfs)
|
||||
(doseq [{:keys [id url]} (:repos me)]
|
||||
(let [repo url]
|
||||
(p/let [config-exists? (fs/file-exists?
|
||||
(util/get-repo-dir url)
|
||||
".git/config")]
|
||||
(if (and config-exists?
|
||||
(db/cloned? repo))
|
||||
(do
|
||||
(git-handler/git-set-username-email! repo me)
|
||||
(periodically-pull-and-push repo {:pull-now? true})
|
||||
;; (periodically-persist-app-metadata repo)
|
||||
)
|
||||
(clone-and-pull repo)))))
|
||||
(js/setTimeout (fn []
|
||||
(clone-and-pull-repos me))
|
||||
500)))
|
||||
|
||||
(defn rebuild-index!
|
||||
[{:keys [id url] :as repo}]
|
||||
(db/remove-conn! url)
|
||||
(db/clear-query-state!)
|
||||
(state/clear-changed-files! url)
|
||||
(-> (p/let [_ (db/remove-db! url)
|
||||
_ (db/remove-files-db! url)]
|
||||
(fs/rmdir (util/get-repo-dir url)))
|
||||
(p/catch (fn [error]
|
||||
(prn "Delete repo failed, error: " error)))
|
||||
(p/finally (fn []
|
||||
(clone-and-pull url)))))
|
||||
|
||||
(defn git-commit-and-push!
|
||||
[commit-message]
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(push repo {:commit-message commit-message
|
||||
:fallback? false
|
||||
:force? true})))
|
||||
|
||||
(defn read-repair-journals!
|
||||
[repo-url]
|
||||
;; TODO: check file corrupts
|
||||
)
|
||||
127
src/main/frontend/handler/route.cljs
Normal file
127
src/main/frontend/handler/route.cljs
Normal file
@@ -0,0 +1,127 @@
|
||||
(ns frontend.handler.route
|
||||
(:require [frontend.util :as util]
|
||||
[reitit.frontend.easy :as rfe]
|
||||
[reitit.frontend.history :as rfh]
|
||||
[frontend.state :as state]
|
||||
[goog.dom :as gdom]
|
||||
[frontend.handler.ui :as ui-handler]
|
||||
[frontend.db :as db]
|
||||
[frontend.date :as date]
|
||||
[clojure.string :as string]
|
||||
[medley.core :as medley]
|
||||
[frontend.text :as text]))
|
||||
|
||||
(defn redirect!
|
||||
"If `push` is truthy, previous page will be left in history."
|
||||
[{:keys [to path-params query-params push]
|
||||
:or {push true}}]
|
||||
(if push
|
||||
(rfe/push-state to path-params query-params)
|
||||
(rfe/replace-state to path-params query-params)))
|
||||
|
||||
(defn redirect-to-home!
|
||||
[]
|
||||
(redirect! {:to :home}))
|
||||
|
||||
(defn redirect-with-fragment!
|
||||
[path]
|
||||
(.pushState js/window.history nil "" path)
|
||||
(rfh/-on-navigate @rfe/history path))
|
||||
|
||||
(defn get-title
|
||||
[name path-params]
|
||||
(case name
|
||||
:home
|
||||
"Logseq"
|
||||
:repos
|
||||
"Repos"
|
||||
:repo-add
|
||||
"Add another repo"
|
||||
:graph
|
||||
"Graph"
|
||||
:all-files
|
||||
"All files"
|
||||
:all-pages
|
||||
"All pages"
|
||||
:all-journals
|
||||
"All journals"
|
||||
:file
|
||||
(str "File " (util/url-decode (:path path-params)))
|
||||
:new-page
|
||||
"Create a new page"
|
||||
:page
|
||||
(let [name (:name path-params)
|
||||
block? (util/uuid-string? name)]
|
||||
(if block?
|
||||
(if-let [block (db/entity [:block/uuid (medley/uuid name)])]
|
||||
(let [content (text/remove-level-spaces (:block/content block)
|
||||
(:block/format block))]
|
||||
(if (> (count content) 48)
|
||||
(str (subs content 0 48) "...")
|
||||
content))
|
||||
"Page no longer exists!!")
|
||||
(util/capitalize-all (util/url-decode name))))
|
||||
:tag
|
||||
(str "#" (util/url-decode (:name path-params)))
|
||||
:diff
|
||||
"Git diff"
|
||||
:draw
|
||||
"Draw"
|
||||
:settings
|
||||
"Settings"
|
||||
:import
|
||||
"Import data into Logseq"
|
||||
"Logseq"))
|
||||
|
||||
(defn set-route-match!
|
||||
[route]
|
||||
(swap! state/state assoc :route-match route)
|
||||
(let [{:keys [data path-params]} route
|
||||
title (get-title (:name data) path-params)]
|
||||
(util/set-title! title)
|
||||
(ui-handler/scroll-and-highlight! nil)))
|
||||
|
||||
(defn go-to-search!
|
||||
[]
|
||||
(when-let [element (gdom/getElement "search_field")]
|
||||
(.focus element)))
|
||||
|
||||
(defn go-to-journals!
|
||||
[]
|
||||
(state/set-journals-length! 1)
|
||||
(let [route (if (state/custom-home-page?)
|
||||
:all-journals
|
||||
:home)]
|
||||
(redirect! {:to route}))
|
||||
(util/scroll-to-top))
|
||||
|
||||
(defn- redirect-to-file!
|
||||
[page]
|
||||
(when-let [path (-> (db/get-page-file (string/lower-case page))
|
||||
:db/id
|
||||
(db/entity)
|
||||
:file/path)]
|
||||
(redirect! {:to :file
|
||||
:path-params {:path path}})))
|
||||
|
||||
(defn toggle-between-page-and-file!
|
||||
[]
|
||||
(let [current-route (state/get-current-route)]
|
||||
(case current-route
|
||||
:home
|
||||
(redirect-to-file! (date/today))
|
||||
|
||||
:all-journals
|
||||
(redirect-to-file! (date/today))
|
||||
|
||||
:page
|
||||
(when-let [page-name (get-in (state/get-route-match) [:path-params :name])]
|
||||
(redirect-to-file! page-name))
|
||||
|
||||
:file
|
||||
(when-let [path (get-in (state/get-route-match) [:path-params :path])]
|
||||
(when-let [page (db/get-file-page path)]
|
||||
(redirect! {:to :page
|
||||
:path-params {:name page}})))
|
||||
|
||||
nil)))
|
||||
20
src/main/frontend/handler/search.cljs
Normal file
20
src/main/frontend/handler/search.cljs
Normal file
@@ -0,0 +1,20 @@
|
||||
(ns frontend.handler.search
|
||||
(:require [goog.object :as gobj]
|
||||
[frontend.state :as state]
|
||||
[goog.dom :as gdom]
|
||||
[frontend.search :as search]))
|
||||
|
||||
(defn search
|
||||
[q]
|
||||
(swap! state/state assoc :search/result
|
||||
{:pages (search/page-search q)
|
||||
:files (search/file-search q)
|
||||
:blocks (search/search q)}))
|
||||
|
||||
(defn clear-search!
|
||||
[]
|
||||
(swap! state/state assoc
|
||||
:search/result nil
|
||||
:search/q "")
|
||||
(when-let [input (gdom/getElement "search_field")]
|
||||
(gobj/set input "value" "")))
|
||||
95
src/main/frontend/handler/ui.cljs
Normal file
95
src/main/frontend/handler/ui.cljs
Normal file
@@ -0,0 +1,95 @@
|
||||
(ns frontend.handler.ui
|
||||
(:require [dommy.core :as dom]
|
||||
[frontend.state :as state]
|
||||
[frontend.db :as db]
|
||||
[rum.core :as rum]
|
||||
[goog.dom :as gdom]
|
||||
[goog.object :as gobj]
|
||||
[frontend.util :as util :refer-macros [profile]]))
|
||||
|
||||
;; sidebars
|
||||
(defn hide-left-sidebar
|
||||
[]
|
||||
(dom/add-class! (dom/by-id "menu")
|
||||
"md:block")
|
||||
(dom/remove-class! (dom/by-id "left-sidebar")
|
||||
"enter")
|
||||
(dom/remove-class! (dom/by-id "search")
|
||||
"sidebar-open")
|
||||
(dom/remove-class! (dom/by-id "main")
|
||||
"sidebar-open"))
|
||||
|
||||
(defn show-left-sidebar
|
||||
[]
|
||||
(dom/remove-class! (dom/by-id "menu")
|
||||
"md:block")
|
||||
(dom/add-class! (dom/by-id "left-sidebar")
|
||||
"enter")
|
||||
(dom/add-class! (dom/by-id "search")
|
||||
"sidebar-open")
|
||||
(dom/add-class! (dom/by-id "main")
|
||||
"sidebar-open"))
|
||||
|
||||
(defn hide-right-sidebar
|
||||
[]
|
||||
(state/hide-right-sidebar!))
|
||||
|
||||
(defn show-right-sidebar
|
||||
[]
|
||||
(state/open-right-sidebar!))
|
||||
|
||||
(defn toggle-right-sidebar!
|
||||
[]
|
||||
(state/toggle-sidebar-open?!))
|
||||
|
||||
|
||||
;; FIXME: re-render all embedded blocks since they will not be re-rendered automatically
|
||||
|
||||
|
||||
(defn re-render-root!
|
||||
[]
|
||||
(when-let [component (state/get-root-component)]
|
||||
(db/clear-query-state-without-refs-and-embeds!)
|
||||
(rum/request-render component)
|
||||
(doseq [component (state/get-custom-query-components)]
|
||||
(rum/request-render component))))
|
||||
|
||||
(defn re-render-file!
|
||||
[]
|
||||
(when-let [component (state/get-file-component)]
|
||||
(when (= :file (state/get-current-route))
|
||||
(rum/request-render component))))
|
||||
|
||||
(defn highlight-element!
|
||||
[fragment]
|
||||
(let [id (and
|
||||
(> (count fragment) 36)
|
||||
(subs fragment (- (count fragment) 36)))]
|
||||
(if (and id (util/uuid-string? id))
|
||||
(let [elements (array-seq (js/document.getElementsByClassName id))]
|
||||
(when (first elements)
|
||||
(util/scroll-to-element (gobj/get (first elements) "id")))
|
||||
(doseq [element elements]
|
||||
(dom/add-class! element "block-highlight")
|
||||
(js/setTimeout #(dom/remove-class! element "block-highlight")
|
||||
4000)))
|
||||
(when-let [element (gdom/getElement fragment)]
|
||||
(util/scroll-to-element fragment)
|
||||
(dom/add-class! element "block-highlight")
|
||||
(js/setTimeout #(dom/remove-class! element "block-highlight")
|
||||
4000)))))
|
||||
|
||||
(defn scroll-and-highlight!
|
||||
[state]
|
||||
(when-let [fragment (util/get-fragment)]
|
||||
(highlight-element! fragment))
|
||||
state)
|
||||
|
||||
(defn add-style-if-exists!
|
||||
[]
|
||||
(when-let [style (or
|
||||
(state/get-custom-css-link)
|
||||
(db/get-custom-css)
|
||||
;; (state/get-custom-css-link)
|
||||
)]
|
||||
(util/add-style! style)))
|
||||
78
src/main/frontend/handler/user.cljs
Normal file
78
src/main/frontend/handler/user.cljs
Normal file
@@ -0,0 +1,78 @@
|
||||
(ns frontend.handler.user
|
||||
(:require [frontend.util :as util :refer-macros [profile]]
|
||||
[frontend.state :as state]
|
||||
[frontend.db :as db]
|
||||
[frontend.config :as config]
|
||||
[frontend.storage :as storage]
|
||||
[promesa.core :as p]
|
||||
[goog.object :as gobj]
|
||||
[frontend.handler.notification :as notification])
|
||||
(:import [goog.format EmailAddress]))
|
||||
|
||||
(defn email? [v]
|
||||
(and v
|
||||
(.isValid (EmailAddress. v))))
|
||||
|
||||
(defn set-email!
|
||||
[email]
|
||||
(when (email? email)
|
||||
(util/post (str config/api "email")
|
||||
{:email email}
|
||||
(fn [result]
|
||||
(db/transact! [{:me/email email}])
|
||||
(swap! state/state assoc-in [:me :email] email))
|
||||
(fn [error]
|
||||
(notification/show! "Email already exists!"
|
||||
:error)))))
|
||||
|
||||
(defn set-cors!
|
||||
[cors-proxy]
|
||||
(util/post (str config/api "cors_proxy")
|
||||
{:cors-proxy cors-proxy}
|
||||
(fn [result]
|
||||
(db/transact! [{:me/cors_proxy cors-proxy}])
|
||||
(swap! state/state assoc-in [:me :cors_proxy] cors-proxy))
|
||||
(fn [error]
|
||||
(notification/show! "Set cors proxy failed." :error)
|
||||
(js/console.dir error))))
|
||||
|
||||
(defn set-preferred-format!
|
||||
[format]
|
||||
(when format
|
||||
(state/set-preferred-format! format)
|
||||
(when (:name (:me @state/state))
|
||||
(util/post (str config/api "set_preferred_format")
|
||||
{:preferred_format (name format)}
|
||||
(fn [_result]
|
||||
(notification/show! "Format set successfully!" :success))
|
||||
(fn [_e])))))
|
||||
|
||||
(defn set-preferred-workflow!
|
||||
[workflow]
|
||||
(when workflow
|
||||
(state/set-preferred-workflow! workflow)
|
||||
(when (:name (:me @state/state))
|
||||
(util/post (str config/api "set_preferred_workflow")
|
||||
{:preferred_workflow (name workflow)}
|
||||
(fn [_result]
|
||||
(notification/show! "Workflow set successfully!" :success))
|
||||
(fn [_e])))))
|
||||
|
||||
(defn- clear-store!
|
||||
[]
|
||||
(p/let [_ (.clear db/localforage-instance)
|
||||
dbs (js/window.indexedDB.databases)]
|
||||
(doseq [db dbs]
|
||||
(js/window.indexedDB.deleteDatabase (gobj/get db "name")))))
|
||||
|
||||
(defn sign-out!
|
||||
[e]
|
||||
(->
|
||||
(do
|
||||
(storage/clear)
|
||||
(clear-store!))
|
||||
(p/catch (fn [e]
|
||||
(println "sign out error: ")
|
||||
(js/console.dir e)))
|
||||
(p/finally (fn []
|
||||
(set! (.-href js/window.location) "/logout")))))
|
||||
Reference in New Issue
Block a user