Skip to content

Commit 1b08726

Browse files
committed
Merge branch 'dev-frontend' into feat/115
2 parents 51b3960 + 9803294 commit 1b08726

File tree

5 files changed

+149
-37
lines changed

5 files changed

+149
-37
lines changed

frontend/src/components/atoms/BlockContent/BlockContent.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
} from '@utils/blockContent';
2626
import { useCommand, useManager } from '@/hooks';
2727
import { focusState } from '@/stores/page';
28-
import { moveBlock } from '@/utils';
28+
import { moveBlock, updateBlock } from '@/utils';
2929

3030
const isGridOrColumn = (block: Block): boolean =>
3131
block.type === BlockType.GRID || block.type === BlockType.COLUMN;
@@ -77,6 +77,7 @@ function BlockContent(blockDTO: Block) {
7777
const caretRef = useRef(0);
7878
const listCnt = useRef(1);
7979
const [Dispatcher] = useCommand();
80+
const [isBlur, setIsBlur] = useState(false);
8081
const draggingBlock = useRecoilValue(draggingBlockState);
8182
const [{ blockIndex }] = useManager(blockDTO.id);
8283
const [dragOverToggle, setDragOverToggle] = useState(false);
@@ -197,17 +198,26 @@ function BlockContent(blockDTO: Block) {
197198
(event.key === 'ArrowRight' &&
198199
focusOffset ===
199200
((focusNode as any).length ?? (focusNode as any).innerText.length)) ||
200-
(event.key === 'Enter' && !event.shiftKey)
201+
(event.key === 'Enter' && !event.shiftKey) ||
202+
event.key === 'Tab' ||
203+
(event.key === 'Backspace' && !focusOffset)
201204
) {
202205
throttleState.isThrottle = true;
203206
event.preventDefault();
204207
setImmediate(() => {
205-
Dispatcher(event.key);
208+
Dispatcher((event.shiftKey ? 'shift' : '') + event.key);
206209
throttleState.isThrottle = false;
207210
});
208211
}
209212
};
210213

