1+ import React , { useState , useRef } from 'react' ;
2+ import { DocumentEditorContainerComponent , Toolbar as DocumentEditorToolbar } from '@syncfusion/ej2-react-documenteditor' ;
3+ import { DialogComponent } from '@syncfusion/ej2-react-popups' ;
4+ import FileManager from './FileManager' ;
5+
6+ DocumentEditorContainerComponent . Inject ( DocumentEditorToolbar ) ;
7+
8+ const DocumentEditor = ( ) => {
9+ const [ maxDocId , setMaxDocId ] = useState ( null ) ;
10+ const containerRef = useRef ( null ) ;
11+ const [ selectedDocId , setSelectedDocId ] = useState ( null ) ;
12+ const [ selectedDocName , setSelectedDocName ] = useState ( null ) ;
13+ const [ showDialog , setShowDialog ] = useState ( false ) ;
14+ const [ showFileManager , setShowFileManager ] = React . useState ( true ) ;
15+ const fileManagerRef = useRef ( null ) ;
16+ const inputRef = useRef ( null ) ;
17+ const contentChanged = useRef ( false ) ;
18+ const [ errorMessage , setErrorMessage ] = useState ( '' ) ;
19+ const randomDefaultName = 'New Document'
20+
21+ const newToolItem = {
22+ prefixIcon : "e-de-ctnr-new" ,
23+ tooltipText : "New" ,
24+ text : "New" ,
25+ id : "CreateNewDoc"
26+ } ;
27+ const openToolItem = {
28+ prefixIcon : "e-de-ctnr-open" ,
29+ tooltipText : "Open file manager" ,
30+ text : "Open" ,
31+ id : "OpenFileManager"
32+ } ;
33+ const downloadToolItem = {
34+ prefixIcon : "e-de-ctnr-download" ,
35+ tooltipText : "Download" ,
36+ text : "Download" ,
37+ id : "DownloadToLocal"
38+ } ;
39+
40+ const hostUrl = "https://localhost:44305/" ;
41+ const toolbarItems = [ newToolItem , openToolItem , downloadToolItem , 'Separator' , 'Undo' , 'Redo' , 'Separator' , 'Image' , 'Table' , 'Hyperlink' , 'Bookmark' , 'TableOfContents' , 'Separator' , 'Header' , 'Footer' , 'PageSetup' , 'PageNumber' , 'Break' , 'InsertFootnote' , 'InsertEndnote' , 'Separator' , 'Find' , 'Separator' , 'Comments' , 'TrackChanges' , 'Separator' , 'LocalClipboard' , 'RestrictEditing' , 'Separator' , 'FormFields' , 'UpdateFields' , 'ContentControl' ] ;
42+
43+ const SaveDocument = ( ) => {
44+ const editor = containerRef . current ?. documentEditor ;
45+ editor . saveAsBlob ( 'Docx' ) . then ( ( blob ) => {
46+ const reader = new FileReader ( ) ;
47+ reader . onloadend = async ( ) => {
48+ const base64Data = reader . result ?. toString ( ) . split ( ',' ) [ 1 ] ;
49+ const fileName = selectedDocName || ( inputRef . current . value || 'Untitled' ) + '.docx' ;
50+ containerRef . current . documentEditor . documentName = fileName ;
51+
52+ try {
53+ await fetch ( hostUrl + `api/documents/${ selectedDocId } /saveDocumentAsync` , {
54+ method : 'POST' ,
55+ headers : {
56+ 'Content-Type' : 'application/json'
57+ } ,
58+ body : JSON . stringify ( {
59+ Base64Content : base64Data ,
60+ FileName : fileName
61+ } )
62+ } ) ;
63+ } catch ( err ) {
64+ console . error ( 'Auto-save error:' , err ) ;
65+ }
66+ } ;
67+ reader . readAsDataURL ( blob ) ;
68+ } ) ;
69+ } ;
70+
71+ const handleDialogSave = async ( ) => {
72+ const documentName = inputRef . current ?. value ?. trim ( ) ;
73+ if ( ! documentName ) {
74+ setErrorMessage ( "Document name cannot be empty." ) ;
75+ return ;
76+ }
77+
78+ const baseFilename = `${ documentName } .docx` ;
79+
80+ // Check if a document with this name already exists
81+ const exists = await checkDocumentExistence ( baseFilename ) ;
82+ if ( exists ) {
83+ setErrorMessage ( "A document with this name already exists. Please choose a different name." ) ;
84+ inputRef . current ?. focus ( ) ;
85+ inputRef . current ?. select ( ) ;
86+ return ;
87+ }
88+
89+ // Proceed to save
90+ setErrorMessage ( "" ) ;
91+ setShowDialog ( false ) ;
92+ const newId = maxDocId + 1 ;
93+ setSelectedDocId ( newId ) ;
94+ setMaxDocId ( newId ) ;
95+ setSelectedDocName ( baseFilename ) ;
96+ containerRef . current . documentEditor . documentName = baseFilename ;
97+ containerRef . current . documentEditor . openBlank ( ) ;
98+ } ;
99+
100+ // Check if a document with a given name already exists on the Azure storage
101+ const checkDocumentExistence = async ( fileName ) => {
102+ try {
103+ const response = await fetch ( hostUrl + 'api/documents/CheckDocumentExistence' , {
104+ method : 'POST' ,
105+ headers : { 'Content-Type' : 'application/json;charset=UTF-8' } ,
106+ body : JSON . stringify ( { fileName : fileName } )
107+ } ) ;
108+ if ( response . ok ) {
109+ const result = await response . json ( ) ;
110+ return result . exists ;
111+ }
112+ return false ;
113+ } catch ( err ) {
114+ console . error ( 'Error checking document existence:' , err ) ;
115+ return false ;
116+ }
117+ } ;
118+
119+ const handleToolbarItemClick = ( args ) => {
120+ let documentName = containerRef . current . documentEditor . documentName ;
121+ const baseDocName = documentName . replace ( / \. [ ^ / . ] + $ / , '' ) ;
122+ switch ( args . item . id ) {
123+ case 'CreateNewDoc' :
124+ setSelectedDocId ( 0 ) ;
125+ setSelectedDocName ( null ) ;
126+ setShowDialog ( true ) ;
127+ containerRef . current . documentEditor . openBlank ( ) ;
128+ containerRef . current . documentEditor . focusIn ( ) ;
129+ break ;
130+ case 'OpenFileManager' :
131+ if ( fileManagerRef . current ) {
132+ fileManagerRef . current . clearSelection ( ) ;
133+ setTimeout ( ( ) => {
134+ fileManagerRef . current . refreshFiles ( ) ;
135+ } , 100 ) ;
136+ containerRef . current . documentEditor . focusIn ( ) ;
137+ }
138+ setShowFileManager ( true ) ;
139+ break ;
140+ case 'DownloadToLocal' :
141+ containerRef . current . documentEditor . save ( baseDocName , 'Docx' ) ;
142+ containerRef . current . documentEditor . focusIn ( ) ;
143+ break ;
144+ default :
145+ break ;
146+ }
147+ } ;
148+
149+ React . useEffect ( ( ) => {
150+ const intervalId = setInterval ( ( ) => {
151+ if ( contentChanged . current ) {
152+ SaveDocument ( ) ;
153+ contentChanged . current = false ;
154+ }
155+ } , 1000 ) ;
156+ return ( ) => clearInterval ( intervalId ) ;
157+ } ) ;
158+
159+ React . useEffect ( ( ) => {
160+ if ( showDialog && inputRef . current ) {
161+ inputRef . current . focus ( ) ;
162+ inputRef . current . select ( ) ;
163+ }
164+ } , [ showDialog ] ) ;
165+
166+ const handleContentChange = ( ) => {
167+ contentChanged . current = true ;
168+ } ;
169+ const loadFileFromFileManager = ( fileId , fileName ) => {
170+ setSelectedDocId ( fileId ) ;
171+ containerRef . current . documentEditor . documentName = fileName ;
172+ setSelectedDocName ( fileName ) ;
173+ } ;
174+
175+ return (
176+ < div >
177+ < FileManager
178+ onFileSelect = { loadFileFromFileManager }
179+ onFileManagerLoaded = { ( id ) => setMaxDocId ( id ) }
180+ editorRef = { containerRef }
181+ fileManagerRef = { fileManagerRef }
182+ visible = { showFileManager }
183+ setVisible = { setShowFileManager }
184+ />
185+ < div id = "document-header" >
186+ { selectedDocName || ( inputRef ?. current ?. value ? inputRef . current . value + '.docx' : '' ) }
187+ </ div >
188+ < DocumentEditorContainerComponent
189+ ref = { containerRef }
190+ height = "calc(100vh - 65px)"
191+ serviceUrl = "https://ej2services.syncfusion.com/production/web-services/api/documenteditor/"
192+ enableToolbar = { true }
193+ toolbarItems = { toolbarItems }
194+ toolbarClick = { handleToolbarItemClick }
195+ contentChange = { handleContentChange }
196+ />
197+ < DialogComponent
198+ visible = { showDialog }
199+ header = 'New Document'
200+ showCloseIcon = { true }
201+ width = '400px'
202+ isModal = { true }
203+ close = { ( ) => setShowDialog ( false ) }
204+ buttons = { [
205+ {
206+ click : handleDialogSave ,
207+ buttonModel : { content : 'Save' , isPrimary : true }
208+ } ,
209+ {
210+ click : ( ) => setShowDialog ( false ) ,
211+ buttonModel : { content : 'Cancel' }
212+ }
213+ ] }
214+ >
215+ < div className = "e-dialog-content" >
216+ < p > Enter document name</ p >
217+ < input
218+ ref = { inputRef }
219+ type = "text"
220+ className = "e-input"
221+ placeholder = "Document name"
222+ defaultValue = { randomDefaultName }
223+ style = { { width : '100%' , marginTop : '10px' } }
224+ />
225+ { errorMessage && (
226+ < div style = { { color : 'red' , marginTop : '4px' } } >
227+ { errorMessage }
228+ </ div >
229+ ) }
230+ </ div >
231+ </ DialogComponent >
232+ </ div >
233+ ) ;
234+ } ;
235+
236+ export default DocumentEditor ;
0 commit comments