Skip to content

Commit 59ac5cf

Browse files
feat: programmatic text selection example
1 parent 07796cd commit 59ac5cf

10 files changed

Lines changed: 258 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"dirname": "programmatic-text-selection",
3+
"tags": [
4+
"editing",
5+
"viewing",
6+
"react"
7+
],
8+
"title": "Programmatic Text Selection"
9+
}
104 KB
Loading
3.86 MB
Binary file not shown.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>SuperDoc React Example</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.jsx"></script>
11+
</body>
12+
</html>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "react-superdoc-example",
3+
"private": true,
4+
"version": "0.0.1",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite"
8+
},
9+
"dependencies": {
10+
"@harbour-enterprises/superdoc": "^0.11.13",
11+
"react": "^19.0.0",
12+
"react-dom": "^19.0.0"
13+
},
14+
"devDependencies": {
15+
"@vitejs/plugin-react": "^4.0.4",
16+
"vite": "^7.0.5",
17+
"prosemirror-state": "^1.4.3"
18+
}
19+
}
67.3 KB
Binary file not shown.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { useRef, useState } from 'react';
2+
import { TextSelection } from 'prosemirror-state';
3+
import DocumentEditor from './components/DocumentEditor';
4+
5+
function App() {
6+
const [documentFile, setDocumentFile] = useState(null);
7+
const selectionLengthRef = useRef(10);
8+
const fileInputRef = useRef(null);
9+
const editorRef = useRef(null);
10+
11+
const handleFileChange = (event) => {
12+
const file = event.target.files?.[0];
13+
if (file) {
14+
setDocumentFile(file);
15+
}
16+
};
17+
18+
const handleEditorReady = (editor) => {
19+
console.log('SuperDoc editor is ready', editor);
20+
editorRef.current = editor;
21+
};
22+
23+
const handleTextSelection = () => {
24+
if (editorRef.current && editorRef.current.activeEditor) {
25+
const activeEditor = editorRef.current.activeEditor;
26+
27+
const { view } = activeEditor;
28+
let { selection } = view.state;
29+
30+
// Get current cursor positions
31+
const fromPos = selection.$from.pos;
32+
33+
// Create a new selection from cursor with user-defined length
34+
const newSelection = TextSelection.create(view.state.doc, fromPos, fromPos + selectionLengthRef.current);
35+
const tr = view.state.tr.setSelection(newSelection);
36+
const state = view.state.apply(tr);
37+
view.updateState(state);
38+
39+
activeEditor.commands.setUnderline();
40+
}
41+
};
42+
43+
return (
44+
<div className="app">
45+
<header>
46+
<h1>SuperDoc Example</h1>
47+
<button onClick={() => fileInputRef.current?.click()}>
48+
Load Document
49+
</button>
50+
<div className="selection-controls">
51+
<label htmlFor="selectionLength">Selection Length:</label>
52+
<input
53+
id="selectionLength"
54+
type="number"
55+
defaultValue={selectionLengthRef.current}
56+
onChange={(e) => selectionLengthRef.current = Number(e.target.value)}
57+
min="1"
58+
max="1000"
59+
/>
60+
<button onClick={handleTextSelection}>
61+
Select Text
62+
</button>
63+
</div>
64+
<input
65+
type="file"
66+
ref={fileInputRef}
67+
accept=".docx, application/vnd.openxmlformats-officedocument.wordprocessingml.document"
68+
onChange={handleFileChange}
69+
style={{ display: 'none' }}
70+
/>
71+
</header>
72+
73+
<main>
74+
<DocumentEditor
75+
initialData={documentFile}
76+
onEditorReady={handleEditorReady}
77+
/>
78+
</main>
79+
80+
<style jsx>{`
81+
.app {
82+
height: 100vh;
83+
display: flex;
84+
flex-direction: column;
85+
}
86+
header {
87+
padding: 1rem;
88+
background: #f5f5f5;
89+
display: flex;
90+
align-items: center;
91+
gap: 1rem;
92+
}
93+
header button {
94+
padding: 0.5rem 1rem;
95+
background: #1355ff;
96+
color: white;
97+
border: none;
98+
border-radius: 4px;
99+
cursor: pointer;
100+
}
101+
header button:hover {
102+
background: #0044ff;
103+
}
104+
.selection-controls {
105+
display: flex;
106+
align-items: center;
107+
gap: 0.5rem;
108+
}
109+
.selection-controls label {
110+
font-size: 0.9rem;
111+
}
112+
.selection-controls input[type="number"] {
113+
width: 80px;
114+
padding: 0.3rem;
115+
border: 1px solid #ccc;
116+
border-radius: 4px;
117+
}
118+
main {
119+
flex: 1;
120+
min-height: 0;
121+
}
122+
`}</style>
123+
</div>
124+
);
125+
}
126+
127+
export default App;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { SuperDoc } from '@harbour-enterprises/superdoc';
2+
import '@harbour-enterprises/superdoc/style.css';
3+
import { useEffect, useRef } from 'react';
4+
5+
const DocumentEditor = ({
6+
initialData = null,
7+
readOnly = false,
8+
onEditorReady
9+
}) => {
10+
const editorRef = useRef(null);
11+
12+
useEffect(() => {
13+
const config = {
14+
selector: '#superdoc',
15+
toolbar: '#superdoc-toolbar',
16+
documentMode: readOnly ? 'viewing' : 'editing',
17+
pagination: true,
18+
rulers: true,
19+
onReady: () => {
20+
if (onEditorReady) {
21+
onEditorReady(editor);
22+
}
23+
},
24+
onEditorCreate: (event) => {
25+
console.log('Editor is created', event);
26+
},
27+
onEditorDestroy: () => {
28+
console.log('Editor is destroyed');
29+
}
30+
}
31+
32+
if (initialData) config.document = initialData;
33+
// config.document = './sample.docx'; // or use path to file
34+
35+
const editor = new SuperDoc(config);
36+
37+
editorRef.current = editor;
38+
39+
// Cleanup on unmount
40+
return () => {
41+
if (editorRef.current) {
42+
editorRef.current = null;
43+
}
44+
};
45+
}, [initialData, readOnly, onEditorReady]);
46+
47+
return (
48+
<div className="document-editor">
49+
<div id="superdoc-toolbar" className="toolbar" />
50+
<div id="superdoc" className="superdoc-container" />
51+
<style jsx>{`
52+
.document-editor {
53+
display: flex;
54+
flex-direction: column;
55+
height: 100%;
56+
width: 100%;
57+
}
58+
.toolbar {
59+
flex: 0 0 auto;
60+
border-bottom: 1px solid #eee;
61+
}
62+
.superdoc-container {
63+
display: flex;
64+
justify-content: center;
65+
flex: 1 1 auto;
66+
overflow: auto;
67+
}
68+
`}</style>
69+
</div>
70+
);
71+
};
72+
73+
export default DocumentEditor;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
import App from './App';
4+
5+
ReactDOM.createRoot(document.getElementById('root')).render(
6+
<React.StrictMode>
7+
<App />
8+
</React.StrictMode>
9+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from 'vite';
2+
import react from '@vitejs/plugin-react';
3+
4+
export default defineConfig({
5+
plugins: [react()],
6+
optimizeDeps: {
7+
include: ['@harbour-enterprises/superdoc']
8+
}
9+
});

0 commit comments

Comments
 (0)