├── .gitignore ├── LICENSE ├── README.md ├── boot.properties ├── build.boot ├── demo └── re_complete │ ├── dictionary.cljs │ └── example.cljs ├── html ├── css │ └── main.css └── index.html ├── src ├── main.cljs.edn └── re_complete │ ├── app.cljs │ ├── core.cljs │ ├── handlers.cljs │ └── utils.cljs ├── test └── re_complete │ ├── app_test.cljs │ └── utils_test.cljs └── version.properties /.gitignore: -------------------------------------------------------------------------------- 1 | ### BOOT ####################################################################### 2 | 3 | /.boot/ 4 | 5 | ### LEININGEN ################################################################## 6 | 7 | .lein-* 8 | 9 | ### MAVEN (include pom.xml in /base) ########################################### 10 | 11 | *.jar 12 | *.war 13 | pom.xml 14 | pom.xml.asc 15 | 16 | ### NREPL ###################################################################### 17 | 18 | .repl-* 19 | .nrepl-* 20 | 21 | ### JAVA ####################################################################### 22 | 23 | /hs_err_pid*.log 24 | 25 | ### OSX ######################################################################## 26 | 27 | .DS_Store 28 | 29 | ### EMACS ###################################################################### 30 | 31 | [#]*[#] 32 | 33 | ### VIM ######################################################################## 34 | 35 | *.swn 36 | *.swo 37 | *.swp 38 | 39 | ### PROJECT #################################################################### 40 | 41 | /target/ 42 | 43 | /out/ 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # re-complete 2 | re-complete is a text completion library for re-frame applications 3 | 4 | [![via Clojars](http://clojars.org/re-complete/latest-version.svg)](http://clojars.org/re-complete) 5 | 6 | This library provides the possibilty to set `trim-chars` and `case-sensitive?` through options. Using the `trim-chars` option, 7 | the user can describe which type of characters he wants to ignore at the beginning and at the end of the word. 8 | 9 | For example, if the `trim-chars` has a value `"()"`, the first eligible item of the input to autocomplete 10 | will be the first item after the `(` or `)`. 11 | 12 | ![alt tag](http://s21.postimg.org/hc3lopv6v/Screen_Shot_2016_03_14_at_15_13_14.png) 13 | 14 | If the `trim-chars` is not set, the first item of the input to autocomplete in this particular case is `(`. 15 | The list of the items to autocomplete is empty because in my autocompletion-data I don't have any words starting on `(`. 16 | 17 | ![alt tag](http://s14.postimg.org/90jw4k7a9/Screen_Shot_2016_03_14_at_15_13_27.png) 18 | 19 | The `case-sentitive?` option has a default value `false`. So if you want your autocomplete to be case 20 | sensitive, you should set `case-sensitive? true` 21 | 22 | # Usage 23 | 24 | The re-complete library has only few functions: 25 | 26 | For setting the `options` you need to `dispatch` your `options` (`:trim-chars` `:case-sensitive?`). 27 | All dispatch functions takes as argument `:linked-component-key` (name for the input) and `options` 28 | 29 | ```Clojure 30 | (dispatch [:options list-name options]) 31 | ``` 32 | 33 | For setting the `dictionary` you need to dispatch your `dictionary` (list of your autocomplete options). 34 | This dispatch function takes as arguments `linked-component-key` (name for the input) and `dictionary` 35 | 36 | ```Clojure 37 | (dispatch [:options list-name dictionary]) 38 | ``` 39 | 40 | The last item you need to dispatch is your `input`. 41 | This dispatch function takes as arguments `linked-component-key` (name for the input) and `input` 42 | 43 | ```Clojure 44 | (dispatch [:options list-name input]) 45 | ``` 46 | [example of the use](https://github.com/ScalaConsultants/re-complete/blob/master/demo/re_complete/example.cljs#L61) 47 | 48 | ```clojure 49 | ((dispatch [:options "vegetable" {:trim-chars "()",}]) 50 | (dispatch [:dictionary "vegetable" '("broccoli" "asparagus" "appricot" "cale")]) 51 | (fn [] 52 | [:ul 53 | [:li 54 | [:input {:type "text" 55 | :value "" 56 | :on-change (fn [event] 57 | (dispatch [:input "vegetable" (.. event -target -value)]))}]]] 58 | [:div.re-completion-list-part 59 | [re-complete/completions "vegetable]])) 60 | ``` 61 | 62 | When the change of input occurred (for example we will write `a` to input), in our app-state we will have 63 | 64 | ```clojure 65 | {:re-complete {:linked-components 66 | {:vegetable {:text "" 67 | :change-index 0 68 | :current-word "a" 69 | :completions ["appricot" "asparagus"] 70 | :dictionary '("broccoli" "asparagus" "appricot" "cale") 71 | :options {:trim-chars "()", 72 | :case-sensitive? false}}}}} 73 | ``` 74 | 75 | The last function we need is `completions` 76 | This function displays list of the items for autocompletition. After click on the item, the item is placed in the right position in text. 77 | 78 | If you want to use custom callback function, you can add it as additional optional argument to `completions` function. 79 | 80 | `completions` takes as an argument `linked-component-key` - name of the input and optional argument `onclick-callback` 81 | 82 | 83 | [example of the use](https://github.com/ScalaConsultants/re-complete/blob/master/demo/re_complete/example.cljs#L89) 84 | 85 | ```clojure 86 | [:div.re-completion-list-part 87 | [re-complete/completions "vegetable"]] 88 | ``` 89 | 90 | # CSS styling 91 | 92 | The `re-completion-list` function renders `ul` with class `re-completion-list` and `li` items with class `re-completion-item`. 93 | 94 | Enjoy the library! 95 | 96 | # License 97 | 98 | Copyright © 2016 99 | 100 | Distributed under the Eclipse Public License. 101 | 102 | -------------------------------------------------------------------------------- /boot.properties: -------------------------------------------------------------------------------- 1 | BOOT_CLOJURE_NAME=org.clojure/clojure 2 | BOOT_CLOJURE_VERSION=1.7.0 3 | BOOT_VERSION=2.5.5 4 | BOOT_EMIT_TARGET=no 5 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :resource-paths #{"src" "demo" "html"} 3 | :dependencies '[;; Boot deps 4 | [adzerk/boot-cljs "1.7.228-1" :scope "test"] 5 | [adzerk/boot-cljs-repl "0.3.0" :scope "test"] 6 | [adzerk/boot-reload "0.4.4" :scope "test"] 7 | [pandeiro/boot-http "0.7.1-SNAPSHOT" :scope "test"] 8 | [degree9/boot-semver "1.2.4" :scope "test"] 9 | [adzerk/bootlaces "0.1.13" :scope "test"] 10 | 11 | ;; Boot test 12 | [crisptrutski/boot-cljs-test "0.2.2-SNAPSHOT" :scope "test"] 13 | 14 | ;; Repl 15 | [cider/cider-nrepl "0.11.0-SNAPSHOT" :scope "test"] 16 | [adzerk/boot-cljs-repl "0.3.0" :scope "test"] 17 | [com.cemerick/piggieback "0.2.1" :scope "test"] 18 | [weasel "0.7.0" :scope "test"] 19 | [org.clojure/tools.nrepl "0.2.12" :scope "test"] 20 | 21 | ;; Dev deps 22 | [org.clojure/clojure "1.7.0"] 23 | [org.clojure/clojurescript "1.7.228"] 24 | [reagent "0.6.0-alpha"] 25 | [re-frame "0.7.0-alpha-3"]]) 26 | 27 | (require 28 | '[adzerk.boot-cljs :refer [cljs]] 29 | '[adzerk.boot-cljs-repl :refer [cljs-repl start-repl]] 30 | '[adzerk.boot-reload :refer [reload]] 31 | '[crisptrutski.boot-cljs-test :refer [test-cljs]] 32 | '[pandeiro.boot-http :refer [serve]] 33 | '[boot-semver.core :refer :all] 34 | '[adzerk.bootlaces :refer :all]) 35 | 36 | (def +version+ (get-version)) 37 | 38 | (bootlaces! +version+) 39 | 40 | (task-options! 41 | pom {:project 're-complete 42 | :version +version+ 43 | :url "https://github.com/Lambda-X/re-complete" 44 | :description "Autocomplete plugin" 45 | :license {"Eclipse Public License" "http://www.eclipse.org/legal/epl-v10.html"}} 46 | test-cljs {:js-env :phantom}) 47 | 48 | (deftask auto-test [] 49 | (set-env! :resource-paths #(conj % "test")) 50 | (comp (watch) 51 | (speak) 52 | (test-cljs))) 53 | 54 | (deftask dev 55 | "Start the dev env..." 56 | [] 57 | (set-env! :source-paths #{"src" "demo"}) 58 | (comp (serve :dir "html") 59 | (watch) 60 | (speak) 61 | (reload :on-jsload 're-complete.example/main) 62 | (cljs-repl) 63 | (cljs :compiler-options {:closure-defines {"goog.DEBUG" false} 64 | :source-map :true 65 | :optimizations :none 66 | :source-map-timestamp true}))) 67 | 68 | (deftask version-file 69 | "A task that includes the version.properties file in the fileset." 70 | [] 71 | (with-pre-wrap [fileset] 72 | (boot.util/info "Add version.properties...\n") 73 | (-> fileset 74 | (add-resource (java.io.File. ".") :include #{#"^version\.properties$"}) 75 | commit!))) 76 | 77 | (deftask build [] 78 | (merge-env! :source-paths #{"src" "demo"} :resource-paths #{"html"}) 79 | (comp (version-file) 80 | (cljs :optimizations :advanced))) 81 | -------------------------------------------------------------------------------- /demo/re_complete/dictionary.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.dictionary) 2 | 3 | (def vegetables '("Amaranth" 4 | "Arrowroot" 5 | "Banana Squash" 6 | "Bell Peppers" 7 | "Black Eyed Peas" 8 | "Black Radish" 9 | "Bok Choy" 10 | "Broccoflower" 11 | "Broccolini" 12 | "Burdock Root" 13 | "Cabbage" 14 | "Carrots" 15 | "Celeriac (Celery Root)" 16 | "Celery" 17 | "Cherry Tomatoes" 18 | "Chinese Eggplants" 19 | "Galangal Root" 20 | "Leek" 21 | "Lettuce" 22 | "Mushrooms" 23 | "Olives" 24 | "Onions" 25 | "Parsnips" 26 | "Pearl Onions" 27 | "Potatoes" 28 | "Rutabagas" 29 | "Salad Savoy" 30 | "Snow Peas" 31 | "Wasabi Root" 32 | "Yucca Root" 33 | "Artichokes" 34 | "Asparagus" 35 | "Belgian Endive" 36 | "Cactus" 37 | "Chayote Squash" 38 | "Chives" 39 | "Collard Greens" 40 | "Corn" 41 | "Fava Beans" 42 | "Fennel" 43 | "Fiddlehead Ferns" 44 | "Green Beans" 45 | "Manoa Lettuce" 46 | "Morel Mushrooms" 47 | "Mustard Greens" 48 | "Pea Pods" 49 | "Peas" 50 | "Purple Asparagus" 51 | "Radicchio" 52 | "Ramps" 53 | "Red Leaf Lettuce" 54 | "Rhubarb" 55 | "Snow Peas" 56 | "Sorrel" 57 | "Spinach" 58 | "Spring Baby Lettuce" 59 | "Swiss Chard" 60 | "Vidalia Onions" 61 | "Watercress" 62 | "Acorn Squash" 63 | "Black Salsify" 64 | "Broccoli" 65 | "Brussels Sprouts" 66 | "Butter Lettuce" 67 | "Buttercup Squash" 68 | "Butternut Squash" 69 | "Cauliflower" 70 | "Chayote Squash" 71 | "Chinese Long Beans" 72 | "Delicata Squash" 73 | "Diakon Radish" 74 | "Endive" 75 | "Garlic" 76 | "Ginger" 77 | "Jalapeno Peppers" 78 | "Jerusalem Artichoke" 79 | "Kohlrabi" 80 | "Pumpkin" 81 | "Radicchio" 82 | "Sweet Dumpling Squash" 83 | "Sweet Potatoes" 84 | "Swiss Chard" 85 | "Turnips" 86 | "Winter Squash")) 87 | 88 | (def fruits '("Acai" 89 | "Aceola" 90 | "Apple" 91 | "Apricots" 92 | "Avocado" 93 | "Banana" 94 | "Blackberry" 95 | "Blueberries" 96 | "Camu Camu berry" 97 | "Cherries" 98 | "Coconut" 99 | "Cranberry" 100 | "Cucumber" 101 | "Currents" 102 | "Dates" 103 | "Durian" 104 | "Fig" 105 | "Goji berries" 106 | "Gooseberry" 107 | "Grapefruit" 108 | "Grapes" 109 | "Jackfruit" 110 | "Kiwi" 111 | "Kumquat" 112 | "Lemon" 113 | "Lime" 114 | "Lucuma" 115 | "Lychee" 116 | "Mango" 117 | "Mangosteen" 118 | "Melon" 119 | "Mulberry" 120 | "Nectarine" 121 | "Orange" 122 | "Papaya" 123 | "Passion Fruit" 124 | "Peach" 125 | "Pear" 126 | "Pineapple" 127 | "Plum" 128 | "Pomegranate" 129 | "Pomelo" 130 | "Prickly Pear" 131 | "Prunes" 132 | "Raspberries" 133 | "Strawberries" 134 | "Tangerine" 135 | "Clementine" 136 | "Watermelon")) 137 | 138 | (def grains '("(Amaranth" 139 | "Barley" 140 | "Brown Rice" 141 | "Brown Rice Bread" 142 | "Brown Rice Tortilla" 143 | "Buckwheat" 144 | "Bulgur (Cracked Wheat)" 145 | "Farro / Emmer" 146 | "Flaxseed" 147 | "Grano" 148 | "Kamut Grain" 149 | "Millet" 150 | "Oats" 151 | "Oat Bread" 152 | "Oat Cereal" 153 | "Oatmeal" 154 | "Popcorn" 155 | "Whole Wheat Cereal Flakes" 156 | "Muesli" 157 | "Rolled Oats" 158 | "Quinoa" 159 | "Rye" 160 | "Sorghum" 161 | "Spelt" 162 | "Teff" 163 | "Triticale" 164 | "Whole Grain Barley" 165 | "Wheat Berries" 166 | "Whole Grain Cornmeal" 167 | "Whole Rye" 168 | "Whole Wheat Bread" 169 | "Whole Wheat Couscous" 170 | "Whole Wheat Crackers" 171 | "Whole Wheat Pasta" 172 | "Whole Wheat Pita Bread" 173 | "Whole Wheat Sandwich Buns And Rolls" 174 | "Whole Wheat Tortillas" 175 | "Wild Rice")) 176 | -------------------------------------------------------------------------------- /demo/re_complete/example.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.example 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [re-complete.core :as re-complete] 4 | [re-complete.dictionary :as dictionary] 5 | [reagent.core :as reagent] 6 | [re-frame.core :refer [register-handler 7 | dispatch 8 | dispatch-sync 9 | register-sub 10 | subscribe]] 11 | [clojure.string :as string])) 12 | 13 | 14 | ;; Initial state 15 | 16 | (def initial-state {}) 17 | 18 | ;; --- Handlers --- 19 | 20 | (register-handler 21 | :initialize 22 | (fn 23 | [db _] 24 | (merge db initial-state))) 25 | 26 | 27 | (register-handler 28 | :add-item-to-list 29 | (fn 30 | [db [_ list-name input]] 31 | (update-in db [(keyword list-name) :added-items] #(vec (conj % input))))) 32 | 33 | 34 | (register-handler 35 | :clear-input 36 | (fn 37 | [db [_ linked-component-key]] 38 | (assoc-in db [:re-complete :linked-components (keyword linked-component-key) :text] ""))) 39 | 40 | ;; --- Subscription Handlers --- 41 | 42 | (register-sub 43 | :get-list 44 | (fn 45 | [db [_ list-name]] 46 | (reaction (get-in @db [(keyword list-name) :added-items])))) 47 | 48 | 49 | ;; --- VIEW --- ;; 50 | 51 | (def my-lists [["vegetable" (sort dictionary/vegetables) {:trim-chars "[]()" 52 | :keys-handling {:visible-items 4 53 | :item-height 20}}] 54 | ["fruit" (sort-by count dictionary/fruits) {:trim-chars "?" 55 | :case-sensitive? true}] 56 | ["grain" dictionary/grains]]) 57 | 58 | (defn list-view [items] 59 | (map (fn [item] 60 | ^{:key item} 61 | [:li.item item]) 62 | items)) 63 | 64 | (defn render-list 65 | ([list-name dictionary] 66 | (render-list list-name dictionary nil)) 67 | ([list-name dictionary options] 68 | (let [get-input (subscribe [:get-previous-input list-name]) 69 | get-list (subscribe [:get-list list-name])] 70 | (dispatch [:options list-name options]) 71 | (dispatch [:dictionary list-name dictionary]) 72 | (fn [] 73 | [:div {:className (str list-name " my-list")} 74 | [:div {:className "panel panel-default re-complete"} 75 | [:div {:className "panel-heading"} 76 | [:h1 (string/capitalize (str list-name "s"))]] 77 | [:div.panel-body 78 | [:ul.checklist 79 | [:li.input 80 | [:input {:type "text" 81 | :className "form-control input-field" 82 | :placeholder (str list-name " name") 83 | :value @get-input 84 | :on-change (fn [event] 85 | (dispatch [:input list-name (.. event -target -value)])) 86 | :on-focus #(dispatch [:focus list-name true]) 87 | :on-blur #(dispatch [:focus list-name false])}] 88 | [:button {:type "button" 89 | :className "btn btn-default button-ok" 90 | :on-click #(do (dispatch [:add-item-to-list list-name @get-input]) 91 | (dispatch [:clear-input list-name]))} 92 | [:span {:className "glyphicon glyphicon-ok check"}]]] 93 | (list-view @get-list)]] 94 | [:div.re-completion-list-part 95 | [re-complete/completions list-name]]]])))) 96 | 97 | (defn my-app [] 98 | (into [:div.my-app] 99 | (map #(into [render-list] %) my-lists))) 100 | 101 | ;; --- Main app fn --- 102 | 103 | (defn ^:export main [] 104 | (dispatch-sync [:initialize]) 105 | (reagent/render [my-app] (.getElementById js/document "app"))) 106 | -------------------------------------------------------------------------------- /html/css/main.css: -------------------------------------------------------------------------------- 1 | body {margin: 0; padding: 0; font-size: 100%; line-height: 1.5;} 2 | article, aside, figcaption, figure, footer, header, nav, section {display: block;} 3 | h1, h2, h3, h4 {margin: 1em 0 .5em; line-height: 1.25;} 4 | h1 {font-size: 2em;} 5 | h2 {font-size: 1.5em;} 6 | h3 {font-size: 1.2em;} 7 | ul, ol {margin: 1em 0; padding-left: 40px;} 8 | p, figure {margin: 1em 0;} 9 | a img {border: none;} 10 | sup, sub {line-height: 0;} 11 | 12 | .form-control.input-field {width: auto; 13 | display: inline-block;} 14 | 15 | li.input {list-style: none; 16 | width:auto; 17 | padding-bottom: 20px; 18 | color: #585858; 19 | font-weight: bold;} 20 | 21 | li.item {list-style: none; 22 | font-size: 16px; 23 | padding-left: 10px; 24 | color: #585858; 25 | font-weight: bold;} 26 | 27 | li.re-completion-item {list-style: none;} 28 | 29 | li.re-completion-selected { 30 | list-style: none; 31 | background-color: gray} 32 | 33 | .re-completion-list-part { 34 | position: absolute; 35 | float: right; 36 | left: 50px; 37 | top: 150px; 38 | z-index: 100;} 39 | 40 | ul.re-completion-list { 41 | max-height: 80px; 42 | min-height: 80px; 43 | min-width: 200px; 44 | overflow-y: scroll; 45 | background: #eee; 46 | padding-left: 15px; 47 | padding-right: 15px; 48 | border-radius: 5px;} 49 | 50 | .my-list { 51 | display: inline-block; 52 | margin-left: 30px;} 53 | 54 | .re-complete { 55 | position: relative; 56 | display: inline-block; 57 | margin-top: 50px; 58 | margin-left: 50px; 59 | width: 500px; 60 | min-height: 300px;} 61 | 62 | .re-complete h1 { 63 | text-align: center; 64 | font-style: italic; 65 | /* width: auto; */ 66 | } 67 | 68 | .vegetable h1 { 69 | color: #51C151;} 70 | 71 | 72 | .fruit h1 { 73 | color: #DA2842;; 74 | } 75 | 76 | .fruit .check {color: #DA2842;} 77 | 78 | .vegetable .check {color: #51C151;} 79 | 80 | p {width: auto;} 81 | 82 | .button-ok { 83 | margin-left: 10px; 84 | } 85 | 86 | .title { 87 | text-align: center; 88 | margin-top: 50px; 89 | margin-bottom: 50px; 90 | color: #9f9f9f; 91 | } -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Re-complete 5 | 6 | 10 | 11 | 12 |

