Add new BitMarkdownViewer component (#12503)#12506
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe PR replaces the JS/Jint-based markdown viewer with a native C# parser, renderer, component, demo, and tests. It adds a pipeline and extension model for Markdown features such as tables, strikethrough, task lists, autolinks, emoji, and automatic heading IDs. ChangesNative C# Markdown Viewer replacement
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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: 10
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cs (1)
30-47: ⚡ Quick winHarden immutability for internal lookup collections.
Line 44–47 expose mutable backing collections through
internalproperties while the class documentation states the pipeline is immutable/thread-safe. Wrapping these maps/lists in read-only containers (and avoiding mutable set exposure) prevents accidental internal mutation.Proposed refactor
+using System.Collections.ObjectModel; ... - InlineParsersByChar = byChar.ToDictionary(kv => kv.Key, kv => (IReadOnlyList<InlineParser>)kv.Value); + InlineParsersByChar = new ReadOnlyDictionary<char, IReadOnlyList<InlineParser>>( + byChar.ToDictionary(kv => kv.Key, kv => (IReadOnlyList<InlineParser>)kv.Value.AsReadOnly())); ... - DelimiterByChar = delimByChar; - DelimiterChars = new HashSet<char>(delimByChar.Keys); + DelimiterByChar = new ReadOnlyDictionary<char, DelimiterProcessor>(delimByChar); + DelimiterChars = delimByChar.Keys.ToHashSet(); ... - internal HashSet<char> DelimiterChars { get; } + internal IReadOnlySet<char> DelimiterChars { get; }🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cs` around lines 30 - 47, The DelimiterChars property on line 36 exposes a mutable HashSet<char> directly, which violates the immutability and thread-safety contract of the BitMarkdownPipeline class. Wrap the DelimiterChars property by changing its return type from HashSet<char> to IReadOnlySet<char> or IReadOnlyCollection<char>, and ensure the internal backing field stores the HashSet while the property returns it wrapped in a read-only view to prevent accidental mutation through the exposed property.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cs`:
- Around line 16-39: The static Emoji dictionary field is mutable and publicly
accessible, but it is also read without synchronization in the Process method
during the Shortcode().Replace operation. This creates a race condition if
callers modify the dictionary at runtime while parsing occurs. Replace the
mutable Dictionary<string, string> field with a thread-safe collection such as
ConcurrentDictionary, or alternatively make it immutable and use a lock when
modifications are needed. Ensure that the TryGetValue call in the Process
method's lambda expression reads from this thread-safe collection safely.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cs`:
- Around line 21-22: The regex pattern in the TaskMarker method currently uses
\s+ which requires at least one whitespace character after the closing bracket,
causing it to reject valid task-list items like "- [ ]" with no trailing text.
Change the quantifier from \s+ to \s* in the TaskMarker regex pattern to make
the whitespace optional while keeping the rest of the pattern intact.
Additionally, check the code around lines 33-37 (mentioned in the comment) for
similar regex patterns that may have the same issue and apply the same fix if
needed.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cs`:
- Around line 13-36: The VisitChildLists and Descendants methods both use
unbounded recursion to traverse the AST tree, which can cause stack overflow
with deeply nested inputs. Refactor both methods to use iterative traversal with
an explicit stack or queue data structure instead of recursive calls. For
VisitChildLists, maintain a queue of nodes to process iteratively, invoking the
action on each node's child lists while pushing child nodes onto the queue for
processing rather than recursing. For Descendants, similarly use a queue to
iteratively yield all descendant nodes without making recursive calls to
Descendants. Both methods should maintain the same traversal semantics and
result order while safely handling arbitrary nesting depths.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cs`:
- Around line 66-76: The GetIndent and StripIndent methods are incorrectly
handling tab characters by treating each tab as a fixed increment of 4 columns,
when they should advance to the next tab stop position. In the GetIndent method,
replace the logic that adds 4 when encountering a tab character with logic that
advances the indent variable to the next multiple of 4 (i.e., next tab stop).
Apply the same tab-stop calculation fix to the StripIndent method (lines 78-88).
This ensures that mixed whitespace prefixes are parsed correctly for
indent-sensitive markdown blocks.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cs`:
- Around line 148-155: The IsQuote method on line 148 uses TrimStart(' ') which
removes all leading spaces before checking for the blockquote marker, causing
lines with 4 or more leading spaces to be incorrectly identified as blockquotes.
According to Markdown specifications, blockquote markers should only be
recognized when preceded by up to 3 leading spaces. Modify the IsQuote method to
check that the line has at most 3 leading spaces before the '>' character,
rejecting lines with 4 or more leading spaces so that indented code blocks are
properly classified before blockquotes are parsed.
- Around line 195-197: The CanInterruptParagraph method in the CoreBlockParsers
class is allowing any ordered list marker to interrupt a paragraph, but it
should only allow ordered lists starting with "1" to interrupt. Modify the check
on line 197 where BlockGrammar.Ordered().IsMatch is called to add an additional
validation that ensures the ordered marker is specifically for the number "1"
before allowing the paragraph interruption. The bullet list check on line 196
should remain unchanged.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cs`:
- Around line 95-111: The AutolinkInlineParser is creating links without
sanitizing URLs through the UrlSanitizer, creating a security vulnerability
where dangerous URLs like javascript:alert(1) bypass validation. Modify all
three Emit calls in this method to sanitize the URL parameter before passing it
to Emit. Specifically, when Emit is called with the URL (either inner for
scheme-based URLs on line 102, "mailto:" + inner for email addresses on line
109, and other cases in lines 116-122), wrap the URL parameter with UrlSanitizer
to ensure all autolinks are sanitized the same way as regular markdown links
are.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cs`:
- Around line 81-89: The IsExternal method is being called with link.Url without
verifying that the URL is not null or empty first, which can cause a runtime
exception when rendering links with missing or invalid URLs. Move the entire
block containing the IsExternal call (the conditional starting with
IsExternal(link.Url) that sets target and rel attributes) inside or after a
null/empty check for link.Url, similar to the existing check on line 81, or add
a guard condition that ensures link.Url is not null/empty before calling
IsExternal. Apply the same fix to the other occurrence mentioned at lines
146-148.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cs`:
- Around line 41-51: The WriteNode method in MarkdownRenderer silently exits
without rendering when no renderer matches a node, causing unrendered content to
be lost without any indication of the problem. After the for loop that iterates
through _renderers completes, add error handling to detect when no renderer
accepted the node. Throw an exception or log an error message indicating which
node type could not be rendered, so developers are aware when a renderer is
missing for a particular node type introduced by an extension.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor`:
- Line 48: The textarea element with class "mdv-editor" that binds to
playgroundMarkdown lacks an accessible name for screen readers. Add an
aria-label attribute to the textarea element with a descriptive label (such as
"Markdown editor" or similar) that clearly identifies the purpose of the
textarea to assistive technology users. Alternatively, you could associate the
textarea with a visible or hidden label element using a for attribute if a label
element is already present in the component structure.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cs`:
- Around line 30-47: The DelimiterChars property on line 36 exposes a mutable
HashSet<char> directly, which violates the immutability and thread-safety
contract of the BitMarkdownPipeline class. Wrap the DelimiterChars property by
changing its return type from HashSet<char> to IReadOnlySet<char> or
IReadOnlyCollection<char>, and ensure the internal backing field stores the
HashSet while the property returns it wrapped in a read-only view to prevent
accidental mutation through the exposed property.
🪄 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: 9e12abe4-20c9-4a8a-883b-898c75587691
📒 Files selected for processing (41)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cs`:
- Around line 13-17: The GeneratedRegex pattern in the AutoLinkExtension class
is missing a start-boundary guard that prevents matching autolinks in the middle
of larger tokens (e.g., foohttps://x.y). Add a word boundary assertion at the
beginning of the regex pattern before the first alternation group to ensure
URLs, www links, and email addresses are only matched when they start at word
boundaries or appropriate positions, not when they appear as substrings within
other tokens.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cs`:
- Around line 93-108: The SplitRow method currently treats every non-escaped
pipe character as a cell separator without considering whether it appears inside
backtick code spans. Add tracking logic to monitor when the parser enters and
exits backtick blocks by checking for backtick characters in the iteration loop
within SplitRow. Only treat a pipe character as a cell separator if it is both
non-escaped (not preceded by backslash) AND not currently inside a backtick code
span. This will allow valid markdown like ``| `a|b` | c |`` to be parsed
correctly with the pipe inside backticks not being treated as a separator.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cs`:
- Around line 72-88: The SplitLines method only recognizes `\n` as a line
boundary in its loop condition, which causes lines separated by standalone `\r`
characters (classic Mac line endings) to be treated as part of the same line.
Modify the loop in SplitLines to check for both `\r` and `\n` characters as line
boundaries, ensuring that `\r\n` sequences are treated as a single line boundary
(not two separate ones) by checking if a `\r` is followed by a `\n` and skipping
the next character appropriately, and verify that the final line handling at the
end also strips any trailing `\r` characters that might remain.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cs`:
- Around line 57-64: In the Use method, the extension is added to the
_extensions list before calling extension.Setup(this), which means if Setup
throws an exception, the extension remains registered and prevents retries.
Reorder the operations so that extension.Setup(this) is called first, and only
if it completes successfully should the extension be added to the _extensions
collection using _extensions.Add(extension).
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cs`:
- Around line 96-99: In the ImageNode case within CoreRenderer.cs, the src
attribute is unconditionally emitted via b.AddAttribute even when img.Url is
empty after sanitization, causing empty src attributes to render. Modify the
code to only call b.AddAttribute for "src" with img.Url when img.Url is not
empty or null, mirroring the pattern used in the link branch to avoid rendering
empty src attributes.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cs`:
- Around line 204-222: The example3RazorCode string contains a textarea element
that is missing an aria-label attribute, which creates an inconsistency with the
actual live demo implementation. Locate the textarea element with class
mdv-editor in the example3RazorCode string and add an appropriate aria-label
attribute to ensure the copied code snippet includes the same accessibility
features as the live demo component.
🪄 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: 90781769-51f1-4d70-91f4-6eb2121b03ef
📒 Files selected for processing (41)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs (1)
42-43: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider stripping additional control characters from scheme.
The scheme normalization removes
\tand\n, but other control characters (e.g.,\r, form feed, or null bytes) could potentially be used for obfuscation in crafted inputs. While browsers also normalize schemes, adding\rwould be a low-cost improvement for defense-in-depth.- string scheme = trimmed[..(colon + 1)].Replace("\t", "").Replace("\n", "").ToLowerInvariant(); + string scheme = trimmed[..(colon + 1)].Replace("\t", "").Replace("\n", "").Replace("\r", "").ToLowerInvariant();🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs` around lines 42 - 43, The scheme normalization in the UrlSanitizer class currently only strips `\t` and `\n` characters, but should also remove other control characters for defense-in-depth. In the scheme variable assignment, add additional Replace calls to remove `\r` (carriage return) and consider other common control characters like form feed or null bytes that could be used for obfuscation in crafted inputs, following the same pattern as the existing `\t` and `\n` removals.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cs`:
- Around line 57-60: The ChildLists property override is returning the nested
children of each ListItemNode (via Items.Select(i => i.Children)) instead of
exposing the list items themselves, which violates the traversal contract and
prevents generic processors from visiting ListItemNode objects. Modify the
ChildLists property to return the Items collection itself as a single enumerable
list instead of selecting each item's children, so that generic traversals can
properly inspect and process the ListItemNode objects directly.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs`:
- Around line 42-43: The scheme normalization in the UrlSanitizer class
currently only strips `\t` and `\n` characters, but should also remove other
control characters for defense-in-depth. In the scheme variable assignment, add
additional Replace calls to remove `\r` (carriage return) and consider other
common control characters like form feed or null bytes that could be used for
obfuscation in crafted inputs, following the same pattern as the existing `\t`
and `\n` removals.
🪄 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: 3688caed-3883-47b2-aa13-15746a2b3410
📒 Files selected for processing (41)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs (1)
71-72: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winHarden the unsafe-link test by asserting the rendered
hrefbehavior directly.Line 71 only checks a lowercase substring in HTML, which is brittle. Assert the link renders without an unsafe
href(or with emptyhref) so sanitizer regressions are caught more reliably.Suggested test assertion update
- Assert.DoesNotContain("javascript:", root.InnerHtml); + var link = root.QuerySelector("a"); + Assert.IsNotNull(link); + Assert.IsFalse(link.HasAttribute("href"));🤖 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/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs` around lines 71 - 72, The current test assertion at line 71 only checks if the substring "javascript:" is absent from the HTML content, which is brittle and doesn't directly validate the sanitizer's behavior. Replace the Assert.DoesNotContain check with a more robust assertion that directly accesses and validates the rendered link's href attribute (the actual `href` property of the element) to ensure it is either empty or does not contain any unsafe protocols like javascript:. This will make the test more reliable and catch sanitizer regressions by testing the actual rendered behavior rather than a substring presence check.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cs`:
- Around line 24-31: The current implementation in the traversal loop calls
action for all child lists before descending into any of their children, which
violates depth-first order when a node has multiple ChildLists. To preserve
depth-first visitation semantics, restructure the loop so that for each list in
current.ChildLists, the action is called immediately before that list's children
are collected and pushed to the stack, rather than calling action on all lists
upfront before any children are processed. This ensures each list and its
descendants are fully processed before moving to the next sibling list.
---
Nitpick comments:
In
`@src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs`:
- Around line 71-72: The current test assertion at line 71 only checks if the
substring "javascript:" is absent from the HTML content, which is brittle and
doesn't directly validate the sanitizer's behavior. Replace the
Assert.DoesNotContain check with a more robust assertion that directly accesses
and validates the rendered link's href attribute (the actual `href` property of
the element) to ensure it is either empty or does not contain any unsafe
protocols like javascript:. This will make the test more reliable and catch
sanitizer regressions by testing the actual rendered behavior rather than a
substring presence check.
🪄 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: 88cebd48-7668-4eb3-89a7-80571e987883
📒 Files selected for processing (41)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cs (1)
24-31:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftPreserve depth-first list visitation order in
VisitChildLists.Lines 24-28 call
actionfor every child list before descending into any children, which changes the documented depth-first behavior when a node has multipleChildListsand can reorder AST rewrites. The action should be called on each list immediately before descending into that list's children, rather than processing all lists upfront.Recommended fix (preserves per-list DFS semantics)
public static void VisitChildLists(MarkdownNode node, Action<IList<MarkdownNode>> action) { - // Iterative depth-first traversal to avoid stack overflow on deeply nested input. - var stack = new Stack<MarkdownNode>(); - stack.Push(node); - while (stack.Count > 0) - { - var current = stack.Pop(); - // Collect children after invoking the action, since the action may - // replace entries in the list (e.g. splitting a text node). - var children = new List<MarkdownNode>(); - foreach (var list in current.ChildLists) - { - action(list); - children.AddRange(list); - } - // Push in reverse so children are processed in document order. - for (int i = children.Count - 1; i >= 0; i--) - stack.Push(children[i]); - } + var stack = new Stack<(MarkdownNode Node, int ListIndex)>(); + stack.Push((node, 0)); + + while (stack.Count > 0) + { + var (current, listIndex) = stack.Pop(); + if (listIndex >= current.ChildLists.Count) + continue; + + // Resume this node's next list after processing current list subtree. + stack.Push((current, listIndex + 1)); + + var list = current.ChildLists[listIndex]; + action(list); + + // Snapshot because action may replace list entries. + var snapshot = list.ToArray(); + for (int i = snapshot.Length - 1; i >= 0; i--) + stack.Push((snapshot[i], 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cs` around lines 24 - 31, The current implementation in the VisitChildLists method calls action on all child lists upfront before descending into any children, which violates depth-first traversal semantics when a node has multiple ChildLists. Reorganize the loop so that for each list in current.ChildLists, the action is called on that specific list immediately before adding its children to be processed, rather than iterating through all lists to call action first and then processing all accumulated children. This preserves true depth-first order where each list and its descendants are fully processed before moving to the next sibling list.
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs (1)
13-15: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider allowing
data:URIs for images.The
data:URI scheme is commonly used for embedding base64-encoded images directly in Markdown. Without it, inline images likewill be blocked. If this is intentional for security reasons (e.g., to prevent large payloads or exfiltration vectors), the current behavior is fine. Otherwise, you may want to add"data:"toAllowedImageSchemes.🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs` around lines 13 - 15, The AllowedImageSchemes array in UrlSanitizer.cs currently only permits http: and https: schemes, which blocks data URIs used for embedding base64-encoded images in Markdown. If not already intentional for security purposes, add "data:" to the AllowedImageSchemes array to allow inline images specified with data URIs like data:image/png;base64,...
🤖 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.
Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cs`:
- Around line 24-31: The current implementation in the VisitChildLists method
calls action on all child lists upfront before descending into any children,
which violates depth-first traversal semantics when a node has multiple
ChildLists. Reorganize the loop so that for each list in current.ChildLists, the
action is called on that specific list immediately before adding its children to
be processed, rather than iterating through all lists to call action first and
then processing all accumulated children. This preserves true depth-first order
where each list and its descendants are fully processed before moving to the
next sibling list.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs`:
- Around line 13-15: The AllowedImageSchemes array in UrlSanitizer.cs currently
only permits http: and https: schemes, which blocks data URIs used for embedding
base64-encoded images in Markdown. If not already intentional for security
purposes, add "data:" to the AllowedImageSchemes array to allow inline images
specified with data URIs like data:image/png;base64,...
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 98d4af75-47b7-4348-9862-6fabcb7074d2
📒 Files selected for processing (41)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cs (1)
59-60:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftExpose
ListNode.Itemsas an owned child list, not a copied snapshot.
ChildListscurrently allocates a newList<MarkdownNode>viaToList(). That list is detached, so generic AST rewrites on that collection never updateListNode.Items.Suggested direction
public sealed class ListNode : MarkdownNode { - public List<ListItemNode> Items { get; } = new(); + public List<MarkdownNode> Items { get; } = new(); - public override IEnumerable<IList<MarkdownNode>> ChildLists - => new[] { (IList<MarkdownNode>)Items.Cast<MarkdownNode>().ToList() }; + public override IList<MarkdownNode> ChildNodes => Items; }(Then adjust list consumers, e.g. renderer/parser call sites, to cast/validate
ListItemNodewhere needed.)🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cs` around lines 59 - 60, The ChildLists property in ListNode currently creates a detached snapshot of Items using ToList(), which means AST rewrites on that collection don't update the original ListNode.Items. Remove the ToList() call and instead return the actual Items collection directly (wrapped in an array for the IEnumerable interface), ensuring it remains connected to the source. Then update all list consumers and call sites (renderer, parser, etc.) to properly cast and validate ListItemNode items as needed when working with this collection.src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cs (1)
21-22:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winConstrain task marker matching to avoid
[x]foofalse positives.Line 21 currently accepts no-space content after
], so[x]foois rewritten as a task item. Match either an empty marker or whitespace+content only.Proposed regex/assignment adjustment
- [GeneratedRegex(@"^\[([ xX])\]\s*(.*)$")] + [GeneratedRegex(@"^\[([ xX])\](?:\s+(.*))?$")] private static partial Regex TaskMarker(); @@ - text.Text = m.Groups[2].Value; + text.Text = m.Groups[2].Success ? m.Groups[2].Value : string.Empty; para.Inlines.Insert(0, new TaskCheckboxNode { Checked = m.Groups[1].Value is "x" or "X" });Also applies to: 36-37
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cs` around lines 21 - 22, The regex pattern in the TaskMarker() method currently allows zero or more spaces after the closing bracket followed by any content, which causes false positives like [x]foo to be matched as task items. Modify the regex pattern to require either whitespace followed by content, or no content at all after the closing bracket, ensuring that task markers without a space separator (like [x]foo) are not matched as valid task items.
🧹 Nitpick comments (1)
src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs (1)
63-77: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAdd unsafe image URI sanitization coverage alongside the link sanitization test.
Current security coverage validates unsafe link
hrefstripping, but not unsafe imagesrc. A sibling test closes that regression gap for the same sanitizer contract.Suggested test addition
+ [TestMethod] + public void BitMarkdownViewerShouldSanitizeUnsafeImageSources() + { + var component = RenderComponent<BitMarkdownViewer>(parameters => + { + parameters.Add(p => p.Markdown, ")"); + }); + + var image = component.Find(".bit-mdv img"); + var src = image.GetAttribute("src") ?? string.Empty; + Assert.IsTrue( + src.Length == 0 || !src.Contains("javascript:", StringComparison.OrdinalIgnoreCase), + $"Unsafe image src was not sanitized: '{src}'."); + }🤖 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/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs` around lines 63 - 77, Create a new test method in the BitMarkdownViewerTests class to validate unsafe image URI sanitization, mirroring the structure of the existing BitMarkdownViewerShouldSanitizeUnsafeLinks method. Render the BitMarkdownViewer component with unsafe image markdown (e.g., )), then find the rendered img element using an appropriate selector, retrieve its src attribute, and assert that the src is either empty or does not contain the javascript scheme using the same validation logic applied to link href attributes in the existing test.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cs`:
- Around line 105-115: In the SplitRow method's backtick handling logic around
lines 105-115, the code does not account for escaped backticks when determining
code-span state. Before processing backticks as code-span delimiters, check if
the backtick is escaped (preceded by a backslash character). If a backtick is
escaped, treat it as a literal character and append it without modifying the
backtickRun state. Only process unescaped backtick runs to toggle the
backtickRun value, ensuring that the row separator detection at line 114 works
correctly and cells are parsed properly.
---
Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cs`:
- Around line 21-22: The regex pattern in the TaskMarker() method currently
allows zero or more spaces after the closing bracket followed by any content,
which causes false positives like [x]foo to be matched as task items. Modify the
regex pattern to require either whitespace followed by content, or no content at
all after the closing bracket, ensuring that task markers without a space
separator (like [x]foo) are not matched as valid task items.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cs`:
- Around line 59-60: The ChildLists property in ListNode currently creates a
detached snapshot of Items using ToList(), which means AST rewrites on that
collection don't update the original ListNode.Items. Remove the ToList() call
and instead return the actual Items collection directly (wrapped in an array for
the IEnumerable interface), ensuring it remains connected to the source. Then
update all list consumers and call sites (renderer, parser, etc.) to properly
cast and validate ListItemNode items as needed when working with this
collection.
---
Nitpick comments:
In
`@src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs`:
- Around line 63-77: Create a new test method in the BitMarkdownViewerTests
class to validate unsafe image URI sanitization, mirroring the structure of the
existing BitMarkdownViewerShouldSanitizeUnsafeLinks method. Render the
BitMarkdownViewer component with unsafe image markdown (e.g.,
)), then find the rendered img element using an
appropriate selector, retrieve its src attribute, and assert that the src is
either empty or does not contain the javascript scheme using the same validation
logic applied to link href attributes in the existing test.
🪄 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: e4c00a84-fbe1-46a8-87e7-4ccead6b203c
📒 Files selected for processing (41)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cs (1)
97-107:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle odd/even backslash parity when interpreting escaped table separators.
Line 104 and Line 107 currently treat any
\|/\`` pair as escaped, even when that backslash is itself escaped (e.g.\|`). This can mis-split cells and code-span state in edge cases.Proposed minimal fix
private static List<string> SplitRow(string line) { string s = line.Trim(); if (s.StartsWith('|')) s = s[1..]; - if (s.EndsWith('|') && !s.EndsWith("\\|")) s = s[..^1]; + if (s.EndsWith('|') && !IsEscapedOutsideCode(s, s.Length - 1)) s = s[..^1]; @@ - if (s[i] == '\\' && i + 1 < s.Length && s[i + 1] == '|' && backtickRun == 0) { sb.Append('|'); i++; } + if (s[i] == '\\' && i + 1 < s.Length && s[i + 1] == '|' && backtickRun == 0 && !IsEscapedOutsideCode(s, i)) { sb.Append('|'); i++; } @@ - else if (s[i] == '\\' && i + 1 < s.Length && s[i + 1] == '`' && backtickRun == 0) { sb.Append('\\'); sb.Append('`'); i++; } + else if (s[i] == '\\' && i + 1 < s.Length && s[i + 1] == '`' && backtickRun == 0 && !IsEscapedOutsideCode(s, i)) { sb.Append('\\'); sb.Append('`'); i++; } @@ cells.Add(sb.ToString()); return cells; } + + private static bool IsEscapedOutsideCode(string s, int index) + { + int slashCount = 0; + for (int j = index - 1; j >= 0 && s[j] == '\\'; j--) slashCount++; + return (slashCount & 1) == 1; + } }🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cs` around lines 97 - 107, The escape handling for pipe and backtick characters in the parsing loop does not account for backslash parity. Currently, the conditions checking s[i] == '\\' at line 104 and line 107 treat any backslash-pipe or backslash-backtick as escaped, even when the backslash itself is escaped (e.g., `\\|` or `\\``). To fix this, count the number of consecutive backslashes preceding the current position by iterating backwards from i, and only treat the pipe or backtick as escaped if the count of preceding backslashes is odd. Update both the condition check and the logic in the loop that processes these escape sequences in the ParseCells method.
🧹 Nitpick comments (3)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cs (1)
22-23: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider caching the wrapped array for repeated traversals.
The default
ChildListsimplementation allocates a new single-element array on every access whenChildNodesis non-null. While most container nodes will overrideChildListsdirectly (asListNodedoes), nodes relying on this default may experience allocation pressure during repeated AST traversals.However, given that this is a fallback for simple containers and the impact is limited, the current implementation is acceptable.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cs` around lines 22 - 23, The ChildLists property in the MarkdownNode class creates a new single-element array on each access when ChildNodes is non-null, causing unnecessary allocations during repeated AST traversals. To fix this, introduce a private cached field to store the wrapped array (initialized lazily when first needed), and modify the ChildLists property to return this cached array instead of creating a new one each time. This approach maintains the same behavior while reducing allocation pressure on nodes that rely on the default ChildLists implementation.src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cs (1)
133-138: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueCode duplication:
CountRunis defined in bothInlineProcessorandInlineHelpers.This identical method exists in
InlineHelpers.CountRun(lines 9-14). Consider removing the private copy here and using the shared utility.Suggested fix
- private static int CountRun(string s, int start, char c) - { - int j = start; - while (j < s.Length && s[j] == c) j++; - return j - start; - } + private static int CountRun(string s, int start, char c) => InlineHelpers.CountRun(s, start, c);🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cs` around lines 133 - 138, The CountRun method in InlineProcessor is duplicating functionality already available in InlineHelpers.CountRun. Remove the private CountRun method from InlineProcessor class and replace all calls to this.CountRun (or any local CountRun calls) with calls to InlineHelpers.CountRun to eliminate code duplication and use the shared utility method instead.src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs (1)
24-30: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueProtocol-relative URLs (
//) may pose security risk in mixed-content scenarios.Allowing
//example.com/pathpasses through unsanitized. If the page is served over HTTP, this could load content from an attacker-controlled HTTP source. Consider whether this is intentional for your use case.🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs` around lines 24 - 30, The UrlSanitizer.cs file allows protocol-relative URLs (those starting with "//") to pass through unsanitized in the condition that checks for StartsWith("#"), StartsWith("/"), StartsWith("./"), and StartsWith("../"), which creates a security risk in mixed-content scenarios. Remove the trimmed.StartsWith("//") check from this validation condition to prevent protocol-relative URLs from being automatically allowed without additional security validation, ensuring that such URLs are either blocked or handled through a more secure validation mechanism if they need to be supported.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs`:
- Around line 43-46: The scheme extraction in the UrlSanitizer uses individual
Replace calls to strip only specific control characters, but this is incomplete
and allows XSS bypasses because it misses other C0 control characters
(0x00–0x1F) and DEL (0x7F) that browsers normalize. Replace the manual character
replacement chain on the scheme variable with a regex pattern that removes all
ASCII C0 control characters and the DEL character in a single operation,
ensuring comprehensive sanitization according to the WHATWG URL Standard.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cs`:
- Line 87: The CopyTo method lacks proper bounds validation and will throw
IndexOutOfRangeException when the destination array lacks sufficient space,
violating the ICollection<T>.CopyTo contract which requires ArgumentException.
Add a validation check before the foreach loop in the CopyTo method that
verifies array.Length - arrayIndex is greater than or equal to items.Count, and
if this condition is not met, throw an ArgumentException with an appropriate
error message describing the insufficient space in the destination array.
- Around line 57-63: The ChildLists property in the ListNode class allocates a
new array and ListItemListView wrapper object on every access, creating
significant memory pressure during AST traversal. Cache this result by creating
a private field to store the wrapper array, initialize it lazily on first access
to ChildLists, and return the cached field on subsequent accesses instead of
creating new allocations each time.
---
Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cs`:
- Around line 97-107: The escape handling for pipe and backtick characters in
the parsing loop does not account for backslash parity. Currently, the
conditions checking s[i] == '\\' at line 104 and line 107 treat any
backslash-pipe or backslash-backtick as escaped, even when the backslash itself
is escaped (e.g., `\\|` or `\\``). To fix this, count the number of consecutive
backslashes preceding the current position by iterating backwards from i, and
only treat the pipe or backtick as escaped if the count of preceding backslashes
is odd. Update both the condition check and the logic in the loop that processes
these escape sequences in the ParseCells method.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cs`:
- Around line 133-138: The CountRun method in InlineProcessor is duplicating
functionality already available in InlineHelpers.CountRun. Remove the private
CountRun method from InlineProcessor class and replace all calls to
this.CountRun (or any local CountRun calls) with calls to InlineHelpers.CountRun
to eliminate code duplication and use the shared utility method instead.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cs`:
- Around line 24-30: The UrlSanitizer.cs file allows protocol-relative URLs
(those starting with "//") to pass through unsanitized in the condition that
checks for StartsWith("#"), StartsWith("/"), StartsWith("./"), and
StartsWith("../"), which creates a security risk in mixed-content scenarios.
Remove the trimmed.StartsWith("//") check from this validation condition to
prevent protocol-relative URLs from being automatically allowed without
additional security validation, ensuring that such URLs are either blocked or
handled through a more secure validation mechanism if they need to be supported.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cs`:
- Around line 22-23: The ChildLists property in the MarkdownNode class creates a
new single-element array on each access when ChildNodes is non-null, causing
unnecessary allocations during repeated AST traversals. To fix this, introduce a
private cached field to store the wrapped array (initialized lazily when first
needed), and modify the ChildLists property to return this cached array instead
of creating a new one each time. This approach maintains the same behavior while
reducing allocation pressure on nodes that rely on the default ChildLists
implementation.
🪄 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: c7dd095f-89a1-43a8-b140-19ae5c24793e
📒 Files selected for processing (41)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cs`:
- Around line 112-121: The backtickRun logic in the PipeTableExtension class
incorrectly enters code-span mode whenever any backtick run is encountered,
without verifying that a matching closing backtick run exists ahead in the
string. This causes unmatched backtick runs to be treated as code delimiters,
which then causes subsequent pipe characters to be treated as literal text
instead of cell delimiters, breaking table parsing. Before setting backtickRun
equal to runLength in the condition at line 116-117, add lookahead logic to scan
the remainder of the string to find a matching backtick run of the same length.
Only set backtickRun if such a matching run exists; otherwise, treat the
unmatched backtick run as plain text by appending it to sb without entering
code-span mode.
🪄 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: 105d7280-4605-4e06-b5a3-50f1dbd52af3
📒 Files selected for processing (42)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cs (1)
110-124: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winGuard against a no-progress trigger parser to prevent an infinite loop.
If a registered
InlineParser.TryParsereturnstruewithout advancingPos, thecontinuere-entersScan()at the same offset forever. Core parsers advance, butInlineParseris a public plugin extension point, so a third-party parser could hang the render thread.🛡️ Proposed defensive guard
foreach (var p in parsers) { if (p.TryParse(this)) { + if (Pos <= save) + throw new InvalidOperationException( + $"Inline parser '{p.GetType().Name}' reported success without advancing the scan position."); handled = true; break; } Pos = save; // parser must not have advanced on failure, but be safe }🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cs` around lines 110 - 124, The code currently has a potential infinite loop vulnerability where if a parser's TryParse method in the foreach loop returns true without advancing the Pos position, the continue statement will re-enter the outer Scan loop at the same position indefinitely. To fix this, after setting handled to true and breaking from the foreach loop (within the if block checking if TryParse returns true), add a guard that verifies Pos has actually advanced compared to the save position before allowing the continue. If a parser returns true without advancing Pos, either advance Pos by 1 or treat it as an unhandled case to prevent infinite loops from third-party plugin parsers.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cs`:
- Around line 222-297: The list parsing logic in the while loop starting at i <
lines.Count only checks whether items are ordered or bullet (via the ordered
flag), but does not track the specific marker character (-, *, + for bullets, or
. vs ) for ordered lists). To fix this, capture and store the specific marker
character from the first matched item by extracting it from the regex match
groups (group 1 contains the number for ordered lists, groups 2-3 contain the
bullet character), then modify the IsSameMarker checks throughout the loop to
verify the specific marker character matches the first item's marker, not just
the list type. When the marker character differs, break to create a separate
list item, ensuring - a followed by * b creates two distinct lists as per
CommonMark specification.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cs`:
- Around line 14-96: The openersBottom dictionary stores integer indices as
values, but the tokens list is mutated during iteration via RemoveRange, Insert,
and RemoveAt operations, causing cached index values to become stale and
invalid. Instead of storing indices in openersBottom, store references to the
Tok objects themselves, making the cache valid across list mutations. Update the
dictionary initialization, the TryGetValue lookups, and the assignments
throughout the ResolveDelimiters method to use Tok object references rather than
integer indices as the cached values.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cs`:
- Around line 110-124: The code currently has a potential infinite loop
vulnerability where if a parser's TryParse method in the foreach loop returns
true without advancing the Pos position, the continue statement will re-enter
the outer Scan loop at the same position indefinitely. To fix this, after
setting handled to true and breaking from the foreach loop (within the if block
checking if TryParse returns true), add a guard that verifies Pos has actually
advanced compared to the save position before allowing the continue. If a parser
returns true without advancing Pos, either advance Pos by 1 or treat it as an
unhandled case to prevent infinite loops from third-party plugin parsers.
🪄 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: 16d740ed-2171-4b79-893f-ec47618d9e17
📒 Files selected for processing (42)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/AutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/Bundles.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/EmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/PipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/StrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/TaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/Abstractions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/AstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreBlockParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/CoreInlineParsers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/DelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/InlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/UrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/CoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/MarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/CoreNodes.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/MarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cs (1)
19-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd column scope on header cells for better table accessibility.
At Line 19, add
scope="col"to<th>so assistive tech can map header-to-cell relationships more reliably.Proposed patch
for (int c = 0; c < table.Header.Count; c++) { b.OpenElement(r.NextSeq(), "th"); + b.AddAttribute(r.NextSeq(), "scope", "col"); AddAlignment(r, b, table, c); r.WriteNodes(b, table.Header[c]); b.CloseElement(); }🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cs` around lines 19 - 23, The table header cells in the BitMarkdownViewerTableRenderer are missing the scope attribute for accessibility. After the b.OpenElement(r.NextSeq(), "th") call that opens the header cell, add an attribute call to set scope="col" on the th element. This attribute should be added before writing the header content with r.WriteNodes, so that assistive technologies can properly map the header-to-cell relationships.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cs`:
- Around line 13-17: The content extraction from m.Groups[2].Value in the
BitMarkdownViewerAtxHeadingParser is not properly handling trailing hash symbols
according to markdown specification. Currently, trailing `#` characters are
being stripped too aggressively regardless of whether they are preceded by
whitespace. Modify the content assignment to only strip trailing `#` characters
when they are preceded by whitespace (e.g., `foo ###` should become `foo`, but
`foo###` should remain as is). This likely requires adding conditional logic to
check for whitespace before the trailing hash symbols before removing them,
rather than using a simple Trim() call.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cs`:
- Around line 15-16: The autolink validation in
BitMarkdownViewerAutolinkInlineParser on line 16 only checks for literal space
characters using inner.Contains(' '), but it should reject all whitespace
characters including tabs and newlines. Replace the inner.Contains(' ') check
with inner.Any(char.IsWhiteSpace) to properly validate that no whitespace of any
kind is present inside the autolink content.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cs`:
- Around line 14-15: The Fence() method regex pattern currently uses [^`]* to
match the info string, which prevents any backticks in the info segment for both
backtick and tilde fence types. This is too restrictive for tilde fences where
backticks should be allowed. Modify the regex pattern in the Fence() method to
use alternation that separately handles backtick fences and tilde fences,
allowing the tilde fences to permit backticks in their info strings while
keeping the backtick fences restrictive. This can be done by using a pattern
like (`{3,})[^\n`]* for backtick fences and (~{3,})[^\n]* for tilde fences,
combined with the alternation operator.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cs`:
- Around line 18-35: The cache key for the openersBottom dictionary at the line
where the key is assigned (using the pattern `(dc, closer.Count % 3)`) is
incomplete and does not account for the closer's ability to open. According to
CommonMark delimiter resolution rules, closers that can also open must use a
separate opener-bottom bucket from those that cannot. Modify the key tuple to
include the closer.CanOpen property alongside the delimiter character and count
modulo 3. This same issue also appears in a similar section handling openers, so
apply the same fix there as well by including the opener's CanOpen property in
that cache key tuple.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cs`:
- Line 30: The Content property assignment in
BitMarkdownViewerIndentedCodeBlockParser uses TrimEnd('\n') which is not
CRLF-safe, leaving trailing carriage returns when content uses CRLF line
endings. Replace the TrimEnd('\n') call with a trimming approach that handles
both CR and LF characters (for example, TrimEnd with both '\r' and '\n'
characters), ensuring the method safely removes trailing newlines regardless of
whether the line ending is LF or CRLF.
- Line 13: The condition in BitMarkdownViewerIndentedCodeBlockParser at the
entry check is not properly validating code block start lines. Currently, the
check only validates that the indentation is at least 4 spaces using
BitMarkdownViewerBlockProcessor.GetIndent, but it fails to verify that the line
contains actual content and is not just whitespace. To fix this, modify the
condition to also ensure that after removing the indentation, the remaining line
is not empty or blank before returning true to indicate a valid code block
start.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cs`:
- Around line 41-47: The BitMarkdownViewerLinkInlineParser class reparses the
link label using the full inline parser on line 46, which allows nested
BitMarkdownViewerLinkNode instances to be created inside a link, resulting in
invalid nested link structures. Either disable link parsing while parsing the
label text, or filter out any BitMarkdownViewerLinkNode instances from the
parsed inlines before adding them as children using link.Children.AddRange.
Choose whichever approach aligns with the parser's design, but ensure that
nested links cannot appear within a link's label.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cs`:
- Around line 23-25: The BitMarkdownViewerListParser currently requires
whitespace after list markers (via BitMarkdownViewerBlockGrammar.Ordered() and
BitMarkdownViewerBlockGrammar.Bullet() checks) to parse list items, which
violates CommonMark specification that allows marker-only items like "-" or "1."
without content. Modify the list detection logic in the parser to also match and
handle list markers without mandatory following whitespace, allowing empty list
items to be properly recognized and parsed. This same fix should be applied at
all occurrences where these grammar patterns are used for list detection.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParagraphParser.cs`:
- Around line 21-23: The condition in the setext heading detection is
incorrectly rejecting valid setext headings by requiring that the underline does
not match a thematic break pattern. In the setext matching block where you check
BitMarkdownViewerBlockGrammar.Setext().Match(l), remove the additional negated
thematic break check !BitMarkdownViewerBlockGrammar.ThematicBreak().IsMatch(l).
The setext underline should be recognized whenever setext.Success is true,
regardless of whether the underline could also technically match a thematic
break pattern. Simplify the condition to only verify setext.Success.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cs`:
- Around line 57-60: In the `Use` method of `BitMarkdownViewerPipelineBuilder`,
the `extension.Setup(this)` call occurs before the extension is added to the
`_extensions` collection. This allows cyclic or self-referential registrations
within the Setup method to bypass the duplicate type check on line 57 and cause
infinite recursion. Reorder the statements so that `_extensions.Add(extension)`
is called before `extension.Setup(this)`. This ensures that if Setup attempts to
register the same extension type again, the duplicate check will catch it and
return early, preventing the recursive cycle.
In
`@src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs`:
- Around line 72-77: The sanitization tests assume that unsafe anchor and image
elements will always exist in the markup, but a valid sanitizer behavior is to
remove unsafe nodes entirely. Make the tests robust by checking if the elements
exist before accessing their attributes. For both the anchor element test
(around line 72 using component.Find(".bit-mdv a")) and the image element test
(around line 89), first verify that the element exists; if it doesn't exist,
that is a secure outcome (element was removed) and the test should pass, and
only if the element exists should you verify that its href or src attributes are
safe.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cs`:
- Around line 19-23: The table header cells in the
BitMarkdownViewerTableRenderer are missing the scope attribute for
accessibility. After the b.OpenElement(r.NextSeq(), "th") call that opens the
header cell, add an attribute call to set scope="col" on the th element. This
attribute should be added before writing the header content with r.WriteNodes,
so that assistive technologies can properly map the header-to-cell
relationships.
🪄 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: ea49fb9c-d55a-41df-8397-ab0f929714ca
📒 Files selected for processing (87)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerColumnAlignment.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerGitHubFlavoredExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerCodeSpanInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEmphasisDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEscapeInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLineBreakInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParagraphParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerThematicBreakParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerUrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerNodeRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerBlockquoteNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeBlockNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeSpanNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerDocumentNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerEmphasisNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerHeadingNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerImageNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLineBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLinkNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemListView.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerMarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerParagraphNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerStrongNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerTextNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerThematicBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cs (1)
5-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueClass doc contradicts the dispatch order. Line 6 says nodes are dispatched to the "first matching" renderer, but
WriteNodeiterates from the last registered renderer downward (last-wins), as line 26 and the pipeline builder's ordering note confirm. Fix the summary to avoid misleading future extension authors.📝 Suggested doc fix
-/// Walks an AST and dispatches each node to the first matching <see cref="BitMarkdownViewerNodeRenderer"/>. +/// Walks an AST and dispatches each node to the last registered matching <see cref="BitMarkdownViewerNodeRenderer"/> +/// (so plugin renderers can override core ones).🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cs` around lines 5 - 8, The class summary for BitMarkdownViewerMarkdownRenderer is inaccurate because WriteNode uses a last-wins renderer lookup rather than the “first matching” renderer described in the doc. Update the XML summary to match the actual dispatch behavior and align it with the renderer selection logic in WriteNode and the ordering implied by the pipeline builder so future contributors are not misled.src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cs (1)
6-18: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider
Lazy<T>for thread-safe pipeline caching.
_gitHub/_advancedare lazily initialized via??=without synchronization. Under concurrent renders (e.g. multiple Blazor Server circuits) two threads can build the pipeline simultaneously. The reference assignment is atomic so this is benign (duplicate build, last writer wins), butLazy<T>expresses the intent and avoids the redundant construction.♻️ Optional: use Lazy<T>
- private static BitMarkdownViewerPipeline? _gitHub; - private static BitMarkdownViewerPipeline? _advanced; + private static readonly Lazy<BitMarkdownViewerPipeline> _gitHub = + new(() => new BitMarkdownViewerPipelineBuilder().UseGitHubFlavored().Build()); + private static readonly Lazy<BitMarkdownViewerPipeline> _advanced = + new(() => new BitMarkdownViewerPipelineBuilder().UseAdvanced().Build()); @@ - public static BitMarkdownViewerPipeline GitHub - => _gitHub ??= new BitMarkdownViewerPipelineBuilder().UseGitHubFlavored().Build(); + public static BitMarkdownViewerPipeline GitHub => _gitHub.Value; @@ - public static BitMarkdownViewerPipeline Advanced - => _advanced ??= new BitMarkdownViewerPipelineBuilder().UseAdvanced().Build(); + public static BitMarkdownViewerPipeline Advanced => _advanced.Value;🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cs` around lines 6 - 18, The GitHub and Advanced pipeline caches in BitMarkdownViewerPipelines are lazily initialized with unsynchronized nullable fields, so concurrent access can trigger duplicate builds. Replace the _gitHub and _advanced backing fields with Lazy<BitMarkdownViewerPipeline> (or an equivalent thread-safe lazy pattern) and keep the existing GitHub and Advanced accessors as the single entry points that return the cached pipeline built via BitMarkdownViewerPipelineBuilder.UseGitHubFlavored and UseAdvanced.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cs`:
- Around line 61-80: `BitMarkdownViewer.BuildRenderTree` is using
runtime-generated sequence numbers via `renderer.NextSeq()`, which breaks
Blazor’s requirement for static sequence literals. Replace every
`renderer.NextSeq()` call in this render method with fixed, consecutive integer
values in source order, keeping the existing attribute and element call
structure unchanged. Use `BuildRenderTree` and
`EffectivePipeline.CreateRenderer()` to locate the render logic and make sure
the sequence numbers remain stable across renders.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiAstProcessor.cs`:
- Around line 15-29: The public static Emoji dictionary in
BitMarkdownViewerEmojiAstProcessor creates process-wide mutable state, so one
caller’s changes affect every rendering instance. Move emoji overrides into
per-pipeline/per-processor configuration, or have
BitMarkdownViewerEmojiAstProcessor snapshot an immutable lookup when it is
constructed instead of exposing a shared writable ConcurrentDictionary. Keep the
lookup localized to the emoji processing path so customization does not leak
across circuits or components.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cs`:
- Around line 72-80: The delimiter resolution in
BitMarkdownViewerDelimiterResolver.TryCreate is skipping the closer after an
unsupported opener candidate, which can hide an earlier valid opener. When
processor.TryCreate returns no node for the current opener/closer pair, do not
advance past the closer as a final decision; instead keep searching for another
opener by continuing the opener scan while preserving the closer for later
matches. Update the logic around openersBottom, closer.Active, and closerIdx so
a rejected candidate only disqualifies that opener/closer pairing, not the whole
closer.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.cs`:
- Around line 25-44: The fenced code block parser is producing OS-dependent
content because BitMarkdownViewerFencedCodeBlockParser uses
StringBuilder.AppendLine while building the code block text. Update the parsing
loop in BitMarkdownViewerFencedCodeBlockParser so it appends a consistent
newline character explicitly instead of relying on Environment.NewLine, and keep
the final TrimTrailingNewline call as-is. This fix should be applied where the
code block content is accumulated before creating
BitMarkdownViewerCodeBlockNode, to ensure rendered output is stable across
platforms.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cs`:
- Around line 8-11: The IBitMarkdownViewerExtension contract currently allows
registering components that may hold mutable state even though
BitMarkdownViewerPipeline caches and shares them across requests. Update the
contract around Setup(BitMarkdownViewerPipelineBuilder) to explicitly require
stateless, thread-safe extensions/components, and if that cannot be guaranteed,
change the pipeline behavior so extension instances are not reused across
executions. Ensure the guidance is reflected where
BitMarkdownViewerPipelineBuilder is used so implementers know the
shared-instance requirement.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerMarkdownNode.cs`:
- Around line 22-24: The lazy cache in BitMarkdownViewerMarkdownNode.ChildLists
is not thread-safe and can race during concurrent access. Fix the property by
removing the _childLists cache entirely or replacing it with a thread-safe
initialization approach such as Lazy<T> or Interlocked.CompareExchange, keeping
the ChildNodes/ChildLists behavior unchanged while avoiding unsynchronized
writes to _childLists.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cs`:
- Around line 6-18: The GitHub and Advanced pipeline caches in
BitMarkdownViewerPipelines are lazily initialized with unsynchronized nullable
fields, so concurrent access can trigger duplicate builds. Replace the _gitHub
and _advanced backing fields with Lazy<BitMarkdownViewerPipeline> (or an
equivalent thread-safe lazy pattern) and keep the existing GitHub and Advanced
accessors as the single entry points that return the cached pipeline built via
BitMarkdownViewerPipelineBuilder.UseGitHubFlavored and UseAdvanced.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cs`:
- Around line 5-8: The class summary for BitMarkdownViewerMarkdownRenderer is
inaccurate because WriteNode uses a last-wins renderer lookup rather than the
“first matching” renderer described in the doc. Update the XML summary to match
the actual dispatch behavior and align it with the renderer selection logic in
WriteNode and the ordering implied by the pipeline builder so future
contributors are not misled.
🪄 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: b7c3ae65-44ae-45b6-b967-4aa730799d14
📒 Files selected for processing (87)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerColumnAlignment.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerGitHubFlavoredExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerCodeSpanInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEmphasisDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEscapeInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLineBreakInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParagraphParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerThematicBreakParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerUrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerNodeRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerBlockquoteNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeBlockNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeSpanNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerDocumentNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerEmphasisNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerHeadingNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerImageNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLineBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLinkNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemListView.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerMarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerParagraphNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerStrongNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerTextNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerThematicBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cs (1)
13-13: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winBlank indented line can still start an empty code block.
A line of only indentation (e.g. four spaces) has an indent ≥ 4, so it passes this entry check and produces an empty code block. CommonMark indented code blocks cannot begin with a blank line. This was previously flagged and does not appear to be addressed in the current code.
Proposed fix
- if (BitMarkdownViewerBlockProcessor.GetIndent(lines[state.Line]) < 4) return false; + if (BitMarkdownViewerBlockProcessor.GetIndent(lines[state.Line]) < 4 + || BitMarkdownViewerBlockProcessor.IsBlank(lines[state.Line])) return false;🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cs` at line 13, The entry check in BitMarkdownViewerIndentedCodeBlockParser.ParseBlock currently treats any line with indent 4 or more as a valid start, which allows a blank indented line to open an empty code block. Update the initial guard in ParseBlock to also reject lines that are blank after indentation, using the existing GetIndent and line content checks around state.Line, so indented code blocks only start from non-empty content.
🧹 Nitpick comments (3)
src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scss (1)
34-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a theme variable for the editor/preview border color.
#8a8886is hardcoded, while the component stylesheet uses theme tokens (e.g.$clr-brd-*). The fixed color won't adapt to dark theme, leaving an inconsistent border in the playground.🤖 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/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scss` around lines 34 - 58, The editor and preview border color is hardcoded in the Markdown viewer demo stylesheet, so it won’t adapt to theme changes. Update the .mdv-editor and .mdv-preview styles to use an existing theme token or shared border variable consistent with the other component styles (for example the same $clr-brd-* family used elsewhere), keeping the border appearance theme-aware in BitMarkdownViewerDemo.razor.scss.src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cs (1)
10-13: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value
Basiclazy initialization is not thread-safe.The class doc states pipelines are thread-safe and shared, but
_basic ??= ...can run the builder multiple times under concurrent first access (e.g. multiple Blazor Server circuits). The result is immutable so this is benign, but you can avoid the redundant build withLazy<T>.♻️ Use Lazy<T> for thread-safe single initialization
- private static BitMarkdownViewerPipeline? _basic; - - /// <summary>A pipeline with only the basic CommonMark core (no flavors).</summary> - public static BitMarkdownViewerPipeline Basic => _basic ??= new BitMarkdownViewerPipelineBuilder().Build(); + private static readonly Lazy<BitMarkdownViewerPipeline> _basic = + new(() => new BitMarkdownViewerPipelineBuilder().Build()); + + /// <summary>A pipeline with only the basic CommonMark core (no flavors).</summary> + public static BitMarkdownViewerPipeline Basic => _basic.Value;🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cs` around lines 10 - 13, The Basic pipeline initialization in BitMarkdownViewerPipeline is not thread-safe because `_basic ??= ...` can build the instance multiple times under concurrent first access. Replace the current lazy field and `Basic` property logic with a thread-safe `Lazy<BitMarkdownViewerPipeline>`-based initialization so the pipeline is created only once even when accessed concurrently.src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cs (1)
113-127: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winRollback parser side effects when an attempt is ignored.
The loop resets only
Pos. If an inline parser appends/flushed content and then returnsfalse, or returnstruewithout advancing, those side effects remain and corrupt the token stream.Proposed fix
foreach (var p in parsers) { + string literalSnapshot = _literal.ToString(); + int tokenCount = _tokens.Count; + if (p.TryParse(this)) { - handled = true; - break; + if (Pos > save) + { + handled = true; + break; + } } - Pos = save; // parser must not have advanced on failure, but be safe + + Pos = save; + _literal.Clear(); + _literal.Append(literalSnapshot); + if (_tokens.Count > tokenCount) + _tokens.RemoveRange(tokenCount, _tokens.Count - tokenCount); }🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cs` around lines 113 - 127, The parser retry logic in BitMarkdownViewerInlineProcessor.TryParse only restores Pos, so ignored parser attempts can still leave behind appended/flushed content and corrupt the token stream. Update the loop around parsers in BitMarkdownViewerInlineProcessor to snapshot and restore any parser side effects when TryParse fails or when it reports success without advancing, not just Pos; use the existing handled/Pos checks to ensure only a parser that both succeeds and consumes input is honored.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cs`:
- Around line 20-27: Task-marker detection in
BitMarkdownViewerTaskListAstProcessor is happening too late in the AST pipeline,
so escaped literals can be misread as real checkboxes. Move the recognition
logic out of the post-inline pass that inspects
BitMarkdownViewerParagraphNode/BitMarkdownViewerTextNode, and instead detect
task markers from the raw list-item source or from a parser flag attached to the
list item before inline flattening occurs. Keep the checkbox insertion and text
trimming behavior, but only apply it when the original list item was explicitly
marked as a task.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockProcessor.cs`:
- Around line 76-85: The StripIndent method currently removes an entire tab even
when only part of its visual width is needed, so mixed indentation can be
over-stripped. Update BitMarkdownViewerBlockProcessor.StripIndent to track the
tab’s visual width and only consume the portion needed to reach the requested
column count, preserving any remaining columns as leading spaces in the returned
substring. Keep the fix localized to StripIndent and validate behavior for
inputs like a tab followed by text with a smaller count.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cs`:
- Around line 78-92: FindLabelEnd in BitMarkdownViewerLinkInlineParser currently
counts every closing bracket, so it can terminate a link label inside backtick
code spans. Update this parser helper to track and skip inline code span regions
while scanning, so `]` characters enclosed by backticks are ignored unless they
truly close the label. Keep the existing escaping and nesting behavior intact
and adjust any related label parsing logic in BitMarkdownViewerLinkInlineParser
if needed.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cs`:
- Around line 61-62: The registration flow in BitMarkdownViewerPipelineBuilder
is leaving the builder in a poisoned state when extension.Setup(this) fails
because _extensions is updated before setup completes. Update the extension
registration path so the extension is only kept in _extensions after Setup
succeeds, or otherwise remove it from _extensions in the failure path inside the
same extension registration method to allow retries and avoid partial
registration.
---
Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cs`:
- Line 13: The entry check in
BitMarkdownViewerIndentedCodeBlockParser.ParseBlock currently treats any line
with indent 4 or more as a valid start, which allows a blank indented line to
open an empty code block. Update the initial guard in ParseBlock to also reject
lines that are blank after indentation, using the existing GetIndent and line
content checks around state.Line, so indented code blocks only start from
non-empty content.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cs`:
- Around line 113-127: The parser retry logic in
BitMarkdownViewerInlineProcessor.TryParse only restores Pos, so ignored parser
attempts can still leave behind appended/flushed content and corrupt the token
stream. Update the loop around parsers in BitMarkdownViewerInlineProcessor to
snapshot and restore any parser side effects when TryParse fails or when it
reports success without advancing, not just Pos; use the existing handled/Pos
checks to ensure only a parser that both succeeds and consumes input is honored.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cs`:
- Around line 10-13: The Basic pipeline initialization in
BitMarkdownViewerPipeline is not thread-safe because `_basic ??= ...` can build
the instance multiple times under concurrent first access. Replace the current
lazy field and `Basic` property logic with a thread-safe
`Lazy<BitMarkdownViewerPipeline>`-based initialization so the pipeline is
created only once even when accessed concurrently.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scss`:
- Around line 34-58: The editor and preview border color is hardcoded in the
Markdown viewer demo stylesheet, so it won’t adapt to theme changes. Update the
.mdv-editor and .mdv-preview styles to use an existing theme token or shared
border variable consistent with the other component styles (for example the same
$clr-brd-* family used elsewhere), keeping the border appearance theme-aware in
BitMarkdownViewerDemo.razor.scss.
🪄 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: 8ae9d480-334a-4762-9728-a90626c34daf
📒 Files selected for processing (87)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerColumnAlignment.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerGitHubFlavoredExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerCodeSpanInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEmphasisDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEscapeInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLineBreakInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParagraphParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerThematicBreakParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerUrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerNodeRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerBlockquoteNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeBlockNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeSpanNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerDocumentNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerEmphasisNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerHeadingNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerImageNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLineBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLinkNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemListView.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerMarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerParagraphNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerStrongNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerTextNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerThematicBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 14
♻️ Duplicate comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cs (1)
63-77: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftNested markdown nodes still use runtime sequence numbers.
Line 77 delegates into
BitMarkdownViewerMarkdownRenderer.WriteNodes, and that path still assigns sequences viaNextSeq()for every nested node. The literals here only fix the outer<div>; the markdown body still misses Blazor's stable-sequence contract, so rerender diffing stays on the slow path.Verify by locating the remaining runtime sequence calls in the renderer stack; you should still see hits under the markdown renderer and node renderers.
#!/bin/bash rg -n '\bNextSeq\s*\(' src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer --iglob '*Renderer*.cs' -C1🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cs` around lines 63 - 77, The outer BitMarkdownViewer builder now uses stable literals, but the nested markdown rendering path still relies on runtime sequence generation. Update BitMarkdownViewerMarkdownRenderer.WriteNodes and the node renderer stack it calls so nested elements use fixed, deterministic sequence numbers instead of NextSeq(), matching Blazor’s stable-sequence contract throughout the markdown body.
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cs (1)
68-74: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winReuse the shared URL sanitizer for generated autolinks.
These
hrefvalues are created outside the normal inline-link path, so explicit links and autolinks can drift if only one side appliesBitMarkdownViewerUrlSanitizer. Runhrefthrough the shared sanitizer before assigningBitMarkdownViewerLinkNode.Url.🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cs` around lines 68 - 74, The autolink generation in BitMarkdownViewerAutoLinkAstProcessor builds href values without the shared sanitization path, so autolinks can differ from normal links. Update the link creation flow in the autolink processor to pass the computed href through BitMarkdownViewerUrlSanitizer before assigning it to BitMarkdownViewerLinkNode.Url, keeping the existing matched/www/email handling intact.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiExtension.cs`:
- Around line 12-17: The BitMarkdownViewerEmojiExtension constructor is storing
the passed IReadOnlyDictionary reference directly, which leaves
BitMarkdownViewerEmojiAstProcessor configuration tied to mutable external state.
Snapshot the emoji overrides inside BitMarkdownViewerEmojiExtension by copying
the incoming overrides into a new dictionary before assigning to the field, and
keep Setup using that stored snapshot when creating
BitMarkdownViewerEmojiAstProcessor.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cs`:
- Around line 46-55: The table body parsing in
BitMarkdownViewerPipeTableBlockParser is too permissive and keeps consuming
lines that should start a new block. Update the row loop in the parser logic to
stop not only on blank lines, but also when the next line is a block starter
(for example headings, quotes, lists, etc.), rather than relying on
Contains('|') alone. Use the existing BitMarkdownViewerBlockProcessor helpers
and the parser’s SplitRow/state.ParseInlines flow so normal table rows still
parse correctly while the next block is preserved.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cs`:
- Around line 30-33: The autolink logic in BitMarkdownViewerAutolinkInlineParser
is too permissive because the email branch uses a simple Contains('@') check,
which turns invalid `<...>` tokens into mailto links. Update the parser’s
email-detection branch in the method that calls Emit to validate the candidate
against the email-autolink grammar first, and only emit a mailto link when the
address is actually valid; otherwise leave the token as plain text.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cs`:
- Around line 42-46: The blockquote marker stripping in StripMarker only removes
an optional space after the leading marker, so tabs remain and break nested
parsing. Update BitMarkdownViewerBlockquoteParser.StripMarker to treat the first
character after the marker as either a space or a tab and strip it consistently,
preserving the remaining inner text for CommonMark blockquote parsing.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cs`:
- Around line 53-60: The rule-of-three check in
BitMarkdownViewerDelimiterResolver should not run for every delimiter processor,
because it can reject valid non-emphasis pairs before TryCreate(...) has a
chance to validate them. Update the delimiter matching logic in the resolver to
scope the CommonMark % 3 rule only to emphasis delimiters like * and _ (or
delegate it to a processor-specific hook), while keeping the generic pairing
flow intact for other processors such as ~~.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEscapeInlineParser.cs`:
- Around line 12-15: The BitMarkdownViewerEscapeInlineParser currently only
treats a backslash followed by LF as a hard break, so CRLF sequences are not
recognized. Update the escape handling in BitMarkdownViewerEscapeInlineParser to
also consume Windows line endings by checking for '\r\n' after the backslash, or
normalize line endings before this parser runs, and make sure the parser
advances state.Pos correctly for the consumed sequence.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cs`:
- Around line 24-26: The indented code block parser in
BitMarkdownViewerIndentedCodeBlockParser should not use AppendLine because it
reintroduces Environment.NewLine and makes parsed Content host-dependent. Update
the parsing loop to append explicit newline characters consistently, matching
the fenced code block normalization behavior already used elsewhere in the
markdown viewer parsing code, so the output stays stable across platforms.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cs`:
- Around line 57-70: The list parser in BitMarkdownViewerListParser is consuming
all spaces after the marker into markerIndent, which drops overflow spaces that
should remain in the item content. Update the marker handling in the ordered and
unordered branches so only the required separator space is counted as
indentation, and preserve any extra spaces by prepending them to firstContent
when computing the item’s initial content. Use the existing marker parsing logic
in BitMarkdownViewerBlockProcessor.GetIndent and the m.Groups values to keep the
content alignment correct.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cs`:
- Around line 61-70: The rollback in the Markdown viewer pipeline builder only
removes the extension from _extensions when extension.Setup(this) throws, but
any parsers, processors, or renderers registered during that setup still remain.
Update the extension registration flow in BitMarkdownViewerPipelineBuilder so
the catch fully reverts all mutations made by Setup, not just _extensions, by
tracking and undoing any additions made before the exception and keeping the
builder reusable after a failed setup.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cs`:
- Around line 8-15: The remarks in IBitMarkdownViewerExtension currently soften
the thread-safety contract by suggesting a “fresh instance per registration”
fallback, which is still unsafe because the built BitMarkdownViewerPipeline
caches and reuses registrations across parses and renders. Update the
documentation to state that anything registered from Setup must be stateless and
thread-safe, and that any mutable per-parse/per-render state must live only in
the passed state/builder objects; remove the guidance implying a stateful
parser/renderer can be made safe just by instantiating it in Setup().
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cs`:
- Around line 34-35: The fenced-code language class generation in
BitMarkdownViewerCoreRenderer’s code-block rendering only splits code.Info on a
literal space, so tabs and other whitespace can leak metadata into the class
name. Update the logic that builds the class attribute for code.Info to take the
first non-empty token across any whitespace before prefixing it with language-,
so markdown fences like the ones handled in the renderer produce a clean
language identifier.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerHeadingNode.cs`:
- Line 6: The public BitMarkdownViewerHeadingNode.Level property currently
allows any int even though it is documented to only support 1-6, so update the
init accessor to enforce that range and prevent invalid node state. Apply the
validation directly on Level in BitMarkdownViewerHeadingNode so invalid values
are rejected at construction time, keeping downstream markdown heading
generation consistent.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cs`:
- Around line 204-239: The published MarkdownViewer playground snippet is out of
sync with the live demo, so update the sample strings to match the actual UI
shown in BitMarkdownViewerDemo, including the wrapper layout, hint text, and the
Reset/Clear actions. Edit example3RazorCode and example3CsharpCode together so
the copied snippet reproduces the same toolbar, editor, preview, and supporting
controls as the rendered example. Keep the snippet aligned with the current
markup and behaviors around SetPlaygroundFlavor and the playground state
variables.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scss`:
- Around line 34-58: The split-pane styles for .mdv-editor and .mdv-preview need
to allow flex items to shrink, otherwise long content can expand the row. Update
the BitMarkdownViewerDemo styling so both panes explicitly opt into shrinking in
the flex layout by adding the appropriate minimum width constraint in the
.mdv-editor and .mdv-preview rules, keeping the content scrollable within each
pane rather than forcing overflow of the container.
---
Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cs`:
- Around line 63-77: The outer BitMarkdownViewer builder now uses stable
literals, but the nested markdown rendering path still relies on runtime
sequence generation. Update BitMarkdownViewerMarkdownRenderer.WriteNodes and the
node renderer stack it calls so nested elements use fixed, deterministic
sequence numbers instead of NextSeq(), matching Blazor’s stable-sequence
contract throughout the markdown body.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cs`:
- Around line 68-74: The autolink generation in
BitMarkdownViewerAutoLinkAstProcessor builds href values without the shared
sanitization path, so autolinks can differ from normal links. Update the link
creation flow in the autolink processor to pass the computed href through
BitMarkdownViewerUrlSanitizer before assigning it to
BitMarkdownViewerLinkNode.Url, keeping the existing matched/www/email handling
intact.
🪄 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: 795b456f-36ad-458f-bb77-bff99e2e82c0
📒 Files selected for processing (87)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerColumnAlignment.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerGitHubFlavoredExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerCodeSpanInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEmphasisDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEscapeInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLineBreakInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParagraphParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerThematicBreakParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerUrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerNodeRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerBlockquoteNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeBlockNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeSpanNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerDocumentNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerEmphasisNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerHeadingNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerImageNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLineBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLinkNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemListView.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerMarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerParagraphNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerStrongNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerTextNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerThematicBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cs (1)
34-37: 🩺 Stability & Availability | 🟠 MajorWhitespace-only info string can throw
IndexOutOfRangeException.The parser in
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.csassigns theInfostring if it is not null or empty. It usesIsNullOrEmptyat line 43, which allows strings containing only whitespace (e.g., " ", "\t") to be set as theInfovalue.In the renderer (
BitMarkdownViewerCoreRenderer.cslines 34-36), the guardIsNullOrEmptyreturns false for whitespace-only strings. Consequently,SplitwithRemoveEmptyEntriesproduces an empty array, causing[0]to throwIndexOutOfRangeExceptionand crash the rendering.Fix: Change the guard to
IsNullOrWhiteSpacein the renderer.Proposed fix
- if (!string.IsNullOrEmpty(code.Info)) - b.AddAttribute(r.NextSeq(), "class", "language-" - + code.Info.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)[0]); + if (!string.IsNullOrWhiteSpace(code.Info)) + b.AddAttribute(r.NextSeq(), "class", "language-" + + code.Info.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)[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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cs` around lines 34 - 37, The Markdown renderer in BitMarkdownViewerCoreRenderer is only checking code.Info with IsNullOrEmpty, so whitespace-only info strings can still reach the language-class logic and make Split(...)[0] throw IndexOutOfRangeException. Update the guard in the rendering path that builds the "language-" CSS class to use IsNullOrWhiteSpace instead, keeping the existing code.Info handling in BitMarkdownViewerCoreRenderer consistent with the parser output from BitMarkdownViewerFencedCodeBlockParser.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cs`:
- Around line 24-34: The autolink scheme check in
BitMarkdownViewerAutolinkInlineParser is too permissive and allows
single-character schemes like drive letters to be emitted as links. Tighten the
validation in the parser’s colon/scheme guard so it requires a minimum scheme
length of 2 before calling Emit, keeping the existing character-class checks in
place.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor`:
- Around line 31-39: The flavor selector buttons in BitMarkdownViewerDemo.razor
only indicate the active choice visually through Variant, so add an accessible
pressed state to each BitButton using aria-pressed or the BitButton equivalent
and bind it to playgroundFlavor in the toggle group. Update the
example3RazorCode snippet to match the same button markup and active-state
behavior so the published code sample stays aligned with the demo.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cs`:
- Around line 234-249: The published playground snippet is missing the
SampleMarkdown definition, so the BitMarkdownViewerDemo.razor.cs example is not
self-contained and will not compile when copied. Update the example around
playgroundMarkdown and ResetPlaygroundSample to either declare SampleMarkdown
within the snippet or replace those references with the inline sample text
directly, keeping the names SetPlaygroundFlavor and ResetPlaygroundSample
intact.
---
Duplicate comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cs`:
- Around line 34-37: The Markdown renderer in BitMarkdownViewerCoreRenderer is
only checking code.Info with IsNullOrEmpty, so whitespace-only info strings can
still reach the language-class logic and make Split(...)[0] throw
IndexOutOfRangeException. Update the guard in the rendering path that builds the
"language-" CSS class to use IsNullOrWhiteSpace instead, keeping the existing
code.Info handling in BitMarkdownViewerCoreRenderer consistent with the parser
output from BitMarkdownViewerFencedCodeBlockParser.
🪄 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: 7db8974c-a756-46c4-b450-ec7dcef1fcfc
📒 Files selected for processing (87)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerColumnAlignment.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerGitHubFlavoredExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerCodeSpanInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEmphasisDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEscapeInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLineBreakInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParagraphParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerThematicBreakParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerUrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerNodeRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerBlockquoteNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeBlockNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeSpanNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerDocumentNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerEmphasisNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerHeadingNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerImageNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLineBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLinkNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemListView.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerMarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerParagraphNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerStrongNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerTextNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerThematicBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
|
@coderabbitai re-review |
|
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (1)
src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cs (1)
13-29: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftUse stable sequence scopes instead of one global counter.
NextSeq()currently advances across the entire document, so inserting a node near the top shifts the sequence numbers for everything after it. WithRenderTreeBuilder, that throws away Blazor's diff-location hints and can cause much larger rerenders on long markdown documents. Consider regioning each rendered node/list item and resetting inner numbering, or otherwise making sequence numbers stable per render callsite instead of per render pass.🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cs` around lines 13 - 29, `BitMarkdownViewerMarkdownRenderer.NextSeq` currently uses one global counter for the whole document, which makes sequence numbers shift for all later content when earlier nodes change. Update `WriteNodes` and `WriteNode` to use stable per-callsite sequence scopes with `RenderTreeBuilder` regions, resetting numbering inside each node/list item so sequence values stay local and deterministic. Keep the fix centered around `BitMarkdownViewerMarkdownRenderer` and its rendering flow rather than a single shared `_seq` across the entire render pass.
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scss`:
- Around line 105-131: Update the BitMarkdownViewer SCSS rules to use logical
properties so they respect rtl rendering from BitMarkdownViewer; replace
left-specific spacing/border declarations in the blockquote, list, and
.task-list-item-checkbox styles with logical equivalents so the layout flips
correctly when dir="rtl" is set.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cs`:
- Around line 99-123: The closing-run lookahead in HasMatchingBacktickRun is
incorrectly counting escaped backticks as valid code-span closers, which causes
SplitRow to stay in backtick mode and miss real cell separators. Update the
backtick scan in BitMarkdownViewerPipeTableBlockParser so it ignores any
backtick run immediately preceded by an escape character and only returns true
for unescaped closing runs, keeping the code-span tracking in SplitRow accurate.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cs`:
- Around line 11-24: The task marker detection in
BitMarkdownViewerTaskListAstProcessor.Process is matching against the full
item.Source, which causes multi-line list items with continuation lines to fail
checkbox detection. Update the logic around TaskMarker() so it extracts and
evaluates only the first logical source line from item.Source before applying
the regex, while keeping the existing raw-source check that avoids treating
escaped literals like "\[ \]" as tasks.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cs`:
- Around line 27-33: Adjust StripClosingHashes in
BitMarkdownViewerAtxHeadingParser so it first ignores any trailing whitespace
before checking for closing # characters. The current logic only trims when the
content literally ends with hashes, so update the method to detect the hash run
after skipping spaces and then return the heading text without the closing
sequence. Keep the fix localized to StripClosingHashes and preserve the existing
whitespace-before-hashes validation.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cs`:
- Around line 8-33: The block-grammar regexes in BitMarkdownViewerBlockGrammar
currently use \s in several Markdown block matchers, which lets Unicode
whitespace like NBSP open or close blocks incorrectly. Update the GeneratedRegex
patterns for ThematicBreak, AtxHeading, Fence, FenceClose, Bullet, and Ordered
to use Markdown-appropriate whitespace classes instead of \s: use [ \t] where
spaces/tabs are allowed and literal spaces where the construct is space-only.
Keep the parser behavior aligned with CommonMark so paragraph interruption and
block recognition remain correct.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cs`:
- Around line 18-23: The blockquote parsing in BitMarkdownViewerBlockquoteParser
is too permissive for lazy continuation lines, because the while loop currently
keeps any nonblank line inside the quote. Update the logic around IsQuote,
BitMarkdownViewerBlockProcessor.IsBlank, and state.StartsBlock so unmarked
continuation is accepted only while the current quoted content is still a
paragraph; once a non-paragraph block like a heading is encountered, later
unmarked lines such as “body” should fall outside the blockquote.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cs`:
- Line 49: The generic delimiter resolver is caching the wrong boundary token in
BitMarkdownViewerDelimiterResolver, causing later matches to stop at a
non-delimiter token instead of the previous delimiter. Update the logic in the
resolver’s matching flow so openersBottom[key] stores the previous delimiter
opener (or equivalent delimiter token) rather than tokens[closerIdx - 1], and
ensure the break condition in the opener scan only stops at that cached
delimiter boundary. Apply the same fix to both the main matching path and the
additional logic referenced by the later block so TryCreate(...) can still
evaluate earlier openers in the same bucket.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cs`:
- Around line 164-176: The parenthesized title parsing in
BitMarkdownViewerLinkInlineParser currently stops at the first closing
parenthesis, so nested titles are truncated. Update the title-scanning logic in
the link parsing flow to track nesting depth when closeCh is ')', while keeping
the existing quote and escape handling intact, so cases like [x](u (a (b)))
parse the full title correctly.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cs`:
- Around line 84-95: The list parsing loop in BitMarkdownViewerListParser is
leaving the outer index on the separating blank line after detecting a loose
list, which causes the next list item to be parsed as a new list. Update the
item-skipping logic in the block that handles blank lines inside the list item
scan so that, when IsSameMarker indicates the next item starts after blank
lines, the parser advances past the blank separator before the outer loop
continues. Use the existing BitMarkdownViewerBlockProcessor helpers and preserve
the loose-list behavior while ensuring the main loop in
BitMarkdownViewerListParser continues from the next marker item instead of
reprocessing the blank line.
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cs`:
- Around line 63-83: The rollback in BitMarkdownViewerPipelineBuilder.Setup only
trims list tails, so a failed extension can still leave BlockParsers,
InlineParsers, DelimiterProcessors, AstProcessors, or Renderers mutated if it
inserted, removed, or reordered items before throwing. Update the Setup failure
handling to restore each snapped list to its exact pre-Setup state rather than
using Rollback truncation, and keep the _extensions cleanup in the catch so the
builder is fully reusable after a failed extension registration.
In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scss`:
- Line 1: The Sass import in BitMarkdownViewerDemo.razor.scss is using a partial
underscore and file extension, which violates the current Stylelint rules.
Update the `@import` to reference the _bit-css-variables.scss stylesheet via its
bare path without the leading underscore or .scss extension so it matches the
expected Sass import style and passes linting.
---
Nitpick comments:
In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cs`:
- Around line 13-29: `BitMarkdownViewerMarkdownRenderer.NextSeq` currently uses
one global counter for the whole document, which makes sequence numbers shift
for all later content when earlier nodes change. Update `WriteNodes` and
`WriteNode` to use stable per-callsite sequence scopes with `RenderTreeBuilder`
regions, resetting numbering inside each node/list item so sequence values stay
local and deterministic. Keep the fix centered around
`BitMarkdownViewerMarkdownRenderer` and its rendering flow rather than a single
shared `_seq` across the entire render pass.
🪄 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: 99688c18-f98d-4eb9-bd65-126daa6481fb
📒 Files selected for processing (87)
src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csprojsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razorsrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scsssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.tssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoIdentifierExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerAutoLinkExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerColumnAlignment.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerEmojiExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerGitHubFlavoredExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelineBuilderExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipelines.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerStrikethroughRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTableRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskCheckboxRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstHelper.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAstProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAutolinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockquoteParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerCodeSpanInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEmphasisDelimiterProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerEscapeInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerFencedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerIndentedCodeBlockParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineHelpers.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerInlineProcessor.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLineBreakInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParagraphParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerThematicBreakParser.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerUrlSanitizer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipeline.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/IBitMarkdownViewerExtension.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerCoreRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerMarkdownRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Rendering/BitMarkdownViewerNodeRenderer.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerBlockquoteNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeBlockNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerCodeSpanNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerDocumentNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerEmphasisNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerHeadingNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerImageNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLineBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerLinkNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemListView.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListItemNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerListNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerMarkdownNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerParagraphNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerStrongNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerTextNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Syntax/BitMarkdownViewerThematicBreakNode.cssrc/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.tssrc/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cssrc/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cssrc/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.jssrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csprojsrc/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razorsrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.cssrc/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scsssrc/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/MarkdownViewer/BitMarkdownViewerTests.cs
💤 Files with no reviewable changes (11)
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Bit.BlazorUI.Extras.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.ts
- src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewerJsRuntimeExtensions.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor
- src/BlazorUI/Bit.BlazorUI.Extras/Services/BitMarkdownService.cs
- src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.razor.cs
- src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/marked.d.ts
- src/BlazorUI/Bit.BlazorUI.Extras/wwwroot/marked/marked-15.0.7.js
| blockquote { | ||
| margin: 0 0 1em 0; | ||
| padding: 0 1em; | ||
| color: $clr-fg-sec; | ||
| border-left: 0.25em solid $clr-brd-pri; | ||
| } | ||
|
|
||
| ul, | ||
| ol { | ||
| margin-top: 0; | ||
| margin-bottom: 1em; | ||
| padding-left: 2em; | ||
| } | ||
|
|
||
| li + li { | ||
| margin-top: 0.25em; | ||
| } | ||
|
|
||
| li:has(> .task-list-item-checkbox), | ||
| li:has(> p > .task-list-item-checkbox) { | ||
| list-style-type: none; | ||
| } | ||
|
|
||
| .task-list-item-checkbox { | ||
| margin: 0 0.5em 0 -1.4em; | ||
| vertical-align: middle; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use logical properties for the RTL-aware container.
BitMarkdownViewer can render with dir="rtl" (Lines 71-75 in src/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.cs), but these rules hardcode left-side spacing and borders. In RTL, blockquotes stay on the wrong edge and task-list checkboxes shift the wrong way.
Proposed fix
blockquote {
margin: 0 0 1em 0;
padding: 0 1em;
color: $clr-fg-sec;
- border-left: 0.25em solid $clr-brd-pri;
+ border-inline-start: 0.25em solid $clr-brd-pri;
}
ul,
ol {
margin-top: 0;
margin-bottom: 1em;
- padding-left: 2em;
+ padding-inline-start: 2em;
}
@@
.task-list-item-checkbox {
- margin: 0 0.5em 0 -1.4em;
+ margin-block: 0;
+ margin-inline-end: 0.5em;
+ margin-inline-start: -1.4em;
vertical-align: middle;
}📝 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.
| blockquote { | |
| margin: 0 0 1em 0; | |
| padding: 0 1em; | |
| color: $clr-fg-sec; | |
| border-left: 0.25em solid $clr-brd-pri; | |
| } | |
| ul, | |
| ol { | |
| margin-top: 0; | |
| margin-bottom: 1em; | |
| padding-left: 2em; | |
| } | |
| li + li { | |
| margin-top: 0.25em; | |
| } | |
| li:has(> .task-list-item-checkbox), | |
| li:has(> p > .task-list-item-checkbox) { | |
| list-style-type: none; | |
| } | |
| .task-list-item-checkbox { | |
| margin: 0 0.5em 0 -1.4em; | |
| vertical-align: middle; | |
| } | |
| blockquote { | |
| margin: 0 0 1em 0; | |
| padding: 0 1em; | |
| color: $clr-fg-sec; | |
| border-inline-start: 0.25em solid $clr-brd-pri; | |
| } | |
| ul, | |
| ol { | |
| margin-top: 0; | |
| margin-bottom: 1em; | |
| padding-inline-start: 2em; | |
| } | |
| li + li { | |
| margin-top: 0.25em; | |
| } | |
| li:has(> .task-list-item-checkbox), | |
| li:has(> p > .task-list-item-checkbox) { | |
| list-style-type: none; | |
| } | |
| .task-list-item-checkbox { | |
| margin-block: 0; | |
| margin-inline-end: 0.5em; | |
| margin-inline-start: -1.4em; | |
| vertical-align: middle; | |
| } |
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/BitMarkdownViewer.scss`
around lines 105 - 131, Update the BitMarkdownViewer SCSS rules to use logical
properties so they respect rtl rendering from BitMarkdownViewer; replace
left-specific spacing/border declarations in the blockquote, list, and
.task-list-item-checkbox styles with logical equivalents so the layout flips
correctly when dir="rtl" is set.
| if (backtickRun == 0) | ||
| { | ||
| if (HasMatchingBacktickRun(s, i + 1, runLength)) backtickRun = runLength; | ||
| } | ||
| else if (runLength == backtickRun) backtickRun = 0; | ||
| sb.Append(s, runStart, runLength); | ||
| } | ||
| else if (s[i] == '|' && backtickRun == 0) { cells.Add(sb.ToString()); sb.Clear(); } | ||
| else sb.Append(s[i]); | ||
| } | ||
| cells.Add(sb.ToString()); | ||
| return cells; | ||
| } | ||
|
|
||
| // Scans from startIndex for a backtick run of exactly the given length, used to | ||
| // decide whether an opening backtick run has a valid closing run (a code span's | ||
| // closing run must contain the same number of backticks as the opening run). | ||
| private static bool HasMatchingBacktickRun(string s, int startIndex, int runLength) | ||
| { | ||
| for (int i = startIndex; i < s.Length; i++) | ||
| { | ||
| if (s[i] != '`') continue; | ||
| int runStart = i; | ||
| while (i + 1 < s.Length && s[i + 1] == '`') i++; | ||
| if (i - runStart + 1 == runLength) return true; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Ignore escaped backticks in the closing-run lookahead.
HasMatchingBacktickRun currently treats \`` as a valid closer. In a row like ``| a | `| b |``, that makesSplitRow` enter code-span mode at Line 101 and swallow the first real cell separator, collapsing columns.
Proposed fix
private static bool HasMatchingBacktickRun(string s, int startIndex, int runLength)
{
for (int i = startIndex; i < s.Length; i++)
{
- if (s[i] != '`') continue;
+ if (s[i] != '`' || IsEscapedOutsideCode(s, i)) continue;
int runStart = i;
while (i + 1 < s.Length && s[i + 1] == '`') i++;
if (i - runStart + 1 == runLength) return true;
}
return false;
}
+
+ private static bool IsEscapedOutsideCode(string s, int index)
+ {
+ int backslashes = 0;
+ for (int i = index - 1; i >= 0 && s[i] == '\\'; i--) backslashes++;
+ return (backslashes & 1) == 1;
+ }📝 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.
| if (backtickRun == 0) | |
| { | |
| if (HasMatchingBacktickRun(s, i + 1, runLength)) backtickRun = runLength; | |
| } | |
| else if (runLength == backtickRun) backtickRun = 0; | |
| sb.Append(s, runStart, runLength); | |
| } | |
| else if (s[i] == '|' && backtickRun == 0) { cells.Add(sb.ToString()); sb.Clear(); } | |
| else sb.Append(s[i]); | |
| } | |
| cells.Add(sb.ToString()); | |
| return cells; | |
| } | |
| // Scans from startIndex for a backtick run of exactly the given length, used to | |
| // decide whether an opening backtick run has a valid closing run (a code span's | |
| // closing run must contain the same number of backticks as the opening run). | |
| private static bool HasMatchingBacktickRun(string s, int startIndex, int runLength) | |
| { | |
| for (int i = startIndex; i < s.Length; i++) | |
| { | |
| if (s[i] != '`') continue; | |
| int runStart = i; | |
| while (i + 1 < s.Length && s[i + 1] == '`') i++; | |
| if (i - runStart + 1 == runLength) return true; | |
| if (backtickRun == 0) | |
| { | |
| if (HasMatchingBacktickRun(s, i + 1, runLength)) backtickRun = runLength; | |
| } | |
| else if (runLength == backtickRun) backtickRun = 0; | |
| sb.Append(s, runStart, runLength); | |
| } | |
| else if (s[i] == '|' && backtickRun == 0) { cells.Add(sb.ToString()); sb.Clear(); } | |
| else sb.Append(s[i]); | |
| } | |
| cells.Add(sb.ToString()); | |
| return cells; | |
| } | |
| // Scans from startIndex for a backtick run of exactly the given length, used to | |
| // decide whether an opening backtick run has a valid closing run (a code span's | |
| // closing run must contain the same number of backticks as the opening run). | |
| private static bool HasMatchingBacktickRun(string s, int startIndex, int runLength) | |
| { | |
| for (int i = startIndex; i < s.Length; i++) | |
| { | |
| if (s[i] != '`' || IsEscapedOutsideCode(s, i)) continue; | |
| int runStart = i; | |
| while (i + 1 < s.Length && s[i + 1] == '`') i++; | |
| if (i - runStart + 1 == runLength) return true; | |
| } | |
| return false; | |
| } | |
| private static bool IsEscapedOutsideCode(string s, int index) | |
| { | |
| int backslashes = 0; | |
| for (int i = index - 1; i >= 0 && s[i] == '\\'; i--) backslashes++; | |
| return (backslashes & 1) == 1; | |
| } |
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerPipeTableBlockParser.cs`
around lines 99 - 123, The closing-run lookahead in HasMatchingBacktickRun is
incorrectly counting escaped backticks as valid code-span closers, which causes
SplitRow to stay in backtick mode and miss real cell separators. Update the
backtick scan in BitMarkdownViewerPipeTableBlockParser so it ignores any
backtick run immediately preceded by an escape character and only returns true
for unescaped closing runs, keeping the code-span tracking in SplitRow accurate.
| [GeneratedRegex(@"^\[([ xX])\](?:\s+(.*))?$")] | ||
| private static partial Regex TaskMarker(); | ||
|
|
||
| public override void Process(BitMarkdownViewerDocumentNode document, BitMarkdownViewerPipeline pipeline) | ||
| { | ||
| foreach (var list in BitMarkdownViewerAstHelper.Descendants(document).OfType<BitMarkdownViewerListNode>()) | ||
| { | ||
| foreach (var item in list.Items) | ||
| { | ||
| // Detect task markers from the raw (pre-inline) item source so that | ||
| // escaped literals like "\[ \]" are not misread as real checkboxes. | ||
| if (item.Source is null) continue; | ||
| var raw = TaskMarker().Match(item.Source); | ||
| if (!raw.Success) continue; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Match only the first source line for task markers.
TaskMarker() is anchored to the entire item.Source, so a valid task item with continuation lines stops matching here and never gets a checkbox. Parse just the first logical line from item.Source before applying the regex.
Suggested fix
- var raw = TaskMarker().Match(item.Source);
+ var firstLine = item.Source;
+ var newlineIndex = firstLine.IndexOfAny(['\r', '\n']);
+ if (newlineIndex >= 0)
+ {
+ firstLine = firstLine[..newlineIndex];
+ }
+
+ var raw = TaskMarker().Match(firstLine);
if (!raw.Success) continue;📝 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.
| [GeneratedRegex(@"^\[([ xX])\](?:\s+(.*))?$")] | |
| private static partial Regex TaskMarker(); | |
| public override void Process(BitMarkdownViewerDocumentNode document, BitMarkdownViewerPipeline pipeline) | |
| { | |
| foreach (var list in BitMarkdownViewerAstHelper.Descendants(document).OfType<BitMarkdownViewerListNode>()) | |
| { | |
| foreach (var item in list.Items) | |
| { | |
| // Detect task markers from the raw (pre-inline) item source so that | |
| // escaped literals like "\[ \]" are not misread as real checkboxes. | |
| if (item.Source is null) continue; | |
| var raw = TaskMarker().Match(item.Source); | |
| if (!raw.Success) continue; | |
| [GeneratedRegex(@"^\[([ xX])\](?:\s+(.*))?$")] | |
| private static partial Regex TaskMarker(); | |
| public override void Process(BitMarkdownViewerDocumentNode document, BitMarkdownViewerPipeline pipeline) | |
| { | |
| foreach (var list in BitMarkdownViewerAstHelper.Descendants(document).OfType<BitMarkdownViewerListNode>()) | |
| { | |
| foreach (var item in list.Items) | |
| { | |
| // Detect task markers from the raw (pre-inline) item source so that | |
| // escaped literals like "\[ \]" are not misread as real checkboxes. | |
| if (item.Source is null) continue; | |
| var firstLine = item.Source; | |
| var newlineIndex = firstLine.IndexOfAny(['\r', '\n']); | |
| if (newlineIndex >= 0) | |
| { | |
| firstLine = firstLine[..newlineIndex]; | |
| } | |
| var raw = TaskMarker().Match(firstLine); | |
| if (!raw.Success) continue; |
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Extensions/BitMarkdownViewerTaskListAstProcessor.cs`
around lines 11 - 24, The task marker detection in
BitMarkdownViewerTaskListAstProcessor.Process is matching against the full
item.Source, which causes multi-line list items with continuation lines to fail
checkbox detection. Update the logic around TaskMarker() so it extracts and
evaluates only the first logical source line from item.Source before applying
the regex, while keeping the existing raw-source check that avoids treating
escaped literals like "\[ \]" as tasks.
| private static string StripClosingHashes(string content) | ||
| { | ||
| int hashStart = content.Length; | ||
| while (hashStart > 0 && content[hashStart - 1] == '#') hashStart--; | ||
| if (hashStart < content.Length && (hashStart == 0 || char.IsWhiteSpace(content[hashStart - 1]))) | ||
| return content[..hashStart]; | ||
| return content; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Handle trailing spaces before stripping closing hashes.
StripClosingHashes only strips when the line literally ends with #. For valid input like # foo ### , the trailing spaces prevent the closing sequence from being removed, so the heading text becomes foo ### instead of foo.
Suggested fix
private static string StripClosingHashes(string content)
{
- int hashStart = content.Length;
+ int end = content.Length;
+ while (end > 0 && char.IsWhiteSpace(content[end - 1])) end--;
+
+ int hashStart = end;
while (hashStart > 0 && content[hashStart - 1] == '#') hashStart--;
- if (hashStart < content.Length && (hashStart == 0 || char.IsWhiteSpace(content[hashStart - 1])))
+ if (hashStart < end && (hashStart == 0 || char.IsWhiteSpace(content[hashStart - 1])))
return content[..hashStart];
return content;
}📝 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.
| private static string StripClosingHashes(string content) | |
| { | |
| int hashStart = content.Length; | |
| while (hashStart > 0 && content[hashStart - 1] == '#') hashStart--; | |
| if (hashStart < content.Length && (hashStart == 0 || char.IsWhiteSpace(content[hashStart - 1]))) | |
| return content[..hashStart]; | |
| return content; | |
| private static string StripClosingHashes(string content) | |
| { | |
| int end = content.Length; | |
| while (end > 0 && char.IsWhiteSpace(content[end - 1])) end--; | |
| int hashStart = end; | |
| while (hashStart > 0 && content[hashStart - 1] == '#') hashStart--; | |
| if (hashStart < end && (hashStart == 0 || char.IsWhiteSpace(content[hashStart - 1]))) | |
| return content[..hashStart]; | |
| return content; | |
| } |
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerAtxHeadingParser.cs`
around lines 27 - 33, Adjust StripClosingHashes in
BitMarkdownViewerAtxHeadingParser so it first ignores any trailing whitespace
before checking for closing # characters. The current logic only trims when the
content literally ends with hashes, so update the method to detect the hash run
after skipping spaces and then return the heading text without the closing
sequence. Keep the fix localized to StripClosingHashes and preserve the existing
whitespace-before-hashes validation.
| [GeneratedRegex(@"^ {0,3}(?:([-*_])\s*)(?:\1\s*){2,}$")] | ||
| public static partial Regex ThematicBreak(); | ||
|
|
||
| // The optional closing run of '#'s is stripped in the parser (only when preceded | ||
| // by whitespace), so the content group here captures the full text after the | ||
| // opening '#'s and is not responsible for removing trailing hashes. | ||
| [GeneratedRegex(@"^ {0,3}(#{1,6})(?:\s+(.*?))?\s*$")] | ||
| public static partial Regex AtxHeading(); | ||
|
|
||
| // Backtick fences may not contain backticks in their info string, but tilde | ||
| // fences may; handle the two fence types with separate alternatives. | ||
| [GeneratedRegex(@"^ {0,3}(?<fence>`{3,})\s*(?<info>[^`\n]*)$|^ {0,3}(?<fence>~{3,})\s*(?<info>[^\n]*)$")] | ||
| public static partial Regex Fence(); | ||
|
|
||
| [GeneratedRegex(@"^ {0,3}(`{3,}|~{3,})\s*$")] | ||
| public static partial Regex FenceClose(); | ||
|
|
||
| // The whitespace + content after the marker is optional so that a marker-only | ||
| // line (e.g. "-" or "1.") is recognised as an empty list item per CommonMark. | ||
| [GeneratedRegex(@"^ {0,3}([-+*])(?:(\s+)(.*))?$")] | ||
| public static partial Regex Bullet(); | ||
|
|
||
| [GeneratedRegex(@"^ {0,3}(\d{1,9})([.)])(?:(\s+)(.*))?$")] | ||
| public static partial Regex Ordered(); | ||
|
|
||
| [GeneratedRegex(@"^ {0,3}(=+|-+)\s*$")] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Restrict block-syntax whitespace to spaces/tabs.
Lines 8-33 use .NET \s, so Unicode whitespace like NBSP can incorrectly open or close blocks (- item, fenced closers with NBSP, etc.). Because these same regexes also back paragraph interruption, that misclassification propagates beyond the immediate parser. Use [ \t] where Markdown allows spaces/tabs, and literal spaces where the construct is space-only.
Suggested fix
- [GeneratedRegex(@"^ {0,3}(?:([-*_])\s*)(?:\1\s*){2,}$")]
+ [GeneratedRegex(@"^ {0,3}(?:([-*_])[ \t]*)(?:\1[ \t]*){2,}$")]
public static partial Regex ThematicBreak();
- [GeneratedRegex(@"^ {0,3}(#{1,6})(?:\s+(.*?))?\s*$")]
+ [GeneratedRegex(@"^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$")]
public static partial Regex AtxHeading();
- [GeneratedRegex(@"^ {0,3}(?<fence>`{3,})\s*(?<info>[^`\n]*)$|^ {0,3}(?<fence>~{3,})\s*(?<info>[^\n]*)$")]
+ [GeneratedRegex(@"^ {0,3}(?<fence>`{3,})[ \t]*(?<info>[^`\n]*)$|^ {0,3}(?<fence>~{3,})[ \t]*(?<info>[^\n]*)$")]
public static partial Regex Fence();
- [GeneratedRegex(@"^ {0,3}(`{3,}|~{3,})\s*$")]
+ [GeneratedRegex(@"^ {0,3}(`{3,}|~{3,})[ \t]*$")]
public static partial Regex FenceClose();
- [GeneratedRegex(@"^ {0,3}([-+*])(?:(\s+)(.*))?$")]
+ [GeneratedRegex(@"^ {0,3}([-+*])(?:([ \t]+)(.*))?$")]
public static partial Regex Bullet();
- [GeneratedRegex(@"^ {0,3}(\d{1,9})([.)])(?:(\s+)(.*))?$")]
+ [GeneratedRegex(@"^ {0,3}(\d{1,9})([.)])(?:([ \t]+)(.*))?$")]
public static partial Regex Ordered();
- [GeneratedRegex(@"^ {0,3}(=+|-+)\s*$")]
+ [GeneratedRegex(@"^ {0,3}(=+|-+)[ \t]*$")]
public static partial Regex Setext();📝 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.
| [GeneratedRegex(@"^ {0,3}(?:([-*_])\s*)(?:\1\s*){2,}$")] | |
| public static partial Regex ThematicBreak(); | |
| // The optional closing run of '#'s is stripped in the parser (only when preceded | |
| // by whitespace), so the content group here captures the full text after the | |
| // opening '#'s and is not responsible for removing trailing hashes. | |
| [GeneratedRegex(@"^ {0,3}(#{1,6})(?:\s+(.*?))?\s*$")] | |
| public static partial Regex AtxHeading(); | |
| // Backtick fences may not contain backticks in their info string, but tilde | |
| // fences may; handle the two fence types with separate alternatives. | |
| [GeneratedRegex(@"^ {0,3}(?<fence>`{3,})\s*(?<info>[^`\n]*)$|^ {0,3}(?<fence>~{3,})\s*(?<info>[^\n]*)$")] | |
| public static partial Regex Fence(); | |
| [GeneratedRegex(@"^ {0,3}(`{3,}|~{3,})\s*$")] | |
| public static partial Regex FenceClose(); | |
| // The whitespace + content after the marker is optional so that a marker-only | |
| // line (e.g. "-" or "1.") is recognised as an empty list item per CommonMark. | |
| [GeneratedRegex(@"^ {0,3}([-+*])(?:(\s+)(.*))?$")] | |
| public static partial Regex Bullet(); | |
| [GeneratedRegex(@"^ {0,3}(\d{1,9})([.)])(?:(\s+)(.*))?$")] | |
| public static partial Regex Ordered(); | |
| [GeneratedRegex(@"^ {0,3}(=+|-+)\s*$")] | |
| [GeneratedRegex(@"^ {0,3}(?:([-*_])[ \t]*)(?:\1[ \t]*){2,}$")] | |
| public static partial Regex ThematicBreak(); | |
| // The optional closing run of '#'s is stripped in the parser (only when preceded | |
| // by whitespace), so the content group here captures the full text after the | |
| // opening '#'s and is not responsible for removing trailing hashes. | |
| [GeneratedRegex(@"^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$")] | |
| public static partial Regex AtxHeading(); | |
| // Backtick fences may not contain backticks in their info string, but tilde | |
| // fences may; handle the two fence types with separate alternatives. | |
| [GeneratedRegex(@"^ {0,3}(?<fence>`{3,})[ \t]*(?<info>[^`\n]*)$|^ {0,3}(?<fence>~{3,})[ \t]*(?<info>[^\n]*)$")] | |
| public static partial Regex Fence(); | |
| [GeneratedRegex(@"^ {0,3}(`{3,}|~{3,})[ \t]*$")] | |
| public static partial Regex FenceClose(); | |
| // The whitespace + content after the marker is optional so that a marker-only | |
| // line (e.g. "-" or "1.") is recognised as an empty list item per CommonMark. | |
| [GeneratedRegex(@"^ {0,3}([-+*])(?:([ \t]+)(.*))?$")] | |
| public static partial Regex Bullet(); | |
| [GeneratedRegex(@"^ {0,3}(\d{1,9})([.)])(?:([ \t]+)(.*))?$")] | |
| public static partial Regex Ordered(); | |
| [GeneratedRegex(@"^ {0,3}(=+|-+)[ \t]*$")] |
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerBlockGrammar.cs`
around lines 8 - 33, The block-grammar regexes in BitMarkdownViewerBlockGrammar
currently use \s in several Markdown block matchers, which lets Unicode
whitespace like NBSP open or close blocks incorrectly. Update the GeneratedRegex
patterns for ThematicBreak, AtxHeading, Fence, FenceClose, Bullet, and Ordered
to use Markdown-appropriate whitespace classes instead of \s: use [ \t] where
spaces/tabs are allowed and literal spaces where the construct is space-only.
Keep the parser behavior aligned with CommonMark so paragraph interruption and
block recognition remain correct.
| while (openerIdx >= 0) | ||
| { | ||
| var opener = tokens[openerIdx]; | ||
| if (bottom is not null && ReferenceEquals(opener, bottom)) break; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Cache the previous delimiter, not the previous token.
openersBottom[key] is populated with tokens[closerIdx - 1], which can be a text/node token. On the next closer in the same bucket, Line 49 breaks as soon as it reaches that non-delimiter token, so any earlier delimiter opener is never considered. That makes the generic resolver mis-parse extensions whose TryCreate(...) rejects one opener/closer pair but could still match an earlier opener.
Suggested fix
if (!found)
{
- openersBottom[key] = closerIdx > 0 ? tokens[closerIdx - 1] : null;
+ Tok? previousDelimiter = null;
+ for (int i = closerIdx - 1; i >= 0; i--)
+ {
+ if (tokens[i].Kind == TokKind.Delim)
+ {
+ previousDelimiter = tokens[i];
+ break;
+ }
+ }
+ openersBottom[key] = previousDelimiter;
if (!closer.CanOpen) closer.Active = false;
closerIdx++;
continue;
}Also applies to: 75-79
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerDelimiterResolver.cs`
at line 49, The generic delimiter resolver is caching the wrong boundary token
in BitMarkdownViewerDelimiterResolver, causing later matches to stop at a
non-delimiter token instead of the previous delimiter. Update the logic in the
resolver’s matching flow so openersBottom[key] stores the previous delimiter
opener (or equivalent delimiter token) rather than tokens[closerIdx - 1], and
ensure the break condition in the opener scan only stops at that cached
delimiter boundary. Apply the same fix to both the main matching path and the
additional logic referenced by the later block so TryCreate(...) can still
evaluate earlier openers in the same bucket.
| if (i < n && (s[i] is '"' or '\'' or '(')) | ||
| { | ||
| char closeCh = s[i] == '(' ? ')' : s[i]; | ||
| i++; | ||
| var tb = new StringBuilder(); | ||
| while (i < n && s[i] != closeCh) | ||
| { | ||
| if (s[i] == '\\' && i + 1 < n) { tb.Append(s[i + 1]); i += 2; continue; } | ||
| tb.Append(s[i++]); | ||
| } | ||
| if (i >= n) return false; | ||
| i++; | ||
| title = tb.ToString(); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Handle balanced parentheses in (...) titles.
This loop stops a parenthesized title at the first ), so valid inputs like [x](u (a (b))) are parsed with title == "a (b" instead of a (b). Track nested parentheses when closeCh is ).
Proposed fix
if (i < n && (s[i] is '"' or '\'' or '('))
{
- char closeCh = s[i] == '(' ? ')' : s[i];
+ bool parenTitle = s[i] == '(';
+ char closeCh = parenTitle ? ')' : s[i];
i++;
var tb = new StringBuilder();
+ int parenDepth = 0;
while (i < n && s[i] != closeCh)
{
if (s[i] == '\\' && i + 1 < n) { tb.Append(s[i + 1]); i += 2; continue; }
+ if (parenTitle)
+ {
+ if (s[i] == '(') { parenDepth++; tb.Append(s[i++]); continue; }
+ if (s[i] == ')' && parenDepth > 0)
+ {
+ parenDepth--;
+ tb.Append(s[i++]);
+ continue;
+ }
+ }
tb.Append(s[i++]);
}
if (i >= n) return false;📝 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.
| if (i < n && (s[i] is '"' or '\'' or '(')) | |
| { | |
| char closeCh = s[i] == '(' ? ')' : s[i]; | |
| i++; | |
| var tb = new StringBuilder(); | |
| while (i < n && s[i] != closeCh) | |
| { | |
| if (s[i] == '\\' && i + 1 < n) { tb.Append(s[i + 1]); i += 2; continue; } | |
| tb.Append(s[i++]); | |
| } | |
| if (i >= n) return false; | |
| i++; | |
| title = tb.ToString(); | |
| if (i < n && (s[i] is '"' or '\'' or '(')) | |
| { | |
| bool parenTitle = s[i] == '('; | |
| char closeCh = parenTitle ? ')' : s[i]; | |
| i++; | |
| var tb = new StringBuilder(); | |
| int parenDepth = 0; | |
| while (i < n && s[i] != closeCh) | |
| { | |
| if (s[i] == '\\' && i + 1 < n) { tb.Append(s[i + 1]); i += 2; continue; } | |
| if (parenTitle) | |
| { | |
| if (s[i] == '(') { parenDepth++; tb.Append(s[i++]); continue; } | |
| if (s[i] == ')' && parenDepth > 0) | |
| { | |
| parenDepth--; | |
| tb.Append(s[i++]); | |
| continue; | |
| } | |
| } | |
| tb.Append(s[i++]); | |
| } | |
| if (i >= n) return false; | |
| i++; | |
| title = tb.ToString(); |
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerLinkInlineParser.cs`
around lines 164 - 176, The parenthesized title parsing in
BitMarkdownViewerLinkInlineParser currently stops at the first closing
parenthesis, so nested titles are truncated. Update the title-scanning logic in
the link parsing flow to track nesting depth when closeCh is ')', while keeping
the existing quote and escape handling intact, so cases like [x](u (a (b)))
parse the full title correctly.
| { | ||
| int j = i + 1; | ||
| while (j < lines.Count && BitMarkdownViewerBlockProcessor.IsBlank(lines[j])) j++; | ||
| if (j < lines.Count && BitMarkdownViewerBlockProcessor.GetIndent(lines[j]) >= markerIndent) | ||
| { | ||
| itemLines.Add(string.Empty); | ||
| itemHadBlank = true; | ||
| i++; | ||
| continue; | ||
| } | ||
| if (j < lines.Count && IsSameMarker(lines[j], ordered, markerChar)) loose = true; | ||
| break; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Advance to the next item after a separating blank line.
Line 94 marks the list as loose, but i still points at the blank line. The outer loop then rechecks that blank line and stops parsing, so - a\n\n- b is split into two lists instead of one loose list.
Proposed fix
- if (j < lines.Count && IsSameMarker(lines[j], ordered, markerChar)) loose = true;
+ if (j < lines.Count && IsSameMarker(lines[j], ordered, markerChar))
+ {
+ loose = true;
+ i = j;
+ }
break;📝 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.
| { | |
| int j = i + 1; | |
| while (j < lines.Count && BitMarkdownViewerBlockProcessor.IsBlank(lines[j])) j++; | |
| if (j < lines.Count && BitMarkdownViewerBlockProcessor.GetIndent(lines[j]) >= markerIndent) | |
| { | |
| itemLines.Add(string.Empty); | |
| itemHadBlank = true; | |
| i++; | |
| continue; | |
| } | |
| if (j < lines.Count && IsSameMarker(lines[j], ordered, markerChar)) loose = true; | |
| break; | |
| { | |
| int j = i + 1; | |
| while (j < lines.Count && BitMarkdownViewerBlockProcessor.IsBlank(lines[j])) j++; | |
| if (j < lines.Count && BitMarkdownViewerBlockProcessor.GetIndent(lines[j]) >= markerIndent) | |
| { | |
| itemLines.Add(string.Empty); | |
| itemHadBlank = true; | |
| i++; | |
| continue; | |
| } | |
| if (j < lines.Count && IsSameMarker(lines[j], ordered, markerChar)) | |
| { | |
| loose = true; | |
| i = j; | |
| } | |
| break; |
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Parsing/BitMarkdownViewerListParser.cs`
around lines 84 - 95, The list parsing loop in BitMarkdownViewerListParser is
leaving the outer index on the separating blank line after detecting a loose
list, which causes the next list item to be parsed as a new list. Update the
item-skipping logic in the block that handles blank lines inside the list item
scan so that, when IsSameMarker indicates the next item starts after blank
lines, the parser advances past the blank separator before the outer loop
continues. Use the existing BitMarkdownViewerBlockProcessor helpers and preserve
the loose-list behavior while ensuring the main loop in
BitMarkdownViewerListParser continues from the next marker item instead of
reprocessing the blank line.
| // Snapshot every registration list so a failed Setup can be fully reverted, | ||
| // not just the _extensions entry. This keeps the builder reusable afterwards. | ||
| int blockParsers = BlockParsers.Count; | ||
| int inlineParsers = InlineParsers.Count; | ||
| int delimiterProcessors = DelimiterProcessors.Count; | ||
| int astProcessors = AstProcessors.Count; | ||
| int renderers = Renderers.Count; | ||
| try | ||
| { | ||
| extension.Setup(this); | ||
| } | ||
| catch | ||
| { | ||
| // Setup failed: undo every mutation it made so the builder isn't left in | ||
| // a partially-registered state and the extension can be retried. | ||
| _extensions.Remove(extension); | ||
| Rollback(BlockParsers, blockParsers); | ||
| Rollback(InlineParsers, inlineParsers); | ||
| Rollback(DelimiterProcessors, delimiterProcessors); | ||
| Rollback(AstProcessors, astProcessors); | ||
| Rollback(Renderers, renderers); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Restore full list snapshots on failed Setup.
This rollback only truncates tail additions. Because Setup(this) gets the raw List<> instances, an extension that does Insert(0, ...), removes entries, or reorders a list before throwing will still leave the builder mutated after the catch.
Suggested fix
- int blockParsers = BlockParsers.Count;
- int inlineParsers = InlineParsers.Count;
- int delimiterProcessors = DelimiterProcessors.Count;
- int astProcessors = AstProcessors.Count;
- int renderers = Renderers.Count;
+ var blockParsers = BlockParsers.ToArray();
+ var inlineParsers = InlineParsers.ToArray();
+ var delimiterProcessors = DelimiterProcessors.ToArray();
+ var astProcessors = AstProcessors.ToArray();
+ var renderers = Renderers.ToArray();
try
{
extension.Setup(this);
}
catch
{
// Setup failed: undo every mutation it made so the builder isn't left in
// a partially-registered state and the extension can be retried.
_extensions.Remove(extension);
- Rollback(BlockParsers, blockParsers);
- Rollback(InlineParsers, inlineParsers);
- Rollback(DelimiterProcessors, delimiterProcessors);
- Rollback(AstProcessors, astProcessors);
- Rollback(Renderers, renderers);
+ Restore(BlockParsers, blockParsers);
+ Restore(InlineParsers, inlineParsers);
+ Restore(DelimiterProcessors, delimiterProcessors);
+ Restore(AstProcessors, astProcessors);
+ Restore(Renderers, renderers);
throw;
}
@@
- private static void Rollback<T>(List<T> list, int originalCount)
+ private static void Restore<T>(List<T> list, IReadOnlyCollection<T> snapshot)
{
- if (list.Count > originalCount)
- list.RemoveRange(originalCount, list.Count - originalCount);
+ list.Clear();
+ list.AddRange(snapshot);
}Also applies to: 89-92
🤖 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/BlazorUI/Bit.BlazorUI.Extras/Components/MarkdownViewer/Pipeline/BitMarkdownViewerPipelineBuilder.cs`
around lines 63 - 83, The rollback in BitMarkdownViewerPipelineBuilder.Setup
only trims list tails, so a failed extension can still leave BlockParsers,
InlineParsers, DelimiterProcessors, AstProcessors, or Renderers mutated if it
inserted, removed, or reordered items before throwing. Update the Setup failure
handling to restore each snapped list to its exact pre-Setup state rather than
using Rollback truncation, and keep the _extensions cleanup in the catch so the
builder is fully reusable after a failed extension registration.
| @@ -1,7 +1,69 @@ | |||
| @import '../../../../Styles/abstracts/_bit-css-variables.scss'; | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Drop the partial underscore and extension from the Sass import.
Stylelint is already flagging this line for scss/load-no-partial-leading-underscore and scss/load-partial-extension, so the new stylesheet will fail linting as written.
Suggested fix
-@import '../../../../Styles/abstracts/_bit-css-variables.scss';
+@import '../../../../Styles/abstracts/bit-css-variables';📝 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 '../../../../Styles/abstracts/_bit-css-variables.scss'; | |
| `@import` '../../../../Styles/abstracts/bit-css-variables'; |
🧰 Tools
🪛 Stylelint (17.13.0)
[error] 1-1: Unexpected leading underscore in imported partial name (scss/load-no-partial-leading-underscore)
(scss/load-no-partial-leading-underscore)
[error] 1-1: Unexpected extension ".scss" in @import (scss/load-partial-extension)
(scss/load-partial-extension)
🤖 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/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/MarkdownViewer/BitMarkdownViewerDemo.razor.scss`
at line 1, The Sass import in BitMarkdownViewerDemo.razor.scss is using a
partial underscore and file extension, which violates the current Stylelint
rules. Update the `@import` to reference the _bit-css-variables.scss stylesheet
via its bare path without the leading underscore or .scss extension so it
matches the expected Sass import style and passes linting.
Source: Linters/SAST tools
closes #12503
Summary by CodeRabbit