From de9139d6d57204dfc8a39d1f0d131def53c33d41 Mon Sep 17 00:00:00 2001 From: M1n <54779580+M1n-74316D65@users.noreply.github.com> Date: Sat, 7 Sep 2024 00:16:58 +0200 Subject: [PATCH] feat: :sparkles: add case sensitivity for command search and improve suggestion rendering --- index.html | 148 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 53 deletions(-) diff --git a/index.html b/index.html index a667cd5..e8aac16 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + @@ -32,6 +32,7 @@ const CONFIG = { commandPathDelimiter: '/', commandSearchDelimiter: ' ', + commandCaseSensitive: false, defaultSearchTemplate: 'https://duckduckgo.com/?q={}', openLinksInNewTab: true, suggestionLimit: 4, @@ -349,40 +350,53 @@ return /^((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)$/i.test(s); } + static #compareKey(key) { + return CONFIG.commandCaseSensitive ? key : key.toLowerCase(); + } + + static #getCommand(key) { + return COMMANDS.get(this.#compareKey(key)); + } + static #parseQuery = (raw) => { const query = raw.trim(); + const compareQuery = this.#compareKey(query); if (this.#isUrl(query)) { const url = this.#hasProtocol(query) ? query : `https://${query}`; return { query, url }; } - if (COMMANDS.has(query)) { - const { key, url } = COMMANDS.get(query); - return { key, query, url }; + if (COMMANDS.has(compareQuery)) { + const { url } = this.#getCommand(query); + return { key: query, query, url }; } - let splitBy = CONFIG.commandSearchDelimiter; - const [searchKey, rawSearch] = query.split(new RegExp(`${splitBy}(.*)`)); + const [commandPart, searchPart] = query.split( + new RegExp(`${CONFIG.commandSearchDelimiter}(.*)`) + ); + const compareCommandPart = this.#compareKey(commandPart); - if (COMMANDS.has(searchKey)) { - const command = COMMANDS.get(searchKey); + if (COMMANDS.has(compareCommandPart)) { + const command = this.#getCommand(commandPart); + const search = searchPart ? searchPart.trim() : ''; const template = new URL(command.searchTemplate ?? '', command.url); - const search = rawSearch.trim(); - const url = Search.#formatSearchUrl(decodeURI(template.href), search); - return { key: searchKey, query, search, splitBy, url }; + const url = this.#formatSearchUrl(decodeURI(template.href), search); + return { key: commandPart, query, search, url }; } - splitBy = CONFIG.commandPathDelimiter; - const [pathKey, path] = query.split(new RegExp(`${splitBy}(.*)`)); + const [pathKey, path] = query.split( + new RegExp(`${CONFIG.commandPathDelimiter}(.*)`) + ); + const comparePathKey = this.#compareKey(pathKey); - if (COMMANDS.has(pathKey)) { - const command = COMMANDS.get(pathKey); - const url = `${new URL(command.url).origin}/${path}`; - return { key: pathKey, path, query, splitBy, url }; + if (COMMANDS.has(comparePathKey)) { + const command = this.#getCommand(pathKey); + const url = `${new URL(command.url).origin}/${path || ''}`; + return { key: pathKey, path, query, url }; } - const url = Search.#formatSearchUrl(CONFIG.defaultSearchTemplate, query); + const url = this.#formatSearchUrl(CONFIG.defaultSearchTemplate, query); return { query, search: query, url }; }; @@ -416,28 +430,45 @@ } #onInput = async () => { - const oq = Search.#parseQuery(this.#input.value); + const parsedQuery = Search.#parseQuery(this.#input.value); - if (!oq.query) { + if (!parsedQuery.query) { this.#close(); return; } - let suggestions = COMMANDS.get(oq.query)?.suggestions ?? []; - - if (oq.search && suggestions.length < CONFIG.suggestionLimit) { - const res = await Search.#fetchDuckDuckGoSuggestions(oq.search); + const command = Search.#getCommand(parsedQuery.key || parsedQuery.query); + let suggestions = command?.suggestions ?? []; + if (parsedQuery.search && suggestions.length < CONFIG.suggestionLimit) { + const ddgSuggestions = await Search.#fetchDuckDuckGoSuggestions( + parsedQuery.search + ); suggestions = suggestions.concat( - oq.splitBy - ? res.map((search) => `${oq.key}${oq.splitBy}${search}`) - : res + parsedQuery.key + ? ddgSuggestions.map( + (s) => `${parsedQuery.key}${CONFIG.commandSearchDelimiter}${s}` + ) + : ddgSuggestions ); } - const nq = Search.#parseQuery(this.#input.value); - if (nq.query !== oq.query) return; - this.#renderSuggestions(suggestions, oq.query); + const newParsedQuery = Search.#parseQuery(this.#input.value); + if ( + Search.#compareKey(newParsedQuery.query) !== + Search.#compareKey(parsedQuery.query) + ) + return; + + const filteredSuggestions = CONFIG.commandCaseSensitive + ? suggestions + : suggestions.filter((s) => + Search.#compareKey(s).startsWith( + Search.#compareKey(parsedQuery.query) + ) + ); + + this.#renderSuggestions(filteredSuggestions, parsedQuery.query); }; #onKeydown = (e) => { @@ -489,33 +520,44 @@ #renderSuggestions(suggestions, query) { this.#suggestions.innerHTML = ''; - const sliced = suggestions.slice(0, CONFIG.suggestionLimit); const template = document.getElementById('suggestion-template'); + const matchTemplate = document.getElementById('match-template'); - for (const [index, suggestion] of sliced.entries()) { - const clone = template.content.cloneNode(true); - const ref = clone.querySelector('.suggestion'); - ref.dataset.index = index; - ref.dataset.suggestion = suggestion; - const escapedQuery = Search.#escapeRegexCharacters(query); - const matched = suggestion.match(new RegExp(escapedQuery, 'i')); - - if (matched) { - const template = document.getElementById('match-template'); + suggestions + .slice(0, CONFIG.suggestionLimit) + .forEach((suggestion, index) => { const clone = template.content.cloneNode(true); - const matchRef = clone.querySelector('.match'); - const pre = suggestion.slice(0, matched.index); - const post = suggestion.slice(matched.index + matched[0].length); - matchRef.innerText = matched[0]; - matchRef.insertAdjacentHTML('beforebegin', pre); - matchRef.insertAdjacentHTML('afterend', post); - ref.append(clone); - } else { - ref.innerText = suggestion; - } + const ref = clone.querySelector('.suggestion'); + ref.dataset.index = index; + ref.dataset.suggestion = suggestion; - this.#suggestions.append(clone); - } + const compareQuery = Search.#compareKey(query); + const compareSuggestion = Search.#compareKey(suggestion); + const matchIndex = compareSuggestion.indexOf(compareQuery); + + if (matchIndex !== -1) { + const pre = suggestion.slice(0, matchIndex); + const match = suggestion.slice( + matchIndex, + matchIndex + query.length + ); + const post = suggestion.slice(matchIndex + query.length); + + const matchClone = matchTemplate.content.cloneNode(true); + const matchRef = matchClone.querySelector('.match'); + matchRef.textContent = match; + + ref.append( + document.createTextNode(pre), + matchClone, + document.createTextNode(post) + ); + } else { + ref.textContent = suggestion; + } + + this.#suggestions.appendChild(clone); + }); } }