mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[JEWEL-1345] Improve Markdown block renderer API
Keep parseEmbeddedHtml=false on the HtmlBlock no-op path (do not wrap raw HTML in HtmlBlockWithAttributes/Paragraph), restore matching test helpers, and strengthen the renderer regression coverage. closes https://github.com/JetBrains/intellij-community/pull/3547 (cherry picked from commit 434b713d0ba2d1608f69cf90ce77a0c6b570ba51) (cherry picked from commit 059c7ff3bc277a53d571c0a2977f8732996d969d) IJ-MR-220560 GitOrigin-RevId: 5599220b7c3654143a1c4b66139832ec6c687dc6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0ae337cc9f
commit
fdf4e18a78
@@ -386,6 +386,7 @@ f:org.jetbrains.jewel.markdown.processing.ProcessingUtilKt
|
||||
- RenderHeading(org.jetbrains.jewel.markdown.MarkdownBlock$Heading,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$Heading$HN,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- RenderHeading(org.jetbrains.jewel.markdown.MarkdownBlock$Heading,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$Heading,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- RenderHtmlBlock(org.jetbrains.jewel.markdown.MarkdownBlock$HtmlBlock,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$HtmlBlock,Z,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- RenderHtmlBlockWithAttributes(org.jetbrains.jewel.markdown.MarkdownBlock$HtmlBlockWithAttributes,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- RenderIndentedCodeBlock(org.jetbrains.jewel.markdown.MarkdownBlock$CodeBlock$IndentedCodeBlock,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$Code$Indented,Z,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- RenderList(org.jetbrains.jewel.markdown.MarkdownBlock$ListBlock,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$List,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- RenderListItem(org.jetbrains.jewel.markdown.MarkdownBlock$ListItem,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
@@ -399,6 +400,7 @@ f:org.jetbrains.jewel.markdown.processing.ProcessingUtilKt
|
||||
- getRendererExtensions():java.util.List
|
||||
- getRootStyling():org.jetbrains.jewel.markdown.rendering.MarkdownStyling
|
||||
- *:plus(org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension):org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer
|
||||
- p:resolveImages(org.jetbrains.jewel.markdown.WithInlineMarkdown,androidx.compose.runtime.Composer,I):kotlin.Pair
|
||||
*f:org.jetbrains.jewel.markdown.rendering.DefaultMarkdownBlockRenderer$Companion
|
||||
*:org.jetbrains.jewel.markdown.rendering.ImageSourceResolver
|
||||
- *sf:Companion:org.jetbrains.jewel.markdown.rendering.ImageSourceResolver$Companion
|
||||
@@ -469,6 +471,10 @@ f:org.jetbrains.jewel.markdown.rendering.InlineMarkdownRendererKt
|
||||
- a:RenderHeading(org.jetbrains.jewel.markdown.MarkdownBlock$Heading,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$Heading$HN,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- a:RenderHeading(org.jetbrains.jewel.markdown.MarkdownBlock$Heading,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$Heading,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- a:RenderHtmlBlock(org.jetbrains.jewel.markdown.MarkdownBlock$HtmlBlock,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$HtmlBlock,Z,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- RenderHtmlBlockWithAttributes(org.jetbrains.jewel.markdown.MarkdownBlock$HtmlBlockWithAttributes,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- b:RenderHtmlBlockWithAttributes(org.jetbrains.jewel.markdown.MarkdownBlock$HtmlBlockWithAttributes,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I,I):V
|
||||
- RenderImagesOnlyParagraph(java.util.Map,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- b:RenderImagesOnlyParagraph(java.util.Map,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I,I):V
|
||||
- a:RenderIndentedCodeBlock(org.jetbrains.jewel.markdown.MarkdownBlock$CodeBlock$IndentedCodeBlock,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$Code$Indented,Z,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- a:RenderList(org.jetbrains.jewel.markdown.MarkdownBlock$ListBlock,org.jetbrains.jewel.markdown.rendering.MarkdownStyling$List,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
- a:RenderListItem(org.jetbrains.jewel.markdown.MarkdownBlock$ListItem,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.runtime.Composer,I):V
|
||||
@@ -484,6 +490,9 @@ f:org.jetbrains.jewel.markdown.rendering.InlineMarkdownRendererKt
|
||||
- a:getRootStyling():org.jetbrains.jewel.markdown.rendering.MarkdownStyling
|
||||
- *a:plus(org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension):org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer
|
||||
*f:org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer$Companion
|
||||
*f:org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer$ComposeDefaultImpls
|
||||
- sf:RenderHtmlBlockWithAttributes$default(org.jetbrains.jewel.markdown.MarkdownBlock$HtmlBlockWithAttributes,Z,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer,androidx.compose.runtime.Composer,I,I):V
|
||||
- sf:RenderImagesOnlyParagraph$default(java.util.Map,androidx.compose.ui.Modifier,org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer,androidx.compose.runtime.Composer,I,I):V
|
||||
*f:org.jetbrains.jewel.markdown.rendering.MarkdownStyling
|
||||
- sf:$stable:I
|
||||
- *sf:Companion:org.jetbrains.jewel.markdown.rendering.MarkdownStyling$Companion
|
||||
|
||||
@@ -468,6 +468,7 @@ package org.jetbrains.jewel.markdown.rendering {
|
||||
method public java.util.List<org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension> getRendererExtensions();
|
||||
method public org.jetbrains.jewel.markdown.rendering.MarkdownStyling getRootStyling();
|
||||
method @SuppressCompatibility @org.jetbrains.annotations.ApiStatus.Experimental @org.jetbrains.jewel.foundation.ExperimentalJewelApi public operator org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer plus(org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension extension);
|
||||
method @androidx.compose.runtime.Composable protected kotlin.Pair<java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent>,java.util.Set<java.lang.String>> resolveImages(org.jetbrains.jewel.markdown.WithInlineMarkdown blockInlineContent);
|
||||
property public org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer inlineRenderer;
|
||||
property public java.util.List<org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension> rendererExtensions;
|
||||
property public org.jetbrains.jewel.markdown.rendering.MarkdownStyling rootStyling;
|
||||
@@ -475,6 +476,8 @@ package org.jetbrains.jewel.markdown.rendering {
|
||||
}
|
||||
|
||||
public static final class DefaultMarkdownBlockRenderer.Companion {
|
||||
method protected androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.style.TextAlign> getLocalTextAlignment();
|
||||
property protected androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.style.TextAlign> LocalTextAlignment;
|
||||
}
|
||||
|
||||
@SuppressCompatibility @org.jetbrains.annotations.ApiStatus.Experimental @org.jetbrains.jewel.foundation.ExperimentalJewelApi public interface ImageSourceResolver {
|
||||
@@ -570,6 +573,8 @@ package org.jetbrains.jewel.markdown.rendering {
|
||||
method @androidx.compose.runtime.Composable public void RenderHeading(org.jetbrains.jewel.markdown.MarkdownBlock.Heading block, org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Heading styling, boolean enabled, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onUrlClick, androidx.compose.ui.Modifier modifier);
|
||||
method @androidx.compose.runtime.Composable public void RenderHeading(org.jetbrains.jewel.markdown.MarkdownBlock.Heading block, org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Heading.HN styling, boolean enabled, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onUrlClick, androidx.compose.ui.Modifier modifier);
|
||||
method @androidx.compose.runtime.Composable public void RenderHtmlBlock(org.jetbrains.jewel.markdown.MarkdownBlock.HtmlBlock block, org.jetbrains.jewel.markdown.rendering.MarkdownStyling.HtmlBlock styling, boolean enabled, androidx.compose.ui.Modifier modifier);
|
||||
method @androidx.compose.runtime.Composable public default void RenderHtmlBlockWithAttributes(org.jetbrains.jewel.markdown.MarkdownBlock.HtmlBlockWithAttributes block, boolean enabled, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onUrlClick, optional androidx.compose.ui.Modifier modifier);
|
||||
method @androidx.compose.runtime.Composable public default void RenderImagesOnlyParagraph(java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> images, optional androidx.compose.ui.Modifier modifier);
|
||||
method @androidx.compose.runtime.Composable public void RenderIndentedCodeBlock(org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.IndentedCodeBlock block, org.jetbrains.jewel.markdown.rendering.MarkdownStyling.Code.Indented styling, boolean enabled, androidx.compose.ui.Modifier modifier);
|
||||
method @androidx.compose.runtime.Composable public void RenderList(org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock block, org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List styling, boolean enabled, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onUrlClick, androidx.compose.ui.Modifier modifier);
|
||||
method @androidx.compose.runtime.Composable public void RenderListItem(org.jetbrains.jewel.markdown.MarkdownBlock.ListItem block, boolean enabled, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onUrlClick, androidx.compose.ui.Modifier modifier);
|
||||
|
||||
+12
-9
@@ -60,7 +60,8 @@ import org.jetbrains.jewel.markdown.scrolling.ScrollingSynchronizer
|
||||
* provided by the [MarkdownParserFactory], but you can provide your own if you need to customize the parser — e.g.,
|
||||
* to ignore certain tags. If [markdownMode] is `MarkdownMode.WithEditor`, make sure you set
|
||||
* `includeSourceSpans(IncludeSourceSpans.BLOCKS)` on the parser.
|
||||
* @param parseEmbeddedHtml If `true`, a subset of native HTML elements will be parsed as Markdown blocks.
|
||||
* @param parseEmbeddedHtml If `true`, a subset of native HTML elements will be parsed as Markdown blocks. See
|
||||
* [MarkdownHtmlConverter].
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@ExperimentalJewelApi
|
||||
@@ -71,7 +72,7 @@ public class MarkdownProcessor(
|
||||
MarkdownParserFactory.create(optimizeEdits = markdownMode is MarkdownMode.EditorPreview, extensions),
|
||||
private val parseEmbeddedHtml: Boolean = false,
|
||||
) {
|
||||
@Suppress("UnusedPrivateProperty") // languageRecognizer is only here for binary compat reasons
|
||||
@Suppress("UnusedPrivateProperty", "DEPRECATION") // languageRecognizer is only here for binary compat reasons
|
||||
@Deprecated(
|
||||
"`languageRecognizer` is not necessary anymore. Use the constructor without it.",
|
||||
replaceWith = ReplaceWith("MarkdownProcessor(extensions, markdownMode, commonMarkParser)"),
|
||||
@@ -85,6 +86,7 @@ public class MarkdownProcessor(
|
||||
parseEmbeddedHtml: Boolean = false,
|
||||
) : this(extensions, markdownMode, commonMarkParser, parseEmbeddedHtml)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Use a version with a `parseEmbeddedHtml` parameter", level = DeprecationLevel.HIDDEN)
|
||||
public constructor(
|
||||
extensions: List<MarkdownProcessorExtension> = emptyList(),
|
||||
@@ -94,6 +96,7 @@ public class MarkdownProcessor(
|
||||
languageRecognizer: (String) -> MimeType? = { MimeType.Known.fromMarkdownLanguageName(it) },
|
||||
) : this(extensions, markdownMode, commonMarkParser, languageRecognizer, false)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Use a version with a `parseEmbeddedHtml` parameter", level = DeprecationLevel.HIDDEN)
|
||||
public constructor(
|
||||
extensions: List<MarkdownProcessorExtension> = emptyList(),
|
||||
@@ -351,7 +354,7 @@ public class MarkdownProcessor(
|
||||
intermediateHtmlBlocks.mapNotNull { htmlElement ->
|
||||
htmlConverter.convert(this@MarkdownProcessor, htmlElement) { newMdBlock, lines ->
|
||||
when (newMdBlock) {
|
||||
is MarkdownBlock.HtmlBlock -> return@convert newMdBlock
|
||||
is MarkdownBlock.HtmlBlockWithAttributes -> return@convert newMdBlock
|
||||
is MarkdownBlock.ListItem ->
|
||||
return@convert newMdBlock // we convert the list item's inner block instead
|
||||
else -> {
|
||||
@@ -370,7 +373,7 @@ public class MarkdownProcessor(
|
||||
MarkdownBlock.BlockQuote(processChildren(this))
|
||||
|
||||
private fun Heading.toMarkdownHeadingOrNull(): MarkdownBlock.Heading? {
|
||||
if (level < 1 || level > 6) return null
|
||||
if (level !in 1..6) return null
|
||||
return MarkdownBlock.Heading(inlineContent = readInlineMarkdown(this@MarkdownProcessor), level = level)
|
||||
}
|
||||
|
||||
@@ -380,6 +383,11 @@ public class MarkdownProcessor(
|
||||
private fun IndentedCodeBlock.toMarkdownCodeBlockOrNull(): CodeBlock.IndentedCodeBlock =
|
||||
CodeBlock.IndentedCodeBlock(literal.trimEnd('\n'))
|
||||
|
||||
private fun HtmlBlock.toMarkdownHtmlBlockOrNull(): MarkdownBlock.HtmlBlock? {
|
||||
if (literal.isBlank()) return null
|
||||
return MarkdownBlock.HtmlBlock(literal.trimEnd('\n'))
|
||||
}
|
||||
|
||||
private fun BulletList.toMarkdownListOrNull(): ListBlock.UnorderedList? {
|
||||
val children = processListItems()
|
||||
if (children.isEmpty()) return null
|
||||
@@ -443,11 +451,6 @@ public class MarkdownProcessor(
|
||||
forEachChild { child -> child.traverseAll(action) }
|
||||
}
|
||||
|
||||
private fun HtmlBlock.toMarkdownHtmlBlockOrNull(): MarkdownBlock.HtmlBlock? {
|
||||
if (literal.isBlank()) return null
|
||||
return MarkdownBlock.HtmlBlock(literal.trimEnd('\n'))
|
||||
}
|
||||
|
||||
/** Creates a copy of this [MarkdownProcessor] with the same properties, plus the provided [extension]. */
|
||||
@ApiStatus.Experimental
|
||||
@ExperimentalJewelApi
|
||||
|
||||
-5
@@ -17,7 +17,6 @@ import org.commonmark.node.Text as CMText
|
||||
import org.commonmark.parser.beta.ParsedInline
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
|
||||
import org.jetbrains.jewel.foundation.util.JewelLogger
|
||||
import org.jetbrains.jewel.markdown.DimensionSize
|
||||
import org.jetbrains.jewel.markdown.InlineMarkdown
|
||||
import org.jetbrains.jewel.markdown.WithInlineMarkdown
|
||||
@@ -153,10 +152,6 @@ internal fun List<InlineMarkdown>.renderAsSimpleText(): String = buildString {
|
||||
is WithTextContent -> append(node.content)
|
||||
is InlineMarkdown.HardLineBreak -> append('\n')
|
||||
is InlineMarkdown.SoftLineBreak -> append(' ')
|
||||
else -> {
|
||||
JewelLogger.getInstance("MarkdownProcessingUtil")
|
||||
.debug("Ignoring node ${node.javaClass.simpleName} for text rendering")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -10,7 +10,7 @@ import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
|
||||
import org.jetbrains.jewel.markdown.scrolling.ScrollingSynchronizer
|
||||
|
||||
/**
|
||||
* Defines a mechanism for converting HTML elements their child elements, and inline elements into supported Markdown
|
||||
* Defines a mechanism for converting HTML elements, their child elements, and inline elements into supported Markdown
|
||||
* elements (blocks or inlines).
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
@@ -76,7 +76,7 @@ internal class MarkdownHtmlConverter {
|
||||
) ?: return null
|
||||
val transformedBlock = transform(convertedBlock, htmlElement.lineRange)
|
||||
|
||||
return if (htmlElement.attributes.isEmpty()) {
|
||||
if (htmlElement.attributes.isEmpty()) {
|
||||
transformedBlock
|
||||
} else {
|
||||
transform(
|
||||
@@ -92,11 +92,11 @@ internal class MarkdownHtmlConverter {
|
||||
processor: MarkdownProcessor,
|
||||
transform: (MarkdownBlock, IntRange) -> MarkdownBlock,
|
||||
): List<MarkdownBlock> {
|
||||
// Some HTML elements may make up a block (e.g. nested list), but some can be pure inlines.
|
||||
// Some HTML elements may make up a block (e.g., nested list), but some can be pure inlines.
|
||||
// For example, `<li><p>Hello</p></li>` is a block inside ListItem, but `<li><b>Hello<b></li>` is not.
|
||||
// What's worse, `<li>Pay attention to <b>this</b>:<ol>...</ol></li>` combines both inlines and blocks.
|
||||
// The idea here is to split the children into sublists of elements convertible to inline lists,
|
||||
// separated by markdown blocks.
|
||||
// separated by Markdown blocks.
|
||||
val blocks = mutableListOf<MarkdownBlock>()
|
||||
val currentInlineHtmlElements = mutableListOf<MarkdownHtmlNode>()
|
||||
var currentLine = lineRange.first
|
||||
|
||||
+40
-15
@@ -88,6 +88,7 @@ import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock.IndentedCodeBlock
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock.CustomBlock
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock.Heading
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock.HtmlBlock
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock.HtmlBlockWithAttributes
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.OrderedList
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.UnorderedList
|
||||
@@ -126,6 +127,8 @@ public open class DefaultMarkdownBlockRenderer(
|
||||
override val rendererExtensions: List<MarkdownRendererExtension> = emptyList(),
|
||||
override val inlineRenderer: InlineMarkdownRenderer = InlineMarkdownRenderer.create(rendererExtensions),
|
||||
) : MarkdownBlockRenderer {
|
||||
private val unsupportedBlockTypes = mutableSetOf<String>()
|
||||
|
||||
@Composable
|
||||
override fun RenderBlocks(
|
||||
blocks: List<MarkdownBlock>,
|
||||
@@ -153,14 +156,12 @@ public open class DefaultMarkdownBlockRenderer(
|
||||
is ListItem -> RenderListItem(block, enabled, onUrlClick, modifier)
|
||||
is Paragraph -> RenderParagraph(block, rootStyling.paragraph, enabled, onUrlClick, modifier)
|
||||
ThematicBreak -> RenderThematicBreak(rootStyling.thematicBreak, enabled, modifier)
|
||||
is MarkdownBlock.HtmlBlockWithAttributes ->
|
||||
RenderHtmlBlockWithAttributes(block, enabled, onUrlClick, modifier)
|
||||
|
||||
is HtmlBlockWithAttributes -> RenderHtmlBlockWithAttributes(block, enabled, onUrlClick, modifier)
|
||||
is CustomBlock -> {
|
||||
rendererExtensions
|
||||
.find { it.blockRenderer?.canRender(block) == true }
|
||||
?.blockRenderer
|
||||
?.RenderCustomBlock(
|
||||
val blockRenderer =
|
||||
rendererExtensions.find { it.blockRenderer?.canRender(block) == true }?.blockRenderer
|
||||
if (blockRenderer != null) {
|
||||
blockRenderer.RenderCustomBlock(
|
||||
block = block,
|
||||
blockRenderer = this,
|
||||
inlineRenderer = inlineRenderer,
|
||||
@@ -168,10 +169,22 @@ public open class DefaultMarkdownBlockRenderer(
|
||||
modifier = modifier,
|
||||
onUrlClick = onUrlClick,
|
||||
)
|
||||
} else {
|
||||
logUnsupportedBlock(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun logUnsupportedBlock(block: MarkdownBlock) {
|
||||
// We do not render unsupported blocks; emit a one-off log per renderer instance (best-effort).
|
||||
val simpleName = block::class.simpleName
|
||||
if (simpleName != null && simpleName !in unsupportedBlockTypes) {
|
||||
JewelLogger.getInstance(javaClass).warn("Cannot render unsupported block type: $simpleName.")
|
||||
unsupportedBlockTypes += simpleName
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun RenderParagraph(
|
||||
block: Paragraph,
|
||||
@@ -660,7 +673,7 @@ public open class DefaultMarkdownBlockRenderer(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun resolveImages(blockInlineContent: WithInlineMarkdown): ResolvedImages {
|
||||
protected open fun resolveImages(blockInlineContent: WithInlineMarkdown): ResolvedImages {
|
||||
val map = remember(blockInlineContent) { mutableStateMapOf<String, InlineTextContent>() }
|
||||
val failedSources = remember(blockInlineContent) { mutableStateSetOf<String>() }
|
||||
val imagesRenderer = rendererExtensions.firstNotNullOfOrNull { it.imageRendererExtension }
|
||||
@@ -705,8 +718,8 @@ public open class DefaultMarkdownBlockRenderer(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
// We use movableContent so changing the flag doesn't reset the content
|
||||
val movableContent = remember { movableContentOf { content() } }
|
||||
// We use movableContent, so changing the flag doesn't reset the content
|
||||
val movableContent = remember(content) { movableContentOf { content() } }
|
||||
if (isScrollable) {
|
||||
HorizontallyScrollableContainer(modifier) { movableContent() }
|
||||
} else {
|
||||
@@ -715,12 +728,21 @@ public open class DefaultMarkdownBlockRenderer(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RenderHtmlBlockWithAttributes(
|
||||
block: MarkdownBlock.HtmlBlockWithAttributes,
|
||||
override fun RenderHtmlBlockWithAttributes(
|
||||
block: HtmlBlockWithAttributes,
|
||||
enabled: Boolean,
|
||||
onUrlClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val mdBlock = block.mdBlock
|
||||
if (
|
||||
block.attributes.isEmpty() &&
|
||||
mdBlock is Paragraph &&
|
||||
mdBlock.inlineContent.singleOrNull() is InlineMarkdown.HtmlInline
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
val textAlignment: TextAlign =
|
||||
when (block.attributes["align"]) {
|
||||
"left" -> TextAlign.Start
|
||||
@@ -1052,8 +1074,11 @@ public open class DefaultMarkdownBlockRenderer(
|
||||
DefaultMarkdownBlockRenderer(rootStyling, rendererExtensions = rendererExtensions + extension, inlineRenderer)
|
||||
|
||||
public companion object {
|
||||
@Suppress("VariableNaming")
|
||||
private val LocalTextAlignment: ProvidableCompositionLocal<TextAlign> = staticCompositionLocalOf {
|
||||
/**
|
||||
* Holds the current text alignment for the subtree of the composition. Used by the HTML parsing path to
|
||||
* transmit text alignment down the tree.
|
||||
*/
|
||||
protected val LocalTextAlignment: ProvidableCompositionLocal<TextAlign> = staticCompositionLocalOf {
|
||||
TextAlign.Start
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -1,5 +1,6 @@
|
||||
package org.jetbrains.jewel.markdown.rendering
|
||||
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -315,6 +316,38 @@ public interface MarkdownBlockRenderer {
|
||||
modifier: Modifier,
|
||||
)
|
||||
|
||||
/**
|
||||
* Renders a [MarkdownBlock.HtmlBlockWithAttributes] into a Compose UI. Raw HTML blocks may be exposed as
|
||||
* [MarkdownBlock.HtmlBlockWithAttributes] wrapping a paragraph with raw HTML inline content. Supported parsed HTML
|
||||
* is converted into Markdown blocks and wrapped with attributes when applicable.
|
||||
*
|
||||
* @param block The HTML block to render.
|
||||
* @param enabled True if the block should be enabled, false otherwise.
|
||||
* @param onUrlClick The callback invoked when a link is clicked.
|
||||
* @param modifier The modifier to be applied to the composable.
|
||||
*/
|
||||
@Composable
|
||||
public fun RenderHtmlBlockWithAttributes(
|
||||
block: MarkdownBlock.HtmlBlockWithAttributes,
|
||||
enabled: Boolean,
|
||||
onUrlClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// No-op by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a paragraph containing only images into a Compose UI. This is delegated to from [RenderParagraph] when it
|
||||
* only contains images, and uses a more efficient path than inline images in a text run.
|
||||
*
|
||||
* @param images The images to render.
|
||||
* @param modifier The modifier to be applied to the composable.
|
||||
*/
|
||||
@Composable
|
||||
public fun RenderImagesOnlyParagraph(images: Map<String, InlineTextContent>, modifier: Modifier = Modifier) {
|
||||
// No-op by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this instance, using the provided non-null parameters, or the current values for the null ones.
|
||||
*
|
||||
|
||||
+1
@@ -111,6 +111,7 @@ public open class ScrollSyncMarkdownBlockRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated(
|
||||
message =
|
||||
"This class function is not scalable as it relies on a pre-resolved MimeType object. " +
|
||||
|
||||
+1
-1
@@ -67,13 +67,13 @@ private fun MarkdownBlock.findDifferenceWith(expected: MarkdownBlock, indentSize
|
||||
return when (this) {
|
||||
is Paragraph -> diffParagraph(this, expected, indent)
|
||||
is BlockQuote -> children.findDifferences((expected as BlockQuote).children, indentSize)
|
||||
is HtmlBlock -> diffHtmlBlock(this, expected, indent)
|
||||
is FencedCodeBlock -> diffFencedCodeBlock(this, expected, indent)
|
||||
is IndentedCodeBlock -> diffIndentedCodeBlock(this, expected, indent)
|
||||
is Heading -> diffHeading(this, expected, indent)
|
||||
is ListBlock -> diffList(this, expected, indentSize, indent)
|
||||
is ListItem -> children.findDifferences((expected as ListItem).children, indentSize)
|
||||
is ThematicBreak -> emptyList() // They can only differ in their node
|
||||
is HtmlBlock -> diffHtmlBlock(this, expected, indent)
|
||||
is HtmlBlockWithAttributes -> diffHtmlBlockWithAttributes(this, expected, indent)
|
||||
else -> error("Unsupported MarkdownBlock: ${this.javaClass.name}")
|
||||
}
|
||||
|
||||
+28
@@ -8,10 +8,13 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertCountEquals
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.runComposeUiTest
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import org.jetbrains.jewel.markdown.MarkdownBlock
|
||||
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
|
||||
import org.jetbrains.jewel.markdown.testing.MarkdownTestTheme
|
||||
import org.jetbrains.jewel.markdown.testing.createMarkdownTestStyling
|
||||
@@ -126,6 +129,31 @@ public class DefaultMarkdownBlockRendererTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun `raw HTML blocks are not rendered by default`() {
|
||||
runComposeUiTest {
|
||||
// parseEmbeddedHtml defaults to false: keep producing HtmlBlock (renderer no-op),
|
||||
// not HtmlBlockWithAttributes wrapping a Paragraph that could leak raw HTML text.
|
||||
val processor = MarkdownProcessor(parseEmbeddedHtml = false)
|
||||
val blocks = processor.processMarkdownDocument("<div>Raw HTML</div>\n\nVisible paragraph")
|
||||
|
||||
assertEquals(2, blocks.size)
|
||||
assertTrue(blocks[0] is MarkdownBlock.HtmlBlock)
|
||||
assertEquals("<div>Raw HTML</div>", (blocks[0] as MarkdownBlock.HtmlBlock).content)
|
||||
|
||||
setContent {
|
||||
MarkdownTestTheme {
|
||||
val renderer = DefaultMarkdownBlockRenderer(createMarkdownTestStyling(), emptyList())
|
||||
renderer.RenderBlocks(blocks, enabled = true, onUrlClick = {}, modifier = Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
onAllNodesWithText("<div>Raw HTML</div>").assertCountEquals(0)
|
||||
onAllNodesWithText("Raw HTML").assertCountEquals(0)
|
||||
onNodeWithText("Visible paragraph").assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun `appendInlineContent uses expected annotation tag`() {
|
||||
val annotated = buildAnnotatedString { appendInlineContent("testId") }
|
||||
|
||||
Reference in New Issue
Block a user