11import '../styles/EditableText.css' ;
2+ import { Trash } from 'react-bootstrap-icons' ;
23
34import { useState , useEffect , useCallback } from 'react' ;
45import FormattedText from './FormattedText' ;
@@ -10,6 +11,8 @@ function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment,
1011 const [ beingEdited , setBeingEdited ] = useState ( false ) ;
1112 const [ editedDocument , setEditedDocument ] = useState ( ) ;
1213 const [ editedText , setEditedText ] = useState ( ) ;
14+ const [ showDeleteModal , setShowDeleteModal ] = useState ( false ) ;
15+ const [ deleteTarget , setDeleteTarget ] = useState ( { src : '' , alt : '' , internal : false , name : '' } ) ;
1316 const PASSAGE = new RegExp ( `\\{${ rubric } } ?([^{]*)` ) ;
1417
1518 let parsePassage = ( rawText ) => ( rubric )
@@ -79,16 +82,99 @@ function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment,
7982 . catch ( console . error ) ;
8083 } ;
8184
85+ const imageRegex = / ! \[ ( [ ^ \] ] * ) \] \( ( [ ^ ) ] + ) \) / g;
86+ let images = [ ] ;
87+ let cleanedimage = text || '' ;
88+ let match ;
89+ while ( ( match = imageRegex . exec ( cleanedimage ) ) !== null ) {
90+ images . push ( { alt : match [ 1 ] , src : match [ 2 ] } ) ;
91+ }
92+ cleanedimage = cleanedimage . replace ( imageRegex , '' ) ;
93+
94+ const confirmDelete = ( ) => {
95+ const { src, alt, internal, name } = deleteTarget ;
96+ const esc = s => s . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
97+ const mdRx = new RegExp ( `!\\[${ esc ( alt ) } \\]\\(${ esc ( src ) } \\)` , 'g' ) ;
98+ const clean = t => ( t || '' ) . replace ( mdRx , '' ) . replace ( / \n { 2 , } / g, '\n\n' ) . trim ( ) ;
99+ if ( internal ) {
100+ backend . deleteAttachment ( id => {
101+ backend . getDocument ( id ) . then ( doc => {
102+ const cleaned = clean ( doc . text ) ;
103+ backend . putDocument ( { ...doc , text : cleaned } ) . then ( r => {
104+ setEditedText ( cleaned ) ;
105+ setEditedDocument ( { ...doc , text : cleaned , _rev : r . rev } ) ;
106+ setLastUpdate ( r . rev ) ;
107+ setShowDeleteModal ( false ) ;
108+ } ) ;
109+ } ) ;
110+ } ) ;
111+ } else {
112+ const cleaned = clean ( editedText ) ;
113+ setEditedText ( cleaned ) ;
114+ setEditedDocument ( p => ( { ...p , text : cleaned } ) ) ;
115+ setShowDeleteModal ( false ) ;
116+ }
117+ } ;
118+
82119 if ( ! beingEdited ) return (
83120 < div className = "editable content position-relative" title = "Edit content..." >
84121 < div className = "formatted-text" onClick = { handleClick } >
85122 < FormattedText { ...{ setHighlightedText, setSelectedText} } >
86- { text || ' ' }
123+ { ( images . length > 0 ? cleanedimage : text ) || ' ' }
87124 </ FormattedText >
125+ { images . map ( ( { src, alt } ) => (
126+ < figure
127+ key = { src + alt }
128+ className = "has-trash-overlay"
129+ style = { { position : 'relative' , display : 'inline-block' , margin : 0 } }
130+ >
131+ < img
132+ src = { src }
133+ alt = { alt }
134+ className = "img-fluid rounded editable-image"
135+ />
136+ < button
137+ className = "trash-overlay"
138+ type = "button"
139+ aria-label = { `Delete image ${ alt || src } ` }
140+ title = { `Delete image ${ alt || src } ` }
141+ onClick = { e => {
142+ e . stopPropagation ( ) ;
143+ const internal = src . includes ( `/${ id } /` ) ;
144+ const name = internal
145+ ? decodeURIComponent ( src . split ( `${ id } /` ) [ 1 ] )
146+ : src ;
147+ setDeleteTarget ( { src, alt, internal, name } ) ;
148+ setShowDeleteModal ( true ) ;
149+ } }
150+ >
151+ < Trash />
152+ </ button >
153+ </ figure >
154+ ) ) }
88155 </ div >
89156 < DiscreeteDropdown >
90157 < PictureUploadAction { ... { id, backend, handleImageUrl} } />
91158 </ DiscreeteDropdown >
159+ { showDeleteModal && (
160+ < div className = "modal fade show d-block" tabIndex = "-1" role = "dialog" >
161+ < div className = "modal-dialog" role = "document" >
162+ < div className = "modal-content" >
163+ < div className = "modal-header" >
164+ < h5 className = "modal-title" > Confirm deletion</ h5 >
165+ < button type = "button" className = "btn-close" onClick = { ( ) => setShowDeleteModal ( false ) } />
166+ </ div >
167+ < div className = "modal-body" >
168+ < p > Delete image { deleteTarget . internal ? `"${ deleteTarget . name } "` : 'external' } ?</ p >
169+ </ div >
170+ < div className = "modal-footer" >
171+ < button type = "button" className = "btn btn-secondary" onClick = { ( ) => setShowDeleteModal ( false ) } > Cancel</ button >
172+ < button type = "button" className = "btn btn-danger" onClick = { confirmDelete } > Delete</ button >
173+ </ div >
174+ </ div >
175+ </ div >
176+ </ div >
177+ ) }
92178 </ div >
93179 ) ;
94180 return (
0 commit comments