get-span-indices / src /SelectionApp.tsx
wadood's picture
added cleaner react view
9bfb26f
// 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]);
};
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={{
maxWidth: "900px",
margin: "40px auto",
fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
color: "#333",
}}
>
<h2 style={{ textAlign: "center", marginBottom: "25px", color: "#1a73e8" }}>
Span Annotation App
</h2>
{/* Settings Panel */}
<div
style={{
marginBottom: "20px",
padding: "15px 20px",
borderRadius: "10px",
background: "#f9f9f9",
boxShadow: "0 2px 6px rgba(0,0,0,0.1)",
display: "flex",
flexWrap: "wrap",
gap: "10px",
alignItems: "center",
}}
>
<strong>Key Names:</strong>
<label>
Span Text:
<input
value={keySpan}
onChange={(e) => setKeySpan(e.target.value)}
style={{ marginLeft: "5px", padding: "4px 8px", borderRadius: "4px", border: "1px solid #ccc" }}
/>
</label>
<label>
Start:
<input
value={keyStart}
onChange={(e) => setKeyStart(e.target.value)}
style={{ marginLeft: "5px", padding: "4px 8px", borderRadius: "4px", border: "1px solid #ccc" }}
/>
</label>
<label>
End:
<input
value={keyEnd}
onChange={(e) => setKeyEnd(e.target.value)}
style={{ marginLeft: "5px", padding: "4px 8px", borderRadius: "4px", border: "1px solid #ccc" }}
/>
</label>
</div>
{/* Editable Div */}
<div
ref={inputRef}
contentEditable
onInput={handleInput}
style={{
width: "100%",
minHeight: "180px",
maxHeight: "450px",
border: "1px solid #ccc",
borderRadius: "10px",
padding: "12px",
whiteSpace: "pre-wrap",
overflowWrap: "break-word",
overflowY: "auto",
resize: "vertical",
boxShadow: "inset 0 2px 6px rgba(0,0,0,0.05)",
fontSize: "16px",
lineHeight: "1.5",
}}
></div>
<button
onClick={getSelectionIndices}
style={{
marginTop: "15px",
padding: "10px 20px",
backgroundColor: "#1a73e8",
color: "#fff",
border: "none",
borderRadius: "6px",
cursor: "pointer",
boxShadow: "0 2px 6px rgba(0,0,0,0.2)",
}}
>
Get Selection JSON
</button>
{/* Display JSON */}
<pre
style={{
marginTop: "20px",
fontFamily: "monospace",
background: "#f4f4f4",
padding: "15px",
borderRadius: "10px",
border: "1px solid #ddd",
whiteSpace: "pre-wrap",
userSelect: "text",
boxShadow: "inset 0 2px 4px rgba(0,0,0,0.05)",
}}
>
{spans.length ? JSON.stringify(renderSpans(), null, 2) : ""}
</pre>
</div>
);
};
export default SelectionApp;