Skip to content

[FEATURE] 텍스트 에디터 table 추가#155

Merged
sunhwaaRj merged 15 commits intodevelopfrom
feature/#154-editor-table
Mar 3, 2026
Merged

[FEATURE] 텍스트 에디터 table 추가#155
sunhwaaRj merged 15 commits intodevelopfrom
feature/#154-editor-table

Conversation

@sunhwaaRj
Copy link
Copy Markdown
Contributor

@sunhwaaRj sunhwaaRj commented Mar 3, 2026

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호


✅ Key Changes

  • 텍스트에디터 기능 중 테이블을 추가합니다.

📸 스크린샷 or 실행영상

image

🎸 기타 사항 or 추가 코멘트

Summary by CodeRabbit

  • 새로운 기능

    • 에디터 표 UI 대폭 개선 — 셀 버블 메뉴·핸들 메뉴, 행·열 컨텍스트 메뉴, 행/열 드래그 이동, 셀 병합·분할, 헤더 토글 및 표 삭제 기능 추가
    • 툴바에 3x3 표 삽입 버튼 추가
  • 스타일/UX

    • 반응형·모바일 친화적 표 스타일, 컬럼 리사이즈 핸들 및 애니메이션 추가
  • 업데이트

    • TipTap 관련 라이브러리 버전 업그레이드 및 표 확장 추가
  • 소소한 변경

    • 몇몇 드롭다운의 토글 아이콘이 질문 아이콘으로 교체되었습니다.

@sunhwaaRj sunhwaaRj self-assigned this Mar 3, 2026
@sunhwaaRj sunhwaaRj added the FEATURE 기능 구현 label Mar 3, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 3, 2026

Warning

Rate limit exceeded

@sunhwaaRj has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 26 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 23bf591 and c7681e9.

📒 Files selected for processing (2)
  • src/app/globals.css
  • src/components/recruit/editor/table/tableUtils.ts

Walkthrough

TipTap 에디터를 3.7.2 → 3.20.0으로 업그레이드하고 @tiptap/extension-table을 추가하여 테이블 노드, 스타일, 버블 메뉴·핸들 메뉴, 행/열 드래그·재배열 유틸리티 및 관련 컴포넌트를 구현합니다.

Changes

Cohort / File(s) Summary
의존성 업데이트
package.json
TipTap 관련 패키지 버전 전면 업데이트(@tiptap/core, @tiptap/react, @tiptap/starter-kit, 등) 및 @tiptap/extension-table 추가
글로벌 스타일
src/app/globals.css
테이블 인터랙션 스타일 추가: 드래그 하이라이트, 선택 셀, 열 리사이즈 핸들, 모바일 반응형, 테이블 레이아웃 고정/자동 전환 등
에디터 통합 / 툴바
src/components/recruit/editor/TextContent.tsx, src/components/recruit/editor/Toolbar.tsx, src/components/recruit/editor/editorExtensions.ts
Editor에 TableKit 등록 및 placeholder 보완, 에디터 컨텐츠 래핑 변경, 3×3 테이블 삽입 버튼 추가, TableBubbleMenu·TableHandles 렌더링 포함
테이블 버블 메뉴
src/components/recruit/editor/table/TableBubbleMenu.tsx
셀 선택 시 표시되는 버블 메뉴 추가: 헤더 토글, 병합/분할, 행·열 추가·삭제, 테이블 삭제 등 액션 구현
핸들 컨텍스트 메뉴
src/components/recruit/editor/table/TableHandleMenu.tsx
행/열 핸들 클릭 시 뜨는 플로팅 메뉴 추가: 행/열 추가·삭제, 테이블 삭제, 취소 옵션 제공 (타입·인터페이스 익스포트 포함)
행/열 핸들 및 드래그
src/components/recruit/editor/table/TableHandles.tsx
테이블 DOM 스캔으로 핸들 위치 계산, 드래그·롱프레스 및 터치 지원, 드래그 하이라이트와 메뉴 트리거, move 동작 호출 구현
테이블 유틸리티
src/components/recruit/editor/table/tableUtils.ts
getTableInfo, moveTableRow, moveTableCol 추가: 테이블 탐색 및 행/열 재배열 트랜잭션 적용
드롭다운 아이콘 교체
src/components/recruit/PositionDropdown.tsx, src/components/recruit/ProjectDropdown.tsx
드롭다운 토글 아이콘을 화살표 → 물음표 아이콘으로 교체

