search.cljs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. (ns frontend.components.search
  2. (:require [rum.core :as rum]
  3. [frontend.util :as util]
  4. [frontend.handler.route :as route]
  5. [frontend.handler.page :as page-handler]
  6. [frontend.handler.file :as file-handler]
  7. [frontend.db :as db]
  8. [frontend.handler.search :as search-handler]
  9. [frontend.ui :as ui]
  10. [frontend.state :as state]
  11. [frontend.mixins :as mixins]
  12. [frontend.config :as config]
  13. [frontend.search :as search]
  14. [clojure.string :as string]
  15. [goog.dom :as gdom]
  16. [frontend.context.i18n :as i18n]))
  17. (rum/defc dropdown-content-wrapper [state content]
  18. [:div.origin-top-left.absolute.left-0.mt-0.rounded-md.shadow-lg
  19. {:class (case state
  20. "entering" "transition ease-out duration-100 transform opacity-0 scale-95"
  21. "entered" "transition ease-out duration-100 transform opacity-100 scale-100"
  22. "exiting" "transition ease-in duration-75 transform opacity-100 scale-100"
  23. "exited" "transition ease-in duration-75 transform opacity-0 scale-95")}
  24. content])
  25. (rum/defc highlight
  26. [content q]
  27. (let [q (search/clean q)
  28. n (count content)
  29. [before after] (string/split content (re-find (re-pattern (str "(?i)" q))
  30. content))
  31. [before after] (if (>= n 64)
  32. [(if before (apply str (take-last 48 before)))
  33. (if after (apply str (take 48 after)))]
  34. [before after])]
  35. [:p
  36. (when-not (string/blank? before)
  37. [:span before])
  38. [:mark q]
  39. (when-not (string/blank? after)
  40. [:span after])]))
  41. (defn- leave-focus
  42. []
  43. (when-let [input (gdom/getElement "search_field")]
  44. (.blur input)))
  45. (rum/defc search-auto-complete
  46. [{:keys [pages files blocks]} search-q]
  47. (rum/with-context [[t] i18n/*tongue-context*]
  48. (let [new-page [{:type :new-page}]
  49. new-file [{:type :new-file}]
  50. pages (map (fn [page] {:type :page :data page}) pages)
  51. files (map (fn [file] {:type :file :data file}) files)
  52. blocks (map (fn [block] {:type :block :data block}) blocks)
  53. result (if config/publishing?
  54. (concat pages files blocks)
  55. (concat new-page pages new-file files blocks))]
  56. [:div.absolute.rounded-md.shadow-lg
  57. {:style (merge
  58. {:top 48
  59. :left 32
  60. :width 500})}
  61. (ui/auto-complete
  62. result
  63. {:on-chosen (fn [{:keys [type data]}]
  64. (search-handler/clear-search!)
  65. (leave-focus)
  66. (case type
  67. :new-page
  68. (page-handler/create! search-q)
  69. :page
  70. (route/redirect! {:to :page
  71. :path-params {:name data}})
  72. :new-file
  73. (file-handler/create! search-q)
  74. :file
  75. (route/redirect! {:to :file
  76. :path-params {:path data}})
  77. :block
  78. (let [page (:page/name (:block/page data))
  79. path (str "/page/" (util/encode-str page) "#ls-block-" (:block/uuid data))]
  80. (route/redirect-with-fragment! path))
  81. nil))
  82. :on-shift-chosen (fn [{:keys [type data]}]
  83. (case type
  84. :page
  85. (let [page (db/entity [:page/name data])]
  86. (state/sidebar-add-block!
  87. (state/get-current-repo)
  88. (:db/id page)
  89. :page
  90. {:page page}))
  91. :block
  92. (let [block (db/entity [:block/uuid (:block/uuid data)])]
  93. (state/sidebar-add-block!
  94. (state/get-current-repo)
  95. (:db/id block)
  96. :block
  97. block))
  98. nil))
  99. :item-render (fn [{:keys [type data]}]
  100. (case type
  101. :new-page
  102. [:div.text.font-bold (str (t :new-page) ": ")
  103. [:span.ml-1 (str "\"" search-q "\"")]]
  104. :new-file
  105. [:div.text.font-bold (str (t :new-file) ": ")
  106. [:span.ml-1 (str "\"" search-q "\"")]]
  107. :page
  108. [:div.text-sm.font-medium
  109. [:span.text-xs.rounded.border.mr-2.px-1 {:title "Page"}
  110. "P"]
  111. data]
  112. :file
  113. [:div.text-sm.font-medium
  114. [:span.text-xs.rounded.border.mr-2.px-1 {:title "File"}
  115. "F"]
  116. data]
  117. :block
  118. (let [{:block/keys [page content]} data]
  119. (let [page (:page/original-name page)]
  120. [:div.flex-1
  121. [:div.text-sm.font-medium page]
  122. (highlight content search-q)]))
  123. nil))})])))
  124. (rum/defc search < rum/reactive
  125. (mixins/event-mixin
  126. (fn [state]
  127. (mixins/hide-when-esc-or-outside
  128. state
  129. :on-hide (fn []
  130. (search-handler/clear-search!)
  131. (leave-focus)))))
  132. []
  133. (let [search-result (state/sub :search/result)
  134. search-q (state/sub :search/q)
  135. show-result? (boolean (seq search-result))]
  136. (rum/with-context [[t] i18n/*tongue-context*]
  137. [:div#search.flex-1.flex
  138. [:div.flex.md:ml-0
  139. [:label.sr-only {:for "search_field"} (t :search)]
  140. [:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
  141. [:div.absolute.inset-y-0.flex.items-center.pointer-events-none.left-0
  142. [:svg.h-5.w-5
  143. {:view-box "0 0 20 20", :fill "currentColor"}
  144. [:path
  145. {:d
  146. "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
  147. :clip-rule "evenodd",
  148. :fill-rule "evenodd"}]]]
  149. [:input#search_field.block.w-full.h-full.pr-3.py-2.rounded-md.focus:outline-none.placeholder-gray-500.focus:placeholder-gray-400.sm:text-sm.sm:bg-transparent
  150. {:style {:padding-left "2rem"}
  151. :placeholder (t :search)
  152. :auto-complete (if (util/chrome?) "chrome-off" "off") ; off not working here
  153. :default-value ""
  154. :on-change (fn [e]
  155. (let [value (util/evalue e)]
  156. (if (string/blank? value)
  157. (search-handler/clear-search!)
  158. (do
  159. (state/set-q! value)
  160. (search-handler/search value)))))}]
  161. (when-not (string/blank? search-q)
  162. (ui/css-transition
  163. {:class-names "fade"
  164. :timeout {:enter 500
  165. :exit 300}}
  166. (search-auto-complete search-result search-q)))]]])))