fix(#1263): render Markdown inside table cells (#1267)

This commit is contained in:
m57 authored and GitHub committed 2026-09-25 22:48:50 +03:00
1 parent 83ac8a3418
commit e80b00a6a7
3 files changed
+74 -19

No files matched your search

@@ -0,0 +1,35 @@
package com.m57.hermescontrol.ui.chat
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@MediumTest
class MarkdownTableRenderingTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun tableHeaderAndCellRenderInlineMarkup() {
composeTestRule.setContent {
MaterialTheme {
MarkdownText(
text = "| **Name** | Value |\n| --- | --- |\n| Item | `code` |",
textColor = MaterialTheme.colorScheme.onSurface,
)
}
}
composeTestRule.onNodeWithText("Name").assertIsDisplayed()
composeTestRule.onNodeWithText("code").assertIsDisplayed()
composeTestRule.onNodeWithText("**Name**").assertDoesNotExist()
composeTestRule.onNodeWithText("`code`").assertDoesNotExist()
}
}
@@ -443,7 +443,15 @@ fun MarkdownText(
} }
is MdBlock.Table -> { is MdBlock.Table -> {
MarkdownTable(block = block, textColor = textColor) MarkdownTable(
block = block,
textColor = textColor,
latexMeasurer = latexMeasurer,
searchQuery = searchQuery,
isCurrentMatch = isCurrentMatch,
linkColor = linkColor,
highlights = highlights,
)
} }
is MdBlock.Footnotes -> { is MdBlock.Footnotes -> {
@@ -10,18 +10,19 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.hrm.latex.renderer.measure.LatexMeasurerState
import com.m57.hermescontrol.theme.SearchHighlightColors
import com.m57.hermescontrol.util.BidiUtils import com.m57.hermescontrol.util.BidiUtils
private val TABLE_COL_WIDTH = 140.dp private val TABLE_COL_WIDTH = 140.dp
@@ -30,6 +31,11 @@ private val TABLE_COL_WIDTH = 140.dp
fun MarkdownTable( fun MarkdownTable(
block: MdBlock.Table, block: MdBlock.Table,
textColor: Color, textColor: Color,
latexMeasurer: LatexMeasurerState,
searchQuery: String,
isCurrentMatch: Boolean,
linkColor: Color,
highlights: SearchHighlightColors,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val isRtl = val isRtl =
@@ -49,18 +55,21 @@ fun MarkdownTable(
.padding(vertical = 4.dp), .padding(vertical = 4.dp),
) { ) {
// Header row // Header row
Row(modifier = Modifier.background(headerBg)) { Row(modifier = Modifier.background(headerBg), verticalAlignment = Alignment.Top) {
block.header.forEachIndexed { idx, cell -> block.header.forEachIndexed { idx, cell ->
val cellRtl = BidiUtils.isRtlText(cell) MarkdownInlineText(
Text( text = cell,
text = if (cellRtl) BidiUtils.anchorTrailingRtl(cell) else cell, textColor = textColor,
textAlign = tableTextAlign(alignments.getOrNull(idx)), latexMeasurer = latexMeasurer,
color = textColor,
style = style =
MaterialTheme.typography.bodySmall.copy( MaterialTheme.typography.bodySmall.copy(
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
textDirection = if (cellRtl) TextDirection.Rtl else TextDirection.Ltr, textAlign = tableTextAlign(alignments.getOrNull(idx)),
), ),
searchQuery = searchQuery,
isCurrentMatch = isCurrentMatch,
linkColor = linkColor,
highlights = highlights,
modifier = modifier =
Modifier Modifier
.width(TABLE_COL_WIDTH) .width(TABLE_COL_WIDTH)
@@ -71,17 +80,20 @@ fun MarkdownTable(
HorizontalDivider(color = textColor.copy(alpha = 0.25f)) HorizontalDivider(color = textColor.copy(alpha = 0.25f))
// Body rows // Body rows
block.rows.forEach { row -> block.rows.forEach { row ->
Row { Row(verticalAlignment = Alignment.Top) {
row.forEachIndexed { idx, cell -> row.forEachIndexed { idx, cell ->
val cellRtl = BidiUtils.isRtlText(cell) MarkdownInlineText(
Text( text = cell,
text = if (cellRtl) BidiUtils.anchorTrailingRtl(cell) else cell, textColor = textColor,
textAlign = tableTextAlign(alignments.getOrNull(idx)), latexMeasurer = latexMeasurer,
color = textColor,
style = style =
MaterialTheme.typography.bodySmall.copy( MaterialTheme.typography.bodySmall.copy(
textDirection = if (cellRtl) TextDirection.Rtl else TextDirection.Ltr, textAlign = tableTextAlign(alignments.getOrNull(idx)),
), ),
searchQuery = searchQuery,
isCurrentMatch = isCurrentMatch,
linkColor = linkColor,
highlights = highlights,
modifier = modifier =
Modifier Modifier
.width(TABLE_COL_WIDTH) .width(TABLE_COL_WIDTH)
@@ -95,9 +107,9 @@ fun MarkdownTable(
} }
} }
private fun tableTextAlign(align: TableAlign?): TextAlign? = private fun tableTextAlign(align: TableAlign?): TextAlign =
when (align) { when (align) {
TableAlign.CENTER -> TextAlign.Center TableAlign.CENTER -> TextAlign.Center
TableAlign.RIGHT -> TextAlign.End TableAlign.RIGHT -> TextAlign.End
else -> null else -> TextAlign.Start
} }