Skip to content

요약에서 strong 태그 노출 문제 해결#451

Open
Bangdayeon wants to merge 1 commit intomainfrom
design/#450-summary-strong-tag
Open

요약에서 strong 태그 노출 문제 해결#451
Bangdayeon wants to merge 1 commit intomainfrom
design/#450-summary-strong-tag

Conversation

@Bangdayeon
Copy link
Copy Markdown
Member

@Bangdayeon Bangdayeon commented Mar 26, 2026

관련 이슈

PR 설명

  • 요약에서 리스트 내의 <strong class="font-semibold">가 그대로 노출되는 문제 해결
  • 리스트 처리 과정에서 이미 변환된 <strong>을 다시 escape해서 깨진 문제였고, escape를 텍스트에만 적용하도록 수정해서 해결

@Bangdayeon Bangdayeon self-assigned this Mar 26, 2026
@Bangdayeon Bangdayeon linked an issue Mar 26, 2026 that may be closed by this pull request
@Bangdayeon Bangdayeon requested a review from Seong-Myeong March 26, 2026 02:25
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Walkthrough

src/hooks/util/parseMarkdown.tsxparseMarkdown 구현이 정규표현식 기반 전역 치환 파이프라인에서 라인별 블록 렌더러로 리팩토링되었습니다. 펜스된 코드 블록을 플레이스홀더로 추출한 뒤 각 라인을 순회하며 리스트(- 연속 항목), 제목(#, ##, ###), 인라인 굵은 텍스트(**...**) 및 인라인 코드(백틱)를 처리하도록 변경되었습니다. 테이블 파싱과 기존의 개행-to-<br/> 정규식 처리는 제거되었고, MarkdownRendererclassName prop이 필수에서 선택(className?: string)으로 변경되어 공개 API가 변경되었습니다.

관련 가능성이 있는 PR

  • 링크카드 디자인 개선 #428: 동일한 src/hooks/util/parseMarkdown.tsx 파일을 수정하며 MarkdownRenderer 관련 API(클래스네임 prop) 변경을 포함하여 코드 수준에서 직접적으로 연결됩니다.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 변경 사항의 핵심 내용인 요약에서 strong 태그 노출 문제를 명확하게 설명하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 있으며, 관련 이슈 번호와 구체적인 수정 내용을 포함하고 있습니다.
Linked Issues check ✅ Passed PR의 코드 변경사항이 #450에서 요구된 리스트 내 strong 태그 노출 문제를 해결하고 있습니다.
Out of Scope Changes check ✅ Passed parseMarkdown.tsx 리팩토링은 #450의 strong 태그 노출 문제 해결을 위한 범위 내의 변경이며, 추가 스코프 외 변경은 없습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch design/#450-summary-strong-tag

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: 2

🤖 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/hooks/util/parseMarkdown.tsx`:
- Around line 46-54: The list-item formatter currently only replaces **...** and
leaves the rest unescaped, allowing raw HTML injection; create (or use) an
inline formatter that fully escapes non-bold text and only unescapes/escapes the
bold inner text via escapeHtml. Concretely, implement a helper (e.g.,
formatInline or normalizeInline) that splits content by the bold pattern, runs
escapeHtml on every non-bold chunk and for bold chunks wraps escapeHtml(inner)
in `<strong class="font-semibold">...</strong>`, then replace the current inline
replace call and the content passed to result.push(`<li...>`) with
formatInline(content); reuse the same helper for the other inline handling block
currently at lines 86-90 so both list items and the other formatter are safe.
- Around line 42-57: The global insertion of "<br/>" is bleeding into list
blocks causing "<ul><br/><li>...</li><br/></ul>"; to fix, treat list blocks as a
single flushable unit in parseMarkdown.tsx by collecting list items while inList
(the code paths that push '<ul class="my-2">' and '</ul>') and only emit the
surrounding '<ul>...</ul>' once all list items are assembled, or alternatively
prevent the global "<br/>" join from running when inList is true — update the
logic around the inList flag and the branches that push `<ul class="my-2">` /
`<li class="ml-4 list-disc">`` and the closing `</ul>` (the blocks around the
shown diff and the similar closing at lines 95-97) so that line-break insertion
is skipped for list blocks or the list is flushed as one block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 206e4c9b-e702-43a5-8f79-9016f1401ec3

📥 Commits

Reviewing files that changed from the base of the PR and between 488e088 and 564bf04.

📒 Files selected for processing (1)
  • src/hooks/util/parseMarkdown.tsx

@Seong-Myeong
Copy link
Copy Markdown
Contributor

코드래빗 답변 확인 부탁드립니다 @Bangdayeon

@Bangdayeon
Copy link
Copy Markdown
Member Author

코드래빗 답변 확인 부탁드립니다 @Bangdayeon

코드래빗 답변 확인 부탁드립니다 @Bangdayeon

코드래빗 답변 확인 부탁드립니다 @Bangdayeon

코드래빗 답변 내용 해결했습니다

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.

♻️ Duplicate comments (1)
src/hooks/util/parseMarkdown.tsx (1)

10-33: ⚠️ Potential issue | 🔴 Critical

formatInline가 아직 raw HTML을 그대로 통과시킵니다.

일반 텍스트를 escapeHtml하지 않아 <img src=x onerror=...> 같은 입력이 Line 119의 dangerouslySetInnerHTML까지 그대로 도달합니다. 지금 순서에서는 `**x**`%%BOLD_0%%로 깨질 수 있어서, 인라인 코드를 먼저 분리하고 나머지 텍스트만 escape한 뒤 마지막에 마크업을 복원하는 쪽이 안전합니다.

🔒 제안 수정안
 const formatInline = (raw: string) => {
   if (!raw) return '';
-
-  // Bold 처리
-  const boldTokens: string[] = [];
-  const tokenized = raw.replace(/\*\*(.+?)\*\*/g, (_, g1) => {
-    const index = boldTokens.length;
-    boldTokens.push(`<strong class="font-semibold">${escapeHtml(g1)}</strong>`);
-    return `%%BOLD_${index}%%`;
-  });
-
-  // 인라인 코드 처리
-  const inlineTokens: string[] = [];
-  const inlineProcessed = tokenized.replace(/`([^`]+)`/g, (_, code) => {
-    const index = inlineTokens.length;
-    inlineTokens.push(`<code class="bg-gray200 rounded px-1 text-sm">${escapeHtml(code)}</code>`);
-    return `%%INLINE_${index}%%`;
-  });
-
-  // placeholder 복원
-  let restored = inlineProcessed.replace(/%%BOLD_(\d+)%%/g, (_, i) => boldTokens[Number(i)]);
-  restored = restored.replace(/%%INLINE_(\d+)%%/g, (_, i) => inlineTokens[Number(i)]);
-
-  return restored;
+  return raw
+    .split(/(`[^`]+`)/g)
+    .map(part => {
+      const inlineMatch = part.match(/^`([^`]+)`$/);
+      if (inlineMatch) {
+        return `<code class="bg-gray200 rounded px-1 text-sm">${escapeHtml(inlineMatch[1])}</code>`;
+      }
+
+      return part
+        .split(/(\*\*.+?\*\*)/g)
+        .map(chunk => {
+          const boldMatch = chunk.match(/^\*\*(.+?)\*\*$/);
+          if (boldMatch) {
+            return `<strong class="font-semibold">${escapeHtml(boldMatch[1])}</strong>`;
+          }
+
+          return escapeHtml(chunk);
+        })
+        .join('');
+    })
+    .join('');
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/util/parseMarkdown.tsx` around lines 10 - 33, formatInline
currently allows raw HTML through because bold is tokenized before the rest of
the text is escaped; change the processing order in formatInline to first
extract/replace inline code spans (`/`([^`]+)`/` -> inlineTokens), then escape
the remaining text with escapeHtml, then tokenize/process bold markers using the
escaped text (store boldTokens from escaped content), and finally restore the
placeholders for bold and inline in that order so no unescaped raw HTML (e.g.,
<img ...>) reaches dangerouslySetInnerHTML; reference the existing symbols
formatInline, inlineTokens, boldTokens, escapeHtml and the placeholder patterns
%%INLINE_n%% / %%BOLD_n%% when implementing.
🤖 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/hooks/util/parseMarkdown.tsx`:
- Around line 10-33: formatInline currently allows raw HTML through because bold
is tokenized before the rest of the text is escaped; change the processing order
in formatInline to first extract/replace inline code spans (`/`([^`]+)`/` ->
inlineTokens), then escape the remaining text with escapeHtml, then
tokenize/process bold markers using the escaped text (store boldTokens from
escaped content), and finally restore the placeholders for bold and inline in
that order so no unescaped raw HTML (e.g., <img ...>) reaches
dangerouslySetInnerHTML; reference the existing symbols formatInline,
inlineTokens, boldTokens, escapeHtml and the placeholder patterns %%INLINE_n%% /
%%BOLD_n%% when implementing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e6e8fdc7-2ef1-4541-b4f5-e5c795d323cb

📥 Commits

Reviewing files that changed from the base of the PR and between 564bf04 and e9fa18f.

📒 Files selected for processing (1)
  • src/hooks/util/parseMarkdown.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

요약에 <strong>태그가 그대로 노출되는 문제

2 participants