diff --git a/web/package.json b/web/package.json index 0baea4debc..f0d2e84bdc 100644 --- a/web/package.json +++ b/web/package.json @@ -17,10 +17,8 @@ "clean": "/usr/bin/rm -rf target; /usr/bin/rm -rf ../resources/static/js/compiled; /usr/bin/rm -rf ../resources/static/js/cljs-runtime;" }, "dependencies": { - "caret-pos": "^1.2.2", "react": "^16.12.0", "react-dom": "^16.12.0", - "react-textarea-autosize": "^7.1.2", "react-transition-group": "^4.3.0" } } diff --git a/web/src/main/frontend/components/file.cljs b/web/src/main/frontend/components/file.cljs index 666bf74a4d..9a9b7baa0e 100644 --- a/web/src/main/frontend/components/file.cljs +++ b/web/src/main/frontend/components/file.cljs @@ -64,11 +64,11 @@ (sidebar/sidebar [:div.content [:h3.mb-2 (str "Update " path)] - [:textarea - {:rows rows - :default-value initial-content + (ui/textarea + {:initial-value initial-content + :value-atom content :on-change #(reset! content (.. % -target -value)) - :auto-focus true}] + :auto-focus true}) [:div.mt-1.mb-1.relative.rounded-md.shadow-sm [:input.form-input.block.w-full.sm:text-sm.sm:leading-5 {:placeholder "Commit message" diff --git a/web/src/main/frontend/components/journal.cljs b/web/src/main/frontend/components/journal.cljs index ffe6a0ebf4..eff5a11f5e 100644 --- a/web/src/main/frontend/components/journal.cljs +++ b/web/src/main/frontend/components/journal.cljs @@ -9,9 +9,9 @@ [frontend.state :as state :refer [edit-content]] [frontend.format.org-mode :as org] [goog.object :as gobj] + [goog.dom :as gdom] [frontend.image :as image] - [frontend.components.content :as content] - [goog.dom :as gdom])) + [frontend.components.content :as content])) (rum/defc editor-box < (mixins/event-mixin @@ -24,15 +24,18 @@ (:edit? @state/state)) :on-hide (fn [] (handler/save-current-edit-journal! (str heading "\n" (string/trimr @edit-content) "\n\n"))))))) + {:did-mount (fn [state] + (handler/move-cursor-to-end (gdom/getElement "journal-edit-box")) + state)} [heading content] [:div.flex-1 - (ui/textarea-autosize - {:ref (fn [ref] - (handler/set-edit-node! ref)) + (ui/textarea + {:id "journal-edit-box" :on-change (fn [e] (handler/reset-cursor-pos! e) (reset! edit-content (util/evalue e))) - :default-value content + :initial-value content + :value-atom edit-content :auto-focus true :style {:border "none" :border-radius 0 @@ -53,8 +56,7 @@ (fn [signed-url] ;; insert into the text (handler/insert-image! signed-url))))))) - ;; :hidden true - }]]) + :hidden true}]]) (defn split-first [re s] (clojure.string/split s re 2)) @@ -67,58 +69,26 @@ result))) (rum/defc journal-cp < rum/reactive - (mixins/event-mixin - (fn [state] - (let [{:keys [title content]} (first (:rum/args state))] - (mixins/hide-when-esc-or-outside - state - nil - :show-fn (fn [] - (:edit? @state/state)) - :on-hide (fn [] - (let [[heading content] (split-heading-body content)] - (handler/save-current-edit-journal! (str heading "\n" (string/trimr @edit-content) "\n\n")))) - :node (gdom/getElement "journal-edit"))))) [{:keys [uuid title content] :as journal}] (let [{:keys [edit? edit-journal]} (rum/react state/state) [heading content] (split-heading-body content)] [:div.flex-1 [:h1.text-gray-600 {:style {:font-weight "450"}} title] - [:div {:id (str "journal-edit") - :content-editable true - :on-input (fn [e] - (let [value (gobj/getValueByKeys e "currentTarget" "textContent")] - (prn "value: " value) - (reset! edit-content value))) - ;; :on-click (fn [] - ;; (handler/edit-journal! content journal) - ;; (reset! edit-content content)) - :style {:padding 8 - :min-height 200}} - (if (or (not content) - (string/blank? content)) - [:div] - (content/html content "org" org/config-with-line-break))] - ;; (if (and edit? (= uuid (:uuid edit-journal))) - ;; (editor-box heading content) - ;; [:div {:id (str "journal-" uuid) - ;; :on-change (fn [e] - ;; (reset! edit-content (util/evalue e))) - ;; :content-editable true - ;; ;; :on-click (fn [] - ;; ;; (handler/edit-journal! content journal) - ;; ;; (reset! edit-content content)) - ;; :style {:padding 8 - ;; :min-height 200}} - ;; (if (or (not content) - ;; (string/blank? content)) - ;; [:div] - ;; (content/html content "org" org/config-with-line-break))]) - ])) + (if (and edit? (= uuid (:uuid edit-journal))) + (editor-box heading content) + [:div {:on-click (fn [] + (handler/edit-journal! content journal) + (reset! edit-content content)) + :style {:padding 8 + :min-height 200}} + (if (or (not content) + (string/blank? content)) + [:div] + (content/html content "org" org/config-with-line-break))])])) -(rum/defc journals +(rum/defc journals < rum/reactive [latest-journals] [:div#journals (ui/infinite-list diff --git a/web/src/main/frontend/components/search.cljs b/web/src/main/frontend/components/search.cljs new file mode 100644 index 0000000000..25402457d9 --- /dev/null +++ b/web/src/main/frontend/components/search.cljs @@ -0,0 +1,55 @@ +(ns frontend.components.search + (:require [rum.core :as rum] + [frontend.util :as util] + [frontend.handler :as handler] + [frontend.ui :as ui] + [frontend.state :as state] + [clojure.string :as string] + [goog.crypt.base64 :as b64])) + +(rum/defc dropdown-content-wrapper [state content] + [:div.origin-top-left.absolute.left-0.mt-0.w-48.rounded-md.shadow-lg + {:class (case state + "entering" "transition ease-out duration-100 transform opacity-0 scale-95" + "entered" "transition ease-out duration-100 transform opacity-100 scale-100" + "exiting" "transition ease-in duration-75 transform opacity-100 scale-100" + "exited" "transition ease-in duration-75 transform opacity-0 scale-95")} + content]) + +(rum/defc search < rum/reactive + [] + (let [search-result (:search/result (rum/react state/state)) + show-result? (boolean (seq search-result))] + [:div.flex-1.flex + [:div.w-full.flex.md:ml-0 + [:label.sr-only {:for "search_field"} "Search"] + [:div.relative.w-full.text-gray-400.focus-within:text-gray-600 + [:div.absolute.inset-y-0.left-0.flex.items-center.pointer-events-none + [:svg.h-5.w-5 + {:viewBox "0 0 20 20", :fill "currentColor"} + [:path + {:d + "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", + :clip-rule "evenodd", + :fill-rule "evenodd"}]]] + [:input#search_field.block.w-full.h-full.pl-8.pr-3.py-2.rounded-md.text-gray-900.placeholder-gray-500.focus:outline-none.focus:placeholder-gray-400.sm:text-sm + {:placeholder "Search" + :default-value "" + :on-change (fn [e] + (let [value (util/evalue e)] + (if-not (string/blank? value) + (handler/search value) + (handler/clear-search!))))}] + (ui/css-transition + {:in show-result? :timeout 0} + (fn [state] + (if show-result? + (dropdown-content-wrapper + state + [:div + [:ul.m-3 + (for [[file content] search-result] + [:li.py-2 {:key (str "search-" file)} + [:a {:href (str "/file/" (b64/encodeString file)) + :on-click handler/clear-search!} + (str file)]])]]))))]]])) diff --git a/web/src/main/frontend/components/sidebar.cljs b/web/src/main/frontend/components/sidebar.cljs index c4a41de019..990ca1eae7 100644 --- a/web/src/main/frontend/components/sidebar.cljs +++ b/web/src/main/frontend/components/sidebar.cljs @@ -5,6 +5,7 @@ [frontend.db :as db] [frontend.components.repo :as repo] [frontend.components.journal :as journal] + [frontend.components.search :as search] [goog.crypt.base64 :as b64] [frontend.util :as util] [frontend.state :as state] @@ -131,20 +132,7 @@ :stroke-linejoin "round", :stroke-linecap "round"}]]] [:div.flex-1.px-4.flex.justify-between - [:div.flex-1.flex - [:div.w-full.flex.md:ml-0 - [:label.sr-only {:for "search_field"} "Search"] - [:div.relative.w-full.text-gray-400.focus-within:text-gray-600 - [:div.absolute.inset-y-0.left-0.flex.items-center.pointer-events-none - [:svg.h-5.w-5 - {:viewBox "0 0 20 20", :fill "currentColor"} - [:path - {:d - "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", - :clip-rule "evenodd", - :fill-rule "evenodd"}]]] - [:input#search_field.block.w-full.h-full.pl-8.pr-3.py-2.rounded-md.text-gray-900.placeholder-gray-500.focus:outline-none.focus:placeholder-gray-400.sm:text-sm - {:placeholder "Search"}]]]] + (search/search) [:div.ml-4.flex.items-center.md:ml-6 [:div {:class (if pulling? "loader")} [:button.p-1.text-gray-400.rounded-full.hover:bg-gray-100.hover:text-gray-500.focus:outline-none.focus:shadow-outline.focus:text-gray-500 diff --git a/web/src/main/frontend/handler.cljs b/web/src/main/frontend/handler.cljs index 991c9995c2..c554e73279 100644 --- a/web/src/main/frontend/handler.cljs +++ b/web/src/main/frontend/handler.cljs @@ -5,6 +5,7 @@ [frontend.state :as state] [frontend.db :as db] [frontend.storage :as storage] + [frontend.search :as search] [frontend.util :as util] [frontend.config :as config] [clojure.walk :as walk] @@ -147,7 +148,8 @@ file-path (str "/" path) default-content (default-month-journal-content)] (p/let [_ (-> (fs/mkdir (str repo-dir "/journals")) - (p/catch identity)) + (p/catch (fn [e] + (prn e)))) file-exists? (fs/create-if-not-exists repo-dir file-path default-content)] (when-not file-exists? (git-add-commit repo-url path "create a month journal" default-content))))) @@ -634,7 +636,6 @@ (defn reset-cursor-pos! [e] (let [new-pos (gobj/getValueByKeys e "target" "selectionEnd")] - (println "cursor position: " new-pos) (reset! state/cursor-pos new-pos))) (defn set-edit-node! @@ -656,6 +657,15 @@ (set! (.-value node) new-content) (move-cursor-to-end node))) +(defn search + [q] + (swap! state/state assoc :search/result (search/search q))) + +(defn clear-search! + [] + (swap! state/state assoc :search/result nil) + (reset! state/q nil)) + (defn start! [] (let [me (set-me-if-exists!)] diff --git a/web/src/main/frontend/search.cljs b/web/src/main/frontend/search.cljs new file mode 100644 index 0000000000..777be87713 --- /dev/null +++ b/web/src/main/frontend/search.cljs @@ -0,0 +1,20 @@ +(ns frontend.search + (:require [frontend.db :as db] + [medley.core :as medley] + [frontend.util :as util])) + +;; TODO: should be very slow! +;; get matched files/pages and highlighted content +(defn search + [q] + (let [contents (->> (db/get-all-files-content (db/get-current-repo)) + (into {}))] + (->> + (medley/map-kv (fn [file content] + [file + (if (re-find (re-pattern (str "(?i)" q)) + content) + content + nil)]) + contents) + (util/remove-nils)))) diff --git a/web/src/main/frontend/state.cljs b/web/src/main/frontend/state.cljs index 3d215097b6..a41b7a91be 100644 --- a/web/src/main/frontend/state.cljs +++ b/web/src/main/frontend/state.cljs @@ -17,9 +17,11 @@ :git/status (storage/get :git/status) :git/error (storage/get :git/error) ;; format => boolean - :format/loading {}} - )) + :format/loading {} + :search/result nil})) (def edit-node (atom nil)) (def edit-content (atom "")) (def cursor-pos (atom nil)) + +(def q (atom nil)) diff --git a/web/src/main/frontend/ui.cljs b/web/src/main/frontend/ui.cljs index bd3e8d10ee..46e12f07da 100644 --- a/web/src/main/frontend/ui.cljs +++ b/web/src/main/frontend/ui.cljs @@ -2,17 +2,52 @@ (:require [rum.core :as rum] [frontend.rum :as r] ["react-transition-group" :refer [TransitionGroup CSSTransition]] - ["react-textarea-autosize" :as Textarea] [frontend.util :as util] [frontend.mixins :as mixins] [frontend.state :as state] + [clojure.string :as string] [goog.object :as gobj] [goog.dom :as gdom])) (defonce transition-group (r/adapt-class TransitionGroup)) (defonce css-transition (r/adapt-class CSSTransition)) -(defonce textarea-autosize (r/adapt-class (gobj/get Textarea "default"))) +(defn- force-update-input + [comp opts] + (assoc (-> opts (dissoc :on-change)) + :on-change (fn [e] + (when-let [on-change (:on-change opts)] + (on-change e)) + (.forceUpdate comp)))) + +(defn- count-newlines + [s] + (count (re-seq #"\n" (or s "")))) + +(rum/defcc textarea < + {:init (fn [state props] + (let [{:keys [initial-value value-atom]} (first (:rum/args state))] + (reset! value-atom (string/trim initial-value))) + state)} + [comp opts] + (when-let [value-atom (:value-atom opts)] + (let [inc-rows (get opts :inc-rows 1) + rows (+ inc-rows (count-newlines @value-atom))] + [:textarea + (-> (force-update-input comp opts) + (assoc :rows rows + :value @value-atom))]))) + +(rum/defc content-editable < + {:did-mount (fn [state] + (let [{:keys [id value]} (first (:rum/args state)) + node (rum/ref-node state id)] + (set! (.-innerText node) value) + state))} + [{:keys [id on-change value]}] + [:div.textarea {:ref id + :on-input #(on-change (.-innerText (.-target %))) + :content-editable "true"}]) (rum/defc dropdown-content-wrapper [state content] [:div.origin-top-right.absolute.right-0.mt-2.w-48.rounded-md.shadow-lg diff --git a/web/src/main/frontend/util.cljs b/web/src/main/frontend/util.cljs index 1983f23b6b..b66efc6db1 100644 --- a/web/src/main/frontend/util.cljs +++ b/web/src/main/frontend/util.cljs @@ -4,8 +4,7 @@ [promesa.core :as p] [clojure.walk :as walk] [clojure.string :as string] - [cljs-bean.core :as bean] - ["caret-pos" :as caret])) + [cljs-bean.core :as bean])) (defn evalue [event] @@ -197,11 +196,3 @@ (reset! t nil) (apply f args)) threshold)))))) - -(defn caret-pos - [input] - ((gobj/get caret "position") input)) - -(defn caret-offset - [input] - ((gobj/get caret "offset") input)) diff --git a/web/yarn.lock b/web/yarn.lock index e050cff7d6..191622e4f9 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2,13 +2,6 @@ # yarn lockfile v1 -"@babel/runtime@^7.1.2": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3": version "7.7.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" @@ -245,11 +238,6 @@ caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001030: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001031.tgz#76f1bdd39e19567b855302f65102d9a8aaad5930" integrity sha512-DpAP5a1NGRLgYfaNCaXIRyGARi+3tJA2quZXNNA1Du26VyVkqvy2tznNu5ANyN1Y5aX44QDotZSVSUSi2uMGjg== -caret-pos@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/caret-pos/-/caret-pos-1.2.2.tgz#b2bac513eaf3680201df38d0de22e8a0d5d0eace" - integrity sha512-C+z3AZU3a/V+YxK+ZvM+fSLs9rRGPAg9ZbuchTfAz572BiT76GOm6H4padNnSf5qKAKLjt0vlm1zJLEN/ftApg== - chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -892,7 +880,7 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -prop-types@^15.6.0, prop-types@^15.6.2: +prop-types@^15.6.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -973,14 +961,6 @@ react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== -react-textarea-autosize@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda" - integrity sha512-uH3ORCsCa3C6LHxExExhF4jHoXYCQwE5oECmrRsunlspaDAbS4mGKNlWZqjLfInWtFQcf0o1n1jC/NGXFdUBCg== - dependencies: - "@babel/runtime" "^7.1.2" - prop-types "^15.6.0" - react-transition-group@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" @@ -1031,11 +1011,6 @@ regenerator-runtime@^0.13.2: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== -regenerator-runtime@^0.13.4: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== - resolve@^1.14.2: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"