page.cljs 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. (ns frontend.handler.page
  2. "Provides util handler fns for pages"
  3. (:require [cljs.reader :as reader]
  4. [clojure.string :as string]
  5. [clojure.walk :as walk]
  6. [datascript.core :as d]
  7. [frontend.commands :as commands]
  8. [frontend.config :as config]
  9. [frontend.date :as date]
  10. [frontend.db :as db]
  11. [frontend.db.conn :as conn]
  12. [frontend.db.model :as model]
  13. [frontend.db.utils :as db-utils]
  14. [frontend.format.block :as block]
  15. [frontend.fs :as fs]
  16. [frontend.handler.common :as common-handler]
  17. [frontend.handler.reorder :as reorder-handler]
  18. [frontend.handler.config :as config-handler]
  19. [frontend.handler.editor :as editor-handler]
  20. [frontend.handler.file-based.editor :as file-editor-handler]
  21. [frontend.handler.plugin :as plugin-handler]
  22. [frontend.handler.notification :as notification]
  23. [frontend.handler.file-based.recent :as recent-handler]
  24. [frontend.handler.route :as route-handler]
  25. [frontend.handler.ui :as ui-handler]
  26. [frontend.handler.web.nfs :as web-nfs]
  27. [frontend.mobile.util :as mobile-util]
  28. [frontend.modules.outliner.core :as outliner-core]
  29. [frontend.modules.outliner.file :as outliner-file]
  30. [frontend.modules.outliner.tree :as outliner-tree]
  31. [frontend.state :as state]
  32. [frontend.util :as util]
  33. [frontend.util.cursor :as cursor]
  34. [frontend.util.fs :as fs-util]
  35. [frontend.util.page-property :as page-property]
  36. [frontend.util.page :as page-util]
  37. [frontend.handler.file-based.property :as file-property]
  38. [frontend.util.url :as url-util]
  39. [goog.functions :refer [debounce]]
  40. [goog.object :as gobj]
  41. [lambdaisland.glogi :as log]
  42. [logseq.db.schema :as db-schema]
  43. [logseq.db.property :as db-property]
  44. [logseq.graph-parser.block :as gp-block]
  45. [logseq.graph-parser.config :as gp-config]
  46. [logseq.graph-parser.property :as gp-property]
  47. [logseq.graph-parser.text :as text]
  48. [logseq.graph-parser.util :as gp-util]
  49. [logseq.graph-parser.util.page-ref :as page-ref]
  50. [promesa.core :as p]
  51. [logseq.common.path :as path]
  52. [medley.core :as medley]
  53. [frontend.handler.property.util :as pu]))
  54. ;; FIXME: add whiteboard
  55. (defn- get-directory
  56. [journal?]
  57. (if journal?
  58. (config/get-journals-directory)
  59. (config/get-pages-directory)))
  60. (defn- get-file-name
  61. [journal? title]
  62. (when-let [s (if journal?
  63. (date/journal-title->default title)
  64. ;; legacy in org-mode format, don't escape slashes except bug reported
  65. (gp-util/page-name-sanity (string/lower-case title)))]
  66. ;; Win10 file path has a length limit of 260 chars
  67. (gp-util/safe-subs s 0 200)))
  68. (defn- build-title [page]
  69. ;; Don't wrap `\"` anymore, as title property is not effected by `,` now
  70. ;; The previous extract behavior isn't unwrapping the `'"` either. So no need
  71. ;; to maintain the compatibility.
  72. (:block/original-name page))
  73. (defn default-properties-block
  74. ([title format page]
  75. (default-properties-block title format page {}))
  76. ([title format page properties]
  77. (let [repo (state/get-current-repo)
  78. db-based? (config/db-based-graph? repo)]
  79. (when-not db-based?
  80. (let [p (common-handler/get-page-default-properties title)
  81. ps (merge p properties)
  82. content (if db-based?
  83. ""
  84. (page-property/insert-properties format "" ps))
  85. refs (gp-block/get-page-refs-from-properties properties
  86. (db/get-db repo)
  87. (state/get-date-formatter)
  88. (state/get-config))]
  89. {:block/uuid (db/new-block-id)
  90. :block/refs refs
  91. :block/left page
  92. :block/format format
  93. :block/content content
  94. :block/parent page
  95. :block/page page
  96. :block/pre-block? true
  97. :block/properties ps
  98. :block/properties-order (keys ps)})))))
  99. (defn- create-title-property?
  100. [repo journal? page-name]
  101. (and (not (config/db-based-graph? repo))
  102. (not journal?)
  103. (= (state/get-filename-format) :legacy) ;; reduce title computation
  104. (fs-util/create-title-property? page-name)))
  105. (defn- build-page-tx [repo format properties page journal? {:keys [whiteboard? class? tags]}]
  106. (when (:block/uuid page)
  107. (let [page-entity [:block/uuid (:block/uuid page)]
  108. title (util/get-page-original-name page)
  109. create-title? (create-title-property? repo journal? title)
  110. page (merge page
  111. (when (seq properties) {:block/properties properties})
  112. (when whiteboard? {:block/type "whiteboard"})
  113. (when class? {:block/type "class"})
  114. (when tags {:block/tags (mapv #(hash-map :db/id
  115. (:db/id (db/entity repo [:block/uuid %])))
  116. tags)}))
  117. page-empty? (db/page-empty? (state/get-current-repo) (:block/name page))
  118. db-based? (config/db-based-graph? (state/get-current-repo))]
  119. (cond
  120. (not page-empty?)
  121. [page]
  122. (and create-title?
  123. (not whiteboard?)
  124. (not db-based?))
  125. (let [properties-block (default-properties-block (build-title page) format page-entity properties)]
  126. [page
  127. properties-block])
  128. (and (seq properties)
  129. (not whiteboard?)
  130. (not db-based?))
  131. [page (file-editor-handler/properties-block repo properties format page-entity)]
  132. :else
  133. [page]))))
  134. (defn create!
  135. "Create page. Has the following options:
  136. * :redirect? - when true, redirect to the created page, otherwise return sanitized page name.
  137. * :split-namespace? - when true, split hierarchical namespace into levels.
  138. * :create-first-block? - when true, create an empty block if the page is empty.
  139. * :uuid - when set, use this uuid instead of generating a new one.
  140. * :class? - when true, adds a :block/type 'class'
  141. * :whiteboard? - when true, adds a :block/type 'whiteboard'
  142. * :tags - tag uuids that are added to :block/tags
  143. * :persist-op? - when true, add an update-page op
  144. TODO: Add other options"
  145. ([title]
  146. (create! title {}))
  147. ([title {:keys [redirect? create-first-block? format properties split-namespace? journal? uuid rename? persist-op?]
  148. :or {redirect? true
  149. create-first-block? true
  150. rename? false
  151. format nil
  152. properties nil
  153. split-namespace? true
  154. uuid nil
  155. persist-op? true}
  156. :as options}]
  157. (let [title (-> (string/trim title)
  158. (text/page-ref-un-brackets!)
  159. ;; remove `#` from tags
  160. (string/replace #"^#+" ""))
  161. title (gp-util/remove-boundary-slashes title)
  162. page-name (util/page-name-sanity-lc title)
  163. repo (state/get-current-repo)
  164. with-uuid? (if (uuid? uuid) uuid true)] ;; FIXME: prettier validation
  165. (when (or (db/page-empty? repo page-name) rename?)
  166. (let [pages (if split-namespace?
  167. (gp-util/split-namespace-pages title)
  168. [title])
  169. format (or format (state/get-preferred-format))
  170. pages (map (fn [page]
  171. ;; only apply uuid to the deepest hierarchy of page to create if provided.
  172. (-> (block/page-name->map page (if (= page title) with-uuid? true))
  173. (assoc :block/format format)))
  174. pages)
  175. txs (->> pages
  176. ;; for namespace pages, only last page need properties
  177. drop-last
  178. (mapcat #(build-page-tx repo format nil % journal? {}))
  179. (remove nil?))
  180. txs (map-indexed (fn [i page]
  181. (if (zero? i)
  182. page
  183. (assoc page :block/namespace
  184. [:block/uuid (:block/uuid (nth txs (dec i)))])))
  185. txs)
  186. last-txs (build-page-tx repo format properties (last pages) journal? (select-keys options [:whiteboard? :class? :tags]))
  187. last-txs (if (seq txs)
  188. (update last-txs 0
  189. (fn [p]
  190. (assoc p :block/namespace [:block/uuid (:block/uuid (last txs))])))
  191. last-txs)
  192. txs (concat
  193. (when (and rename? uuid)
  194. (when-let [e (db/entity [:block/uuid uuid])]
  195. [[:db/retract (:db/id e) :block/namespace]
  196. [:db/retract (:db/id e) :block/refs]]))
  197. txs
  198. last-txs)]
  199. (when (seq txs)
  200. (db/transact! repo txs {:persist-op? persist-op?})))
  201. (when create-first-block?
  202. (when (or
  203. (db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
  204. (create-title-property? repo journal? page-name))
  205. (editor-handler/api-insert-new-block! "" {:page page-name}))))
  206. (when redirect?
  207. (route-handler/redirect-to-page! page-name))
  208. page-name)))
  209. (defn delete-file!
  210. [repo page-name unlink-file?]
  211. (let [file (db/get-page-file page-name)
  212. file-path (:file/path file)]
  213. ;; delete file
  214. (when-not (string/blank? file-path)
  215. (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
  216. (when unlink-file?
  217. (-> (fs/unlink! repo (config/get-repo-fpath repo file-path) nil)
  218. (p/catch (fn [error] (js/console.error error))))))))
  219. (defn- compute-new-file-path
  220. "Construct the full path given old full path and the file sanitized body.
  221. Ext. included in the `old-path`."
  222. [old-path new-file-name-body]
  223. (let [result (string/split old-path "/")
  224. ext (last (string/split (last result) "."))
  225. new-file (str new-file-name-body "." ext)
  226. parts (concat (butlast result) [new-file])]
  227. (util/string-join-path parts)))
  228. (defn rename-file!
  229. "emit file-rename events to :file/rename-event-chan
  230. force-fs? - when true, rename file event the db transact is failed."
  231. ([file new-file-name-body ok-handler]
  232. (rename-file! file new-file-name-body ok-handler false))
  233. ([file new-file-name-body ok-handler force-fs?]
  234. (let [repo (state/get-current-repo)
  235. file (db/pull (:db/id file))
  236. old-path (:file/path file)
  237. new-path (compute-new-file-path old-path new-file-name-body)
  238. transact #(db/transact! repo [{:db/id (:db/id file)
  239. :file/path new-path}])]
  240. ;; update db
  241. (if force-fs?
  242. (try (transact) ;; capture error and continue FS rename if failed
  243. (catch :default e
  244. (log/error :rename-file e)))
  245. (transact)) ;; interrupted if failed
  246. (->
  247. (p/let [_ (state/offer-file-rename-event-chan! {:repo repo
  248. :old-path old-path
  249. :new-path new-path})
  250. _ (fs/rename! repo old-path new-path)]
  251. (ok-handler))
  252. (p/catch (fn [error]
  253. (println "file rename failed: " error)))))))
  254. (defn- replace-page-ref!
  255. "Unsanitized names"
  256. [content old-name new-name]
  257. (let [[original-old-name original-new-name] (map string/trim [old-name new-name])
  258. [old-ref new-ref] (map page-ref/->page-ref [old-name new-name])
  259. [old-name new-name] (map #(if (string/includes? % "/")
  260. (string/replace % "/" ".")
  261. %)
  262. [original-old-name original-new-name])
  263. old-org-ref (and (= :org (state/get-preferred-format))
  264. (:org-mode/insert-file-link? (state/get-config))
  265. (re-find
  266. (re-pattern
  267. (util/format
  268. "\\[\\[file:\\.*/.*%s\\.org\\]\\[(.*?)\\]\\]" old-name))
  269. content))]
  270. (-> (if old-org-ref
  271. (let [[old-full-ref old-label] old-org-ref
  272. new-label (if (= old-label original-old-name)
  273. original-new-name
  274. old-label)
  275. new-full-ref (-> (string/replace old-full-ref old-name new-name)
  276. (string/replace (str "[" old-label "]")
  277. (str "[" new-label "]")))]
  278. (string/replace content old-full-ref new-full-ref))
  279. content)
  280. (string/replace old-ref new-ref))))
  281. (defn- replace-tag-ref!
  282. [content old-name new-name]
  283. (let [old-tag (util/format "#%s" old-name)
  284. new-tag (if (re-find #"[\s\t]+" new-name)
  285. (util/format "#[[%s]]" new-name)
  286. (str "#" new-name))]
  287. ;; hash tag parsing rules https://github.com/logseq/mldoc/blob/701243eaf9b4157348f235670718f6ad19ebe7f8/test/test_markdown.ml#L631
  288. ;; Safari doesn't support look behind, don't use
  289. ;; TODO: parse via mldoc
  290. (string/replace content
  291. (re-pattern (str "(?i)(^|\\s)(" (util/escape-regex-chars old-tag) ")(?=[,\\.]*($|\\s))"))
  292. ;; case_insense^ ^lhs ^_grp2 look_ahead^ ^_grp3
  293. (fn [[_match lhs _grp2 _grp3]]
  294. (str lhs new-tag)))))
  295. (defn- replace-property-ref!
  296. [content old-name new-name format]
  297. (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
  298. org-format? (= :org format)
  299. old-property (if org-format? (gp-property/colons-org old-name) (str old-name gp-property/colons))
  300. new-property (if org-format? (gp-property/colons-org (name new-name)) (str (name new-name) gp-property/colons))]
  301. (util/replace-ignore-case content old-property new-property)))
  302. (defn- replace-old-page!
  303. "Unsanitized names"
  304. [content old-name new-name format]
  305. (when (and (string? content) (string? old-name) (string? new-name))
  306. (-> content
  307. (replace-page-ref! old-name new-name)
  308. (replace-tag-ref! old-name new-name)
  309. (replace-property-ref! old-name new-name format))))
  310. (defn- walk-replace-old-page!
  311. "Unsanitized names"
  312. [form old-name new-name format]
  313. (walk/postwalk (fn [f]
  314. (cond
  315. (and (vector? f)
  316. (contains? #{"Search" "Label"} (first f))
  317. (string/starts-with? (second f) (str old-name "/")))
  318. [(first f) (string/replace-first (second f)
  319. (str old-name "/")
  320. (str new-name "/"))]
  321. (string? f)
  322. (if (= f old-name)
  323. new-name
  324. (replace-old-page! f old-name new-name format))
  325. (and (keyword f) (= (name f) old-name))
  326. (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
  327. :else
  328. f))
  329. form))
  330. (defn favorited?
  331. [page-name]
  332. (let [favorites (->> (:favorites (state/get-config))
  333. (filter string?)
  334. (map string/lower-case)
  335. (set))]
  336. (contains? favorites page-name)))
  337. (defn favorite-page!
  338. [page-name]
  339. (when-not (string/blank? page-name)
  340. (let [favorites (->
  341. (cons
  342. page-name
  343. (or (:favorites (state/get-config)) []))
  344. (distinct)
  345. (vec))]
  346. (config-handler/set-config! :favorites favorites))))
  347. (defn unfavorite-page!
  348. [page-name]
  349. (when-not (string/blank? page-name)
  350. (let [old-favorites (:favorites (state/get-config))
  351. new-favorites (->> old-favorites
  352. (remove #(= (string/lower-case %) (string/lower-case page-name)))
  353. (vec))]
  354. (when-not (= old-favorites new-favorites)
  355. (config-handler/set-config! :favorites new-favorites)))))
  356. (defn toggle-favorite! []
  357. ;; NOTE: in journals or settings, current-page is nil
  358. (when-let [page-name (state/get-current-page)]
  359. (let [favorites (:favorites (state/sub-config))
  360. favorited? (contains? (set (map string/lower-case favorites))
  361. (string/lower-case page-name))]
  362. (if favorited?
  363. (unfavorite-page! page-name)
  364. (favorite-page! page-name)))))
  365. (defn db-refs->page
  366. "Replace [[page name]] with page name"
  367. [repo page-entity]
  368. (when (config/db-based-graph? repo)
  369. (let [refs (:block/_refs page-entity)
  370. id-ref->page #(db-utils/special-id-ref->page % [page-entity])]
  371. (when (seq refs)
  372. (let [tx-data (mapcat (fn [{:block/keys [raw-content properties] :as ref}]
  373. ;; block content or properties
  374. (let [content' (id-ref->page raw-content)
  375. content-tx (when (not= raw-content content')
  376. {:db/id (:db/id ref)
  377. :block/content content'})
  378. page-uuid (:block/uuid page-entity)
  379. properties' (-> (medley/map-vals (fn [v]
  380. (cond
  381. (and (coll? v) (uuid? (first v)))
  382. (vec (remove #{page-uuid} v))
  383. (and (uuid? v) (= v page-uuid))
  384. nil
  385. (and (coll? v) (string? (first v)))
  386. (mapv id-ref->page v)
  387. (string? v)
  388. (id-ref->page v)
  389. :else
  390. v)) properties)
  391. (util/remove-nils-non-nested))
  392. tx (merge
  393. content-tx
  394. (when (not= (seq properties) (seq properties'))
  395. {:db/id (:db/id ref)
  396. :block/properties properties'}))]
  397. (concat
  398. [[:db/retract (:db/id ref) :block/refs (:db/id page-entity)]]
  399. (when tx [tx])))) refs)]
  400. tx-data)))))
  401. (defn db-replace-ref
  402. "Replace from-page refs with to-page"
  403. [repo from-page to-page]
  404. (when (config/db-based-graph? repo)
  405. (let [refs (:block/_refs from-page)
  406. from-uuid (:block/uuid from-page)
  407. to-uuid (:block/uuid to-page)
  408. replace-ref (fn [content] (string/replace content (str from-uuid) (str to-uuid)))]
  409. (when (seq refs)
  410. (let [tx-data (mapcat
  411. (fn [{:block/keys [raw-content properties] :as ref}]
  412. ;; block content or properties
  413. (let [content' (replace-ref raw-content)
  414. content-tx (when (not= raw-content content')
  415. {:db/id (:db/id ref)
  416. :block/content content'})
  417. properties' (-> (medley/map-vals (fn [v]
  418. (cond
  419. (and (coll? v) (uuid? (first v)))
  420. (mapv (fn [id] (if (= id from-uuid) to-uuid id)) v)
  421. (and (uuid? v) (= v from-uuid))
  422. to-uuid
  423. (and (coll? v) (string? (first v)))
  424. (mapv replace-ref v)
  425. (string? v)
  426. (replace-ref v)
  427. :else
  428. v)) properties)
  429. (util/remove-nils-non-nested))
  430. tx (merge
  431. content-tx
  432. (when (not= (seq properties) (seq properties'))
  433. {:db/id (:db/id ref)
  434. :block/properties properties'}))]
  435. (concat
  436. [[:db/add (:db/id ref) :block/refs (:db/id to-page)]
  437. [:db/retract (:db/id ref) :block/refs (:db/id from-page)]]
  438. (when tx [tx]))))
  439. refs)]
  440. tx-data)))))
  441. (defn- page-unable-to-delete
  442. "If a page is unable to delete, returns a map with more information. Otherwise returns nil"
  443. [repo page]
  444. (try
  445. (cond
  446. (and (contains? (:block/type page) "class")
  447. (seq (model/get-tag-blocks repo (:block/name page))))
  448. {:msg "Unable to delete this page because blocks are tagged with this page"}
  449. (contains? (:block/type page) "property")
  450. (cond (seq (model/get-classes-with-property (:block/uuid page)))
  451. {:msg "Unable to delete this page because classes use this property"}
  452. (->> (model/get-block-property-values (:block/uuid page))
  453. (filter (fn [[_ v]] (if (seq? v) (seq v) (some? v))))
  454. seq)
  455. {:msg "Unable to delete this page because blocks use this property"}))
  456. (catch :default e
  457. (log/error :exception e)
  458. (state/pub-event! [:capture-error {:error e}])
  459. {:msg (str "An unexpected failure while deleting: " e)})))
  460. (defn delete!
  461. "Deletes a page and then either calls the ok-handler or the error-handler if unable to delete"
  462. [page-name ok-handler & {:keys [delete-file? redirect-to-home? persist-op? error-handler]
  463. :or {delete-file? true
  464. redirect-to-home? true
  465. persist-op? true
  466. error-handler (fn [{:keys [msg]}] (log/error :msg msg))}}]
  467. (when redirect-to-home? (route-handler/redirect-to-home!))
  468. (when page-name
  469. (when-let [repo (state/get-current-repo)]
  470. (let [page-name (util/page-name-sanity-lc page-name)
  471. blocks (db/get-page-blocks-no-cache page-name)
  472. truncate-blocks-tx-data (mapv
  473. (fn [block]
  474. [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
  475. blocks)
  476. page (db/entity [:block/name page-name])]
  477. (if-let [msg (and (config/db-based-graph? repo)
  478. (page-unable-to-delete repo page))]
  479. (error-handler msg)
  480. (let [_ (delete-file! repo page-name delete-file?)
  481. ;; if other page alias this pagename,
  482. ;; then just remove some attrs of this entity instead of retractEntity
  483. delete-page-tx (cond
  484. (not (:block/_namespace page))
  485. (if (model/get-alias-source-page (state/get-current-repo) page-name)
  486. (when-let [id (:db/id (db/entity [:block/name page-name]))]
  487. (mapv (fn [attribute]
  488. [:db/retract id attribute])
  489. db-schema/retract-page-attributes))
  490. (concat (db-refs->page repo page)
  491. [[:db.fn/retractEntity [:block/name page-name]]]))
  492. :else
  493. nil)
  494. tx-data (concat truncate-blocks-tx-data delete-page-tx)]
  495. (db/transact! repo tx-data {:outliner-op :delete-page :persist-op? persist-op?})
  496. (unfavorite-page! page-name)
  497. (when (fn? ok-handler) (ok-handler))
  498. (ui-handler/re-render-root!)))))))
  499. (defn- rename-update-block-refs!
  500. [refs from-id to-id]
  501. (->> refs
  502. (remove #{{:db/id from-id}})
  503. (cons {:db/id to-id})
  504. (distinct)
  505. (vec)))
  506. (defn- rename-update-refs!
  507. "Unsanitized only"
  508. [page old-original-name new-name]
  509. ;; update all pages which have references to this page
  510. (let [repo (state/get-current-repo)
  511. to-page (db/entity [:block/name (util/page-name-sanity-lc new-name)])
  512. blocks (:block/_refs (db/entity (:db/id page)))
  513. page-ids (->> (map (fn [b]
  514. {:db/id (:db/id (:block/page b))}) blocks)
  515. (set))
  516. tx (->> (map (fn [{:block/keys [uuid content properties format] :as block}]
  517. (let [content (let [content' (replace-old-page! content old-original-name new-name format)]
  518. (when-not (= content' content)
  519. content'))
  520. properties (let [properties' (walk-replace-old-page! properties old-original-name new-name format)]
  521. (when-not (= properties' properties)
  522. properties'))]
  523. (when (or content properties)
  524. (util/remove-nils-non-nested
  525. {:block/uuid uuid
  526. :block/content content
  527. :block/properties properties
  528. :block/properties-order (when (seq properties)
  529. (map first properties))
  530. :block/refs (->> (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))
  531. (map :db/id)
  532. (set))})))) blocks)
  533. (remove nil?))]
  534. (db/transact! repo tx)
  535. (doseq [page-id page-ids]
  536. (outliner-file/sync-to-file page-id))))
  537. (defn- rename-update-namespace!
  538. "update :block/namespace of the renamed block"
  539. [page old-original-name new-name]
  540. (let [old-namespace? (text/namespace-page? old-original-name)
  541. new-namespace? (text/namespace-page? new-name)
  542. repo (state/get-current-repo)]
  543. (cond
  544. new-namespace?
  545. ;; update namespace
  546. (let [namespace (first (gp-util/split-last "/" new-name))]
  547. (when namespace
  548. (create! namespace {:redirect? false}) ;; create parent page if not exist, creation of namespace ref is handled in `create!`
  549. (let [namespace-block (db/pull [:block/name (gp-util/page-name-sanity-lc namespace)])
  550. page-txs [{:db/id (:db/id page)
  551. :block/namespace (:db/id namespace-block)}]]
  552. (db/transact! repo page-txs))))
  553. old-namespace?
  554. ;; retract namespace
  555. (db/transact! [[:db/retract (:db/id page) :block/namespace]])
  556. :else
  557. nil)))
  558. (defn- rename-page-aux
  559. "Only accepts unsanitized page names"
  560. [old-name new-name redirect?]
  561. (let [old-page-name (util/page-name-sanity-lc old-name)
  562. new-file-name-body (fs-util/file-name-sanity new-name) ;; w/o file extension
  563. new-page-name (util/page-name-sanity-lc new-name)
  564. repo (state/get-current-repo)
  565. page (db/pull [:block/name old-page-name])]
  566. (when (and repo page)
  567. (let [old-original-name (:block/original-name page)
  568. file (:block/file page)
  569. journal? (:block/journal? page)
  570. properties-block (:data (outliner-tree/-get-down (outliner-core/block page)))
  571. properties-content (:block/content properties-block)
  572. properties-block-tx (when (and properties-block
  573. properties-content
  574. (string/includes? (util/page-name-sanity-lc properties-content)
  575. old-page-name))
  576. (let [front-matter? (and (file-property/front-matter?-when-file-based properties-content)
  577. (= :markdown (:block/format properties-block)))]
  578. {:db/id (:db/id properties-block)
  579. :block/content (file-property/insert-property
  580. (:block/format properties-block)
  581. properties-content
  582. :title
  583. new-name
  584. front-matter?)}))
  585. page-txs [{:db/id (:db/id page)
  586. :block/uuid (:block/uuid page)
  587. :block/name new-page-name
  588. :block/original-name new-name}]
  589. page-txs (if properties-block-tx (conj page-txs properties-block-tx) page-txs)]
  590. (db/transact! repo page-txs)
  591. (when (and (not (config/db-based-graph? repo))
  592. (fs-util/create-title-property? new-page-name))
  593. (page-property/add-property! new-page-name :title new-name))
  594. (when (and file (not journal?))
  595. (rename-file! file new-file-name-body (fn [] nil)))
  596. (let [home (get (state/get-config) :default-home {})]
  597. (when (= old-page-name (util/page-name-sanity-lc (get home :page "")))
  598. (config-handler/set-config! :default-home (assoc home :page new-name))))
  599. (rename-update-refs! page old-original-name new-name)
  600. (rename-update-namespace! page old-original-name new-name)
  601. (outliner-file/sync-to-file page))
  602. ;; Redirect to the newly renamed page
  603. (when redirect?
  604. (route-handler/redirect! {:to (if (model/whiteboard-page? page) :whiteboard :page)
  605. :push false
  606. :path-params {:name new-page-name}}))
  607. (when (favorited? old-page-name)
  608. (p/do!
  609. (unfavorite-page! old-page-name)
  610. (favorite-page! new-page-name)))
  611. (recent-handler/update-or-add-renamed-page repo old-page-name new-page-name)
  612. (ui-handler/re-render-root!))))
  613. (defn- rename-nested-pages
  614. "Unsanitized names only"
  615. [old-ns-name new-ns-name]
  616. (let [repo (state/get-current-repo)
  617. nested-page-str (page-ref/->page-ref (util/page-name-sanity-lc old-ns-name))
  618. ns-prefix-format-str (str page-ref/left-brackets "%s/")
  619. ns-prefix (util/format ns-prefix-format-str (util/page-name-sanity-lc old-ns-name))
  620. nested-pages (db/get-pages-by-name-partition repo nested-page-str)
  621. nested-pages-ns (db/get-pages-by-name-partition repo ns-prefix)]
  622. (when nested-pages
  623. ;; rename page "[[obsidian]] is a tool" to "[[logseq]] is a tool"
  624. (doseq [{:block/keys [name original-name]} nested-pages]
  625. (let [old-page-title (or original-name name)
  626. new-page-title (string/replace
  627. old-page-title
  628. (page-ref/->page-ref old-ns-name)
  629. (page-ref/->page-ref new-ns-name))]
  630. (when (and old-page-title new-page-title)
  631. (p/do!
  632. (rename-page-aux old-page-title new-page-title false)
  633. (println "Renamed " old-page-title " to " new-page-title))))))
  634. (when nested-pages-ns
  635. ;; rename page "[[obsidian/page1]] is a tool" to "[[logseq/page1]] is a tool"
  636. (doseq [{:block/keys [name original-name]} nested-pages-ns]
  637. (let [old-page-title (or original-name name)
  638. new-page-title (string/replace
  639. old-page-title
  640. (util/format ns-prefix-format-str old-ns-name)
  641. (util/format ns-prefix-format-str new-ns-name))]
  642. (when (and old-page-title new-page-title)
  643. (p/do!
  644. (rename-page-aux old-page-title new-page-title false)
  645. (println "Renamed " old-page-title " to " new-page-title))))))))
  646. (defn- rename-namespace-pages!
  647. "Original names (unsanitized only)"
  648. [repo old-name new-name]
  649. (let [pages (db/get-namespace-pages repo old-name)
  650. page (db/pull [:block/name (util/page-name-sanity-lc old-name)])
  651. pages (cons page pages)]
  652. (doseq [{:block/keys [name original-name]} pages]
  653. (let [old-page-title (or original-name name)
  654. ;; only replace one time, for the case that the namespace is a sub-string of the sub-namespace page name
  655. ;; Example: has pages [[work]] [[work/worklog]],
  656. ;; we want to rename [[work/worklog]] to [[work1/worklog]] when rename [[work]] to [[work1]],
  657. ;; but don't rename [[work/worklog]] to [[work1/work1log]]
  658. new-page-title (string/replace-first old-page-title old-name new-name)
  659. redirect? (= name (:block/name page))]
  660. (when (and old-page-title new-page-title)
  661. (p/let [_ (rename-page-aux old-page-title new-page-title redirect?)]
  662. (println "Renamed " old-page-title " to " new-page-title)))))))
  663. (defn file-based-merge-pages!
  664. "Only accepts sanitized page names"
  665. [from-page-name to-page-name]
  666. (when (and (db/page-exists? from-page-name)
  667. (db/page-exists? to-page-name)
  668. (not= from-page-name to-page-name))
  669. (let [to-page (db/entity [:block/name to-page-name])
  670. to-id (:db/id to-page)
  671. from-page (db/entity [:block/name from-page-name])
  672. from-id (:db/id from-page)
  673. from-first-child (some->> (db/pull from-id)
  674. (outliner-core/block)
  675. (outliner-tree/-get-down)
  676. (outliner-core/get-data))
  677. to-last-direct-child-id (model/get-block-last-direct-child (db/get-db) to-id false)
  678. repo (state/get-current-repo)
  679. conn (conn/get-db repo false)
  680. datoms (d/datoms @conn :avet :block/page from-id)
  681. block-eids (mapv :e datoms)
  682. blocks (db-utils/pull-many repo '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
  683. tx-data (map (fn [block]
  684. (let [id (:db/id block)]
  685. (cond->
  686. {:db/id id
  687. :block/page {:db/id to-id}
  688. :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)}
  689. (and from-first-child (= id (:db/id from-first-child)))
  690. (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
  691. (= (:block/parent block) {:db/id from-id})
  692. (assoc :block/parent {:db/id to-id})))) blocks)]
  693. (db/transact! repo tx-data)
  694. (outliner-file/sync-to-file {:db/id to-id})
  695. (rename-update-refs! from-page
  696. (util/get-page-original-name from-page)
  697. (util/get-page-original-name to-page))
  698. (rename-update-namespace! from-page
  699. (util/get-page-original-name from-page)
  700. (util/get-page-original-name to-page)))
  701. (delete! from-page-name nil)
  702. (route-handler/redirect! {:to :page
  703. :push false
  704. :path-params {:name to-page-name}})))
  705. (defn db-based-merge-pages!
  706. [from-page-name to-page-name persist-op?]
  707. (when (and (db/page-exists? from-page-name)
  708. (db/page-exists? to-page-name)
  709. (not= from-page-name to-page-name))
  710. (let [to-page (db/entity [:block/name to-page-name])
  711. to-id (:db/id to-page)
  712. from-page (db/entity [:block/name from-page-name])
  713. from-id (:db/id from-page)
  714. from-first-child (some->> (db/pull from-id)
  715. (outliner-core/block)
  716. (outliner-tree/-get-down)
  717. (outliner-core/get-data))
  718. to-last-direct-child-id (model/get-block-last-direct-child (db/get-db) to-id false)
  719. repo (state/get-current-repo)
  720. conn (conn/get-db repo false)
  721. datoms (d/datoms @conn :avet :block/page from-id)
  722. block-eids (mapv :e datoms)
  723. blocks (db-utils/pull-many repo '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
  724. blocks-tx-data (map (fn [block]
  725. (let [id (:db/id block)]
  726. (cond->
  727. {:db/id id
  728. :block/page {:db/id to-id}}
  729. (and from-first-child (= id (:db/id from-first-child)))
  730. (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
  731. (= (:block/parent block) {:db/id from-id})
  732. (assoc :block/parent {:db/id to-id})))) blocks)
  733. replace-ref-tx-data (db-replace-ref repo from-page to-page)
  734. tx-data (concat blocks-tx-data replace-ref-tx-data)]
  735. (db/transact! repo tx-data {:persist-op? persist-op?})
  736. (rename-update-namespace! from-page
  737. (util/get-page-original-name from-page)
  738. (util/get-page-original-name to-page)))
  739. (delete! from-page-name nil :redirect-to-home? false :persist-op? persist-op?)
  740. (route-handler/redirect! {:to :page
  741. :push false
  742. :path-params {:name to-page-name}})))
  743. (defn db-based-rename!
  744. ([old-name new-name]
  745. (db-based-rename! old-name new-name true true))
  746. ([old-name new-name redirect? persist-op?]
  747. (let [repo (state/get-current-repo)
  748. old-name (string/trim old-name)
  749. new-name (string/trim new-name)
  750. old-page-name (util/page-name-sanity-lc old-name)
  751. page-e (db/entity [:block/name old-page-name])
  752. new-page-name (util/page-name-sanity-lc new-name)
  753. name-changed? (not= old-name new-name)]
  754. (if (and old-name
  755. new-name
  756. (not (string/blank? new-name))
  757. name-changed?)
  758. (cond
  759. (= old-page-name new-page-name) ; case changed
  760. (db/transact! repo
  761. [{:db/id (:db/id page-e)
  762. :block/original-name new-name}]
  763. {:persist-op? persist-op?})
  764. (and (not= old-page-name new-page-name)
  765. (db/entity [:block/name new-page-name])) ; merge page
  766. (db-based-merge-pages! old-page-name new-page-name persist-op?)
  767. :else ; rename
  768. (create! new-name
  769. {:rename? true
  770. :uuid (:block/uuid page-e)
  771. :redirect? redirect?
  772. :create-first-block? false
  773. :persist-op? persist-op?}))
  774. (when (string/blank? new-name)
  775. (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
  776. (ui-handler/re-render-root!))))
  777. (defn file-based-rename!
  778. "Accepts unsanitized page names"
  779. ([old-name new-name] (file-based-rename! old-name new-name true))
  780. ([old-name new-name redirect?]
  781. (let [repo (state/get-current-repo)
  782. old-name (string/trim old-name)
  783. new-name (string/trim new-name)
  784. old-page-name (util/page-name-sanity-lc old-name)
  785. new-page-name (util/page-name-sanity-lc new-name)
  786. name-changed? (not= old-name new-name)]
  787. (if (and old-name
  788. new-name
  789. (not (string/blank? new-name))
  790. name-changed?)
  791. (do
  792. (cond
  793. (= old-page-name new-page-name)
  794. (rename-page-aux old-name new-name redirect?)
  795. (db/pull [:block/name new-page-name])
  796. (file-based-merge-pages! old-page-name new-page-name)
  797. :else
  798. (rename-namespace-pages! repo old-name new-name))
  799. (rename-nested-pages old-name new-name))
  800. (when (string/blank? new-name)
  801. (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
  802. (ui-handler/re-render-root!))))
  803. (defn rename!
  804. ([old-name new-name] (rename! old-name new-name true))
  805. ([old-name new-name redirect?] (rename! old-name new-name redirect? true))
  806. ([old-name new-name redirect? persist-op?]
  807. (if (config/db-based-graph? (state/get-current-repo))
  808. (db-based-rename! old-name new-name redirect? persist-op?)
  809. (file-based-rename! old-name new-name redirect?))))
  810. (defn reorder-favorites!
  811. [{:keys [to up?]}]
  812. (let [target (get @state/state :favorites/dragging)
  813. favorites (->> (reorder-handler/reorder-items (:favorites (state/get-config))
  814. {:target target
  815. :to to
  816. :up? up?})
  817. (mapv util/safe-page-name-sanity-lc))]
  818. (config-handler/set-config! :favorites favorites)))
  819. (defn has-more-journals?
  820. []
  821. (let [current-length (:journals-length @state/state)]
  822. (< current-length (db/get-journals-length))))
  823. (defn load-more-journals!
  824. []
  825. (when (has-more-journals?)
  826. (state/set-journals-length! (+ (:journals-length @state/state) 7))))
  827. (defn update-public-attribute!
  828. [page-name value]
  829. (page-property/add-property! page-name :public value))
  830. (defn get-page-ref-text
  831. [page]
  832. (let [edit-block-file-path (model/get-block-file-path (state/get-edit-block))
  833. page-name (string/lower-case page)]
  834. (if (and edit-block-file-path
  835. (state/org-mode-file-link? (state/get-current-repo)))
  836. (if-let [ref-file-path (:file/path (db/get-page-file page-name))]
  837. (util/format "[[file:%s][%s]]"
  838. (util/get-relative-path edit-block-file-path ref-file-path)
  839. page)
  840. (let [journal? (date/valid-journal-title? page)
  841. ref-file-path (str
  842. (if (or (util/electron?) (mobile-util/native-platform?))
  843. (-> (config/get-repo-dir (state/get-current-repo))
  844. js/decodeURI
  845. (string/replace #"/+$" "")
  846. (str "/"))
  847. "")
  848. (get-directory journal?)
  849. "/"
  850. (get-file-name journal? page)
  851. ".org")]
  852. (create! page {:redirect? false})
  853. (util/format "[[file:%s][%s]]"
  854. (util/get-relative-path edit-block-file-path ref-file-path)
  855. page)))
  856. (page-ref/->page-ref page))))
  857. (defn init-commands!
  858. []
  859. (commands/init-commands! get-page-ref-text))
  860. (def rebuild-slash-commands-list!
  861. (debounce init-commands! 1500))
  862. (defn template-exists?
  863. [title]
  864. (when title
  865. (let [templates (keys (db/get-all-templates))]
  866. (when (seq templates)
  867. (let [templates (map string/lower-case templates)]
  868. (contains? (set templates) (string/lower-case title)))))))
  869. (defn ls-dir-files!
  870. ([ok-handler] (ls-dir-files! ok-handler nil))
  871. ([ok-handler opts]
  872. (web-nfs/ls-dir-files-with-handler!
  873. (fn [e]
  874. (init-commands!)
  875. (when ok-handler
  876. (ok-handler e)))
  877. opts)))
  878. (defn get-all-pages
  879. [repo]
  880. (->> (db/get-all-pages repo)
  881. (remove (fn [p]
  882. (let [name (:block/name p)]
  883. (or (util/uuid-string? name)
  884. (gp-config/draw? name)
  885. (db/built-in-pages-names (string/upper-case name))
  886. (db-property/built-in-properties-keys-str name)
  887. (contains? (:block/type p) "macro")))))
  888. (common-handler/fix-pages-timestamps)))
  889. (defn get-filters
  890. [page-name]
  891. (let [properties (db/get-page-properties page-name)]
  892. (if (config/db-based-graph? (state/get-current-repo))
  893. (pu/lookup properties :filters)
  894. (let [properties-str (or (:filters properties) "{}")]
  895. (try (reader/read-string properties-str)
  896. (catch :default e
  897. (log/error :syntax/filters e)))))))
  898. (defn save-filter!
  899. [page-name filter-state]
  900. (page-property/add-property! page-name :filters filter-state))
  901. ;; Editor
  902. (defn page-not-exists-handler
  903. [input id q current-pos]
  904. (state/clear-editor-action!)
  905. (if (state/org-mode-file-link? (state/get-current-repo))
  906. (let [page-ref-text (get-page-ref-text q)
  907. value (gobj/get input "value")
  908. old-page-ref (page-ref/->page-ref q)
  909. new-value (string/replace value
  910. old-page-ref
  911. page-ref-text)]
  912. (state/set-edit-content! id new-value)
  913. (let [new-pos (+ current-pos
  914. (- (count page-ref-text)
  915. (count old-page-ref))
  916. 2)]
  917. (cursor/move-cursor-to input new-pos)))
  918. (let [current-selected (util/get-selected-text)]
  919. (cursor/move-cursor-forward input (+ 2 (count current-selected))))))
  920. (defn on-chosen-handler
  921. [input id _q pos format]
  922. (let [current-pos (cursor/pos input)
  923. edit-content (state/sub :editor/content :path-in-sub-atom id)
  924. action (state/get-editor-action)
  925. hashtag? (= action :page-search-hashtag)
  926. q (or
  927. (editor-handler/get-selected-text)
  928. (when hashtag?
  929. (gp-util/safe-subs edit-content pos current-pos))
  930. (when (> (count edit-content) current-pos)
  931. (gp-util/safe-subs edit-content pos current-pos)))]
  932. (if hashtag?
  933. (fn [chosen e]
  934. (util/stop e)
  935. (state/clear-editor-action!)
  936. (let [wrapped? (= page-ref/left-brackets (gp-util/safe-subs edit-content (- pos 2) pos))
  937. chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
  938. (page-ref/->page-ref chosen)
  939. chosen)
  940. q (if (editor-handler/get-selected-text) "" q)
  941. last-pattern (if wrapped?
  942. q
  943. (if (= \# (first q))
  944. (subs q 1)
  945. q))
  946. last-pattern (str "#" (when wrapped? page-ref/left-brackets) last-pattern)]
  947. (editor-handler/insert-command! id
  948. (str "#" (when wrapped? page-ref/left-brackets) chosen)
  949. format
  950. {:last-pattern last-pattern
  951. :end-pattern (when wrapped? page-ref/right-brackets)
  952. :command :page-ref})
  953. (when input (.focus input))))
  954. (fn [chosen e]
  955. (util/stop e)
  956. (state/clear-editor-action!)
  957. (let [page-ref-text (get-page-ref-text chosen)]
  958. (editor-handler/insert-command! id
  959. page-ref-text
  960. format
  961. {:last-pattern (str page-ref/left-brackets (if (editor-handler/get-selected-text) "" q))
  962. :end-pattern page-ref/right-brackets
  963. :postfix-fn (fn [s] (util/replace-first page-ref/right-brackets s ""))
  964. :command :page-ref}))))))
  965. (defn create-today-journal!
  966. []
  967. (when-let [repo (state/get-current-repo)]
  968. (when (and (state/enable-journals? repo)
  969. (not (:graph/loading? @state/state))
  970. (not (state/loading-files? repo))
  971. (not (state/whiteboard-route?)))
  972. (state/set-today! (date/today))
  973. (when (or (config/db-based-graph? repo)
  974. (config/local-file-based-graph? repo)
  975. (and (= "local" repo) (not (mobile-util/native-platform?))))
  976. (let [title (date/today)
  977. today-page (util/page-name-sanity-lc title)
  978. format (state/get-preferred-format repo)
  979. template (state/get-default-journal-template)
  980. create-f (fn []
  981. (create! title {:redirect? false
  982. :split-namespace? false
  983. :create-first-block? (not template)
  984. :journal? true})
  985. (state/pub-event! [:journal/insert-template today-page])
  986. (ui-handler/re-render-root!)
  987. (plugin-handler/hook-plugin-app :today-journal-created {:title today-page}))]
  988. (when (db/page-empty? repo today-page)
  989. (if (config/db-based-graph? repo)
  990. (let [page-exists (db/get-page today-page)]
  991. (when-not page-exists
  992. (create-f)))
  993. (p/let [file-name (date/journal-title->default title)
  994. file-rpath (str (config/get-journals-directory) "/" file-name "."
  995. (config/get-file-extension format))
  996. repo-dir (config/get-repo-dir repo)
  997. file-exists? (fs/file-exists? repo-dir file-rpath)
  998. file-content (when file-exists?
  999. (fs/read-file repo-dir file-rpath))]
  1000. (when (or (not file-exists?)
  1001. (and file-exists? (string/blank? file-content)))
  1002. (create-f))))))))))
  1003. (defn open-today-in-sidebar
  1004. []
  1005. (when-let [page (db/entity [:block/name (util/page-name-sanity-lc (date/today))])]
  1006. (state/sidebar-add-block!
  1007. (state/get-current-repo)
  1008. (:db/id page)
  1009. :page)))
  1010. (defn open-file-in-default-app []
  1011. (if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
  1012. (let [repo-dir (config/get-repo-dir (state/get-current-repo))
  1013. file-fpath (path/path-join repo-dir file-rpath)]
  1014. (js/window.apis.openPath file-fpath))
  1015. (notification/show! "No file found" :warning)))
  1016. (defn copy-current-file
  1017. "FIXME: clarify usage, copy file or copy file path"
  1018. []
  1019. (if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
  1020. (let [repo-dir (config/get-repo-dir (state/get-current-repo))
  1021. file-fpath (path/path-join repo-dir file-rpath)]
  1022. (util/copy-to-clipboard! file-fpath))
  1023. (notification/show! "No file found" :warning)))
  1024. (defn open-file-in-directory []
  1025. (if-let [file-rpath (and (util/electron?) (page-util/get-page-file-rpath))]
  1026. (let [repo-dir (config/get-repo-dir (state/get-current-repo))
  1027. file-fpath (path/path-join repo-dir file-rpath)]
  1028. (js/window.apis.showItemInFolder file-fpath))
  1029. (notification/show! "No file found" :warning)))
  1030. (defn copy-page-url
  1031. ([] (copy-page-url (page-util/get-current-page-name)))
  1032. ([page-name]
  1033. (if page-name
  1034. (util/copy-to-clipboard!
  1035. (url-util/get-logseq-graph-page-url nil (state/get-current-repo) page-name))
  1036. (notification/show! "No page found to copy" :warning))))