[Feat] WTH-136 : 마크다운 기반 게시글 작성 기능 구현#5
Conversation
…o WTH-136-마크다운-기반-게시글-작성-기능-구현
- useRef + scrollIntoView로 키보드 탐색 시 선택 항목 자동 스크롤 - data-index 속성으로 선택된 버튼 요소 탐색 - 하드코딩 gray-* 색상을 디자인 토큰으로 교체
- showSlashMenuRef로 handleKeyDown stale closure 방지 - onSelectionUpdate로 커서 이동 시 슬래시 메뉴 자동 닫기 - Backspace 시 listItem 처리 제거 (Tiptap 자체 처리) - 툴바 버튼 디자인 토큰 적용
📝 WalkthroughWalkthroughTipTap 기반의 클라이언트 사이드 에디터 도입: 의존성 추가, 에디터 컴포넌트 및 슬래시/버블 메뉴 구현, Zustand 스토어와 타입 정의, ProseMirror 관련 전역 스타일 추가(파일 출력/서명 미변경). Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant BoardPage
participant Editor
participant TipTap
participant usePostStore
participant SlashMenu
User->>BoardPage: 페이지 접속
BoardPage->>Editor: dynamic 로드 (클라이언트)
Editor->>TipTap: 에디터 초기화 (확장 설정)
TipTap-->>Editor: 에디터 인스턴스 반환
Editor->>usePostStore: setContent 초기 동기화
User->>Editor: 텍스트 입력
Editor->>TipTap: 콘텐츠 업데이트
TipTap-->>Editor: onUpdate 이벤트
Editor->>usePostStore: setContent(content)
User->>Editor: '/' 입력
Editor->>SlashMenu: FloatingMenu 표시
User->>SlashMenu: 항목 선택
SlashMenu->>TipTap: 선택된 커맨드 실행 (삭제 후 명령)
TipTap-->>Editor: 콘텐츠 변경
Editor->>usePostStore: setContent(content)
SlashMenu->>SlashMenu: 메뉴 닫기
sequenceDiagram
participant User
participant Editor
participant BubbleMenu
participant TipTap
participant usePostStore
User->>Editor: 텍스트 선택 또는 포인터 이동
Editor->>BubbleMenu: 상태 업데이트 (활성/비활성)
BubbleMenu-->>User: 포매팅 툴 표시
User->>BubbleMenu: 'Bold' 클릭
BubbleMenu->>TipTap: toggleBold() 실행
TipTap-->>Editor: 스타일 변경
Editor->>usePostStore: setContent(content)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
PR 검증 결과❌ TypeScript: 실패 |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/`(private)/(main)/board/page.tsx:
- Around line 8-10: The main element in page.tsx is using a hardcoded utility
class p-8; replace it with the spacing design token class (e.g., p-200 or the
appropriate p-100~500 value) to comply with the spacing tokens, e.g., change the
<main className="mx-auto max-w-3xl p-8"> usage to use a token like p-200 (or
add/extend token styles if none match the required spacing), keeping other
classes (mx-auto, max-w-3xl) and leaving the <Editor /> component unchanged.
In `@src/app/globals.css`:
- Around line 2-4: Stylelint is flagging unknown at-rules (`@custom-variant`,
`@plugin`, `@import` 'tw-animate-css') in globals.css; fix by either adding a
per-file disable comment for at-rule-no-unknown or updating the stylelint config
to allow these at-rules (e.g., register customAtRules or plugins) so
`@custom-variant`, `@plugin` and the tw-animate-css import are accepted; locate the
occurrences of "@custom-variant", "@plugin" and the "@import 'tw-animate-css'"
line and apply the chosen fix consistently.
- Around line 494-542: Several ProseMirror rules (.ProseMirror blockquote,
.ProseMirror code:not(pre code), .ProseMirror pre, .ProseMirror pre code,
.ProseMirror ul[data-type='taskList'] li p) use hardcoded spacing and radius
values (rem/px); replace those with existing design token CSS variables (e.g.
spacing tokens like --space-2/--space-4 or radius tokens like
--radius-sm/--radius-md) or add new tokens in the globals.css token section, and
then reference the tokens (or `@utility` classes you define) instead of literal
values for padding, margin and border-radius to comply with tokenization rules.
In `@src/components/board/Editor/index.tsx`:
- Around line 156-248: The bubble menu button classes are using inline template
literals and raw spacing/typography utilities; refactor by extracting a
cva-based variant (e.g., BubbleMenuButton = cva(...)) for the common button
styles and tokenized variants (active/inactive) and replace each button's
className with cn(BubbleMenuButton({ state: editor.isActive(...) ? 'active' :
'default' }), additionalTokenIfNeeded). Update all buttons inside the BubbleMenu
(the Bold/Italic/Code buttons and the H1/H2/H3 heading toggles) to use cn from
'@/lib/cn' and the new BubbleMenuButton cva variants, and replace raw tokens
like p-1, px-2, gap-0.5, text-sm with the design token classes (p-100~500,
gap-100~400, typo-button*/typo-h1~h3 etc.) so styles are unified via cn() + cva.
- Around line 268-272: The EditorContent wrapper and its parent div use raw
Tailwind utilities (pl-14, prose-gray, text-gray-400, text-(--h1-size), etc.)
that violate design-token rules; update the parent div's padding (replace pl-14)
with the tokenized spacing utility (e.g., p-100/p-l-100 as per your token set)
and replace all typography and color utilities in the EditorContent className
with the design token classes (use typo-h1/typo-h2/typo-h3 for headings,
appropriate typo-body/typo-* for paragraphs, and
text-text-strong/text-text-normal/text-text-alternative for colors) and ensure
any placeholder/pseudo rules keep the tokenized text classes instead of
text-gray-400; locate the className on the EditorContent component and the
surrounding div to make these substitutions.
In `@src/components/board/Editor/SlashMenu.tsx`:
- Around line 106-141: In SlashMenu (rendering GROUPS.map and each item button)
replace the template-literal className concatenation on the item <button> and
the inner <span> and <p> tags with the cn() helper imported from '@/lib/cn', and
swap raw spacing/typography classes (px-3, py-2, gap-3, text-sm, text-xs, h-8,
w-8, etc.) for your design token classes (spacing p-100~500, gap-100~400 and
typography typo-* such as typo-body1/2 or typo-caption1/2) so e.g. the
conditional selected vs hover classes remain wrapped by cn(...) while using
tokens for padding, gap and text sizes; ensure handleSelect, selectedIndex and
runningIndex logic is unchanged and keep the data-index and onMouseDown behavior
intact.
In `@src/constants/editor.ts`:
- Around line 4-67: MenuItem currently types icon as string and
STYLE_ITEMS/INSERT_ITEMS use string glyphs; change MenuItem.icon to LucideIcon |
React.ReactNode, replace string icons in STYLE_ITEMS and INSERT_ITEMS with
corresponding Lucide React components (e.g., Type, TypeH1/Heading variants,
List, ListOrdered, CheckSquare, Quote, Code, Minus for separator) and update
SlashMenu.tsx render logic to render the icon component instead of printing a
string; ensure imports from 'lucide-react' are added where MenuItem,
STYLE_ITEMS, INSERT_ITEMS, and SlashMenu are defined.
| return ( | ||
| <main className="mx-auto max-w-3xl p-8"> | ||
| <Editor /> |
There was a problem hiding this comment.
메인 패딩은 토큰 간격 클래스로 맞춰주세요.
p-8 대신 토큰 간격(p-100~500)으로 치환하거나 필요한 경우 토큰을 확장해서 사용해주세요.
As per coding guidelines, Use spacing design token classes: p-100~500, gap-100~400.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/`(private)/(main)/board/page.tsx around lines 8 - 10, The main
element in page.tsx is using a hardcoded utility class p-8; replace it with the
spacing design token class (e.g., p-200 or the appropriate p-100~500 value) to
comply with the spacing tokens, e.g., change the <main className="mx-auto
max-w-3xl p-8"> usage to use a token like p-200 (or add/extend token styles if
none match the required spacing), keeping other classes (mx-auto, max-w-3xl) and
leaving the <Editor /> component unchanged.
| @import 'tw-animate-css'; | ||
| @custom-variant dark (&:is(.dark *)); | ||
| @plugin "@tailwindcss/typography"; |
There was a problem hiding this comment.
Stylelint 에러(@custom-variant/@plugin) 처리 필요.
현재 stylelint가 unknown at-rule로 에러를 내고 있어 린트 실패 가능성이 큽니다. 파일 단위 예외 처리 또는 stylelint 설정에서 해당 at-rule을 허용해주세요.
🔧 예시 수정
`@import` 'tailwindcss';
`@import` 'tw-animate-css';
+/* stylelint-disable-next-line scss/at-rule-no-unknown */
`@custom-variant` dark (&:is(.dark *));
+/* stylelint-disable-next-line scss/at-rule-no-unknown */
`@plugin` "@tailwindcss/typography";📝 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.
| @import 'tw-animate-css'; | |
| @custom-variant dark (&:is(.dark *)); | |
| @plugin "@tailwindcss/typography"; | |
| `@import` 'tw-animate-css'; | |
| /* stylelint-disable-next-line scss/at-rule-no-unknown */ | |
| `@custom-variant` dark (&:is(.dark *)); | |
| /* stylelint-disable-next-line scss/at-rule-no-unknown */ | |
| `@plugin` "@tailwindcss/typography"; |
🧰 Tools
🪛 Stylelint (17.3.0)
[error] 3-3: Unexpected unknown at-rule "@custom-variant" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
[error] 4-4: Unexpected unknown at-rule "@plugin" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/globals.css` around lines 2 - 4, Stylelint is flagging unknown
at-rules (`@custom-variant`, `@plugin`, `@import` 'tw-animate-css') in globals.css;
fix by either adding a per-file disable comment for at-rule-no-unknown or
updating the stylelint config to allow these at-rules (e.g., register
customAtRules or plugins) so `@custom-variant`, `@plugin` and the tw-animate-css
import are accepted; locate the occurrences of "@custom-variant", "@plugin" and
the "@import 'tw-animate-css'" line and apply the chosen fix consistently.
| <div className="relative pl-14"> | ||
| <EditorContent | ||
| editor={editor} | ||
| className="prose prose-gray max-w-none [&_.ProseMirror]:min-h-[400px] [&_.ProseMirror]:focus:outline-none [&_.ProseMirror_h1]:leading-(--h1-line-height) [&_.ProseMirror_h1]:text-(--h1-size) [&_.ProseMirror_h2]:leading-(--h2-line-height) [&_.ProseMirror_h2]:text-(--h2-size) [&_.ProseMirror_h3]:leading-(--h3-line-height) [&_.ProseMirror_h3]:text-(--h3-size) [&_.ProseMirror_p.is-empty::before]:pointer-events-none [&_.ProseMirror_p.is-empty::before]:float-left [&_.ProseMirror_p.is-empty::before]:h-0 [&_.ProseMirror_p.is-empty::before]:text-gray-400 [&_.ProseMirror_p.is-empty::before]:content-[attr(data-placeholder)] [&_.ProseMirror_ul[data-type=taskList]]:my-0 [&_.ProseMirror_ul[data-type=taskList]]:list-none [&_.ProseMirror_ul[data-type=taskList]_li]:my-0 [&_.ProseMirror_ul[data-type=taskList]_li]:flex [&_.ProseMirror_ul[data-type=taskList]_li]:items-center [&_.ProseMirror_ul[data-type=taskList]_li]:gap-3 [&_.ProseMirror>*]:my-3" | ||
| /> |
There was a problem hiding this comment.
EditorContent의 타이포/컬러 토큰 적용이 필요합니다.
prose-gray, text-gray-400, text-(--h1-size) 등 비토큰 스타일이 섞여 있어 디자인 토큰 규칙을 위반합니다. typo-h1~h3 유틸리티와 text-text-* 토큰으로 치환하고, pl-14도 토큰화된 간격 클래스(또는 토큰 기반 유틸리티)로 맞춰주세요.
🔧 예시 수정
- className="prose prose-gray max-w-none [&_.ProseMirror]:min-h-[400px] [&_.ProseMirror]:focus:outline-none [&_.ProseMirror_h1]:leading-(--h1-line-height) [&_.ProseMirror_h1]:text-(--h1-size) [&_.ProseMirror_h2]:leading-(--h2-line-height) [&_.ProseMirror_h2]:text-(--h2-size) [&_.ProseMirror_h3]:leading-(--h3-line-height) [&_.ProseMirror_h3]:text-(--h3-size) [&_.ProseMirror_p.is-empty::before]:pointer-events-none [&_.ProseMirror_p.is-empty::before]:float-left [&_.ProseMirror_p.is-empty::before]:h-0 [&_.ProseMirror_p.is-empty::before]:text-gray-400 [&_.ProseMirror_p.is-empty::before]:content-[attr(data-placeholder)] ..."
+ className="prose max-w-none [&_.ProseMirror]:min-h-[400px] [&_.ProseMirror]:focus:outline-none [&_.ProseMirror_h1]:typo-h1 [&_.ProseMirror_h2]:typo-h2 [&_.ProseMirror_h3]:typo-h3 [&_.ProseMirror_p.is-empty::before]:pointer-events-none [&_.ProseMirror_p.is-empty::before]:float-left [&_.ProseMirror_p.is-empty::before]:h-0 [&_.ProseMirror_p.is-empty::before]:text-text-disabled [&_.ProseMirror_p.is-empty::before]:content-[attr(data-placeholder)] ..."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/board/Editor/index.tsx` around lines 268 - 272, The
EditorContent wrapper and its parent div use raw Tailwind utilities (pl-14,
prose-gray, text-gray-400, text-(--h1-size), etc.) that violate design-token
rules; update the parent div's padding (replace pl-14) with the tokenized
spacing utility (e.g., p-100/p-l-100 as per your token set) and replace all
typography and color utilities in the EditorContent className with the design
token classes (use typo-h1/typo-h2/typo-h3 for headings, appropriate
typo-body/typo-* for paragraphs, and
text-text-strong/text-text-normal/text-text-alternative for colors) and ensure
any placeholder/pseudo rules keep the tokenized text classes instead of
text-gray-400; locate the className on the EditorContent component and the
surrounding div to make these substitutions.
| return ( | ||
| <div className="border-line bg-container-neutral w-64 overflow-hidden rounded-lg border shadow-xl"> | ||
| <div ref={scrollContainerRef} className="max-h-80 overflow-x-hidden overflow-y-auto"> | ||
| {GROUPS.map((group, groupIdx) => ( | ||
| <div key={group.title}> | ||
| <div className={`px-3 pt-2 pb-1 ${groupIdx !== 0 ? 'border-line border-t' : ''}`}> | ||
| <p className="text-text-disabled text-xs font-semibold tracking-wider uppercase"> | ||
| {group.title} | ||
| </p> | ||
| </div> | ||
|
|
||
| {group.items.map((item) => { | ||
| const currentIndex = runningIndex++; | ||
| const isSelected = currentIndex === selectedIndex; | ||
|
|
||
| return ( | ||
| <button | ||
| key={item.label} | ||
| type="button" | ||
| data-index={currentIndex} | ||
| onMouseDown={(e) => { | ||
| e.preventDefault(); | ||
| handleSelect(item); | ||
| }} | ||
| className={`flex w-full items-center gap-3 px-3 py-2 text-left transition-colors ${ | ||
| isSelected | ||
| ? 'bg-container-neutral-interaction' | ||
| : 'hover:bg-container-neutral-alternative' | ||
| }`} | ||
| > | ||
| <span className="border-line bg-container-neutral-alternative text-text-alternative flex h-8 w-8 shrink-0 items-center justify-center rounded border text-sm font-medium"> | ||
| {item.icon} | ||
| </span> | ||
| <div> | ||
| <p className="text-text-strong text-sm font-medium">{item.label}</p> | ||
| <p className="text-text-disabled text-xs">{item.description}</p> |
There was a problem hiding this comment.
className 결합 및 간격/타이포 토큰 적용이 필요합니다.
템플릿 리터럴로 클래스 결합 중이며, px-3, py-2, gap-3, text-sm/xs 등 비토큰 간격/타이포가 섞여 있습니다. cn()으로 결합을 통일하고, 간격·타이포는 p-100~500, gap-100~400, typo-* 토큰 클래스로 치환해주세요.
🔧 예시 수정
+import { cn } from '@/lib/cn';
...
-<div className={`px-3 pt-2 pb-1 ${groupIdx !== 0 ? 'border-line border-t' : ''}`}>
+<div className={cn('px-3 pt-2 pb-1', groupIdx !== 0 && 'border-line border-t')}>
...
- className={`flex w-full items-center gap-3 px-3 py-2 text-left transition-colors ${
- isSelected ? 'bg-container-neutral-interaction' : 'hover:bg-container-neutral-alternative'
- }`}
+ className={cn(
+ 'flex w-full items-center gap-3 px-3 py-2 text-left transition-colors',
+ isSelected ? 'bg-container-neutral-interaction' : 'hover:bg-container-neutral-alternative',
+ )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/board/Editor/SlashMenu.tsx` around lines 106 - 141, In
SlashMenu (rendering GROUPS.map and each item button) replace the
template-literal className concatenation on the item <button> and the inner
<span> and <p> tags with the cn() helper imported from '@/lib/cn', and swap raw
spacing/typography classes (px-3, py-2, gap-3, text-sm, text-xs, h-8, w-8, etc.)
for your design token classes (spacing p-100~500, gap-100~400 and typography
typo-* such as typo-body1/2 or typo-caption1/2) so e.g. the conditional selected
vs hover classes remain wrapped by cn(...) while using tokens for padding, gap
and text sizes; ensure handleSelect, selectedIndex and runningIndex logic is
unchanged and keep the data-index and onMouseDown behavior intact.
| export const STYLE_ITEMS: MenuItem[] = [ | ||
| { | ||
| label: 'Text', | ||
| description: '일반 텍스트', | ||
| icon: 'T', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().clearNodes().setParagraph().run(), | ||
| }, | ||
| { | ||
| label: 'Heading 1', | ||
| description: '큰 제목', | ||
| icon: 'H1', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 1 }).run(), | ||
| }, | ||
| { | ||
| label: 'Heading 2', | ||
| description: '중간 제목', | ||
| icon: 'H2', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 2 }).run(), | ||
| }, | ||
| { | ||
| label: 'Heading 3', | ||
| description: '작은 제목', | ||
| icon: 'H3', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleHeading({ level: 3 }).run(), | ||
| }, | ||
| { | ||
| label: 'Bullet List', | ||
| description: '순서 없는 목록', | ||
| icon: '•', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleBulletList().run(), | ||
| }, | ||
| { | ||
| label: 'Numbered List', | ||
| description: '순서 있는 목록', | ||
| icon: '1.', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleOrderedList().run(), | ||
| }, | ||
| { | ||
| label: 'To-do List', | ||
| description: '체크리스트', | ||
| icon: '☑', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleTaskList().run(), | ||
| }, | ||
| { | ||
| label: 'Blockquote', | ||
| description: '인용', | ||
| icon: '"', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleBlockquote().run(), | ||
| }, | ||
| { | ||
| label: 'Code Block', | ||
| description: '코드 블록', | ||
| icon: '</>', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().toggleCodeBlock().run(), | ||
| }, | ||
| ]; | ||
|
|
||
| export const INSERT_ITEMS: MenuItem[] = [ | ||
| { | ||
| label: 'Separator', | ||
| description: '구분선', | ||
| icon: '—', | ||
| command: (editor: TiptapEditor) => editor.chain().focus().setHorizontalRule().run(), | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# Find MenuItem interface definition
rg -n "interface MenuItem|type MenuItem" srcRepository: Team-Weeth/weeth-client
Length of output: 116
🏁 Script executed:
# Check how STYLE_ITEMS and INSERT_ITEMS are used across the codebase
rg -n "STYLE_ITEMS|INSERT_ITEMS" src --type ts --type tsx -A 2Repository: Team-Weeth/weeth-client
Length of output: 94
🏁 Script executed:
# Search for icon usage in menu-related components
rg -n "\.icon" src --type ts --type tsx -B 1 -A 1Repository: Team-Weeth/weeth-client
Length of output: 94
🏁 Script executed:
# Read MenuItem interface definition
cat -n src/types/editor.tsRepository: Team-Weeth/weeth-client
Length of output: 307
🏁 Script executed:
# Check STYLE_ITEMS and INSERT_ITEMS usage without tsx flag
rg -n "STYLE_ITEMS|INSERT_ITEMS" src -A 2Repository: Team-Weeth/weeth-client
Length of output: 897
🏁 Script executed:
# Check icon property usage
rg -n "\.icon" src -B 1 -A 1Repository: Team-Weeth/weeth-client
Length of output: 451
🏁 Script executed:
# Check if Lucide React is already installed in the project
rg -n "lucide-react" src -i | head -20Repository: Team-Weeth/weeth-client
Length of output: 231
🏁 Script executed:
# Check package.json for lucide-react dependency
cat package.json | grep -A 5 -B 5 "lucide"Repository: Team-Weeth/weeth-client
Length of output: 394
MenuItem 아이콘을 Lucide React 컴포넌트로 변환해주세요.
현재 icon이 문자열로 정의되어 있어 코딩 가이드라인(Lucide React 사용)을 위반합니다. 다음 변경이 필요합니다:
MenuItem인터페이스의icon타입을string에서LucideIcon또는React.ReactNode로 변경STYLE_ITEMS와INSERT_ITEMS의 아이콘을 Lucide 컴포넌트로 매핑 (예:T→Type,H1→Heading1,•→List)SlashMenu.tsx의 렌더링 로직을 아이콘 컴포넌트 렌더링으로 수정
Lucide React는 이미 프로젝트에 설치되어 있으므로 즉시 적용 가능합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/constants/editor.ts` around lines 4 - 67, MenuItem currently types icon
as string and STYLE_ITEMS/INSERT_ITEMS use string glyphs; change MenuItem.icon
to LucideIcon | React.ReactNode, replace string icons in STYLE_ITEMS and
INSERT_ITEMS with corresponding Lucide React components (e.g., Type,
TypeH1/Heading variants, List, ListOrdered, CheckSquare, Quote, Code, Minus for
separator) and update SlashMenu.tsx render logic to render the icon component
instead of printing a string; ensure imports from 'lucide-react' are added where
MenuItem, STYLE_ITEMS, INSERT_ITEMS, and SlashMenu are defined.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/app/`(private)/(main)/board/page.tsx:
- Line 9: Replace the out-of-range spacing token "p-700" on the <main> element
(className="mx-auto max-w-3xl p-700") with an allowed spacing design token
(p-100 through p-500) or update the design token config if you intentionally
need a larger value; change the className to use an approved token (e.g., p-500)
or add a documented token expansion in the spacing tokens configuration and
reference that new token instead of "p-700".
In `@src/app/globals.css`:
- Around line 522-526: Replace the hardcoded border thickness in the
.ProseMirror blockquote rule (the border-left: 3px declaration) with a design
token CSS variable (e.g., var(--border-width-300) or a new token like
--blockquote-border) and add that token definition and an associated utility
class in src/app/globals.css; locate the .ProseMirror blockquote selector to
update border-left to use the token and then declare the new --blockquote-border
(or map it to an existing --border-width token) near the other design tokens and
add an `@utility` class for reuse.
- Around line 2-4: Stylelint flags unknown at-rules (`@custom-variant`, `@plugin`)
in globals.css; either add a file-level exception or register the custom
at-rules in stylelint config. Fix by updating stylelint config (rule
at-rule-no-unknown -> set ignoreAtRules to include "custom-variant" and "plugin"
and any other Tailwind-specific at-rules) or add a top-of-file stylelint disable
comment to globals.css to ignore these at-rules; ensure you reference the exact
tokens "@custom-variant" and "@plugin" when adding them to the ignore list so
the linter stops failing.
In `@src/components/board/Editor/index.tsx`:
- Around line 256-260: The EditorContent wrapper currently uses non-token
spacing classes (e.g., pl-800, gap-3, my-3) in the className; replace those with
approved spacing design tokens (p-100..p-500 for padding, gap-100..gap-400 for
gaps, and appropriate vertical margin tokens such as my-100..my-500) and update
the className string on the EditorContent element so all spacing classes conform
to the project's token ranges (refer to the EditorContent component and the
surrounding div that currently uses pl-800 to locate the change).
In `@src/components/board/Editor/SlashMenu.tsx`:
- Around line 106-142: The className usage mixes template literals and raw
utility classes and needs to use cn() and design tokens: import { cn } from
'@/lib/cn', then replace manual concatenation in GROUPS rendering (the outer
div, group header div, each item's button, the icon span and the two <p> labels
inside the item) to use cn() for merging conditional classes (e.g., the
isSelected background toggle) and swap spacing/typography utilities (px-3,
gap-3, py-2, text-xs, text-sm, etc.) with the project's spacing and typography
tokens (p-100~500, gap-100~400 and appropriate typo-* classes) while preserving
existing conditional logic that uses runningIndex, selectedIndex and the
onMouseDown -> handleSelect flow so behavior of handleSelect, Icon rendering and
data-index remain unchanged.
JIN921
left a comment
There was a problem hiding this comment.
useEffect의 클린업이나 useMemo를 사용한 리렌더 방지가 잘 되어 있어, zustand 관련해 말씀 드린 부분 빼면 고칠 부분은 없어 보입니다 수고하셨어요!!
슬래쉬로 마크다운 작성이 이런식으로 이루어 지는군요 신기해요 👀👀
(근데 슬래쉬 메뉴에 스크롤 없으면 더 예쁠 거 같습니다 ㅎ.ㅎ,, )
| status: 'DRAFT' as const, | ||
| }; | ||
|
|
||
| export const usePostStore = create<PostState>((set, get) => ({ |
There was a problem hiding this comment.
요거 상태 추적이랑 타입 정의를 위해서 devtools랑 combine 사용하면 더 좋을 거 같아요!
| // Actions | ||
| setBoard: (board: string) => void; | ||
| setTitle: (title: string) => void; | ||
| setCohort: (cohort: number) => void; |
There was a problem hiding this comment.
보다가 궁금해서 그러는데 이건 뭘 설정해 주는 걸까요..?? cohort..??? 집단 무리..??
✅ PR 유형
어떤 변경 사항이 있었나요?
📌 관련 이슈번호
✅ Key Changes
Tiptap 기반 에디터 구현: 제목(H1~H3), 텍스트 스타일(굵게/기울임), 인라인 코드 및 코드 블록, 인용문, 목록(순서/비순서), 체크리스트, 구분선 등 마크다운 형식의 주요 블록 타입 지원
슬래시 메뉴 구현: / 입력 시 스타일(Style) 및 삽입(Insert) 그룹 커맨드 노출, 키보드 탐색 및 자동 스크롤 지원
버블 메뉴 구현 (임시): 텍스트 선택 시 인라인 포맷 버튼 표시
게시글 전역 상태 스토어 구성:
content필드 사전 연결을 위해 기존 API 기반으로 임시 구현ProseMirror 전역 스타일 적용: 인용문, 코드 블록, 체크리스트 등 주요 블록에 디자인 토큰 기반 스타일 적용 (요것도 임시...입니다 UI 확정 시 반영 예정입니당)
📸 스크린샷 or 실행영상
에디터
슬래시 메뉴 (슬래시 입력 시 표시)
버블 메뉴 (드래그 시 표시)
🎸 기타 사항 or 추가 코멘트
Notion 에디터를 기반으로 구현했습니다!
tiptap라이브러리를 사용하긴 했지만 유료 기능을 제외하고 구현하다 보니 코드가 다소 길어졌네요...... ㅜ.^index.tsx(에디터),SlashMenu.tsx(슬래시 메뉴) 두 코드의 기능적인 부분만 확인해 주셔도 무방합니다. . .BubbleMenu(index.tsx의 156~249라인)의 경우 아직 임시로만 구현해 둔 기능이라... 예진님과 간단한 POC 진행 후 확정되면 컴포넌트 분리하여 정리할 예정입니다!게시글 전역 상태 스토어(
usePostStore.ts)는content필드만 미리 연결하기 위해 기존 API를 기반으로 구현했습니다! 추후 변동 있을 경우 반영할 예정입니당Summary by CodeRabbit
릴리스 노트
신기능
스타일
문서/구성