mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
47157e7a55
* feat(editor): add Vim keyboard annotation controls * feat(ui): add optional live Vim HUD * feat(ui): finish Vim HUD experience * feat(ui): promote Vim to dedicated settings panel * feat(ui): make Vim document focus automatic * feat(ui): let Vim HUD hide its key panel * fix(ui): harden Vim selection UX
85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
/** Elements whose text must not become part of a document annotation range. */
|
|
const NON_ANNOTATABLE_SELECTOR = [
|
|
'button',
|
|
'input',
|
|
'textarea',
|
|
'select',
|
|
'script',
|
|
'style',
|
|
'[contenteditable]:not([contenteditable="false"])',
|
|
'[data-pinpoint-ignore]',
|
|
'.select-none',
|
|
'.annotation-toolbar',
|
|
'.katex',
|
|
].join(',');
|
|
|
|
const DOCUMENT_KEYBOARD_CONTROL_SELECTOR = [
|
|
'button',
|
|
'input',
|
|
'textarea',
|
|
'select',
|
|
'a[href]',
|
|
'summary',
|
|
'[contenteditable]:not([contenteditable="false"])',
|
|
'[role="button"]',
|
|
'[role="link"]',
|
|
'[role="textbox"]',
|
|
'[role="dialog"]',
|
|
].join(',');
|
|
|
|
/**
|
|
* Return the annotatable text nodes under an element in document order.
|
|
* Existing highlight wrappers remain eligible so Vim selections can overlap
|
|
* annotations just like pointer selections.
|
|
*/
|
|
export function getAnnotatableTextNodes(element: Element): Text[] {
|
|
const nodes: Text[] = [];
|
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, {
|
|
acceptNode(node) {
|
|
const parent = node.parentElement;
|
|
if (!parent || parent.closest(NON_ANNOTATABLE_SELECTOR)) {
|
|
return NodeFilter.FILTER_REJECT;
|
|
}
|
|
return node.textContent?.length
|
|
? NodeFilter.FILTER_ACCEPT
|
|
: NodeFilter.FILTER_REJECT;
|
|
},
|
|
});
|
|
|
|
let node = walker.nextNode();
|
|
while (node) {
|
|
if (node instanceof Text) nodes.push(node);
|
|
node = walker.nextNode();
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
/**
|
|
* Return whether a keyboard event originated in an editable or interactive
|
|
* control that must retain ownership of ordinary typing keys.
|
|
*/
|
|
export function isDocumentKeyboardControl(target: EventTarget | null): boolean {
|
|
if (!(target instanceof Element)) return false;
|
|
return target.matches(DOCUMENT_KEYBOARD_CONTROL_SELECTOR)
|
|
|| target.closest(DOCUMENT_KEYBOARD_CONTROL_SELECTOR) !== null
|
|
|| (target instanceof HTMLElement && target.isContentEditable);
|
|
}
|
|
|
|
/**
|
|
* Create a text-node-anchored range spanning an element's annotatable text.
|
|
* Web-highlighter cannot paint element-node endpoints, so callers share this
|
|
* conversion instead of synthesizing subtly different ranges.
|
|
*/
|
|
export function createTextRange(element: HTMLElement): Range | null {
|
|
const nodes = getAnnotatableTextNodes(element);
|
|
const firstNode = nodes[0];
|
|
const lastNode = nodes[nodes.length - 1];
|
|
|
|
if (!firstNode || !lastNode) return null;
|
|
|
|
const range = document.createRange();
|
|
range.setStart(firstNode, 0);
|
|
range.setEnd(lastNode, lastNode.length);
|
|
return range;
|
|
}
|