Security Remediation: All Phases Complete (84 fixes) #348
@@ -53,6 +53,8 @@ export function LinkAutocomplete({
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const mirrorRef = useRef<HTMLDivElement | null>(null);
|
||||
const cursorSpanRef = useRef<HTMLSpanElement | null>(null);
|
||||
|
||||
/**
|
||||
* Search for knowledge entries matching the query.
|
||||
@@ -132,15 +134,37 @@ export function LinkAutocomplete({
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculate dropdown position relative to cursor
|
||||
* Calculate dropdown position relative to cursor.
|
||||
* Uses a persistent off-screen mirror element (via refs) to avoid
|
||||
* creating and removing DOM nodes on every keystroke, which causes
|
||||
* layout thrashing.
|
||||
*/
|
||||
const calculateDropdownPosition = useCallback(
|
||||
(textarea: HTMLTextAreaElement, cursorIndex: number): { top: number; left: number } => {
|
||||
// Create a mirror div to measure text position
|
||||
const mirror = document.createElement("div");
|
||||
const styles = window.getComputedStyle(textarea);
|
||||
// Lazily create the mirror element once, then reuse it
|
||||
if (!mirrorRef.current) {
|
||||
const mirror = document.createElement("div");
|
||||
mirror.style.position = "absolute";
|
||||
mirror.style.visibility = "hidden";
|
||||
mirror.style.height = "auto";
|
||||
mirror.style.whiteSpace = "pre-wrap";
|
||||
mirror.style.pointerEvents = "none";
|
||||
document.body.appendChild(mirror);
|
||||
mirrorRef.current = mirror;
|
||||
|
||||
// Copy relevant styles
|
||||
const span = document.createElement("span");
|
||||
span.textContent = "|";
|
||||
cursorSpanRef.current = span;
|
||||
}
|
||||
|
||||
const mirror = mirrorRef.current;
|
||||
const cursorSpan = cursorSpanRef.current;
|
||||
if (!cursorSpan) {
|
||||
return { top: 0, left: 0 };
|
||||
}
|
||||
|
||||
// Sync styles from the textarea so measurement is accurate
|
||||
const styles = window.getComputedStyle(textarea);
|
||||
const stylesToCopy = [
|
||||
"fontFamily",
|
||||
"fontSize",
|
||||
@@ -161,31 +185,19 @@ export function LinkAutocomplete({
|
||||
}
|
||||
});
|
||||
|
||||
mirror.style.position = "absolute";
|
||||
mirror.style.visibility = "hidden";
|
||||
mirror.style.width = `${String(textarea.clientWidth)}px`;
|
||||
mirror.style.height = "auto";
|
||||
mirror.style.whiteSpace = "pre-wrap";
|
||||
|
||||
// Get text up to cursor
|
||||
// Update content: text before cursor + cursor marker span
|
||||
const textBeforeCursor = textarea.value.substring(0, cursorIndex);
|
||||
mirror.textContent = textBeforeCursor;
|
||||
|
||||
// Create a span for the cursor position
|
||||
const cursorSpan = document.createElement("span");
|
||||
cursorSpan.textContent = "|";
|
||||
mirror.appendChild(cursorSpan);
|
||||
|
||||
document.body.appendChild(mirror);
|
||||
|
||||
const textareaRect = textarea.getBoundingClientRect();
|
||||
const cursorSpanRect = cursorSpan.getBoundingClientRect();
|
||||
|
||||
const top = cursorSpanRect.top - textareaRect.top + textarea.scrollTop + 20;
|
||||
const left = cursorSpanRect.left - textareaRect.left + textarea.scrollLeft;
|
||||
|
||||
document.body.removeChild(mirror);
|
||||
|
||||
return { top, left };
|
||||
},
|
||||
[]
|
||||
@@ -346,7 +358,8 @@ export function LinkAutocomplete({
|
||||
}, [textareaRef, handleInput, handleKeyDown]);
|
||||
|
||||
/**
|
||||
* Cleanup timeout and abort in-flight requests on unmount
|
||||
* Cleanup timeout, abort in-flight requests, and remove the
|
||||
* persistent mirror element on unmount
|
||||
*/
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
@@ -356,6 +369,11 @@ export function LinkAutocomplete({
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
if (mirrorRef.current) {
|
||||
document.body.removeChild(mirrorRef.current);
|
||||
mirrorRef.current = null;
|
||||
cursorSpanRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user