From 7b74117d85075dc19942afe7956f7f14f7f2423d Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Tue, 5 Jul 2022 08:05:09 +0800 Subject: [PATCH] enhance: auto-complete for property values --- src/main/frontend/commands.cljs | 26 ++++++------- src/main/frontend/components/block.cljs | 8 ++-- src/main/frontend/components/editor.cljs | 41 ++++++++++++++++---- src/main/frontend/db/model.cljs | 24 ++++++++++-- src/main/frontend/handler/editor.cljs | 49 ++++++++++++++++++++---- src/main/frontend/handler/page.cljs | 8 ++-- src/main/frontend/search.cljs | 14 ++++++- src/main/frontend/state.cljs | 1 - src/main/frontend/util/text.cljs | 18 +++++---- 9 files changed, 142 insertions(+), 47 deletions(-) diff --git a/src/main/frontend/commands.cljs b/src/main/frontend/commands.cljs index 62c9a9a41..407a7a52d 100644 --- a/src/main/frontend/commands.cljs +++ b/src/main/frontend/commands.cljs @@ -333,19 +333,19 @@ (+ current-pos i))) current-pos) orig-prefix (subs edit-content 0 current-pos) - space? (when (and last-pattern orig-prefix) - (let [s (when-let [last-index (string/last-index-of orig-prefix last-pattern)] - (gp-util/safe-subs orig-prefix 0 last-index))] - (not - (or - (and s - (string/ends-with? s "(") - (or (string/starts-with? last-pattern "((") - (string/starts-with? last-pattern "[["))) - (and s (string/starts-with? s "{{embed")))))) - space? (if (and space? (string/starts-with? last-pattern "#[[")) - false - space?) + space? (let [space? (when (and last-pattern orig-prefix) + (let [s (when-let [last-index (string/last-index-of orig-prefix last-pattern)] + (gp-util/safe-subs orig-prefix 0 last-index))] + (not + (or + (and s + (string/ends-with? s "(") + (or (string/starts-with? last-pattern "((") + (string/starts-with? last-pattern "[["))) + (and s (string/starts-with? s "{{embed"))))))] + (if (and space? (string/starts-with? last-pattern "#[[")) + false + space?)) prefix (cond (and backward-truncate-number (integer? backward-truncate-number)) (str (gp-util/safe-subs orig-prefix 0 (- (count orig-prefix) backward-truncate-number)) diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index c378f0884..74b1a66d2 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -421,7 +421,9 @@ [config page-name-in-block page-name redirect-page-name page-entity contents-page? children html-export? label] (let [tag? (:tag? config)] [:a - {:class (if tag? "tag" "page-ref") + {:class (cond-> (if tag? "tag" "page-ref") + (:property? config) + (str " page-property-key")) :data-ref page-name :on-mouse-down (fn [e] @@ -1805,9 +1807,7 @@ [config block k v] (let [date (and (= k :date) (date/get-locale-string (str v)))] [:div - [:a.page-property-key - {:href (rfe/href :page {:name (name k)})} - (name k)] + (page-cp (assoc config :property? true) {:block/name (name k)}) [:span.mr-1 ":"] (cond (int? v) diff --git a/src/main/frontend/components/editor.cljs b/src/main/frontend/components/editor.cljs index a4c57ce15..8514eb73f 100644 --- a/src/main/frontend/components/editor.cljs +++ b/src/main/frontend/components/editor.cljs @@ -237,22 +237,47 @@ (when input (let [current-pos (cursor/pos input) edit-content (state/sub [:editor/content id]) - q (or - (when (>= (count edit-content) current-pos) - (subs edit-content pos current-pos)) - "") + q (:searching-property (editor-handler/get-searching-property input)) matched-properties (editor-handler/get-matched-properties q) + q-property (string/replace (string/lower-case q) #"\s+" "-") non-exist-handler (fn [_state] - ((editor-handler/property-on-chosen-handler id q) nil))] + ((editor-handler/property-on-chosen-handler id q-property) nil))] (ui/auto-complete matched-properties - {:on-chosen (editor-handler/property-on-chosen-handler id q) + {:on-chosen (editor-handler/property-on-chosen-handler id q-property) :on-enter non-exist-handler - :empty-placeholder [:div.px-4.py-2.text-sm (str "Create a new property: " q)] + :empty-placeholder [:div.px-4.py-2.text-sm (str "Create a new property: " q-property)] :header [:div.px-4.py-2.text-sm.font-medium "Matched properties: "] :item-render (fn [property] property) :class "black"}))))) +(rum/defc property-value-search < rum/reactive + {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)} + [id] + (let [pos (:pos (:pos (state/get-editor-action-data))) + property (:property (state/get-editor-action-data)) + input (gdom/getElement id)] + (when (and input + (not (string/blank? property))) + (let [current-pos (cursor/pos input) + edit-content (state/sub [:editor/content id]) + start-idx (string/last-index-of (subs edit-content 0 current-pos) "::") + q (or + (when (>= current-pos (+ start-idx 2)) + (subs edit-content (+ start-idx 2) current-pos)) + "") + matched-values (editor-handler/get-matched-property-values property q) + non-exist-handler (fn [_state] + ((editor-handler/property-value-on-chosen-handler id q) nil))] + (ui/auto-complete + matched-values + {:on-chosen (editor-handler/property-value-on-chosen-handler id q) + :on-enter non-exist-handler + :empty-placeholder [:div.px-4.py-2.text-sm (str "Create a new property value: " q)] + :header [:div.px-4.py-2.text-sm.font-medium "Matched property values: "] + :item-render (fn [property-value] property-value) + :class "black"}))))) + (rum/defcs input < rum/reactive (rum/local {} ::input-value) (mixins/event-mixin @@ -515,6 +540,8 @@ (= :property-search action) (animated-modal "property-search" (property-search id) true) + (= :property-value-search action) + (animated-modal "property-value-search" (property-value-search id) true) (= :datepicker action) (animated-modal "date-picker" (datetime-comp/date-picker id format nil) false) diff --git a/src/main/frontend/db/model.cljs b/src/main/frontend/db/model.cljs index 84e2770d9..fb326af50 100644 --- a/src/main/frontend/db/model.cljs +++ b/src/main/frontend/db/model.cljs @@ -1348,14 +1348,32 @@ :where [_ :block/properties ?p]] (conn/get-db)) - properties (remove (fn [m] (or (empty? m) - (and (= 1 (count m)) - (= :id (first (keys m)))))) properties)] + properties (remove (fn [m] (empty? m)) properties)] (->> (map keys properties) (apply concat) distinct + (remove #{:id}) sort))) +(defn get-property-values + [property] + (let [pred (fn [_db properties] + (get properties property))] + (->> + (d/q + '[:find [?property-val ...] + :in $ ?pred + :where + [_ :block/properties ?p] + [(?pred $ ?p) ?property-val]] + (conn/get-db) + pred) + (map (fn [x] (if (coll? x) x [x]))) + (apply concat) + (remove string/blank?) + (distinct) + (sort)))) + (defn get-template-by-name [name] (when (string? name) diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index 4c129f4bf..a711bc49f 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -1620,6 +1620,10 @@ [q] (search/property-search q)) +(defn get-matched-property-values + [property q] + (search/property-value-search property q)) + (defn get-matched-commands [input] (try @@ -1848,6 +1852,15 @@ (and (= last-input-char last-prev-input-char commands/colon) (or (nil? prev-prev-input-char) (= prev-prev-input-char "\n"))) + (do + (cursor/move-cursor-backward input 2) + (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}) + (state/set-editor-show-property-search! true)) + + (and + (not= :property-search (state/get-editor-action)) + (or (wrapped-by? input "" "::") + (wrapped-by? input "\n" "::"))) (do (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}) (state/set-editor-show-property-search! true)) @@ -2054,17 +2067,39 @@ (insert-template! element-id db-id {:replace-empty-target? true}))) +(defn get-searching-property + [input] + (let [value (.-value input) + pos (util/get-selection-start input) + postfix (subs value pos) + end-index (when-let [idx (string/index-of postfix "::")] + (+ (max 0 (count (subs value 0 pos))) idx)) + start-index (or (when-let [p (string/last-index-of (subs value 0 pos) "\n")] + (inc p)) + 0)] + {:end-index end-index + :searching-property (when (and start-index end-index (>= end-index start-index)) + (subs value start-index end-index))})) + (defn property-on-chosen-handler [element-id q] (fn [property] (when-let [input (gdom/getElement element-id)] - (let [value (.-value input) - pos (util/get-selection-start input) - last-index (string/last-index-of (subs value 0 pos) "::") - last-pattern (subs value last-index pos)] - (commands/insert! element-id (str (or property q) ":: ") - {:last-pattern last-pattern}) - (state/clear-editor-action!))))) + (let [{:keys [end-index searching-property]} (get-searching-property input)] + (cursor/move-cursor-to input (+ end-index 2)) + (commands/insert! element-id (str (or property q) "::") + {:last-pattern (str searching-property "::")}) + (state/set-editor-action! :property-value-search) + (state/set-editor-action-data! {:property (or property q) + :pos (cursor/pos input)}))))) + +(defn property-value-on-chosen-handler + [element-id q] + (fn [property-value] + (when-let [input (gdom/getElement element-id)] + (commands/insert! element-id (or property-value q) + {:last-pattern q})) + (state/clear-editor-action!))) (defn parent-is-page? [{{:block/keys [parent page]} :data :as node}] diff --git a/src/main/frontend/handler/page.cljs b/src/main/frontend/handler/page.cljs index 87bf63f8f..44624badf 100644 --- a/src/main/frontend/handler/page.cljs +++ b/src/main/frontend/handler/page.cljs @@ -242,7 +242,7 @@ (defn- replace-property-ref! [content old-name new-name] - (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "_")) + (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "-")) old-property (str old-name "::") new-property (str (name new-name) "::")] (util/replace-ignore-case content old-property new-property))) @@ -274,7 +274,7 @@ (replace-old-page! f old-name new-name)) (and (keyword f) (= (name f) old-name)) - (keyword (string/replace (string/lower-case new-name) #"\s+" "_")) + (keyword (string/replace (string/lower-case new-name) #"\s+" "-")) :else f)) @@ -380,6 +380,7 @@ {:block/uuid uuid :block/content content :block/properties properties + :block/properties-order (map first properties) :block/refs (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page)) :block/path-refs (rename-update-block-refs! (:block/path-refs block) (:db/id page) (:db/id to-page))})))) blocks) (remove nil?))] @@ -568,7 +569,8 @@ (rename-namespace-pages! repo old-name new-name)) (rename-nested-pages old-name new-name)) (when (string/blank? new-name) - (notification/show! "Please use a valid name, empty name is not allowed!" :error))))) + (notification/show! "Please use a valid name, empty name is not allowed!" :error))) + (ui-handler/re-render-root!))) (defn- split-col-by-element [col element] diff --git a/src/main/frontend/search.cljs b/src/main/frontend/search.cljs index 5b947b116..410a4687f 100644 --- a/src/main/frontend/search.cljs +++ b/src/main/frontend/search.cljs @@ -172,7 +172,19 @@ (if (string/blank? q) properties (let [result (fuzzy-search properties q :limit limit)] - (vec result))))))) + (vec result))))))) + +(defn property-value-search + ([property q] + (property-value-search property q 10)) + ([property q limit] + (let [q (clean-str q) + result (db-model/get-property-values (keyword property))] + (when (seq result) + (if (string/blank? q) + result + (let [result (fuzzy-search result q :limit limit)] + (vec result))))))) (defn sync-search-indice! [repo tx-report] diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs index 0a5d7d837..54a369f8c 100644 --- a/src/main/frontend/state.cljs +++ b/src/main/frontend/state.cljs @@ -588,7 +588,6 @@ (defn set-editor-action! [value] - (js/console.trace) (set-state! :editor/action value)) (defn set-editor-action-data! diff --git a/src/main/frontend/util/text.cljs b/src/main/frontend/util/text.cljs index e27bfb705..d839c4d32 100644 --- a/src/main/frontend/util/text.cljs +++ b/src/main/frontend/util/text.cljs @@ -86,19 +86,21 @@ (defn get-string-all-indexes "Get all indexes of `value` in the string `s`." - [s value] - (loop [acc [] - i 0] - (if-let [i (string/index-of s value i)] - (recur (conj acc i) (+ i (count value))) - acc))) + [s value before?] + (if (= value "") + (if before? [0] [(dec (count s))]) + (loop [acc [] + i 0] + (if-let [i (string/index-of s value i)] + (recur (conj acc i) (+ i (count value))) + acc)))) (defn wrapped-by? "`pos` must be wrapped by `before` and `and` in string `value`, e.g. ((a|b))" [value pos before end] - (let [before-matches (->> (get-string-all-indexes value before) + (let [before-matches (->> (get-string-all-indexes value before true) (map (fn [i] [i :before]))) - end-matches (->> (get-string-all-indexes value end) + end-matches (->> (get-string-all-indexes value end false) (map (fn [i] [i :end]))) indexes (sort-by first (concat before-matches end-matches [[pos :between]])) ks (map second indexes)