From d37020870600d6c842f9a75ebd3986df6010c25c Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sat, 2 Aug 2025 21:54:15 +0530 Subject: Add hints extra for opening links (number keys only) --- assets/javascript/finder.js | 132 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 assets/javascript/finder.js (limited to 'assets') diff --git a/assets/javascript/finder.js b/assets/javascript/finder.js new file mode 100644 index 0000000..925a135 --- /dev/null +++ b/assets/javascript/finder.js @@ -0,0 +1,132 @@ +(() => { + /** @typedef {{ key: string; elem: HTMLElement; labelElem: HTMLElement | null }} Match */ + + const addRoot = () => { + const root = document.createElement('div'); + document.body.appendChild(root); + return root; + } + + /** + * @param match {Match} + * @return {Promise} + */ + const createLabelElem = (match) => new Promise((resolve, reject) => { + const label = Object.assign(document.createElement('div'), { + className: '__nullbrowser_findlabel', + textContent: match.key, + }); + requestAnimationFrame(() => { + const rect = match.elem.getBoundingClientRect(); + if (rect.top < 0 | rect.top > window.innerHeight) return reject(null); + if (rect.left < 0 | rect.left > window.innerWidth) return reject(null); + + resolve(label); + requestAnimationFrame(() => { + Object.assign(label.style, { + position: 'fixed', + zIndex: 999999999, // maybe a few more 9s will do + top: `${rect.top}px`, + left: `${rect.left}px`, + backgroundColor: 'yellow', + border: '1px solid #000', + boxShadow: '0 0 3px #555', + color: '#222', + padding: '1px 2px', + fontSize: '10px', + fontWeight: 'bold', + fontFamily: 'monospace', + }); + }); + }); + }); + + const finder = { + /** @type {string} */ + keys: '', + /** @type {Array} */ + matches: [], + /** @type {HTMLElement | null} */ + labelsRoot: null, + /** @type {boolean} */ + openInNewView: false, + + stop() { + finder.matches = []; + finder.keys = ''; + [...(finder.labelsRoot?.children ?? [])].forEach(child => { + child.remove(); + }); + }, + + /** + * @param selector {string} + * @param new_view {boolean} + */ + start(selector, new_view) { + finder.stop() + finder.labelsRoot ??= addRoot(); + finder.createMatches(selector); + finder.openInNewView = new_view; + }, + + /** @param char {string} */ + filterOutByKey(char) { + if (!/[0-9]+/.test(char)) return; // NOTE: temporary + + finder.keys = finder.keys + char; + finder.matches = finder.matches.filter(m => { + const isMatch = m.key.startsWith(finder.keys); + if (!isMatch) { + m.labelElem?.remove(); + return false; + } + return true; + }); + if (finder.matches.length <= 1) { + const match = finder.matches[0] + finder.stop(); + if (match) { + if (match.elem?.href) { + if (finder.openInNewView) { + window.open(match.elem?.href) + } else { + location.href = match.elem?.href + } + } else { + match.elem?.click() + } + } + } + }, + + /** @param selector {string} */ + createMatches(selector) { + finder.matches = []; + const elements = document.querySelectorAll(selector); + + const matches = [...elements].map(async (elem, index) => { + /** @type {Match} */ + const match = { + elem, + key: `${index}`.padStart(`${elements.length}`.length, '0'), + labelElem: null, + } + match.labelElem = await createLabelElem(match); + return match + }); + + Promise.allSettled(matches).then(matches => { + matches.forEach(result => { + if (result.status !== 'fulfilled') return; + if (result.value.labelElem) + finder.labelsRoot.appendChild(result.value.labelElem) + finder.matches.push(result.value) + }); + }); + }, + }; + + window._nullbrowser ||= {}; + window._nullbrowser.finder ||= finder; +})(); -- cgit v1.3.1