page.cljs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. (ns frontend.handler.page
  2. (:require [clojure.string :as string]
  3. [frontend.db :as db]
  4. [datascript.core :as d]
  5. [frontend.state :as state]
  6. [frontend.util :as util :refer-macros [profile]]
  7. [frontend.tools.html-export :as html-export]
  8. [frontend.config :as config]
  9. [frontend.handler.common :as common-handler]
  10. [frontend.handler.route :as route-handler]
  11. [frontend.handler.file :as file-handler]
  12. [frontend.handler.repo :as repo-handler]
  13. [frontend.handler.git :as git-handler]
  14. [frontend.handler.editor :as editor-handler]
  15. [frontend.handler.project :as project-handler]
  16. [frontend.handler.notification :as notification]
  17. [frontend.handler.config :as config-handler]
  18. [frontend.handler.ui :as ui-handler]
  19. [frontend.commands :as commands]
  20. [frontend.date :as date]
  21. [clojure.walk :as walk]
  22. [frontend.git :as git]
  23. [frontend.fs :as fs]
  24. [promesa.core :as p]
  25. [lambdaisland.glogi :as log]
  26. [frontend.format.mldoc :as mldoc]
  27. [cljs-time.core :as t]
  28. [cljs-time.coerce :as tc]))
  29. (defn- get-directory
  30. [journal?]
  31. (if journal?
  32. config/default-journals-directory
  33. (config/get-pages-directory)))
  34. (defn- get-file-name
  35. [journal? title]
  36. (if journal?
  37. (date/journal-title->default title)
  38. (util/page-name-sanity (string/lower-case title))))
  39. (defn create!
  40. ([title]
  41. (create! title {}))
  42. ([title {:keys [redirect?]
  43. :or {redirect? true}}]
  44. (let [title (and title (string/trim title))
  45. repo (state/get-current-repo)
  46. dir (util/get-repo-dir repo)
  47. journal-page? (date/valid-journal-title? title)
  48. directory (get-directory journal-page?)]
  49. (when dir
  50. (p/let [_ (-> (fs/mkdir! (str dir "/" directory))
  51. (p/catch (fn [_e])))]
  52. (let [format (name (state/get-preferred-format))
  53. page (string/lower-case title)
  54. path (str (get-file-name journal-page? title)
  55. "."
  56. (if (= format "markdown") "md" format))
  57. path (str directory "/" path)
  58. file-path (str "/" path)]
  59. (p/let [exists? (fs/file-exists? dir file-path)]
  60. (if exists?
  61. (notification/show!
  62. [:p.content
  63. (util/format "File %s already exists!" file-path)]
  64. :error)
  65. ;; create the file
  66. (let [content (util/default-content-with-title format title)]
  67. (p/let [_ (fs/create-if-not-exists repo dir file-path content)
  68. _ (git-handler/git-add repo path)]
  69. (file-handler/reset-file! repo path content)
  70. (when redirect?
  71. (route-handler/redirect! {:to :page
  72. :path-params {:name page}})
  73. (let [blocks (db/get-page-blocks page)
  74. last-block (last blocks)]
  75. (when last-block
  76. (js/setTimeout
  77. #(editor-handler/edit-last-block-for-new-page! last-block 0)
  78. 100))))))))))))))
  79. (defn page-add-properties!
  80. [page-name properties]
  81. (let [page (db/entity [:page/name page-name])
  82. page-format (db/get-page-format page-name)
  83. properties-content (db/get-page-properties-content page-name)
  84. properties-content (if properties-content
  85. (string/trim properties-content)
  86. (config/properties-wrapper page-format))]
  87. (let [file (db/entity (:db/id (:page/file page)))
  88. file-path (:file/path file)
  89. file-content (db/get-file file-path)
  90. after-content (subs file-content (inc (count properties-content)))
  91. new-properties-content (db/add-properties! page-format properties-content properties)
  92. full-content (str new-properties-content "\n\n" (string/trim after-content))]
  93. (file-handler/alter-file (state/get-current-repo)
  94. file-path
  95. full-content
  96. {:reset? true
  97. :re-render-root? true}))))
  98. (defn page-remove-property!
  99. [page-name k]
  100. (when-let [properties-content (string/trim (db/get-page-properties-content page-name))]
  101. (let [page (db/entity [:page/name page-name])
  102. file (db/entity (:db/id (:page/file page)))
  103. file-path (:file/path file)
  104. file-content (db/get-file file-path)
  105. after-content (subs file-content (count properties-content))
  106. page-format (db/get-page-format page-name)
  107. new-properties-content (let [lines (string/split-lines properties-content)
  108. prefix (case page-format
  109. :org (str "#+" (string/upper-case k) ": ")
  110. :markdown (str (string/lower-case k) ": ")
  111. "")
  112. exists? (atom false)
  113. lines (remove #(util/starts-with? % prefix) lines)]
  114. (string/join "\n" lines))
  115. full-content (str new-properties-content "\n\n" (string/trim after-content))]
  116. (file-handler/alter-file (state/get-current-repo)
  117. file-path
  118. full-content
  119. {:reset? true
  120. :re-render-root? true}))))
  121. (defn published-success-handler
  122. [page-name]
  123. (fn [result]
  124. (let [permalink (:permalink result)]
  125. (page-add-properties! page-name {"permalink" permalink})
  126. (let [win (js/window.open (str
  127. config/website
  128. "/"
  129. (state/get-current-project)
  130. "/"
  131. permalink))]
  132. (.focus win)))))
  133. (defn published-failed-handler
  134. [error]
  135. (notification/show!
  136. "Publish failed, please give it another try."
  137. :error))
  138. (defn get-plugins
  139. [blocks]
  140. (let [plugins (atom {})
  141. add-plugin #(swap! plugins assoc % true)]
  142. (walk/postwalk
  143. (fn [x]
  144. (if (and (vector? x)
  145. (>= (count x) 2))
  146. (let [[type option] x]
  147. (case type
  148. "Src" (when (:language option)
  149. (add-plugin "highlight"))
  150. "Export" (when (= option "latex")
  151. (add-plugin "latex"))
  152. "Latex_Fragment" (add-plugin "latex")
  153. "Math" (add-plugin "latex")
  154. "Latex_Environment" (add-plugin "latex")
  155. nil)
  156. x)
  157. x))
  158. (map :block/body blocks))
  159. @plugins))
  160. (defn publish-page-as-slide!
  161. ([page-name project-add-modal]
  162. (publish-page-as-slide! page-name (db/get-page-blocks page-name) project-add-modal))
  163. ([page-name blocks project-add-modal]
  164. (project-handler/exists-or-create!
  165. (fn [project]
  166. (config-handler/set-config! [:project :name] project)
  167. (page-add-properties! page-name {"published" true
  168. "slide" true})
  169. (let [properties (db/get-page-properties page-name)
  170. plugins (get-plugins blocks)
  171. data {:project project
  172. :title page-name
  173. :permalink (:permalink properties)
  174. :html (html-export/export-page page-name blocks notification/show!)
  175. :tags (:tags properties)
  176. :settings (merge
  177. (assoc properties
  178. :slide true
  179. :published true)
  180. plugins)
  181. :repo (state/get-current-repo)}]
  182. (util/post (str config/api "pages")
  183. data
  184. (published-success-handler page-name)
  185. published-failed-handler)))
  186. project-add-modal)))
  187. (defn publish-page!
  188. [page-name project-add-modal]
  189. (project-handler/exists-or-create!
  190. (fn [project]
  191. (let [properties (db/get-page-properties page-name)
  192. slide? (let [slide (:slide properties)]
  193. (or (true? slide)
  194. (= "true" slide)))
  195. blocks (db/get-page-blocks page-name)
  196. plugins (get-plugins blocks)]
  197. (p/let [_ (config-handler/set-config! [:project :name] project)]
  198. (if slide?
  199. (publish-page-as-slide! page-name blocks project-add-modal)
  200. (do
  201. (page-add-properties! page-name {"published" true})
  202. (let [data {:project project
  203. :title page-name
  204. :permalink (:permalink properties)
  205. :html (html-export/export-page page-name blocks notification/show!)
  206. :tags (:tags properties)
  207. :settings (merge properties plugins)
  208. :repo (state/get-current-repo)}]
  209. (util/post (str config/api "pages")
  210. data
  211. (published-success-handler page-name)
  212. published-failed-handler))))
  213. (state/close-modal!))))
  214. project-add-modal))
  215. (defn unpublished-success-handler
  216. [page-name]
  217. (fn [result]
  218. (state/close-modal!)
  219. (notification/show!
  220. "Un-publish successfully!"
  221. :success)))
  222. (defn unpublished-failed-handler
  223. [error]
  224. (notification/show!
  225. "Un-publish failed, please give it another try."
  226. :error))
  227. (defn unpublish-page!
  228. [page-name]
  229. (page-add-properties! page-name {"published" false})
  230. (let [properties (db/get-page-properties page-name)
  231. permalink (:permalink properties)
  232. project (state/get-current-project)]
  233. (if (and project permalink)
  234. (util/delete (str config/api project "/" permalink)
  235. (unpublished-success-handler page-name)
  236. unpublished-failed-handler)
  237. (notification/show!
  238. "Can't find the permalink of this page!"
  239. :error))))
  240. (defn delete!
  241. [page-name ok-handler]
  242. (when page-name
  243. (when-let [repo (state/get-current-repo)]
  244. (let [page-name (string/lower-case page-name)]
  245. (let [file (db/get-page-file page-name)
  246. file-path (:file/path file)]
  247. ;; delete file
  248. (when-not (string/blank? file-path)
  249. (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
  250. (when-let [files-conn (db/get-files-conn repo)]
  251. (d/transact! files-conn [[:db.fn/retractEntity [:file/path file-path]]]))
  252. (let [blocks (db/get-page-blocks page-name)
  253. tx-data (mapv
  254. (fn [block]
  255. [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
  256. blocks)]
  257. (db/transact! tx-data)
  258. ;; remove file
  259. (->
  260. (p/let [_ (git/remove-file repo file-path)
  261. _ (fs/unlink! (str (util/get-repo-dir repo)
  262. "/"
  263. file-path)
  264. nil)]
  265. (common-handler/check-changed-files-status)
  266. (repo-handler/push-if-auto-enabled! repo))
  267. (p/catch (fn [err]
  268. (prn "error: " err))))))
  269. (db/transact! [[:db.fn/retractEntity [:page/name page-name]]])
  270. (ok-handler))))))
  271. (defn- compute-new-file-path
  272. [old-path new-page-name]
  273. (let [result (string/split old-path "/")
  274. file-name (util/page-name-sanity new-page-name)
  275. ext (last (string/split (last result) "."))
  276. new-file (str file-name "." ext)
  277. parts (concat (butlast result) [new-file])]
  278. (string/join "/" parts)))
  279. (defn rename-file!
  280. [file new-name ok-handler]
  281. (let [repo (state/get-current-repo)
  282. old-path (:file/path file)
  283. new-path (compute-new-file-path old-path new-name)]
  284. ;; update db
  285. (db/transact! repo [{:db/id (:db/id file)
  286. :file/path new-path}])
  287. ;; update files db
  288. (let [conn (db/get-files-conn repo)]
  289. (when-let [file (d/entity (d/db conn) [:file/path old-path])]
  290. (d/transact! conn [{:db/id (:db/id file)
  291. :file/path new-path}])))
  292. (->
  293. (p/let [_ (fs/rename! repo
  294. (str (util/get-repo-dir repo) "/" old-path)
  295. (str (util/get-repo-dir repo) "/" new-path))
  296. _ (when-not (config/local-db? repo)
  297. (git/rename repo old-path new-path))]
  298. (common-handler/check-changed-files-status)
  299. (ok-handler))
  300. (p/catch (fn [error]
  301. (println "file rename failed: " error))))))
  302. (defn rename!
  303. [old-name new-name]
  304. (when (and old-name new-name
  305. (not= (string/lower-case old-name) (string/lower-case new-name)))
  306. (when-let [repo (state/get-current-repo)]
  307. (if (db/entity [:page/name (string/lower-case new-name)])
  308. (notification/show! "Page already exists!" :error)
  309. (when-let [page (db/entity [:page/name (string/lower-case old-name)])]
  310. (let [old-original-name (:page/original-name page)
  311. file (:page/file page)
  312. journal? (:page/journal? page)]
  313. (d/transact! (db/get-conn repo false)
  314. [{:db/id (:db/id page)
  315. :page/name (string/lower-case new-name)
  316. :page/original-name new-name}])
  317. (when (and file (not journal?))
  318. (rename-file! file new-name
  319. (fn []
  320. (page-add-properties! (string/lower-case new-name) {:title new-name}))))
  321. ;; update all files which have references to this page
  322. (let [files (db/get-files-that-referenced-page (:db/id page))]
  323. (doseq [file-path files]
  324. (let [file-content (db/get-file file-path)
  325. ;; FIXME: not safe
  326. new-content (string/replace file-content
  327. (util/format "[[%s]]" old-original-name)
  328. (util/format "[[%s]]" new-name))]
  329. (file-handler/alter-file repo
  330. file-path
  331. new-content
  332. {:reset? true
  333. :re-render-root? false})))))
  334. ;; TODO: update browser history, remove the current one
  335. ;; Redirect to the new page
  336. (route-handler/redirect! {:to :page
  337. :path-params {:name (string/lower-case new-name)}})
  338. (notification/show! "Page renamed successfully!" :success)
  339. (repo-handler/push-if-auto-enabled! repo)
  340. (ui-handler/re-render-root!))))))
  341. (defn rename-when-alter-title-property!
  342. [page path format original-content content]
  343. (when (and page (contains? config/mldoc-support-formats format))
  344. (let [old-name page
  345. new-name (let [ast (mldoc/->edn content (mldoc/default-config format))]
  346. (db/get-page-name path ast))]
  347. (when (not= old-name new-name)
  348. (rename! old-name new-name)))))
  349. (defn handle-add-page-to-contents!
  350. [page-name]
  351. (let [last-block (last (db/get-page-blocks (state/get-current-repo) "contents"))
  352. last-empty? (>= 3 (count (:block/content last-block)))
  353. heading-pattern (config/get-block-pattern (state/get-preferred-format))
  354. pre-str (str heading-pattern heading-pattern)
  355. new-content (if last-empty?
  356. (str pre-str " [[" page-name "]]")
  357. (str (string/trimr (:block/content last-block))
  358. "\n"
  359. pre-str " [[" page-name "]]"))]
  360. (editor-handler/insert-new-block-aux!
  361. last-block
  362. new-content
  363. {:create-new-block? false
  364. :ok-handler
  365. (fn [_]
  366. (notification/show! "Added to contents!" :success)
  367. (editor-handler/clear-when-saved!))
  368. :with-level? true
  369. :new-level 2
  370. :current-page "Contents"})))
  371. (defn load-more-journals!
  372. []
  373. (let [current-length (:journals-length @state/state)]
  374. (when (< current-length (db/get-journals-length))
  375. (state/update-state! :journals-length inc))))
  376. (defn update-public-attribute!
  377. [page-name value]
  378. (page-add-properties! page-name {:public value}))
  379. (defn get-page-ref-text
  380. [page]
  381. (when-let [edit-block (state/get-edit-block)]
  382. (let [page-name (string/lower-case page)
  383. edit-block-file-path (-> (:db/id (:block/file edit-block))
  384. (db/entity)
  385. :file/path)]
  386. (if (and edit-block-file-path
  387. (state/org-mode-file-link? (state/get-current-repo)))
  388. (if-let [ref-file-path (:file/path (:page/file (db/entity [:page/name page-name])))]
  389. (util/format "[[file:%s][%s]]"
  390. (util/get-relative-path edit-block-file-path ref-file-path)
  391. page)
  392. (let [journal? (date/valid-journal-title? page)
  393. ref-file-path (str (get-directory journal?)
  394. "/"
  395. (get-file-name journal? page)
  396. ".org")]
  397. (create! page {:redirect? false})
  398. (util/format "[[file:%s][%s]]"
  399. (util/get-relative-path edit-block-file-path ref-file-path)
  400. page)))
  401. (util/format "[[%s]]" page)))))
  402. (defn init-commands!
  403. []
  404. (commands/init-commands! get-page-ref-text))
  405. (defn delete-page-from-logseq
  406. [project permalink]
  407. (let [url (util/format "%s%s/%s" config/api project permalink)]
  408. (js/Promise.
  409. (fn [resolve reject]
  410. (util/delete url
  411. (fn [result]
  412. (resolve result))
  413. (fn [error]
  414. (log/error :page/http-delete-failed error)
  415. (reject error)))))))
  416. (defn get-page-list-by-project-name
  417. [project]
  418. (js/Promise.
  419. (fn [resolve _]
  420. (if-not (string? project)
  421. (resolve :project-name-is-invalid)
  422. (let [url (util/format "%sprojects/%s/pages" config/api project)]
  423. (util/fetch url
  424. (fn [result]
  425. (log/debug :page/get-page-list result)
  426. (let [data (:result result)]
  427. (if (sequential? data)
  428. (do (state/set-published-pages data)
  429. (resolve data))
  430. (log/error :page/http-get-list-result-malformed result))))
  431. (fn [error]
  432. (log/error :page/http-get-list-failed error)
  433. (resolve error))))))))
  434. (defn update-state-and-notify
  435. [page-name]
  436. (page-add-properties! page-name {:published false})
  437. (notification/show! (util/format "Remove Page \"%s\" from Logseq server success" page-name) :success))
  438. (defn add-page-to-recent!
  439. [repo page]
  440. (let [pages (or (db/get-key-value repo :recent/pages)
  441. '())
  442. new-pages (take 12 (distinct (cons page pages)))]
  443. (db/set-key-value repo :recent/pages new-pages)))
  444. (defn template-exists?
  445. [title]
  446. (when title
  447. (let [templates (keys (db/get-all-templates))]
  448. (when (seq templates)
  449. (let [templates (map string/lower-case templates)]
  450. (contains? (set templates) (string/lower-case title)))))))
  451. (defn get-pages-with-modified-at
  452. [repo]
  453. (let [now-long (tc/to-long (t/now))]
  454. (->> (db/get-modified-pages repo)
  455. (seq)
  456. (sort-by (fn [[page modified-at]]
  457. [modified-at page]))
  458. (reverse)
  459. (remove (fn [[page modified-at]]
  460. (or (util/file-page? page)
  461. (and modified-at
  462. (> modified-at now-long))))))))
  463. (defn page-exists?
  464. [page-name]
  465. (when page-name
  466. (db/entity [:page/name page-name])))