mirror of
https://github.com/logseq/logseq.git
synced 2026-04-25 14:44:53 +00:00
2368 lines
73 KiB
Clojure
2368 lines
73 KiB
Clojure
(ns frontend.state
|
|
"Provides main application state, fns associated to set and state based rum
|
|
cursors"
|
|
(:require [cljs-bean.core :as bean]
|
|
[cljs.core.async :as async :refer [>!]]
|
|
[cljs.spec.alpha :as s]
|
|
[clojure.set :as set]
|
|
[clojure.string :as string]
|
|
[datascript.core :as d]
|
|
[dommy.core :as dom]
|
|
[electron.ipc :as ipc]
|
|
[frontend.db.conn-state :as db-conn-state]
|
|
[frontend.db.transact :as db-transact]
|
|
[frontend.flows :as flows]
|
|
[frontend.mobile.util :as mobile-util]
|
|
[frontend.spec.storage :as storage-spec]
|
|
[frontend.storage :as storage]
|
|
[frontend.util :as util]
|
|
[frontend.util.cursor :as cursor]
|
|
[goog.dom :as gdom]
|
|
[goog.object :as gobj]
|
|
[logseq.common.config :as common-config]
|
|
[logseq.db :as ldb]
|
|
[logseq.db.common.entity-plus :as entity-plus]
|
|
[logseq.db.sqlite.util :as sqlite-util]
|
|
[logseq.shui.dialog.core :as shui-dialog]
|
|
[logseq.shui.hooks :as hooks]
|
|
[logseq.shui.ui :as shui]
|
|
[missionary.core :as m]
|
|
[promesa.core :as p]
|
|
[rum.core :as rum]))
|
|
|
|
(defonce *profile-state (volatile! {}))
|
|
|
|
(defonce *db-worker (atom nil))
|
|
(defonce *editor-info (atom nil))
|
|
|
|
(defn- <invoke-db-worker*
|
|
[qkw direct-pass-args? args-list]
|
|
(let [worker @*db-worker]
|
|
(when (nil? worker)
|
|
(prn :<invoke-db-worker-error qkw)
|
|
(throw (ex-info "db-worker has not been initialized" {})))
|
|
(apply worker qkw direct-pass-args? args-list)))
|
|
|
|
(defn <invoke-db-worker
|
|
"invoke db-worker thread api"
|
|
[qkw & args]
|
|
(<invoke-db-worker* qkw false args))
|
|
|
|
(defn <invoke-db-worker-direct-pass-args
|
|
"invoke db-worker thread api.
|
|
But directly pass args to db-worker(won't do transit-write on them)."
|
|
[qkw & args]
|
|
(<invoke-db-worker* qkw true args))
|
|
|
|
;; Stores main application state
|
|
(defonce ^:large-vars/data-var state
|
|
(let [document-mode? (or (storage/get :document/mode?) false)
|
|
current-graph (let [url-graph (:graph (util/parse-params))
|
|
graph (or url-graph (storage/get :git/current-repo))]
|
|
(when graph (ipc/ipc "setCurrentGraph" graph))
|
|
graph)]
|
|
(atom
|
|
{:client-id (str (random-uuid))
|
|
:route-match nil
|
|
:today nil
|
|
:system/events (async/chan 1000)
|
|
:file/unlinked-dirs #{}
|
|
:reactive/custom-queries (async/chan 1000)
|
|
:notification/show? false
|
|
:notification/content nil
|
|
:repo/loading-files? {}
|
|
:nfs/refreshing? nil
|
|
:instrument/disabled? (storage/get "instrument-disabled")
|
|
;; TODO: how to detect the network reliably?
|
|
;; NOTE: prefer to use flows/network-online-event-flow
|
|
:network/online? true
|
|
:indexeddb/support? true
|
|
:me nil
|
|
:git/current-repo current-graph
|
|
:draw? false
|
|
:db/restoring? nil
|
|
|
|
:search/q ""
|
|
:search/mode nil ; nil -> global mode, :graph -> add graph filter, etc.
|
|
:search/args nil
|
|
:search/result nil
|
|
:search/graph-filters []
|
|
:search/engines {}
|
|
|
|
;; modals
|
|
:modal/dropdowns {}
|
|
:modal/id nil
|
|
|
|
;; ui
|
|
:ui/viewport {}
|
|
|
|
;; left sidebar
|
|
:ui/navigation-item-collapsed? {}
|
|
:ui/recent-pages (or (storage/get :ui/recent-pages) {})
|
|
|
|
;; right sidebar
|
|
:ui/handbooks-open? false
|
|
:ui/help-open? false
|
|
:ui/fullscreen? false
|
|
:ui/settings-open? false
|
|
:ui/sidebar-open? false
|
|
:ui/sidebar-width "40%"
|
|
:ui/left-sidebar-open? (boolean (storage/get :ls-left-sidebar-open?))
|
|
:ui/theme (or (storage/get :ui/theme) "light")
|
|
:ui/system-theme? ((fnil identity (or util/mac? util/win32? false)) (storage/get :ui/system-theme?))
|
|
:ui/custom-theme (or (storage/get :ui/custom-theme) {:light {:mode "light"} :dark {:mode "dark"}})
|
|
:ui/wide-mode? (storage/get :ui/wide-mode)
|
|
:ui/radix-color (storage/get :ui/radix-color)
|
|
:ui/editor-font (storage/get :ui/editor-font)
|
|
|
|
;; ui/collapsed-blocks is to separate the collapse/expand state from db for:
|
|
;; 1. right sidebar
|
|
;; 2. zoom-in view
|
|
;; 3. queries
|
|
;; 4. references
|
|
;; graph => {:block-id bool}
|
|
:ui/collapsed-blocks {}
|
|
:ui/sidebar-collapsed-blocks {}
|
|
:ui/root-component nil
|
|
:ui/file-component nil
|
|
:ui/developer-mode? (or (= (storage/get "developer-mode") "true")
|
|
false)
|
|
;; remember scroll positions of visited paths
|
|
:ui/paths-scroll-positions (atom {})
|
|
:ui/main-container-scroll-top (atom nil)
|
|
:ui/shortcut-tooltip? (if (false? (storage/get :ui/shortcut-tooltip?))
|
|
false
|
|
true)
|
|
:ui/scrolling? (atom false)
|
|
:ui/show-empty-and-hidden-properties? (atom {:mode :global
|
|
:show? false})
|
|
:document/mode? document-mode?
|
|
|
|
:config {}
|
|
:block/component-editing-mode? false
|
|
:editor/op (atom nil)
|
|
:editor/start-pos (atom nil)
|
|
:editor/async-unsaved-chars (atom nil)
|
|
:editor/hidden-editors #{} ;; page names
|
|
:editor/draw-mode? false
|
|
|
|
:editor/action (atom nil)
|
|
:editor/action-data nil
|
|
;; With label or other data
|
|
:editor/last-saved-cursor (atom {})
|
|
:editor/editing? (atom {})
|
|
:editor/in-composition? false
|
|
:editor/content (atom {})
|
|
:editor/block (atom nil)
|
|
:editor/set-timestamp-block (atom nil) ;; click rendered block timestamp-cp to set timestamp
|
|
:editor/last-input-time (atom {})
|
|
:editor/document-mode? document-mode?
|
|
:editor/args (atom nil)
|
|
:editor/on-paste? (atom false)
|
|
:editor/last-key-code (atom nil)
|
|
:ui/global-last-key-code (atom nil)
|
|
:editor/block-op-type nil ;; :cut, :copy
|
|
:editor/block-refs (atom #{})
|
|
|
|
;; Stores deleted refed blocks, indexed by repo
|
|
:editor/last-replace-ref-content-tx nil
|
|
|
|
;; for audio record
|
|
:editor/record-status "NONE"
|
|
|
|
:editor/code-block-context nil
|
|
:editor/latest-shortcut (atom nil)
|
|
|
|
:history/paused? (atom false)
|
|
:editor/cursor-range (atom nil)
|
|
:editor/container-id (atom nil)
|
|
:editor/next-edit-block (atom nil)
|
|
:editor/raw-mode-block (atom nil)
|
|
:editor/virtualized-scroll-fn nil
|
|
|
|
;; Warning: blocks order is determined when setting this attribute
|
|
:selection/blocks (atom [])
|
|
:selection/start-block (atom nil)
|
|
;; nil, :up or :down
|
|
;; used to determine selection direction when two or more blocks are selected
|
|
:selection/direction (atom nil)
|
|
:selection/selected-all? (atom false)
|
|
:custom-context-menu/show? false
|
|
:custom-context-menu/links nil
|
|
:custom-context-menu/position nil
|
|
|
|
;; pages or blocks in the right sidebar
|
|
;; It is a list of `[repo db-id block-type block-data]` 4-tuple
|
|
:sidebar/blocks '()
|
|
|
|
:preferred-language (storage/get :preferred-language)
|
|
|
|
;; electron
|
|
:electron/auto-updater-downloaded false
|
|
:electron/updater-pending? false
|
|
:electron/updater {}
|
|
:electron/user-cfgs nil
|
|
:electron/server nil
|
|
:electron/window-maximized? false
|
|
:electron/window-fullscreen? false
|
|
|
|
;; assets
|
|
:assets/alias-enabled? (or (storage/get :assets/alias-enabled?) false)
|
|
:assets/alias-dirs (or (storage/get :assets/alias-dirs) [])
|
|
:assets/asset-file-write-finish (atom {})
|
|
|
|
;; mobile
|
|
:mobile/container-urls nil
|
|
:mobile/show-action-bar? false
|
|
:mobile/actioned-block nil
|
|
:mobile/show-toolbar? false
|
|
:mobile/show-recording-bar? false
|
|
:mobile/show-tabbar? false
|
|
|
|
;; plugin
|
|
:plugin/enabled (and util/plugin-platform?
|
|
;; true false :theme-only
|
|
((fnil identity true) (storage/get ::storage-spec/lsp-core-enabled)))
|
|
:plugin/preferences nil
|
|
:plugin/indicator-text nil
|
|
:plugin/installed-plugins {}
|
|
:plugin/installed-themes []
|
|
:plugin/installed-slash-commands {}
|
|
:plugin/installed-ui-items {}
|
|
:plugin/installed-resources {}
|
|
:plugin/installed-hooks {}
|
|
:plugin/installed-services {}
|
|
:plugin/simple-commands {}
|
|
:plugin/selected-theme nil
|
|
:plugin/selected-unpacked-pkg nil
|
|
:plugin/marketplace-pkgs nil
|
|
:plugin/marketplace-stats nil
|
|
:plugin/installing nil
|
|
:plugin/active-readme nil
|
|
:plugin/updates-auto-checking? false
|
|
:plugin/updates-pending {}
|
|
:plugin/updates-coming {}
|
|
:plugin/updates-downloading? false
|
|
:plugin/updates-unchecked #{}
|
|
:plugin/navs-settings? true
|
|
:plugin/focused-settings nil ;; plugin id
|
|
|
|
;; pdf
|
|
:pdf/system-win? false
|
|
:pdf/current nil
|
|
:pdf/ref-highlight nil
|
|
:pdf/block-highlight-colored? (or (storage/get "ls-pdf-hl-block-is-colored") true)
|
|
:pdf/auto-open-ctx-menu? (not= false (storage/get "ls-pdf-auto-open-ctx-menu"))
|
|
|
|
;; all notification contents as k-v pairs
|
|
:notification/contents {}
|
|
:graph/syncing? false
|
|
;; graph -> state
|
|
:graph/parsing-state {}
|
|
|
|
:copy/export-block-text-indent-style (or (storage/get :copy/export-block-text-indent-style)
|
|
"dashes")
|
|
:copy/export-block-text-remove-options (or (storage/get :copy/export-block-text-remove-options)
|
|
#{})
|
|
:copy/export-block-text-other-options (or (storage/get :copy/export-block-text-other-options)
|
|
{})
|
|
:date-picker/date nil
|
|
|
|
:youtube/players {}
|
|
|
|
;; command palette
|
|
:command-palette/commands (atom [])
|
|
|
|
:view/components {}
|
|
:view/selected-blocks nil
|
|
|
|
:srs/mode? false
|
|
|
|
:srs/cards-due-count nil
|
|
|
|
:reactive/query-dbs {}
|
|
|
|
;; login, userinfo, token, ...
|
|
:auth/refresh-token (storage/get "refresh-token")
|
|
:auth/access-token nil
|
|
:auth/id-token nil
|
|
|
|
;; file-sync
|
|
:file-sync/jstour-inst nil
|
|
:file-sync/onboarding-state (or (storage/get :file-sync/onboarding-state)
|
|
{:welcome false})
|
|
:file-sync/remote-graphs {:loading false :graphs nil}
|
|
:file-sync/set-remote-graph-password-result {}
|
|
|
|
;; graph-uuid -> {:graphs-txid {}
|
|
;; :file-sync/sync-manager {}
|
|
;; :file-sync/sync-state {}
|
|
;; ;; {file-path -> payload}
|
|
;; :file-sync/progress {}
|
|
;; :file-sync/start-time {}
|
|
;; :file-sync/last-synced-at {}}
|
|
:file-sync/graph-state {:current-graph-uuid nil}
|
|
;; graph-uuid -> ...
|
|
|
|
:rtc/state (atom {})
|
|
;; only latest rtc-log stored here, when a log stream is needed,
|
|
;; use missionary to create a rtc-log-flow, use (missionary.core/watch <atom>)
|
|
:rtc/log (atom nil)
|
|
:rtc/uploading? false
|
|
:rtc/downloading-graph-uuid nil
|
|
:rtc/graphs []
|
|
:rtc/online-info (atom {})
|
|
:rtc/asset-upload-download-progress (atom {})
|
|
|
|
:user/info {:UserGroups (storage/get :user-groups)}
|
|
:encryption/graph-parsing? false
|
|
|
|
:ui/loading? {}
|
|
:ui/container-id (atom 0)
|
|
:ui/cached-key->container-id (atom {})
|
|
:feature/enable-sync? (storage/get :logseq-sync-enabled)
|
|
:feature/enable-sync-diff-merge? ((fnil identity true) (storage/get :logseq-sync-diff-merge-enabled))
|
|
:feature/enable-rtc? (storage/get :logseq-rtc-enabled)
|
|
|
|
:file/rename-event-chan (async/chan 100)
|
|
:ui/find-in-page nil
|
|
:graph/importing nil
|
|
:graph/importing-state {}
|
|
:graph/loading? nil
|
|
:handbook/route-chan (async/chan (async/sliding-buffer 1))
|
|
|
|
:whiteboard/onboarding-whiteboard? (or (storage/get :ls-onboarding-whiteboard?) false)
|
|
:whiteboard/onboarding-tour? (or (storage/get :whiteboard-onboarding-tour?) false)
|
|
:whiteboard/last-persisted-at {}
|
|
:whiteboard/pending-tx-data {}
|
|
:system/info {}
|
|
;; Whether block is selected
|
|
:ui/select-query-cache (atom {})
|
|
:ui/toggle-highlight-recent-blocks? (atom false)
|
|
:ui/highlight-recent-days (atom (or (storage/get :ui/highlight-recent-days)
|
|
3))
|
|
:favorites/updated? (atom 0)
|
|
:db/async-queries (atom {})
|
|
:db/latest-transacted-entity-uuids (atom {})})))
|
|
|
|
;; User configuration getters under :config (and sometimes :me)
|
|
;; ========================================
|
|
;; TODO: Refactor default config values to be data driven. Currently they are all
|
|
;; buried in getters
|
|
;; TODO: Refactor our access to be more data driven. Currently each getter
|
|
;; (re-)fetches get-current-repo needlessly
|
|
;; TODO: Add consistent validation. Only a few config options validate at get time
|
|
|
|
(def common-default-config
|
|
"Common default config for a user's repo config"
|
|
{:feature/enable-search-remove-accents? true
|
|
:ui/auto-expand-block-refs? true
|
|
|
|
;; For flushing the settings of old versions. Don't bump this value.
|
|
;; There are only two kinds of graph, one is not upgraded (:legacy) and one is upgraded (:triple-lowbar)
|
|
;; For not upgraded graphs, the config will have no key `:file/name-format`
|
|
;; Then the default value is applied
|
|
:file/name-format :legacy})
|
|
|
|
(def file-default-config
|
|
"Default repo config for file graphs"
|
|
(merge common-default-config
|
|
;; The "NOW" query returns tasks with "NOW" or "DOING" status.
|
|
;; The "NEXT" query returns tasks with "NOW", "LATER", or "TODO" status.
|
|
{:default-queries
|
|
{:journals
|
|
[{:title "🔨 NOW"
|
|
:query '[:find (pull ?h [*])
|
|
:in $ ?start ?today
|
|
:where
|
|
(task ?h #{"NOW" "DOING"})
|
|
[?h :block/page ?p]
|
|
[?p :block/journal-day ?d]
|
|
[(>= ?d ?start)]
|
|
[(<= ?d ?today)]]
|
|
:inputs [:14d :today]
|
|
:result-transform '(fn [result]
|
|
(sort-by (fn [h]
|
|
(get h :block/priority "Z")) result))
|
|
:group-by-page? false
|
|
:collapsed? false}
|
|
{:title "📅 NEXT"
|
|
:query '[:find (pull ?h [*])
|
|
:in $ ?start ?next
|
|
:where
|
|
(task ?h #{"NOW" "LATER" "TODO"})
|
|
[?h :block/page ?p]
|
|
[?p :block/journal-day ?d]
|
|
[(> ?d ?start)]
|
|
[(< ?d ?next)]]
|
|
:inputs [:today :7d-after]
|
|
:group-by-page? false
|
|
:collapsed? false}]}}))
|
|
|
|
(def db-default-config
|
|
"Default repo config for DB graphs"
|
|
(merge common-default-config
|
|
;; The "DOING" query returns tasks with "Doing" status for recent past days
|
|
;; The "TODO" query returns tasks with "Todo" status for upcoming future days
|
|
{:default-queries
|
|
{:journals
|
|
[{:title [:span (shui/tabler-icon "InProgress50" {:class "align-middle pr-1"}) [:span.align-middle "DOING"]]
|
|
:query '[:find (pull ?b [*])
|
|
:in $ ?start ?today
|
|
:where
|
|
(task ?b #{"Doing"})
|
|
[?b :block/page ?p]
|
|
[?p :block/journal-day ?d]
|
|
[(>= ?d ?start)]
|
|
[(<= ?d ?today)]]
|
|
:inputs [:14d :today]
|
|
:collapsed? false}
|
|
{:title [:span (shui/tabler-icon "Todo" {:class "align-middle pr-1"}) [:span.align-middle "TODO"]]
|
|
:query '[:find (pull ?b [*])
|
|
:in $ ?start ?next
|
|
:where
|
|
(task ?b #{"Todo"})
|
|
[?b :block/page ?p]
|
|
[?p :block/journal-day ?d]
|
|
[(> ?d ?start)]
|
|
[(< ?d ?next)]]
|
|
:inputs [:today :7d-after]
|
|
:group-by-page? false
|
|
:collapsed? false}]}
|
|
:ui/hide-empty-properties? false}))
|
|
|
|
;; State that most user config is dependent on
|
|
(declare get-current-repo sub set-state!)
|
|
|
|
(defn merge-configs
|
|
"Merges user configs in given orders. All values are overridden except for maps
|
|
which are merged."
|
|
[& configs]
|
|
(->> configs
|
|
(filter map?)
|
|
(apply merge-with
|
|
(fn merge-config [current new]
|
|
(if (and (map? current) (map? new))
|
|
(merge current new)
|
|
new)))))
|
|
|
|
(defn get-global-config
|
|
[]
|
|
(get-in @state [:config ::global-config]))
|
|
|
|
(defn get-global-config-str-content
|
|
[]
|
|
(get-in @state [:config ::global-config-str-content]))
|
|
|
|
(defn get-graph-config
|
|
([] (get-graph-config (get-current-repo)))
|
|
([repo-url] (get-in @state [:config repo-url])))
|
|
|
|
(defn get-config
|
|
"User config for the given repo or current repo if none given. All config fetching
|
|
should be done through this fn in order to get global config and config defaults"
|
|
([]
|
|
(get-config (get-current-repo)))
|
|
([repo-url]
|
|
(merge-configs
|
|
(if (sqlite-util/db-based-graph? repo-url) db-default-config file-default-config)
|
|
(get-global-config)
|
|
(get-graph-config repo-url))))
|
|
|
|
(defn publishing-enable-editing?
|
|
[]
|
|
(and common-config/PUBLISHING (:publishing/enable-editing? (get-config))))
|
|
|
|
(defn enable-editing?
|
|
[]
|
|
(or (not common-config/PUBLISHING) (:publishing/enable-editing? (get-config))))
|
|
|
|
(defonce built-in-macros
|
|
{"img" "[:img.$4 {:src \"$1\" :style {:width $2 :height $3}}]"})
|
|
|
|
(defn get-macros
|
|
[]
|
|
(merge
|
|
built-in-macros
|
|
(:macros (get-config))))
|
|
|
|
(defn set-assets-alias-enabled!
|
|
[v]
|
|
(set-state! :assets/alias-enabled? (boolean v))
|
|
(storage/set :assets/alias-enabled? (boolean v)))
|
|
|
|
(defn set-assets-alias-dirs!
|
|
[dirs]
|
|
(when dirs
|
|
(set-state! :assets/alias-dirs dirs)
|
|
(storage/set :assets/alias-dirs dirs)))
|
|
|
|
(defn get-custom-css-link
|
|
[]
|
|
(:custom-css-url (get-config)))
|
|
|
|
(defn get-custom-js-link
|
|
[]
|
|
(:custom-js-url (get-config)))
|
|
|
|
(defn get-default-journal-template
|
|
[]
|
|
(when-let [template (get-in (get-config) [:default-templates :journals])]
|
|
(when-not (string/blank? template)
|
|
(string/trim template))))
|
|
|
|
(defn all-pages-public?
|
|
[]
|
|
(let [value (:publishing/all-pages-public? (get-config))
|
|
value (if (some? value) value (:all-pages-public? (get-config)))]
|
|
(true? value)))
|
|
|
|
(defn get-default-home
|
|
[]
|
|
(:default-home (get-config)))
|
|
|
|
(defn custom-home-page?
|
|
[]
|
|
(some? (:page (get-default-home))))
|
|
|
|
(defn get-preferred-format
|
|
([]
|
|
(get-preferred-format (get-current-repo)))
|
|
([repo-url]
|
|
(keyword
|
|
(or
|
|
(common-config/get-preferred-format (get-config repo-url))
|
|
(get-in @state [:me :preferred_format] "markdown")))))
|
|
|
|
(defn markdown?
|
|
[]
|
|
(= (keyword (get-preferred-format))
|
|
:markdown))
|
|
|
|
(defn get-pages-directory
|
|
[]
|
|
(or
|
|
(when-let [repo (get-current-repo)]
|
|
(:pages-directory (get-config repo)))
|
|
"pages"))
|
|
|
|
(defn get-journals-directory
|
|
[]
|
|
(or
|
|
(when-let [repo (get-current-repo)]
|
|
(:journals-directory (get-config repo)))
|
|
"journals"))
|
|
|
|
(defn get-whiteboards-directory
|
|
[]
|
|
(or
|
|
(when-let [repo (get-current-repo)]
|
|
(:whiteboards-directory (get-config repo)))
|
|
"whiteboards"))
|
|
|
|
(defn org-mode-file-link?
|
|
[repo]
|
|
(:org-mode/insert-file-link? (get-config repo)))
|
|
|
|
(defn get-journal-file-name-format
|
|
[]
|
|
(when-let [repo (get-current-repo)]
|
|
(:journal/file-name-format (get-config repo))))
|
|
|
|
(defn get-preferred-workflow
|
|
[]
|
|
(keyword
|
|
(or
|
|
(when-let [workflow (:preferred-workflow (get-config))]
|
|
(let [workflow (name workflow)]
|
|
(if (util/safe-re-find #"now|NOW" workflow)
|
|
:now
|
|
:todo)))
|
|
(get-in @state [:me :preferred_workflow] :now))))
|
|
|
|
(defn get-preferred-todo
|
|
[]
|
|
(if (= (get-preferred-workflow) :now)
|
|
"LATER"
|
|
"TODO"))
|
|
|
|
(defn get-date-formatter
|
|
[]
|
|
(let [repo (get-current-repo)]
|
|
(if (sqlite-util/db-based-graph? repo)
|
|
(when-let [conn (db-conn-state/get-conn repo)]
|
|
(get (entity-plus/entity-memoized @conn :logseq.class/Journal)
|
|
:logseq.property.journal/title-format
|
|
"MMM do, yyyy"))
|
|
(common-config/get-date-formatter (get-config)))))
|
|
|
|
(defn custom-shortcuts []
|
|
(merge (storage/get :ls-shortcuts)
|
|
(:shortcuts (get-config))))
|
|
|
|
(defn get-commands
|
|
[]
|
|
(:commands (get-config)))
|
|
|
|
(defn get-scheduled-future-days
|
|
[]
|
|
(let [days (:scheduled/future-days (get-config))]
|
|
(or (when (int? days) days) 7)))
|
|
|
|
(defn get-start-of-week
|
|
[]
|
|
(or (:start-of-week (get-config))
|
|
(get-in @state [:me :settings :start-of-week])
|
|
6))
|
|
|
|
(defn get-ref-open-blocks-level
|
|
[]
|
|
(or
|
|
(when-let [value (:ref/default-open-blocks-level (get-config))]
|
|
(when (pos-int? value)
|
|
(min value 9)))
|
|
2))
|
|
|
|
(defn get-export-bullet-indentation
|
|
[]
|
|
(case (get (get-config) :export/bullet-indentation :tab)
|
|
:eight-spaces
|
|
" "
|
|
:four-spaces
|
|
" "
|
|
:two-spaces
|
|
" "
|
|
:tab
|
|
"\t"))
|
|
|
|
(defn enable-search-remove-accents?
|
|
[]
|
|
(:feature/enable-search-remove-accents? (get-config)))
|
|
|
|
;; State cursor fns for use with rum components
|
|
;; ============================================
|
|
|
|
(declare document-mode?)
|
|
|
|
(defn sub
|
|
"Creates a rum cursor, https://github.com/tonsky/rum#cursors, for use in rum components.
|
|
Similar to re-frame subscriptions"
|
|
[ks & {:keys [path-in-sub-atom]}]
|
|
(let [ks-coll? (coll? ks)
|
|
get-fn (if ks-coll? get-in get)
|
|
s (get-fn @state ks)
|
|
s-atom? (util/atom? s)
|
|
path-coll?-in-sub-atom (coll? path-in-sub-atom)]
|
|
(cond
|
|
(and s-atom? path-in-sub-atom path-coll?-in-sub-atom)
|
|
(util/react (rum/cursor-in s path-in-sub-atom))
|
|
|
|
(and s-atom? path-in-sub-atom)
|
|
(util/react (rum/cursor s path-in-sub-atom))
|
|
|
|
s-atom? (util/react s)
|
|
ks-coll? (util/react (rum/cursor-in state ks))
|
|
:else (util/react (rum/cursor state ks)))))
|
|
|
|
(defn set-editing-block-id!
|
|
[container-block]
|
|
(reset! (:editor/editing? @state) {container-block true}))
|
|
|
|
(defn- sub-flow-state
|
|
[flow watch-ref sub-value-f deps]
|
|
(let [checkf (hooks/use-callback sub-value-f deps)
|
|
init-value (checkf @watch-ref)
|
|
flow (hooks/use-memo
|
|
#(m/eduction
|
|
(map checkf)
|
|
(dedupe)
|
|
(drop-while (fn [x] (identical? x init-value)))
|
|
flow)
|
|
[init-value])]
|
|
(hooks/use-flow-state init-value flow)))
|
|
|
|
(def ^:private editing-flow
|
|
(m/watch (:editor/editing? @state)))
|
|
|
|
(defn sub-editing?
|
|
[container-block]
|
|
(sub-flow-state editing-flow
|
|
(:editor/editing? @state)
|
|
(fn [s] (boolean (get s container-block)))
|
|
[container-block]))
|
|
|
|
(defn sub-config
|
|
"Sub equivalent to get-config which should handle all sub user-config access"
|
|
([] (sub-config (get-current-repo)))
|
|
([repo]
|
|
(let [config (sub :config)]
|
|
(merge-configs (if (and (string? repo) (sqlite-util/db-based-graph? repo)) db-default-config file-default-config)
|
|
(get config ::global-config)
|
|
(get config repo)))))
|
|
|
|
(defn enable-grammarly?
|
|
[]
|
|
(true? (:feature/enable-grammarly? (sub-config))))
|
|
|
|
(defn scheduled-deadlines-disabled?
|
|
[]
|
|
(true? (:feature/disable-scheduled-and-deadline-query? (sub-config))))
|
|
|
|
(defn enable-timetracking?
|
|
[]
|
|
(not (false? (:feature/enable-timetracking? (sub-config)))))
|
|
|
|
(defn enable-fold-button-right?
|
|
[]
|
|
(let [_ (sub :ui/viewport)]
|
|
(and (util/mobile?)
|
|
(util/sm-breakpoint?))))
|
|
|
|
(defn enable-journals?
|
|
([]
|
|
(enable-journals? (get-current-repo)))
|
|
([repo]
|
|
(not (false? (:feature/enable-journals? (sub-config repo))))))
|
|
|
|
(defn enable-flashcards?
|
|
([]
|
|
(enable-flashcards? (get-current-repo)))
|
|
([repo]
|
|
(not (false? (:feature/enable-flashcards? (sub-config repo))))))
|
|
|
|
(defn enable-sync?
|
|
[]
|
|
(sub :feature/enable-sync?))
|
|
|
|
(defn enable-sync-diff-merge?
|
|
[]
|
|
(sub :feature/enable-sync-diff-merge?))
|
|
|
|
(defn enable-whiteboards?
|
|
([]
|
|
(enable-whiteboards? (get-current-repo)))
|
|
([repo]
|
|
(not (false? (:feature/enable-whiteboards? (sub-config repo))))))
|
|
|
|
(defn enable-rtc?
|
|
[]
|
|
(sub :feature/enable-rtc?))
|
|
|
|
(defn enable-git-auto-push?
|
|
[repo]
|
|
(not (false? (:git-auto-push (sub-config repo)))))
|
|
|
|
(defn graph-settings
|
|
[]
|
|
(:graph/settings (sub-config)))
|
|
|
|
(defn graph-forcesettings
|
|
[]
|
|
(:graph/forcesettings (sub-config)))
|
|
|
|
;; Enable by default
|
|
(defn show-brackets?
|
|
[]
|
|
(not (false? (:ui/show-brackets? (sub-config)))))
|
|
|
|
(defn sub-default-home-page
|
|
[]
|
|
(get-in (sub-config) [:default-home :page] ""))
|
|
|
|
(defn- get-selected-block-ids
|
|
[blocks]
|
|
(->> blocks
|
|
(remove nil?)
|
|
(keep #(when-let [id (dom/attr % "blockid")]
|
|
(uuid id)))
|
|
(distinct)))
|
|
|
|
(defn block-content-max-length
|
|
[repo]
|
|
(or (:block/title-max-length (sub-config repo))
|
|
;; backward compatible
|
|
(:block/content-max-length (sub-config repo))
|
|
10000))
|
|
|
|
(defn mobile?
|
|
[]
|
|
(or (util/mobile?) (mobile-util/native-platform?)))
|
|
|
|
(defn enable-tooltip?
|
|
[]
|
|
(if (mobile?)
|
|
false
|
|
(get (sub-config) :ui/enable-tooltip? true)))
|
|
|
|
(defn show-command-doc?
|
|
[]
|
|
(get (sub-config) :ui/show-command-doc? true))
|
|
|
|
(defn logical-outdenting?
|
|
[]
|
|
(:editor/logical-outdenting? (sub-config)))
|
|
|
|
(defn show-full-blocks?
|
|
[]
|
|
(:ui/show-full-blocks? (sub-config)))
|
|
|
|
(defn preferred-pasting-file?
|
|
[]
|
|
(:editor/preferred-pasting-file? (sub-config)))
|
|
|
|
(defn auto-expand-block-refs?
|
|
[]
|
|
(:ui/auto-expand-block-refs? (sub-config)))
|
|
|
|
(defn doc-mode-enter-for-new-line?
|
|
[]
|
|
(and (document-mode?)
|
|
(not (:shortcut/doc-mode-enter-for-new-block? (get-config)))))
|
|
|
|
(defn user-groups
|
|
[]
|
|
(set (sub [:user/info :UserGroups])))
|
|
|
|
;; State mutation helpers
|
|
;; ======================
|
|
|
|
(defn set-state!
|
|
[path value & {:keys [path-in-sub-atom]}]
|
|
(vswap! *profile-state update path inc)
|
|
(let [path-coll? (coll? path)
|
|
get-fn (if path-coll? get-in get)
|
|
s (get-fn @state path)
|
|
s-atom? (util/atom? s)
|
|
path-coll?-in-sub-atom (coll? path-in-sub-atom)]
|
|
(cond
|
|
(and s-atom? path-in-sub-atom path-coll?-in-sub-atom)
|
|
(let [old-v (get-in @s path-in-sub-atom)]
|
|
(when (not= old-v value)
|
|
(swap! s assoc-in path-in-sub-atom value)))
|
|
|
|
(and s-atom? path-in-sub-atom)
|
|
(let [old-v (get @s path-in-sub-atom)]
|
|
(when (not= old-v value)
|
|
(swap! s assoc path-in-sub-atom value)))
|
|
|
|
s-atom?
|
|
(when (not= @s value)
|
|
(reset! s value))
|
|
|
|
path-coll?
|
|
(when (not= s value)
|
|
(swap! state assoc-in path value))
|
|
|
|
:else
|
|
(when (not= s value)
|
|
(swap! state assoc path value))))
|
|
nil)
|
|
|
|
(defn update-state!
|
|
[path f & {:keys [path-in-sub-atom]}]
|
|
(vswap! *profile-state update path inc)
|
|
(let [path-coll? (coll? path)
|
|
get-fn (if path-coll? get-in get)
|
|
s (get-fn @state path)
|
|
s-atom? (util/atom? s)
|
|
path-coll?-in-sub-atom (coll? path-in-sub-atom)]
|
|
(cond
|
|
(and s-atom? path-in-sub-atom path-coll?-in-sub-atom)
|
|
(swap! s update-in path-in-sub-atom f)
|
|
|
|
(and s-atom? path-in-sub-atom)
|
|
(swap! s update path-in-sub-atom f)
|
|
|
|
s-atom? (swap! s f)
|
|
path-coll? (swap! state update-in path f)
|
|
:else (swap! state update path f)))
|
|
nil)
|
|
|
|
;; State getters and setters
|
|
;; =========================
|
|
;; These fns handle any key except :config.
|
|
;; Some state is also stored in local storage and/or sent to electron's main process
|
|
|
|
(defn get-route-match
|
|
[]
|
|
(:route-match @state))
|
|
|
|
(defn get-current-route
|
|
[]
|
|
(get-in (get-route-match) [:data :name]))
|
|
|
|
(defn home?
|
|
[]
|
|
(= :home (get-current-route)))
|
|
|
|
(defn whiteboard-dashboard?
|
|
[]
|
|
(= :whiteboards (get-current-route)))
|
|
|
|
(defn get-current-page
|
|
[]
|
|
(when (= :page (get-current-route))
|
|
(get-in (get-route-match)
|
|
[:path-params :name])))
|
|
|
|
(defn route-has-p?
|
|
[]
|
|
(get-in (get-route-match) [:query-params :p]))
|
|
|
|
(defn get-current-repo
|
|
"Returns the current repo URL, or else open demo graph"
|
|
[]
|
|
(:git/current-repo @state))
|
|
|
|
(defn get-remote-file-graphs
|
|
[]
|
|
(get-in @state [:file-sync/remote-graphs :graphs]))
|
|
|
|
(defn get-rtc-graphs
|
|
[]
|
|
(:rtc/graphs @state))
|
|
|
|
(defn get-remote-graph-info-by-uuid
|
|
[uuid]
|
|
(when-let [graphs (seq (get-in @state [:file-sync/remote-graphs :graphs]))]
|
|
(some #(when (= (:GraphUUID %) (str uuid)) %) graphs)))
|
|
|
|
(defn get-remote-graph-usage
|
|
[]
|
|
(when-let [graphs (seq (get-in @state [:file-sync/remote-graphs :graphs]))]
|
|
(->> graphs
|
|
(map #(hash-map :uuid (:GraphUUID %)
|
|
:name (:GraphName %)
|
|
:used-gbs (/ (:GraphStorageUsage %) 1024 1024 1024)
|
|
:limit-gbs (/ (:GraphStorageLimit %) 1024 1024 1024)
|
|
:used-percent (/ (:GraphStorageUsage %) (:GraphStorageLimit %) 0.01)))
|
|
(map #(assoc % :free-gbs (- (:limit-gbs %) (:used-gbs %))))
|
|
(vec))))
|
|
|
|
(defn delete-remote-graph!
|
|
[repo]
|
|
(let [remove-repo! (fn [repos]
|
|
(remove #(and
|
|
(:GraphUUID repo)
|
|
(:GraphUUID %)
|
|
(= (:GraphUUID repo) (:GraphUUID %))) repos))]
|
|
(if (:rtc-graph? repo)
|
|
(swap! state update :rtc/graphs remove-repo!)
|
|
(swap! state update-in [:file-sync/remote-graphs :graphs] remove-repo!))))
|
|
|
|
(defn add-remote-graph!
|
|
[repo]
|
|
(swap! state update-in [:file-sync/remote-graphs :graphs]
|
|
(fn [repos]
|
|
(->> (conj repos repo)
|
|
(distinct)))))
|
|
|
|
(defn get-repos
|
|
[]
|
|
(get-in @state [:me :repos]))
|
|
|
|
(defn set-repos!
|
|
[repos]
|
|
(set-state! [:me :repos] (distinct repos)))
|
|
|
|
(defn add-repo!
|
|
[repo]
|
|
(when (not (string/blank? repo))
|
|
(update-state! [:me :repos]
|
|
(fn [repos]
|
|
(->> (conj repos repo)
|
|
(distinct))))))
|
|
|
|
(defn set-current-repo!
|
|
[repo]
|
|
(swap! state assoc :git/current-repo repo)
|
|
(reset! flows/*current-repo repo)
|
|
(if repo
|
|
(storage/set :git/current-repo repo)
|
|
(storage/remove :git/current-repo))
|
|
(ipc/ipc "setCurrentGraph" repo))
|
|
|
|
(defn set-preferred-format!
|
|
[format]
|
|
(swap! state assoc-in [:me :preferred_format] (name format)))
|
|
|
|
(defn set-preferred-workflow!
|
|
[workflow]
|
|
(swap! state assoc-in [:me :preferred_workflow] (name workflow)))
|
|
|
|
(defn set-preferred-language!
|
|
[language]
|
|
(set-state! :preferred-language (name language))
|
|
(storage/set :preferred-language (name language)))
|
|
|
|
(defn delete-repo!
|
|
[repo]
|
|
(swap! state update-in [:me :repos]
|
|
(fn [repos]
|
|
(->> (remove #(or (= (:url repo) (:url %))
|
|
(and
|
|
(:GraphUUID repo)
|
|
(:GraphUUID %)
|
|
(= (:GraphUUID repo) (:GraphUUID %)))) repos)
|
|
(util/distinct-by :url)))))
|
|
|
|
(defn set-timestamp-block!
|
|
[value]
|
|
(set-state! :editor/set-timestamp-block value))
|
|
|
|
(defn get-timestamp-block
|
|
[]
|
|
@(:editor/set-timestamp-block @state))
|
|
|
|
(defn get-edit-block
|
|
[]
|
|
@(get @state :editor/block))
|
|
|
|
(defn editing?
|
|
[]
|
|
(seq @(:editor/editing? @state)))
|
|
|
|
(defn get-edit-input-id
|
|
[]
|
|
(when-not (exists? js/process)
|
|
(when (editing?)
|
|
(try
|
|
(when-let [elem (or (when-let [id (:block/uuid (get-edit-block))]
|
|
(gdom/getElement (str "edit-block-" id)))
|
|
js/document.activeElement)]
|
|
(when (util/input? elem)
|
|
(let [id (gobj/get elem "id")]
|
|
(when (string/starts-with? id "edit-block-")
|
|
id))))
|
|
(catch :default _e)))))
|
|
|
|
(defn set-edit-content!
|
|
([value] (set-edit-content! (get-edit-input-id) value))
|
|
([input-id value] (set-edit-content! input-id value true))
|
|
([input-id value set-input-value?]
|
|
(when input-id
|
|
(when set-input-value?
|
|
(when-let [input (gdom/getElement input-id)]
|
|
(util/set-change-value input value)))
|
|
(set-state! :editor/content value :path-in-sub-atom
|
|
(or (:block/uuid (get-edit-block)) input-id)))))
|
|
|
|
(defn get-input
|
|
[]
|
|
(when-let [id (get-edit-input-id)]
|
|
(gdom/getElement id)))
|
|
|
|
(defn get-edit-content
|
|
[]
|
|
(when-let [id (:block/uuid (get-edit-block))]
|
|
(get @(:editor/content @state) id)))
|
|
|
|
(defn sub-edit-content
|
|
([]
|
|
(sub-edit-content (:block/uuid (get-edit-block))))
|
|
([block-id]
|
|
(when block-id
|
|
(sub :editor/content {:path-in-sub-atom block-id}))))
|
|
|
|
(defn set-selection-start-block!
|
|
[start-block]
|
|
(set-state! :selection/start-block start-block))
|
|
|
|
(defn get-selection-start-block
|
|
[]
|
|
(or @(get @state :selection/start-block)
|
|
(when-let [edit-block (get-edit-block)]
|
|
(let [node (util/rec-get-node edit-block "ls-block")]
|
|
(set-selection-start-block! node)
|
|
node))))
|
|
|
|
(defn get-cursor-range
|
|
[]
|
|
@(:editor/cursor-range @state))
|
|
|
|
(defn set-cursor-range!
|
|
[range]
|
|
(set-state! :editor/cursor-range range))
|
|
|
|
(defn set-search-mode!
|
|
([value] (set-search-mode! value nil))
|
|
([value args]
|
|
(set-state! :search/mode value)
|
|
(set-state! :search/args args)))
|
|
|
|
(defn set-editor-action!
|
|
[value]
|
|
(set-state! :editor/action value))
|
|
|
|
(defn set-editor-action-data!
|
|
[value]
|
|
(set-state! :editor/action-data value))
|
|
|
|
(defn get-editor-action
|
|
[]
|
|
@(:editor/action @state))
|
|
|
|
(defn get-editor-action-data
|
|
[]
|
|
(:editor/action-data @state))
|
|
|
|
(defn get-editor-show-page-search?
|
|
[]
|
|
(= (get-editor-action) :page-search))
|
|
|
|
(defn get-editor-show-page-search-hashtag?
|
|
[]
|
|
(= (get-editor-action) :page-search-hashtag))
|
|
|
|
(defn get-editor-show-block-search?
|
|
[]
|
|
(= (get-editor-action) :block-search))
|
|
|
|
(defn set-editor-show-input!
|
|
[value]
|
|
(if value
|
|
(do
|
|
(set-editor-action-data! (assoc (get-editor-action-data) :options value))
|
|
(set-editor-action! :input))
|
|
(do
|
|
(set-editor-action! nil)
|
|
(set-editor-action-data! nil))))
|
|
|
|
(defn get-editor-show-input
|
|
[]
|
|
(when (= (get-editor-action) :input)
|
|
(get @state :editor/action-data)))
|
|
|
|
(defn set-editor-show-commands!
|
|
[]
|
|
(when-not (get-editor-action) (set-editor-action! :commands)))
|
|
|
|
(defn clear-editor-action!
|
|
[]
|
|
(set-state! :editor/action nil))
|
|
|
|
(defn get-edit-pos
|
|
[]
|
|
(when-let [input (get-input)]
|
|
(util/get-selection-start input)))
|
|
|
|
(defn get-selection-direction
|
|
[]
|
|
@(:selection/direction @state))
|
|
|
|
(defn get-unsorted-selection-blocks
|
|
[]
|
|
@(:selection/blocks @state))
|
|
|
|
(defn get-selection-blocks
|
|
[]
|
|
(let [result (get-unsorted-selection-blocks)
|
|
direction (get-selection-direction)]
|
|
(if (= direction :up)
|
|
(vec (reverse result))
|
|
result)))
|
|
|
|
(defn get-selection-block-ids
|
|
[]
|
|
(get-selected-block-ids (get-selection-blocks)))
|
|
|
|
(def ^:private block-selected-flow
|
|
(m/watch (:selection/blocks @state)))
|
|
|
|
(defn sub-block-selected?
|
|
[block-id]
|
|
(assert (uuid? block-id))
|
|
(sub-flow-state block-selected-flow
|
|
(:selection/blocks @state)
|
|
(fn [blocks]
|
|
(some #{block-id} (get-selected-block-ids blocks)))
|
|
[block-id]))
|
|
|
|
(defn dom-clear-selection!
|
|
[]
|
|
(doseq [node (dom/by-class "selected")]
|
|
(dom/remove-class! node "selected")))
|
|
|
|
(defn mark-dom-blocks-as-selected
|
|
[nodes]
|
|
(doseq [node nodes]
|
|
(dom/add-class! node "selected")
|
|
(when (dom/has-class? node "ls-table-row")
|
|
(.focus node))))
|
|
|
|
(defn get-events-chan
|
|
[]
|
|
(:system/events @state))
|
|
|
|
(defn pub-event!
|
|
{:malli/schema [:=> [:cat vector?] :any]}
|
|
[payload]
|
|
(let [d (p/deferred)
|
|
chan (get-events-chan)]
|
|
(async/put! chan [payload d])
|
|
d))
|
|
|
|
(defn- set-selection-blocks-aux!
|
|
[blocks]
|
|
(set-state! :view/selected-blocks nil)
|
|
(let [selected-ids (set (get-selected-block-ids @(:selection/blocks @state)))
|
|
_ (set-state! :selection/blocks blocks)
|
|
new-ids (set (get-selection-block-ids))
|
|
removed (set/difference selected-ids new-ids)]
|
|
(mark-dom-blocks-as-selected blocks)
|
|
(doseq [id removed]
|
|
(doseq [node (dom/sel (util/format "[blockid='%s']" id))]
|
|
(dom/remove-class! node "selected")
|
|
(when (dom/has-class? node "ls-table-row")
|
|
(.blur node))))))
|
|
|
|
(defn set-selection-blocks!
|
|
([blocks]
|
|
(set-selection-blocks! blocks nil))
|
|
([blocks direction]
|
|
(when (seq blocks)
|
|
(let [blocks (vec (remove nil? blocks))]
|
|
(set-selection-blocks-aux! blocks)
|
|
(when direction (set-state! :selection/direction direction))
|
|
(let [ids (get-selection-block-ids)]
|
|
(when (seq ids) (pub-event! [:editor/load-blocks ids])))))))
|
|
|
|
(defn state-clear-selection!
|
|
[]
|
|
(set-state! :selection/blocks nil)
|
|
(set-state! :selection/direction nil)
|
|
(set-state! :selection/start-block nil)
|
|
(set-state! :selection/selected-all? false)
|
|
(pub-event! [:editor/hide-action-bar]))
|
|
|
|
(defn clear-selection!
|
|
[]
|
|
(dom-clear-selection!)
|
|
(state-clear-selection!))
|
|
|
|
(defn get-selection-start-block-or-first
|
|
[]
|
|
(or (get-selection-start-block)
|
|
(some-> (first (get-selection-blocks))
|
|
(gobj/get "id"))))
|
|
|
|
(defn selection?
|
|
"True sense of selection mode with valid selected block"
|
|
[]
|
|
(seq (get-selection-blocks)))
|
|
|
|
(defn conj-selection-block!
|
|
[block-or-blocks direction]
|
|
(let [selection-blocks (get-unsorted-selection-blocks)
|
|
block-or-blocks (if (sequential? block-or-blocks) block-or-blocks [block-or-blocks])
|
|
blocks (-> (concat selection-blocks block-or-blocks)
|
|
distinct)]
|
|
(set-selection-blocks! blocks direction)))
|
|
|
|
(defn drop-selection-block!
|
|
[block]
|
|
(set-selection-blocks-aux! (-> (remove #(= block %) (get-unsorted-selection-blocks))
|
|
vec)))
|
|
|
|
(defn drop-selection-blocks-starts-with!
|
|
[block]
|
|
(let [blocks (get-unsorted-selection-blocks)
|
|
blocks' (-> (take-while (fn [b] (not= (.-id b) (.-id block))) blocks)
|
|
vec
|
|
(conj block))]
|
|
(set-selection-blocks-aux! blocks')))
|
|
|
|
(defn drop-last-selection-block!
|
|
[]
|
|
(let [blocks @(:selection/blocks @state)
|
|
blocks' (vec (butlast blocks))]
|
|
(set-selection-blocks-aux! blocks')
|
|
(last blocks)))
|
|
|
|
(defn hide-custom-context-menu!
|
|
[]
|
|
(swap! state assoc
|
|
:custom-context-menu/show? false
|
|
:custom-context-menu/links nil
|
|
:custom-context-menu/position nil))
|
|
|
|
(defn toggle-navigation-item-collapsed!
|
|
[item]
|
|
(update-state! [:ui/navigation-item-collapsed? item] not))
|
|
|
|
(defn toggle-sidebar-open?!
|
|
[]
|
|
(swap! state update :ui/sidebar-open? not))
|
|
|
|
(defn open-right-sidebar!
|
|
[]
|
|
(swap! state assoc :ui/sidebar-open? true))
|
|
|
|
(defn hide-right-sidebar!
|
|
[]
|
|
(swap! state assoc :ui/sidebar-open? false))
|
|
|
|
(defn sidebar-move-block!
|
|
[from to]
|
|
(update-state! :sidebar/blocks (fn [blocks]
|
|
(let [to (if (> from to) (inc to) to)]
|
|
(if (not= to from)
|
|
(let [item (nth blocks from)
|
|
blocks (keep-indexed #(when (not= %1 from) %2) blocks)
|
|
[l r] (split-at to blocks)]
|
|
(concat l [item] r))
|
|
blocks)))))
|
|
|
|
(defn sidebar-remove-block!
|
|
[idx]
|
|
(update-state! :sidebar/blocks (fn [blocks]
|
|
(if (string? idx)
|
|
(remove #(= (second %) idx) blocks)
|
|
(util/drop-nth idx blocks))))
|
|
(when (empty? (:sidebar/blocks @state))
|
|
(hide-right-sidebar!)))
|
|
|
|
(defn sidebar-remove-deleted-block!
|
|
[ids]
|
|
(let [ids-set (set ids)]
|
|
(update-state! :sidebar/blocks (fn [items]
|
|
(remove (fn [[repo id _]]
|
|
(and (= repo (get-current-repo)) (contains? ids-set id))) items)))
|
|
(when (empty? (:sidebar/blocks @state))
|
|
(hide-right-sidebar!))))
|
|
|
|
(defn sidebar-remove-rest!
|
|
[db-id]
|
|
(update-state! :sidebar/blocks (fn [blocks]
|
|
(remove #(not= (second %) db-id) blocks)))
|
|
(set-state! [:ui/sidebar-collapsed-blocks db-id] false))
|
|
|
|
(defn sidebar-replace-block!
|
|
[old-sidebar-key new-sidebar-key]
|
|
(update-state! :sidebar/blocks (fn [blocks]
|
|
(map #(if (= % old-sidebar-key)
|
|
new-sidebar-key
|
|
%) blocks))))
|
|
|
|
(defn sidebar-block-exists?
|
|
[idx]
|
|
(some #(= (second %) idx) (:sidebar/blocks @state)))
|
|
|
|
(defn clear-sidebar-blocks!
|
|
[]
|
|
(set-state! :sidebar/blocks '()))
|
|
|
|
(defn sidebar-block-toggle-collapse!
|
|
[db-id]
|
|
(when db-id
|
|
(update-state! [:ui/sidebar-collapsed-blocks db-id] not)))
|
|
|
|
(defn sidebar-block-collapse-rest!
|
|
[db-id]
|
|
(let [items (disj (set (map second (:sidebar/blocks @state))) db-id)]
|
|
(doseq [item items] (set-state! [:ui/sidebar-collapsed-blocks item] true))))
|
|
|
|
(defn sidebar-block-set-collapsed-all!
|
|
[collapsed?]
|
|
(let [items (map second (:sidebar/blocks @state))]
|
|
(doseq [item items]
|
|
(set-state! [:ui/sidebar-collapsed-blocks item] collapsed?))))
|
|
|
|
(defn clear-editor-last-pos!
|
|
[]
|
|
(set-state! :editor/last-saved-cursor {}))
|
|
|
|
(defn clear-cursor-range!
|
|
[]
|
|
(set-state! :editor/cursor-range nil))
|
|
|
|
(defn clear-edit!
|
|
[& {:keys [clear-editing-block?]
|
|
:or {clear-editing-block? true}}]
|
|
(clear-editor-action!)
|
|
(when clear-editing-block?
|
|
(set-state! :editor/editing? {})
|
|
(set-state! :editor/block nil))
|
|
(set-state! :editor/start-pos nil)
|
|
(clear-editor-last-pos!)
|
|
(clear-cursor-range!)
|
|
(set-state! :editor/content {})
|
|
(set-state! :ui/select-query-cache {})
|
|
(set-state! :editor/block-refs #{})
|
|
(set-state! :editor/action-data nil)
|
|
(set-state! :view/selected-blocks nil))
|
|
|
|
(defn into-code-editor-mode!
|
|
[]
|
|
(set-state! :editor/cursor-range nil)
|
|
(swap! state assoc :editor/code-mode? true))
|
|
|
|
(defn set-editor-last-pos!
|
|
[new-pos]
|
|
(update-state! :editor/last-saved-cursor
|
|
(fn [m] (assoc m (:block/uuid (get-edit-block)) new-pos))))
|
|
|
|
(defn get-editor-last-pos
|
|
[]
|
|
(get @(:editor/last-saved-cursor @state) (:block/uuid (get-edit-block))))
|
|
|
|
(defn set-block-content-and-last-pos!
|
|
[edit-input-id content new-pos]
|
|
(when edit-input-id
|
|
(set-edit-content! edit-input-id content)
|
|
(set-editor-last-pos! new-pos)))
|
|
|
|
(defn set-theme-mode!
|
|
[mode]
|
|
(when (mobile-util/native-platform?)
|
|
(if (= mode "light")
|
|
(util/set-theme-light)
|
|
(util/set-theme-dark)))
|
|
(set-state! :ui/theme mode)
|
|
(storage/set :ui/theme mode))
|
|
|
|
(defn sync-system-theme!
|
|
[]
|
|
(let [system-dark? (.-matches (js/window.matchMedia "(prefers-color-scheme: dark)"))]
|
|
(set-theme-mode! (if system-dark? "dark" "light"))
|
|
(set-state! :ui/system-theme? true)
|
|
(storage/set :ui/system-theme? true)))
|
|
|
|
(defn use-theme-mode!
|
|
[theme-mode]
|
|
(if (= theme-mode "system")
|
|
(sync-system-theme!)
|
|
(do
|
|
(set-theme-mode! theme-mode)
|
|
(set-state! :ui/system-theme? false)
|
|
(storage/set :ui/system-theme? false))))
|
|
|
|
(defn- toggle-theme
|
|
[theme]
|
|
(if (= theme "dark") "light" "dark"))
|
|
|
|
(defn toggle-theme!
|
|
[]
|
|
(use-theme-mode! (toggle-theme (:ui/theme @state))))
|
|
|
|
(defn set-custom-theme!
|
|
([custom-theme]
|
|
(set-custom-theme! nil custom-theme))
|
|
([mode theme]
|
|
(set-state! (if mode [:ui/custom-theme (keyword mode)] :ui/custom-theme) theme)
|
|
(storage/set :ui/custom-theme (:ui/custom-theme @state))))
|
|
|
|
(defn restore-mobile-theme!
|
|
"Restore mobile theme setting from local storage"
|
|
[]
|
|
(let [mode (or (storage/get :ui/theme) "light")
|
|
system-theme? (storage/get :ui/system-theme?)]
|
|
(when (and (not system-theme?)
|
|
(mobile-util/native-platform?))
|
|
(if (= mode "light")
|
|
(util/set-theme-light)
|
|
(util/set-theme-dark)))))
|
|
|
|
(defn set-root-component!
|
|
[component]
|
|
(set-state! :ui/root-component component))
|
|
|
|
(defn get-root-component
|
|
[]
|
|
(get @state :ui/root-component))
|
|
|
|
(defn load-app-user-cfgs
|
|
([] (load-app-user-cfgs false))
|
|
([refresh?]
|
|
(when (util/electron?)
|
|
(p/let [cfgs (if (or refresh? (nil? (:electron/user-cfgs @state)))
|
|
(ipc/ipc :userAppCfgs)
|
|
(:electron/user-cfgs @state))
|
|
cfgs (if (object? cfgs) (bean/->clj cfgs) cfgs)]
|
|
(set-state! :electron/user-cfgs cfgs)))))
|
|
|
|
(defn setup-electron-updater!
|
|
[]
|
|
(when (util/electron?)
|
|
(js/window.apis.setUpdatesCallback
|
|
(fn [_ args]
|
|
(let [data (bean/->clj args)
|
|
pending? (not= (:type data) "completed")]
|
|
(set-state! :electron/updater-pending? pending?)
|
|
(when pending? (set-state! :electron/updater data))
|
|
nil)))))
|
|
|
|
(defn set-file-component!
|
|
[component]
|
|
(set-state! :ui/file-component component))
|
|
|
|
(defn clear-file-component!
|
|
[]
|
|
(set-state! :ui/file-component nil))
|
|
|
|
(defn save-scroll-position!
|
|
([value]
|
|
(save-scroll-position! value js/window.location.hash))
|
|
([value path]
|
|
(set-state! :ui/paths-scroll-positions value :path-in-sub-atom path)))
|
|
|
|
(defn save-main-container-position!
|
|
[value]
|
|
(when (not= value @(:ui/main-container-scroll-top @state))
|
|
(set-state! :ui/main-container-scroll-top value)))
|
|
|
|
(defn get-saved-scroll-position
|
|
([]
|
|
(get-saved-scroll-position js/window.location.hash))
|
|
([path]
|
|
(get @(get @state :ui/paths-scroll-positions) path 0)))
|
|
|
|
(defn set-today!
|
|
[value]
|
|
(set-state! :today value))
|
|
|
|
(defn get-me
|
|
[]
|
|
(:me @state))
|
|
|
|
(defn set-db-restoring!
|
|
[value]
|
|
(set-state! :db/restoring? value))
|
|
|
|
(defn set-indexedb-support!
|
|
[value]
|
|
(set-state! :indexeddb/support? value))
|
|
|
|
(defn modal-opened?
|
|
[]
|
|
(shui-dialog/has-modal?))
|
|
|
|
(defn close-modal! []
|
|
(shui/dialog-close!))
|
|
|
|
(defn get-reactive-custom-queries-chan
|
|
[]
|
|
(:reactive/custom-queries @state))
|
|
|
|
(defn get-left-sidebar-open?
|
|
[]
|
|
(get-in @state [:ui/left-sidebar-open?]))
|
|
|
|
(defn set-left-sidebar-open!
|
|
[value]
|
|
(storage/set "ls-left-sidebar-open?" (boolean value))
|
|
(set-state! :ui/left-sidebar-open? value))
|
|
|
|
(defn toggle-left-sidebar!
|
|
[]
|
|
(set-left-sidebar-open!
|
|
(not (get-left-sidebar-open?))))
|
|
|
|
(defn set-developer-mode!
|
|
[value]
|
|
(set-state! :ui/developer-mode? value)
|
|
(storage/set "developer-mode" (str value)))
|
|
|
|
(defn developer-mode?
|
|
[]
|
|
(:ui/developer-mode? @state))
|
|
|
|
(defn get-notification-contents
|
|
[]
|
|
(get @state :notification/contents))
|
|
|
|
(defn document-mode?
|
|
[]
|
|
(get @state :document/mode?))
|
|
|
|
(defn toggle-document-mode!
|
|
[]
|
|
(let [mode (document-mode?)]
|
|
(set-state! :document/mode? (not mode))
|
|
(storage/set :document/mode? (not mode))))
|
|
|
|
(defn toggle-highlight-recent-blocks!
|
|
[]
|
|
(let [value @(:ui/toggle-highlight-recent-blocks? @state)]
|
|
(set-state! :ui/toggle-highlight-recent-blocks? (not value))))
|
|
|
|
(defn shortcut-tooltip-enabled?
|
|
[]
|
|
(get @state :ui/shortcut-tooltip?))
|
|
|
|
(defn toggle-shortcut-tooltip!
|
|
[]
|
|
(let [mode (shortcut-tooltip-enabled?)]
|
|
(set-state! :ui/shortcut-tooltip? (not mode))
|
|
(storage/set :ui/shortcut-tooltip? (not mode))))
|
|
|
|
(defn set-config!
|
|
[repo-url value]
|
|
(when value (set-state! [:config repo-url] value)))
|
|
|
|
(defn set-global-config!
|
|
[value str-content]
|
|
;; Placed under :config so cursors can work seamlessly
|
|
(when value
|
|
(set-config! ::global-config value)
|
|
(set-config! ::global-config-str-content str-content)))
|
|
|
|
(defn get-wide-mode?
|
|
[]
|
|
(:ui/wide-mode? @state))
|
|
|
|
(defn toggle-wide-mode!
|
|
[]
|
|
(update-state! :ui/wide-mode? not))
|
|
|
|
(defn set-online!
|
|
[value]
|
|
(set-state! :network/online? value)
|
|
;; to avoid watch whole big state atom,
|
|
;; there's an atom flows/*network-online?,
|
|
;; then we can use flows/network-online-event-flow
|
|
(reset! flows/*network-online? value))
|
|
|
|
(defn get-plugins-slash-commands
|
|
[]
|
|
(mapcat seq (flatten (vals (:plugin/installed-slash-commands @state)))))
|
|
|
|
(defn get-plugins-commands-with-type
|
|
[type]
|
|
(->> (apply concat (vals (:plugin/simple-commands @state)))
|
|
(filterv #(= (keyword (first %)) (keyword type)))))
|
|
|
|
(defn get-plugins-ui-items-with-type
|
|
[type]
|
|
(->> (apply concat (vals (:plugin/installed-ui-items @state)))
|
|
(filterv #(= (keyword (first %)) (keyword type)))))
|
|
|
|
(defn get-plugin-resources-with-type
|
|
[pid type]
|
|
(when-let [pid (and type (keyword pid))]
|
|
(get-in @state [:plugin/installed-resources pid (keyword type)])))
|
|
|
|
(defn get-plugin-resource
|
|
[pid type key]
|
|
(when-let [resources (get-plugin-resources-with-type pid type)]
|
|
(get resources key)))
|
|
|
|
(defn upt-plugin-resource
|
|
[pid type key attr val]
|
|
(when-let [resource (get-plugin-resource pid type key)]
|
|
(let [resource (assoc resource (keyword attr) val)]
|
|
(set-state!
|
|
[:plugin/installed-resources (keyword pid) (keyword type) key] resource)
|
|
resource)))
|
|
|
|
(defn get-plugin-services
|
|
[pid type]
|
|
(when-let [installed (and pid (:plugin/installed-services @state))]
|
|
(some->> (seq (get installed (keyword pid)))
|
|
(filterv #(= type (:type %))))))
|
|
|
|
(defn install-plugin-service
|
|
([pid type name] (install-plugin-service pid type name nil))
|
|
([pid type name opts]
|
|
(when-let [pid (and pid type name (keyword pid))]
|
|
(let [exists (get-plugin-services pid type)]
|
|
(when-let [service (and (or (not exists) (not (some #(= name (:name %)) exists)))
|
|
{:pid pid :type type :name name :opts opts})]
|
|
(update-state! [:plugin/installed-services pid] #(conj (vec %) service))
|
|
|
|
;; search engines state for results
|
|
(when (= type :search)
|
|
(set-state! [:search/engines (str pid name)] service)))))))
|
|
|
|
(defn uninstall-plugin-service
|
|
[pid type-or-all]
|
|
(when-let [pid (keyword pid)]
|
|
(when-let [installed (get (:plugin/installed-services @state) pid)]
|
|
(let [remove-all? (or (true? type-or-all) (nil? type-or-all))
|
|
remains (if remove-all? nil (filterv #(not= type-or-all (:type %)) installed))
|
|
removed (if remove-all? installed (filterv #(= type-or-all (:type %)) installed))]
|
|
(set-state! [:plugin/installed-services pid] remains)
|
|
|
|
;; search engines state for results
|
|
(when-let [removed' (seq (filter #(= :search (:type %)) removed))]
|
|
(update-state! :search/engines #(apply dissoc % (mapv (fn [{:keys [pid name]}] (str pid name)) removed'))))))))
|
|
|
|
(defn get-all-plugin-services-with-type
|
|
[type]
|
|
(when-let [installed (vals (:plugin/installed-services @state))]
|
|
(mapcat (fn [s] (filter #(= (keyword type) (:type %)) s)) installed)))
|
|
|
|
(defn get-all-plugin-search-engines
|
|
[]
|
|
(:search/engines @state))
|
|
|
|
(defn update-plugin-search-engine
|
|
[pid name f]
|
|
(when-let [pid (keyword pid)]
|
|
(set-state! :search/engines
|
|
(update-vals (get-all-plugin-search-engines)
|
|
#(if (and (= pid (:pid %)) (= name (:name %)))
|
|
(f %) %)))))
|
|
|
|
(defn reset-plugin-search-engines
|
|
[]
|
|
(when-let [engines (get-all-plugin-search-engines)]
|
|
(set-state! :search/engines
|
|
(update-vals engines #(assoc % :result nil)))))
|
|
|
|
(defn install-plugin-hook
|
|
([pid hook] (install-plugin-hook pid hook true))
|
|
([pid hook opts]
|
|
(when-let [pid (keyword pid)]
|
|
(set-state!
|
|
[:plugin/installed-hooks hook]
|
|
(assoc
|
|
((fnil identity {}) (get-in @state [:plugin/installed-hooks hook]))
|
|
pid opts)) true)))
|
|
|
|
(defn uninstall-plugin-hook
|
|
[pid hook-or-all]
|
|
(when-let [pid (keyword pid)]
|
|
(if (nil? hook-or-all)
|
|
(swap! state update :plugin/installed-hooks #(update-vals % (fn [ids] (dissoc ids pid))))
|
|
(when-let [coll (get-in @state [:plugin/installed-hooks hook-or-all])]
|
|
(set-state! [:plugin/installed-hooks hook-or-all] (dissoc coll pid))))
|
|
true))
|
|
|
|
(defn slot-hook-exist?
|
|
[uuid]
|
|
(when-let [type (and uuid (string/replace (str uuid) "-" "_"))]
|
|
(when-let [hooks (sub :plugin/installed-hooks)]
|
|
(contains? hooks (str "hook:editor:slot_" type)))))
|
|
|
|
(defn active-tldraw-app
|
|
[]
|
|
(when-let [tldraw-el (.querySelector js/document.body ".logseq-tldraw[data-tlapp]")]
|
|
(gobj/get js/window.tlapps (.. tldraw-el -dataset -tlapp))))
|
|
|
|
(defn tldraw-editing-logseq-block?
|
|
[]
|
|
(when-let [app (active-tldraw-app)]
|
|
(and (= 1 (.. app -selectedShapesArray -length))
|
|
(= (.. app -editingShape) (.. app -selectedShapesArray (at 0))))))
|
|
|
|
(defn set-graph-syncing?
|
|
[value]
|
|
(set-state! :graph/syncing? value))
|
|
|
|
(defn set-editor-in-composition!
|
|
[value]
|
|
(set-state! :editor/in-composition? value))
|
|
|
|
(defn editor-in-composition?
|
|
[]
|
|
(:editor/in-composition? @state))
|
|
|
|
(defn set-loading-files!
|
|
[repo value]
|
|
(when repo
|
|
(set-state! [:repo/loading-files? repo] value)))
|
|
|
|
(defn loading-files?
|
|
[repo]
|
|
(get-in @state [:repo/loading-files? repo]))
|
|
|
|
(defn set-editor-last-input-time!
|
|
[repo time]
|
|
(set-state! :editor/last-input-time time :path-in-sub-atom repo))
|
|
|
|
(defn input-idle?
|
|
[repo & {:keys [diff]
|
|
:or {diff 1000}}]
|
|
(when repo
|
|
(let [last-input-time (get @(get @state :editor/last-input-time) repo)]
|
|
(or
|
|
(nil? last-input-time)
|
|
|
|
(let [now (util/time-ms)]
|
|
(>= (- now last-input-time) diff))
|
|
|
|
;; not in editing mode
|
|
;; Is this a good idea to put whiteboard check here?
|
|
(not (get-edit-input-id))))))
|
|
|
|
(defn set-nfs-refreshing!
|
|
[value]
|
|
(set-state! :nfs/refreshing? value))
|
|
|
|
(defn nfs-refreshing?
|
|
[]
|
|
(:nfs/refreshing? @state))
|
|
|
|
(defn set-search-result!
|
|
[value]
|
|
(set-state! :search/result value))
|
|
|
|
(defn clear-search-result!
|
|
[]
|
|
(set-search-result! nil))
|
|
|
|
(defn add-graph-search-filter!
|
|
[q]
|
|
(when-not (string/blank? q)
|
|
(update-state! :search/graph-filters
|
|
(fn [value]
|
|
(vec (distinct (conj value q)))))))
|
|
|
|
(defn remove-search-filter!
|
|
[q]
|
|
(when-not (string/blank? q)
|
|
(update-state! :search/graph-filters
|
|
(fn [value]
|
|
(remove #{q} value)))))
|
|
|
|
(defn clear-search-filters!
|
|
[]
|
|
(set-state! :search/graph-filters []))
|
|
|
|
(defn get-search-mode
|
|
[]
|
|
(:search/mode @state))
|
|
|
|
(defn toggle!
|
|
[path]
|
|
(update-state! path not))
|
|
|
|
(defn toggle-settings!
|
|
[]
|
|
(toggle! :ui/settings-open?))
|
|
|
|
(defn settings-open?
|
|
[]
|
|
(:ui/settings-open? @state))
|
|
|
|
(defn close-settings!
|
|
[]
|
|
(set-state! :ui/settings-open? false))
|
|
|
|
(defn open-settings!
|
|
([] (open-settings! true))
|
|
([active-tab] (set-state! :ui/settings-open? active-tab)))
|
|
|
|
(defn sidebar-add-block!
|
|
[repo db-id block-type]
|
|
(when (not (util/sm-breakpoint?))
|
|
(let [page (and (sqlite-util/db-based-graph? repo)
|
|
(= :page block-type)
|
|
(some-> (db-conn-state/get-conn repo) deref (d/entity db-id)))]
|
|
(if (and page
|
|
;; TODO: Use config/dev? when it's not a circular dep
|
|
(not goog.DEBUG)
|
|
(or (and (ldb/hidden? page) (not (ldb/property? page)))
|
|
(and (ldb/built-in? page) (ldb/private-built-in-page? page))))
|
|
(pub-event! [:notification/show {:content "Cannot open an internal page." :status :warning}])
|
|
(when db-id
|
|
(update-state! :sidebar/blocks (fn [blocks]
|
|
(->> (remove #(= (second %) db-id) blocks)
|
|
(cons [repo db-id block-type])
|
|
(distinct))))
|
|
(set-state! [:ui/sidebar-collapsed-blocks db-id] false)
|
|
(open-right-sidebar!)
|
|
(when-let [elem (gdom/getElementByClass "sidebar-item-list")]
|
|
(util/scroll-to elem 0)))))))
|
|
|
|
(defn get-export-block-text-indent-style []
|
|
(:copy/export-block-text-indent-style @state))
|
|
|
|
(defn set-export-block-text-indent-style!
|
|
[v]
|
|
(set-state! :copy/export-block-text-indent-style v)
|
|
(storage/set :copy/export-block-text-indent-style v))
|
|
|
|
(defn get-recent-pages
|
|
[]
|
|
(get-in @state [:ui/recent-pages (get-current-repo)]))
|
|
|
|
(defn set-recent-pages!
|
|
[v]
|
|
(set-state! [:ui/recent-pages (get-current-repo)] v)
|
|
(storage/set :ui/recent-pages (:ui/recent-pages @state)))
|
|
|
|
(defn get-export-block-text-remove-options []
|
|
(:copy/export-block-text-remove-options @state))
|
|
|
|
(defn update-export-block-text-remove-options!
|
|
[e k]
|
|
(let [f (if (util/echecked? e) conj disj)]
|
|
(update-state! :copy/export-block-text-remove-options
|
|
#(f % k))
|
|
(storage/set :copy/export-block-text-remove-options
|
|
(get-export-block-text-remove-options))))
|
|
|
|
(defn get-export-block-text-other-options []
|
|
(:copy/export-block-text-other-options @state))
|
|
|
|
(defn update-export-block-text-other-options!
|
|
[k v]
|
|
(update-state! :copy/export-block-text-other-options #(assoc % k v)))
|
|
|
|
(defn set-editor-args!
|
|
[args]
|
|
(set-state! :editor/args args))
|
|
|
|
(defn editing-whiteboard-portal?
|
|
[]
|
|
(and (active-tldraw-app) (tldraw-editing-logseq-block?)))
|
|
|
|
(defn block-component-editing?
|
|
[]
|
|
(and (:block/component-editing-mode? @state)
|
|
(not (editing-whiteboard-portal?))))
|
|
|
|
(defn set-block-component-editing-mode!
|
|
[value]
|
|
(set-state! :block/component-editing-mode? value))
|
|
|
|
(defn get-editor-args
|
|
[]
|
|
@(:editor/args @state))
|
|
|
|
(defn get-editor-block-container
|
|
[]
|
|
(some-> (get-edit-input-id)
|
|
(gdom/getElement)
|
|
(util/rec-get-node "ls-block")))
|
|
|
|
(defn set-page-blocks-cp!
|
|
[value]
|
|
(set-state! [:view/components :page-blocks] value))
|
|
|
|
(defn get-page-blocks-cp
|
|
[]
|
|
(get-in @state [:view/components :page-blocks]))
|
|
|
|
;; To avoid circular dependencies
|
|
(defn set-component!
|
|
[k value]
|
|
(set-state! [:view/components k] value))
|
|
|
|
(defn get-component
|
|
[k]
|
|
(get-in @state [:view/components k]))
|
|
|
|
(defn exit-editing-and-set-selected-blocks!
|
|
([blocks]
|
|
(exit-editing-and-set-selected-blocks! blocks nil))
|
|
([blocks direction]
|
|
(clear-edit!)
|
|
(set-selection-blocks! blocks direction)))
|
|
|
|
(defn set-editing!
|
|
[edit-input-id content block cursor-range & {:keys [db move-cursor? container-id property-block direction event pos]
|
|
:or {move-cursor? true}}]
|
|
(when-not (exists? js/process)
|
|
(when (and edit-input-id block
|
|
(or
|
|
(publishing-enable-editing?)
|
|
(not common-config/PUBLISHING)))
|
|
(let [block-element (gdom/getElement (string/replace edit-input-id "edit-block" "ls-block"))
|
|
container (util/get-block-container block-element)
|
|
block (if container
|
|
(assoc block
|
|
:block.temp/container (gobj/get container "id"))
|
|
block)
|
|
block (assoc block :block.editing/direction direction
|
|
:block.editing/event event
|
|
:block.editing/pos pos)
|
|
content (string/trim (or content ""))]
|
|
(assert (and container-id (:block/uuid block))
|
|
"container-id or block uuid is missing")
|
|
(set-state! :editor/block-refs #{})
|
|
(set-state! :editor/block block)
|
|
(if property-block
|
|
(set-editing-block-id! [container-id (:block/uuid property-block) (:block/uuid block)])
|
|
(set-editing-block-id! [container-id (:block/uuid block)]))
|
|
(set-state! :editor/container-id container-id)
|
|
(set-state! :editor/content content :path-in-sub-atom (:block/uuid block))
|
|
(set-state! :editor/last-key-code nil)
|
|
(set-state! :editor/set-timestamp-block nil)
|
|
(set-state! :editor/cursor-range cursor-range)
|
|
(when (= :code (:logseq.property.node/display-type (d/entity db (:db/id block))))
|
|
(pub-event! [:editor/focus-code-editor block block-element]))
|
|
(when-let [input (gdom/getElement edit-input-id)]
|
|
(let [pos (count cursor-range)]
|
|
(when content
|
|
(util/set-change-value input content))
|
|
|
|
(when (and move-cursor? (not (block-component-editing?)))
|
|
(cursor/move-cursor-to input pos))
|
|
|
|
(when (or (util/mobile?) (mobile-util/native-platform?))
|
|
(set-state! :mobile/show-action-bar? false))))))))
|
|
|
|
(defn action-bar-open?
|
|
[]
|
|
(:mobile/show-action-bar? @state))
|
|
|
|
(defn get-git-auto-commit-enabled?
|
|
[]
|
|
(false? (sub [:electron/user-cfgs :git/disable-auto-commit?])))
|
|
|
|
(defn get-git-commit-on-close-enabled?
|
|
[]
|
|
(sub [:electron/user-cfgs :git/commit-on-close?]))
|
|
|
|
(defn set-last-key-code!
|
|
[key-code]
|
|
(set-state! :editor/last-key-code key-code))
|
|
|
|
(defn get-last-key-code
|
|
[]
|
|
@(:editor/last-key-code @state))
|
|
|
|
(defn set-ui-last-key-code!
|
|
[key-code]
|
|
(set-state! :ui/global-last-key-code key-code))
|
|
|
|
(defn get-ui-last-key-code
|
|
[]
|
|
@(:ui/global-last-key-code @state))
|
|
|
|
(defn set-block-op-type!
|
|
[op-type]
|
|
(set-state! :editor/block-op-type op-type))
|
|
|
|
(defn get-block-op-type
|
|
[]
|
|
(:editor/block-op-type @state))
|
|
|
|
(defn feature-http-server-enabled?
|
|
[]
|
|
(boolean (storage/get ::storage-spec/http-server-enabled)))
|
|
|
|
(defn get-plugin-by-id
|
|
[id]
|
|
(when-let [id (and id (keyword id))]
|
|
(get-in @state [:plugin/installed-plugins id])))
|
|
|
|
(defn get-enabled?-installed-plugins
|
|
([theme?] (get-enabled?-installed-plugins theme? true false false))
|
|
([theme? enabled? include-unpacked? include-all?]
|
|
(filterv
|
|
#(and (if include-unpacked? true (or (:webMode %) (:iir %)))
|
|
(if-not (boolean? enabled?) true (= (not enabled?) (boolean (get-in % [:settings :disabled]))))
|
|
(or include-all? (if (boolean? theme?) (= (boolean theme?) (:theme %)) true)))
|
|
(vals (:plugin/installed-plugins @state)))))
|
|
|
|
(defn lsp-enabled?-or-theme
|
|
[]
|
|
(:plugin/enabled @state))
|
|
|
|
(def lsp-enabled?
|
|
(lsp-enabled?-or-theme))
|
|
|
|
(defn consume-updates-from-coming-plugin!
|
|
[payload updated?]
|
|
(when-let [id (keyword (:id payload))]
|
|
(let [prev-pending? (boolean (seq (:plugin/updates-pending @state)))]
|
|
(println "Updates: consumed pending - " id)
|
|
(swap! state update :plugin/updates-pending dissoc id)
|
|
(if updated?
|
|
(if-let [error (:error-code payload)]
|
|
(swap! state update-in [:plugin/updates-coming id] assoc :error-code error)
|
|
(swap! state update :plugin/updates-coming dissoc id))
|
|
(swap! state update :plugin/updates-coming assoc id payload))
|
|
(pub-event! [:plugin/consume-updates id prev-pending? updated?]))))
|
|
|
|
(defn coming-update-new-version?
|
|
[pkg]
|
|
(and pkg (:latest-version pkg)))
|
|
|
|
(defn plugin-update-available?
|
|
[id]
|
|
(when-let [pkg (and id (get (:plugin/updates-coming @state) (keyword id)))]
|
|
(coming-update-new-version? pkg)))
|
|
|
|
(defn all-available-coming-updates
|
|
([] (all-available-coming-updates (:plugin/updates-coming @state)))
|
|
([updates] (when-let [updates (vals updates)]
|
|
(filterv #(coming-update-new-version? %) updates))))
|
|
|
|
(defn get-next-selected-coming-update
|
|
[]
|
|
(when-let [updates (all-available-coming-updates)]
|
|
(let [unchecked (:plugin/updates-unchecked @state)]
|
|
(first (filter #(and (not (and (seq unchecked) (contains? unchecked (:id %))))
|
|
(not (:error-code %))) updates)))))
|
|
|
|
(defn set-unchecked-update
|
|
[id unchecked?]
|
|
(swap! state update :plugin/updates-unchecked (if unchecked? conj disj) id))
|
|
|
|
(defn reset-unchecked-update
|
|
[]
|
|
(swap! state assoc :plugin/updates-unchecked #{}))
|
|
|
|
(defn reset-all-updates-state
|
|
[]
|
|
(swap! state assoc
|
|
:plugin/updates-auto-checking? false
|
|
:plugin/updates-pending {}
|
|
:plugin/updates-coming {}
|
|
:plugin/updates-downloading? false))
|
|
|
|
(defn sub-right-sidebar-blocks
|
|
[]
|
|
(when-let [current-repo (get-current-repo)]
|
|
(->> (sub :sidebar/blocks)
|
|
(filter #(= (first %) current-repo)))))
|
|
|
|
(defn toggle-collapsed-block!
|
|
[block-id]
|
|
(let [current-repo (get-current-repo)]
|
|
(update-state! [:ui/collapsed-blocks current-repo block-id] not)))
|
|
|
|
(defn set-collapsed-block!
|
|
[block-id value]
|
|
(let [current-repo (get-current-repo)]
|
|
(set-state! [:ui/collapsed-blocks current-repo block-id] value)))
|
|
|
|
(defn sub-block-collapsed
|
|
[block-id]
|
|
(sub [:ui/collapsed-blocks (get-current-repo) block-id]))
|
|
|
|
(defn get-block-collapsed
|
|
[block-id]
|
|
(get-in @state [:ui/collapsed-blocks (get-current-repo) block-id]))
|
|
|
|
(defn get-modal-id
|
|
[]
|
|
(shui-dialog/get-last-modal-id))
|
|
|
|
(defn set-auth-id-token
|
|
[id-token]
|
|
(set-state! :auth/id-token id-token))
|
|
|
|
(defn set-auth-refresh-token
|
|
[refresh-token]
|
|
(set-state! :auth/refresh-token refresh-token))
|
|
|
|
(defn set-auth-access-token
|
|
[access-token]
|
|
(set-state! :auth/access-token access-token))
|
|
|
|
(defn get-auth-id-token []
|
|
(sub :auth/id-token))
|
|
|
|
(defn get-auth-refresh-token []
|
|
(:auth/refresh-token @state))
|
|
|
|
(defn set-file-sync-manager [graph-uuid v]
|
|
(when (and graph-uuid v)
|
|
(set-state! [:file-sync/graph-state graph-uuid :file-sync/sync-manager] v)))
|
|
|
|
(defn get-file-sync-manager [graph-uuid]
|
|
(get-in @state [:file-sync/graph-state graph-uuid :file-sync/sync-manager]))
|
|
|
|
(defn clear-file-sync-state! [graph-uuid]
|
|
(set-state! [:file-sync/graph-state graph-uuid] nil))
|
|
|
|
(defn clear-file-sync-progress! [graph-uuid]
|
|
(set-state! [:file-sync/graph-state
|
|
graph-uuid
|
|
:file-sync/progress]
|
|
nil))
|
|
|
|
(defn set-file-sync-state [graph-uuid v]
|
|
(when v (s/assert :frontend.fs.sync/sync-state v))
|
|
(set-state! [:file-sync/graph-state graph-uuid :file-sync/sync-state] v))
|
|
|
|
(defn get-current-file-sync-graph-uuid
|
|
[]
|
|
(get-in @state [:file-sync/graph-state :current-graph-uuid]))
|
|
|
|
(defn sub-current-file-sync-graph-uuid
|
|
[]
|
|
(sub [:file-sync/graph-state :current-graph-uuid]))
|
|
|
|
(defn get-file-sync-state
|
|
([]
|
|
(get-file-sync-state (get-current-file-sync-graph-uuid)))
|
|
([graph-uuid]
|
|
(get-in @state [:file-sync/graph-state graph-uuid :file-sync/sync-state])))
|
|
|
|
(defn sub-file-sync-state
|
|
[graph-uuid]
|
|
(sub [:file-sync/graph-state graph-uuid :file-sync/sync-state]))
|
|
|
|
(defn reset-parsing-state!
|
|
[]
|
|
(set-state! [:graph/parsing-state (get-current-repo)] {}))
|
|
|
|
(defn set-parsing-state!
|
|
[m]
|
|
(update-state! [:graph/parsing-state (get-current-repo)]
|
|
(if (fn? m) m
|
|
(fn [old-value] (merge old-value m)))))
|
|
|
|
(defn http-proxy-enabled-or-val? []
|
|
(when-let [{:keys [type protocol host port] :as agent-opts} (sub [:electron/user-cfgs :settings/agent])]
|
|
(when (and (not (contains? #{"system"} type))
|
|
(every? not-empty (vals agent-opts)))
|
|
(str protocol "://" host ":" port))))
|
|
|
|
(defn get-sync-graph-by-id
|
|
[graph-uuid]
|
|
(when graph-uuid
|
|
(let [graph (first (filter #(= graph-uuid (:GraphUUID %))
|
|
(get-repos)))]
|
|
(when (:url graph)
|
|
graph))))
|
|
|
|
(defn unlinked-dir?
|
|
[dir]
|
|
(contains? (:file/unlinked-dirs @state) dir))
|
|
|
|
(defn get-file-rename-event-chan
|
|
[]
|
|
(:file/rename-event-chan @state))
|
|
|
|
(defn offer-file-rename-event-chan!
|
|
[v]
|
|
{:pre [(map? v)
|
|
(= #{:repo :old-path :new-path} (set (keys v)))]}
|
|
(async/offer! (get-file-rename-event-chan) v))
|
|
|
|
(defn set-onboarding-whiteboard!
|
|
[v]
|
|
(set-state! :whiteboard/onboarding-whiteboard? v)
|
|
(storage/set :ls-onboarding-whiteboard? v))
|
|
|
|
(defn get-onboarding-whiteboard?
|
|
[]
|
|
(get-in @state [:whiteboard/onboarding-whiteboard?]))
|
|
|
|
(defn get-local-container-root-url
|
|
[]
|
|
(when (mobile-util/native-ios?)
|
|
(get-in @state [:mobile/container-urls :localContainerUrl])))
|
|
|
|
(defn get-icloud-container-root-url
|
|
[]
|
|
(when (mobile-util/native-ios?)
|
|
(get-in @state [:mobile/container-urls :iCloudContainerUrl])))
|
|
|
|
(defn get-current-pdf
|
|
[]
|
|
(:pdf/current @state))
|
|
|
|
(defn set-current-pdf!
|
|
[inflated-file]
|
|
(let [settle-file! #(set-state! :pdf/current inflated-file)]
|
|
(if-not (get-current-pdf)
|
|
(settle-file!)
|
|
(when (apply not= (map :identity [inflated-file (get-current-pdf)]))
|
|
(set-state! :pdf/current nil)
|
|
(js/setTimeout #(settle-file!) 16)))))
|
|
|
|
(defn focus-whiteboard-shape
|
|
([shape-id]
|
|
(focus-whiteboard-shape (active-tldraw-app) shape-id))
|
|
([tln shape-id]
|
|
(when-let [^js api (gobj/get tln "api")]
|
|
(when (and shape-id (parse-uuid shape-id))
|
|
(. api selectShapes shape-id)
|
|
(. api zoomToSelection)))))
|
|
|
|
(defn set-user-info!
|
|
[info]
|
|
(when info
|
|
(set-state! :user/info info)
|
|
(let [groups (:UserGroups info)]
|
|
(when (seq groups)
|
|
(storage/set :user-groups groups)))))
|
|
|
|
(defn get-user-info []
|
|
(sub :user/info))
|
|
|
|
(defn clear-user-info!
|
|
[]
|
|
(storage/remove :user-groups))
|
|
|
|
(defn set-color-accent! [color]
|
|
(swap! state assoc :ui/radix-color color)
|
|
(storage/set :ui/radix-color color)
|
|
(util/set-android-theme))
|
|
|
|
(defn set-editor-font! [font]
|
|
(let [font (if (keyword? font) (name font) (str font))]
|
|
(swap! state assoc :ui/editor-font font)
|
|
(storage/set :ui/editor-font font)))
|
|
|
|
(defn handbook-open?
|
|
[]
|
|
(:ui/handbooks-open? @state))
|
|
|
|
(defn get-handbook-route-chan
|
|
[]
|
|
(:handbook/route-chan @state))
|
|
|
|
(defn open-handbook-pane!
|
|
[k]
|
|
(when-not (handbook-open?)
|
|
(set-state! :ui/handbooks-open? true))
|
|
(js/setTimeout #(async/go
|
|
(>! (get-handbook-route-chan) k))))
|
|
|
|
(defn update-favorites-updated!
|
|
[]
|
|
(update-state! :favorites/updated? inc))
|
|
|
|
(def get-worker-next-request-id db-transact/get-next-request-id)
|
|
(def add-worker-request! db-transact/add-request!)
|
|
|
|
(defn get-next-container-id
|
|
[]
|
|
(swap! (:ui/container-id @state) inc))
|
|
|
|
(defn get-container-id
|
|
"Either cached container-id or a new id"
|
|
[key]
|
|
(if (seq key)
|
|
(or (get @(:ui/cached-key->container-id @state) key)
|
|
(let [id (get-next-container-id)]
|
|
(swap! (:ui/cached-key->container-id @state) assoc key id)
|
|
id))
|
|
(get-next-container-id)))
|
|
|
|
(defn get-current-editor-container-id
|
|
[]
|
|
@(:editor/container-id @state))
|
|
|
|
(comment
|
|
(defn remove-container-key!
|
|
[key]
|
|
(swap! (:ui/cached-key->container-id @state) dissoc key)))
|
|
|
|
(defn get-editor-info
|
|
[]
|
|
(when-let [edit-block (get-edit-block)]
|
|
{:block-uuid (:block/uuid edit-block)
|
|
:container-id (or @(:editor/container-id @state) :unknown-container)
|
|
:start-pos @(:editor/start-pos @state)
|
|
:end-pos (get-edit-pos)}))
|
|
|
|
(defn conj-block-ref!
|
|
[ref-entity]
|
|
(let [refs! (:editor/block-refs @state)]
|
|
(swap! refs! conj ref-entity)))
|
|
|
|
(defn get-highlight-recent-days
|
|
[]
|
|
@(:ui/highlight-recent-days @state))
|
|
|
|
(defn set-highlight-recent-days!
|
|
[days]
|
|
(reset! (:ui/highlight-recent-days @state) days)
|
|
(storage/set :ui/highlight-recent-days days))
|
|
|
|
(defn set-rtc-enabled!
|
|
[value]
|
|
(storage/set :logseq-rtc-enabled value)
|
|
(set-state! :feature/enable-rtc? value))
|