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 [ showLengthInput , setShowLengthInput ] = useState ( true ) ;
8+ const selectionMethodRef = useRef ( 'length' ) ;
9+ const selectionLengthRef = useRef ( 10 ) ;
10+ const fileInputRef = useRef ( null ) ;
11+ const editorRef = useRef ( null ) ;
12+
13+ const handleFileChange = ( event ) => {
14+ const file = event . target . files ?. [ 0 ] ;
15+ if ( file ) {
16+ setDocumentFile ( file ) ;
17+ }
18+ } ;
19+
20+ const handleEditorReady = ( editor ) => {
21+ console . log ( 'SuperDoc editor is ready' , editor ) ;
22+ editorRef . current = editor ;
23+ } ;
24+
25+ const getCurrentSelection = ( ) => {
26+ const { view } = editorRef . current . activeEditor ;
27+ return view . state . selection ;
28+ } ;
29+
30+ const getLengthBasedPositions = ( ) => {
31+ const selection = getCurrentSelection ( ) ;
32+ const { view } = editorRef . current . activeEditor ;
33+ const currentPos = selection . from ;
34+ const selectionLength = selectionLengthRef . current ;
35+ const docLength = view . state . doc . content . size ;
36+
37+ return {
38+ from : currentPos ,
39+ to : Math . min ( currentPos + selectionLength , docLength )
40+ } ;
41+ } ;
42+
43+ const getLineBasedPositions = ( ) => {
44+ const selection = getCurrentSelection ( ) ;
45+ const { $from } = selection ;
46+ return {
47+ from : $from . start ( ) ,
48+ to : $from . end ( )
49+ } ;
50+ } ;
51+
52+ const applySelection = ( from , to ) => {
53+ const activeEditor = editorRef . current . activeEditor ;
54+ const { view } = activeEditor ;
55+
56+ const newSelection = TextSelection . create ( view . state . doc , from , to ) ;
57+ const tr = view . state . tr . setSelection ( newSelection ) ;
58+ const state = view . state . apply ( tr ) ;
59+ view . updateState ( state ) ;
60+
61+ activeEditor . commands . setUnderline ( ) ;
62+ } ;
63+
64+ const handleSelection = ( getPositions ) => {
65+ const { from, to } = getPositions ( ) ;
66+ applySelection ( from , to ) ;
67+ } ;
68+
69+ const handleLengthSelection = ( ) => handleSelection ( getLengthBasedPositions ) ;
70+ const handleLineSelection = ( ) => handleSelection ( getLineBasedPositions ) ;
71+
72+ const handleSelectionClick = ( ) => {
73+ if ( selectionMethodRef . current === 'length' ) {
74+ handleLengthSelection ( ) ;
75+ } else {
76+ handleLineSelection ( ) ;
77+ }
78+ } ;
79+
80+ return (
81+ < div className = "app" >
82+ < header >
83+ < h1 > SuperDoc Example</ h1 >
84+ < button onClick = { ( ) => fileInputRef . current ?. click ( ) } >
85+ Load Document
86+ </ button >
87+ < div className = "selection-controls" >
88+ < div className = "selection-group" >
89+ < label htmlFor = "selectionMethod" > Selection method:</ label >
90+ < select
91+ id = "selectionMethod"
92+ defaultValue = { selectionMethodRef . current }
93+ onChange = { ( e ) => {
94+ selectionMethodRef . current = e . target . value ;
95+ setShowLengthInput ( e . target . value === 'length' ) ;
96+ } }
97+ >
98+ < option value = "length" > By Length</ option >
99+ < option value = "line" > By Line</ option >
100+ </ select >
101+
102+ { /* hide input when not needed */ }
103+ { showLengthInput && (
104+ < >
105+ < label htmlFor = "selectionLength" > Characters:</ label >
106+ < input
107+ id = "selectionLength"
108+ type = "number"
109+ defaultValue = { selectionLengthRef . current }
110+ onChange = { ( e ) => selectionLengthRef . current = Number ( e . target . value ) }
111+ min = "1"
112+ max = "1000"
113+ />
114+ </ >
115+ ) }
116+
117+ < button onClick = { handleSelectionClick } >
118+ Select and underline
119+ </ button >
120+ </ div >
121+ </ div >
122+ < input
123+ type = "file"
124+ ref = { fileInputRef }
125+ accept = ".docx, application/vnd.openxmlformats-officedocument.wordprocessingml.document"
126+ onChange = { handleFileChange }
127+ style = { { display : 'none' } }
128+ />
129+ </ header >
130+
131+ < main >
132+ < DocumentEditor
133+ initialData = { documentFile }
134+ onEditorReady = { handleEditorReady }
135+ />
136+ </ main >
137+
138+ < style jsx > { `
139+ .app {
140+ height: 100vh;
141+ display: flex;
142+ flex-direction: column;
143+ }
144+ header {
145+ padding: 1rem;
146+ background: #f5f5f5;
147+ display: flex;
148+ align-items: center;
149+ gap: 1rem;
150+ }
151+ header button {
152+ padding: 0.5rem 1rem;
153+ background: #1355ff;
154+ color: white;
155+ border: none;
156+ border-radius: 4px;
157+ cursor: pointer;
158+ }
159+ header button:hover {
160+ background: #0044ff;
161+ }
162+ .selection-controls {
163+ display: flex;
164+ align-items: center;
165+ gap: 1rem;
166+ }
167+ .selection-group {
168+ display: flex;
169+ align-items: center;
170+ gap: 0.5rem;
171+ padding: 0.5rem;
172+ border: 1px solid #ddd;
173+ border-radius: 4px;
174+ background: #fafafa;
175+ }
176+ .selection-group > label:first-child {
177+ font-weight: bold;
178+ font-size: 0.9rem;
179+ color: #333;
180+ }
181+ .selection-controls label {
182+ font-size: 0.9rem;
183+ }
184+ .selection-controls input[type="number"] {
185+ width: 80px;
186+ padding: 0.3rem;
187+ border: 1px solid #ccc;
188+ border-radius: 4px;
189+ }
190+ .selection-controls select {
191+ padding: 0.3rem;
192+ border: 1px solid #ccc;
193+ border-radius: 4px;
194+ background: white;
195+ }
196+ main {
197+ flex: 1;
198+ min-height: 0;
199+ }
200+ ` } </ style >
201+ </ div >
202+ ) ;
203+ }
204+
205+ export default App ;
0 commit comments