feat: add GitHub-style alert blockquotes#174
Conversation
Adds support for the 5 GitHub alert types (NOTE, TIP, IMPORTANT, WARNING, CAUTION) as a new TipTap extension with full markdown round-trip support. - AlertBlockquote.ts: new Node extension with custom marked.js tokenizer, parseMarkdown, renderMarkdown, and InputRule - Editor.tsx: registers extension + adds 5 toolbar buttons with active state - SlashCommand.tsx: adds /note, /tip, /important, /warning, /caution commands - App.css: alert styles (color-coded border + label), fixes blockquote bold/italic default from Tailwind Typography
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds an ChangesAlert Blockquote Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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: 2
🧹 Nitpick comments (2)
src/components/editor/Editor.tsx (1)
72-72: ⚡ Quick winCentralize alert button metadata with shared
AlertTypetyping.Line 356 widens
alertTypetostring, and this same mapping is duplicated insrc/components/editor/SlashCommand.tsx(Lines 119-125). This increases drift risk across toolbar/slash behavior.Proposed fix
-import { AlertBlockquote } from "./AlertBlockquote"; +import { AlertBlockquote, type AlertType } from "./AlertBlockquote"; +const ALERT_BUTTONS: Array<{ alertType: AlertType; label: string; color: string }> = [ + { alertType: "NOTE", label: "Note", color: "`#4493f8`" }, + { alertType: "TIP", label: "Tip", color: "`#3fb950`" }, + { alertType: "IMPORTANT", label: "Important", color: "`#ab7df8`" }, + { alertType: "WARNING", label: "Warning", color: "`#d29922`" }, + { alertType: "CAUTION", label: "Caution", color: "`#f85149`" }, +]; ... - {( - [ - { alertType: "NOTE", label: "Note", color: "`#4493f8`" }, - { alertType: "TIP", label: "Tip", color: "`#3fb950`" }, - { alertType: "IMPORTANT", label: "Important", color: "`#ab7df8`" }, - { alertType: "WARNING", label: "Warning", color: "`#d29922`" }, - { alertType: "CAUTION", label: "Caution", color: "`#f85149`" }, - ] as { alertType: string; label: string; color: string }[] - ).map(({ alertType, label, color }) => ( + {ALERT_BUTTONS.map(({ alertType, label, color }) => (Also applies to: 349-357
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/editor/Editor.tsx` at line 72, The alert type mapping is duplicated across Editor.tsx and SlashCommand.tsx, causing maintenance drift risk. Extract the alert type metadata mapping into a shared AlertType type definition in a centralized location (such as a types or constants file). Replace the duplicate mappings around line 356 in Editor.tsx and lines 119-125 in SlashCommand.tsx with references to this shared AlertType definition, ensuring both files use the same type-safe alert configuration instead of allowing alertType to be widened to string.src/components/editor/AlertBlockquote.ts (1)
28-33: ⚡ Quick winExpose alert type semantically (not only via CSS
::before).The visible type label is generated in CSS, so assistive tech may miss it. Add a semantic label in the rendered node so alert type is announced consistently.
Proposed fix
renderHTML({ node, HTMLAttributes }) { - const type = ((node.attrs.alertType as string) || "NOTE").toLowerCase(); + const alertType = normalizeAlertType(node.attrs.alertType as string | undefined); + const type = alertType.toLowerCase(); return [ "blockquote", - mergeAttributes(HTMLAttributes, { class: `alert alert-${type}` }), + mergeAttributes(HTMLAttributes, { + class: `alert alert-${type}`, + "aria-label": `${alertType.toLowerCase()} alert`, + }), 0, ]; },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/editor/AlertBlockquote.ts` around lines 28 - 33, The renderHTML method in AlertBlockquote currently relies on CSS to display the alert type label, which makes it invisible to assistive technology. Modify the return statement to include a semantic HTML element (such as a div or span with appropriate ARIA attributes) that contains the alert type text as actual content. This element should be inserted after the blockquote opening tag so the alert type is announced by screen readers and other assistive technology consistently alongside the CSS-based visual indicator.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/App.css`:
- Around line 668-673: Replace the hardcoded left-side border properties with
logical CSS properties to support RTL text direction. In the alert styling
blocks at lines 668-673 and 690-703, change border-left-width to
border-inline-start-width, border-left-style to border-inline-start-style, and
update the border-radius value from "0 4px 4px 0" to use logical equivalents
(border-start-end-radius and border-end-end-radius) so that the accent border
and rounded corners automatically adapt to RTL layouts without requiring
separate RTL-specific rules.
In `@src/components/editor/AlertBlockquote.ts`:
- Around line 18-19: The parseHTML method on line 18 casts the data-alert-type
attribute directly to AlertType without validation, allowing unsupported values
to leak into markdown output and break round-trip serialization. Validate that
the alertType value is one of the supported AlertType values in the parseHTML
method, and apply the same validation in the renderHTML method and the rendering
logic at lines 90-91 and 101-105 to ensure only valid alert types are serialized
and rendered, falling back to a default value like "NOTE" for any unsupported
values.
---
Nitpick comments:
In `@src/components/editor/AlertBlockquote.ts`:
- Around line 28-33: The renderHTML method in AlertBlockquote currently relies
on CSS to display the alert type label, which makes it invisible to assistive
technology. Modify the return statement to include a semantic HTML element (such
as a div or span with appropriate ARIA attributes) that contains the alert type
text as actual content. This element should be inserted after the blockquote
opening tag so the alert type is announced by screen readers and other assistive
technology consistently alongside the CSS-based visual indicator.
In `@src/components/editor/Editor.tsx`:
- Line 72: The alert type mapping is duplicated across Editor.tsx and
SlashCommand.tsx, causing maintenance drift risk. Extract the alert type
metadata mapping into a shared AlertType type definition in a centralized
location (such as a types or constants file). Replace the duplicate mappings
around line 356 in Editor.tsx and lines 119-125 in SlashCommand.tsx with
references to this shared AlertType definition, ensuring both files use the same
type-safe alert configuration instead of allowing alertType to be widened to
string.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3ccdc373-ccc9-4d30-9ca6-94e5bca67bc5
📒 Files selected for processing (4)
src/App.csssrc/components/editor/AlertBlockquote.tssrc/components/editor/Editor.tsxsrc/components/editor/SlashCommand.tsx
- Add normalizeAlertType() to validate alertType everywhere it's read
from HTML attrs, markdown tokens, or node attrs
- Add aria-label to alert blockquotes for screen reader accessibility
- Extract ALERT_TYPES/ALERT_META into AlertBlockquote.ts as a single
source of truth, used by both Editor.tsx and SlashCommand.tsx
- Use logical CSS properties (border-inline-start, border-*-end-radius)
instead of border-left for RTL support
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/editor/AlertBlockquote.ts (1)
91-95: ⚡ Quick winBuild the regex dynamically from ALERT_TYPES to prevent synchronization issues.
The tokenizer regex hardcodes
NOTE|TIP|IMPORTANT|WARNING|CAUTION, duplicating the ALERT_TYPES constant. If a new alert type is added to ALERT_TYPES, the regex must be manually updated or markdown files with the new type won't parse.♻️ Proposed fix
tokenize(src: string, _tokens: MarkdownToken[]) { + const alertPattern = ALERT_TYPES.join("|"); + const regex = new RegExp( + `^> \\[!(${alertPattern})\\][ \\t]*\\r?\\n?((?:>[ \\t]?[^\\n]*\\r?\\n?)*)`, + "i" + ); - const match = src.match( - /^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*\r?\n?((?:>[ \t]?[^\n]*\r?\n?)*)/i, - ); + const match = src.match(regex); if (!match) return undefined;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/editor/AlertBlockquote.ts` around lines 91 - 95, The regex pattern in the tokenize method hardcodes the alert types (NOTE|TIP|IMPORTANT|WARNING|CAUTION) instead of deriving them from the ALERT_TYPES constant, which causes maintenance issues when new types are added. Build the regex dynamically by extracting the type keys from ALERT_TYPES, joining them with pipe delimiters, and constructing the regex pattern at runtime. This ensures the tokenizer automatically recognizes any new alert types added to ALERT_TYPES without requiring manual regex updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/components/editor/AlertBlockquote.ts`:
- Around line 91-95: The regex pattern in the tokenize method hardcodes the
alert types (NOTE|TIP|IMPORTANT|WARNING|CAUTION) instead of deriving them from
the ALERT_TYPES constant, which causes maintenance issues when new types are
added. Build the regex dynamically by extracting the type keys from ALERT_TYPES,
joining them with pipe delimiters, and constructing the regex pattern at
runtime. This ensures the tokenizer automatically recognizes any new alert types
added to ALERT_TYPES without requiring manual regex updates.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9508bbae-63db-42ec-9427-597437c6d6ec
📒 Files selected for processing (4)
src/App.csssrc/components/editor/AlertBlockquote.tssrc/components/editor/Editor.tsxsrc/components/editor/SlashCommand.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/editor/SlashCommand.tsx
- src/App.css
- src/components/editor/Editor.tsx
Per CodeRabbit follow-up: the markdown tokenizer regex previously hardcoded the 5 alert type names separately from ALERT_TYPES. Now built dynamically from ALERT_TYPES so the two can't drift out of sync.
Summary
NOTE,TIP,IMPORTANT,WARNING,CAUTION) as a dedicated TipTap node extension>then[!NOTE]), slash commands (/note,/tip…), toolbar buttons.mdfiles are parsed and rendered correctly; saving and reopening preserves themChanges
New file:
src/components/editor/AlertBlockquote.tsNew TipTap
Nodeextension:> [!TYPE]syntaxparseMarkdown/renderMarkdown→ bidirectional markdown serializationInputRule→ type>then[!NOTE]inside a blockquote to convert it to an alertparseHTML→ handles<blockquote data-alert-type="...">for copy/pasteModified file:
src/components/editor/Editor.tsxAlertBlockquoteextensionModified file:
src/components/editor/SlashCommand.tsxalert,callout(shared) +info,hint,warn,danger(type-specific)Modified file:
src/App.css::beforelabel per typefont-weight: 500/font-style: italicon blockquotesKnown limitations
**text**). This is a constraint of the@tiptap/markdownv3 custom block extension API, which does not expose the block-recursive inline processing pipeline to custom extensions.Tested so far
npm run tauri buildpasses>then[!NOTE]→ blockquote converts to NOTE alert; repeat for all 5 types/notein slash command → alert wraps current paragraph; repeat for all 5 types.mdfile containing> [!NOTE]syntax → rendered as styled alertVisuals
Summary by CodeRabbit
[!TYPE]> [!TYPE]blockquote alerts