commands.cljs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. (ns frontend.commands
  2. "Provides functionality for commands and advanced commands"
  3. (:require [clojure.string :as string]
  4. [frontend.config :as config]
  5. [frontend.date :as date]
  6. [frontend.db :as db]
  7. [frontend.db.utils :as db-util]
  8. [frontend.handler.draw :as draw]
  9. [frontend.handler.notification :as notification]
  10. [frontend.handler.plugin :as plugin-handler]
  11. [frontend.extensions.video.youtube :as youtube]
  12. [frontend.search :as search]
  13. [frontend.state :as state]
  14. [frontend.util :as util]
  15. [frontend.util.cursor :as cursor]
  16. [frontend.util.marker :as marker]
  17. [frontend.util.priority :as priority]
  18. [frontend.util.property :as property]
  19. [logseq.graph-parser.util :as gp-util]
  20. [logseq.graph-parser.config :as gp-config]
  21. [logseq.graph-parser.property :as gp-property]
  22. [logseq.graph-parser.util.page-ref :as page-ref]
  23. [logseq.graph-parser.util.block-ref :as block-ref]
  24. [goog.dom :as gdom]
  25. [goog.object :as gobj]
  26. [promesa.core :as p]))
  27. ;; TODO: move to frontend.handler.editor.commands
  28. (defonce angle-bracket "<")
  29. (defonce colon ":")
  30. (defonce *current-command (atom nil))
  31. (def query-doc
  32. [:div {:on-mouse-down (fn [e] (.stopPropagation e))}
  33. [:div.font-medium.text-lg.mb-2 "Query examples:"]
  34. [:ul.mb-1
  35. [:li.mb-1 [:code "{{query #tag}}"]]
  36. [:li.mb-1 [:code "{{query [[page]]}}"]]
  37. [:li.mb-1 [:code "{{query \"full-text search\"}}"]]
  38. [:li.mb-1 [:code "{{query (and [[project]] (task NOW LATER))}}"]]
  39. [:li.mb-1 [:code "{{query (or [[page 1]] [[page 2]])}}"]]
  40. [:li.mb-1 [:code "{{query (and (between -7d +7d) (task DONE))}}"]]
  41. [:li.mb-1 [:code "{{query (property key value)}}"]]
  42. [:li.mb-1 [:code "{{query (page-tags #tag)}}"]]]
  43. [:p "Check more examples at "
  44. [:a {:href "https://docs.logseq.com/#/page/queries"
  45. :target "_blank"}
  46. "Queries documentation"]
  47. "."]])
  48. (defn link-steps []
  49. [[:editor/input (str (state/get-editor-command-trigger) "link")]
  50. [:editor/show-input [{:command :link
  51. :id :link
  52. :placeholder "Link"
  53. :autoFocus true}
  54. {:command :link
  55. :id :label
  56. :placeholder "Label"}]]])
  57. (defn image-link-steps []
  58. [[:editor/input (str (state/get-editor-command-trigger) "link")]
  59. [:editor/show-input [{:command :image-link
  60. :id :link
  61. :placeholder "Link"
  62. :autoFocus true}
  63. {:command :image-link
  64. :id :label
  65. :placeholder "Label"}]]])
  66. (defn zotero-steps []
  67. [[:editor/input (str (state/get-editor-command-trigger) "zotero")]
  68. [:editor/show-zotero]])
  69. (def *extend-slash-commands (atom []))
  70. (defn register-slash-command [cmd]
  71. (swap! *extend-slash-commands conj cmd))
  72. (defn ->marker
  73. [marker]
  74. [[:editor/clear-current-slash]
  75. [:editor/set-marker marker]
  76. [:editor/move-cursor-to-end]])
  77. (defn ->priority
  78. [priority]
  79. [[:editor/clear-current-slash]
  80. [:editor/set-priority priority]
  81. [:editor/move-cursor-to-end]])
  82. (defn ->inline
  83. [type]
  84. (let [template (util/format "@@%s: @@"
  85. type)]
  86. [[:editor/input template {:last-pattern (state/get-editor-command-trigger)
  87. :backward-pos 2}]]))
  88. (defn embed-page
  89. []
  90. (conj
  91. [[:editor/input "{{embed [[]]}}" {:last-pattern (state/get-editor-command-trigger)
  92. :backward-pos 4}]]
  93. [:editor/search-page :embed]))
  94. (defn embed-block
  95. []
  96. [[:editor/input "{{embed (())}}" {:last-pattern (state/get-editor-command-trigger)
  97. :backward-pos 4}]
  98. [:editor/search-block :embed]])
  99. (defn get-preferred-workflow
  100. []
  101. (let [workflow (state/get-preferred-workflow)]
  102. (if (= :now workflow)
  103. [["LATER" (->marker "LATER")]
  104. ["NOW" (->marker "NOW")]
  105. ["TODO" (->marker "TODO")]
  106. ["DOING" (->marker "DOING")]]
  107. [["TODO" (->marker "TODO")]
  108. ["DOING" (->marker "DOING")]
  109. ["LATER" (->marker "LATER")]
  110. ["NOW" (->marker "NOW")]])))
  111. ;; Credits to roamresearch.com
  112. (defn- ->heading
  113. [heading]
  114. [[:editor/clear-current-slash]
  115. [:editor/set-heading heading]
  116. [:editor/move-cursor-to-end]])
  117. (defn- headings
  118. []
  119. (mapv (fn [level]
  120. (let [heading (str "h" level)]
  121. [heading (->heading level)])) (range 1 7)))
  122. (defonce *matched-commands (atom nil))
  123. (defonce *initial-commands (atom nil))
  124. (defonce *first-command-group
  125. {"Page reference" "BASIC"
  126. "Tomorrow" "TIME & DATE"
  127. "LATER" "TASK"
  128. "A" "PRIORITY"
  129. "Query" "ADVANCED"
  130. "Quote" "ORG-MODE"})
  131. (defn ->block
  132. ([type]
  133. (->block type nil))
  134. ([type optional]
  135. (let [format (get (state/get-edit-block) :block/format)
  136. markdown-src? (and (= format :markdown)
  137. (= (string/lower-case type) "src"))
  138. [left right] (cond
  139. markdown-src?
  140. ["```" "\n```"]
  141. :else
  142. (->> ["#+BEGIN_%s" "\n#+END_%s"]
  143. (map #(util/format %
  144. (string/upper-case type)))))
  145. template (str
  146. left
  147. (if optional (str " " optional) "")
  148. "\n"
  149. right)
  150. backward-pos (if (= type "src")
  151. (+ 1 (count right))
  152. (count right))]
  153. [[:editor/input template {:type "block"
  154. :last-pattern angle-bracket
  155. :backward-pos backward-pos}]])))
  156. (defn ->properties
  157. []
  158. [[:editor/clear-current-bracket]
  159. [:editor/insert-properties]
  160. [:editor/move-cursor-to-properties]])
  161. ;; https://orgmode.org/manual/Structure-Templates.html
  162. (defn block-commands-map
  163. []
  164. (->>
  165. (concat
  166. [["Quote" (->block "quote")]
  167. ["Src" (->block "src" "")]
  168. ["Query" (->block "query")]
  169. ["Latex export" (->block "export" "latex")]
  170. ;; FIXME: current page's format
  171. (when (= :org (state/get-preferred-format))
  172. ["Properties" (->properties)])
  173. ["Note" (->block "note")]
  174. ["Tip" (->block "tip")]
  175. ["Important" (->block "important")]
  176. ["Caution" (->block "caution")]
  177. ["Pinned" (->block "pinned")]
  178. ["Warning" (->block "warning")]
  179. ["Example" (->block "example")]
  180. ["Export" (->block "export")]
  181. ["Verse" (->block "verse")]
  182. ["Ascii" (->block "export" "ascii")]
  183. ["Center" (->block "center")]
  184. ["Comment" (->block "comment")]]
  185. ;; Allow user to modify or extend, should specify how to extend.
  186. (state/get-commands))
  187. (remove nil?)
  188. (util/distinct-by-last-wins first)))
  189. (defn commands-map
  190. [get-page-ref-text]
  191. (->>
  192. (concat
  193. ;; basic
  194. [["Page reference" [[:editor/input page-ref/left-and-right-brackets {:backward-pos 2}]
  195. [:editor/search-page]] "Create a backlink to a page"]
  196. ["Page embed" (embed-page) "Embed a page here"]
  197. ["Block reference" [[:editor/input block-ref/left-and-right-parens {:backward-pos 2}]
  198. [:editor/search-block :reference]] "Create a backlink to a block"]
  199. ["Block embed" (embed-block) "Embed a block here" "Embed a block here"]
  200. ["Link" (link-steps) "Create a HTTP link"]
  201. ["Image link" (image-link-steps) "Create a HTTP link to a image"]
  202. (when (state/markdown?)
  203. ["Underline" [[:editor/input "<ins></ins>"
  204. {:last-pattern (state/get-editor-command-trigger)
  205. :backward-pos 6}]] "Create a underline text decoration"])
  206. ["Template" [[:editor/input (state/get-editor-command-trigger) nil]
  207. [:editor/search-template]] "Insert a created template here"]
  208. (cond
  209. (and (util/electron?) (config/local-db? (state/get-current-repo)))
  210. ["Upload an asset" [[:editor/click-hidden-file-input :id]] "Upload file types like image, pdf, docx, etc.)"]
  211. ;; ["Upload an image" [[:editor/click-hidden-file-input :id]]]
  212. )]
  213. (headings)
  214. ;; time & date
  215. [["Tomorrow" #(get-page-ref-text (date/tomorrow)) "Insert the date of tomorrow"]
  216. ["Yesterday" #(get-page-ref-text (date/yesterday)) "Insert the date of yesterday"]
  217. ["Today" #(get-page-ref-text (date/today)) "Insert the date of today"]
  218. ["Current time" #(date/get-current-time) "Insert current time"]
  219. ["Date picker" [[:editor/show-date-picker]] "Pick a date and insert here"]]
  220. ;; task management
  221. (get-preferred-workflow)
  222. [["DONE" (->marker "DONE")]
  223. ["WAITING" (->marker "WAITING")]
  224. ["CANCELED" (->marker "CANCELED")]
  225. ["Deadline" [[:editor/clear-current-slash]
  226. [:editor/show-date-picker :deadline]]]
  227. ["Scheduled" [[:editor/clear-current-slash]
  228. [:editor/show-date-picker :scheduled]]]]
  229. ;; priority
  230. [["A" (->priority "A")]
  231. ["B" (->priority "B")]
  232. ["C" (->priority "C")]]
  233. ;; advanced
  234. [["Query" [[:editor/input "{{query }}" {:backward-pos 2}]] query-doc]
  235. ["Zotero" (zotero-steps) "Import Zotero journal article"]
  236. ["Query table function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query table function"]
  237. ["Calculator" [[:editor/input "```calc\n\n```" {:backward-pos 4}]
  238. [:codemirror/focus]] "Insert a calculator"]
  239. ["Draw" (fn []
  240. (let [file (draw/file-name)
  241. path (str gp-config/default-draw-directory "/" file)
  242. text (page-ref/->page-ref path)]
  243. (p/let [_ (draw/create-draw-with-default-content path)]
  244. (println "draw file created, " path))
  245. text)) "Draw a graph with Excalidraw"]
  246. ["Embed HTML " (->inline "html")]
  247. ["Embed Video URL" [[:editor/input "{{video }}" {:last-pattern (state/get-editor-command-trigger)
  248. :backward-pos 2}]]]
  249. ["Embed Youtube timestamp" [[:youtube/insert-timestamp]]]
  250. ["Embed Twitter tweet" [[:editor/input "{{tweet }}" {:last-pattern (state/get-editor-command-trigger)
  251. :backward-pos 2}]]]]
  252. @*extend-slash-commands
  253. ;; Allow user to modify or extend, should specify how to extend.
  254. (state/get-commands)
  255. (state/get-plugins-commands))
  256. (remove nil?)
  257. (util/distinct-by-last-wins first)))
  258. (defn init-commands!
  259. [get-page-ref-text]
  260. (let [commands (commands-map get-page-ref-text)]
  261. (reset! *initial-commands commands)
  262. (reset! *matched-commands commands)))
  263. (defonce *matched-block-commands (atom (block-commands-map)))
  264. (defn reinit-matched-commands!
  265. []
  266. (reset! *matched-commands @*initial-commands))
  267. (defn reinit-matched-block-commands!
  268. []
  269. (reset! *matched-block-commands (block-commands-map)))
  270. (defn restore-state
  271. []
  272. (state/clear-editor-action!)
  273. (reinit-matched-commands!)
  274. (reinit-matched-block-commands!))
  275. (defn insert!
  276. [id value
  277. {:keys [last-pattern postfix-fn backward-pos forward-pos end-pattern backward-truncate-number]
  278. :as _option}]
  279. (when-let [input (gdom/getElement id)]
  280. (let [last-pattern (when-not backward-truncate-number
  281. (or last-pattern (state/get-editor-command-trigger)))
  282. edit-content (gobj/get input "value")
  283. current-pos (cursor/pos input)
  284. current-pos (or
  285. (when (and end-pattern (string? end-pattern))
  286. (when-let [i (string/index-of (gp-util/safe-subs edit-content current-pos) end-pattern)]
  287. (+ current-pos i)))
  288. current-pos)
  289. orig-prefix (subs edit-content 0 current-pos)
  290. space? (let [space? (when (and last-pattern orig-prefix)
  291. (let [s (when-let [last-index (string/last-index-of orig-prefix last-pattern)]
  292. (gp-util/safe-subs orig-prefix 0 last-index))]
  293. (not
  294. (or
  295. (and s
  296. (string/ends-with? s "(")
  297. (or (string/starts-with? last-pattern block-ref/left-parens)
  298. (string/starts-with? last-pattern page-ref/left-brackets)))
  299. (and s (string/starts-with? s "{{embed"))
  300. (and s (= (last s) \#) (string/starts-with? last-pattern "[["))
  301. (and last-pattern
  302. (or (string/ends-with? last-pattern gp-property/colons)
  303. (string/starts-with? last-pattern gp-property/colons)))))))]
  304. (if (and space? (string/starts-with? last-pattern "#[["))
  305. false
  306. space?))
  307. prefix (cond
  308. (and backward-truncate-number (integer? backward-truncate-number))
  309. (str (gp-util/safe-subs orig-prefix 0 (- (count orig-prefix) backward-truncate-number))
  310. (when-not (zero? backward-truncate-number)
  311. value))
  312. (string/blank? last-pattern)
  313. (if space?
  314. (util/concat-without-spaces orig-prefix value)
  315. (str orig-prefix value))
  316. :else
  317. (util/replace-last last-pattern orig-prefix value space?))
  318. postfix (subs edit-content current-pos)
  319. postfix (if postfix-fn (postfix-fn postfix) postfix)
  320. new-value (cond
  321. (string/blank? postfix)
  322. prefix
  323. space?
  324. (util/concat-without-spaces prefix postfix)
  325. :else
  326. (str prefix postfix))
  327. new-pos (- (count prefix)
  328. (or backward-pos 0))]
  329. (when-not (string/blank? new-value)
  330. (state/set-block-content-and-last-pos! id new-value new-pos)
  331. (cursor/move-cursor-to input
  332. (if (and (or backward-pos forward-pos)
  333. (not= end-pattern page-ref/right-brackets))
  334. new-pos
  335. (inc new-pos)))))))
  336. (defn simple-insert!
  337. [id value
  338. {:keys [backward-pos forward-pos check-fn]
  339. :as _option}]
  340. (let [input (gdom/getElement id)
  341. edit-content (gobj/get input "value")
  342. current-pos (cursor/pos input)
  343. prefix (subs edit-content 0 current-pos)
  344. new-value (str prefix
  345. value
  346. (subs edit-content current-pos))
  347. new-pos (- (+ (count prefix)
  348. (count value)
  349. (or forward-pos 0))
  350. (or backward-pos 0))]
  351. (state/set-block-content-and-last-pos! id new-value new-pos)
  352. (cursor/move-cursor-to input new-pos)
  353. (when check-fn
  354. (check-fn new-value (dec (count prefix)) new-pos))))
  355. (defn simple-replace!
  356. [id value selected
  357. {:keys [backward-pos forward-pos check-fn]
  358. :as _option}]
  359. (let [selected? (not (string/blank? selected))
  360. input (gdom/getElement id)
  361. edit-content (gobj/get input "value")]
  362. (when edit-content
  363. (let [current-pos (cursor/pos input)
  364. prefix (subs edit-content 0 current-pos)
  365. postfix (if selected?
  366. (string/replace-first (subs edit-content current-pos)
  367. selected
  368. "")
  369. (subs edit-content current-pos))
  370. new-value (str prefix value postfix)
  371. new-pos (- (+ (count prefix)
  372. (count value)
  373. (or forward-pos 0))
  374. (or backward-pos 0))]
  375. (state/set-block-content-and-last-pos! id new-value new-pos)
  376. (cursor/move-cursor-to input new-pos)
  377. (when selected?
  378. (.setSelectionRange input new-pos (+ new-pos (count selected))))
  379. (when check-fn
  380. (check-fn new-value (dec (count prefix))))))))
  381. (defn delete-pair!
  382. [id]
  383. (let [input (gdom/getElement id)
  384. edit-content (gobj/get input "value")
  385. current-pos (cursor/pos input)
  386. prefix (subs edit-content 0 (dec current-pos))
  387. new-value (str prefix
  388. (subs edit-content (inc current-pos)))
  389. new-pos (count prefix)]
  390. (state/set-block-content-and-last-pos! id new-value new-pos)
  391. (cursor/move-cursor-to input new-pos)))
  392. (defn delete-selection!
  393. [id]
  394. (let [input (gdom/getElement id)
  395. edit-content (gobj/get input "value")
  396. start (util/get-selection-start input)
  397. end (util/get-selection-end input)]
  398. (when-not (= start end)
  399. (let [prefix (subs edit-content 0 start)
  400. new-value (str prefix
  401. (subs edit-content end))
  402. new-pos (count prefix)]
  403. (state/set-block-content-and-last-pos! id new-value new-pos)
  404. (cursor/move-cursor-to input new-pos)))))
  405. (defn get-matched-commands
  406. ([text]
  407. (get-matched-commands text @*initial-commands))
  408. ([text commands]
  409. (search/fuzzy-search commands text
  410. :extract-fn first
  411. :limit 50)))
  412. (defmulti handle-step first)
  413. (defmethod handle-step :editor/hook [[_ event {:keys [pid uuid] :as payload}] format]
  414. (plugin-handler/hook-plugin-editor event (merge payload {:format format :uuid (or uuid (:block/uuid (state/get-edit-block)))}) pid))
  415. (defmethod handle-step :editor/input [[_ value option]]
  416. (when-let [input-id (state/get-edit-input-id)]
  417. (let [type (:type option)
  418. input (gdom/getElement input-id)
  419. beginning-of-line? (or (cursor/beginning-of-line? input)
  420. (= 1 (:pos (:pos (state/get-editor-action-data)))))
  421. value (if (and (contains? #{"block" "properties"} type)
  422. (not beginning-of-line?))
  423. (str "\n" value)
  424. value)]
  425. (insert! input-id value option)
  426. (state/clear-editor-action!))))
  427. (defmethod handle-step :editor/cursor-back [[_ n]]
  428. (when-let [input-id (state/get-edit-input-id)]
  429. (when-let [current-input (gdom/getElement input-id)]
  430. (cursor/move-cursor-backward current-input n))))
  431. (defmethod handle-step :editor/cursor-forward [[_ n]]
  432. (when-let [input-id (state/get-edit-input-id)]
  433. (when-let [current-input (gdom/getElement input-id)]
  434. (cursor/move-cursor-forward current-input n))))
  435. (defmethod handle-step :editor/move-cursor-to-end [[_]]
  436. (when-let [input-id (state/get-edit-input-id)]
  437. (when-let [current-input (gdom/getElement input-id)]
  438. (cursor/move-cursor-to-end current-input))))
  439. (defmethod handle-step :editor/restore-saved-cursor [[_]]
  440. (when-let [input-id (state/get-edit-input-id)]
  441. (when-let [current-input (gdom/getElement input-id)]
  442. (cursor/move-cursor-to current-input (state/get-editor-last-pos)))))
  443. (defmethod handle-step :editor/clear-current-slash [[_ space?]]
  444. (when-let [input-id (state/get-edit-input-id)]
  445. (when-let [current-input (gdom/getElement input-id)]
  446. (let [edit-content (gobj/get current-input "value")
  447. current-pos (cursor/pos current-input)
  448. prefix (subs edit-content 0 current-pos)
  449. prefix (util/replace-last (state/get-editor-command-trigger) prefix "" (boolean space?))
  450. new-value (str prefix
  451. (subs edit-content current-pos))]
  452. (state/set-block-content-and-last-pos! input-id
  453. new-value
  454. (count prefix))))))
  455. (defmethod handle-step :editor/clear-current-bracket [[_ space?]]
  456. (when-let [input-id (state/get-edit-input-id)]
  457. (when-let [current-input (gdom/getElement input-id)]
  458. (let [edit-content (gobj/get current-input "value")
  459. current-pos (cursor/pos current-input)
  460. prefix (subs edit-content 0 current-pos)
  461. prefix (util/replace-last angle-bracket prefix "" (boolean space?))
  462. new-value (str prefix
  463. (subs edit-content current-pos))]
  464. (state/set-block-content-and-last-pos! input-id
  465. new-value
  466. (count prefix))))))
  467. (defn compute-pos-delta-when-change-marker
  468. [edit-content marker pos]
  469. (let [old-marker (some->> (first (util/safe-re-find marker/bare-marker-pattern edit-content))
  470. (string/trim))
  471. pos-delta (- (count marker)
  472. (count old-marker))
  473. pos-delta (cond (string/blank? old-marker)
  474. (inc pos-delta)
  475. (string/blank? marker)
  476. (dec pos-delta)
  477. :else
  478. pos-delta)]
  479. (max (+ pos pos-delta) 0)))
  480. (defmethod handle-step :editor/set-marker [[_ marker] format]
  481. (when-let [input-id (state/get-edit-input-id)]
  482. (when-let [current-input (gdom/getElement input-id)]
  483. (let [edit-content (gobj/get current-input "value")
  484. slash-pos (:pos (:pos (state/get-editor-action-data)))
  485. [re-pattern new-line-re-pattern] (if (= :org format)
  486. [#"\*+\s" #"\n\*+\s"]
  487. [#"#+\s" #"\n#+\s"])
  488. pos (let [prefix (subs edit-content 0 (dec slash-pos))]
  489. (if-let [matches (seq (util/re-pos new-line-re-pattern prefix))]
  490. (let [[start-pos content] (last matches)]
  491. (+ start-pos (count content)))
  492. (count (util/safe-re-find re-pattern prefix))))
  493. new-value (str (subs edit-content 0 pos)
  494. (string/replace-first (subs edit-content pos)
  495. (marker/marker-pattern format)
  496. (str marker " ")))]
  497. (state/set-edit-content! input-id new-value)
  498. (let [new-pos (compute-pos-delta-when-change-marker
  499. edit-content marker (dec slash-pos))]
  500. ;; TODO: any performance issue?
  501. (js/setTimeout #(cursor/move-cursor-to current-input new-pos) 10))))))
  502. (defmethod handle-step :editor/set-priority [[_ priority] _format]
  503. (when-let [input-id (state/get-edit-input-id)]
  504. (when-let [current-input (gdom/getElement input-id)]
  505. (let [format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format))
  506. edit-content (gobj/get current-input "value")
  507. new-priority (util/format "[#%s]" priority)
  508. new-value (string/trim (priority/add-or-update-priority edit-content format new-priority))]
  509. (state/set-edit-content! input-id new-value)))))
  510. (defmethod handle-step :editor/insert-properties [[_ _] _format]
  511. (when-let [input-id (state/get-edit-input-id)]
  512. (when-let [current-input (gdom/getElement input-id)]
  513. (let [format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format))
  514. edit-content (gobj/get current-input "value")
  515. new-value (property/insert-property format edit-content "" "")]
  516. (state/set-edit-content! input-id new-value)))))
  517. (defmethod handle-step :editor/move-cursor-to-properties [[_]]
  518. (when-let [input-id (state/get-edit-input-id)]
  519. (when-let [current-input (gdom/getElement input-id)]
  520. (let [format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format))]
  521. (property/goto-properties-end format current-input)
  522. (cursor/move-cursor-backward current-input 3)))))
  523. (defonce markdown-heading-pattern #"^#+\s+")
  524. (defn set-markdown-heading
  525. [content heading]
  526. (let [heading-str (apply str (repeat heading "#"))]
  527. (if (util/safe-re-find markdown-heading-pattern content)
  528. (string/replace-first content
  529. markdown-heading-pattern
  530. (str heading-str " "))
  531. (str heading-str " " (string/triml content)))))
  532. (defn clear-markdown-heading
  533. [content]
  534. [:pre (string? content)]
  535. (string/replace-first content
  536. markdown-heading-pattern
  537. ""))
  538. (defmethod handle-step :editor/set-heading [[_ heading]]
  539. (when-let [input-id (state/get-edit-input-id)]
  540. (when-let [current-input (gdom/getElement input-id)]
  541. (let [current-block (state/get-edit-block)
  542. format (:block/format current-block)]
  543. (if (= format :markdown)
  544. (let [edit-content (gobj/get current-input "value")
  545. new-content (set-markdown-heading edit-content heading)]
  546. (state/set-edit-content! input-id new-content))
  547. (state/pub-event! [:editor/set-org-mode-heading current-block heading]))))))
  548. (defmethod handle-step :editor/search-page [[_]]
  549. (state/set-editor-action! :page-search))
  550. (defmethod handle-step :editor/search-page-hashtag [[_]]
  551. (state/set-editor-action! :page-search-hashtag))
  552. (defmethod handle-step :editor/search-block [[_ _type]]
  553. (state/set-editor-action! :block-search))
  554. (defmethod handle-step :editor/search-template [[_]]
  555. (state/set-editor-action! :template-search))
  556. (defmethod handle-step :editor/show-input [[_ option]]
  557. (state/set-editor-show-input! option))
  558. (defmethod handle-step :editor/show-zotero [[_]]
  559. (state/set-editor-action! :zotero))
  560. (defn insert-youtube-timestamp
  561. []
  562. (let [input-id (state/get-edit-input-id)
  563. macro (youtube/gen-youtube-ts-macro)]
  564. (when-let [input (gdom/getElement input-id)]
  565. (when macro
  566. (util/insert-at-current-position! input (str macro " "))))))
  567. (defmethod handle-step :youtube/insert-timestamp [[_]]
  568. (let [input-id (state/get-edit-input-id)
  569. macro (youtube/gen-youtube-ts-macro)]
  570. (insert! input-id macro {})))
  571. (defmethod handle-step :editor/show-date-picker [[_ type]]
  572. (if (and
  573. (contains? #{:scheduled :deadline} type)
  574. (when-let [value (gobj/get (state/get-input) "value")]
  575. (string/blank? value)))
  576. (do
  577. (notification/show! [:div "Please add some content first."] :warning)
  578. (restore-state))
  579. (state/set-editor-action! :datepicker)))
  580. (defmethod handle-step :editor/click-hidden-file-input [[_ _input-id]]
  581. (when-let [input-file (gdom/getElement "upload-file")]
  582. (.click input-file)))
  583. (defmethod handle-step :default [[type & _args]]
  584. (prn "No handler for step: " type))
  585. (defn handle-steps
  586. [vector format]
  587. (doseq [step vector]
  588. (handle-step step format)))
  589. (defn exec-plugin-simple-command!
  590. [pid {:keys [block-id] :as cmd} action]
  591. (let [format (and block-id (:block/format (db-util/pull [:block/uuid block-id])))
  592. inputs (vector (conj action (assoc cmd :pid pid)))]
  593. (handle-steps inputs format)))