-
Notifications
You must be signed in to change notification settings - Fork 1
[Fix/#115] 노트 저장 json 구조 수정 #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough노트 편집기 컨텍스트에 TipTap JSON과 백엔드 API 노트 JSON 간의 변환기를 추가하고, 노트 로드 시 API→TipTap으로, 저장 시 TipTap→API로 변환을 통합했습니다. 또한 NoteContentAttrs에 선택적 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Editor as TipTap Editor
participant Context as NoteEditorContext
participant Transformer as note-content-transformer
participant API as Backend API
Editor->>Context: 요청된 노트 로드 (note data)
Context->>Transformer: transformContentFromApi(apiNote.contents)
Transformer-->>Context: TipTap 호환 JSON
Context-->>Editor: 초기 콘텐츠 설정 (initialContent)
Editor->>Context: 사용자 편집 후 저장 요청 (TipTap JSON)
Context->>Transformer: transformContentForApi(tipTapJson)
Transformer-->>Context: 백엔드 포맷(contents)
Context->>API: 저장 요청 (request.contents)
API-->>Context: 저장 결과
Context-->>Editor: 저장 완료/오류 상태
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related issues
시 (Poem)
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✨ Storybook 확인: 🔗 https://6785fc0fb0ff9f8d21d80624-jzykomjnto.chromatic.com/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/contexts/note-editor-context.tsx (2)
19-75: 변환 로직 확인 및 에러 핸들링 고려 필요백엔드 형식을 TipTap 형식으로 변환하는 로직이 전반적으로 잘 구현되어 있습니다. 특히 line 27에서
reversed속성을?? false로 안전하게 처리한 점이 좋습니다.다만 예상치 못한 데이터 구조가 들어올 경우를 대비한 에러 핸들링을 추가하는 것을 권장합니다. 특히 백엔드 API 응답의 형식이 변경되거나 손상된 데이터가 전달될 경우 앱이 크래시될 수 있습니다.
선택적 개선사항: 기본값 문자열들("질문을 입력하세요", "답변을 입력하세요" 등)을 상수로 추출하면 유지보수성이 향상됩니다.
78-126: blankcard의 attrs 처리 일관성 확인 필요전반적인 변환 로직은 잘 구현되어 있습니다.
vocacard의 경우 line 90에서
...content.attrs를 사용해reversed를 포함한 모든 속성을 보존하는 반면, blankcard(lines 107-114)는question_front,question_back,answer만 명시적으로 설정합니다.현재는 blankcard가 추가 attrs를 사용하지 않아 문제가 없지만, 향후 blankcard에 attrs가 추가될 경우 데이터 손실이 발생할 수 있습니다. 의도적인 설계인지 확인해주세요.
추가로, vocacard와 동일한 에러 핸들링 개선 권장사항이 적용됩니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/contexts/note-editor-context.tsx(3 hunks)src/types/note/note-request.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/types/note/note-request.ts (3)
src/pages/note-editor/components/tiptap-node/voca-card-node/voca-card-node-extension.ts (3)
addAttributes(28-38)attributes(33-35)state(84-115)src/pages/note-editor/components/tiptap-node/voca-card-node/answer-node.tsx (1)
updateReversed(28-49)src/pages/note-editor/components/tiptap-node/voca-card-node/voca-card-node.tsx (1)
reversed(5-15)
src/contexts/note-editor-context.tsx (1)
src/types/note/note-request.ts (2)
NoteContent(45-51)WriteNoteRequest(53-58)
🔇 Additional comments (4)
src/types/note/note-request.ts (1)
27-38: LGTM! reversed 필드 추가가 적절합니다.vocacard의 뒤집기 상태를 추적하기 위한
reversed?: boolean필드 추가가 관련 코드(voca-card-node-extension.ts)의 구현과 일관성 있게 잘 되어 있습니다.src/contexts/note-editor-context.tsx (3)
7-16: 텍스트 추출 로직이 깔끔합니다.재귀적으로 텍스트를 추출하는 로직이 잘 구현되어 있습니다. 옵셔널 체이닝으로 undefined 케이스도 적절히 처리되고 있습니다.
181-184: 로드 시 변환 통합이 적절합니다.백엔드 형식을 TipTap 형식으로 변환한 후 initialContent로 설정하는 흐름이 올바르게 구현되어 있습니다. 디버그 로깅도 개발 시 유용합니다.
206-214: 저장 시 변환 통합이 올바릅니다.TipTap JSON을 백엔드 API 형식으로 변환한 후 전송하는 로직이 정확하게 구현되어 있습니다. 이를 통해 vocacard의
reversed속성을 포함한 모든 데이터가 올바르게 저장될 것입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/contexts/note-editor-context.tsx(3 hunks)src/utils/note-content-transformer.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/contexts/note-editor-context.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/utils/note-content-transformer.ts (1)
src/types/note/note-request.ts (1)
NoteContent(45-51)
🔇 Additional comments (2)
src/utils/note-content-transformer.ts (2)
4-13: 헬퍼 함수 로직이 올바릅니다.텍스트 노드에서 직접 텍스트를 추출하고, 중첩된 콘텐츠를 재귀적으로 처리하는 로직이 정확합니다. 빈 콘텐츠에 대한 조기 반환과 빈 문자열 폴백 처리도 적절합니다.
16-71: API에서 TipTap 형식으로의 변환 로직이 올바릅니다.양방향 변환 로직에서 vocacard와 blankcard 타입을 모두 적절하게 처리하고 있으며, 재귀적 변환도 정확합니다. 기본값 처리와 옵셔널 체이닝 사용이 적절합니다.
| export const transformContentForApi = (content: NoteContent): NoteContent => { | ||
| // vocacard 변환 | ||
| if (content.type === "vocacard") { | ||
| const questionNode = content.content?.find((c) => c.type === "question"); | ||
| const answerNode = content.content?.find((c) => c.type === "answer"); | ||
|
|
||
| const questionText = extractTextFromContent(questionNode?.content); | ||
| const answerText = extractTextFromContent(answerNode?.content); | ||
|
|
||
| return { | ||
| type: "vocacard", | ||
| attrs: { | ||
| ...content.attrs, | ||
| question_front: questionText, | ||
| answer: [answerText], | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // blankcard 변환 | ||
| if (content.type === "blankcard") { | ||
| const prefixNode = content.content?.find((c) => c.type === "prefix"); | ||
| const blankNode = content.content?.find((c) => c.type === "blank"); | ||
| const suffixNode = content.content?.find((c) => c.type === "suffix"); | ||
|
|
||
| const prefixText = extractTextFromContent(prefixNode?.content); | ||
| const blankText = extractTextFromContent(blankNode?.content); | ||
| const suffixText = extractTextFromContent(suffixNode?.content); | ||
|
|
||
| return { | ||
| type: "blankcard", | ||
| attrs: { | ||
| question_front: prefixText, | ||
| question_back: suffixText, | ||
| answer: [blankText], | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| if (content.content) { | ||
| return { | ||
| ...content, | ||
| content: content.content.map(transformContentForApi), | ||
| }; | ||
| } | ||
|
|
||
| return content; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vocacard와 blankcard 간 attrs 처리 불일치를 수정하세요.
vocacard 변환(86번 줄)에서는 ...content.attrs로 기존 속성을 보존하지만, blankcard 변환(105-109번 줄)에서는 기존 속성을 보존하지 않습니다. 이로 인해 blankcard의 추가 속성(예: reversed 또는 향후 추가될 필드)이 손실될 수 있습니다.
다음 diff를 적용하여 blankcard도 기존 attrs를 보존하도록 수정하세요:
return {
type: "blankcard",
attrs: {
+ ...content.attrs,
question_front: prefixText,
question_back: suffixText,
answer: [blankText],
},
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const transformContentForApi = (content: NoteContent): NoteContent => { | |
| // vocacard 변환 | |
| if (content.type === "vocacard") { | |
| const questionNode = content.content?.find((c) => c.type === "question"); | |
| const answerNode = content.content?.find((c) => c.type === "answer"); | |
| const questionText = extractTextFromContent(questionNode?.content); | |
| const answerText = extractTextFromContent(answerNode?.content); | |
| return { | |
| type: "vocacard", | |
| attrs: { | |
| ...content.attrs, | |
| question_front: questionText, | |
| answer: [answerText], | |
| }, | |
| }; | |
| } | |
| // blankcard 변환 | |
| if (content.type === "blankcard") { | |
| const prefixNode = content.content?.find((c) => c.type === "prefix"); | |
| const blankNode = content.content?.find((c) => c.type === "blank"); | |
| const suffixNode = content.content?.find((c) => c.type === "suffix"); | |
| const prefixText = extractTextFromContent(prefixNode?.content); | |
| const blankText = extractTextFromContent(blankNode?.content); | |
| const suffixText = extractTextFromContent(suffixNode?.content); | |
| return { | |
| type: "blankcard", | |
| attrs: { | |
| question_front: prefixText, | |
| question_back: suffixText, | |
| answer: [blankText], | |
| }, | |
| }; | |
| } | |
| if (content.content) { | |
| return { | |
| ...content, | |
| content: content.content.map(transformContentForApi), | |
| }; | |
| } | |
| return content; | |
| }; | |
| export const transformContentForApi = (content: NoteContent): NoteContent => { | |
| // vocacard 변환 | |
| if (content.type === "vocacard") { | |
| const questionNode = content.content?.find((c) => c.type === "question"); | |
| const answerNode = content.content?.find((c) => c.type === "answer"); | |
| const questionText = extractTextFromContent(questionNode?.content); | |
| const answerText = extractTextFromContent(answerNode?.content); | |
| return { | |
| type: "vocacard", | |
| attrs: { | |
| ...content.attrs, | |
| question_front: questionText, | |
| answer: [answerText], | |
| }, | |
| }; | |
| } | |
| // blankcard 변환 | |
| if (content.type === "blankcard") { | |
| const prefixNode = content.content?.find((c) => c.type === "prefix"); | |
| const blankNode = content.content?.find((c) => c.type === "blank"); | |
| const suffixNode = content.content?.find((c) => c.type === "suffix"); | |
| const prefixText = extractTextFromContent(prefixNode?.content); | |
| const blankText = extractTextFromContent(blankNode?.content); | |
| const suffixText = extractTextFromContent(suffixNode?.content); | |
| return { | |
| type: "blankcard", | |
| attrs: { | |
| ...content.attrs, | |
| question_front: prefixText, | |
| question_back: suffixText, | |
| answer: [blankText], | |
| }, | |
| }; | |
| } | |
| if (content.content) { | |
| return { | |
| ...content, | |
| content: content.content.map(transformContentForApi), | |
| }; | |
| } | |
| return content; | |
| }; |
🤖 Prompt for AI Agents
In src/utils/note-content-transformer.ts around lines 74 to 121, the blankcard
branch returns attrs without preserving existing content.attrs (unlike
vocacard), which can drop fields like reversed; modify the blankcard return to
spread existing attrs into attrs (e.g. attrs: { ...content.attrs,
question_front: prefixText, question_back: suffixText, answer: [blankText] }) so
all prior attributes are retained while still setting the transformed fields.
Summary
노트 저장 json 구조 수정
Tasks
To Reviewer
이제 단어카드와 빈칸카드 포함해서도 저장이랑 불러오기 잘 됨
Summary by CodeRabbit
릴리스 노트
개선 사항
새로운 기능
✏️ Tip: You can customize this high-level summary in your review settings.