summaryrefslogtreecommitdiff
path: root/searx/static/themes/simple/js/keyboard.min.js.map
diff options
context:
space:
mode:
Diffstat (limited to 'searx/static/themes/simple/js/keyboard.min.js.map')
-rw-r--r--searx/static/themes/simple/js/keyboard.min.js.map2
1 files changed, 1 insertions, 1 deletions
diff --git a/searx/static/themes/simple/js/keyboard.min.js.map b/searx/static/themes/simple/js/keyboard.min.js.map
index 6012b6d3a..ee2fb5092 100644
--- a/searx/static/themes/simple/js/keyboard.min.js.map
+++ b/searx/static/themes/simple/js/keyboard.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"keyboard.min.js","names":["baseKeyBinding: Record<string, KeyBinding>","event: KeyboardEvent","keyBindingLayouts: Record<KeyBindingLayout, Record<string, KeyBinding>>","keyBindings: Record<string, KeyBinding>","element?: HTMLElement","resultElement?: HTMLElement","which: HighlightResultElement | HTMLElement","noScroll?: boolean","keepFocus?: boolean","next: HTMLElement | undefined","css_selector: string","amount: number","position: number","nav: HighlightResultElement","newTab: boolean","divElement: HTMLElement","keyBindings: typeof baseKeyBinding","categories: Record<string, KeyBinding[]>","event: PointerEvent","event: FocusEvent"],"sources":["../../../../../client/simple/src/js/main/keyboard.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { assertElement, listen, mutable, settings } from \"../core/toolkit.ts\";\n\nexport type KeyBindingLayout = \"default\" | \"vim\";\n\ntype KeyBinding = {\n key: string;\n fun: (event: KeyboardEvent) => void;\n des: string;\n cat: string;\n};\n\ntype HighlightResultElement = \"down\" | \"up\" | \"visible\" | \"bottom\" | \"top\";\n\n/* common base for layouts */\nconst baseKeyBinding: Record<string, KeyBinding> = {\n Escape: {\n key: \"ESC\",\n fun: (event: KeyboardEvent) => removeFocus(event),\n des: \"remove focus from the focused input\",\n cat: \"Control\"\n },\n c: {\n key: \"c\",\n fun: () => copyURLToClipboard(),\n des: \"copy url of the selected result to the clipboard\",\n cat: \"Results\"\n },\n h: {\n key: \"h\",\n fun: () => toggleHelp(keyBindings),\n des: \"toggle help window\",\n cat: \"Other\"\n },\n i: {\n key: \"i\",\n fun: () => searchInputFocus(),\n des: \"focus on the search input\",\n cat: \"Control\"\n },\n n: {\n key: \"n\",\n fun: () => GoToNextPage(),\n des: \"go to next page\",\n cat: \"Results\"\n },\n o: {\n key: \"o\",\n fun: () => openResult(false),\n des: \"open search result\",\n cat: \"Results\"\n },\n p: {\n key: \"p\",\n fun: () => GoToPreviousPage(),\n des: \"go to previous page\",\n cat: \"Results\"\n },\n r: {\n key: \"r\",\n fun: () => reloadPage(),\n des: \"reload page from the server\",\n cat: \"Control\"\n },\n t: {\n key: \"t\",\n fun: () => openResult(true),\n des: \"open the result in a new tab\",\n cat: \"Results\"\n }\n};\n\nconst keyBindingLayouts: Record<KeyBindingLayout, Record<string, KeyBinding>> = {\n // SearXNG layout\n default: {\n ArrowLeft: {\n key: \"←\",\n fun: () => highlightResult(\"up\")(),\n des: \"select previous search result\",\n cat: \"Results\"\n },\n ArrowRight: {\n key: \"→\",\n fun: () => highlightResult(\"down\")(),\n des: \"select next search result\",\n cat: \"Results\"\n },\n ...baseKeyBinding\n },\n\n // Vim-like keyboard layout\n vim: {\n b: {\n key: \"b\",\n fun: () => scrollPage(-window.innerHeight),\n des: \"scroll one page up\",\n cat: \"Navigation\"\n },\n d: {\n key: \"d\",\n fun: () => scrollPage(window.innerHeight / 2),\n des: \"scroll half a page down\",\n cat: \"Navigation\"\n },\n f: {\n key: \"f\",\n fun: () => scrollPage(window.innerHeight),\n des: \"scroll one page down\",\n cat: \"Navigation\"\n },\n g: {\n key: \"g\",\n fun: () => scrollPageTo(-document.body.scrollHeight, \"top\"),\n des: \"scroll to the top of the page\",\n cat: \"Navigation\"\n },\n j: {\n key: \"j\",\n fun: () => highlightResult(\"down\")(),\n des: \"select next search result\",\n cat: \"Results\"\n },\n k: {\n key: \"k\",\n fun: () => highlightResult(\"up\")(),\n des: \"select previous search result\",\n cat: \"Results\"\n },\n u: {\n key: \"u\",\n fun: () => scrollPage(-window.innerHeight / 2),\n des: \"scroll half a page up\",\n cat: \"Navigation\"\n },\n v: {\n key: \"v\",\n fun: () => scrollPageTo(document.body.scrollHeight, \"bottom\"),\n des: \"scroll to the bottom of the page\",\n cat: \"Navigation\"\n },\n y: {\n key: \"y\",\n fun: () => copyURLToClipboard(),\n des: \"copy url of the selected result to the clipboard\",\n cat: \"Results\"\n },\n ...baseKeyBinding\n }\n};\n\nconst keyBindings: Record<string, KeyBinding> =\n settings.hotkeys && settings.hotkeys in keyBindingLayouts\n ? keyBindingLayouts[settings.hotkeys]\n : keyBindingLayouts.default;\n\nconst isElementInDetail = (element?: HTMLElement): boolean => {\n const ancestor = element?.closest(\".detail, .result\");\n return ancestor?.classList.contains(\"detail\") ?? false;\n};\n\nconst getResultElement = (element?: HTMLElement): HTMLElement | undefined => {\n return element?.closest(\".result\") ?? undefined;\n};\n\nconst isImageResult = (resultElement?: HTMLElement): boolean => {\n return resultElement?.classList.contains(\"result-images\") ?? false;\n};\n\nconst highlightResult =\n (which: HighlightResultElement | HTMLElement) =>\n (noScroll?: boolean, keepFocus?: boolean): void => {\n let effectiveWhich = which;\n let current = document.querySelector<HTMLElement>(\".result[data-vim-selected]\");\n if (!current) {\n // no selection : choose the first one\n current = document.querySelector<HTMLElement>(\".result\");\n if (!current) {\n // no first one : there are no results\n return;\n }\n // replace up/down actions by selecting first one\n if (which === \"down\" || which === \"up\") {\n effectiveWhich = current;\n }\n }\n\n const results = Array.from(document.querySelectorAll<HTMLElement>(\".result\"));\n\n let next: HTMLElement | undefined;\n\n if (typeof effectiveWhich !== \"string\") {\n next = effectiveWhich;\n } else {\n switch (effectiveWhich) {\n case \"visible\": {\n const top = document.documentElement.scrollTop || document.body.scrollTop;\n const bot = top + document.documentElement.clientHeight;\n\n for (const element of results) {\n const etop = element.offsetTop;\n const ebot = etop + element.clientHeight;\n if (ebot <= bot && etop > top) {\n next = element;\n break;\n }\n }\n break;\n }\n case \"down\":\n next = results[results.indexOf(current) + 1] || current;\n break;\n case \"up\":\n next = results[results.indexOf(current) - 1] || current;\n break;\n case \"bottom\":\n next = results.at(-1);\n break;\n // biome-ignore lint/complexity/noUselessSwitchCase: fallthrough is intended\n case \"top\":\n default:\n next = results[0];\n }\n }\n\n if (next) {\n current.removeAttribute(\"data-vim-selected\");\n next.setAttribute(\"data-vim-selected\", \"true\");\n\n if (!keepFocus) {\n const link = next.querySelector<HTMLAnchorElement>(\"h3 a\") || next.querySelector<HTMLAnchorElement>(\"a\");\n link?.focus();\n }\n\n if (!noScroll) {\n mutable.scrollPageToSelected?.();\n }\n }\n };\n\nconst reloadPage = (): void => {\n document.location.reload();\n};\n\nconst removeFocus = (event: KeyboardEvent): void => {\n const target = event.target as HTMLElement;\n const tagName = target?.tagName?.toLowerCase();\n\n if (document.activeElement && (tagName === \"input\" || tagName === \"select\" || tagName === \"textarea\")) {\n (document.activeElement as HTMLElement).blur();\n } else {\n mutable.closeDetail?.();\n }\n};\n\nconst pageButtonClick = (css_selector: string): void => {\n const button = document.querySelector<HTMLButtonElement>(css_selector);\n if (button) {\n button.click();\n }\n};\n\nconst GoToNextPage = (): void => {\n pageButtonClick('nav#pagination .next_page button[type=\"submit\"]');\n};\n\nconst GoToPreviousPage = (): void => {\n pageButtonClick('nav#pagination .previous_page button[type=\"submit\"]');\n};\n\nmutable.scrollPageToSelected = (): void => {\n const sel = document.querySelector<HTMLElement>(\".result[data-vim-selected]\");\n if (!sel) return;\n\n const wtop = document.documentElement.scrollTop || document.body.scrollTop;\n const height = document.documentElement.clientHeight;\n const etop = sel.offsetTop;\n const ebot = etop + sel.clientHeight;\n const offset = 120;\n\n // first element ?\n if (!sel.previousElementSibling && ebot < height) {\n // set to the top of page if the first element\n // is fully included in the viewport\n window.scroll(window.scrollX, 0);\n return;\n }\n\n if (wtop > etop - offset) {\n window.scroll(window.scrollX, etop - offset);\n } else {\n const wbot = wtop + height;\n if (wbot < ebot + offset) {\n window.scroll(window.scrollX, ebot - height + offset);\n }\n }\n};\n\nconst scrollPage = (amount: number): void => {\n window.scrollBy(0, amount);\n highlightResult(\"visible\")();\n};\n\nconst scrollPageTo = (position: number, nav: HighlightResultElement): void => {\n window.scrollTo(0, position);\n highlightResult(nav)();\n};\n\nconst searchInputFocus = (): void => {\n window.scrollTo(0, 0);\n\n const q = document.querySelector<HTMLInputElement>(\"#q\");\n if (q) {\n q.focus();\n\n if (q.setSelectionRange) {\n const len = q.value.length;\n\n q.setSelectionRange(len, len);\n }\n }\n};\n\nconst openResult = (newTab: boolean): void => {\n let link = document.querySelector<HTMLAnchorElement>(\".result[data-vim-selected] h3 a\");\n if (!link) {\n link = document.querySelector<HTMLAnchorElement>(\".result[data-vim-selected] > a\");\n }\n if (!link) return;\n\n const url = link.getAttribute(\"href\");\n if (url) {\n if (newTab) {\n window.open(url);\n } else {\n window.location.href = url;\n }\n }\n};\n\nconst initHelpContent = (divElement: HTMLElement, keyBindings: typeof baseKeyBinding): void => {\n const categories: Record<string, KeyBinding[]> = {};\n\n for (const binding of Object.values(keyBindings)) {\n const cat = binding.cat;\n categories[cat] ??= [];\n categories[cat].push(binding);\n }\n\n const sortedCategoryKeys = Object.keys(categories).sort(\n (a, b) => (categories[b]?.length ?? 0) - (categories[a]?.length ?? 0)\n );\n\n let html = '<a href=\"#\" class=\"close\" aria-label=\"close\" title=\"close\">×</a>';\n html += \"<h3>How to navigate SearXNG with hotkeys</h3>\";\n html += \"<table>\";\n\n for (const [i, categoryKey] of sortedCategoryKeys.entries()) {\n const bindings = categories[categoryKey];\n if (!bindings || bindings.length === 0) continue;\n\n const isFirst = i % 2 === 0;\n const isLast = i === sortedCategoryKeys.length - 1;\n\n if (isFirst) {\n html += \"<tr>\";\n }\n\n html += \"<td>\";\n html += `<h4>${categoryKey}</h4>`;\n html += '<ul class=\"list-unstyled\">';\n\n for (const binding of bindings) {\n html += `<li><kbd>${binding.key}</kbd> ${binding.des}</li>`;\n }\n\n html += \"</ul>\";\n html += \"</td>\";\n\n if (!isFirst || isLast) {\n html += \"</tr>\";\n }\n }\n\n html += \"</table>\";\n\n divElement.innerHTML = html;\n};\n\nconst toggleHelp = (keyBindings: typeof baseKeyBinding): void => {\n let helpPanel = document.querySelector<HTMLElement>(\"#vim-hotkeys-help\");\n if (helpPanel) {\n // toggle hidden\n helpPanel.classList.toggle(\"invisible\");\n } else {\n // first call\n helpPanel = Object.assign(document.createElement(\"div\"), {\n id: \"vim-hotkeys-help\",\n className: \"dialog-modal\"\n });\n initHelpContent(helpPanel, keyBindings);\n const body = document.getElementsByTagName(\"body\")[0];\n if (body) {\n body.appendChild(helpPanel);\n }\n }\n};\n\nconst copyURLToClipboard = async (): Promise<void> => {\n const currentUrlElement = document.querySelector<HTMLAnchorElement>(\".result[data-vim-selected] h3 a\");\n assertElement(currentUrlElement);\n\n const url = currentUrlElement.getAttribute(\"href\");\n if (url) {\n await navigator.clipboard.writeText(url);\n }\n};\n\nlisten(\"click\", \".result\", function (this: HTMLElement, event: PointerEvent) {\n if (!isElementInDetail(event.target as HTMLElement)) {\n highlightResult(this)(true, true);\n\n const resultElement = getResultElement(event.target as HTMLElement);\n\n if (resultElement && isImageResult(resultElement)) {\n event.preventDefault();\n mutable.selectImage?.(resultElement);\n }\n }\n});\n\n// FIXME: Focus might also trigger Pointer event ^^^\nlisten(\n \"focus\",\n \".result a\",\n (event: FocusEvent) => {\n if (!isElementInDetail(event.target as HTMLElement)) {\n const resultElement = getResultElement(event.target as HTMLElement);\n\n if (resultElement && !resultElement.hasAttribute(\"data-vim-selected\")) {\n highlightResult(resultElement)(true);\n }\n\n if (resultElement && isImageResult(resultElement)) {\n event.preventDefault();\n mutable.selectImage?.(resultElement);\n }\n }\n },\n { capture: true }\n);\n\nlisten(\"keydown\", document, (event: KeyboardEvent) => {\n // check for modifiers so we don't break browser's hotkeys\n if (Object.hasOwn(keyBindings, event.key) && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) {\n const tagName = (event.target as HTMLElement)?.tagName?.toLowerCase();\n\n if (event.key === \"Escape\") {\n keyBindings[event.key]?.fun(event);\n } else if (event.target === document.body || tagName === \"a\" || tagName === \"button\") {\n event.preventDefault();\n keyBindings[event.key]?.fun(event);\n }\n }\n});\n\nmutable.selectNext = highlightResult(\"down\");\nmutable.selectPrevious = highlightResult(\"up\");\n"],"mappings":"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"} \ No newline at end of file
+{"version":3,"file":"keyboard.min.js","names":["baseKeyBinding: Record<string, KeyBinding>","keyBindingLayouts: Record<KeyBindingLayout, Record<string, KeyBinding>>","keyBindings: Record<string, KeyBinding>","next: HTMLElement | undefined","categories: Record<string, KeyBinding[]>"],"sources":["../../../../../client/simple/src/js/main/keyboard.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { assertElement, listen, mutable, settings } from \"../core/toolkit.ts\";\n\nexport type KeyBindingLayout = \"default\" | \"vim\";\n\ntype KeyBinding = {\n key: string;\n fun: (event: KeyboardEvent) => void;\n des: string;\n cat: string;\n};\n\ntype HighlightResultElement = \"down\" | \"up\" | \"visible\" | \"bottom\" | \"top\";\n\n/* common base for layouts */\nconst baseKeyBinding: Record<string, KeyBinding> = {\n Escape: {\n key: \"ESC\",\n fun: (event: KeyboardEvent) => removeFocus(event),\n des: \"remove focus from the focused input\",\n cat: \"Control\"\n },\n c: {\n key: \"c\",\n fun: () => copyURLToClipboard(),\n des: \"copy url of the selected result to the clipboard\",\n cat: \"Results\"\n },\n h: {\n key: \"h\",\n fun: () => toggleHelp(keyBindings),\n des: \"toggle help window\",\n cat: \"Other\"\n },\n i: {\n key: \"i\",\n fun: () => searchInputFocus(),\n des: \"focus on the search input\",\n cat: \"Control\"\n },\n n: {\n key: \"n\",\n fun: () => GoToNextPage(),\n des: \"go to next page\",\n cat: \"Results\"\n },\n o: {\n key: \"o\",\n fun: () => openResult(false),\n des: \"open search result\",\n cat: \"Results\"\n },\n p: {\n key: \"p\",\n fun: () => GoToPreviousPage(),\n des: \"go to previous page\",\n cat: \"Results\"\n },\n r: {\n key: \"r\",\n fun: () => reloadPage(),\n des: \"reload page from the server\",\n cat: \"Control\"\n },\n t: {\n key: \"t\",\n fun: () => openResult(true),\n des: \"open the result in a new tab\",\n cat: \"Results\"\n }\n};\n\nconst keyBindingLayouts: Record<KeyBindingLayout, Record<string, KeyBinding>> = {\n // SearXNG layout\n default: {\n ArrowLeft: {\n key: \"←\",\n fun: () => highlightResult(\"up\")(),\n des: \"select previous search result\",\n cat: \"Results\"\n },\n ArrowRight: {\n key: \"→\",\n fun: () => highlightResult(\"down\")(),\n des: \"select next search result\",\n cat: \"Results\"\n },\n ...baseKeyBinding\n },\n\n // Vim-like keyboard layout\n vim: {\n b: {\n key: \"b\",\n fun: () => scrollPage(-window.innerHeight),\n des: \"scroll one page up\",\n cat: \"Navigation\"\n },\n d: {\n key: \"d\",\n fun: () => scrollPage(window.innerHeight / 2),\n des: \"scroll half a page down\",\n cat: \"Navigation\"\n },\n f: {\n key: \"f\",\n fun: () => scrollPage(window.innerHeight),\n des: \"scroll one page down\",\n cat: \"Navigation\"\n },\n g: {\n key: \"g\",\n fun: () => scrollPageTo(-document.body.scrollHeight, \"top\"),\n des: \"scroll to the top of the page\",\n cat: \"Navigation\"\n },\n j: {\n key: \"j\",\n fun: () => highlightResult(\"down\")(),\n des: \"select next search result\",\n cat: \"Results\"\n },\n k: {\n key: \"k\",\n fun: () => highlightResult(\"up\")(),\n des: \"select previous search result\",\n cat: \"Results\"\n },\n u: {\n key: \"u\",\n fun: () => scrollPage(-window.innerHeight / 2),\n des: \"scroll half a page up\",\n cat: \"Navigation\"\n },\n v: {\n key: \"v\",\n fun: () => scrollPageTo(document.body.scrollHeight, \"bottom\"),\n des: \"scroll to the bottom of the page\",\n cat: \"Navigation\"\n },\n y: {\n key: \"y\",\n fun: () => copyURLToClipboard(),\n des: \"copy url of the selected result to the clipboard\",\n cat: \"Results\"\n },\n ...baseKeyBinding\n }\n};\n\nconst keyBindings: Record<string, KeyBinding> =\n settings.hotkeys && settings.hotkeys in keyBindingLayouts\n ? keyBindingLayouts[settings.hotkeys]\n : keyBindingLayouts.default;\n\nconst isElementInDetail = (element?: HTMLElement): boolean => {\n const ancestor = element?.closest(\".detail, .result\");\n return ancestor?.classList.contains(\"detail\") ?? false;\n};\n\nconst getResultElement = (element?: HTMLElement): HTMLElement | undefined => {\n return element?.closest(\".result\") ?? undefined;\n};\n\nconst isImageResult = (resultElement?: HTMLElement): boolean => {\n return resultElement?.classList.contains(\"result-images\") ?? false;\n};\n\nconst highlightResult =\n (which: HighlightResultElement | HTMLElement) =>\n (noScroll?: boolean, keepFocus?: boolean): void => {\n let effectiveWhich = which;\n let current = document.querySelector<HTMLElement>(\".result[data-vim-selected]\");\n if (!current) {\n // no selection : choose the first one\n current = document.querySelector<HTMLElement>(\".result\");\n if (!current) {\n // no first one : there are no results\n return;\n }\n // replace up/down actions by selecting first one\n if (which === \"down\" || which === \"up\") {\n effectiveWhich = current;\n }\n }\n\n const results = Array.from(document.querySelectorAll<HTMLElement>(\".result\"));\n\n let next: HTMLElement | undefined;\n\n if (typeof effectiveWhich !== \"string\") {\n next = effectiveWhich;\n } else {\n switch (effectiveWhich) {\n case \"visible\": {\n const top = document.documentElement.scrollTop || document.body.scrollTop;\n const bot = top + document.documentElement.clientHeight;\n\n for (const element of results) {\n const etop = element.offsetTop;\n const ebot = etop + element.clientHeight;\n if (ebot <= bot && etop > top) {\n next = element;\n break;\n }\n }\n break;\n }\n case \"down\":\n next = results[results.indexOf(current) + 1] || current;\n break;\n case \"up\":\n next = results[results.indexOf(current) - 1] || current;\n break;\n case \"bottom\":\n next = results.at(-1);\n break;\n // biome-ignore lint/complexity/noUselessSwitchCase: fallthrough is intended\n case \"top\":\n default:\n next = results[0];\n }\n }\n\n if (next) {\n current.removeAttribute(\"data-vim-selected\");\n next.setAttribute(\"data-vim-selected\", \"true\");\n\n if (!keepFocus) {\n const link = next.querySelector<HTMLAnchorElement>(\"h3 a\") || next.querySelector<HTMLAnchorElement>(\"a\");\n link?.focus();\n }\n\n if (!noScroll) {\n mutable.scrollPageToSelected?.();\n }\n }\n };\n\nconst reloadPage = (): void => {\n document.location.reload();\n};\n\nconst removeFocus = (event: KeyboardEvent): void => {\n const target = event.target as HTMLElement;\n const tagName = target?.tagName?.toLowerCase();\n\n if (document.activeElement && (tagName === \"input\" || tagName === \"select\" || tagName === \"textarea\")) {\n (document.activeElement as HTMLElement).blur();\n } else {\n mutable.closeDetail?.();\n }\n};\n\nconst pageButtonClick = (css_selector: string): void => {\n const button = document.querySelector<HTMLButtonElement>(css_selector);\n if (button) {\n button.click();\n }\n};\n\nconst GoToNextPage = (): void => {\n pageButtonClick('nav#pagination .next_page button[type=\"submit\"]');\n};\n\nconst GoToPreviousPage = (): void => {\n pageButtonClick('nav#pagination .previous_page button[type=\"submit\"]');\n};\n\nmutable.scrollPageToSelected = (): void => {\n const sel = document.querySelector<HTMLElement>(\".result[data-vim-selected]\");\n if (!sel) return;\n\n const wtop = document.documentElement.scrollTop || document.body.scrollTop;\n const height = document.documentElement.clientHeight;\n const etop = sel.offsetTop;\n const ebot = etop + sel.clientHeight;\n const offset = 120;\n\n // first element ?\n if (!sel.previousElementSibling && ebot < height) {\n // set to the top of page if the first element\n // is fully included in the viewport\n window.scroll(window.scrollX, 0);\n return;\n }\n\n if (wtop > etop - offset) {\n window.scroll(window.scrollX, etop - offset);\n } else {\n const wbot = wtop + height;\n if (wbot < ebot + offset) {\n window.scroll(window.scrollX, ebot - height + offset);\n }\n }\n};\n\nconst scrollPage = (amount: number): void => {\n window.scrollBy(0, amount);\n highlightResult(\"visible\")();\n};\n\nconst scrollPageTo = (position: number, nav: HighlightResultElement): void => {\n window.scrollTo(0, position);\n highlightResult(nav)();\n};\n\nconst searchInputFocus = (): void => {\n window.scrollTo(0, 0);\n\n const q = document.querySelector<HTMLInputElement>(\"#q\");\n if (q) {\n q.focus();\n\n if (q.setSelectionRange) {\n const len = q.value.length;\n\n q.setSelectionRange(len, len);\n }\n }\n};\n\nconst openResult = (newTab: boolean): void => {\n let link = document.querySelector<HTMLAnchorElement>(\".result[data-vim-selected] h3 a\");\n if (!link) {\n link = document.querySelector<HTMLAnchorElement>(\".result[data-vim-selected] > a\");\n }\n if (!link) return;\n\n const url = link.getAttribute(\"href\");\n if (url) {\n if (newTab) {\n window.open(url);\n } else {\n window.location.href = url;\n }\n }\n};\n\nconst initHelpContent = (divElement: HTMLElement, keyBindings: typeof baseKeyBinding): void => {\n const categories: Record<string, KeyBinding[]> = {};\n\n for (const binding of Object.values(keyBindings)) {\n const cat = binding.cat;\n categories[cat] ??= [];\n categories[cat].push(binding);\n }\n\n const sortedCategoryKeys = Object.keys(categories).sort(\n (a, b) => (categories[b]?.length ?? 0) - (categories[a]?.length ?? 0)\n );\n\n let html = '<a href=\"#\" class=\"close\" aria-label=\"close\" title=\"close\">×</a>';\n html += \"<h3>How to navigate SearXNG with hotkeys</h3>\";\n html += \"<table>\";\n\n for (const [i, categoryKey] of sortedCategoryKeys.entries()) {\n const bindings = categories[categoryKey];\n if (!bindings || bindings.length === 0) continue;\n\n const isFirst = i % 2 === 0;\n const isLast = i === sortedCategoryKeys.length - 1;\n\n if (isFirst) {\n html += \"<tr>\";\n }\n\n html += \"<td>\";\n html += `<h4>${categoryKey}</h4>`;\n html += '<ul class=\"list-unstyled\">';\n\n for (const binding of bindings) {\n html += `<li><kbd>${binding.key}</kbd> ${binding.des}</li>`;\n }\n\n html += \"</ul>\";\n html += \"</td>\";\n\n if (!isFirst || isLast) {\n html += \"</tr>\";\n }\n }\n\n html += \"</table>\";\n\n divElement.innerHTML = html;\n};\n\nconst toggleHelp = (keyBindings: typeof baseKeyBinding): void => {\n let helpPanel = document.querySelector<HTMLElement>(\"#vim-hotkeys-help\");\n if (helpPanel) {\n // toggle hidden\n helpPanel.classList.toggle(\"invisible\");\n } else {\n // first call\n helpPanel = Object.assign(document.createElement(\"div\"), {\n id: \"vim-hotkeys-help\",\n className: \"dialog-modal\"\n });\n initHelpContent(helpPanel, keyBindings);\n const body = document.getElementsByTagName(\"body\")[0];\n if (body) {\n body.appendChild(helpPanel);\n }\n }\n};\n\nconst copyURLToClipboard = async (): Promise<void> => {\n const currentUrlElement = document.querySelector<HTMLAnchorElement>(\".result[data-vim-selected] h3 a\");\n assertElement(currentUrlElement);\n\n const url = currentUrlElement.getAttribute(\"href\");\n if (url) {\n await navigator.clipboard.writeText(url);\n }\n};\n\nlisten(\"click\", \".result\", function (this: HTMLElement, event: PointerEvent) {\n if (!isElementInDetail(event.target as HTMLElement)) {\n highlightResult(this)(true, true);\n\n const resultElement = getResultElement(event.target as HTMLElement);\n\n if (resultElement && isImageResult(resultElement)) {\n event.preventDefault();\n mutable.selectImage?.(resultElement);\n }\n }\n});\n\n// FIXME: Focus might also trigger Pointer event ^^^\nlisten(\n \"focus\",\n \".result a\",\n (event: FocusEvent) => {\n if (!isElementInDetail(event.target as HTMLElement)) {\n const resultElement = getResultElement(event.target as HTMLElement);\n\n if (resultElement && !resultElement.hasAttribute(\"data-vim-selected\")) {\n highlightResult(resultElement)(true);\n }\n\n if (resultElement && isImageResult(resultElement)) {\n event.preventDefault();\n mutable.selectImage?.(resultElement);\n }\n }\n },\n { capture: true }\n);\n\nlisten(\"keydown\", document, (event: KeyboardEvent) => {\n // check for modifiers so we don't break browser's hotkeys\n if (Object.hasOwn(keyBindings, event.key) && !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey) {\n const tagName = (event.target as HTMLElement)?.tagName?.toLowerCase();\n\n if (event.key === \"Escape\") {\n keyBindings[event.key]?.fun(event);\n } else if (event.target === document.body || tagName === \"a\" || tagName === \"button\") {\n event.preventDefault();\n keyBindings[event.key]?.fun(event);\n }\n }\n});\n\nmutable.selectNext = highlightResult(\"down\");\nmutable.selectPrevious = highlightResult(\"up\");\n"],"mappings":"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"} \ No newline at end of file