Spaces:
Running
Running
react based conversion
Browse files- src/App.tsx +6 -33
- src/SelectionApp.tsx +134 -0
src/App.tsx
CHANGED
|
@@ -1,35 +1,8 @@
|
|
| 1 |
-
import
|
| 2 |
-
import
|
| 3 |
-
import viteLogo from '/vite.svg'
|
| 4 |
-
import './App.css'
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
|
|
|
| 8 |
|
| 9 |
-
|
| 10 |
-
<>
|
| 11 |
-
<div>
|
| 12 |
-
<a href="https://vite.dev" target="_blank">
|
| 13 |
-
<img src={viteLogo} className="logo" alt="Vite logo" />
|
| 14 |
-
</a>
|
| 15 |
-
<a href="https://react.dev" target="_blank">
|
| 16 |
-
<img src={reactLogo} className="logo react" alt="React logo" />
|
| 17 |
-
</a>
|
| 18 |
-
</div>
|
| 19 |
-
<h1>Vite + React</h1>
|
| 20 |
-
<div className="card">
|
| 21 |
-
<button onClick={() => setCount((count) => count + 1)}>
|
| 22 |
-
count is {count}
|
| 23 |
-
</button>
|
| 24 |
-
<p>
|
| 25 |
-
Add <code>app_build_command: npm run build</code> and <code>app_file: dist/index.html</code> in your README's metadata to easily build and deploy your app :)
|
| 26 |
-
</p>
|
| 27 |
-
</div>
|
| 28 |
-
<p className="read-the-docs">
|
| 29 |
-
Click on the Vite and React logos to learn more
|
| 30 |
-
</p>
|
| 31 |
-
</>
|
| 32 |
-
)
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
export default App
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import SelectionApp from "./SelectionApp";
|
|
|
|
|
|
|
| 3 |
|
| 4 |
+
const App: React.FC = () => {
|
| 5 |
+
return <SelectionApp />;
|
| 6 |
+
};
|
| 7 |
|
| 8 |
+
export default App;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/SelectionApp.tsx
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef } from "react";
|
| 2 |
+
|
| 3 |
+
interface Span {
|
| 4 |
+
span_text: string;
|
| 5 |
+
start: number;
|
| 6 |
+
end: number;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
const SelectionApp: React.FC = () => {
|
| 10 |
+
const [spans, setSpans] = useState<Span[]>([]);
|
| 11 |
+
const [keySpan, setKeySpan] = useState<string>("span_text");
|
| 12 |
+
const [keyStart, setKeyStart] = useState<string>("start");
|
| 13 |
+
const [keyEnd, setKeyEnd] = useState<string>("end");
|
| 14 |
+
const [lastText, setLastText] = useState<string>("");
|
| 15 |
+
|
| 16 |
+
const inputRef = useRef<HTMLDivElement>(null);
|
| 17 |
+
|
| 18 |
+
// Capture the selected text and its indices
|
| 19 |
+
const getSelectionIndices = () => {
|
| 20 |
+
if (!inputRef.current) return;
|
| 21 |
+
const container = inputRef.current;
|
| 22 |
+
const selection = window.getSelection();
|
| 23 |
+
if (!selection || !selection.rangeCount) return;
|
| 24 |
+
|
| 25 |
+
const range = selection.getRangeAt(0);
|
| 26 |
+
if (!container.contains(range.startContainer) || !container.contains(range.endContainer))
|
| 27 |
+
return;
|
| 28 |
+
|
| 29 |
+
const selectedText = selection.toString();
|
| 30 |
+
if (!selectedText) return;
|
| 31 |
+
|
| 32 |
+
// Compute start/end indices
|
| 33 |
+
const preRange = document.createRange();
|
| 34 |
+
preRange.setStart(container, 0);
|
| 35 |
+
preRange.setEnd(range.startContainer, range.startOffset);
|
| 36 |
+
const startIndex = preRange.toString().length;
|
| 37 |
+
const endIndex = startIndex + selectedText.length;
|
| 38 |
+
|
| 39 |
+
const newSpan: Span = { span_text: selectedText, start: startIndex, end: endIndex };
|
| 40 |
+
setSpans((prev) => [newSpan, ...prev].slice(0, 5)); // Keep only latest 5
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
// Map internal spans to current key names
|
| 44 |
+
const renderSpans = () => {
|
| 45 |
+
return spans.map((s) => ({
|
| 46 |
+
[keySpan]: s.span_text,
|
| 47 |
+
[keyStart]: s.start,
|
| 48 |
+
[keyEnd]: s.end,
|
| 49 |
+
}));
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
// Clear spans if input text changes
|
| 53 |
+
const handleInput = (e: React.FormEvent<HTMLDivElement>) => {
|
| 54 |
+
const currentText = e.currentTarget.innerText;
|
| 55 |
+
if (currentText !== lastText) {
|
| 56 |
+
setSpans([]);
|
| 57 |
+
setLastText(currentText);
|
| 58 |
+
}
|
| 59 |
+
};
|
| 60 |
+
|
| 61 |
+
return (
|
| 62 |
+
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
|
| 63 |
+
<h3>Highlight text to get indices</h3>
|
| 64 |
+
|
| 65 |
+
{/* Settings Panel */}
|
| 66 |
+
<div
|
| 67 |
+
style={{
|
| 68 |
+
marginBottom: "15px",
|
| 69 |
+
padding: "10px",
|
| 70 |
+
border: "1px solid #ccc",
|
| 71 |
+
borderRadius: "6px",
|
| 72 |
+
background: "#fafafa",
|
| 73 |
+
width: "90%",
|
| 74 |
+
}}
|
| 75 |
+
>
|
| 76 |
+
<strong>Settings (rename keys):</strong>
|
| 77 |
+
<br />
|
| 78 |
+
<br />
|
| 79 |
+
<label>
|
| 80 |
+
Span Text Key:{" "}
|
| 81 |
+
<input value={keySpan} onChange={(e) => setKeySpan(e.target.value)} />
|
| 82 |
+
</label>
|
| 83 |
+
<label style={{ marginLeft: "10px" }}>
|
| 84 |
+
Start Key:{" "}
|
| 85 |
+
<input value={keyStart} onChange={(e) => setKeyStart(e.target.value)} />
|
| 86 |
+
</label>
|
| 87 |
+
<label style={{ marginLeft: "10px" }}>
|
| 88 |
+
End Key:{" "}
|
| 89 |
+
<input value={keyEnd} onChange={(e) => setKeyEnd(e.target.value)} />
|
| 90 |
+
</label>
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
{/* Editable Div */}
|
| 94 |
+
<div
|
| 95 |
+
ref={inputRef}
|
| 96 |
+
contentEditable
|
| 97 |
+
onInput={handleInput}
|
| 98 |
+
style={{
|
| 99 |
+
width: "90%",
|
| 100 |
+
minHeight: "150px",
|
| 101 |
+
border: "1px solid #ccc",
|
| 102 |
+
padding: "10px",
|
| 103 |
+
whiteSpace: "pre-wrap",
|
| 104 |
+
overflowWrap: "break-word",
|
| 105 |
+
}}
|
| 106 |
+
></div>
|
| 107 |
+
|
| 108 |
+
<button
|
| 109 |
+
onClick={getSelectionIndices}
|
| 110 |
+
style={{ marginTop: "10px", padding: "5px 10px" }}
|
| 111 |
+
>
|
| 112 |
+
Get Span JSON
|
| 113 |
+
</button>
|
| 114 |
+
|
| 115 |
+
{/* Display JSON */}
|
| 116 |
+
<pre
|
| 117 |
+
style={{
|
| 118 |
+
marginTop: "15px",
|
| 119 |
+
fontFamily: "monospace",
|
| 120 |
+
background: "#f4f4f4",
|
| 121 |
+
padding: "10px",
|
| 122 |
+
borderRadius: "6px",
|
| 123 |
+
border: "1px solid #ddd",
|
| 124 |
+
whiteSpace: "pre-wrap",
|
| 125 |
+
userSelect: "text",
|
| 126 |
+
}}
|
| 127 |
+
>
|
| 128 |
+
{spans.length ? JSON.stringify(renderSpans(), null, 2) : ""}
|
| 129 |
+
</pre>
|
| 130 |
+
</div>
|
| 131 |
+
);
|
| 132 |
+
};
|
| 133 |
+
|
| 134 |
+
export default SelectionApp;
|