get-span-indices / src /SelectionApp.tsx
wadood's picture
fixed behaviour with consecutive newlines
b621168
raw
history blame
4.35 kB
// SelectionApp.tsx
import React, { useState, useRef } from "react";
interface Span {
span_text: string;
start: number;
end: number;
}
const SelectionApp: React.FC = () => {
const [spans, setSpans] = useState<Span[]>([]);
const [keySpan, setKeySpan] = useState<string>("span_text");
const [keyStart, setKeyStart] = useState<string>("start");
const [keyEnd, setKeyEnd] = useState<string>("end");
const [lastText, setLastText] = useState<string>("");
const inputRef = useRef<HTMLDivElement>(null);
const getNormalizedText = (container: HTMLElement): string => {
let html = container.innerHTML;
html = html.replace(/<div><br><\/div>/gi, '\n');
html = html.replace(/<div>/gi, '\n').replace(/<\/div>/gi, '');
html = html.replace(/<br\s*\/?>/gi, '\n');
html = html.replace(/&nbsp;/g, ' ');
html = html.replace(/<[^>]+>/g, '');
return html;
};
const getSelectionIndices = () => {
if (!inputRef.current) return;
const container = inputRef.current;
const selection = window.getSelection();
if (!selection || !selection.rangeCount) return;
const range = selection.getRangeAt(0);
if (!container.contains(range.startContainer) || !container.contains(range.endContainer))
return;
const selectedText = selection.toString();
if (!selectedText) return;
const preRange = range.cloneRange();
preRange.selectNodeContents(container);
preRange.setEnd(range.startContainer, range.startOffset);
const tempDiv = document.createElement("div");
tempDiv.appendChild(preRange.cloneContents());
const preText = getNormalizedText(tempDiv);
const start = preText.length;
const end = start + selectedText.length;
const newSpan: Span = { span_text: selectedText, start, end };
setSpans((prev) => [newSpan, ...prev].slice(0, 5));
};
const renderSpans = () => {
return spans.map((s) => ({
[keySpan]: s.span_text,
[keyStart]: s.start,
[keyEnd]: s.end,
}));
};
const handleInput = (e: React.FormEvent<HTMLDivElement>) => {
const currentText = getNormalizedText(e.currentTarget);
if (currentText !== lastText) {
setSpans([]);
setLastText(currentText);
}
};
return (
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
<h3>Highlight text to get indices</h3>
{/* Settings Panel */}
<div
style={{
marginBottom: "15px",
padding: "10px",
border: "1px solid #ccc",
borderRadius: "6px",
background: "#fafafa",
width: "90%",
}}
>
<strong>Settings (rename keys):</strong>
<br />
<br />
<label>
Span Text Key:{" "}
<input value={keySpan} onChange={(e) => setKeySpan(e.target.value)} />
</label>
<label style={{ marginLeft: "10px" }}>
Start Key:{" "}
<input value={keyStart} onChange={(e) => setKeyStart(e.target.value)} />
</label>
<label style={{ marginLeft: "10px" }}>
End Key:{" "}
<input value={keyEnd} onChange={(e) => setKeyEnd(e.target.value)} />
</label>
</div>
{/* Editable Div */}
<div
ref={inputRef}
contentEditable
onInput={handleInput}
style={{
width: "90%",
minHeight: "150px",
maxHeight: "400px",
border: "1px solid #ccc",
padding: "10px",
whiteSpace: "pre-wrap",
overflowWrap: "break-word",
overflowY: "auto", // scroll if content exceeds height
resize: "vertical", // make resizable vertically
}}
>
Paste or type your text here...
</div>
<button
onClick={getSelectionIndices}
style={{ marginTop: "10px", padding: "5px 10px" }}
>
Get Selection JSON
</button>
{/* Display JSON */}
<pre
style={{
marginTop: "15px",
fontFamily: "monospace",
background: "#f4f4f4",
padding: "10px",
borderRadius: "6px",
border: "1px solid #ddd",
whiteSpace: "pre-wrap",
userSelect: "text",
}}
>
{spans.length ? JSON.stringify(renderSpans(), null, 2) : ""}
</pre>
</div>
);
};
export default SelectionApp;