-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
I need to extract the selected text (with coordinates, text, pages etc.) and I do it like this
export function selectionToRestriction(e: MouseEvent, root: HTMLElement, zoom: number = 1): SelectionResult | null {
const sel = window.getSelection();
if (!sel || sel.isCollapsed) return null;
const exactText = sel.toString().trim();
if (!exactText) return null;
const range = sel.getRangeAt(0);
const clientRects = Array.from(range.getClientRects());
if (!clientRects.length) return null;
const pageNodes = Array.from(root.querySelectorAll<HTMLElement>('[data-rp^="page-"]')).map(node => ({
page: Number(node.getAttribute('data-rp')!.replace('page-', '')),
rect: node.getBoundingClientRect()
}));
const pageMap = new Map<number, DOMRect[]>();
for (const r of clientRects) {
if (r.width < 0.5 || r.height < 0.5) continue;
const cx = r.left + r.width / 2;
const cy = r.top + r.height / 2;
const owningPage = pageNodes.find(
p => cx >= p.rect.left && cx <= p.rect.right && cy >= p.rect.top && cy <= p.rect.bottom
);
if (!owningPage) continue;
if (!pageMap.has(owningPage.page)) pageMap.set(owningPage.page, []);
pageMap.get(owningPage.page)!.push(r);
}
if (!pageMap.size) return null;
const containerRect = root.getBoundingClientRect();
const metadataList: RestrictionMetadata[] = [];
for (const [page, rects] of pageMap) {
const coords = rects.map(r => ({
xStart: (r.left - containerRect.left) / zoom,
yStart: (r.top - containerRect.top) / zoom,
width: r.width / zoom,
height: r.height / zoom
}));
const coordsFiltered = coords.filter(r => r.width > 0.5 && r.height > 0.5);
if (!coordsFiltered.length) continue;
const merged = mergeOverlappingRects(mergeRectsByLine(coordsFiltered));
const mergedFiltered = merged.filter(r => r.width > 0.5 && r.height > 0.5);
if (!mergedFiltered.length) continue;
const area = mergedFiltered.reduce(
(acc, r) => {
const right = r.xStart + r.width;
const bottom = r.yStart + r.height;
return {
xStart: Math.min(acc.xStart, r.xStart),
yStart: Math.min(acc.yStart, r.yStart),
width: Math.max(acc.width, right - acc.xStart),
height: Math.max(acc.height, bottom - acc.yStart)
};
},
{ ...mergedFiltered[0] }
);
metadataList.push({ page, exactText, textRows: mergedFiltered, area });
}
metadataList.sort((a, b) => a.page - b.page);
return {
text: exactText,
popup: {
text: exactText,
x: (e.clientX - containerRect.left) / zoom,
y: (e.clientY - containerRect.top) / zoom
},
newRestriction: {
coordinates: metadataList
}
};
}useEffect(() => {
const root = rootRef.current;
if (!root) return;
const onMouseUp = (e: MouseEvent) => {
const restriction = selectionToRestriction(e, root, currentZoom);
if (!restriction) {
setPopup(null);
return;
}
setPopup({ ...restriction.popup });
setNewRestriction(restriction.newRestriction);
};
root.addEventListener('mouseup', onMouseUp);
return () => root.removeEventListener('mouseup', onMouseUp);
}, [rootRef, currentZoom, setPopup, setNewRestriction]);It would be nice if we had something similar natively
Metadata
Metadata
Assignees
Labels
No labels