123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- (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.common :as common-handler]
- [frontend.handler.route :as route-handler]
- [frontend.handler.file :as file-handler]
- [frontend.handler.repo :as repo-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.config :as config-handler]
- [frontend.handler.ui :as ui-handler]
- [frontend.commands :as commands]
- [frontend.date :as date]
- [clojure.walk :as walk]
- [frontend.git :as git]
- [frontend.fs :as fs]
- [promesa.core :as p]
- [lambdaisland.glogi :as log]
- [frontend.format.mldoc :as mldoc]
- [cljs-time.core :as t]
- [cljs-time.coerce :as tc]))
- (defn- get-directory
- [journal?]
- (if journal?
- config/default-journals-directory
- (config/get-pages-directory)))
- (defn- get-file-name
- [journal? title]
- (if journal?
- (date/journal-title->default title)
- (util/page-name-sanity (string/lower-case title))))
- (defn create!
- ([title]
- (create! title {}))
- ([title {:keys [redirect?]
- :or {redirect? true}}]
- (let [title (and title (string/trim title))
- repo (state/get-current-repo)
- dir (util/get-repo-dir repo)
- journal-page? (date/valid-journal-title? title)
- directory (get-directory journal-page?)]
- (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 (get-file-name journal-page? title)
- "."
- (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 repo dir file-path content)
- _ (git-handler/git-add repo path)]
- (file-handler/reset-file! repo path content)
- (when redirect?
- (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]
- (config-handler/set-config! [:project :name] 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)]
- (p/let [_ (config-handler/set-config! [:project :name] project)]
- (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))))
- (state/close-modal!))))
- project-add-modal))
- (defn unpublished-success-handler
- [page-name]
- (fn [result]
- (state/close-modal!)
- (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-not (string/blank? 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)
- _ (fs/unlink! (str (util/get-repo-dir repo)
- "/"
- file-path)
- nil)]
- (common-handler/check-changed-files-status)
- (repo-handler/push-if-auto-enabled! repo))
- (p/catch (fn [err]
- (prn "error: " err))))))
- (db/transact! [[:db.fn/retractEntity [:page/name page-name]]])
- (ok-handler))))))
- (defn- compute-new-file-path
- [old-path new-page-name]
- (let [result (string/split old-path "/")
- file-name (util/page-name-sanity new-page-name)
- ext (last (string/split (last result) "."))
- new-file (str file-name "." ext)
- parts (concat (butlast result) [new-file])]
- (string/join "/" parts)))
- (defn rename-file!
- [file new-name ok-handler]
- (let [repo (state/get-current-repo)
- old-path (:file/path file)
- new-path (compute-new-file-path old-path new-name)]
- ;; update db
- (db/transact! repo [{:db/id (:db/id file)
- :file/path new-path}])
- ;; update files db
- (let [conn (db/get-files-conn repo)]
- (when-let [file (d/entity (d/db conn) [:file/path old-path])]
- (d/transact! conn [{:db/id (:db/id file)
- :file/path new-path}])))
- (->
- (p/let [_ (fs/rename! repo
- (str (util/get-repo-dir repo) "/" old-path)
- (str (util/get-repo-dir repo) "/" new-path))
- _ (when-not (config/local-db? repo)
- (git/rename repo old-path new-path))]
- (common-handler/check-changed-files-status)
- (ok-handler))
- (p/catch (fn [error]
- (println "file rename failed: " error))))))
- (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)]
- (if (db/entity [:page/name (string/lower-case new-name)])
- (notification/show! "Page already exists!" :error)
- (when-let [page (db/entity [:page/name (string/lower-case old-name)])]
- (let [old-original-name (:page/original-name page)
- file (:page/file page)
- journal? (:page/journal? 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 (and file (not journal?))
- (rename-file! file new-name
- (fn []
- (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 (string/lower-case new-name)}})
- (notification/show! "Page renamed successfully!" :success)
- (repo-handler/push-if-auto-enabled! repo)
- (ui-handler/re-render-root!))))))
- (defn rename-when-alter-title-property!
- [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 (string/trimr (:block/content last-block))
- "\n"
- pre-str " [[" page-name "]]"))]
- (editor-handler/insert-new-block-aux!
- last-block
- new-content
- {:create-new-block? false
- :ok-handler
- (fn [_]
- (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}))
- (defn get-page-ref-text
- [page]
- (when-let [edit-block (state/get-edit-block)]
- (let [page-name (string/lower-case page)
- edit-block-file-path (-> (:db/id (:block/file edit-block))
- (db/entity)
- :file/path)]
- (if (and edit-block-file-path
- (state/org-mode-file-link? (state/get-current-repo)))
- (if-let [ref-file-path (:file/path (:page/file (db/entity [:page/name 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 (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)))
- (util/format "[[%s]]" page)))))
- (defn init-commands!
- []
- (commands/init-commands! get-page-ref-text))
- (defn delete-page-from-logseq
- [project permalink]
- (let [url (util/format "%s%s/%s" config/api project permalink)]
- (js/Promise.
- (fn [resolve reject]
- (util/delete url
- (fn [result]
- (resolve result))
- (fn [error]
- (log/error :page/http-delete-failed error)
- (reject error)))))))
- (defn get-page-list-by-project-name
- [project]
- (js/Promise.
- (fn [resolve _]
- (if-not (string? project)
- (resolve :project-name-is-invalid)
- (let [url (util/format "%sprojects/%s/pages" config/api project)]
- (util/fetch url
- (fn [result]
- (log/debug :page/get-page-list result)
- (let [data (:result result)]
- (if (sequential? data)
- (do (state/set-published-pages data)
- (resolve data))
- (log/error :page/http-get-list-result-malformed result))))
- (fn [error]
- (log/error :page/http-get-list-failed error)
- (resolve error))))))))
- (defn update-state-and-notify
- [page-name]
- (page-add-properties! page-name {:published false})
- (notification/show! (util/format "Remove Page \"%s\" from Logseq server success" page-name) :success))
- (defn add-page-to-recent!
- [repo page]
- (let [pages (or (db/get-key-value repo :recent/pages)
- '())
- new-pages (take 12 (distinct (cons page pages)))]
- (db/set-key-value repo :recent/pages new-pages)))
- (defn template-exists?
- [title]
- (when title
- (let [templates (keys (db/get-all-templates))]
- (when (seq templates)
- (let [templates (map string/lower-case templates)]
- (contains? (set templates) (string/lower-case title)))))))
- (defn get-pages-with-modified-at
- [repo]
- (let [now-long (tc/to-long (t/now))]
- (->> (db/get-modified-pages repo)
- (seq)
- (sort-by (fn [[page modified-at]]
- [modified-at page]))
- (reverse)
- (remove (fn [[page modified-at]]
- (or (util/file-page? page)
- (and modified-at
- (> modified-at now-long))))))))
- (defn page-exists?
- [page-name]
- (when page-name
- (db/entity [:page/name page-name])))
|