에디터 유튜브 링크 자동 임베드#558
Conversation
유튜브 embed CSP 허용(frame-src), 코드블록 위치 계산 훅 분리, 상수명 정규화(IMAGE_MIME_TO_EXT/EXTS), PASS_THROUGH_PREFIXES 추출, 스타일 상수 모듈 레벨로 분리, 테스트 설명 한국어화 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 26 minutes and 11 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughYouTube 임베드 기능이 마크다운 에디터와 콘텐츠 렌더링 경로에 추가되었습니다. 독립 실행형 YouTube URL을 감지하여 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Editor as TipTap Editor
participant Preprocessor as URL Preprocessor
participant Sanitizer as HTML Sanitizer
participant Renderer as DOM Renderer
User->>Editor: Paste YouTube URL
Editor->>Preprocessor: replaceStandaloneYouTubeLinksWithEmbeds(content)
Preprocessor->>Preprocessor: extractYouTubeEmbedInfo(url)
Preprocessor->>Preprocessor: createYouTubeEmbedHtml(url)
Preprocessor-->>Editor: HTML with <iframe> markup
Editor->>Sanitizer: Parse & sanitize HTML
Sanitizer->>Sanitizer: Allow iframe + media attributes
Sanitizer->>Sanitizer: applyYouTubeIframeAttributes(doc)
Sanitizer-->>Editor: Sanitized HTML
Editor->>Renderer: Render to DOM
Renderer-->>User: Display YouTube embed with styling
sequenceDiagram
participant User
participant Editor as TipTap Editor
participant Extension as YouTubeEmbedExtension
participant Hook as useActiveCodeBlockControl
User->>Editor: Focus editor / Select text
Editor->>Hook: Track editor state + window resize
Hook->>Hook: Measure code block position
Hook-->>Editor: ActiveCodeBlockControl (position/language)
User->>Editor: Paste YouTube URL (in text)
Editor->>Extension: insertYouTubeEmbed()
Extension->>Extension: Validate selection (not in codeBlock)
Extension->>Editor: Insert youtubeEmbed node + paragraph
Editor-->>User: Display embedded YouTube iframe
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
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)
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 |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/common/ui/rich-text/markdown-content-core.tsx (1)
210-221:⚠️ Potential issue | 🟠 Major유튜브 치환 후에 나머지 Markdown 파싱이 건너뛰어집니다.
replaceStandaloneYouTubeLinksWithEmbeds()가 standalone URL을<iframe>로 바꾸면, Line 214의isHtmlContent(contentWithEmbeds)가 바로true가 됩니다. 그래서**bold**, 리스트, 헤딩 같은 주변 Markdown이 더 이상marked.parse()를 거치지 못하고 평문으로 남습니다. HTML 여부는 원본content기준으로 판단하고, 원본이 Markdown이었다면 치환 후 문자열도 그대로marked.parse()에 넣는 쪽이 맞습니다.🔧 Proposed fix
- const contentWithEmbeds = replaceStandaloneYouTubeLinksWithEmbeds(content); + const isOriginalHtml = isHtmlContent(content); + const contentWithEmbeds = replaceStandaloneYouTubeLinksWithEmbeds(content); let html: string; - if (isHtmlContent(contentWithEmbeds)) { + if (isOriginalHtml) { html = contentWithEmbeds; } else { const rendered = marked.parse(contentWithEmbeds, { breaks: true, gfm: true,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/common/ui/rich-text/markdown-content-core.tsx` around lines 210 - 221, The current logic checks isHtmlContent against contentWithEmbeds so embeds converted to <iframe> short-circuit Markdown parsing; change the branch to determine HTML-ness from the original content (use isHtmlContent(content)) and then: if original content is HTML set html = contentWithEmbeds, otherwise run marked.parse on contentWithEmbeds (using marked.parse options currently present) and assign the resulting string to html; update references to contentWithEmbeds, content, isHtmlContent, replaceStandaloneYouTubeLinksWithEmbeds, and marked.parse accordingly.
🧹 Nitpick comments (1)
src/components/common/ui/editor/use-active-code-block-control.ts (1)
51-53:language읽기는 단언보다 가드가 낫습니다.여기서는 런타임 데이터에
as string | undefined를 씌우고 있어서, 예상 밖 값이 들어와도 타입 시스템만 조용히 통과합니다. 속성 존재 여부를 가드한 뒤 fallback을 두는 쪽이 이 코드베이스 규칙에도 맞습니다.As per coding guidelines, "Use
inguard with fallback instead ofastype assertion for enum-like string type assertions when the backend may send unknown values. TypeScriptasdoes not protect at runtime."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/common/ui/editor/use-active-code-block-control.ts` around lines 51 - 53, Replace the unsafe assertion on editor.getAttributes('codeBlock').language with a runtime guard: read attrs = editor.getAttributes('codeBlock'), check that attrs has a "language" key and that typeof attrs.language === 'string' (or use the 'in' operator) and only then assign to the local variable language; otherwise set language = 'plaintext'. Update any code using the current language constant to use this guarded value (references: editor.getAttributes('codeBlock'), language).
🤖 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/global.css`:
- Around line 1123-1141: Replace hardcoded spacing/radius/outline pixel values
in the selectors .tiptap-editor .tiptap iframe.youtube-embed, .tiptap-editor
.tiptap img.ProseMirror-selectednode, and .tiptap-editor .tiptap
iframe.youtube-embed.ProseMirror-selectednode with theme tokens: change
border-radius: 8px to border-radius: var(--radius-100), margin-bottom: 12px to
margin-bottom: var(--spacing-150), outline: 2px solid var(--color-border-brand)
to outline: var(--border-width-100) solid var(--color-border-brand) (or the
project border-width token), and outline-offset: 2px to outline-offset:
var(--spacing-25); after edits run yarn prettier:fix to apply project formatting
rules.
In `@src/components/common/ui/editor/extensions.ts`:
- Around line 149-151: The youtubeEmbed atom node fallback currently returns
['p', 0], which inserts a content hole and causes RangeError since youtubeEmbed
is a leaf atom; change the fallback to return a leaf-safe DOM spec without a
content hole (for example ['p'] or ['span'] with no hole) in the branch that
checks if (!attrs) so the youtubeEmbed node returns a valid leaf element rather
than an array containing a content hole.
In `@src/components/common/ui/editor/image-utils.ts`:
- Around line 111-116: The current getExtensionFromMime uses an unsafe type
assertion (mimeType as SupportedImageMimeType); instead call or use the existing
normalizeImageMimeType to first narrow/validate the mimeType (or perform an
`in`-style guard against IMAGE_MIME_TO_EXT keys) and then lookup
IMAGE_MIME_TO_EXT using the narrowed value, falling back to
mimeType.split('/')[1]?.toLowerCase() ?? ''. Update getExtensionFromMime to
accept the normalized/guarded mime string (or perform the guard inline) so you
remove the `as` assertion and safely handle unknown backend values.
In `@src/components/common/ui/editor/markdown-content.tsx`:
- Around line 58-67: isHtmlContent is being called after
replaceStandaloneYouTubeLinksWithEmbeds, so an injected <iframe> can make it
return true and skip Markdown parsing; instead, determine HTML-ness from the
original normalizedContent and then decide rendering. Change the flow so you
call isHtmlContent(normalizedContent) first, store the result (e.g., isHtml),
then compute normalizedContentWithEmbeds =
replaceStandaloneYouTubeLinksWithEmbeds(normalizedContent) and if isHtml set
html = normalizedContentWithEmbeds else set html =
marked.parse(normalizedContentWithEmbeds, { breaks: true }) so Markdown is only
skipped when the original content is actually HTML; reference
variables/functions: normalizedContent, normalizedContentWithEmbeds,
replaceStandaloneYouTubeLinksWithEmbeds, isHtmlContent, marked.parse.
In `@src/components/common/ui/editor/use-active-code-block-control.ts`:
- Around line 20-60: The memoization prevents recalculation of code-block
control coordinates; remove the useMemo wrapper and compute the returned value
each render instead. Specifically, replace the "return useMemo(() => { ... },
[editor, wrapperRef])" with the same body executed directly and return the
result (keep the existing resize forceUpdate state and effect), ensuring you
still reference editor.isActive('codeBlock'), wrapperRef.current,
editor.state.selection ($from), $from.before(),
editor.view.nodeDOM(codeBlockPos), and editor.getAttributes('codeBlock') to
derive language, top, and left; do not rely on useMemo or its dependency array
so coordinates update on selection/language/resize changes.
---
Outside diff comments:
In `@src/components/common/ui/rich-text/markdown-content-core.tsx`:
- Around line 210-221: The current logic checks isHtmlContent against
contentWithEmbeds so embeds converted to <iframe> short-circuit Markdown
parsing; change the branch to determine HTML-ness from the original content (use
isHtmlContent(content)) and then: if original content is HTML set html =
contentWithEmbeds, otherwise run marked.parse on contentWithEmbeds (using
marked.parse options currently present) and assign the resulting string to html;
update references to contentWithEmbeds, content, isHtmlContent,
replaceStandaloneYouTubeLinksWithEmbeds, and marked.parse accordingly.
---
Nitpick comments:
In `@src/components/common/ui/editor/use-active-code-block-control.ts`:
- Around line 51-53: Replace the unsafe assertion on
editor.getAttributes('codeBlock').language with a runtime guard: read attrs =
editor.getAttributes('codeBlock'), check that attrs has a "language" key and
that typeof attrs.language === 'string' (or use the 'in' operator) and only then
assign to the local variable language; otherwise set language = 'plaintext'.
Update any code using the current language constant to use this guarded value
(references: editor.getAttributes('codeBlock'), language).
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 40ee5e84-767f-4594-ac43-818efdcee17c
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (12)
next.config.tssrc/app/global.csssrc/components/common/ui/editor/extensions.tssrc/components/common/ui/editor/image-utils.tssrc/components/common/ui/editor/markdown-content-assets.tssrc/components/common/ui/editor/markdown-content.tsxsrc/components/common/ui/editor/markdown-editor.tsxsrc/components/common/ui/editor/markdown-sanitizer.tssrc/components/common/ui/editor/use-active-code-block-control.tssrc/components/common/ui/editor/youtube-utils.test.tssrc/components/common/ui/editor/youtube-utils.tssrc/components/common/ui/rich-text/markdown-content-core.tsx
…n 파싱 스킵 수정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
YouTubeEmbedExtensionTiptap 노드로 iframe을 안전한 블록 노드로 관리frame-src에www.youtube-nocookie.com허용 추가Changes
Features
youtube-utils.tsextensions.tsYouTubeEmbedExtension— iframe을 ProseMirror 블록 노드로 처리markdown-editor.tsxmarkdown-content-core.tsxmarkdown-sanitizer.tsnext.config.tsframe-src에 youtube-nocookie.com 추가global.cssRefactoring
use-active-code-block-control.tsimage-utils.tsIMAGE_MIME_TO_EXT/IMAGE_MIME_TO_EXTS상수명 명확화markdown-content-assets.tsPASS_THROUGH_PREFIXES상수 추출,/images/분기 통합markdown-content.tsxMARKDOWN_CONTENT_BASE_STYLES모듈 레벨 상수 추출youtube-utils.test.tsTest plan
youtube.com/watch?v=...) 붙여넣기 → iframe embed로 변환 확인youtu.be단축 URL 붙여넣기 → embed 변환 확인?t=90,?start=43) 파라미터가 embed URL에 반영되는지 확인MarkdownContent)에서 기존 저장된 유튜브 iframe이 정상 렌더링되는지 확인🤖 Generated with Claude Code
Summary by CodeRabbit
릴리스 노트
New Features
Tests