From 2e4ed9de07b8b4726a84983cdff87526cdf998cf Mon Sep 17 00:00:00 2001 From: cfinegan Date: Wed, 12 Jan 2022 12:12:35 -0500 Subject: [PATCH 1/6] Emote auto-complete now prefers results that start with the user input --- pnpm-lock.yaml | 38 +++++++------ .../twitch-twilight/modules/chat/input.jsx | 56 ++++++++++++++----- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 346e34a3..f2e77e77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,7 @@ dependencies: graphql: 16.0.1 graphql-tag: 2.12.6_graphql@16.0.1 js-cookie: 2.2.1 + jszip: 3.7.1 markdown-it: 12.2.0 markdown-it-link-attributes: 3.0.0 mnemonist: 0.38.5 @@ -85,7 +86,6 @@ dependencies: raven-js: 3.27.2 react: 17.0.2 safe-regex: 2.1.1 - sass: 1.43.4 sortablejs: 1.14.0 sourcemapped-stacktrace: 1.1.11 text-diff: 1.0.1 @@ -117,9 +117,9 @@ devDependencies: extract-loader: 2.0.1 file-loader: 4.3.0_webpack@4.46.0 json-loader: 0.5.7 - jszip: 3.7.1 raw-loader: 3.1.0_webpack@4.46.0 rimraf: 3.0.2 + sass: 1.43.4 sass-loader: 7.3.1_webpack@4.46.0 semver: 7.3.5 terser-webpack-plugin: 4.2.3_webpack@4.46.0 @@ -988,6 +988,7 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.0 + dev: true /aproba/1.2.0: resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==} @@ -1173,6 +1174,7 @@ packages: /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + dev: true /binary/0.3.0: resolution: {integrity: sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=} @@ -1260,6 +1262,7 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 + dev: true /brorand/1.1.0: resolution: {integrity: sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=} @@ -1513,6 +1516,7 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.2 + dev: true /chownr/1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -1760,7 +1764,6 @@ packages: /core-util-is/1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: true /create-ecdh/4.0.4: resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} @@ -2593,6 +2596,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /finalhandler/1.1.2: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} @@ -2748,6 +2752,7 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true + dev: true optional: true /fstream/1.0.12: @@ -2804,6 +2809,7 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob/7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} @@ -3107,7 +3113,7 @@ packages: /immediate/3.0.6: resolution: {integrity: sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=} - dev: true + dev: false /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -3157,7 +3163,6 @@ packages: /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true /internal-ip/6.2.0: resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==} @@ -3243,6 +3248,7 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 + dev: true /is-boolean-object/1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -3327,6 +3333,7 @@ packages: /is-extglob/2.1.1: resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} engines: {node: '>=0.10.0'} + dev: true /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -3338,6 +3345,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-ip/3.1.0: resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} @@ -3368,6 +3376,7 @@ packages: /is-number/7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-path-cwd/2.2.0: resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} @@ -3466,7 +3475,6 @@ packages: /isarray/1.0.0: resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} - dev: true /isexe/2.0.0: resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} @@ -3564,7 +3572,7 @@ packages: pako: 1.0.11 readable-stream: 2.3.7 set-immediate-shim: 1.0.1 - dev: true + dev: false /kind-of/3.2.2: resolution: {integrity: sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=} @@ -3602,7 +3610,7 @@ packages: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} dependencies: immediate: 3.0.6 - dev: true + dev: false /linkify-it/3.0.3: resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} @@ -4079,6 +4087,7 @@ packages: /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + dev: true /npm-run-path/4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} @@ -4314,7 +4323,6 @@ packages: /pako/1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - dev: true /parallel-transform/1.2.0: resolution: {integrity: sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==} @@ -4425,6 +4433,7 @@ packages: /picomatch/2.3.0: resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} engines: {node: '>=8.6'} + dev: true /pify/2.3.0: resolution: {integrity: sha1-7RQaasBDqEnqWISY59yosVMw6Qw=} @@ -4548,7 +4557,6 @@ packages: /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: true /process/0.11.10: resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=} @@ -4719,7 +4727,6 @@ packages: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 - dev: true /readable-stream/3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} @@ -4745,6 +4752,7 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.0 + dev: true /rechoir/0.7.1: resolution: {integrity: sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==} @@ -4889,7 +4897,6 @@ packages: /safe-buffer/5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: true /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4931,7 +4938,7 @@ packages: hasBin: true dependencies: chokidar: 3.5.2 - dev: false + dev: true /schema-utils/1.0.0: resolution: {integrity: sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==} @@ -5045,7 +5052,7 @@ packages: /set-immediate-shim/1.0.1: resolution: {integrity: sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=} engines: {node: '>=0.10.0'} - dev: true + dev: false /set-value/4.1.0: resolution: {integrity: sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==} @@ -5353,7 +5360,6 @@ packages: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 - dev: true /string_decoder/1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -5547,6 +5553,7 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true /to-regex/3.0.2: resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} @@ -5697,7 +5704,6 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} - dev: true /util/0.10.3: resolution: {integrity: sha1-evsa/lCAUkZInj23/g7TeTNqwPk=} diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 008086ff..6feccf01 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -606,7 +606,7 @@ export default class Input extends Module { results = Array.isArray(results) ? results.concat(emoji) : emoji; } - results = t.sortFavorites(results); + results = t.sortEmotes(results, input); return limitResults && results.length > 25 ? results.slice(0, 25) : results; } @@ -647,21 +647,49 @@ export default class Input extends Module { // eslint-disable-next-line class-methods-use-this - sortFavorites(results) { - if (!this.chat.context.get('chat.tab-complete.prioritize-favorites')) { - return results; + sortEmotes(emotes, input) { + const preferFavorites = this.chat.context.get('chat.tab-complete.prioritize-favorites'); + const lowerCaseInput = input.toLowerCase(); + + const locale = new Intl.Collator(); + const localeCI = new Intl.Collator(undefined, {sensitivity: 'accent'}); // case insensitive + + const startsWithInput = new Set(); + const startsWithInputCI = new Set(); // case insensitive + for (let i = 0; i < emotes.length; ++i) { + const str = emotes[i].replacement; + if (str.startsWith(input)) + startsWithInput.add(str); + else if (str.toLowerCase().startsWith(lowerCaseInput)) + startsWithInputCI.add(str); } - return results.sort((a, b) => { - if (a.favorite) { - return b.favorite ? a.replacement.localeCompare(b.replacement) : -1; - } - else if (b.favorite) { - return 1; - } - else { - a.replacement.localeCompare(b.replacement) - } + return emotes.sort((a, b) => { + const aStr = a.replacement; + const bStr = b.replacement; + + // Prefer favorites over non-favorites, if enabled + if (preferFavorites && (a.favorite ^ b.favorite)) + return 0 - a.favorite + b.favorite; + + // Prefer case-sensitive prefix matches + const aStartsWithInput = startsWithInput.has(aStr); + const bStartsWithInput = startsWithInput.has(bStr); + if (aStartsWithInput && bStartsWithInput) + return locale.compare(aStr, bStr); + else if (aStartsWithInput) return -1; + else if (bStartsWithInput) return 1; + + // Else prefer case-insensitive prefix matches + const aStartsWithInputCI = aStartsWithInput || startsWithInputCI.has(aStr); + const bStartsWithInputCI = bStartsWithInput || startsWithInputCI.has(bStr); + if (aStartsWithInputCI && bStartsWithInputCI) + return localeCI.compare(aStr, bStr); + else if (aStartsWithInputCI) return -1; + else if (bStartsWithInputCI) return 1; + + // Else alphabetize + return locale.compare(aStr, bStr); }); } From 0610175906e50ab8aa15dcf7b50b5529ea133984 Mon Sep 17 00:00:00 2001 From: cfinegan Date: Sat, 15 Jan 2022 21:30:45 -0500 Subject: [PATCH 2/6] Moved collators to module-level declarations Existing uses of String.localeCompare are replaced with locale.compare for efficiency. --- src/sites/twitch-twilight/modules/chat/input.jsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 6feccf01..83ddd1dc 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -12,6 +12,10 @@ import { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES, import Twilight from 'site'; +// Prefer using these statically-allocated collators to String.localeCompare +const locale = Intl.Collator(); +const localeCaseInsensitive = Intl.Collator(undefined, {sensitivity: 'accent'}); + function getNodeText(node) { if ( ! node ) return ''; @@ -651,9 +655,6 @@ export default class Input extends Module { const preferFavorites = this.chat.context.get('chat.tab-complete.prioritize-favorites'); const lowerCaseInput = input.toLowerCase(); - const locale = new Intl.Collator(); - const localeCI = new Intl.Collator(undefined, {sensitivity: 'accent'}); // case insensitive - const startsWithInput = new Set(); const startsWithInputCI = new Set(); // case insensitive for (let i = 0; i < emotes.length; ++i) { @@ -684,7 +685,7 @@ export default class Input extends Module { const aStartsWithInputCI = aStartsWithInput || startsWithInputCI.has(aStr); const bStartsWithInputCI = bStartsWithInput || startsWithInputCI.has(bStr); if (aStartsWithInputCI && bStartsWithInputCI) - return localeCI.compare(aStr, bStr); + return localeCaseInsensitive.compare(aStr, bStr); else if (aStartsWithInputCI) return -1; else if (bStartsWithInputCI) return 1; @@ -804,8 +805,8 @@ export default class Input extends Module { } results_usage.sort((a,b) => b.count - a.count); - results_starting.sort((a,b) => a.replacement.localeCompare(b.replacement)); - results_other.sort((a,b) => a.replacement.localeCompare(b.replacement)); + results_starting.sort((a,b) => locale.compare(a.replacement, b.replacement)); + results_other.sort((a,b) => locale.compare(a.replacement, b.replacement)); return results_usage.concat(results_starting).concat(results_other); } From f85f0c49b6675e2a88cabe1cb87871866402b90f Mon Sep 17 00:00:00 2001 From: cfinegan Date: Mon, 17 Jan 2022 15:57:44 -0500 Subject: [PATCH 3/6] Do less string checking in sortEmotes --- .../twitch-twilight/modules/chat/input.jsx | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 83ddd1dc..705457ce 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -16,6 +16,13 @@ import Twilight from 'site'; const locale = Intl.Collator(); const localeCaseInsensitive = Intl.Collator(undefined, {sensitivity: 'accent'}); +// Describes how an emote matches against a given input +// Higher values represent a more exact match +const NO_MATCH = 0; +const NON_PREFIX_MATCH = 1; +const CASE_INSENSITIVE_PREFIX_MATCH = 2; +const EXACT_PREFIX_MATCH = 3; + function getNodeText(node) { if ( ! node ) return ''; @@ -576,7 +583,10 @@ export default class Input extends Module { inst.doesEmoteMatchTerm = function(emote, term) { const emote_name = emote.name || emote.token; if ( ! emote_name ) - return false; + return NO_MATCH; + + if (emote_name.startsWith(term)) + return EXACT_PREFIX_MATCH; let emote_lower = emote.tokenLower; if ( ! emote_lower ) @@ -584,13 +594,13 @@ export default class Input extends Module { const term_lower = term.toLowerCase(); if (emote_lower.startsWith(term_lower)) - return true; + return CASE_INSENSITIVE_PREFIX_MATCH; const idx = emote_name.indexOf(term.charAt(0).toUpperCase()); - if (idx !== -1) - return emote_lower.slice(idx + 1).startsWith(term_lower.slice(1)); + if (idx !== -1 && emote_lower.slice(idx + 1).startsWith(term_lower.slice(1))) + return NON_PREFIX_MATCH; - return false; + return NO_MATCH; } inst.getMatchedEmotes = function(input) { @@ -653,17 +663,6 @@ export default class Input extends Module { // eslint-disable-next-line class-methods-use-this sortEmotes(emotes, input) { const preferFavorites = this.chat.context.get('chat.tab-complete.prioritize-favorites'); - const lowerCaseInput = input.toLowerCase(); - - const startsWithInput = new Set(); - const startsWithInputCI = new Set(); // case insensitive - for (let i = 0; i < emotes.length; ++i) { - const str = emotes[i].replacement; - if (str.startsWith(input)) - startsWithInput.add(str); - else if (str.toLowerCase().startsWith(lowerCaseInput)) - startsWithInputCI.add(str); - } return emotes.sort((a, b) => { const aStr = a.replacement; @@ -674,16 +673,16 @@ export default class Input extends Module { return 0 - a.favorite + b.favorite; // Prefer case-sensitive prefix matches - const aStartsWithInput = startsWithInput.has(aStr); - const bStartsWithInput = startsWithInput.has(bStr); + const aStartsWithInput = (a.match_type === EXACT_PREFIX_MATCH); + const bStartsWithInput = (b.match_type === EXACT_PREFIX_MATCH); if (aStartsWithInput && bStartsWithInput) return locale.compare(aStr, bStr); else if (aStartsWithInput) return -1; else if (bStartsWithInput) return 1; // Else prefer case-insensitive prefix matches - const aStartsWithInputCI = aStartsWithInput || startsWithInputCI.has(aStr); - const bStartsWithInputCI = bStartsWithInput || startsWithInputCI.has(bStr); + const aStartsWithInputCI = (a.match_type === CASE_INSENSITIVE_PREFIX_MATCH); + const bStartsWithInputCI = (b.match_type === CASE_INSENSITIVE_PREFIX_MATCH); if (aStartsWithInputCI && bStartsWithInputCI) return localeCaseInsensitive.compare(aStr, bStr); else if (aStartsWithInputCI) return -1; @@ -785,19 +784,21 @@ export default class Input extends Module { search = input.startsWith(':') ? input.slice(1) : input; for(const emote of emotes) { - if ( inst.doesEmoteMatchTerm(emote, search) ) { + const match_type = inst.doesEmoteMatchTerm(emote, search); + if ( match_type !== NO_MATCH ) { const element = { current: input, emote, replacement: emote.token, element: inst.renderEmoteSuggestion(emote), favorite: emote.favorite, - count: this.EmoteUsageCount[emote.token] || 0 + count: this.EmoteUsageCount[emote.token] || 0, + match_type }; if ( element.count > 0 ) results_usage.push(element); - else if ( emote.token.toLowerCase().startsWith(search) ) + else if ( match_type > NON_PREFIX_MATCH ) results_starting.push(element); else results_other.push(element); @@ -943,14 +944,16 @@ export default class Input extends Module { results = []; for(const emote of emotes) { - if ( inst.doesEmoteMatchTerm(emote, search) ) + const match_type = inst.doesEmoteMatchTerm(emote, search) + if ( match_type !== NO_MATCH ) results.push({ current: input, emote, replacement: emote.token, element: inst.renderEmoteSuggestion(emote), favorite: emote.favorite, - count: 0 // TODO: Count stuff? + count: 0, // TODO: Count stuff? + match_type }); } From 19445fd5b0c37df4fe08f844d50ccf0b7288bc6b Mon Sep 17 00:00:00 2001 From: cfinegan Date: Mon, 17 Jan 2022 16:30:14 -0500 Subject: [PATCH 4/6] Remove unused parameter in sortEmotes --- src/sites/twitch-twilight/modules/chat/input.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 705457ce..50bcc916 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -620,7 +620,7 @@ export default class Input extends Module { results = Array.isArray(results) ? results.concat(emoji) : emoji; } - results = t.sortEmotes(results, input); + results = t.sortEmotes(results); return limitResults && results.length > 25 ? results.slice(0, 25) : results; } @@ -661,7 +661,7 @@ export default class Input extends Module { // eslint-disable-next-line class-methods-use-this - sortEmotes(emotes, input) { + sortEmotes(emotes) { const preferFavorites = this.chat.context.get('chat.tab-complete.prioritize-favorites'); return emotes.sort((a, b) => { From c4301a234d56af7c324e2de3baf61502b25ef2a8 Mon Sep 17 00:00:00 2001 From: cfinegan Date: Thu, 20 Jan 2022 21:03:27 -0500 Subject: [PATCH 5/6] sortEmotes now prefers or disprefers emojis based on user settings Previously, it erroneously sorted them based on the value of the emoji's codepoint sequence. --- src/sites/twitch-twilight/modules/chat/input.jsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 50bcc916..752934cd 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -663,15 +663,24 @@ export default class Input extends Module { // eslint-disable-next-line class-methods-use-this sortEmotes(emotes) { const preferFavorites = this.chat.context.get('chat.tab-complete.prioritize-favorites'); + const canBeTriggeredByTab = this.chat.context.get('chat.tab-complete.emotes-without-colon'); return emotes.sort((a, b) => { - const aStr = a.replacement; - const bStr = b.replacement; + const aStr = a.matched || a.replacement; + const bStr = b.matched || b.replacement; // Prefer favorites over non-favorites, if enabled if (preferFavorites && (a.favorite ^ b.favorite)) return 0 - a.favorite + b.favorite; + // Prefer emoji over emotes if tab-complete is enabled, disprefer them otherwise + const aIsEmoji = !!a.matched; + const bIsEmoji = !!b.matched; + if (aIsEmoji ^ bIsEmoji) { + if (canBeTriggeredByTab) return 0 - aIsEmoji + bIsEmoji; + else return 0 - bIsEmoji + aIsEmoji; + } + // Prefer case-sensitive prefix matches const aStartsWithInput = (a.match_type === EXACT_PREFIX_MATCH); const bStartsWithInput = (b.match_type === EXACT_PREFIX_MATCH); From 0ea064ad6870e05c03dbddcc51494ed08c590240 Mon Sep 17 00:00:00 2001 From: cfinegan Date: Thu, 20 Jan 2022 21:48:43 -0500 Subject: [PATCH 6/6] Adds toggle for new emote sorting behavior --- .../twitch-twilight/modules/chat/input.jsx | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 752934cd..c4540c63 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -128,6 +128,15 @@ export default class Input extends Module { } }); + this.settings.add('chat.tab-complete.prioritize-prefix-matches', { + default: false, + ui: { + path: 'Chat > Input >> Tab Completion', + title: 'Prioritize emotes that start with user input.', + component: 'setting-check-box' + } + }); + // Components @@ -664,6 +673,7 @@ export default class Input extends Module { sortEmotes(emotes) { const preferFavorites = this.chat.context.get('chat.tab-complete.prioritize-favorites'); const canBeTriggeredByTab = this.chat.context.get('chat.tab-complete.emotes-without-colon'); + const prioritizePrefixMatches = this.chat.context.get('chat.tab-complete.prioritize-prefix-matches'); return emotes.sort((a, b) => { const aStr = a.matched || a.replacement; @@ -673,32 +683,37 @@ export default class Input extends Module { if (preferFavorites && (a.favorite ^ b.favorite)) return 0 - a.favorite + b.favorite; - // Prefer emoji over emotes if tab-complete is enabled, disprefer them otherwise - const aIsEmoji = !!a.matched; - const bIsEmoji = !!b.matched; - if (aIsEmoji ^ bIsEmoji) { - if (canBeTriggeredByTab) return 0 - aIsEmoji + bIsEmoji; - else return 0 - bIsEmoji + aIsEmoji; + if (prioritizePrefixMatches) { + // Prefer emoji over emotes if tab-complete is enabled, disprefer them otherwise + const aIsEmoji = !!a.matched; + const bIsEmoji = !!b.matched; + if (aIsEmoji ^ bIsEmoji) { + if (canBeTriggeredByTab) return 0 - aIsEmoji + bIsEmoji; + else return 0 - bIsEmoji + aIsEmoji; + } + + // Prefer case-sensitive prefix matches + const aStartsWithInput = (a.match_type === EXACT_PREFIX_MATCH); + const bStartsWithInput = (b.match_type === EXACT_PREFIX_MATCH); + if (aStartsWithInput && bStartsWithInput) + return locale.compare(aStr, bStr); + else if (aStartsWithInput) return -1; + else if (bStartsWithInput) return 1; + + // Else prefer case-insensitive prefix matches + const aStartsWithInputCI = (a.match_type === CASE_INSENSITIVE_PREFIX_MATCH); + const bStartsWithInputCI = (b.match_type === CASE_INSENSITIVE_PREFIX_MATCH); + if (aStartsWithInputCI && bStartsWithInputCI) + return localeCaseInsensitive.compare(aStr, bStr); + else if (aStartsWithInputCI) return -1; + else if (bStartsWithInputCI) return 1; + + // Else alphabetize + return locale.compare(aStr, bStr); } - // Prefer case-sensitive prefix matches - const aStartsWithInput = (a.match_type === EXACT_PREFIX_MATCH); - const bStartsWithInput = (b.match_type === EXACT_PREFIX_MATCH); - if (aStartsWithInput && bStartsWithInput) - return locale.compare(aStr, bStr); - else if (aStartsWithInput) return -1; - else if (bStartsWithInput) return 1; - - // Else prefer case-insensitive prefix matches - const aStartsWithInputCI = (a.match_type === CASE_INSENSITIVE_PREFIX_MATCH); - const bStartsWithInputCI = (b.match_type === CASE_INSENSITIVE_PREFIX_MATCH); - if (aStartsWithInputCI && bStartsWithInputCI) - return localeCaseInsensitive.compare(aStr, bStr); - else if (aStartsWithInputCI) return -1; - else if (bStartsWithInputCI) return 1; - - // Else alphabetize - return locale.compare(aStr, bStr); + // Keep unsorted order for non-favorite items if prefix matching is not enabled. + return 0; }); }