214+
useEffect(() => {
215+
(async () => {
216+
const { block: updatedBlock } = await updateBlock(blockDTO);
217+
setBlockMap({ ...blockMap, [blockDTO.id]: updatedBlock });
218+
})();
219+
}, [isBlur]);
220+
211221
useEffect(() => {
212222
blockRefState[blockDTO.id] = contentEditableRef;
213223
return () => {
@@ -288,6 +298,7 @@ function BlockContent(blockDTO: Block) {
288298
placeholder={placeHolder[blockDTO.type]}
289299
onInput={handleValue}
290300
onKeyUp={handleKeyUp}
301+
onBlur={() => setIsBlur(!isBlur)}
291302
>
292303
{blockDTO.value}
293304
</div>

frontend/src/components/molecules/Title/Title.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,11 @@ const titleCss = () => css`
3939
4040
font-family: inter, Helvetica, 'Apple Color Emoji', Arial, sans-serif,
4141
'Segoe UI Emoji', 'Segoe UI Symbol';
42-
42+
4343
&:empty:before {
4444
content: 'Untitled';
4545
color: rgba(55, 53, 47, 0.15);
4646
}
47-
}
4847
`;
4948

5049
function Title(): JSX.Element {

frontend/src/hooks/useCommand.tsx

+51-8
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ import { Block, BlockType } from '@/schemes';
66
const useCommand = () => {
77
const [focusId, setFocusId] = useRecoilState(focusState);
88
const [
9-
{ block, blockIndex },
9+
{ block, blockIndex, siblingsIdList, parent, grandParent, blockMap },
1010
{
1111
getPrevBlock,
1212
getNextBlock,
13-
addChild,
14-
addSibling,
13+
insertSibling,
14+
insertNewChild,
15+
insertNewSibling,
1516
setBlock,
17+
pullIn,
18+
pullOut,
1619
startTransaction,
1720
commitTransaction,
21+
deleteBlock,
1822
},
1923
] = useManager(focusId);
2024

@@ -74,10 +78,10 @@ const useCommand = () => {
7478
const { focusOffset } = window.getSelection();
7579
startTransaction();
7680
if (!focusOffset) {
77-
addSibling({}, blockIndex);
81+
insertNewSibling({}, blockIndex);
7882
} else if (block.childIdList.length) {
79-
setBlock({ value: before });
80-
const newBlock = addChild({ value: after });
83+
setBlock(block.id, { value: before });
84+
const newBlock = insertNewChild({ value: after });
8185
setFocus(newBlock);
8286
} else {
8387
const type = [
@@ -87,13 +91,52 @@ const useCommand = () => {
8791
].includes(block.type)
8892
? block.type
8993
: BlockType.TEXT;
90-
setBlock({ value: before });
91-
const newBlock = addSibling({ value: after, type });
94+
setBlock(block.id, { value: before });
95+
const newBlock = insertNewSibling({ value: after, type });
9296
setFocus(newBlock);
9397
}
9498
commitTransaction();
9599
break;
96100
}
101+
case 'Tab': {
102+
startTransaction();
103+
pullIn();
104+
commitTransaction();
105+
break;
106+
}
107+
case 'shiftTab': {
108+
startTransaction();
109+
pullOut();
110+
commitTransaction();
111+
break;
112+
}
113+
case 'Backspace': {
114+
startTransaction();
115+
if (block.type !== BlockType.TEXT) {
116+
setBlock(block.id, { type: BlockType.TEXT });
117+
} else if (
118+
siblingsIdList.length - 1 === blockIndex &&
119+
grandParent &&
120+
grandParent.type !== BlockType.GRID
121+
) {
122+
pullOut();
123+
} else {
124+
const [, after] = getSlicedValueToCaretOffset();
125+
const prevBlock = getPrevBlock();
126+
if (prevBlock) {
127+
const deletedBlockChildrenIdList = deleteBlock();
128+
deletedBlockChildrenIdList.forEach((id, index) =>
129+
insertSibling(id, index),
130+
);
131+
setFocus(
132+
setBlock(prevBlock.id, { value: prevBlock.value + after }),
133+
);
134+
setCaretOffset(prevBlock.value.length);
135+
}
136+
}
137+
commitTransaction();
138+
break;
139+
}
97140
}
98141
};
99142
return [dispatcher];

frontend/src/hooks/useFamily.tsx

+13-7
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ const useFamily = (blockId: string): [BlockFamily, FamilyFunc] => {
4141
return children[0];
4242
}
4343
if (blockIndex !== siblings.length - 1) {
44-
/** 자식이 없고 마지막 자식이 아니므로 다음 형제가 다음 Block 이다. */
45-
return siblings[blockIndex + 1];
44+
const nextSibling = siblings[blockIndex + 1];
45+
switch (nextSibling.type) {
46+
case BlockType.COLUMN:
47+
return blockMap[nextSibling.childIdList[0]];
48+
case BlockType.GRID:
49+
return blockMap[blockMap[nextSibling.childIdList[0]].childIdList[0]];
50+
default:
51+
/** 다음 부모의 타입이 COLUMN이나 GRID가 아니면 다음 Block 이다. */
52+
return nextSibling;
53+
}
4654
}
4755
/* block이 마지막 자식일 때. 다음 부모가 다음 Block 이다. */
48-
const targetParentBlock = parents[parentIndex + 1];
56+
const targetParentBlock = parents?.[parentIndex + 1];
4957
if (!targetParentBlock) {
5058
/** 현재 블록이 마지막 블록이다. */
5159
return null;
@@ -70,10 +78,8 @@ const useFamily = (blockId: string): [BlockFamily, FamilyFunc] => {
7078
/** blockIndex가 0이 아니면 이전 형제에서 prev block을 찾을 수 있다. */
7179
const prevSibling = siblings[blockIndex - 1];
7280
if (prevSibling?.childIdList.length) {
73-
/* 이전 형제에게 자식이 있다면 마지막 자식이 prev block 이다. */
74-
return blockMap[
75-
prevSibling.childIdList[prevSibling.childIdList.length - 1]
76-
];
81+
/* 이전 형제에게 자식이 있다면 마지막 후손이 prev block 이다. */
82+
return findLastDescendant(prevSibling);
7783
}
7884
/* 이전 형제에게 자식이 없다면 형제가 prev block 이다. */
7985
return prevSibling;

frontend/src/hooks/useManager.tsx

+70-17
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import { Block, BlockType, BlockFamily, FamilyFunc, BlockMap } from '@/schemes';
22
import { useFamily } from '@/hooks';
33

44
interface ManagerFunc {
5-
addChild: (option?: any, insertIndex?: number) => Block;
6-
addSibling: (option?: any, insertIndex?: number) => Block;
7-
setBlock: (option?: any) => void;
5+
insertNewChild: (option?: any, insertIndex?: number) => Block;
6+
insertNewSibling: (option?: any, insertIndex?: number) => Block;
7+
insertSibling: (id: string, inserIndex?: number) => Block;
8+
setBlock: (id: string, option?: any) => Block;
89
startTransaction: () => void;
910
commitTransaction: () => void;
11+
pullIn: () => Block;
12+
pullOut: () => Block;
13+
deleteBlock: () => string[];
1014
}
1115

1216
const useManger = (
@@ -36,13 +40,15 @@ const useManger = (
3640
familyFunc.setBlockMap(transaction);
3741
};
3842

39-
const setBlock = (option: any = {}) => {
40-
transaction[block.id] = {
41-
...block,
43+
const setBlock = (id: string, option: any = {}) => {
44+
transaction[id] = {
45+
...transaction[id],
4246
...option,
4347
};
48+
return transaction[id];
4449
};
45-
const addChild = (option: any = {}, insertIndex = 0): Block => {
50+
51+
const insertNewChild = (option: any = {}, insertIndex = 0): Block => {
4652
const id = `${block.id}${children.length + 1}_${Date.now()}`;
4753
const newBlock: Block = {
4854
id,
@@ -53,17 +59,28 @@ const useManger = (
5359
pageId: page.id,
5460
...option,
5561
};
56-
transaction[id] = newBlock;
62+
setBlock(id, newBlock);
5763
const copyChildIdList = [...childrenIdList];
5864
copyChildIdList.splice(insertIndex, 0, id);
59-
transaction[block.id] = {
60-
...transaction[block.id],
65+
setBlock(block.id, {
6166
childIdList: copyChildIdList,
62-
};
67+
});
6368
return newBlock;
6469
};
6570

66-
const addSibling = (
71+
const insertSibling = (id: string, insertIndex: number = 0) => {
72+
const copySiblingsIdList = transaction[parent.id].childIdList;
73+
copySiblingsIdList.splice(insertIndex, 0, id);
74+
setBlock(parent.id, {
75+
childrenIdList: copySiblingsIdList,
76+
});
77+
setBlock(id, {
78+
parentId: parent.id,
79+
});
80+
return transaction[id];
81+
};
82+
83+
const insertNewSibling = (
6784
option: any = {},
6885
insertIndex = blockIndex + 1,
6986
): Block => {
@@ -80,22 +97,58 @@ const useManger = (
8097
transaction[id] = newBlock;
8198
const copySiblingsIdList = [...siblingsIdList];
8299
copySiblingsIdList.splice(insertIndex, 0, id);
83-
transaction[parent.id] = {
84-
...parent,
100+
setBlock(parent.id, {
85101
childIdList: copySiblingsIdList,
86-
};
102+
});
87103
return newBlock;
88104
};
89105

106+
const deleteBlock = () => {
107+
const filteredSiblingsIdList = siblingsIdList.filter(
108+
(id) => id !== block.id,
109+
);
110+
setBlock(parent.id, {
111+
childIdList: filteredSiblingsIdList,
112+
});
113+
return [...transaction[block.id].childIdList];
114+
};
115+
116+
const pullIn = () => {
117+
if (blockIndex) {
118+
const targetSibling = siblings[blockIndex - 1];
119+
setBlock(siblingsIdList[blockIndex - 1], {
120+
childIdList: [...targetSibling.childIdList, block.id],
121+
});
122+
deleteBlock();
123+
setBlock(block.id, { parentId: siblingsIdList[blockIndex - 1] });
124+
}
125+
return block;
126+
};
127+
128+
const pullOut = () => {
129+
if (grandParent && grandParent.type !== BlockType.GRID) {
130+
deleteBlock();
131+
const copyParentsIdList = [...parentsIdList];
132+
copyParentsIdList.splice(parentIndex + 1, 0, block.id);
133+
setBlock(block.id, { parentId: grandParent.id });
134+
setBlock(grandParent.id, { childIdList: copyParentsIdList });
135+
}
136+
return block;
137+
};
138+
90139
return [
91140
family,
92141
{
93142
...familyFunc,
94-
addChild,
95-
addSibling,
143+
insertNewChild,
144+
insertNewSibling,
145+
insertSibling,
96146
setBlock,
97147
startTransaction,
98148
commitTransaction,
149+
pullIn,
150+
pullOut,
151+
deleteBlock,
99152
},
100153
];
101154
};

0 commit comments

Comments
 (0)