From 08970fec3ee4a3af2d8cb6cd3c97147f7cb37218 Mon Sep 17 00:00:00 2001 From: Ivan Morgillo Date: Tue, 9 Jul 2024 12:49:52 +0200 Subject: [PATCH] Migrate to the new LinkAnnotation API (#425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * replace deprecated UrlAnnotation with LinkAnnotation * extract determinePointerIcon() * Replace deprecated UrlAnnotation with LinkAnnotation * replace deprecated ClickableText * Introduced LinkAnnotation.Clickable to drive the behaviour of links * Replaced SimpleClickableText with the good ol' Text * Successfully wired onUrlClick and onTextClick lambdas and the enabled flag. Closes #418 * update api files * add disabled UI for links When links are disabled, we show something that looks like a link, but it's not clickable and has no 👆 pointer icon. * add a checkbox to toggle links in the Markdown standalone showcase --- markdown/core/api/core.api | 8 +- .../DefaultInlineMarkdownRenderer.kt | 25 ++- .../rendering/DefaultMarkdownBlockRenderer.kt | 160 ++++++++---------- .../rendering/InlineMarkdownRenderer.kt | 1 + .../samples/standalone/view/MarkdownView.kt | 14 +- .../view/markdown/MarkdownEditor.kt | 32 +++- .../view/markdown/MarkdownPreview.kt | 4 +- 7 files changed, 136 insertions(+), 108 deletions(-) diff --git a/markdown/core/api/core.api b/markdown/core/api/core.api index 54a112605..64849a927 100644 --- a/markdown/core/api/core.api +++ b/markdown/core/api/core.api @@ -457,7 +457,7 @@ public class org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRendere public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer$Companion; public fun (Ljava/util/List;)V public fun ([Lorg/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension;)V - public fun renderAsAnnotatedString (Ljava/lang/Iterable;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;Z)Landroidx/compose/ui/text/AnnotatedString; + public fun renderAsAnnotatedString (Ljava/lang/Iterable;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ZLkotlin/jvm/functions/Function1;)Landroidx/compose/ui/text/AnnotatedString; } public final class org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer$Companion { @@ -486,7 +486,7 @@ public class org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer public abstract interface class org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer { public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer$Companion; - public abstract fun renderAsAnnotatedString (Ljava/lang/Iterable;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;Z)Landroidx/compose/ui/text/AnnotatedString; + public abstract fun renderAsAnnotatedString (Ljava/lang/Iterable;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ZLkotlin/jvm/functions/Function1;)Landroidx/compose/ui/text/AnnotatedString; } public final class org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer$Companion { @@ -494,6 +494,10 @@ public final class org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer public static synthetic fun default$default (Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer$Companion;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer; } +public final class org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer$DefaultImpls { + public static synthetic fun renderAsAnnotatedString$default (Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;Ljava/lang/Iterable;Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Landroidx/compose/ui/text/AnnotatedString; +} + public final class org/jetbrains/jewel/markdown/rendering/InlinesStyling { public static final field $stable I public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/InlinesStyling$Companion; diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt index 86dae2bc8..26dfc56e5 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer.kt @@ -4,9 +4,8 @@ import androidx.compose.foundation.text.appendInlineContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString.Builder -import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.UrlAnnotation import androidx.compose.ui.text.buildAnnotatedString import org.commonmark.renderer.text.TextContentRenderer import org.jetbrains.jewel.foundation.ExperimentalJewelApi @@ -14,11 +13,14 @@ import org.jetbrains.jewel.markdown.InlineMarkdown import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension @ExperimentalJewelApi -public open class DefaultInlineMarkdownRenderer(rendererExtensions: List) : InlineMarkdownRenderer { +public open class DefaultInlineMarkdownRenderer( + rendererExtensions: List, +) : InlineMarkdownRenderer { public constructor(vararg extensions: MarkdownProcessorExtension) : this(extensions.toList()) private val plainTextRenderer = - TextContentRenderer.builder() + TextContentRenderer + .builder() .extensions(rendererExtensions.map { it.textRendererExtension }) .build() @@ -26,16 +28,17 @@ public open class DefaultInlineMarkdownRenderer(rendererExtensions: List, styling: InlinesStyling, enabled: Boolean, + onUrlClicked: ((String) -> Unit)?, ): AnnotatedString = buildAnnotatedString { - appendInlineMarkdownFrom(inlineMarkdown, styling, enabled) + appendInlineMarkdownFrom(inlineMarkdown, styling, enabled, onUrlClicked) } - @OptIn(ExperimentalTextApi::class) private fun Builder.appendInlineMarkdownFrom( inlineMarkdown: Iterable, styling: InlinesStyling, enabled: Boolean, + onUrlClicked: ((String) -> Unit)? = null, ) { for (child in inlineMarkdown) { when (child) { @@ -60,7 +63,15 @@ public open class DefaultInlineMarkdownRenderer(rendererExtensions: List { withStyles(styling.link.withEnabled(enabled), child) { - pushUrlAnnotation(UrlAnnotation(it.nativeNode.destination)) + if (enabled) { + val destination = it.nativeNode.destination + val link = + LinkAnnotation.Clickable( + tag = destination, + linkInteractionListener = { _ -> onUrlClicked?.invoke(destination) }, + ) + pushLink(link) + } appendInlineMarkdownFrom(it.children, styling, enabled) } } diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt index 6c96f02a8..4f0ea7757 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt @@ -6,7 +6,9 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.HorizontalScrollbar import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -20,7 +22,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollbarAdapter -import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -42,7 +43,6 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection.Ltr @@ -109,7 +109,8 @@ public open class DefaultMarkdownBlockRenderer( is Paragraph -> render(block, rootStyling.paragraph, enabled, onUrlClick, onTextClick) ThematicBreak -> renderThematicBreak(rootStyling.thematicBreak) is CustomBlock -> { - rendererExtensions.find { it.blockRenderer.canRender(block) } + rendererExtensions + .find { it.blockRenderer.canRender(block) } ?.blockRenderer ?.render(block, blockRenderer = this, inlineRenderer, enabled, onUrlClick, onTextClick) } @@ -124,14 +125,29 @@ public open class DefaultMarkdownBlockRenderer( onUrlClick: (String) -> Unit, onTextClick: () -> Unit, ) { - val renderedContent = rememberRenderedContent(block, styling.inlinesStyling, enabled) - SimpleClickableText( + val renderedContent = + rememberRenderedContent( + block, + styling.inlinesStyling, + enabled, + onUrlClick, + ) + val textColor = + styling.inlinesStyling.textStyle.color + .takeOrElse { LocalContentColor.current } + .takeOrElse { styling.inlinesStyling.textStyle.color } + val mergedStyle = styling.inlinesStyling.textStyle.merge(TextStyle(color = textColor)) + val interactionSource = remember { MutableInteractionSource() } + + Text( + modifier = + Modifier.clickable( + interactionSource = interactionSource, + indication = null, + onClick = onTextClick, + ), text = renderedContent, - textStyle = styling.inlinesStyling.textStyle, - color = styling.inlinesStyling.textStyle.color.takeOrElse { LocalContentColor.current }, - enabled = enabled, - onUnhandledClick = onTextClick, - onUrlClick = onUrlClick, + style = mergedStyle, ) } @@ -162,7 +178,13 @@ public open class DefaultMarkdownBlockRenderer( onUrlClick: (String) -> Unit, onTextClick: () -> Unit, ) { - val renderedContent = rememberRenderedContent(block, styling.inlinesStyling, enabled) + val renderedContent = + rememberRenderedContent( + block, + styling.inlinesStyling, + enabled, + onUrlClick, + ) Heading( renderedContent, styling.inlinesStyling.textStyle, @@ -170,9 +192,6 @@ public open class DefaultMarkdownBlockRenderer( styling.underlineWidth, styling.underlineColor, styling.underlineGap, - enabled, - onUrlClick, - onTextClick, ) } @@ -184,18 +203,15 @@ public open class DefaultMarkdownBlockRenderer( underlineWidth: Dp, underlineColor: Color, underlineGap: Dp, - enabled: Boolean, - onUrlClick: (String) -> Unit, - onTextClick: () -> Unit, ) { Column(modifier = Modifier.padding(paddingValues)) { - SimpleClickableText( + val textColor = + textStyle.color + .takeOrElse { LocalContentColor.current.takeOrElse { textStyle.color } } + val mergedStyle = textStyle.merge(TextStyle(color = textColor)) + Text( text = renderedContent, - textStyle = textStyle, - color = textStyle.color.takeOrElse { LocalContentColor.current }, - enabled = enabled, - onUnhandledClick = onTextClick, - onUrlClick = onUrlClick, + style = mergedStyle, ) if (underlineWidth > 0.dp && underlineColor.isSpecified) { @@ -214,21 +230,21 @@ public open class DefaultMarkdownBlockRenderer( onTextClick: () -> Unit, ) { Column( - Modifier.drawBehind { - val isLtr = layoutDirection == Ltr - val lineWidthPx = styling.lineWidth.toPx() - val x = if (isLtr) lineWidthPx / 2 else size.width - lineWidthPx / 2 - - drawLine( - styling.lineColor, - Offset(x, 0f), - Offset(x, size.height), - lineWidthPx, - styling.strokeCap, - styling.pathEffect, - ) - } - .padding(styling.padding), + Modifier + .drawBehind { + val isLtr = layoutDirection == Ltr + val lineWidthPx = styling.lineWidth.toPx() + val x = if (isLtr) lineWidthPx / 2 else size.width - lineWidthPx / 2 + + drawLine( + styling.lineColor, + Offset(x, 0f), + Offset(x, size.height), + lineWidthPx, + styling.strokeCap, + styling.pathEffect, + ) + }.padding(styling.padding), verticalArrangement = Arrangement.spacedBy(rootStyling.blockVerticalSpacing), ) { CompositionLocalProvider(LocalContentColor provides styling.textColor) { @@ -278,7 +294,8 @@ public open class DefaultMarkdownBlockRenderer( style = styling.numberStyle, color = styling.numberStyle.color.takeOrElse { LocalContentColor.current }, modifier = - Modifier.widthIn(min = styling.numberMinWidth) + Modifier + .widthIn(min = styling.numberMinWidth) .pointerHoverIcon(PointerIcon.Default, overrideDescendants = true), textAlign = styling.numberTextAlign, ) @@ -357,7 +374,8 @@ public open class DefaultMarkdownBlockRenderer( ) { HorizontallyScrollingContainer( isScrollable = styling.scrollsHorizontally, - Modifier.background(styling.background, styling.shape) + Modifier + .background(styling.background, styling.shape) .border(styling.borderWidth, styling.borderColor, styling.shape) .then(if (styling.fillWidth) Modifier.fillMaxWidth() else Modifier), ) { @@ -366,7 +384,8 @@ public open class DefaultMarkdownBlockRenderer( style = styling.editorTextStyle, color = styling.editorTextStyle.color.takeOrElse { LocalContentColor.current }, modifier = - Modifier.padding(styling.padding) + Modifier + .padding(styling.padding) .pointerHoverIcon(PointerIcon.Default, overrideDescendants = true), ) } @@ -379,7 +398,8 @@ public open class DefaultMarkdownBlockRenderer( ) { HorizontallyScrollingContainer( isScrollable = styling.scrollsHorizontally, - Modifier.background(styling.background, styling.shape) + Modifier + .background(styling.background, styling.shape) .border(styling.borderWidth, styling.borderColor, styling.shape) .then(if (styling.fillWidth) Modifier.fillMaxWidth() else Modifier), ) { @@ -419,7 +439,7 @@ public open class DefaultMarkdownBlockRenderer( infoText: String, alignment: Alignment.Horizontal, textStyle: TextStyle, - modifier: Modifier, + modifier: Modifier = Modifier, ) { Column(modifier, horizontalAlignment = alignment) { DisableSelection { @@ -450,53 +470,9 @@ public open class DefaultMarkdownBlockRenderer( block: BlockWithInlineMarkdown, styling: InlinesStyling, enabled: Boolean, + onUrlClick: ((String) -> Unit)? = null, ) = remember(block.inlineContent, styling, enabled) { - inlineRenderer.renderAsAnnotatedString(block.inlineContent, styling, enabled) - } - - @OptIn(ExperimentalTextApi::class) - @Composable - private fun SimpleClickableText( - text: AnnotatedString, - textStyle: TextStyle, - enabled: Boolean, - modifier: Modifier = Modifier, - color: Color = Color.Unspecified, - onUrlClick: (String) -> Unit, - onUnhandledClick: () -> Unit, - ) { - var hoverPointerIcon by remember { mutableStateOf(PointerIcon.Default) } - val actualPointerIcon = - remember(hoverPointerIcon, enabled) { - if (enabled) hoverPointerIcon else PointerIcon.Default - } - - val textColor = color.takeOrElse { LocalContentColor.current.takeOrElse { textStyle.color } } - - val mergedStyle = textStyle.merge(TextStyle(color = textColor)) - - ClickableText( - text = text, - style = mergedStyle, - modifier = modifier.pointerHoverIcon(actualPointerIcon, true), - onHover = { offset -> - hoverPointerIcon = - if (offset == null || text.getUrlAnnotations(offset, offset).isEmpty()) { - PointerIcon.Default - } else { - PointerIcon.Hand - } - }, - ) { offset -> - if (!enabled) return@ClickableText - - val span = text.getUrlAnnotations(offset, offset).firstOrNull() - if (span != null) { - onUrlClick(span.item.url) - } else { - onUnhandledClick() - } - } + inlineRenderer.renderAsAnnotatedString(block.inlineContent, styling, enabled, onUrlClick) } @Composable @@ -511,7 +487,8 @@ public open class DefaultMarkdownBlockRenderer( content = { val scrollState = rememberScrollState() Box( - Modifier.layoutId("mainContent") + Modifier + .layoutId("mainContent") .then(if (isScrollable) Modifier.horizontalScroll(scrollState) else Modifier), ) { content() @@ -529,7 +506,8 @@ public open class DefaultMarkdownBlockRenderer( HorizontalScrollbar( rememberScrollbarAdapter(scrollState), - Modifier.layoutId("containerHScrollbar") + Modifier + .layoutId("containerHScrollbar") .padding(start = 2.dp, end = 2.dp, bottom = 2.dp) .alpha(alpha), ) diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt index f35a14547..67b7c0ff5 100644 --- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt +++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer.kt @@ -15,6 +15,7 @@ public interface InlineMarkdownRenderer { inlineMarkdown: Iterable, styling: InlinesStyling, enabled: Boolean, + onUrlClicked: ((String) -> Unit)? = null, ): AnnotatedString public companion object { diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/MarkdownView.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/MarkdownView.kt index a59cfddfa..fdd282da6 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/MarkdownView.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/MarkdownView.kt @@ -24,10 +24,20 @@ import org.jetbrains.jewel.ui.component.Divider fun MarkdownDemo() { Row(Modifier.trackActivation().fillMaxSize().background(JewelTheme.globalColors.panelBackground)) { var currentMarkdown by remember { mutableStateOf(JewelReadme) } - MarkdownEditor(currentMarkdown, { currentMarkdown = it }, Modifier.fillMaxHeight().weight(1f)) + var linksAreEnabled by remember { mutableStateOf(true) } + MarkdownEditor( + currentMarkdown = currentMarkdown, + onMarkdownChange = { currentMarkdown = it }, + onLinksEnabledChange = { linksAreEnabled = it }, + modifier = Modifier.fillMaxHeight().weight(1f), + ) Divider(Orientation.Vertical, Modifier.fillMaxHeight()) - MarkdownPreview(currentMarkdown, Modifier.fillMaxHeight().weight(1f)) + MarkdownPreview( + modifier = Modifier.fillMaxHeight().weight(1f), + rawMarkdown = currentMarkdown, + linksAreEnabled = linksAreEnabled, + ) } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt index dfe174a47..9e5b7059d 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownEditor.kt @@ -24,6 +24,7 @@ import com.darkrockstudios.libraries.mpfilepicker.JvmFile import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons import org.jetbrains.jewel.ui.Orientation +import org.jetbrains.jewel.ui.component.Checkbox import org.jetbrains.jewel.ui.component.Divider import org.jetbrains.jewel.ui.component.Icon import org.jetbrains.jewel.ui.component.OutlinedButton @@ -35,22 +36,29 @@ import org.jetbrains.jewel.ui.component.TextArea internal fun MarkdownEditor( currentMarkdown: String, onMarkdownChange: (String) -> Unit, + onLinksEnabledChange: (Boolean) -> Unit, modifier: Modifier = Modifier, ) { Column(modifier) { ControlsRow( - onMarkdownChange, - Modifier.fillMaxWidth().background(JewelTheme.globalColors.panelBackground).padding(8.dp), + modifier = Modifier.fillMaxWidth().background(JewelTheme.globalColors.panelBackground).padding(8.dp), + onMarkdownChange = onMarkdownChange, + onLinksEnabledChange = onLinksEnabledChange, ) Divider(orientation = Orientation.Horizontal) - Editor(currentMarkdown, onMarkdownChange, Modifier.fillMaxWidth().weight(1f)) + Editor( + currentMarkdown = currentMarkdown, + onMarkdownChange = onMarkdownChange, + modifier = Modifier.fillMaxWidth().weight(1f), + ) } } @Composable private fun ControlsRow( - onMarkdownChange: (String) -> Unit, modifier: Modifier = Modifier, + onMarkdownChange: (String) -> Unit, + onLinksEnabledChange: (Boolean) -> Unit, ) { Row( modifier.horizontalScroll(rememberScrollState()), @@ -117,6 +125,20 @@ private fun ControlsRow( } } } + + var linksAreEnabled by remember { mutableStateOf(true) } + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Text("Links enabled") + Checkbox( + checked = linksAreEnabled, + onCheckedChange = { + linksAreEnabled = it + onLinksEnabledChange(it) + }, + ) + } } } @@ -124,7 +146,7 @@ private fun ControlsRow( private fun Editor( currentMarkdown: String, onMarkdownChange: (String) -> Unit, - modifier: Modifier, + modifier: Modifier = Modifier, ) { Box(modifier.padding(16.dp)) { TextArea( diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt index 5a9a58dcb..072c29f53 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt @@ -43,8 +43,9 @@ import java.net.URI @Composable internal fun MarkdownPreview( - rawMarkdown: String, modifier: Modifier = Modifier, + rawMarkdown: String, + linksAreEnabled: Boolean, ) { val isDark = JewelTheme.isDark @@ -98,6 +99,7 @@ internal fun MarkdownPreview( contentPadding = PaddingValues(16.dp), state = lazyListState, selectable = true, + enabled = linksAreEnabled, onUrlClick = { url -> Desktop.getDesktop().browse(URI.create(url)) }, )