Sequence Diagram

sequenceDiagram
    actor User
    participant Editor as "TipTap Editor"
    participant Selection as "CellSelection"
    participant BubbleMenu as "TableBubbleMenu"
    participant TableHandles as "TableHandles"
    participant Utilities as "tableUtils"

    User->>Editor: 셀 선택 / 커서 이동
    Editor->>Selection: CellSelection 활성화

    alt 버블 메뉴 흐름
        Selection->>BubbleMenu: 활성화 조건 충족
        BubbleMenu->>User: 병합/분할/행·열 조작 UI 표시
        User->>BubbleMenu: 액션 클릭 (예: 행 추가)
        BubbleMenu->>Editor: editor.chain() 호출
        Editor->>Utilities: 테이블 수정 요청 (addRow/deleteCol 등)
        Utilities->>Editor: 트랜잭션 적용
        Editor->>User: 변경 반영
    end

    alt 핸들 드래그 흐름
        User->>TableHandles: 행/열 핸들 드래그 시작
        TableHandles->>TableHandles: 드래그 상태/하이라이트 업데이트
        User->>TableHandles: 드랍
        TableHandles->>Utilities: moveTableRow / moveTableCol 호출
        Utilities->>Editor: 재배열 트랜잭션 적용
        Editor->>User: 변경 반영
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • woneeeee
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning PositionDropdown.tsx와 ProjectDropdown.tsx의 아이콘 변경은 테이블 기능과 무관한 변경사항입니다. 테이블 기능과 관련 없는 드롭다운 아이콘 변경사항을 별도 PR로 분리하거나 그 의도를 설명하는 커밋 메시지를 추가하십시오.
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 텍스트 에디터에 테이블 기능을 추가하는 변경사항의 핵심을 명확하게 설명합니다.
Description check ✅ Passed PR 설명이 템플릿의 필수 섹션(PR 유형, 관련 이슈, Key Changes)을 모두 포함하고 있으며 스크린샷도 첨부되어 있습니다.
Linked Issues check ✅ Passed PR의 코드 변경사항이 #154의 목표인 테이블 기능 추가를 완벽하게 구현하고 있습니다. TipTap 테이블 확장을 통해 Table Node를 활용하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#154-editor-table

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/globals.css`:
- Around line 265-268: The background-color use of rgb(var(--color-primary-100)
/ 0.3) can be invalid if --color-primary-100 holds a hex value; update the
.table-drag-highlight rule so the variable supplies RGB components or use a CSS
function that accepts hex; e.g., either change the source of --color-primary-100
to an "R G B" value and keep background-color: rgb(var(--color-primary-100) /
0.3), or replace the usage with a color-mix expression (e.g. color-mix(in srgb,
var(--color-primary-100) 30%, transparent)) so the highlight alpha is applied
reliably; refer to the .table-drag-highlight selector and the
--color-primary-100 variable when making the change.
- Around line 307-312: Replace the deprecated CSS declaration by removing
"word-break: break-word" in the rules targeting ".ProseMirror table td" and
".ProseMirror table th" and add "overflow-wrap: anywhere" instead; update the
styles in the rule that currently contains "word-break: break-word" so the table
cell rules use the standard "overflow-wrap: anywhere" property to satisfy
linting and follow current CSS specs.

In `@src/components/recruit/editor/table/TableHandleMenu.tsx`:
- Around line 30-35: The button in TableHandleMenu.tsx currently only wires the
action to onMouseDown which prevents keyboard activation; replace or supplement
that with a proper onClick handler (and/or an onKeyDown that triggers on
Enter/Space) so the passed onClick prop is invoked for keyboard users as well;
update the button that currently uses onMouseDown to call onClick via onClick
(and add an onKeyDown check for 'Enter'/' ' if you keep onMouseDown) so keyboard
activation works correctly.

In `@src/components/recruit/editor/table/TableHandles.tsx`:
- Around line 44-54: computeHandles currently always uses the first <table> in
editor.view.dom which causes overlays/handles to mismatch when multiple tables
exist; change computeHandles to locate the DOM table that corresponds to the
current editor selection instead of dom.querySelectorAll('table')[0]. Use the
editor view/selection (editor.view.state.selection or
editor.view.domAtPos(selection.from)) to find the DOM node at the selection
position, walk up to the nearest HTMLTableElement (e.g., node.closest('table'))
and compute tableRect/handles from that table, then call setHandles; update any
other places that mirror this logic (the code around computeHandles, and
references at the points noted) to use the selected-table lookup rather than
always the first table.
- Around line 39-40: The longPressTimer stored in longPressTimer ref can survive
component unmount and keep firing; add a cleanup that clears it on unmount:
inside the TableHandles component add a useEffect with a cleanup function that
checks longPressTimer.current, calls clearTimeout(longPressTimer.current) and
sets longPressTimer.current = null (and optionally isDragging.current = false)
to ensure no delayed callbacks run after unmount; apply the same cleanup pattern
for any other long-press timers referenced around the longPressTimer usage
(e.g., the handlers tied to the mouseup/mouseleave logic).

In `@src/components/recruit/editor/table/tableUtils.ts`:
- Line 36: The forEach callback currently returns the result of rowNodes.push
implicitly (tableNode.forEach((row) => rowNodes.push(row))), triggering the lint
rule; change the callback to a block-bodied function to avoid implicit return
(e.g., tableNode.forEach((row) => { rowNodes.push(row); })) or replace with an
explicit loop, ensuring you reference tableNode.forEach and rowNodes.push so the
callback does not return the push value.

In `@src/components/recruit/editor/Toolbar.tsx`:
- Around line 189-196: The code currently queries all tables via
querySelectorAll('table colgroup col') inside the requestAnimationFrame
callback, which changes every table's col widths; instead scope the DOM lookup
to the newly inserted table by locating the table element for the current
selection/insert position (e.g., use
editor.view.domAtPos(editor.state.selection.from).node or the selection's DOM
node and call .closest('table') to get the specific table element), then run
querySelectorAll('colgroup col') on that table element and apply the width only
if that table exists; update the code in the requestAnimationFrame callback
(referencing editor.view.dom, editor.state.selection, and the cols variable)
accordingly and early-return if no scoped table/cols found.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1aeb9cd and 50ab9bf.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • public/icons/editor/table-selected.svg is excluded by !**/*.svg
  • public/icons/editor/table.svg is excluded by !**/*.svg
📒 Files selected for processing (9)
  • package.json
  • src/app/globals.css
  • src/components/recruit/editor/TextContent.tsx
  • src/components/recruit/editor/Toolbar.tsx
  • src/components/recruit/editor/editorExtensions.ts
  • src/components/recruit/editor/table/TableBubbleMenu.tsx
  • src/components/recruit/editor/table/TableHandleMenu.tsx
  • src/components/recruit/editor/table/TableHandles.tsx
  • src/components/recruit/editor/table/tableUtils.ts

Comment thread src/app/globals.css
Comment thread src/app/globals.css
Comment thread src/components/recruit/editor/table/TableHandleMenu.tsx
Comment thread src/components/recruit/editor/table/TableHandles.tsx
Comment thread src/components/recruit/editor/table/TableHandles.tsx
Comment thread src/components/recruit/editor/table/tableUtils.ts Outdated
Comment thread src/components/recruit/editor/Toolbar.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a 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

♻️ Duplicate comments (1)
src/app/globals.css (1)

265-268: ⚠️ Potential issue | 🟠 Major

.table-drag-highlight 배경색 선언이 현재 토큰 구조에서 무효가 될 수 있습니다.

Line 267의 rgb(var(--color-primary-100) / 0.3)--color-primary-100이 hex 값으로 해석되면 유효하지 않은 색상식이 되어 하이라이트가 적용되지 않을 수 있습니다.

제안 코드
 .table-drag-highlight {
   opacity: 0.4;
-  background-color: rgb(var(--color-primary-100) / 0.3);
+  background-color: color-mix(in srgb, var(--color-primary-100) 30%, transparent);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/globals.css` around lines 265 - 268, The .table-drag-highlight rule
uses background-color: rgb(var(--color-primary-100) / 0.3) which fails if
--color-primary-100 is a hex string; fix by referencing an RGB-formatted token
or using rgba() with a token that contains comma-separated RGB components:
update the CSS for .table-drag-highlight to use a var that stores "R, G, B" and
call background-color: rgba(var(--color-primary-100-rgb), 0.3) (or create a new
--color-primary-100-rgb token from the hex token and use that) so the alpha is
applied reliably regardless of original token format.
🧹 Nitpick comments (1)
src/components/recruit/editor/Toolbar.tsx (1)

