page.cljs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. (ns frontend.handler.page
  2. (:require [cljs.reader :as reader]
  3. [clojure.string :as string]
  4. [clojure.walk :as walk]
  5. [datascript.core :as d]
  6. [frontend.commands :as commands]
  7. [frontend.config :as config]
  8. [frontend.context.i18n :refer [t]]
  9. [frontend.date :as date]
  10. [frontend.db :as db]
  11. [logseq.db.schema :as db-schema]
  12. [frontend.db.model :as model]
  13. [frontend.db.utils :as db-utils]
  14. [frontend.db.conn :as conn]
  15. [frontend.fs :as fs]
  16. [frontend.handler.common :as common-handler]
  17. [frontend.handler.editor :as editor-handler]
  18. [frontend.handler.notification :as notification]
  19. [frontend.handler.route :as route-handler]
  20. [frontend.handler.ui :as ui-handler]
  21. [frontend.handler.web.nfs :as web-nfs]
  22. [frontend.handler.config :as config-handler]
  23. [frontend.handler.recent :as recent-handler]
  24. [frontend.modules.outliner.core :as outliner-core]
  25. [frontend.modules.outliner.file :as outliner-file]
  26. [frontend.modules.outliner.tree :as outliner-tree]
  27. [frontend.state :as state]
  28. [frontend.util :as util]
  29. [frontend.util.cursor :as cursor]
  30. [frontend.util.property :as property]
  31. [frontend.util.page-property :as page-property]
  32. [goog.object :as gobj]
  33. [lambdaisland.glogi :as log]
  34. [promesa.core :as p]
  35. [frontend.mobile.util :as mobile-util]
  36. [logseq.graph-parser.util :as gp-util]
  37. [logseq.graph-parser.config :as gp-config]
  38. [logseq.graph-parser.block :as gp-block]
  39. [frontend.format.block :as block]
  40. [goog.functions :refer [debounce]]))
  41. ;; FIXME: add whiteboard
  42. (defn- get-directory
  43. [journal?]
  44. (if journal?
  45. (config/get-journals-directory)
  46. (config/get-pages-directory)))
  47. (defn- get-file-name
  48. [journal? title]
  49. (when-let [s (if journal?
  50. (date/journal-title->default title)
  51. (gp-util/page-name-sanity (string/lower-case title)))]
  52. ;; Win10 file path has a length limit of 260 chars
  53. (gp-util/safe-subs s 0 200)))
  54. (defn get-page-file-path
  55. ([] (get-page-file-path (state/get-current-page)))
  56. ([page-name]
  57. (when page-name
  58. (let [page-name (util/page-name-sanity-lc page-name)]
  59. (when-let [page (db/entity [:block/name page-name])]
  60. (:file/path (:block/file page)))))))
  61. (defn- build-title [page]
  62. (let [original-name (:block/original-name page)]
  63. (if (string/includes? original-name ",")
  64. (util/format "\"%s\"" original-name)
  65. original-name)))
  66. (defn default-properties-block
  67. ([title format page]
  68. (default-properties-block title format page {}))
  69. ([title format page properties]
  70. (let [p (common-handler/get-page-default-properties title)
  71. ps (merge p properties)
  72. content (page-property/insert-properties format "" ps)
  73. refs (gp-block/get-page-refs-from-properties format properties
  74. (db/get-db (state/get-current-repo))
  75. (state/get-date-formatter))]
  76. {:block/uuid (db/new-block-id)
  77. :block/properties ps
  78. :block/properties-order (keys ps)
  79. :block/refs refs
  80. :block/left page
  81. :block/format format
  82. :block/content content
  83. :block/parent page
  84. :block/page page})))
  85. (defn- create-title-property?
  86. [journal? page-name]
  87. (and (not journal?)
  88. (util/create-title-property? page-name)))
  89. (defn- build-page-tx [format properties page journal? whiteboard?]
  90. (when (:block/uuid page)
  91. (let [page-entity [:block/uuid (:block/uuid page)]
  92. create-title? (create-title-property? journal?
  93. (or
  94. (:block/original-name page)
  95. (:block/name page)))
  96. page (merge page
  97. (when (seq properties) {:block/properties properties})
  98. (when whiteboard? {:block/whiteboard? whiteboard?}))
  99. page-empty? (db/page-empty? (state/get-current-repo) (:block/name page))]
  100. (cond
  101. (not page-empty?)
  102. [page]
  103. create-title?
  104. (let [properties-block (default-properties-block (build-title page) format page-entity properties)]
  105. [page
  106. properties-block])
  107. (seq properties)
  108. [page (editor-handler/properties-block properties format page-entity)]
  109. :else
  110. [page]))))
  111. (defn create!
  112. "Create page.
  113. :redirect? - when true, redirect to the created page, otherwise return sanitized page name.
  114. :split-namespace? - when true, split hierarchical namespace into levels.
  115. :create-first-block? - when true, create an empty block if the page is empty.
  116. :uuid - when set, use this uuid instead of generating a new one."
  117. ([title]
  118. (create! title {}))
  119. ([title {:keys [redirect? create-first-block? format properties split-namespace? journal? uuid whiteboard?]
  120. :or {redirect? true
  121. create-first-block? true
  122. format nil
  123. properties nil
  124. split-namespace? true
  125. uuid nil}}]
  126. (let [title (string/trim title)
  127. title (gp-util/remove-boundary-slashes title)
  128. page-name (util/page-name-sanity-lc title)
  129. repo (state/get-current-repo)
  130. with-uuid? (if (uuid? uuid) uuid true)] ;; FIXME: prettier validation
  131. (when (db/page-empty? repo page-name)
  132. (let [pages (if split-namespace?
  133. (gp-util/split-namespace-pages title)
  134. [title])
  135. format (or format (state/get-preferred-format))
  136. pages (map (fn [page]
  137. ;; only apply uuid to the deepest hierarchy of page to create if provided.
  138. (-> (block/page-name->map page (if (= page title) with-uuid? true))
  139. (assoc :block/format format)))
  140. pages)
  141. txs (->> pages
  142. ;; for namespace pages, only last page need properties
  143. drop-last
  144. (mapcat #(build-page-tx format nil % journal? whiteboard?))
  145. (remove nil?)
  146. (remove (fn [m]
  147. (some? (db/entity [:block/name (:block/name m)])))))
  148. last-txs (build-page-tx format properties (last pages) journal? whiteboard?)
  149. txs (concat txs last-txs)]
  150. (when (seq txs)
  151. (db/transact! txs)))
  152. (when create-first-block?
  153. (when (or
  154. (db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
  155. (create-title-property? journal? page-name))
  156. (editor-handler/api-insert-new-block! "" {:page page-name}))))
  157. (when redirect?
  158. (route-handler/redirect-to-page! page-name))
  159. page-name)))
  160. (defn delete-file!
  161. [repo page-name unlink-file?]
  162. (let [file (db/get-page-file page-name)
  163. file-path (:file/path file)]
  164. ;; delete file
  165. (when-not (string/blank? file-path)
  166. (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
  167. (when unlink-file?
  168. (-> (fs/unlink! repo (config/get-repo-path repo file-path) nil)
  169. (p/catch (fn [error] (js/console.error error))))))))
  170. (defn- compute-new-file-path
  171. [old-path new-name]
  172. (let [result (string/split old-path "/")
  173. file-name (gp-util/page-name-sanity new-name true)
  174. ext (last (string/split (last result) "."))
  175. new-file (str file-name "." ext)
  176. parts (concat (butlast result) [new-file])]
  177. (string/join "/" parts)))
  178. (defn rename-file!
  179. [file new-name ok-handler]
  180. (let [repo (state/get-current-repo)
  181. file (db/pull (:db/id file))
  182. old-path (:file/path file)
  183. new-path (compute-new-file-path old-path new-name)]
  184. ;; update db
  185. (db/transact! repo [{:db/id (:db/id file)
  186. :file/path new-path}])
  187. (->
  188. (p/let [_ (fs/rename! repo old-path new-path)]
  189. (ok-handler))
  190. (p/catch (fn [error]
  191. (println "file rename failed: " error))))))
  192. (defn- replace-page-ref!
  193. "Unsanitized names"
  194. [content old-name new-name]
  195. (let [[original-old-name original-new-name] (map string/trim [old-name new-name])
  196. [old-ref new-ref] (map #(util/format "[[%s]]" %) [old-name new-name])
  197. [old-name new-name] (map #(if (string/includes? % "/")
  198. (string/replace % "/" ".")
  199. %)
  200. [original-old-name original-new-name])
  201. old-org-ref (and (= :org (state/get-preferred-format))
  202. (:org-mode/insert-file-link? (state/get-config))
  203. (re-find
  204. (re-pattern
  205. (util/format
  206. "\\[\\[file:\\.*/.*%s\\.org\\]\\[(.*?)\\]\\]" old-name))
  207. content))]
  208. (-> (if old-org-ref
  209. (let [[old-full-ref old-label] old-org-ref
  210. new-label (if (= old-label original-old-name)
  211. original-new-name
  212. old-label)
  213. new-full-ref (-> (string/replace old-full-ref old-name new-name)
  214. (string/replace (str "[" old-label "]")
  215. (str "[" new-label "]")))]
  216. (string/replace content old-full-ref new-full-ref))
  217. content)
  218. (string/replace old-ref new-ref))))
  219. (defn- replace-tag-ref!
  220. [content old-name new-name]
  221. (let [old-tag (util/format "#%s" old-name)
  222. new-tag (if (re-find #"[\s\t]+" new-name)
  223. (util/format "#[[%s]]" new-name)
  224. (str "#" new-name))]
  225. (-> (util/replace-ignore-case content (str "^" old-tag "\\b") new-tag)
  226. (util/replace-ignore-case (str " " old-tag " ") (str " " new-tag " "))
  227. (util/replace-ignore-case (str " " old-tag "$") (str " " new-tag)))))
  228. (defn- replace-property-ref!
  229. [content old-name new-name]
  230. (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
  231. old-property (str old-name "::")
  232. new-property (str (name new-name) "::")]
  233. (util/replace-ignore-case content old-property new-property)))
  234. (defn- replace-old-page!
  235. "Unsanitized names"
  236. [content old-name new-name]
  237. (when (and (string? content) (string? old-name) (string? new-name))
  238. (-> content
  239. (replace-page-ref! old-name new-name)
  240. (replace-tag-ref! old-name new-name)
  241. (replace-property-ref! old-name new-name))))
  242. (defn- walk-replace-old-page!
  243. "Unsanitized names"
  244. [form old-name new-name]
  245. (walk/postwalk (fn [f]
  246. (cond
  247. (and (vector? f)
  248. (contains? #{"Search" "Label"} (first f))
  249. (string/starts-with? (second f) (str old-name "/")))
  250. [(first f) (string/replace-first (second f)
  251. (str old-name "/")
  252. (str new-name "/"))]
  253. (string? f)
  254. (if (= f old-name)
  255. new-name
  256. (replace-old-page! f old-name new-name))
  257. (and (keyword f) (= (name f) old-name))
  258. (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
  259. :else
  260. f))
  261. form))
  262. (defn favorited?
  263. [page-name]
  264. (let [favorites (->> (:favorites (state/get-config))
  265. (filter string?)
  266. (map string/lower-case)
  267. (set))]
  268. (contains? favorites page-name)))
  269. (defn favorite-page!
  270. [page-name]
  271. (when-not (string/blank? page-name)
  272. (let [favorites (->
  273. (cons
  274. page-name
  275. (or (:favorites (state/get-config)) []))
  276. (distinct)
  277. (vec))]
  278. (config-handler/set-config! :favorites favorites))))
  279. (defn unfavorite-page!
  280. [page-name]
  281. (when-not (string/blank? page-name)
  282. (let [favorites (->> (:favorites (state/get-config))
  283. (remove #(= (string/lower-case %) (string/lower-case page-name)))
  284. (vec))]
  285. (config-handler/set-config! :favorites favorites))))
  286. (defn toggle-favorite! []
  287. ;; NOTE: in journals or settings, current-page is nil
  288. (when-let [page-name (state/get-current-page)]
  289. (let [favorites (:favorites (state/sub-graph-config))
  290. favorited? (contains? (set (map string/lower-case favorites))
  291. (string/lower-case page-name))]
  292. (if favorited?
  293. (unfavorite-page! page-name)
  294. (favorite-page! page-name)))))
  295. (defn delete!
  296. [page-name ok-handler & {:keys [delete-file?]
  297. :or {delete-file? true}}]
  298. (route-handler/redirect-to-home!)
  299. (when page-name
  300. (when-let [repo (state/get-current-repo)]
  301. (let [page-name (util/page-name-sanity-lc page-name)
  302. blocks (db/get-page-blocks-no-cache page-name)
  303. tx-data (mapv
  304. (fn [block]
  305. [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
  306. blocks)
  307. page (db/entity [:block/name page-name])]
  308. (db/transact! tx-data)
  309. (delete-file! repo page-name delete-file?)
  310. ;; if other page alias this pagename,
  311. ;; then just remove some attrs of this entity instead of retractEntity
  312. (when-not (:block/_namespace page)
  313. (if (model/get-alias-source-page (state/get-current-repo) page-name)
  314. (when-let [id (:db/id (db/entity [:block/name page-name]))]
  315. (let [txs (mapv (fn [attribute]
  316. [:db/retract id attribute])
  317. db-schema/retract-page-attributes)]
  318. (db/transact! txs)))
  319. (db/transact! [[:db.fn/retractEntity [:block/name page-name]]])))
  320. (unfavorite-page! page-name)
  321. (when (fn? ok-handler) (ok-handler))
  322. (ui-handler/re-render-root!)))))
  323. (defn- rename-update-block-refs!
  324. [refs from-id to-id]
  325. (->> refs
  326. (remove #{{:db/id from-id}})
  327. (cons {:db/id to-id})
  328. (distinct)
  329. (vec)))
  330. (defn- rename-update-refs!
  331. "Unsanitized only"
  332. [page old-original-name new-name]
  333. ;; update all pages which have references to this page
  334. (let [repo (state/get-current-repo)
  335. to-page (db/entity [:block/name (util/page-name-sanity-lc new-name)])
  336. blocks (db/get-page-referenced-blocks-no-cache (:db/id page))
  337. page-ids (->> (map :block/page blocks)
  338. (remove nil?)
  339. (set))
  340. tx (->> (map (fn [{:block/keys [uuid content properties] :as block}]
  341. (let [content (let [content' (replace-old-page! content old-original-name new-name)]
  342. (when-not (= content' content)
  343. content'))
  344. properties (let [properties' (walk-replace-old-page! properties old-original-name new-name)]
  345. (when-not (= properties' properties)
  346. properties'))]
  347. (when (or content properties)
  348. (util/remove-nils-non-nested
  349. {:block/uuid uuid
  350. :block/content content
  351. :block/properties properties
  352. :block/properties-order (map first properties)
  353. :block/refs (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))
  354. :block/path-refs (rename-update-block-refs! (:block/path-refs block) (:db/id page) (:db/id to-page))})))) blocks)
  355. (remove nil?))]
  356. (db/transact! repo tx)
  357. (doseq [page-id page-ids]
  358. (outliner-file/sync-to-file page-id))))
  359. (defn- rename-page-aux
  360. "Only accepts unsanitized page names"
  361. [old-name new-name redirect?]
  362. (let [old-page-name (util/page-name-sanity-lc old-name)
  363. new-file-name (util/file-name-sanity new-name)
  364. new-page-name (util/page-name-sanity-lc new-name)
  365. repo (state/get-current-repo)
  366. page (db/pull [:block/name old-page-name])]
  367. (when (and repo page)
  368. (let [old-original-name (:block/original-name page)
  369. file (:block/file page)
  370. journal? (:block/journal? page)
  371. properties-block (:data (outliner-tree/-get-down (outliner-core/block page)))
  372. properties-block-tx (when (and properties-block
  373. (string/includes? (util/page-name-sanity-lc (:block/content properties-block))
  374. old-page-name))
  375. (let [front-matter? (and (property/front-matter? (:block/content properties-block))
  376. (= :markdown (:block/format properties-block)))]
  377. {:db/id (:db/id properties-block)
  378. :block/content (property/insert-property (:block/format properties-block)
  379. (:block/content properties-block)
  380. :title
  381. new-name
  382. front-matter?)}))
  383. page-txs [{:db/id (:db/id page)
  384. :block/uuid (:block/uuid page)
  385. :block/name new-page-name
  386. :block/original-name new-name}]
  387. page-txs (if properties-block-tx (conj page-txs properties-block-tx) page-txs)]
  388. (d/transact! (db/get-db repo false) page-txs)
  389. ;; If page name changed after sanitization
  390. (when (or (util/create-title-property? new-page-name)
  391. (not= (gp-util/page-name-sanity new-name false) new-name))
  392. (page-property/add-property! new-page-name :title new-name))
  393. (when (and file (not journal?))
  394. (rename-file! file new-file-name (fn [] nil)))
  395. (rename-update-refs! page old-original-name new-name)
  396. (outliner-file/sync-to-file page))
  397. ;; Redirect to the newly renamed page
  398. (when redirect?
  399. (route-handler/redirect! {:to :page
  400. :push false
  401. :path-params {:name new-page-name}}))
  402. (when (favorited? old-page-name)
  403. (p/do!
  404. (unfavorite-page! old-page-name)
  405. (favorite-page! new-page-name)))
  406. (recent-handler/update-or-add-renamed-page repo old-page-name new-page-name)
  407. (ui-handler/re-render-root!))))
  408. (defn- rename-nested-pages
  409. "Unsanitized names only"
  410. [old-ns-name new-ns-name]
  411. (let [repo (state/get-current-repo)
  412. nested-page-str (util/format "[[%s]]" (util/page-name-sanity-lc old-ns-name))
  413. ns-prefix (util/format "[[%s/" (util/page-name-sanity-lc old-ns-name))
  414. nested-pages (db/get-pages-by-name-partition repo nested-page-str)
  415. nested-pages-ns (db/get-pages-by-name-partition repo ns-prefix)]
  416. (when nested-pages
  417. ;; rename page "[[obsidian]] is a tool" to "[[logseq]] is a tool"
  418. (doseq [{:block/keys [name original-name]} nested-pages]
  419. (let [old-page-title (or original-name name)
  420. new-page-title (string/replace
  421. old-page-title
  422. (util/format "[[%s]]" old-ns-name)
  423. (util/format "[[%s]]" new-ns-name))]
  424. (when (and old-page-title new-page-title)
  425. (p/do!
  426. (rename-page-aux old-page-title new-page-title false)
  427. (println "Renamed " old-page-title " to " new-page-title))))))
  428. (when nested-pages-ns
  429. ;; rename page "[[obsidian/page1]] is a tool" to "[[logseq/page1]] is a tool"
  430. (doseq [{:block/keys [name original-name]} nested-pages-ns]
  431. (let [old-page-title (or original-name name)
  432. new-page-title (string/replace
  433. old-page-title
  434. (util/format "[[%s/" old-ns-name)
  435. (util/format "[[%s/" new-ns-name))]
  436. (when (and old-page-title new-page-title)
  437. (p/do!
  438. (rename-page-aux old-page-title new-page-title false)
  439. (println "Renamed " old-page-title " to " new-page-title))))))))
  440. (defn- rename-namespace-pages!
  441. "Original names (unsanitized only)"
  442. [repo old-name new-name]
  443. (let [pages (db/get-namespace-pages repo old-name)
  444. page (db/pull [:block/name (util/page-name-sanity-lc old-name)])
  445. pages (cons page pages)]
  446. (doseq [{:block/keys [name original-name]} pages]
  447. (let [old-page-title (or original-name name)
  448. ;; only replace one time, for the case that the namespace is a sub-string of the sub-namespace page name
  449. ;; Example: has pages [[work]] [[work/worklog]],
  450. ;; we want to rename [[work/worklog]] to [[work1/worklog]] when rename [[work]] to [[work1]],
  451. ;; but don't rename [[work/worklog]] to [[work1/work1log]]
  452. new-page-title (string/replace-first old-page-title old-name new-name)
  453. redirect? (= name (:block/name page))]
  454. (when (and old-page-title new-page-title)
  455. (p/let [_ (rename-page-aux old-page-title new-page-title redirect?)]
  456. (println "Renamed " old-page-title " to " new-page-title)))))))
  457. (defn merge-pages!
  458. "Only accepts sanitized page names"
  459. [from-page-name to-page-name]
  460. (when (and (db/page-exists? from-page-name)
  461. (db/page-exists? to-page-name)
  462. (not= from-page-name to-page-name))
  463. (let [to-page (db/entity [:block/name to-page-name])
  464. to-id (:db/id to-page)
  465. from-page (db/entity [:block/name from-page-name])
  466. from-id (:db/id from-page)
  467. from-first-child (some->> (db/pull from-id)
  468. (outliner-core/block)
  469. (outliner-tree/-get-down)
  470. (outliner-core/get-data))
  471. to-last-direct-child-id (model/get-block-last-direct-child (db/get-db) to-id false)
  472. repo (state/get-current-repo)
  473. conn (conn/get-db repo false)
  474. datoms (d/datoms @conn :avet :block/page from-id)
  475. block-eids (mapv :e datoms)
  476. blocks (db-utils/pull-many repo '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
  477. tx-data (map (fn [block]
  478. (let [id (:db/id block)]
  479. (cond->
  480. {:db/id id
  481. :block/page {:db/id to-id}
  482. :block/path-refs (rename-update-block-refs! (:block/path-refs block) from-id to-id)
  483. :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)}
  484. (and from-first-child (= id (:db/id from-first-child)))
  485. (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
  486. (= (:block/parent block) {:db/id from-id})
  487. (assoc :block/parent {:db/id to-id})))) blocks)]
  488. (d/transact! conn tx-data)
  489. (outliner-file/sync-to-file {:db/id to-id})
  490. (rename-update-refs! from-page
  491. (util/get-page-original-name from-page)
  492. (util/get-page-original-name to-page)))
  493. (delete! from-page-name nil)
  494. (route-handler/redirect! {:to :page
  495. :push false
  496. :path-params {:name to-page-name}})))
  497. (defn rename!
  498. "Accepts unsanitized page names"
  499. [old-name new-name]
  500. (let [repo (state/get-current-repo)
  501. old-name (string/trim old-name)
  502. new-name (string/trim new-name)
  503. old-page-name (util/page-name-sanity-lc old-name)
  504. new-page-name (util/page-name-sanity-lc new-name)
  505. name-changed? (not= old-name new-name)]
  506. (if (and old-name
  507. new-name
  508. (not (string/blank? new-name))
  509. name-changed?)
  510. (do
  511. (cond
  512. (= old-page-name new-page-name)
  513. (rename-page-aux old-name new-name true)
  514. (db/pull [:block/name new-page-name])
  515. (merge-pages! old-page-name new-page-name)
  516. :else
  517. (rename-namespace-pages! repo old-name new-name))
  518. (rename-nested-pages old-name new-name))
  519. (when (string/blank? new-name)
  520. (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
  521. (ui-handler/re-render-root!)))
  522. (defn- split-col-by-element
  523. [col element]
  524. (let [col (vec col)
  525. idx (.indexOf col element)]
  526. [(subvec col 0 (inc idx))
  527. (subvec col (inc idx))]))
  528. (defn reorder-favorites!
  529. [{:keys [to up?]}]
  530. (let [favorites (:favorites (state/get-config))
  531. from (get @state/state :favorites/dragging)]
  532. (when (and from to (not= from to))
  533. (let [[prev next] (split-col-by-element favorites to)
  534. [prev next] (mapv #(remove (fn [e] (= from e)) %) [prev next])
  535. favorites (->>
  536. (if up?
  537. (concat (drop-last prev) [from (last prev)] next)
  538. (concat prev [from] next))
  539. (remove nil?)
  540. (distinct))]
  541. (config-handler/set-config! :favorites favorites)))))
  542. (defn has-more-journals?
  543. []
  544. (let [current-length (:journals-length @state/state)]
  545. (< current-length (db/get-journals-length))))
  546. (defn load-more-journals!
  547. []
  548. (when (has-more-journals?)
  549. (state/set-journals-length! (+ (:journals-length @state/state) 7))))
  550. (defn update-public-attribute!
  551. [page-name value]
  552. (page-property/add-property! page-name :public value))
  553. (defn get-page-ref-text
  554. [page]
  555. (let [edit-block-file-path (model/get-block-file-path (state/get-edit-block))
  556. page-name (string/lower-case page)]
  557. (if (and edit-block-file-path
  558. (state/org-mode-file-link? (state/get-current-repo)))
  559. (if-let [ref-file-path (:file/path (db/get-page-file page-name))]
  560. (util/format "[[file:%s][%s]]"
  561. (util/get-relative-path edit-block-file-path ref-file-path)
  562. page)
  563. (let [journal? (date/valid-journal-title? page)
  564. ref-file-path (str
  565. (if (or (util/electron?) (mobile-util/native-platform?))
  566. (-> (config/get-repo-dir (state/get-current-repo))
  567. js/decodeURI
  568. (string/replace #"/+$" "")
  569. (str "/"))
  570. "")
  571. (get-directory journal?)
  572. "/"
  573. (get-file-name journal? page)
  574. ".org")]
  575. (create! page {:redirect? false})
  576. (util/format "[[file:%s][%s]]"
  577. (util/get-relative-path edit-block-file-path ref-file-path)
  578. page)))
  579. (util/format "[[%s]]" page))))
  580. (defn init-commands!
  581. []
  582. (commands/init-commands! get-page-ref-text))
  583. (def rebuild-slash-commands-list!
  584. (debounce init-commands! 1500))
  585. (defn template-exists?
  586. [title]
  587. (when title
  588. (let [templates (keys (db/get-all-templates))]
  589. (when (seq templates)
  590. (let [templates (map string/lower-case templates)]
  591. (contains? (set templates) (string/lower-case title)))))))
  592. (defn ls-dir-files!
  593. [ok-handler]
  594. (web-nfs/ls-dir-files-with-handler!
  595. (fn []
  596. (init-commands!)
  597. (when ok-handler (ok-handler)))))
  598. (defn get-all-pages
  599. [repo]
  600. (->> (db/get-all-pages repo)
  601. (remove (fn [p]
  602. (let [name (:block/name p)]
  603. (or (util/uuid-string? name)
  604. (gp-config/draw? name)
  605. (db/built-in-pages-names (string/upper-case name))))))
  606. (common-handler/fix-pages-timestamps)))
  607. (defn get-filters
  608. [page-name]
  609. (let [properties (db/get-page-properties page-name)
  610. properties-str (get properties :filters "{}")]
  611. (try (reader/read-string properties-str)
  612. (catch js/Error e
  613. (log/error :syntax/filters e)))))
  614. (defn save-filter!
  615. [page-name filter-state]
  616. (page-property/add-property! page-name :filters filter-state))
  617. ;; Editor
  618. (defn page-not-exists-handler
  619. [input id q current-pos]
  620. (state/clear-editor-action!)
  621. (if (state/org-mode-file-link? (state/get-current-repo))
  622. (let [page-ref-text (get-page-ref-text q)
  623. value (gobj/get input "value")
  624. old-page-ref (util/format "[[%s]]" q)
  625. new-value (string/replace value
  626. old-page-ref
  627. page-ref-text)]
  628. (state/set-edit-content! id new-value)
  629. (let [new-pos (+ current-pos
  630. (- (count page-ref-text)
  631. (count old-page-ref))
  632. 2)]
  633. (cursor/move-cursor-to input new-pos)))
  634. (let [current-selected (util/get-selected-text)]
  635. (cursor/move-cursor-forward input (+ 2 (count current-selected))))))
  636. (defn on-chosen-handler
  637. [input id _q pos format]
  638. (let [current-pos (cursor/pos input)
  639. edit-content (state/sub [:editor/content id])
  640. action (state/get-editor-action)
  641. hashtag? (= action :page-search-hashtag)
  642. q (or
  643. @editor-handler/*selected-text
  644. (when hashtag?
  645. (gp-util/safe-subs edit-content pos current-pos))
  646. (when (> (count edit-content) current-pos)
  647. (gp-util/safe-subs edit-content pos current-pos)))]
  648. (if hashtag?
  649. (fn [chosen _click?]
  650. (state/clear-editor-action!)
  651. (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
  652. prefix (str (t :new-page) ": ")
  653. chosen (if (string/starts-with? chosen prefix) ;; FIXME: What if a page named "New page: XXX"?
  654. (string/replace-first chosen prefix "")
  655. chosen)
  656. chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
  657. (util/format "[[%s]]" chosen)
  658. chosen)
  659. q (if @editor-handler/*selected-text "" q)
  660. [last-pattern forward-pos] (if wrapped?
  661. [q 3]
  662. (if (= \# (first q))
  663. [(subs q 1) 1]
  664. [q 2]))
  665. last-pattern (str "#" (when wrapped? "[[") last-pattern)]
  666. (editor-handler/insert-command! id
  667. (str "#" (when wrapped? "[[") chosen)
  668. format
  669. {:last-pattern last-pattern
  670. :end-pattern (when wrapped? "]]")
  671. :forward-pos forward-pos})))
  672. (fn [chosen _click?]
  673. (state/clear-editor-action!)
  674. (let [prefix (str (t :new-page) ": ")
  675. chosen (if (string/starts-with? chosen prefix)
  676. (string/replace-first chosen prefix "")
  677. chosen)
  678. page-ref-text (get-page-ref-text chosen)]
  679. (editor-handler/insert-command! id
  680. page-ref-text
  681. format
  682. {:last-pattern (str "[[" (if @editor-handler/*selected-text "" q))
  683. :end-pattern "]]"
  684. :postfix-fn (fn [s] (util/replace-first "]]" s ""))
  685. :forward-pos 3}))))))
  686. (defn create-today-journal!
  687. []
  688. (when-let [repo (state/get-current-repo)]
  689. (when (and (state/enable-journals? repo)
  690. (not (state/loading-files? repo)))
  691. (state/set-today! (date/today))
  692. (when (or (config/local-db? repo)
  693. (and (= "local" repo) (not (mobile-util/native-platform?))))
  694. (let [title (date/today)
  695. today-page (util/page-name-sanity-lc title)
  696. format (state/get-preferred-format repo)
  697. file-name (date/journal-title->default title)
  698. path (str (config/get-journals-directory) "/" file-name "."
  699. (config/get-file-extension format))
  700. file-path (str "/" path)
  701. repo-dir (config/get-repo-dir repo)
  702. template (state/get-default-journal-template)]
  703. (p/let [file-exists? (fs/file-exists? repo-dir file-path)
  704. file-content (when file-exists?
  705. (fs/read-file repo-dir file-path))]
  706. (when (and (db/page-empty? repo today-page)
  707. (or (not file-exists?)
  708. (and file-exists? (string/blank? file-content))))
  709. (create! title {:redirect? false
  710. :split-namespace? false
  711. :create-first-block? (not template)
  712. :journal? true})
  713. (state/pub-event! [:journal/insert-template today-page])
  714. (ui-handler/re-render-root!))))))))
  715. (defn open-today-in-sidebar
  716. []
  717. (when-let [page (db/entity [:block/name (util/page-name-sanity-lc (date/today))])]
  718. (state/sidebar-add-block!
  719. (state/get-current-repo)
  720. (:db/id page)
  721. :page)))
  722. (defn open-file-in-default-app []
  723. (when-let [file-path (and (util/electron?) (get-page-file-path))]
  724. (js/window.apis.openPath file-path)))
  725. (defn copy-current-file []
  726. (when-let [file-path (and (util/electron?) (get-page-file-path))]
  727. (util/copy-to-clipboard! file-path)))
  728. (defn open-file-in-directory []
  729. (when-let [file-path (and (util/electron?) (get-page-file-path))]
  730. (js/window.apis.showItemInFolder file-path)))