From e6d09718e62c89055d38274e1fa7b42daea6e555 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Mon, 4 Aug 2025 22:37:49 +0530 Subject: Update hints to go back to normal mode when matching is over --- assets/javascript/finder.js | 132 -------------------------------------- assets/javascript/hints.js | 152 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 132 deletions(-) delete mode 100644 assets/javascript/finder.js create mode 100644 assets/javascript/hints.js (limited to 'assets') diff --git a/assets/javascript/finder.js b/assets/javascript/finder.js deleted file mode 100644 index 925a135..0000000 --- a/assets/javascript/finder.js +++ /dev/null @@ -1,132 +0,0 @@ -(() => { - /** @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; -})(); diff --git a/assets/javascript/hints.js b/assets/javascript/hints.js new file mode 100644 index 0000000..ce958f2 --- /dev/null +++ b/assets/javascript/hints.js @@ -0,0 +1,152 @@ +(() => { + /** @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 hints = { + /** @type {string} */ + keys: '', + /** @type {Array} */ + matches: [], + /** @type {HTMLElement | null} */ + labelsRoot: null, + /** @type {boolean} */ + openInNewView: false, + + stop() { + hints.matches = []; + hints.keys = ''; + [...(hints.labelsRoot?.children ?? [])].forEach(child => { + child.remove(); + }); + }, + + /** + * @param selector {string} + * @param new_view {boolean} + */ + start(selector, new_view) { + hints.stop() + hints.labelsRoot ??= addRoot(); + hints.createMatches(selector); + hints.openInNewView = new_view; + }, + + /** + * @param {string} char + * @return {boolean} + */ + filterOutByKey(char) { + if (!/[0-9]+/.test(char)) return false; + + hints.keys = hints.keys + char; + hints.matches = hints.matches.filter(m => { + const isMatch = m.key.startsWith(hints.keys); + if (!isMatch) { + m.labelElem?.remove(); + return false + } + if (m.labelElem) { + const text = m.labelElem.textContent + const start = text.slice(0, hints.keys.length) + const end = text.slice(hints.keys.length) + m.labelElem.innerHTML = `${start}${end}` + } + return true + }); + + if (hints.matches.length === 0) { + hints.stop() + return true + } + + if (hints.matches.length === 1) { + const match = hints.matches[0] + hints.stop(); + if (!match) { + console.log(hints.matches); + return true; + } + if (match.elem?.href) { + if (hints.openInNewView) { + window.open(match.elem?.href) + } else { + location.href = match.elem?.href + } + } else { + match.elem?.click() + } + return true + } + + return false + }, + + /** @param selector {string} */ + createMatches(selector) { + hints.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) + hints.labelsRoot.appendChild(result.value.labelElem) + hints.matches.push(result.value) + }); + }); + }, + }; + + window._nullbrowser ||= {}; + window._nullbrowser.hints ||= hints; +})(); -- cgit v1.3.1