<commit_before>(ns clojuredocs.mods.search
  (:require [reagent.core :as rea]
            [nsfw.ops :as ops]
            [nsfw.page :as page]
            [dommy.core :as dommy :refer-macros [sel1 sel]]
            [cljs.core.async :as async
             :refer [<! >! chan close! sliding-buffer put! alts! timeout pipe mult tap]]
            [clojuredocs.ajax :refer [ajax]]
            [clojuredocs.anim :as anim]
            [clojure.string :as str]
            [cljs.reader :as reader]
            [clojuredocs.util :as util]
            [clojuredocs.metrics :as metrics])
  (:require-macros [cljs.core.async.macros :refer [go go-loop]]))

(defn ellipsis [n s]
  (if (> (- (count s) 3) n)
    (str (->> s
              (take (- n 3))
              (apply str))
         "...")
    s))

(defn handle-search-active-state [ac-text]
  (if (empty? ac-text)
    (dommy/remove-class! (sel1 :body) :search-active)
    (dommy/add-class! (sel1 :body) :search-active)))

;; Landing page autocomplete

(defn $ac-see-alsos [see-alsos]
  (when-not (empty? see-alsos)
    (let [limit 5
          num-left (- (count see-alsos) limit)
          see-alsos (take limit see-alsos)]
      [:div.see-alsos
       [:span.see-also-label "see also:"]
       [:ul
        (->> see-alsos
             (map-indexed
               (fn [i {:keys [ns name] :as sa}]
                 ^{:key i}
                 [:li
                  [:a {:href (util/var-path ns name) :class "var-link"}
                   [:span.namespace ns]
                   "/"
                   [:span.name name]]])))]
       (when (> num-left 0)
         [:span.remaining-label
          (str "+ " num-left " more")])])))

(defn $ac-entry-var [{:keys [href name ns doc see-alsos type examples-count]}]
  [:div.ac-entry
   [:span.ac-type type " / " examples-count " ex"  #_[:br] #_(util/pluralize examples-count "Example" "Examples")]
   [:h4
    [:a {:href href
         :on-click #(do
                      (when (or (.-ctrlKey %)
                                (.-metaKey %)
                                (= 1 (.-button %))) ; middle click
                        (.stopPropagation %)))}
     name " (" ns ")"]]
   [:p (ellipsis 100 doc)]
   ($ac-see-alsos see-alsos)])

(defn $ac-entry-ns [{:keys [href name ns doc desc see-alsos type]}]
  [:div.ac-entry
   [:span.ac-type type]
   [:h4
    [:a {:href href} name]]
   [:p (ellipsis 100 (or doc desc))]
   ($ac-see-alsos see-alsos)])

(defn $ac-entry-page [{:keys [href name desc type href]}]
  [:div.ac-entry
   [:span.ac-type type]
   [:h4
    [:a {:href href} name]]
   [:p (ellipsis 100 desc)]])

