summaryrefslogtreecommitdiff
path: root/searx/static/themes/simple/js/keyboard.min.js.map
blob: 1943d9209060d20a8176e18bcab4ab1aa4876523 (plain)
1
{"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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"}