176-207: 표 내부에서는 버튼을 disabled로 노출해 의도를 명확히 해주세요.

현재는 Line 180에서 no-op 처리만 하고 있어 클릭 가능한 UI처럼 보입니다. disabled/aria-disabled를 같이 주면 사용자 의도와 동작이 일치합니다.

제안 코드
       <button
         type="button"
+        disabled={editor.isActive('table')}
+        aria-disabled={editor.isActive('table')}
         onClick={() => {
           // 표 안에 있으면 삽입 막기
           if (editor.isActive('table')) return;
@@
-        className="cursor-pointer transition-opacity hover:opacity-80"
+        className="cursor-pointer transition-opacity hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-40"
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/recruit/editor/Toolbar.tsx` around lines 176 - 207, The button
currently only no-ops when inside a table (checked via
editor.isActive('table')), but remains visually interactive; update the Toolbar
button render so it receives a disabled={editor.isActive('table')} prop and
aria-disabled={editor.isActive('table')} (or a single const isTable =
editor.isActive('table') reused) and adjust the className to reflect a disabled
state when isTable is true; keep the existing onClick logic but still guard at
the start with if (isTable) return to avoid running the insertion flow. This
uses the existing editor.isActive check and the same button element in
Toolbar.tsx to make the UI state match behavior.
🤖 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/globals.css`:
- Around line 302-303: 빈 CSS 규칙 ".ProseMirror table colgroup col { }"가
Stylelint의 block-no-empty 오류를 발생시키므로 해당 빈 블록을 삭제하거나 실제로 필요한 스타일이 있다면 적절한 속성(예:
width, background 등)을 추가하여 비어있지 않도록 변경하세요; 대상 식별자는 ".ProseMirror table colgroup
col"이며 파일의 해당 선택자를 찾아 블록을 제거하거나 내용을 채우면 됩니다.

---

Duplicate comments:
In `@src/app/globals.css`:
- Around line 265-268: The .table-drag-highlight rule uses background-color:
rgb(var(--color-primary-100) / 0.3) which fails if --color-primary-100 is a hex
string; fix by referencing an RGB-formatted token or using rgba() with a token
that contains comma-separated RGB components: update the CSS for
.table-drag-highlight to use a var that stores "R, G, B" and call
background-color: rgba(var(--color-primary-100-rgb), 0.3) (or create a new
--color-primary-100-rgb token from the hex token and use that) so the alpha is
applied reliably regardless of original token format.

---

Nitpick comments:
In `@src/components/recruit/editor/Toolbar.tsx`:
- Around line 176-207: The button currently only no-ops when inside a table
(checked via editor.isActive('table')), but remains visually interactive; update
the Toolbar button render so it receives a disabled={editor.isActive('table')}
prop and aria-disabled={editor.isActive('table')} (or a single const isTable =
editor.isActive('table') reused) and adjust the className to reflect a disabled
state when isTable is true; keep the existing onClick logic but still guard at
the start with if (isTable) return to avoid running the insertion flow. This
uses the existing editor.isActive check and the same button element in
Toolbar.tsx to make the UI state match behavior.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50ab9bf and 762f2c6.

📒 Files selected for processing (5)
  • src/app/globals.css
  • src/components/recruit/editor/TextContent.tsx
  • src/components/recruit/editor/Toolbar.tsx
  • src/components/recruit/editor/editorExtensions.ts
  • src/components/recruit/editor/table/TableHandles.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/recruit/editor/TextContent.tsx

Comment thread src/app/globals.css Outdated
@sunhwaaRj sunhwaaRj merged commit 0902947 into develop Mar 3, 2026
2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 4, 2026
9 tasks
@sunhwaaRj sunhwaaRj deleted the feature/#154-editor-table branch March 8, 2026 16:20
@coderabbitai coderabbitai Bot mentioned this pull request Mar 29, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FEATURE 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 에디터 table 추가

1 participant