(defn $ac-entry [{:keys [type] :as ac-entry}]
  (cond
    (get #{"function" "macro" "var" "special-form"} type)
    ($ac-entry-var ac-entry)

    (= "namespace" type) ($ac-entry-ns ac-entry)

    (= "page" type) ($ac-entry-page ac-entry)
    :else (.log js/console (str "Couldn't render ac entry:" type))))

(defn focused? [$el]
  (= $el (.-activeElement js/document)))

(defn cancel-search? [{:keys [key-code]}]
  (= 27 key-code))

(defn next-res? [{:keys [key-code ctrl?]}]
  (or (and ctrl? (= 78 key-code)) ; ctrl-n
      (= 40 key-code)             ; down arrow
      ))

(defn prev-res? [{:keys [key-code ctrl?]}]
  (or (and ctrl? (= 80 key-code)) ; ctrl-p
      (= 38 key-code)             ; up arrow
      ))

(defn $quick-search-bar [app bus]
  (let [on-change-throttle
        (page/throttle-debounce
          (fn [text]
            (ops/send bus ::ac-text-throttled text))
          {:throttle 200
           :debounce 200})]
    (rea/create-class
      {:reagent-render
       (fn [{:keys [highlighted-index search-loading?
                    ac-results ac-text search-focused?
                    placeholder] :as app}]
         [:form.search
          {:autoComplete "off"
           :on-submit (fn [e]
                        (let [res (and (not (empty? ac-results))
                                       (nth ac-results (or highlighted-index 0)))]
                          (if res
                            (ops/send bus
                              ::ac-select
                              (nth ac-results (or highlighted-index 0)))
                            (ops/send bus
                              ::var-search
                              ac-text)))
                        (.preventDefault e)
                        nil)
           :action "/search"
           :method :get}
          [:input.form-control.query
           {:class (when search-loading? " loading")
            :placeholder (or placeholder "Looking for? (ctrl-s)")
            :name "q"
            :autoComplete "off"
            :ref "input"
            :value ac-text
            :on-change (fn [e]
                         (let [text (.. e -target -value)]
                           (ops/send bus ::ac-text-changed text)
                           (on-change-throttle text)))
            :on-key-down (fn [e]
                           (let [act {:key-code (.-keyCode e)
                                      :ctrl? (.-ctrlKey e)}]
                             (cond
                               (cancel-search? act) (do
                                                      (ops/send bus ::cancel-search ac-text)
                                                      (.preventDefault e))
                               (next-res? act) (do
                                                 (ops/send bus ::move-highlight 1)
                                                 (.preventDefault e))
                               (prev-res? act) (do
                                                 (ops/send bus ::move-highlight -1)
                                                 (.preventDefault e)))))}]])
       :did-mount (fn []
                    #_(let [$input (om/get-node owner "input")]
                        (when (and (not (focused? $input))
                                   search-focused?)
                          (.focus $input)
                          (aset $input "value" (.-value $input)))))
       :did-update (fn [])
       #_(fn []
           (handle-search-active-state ac-text)
           (let [$input (om/get-node owner "input")]
             (when (and (not (focused? $input))
                        search-focused?)
               (.focus $input)
               (aset $input "value" (.-value $input)))))})))

(defn $nav-search-widget [!state bus]
  [$quick-search-bar
   (merge @!state {:placeholder "Looking for? (ctrl-s)"})
   bus])<commit_msg>Fix autofocus on home page. Fixes #150<commit_after>(ns clojuredocs.mods.search
  (:require [reagent.core :as rea]
            [nsfw.ops :as ops]
            [nsfw.page :as page]
            [dommy.core :as dommy :refer-macros [sel1 sel]]
            [cljs.core.async :as async
             :refer [<! >! chan close! sliding-buffer put! alts! timeout pipe mult tap]]
            [clojuredocs.ajax :refer [ajax]]
            [clojuredocs.anim :as anim]
            [clojure.string :as str]
            [cljs.reader :as reader]
            [clojuredocs.util :as util]
            [clojuredocs.metrics :as metrics])
  (:require-macros [cljs.core.async.macros :refer [go go-loop]]))

(defn ellipsis [n s]
  (if (> (- (count s) 3) n)
    (str (->> s
              (take (- n 3))
              (apply str))
         "...")
    s))

(defn handle-search-active-state [ac-text]
  (if (empty? ac-text)
    (dommy/remove-class! (sel1 :body) :search-active)
    (dommy/add-class! (sel1 :body) :search-active)))

;; Landing page autocomplete

(defn $ac-see-alsos [see-alsos]
  (when-not (empty? see-alsos)
    (let [limit 5
          num-left (- (count see-alsos) limit)
          see-alsos (take limit see-alsos)]
      [:div.see-alsos
       [:span.see-also-label "see also:"]
       [:ul
        (->> see-alsos
             (map-indexed
               (fn [i {:keys [ns name] :as sa}]
                 ^{:key i}
                 [:li
                  [:a {:href (util/var-path ns name) :class "var-link"}
                   [:span.namespace ns]
                   "/"
                   [:span.name name]]])))]
       (when (> num-left 0)
         [:span.remaining-label
          (str "+ " num-left " more")])])))

(defn $ac-entry-var [{:keys [href name ns doc see-alsos type examples-count]}]
  [:div.ac-entry
   [:span.ac-type type " / " examples-count " ex"  #_[:br] #_(util/pluralize examples-count "Example" "Examples")]
   [:h4
    [:a {:href href
         :on-click #(do
                      (when (or (.-ctrlKey %)
                                (.-metaKey %)
                                (= 1 (.-button %))) ; middle click
                        (.stopPropagation %)))}
     name " (" ns ")"]]
   [:p (ellipsis 100 doc)]
   ($ac-see-alsos see-alsos)])

