| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 1 | /* | 
 | 2 |  * MenuItems may define: | 
 | 3 |  * | 
 | 4 |  * onclick: action happen on click and enter. | 
 | 5 |  * further: action happen on right arrow | 
 | 6 |  */ | 
 | 7 |  | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 8 | "use strict"; | 
 | 9 |  | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 10 | /** | 
 | 11 |  * Item in the Dropdown menu | 
 | 12 |  */ | 
 | 13 | define({ | 
 | 14 |   /** | 
 | 15 |    * Create a new MenuItem object. | 
 | 16 |    * | 
 | 17 |    * @constructor | 
 | 18 |    * @this {MenuItem} | 
 | 19 |    * @param {Array.<string>} An array object of name, action and | 
 | 20 |    *   optionally a description | 
 | 21 |    */ | 
 | 22 |   create : function (params) { | 
 | 23 |     return Object.create(this)._init(params); | 
 | 24 |   }, | 
 | 25 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 26 |  | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 27 |   /** | 
 | 28 |    * Upgrade this object to another object, | 
 | 29 |    * while private data stays intact. | 
 | 30 |    * | 
 | 31 |    * @param {Object] An object with properties. | 
 | 32 |    */ | 
 | 33 |   upgradeTo : function (props) { | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 34 |     for (let prop in props) { | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 35 |       this[prop] = props[prop]; | 
 | 36 |     }; | 
 | 37 |     return this; | 
 | 38 |   }, | 
 | 39 |  | 
| Nils Diewald | 7148c6f | 2015-05-04 15:07:53 +0000 | [diff] [blame] | 40 |  | 
 | 41 |   /** | 
 | 42 |    * Get or set the content of the meun item. | 
 | 43 |    */ | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 44 |   content : function (content) { | 
 | 45 |     if (arguments.length === 1) | 
 | 46 |       this._content = document.createTextNode(content); | 
 | 47 |     return this._content; | 
 | 48 |   }, | 
 | 49 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 50 |  | 
| Nils Diewald | 7148c6f | 2015-05-04 15:07:53 +0000 | [diff] [blame] | 51 |   /** | 
| Nils Diewald | 7148c6f | 2015-05-04 15:07:53 +0000 | [diff] [blame] | 52 |    * Get or set the information for action of this item.  | 
 | 53 |    */ | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 54 |   action : function (action) { | 
 | 55 |     if (arguments.length === 1) | 
 | 56 |       this._action = action; | 
 | 57 |     return this._action; | 
 | 58 |   }, | 
| Akron | 52ed22d | 2018-07-11 17:05:19 +0200 | [diff] [blame] | 59 |    | 
 | 60 |  | 
 | 61 |   /** | 
 | 62 |    * Get the lower cased field of the item | 
 | 63 |    * (used for analyses). | 
 | 64 |    */ | 
 | 65 |   lcField : function () { | 
 | 66 |     return this._lcField; | 
 | 67 |   }, | 
 | 68 |  | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 69 |  | 
 | 70 |   /** | 
 | 71 |    * Check or set if the item is active | 
 | 72 |    * | 
 | 73 |    * @param {boolean|null} State of activity | 
 | 74 |    */ | 
 | 75 |   active : function (bool) { | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 76 |     const cl = this.element().classList; | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 77 |     if (bool === undefined) | 
 | 78 |       return cl.contains("active"); | 
 | 79 |     else if (bool) | 
 | 80 |       cl.add("active"); | 
 | 81 |     else | 
 | 82 |       cl.remove("active"); | 
 | 83 |   }, | 
 | 84 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 85 |  | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 86 |   /** | 
 | 87 |    * Check or set if the item is | 
 | 88 |    * at the boundary of the menu | 
 | 89 |    * list | 
 | 90 |    * | 
 | 91 |    * @param {boolean|null} State of activity | 
 | 92 |    */ | 
 | 93 |   noMore : function (bool) { | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 94 |     const cl = this.element().classList; | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 95 |     if (bool === undefined) | 
 | 96 |       return cl.contains("no-more"); | 
 | 97 |     else if (bool) | 
 | 98 |       cl.add("no-more"); | 
 | 99 |     else | 
 | 100 |       cl.remove("no-more"); | 
 | 101 |   }, | 
 | 102 |    | 
 | 103 |   /** | 
 | 104 |    * Get the document element of the menu item | 
 | 105 |    */ | 
 | 106 |   element : function () { | 
 | 107 |     // already defined | 
 | 108 |     if (this._element !== undefined) | 
 | 109 |       return this._element; | 
 | 110 |      | 
 | 111 |     // Create list item | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 112 |     const li = document.createElement("li"); | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 113 |  | 
 | 114 |     // Connect action | 
 | 115 |     if (this["onclick"] !== undefined) { | 
 | 116 |       li["onclick"] = this.onclick.bind(this); | 
 | 117 |     }; | 
 | 118 |  | 
 | 119 |     // Append template | 
 | 120 |     li.appendChild(this.content()); | 
 | 121 |      | 
 | 122 |     return this._element = li; | 
 | 123 |   }, | 
 | 124 |  | 
 | 125 |   /** | 
 | 126 |    * Highlight parts of the item | 
 | 127 |    * | 
 | 128 |    * @param {string} Prefix string for highlights | 
 | 129 |    */ | 
 | 130 |   highlight : function (prefix) { | 
| Akron | 6ed1399 | 2016-05-23 18:06:05 +0200 | [diff] [blame] | 131 |  | 
 | 132 |     // The prefix already matches | 
 | 133 |     if (this._prefix === prefix) | 
 | 134 |       return; | 
 | 135 |  | 
 | 136 |     // There is a prefix but it doesn't match | 
 | 137 |     if (this._prefix !== null) { | 
 | 138 |       this.lowlight(); | 
 | 139 |     } | 
 | 140 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 141 |     const children = this.element().childNodes; | 
 | 142 |     for (let i = children.length -1; i >= 0; i--) { | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 143 |       this._highlight(children[i], prefix); | 
 | 144 |     }; | 
| Akron | 6ed1399 | 2016-05-23 18:06:05 +0200 | [diff] [blame] | 145 |  | 
 | 146 |     this._prefix = prefix; | 
 | 147 |   }, | 
 | 148 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 149 |  | 
| Akron | 6ed1399 | 2016-05-23 18:06:05 +0200 | [diff] [blame] | 150 |   /** | 
 | 151 |    * Remove highlight of the menu item | 
 | 152 |    */ | 
 | 153 |   lowlight : function () { | 
 | 154 |     if (this._prefix === null) | 
 | 155 |       return; | 
 | 156 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 157 |     const e = this.element(); | 
| Akron | 6ed1399 | 2016-05-23 18:06:05 +0200 | [diff] [blame] | 158 |      | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 159 |     const marks = e.getElementsByTagName("mark"); | 
 | 160 |  | 
 | 161 |     for (let i = marks.length - 1; i >= 0; i--) { | 
 | 162 |  | 
| Akron | 6ed1399 | 2016-05-23 18:06:05 +0200 | [diff] [blame] | 163 |       // Replace with content | 
 | 164 |       marks[i].parentNode.replaceChild( | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 165 |         // Create text node clone | 
 | 166 | 	      document.createTextNode( | 
 | 167 | 	        marks[i].firstChild.nodeValue | 
 | 168 |         ), | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 169 | 	      marks[i] | 
| Akron | 6ed1399 | 2016-05-23 18:06:05 +0200 | [diff] [blame] | 170 |       ); | 
 | 171 |     }; | 
 | 172 |  | 
 | 173 |     // Remove consecutive textnodes | 
 | 174 |     e.normalize(); | 
 | 175 |     this._prefix = null; | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 176 |   }, | 
 | 177 |  | 
| Akron | 52ed22d | 2018-07-11 17:05:19 +0200 | [diff] [blame] | 178 |  | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 179 |   // Highlight a certain substring of the menu item | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 180 |   _highlight : function (elem, prefixString) {     | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 181 |     if (elem.nodeType === 3) { | 
 | 182 |        | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 183 |       const text   = elem.nodeValue; | 
 | 184 |       const textlc = text.toLowerCase(); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 185 |  | 
 | 186 |       // Split prefixes | 
 | 187 |       if (prefixString) { | 
| Akron | 359c7e1 | 2017-12-19 12:06:55 +0100 | [diff] [blame] | 188 |  | 
 | 189 |         // ND: | 
 | 190 |         //   Doing this in a single line can trigger | 
 | 191 |         //   a deep-recursion in Firefox 57.01, though I don't know why. | 
 | 192 |         prefixString = prefixString.trim(); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 193 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 194 |         const prefixes = prefixString.split(" "); | 
 | 195 |  | 
 | 196 |         let testPos, | 
 | 197 |             pos = -1, | 
 | 198 |             len = 0; | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 199 |  | 
 | 200 |         // Iterate over all prefixes and get the best one | 
| Akron | b50964a | 2020-10-12 11:44:37 +0200 | [diff] [blame] | 201 |         // for (var i = 0; i < prefixes.length; i++) { | 
 | 202 |         prefixes.forEach(function(i) { | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 203 |  | 
 | 204 |           // Get first pos of a matching prefix | 
| Akron | b50964a | 2020-10-12 11:44:37 +0200 | [diff] [blame] | 205 |           testPos = textlc.indexOf(i); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 206 |           if (testPos < 0) | 
| Akron | b50964a | 2020-10-12 11:44:37 +0200 | [diff] [blame] | 207 |             return; | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 208 |  | 
 | 209 |           if (pos === -1 || testPos < pos) { | 
 | 210 |             pos = testPos; | 
| Akron | b50964a | 2020-10-12 11:44:37 +0200 | [diff] [blame] | 211 |             len = i.length; | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 212 |           } | 
| Akron | b50964a | 2020-10-12 11:44:37 +0200 | [diff] [blame] | 213 |           else if (testPos === pos && i.length > len) { | 
 | 214 |             len = i.length; | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 215 |           }; | 
| Akron | b50964a | 2020-10-12 11:44:37 +0200 | [diff] [blame] | 216 |         }); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 217 |  | 
 | 218 |         // Matches! | 
 | 219 |         if (pos >= 0) { | 
 | 220 | 	 | 
 | 221 | 	        // First element | 
 | 222 | 	        if (pos > 0) { | 
 | 223 | 	          elem.parentNode.insertBefore( | 
 | 224 | 	            document.createTextNode(text.substr(0, pos)), | 
 | 225 | 	            elem | 
 | 226 | 	          ); | 
 | 227 | 	        }; | 
 | 228 | 	 | 
 | 229 | 	        // Second element | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 230 | 	        const hl = document.createElement("mark"); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 231 | 	        hl.appendChild( | 
 | 232 | 	          document.createTextNode(text.substr(pos, len)) | 
 | 233 | 	        ); | 
 | 234 | 	        elem.parentNode.insertBefore(hl, elem); | 
 | 235 | 	 | 
 | 236 | 	        // Third element | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 237 | 	        const third = text.substr(pos + len); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 238 |  | 
 | 239 | 	        if (third.length > 0) { | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 240 | 	          const thirdE = document.createTextNode(third); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 241 | 	          elem.parentNode.insertBefore( | 
 | 242 | 	            thirdE, | 
 | 243 | 	            elem | 
 | 244 | 	          ); | 
 | 245 | 	          this._highlight(thirdE, prefixString); | 
 | 246 | 	        }; | 
 | 247 | 	 | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 248 |           elem.parentNode.removeChild(elem); | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 249 |         }; | 
 | 250 |       }; | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 251 |     } | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 252 |  | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 253 |     else { | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 254 |       const children = elem.childNodes; | 
 | 255 |       for (let i = children.length -1; i >= 0; i--) { | 
| Akron | acffc65 | 2017-12-18 21:21:25 +0100 | [diff] [blame] | 256 | 	      this._highlight(children[i], prefixString); | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 257 |       }; | 
 | 258 |     }; | 
 | 259 |   }, | 
 | 260 |  | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 261 |   // Initialize menu item | 
 | 262 |   _init : function (params) { | 
 | 263 |      | 
| Akron | c82513b | 2016-06-11 11:22:36 +0200 | [diff] [blame] | 264 |     if (params[0] === undefined) { | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 265 |       throw new Error("Missing parameters"); | 
| Akron | c82513b | 2016-06-11 11:22:36 +0200 | [diff] [blame] | 266 |     }; | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 267 |  | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 268 |     const t = this; | 
 | 269 |  | 
 | 270 |     t.content(params[0]); | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 271 |      | 
| Akron | 52ed22d | 2018-07-11 17:05:19 +0200 | [diff] [blame] | 272 |     if (params.length > 1) { | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 273 |       t._action = params[1]; | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 274 |  | 
| Akron | 52ed22d | 2018-07-11 17:05:19 +0200 | [diff] [blame] | 275 |       if (params.length > 2) | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 276 |         t._onclick = params[2]; | 
| Akron | 52ed22d | 2018-07-11 17:05:19 +0200 | [diff] [blame] | 277 |     }; | 
 | 278 |      | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 279 |     t._lcField = ' ' + t.content().textContent.toLowerCase(); | 
 | 280 |     t._prefix = null; | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 281 |  | 
 | 282 |     return this; | 
 | 283 |   }, | 
 | 284 |  | 
| Akron | 52ed22d | 2018-07-11 17:05:19 +0200 | [diff] [blame] | 285 |  | 
 | 286 |   /** | 
 | 287 |    * The click action of the menu item. | 
 | 288 |    */ | 
 | 289 |   onclick : function (e) { | 
| Akron | c53cfc8 | 2020-10-19 11:00:58 +0200 | [diff] [blame] | 290 |     const m = this.menu(); | 
| Akron | 52ed22d | 2018-07-11 17:05:19 +0200 | [diff] [blame] | 291 |  | 
 | 292 |     // Reset prefix | 
 | 293 |     m.prefix(""); | 
 | 294 |  | 
 | 295 |     if (this._onclick) | 
 | 296 |       this._onclick.apply(this, e); | 
 | 297 |  | 
 | 298 |     m.hide(); | 
 | 299 |   }, | 
 | 300 |  | 
 | 301 |    | 
| Nils Diewald | 0e6992a | 2015-04-14 20:13:52 +0000 | [diff] [blame] | 302 |   /** | 
 | 303 |    * Return menu list. | 
 | 304 |    */ | 
 | 305 |   menu : function () { | 
 | 306 |     return this._menu; | 
 | 307 |   } | 
 | 308 | }); |