-
Notifications
You must be signed in to change notification settings - Fork 48
feat: add support for additional HTML tags in RichView #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -173,8 +173,117 @@ public class HTMLToMarkdownConverter { | |||||||||||||||||||||||||||||||||
| case "hr": | ||||||||||||||||||||||||||||||||||
| result += "\n---\n" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Table support | ||||||||||||||||||||||||||||||||||
| case "table": | ||||||||||||||||||||||||||||||||||
| result += try convertTable(childElement) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| case "thead", "tbody", "tfoot": | ||||||||||||||||||||||||||||||||||
| // These are handled by table, but if encountered alone, process children | ||||||||||||||||||||||||||||||||||
| result += try convertElement(childElement) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| case "tr", "th", "td": | ||||||||||||||||||||||||||||||||||
| // These should be handled by table, but if encountered alone, process children | ||||||||||||||||||||||||||||||||||
| result += try convertElement(childElement) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Strikethrough | ||||||||||||||||||||||||||||||||||
| case "del", "s", "strike": | ||||||||||||||||||||||||||||||||||
| let content = try convertElement(childElement) | ||||||||||||||||||||||||||||||||||
| result += "~~\(content)~~" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Underline - no standard markdown, render as emphasized text | ||||||||||||||||||||||||||||||||||
| case "u", "ins": | ||||||||||||||||||||||||||||||||||
| let content = try convertElement(childElement) | ||||||||||||||||||||||||||||||||||
| result += "_\(content)_" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Superscript/subscript - render with markers | ||||||||||||||||||||||||||||||||||
| case "sup": | ||||||||||||||||||||||||||||||||||
| let content = try convertElement(childElement) | ||||||||||||||||||||||||||||||||||
| result += "^\(content)" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| case "sub": | ||||||||||||||||||||||||||||||||||
| let content = try convertElement(childElement) | ||||||||||||||||||||||||||||||||||
| result += "~\(content)" | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| // Superscript/subscript - render with markers | |
| case "sup": | |
| let content = try convertElement(childElement) | |
| result += "^\(content)" | |
| case "sub": | |
| let content = try convertElement(childElement) | |
| result += "~\(content)" | |
| // Superscript/subscript - render as HTML tags to preserve formatting | |
| case "sup": | |
| let content = try convertElement(childElement) | |
| result += "<sup>\(content)</sup>" | |
| case "sub": | |
| let content = try convertElement(childElement) | |
| result += "<sub>\(content)</sub>" |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Table cells containing pipe characters (|) are not escaped, which will break the markdown table structure. For example, a cell containing "option A | option B" would be split into multiple columns.
Consider escaping pipe characters in cell content before building the table:
let content = try convertElement(cell)
.replacingOccurrences(of: "\n", with: " ")
.replacingOccurrences(of: "|", with: "\\|") // Escape pipes
.trimmingCharacters(in: .whitespaces)| .replacingOccurrences(of: "\n", with: " ") | |
| .replacingOccurrences(of: "\n", with: " ") | |
| .replacingOccurrences(of: "|", with: "\\|") // Escape pipes for Markdown tables |
Outdated
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The headerRowCount variable is incremented when a header row is detected but is never used to influence the table conversion logic. The separator is unconditionally added after the first row (line 378) regardless of whether a header was detected.
Consider either:
- Removing this unused variable and the
isHeaderRowlogic if it's not needed - Using it to control separator placement (though markdown tables always require a separator after the first row, so the current behavior may be intentional)
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -80,6 +80,12 @@ public class MarkdownRenderer { | |||||||||
| } else if line.starts(with: "---") { | ||||||||||
| // Horizontal rule | ||||||||||
| attributedString.append(AttributedString("—————————————\n")) | ||||||||||
| } else if line.starts(with: "|") && line.hasSuffix("|") { | ||||||||||
| // Markdown table | ||||||||||
| let (tableBlock, linesConsumed) = extractTableBlock(lines, startIndex: index) | ||||||||||
| attributedString.append(renderTable(tableBlock)) | ||||||||||
| index += linesConsumed | ||||||||||
| continue | ||||||||||
| } else { | ||||||||||
| // Regular paragraph with inline formatting | ||||||||||
| attributedString.append(renderInlineMarkdown(line)) | ||||||||||
|
|
@@ -296,6 +302,46 @@ public class MarkdownRenderer { | |||||||||
| continue | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Check for strikethrough | ||||||||||
| if let strikeMatch = currentText.firstMatch(of: /~~(.+?)~~/) { | ||||||||||
| // Add text before strikethrough | ||||||||||
| let beforeRange = currentText.startIndex..<strikeMatch.range.lowerBound | ||||||||||
| if !beforeRange.isEmpty { | ||||||||||
| result.append(renderPlainText(String(currentText[beforeRange]))) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Add strikethrough text | ||||||||||
| var strikeText = AttributedString(String(strikeMatch.1)) | ||||||||||
| strikeText.font = .system(size: stylesheet.body.fontSize) | ||||||||||
| strikeText.foregroundColor = stylesheet.body.color.uiColor | ||||||||||
| strikeText.strikethroughStyle = .single | ||||||||||
| result.append(strikeText) | ||||||||||
|
|
||||||||||
| // Continue with remaining text | ||||||||||
| currentText = String(currentText[strikeMatch.range.upperBound...]) | ||||||||||
| continue | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Check for highlight/mark | ||||||||||
| if let highlightMatch = currentText.firstMatch(of: /==(.+?)==/) { | ||||||||||
| // Add text before highlight | ||||||||||
| let beforeRange = currentText.startIndex..<highlightMatch.range.lowerBound | ||||||||||
| if !beforeRange.isEmpty { | ||||||||||
| result.append(renderPlainText(String(currentText[beforeRange]))) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Add highlighted text | ||||||||||
| var highlightText = AttributedString(String(highlightMatch.1)) | ||||||||||
| highlightText.font = .system(size: stylesheet.body.fontSize) | ||||||||||
| highlightText.foregroundColor = stylesheet.body.color.uiColor | ||||||||||
| highlightText.backgroundColor = Color.yellow.opacity(0.3) | ||||||||||
| result.append(highlightText) | ||||||||||
|
|
||||||||||
| // Continue with remaining text | ||||||||||
| currentText = String(currentText[highlightMatch.range.upperBound...]) | ||||||||||
| continue | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // No more special elements, add remaining text | ||||||||||
| result.append(renderPlainText(currentText)) | ||||||||||
| break | ||||||||||
|
|
@@ -322,4 +368,94 @@ public class MarkdownRenderer { | |||||||||
| let content = String(match.2) | ||||||||||
| return (number, content) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // MARK: - Table Rendering | ||||||||||
|
|
||||||||||
| /// Extract table block from lines | ||||||||||
| private func extractTableBlock(_ lines: [String], startIndex: Int) -> ([[String]], Int) { | ||||||||||
| var rows: [[String]] = [] | ||||||||||
| var index = startIndex | ||||||||||
|
|
||||||||||
| while index < lines.count { | ||||||||||
| let line = lines[index] | ||||||||||
|
|
||||||||||
| // Check if line is a table row | ||||||||||
| guard line.starts(with: "|") && line.hasSuffix("|") else { | ||||||||||
| break | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Skip separator row (| --- | --- |) | ||||||||||
| if line.contains("---") { | ||||||||||
|
||||||||||
| // Skip separator row (| --- | --- |) | |
| if line.contains("---") { | |
| // Skip separator row (| --- | --- | or with colons for alignment) | |
| if line.range(of: #"^\|\s*(:?-+:?)\s*(\|\s*(:?-+:?)\s*)*\|$"#, options: .regularExpression) != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The underline HTML tags (
<u>and<ins>) are converted to markdown format_text_, but the MarkdownRenderer does not have logic to render this format with actual underline styling. The underscore format will be interpreted as italic (similar to*text*), not underline.Consider either:
renderInlineMarkdown()method to handle_text_withunderlineStyle = .single<u>text</u>or custom markers)