(defn $ac-entry-ns [{:keys [href name ns doc desc see-alsos type]}]
  [:div.ac-entry
   [:span.ac-type type]
   [:h4
    [:a {:href href} name]]
   [:p (ellipsis 100 (or doc desc))]
   ($ac-see-alsos see-alsos)])

(defn $ac-entry-page [{:keys [href name desc type href]}]
  [:div.ac-entry
   [:span.ac-type type]
   [:h4
    [:a {:href href} name]]
   [:p (ellipsis 100 desc)]])

(defn $ac-entry [{:keys [type] :as ac-entry}]
  (cond
    (get #{"function" "macro" "var" "special-form"} type)
    ($ac-entry-var ac-entry)

    (= "namespace" type) ($ac-entry-ns ac-entry)

    (= "page" type) ($ac-entry-page ac-entry)
    :else (.log js/console (str "Couldn't render ac entry:" type))))

(defn focused? [$el]
  (= $el (.-activeElement js/document)))

(defn cancel-search? [{:keys [key-code]}]
  (= 27 key-code))

(defn next-res? [{:keys [key-code ctrl?]}]
  (or (and ctrl? (= 78 key-code)) ; ctrl-n
      (= 40 key-code)             ; down arrow
      ))

(defn prev-res? [{:keys [key-code ctrl?]}]
  (or (and ctrl? (= 80 key-code)) ; ctrl-p
      (= 38 key-code)             ; up arrow
      ))

(defn $quick-search-bar [app bus]
  (let [on-change-throttle
        (page/throttle-debounce
          (fn [text]
            (ops/send bus ::ac-text-throttled text))
          {:throttle 200
           :debounce 200})]
    (rea/create-class
      {:reagent-render
       (fn [{:keys [highlighted-index search-loading?
                    ac-results ac-text search-focused?
                    placeholder] :as app}]
         [:form.search
          {:autoComplete "off"
           :on-submit (fn [e]
                        (let [res (and (not (empty? ac-results))
                                       (nth ac-results (or highlighted-index 0)))]
                          (if res
                            (ops/send bus
                              ::ac-select
                              (nth ac-results (or highlighted-index 0)))
                            (ops/send bus
                              ::var-search
                              ac-text)))
                        (.preventDefault e)
                        nil)
           :action "/search"
           :method :get}
          [:input.form-control.query
           {:class (when search-loading? " loading")
            :placeholder (or placeholder "Looking for? (ctrl-s)")
            :name "q"
            :autoComplete "off"
            :autoFocus "autofocus"
            :value ac-text
            :on-change (fn [e]
                         (let [text (.. e -target -value)]
                           (ops/send bus ::ac-text-changed text)
                           (on-change-throttle text)))
            :on-key-down (fn [e]
                           (let [act {:key-code (.-keyCode e)
                                      :ctrl? (.-ctrlKey e)}]
                             (cond
                               (cancel-search? act) (do
                                                      (ops/send bus ::cancel-search ac-text)
                                                      (.preventDefault e))
                               (next-res? act) (do
                                                 (ops/send bus ::move-highlight 1)
                                                 (.preventDefault e))
                               (prev-res? act) (do
                                                 (ops/send bus ::move-highlight -1)
                                                 (.preventDefault e)))))}]])
       :component-did-mount
       (fn [this]
         (when-let [$form (rea/dom-node this)]
           (let [$input (dommy/sel1 $form :input.query)]
             (when (and $input
                        (not (focused? $input)))
               (.focus $input)
               (aset $input "value" (.-value $input))))))
       :did-update (fn [])
       #_(fn []
           (handle-search-active-state ac-text)
           (let [$input (om/get-node owner "input")]
             (when (and (not (focused? $input))
                        search-focused?)
               (.focus $input)
               (aset $input "value" (.-value $input)))))})))

(defn $nav-search-widget [!state bus]
  [$quick-search-bar
   (merge @!state {:placeholder "Looking for? (ctrl-s)"})
   bus])
{'fraction_non_alphanumeric': 0.15289510059898634, 'fraction_numerical': 0.006450622024266626, 'mean_word_length': 1.2943974630021142, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 35, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4995911', 'n_tokens_mistral': 4132, 'n_tokens_neox': 3891, 'n_words': 1004}