diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6b4e032..a70a545 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -58,6 +58,7 @@ jobs:
./gradlew multiplatform-markdown-renderer-m3:publishAllPublicationsToMavenCentralRepository --no-daemon --no-configure-on-demand --no-parallel
./gradlew multiplatform-markdown-renderer-coil2:publishAllPublicationsToMavenCentralRepository --no-daemon --no-configure-on-demand --no-parallel
./gradlew multiplatform-markdown-renderer-coil3:publishAllPublicationsToMavenCentralRepository --no-daemon --no-configure-on-demand --no-parallel
+ ./gradlew multiplatform-markdown-renderer-code:publishAllPublicationsToMavenCentralRepository --no-daemon --no-configure-on-demand --no-parallel
build:
name: Build
diff --git a/Gemfile.lock b/Gemfile.lock
index 849838b..641c286 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -57,8 +57,7 @@ GEM
open4 (1.3.4)
public_suffix (5.0.3)
rchardet (1.8.0)
- rexml (3.2.8)
- strscan (>= 3.0.9)
+ rexml (3.3.9)
ruby-ll (2.1.3)
ansi
ast
@@ -66,7 +65,6 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
- strscan (3.1.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.4.2)
diff --git a/README.md b/README.md
index e094472..3dfafec 100644
--- a/README.md
+++ b/README.md
@@ -224,7 +224,42 @@ Markdown(
paragraph = customParagraphComponent
)
)
+```
+
+Another example to of a custom component is changing the rendering of an unordered list.
+
+```kotlin
+// Define a custom component for rendering unordered list items in Markdown
+val customUnorderedListComponent: MarkdownComponent = {
+ // Use the MarkdownListItems composable to render the list items
+ MarkdownListItems(it.content, it.node, level = 0) { index, child ->
+ // Create a row layout for each list item with spacing between elements
+ Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ // Render an icon for the bullet point with a green tint
+ Icon(
+ imageVector = icon,
+ tint = Color.Green,
+ contentDescription = null,
+ modifier = Modifier.size(20.dp),
+ )
+ // Extract the bullet marker text from the child node
+ val bulletMarker: String = child.findChildOfType(MarkdownTokenTypes.LIST_BULLET)?.getTextInNode(it.content).toString()
+ // Extract the item text and remove the bullet marker from it
+ val itemText = child.getTextInNode(it.content).toString().replace(bulletMarker, "")
+
+ // Render the item text using the MarkdownText composable
+ MarkdownText(content = itemText)
+ }
+ }
+}
+// Define the `Markdown` composable and pass in the custom unorderedList component
+Markdown(
+ content,
+ components = markdownComponents(
+ unorderedList = customUnorderedListComponent
+ )
+)
```
@@ -251,7 +286,8 @@ Markdown(
```
> [!NOTE]
-> 0.21.0 adds JVM support for this dependency via `HTTPUrlConnection` -> however this is expected to be removed in the future.
+> 0.21.0 adds JVM support for this dependency via `HTTPUrlConnection` -> however this is expected to be removed in the
+> future.
> [!NOTE]
> Please refer to the official coil2 documentation on how to adjust the `ImageLoader`
@@ -276,6 +312,43 @@ Markdown(
> [!NOTE]
> The `coil3` module does depend on SNAPSHOT builds of coil3
+### Syntax Highlighting
+
+The library (introduced with 0.27.0) offers optional support for syntax highlighting via
+the [Highlights](https://github.com/SnipMeDev/Highlights) project.
+This support is not included in the core, and can be enabled by adding the `multiplatform-markdown-renderer-code`
+dependency.
+
+```groovy
+implementation("com.mikepenz:multiplatform-markdown-renderer-code:${version}")
+```
+
+Once added, the `Markdown` has to be configured to use the alternative code highlighter.
+
+```kotlin
+// Use default color scheme
+Markdown(
+ MARKDOWN,
+ components = markdownComponents(
+ codeBlock = highlightedCodeBlock,
+ codeFence = highlightedCodeFence,
+ )
+)
+
+// ADVANCED: Customize Highlights library by defining different theme
+val isDarkTheme = isSystemInDarkTheme()
+val highlightsBuilder = remember(isDarkTheme) {
+ Highlights.Builder().theme(SyntaxThemes.atom(darkMode = isDarkTheme))
+}
+Markdown(
+ MARKDOWN,
+ components = markdownComponents(
+ codeBlock = { MarkdownHighlightedCodeBlock(it.content, it.node, highlightsBuilder) },
+ codeFence = { MarkdownHighlightedCodeFence(it.content, it.node, highlightsBuilder) },
+ )
+)
+```
+
## Dependency
This project uses JetBrains [markdown](https://github.com/JetBrains/markdown/) Multiplatform
diff --git a/app-desktop/build.gradle.kts b/app-desktop/build.gradle.kts
index 8e8521a..128de8f 100644
--- a/app-desktop/build.gradle.kts
+++ b/app-desktop/build.gradle.kts
@@ -19,6 +19,7 @@ dependencies {
implementation(projects.multiplatformMarkdownRenderer)
implementation(projects.multiplatformMarkdownRendererM2)
implementation(projects.multiplatformMarkdownRendererCoil3)
+ implementation(projects.multiplatformMarkdownRendererCode)
implementation(compose.desktop.currentOs)
implementation(compose.foundation)
diff --git a/app-desktop/src/main/kotlin/main.kt b/app-desktop/src/main/kotlin/main.kt
index 99e2e47..eb102aa 100644
--- a/app-desktop/src/main/kotlin/main.kt
+++ b/app-desktop/src/main/kotlin/main.kt
@@ -1,3 +1,4 @@
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
@@ -11,12 +12,17 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl
+import com.mikepenz.markdown.compose.components.markdownComponents
+import com.mikepenz.markdown.compose.elements.MarkdownHighlightedCodeBlock
+import com.mikepenz.markdown.compose.elements.MarkdownHighlightedCodeFence
import com.mikepenz.markdown.compose.extendedspans.ExtendedSpans
import com.mikepenz.markdown.compose.extendedspans.RoundedCornerSpanPainter
import com.mikepenz.markdown.compose.extendedspans.SquigglyUnderlineSpanPainter
import com.mikepenz.markdown.compose.extendedspans.rememberSquigglyUnderlineAnimator
import com.mikepenz.markdown.m2.Markdown
import com.mikepenz.markdown.model.markdownExtendedSpans
+import dev.snipme.highlights.Highlights
+import dev.snipme.highlights.model.SyntaxThemes
fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "Markdown Sample") {
@@ -29,9 +35,17 @@ fun main() = application {
}
) {
val scrollState = rememberScrollState()
+ val isDarkTheme = isSystemInDarkTheme()
+ val highlightsBuilder = remember(isDarkTheme) {
+ Highlights.Builder().theme(SyntaxThemes.atom(darkMode = isDarkTheme))
+ }
Markdown(
MARKDOWN,
+ components = markdownComponents(
+ codeBlock = { MarkdownHighlightedCodeBlock(it.content, it.node, highlightsBuilder) },
+ codeFence = { MarkdownHighlightedCodeFence(it.content, it.node, highlightsBuilder) },
+ ),
imageTransformer = Coil3ImageTransformerImpl,
extendedSpans = markdownExtendedSpans {
val animator = rememberSquigglyUnderlineAnimator()
@@ -76,7 +90,7 @@ This is a paragraph with a [link](https://www.jetbrains.com/).
This is a code block:
```kotlin
fun main() {
-println("Hello, world!")
+ println("Hello, world!")
}
```
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index bc8575f..677d40e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,7 +18,7 @@ android {
defaultConfig {
applicationId = "com.mikepenz.markdown"
- minSdk = libs.versions.minSdk.get().toInt()
+ minSdk = 24 // anything below faces: https://github.com/mikepenz/multiplatform-markdown-renderer/issues/223
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = property("VERSION_CODE").toString().toInt()
diff --git a/app/src/screenshotTest/kotlin/com.mikepenz.markdown.ui/SnapshotTests.kt b/app/src/screenshotTest/kotlin/com.mikepenz.markdown.ui/SnapshotTests.kt
index 1217fe5..0934c64 100644
--- a/app/src/screenshotTest/kotlin/com.mikepenz.markdown.ui/SnapshotTests.kt
+++ b/app/src/screenshotTest/kotlin/com.mikepenz.markdown.ui/SnapshotTests.kt
@@ -63,11 +63,11 @@ private val MARKDOWN_DEFAULT = """
###### This is an H6
-This is a paragraph with some *italic* and **bold** text.
+This is a paragraph with some *italic* and **bold** text\.
-This is a paragraph with some `inline code`.
+This is a paragraph with some `inline code`\.
-This is a paragraph with a [link](https://www.jetbrains.com/).
+This is a paragraph with a [link](https://www.jetbrains.com/)\.
This is a code block:
```kotlin
diff --git a/gradle.properties b/gradle.properties
index 3c2dd86..a8ac6a9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
# Maven stuff
GROUP=com.mikepenz
-VERSION_NAME=0.26.0
-VERSION_CODE=2600
+VERSION_NAME=0.27.0
+VERSION_CODE=2700
POM_URL=https://github.com/mikepenz/multiplatform-markdown-renderer
@@ -30,6 +30,7 @@ kotlin.js.compiler=both
kotlin.mpp.stability.nowarn=true
kotlin.mpp.androidSourceSetLayoutVersion=2
+kotlin.suppressGradlePluginWarnings=IncorrectCompileOnlyDependencyWarning
org.jetbrains.compose.experimental.wasm.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6715e50..52b62c1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,23 +1,24 @@
[versions]
-agp = "8.5.2"
+agp = "8.7.2"
compileSdk = "34"
minSdk = "21"
targetSdk = "34"
-androidx-activityCompose = "1.9.1"
+androidx-activityCompose = "1.9.3"
androidx-material = "1.12.0"
-compose = "1.6.8"
-compose-plugin = "1.6.11"
-kotlin = "2.0.20"
-coroutines = "1.8.1"
-dokka = "1.9.20"
-coil = "3.0.0-alpha10"
+compose = "1.7.5"
+compose-plugin = "1.7.0"
+kotlin = "2.0.21"
+coroutines = "1.9.0"
+dokka = "2.0.0-Beta"
+coil = "3.0.0-rc02"
coil2 = "2.7.0"
aboutlib = "11.2.3"
markdown = "0.7.3"
-detekt = "1.23.6"
-gradleMvnPublish = "0.29.0"
-screenshot = "0.0.1-alpha05"
-ktor = "3.0.0-beta-2"
+detekt = "1.23.7"
+gradleMvnPublish = "0.30.0"
+screenshot = "0.0.1-alpha07"
+ktor = "3.0.1"
+highlights = "0.9.3"
[libraries]
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" }
@@ -39,6 +40,7 @@ markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" }
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
+highlights = { module = "dev.snipme:highlights", version.ref = "highlights" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
diff --git a/multiplatform-markdown-renderer-code/.gitignore b/multiplatform-markdown-renderer-code/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/multiplatform-markdown-renderer-code/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/multiplatform-markdown-renderer-code/build.gradle.kts b/multiplatform-markdown-renderer-code/build.gradle.kts
new file mode 100644
index 0000000..cf4721c
--- /dev/null
+++ b/multiplatform-markdown-renderer-code/build.gradle.kts
@@ -0,0 +1,174 @@
+import com.vanniktech.maven.publish.SonatypeHost
+import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
+import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
+
+plugins {
+ kotlin("multiplatform")
+ alias(libs.plugins.androidLibrary)
+ alias(libs.plugins.jetbrainsCompose)
+ alias(libs.plugins.composeCompiler)
+ alias(libs.plugins.dokka)
+ alias(libs.plugins.mavenPublish)
+}
+
+android {
+ namespace = "com.mikepenz.markdown.code"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.minSdk.get().toInt()
+ targetSdk = libs.versions.targetSdk.get().toInt()
+ }
+
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
+ )
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ tasks.withType {
+ kotlinOptions.jvmTarget = "11"
+
+ kotlinOptions {
+ if (project.findProperty("composeCompilerReports") == "true") {
+ freeCompilerArgs += listOf(
+ "-P",
+ "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.buildDir.absolutePath}/compose_compiler"
+ )
+ }
+ if (project.findProperty("composeCompilerMetrics") == "true") {
+ freeCompilerArgs += listOf(
+ "-P",
+ "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.buildDir.absolutePath}/compose_compiler"
+ )
+ }
+ }
+ }
+}
+
+kotlin {
+ applyDefaultHierarchyTemplate()
+
+ targets.all {
+ compilations.all {
+ compilerOptions.configure {
+ languageVersion.set(KotlinVersion.KOTLIN_1_9)
+ apiVersion.set(KotlinVersion.KOTLIN_1_9)
+ }
+ }
+ }
+ androidTarget {
+ publishLibraryVariants("release")
+ }
+ jvm {
+ compilations {
+ all {
+ kotlinOptions.jvmTarget = "11"
+ }
+ }
+
+ testRuns["test"].executionTask.configure {
+ useJUnit {
+ excludeCategories("org.intellij.markdown.ParserPerformanceTest")
+ }
+ }
+ }
+ @OptIn(ExperimentalWasmDsl::class)
+ wasmJs {
+ browser()
+ }
+ js(IR) {
+ nodejs()
+ }
+ macosX64()
+ macosArm64()
+ iosX64()
+ iosArm64()
+ iosSimulatorArm64()
+
+ sourceSets {
+ val commonMain by getting
+ val commonTest by getting {
+ dependencies {
+ implementation(kotlin("test"))
+ }
+ }
+ val fileBasedTest by creating {
+ dependsOn(commonTest)
+ }
+ val jvmTest by getting {
+ dependsOn(fileBasedTest)
+ }
+ val jsTest by getting {
+ dependsOn(fileBasedTest)
+ }
+ val nativeMain by getting {
+ dependsOn(commonMain)
+ }
+ val nativeTest by getting {
+ dependsOn(fileBasedTest)
+ }
+ val nativeSourceSets = listOf(
+ "macosX64",
+ "macosArm64",
+ "ios",
+ "iosSimulatorArm64"
+ ).map { "${it}Main" }
+ for (set in nativeSourceSets) {
+ getByName(set).dependsOn(nativeMain)
+ }
+ val nativeTestSourceSets = listOf(
+ "macosX64",
+ "macosArm64"
+ ).map { "${it}Test" }
+ for (set in nativeTestSourceSets) {
+ getByName(set).dependsOn(nativeTest)
+ getByName(set).dependsOn(fileBasedTest)
+ }
+ }
+}
+
+dependencies {
+ commonMainApi(projects.multiplatformMarkdownRenderer)
+ commonMainCompileOnly(compose.runtime)
+ commonMainCompileOnly(compose.ui)
+ commonMainCompileOnly(compose.foundation)
+
+ commonMainApi(libs.highlights)
+}
+
+tasks.dokkaHtml.configure {
+ dokkaSourceSets {
+ configureEach {
+ noAndroidSdkLink.set(false)
+ }
+ }
+}
+
+tasks.create("javadocJar") {
+ dependsOn("dokkaJavadoc")
+ archiveClassifier.set("javadoc")
+ from("${layout.buildDirectory}/javadoc")
+}
+
+mavenPublishing {
+ publishToMavenCentral(SonatypeHost.S01)
+ signAllPublications()
+}
+
+publishing {
+ repositories {
+ maven {
+ name = "installLocally"
+ setUrl("${rootProject.layout.buildDirectory}/localMaven")
+ }
+ }
+}
diff --git a/multiplatform-markdown-renderer-code/gradle.properties b/multiplatform-markdown-renderer-code/gradle.properties
new file mode 100755
index 0000000..80bc452
--- /dev/null
+++ b/multiplatform-markdown-renderer-code/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=Multiplatform Markdown Renderer - Code
+POM_DESCRIPTION=Kotlin Multiplatform Markdown Renderer. (Android, Desktop, ...) powered by Compose Multiplatform
+POM_ARTIFACT_ID=multiplatform-markdown-renderer-code
\ No newline at end of file
diff --git a/multiplatform-markdown-renderer-code/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownHighlightedCode.kt b/multiplatform-markdown-renderer-code/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownHighlightedCode.kt
new file mode 100644
index 0000000..51cc73d
--- /dev/null
+++ b/multiplatform-markdown-renderer-code/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownHighlightedCode.kt
@@ -0,0 +1,109 @@
+package com.mikepenz.markdown.compose.elements
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.*
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import com.mikepenz.markdown.compose.LocalMarkdownColors
+import com.mikepenz.markdown.compose.LocalMarkdownDimens
+import com.mikepenz.markdown.compose.LocalMarkdownPadding
+import com.mikepenz.markdown.compose.LocalMarkdownTypography
+import com.mikepenz.markdown.compose.components.MarkdownComponent
+import com.mikepenz.markdown.compose.elements.material.MarkdownBasicText
+import dev.snipme.highlights.Highlights
+import dev.snipme.highlights.model.BoldHighlight
+import dev.snipme.highlights.model.ColorHighlight
+import dev.snipme.highlights.model.SyntaxLanguage
+import org.intellij.markdown.ast.ASTNode
+
+/** Default definition for the [MarkdownHighlightedCodeFence]. Uses default theme, attempts to apply language from markdown. */
+val highlightedCodeFence: MarkdownComponent = { MarkdownHighlightedCodeFence(it.content, it.node) }
+
+/** Default definition for the [MarkdownHighlightedCodeBlock]. Uses default theme, attempts to apply language from markdown. */
+val highlightedCodeBlock: MarkdownComponent = { MarkdownHighlightedCodeBlock(it.content, it.node) }
+
+@Composable
+fun MarkdownHighlightedCodeFence(content: String, node: ASTNode, highlights: Highlights.Builder = Highlights.Builder()) {
+ MarkdownCodeFence(content, node) { code, language ->
+ MarkdownHighlightedCode(code, language, highlights)
+ }
+}
+
+@Composable
+fun MarkdownHighlightedCodeBlock(content: String, node: ASTNode, highlights: Highlights.Builder = Highlights.Builder()) {
+ MarkdownCodeBlock(content, node) { code, language ->
+ MarkdownHighlightedCode(code, language, highlights)
+ }
+}
+
+@Composable
+fun MarkdownHighlightedCode(
+ code: String,
+ language: String?,
+ highlights: Highlights.Builder = Highlights.Builder(),
+ style: TextStyle = LocalMarkdownTypography.current.code,
+) {
+ val backgroundCodeColor = LocalMarkdownColors.current.codeBackground
+ val codeBackgroundCornerSize = LocalMarkdownDimens.current.codeBackgroundCornerSize
+ val codeBlockPadding = LocalMarkdownPadding.current.codeBlock
+ val syntaxLanguage = remember(language) { language?.let { SyntaxLanguage.getByName(it) } }
+
+ val codeHighlights by remembering(code) {
+ derivedStateOf {
+ highlights
+ .code(code)
+ .let { if (syntaxLanguage != null) it.language(syntaxLanguage) else it }
+ .build()
+ }
+ }
+
+ MarkdownCodeBackground(
+ color = backgroundCodeColor,
+ shape = RoundedCornerShape(codeBackgroundCornerSize),
+ modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
+ ) {
+ MarkdownBasicText(
+ buildAnnotatedString {
+ text(codeHighlights.getCode())
+ codeHighlights.getHighlights()
+ .filterIsInstance()
+ .forEach {
+ addStyle(
+ SpanStyle(color = Color(it.rgb).copy(alpha = 1f)),
+ start = it.location.start,
+ end = it.location.end,
+ )
+ }
+ codeHighlights.getHighlights()
+ .filterIsInstance()
+ .forEach {
+ addStyle(
+ SpanStyle(fontWeight = FontWeight.Bold),
+ start = it.location.start,
+ end = it.location.end,
+ )
+ }
+ },
+ color = LocalMarkdownColors.current.codeText,
+ modifier = Modifier.horizontalScroll(rememberScrollState()).padding(codeBlockPadding),
+ style = style
+ )
+ }
+}
+
+@Composable
+internal inline fun remembering(
+ key1: K,
+ crossinline calculation: @DisallowComposableCalls (K) -> T,
+): T = remember(key1) { calculation(key1) }
+
+internal fun AnnotatedString.Builder.text(text: String, style: SpanStyle = SpanStyle()) = withStyle(style = style) {
+ append(text)
+}
\ No newline at end of file
diff --git a/multiplatform-markdown-renderer-m2/src/commonMain/kotlin/com/mikepenz/markdown/m2/MarkdownTypography.kt b/multiplatform-markdown-renderer-m2/src/commonMain/kotlin/com/mikepenz/markdown/m2/MarkdownTypography.kt
index 3a19667..a66bef1 100644
--- a/multiplatform-markdown-renderer-m2/src/commonMain/kotlin/com/mikepenz/markdown/m2/MarkdownTypography.kt
+++ b/multiplatform-markdown-renderer-m2/src/commonMain/kotlin/com/mikepenz/markdown/m2/MarkdownTypography.kt
@@ -6,6 +6,8 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
import com.mikepenz.markdown.model.DefaultMarkdownTypography
import com.mikepenz.markdown.model.MarkdownTypography
@@ -19,13 +21,18 @@ fun markdownTypography(
h6: TextStyle = MaterialTheme.typography.h6,
text: TextStyle = MaterialTheme.typography.body1,
code: TextStyle = MaterialTheme.typography.body2.copy(fontFamily = FontFamily.Monospace),
+ inlineCode: TextStyle = text.copy(fontFamily = FontFamily.Monospace),
quote: TextStyle = MaterialTheme.typography.body2.plus(SpanStyle(fontStyle = FontStyle.Italic)),
paragraph: TextStyle = MaterialTheme.typography.body1,
ordered: TextStyle = MaterialTheme.typography.body1,
bullet: TextStyle = MaterialTheme.typography.body1,
- list: TextStyle = MaterialTheme.typography.body1
+ list: TextStyle = MaterialTheme.typography.body1,
+ link: TextStyle = MaterialTheme.typography.body1.copy(
+ fontWeight = FontWeight.Bold,
+ textDecoration = TextDecoration.Underline
+ ),
): MarkdownTypography = DefaultMarkdownTypography(
h1 = h1, h2 = h2, h3 = h3, h4 = h4, h5 = h5, h6 = h6,
- text = text, quote = quote, code = code, paragraph = paragraph,
- ordered = ordered, bullet = bullet, list = list
+ text = text, quote = quote, code = code, inlineCode = inlineCode, paragraph = paragraph,
+ ordered = ordered, bullet = bullet, list = list, link = link,
)
diff --git a/multiplatform-markdown-renderer-m3/src/commonMain/kotlin/com/mikepenz/markdown/m3/MarkdownTypography.kt b/multiplatform-markdown-renderer-m3/src/commonMain/kotlin/com/mikepenz/markdown/m3/MarkdownTypography.kt
index 70317d1..e90bca7 100644
--- a/multiplatform-markdown-renderer-m3/src/commonMain/kotlin/com/mikepenz/markdown/m3/MarkdownTypography.kt
+++ b/multiplatform-markdown-renderer-m3/src/commonMain/kotlin/com/mikepenz/markdown/m3/MarkdownTypography.kt
@@ -6,6 +6,8 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
import com.mikepenz.markdown.model.DefaultMarkdownTypography
import com.mikepenz.markdown.model.MarkdownTypography
@@ -19,13 +21,18 @@ fun markdownTypography(
h6: TextStyle = MaterialTheme.typography.titleLarge,
text: TextStyle = MaterialTheme.typography.bodyLarge,
code: TextStyle = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
+ inlineCode: TextStyle = text.copy(fontFamily = FontFamily.Monospace),
quote: TextStyle = MaterialTheme.typography.bodyMedium.plus(SpanStyle(fontStyle = FontStyle.Italic)),
paragraph: TextStyle = MaterialTheme.typography.bodyLarge,
ordered: TextStyle = MaterialTheme.typography.bodyLarge,
bullet: TextStyle = MaterialTheme.typography.bodyLarge,
- list: TextStyle = MaterialTheme.typography.bodyLarge
+ list: TextStyle = MaterialTheme.typography.bodyLarge,
+ link: TextStyle = MaterialTheme.typography.bodyLarge.copy(
+ fontWeight = FontWeight.Bold,
+ textDecoration = TextDecoration.Underline
+ ),
): MarkdownTypography = DefaultMarkdownTypography(
h1 = h1, h2 = h2, h3 = h3, h4 = h4, h5 = h5, h6 = h6,
- text = text, quote = quote, code = code, paragraph = paragraph,
- ordered = ordered, bullet = bullet, list = list
+ text = text, quote = quote, code = code, inlineCode = inlineCode, paragraph = paragraph,
+ ordered = ordered, bullet = bullet, list = list, link = link,
)
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/components/MarkdownComponents.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/components/MarkdownComponents.kt
index 73e3378..9eeda11 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/components/MarkdownComponents.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/components/MarkdownComponents.kt
@@ -9,12 +9,12 @@ import androidx.compose.ui.Modifier
import com.mikepenz.markdown.compose.LocalReferenceLinkHandler
import com.mikepenz.markdown.compose.elements.*
import com.mikepenz.markdown.model.MarkdownTypography
+import com.mikepenz.markdown.utils.getUnescapedTextInNode
import org.intellij.markdown.IElementType
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.findChildOfType
-import org.intellij.markdown.ast.getTextInNode
typealias MarkdownComponent = @Composable ColumnScope.(MarkdownComponentModel) -> Unit
@@ -29,7 +29,7 @@ data class MarkdownComponentModel(
val typography: MarkdownTypography,
)
-private fun MarkdownComponentModel.getTextInNode() = node.getTextInNode(content)
+private fun MarkdownComponentModel.getUnescapedTextInNode() = node.getUnescapedTextInNode(content)
fun markdownComponents(
text: MarkdownComponent = CurrentComponentsBridge.text,
@@ -130,7 +130,7 @@ private class DefaultMarkdownComponents(
*/
object CurrentComponentsBridge {
val text: MarkdownComponent = {
- MarkdownText(it.getTextInNode().toString())
+ MarkdownText(it.getUnescapedTextInNode())
}
val eol: MarkdownComponent = { }
val codeFence: MarkdownComponent = {
@@ -184,11 +184,10 @@ object CurrentComponentsBridge {
}
val linkDefinition: MarkdownComponent = {
val linkLabel =
- it.node.findChildOfType(MarkdownElementTypes.LINK_LABEL)?.getTextInNode(it.content)
- ?.toString()
+ it.node.findChildOfType(MarkdownElementTypes.LINK_LABEL)?.getUnescapedTextInNode(it.content)
if (linkLabel != null) {
val destination = it.node.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
- ?.getTextInNode(it.content)?.toString()
+ ?.getUnescapedTextInNode(it.content)
LocalReferenceLinkHandler.current.store(linkLabel, destination)
}
}
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownCode.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownCode.kt
index 9210636..1013932 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownCode.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownCode.kt
@@ -23,7 +23,10 @@ import com.mikepenz.markdown.compose.LocalMarkdownDimens
import com.mikepenz.markdown.compose.LocalMarkdownPadding
import com.mikepenz.markdown.compose.LocalMarkdownTypography
import com.mikepenz.markdown.compose.elements.material.MarkdownBasicText
+import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
+import org.intellij.markdown.ast.findChildOfType
+import org.intellij.markdown.ast.getTextInNode
@Composable
private fun MarkdownCode(
@@ -34,15 +37,10 @@ private fun MarkdownCode(
val codeBackgroundCornerSize = LocalMarkdownDimens.current.codeBackgroundCornerSize
val codeBlockPadding = LocalMarkdownPadding.current.codeBlock
MarkdownCodeBackground(
- color = backgroundCodeColor,
- shape = RoundedCornerShape(codeBackgroundCornerSize),
- modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
+ color = backgroundCodeColor, shape = RoundedCornerShape(codeBackgroundCornerSize), modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
) {
MarkdownBasicText(
- code,
- color = LocalMarkdownColors.current.codeText,
- modifier = Modifier.horizontalScroll(rememberScrollState()).padding(codeBlockPadding),
- style = style
+ code, color = LocalMarkdownColors.current.codeText, modifier = Modifier.horizontalScroll(rememberScrollState()).padding(codeBlockPadding), style = style
)
}
}
@@ -51,14 +49,17 @@ private fun MarkdownCode(
fun MarkdownCodeFence(
content: String,
node: ASTNode,
+ block: @Composable (String, String?) -> Unit = { code, _ -> MarkdownCode(code) },
) {
// CODE_FENCE_START, FENCE_LANG, {content // CODE_FENCE_CONTENT // x-times}, CODE_FENCE_END
// CODE_FENCE_START, EOL, {content // CODE_FENCE_CONTENT // x-times}, EOL
// CODE_FENCE_START, EOL, {content // CODE_FENCE_CONTENT // x-times}
+
+ val language = node.findChildOfType(MarkdownTokenTypes.FENCE_LANG)?.getTextInNode(content)?.toString()
if (node.children.size >= 3) {
val start = node.children[2].startOffset
val end = node.children[(node.children.size - 2).coerceAtLeast(2)].endOffset
- MarkdownCode(content.subSequence(start, end).toString().replaceIndent())
+ block(content.subSequence(start, end).toString().replaceIndent(), language)
} else {
// invalid code block, skipping
}
@@ -68,15 +69,16 @@ fun MarkdownCodeFence(
fun MarkdownCodeBlock(
content: String,
node: ASTNode,
+ block: @Composable (String, String?) -> Unit = { code, _ -> MarkdownCode(code) },
) {
val start = node.children[0].startOffset
val end = node.children[node.children.size - 1].endOffset
- MarkdownCode(content.subSequence(start, end).toString().replaceIndent())
+ val language = node.findChildOfType(MarkdownTokenTypes.FENCE_LANG)?.getTextInNode(content)?.toString()
+ block(content.subSequence(start, end).toString().replaceIndent(), language)
}
-
@Composable
-internal fun MarkdownCodeBackground(
+fun MarkdownCodeBackground(
color: Color,
modifier: Modifier = Modifier,
shape: Shape = RectangleShape,
@@ -84,17 +86,10 @@ internal fun MarkdownCodeBackground(
elevation: Dp = 0.dp,
content: @Composable () -> Unit,
) {
- Box(
- modifier = modifier
- .shadow(elevation, shape, clip = false)
- .then(if (border != null) Modifier.border(border, shape) else Modifier)
- .background(color = color, shape = shape)
- .clip(shape)
- .semantics(mergeDescendants = false) {
- isTraversalGroup = true
- }
- .pointerInput(Unit) {},
- propagateMinConstraints = true
+ Box(modifier = modifier.shadow(elevation, shape, clip = false).then(if (border != null) Modifier.border(border, shape) else Modifier).background(color = color, shape = shape)
+ .clip(shape).semantics(mergeDescendants = false) {
+ isTraversalGroup = true
+ }.pointerInput(Unit) {}, propagateMinConstraints = true
) {
content()
}
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt
index 82b88a5..a414dab 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt
@@ -4,14 +4,14 @@ import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import com.mikepenz.markdown.compose.LocalImageTransformer
import com.mikepenz.markdown.utils.findChildOfTypeRecursive
+import com.mikepenz.markdown.utils.getUnescapedTextInNode
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.ast.ASTNode
-import org.intellij.markdown.ast.getTextInNode
@Composable
fun MarkdownImage(content: String, node: ASTNode) {
- val link = node.findChildOfTypeRecursive(MarkdownElementTypes.LINK_DESTINATION)?.getTextInNode(content)?.toString() ?: return
+ val link = node.findChildOfTypeRecursive(MarkdownElementTypes.LINK_DESTINATION)?.getUnescapedTextInNode(content) ?: return
LocalImageTransformer.current.transform(link)?.let { imageData ->
Image(
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownList.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownList.kt
index f95830d..08e7007 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownList.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownList.kt
@@ -9,6 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import com.mikepenz.markdown.compose.*
import com.mikepenz.markdown.compose.elements.material.MarkdownBasicText
+import com.mikepenz.markdown.utils.getUnescapedTextInNode
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownElementTypes.ORDERED_LIST
import org.intellij.markdown.MarkdownElementTypes.UNORDERED_LIST
@@ -16,7 +17,6 @@ import org.intellij.markdown.MarkdownTokenTypes.Companion.LIST_BULLET
import org.intellij.markdown.MarkdownTokenTypes.Companion.LIST_NUMBER
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.findChildOfType
-import org.intellij.markdown.ast.getTextInNode
@Composable
fun MarkdownListItems(
@@ -65,7 +65,7 @@ fun MarkdownOrderedList(
MarkdownBasicText(
text = orderedListHandler.transform(
LIST_NUMBER,
- child.findChildOfType(LIST_NUMBER)?.getTextInNode(content),
+ child.findChildOfType(LIST_NUMBER)?.getUnescapedTextInNode(content),
index
),
style = style,
@@ -98,7 +98,7 @@ fun MarkdownBulletList(
MarkdownBasicText(
bulletHandler.transform(
LIST_BULLET,
- child.findChildOfType(LIST_BULLET)?.getTextInNode(content),
+ child.findChildOfType(LIST_BULLET)?.getUnescapedTextInNode(content),
index
),
style = style,
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt
index 0019e28..2ec4758 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt
@@ -128,32 +128,36 @@ fun MarkdownText(
imageState.setContainerSize(coordinates.size)
}
}
- .animateContentSize(),
+ .let {
+ if (placeholderState.animate) it.animateContentSize() else it
+ },
style = style,
color = LocalMarkdownColors.current.text,
- inlineContent = mapOf(MARKDOWN_TAG_IMAGE_URL to InlineTextContent(
- Placeholder(
- width = placeholderState.size.width.sp,
- height = placeholderState.size.height.sp,
- placeholderVerticalAlign = placeholderState.verticalAlign
- )
- ) { link ->
- transformer.transform(link)?.let { imageData ->
- val intrinsicSize = transformer.intrinsicSize(imageData.painter)
- LaunchedEffect(intrinsicSize) {
- imageState.setImageSize(intrinsicSize)
- }
- Image(
- painter = imageData.painter,
- contentDescription = imageData.contentDescription,
- modifier = imageData.modifier,
- alignment = imageData.alignment,
- contentScale = imageData.contentScale,
- alpha = imageData.alpha,
- colorFilter = imageData.colorFilter
+ inlineContent = mapOf(
+ MARKDOWN_TAG_IMAGE_URL to InlineTextContent(
+ Placeholder(
+ width = placeholderState.size.width.sp,
+ height = placeholderState.size.height.sp,
+ placeholderVerticalAlign = placeholderState.verticalAlign
)
+ ) { link ->
+ transformer.transform(link)?.let { imageData ->
+ val intrinsicSize = transformer.intrinsicSize(imageData.painter)
+ LaunchedEffect(intrinsicSize) {
+ imageState.setImageSize(intrinsicSize)
+ }
+ Image(
+ painter = imageData.painter,
+ contentDescription = imageData.contentDescription,
+ modifier = imageData.modifier,
+ alignment = imageData.alignment,
+ contentScale = imageData.contentScale,
+ alpha = imageData.alpha,
+ colorFilter = imageData.colorFilter
+ )
+ }
}
- }),
+ ),
onTextLayout = {
layoutResult.value = it
onTextLayout.invoke(it)
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/material/TextWrapper.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/material/TextWrapper.kt
index aea0ffc..c7150b0 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/material/TextWrapper.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/material/TextWrapper.kt
@@ -70,7 +70,7 @@ internal fun MarkdownBasicText(
}
@Composable
-internal fun MarkdownBasicText(
+fun MarkdownBasicText(
text: AnnotatedString,
style: TextStyle,
modifier: Modifier = Modifier,
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt
index 5cf6836..0a2e30c 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt
@@ -55,6 +55,7 @@ interface ImageTransformer {
data class PlaceholderConfig(
val size: Size,
val verticalAlign: PlaceholderVerticalAlign = PlaceholderVerticalAlign.Bottom,
+ val animate: Boolean = true,
)
@Immutable
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownTypography.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownTypography.kt
index 788f08a..bade6c8 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownTypography.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownTypography.kt
@@ -6,6 +6,7 @@ import androidx.compose.ui.text.TextStyle
interface MarkdownTypography {
val text: TextStyle
val code: TextStyle
+ val inlineCode: TextStyle
val h1: TextStyle
val h2: TextStyle
val h3: TextStyle
@@ -17,6 +18,7 @@ interface MarkdownTypography {
val ordered: TextStyle
val bullet: TextStyle
val list: TextStyle
+ val link: TextStyle
}
@Immutable
@@ -29,9 +31,11 @@ class DefaultMarkdownTypography(
override val h6: TextStyle,
override val text: TextStyle,
override val code: TextStyle,
+ override val inlineCode: TextStyle,
override val quote: TextStyle,
override val paragraph: TextStyle,
override val ordered: TextStyle,
override val bullet: TextStyle,
- override val list: TextStyle
+ override val list: TextStyle,
+ override val link: TextStyle,
) : MarkdownTypography
\ No newline at end of file
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/AnnotatedStringKtx.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/AnnotatedStringKtx.kt
index 1ad64b5..fa0fe8d 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/AnnotatedStringKtx.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/AnnotatedStringKtx.kt
@@ -4,17 +4,16 @@ import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
-import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import com.mikepenz.markdown.compose.LocalMarkdownAnnotator
import com.mikepenz.markdown.compose.LocalMarkdownColors
+import com.mikepenz.markdown.compose.LocalMarkdownTypography
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.findChildOfType
-import org.intellij.markdown.ast.getTextInNode
import org.intellij.markdown.flavours.gfm.GFMElementTypes
import org.intellij.markdown.flavours.gfm.GFMTokenTypes
@@ -22,23 +21,19 @@ import org.intellij.markdown.flavours.gfm.GFMTokenTypes
internal fun AnnotatedString.Builder.appendMarkdownLink(content: String, node: ASTNode) {
val linkText = node.findChildOfType(MarkdownElementTypes.LINK_TEXT)?.children?.innerList()
if (linkText == null) {
- append(node.getTextInNode(content).toString())
+ append(node.getUnescapedTextInNode(content))
return
}
val destination = node.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
- ?.getTextInNode(content)
+ ?.getUnescapedTextInNode(content)
?.toString()
val linkLabel = node.findChildOfType(MarkdownElementTypes.LINK_LABEL)
- ?.getTextInNode(content)?.toString()
+ ?.getUnescapedTextInNode(content)
val annotation = destination ?: linkLabel
if (annotation != null) pushStringAnnotation(MARKDOWN_TAG_URL, annotation)
- pushStyle(
- SpanStyle(
- color = LocalMarkdownColors.current.linkText,
- textDecoration = TextDecoration.Underline,
- fontWeight = FontWeight.Bold
- )
- )
+ val linkColor = LocalMarkdownColors.current.linkText
+ val linkTextStyle = LocalMarkdownTypography.current.link.copy(color = linkColor).toSpanStyle()
+ pushStyle(linkTextStyle)
buildMarkdownAnnotatedString(content, linkText)
pop()
if (annotation != null) pop()
@@ -49,15 +44,11 @@ internal fun AnnotatedString.Builder.appendAutoLink(content: String, node: ASTNo
val targetNode = node.children.firstOrNull {
it.type.name == MarkdownElementTypes.AUTOLINK.name
} ?: node
- val destination = targetNode.getTextInNode(content).toString()
+ val destination = targetNode.getUnescapedTextInNode(content)
pushStringAnnotation(MARKDOWN_TAG_URL, (destination))
- pushStyle(
- SpanStyle(
- color = LocalMarkdownColors.current.linkText,
- textDecoration = TextDecoration.Underline,
- fontWeight = FontWeight.Bold
- )
- )
+ val linkColor = LocalMarkdownColors.current.linkText
+ val linkTextStyle = LocalMarkdownTypography.current.link.copy(color = linkColor).toSpanStyle()
+ pushStyle(linkTextStyle)
append(destination)
pop()
}
@@ -100,7 +91,7 @@ fun AnnotatedString.Builder.buildMarkdownAnnotatedString(content: String, childr
MarkdownElementTypes.IMAGE -> child.findChildOfTypeRecursive(
MarkdownElementTypes.LINK_DESTINATION
)?.let {
- appendInlineContent(MARKDOWN_TAG_IMAGE_URL, it.getTextInNode(content).toString())
+ appendInlineContent(MARKDOWN_TAG_IMAGE_URL, it.getUnescapedTextInNode(content))
}
MarkdownElementTypes.EMPH -> {
@@ -122,12 +113,12 @@ fun AnnotatedString.Builder.buildMarkdownAnnotatedString(content: String, childr
}
MarkdownElementTypes.CODE_SPAN -> {
+ val codeStyle = LocalMarkdownTypography.current.inlineCode
pushStyle(
- SpanStyle(
- fontFamily = FontFamily.Monospace,
+ codeStyle.copy(
color = LocalMarkdownColors.current.inlineCodeText,
background = LocalMarkdownColors.current.inlineCodeBackground
- )
+ ).toSpanStyle()
)
append(' ')
buildMarkdownAnnotatedString(content, child.children.innerList())
@@ -141,9 +132,9 @@ fun AnnotatedString.Builder.buildMarkdownAnnotatedString(content: String, childr
MarkdownElementTypes.FULL_REFERENCE_LINK -> appendMarkdownLink(content, child)
// Token Types
- MarkdownTokenTypes.TEXT -> append(child.getTextInNode(content).toString())
+ MarkdownTokenTypes.TEXT -> append(child.getUnescapedTextInNode(content))
GFMTokenTypes.GFM_AUTOLINK -> if (child.parent == MarkdownElementTypes.LINK_TEXT) {
- append(child.getTextInNode(content).toString())
+ append(child.getUnescapedTextInNode(content))
} else appendAutoLink(content, child)
MarkdownTokenTypes.SINGLE_QUOTE -> append('\'')
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/EntityConverter.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/EntityConverter.kt
new file mode 100644
index 0000000..e6fed62
--- /dev/null
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/EntityConverter.kt
@@ -0,0 +1,38 @@
+package com.mikepenz.markdown.utils
+
+import org.intellij.markdown.html.entities.Entities
+
+/**
+ * Based on: https://github.com/JetBrains/markdown/blob/master/src/commonMain/kotlin/org/intellij/markdown/html/entities/EntityConverter.kt
+ * Removed HTML focused escaping by https://github.com/mikepenz/multiplatform-markdown-renderer/pull/222
+ */
+object EntityConverter {
+ private const val escapeAllowedString = """!"#\$%&'\(\)\*\+,\-.\/:;<=>\?@\[\\\]\^_`{\|}~"""
+ private val REGEX = Regex("""&(?:([a-zA-Z0-9]+)|#([0-9]{1,8})|#[xX]([a-fA-F0-9]{1,8}));|(["&<>])""")
+ private val REGEX_ESCAPES = Regex("${REGEX.pattern}|\\\\([$escapeAllowedString])")
+
+ fun replaceEntities(
+ text: CharSequence,
+ processEntities: Boolean,
+ processEscapes: Boolean
+ ): String {
+ val regex = if (processEscapes) REGEX_ESCAPES else REGEX
+ return regex.replace(text) { match ->
+ val g = match.groups
+ when {
+ g.size > 5 && g[5] != null -> g[5]!!.value[0].toString()
+ g[4] != null -> match.value
+ else -> {
+ val code = when {
+ !processEntities -> null
+ g[1] != null -> Entities.map[match.value]
+ g[2] != null -> g[2]!!.value.toInt()
+ g[3] != null -> g[3]!!.value.toInt(16)
+ else -> null
+ }
+ code?.toChar()?.toString() ?: "&${match.value.substring(1)}"
+ }
+ }
+ }
+ }
+}
diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/Extensions.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/Extensions.kt
index 9fe1a47..0684649 100644
--- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/Extensions.kt
+++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/Extensions.kt
@@ -2,6 +2,7 @@ package com.mikepenz.markdown.utils
import org.intellij.markdown.IElementType
import org.intellij.markdown.ast.ASTNode
+import org.intellij.markdown.ast.getTextInNode
/**
* Tag used to indicate an url for inline content. Required for click handling.
@@ -35,3 +36,11 @@ internal fun ASTNode.findChildOfTypeRecursive(type: IElementType): ASTNode? {
* E.g. we don't want to render the brackets of a link
*/
internal fun List.innerList(): List = this.subList(1, this.size - 1)
+
+internal fun ASTNode.getUnescapedTextInNode(allFileText: CharSequence): String {
+ val escapedText = getTextInNode(allFileText).toString()
+ return EntityConverter.replaceEntities(escapedText,
+ processEntities = false,
+ processEscapes = true
+ )
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index d6f095c..1c1d82c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -25,6 +25,7 @@ include(":multiplatform-markdown-renderer-m2")
include(":multiplatform-markdown-renderer-m3")
include(":multiplatform-markdown-renderer-coil2")
include(":multiplatform-markdown-renderer-coil3")
+include(":multiplatform-markdown-renderer-code")
include(":app")
include(":app-desktop")