1
1
/** @jsx jsx */
2
2
/** @jsxRuntime classic */
3
3
import { jsx , css } from '@emotion/react' ;
4
- import React , {
5
- useEffect ,
6
- useRef ,
7
- FormEvent ,
8
- KeyboardEvent ,
9
- useState ,
10
- } from 'react' ;
11
- import { useRecoilState , useRecoilValue } from 'recoil' ;
4
+ import React , { useEffect , useRef , KeyboardEvent , useState } from 'react' ;
5
+ import { useRecoilValue } from 'recoil' ;
12
6
13
- import {
14
- blockMapState ,
15
- blockRefState ,
16
- draggingBlockState ,
17
- throttleState ,
18
- } from '@/stores' ;
7
+ import { blockRefState , draggingBlockState , throttleState } from '@/stores' ;
19
8
import { Block , BlockType } from '@/schemes' ;
20
9
import {
21
10
regex ,
@@ -25,7 +14,7 @@ import {
25
14
} from '@utils/blockContent' ;
26
15
import { useCommand , useManager } from '@/hooks' ;
27
16
import { focusState } from '@/stores/page' ;
28
- import { moveBlock , updateBlock } from '@/utils' ;
17
+ import { moveBlock , debounce } from '@/utils' ;
29
18
30
19
const isGridOrColumn = ( block : Block ) : boolean =>
31
20
block . type === BlockType . GRID || block . type === BlockType . COLUMN ;
@@ -72,14 +61,14 @@ const dragOverCss = () => css`
72
61
73
62
function BlockContent ( blockDTO : Block ) {
74
63
const contentEditableRef = useRef ( null ) ;
75
- const [ blockMap , setBlockMap ] = useRecoilState ( blockMapState ) ;
76
64
const focusId = useRecoilValue ( focusState ) ;
77
- const caretRef = useRef ( 0 ) ;
78
65
const listCnt = useRef ( 1 ) ;
79
66
const [ Dispatcher ] = useCommand ( ) ;
80
- const [ isBlur , setIsBlur ] = useState ( false ) ;
67
+ const [
68
+ { blockIndex, prevSiblings } ,
69
+ { commitTransaction, startTransaction, setBlock, setCaretOffset } ,
70
+ ] = useManager ( blockDTO . id ) ;
81
71
const draggingBlock = useRecoilValue ( draggingBlockState ) ;
82
- const [ { blockIndex } ] = useManager ( blockDTO . id ) ;
83
72
const [ dragOverToggle , setDragOverToggle ] = useState ( false ) ;
84
73
85
74
useEffect ( ( ) => {
@@ -93,23 +82,7 @@ function BlockContent(blockDTO: Block) {
93
82
if ( focusId === blockDTO . id ) contentEditableRef . current . focus ( ) ;
94
83
} , [ focusId ] ) ;
95
84
96
- useEffect ( ( ) => {
97
- const selection = window . getSelection ( ) ;
98
- const nodeLength = selection . focusNode ?. nodeValue ?. length ?? 0 ;
99
- if ( caretRef . current > nodeLength ) {
100
- caretRef . current = nodeLength ;
101
- }
102
- selection . collapse ( selection . focusNode , caretRef . current ) ;
103
- } , [ blockDTO . value ] ) ;
104
-
105
- const indexInSibling : number = blockMap [
106
- blockDTO . parentId
107
- ] . childIdList . findIndex ( ( childId : string ) => childId === blockDTO . id ) ;
108
-
109
- const upperBlocks : Array < Block > = blockMap [ blockDTO . parentId ] . childIdList
110
- . map ( ( childId : any ) => blockMap [ childId ] )
111
- . slice ( 0 , indexInSibling )
112
- . reverse ( ) ;
85
+ const upperBlocks : Array < Block > = prevSiblings . reverse ( ) ;
113
86
114
87
const isUpperBlockEqualToNumberList = ( ) : boolean => {
115
88
if ( upperBlocks . length ) {
@@ -129,27 +102,27 @@ function BlockContent(blockDTO: Block) {
129
102
130
103
const FIRST_LIST_NUMBER = '1' ;
131
104
132
- const handleBlock = ( value : string , type ?: BlockType ) =>
133
- type
134
- ? setBlockMap ( {
135
- ... blockMap ,
136
- [ blockDTO . id ] : { ... blockDTO , value , type } ,
137
- } )
138
- : setBlockMap ( {
139
- ... blockMap ,
140
- [ blockDTO . id ] : { ... blockDTO , value } ,
141
- } ) ;
105
+ const handleBlock = ( value : string , type ?: BlockType , caretOffset = - 1 ) => {
106
+ const { focusOffset } = window . getSelection ( ) ;
107
+ startTransaction ( ) ;
108
+ setBlock ( blockDTO . id , { value , type : type || blockDTO . type } ) ;
109
+ contentEditableRef . current . blur ( ) ;
110
+ setTimeout ( ( ) => {
111
+ setCaretOffset ( caretOffset === - 1 ? focusOffset : caretOffset ) ;
112
+ } ) ;
113
+ commitTransaction ( ) ;
114
+ } ;
142
115
143
- const handleValue = ( event : FormEvent < HTMLDivElement > ) => {
144
- const content = event . currentTarget . textContent ;
116
+ const handleValue = ( ) => {
117
+ const content = contentEditableRef . current . textContent ;
145
118
const newType = Object . entries ( regex ) . find ( ( testRegex ) =>
146
119
testRegex [ 1 ] . test ( content ) ,
147
120
) ;
148
121
149
122
if ( newType ) {
150
123
if ( newType [ 0 ] === BlockType . NUMBERED_LIST ) {
151
- if ( ! indexInSibling && content [ 0 ] !== FIRST_LIST_NUMBER ) return ;
152
- if ( indexInSibling ) {
124
+ if ( ! blockIndex && content [ 0 ] !== FIRST_LIST_NUMBER ) return ;
125
+ if ( blockIndex ) {
153
126
const numberListUpperBlock = isUpperBlockEqualToNumberList ( ) ;
154
127
if ( ! numberListUpperBlock && content [ 0 ] !== FIRST_LIST_NUMBER ) return ;
155
128
if (
@@ -164,28 +137,11 @@ function BlockContent(blockDTO: Block) {
164
137
content . slice ( content . indexOf ( ' ' ) + 1 , content . length ) ,
165
138
newType [ 0 ] as BlockType ,
166
139
) ;
167
- caretRef . current = 0 ;
168
140
return ;
169
141
}
170
142
handleBlock ( content ) ;
171
- const selection = window . getSelection ( ) ;
172
- caretRef . current = selection . focusOffset ;
173
- } ;
174
-
175
- const handleKeyUp = ( event : KeyboardEvent < HTMLDivElement > ) => {
176
- const content = event . currentTarget . textContent ;
177
- if (
178
- event . key === 'Backspace' &&
179
- ( ! blockDTO . value || ! window . getSelection ( ) . focusOffset )
180
- ) {
181
- handleBlock ( content , BlockType . TEXT ) ;
182
- }
183
-
184
- if ( event . key === 'Enter' && event . shiftKey ) {
185
- handleBlock ( content ) ;
186
- caretRef . current = window . getSelection ( ) . focusOffset ;
187
- }
188
143
} ;
144
+ const updateValue = useRef ( debounce ( handleValue , 300 ) ) . current ;
189
145
190
146
const handleKeyDown = ( event : KeyboardEvent < HTMLDivElement > ) => {
191
147
const { focusNode, focusOffset } = window . getSelection ( ) ;
@@ -211,13 +167,6 @@ function BlockContent(blockDTO: Block) {
211
167
}
212
168
} ;
213
169
214
- useEffect ( ( ) => {
215
- ( async ( ) => {
216
- const { block : updatedBlock } = await updateBlock ( blockDTO ) ;
217
- setBlockMap ( { ...blockMap , [ blockDTO . id ] : updatedBlock } ) ;
218
- } ) ( ) ;
219
- } , [ isBlur ] ) ;
220
-
221
170
useEffect ( ( ) => {
222
171
blockRefState [ blockDTO . id ] = contentEditableRef ;
223
172
return ( ) => {
@@ -226,32 +175,26 @@ function BlockContent(blockDTO: Block) {
226
175
} , [ ] ) ;
227
176
228
177
useEffect ( ( ) => {
229
- if ( focusId === blockDTO . id ) contentEditableRef . current . focus ( ) ;
178
+ if ( focusId === blockDTO . id ) {
179
+ contentEditableRef . current . focus ( ) ;
180
+ }
230
181
} , [ focusId ] ) ;
231
182
232
183
useEffect ( ( ) => {
233
- const selection = window . getSelection ( ) ;
234
- const nodeLength = selection . focusNode ?. nodeValue ?. length ?? 0 ;
235
- if ( caretRef . current > nodeLength ) {
236
- caretRef . current = nodeLength ;
237
- }
238
- selection . collapse ( selection . focusNode , caretRef . current ) ;
239
-
240
184
if ( blockDTO . type === BlockType . NUMBERED_LIST ) {
241
185
const numberListUpperBlock = isUpperBlockEqualToNumberList ( ) ;
242
- if ( ! indexInSibling || ! numberListUpperBlock ) {
186
+ if ( ! blockIndex || ! numberListUpperBlock ) {
243
187
listCnt . current = 1 ;
244
188
return ;
245
189
}
246
190
if ( numberListUpperBlock ) {
247
191
listCnt . current = cntOfUpperNumberListBlock ( ) + 1 ;
248
192
}
249
193
}
250
- } , [ blockDTO . value ] ) ;
194
+ } , [ blockDTO . type ] ) ;
251
195
252
196
const dragOverHandler = ( event : React . DragEvent < HTMLDivElement > ) => {
253
197
event . dataTransfer . dropEffect = 'move' ;
254
-
255
198
event . preventDefault ( ) ;
256
199
} ;
257
200
@@ -269,14 +212,11 @@ function BlockContent(blockDTO: Block) {
269
212
toId : blockDTO . parentId ,
270
213
index : blockIndex + 1 ,
271
214
} ) ;
272
- setBlockMap ( ( prev ) => {
273
- const next = { ...prev } ;
274
- next [ block . id ] = block ;
275
- fromBlock && ( next [ fromBlock . id ] = fromBlock ) ;
276
- next [ to . id ] = to ;
277
- return next ;
278
- } ) ;
279
-
215
+ startTransaction ( ) ;
216
+ setBlock ( block . id , block ) ;
217
+ fromBlock && setBlock ( fromBlock . id , fromBlock ) ;
218
+ setBlock ( to . id , to ) ;
219
+ commitTransaction ( ) ;
280
220
event . preventDefault ( ) ;
281
221
} ;
282
222
@@ -296,9 +236,7 @@ function BlockContent(blockDTO: Block) {
296
236
onKeyDown = { handleKeyDown }
297
237
suppressContentEditableWarning
298
238
placeholder = { placeHolder [ blockDTO . type ] }
299
- onInput = { handleValue }
300
- onKeyUp = { handleKeyUp }
301
- onBlur = { ( ) => setIsBlur ( ! isBlur ) }
239
+ onInput = { updateValue }
302
240
>
303
241
{ blockDTO . value }
304
242
</ div >
0 commit comments