My Shopping lists ...

13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main.cljs.edn: -------------------------------------------------------------------------------- 1 | {:require [re-complete.example] 2 | :init-fns [re-complete.example/main] 3 | :compiler-options {}} 4 | -------------------------------------------------------------------------------- /src/re_complete/app.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.app 2 | (:require [re-complete.utils :as utils] 3 | [clojure.string :as string] 4 | [re-frame.core :refer [dispatch subscribe]])) 5 | 6 | (defn case-sensitivity [case-sensitive? dictionary-item input] 7 | (if (= input "") 8 | nil 9 | (if case-sensitive? 10 | (string/starts-with? dictionary-item input) 11 | (string/starts-with? (string/lower-case dictionary-item) (string/lower-case input))))) 12 | 13 | (defn items-to-complete 14 | "List of the items to autocomplete by given input and list of the all items" 15 | [case-sensitive? dictionary input] 16 | (if (= input nil) 17 | [] 18 | (filter #(case-sensitivity case-sensitive? % input) dictionary))) 19 | 20 | (defn index [previous-input input] 21 | "Finds the index where the change occured" 22 | (let [real-index (count (take-while #(identical? (first %) (second %)) 23 | (map vector previous-input input)))] 24 | (if (< (count previous-input) (count input)) 25 | real-index 26 | (- real-index 1)))) 27 | 28 | (defn current-word [input index] 29 | "Finds the current word based on index" 30 | (let [current-word-item (last (string/split (->> input 31 | (take (inc index)) 32 | (reduce str)) 33 | #" "))] 34 | (if (->> input 35 | (take (inc index)) 36 | last 37 | (string/blank?)) 38 | "" 39 | current-word-item))) 40 | 41 | (defn opening-excluded-chars [word excluded-chars] 42 | (if ((set (map #(= (first word) %) excluded-chars)) true) 43 | (opening-excluded-chars (apply str (rest word)) excluded-chars) 44 | word)) 45 | 46 | (defn closing-excluded-chars [word excluded-chars] 47 | (if ((set (map #(= (last word) %) excluded-chars)) true) 48 | (closing-excluded-chars (apply str (butlast word)) excluded-chars) 49 | word)) 50 | 51 | (defn completions [word dictionary {:keys [trim-chars case-sensitive?]}] 52 | (let [new-text (-> word 53 | (opening-excluded-chars trim-chars) 54 | (closing-excluded-chars trim-chars))] 55 | (items-to-complete case-sensitive? dictionary new-text))) 56 | 57 | (defn words-to-index 58 | "Words to change index" 59 | [index text] 60 | (let [text-to-index (-> index 61 | inc 62 | (take text))] 63 | (string/split (string/join "" text-to-index) #" "))) 64 | 65 | (defn index-of-word 66 | "Index of word for autocompletion" 67 | [index text] 68 | (-> (words-to-index index text) 69 | count 70 | dec)) 71 | 72 | (defn index-in-word 73 | "Position index in word for autocompletion" 74 | [index text] 75 | (if (= (count (words-to-index index text)) 1) 76 | index 77 | (->> (words-to-index index text) 78 | last 79 | count 80 | dec))) 81 | 82 | (defn complete-word-with-trimmed-chars 83 | "Autocomplete word and ignore regex at the beginning and at the end of the word" 84 | [index-in-word word word-to-complete trim-chars] 85 | (let [partitioned-by-trimmed-chars (vec (utils/partition-by-regexp word trim-chars)) 86 | index-of-part-to-complete (-> index-in-word 87 | inc 88 | (take word) 89 | (utils/partition-by-regexp trim-chars) 90 | count 91 | dec)] 92 | (->> (update-in partitioned-by-trimmed-chars [index-of-part-to-complete] 93 | #(str word-to-complete)) 94 | (string/join "")))) 95 | 96 | (defn complete-word-to-string 97 | "Autocomplete word with trimmed chars to input string" 98 | [index trim-chars text word-to-complete] 99 | (->> (update-in (string/split text #" ") [(index-of-word index text)] 100 | #(complete-word-with-trimmed-chars (index-in-word index text) 101 | % 102 | word-to-complete 103 | trim-chars)) 104 | (string/join " "))) 105 | 106 | 107 | (defn clear-complete-items [db linked-component-key] 108 | (assoc-in db [:re-complete :linked-components linked-component-key :completions] [])) 109 | 110 | (defn clear-selected-item [db linked-component-key] 111 | (assoc-in db [:re-complete :linked-components linked-component-key :selected-item] nil)) 112 | 113 | (defn next-item [db linked-component-key] 114 | (let [suggestion-list (get-in db [:re-complete :linked-components linked-component-key :completions]) 115 | suggestion-indexed-vector (map-indexed vector suggestion-list) 116 | selected-item (get-in db [:re-complete :linked-components linked-component-key :selected-item]) 117 | next-selected-item-index (inc (first selected-item))] 118 | (if-not selected-item 119 | (first suggestion-indexed-vector) 120 | (if (= (count suggestion-list) next-selected-item-index) 121 | (first suggestion-indexed-vector) 122 | (nth suggestion-indexed-vector next-selected-item-index))))) 123 | 124 | (defn previous-item [db linked-component-key] 125 | (let [suggestion-list (get-in db [:re-complete :linked-components linked-component-key :completions]) 126 | suggestion-indexed-vector (map-indexed vector suggestion-list) 127 | selected-item (get-in db [:re-complete :linked-components linked-component-key :selected-item]) 128 | previous-selected-item-index (dec (first selected-item))] 129 | (if-not selected-item 130 | (last suggestion-indexed-vector) 131 | (if (= -1 previous-selected-item-index) 132 | (last suggestion-indexed-vector) 133 | (nth suggestion-indexed-vector previous-selected-item-index))))) 134 | 135 | (defn add-completed-word [db linked-component-key selected-word] 136 | (do (-> db 137 | (update-in [:re-complete :linked-components linked-component-key :text] 138 | #(complete-word-to-string 139 | (get-in db [:re-complete :linked-components linked-component-key :change-index]) 140 | (get-in db [:re-complete :linked-components linked-component-key :options :trim-chars]) 141 | % 142 | selected-word)) 143 | (clear-complete-items linked-component-key) 144 | (clear-selected-item linked-component-key)))) 145 | 146 | (defn clear-completions 147 | [db linked-component-key] 148 | (assoc-in db [:re-complete :linked-components linked-component-key :completions] [])) 149 | 150 | (defn scrolling-down [linked-component-key selected-item-index node current-view keys-handling] 151 | (let [number-of-visible-items (:visible-items keys-handling) 152 | one-item-height (:item-height keys-handling) 153 | selected-item-number (+ 1 selected-item-index) 154 | start-current-view (when (= @current-view [0 0]) 155 | (reset! current-view [1 number-of-visible-items]))] 156 | (cond (> selected-item-number (second @current-view)) (do (set! (.-scrollTop node) (* (- selected-item-number 1) one-item-height)) 157 | (swap! current-view (fn [[_ s]] 158 | [(inc s) (+ s number-of-visible-items)]))) 159 | (= selected-item-number 1) (do (set! (.-scrollTop node) 0) 160 | (reset! current-view [1 number-of-visible-items])) 161 | :else nil))) 162 | 163 | (defn scrolling-up [linked-component-key selected-item-index node current-view items-to-complete keys-handling] 164 | (let [number-of-visible-items (:visible-items keys-handling) 165 | number-of-items-to-complete (count items-to-complete) 166 | one-item-height (:item-height keys-handling) 167 | selected-item-number (+ 1 selected-item-index) 168 | current-position (* (+ selected-item-index 1) one-item-height) 169 | start-current-view (when (= @current-view [0 0]) 170 | (reset! current-view [(- number-of-items-to-complete number-of-visible-items) number-of-items-to-complete]))] 171 | (cond (= (inc selected-item-number) (first @current-view)) (do (set! (.-scrollTop node) (- current-position (* number-of-visible-items one-item-height))) 172 | (swap! current-view (fn [[f s]] 173 | [(- f number-of-visible-items) (- s number-of-visible-items)]))) 174 | (= selected-item-number number-of-items-to-complete) (do (set! (.-scrollTop node) (- (* one-item-height number-of-items-to-complete) 1)) 175 | (reset! current-view [(- number-of-items-to-complete (dec number-of-visible-items)) number-of-items-to-complete])) 176 | :else nil))) 177 | 178 | (defn keys-handling [linked-component-key onclick-callback node current-view] 179 | (.addEventListener js/window "keydown" 180 | (let [items (subscribe [:get-items-to-complete linked-component-key]) 181 | selected (subscribe [:get-selected-item linked-component-key])] 182 | (fn [e] 183 | (let [key-code (.-keyCode e)] 184 | (when (and (#{13 38 40 9 27} key-code) 185 | (seq @items)) 186 | (if-not (and (= 13 key-code) (nil? @selected)) 187 | (do 188 | (dispatch [:keys-handling linked-component-key key-code onclick-callback node current-view]) 189 | (.stopPropagation e) 190 | (.preventDefault e)) 191 | (dispatch [:clear-completions linked-component-key])))))) 192 | true)) 193 | -------------------------------------------------------------------------------- /src/re_complete/core.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.core 2 | (:require [re-complete.app :as app] 3 | [re-complete.handlers :as handlers] 4 | [re-frame.core :refer [dispatch subscribe]] 5 | [clojure.string :as string] 6 | [reagent.core :as reagent])) 7 | 8 | (defn completion-list 9 | "Render list of the items to autocomplete. 10 | Every item of the list is dispatched to the right place in the right input with :on-click event." 11 | [linked-component-key onclick-callback] 12 | (let [linked-component-keyword (keyword linked-component-key) 13 | items-to-re-complete (subscribe [:get-items-to-complete linked-component-keyword]) 14 | current-word (subscribe [:get-previous-input linked-component-keyword]) 15 | selected-item (subscribe [:get-selected-item linked-component-keyword]) 16 | is-mouse-on-suggestion-list? (subscribe [:is-mouse-on-suggestion-list linked-component-keyword]) 17 | focus? (subscribe [:focus? linked-component-key])] 18 | (fn [] 19 | (let [selected @selected-item] 20 | (when (zero? (count @items-to-re-complete)) 21 | (dispatch [:clear-selected-item linked-component-keyword])) 22 | [:ul {:className (str "re-completion-list " linked-component-key) 23 | :style {:visibility (if (empty? @items-to-re-complete) "hidden" "visible") 24 | :display (if @focus? "inline-block" "none")}} 25 | (when-not (string/blank? @current-word) 26 | (map (fn [item] 27 | (if (= (str (second selected)) 28 | (str item)) 29 | ^{:key item} 30 | [:li.re-completion-selected 31 | {:on-click #(do (dispatch [:add-completed-word linked-component-keyword item]) 32 | (when onclick-callback 33 | (onclick-callback))) 34 | :on-mouse-out #(dispatch [:mouse-on-suggestion-list linked-component-key false])} 35 | item] 36 | ^{:key item} 37 | [:li.re-completion-item 38 | {:on-click #(do (dispatch [:add-completed-word linked-component-keyword item]) 39 | (when onclick-callback 40 | (onclick-callback))) 41 | :on-mouse-over #(when-not @is-mouse-on-suggestion-list? 42 | (dispatch [:selected-item linked-component-keyword item])) 43 | :on-mouse-out #(dispatch [:mouse-on-suggestion-list linked-component-key false])} 44 | item])) 45 | @items-to-re-complete))])))) 46 | 47 | (defn setup-key-handling [this linked-component-key onclick-callback] 48 | (let [node (reagent/dom-node this) 49 | current-view (atom [0 0])] 50 | (app/keys-handling (keyword linked-component-key) onclick-callback node current-view))) 51 | 52 | (defn completions 53 | ([linked-component-key] 54 | (reagent/create-class 55 | {:component-did-mount (fn [this] 56 | (setup-key-handling this linked-component-key nil)) 57 | :reagent-render (fn [linked-component-key] 58 | (completion-list linked-component-key nil))})) 59 | ([linked-component-key onclick-callback] 60 | (reagent/create-class 61 | {:component-did-mount (fn [this] 62 | (setup-key-handling this linked-component-key onclick-callback)) 63 | :reagent-render (fn [linked-component-key onclick-callback] 64 | (completion-list linked-component-key onclick-callback))}))) 65 | -------------------------------------------------------------------------------- /src/re_complete/handlers.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.handlers 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [re-frame.core :refer [register-handler 4 | register-sub 5 | dispatch 6 | subscribe]] 7 | [re-complete.app :as app])) 8 | 9 | 10 | (def trim-chars-default "") 11 | 12 | (def case-sensitive-default false) 13 | 14 | ;; --- Handlers --- 15 | 16 | (register-handler 17 | :options 18 | (fn [db [_ linked-component-key options]] 19 | (let [trim-chars (:trim-chars options) 20 | case-sensitive? (:case-sensitive? options) 21 | keys-handling (if (:keys-handling options) 22 | (:keys-handling options) 23 | (:keys-handling nil)) 24 | filled-options (cond (and trim-chars case-sensitive?) {:trim-chars trim-chars 25 | :case-sensitive? true 26 | :keys-handling keys-handling} 27 | trim-chars {:trim-chars trim-chars 28 | :case-sensitive? case-sensitive-default 29 | :keys-handling keys-handling} 30 | case-sensitive? {:case-sensitive? true 31 | :trim-chars trim-chars-default 32 | :keys-handling keys-handling} 33 | :else {:trim-chars trim-chars-default 34 | :case-sensitive? case-sensitive-default 35 | :keys-handling keys-handling})] 36 | (assoc-in db [:re-complete :linked-components (keyword linked-component-key)] {:options filled-options})))) 37 | 38 | (register-handler 39 | :focus 40 | (fn [db [_ linked-component-key focus?]] 41 | (assoc-in db [:re-complete :linked-components (keyword linked-component-key) :focus] focus?))) 42 | 43 | (register-handler 44 | :dictionary 45 | (fn [db [_ linked-component-key dictionary]] 46 | (assoc-in db [:re-complete :linked-components (keyword linked-component-key) :dictionary] dictionary))) 47 | 48 | (register-handler 49 | :mouse-on-suggestion-list 50 | (fn [db [_ linked-component-key is-mouse-on-suggestion-list?]] 51 | (assoc-in db [:re-complete :linked-components (keyword linked-component-key) :mouse-on-suggestion-list] is-mouse-on-suggestion-list?))) 52 | 53 | (register-handler 54 | :input 55 | (fn [db [_ linked-component-key input]] 56 | (let [linked-component-keyword (keyword linked-component-key) 57 | previous-input (get-in db [:re-complete :linked-components linked-component-keyword :text]) 58 | options (get-in db [:re-complete :linked-components linked-component-keyword :options]) 59 | dictionary (get-in db [:re-complete :linked-components linked-component-keyword :dictionary]) 60 | index (app/index previous-input input) 61 | current-word (app/current-word input index)] 62 | (-> db 63 | (assoc-in [:re-complete :linked-components linked-component-keyword :text] input) 64 | (assoc-in [:re-complete :linked-components linked-component-keyword :change-index] index) 65 | (assoc-in [:re-complete :linked-components linked-component-keyword :current-word] current-word) 66 | (assoc-in [:re-complete :linked-components linked-component-keyword :completions] (app/completions current-word dictionary options)) 67 | (assoc-in [:re-complete :linked-components linked-component-keyword :selected-item] nil) 68 | (assoc-in [:re-complete :linked-components linked-component-keyword :mouse-on-suggestion-list] false))))) 69 | 70 | (register-handler 71 | :add-completed-word 72 | (fn [db [_ linked-component-key selected-word]] 73 | (app/add-completed-word db linked-component-key selected-word))) 74 | 75 | (register-handler 76 | :clear-selected-item 77 | (fn [db [_ linked-component-key]] 78 | (app/clear-selected-item db linked-component-key))) 79 | 80 | (register-handler 81 | :clear-completions 82 | (fn [db [_ linked-component-key]] 83 | (app/clear-completions db linked-component-key))) 84 | 85 | (register-handler 86 | :keys-handling 87 | (fn [db [_ linked-component-key key-code onclick-callback node current-view]] 88 | (let [selected-item (get-in db [:re-complete :linked-components linked-component-key :selected-item]) 89 | items-to-complete (get-in db [:re-complete :linked-components linked-component-key :completions]) 90 | focus? (get-in db [:re-complete :linked-components linked-component-key :focus]) 91 | options (get-in db [:re-complete :linked-components linked-component-key :options]) 92 | keys-handling (:keys-handling options)] 93 | (if focus? 94 | (cond (= key-code 40) (let [next-item (app/next-item db linked-component-key) 95 | db (-> db 96 | (assoc-in [:re-complete :linked-components linked-component-key :selected-item] next-item) 97 | (assoc-in [:re-complete :linked-components linked-component-key :mouse-on-suggestion-list] true))] 98 | (when keys-handling 99 | (app/scrolling-down linked-component-key (first next-item) node current-view keys-handling)) 100 | db) 101 | (= key-code 38) (let [previous-item (app/previous-item db linked-component-key) 102 | db (-> db 103 | (assoc-in [:re-complete :linked-components linked-component-key :selected-item] previous-item) 104 | (assoc-in [:re-complete :linked-components linked-component-key :mouse-on-suggestion-list] true))] 105 | (when keys-handling 106 | (app/scrolling-up linked-component-key (first previous-item) node current-view items-to-complete keys-handling)) 107 | db) 108 | (= key-code 13) (let [db (app/add-completed-word db linked-component-key (second selected-item))] 109 | (when onclick-callback (onclick-callback)) 110 | db) 111 | (= key-code 9) (let [db (app/add-completed-word db linked-component-key (first items-to-complete))] 112 | (when onclick-callback (onclick-callback)) 113 | db) 114 | (= key-code 27) (app/clear-completions db linked-component-key)) 115 | db)))) 116 | 117 | (register-handler 118 | :selected-item 119 | (fn [db [_ linked-component-key hovered-item]] 120 | (let [suggestion-list (get-in db [:re-complete :linked-components linked-component-key :completions]) 121 | suggestion-indexed-vector (map-indexed vector suggestion-list) 122 | hovered-item-with-index (first (filter (comp (partial = hovered-item) second) suggestion-indexed-vector))] 123 | (assoc-in db [:re-complete :linked-components linked-component-key :selected-item] hovered-item-with-index)))) 124 | 125 | ;; --- Subscriptions --- 126 | 127 | (register-sub 128 | :get-previous-input 129 | (fn [db [_ linked-component-key]] 130 | (reaction (get-in @db [:re-complete :linked-components (keyword linked-component-key) :text])))) 131 | 132 | (register-sub 133 | :get-items-to-complete 134 | (fn [db [_ linked-component-key]] 135 | (reaction (get-in @db [:re-complete :linked-components linked-component-key :completions])))) 136 | 137 | (register-sub 138 | :get-selected-item 139 | (fn [db [_ linked-component-key]] 140 | (reaction (get-in @db [:re-complete :linked-components linked-component-key :selected-item])))) 141 | 142 | (register-sub 143 | :get-options 144 | (fn [db [_ linked-component-key]] 145 | (reaction (get-in @db [:re-complete :linked-components (keyword linked-component-key) :options])))) 146 | 147 | (register-sub 148 | :is-mouse-on-suggestion-list 149 | (fn [db [_ linked-component-key]] 150 | (reaction (get-in @db [:re-complete :linked-components (keyword linked-component-key) :mouse-on-suggestion-list])))) 151 | 152 | (register-sub 153 | :focus? 154 | (fn [db [_ linked-component-key]] 155 | (reaction (get-in @db [:re-complete :linked-components (keyword linked-component-key) :focus])))) 156 | -------------------------------------------------------------------------------- /src/re_complete/utils.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.utils 2 | (:require [clojure.string :as string])) 3 | 4 | (defn str-to-pattern 5 | "Functions transform given string to pattern" 6 | [string] 7 | (->> string 8 | (map (partial str "\\")) 9 | (interpose "|") 10 | (apply str) 11 | re-pattern)) 12 | 13 | 14 | (defn partition-by-regexp 15 | "Function partitions characters from string into multiple strings by given regexp" 16 | [word trim-chars] 17 | (map #(string/join "" %) 18 | (partition-by #(re-find (str-to-pattern trim-chars) %) (mapv str word)))) 19 | -------------------------------------------------------------------------------- /test/re_complete/app_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.app-test 2 | (:require [re-complete.app :as app] 3 | [cljs.test :refer-macros [deftest is]])) 4 | 5 | (def previous-input "appricot ( tomato") 6 | 7 | (def input-text "appricot (b tomato") 8 | 9 | (def word-to-autocomplete "broccolini") 10 | 11 | (def options {:trim-chars "()" 12 | :sort-fn count}) 13 | 14 | (def dictionary '("appricot" "broccolini" "tomato" "bok choy" "bell peppers" "amaranth" "leek")) 15 | 16 | (deftest items-to-autocomplete 17 | (is (= (app/items-to-autocomplete dictionary "b") '("broccolini" "bok choy" "bell peppers")))) 18 | 19 | (deftest index 20 | (is (= (app/index previous-input input-text) 10))) 21 | 22 | (deftest current-word 23 | (is (= (app/current-word input-text (app/index previous-input input-text)) "(b"))) 24 | 25 | (deftest completions 26 | (is (= (app/completions (app/current-word input-text (app/index previous-input input-text)) dictionary options) ["bok choy" "broccolini" "bell peppers"]))) 27 | 28 | (deftest words-to-index 29 | (is (= (app/words-to-index (app/index previous-input input-text) input-text) ["appricot" "(b"]))) 30 | 31 | (deftest index-of-word 32 | (is (= (app/index-of-word (app/index previous-input input-text) input-text) 1))) 33 | 34 | (deftest index-in-word 35 | (is (= (app/index-in-word (app/index previous-input input-text) input-text) 1))) 36 | 37 | (deftest autocomplete-word-with-trimmed-chars 38 | (let [change-index (app/index previous-input input-text)] 39 | (is (= (app/autocomplete-word-with-trimmed-chars (app/index-in-word change-index input-text) (app/current-word input-text change-index) word-to-autocomplete (:trim-chars options)) "(broccolini")))) 40 | 41 | (deftest autocomplete-word-to-string 42 | (is (= (app/autocomplete-word-to-string (app/index previous-input input-text) (:trim-chars options) input-text word-to-autocomplete) "appricot (broccolini tomato"))) 43 | -------------------------------------------------------------------------------- /test/re_complete/utils_test.cljs: -------------------------------------------------------------------------------- 1 | (ns re-complete.utils-test 2 | (:require [re-complete.utils :as utils] 3 | [cljs.test :refer-macros [deftest is]])) 4 | 5 | (deftest regex-char-esc-smap 6 | (is (= (utils/regex-char-esc-smap "?()[]") {"?" "\\?|", "(" "\\(|", ")" "\\)|", "[" "\\[|", "]" "\\]|"}))) 7 | 8 | (deftest partition-by-regexp 9 | (is (= (utils/partition-by-regexp "(appricot?)" "?()[]") '("(" "appricot" "?" ")")))) 10 | -------------------------------------------------------------------------------- /version.properties: -------------------------------------------------------------------------------- 1 | VERSION=0.1.4-1-SNAPSHOT --------------------------------------------------------------------------------