commands.cljs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  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.extensions.video.youtube :as youtube]
  8. [frontend.handler.db-based.property :as db-property-handler]
  9. [frontend.handler.db-based.property.util :as db-pu]
  10. [frontend.handler.draw :as draw]
  11. [frontend.handler.file-based.property :as file-property-handler]
  12. [frontend.handler.file-based.status :as file-based-status]
  13. [frontend.handler.notification :as notification]
  14. [frontend.handler.plugin :as plugin-handler]
  15. [frontend.handler.property.file :as property-file]
  16. [frontend.ref :as ref]
  17. [frontend.search :as search]
  18. [frontend.state :as state]
  19. [frontend.util :as util]
  20. [frontend.util.cursor :as cursor]
  21. [frontend.util.file-based.priority :as priority]
  22. [goog.dom :as gdom]
  23. [goog.object :as gobj]
  24. [logseq.common.config :as common-config]
  25. [logseq.common.util :as common-util]
  26. [logseq.common.util.block-ref :as block-ref]
  27. [logseq.common.util.macro :as macro-util]
  28. [logseq.common.util.page-ref :as page-ref]
  29. [logseq.graph-parser.property :as gp-property]
  30. [promesa.core :as p]))
  31. ;; TODO: move to frontend.handler.editor.commands
  32. (defonce hashtag "#")
  33. (defonce colon ":")
  34. (defonce command-trigger "/")
  35. (defonce command-ask "\\")
  36. (defonce *current-command (atom nil))
  37. (def query-doc
  38. [:div {:on-pointer-down (fn [e] (.stopPropagation e))}
  39. [:div.font-medium.text-lg.mb-2 "Query examples:"]
  40. [:ul.mb-1
  41. [:li.mb-1 [:code "{{query #tag}}"]]
  42. [:li.mb-1 [:code "{{query [[page]]}}"]]
  43. [:li.mb-1 [:code "{{query \"full-text search\"}}"]]
  44. [:li.mb-1 [:code "{{query (and [[project]] (task NOW LATER))}}"]]
  45. [:li.mb-1 [:code "{{query (or [[page 1]] [[page 2]])}}"]]
  46. [:li.mb-1 [:code "{{query (and (between -7d +7d) (task DONE))}}"]]
  47. [:li.mb-1 [:code "{{query (property key value)}}"]]
  48. [:li.mb-1 [:code "{{query (page-tags #tag)}}"]]]
  49. [:p "Check more examples at "
  50. [:a {:href "https://docs.logseq.com/#/page/queries"
  51. :target "_blank"}
  52. "Queries documentation"]
  53. "."]])
  54. (defn link-steps []
  55. [[:editor/input (str command-trigger "link")]
  56. [:editor/show-input [{:command :link
  57. :id :link
  58. :placeholder "Link"
  59. :autoFocus true}
  60. {:command :link
  61. :id :label
  62. :placeholder "Label"}]]])
  63. (defn image-link-steps []
  64. [[:editor/input (str command-trigger "link")]
  65. [:editor/show-input [{:command :image-link
  66. :id :link
  67. :placeholder "Link"
  68. :autoFocus true}
  69. {:command :image-link
  70. :id :label
  71. :placeholder "Label"}]]])
  72. (defn zotero-steps []
  73. [[:editor/input (str command-trigger "zotero")]
  74. [:editor/show-zotero]])
  75. (def *extend-slash-commands (atom []))
  76. (defn register-slash-command [cmd]
  77. (swap! *extend-slash-commands conj cmd))
  78. (defn ->marker
  79. [marker]
  80. [[:editor/clear-current-slash]
  81. [:editor/set-status marker]
  82. [:editor/move-cursor-to-end]])
  83. (defn ->priority
  84. [priority]
  85. [[:editor/clear-current-slash]
  86. [:editor/set-priority priority]
  87. [:editor/move-cursor-to-end]])
  88. (defn ->inline
  89. [type]
  90. (let [template (util/format "@@%s: @@"
  91. type)]
  92. [[:editor/input template {:last-pattern command-trigger
  93. :backward-pos 2}]]))
  94. (defn file-based-embed-page
  95. []
  96. [[:editor/input "{{embed [[]]}}" {:last-pattern command-trigger
  97. :backward-pos 4}]
  98. [:editor/search-page :embed]])
  99. (defn file-based-embed-block
  100. []
  101. [[:editor/input "{{embed (())}}" {:last-pattern command-trigger
  102. :backward-pos 4}]
  103. [:editor/search-block :embed]])
  104. (defn file-based-statuses
  105. []
  106. (let [workflow (state/get-preferred-workflow)]
  107. (if (= :now workflow)
  108. ["LATER" "NOW" "TODO" "DOING" "DONE" "WAITING" "CANCELED"]
  109. ["TODO" "DOING" "LATER" "NOW" "DONE" "WAITING" "CANCELED"])))
  110. (defn db-based-statuses
  111. []
  112. (map (fn [e] (:block/title e))
  113. (db-pu/get-closed-property-values :logseq.property/status)))
  114. (defn db-based-embed-page
  115. []
  116. [[:editor/input "[[]]" {:last-pattern command-trigger
  117. :backward-pos 2}]
  118. [:editor/search-page :embed]])
  119. (defn db-based-embed-block
  120. []
  121. [[:editor/input "" {:last-pattern command-trigger}]
  122. [:editor/search-block :embed]])
  123. (defn db-based-query
  124. []
  125. [[:editor/input "" {:last-pattern command-trigger}]
  126. [:editor/run-query-command]])
  127. (defn file-based-query
  128. []
  129. [[:editor/input (str macro-util/query-macro " }}") {:backward-pos 2}]
  130. [:editor/exit]])
  131. (defn query-steps
  132. []
  133. (if (config/db-based-graph? (state/get-current-repo))
  134. (db-based-query)
  135. (file-based-query)))
  136. (defn- calc-steps
  137. []
  138. (if (config/db-based-graph? (state/get-current-repo))
  139. [[:editor/input "" {:last-pattern command-trigger}]
  140. [:editor/upsert-type-block :code "calc"]
  141. [:codemirror/focus]]
  142. [[:editor/input "```calc\n\n```" {:type "block"
  143. :backward-pos 4}]
  144. [:codemirror/focus]]))
  145. (defn ->block
  146. ([type]
  147. (->block type nil))
  148. ([type optional]
  149. (let [format (get (state/get-edit-block) :block/format)
  150. markdown-src? (and (= format :markdown)
  151. (= (string/lower-case type) "src"))
  152. [left right] (cond
  153. markdown-src?
  154. ["```" "\n```"]
  155. :else
  156. (->> ["#+BEGIN_%s" "\n#+END_%s"]
  157. (map #(util/format %
  158. (string/upper-case type)))))
  159. template (str
  160. left
  161. (if optional (str " " optional) "")
  162. "\n"
  163. right)
  164. backward-pos (if (= type "src")
  165. (+ 1 (count right))
  166. (count right))]
  167. [[:editor/input template {:type "block"
  168. :last-pattern command-trigger
  169. :backward-pos backward-pos}]])))
  170. (defn- advanced-query-steps
  171. []
  172. (if (config/db-based-graph? (state/get-current-repo))
  173. [[:editor/input "" {:last-pattern command-trigger}]
  174. [:editor/set-property :block/tags :logseq.class/Query]
  175. [:editor/set-property :logseq.property/query ""]
  176. [:editor/set-property-on-block-property :logseq.property/query :logseq.property.node/display-type :code]
  177. [:editor/set-property-on-block-property :logseq.property/query :logseq.property.code/lang "clojure"]
  178. [:editor/exit]]
  179. (->block "query")))
  180. (defn db-based-code-block
  181. []
  182. [[:editor/input "" {:last-pattern command-trigger}]
  183. [:editor/upsert-type-block :code]
  184. [:editor/exit]])
  185. (defn file-based-code-block
  186. []
  187. [[:editor/input "```\n```\n" {:type "block"
  188. :backward-pos 5
  189. :only-breakline? true}]
  190. [:editor/select-code-block-mode]])
  191. (defn code-block-steps
  192. []
  193. (if (config/db-based-graph? (state/get-current-repo))
  194. (db-based-code-block)
  195. (file-based-code-block)))
  196. (defn quote-block-steps
  197. []
  198. (if (config/db-based-graph? (state/get-current-repo))
  199. [[:editor/input "" {:last-pattern command-trigger}]
  200. [:editor/set-property :logseq.property.node/display-type :quote]]
  201. (->block "quote")))
  202. (defn math-block-steps
  203. []
  204. (if (config/db-based-graph? (state/get-current-repo))
  205. [[:editor/input "" {:last-pattern command-trigger}]
  206. [:editor/set-property :logseq.property.node/display-type :math]]
  207. (->block "export" "latex")))
  208. (defn get-statuses
  209. []
  210. (let [db-based? (config/db-based-graph? (state/get-current-repo))
  211. result (->>
  212. (if db-based?
  213. (db-based-statuses)
  214. (file-based-statuses))
  215. (mapv (fn [command]
  216. (let [icon (if db-based?
  217. (case command
  218. "Canceled" "Cancelled"
  219. "Doing" "InProgress50"
  220. command)
  221. "square-asterisk")]
  222. [command (->marker command) (str "Set status to " command) icon]))))]
  223. (when (seq result)
  224. (map (fn [v] (conj v "TASK STATUS")) result))))
  225. (defn file-based-priorities
  226. []
  227. ["A" "B" "C"])
  228. (defn db-based-priorities
  229. []
  230. (map (fn [e] (:block/title e))
  231. (db-pu/get-closed-property-values :logseq.property/priority)))
  232. (defn get-priorities
  233. []
  234. (let [db-based? (config/db-based-graph? (state/get-current-repo))
  235. with-no-priority #(if db-based? (cons ["No priority" (->priority nil) "" :icon/priorityLvlNone] %) %)
  236. result (->>
  237. (if db-based?
  238. (db-based-priorities)
  239. (file-based-priorities))
  240. (mapv (fn [item]
  241. (let [command item]
  242. [command
  243. (->priority item)
  244. (str "Set priority to " item)
  245. (if db-based?
  246. (str "priorityLvl" item)
  247. (str "circle-letter-" (util/safe-lower-case item)))])))
  248. (with-no-priority)
  249. (vec))]
  250. (when (seq result)
  251. (map (fn [v] (into v ["PRIORITY"])) result))))
  252. ;; Credits to roamresearch.com
  253. (defn- ->heading
  254. [heading]
  255. [[:editor/clear-current-slash]
  256. [:editor/set-heading heading]
  257. [:editor/move-cursor-to-end]])
  258. (defn- headings
  259. []
  260. (mapv (fn [level]
  261. (let [heading (str "Heading " level)]
  262. [heading (->heading level) heading (str "h-" level) "Heading"])) (range 1 7)))
  263. (defonce *latest-matched-command (atom ""))
  264. (defonce *matched-commands (atom nil))
  265. (defonce *initial-commands (atom nil))
  266. (defn ->properties
  267. []
  268. [[:editor/clear-current-slash]
  269. [:editor/insert-properties]
  270. [:editor/move-cursor-to-properties]])
  271. (defn ^:large-vars/cleanup-todo commands-map
  272. [get-page-ref-text]
  273. (let [db? (config/db-based-graph? (state/get-current-repo))
  274. embed-page (if db? db-based-embed-page file-based-embed-page)
  275. embed-block (if db? db-based-embed-block file-based-embed-block)]
  276. (->>
  277. (concat
  278. ;; basic
  279. [[(if db? "Node reference" "Page reference")
  280. [[:editor/input page-ref/left-and-right-brackets {:backward-pos 2}]
  281. [:editor/search-page]]
  282. (if db? "Create a backlink to a node (a page or a block)"
  283. "Create a backlink to a BLOCK")
  284. :icon/pageRef
  285. "BASIC"]
  286. (when-not db? ["Page embed" (embed-page) "Embed a page here" :icon/pageEmbed])
  287. (when-not db?
  288. ["Block reference" [[:editor/input block-ref/left-and-right-parens {:backward-pos 2}]
  289. [:editor/search-block :reference]]
  290. "Create a backlink to a block" :icon/blockRef])
  291. [(if db? "Node embed" "Block embed")
  292. (embed-block)
  293. (if db? "Embed a node here" "Embed a block here")
  294. :icon/blockEmbed]]
  295. ;; format
  296. [["Link" (link-steps) "Create a HTTP link" :icon/link "FORMAT"]
  297. ["Image link" (image-link-steps) "Create a HTTP link to a image" :icon/photoLink]
  298. (when (state/markdown?)
  299. ["Underline" [[:editor/input "<ins></ins>"
  300. {:last-pattern command-trigger
  301. :backward-pos 6}]] "Create a underline text decoration"
  302. :icon/underline])
  303. ["Code block"
  304. (code-block-steps)
  305. "Insert code block"
  306. :icon/code]
  307. ["Quote"
  308. (quote-block-steps)
  309. "Create a quote block"
  310. :icon/quote]
  311. ["Math block"
  312. (math-block-steps)
  313. "Create a latex block"
  314. :icon/math]]
  315. (headings)
  316. ;; task management
  317. (get-statuses)
  318. ;; task date
  319. [["Deadline"
  320. [[:editor/clear-current-slash]
  321. [:editor/set-deadline]]
  322. ""
  323. :icon/calendar-stats
  324. "TASK DATE"]
  325. ["Scheduled"
  326. [[:editor/clear-current-slash]
  327. [:editor/set-scheduled]]
  328. ""
  329. :icon/calendar-month
  330. "TASK DATE"]]
  331. ;; priority
  332. (get-priorities)
  333. ;; time & date
  334. [["Tomorrow"
  335. #(get-page-ref-text (date/tomorrow))
  336. "Insert the date of tomorrow"
  337. :icon/tomorrow
  338. "TIME & DATE"]
  339. ["Yesterday" #(get-page-ref-text (date/yesterday)) "Insert the date of yesterday" :icon/yesterday]
  340. ["Today" #(get-page-ref-text (date/today)) "Insert the date of today" :icon/calendar]
  341. ["Current time" #(date/get-current-time) "Insert current time" :icon/clock]
  342. ["Date picker" [[:editor/show-date-picker]] "Pick a date and insert here" :icon/calendar-dots]]
  343. ;; order list
  344. [["Number list"
  345. [[:editor/clear-current-slash]
  346. [:editor/toggle-own-number-list]]
  347. "Number list"
  348. :icon/numberedParents
  349. "LIST TYPE"]
  350. ["Number children" [[:editor/clear-current-slash]
  351. [:editor/toggle-children-number-list]]
  352. "Number children"
  353. :icon/numberedChildren]]
  354. ;; https://orgmode.org/manual/Structure-Templates.html
  355. (when-not db?
  356. (cond->
  357. [;; Should this be replaced by "Code block"?
  358. ["Src" (->block "src") "Create a code block"]
  359. ["Math block" (->block "export" "latex") "Create a latex block"]
  360. ["Note" (->block "note") "Create a note block"]
  361. ["Tip" (->block "tip") "Create a tip block"]
  362. ["Important" (->block "important") "Create an important block"]
  363. ["Caution" (->block "caution") "Create a caution block"]
  364. ["Pinned" (->block "pinned") "Create a pinned block"]
  365. ["Warning" (->block "warning") "Create a warning block"]
  366. ["Example" (->block "example") "Create an example block"]
  367. ["Export" (->block "export") "Create an export block"]
  368. ["Verse" (->block "verse") "Create a verse block"]
  369. ["Ascii" (->block "export" "ascii") "Create an ascii block"]
  370. ["Center" (->block "center") "Create a center block"]]
  371. ;; FIXME: current page's format
  372. (= :org (state/get-preferred-format))
  373. (conj ["Properties" (->properties)])))
  374. ;; advanced
  375. [["Query" (query-steps) query-doc :icon/query "ADVANCED"]
  376. ["Advanced Query" (advanced-query-steps) "Create an advanced query block" :icon/query]
  377. (when-not db?
  378. ["Zotero" (zotero-steps) "Import Zotero journal article" :icon/circle-letter-z])
  379. (when-not db?
  380. ["Query function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query function" :icon/queryCode])
  381. ["Calculator"
  382. (calc-steps)
  383. "Insert a calculator" :icon/calculator]
  384. (when-not db?
  385. ["Draw" (fn []
  386. (let [file (draw/file-name)
  387. path (str common-config/default-draw-directory "/" file)
  388. text (ref/->page-ref path)]
  389. (p/let [_ (draw/create-draw-with-default-content path)]
  390. (println "draw file created, " path))
  391. text)) "Draw a graph with Excalidraw"])
  392. (when (util/electron?)
  393. ["Upload an asset"
  394. [[:editor/click-hidden-file-input :id]]
  395. "Upload file types like image, pdf, docx, etc.)"
  396. :icon/upload])
  397. ["Template" [[:editor/input command-trigger nil]
  398. [:editor/search-template]] "Insert a created template here"
  399. :icon/template]
  400. ["Embed HTML " (->inline "html") "" :icon/htmlEmbed]
  401. ["Embed Video URL" [[:editor/input "{{video }}" {:last-pattern command-trigger
  402. :backward-pos 2}]] ""
  403. :icon/videoEmbed]
  404. ["Embed Youtube timestamp" [[:youtube/insert-timestamp]] "" :icon/videoEmbed]
  405. ["Embed Twitter tweet" [[:editor/input "{{tweet }}" {:last-pattern command-trigger
  406. :backward-pos 2}]] ""
  407. :icon/xEmbed]
  408. (when db?
  409. ["Add new property" [[:editor/clear-current-slash]
  410. [:editor/new-property]] ""
  411. :icon/cube-plus])]
  412. (let [commands (cond->> @*extend-slash-commands
  413. db?
  414. (remove (fn [command] (when (map? (last command))
  415. (false? (:db-graph? (last command)))))))]
  416. commands)
  417. ;; Allow user to modify or extend, should specify how to extend.
  418. (state/get-commands)
  419. (when-let [plugin-commands (seq (some->> (state/get-plugins-slash-commands)
  420. (mapv #(vec (concat % [nil :icon/puzzle])))))]
  421. (-> plugin-commands (vec) (update 0 (fn [v] (conj v "PLUGINS"))))))
  422. (remove nil?)
  423. (util/distinct-by-last-wins first))))
  424. (defn init-commands!
  425. [get-page-ref-text]
  426. (let [commands (commands-map get-page-ref-text)]
  427. (reset! *latest-matched-command "")
  428. (reset! *initial-commands commands)
  429. (reset! *matched-commands commands)))
  430. (defn set-matched-commands!
  431. [command matched-commands]
  432. (reset! *latest-matched-command command)
  433. (reset! *matched-commands matched-commands))
  434. (defn reinit-matched-commands!
  435. []
  436. (set-matched-commands! "" @*initial-commands))
  437. (defn restore-state
  438. []
  439. (state/clear-editor-action!)
  440. (reinit-matched-commands!))
  441. (defn insert!
  442. [id value
  443. {:keys [last-pattern postfix-fn backward-pos end-pattern backward-truncate-number
  444. command only-breakline?]
  445. :as _option}]
  446. (when-let [input (gdom/getElement id)]
  447. (let [last-pattern (when-not (= last-pattern :skip-check)
  448. (when-not backward-truncate-number
  449. (or last-pattern command-trigger)))
  450. edit-content (gobj/get input "value")
  451. current-pos (cursor/pos input)
  452. current-pos (or
  453. (when (and end-pattern (string? end-pattern))
  454. (when-let [i (string/index-of (common-util/safe-subs edit-content current-pos) end-pattern)]
  455. (+ current-pos i)))
  456. current-pos)
  457. orig-prefix (subs edit-content 0 current-pos)
  458. postfix (subs edit-content current-pos)
  459. postfix (if postfix-fn (postfix-fn postfix) postfix)
  460. space? (let [space? (when (and last-pattern orig-prefix)
  461. (let [s (when-let [last-index (string/last-index-of orig-prefix last-pattern)]
  462. (common-util/safe-subs orig-prefix 0 last-index))]
  463. (not
  464. (or
  465. (and (= :page-ref command)
  466. (util/cjk-string? value)
  467. (or (util/cjk-string? (str (last orig-prefix)))
  468. (util/cjk-string? (str (first postfix)))))
  469. (and s
  470. (string/ends-with? s "(")
  471. (or (string/starts-with? last-pattern block-ref/left-parens)
  472. (string/starts-with? last-pattern page-ref/left-brackets)))
  473. (and s (string/starts-with? s "{{embed"))
  474. (and s (= (last s) \#) (string/starts-with? last-pattern "[["))
  475. (and last-pattern
  476. (or (string/ends-with? last-pattern gp-property/colons)
  477. (string/starts-with? last-pattern gp-property/colons)))))))]
  478. (if (and space? (or (string/starts-with? last-pattern "#[[")
  479. (string/starts-with? last-pattern "```")))
  480. false
  481. space?))
  482. prefix (cond
  483. (and backward-truncate-number (integer? backward-truncate-number))
  484. (str (common-util/safe-subs orig-prefix 0 (- (count orig-prefix) backward-truncate-number))
  485. (when-not (zero? backward-truncate-number)
  486. value))
  487. (string/blank? last-pattern)
  488. (if space?
  489. (util/concat-without-spaces orig-prefix value)
  490. (str orig-prefix value))
  491. :else
  492. (util/replace-last last-pattern orig-prefix value space?))
  493. postfix (cond-> postfix
  494. (and only-breakline? postfix
  495. (= (get postfix 0) "\n"))
  496. (string/replace-first "\n" ""))
  497. new-value (cond
  498. (string/blank? postfix)
  499. prefix
  500. space?
  501. (util/concat-without-spaces prefix postfix)
  502. :else
  503. (str prefix postfix))
  504. new-pos (- (count prefix)
  505. (or backward-pos 0))]
  506. (when-not (and (not (string/blank? value))
  507. (string/blank? new-value))
  508. (state/set-block-content-and-last-pos! id new-value new-pos)
  509. (cursor/move-cursor-to input new-pos)))))
  510. (defn simple-insert!
  511. [id value
  512. {:keys [backward-pos forward-pos check-fn]
  513. :as _option}]
  514. (let [input (gdom/getElement id)
  515. edit-content (gobj/get input "value")
  516. current-pos (cursor/pos input)
  517. prefix (subs edit-content 0 current-pos)
  518. surfix (subs edit-content current-pos)
  519. new-value (str prefix
  520. value
  521. surfix)
  522. new-pos (- (+ (count prefix)
  523. (count value)
  524. (or forward-pos 0))
  525. (or backward-pos 0))]
  526. (state/set-edit-content! (state/get-edit-input-id)
  527. (str prefix value))
  528. ;; HACK: save scroll-pos of current pos, then add trailing content
  529. (let [scroll-container (util/nearest-scrollable-container input)
  530. scroll-pos (.-scrollTop scroll-container)]
  531. (state/set-block-content-and-last-pos! id new-value new-pos)
  532. (cursor/move-cursor-to input new-pos)
  533. (set! (.-scrollTop scroll-container) scroll-pos)
  534. (when check-fn
  535. (check-fn new-value (dec (count prefix)) new-pos)))))
  536. (defn simple-replace!
  537. [id value selected
  538. {:keys [backward-pos forward-pos check-fn]
  539. :as _option}]
  540. (let [selected? (not (string/blank? selected))
  541. input (gdom/getElement id)
  542. edit-content (gobj/get input "value")]
  543. (when edit-content
  544. (let [current-pos (cursor/pos input)
  545. prefix (subs edit-content 0 current-pos)
  546. postfix (if selected?
  547. (string/replace-first (subs edit-content current-pos)
  548. selected
  549. "")
  550. (subs edit-content current-pos))
  551. new-value (str prefix value postfix)
  552. new-pos (- (+ (count prefix)
  553. (count value)
  554. (or forward-pos 0))
  555. (or backward-pos 0))]
  556. (state/set-block-content-and-last-pos! id new-value new-pos)
  557. (cursor/move-cursor-to input new-pos)
  558. (when selected?
  559. (.setSelectionRange input new-pos (+ new-pos (count selected))))
  560. (when check-fn
  561. (check-fn new-value (dec (count prefix))))))))
  562. (defn delete-pair!
  563. [id]
  564. (let [input (gdom/getElement id)
  565. edit-content (gobj/get input "value")
  566. current-pos (cursor/pos input)
  567. prefix (subs edit-content 0 (dec current-pos))
  568. new-value (str prefix
  569. (subs edit-content (inc current-pos)))
  570. new-pos (count prefix)]
  571. (state/set-block-content-and-last-pos! id new-value new-pos)
  572. (cursor/move-cursor-to input new-pos)))
  573. (defn delete-selection!
  574. [id]
  575. (let [input (gdom/getElement id)
  576. edit-content (gobj/get input "value")
  577. start (util/get-selection-start input)
  578. end (util/get-selection-end input)]
  579. (when-not (= start end)
  580. (let [prefix (subs edit-content 0 start)
  581. new-value (str prefix
  582. (subs edit-content end))
  583. new-pos (count prefix)]
  584. (state/set-block-content-and-last-pos! id new-value new-pos)
  585. (cursor/move-cursor-to input new-pos)))))
  586. (defn get-matched-commands
  587. ([text]
  588. (get-matched-commands text @*initial-commands))
  589. ([text commands]
  590. (search/fuzzy-search commands text
  591. :extract-fn first
  592. :limit 50)))
  593. (defmulti handle-step first)
  594. (defmethod handle-step :editor/hook [[_ event {:keys [pid uuid] :as payload}] format]
  595. (plugin-handler/hook-plugin-editor event (merge payload {:format format :uuid (or uuid (:block/uuid (state/get-edit-block)))}) pid))
  596. (defmethod handle-step :editor/input [[_ value option]]
  597. (when-let [input-id (state/get-edit-input-id)]
  598. (let [type (:type option)
  599. input (gdom/getElement input-id)
  600. beginning-of-line? (or (cursor/beginning-of-line? input)
  601. (= 1 (:pos (:pos (state/get-editor-action-data)))))
  602. value (if (and (contains? #{"block" "properties"} type)
  603. (not beginning-of-line?))
  604. (str "\n" value)
  605. value)]
  606. (insert! input-id value option)
  607. (state/clear-editor-action!))))
  608. (defmethod handle-step :editor/cursor-back [[_ n]]
  609. (when-let [input-id (state/get-edit-input-id)]
  610. (when-let [current-input (gdom/getElement input-id)]
  611. (cursor/move-cursor-backward current-input n))))
  612. (defmethod handle-step :editor/cursor-forward [[_ n]]
  613. (when-let [input-id (state/get-edit-input-id)]
  614. (when-let [current-input (gdom/getElement input-id)]
  615. (cursor/move-cursor-forward current-input n))))
  616. (defmethod handle-step :editor/move-cursor-to-end [[_]]
  617. (when-let [input-id (state/get-edit-input-id)]
  618. (when-let [current-input (gdom/getElement input-id)]
  619. (cursor/move-cursor-to-end current-input))))
  620. (defmethod handle-step :editor/restore-saved-cursor [[_]]
  621. (when-let [input-id (state/get-edit-input-id)]
  622. (when-let [current-input (gdom/getElement input-id)]
  623. (cursor/move-cursor-to current-input (state/get-editor-last-pos)))))
  624. (defmethod handle-step :editor/clear-current-slash [[_ space?]]
  625. (when-let [input-id (state/get-edit-input-id)]
  626. (when-let [current-input (gdom/getElement input-id)]
  627. (let [edit-content (gobj/get current-input "value")
  628. current-pos (cursor/pos current-input)
  629. prefix (subs edit-content 0 current-pos)
  630. prefix (util/replace-last command-trigger prefix "" (boolean space?))
  631. new-value (str prefix
  632. (subs edit-content current-pos))]
  633. (state/set-block-content-and-last-pos! input-id
  634. new-value
  635. (count prefix))))))
  636. (defn compute-pos-delta-when-change-marker
  637. [edit-content marker pos]
  638. (let [old-marker (some->> (first (util/safe-re-find file-based-status/bare-marker-pattern edit-content))
  639. (string/trim))
  640. pos-delta (- (count marker)
  641. (count old-marker))
  642. pos-delta (cond (string/blank? old-marker)
  643. (inc pos-delta)
  644. (string/blank? marker)
  645. (dec pos-delta)
  646. :else
  647. pos-delta)]
  648. (max (+ pos pos-delta) 0)))
  649. (defn- file-based-set-status
  650. [marker format]
  651. (when-let [input-id (state/get-edit-input-id)]
  652. (when-let [current-input (gdom/getElement input-id)]
  653. (let [edit-content (gobj/get current-input "value")
  654. slash-pos (:pos (:pos (state/get-editor-action-data)))
  655. [re-pattern new-line-re-pattern] (if (= :org format)
  656. [#"\*+\s" #"\n\*+\s"]
  657. [#"#+\s" #"\n#+\s"])
  658. pos (let [prefix (subs edit-content 0 (dec slash-pos))]
  659. (if-let [matches (seq (util/re-pos new-line-re-pattern prefix))]
  660. (let [[start-pos content] (last matches)]
  661. (+ start-pos (count content)))
  662. (count (util/safe-re-find re-pattern prefix))))
  663. new-value (str (subs edit-content 0 pos)
  664. (string/replace-first (subs edit-content pos)
  665. (file-based-status/marker-pattern format)
  666. (str marker " ")))]
  667. (state/set-edit-content! input-id new-value)
  668. (let [new-pos (compute-pos-delta-when-change-marker
  669. edit-content marker (dec slash-pos))]
  670. ;; TODO: any performance issue?
  671. (js/setTimeout #(cursor/move-cursor-to current-input new-pos) 10))))))
  672. (defn- db-based-set-status
  673. [status]
  674. (when-let [block (state/get-edit-block)]
  675. (db-property-handler/batch-set-property-closed-value! [(:block/uuid block)] :logseq.property/status status)))
  676. (defmethod handle-step :editor/set-status [[_ status] format]
  677. (if (config/db-based-graph? (state/get-current-repo))
  678. (db-based-set-status status)
  679. (file-based-set-status status format)))
  680. (defmethod handle-step :editor/set-property [[_ property-id value]]
  681. (when (config/db-based-graph? (state/get-current-repo))
  682. (when-let [block (state/get-edit-block)]
  683. (db-property-handler/set-block-property! (:db/id block) property-id value))))
  684. (defmethod handle-step :editor/set-property-on-block-property [[_ block-property-id property-id value]]
  685. (when (config/db-based-graph? (state/get-current-repo))
  686. (let [updated-block (when-let [block-uuid (:block/uuid (state/get-edit-block))]
  687. (db/entity [:block/uuid block-uuid]))
  688. block-property-value (get updated-block block-property-id)]
  689. (when block-property-value
  690. (db-property-handler/set-block-property! (:db/id block-property-value) property-id value)))))
  691. (defmethod handle-step :editor/upsert-type-block [[_ type lang]]
  692. (when (config/db-based-graph? (state/get-current-repo))
  693. (when-let [block (state/get-edit-block)]
  694. (state/pub-event! [:editor/upsert-type-block {:block block :type type :lang lang}]))))
  695. (defn- file-based-set-priority
  696. [priority]
  697. (when-let [input-id (state/get-edit-input-id)]
  698. (when-let [current-input (gdom/getElement input-id)]
  699. (let [format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format))
  700. edit-content (gobj/get current-input "value")
  701. new-priority (util/format "[#%s]" priority)
  702. new-value (string/trim (priority/add-or-update-priority edit-content format new-priority))]
  703. (state/set-edit-content! input-id new-value)))))
  704. (defn- db-based-set-priority
  705. [priority]
  706. (when-let [block (state/get-edit-block)]
  707. (if (nil? priority)
  708. (db-property-handler/remove-block-property! (:block/uuid block) :logseq.property/priority)
  709. (db-property-handler/batch-set-property-closed-value! [(:block/uuid block)] :logseq.property/priority priority))))
  710. (defmethod handle-step :editor/set-priority [[_ priority] _format]
  711. (if (config/db-based-graph? (state/get-current-repo))
  712. (db-based-set-priority priority)
  713. (file-based-set-priority priority)))
  714. (defmethod handle-step :editor/set-scheduled [[_]]
  715. (if (config/db-based-graph? (state/get-current-repo))
  716. (state/pub-event! [:editor/new-property {:property-key "Scheduled"}])
  717. (handle-step [:editor/show-date-picker :scheduled])))
  718. (defmethod handle-step :editor/set-deadline [[_]]
  719. (if (config/db-based-graph? (state/get-current-repo))
  720. (state/pub-event! [:editor/new-property {:property-key "Deadline"}])
  721. (handle-step [:editor/show-date-picker :deadline])))
  722. (defmethod handle-step :editor/run-query-command [[_]]
  723. (state/pub-event! [:editor/run-query-command]))
  724. (defmethod handle-step :editor/insert-properties [[_ _] _format]
  725. (when-let [input-id (state/get-edit-input-id)]
  726. (when-let [current-input (gdom/getElement input-id)]
  727. (let [format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format))
  728. edit-content (gobj/get current-input "value")
  729. new-value (file-property-handler/insert-property format edit-content "" "")]
  730. (state/set-edit-content! input-id new-value)))))
  731. (defmethod handle-step :editor/move-cursor-to-properties [[_]]
  732. (when-let [input-id (state/get-edit-input-id)]
  733. (when-let [current-input (gdom/getElement input-id)]
  734. (let [format (or (db/get-page-format (state/get-current-page)) (state/get-preferred-format))]
  735. (property-file/goto-properties-end-when-file-based format current-input)
  736. (cursor/move-cursor-backward current-input 3)))))
  737. (defn file-based-set-markdown-heading
  738. [content heading]
  739. (let [heading-str (apply str (repeat heading "#"))]
  740. (if (util/safe-re-find common-util/markdown-heading-pattern content)
  741. (common-util/clear-markdown-heading content)
  742. (str heading-str " " (string/triml content)))))
  743. (def clear-markdown-heading common-util/clear-markdown-heading)
  744. (defmethod handle-step :editor/set-heading [[_ heading]]
  745. (when-let [input-id (state/get-edit-input-id)]
  746. (when-let [current-input (gdom/getElement input-id)]
  747. (let [current-block (state/get-edit-block)
  748. format (get current-block :block/format :markdown)]
  749. (if (config/db-based-graph?)
  750. (state/pub-event! [:editor/set-heading current-block heading])
  751. (if (= format :markdown)
  752. (let [edit-content (gobj/get current-input "value")
  753. new-content (file-based-set-markdown-heading edit-content heading)]
  754. (state/set-edit-content! input-id new-content))
  755. (state/pub-event! [:editor/set-heading current-block heading])))))))
  756. (defmethod handle-step :editor/search-page [_]
  757. (state/set-editor-action! :page-search))
  758. (defmethod handle-step :editor/search-page-hashtag [[_]]
  759. (state/set-editor-action! :page-search-hashtag))
  760. (defmethod handle-step :editor/search-block [[_ type]]
  761. (when (and (= type :embed) (config/db-based-graph? (state/get-current-repo)))
  762. (reset! *current-command "Block embed")
  763. (state/set-editor-action-data! {:pos (cursor/get-caret-pos (state/get-input))}))
  764. (state/set-editor-action! :block-search))
  765. (defmethod handle-step :editor/search-template [[_]]
  766. (state/set-editor-action! :template-search))
  767. (defmethod handle-step :editor/show-input [[_ option]]
  768. (state/set-editor-show-input! option))
  769. (defmethod handle-step :editor/show-zotero [[_]]
  770. (state/set-editor-action! :zotero))
  771. (defn insert-youtube-timestamp
  772. []
  773. (let [input-id (state/get-edit-input-id)
  774. macro (youtube/gen-youtube-ts-macro)]
  775. (when-let [input (gdom/getElement input-id)]
  776. (when macro
  777. (util/insert-at-current-position! input (str macro " "))))))
  778. (defmethod handle-step :youtube/insert-timestamp [[_]]
  779. (let [input-id (state/get-edit-input-id)
  780. macro (youtube/gen-youtube-ts-macro)]
  781. (insert! input-id macro {})))
  782. (defmethod handle-step :editor/toggle-children-number-list [[_]]
  783. (when-let [block (state/get-edit-block)]
  784. (state/pub-event! [:editor/toggle-children-number-list block])))
  785. (defmethod handle-step :editor/toggle-own-number-list [[_]]
  786. (when-let [block (state/get-edit-block)]
  787. (state/pub-event! [:editor/toggle-own-number-list block])))
  788. (defmethod handle-step :editor/remove-own-number-list [[_]]
  789. (when-let [block (state/get-edit-block)]
  790. (state/pub-event! [:editor/remove-own-number-list block])))
  791. (defmethod handle-step :editor/show-date-picker [[_ type]]
  792. (if (and
  793. (contains? #{:scheduled :deadline} type)
  794. (string/blank? (gobj/get (state/get-input) "value")))
  795. (do
  796. (notification/show! [:div "Please add some content first."] :warning)
  797. (restore-state))
  798. (do
  799. (state/set-timestamp-block! nil)
  800. (state/set-editor-action! :datepicker))))
  801. (defmethod handle-step :editor/select-code-block-mode [[_]]
  802. (-> (p/delay 50)
  803. (p/then
  804. (fn []
  805. (when-let [input (state/get-input)]
  806. ;; update action cursor position
  807. (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
  808. (state/set-editor-action! :select-code-block-mode))))))
  809. (defmethod handle-step :editor/click-hidden-file-input [[_ _input-id]]
  810. (when-let [input-file (gdom/getElement "upload-file")]
  811. (.click input-file)))
  812. (defmethod handle-step :editor/exit [[_]]
  813. (p/do!
  814. (state/pub-event! [:editor/save-current-block])
  815. (state/clear-edit!)))
  816. (defmethod handle-step :editor/new-property [[_]]
  817. (state/pub-event! [:editor/new-property]))
  818. (defmethod handle-step :default [[type & _args]]
  819. (prn "No handler for step: " type))
  820. (defn handle-steps
  821. [vector' format]
  822. (if (config/db-based-graph? (state/get-current-repo))
  823. (p/doseq [step vector']
  824. (handle-step step format))
  825. (doseq [step vector']
  826. (handle-step step format))))
  827. (defn exec-plugin-simple-command!
  828. [pid {:keys [block-id] :as cmd} action]
  829. (let [format (and block-id (get (db/entity [:block/uuid block-id]) :block/format :markdown))
  830. inputs (vector (conj action (assoc cmd :pid pid)))]
  831. (handle-steps inputs format)))