Files
logseq/src/main/frontend/handler/page.cljs
2024-01-10 23:52:47 +08:00

350 lines
15 KiB
Clojure

(ns frontend.handler.page
"Provides util handler fns for pages"
(:require [cljs.reader :as reader]
[clojure.string :as string]
[frontend.commands :as commands]
[frontend.config :as config]
[frontend.date :as date]
[frontend.db :as db]
[frontend.db.async :as db-async]
[frontend.db.model :as model]
[frontend.fs :as fs]
[frontend.handler.common :as common-handler]
[frontend.handler.common.page :as page-common-handler]
[frontend.handler.config :as config-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.plugin :as plugin-handler]
[frontend.handler.notification :as notification]
[frontend.handler.property :as property-handler]
[frontend.handler.ui :as ui-handler]
[frontend.handler.web.nfs :as web-nfs]
[frontend.worker.handler.page.rename :as worker-page-rename]
[frontend.mobile.util :as mobile-util]
[frontend.state :as state]
[frontend.util :as util]
[frontend.util.cursor :as cursor]
[frontend.util.page :as page-util]
[frontend.util.url :as url-util]
[goog.functions :refer [debounce]]
[goog.object :as gobj]
[lambdaisland.glogi :as log]
[logseq.db.frontend.property :as db-property]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
[logseq.common.util.page-ref :as page-ref]
[promesa.core :as p]
[logseq.common.path :as path]
[frontend.handler.property.util :as pu]
[electron.ipc :as ipc]
[frontend.context.i18n :refer [t]]))
(def create! page-common-handler/create!)
(def delete! page-common-handler/delete!)
(def unfavorite-page! page-common-handler/unfavorite-page!)
(def favorite-page! page-common-handler/favorite-page!)
;; FIXME: add whiteboard
(defn- get-directory
[journal?]
(if journal?
(config/get-journals-directory)
(config/get-pages-directory)))
(defn- get-file-name
[journal? title]
(when-let [s (if journal?
(date/journal-title->default title)
;; legacy in org-mode format, don't escape slashes except bug reported
(common-util/page-name-sanity (string/lower-case title)))]
;; Win10 file path has a length limit of 260 chars
(common-util/safe-subs s 0 200)))
(defn toggle-favorite! []
;; NOTE: in journals or settings, current-page is nil
(when-let [page-name (state/get-current-page)]
(let [favorites (:favorites (state/sub-config))
favorited? (contains? (set (map string/lower-case favorites))
(string/lower-case page-name))]
(if favorited?
(unfavorite-page! page-name)
(favorite-page! page-name)))))
(defn rename!
[old-name new-name & {:as opts}]
(let [repo (state/get-current-repo)
conn (db/get-db repo false)
config (state/get-config repo)
result (worker-page-rename/rename! repo conn config old-name new-name opts)]
(case result
:invalid-empty-name
(notification/show! "Please use a valid name, empty name is not allowed!" :error)
:merge-whiteboard-pages
(notification/show! "Can't merge whiteboard pages" :error)
nil)
result))
(defn reorder-favorites!
[favorites]
(config-handler/set-config! :favorites favorites))
(defn has-more-journals?
[]
(let [current-length (:journals-length @state/state)]
(< current-length (db/get-journals-length))))
(defn load-more-journals!
[]
(when (has-more-journals?)
(state/set-journals-length! (+ (:journals-length @state/state) 7))))
(defn update-public-attribute!
[page-name value]
(property-handler/add-page-property! page-name :public value))
(defn get-page-ref-text
[page]
(let [edit-block-file-path (model/get-block-file-path (state/get-edit-block))
page-name (string/lower-case page)]
(if (and edit-block-file-path
(state/org-mode-file-link? (state/get-current-repo)))
(if-let [ref-file-path (:file/path (db/get-page-file page-name))]
(util/format "[[file:%s][%s]]"
(util/get-relative-path edit-block-file-path ref-file-path)
page)
(let [journal? (date/valid-journal-title? page)
ref-file-path (str
(if (or (util/electron?) (mobile-util/native-platform?))
(-> (config/get-repo-dir (state/get-current-repo))
js/decodeURI
(string/replace #"/+$" "")
(str "/"))
"")
(get-directory journal?)
"/"
(get-file-name journal? page)
".org")]
(create! page {:redirect? false})
(util/format "[[file:%s][%s]]"
(util/get-relative-path edit-block-file-path ref-file-path)
page)))
(page-ref/->page-ref page))))
(defn init-commands!
[]
(commands/init-commands! get-page-ref-text))
(def rebuild-slash-commands-list!
(debounce init-commands! 1500))
(defn <template-exists?
[title]
(when title
(p/let [result (db-async/<get-all-templates (state/get-current-repo))
templates (keys result)]
(when (seq templates)
(let [templates (map string/lower-case templates)]
(contains? (set templates) (string/lower-case title)))))))
(defn ls-dir-files!
([ok-handler] (ls-dir-files! ok-handler nil))
([ok-handler opts]
(web-nfs/ls-dir-files-with-handler!
(fn [e]
(init-commands!)
(when ok-handler
(ok-handler e)))
opts)))
(defn get-all-pages
[repo]
(->> (db/get-all-pages repo)
(remove (fn [p]
(let [name (:block/name p)]
(or (util/uuid-string? name)
(common-config/draw? name)
(db/built-in-pages-names (string/upper-case name))
(db-property/built-in-properties-keys-str name)))))
(common-handler/fix-pages-timestamps)))
(defn get-filters
[page-name]
(let [properties (db/get-page-properties page-name)]
(if (config/db-based-graph? (state/get-current-repo))
(pu/lookup properties :filters)
(let [properties-str (or (:filters properties) "{}")]
(try (reader/read-string properties-str)
(catch :default e
(log/error :syntax/filters e)))))))
(defn save-filter!
[page-name filter-state]
(property-handler/add-page-property! page-name :filters filter-state))
;; Editor
(defn page-not-exists-handler
[input id q current-pos]
(state/clear-editor-action!)
(if (state/org-mode-file-link? (state/get-current-repo))
(let [page-ref-text (get-page-ref-text q)
value (gobj/get input "value")
old-page-ref (page-ref/->page-ref q)
new-value (string/replace value
old-page-ref
page-ref-text)]
(state/set-edit-content! id new-value)
(let [new-pos (+ current-pos
(- (count page-ref-text)
(count old-page-ref))
2)]
(cursor/move-cursor-to input new-pos)))
(let [current-selected (util/get-selected-text)]
(cursor/move-cursor-forward input (+ 2 (count current-selected))))))
(defn on-chosen-handler
[input id _q pos format]
(let [current-pos (cursor/pos input)
edit-content (state/get-edit-content)
action (state/get-editor-action)
hashtag? (= action :page-search-hashtag)
q (or
(editor-handler/get-selected-text)
(when hashtag?
(common-util/safe-subs edit-content pos current-pos))
(when (> (count edit-content) current-pos)
(common-util/safe-subs edit-content pos current-pos)))
db-based? (config/db-based-graph? (state/get-current-repo))]
(if hashtag?
(fn [chosen e]
(util/stop e)
(state/clear-editor-action!)
(let [class? (string/starts-with? chosen (t :new-class))
chosen (-> chosen
(string/replace-first (str (t :new-class) " ") "")
(string/replace-first (str (t :new-page) " ") ""))
wrapped? (= page-ref/left-brackets (common-util/safe-subs edit-content (- pos 2) pos))
wrapped-tag (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
(page-ref/->page-ref chosen)
chosen)
q (if (editor-handler/get-selected-text) "" q)
last-pattern (if wrapped?
q
(if (= \# (first q))
(subs q 1)
q))
last-pattern (str "#" (when wrapped? page-ref/left-brackets) last-pattern)]
(when db-based?
(let [tag (string/trim chosen)
edit-block (state/get-edit-block)]
(when (and (not (string/blank? tag)) (:block/uuid edit-block))
(let [tag-entity (db/entity [:block/name (util/page-name-sanity-lc tag)])]
(when-not tag-entity
(create! tag {:redirect? false
:create-first-block? false
:class? class?}))
(when class?
(let [repo (state/get-current-repo)
tag-entity (or tag-entity (db/entity [:block/name (util/page-name-sanity-lc tag)]))
tx-data [[:db/add [:block/uuid (:block/uuid edit-block)] :block/tags (:db/id tag-entity)]
[:db/add [:block/uuid (:block/uuid edit-block)] :block/refs (:db/id tag-entity)]]]
(db/transact! repo tx-data {:outliner-op :save-block})))))))
(editor-handler/insert-command! id
(str "#" wrapped-tag)
format
{:last-pattern last-pattern
:end-pattern (when wrapped? page-ref/right-brackets)
:command :page-ref})
(when input (.focus input))))
(fn [chosen e]
(util/stop e)
(state/clear-editor-action!)
(let [page-ref-text (get-page-ref-text chosen)]
(editor-handler/insert-command! id
page-ref-text
format
{:last-pattern (str page-ref/left-brackets (if (editor-handler/get-selected-text) "" q))
:end-pattern page-ref/right-brackets
:postfix-fn (fn [s] (util/replace-first page-ref/right-brackets s ""))
:command :page-ref}))))))
(defn create-today-journal!
[]
(when-let [repo (state/get-current-repo)]
(when (and (state/enable-journals? repo)
(not (:graph/loading? @state/state))
(not (state/loading-files? repo))
(not (state/whiteboard-route?)))
(state/set-today! (date/today))
(when (or (config/db-based-graph? repo)
(config/local-file-based-graph? repo)
(and (= config/demo-repo repo) (not (mobile-util/native-platform?))))
(let [title (date/today)
today-page (util/page-name-sanity-lc title)
format (state/get-preferred-format repo)
template (state/get-default-journal-template)
create-f (fn []
(p/do!
(create! title {:redirect? false
:split-namespace? false
:create-first-block? (not template)
:journal? true
:today-journal? true})
(state/pub-event! [:journal/insert-template today-page])
(ui-handler/re-render-root!)
(plugin-handler/hook-plugin-app :today-journal-created {:title today-page})))]
(when (db/page-empty? repo today-page)
(if (config/db-based-graph? repo)
(let [page-exists (db/get-page today-page)]
(when-not page-exists
(create-f)))
(p/let [file-name (date/journal-title->default title)
file-rpath (str (config/get-journals-directory) "/" file-name "."
(config/get-file-extension format))
repo-dir (config/get-repo-dir repo)
file-exists? (fs/file-exists? repo-dir file-rpath)
file-content (when file-exists?
(fs/read-file repo-dir file-rpath))]
(when (or (not file-exists?)
(and file-exists? (string/blank? file-content)))
(create-f))))))))))
(defn open-today-in-sidebar
[]
(when-let [page (db/entity [:block/name (util/page-name-sanity-lc (date/today))])]
(state/sidebar-add-block!
(state/get-current-repo)
(:db/id page)
:page)))
(defn open-file-in-default-app []
(if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
(let [repo-dir (config/get-repo-dir (state/get-current-repo))
file-fpath (path/path-join repo-dir file-rpath)]
(js/window.apis.openPath file-fpath))
(notification/show! "No file found" :warning)))
(defn copy-current-file
"FIXME: clarify usage, copy file or copy file path"
[]
(if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
(let [repo-dir (config/get-repo-dir (state/get-current-repo))
file-fpath (path/path-join repo-dir file-rpath)]
(util/copy-to-clipboard! file-fpath))
(notification/show! "No file found" :warning)))
(defn open-file-in-directory []
(if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
(let [repo-dir (config/get-repo-dir (state/get-current-repo))
file-fpath (path/path-join repo-dir file-rpath)]
(ipc/ipc "openFileInFolder" file-fpath))
(notification/show! "No file found" :warning)))
(defn copy-page-url
([] (copy-page-url (page-util/get-current-page-name)))
([page-name]
(if page-name
(util/copy-to-clipboard!
(url-util/get-logseq-graph-page-url nil (state/get-current-repo) page-name))
(notification/show! "No page found to copy" :warning))))