Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce richtext-markdown as a generic Markdown renderer #129

Merged
merged 2 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions android-sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("com.android.application")
kotlin("android")
id("org.jetbrains.compose") version Compose.desktopVersion
}

android {
Expand Down Expand Up @@ -36,8 +37,8 @@ dependencies {
implementation(project(":slideshow"))
implementation(AndroidX.appcompat)
implementation(Compose.activity)
implementation(Compose.foundation)
implementation(Compose.icons)
implementation(Compose.material)
implementation(Compose.tooling)
implementation(compose.foundation)
implementation(compose.materialIconsExtended)
implementation(compose.material)
implementation(compose.uiTooling)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
Expand All @@ -30,8 +32,9 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.markdown.MarkdownParseOptions
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
import com.halilibo.richtext.commonmark.MarkdownParseOptions
import com.halilibo.richtext.markdown.BasicMarkdown
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material.RichText
import com.halilibo.richtext.ui.resolveDefaults
Expand All @@ -41,6 +44,7 @@ import com.halilibo.richtext.ui.resolveDefaults
MarkdownSample()
}

@OptIn(ExperimentalLayoutApi::class)
@Composable fun MarkdownSample() {
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
var isDarkModeEnabled by remember { mutableStateOf(false) }
Expand Down Expand Up @@ -70,29 +74,29 @@ import com.halilibo.richtext.ui.resolveDefaults
// Config
Card(elevation = 4.dp) {
Column {
CheckboxPreference(
onClick = {
isDarkModeEnabled = !isDarkModeEnabled
},
checked = isDarkModeEnabled,
label = "Dark Mode"
)

CheckboxPreference(
onClick = {
isWordWrapEnabled = !isWordWrapEnabled
},
checked = isWordWrapEnabled,
label = "Word Wrap"
)

CheckboxPreference(
onClick = {
isAutolinkEnabled = !isAutolinkEnabled
},
checked = isAutolinkEnabled,
label = "Autolink"
)
FlowRow {
CheckboxPreference(
onClick = {
isDarkModeEnabled = !isDarkModeEnabled
},
checked = isDarkModeEnabled,
label = "Dark Mode"
)
CheckboxPreference(
onClick = {
isWordWrapEnabled = !isWordWrapEnabled
},
checked = isWordWrapEnabled,
label = "Word Wrap"
)
CheckboxPreference(
onClick = {
isAutolinkEnabled = !isAutolinkEnabled
},
checked = isAutolinkEnabled,
label = "Autolink"
)
}

RichTextStyleConfig(
richTextStyle = richTextStyle,
Expand All @@ -104,13 +108,20 @@ import com.halilibo.richtext.ui.resolveDefaults
SelectionContainer {
Column(Modifier.verticalScroll(rememberScrollState())) {
ProvideTextStyle(TextStyle(lineHeight = 1.3.em)) {
val parser = remember(markdownParseOptions) {
CommonmarkAstNodeParser(markdownParseOptions)
}

val astNode = remember(parser) {
parser.parse(sampleMarkdown)
}

RichText(
style = richTextStyle,
modifier = Modifier.padding(8.dp),
) {
Markdown(
content = sampleMarkdown,
markdownParseOptions = markdownParseOptions,
BasicMarkdown(
astNode = astNode,
onLinkClicked = {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ subprojects {
allWarningsAsErrors = true
}

freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn")
freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn", "-Xexpect-actual-classes")
}
}

Expand Down
15 changes: 4 additions & 11 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object Network {

object Kotlin {
// keep in sync with buildSrc/build.gradle.kts
val version = "1.9.20"
val version = "1.9.22"
val binaryCompatibilityValidatorPlugin = "org.jetbrains.kotlinx:binary-compatibility-validator:0.9.0"
val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$version"

Expand All @@ -31,19 +31,12 @@ val ktlint = "org.jlleitschuh.gradle:ktlint-gradle:10.0.0"

object Compose {
val version = "1.5.4"
val compilerVersion = "1.5.4"
val desktopVersion = "1.5.11"
val compilerVersion = "1.5.8"
val desktopVersion = "1.5.12"
val activity = "androidx.activity:activity-compose:1.7.2"
val foundation = "androidx.compose.foundation:foundation:$version"
val material = "androidx.compose.material:material:$version"
val material3 = "androidx.compose.material3:material3:1.0.1"
val icons = "androidx.compose.material:material-icons-extended:$version"
val test = "androidx.ui:ui-test:$version"
val tooling = "androidx.compose.ui:ui-tooling:$version"
val toolingData = "androidx.compose.ui:ui-tooling-data:$version"
val desktopPreview = "org.jetbrains.compose.ui:ui-tooling-preview-desktop:$desktopVersion"
val multiplatformUiUtil = "org.jetbrains.compose.ui:ui-util:$desktopVersion"
val coil = "io.coil-kt:coil-compose:2.4.0"
val coil = "io.coil-kt:coil-compose:2.5.0"
}

object Commonmark {
Expand Down
1 change: 0 additions & 1 deletion desktop-sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.singleWindowApplication
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.commonmark.Markdown
import com.halilibo.richtext.ui.CodeBlockStyle
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material.RichText
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Compose Richtext is a collection of Compose libraries for working with rich text formatting and
documents.

`richtext-ui`, `richtext-commonmark`, and `richtext-ui-material`|`richtext-ui-material3` are Kotlin Multiplatform(KMP) Compose Libraries.
`richtext-ui`, `richtext-markdown`, `richtext-commonmark`, and `richtext-ui-material`|`richtext-ui-material3` are Kotlin Multiplatform(KMP) Compose Libraries.
All these modules can be used in Android and Desktop Compose apps.

Each library is documented separately, see the navigation menu for the list. This site also includes
Expand Down
40 changes: 35 additions & 5 deletions docs/richtext-commonmark.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Android Library](https://img.shields.io/badge/Platform-Android-green.svg?style=for-the-badge)](https://developer.android.com/studio/build/dependencies)
[![JVM Library](https://img.shields.io/badge/Platform-JVM-red.svg?style=for-the-badge)](https://kotlinlang.org/docs/mpp-intro.html)

Library for rendering Markdown in Compose using [CommonMark](https://github.com/commonmark/commonmark-java)
Library for parsing and rendering Markdown in Compose using [CommonMark](https://github.com/commonmark/commonmark-java)
library/spec to parse, and `richtext-ui` to render.

## Gradle
Expand All @@ -14,7 +14,37 @@ dependencies {
}
```

## Usage
## Parsing

`richtext-markdown` module renders a given Markdown Abstract Syntax Tree. It accepts a root
`AstNode`. This library gives you a parser called `CommonmarkAstNodeParser` to easily convert any
String to an `AstNode` that represents the Markdown tree.

```kotlin
val parser = CommonmarkAstNodeParser()
val astNode = parser.parse(
"""
# Demo

Emphasis, aka italics, with *asterisks* or _underscores_. Strong emphasis, aka bold, with **asterisks** or __underscores__. Combined emphasis with **asterisks and _underscores_**. [Links with two blocks, text in square-brackets, destination is in parentheses.](https://www.example.com). Inline `code` has `back-ticks around` it.

1. First ordered list item
2. Another item
* Unordered sub-list.
3. And another item.
You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).

* Unordered list can use asterisks
- Or minuses
+ Or pluses
""".trimIndent()
)
// ...

RichTextScope.BasicMarkdown(astNode)
```

## Rendering

The simplest way to render markdown is just pass a string to the [`Markdown`](../api/richtext-commonmark/com.halilibo.richtext.markdown/-markdown.html)
composable under RichText scope:
Expand Down Expand Up @@ -61,12 +91,12 @@ Which produces something like this:

![markdown demo](img/markdown-demo.png)

## MarkdownParseOptions
## [`MarkdownParseOptions`](../api/richtext-commonmark/com.halilibo.richtext.commonmark/-markdown-parse-options.html)

Passing `MarkdownParseOptions` into `Markdown` provides the ability to control some aspects of the markdown parser:
Passing `MarkdownParseOptions` into either `Markdown` composable or `CommonmarkAstNodeParser.parse` method provides the ability to control some aspects of the markdown parser:

```kotlin
val markdownParseOptions = MarkdownParseOptions.Default.copy(
val markdownParseOptions = MarkdownParseOptions(
autolink = false
)

Expand Down
52 changes: 52 additions & 0 deletions docs/richtext-markdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Markdown

[![Android Library](https://img.shields.io/badge/Platform-Android-green.svg?style=for-the-badge)](https://developer.android.com/studio/build/dependencies)
[![JVM Library](https://img.shields.io/badge/Platform-JVM-red.svg?style=for-the-badge)](https://kotlinlang.org/docs/mpp-intro.html)

Library for rendering Markdown tree that is defined as an `AstNode`. This module would be useless
for someone who is looking to just render a Markdown string. Please take a look to
`richtext-commonmark` for such features. `richtext-markdown` behaves as sort of a building block.
You can create your own parser or use 3rd party ones that converts any Markdown string to an
`AstNode` tree.

## Gradle

```kotlin
dependencies {
implementation("com.halilibo.compose-richtext:richtext-markdown:${richtext_version}")
}
```

## Rendering

The simplest way to render markdown is just pass an `AstNode` to the [`Markdown`](../api/richtext-commonmark/com.halilibo.richtext.markdown/-markdown.html)
composable under RichText scope:

~~~kotlin
RichText(
modifier = Modifier.padding(16.dp)
) {
// requires richtext-commonmark module.
val parser = remember(options) { CommonmarkAstNodeParser(options) }
val astNode = remember(parser) {
parser.parse(
"""
# Demo

Emphasis, aka italics, with *asterisks* or _underscores_. Strong emphasis, aka bold, with **asterisks** or __underscores__. Combined emphasis with **asterisks and _underscores_**. [Links with two blocks, text in square-brackets, destination is in parentheses.](https://www.example.com). Inline `code` has `back-ticks around` it.

1. First ordered list item
2. Another item
* Unordered sub-list.
3. And another item.
You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).

* Unordered list can use asterisks
- Or minuses
+ Or pluses
""".trimIndent()
)
}
BasicMarkdown(astNode)
}
~~~
2 changes: 1 addition & 1 deletion docs/richtext-ui-material3.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Richtext UI Material
# Richtext UI Material 3

[![Android Library](https://img.shields.io/badge/Platform-Android-green.svg?style=for-the-badge)](https://developer.android.com/studio/build/dependencies)
[![JVM Library](https://img.shields.io/badge/Platform-JVM-red.svg?style=for-the-badge)](https://kotlinlang.org/docs/mpp-intro.html)
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ nav:
- richtext-ui-material.md
- richtext-ui-material3.md
- richtext-ui.md
- richtext-markdown.md
- richtext-commonmark.md
- printing.md
- slideshow.md
Expand Down
7 changes: 4 additions & 3 deletions printing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
plugins {
id("richtext-android-library")
id("org.jetbrains.dokka")
id("org.jetbrains.compose") version Compose.desktopVersion
}

android {
namespace = "com.zachklipp.richtext.ui.printing"
}

dependencies {
implementation(Compose.foundation)
implementation(Compose.tooling)
implementation(compose.foundation)
implementation(compose.uiTooling)
// For slot table analysis.
implementation(Compose.toolingData)
implementation(Compose.activity)

// TODO Migrate off this.
implementation(Compose.material)
implementation(compose.material)
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java).all {
Expand Down
9 changes: 2 additions & 7 deletions richtext-commonmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,23 @@ repositories {
}

android {
namespace = "com.halilibo.richtext.markdown"
namespace = "com.halilibo.richtext.commonmark"
}

kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
api(project(":richtext-ui"))
api(project(":richtext-markdown"))
}
}
val commonTest by getting

val androidMain by getting {
kotlin.srcDir("src/commonJvmAndroid/kotlin")
dependencies {
implementation(Compose.coil)

implementation(Commonmark.core)
implementation(Commonmark.tables)
implementation(Commonmark.strikethrough)
Expand All @@ -38,9 +36,6 @@ kotlin {
val jvmMain by getting {
kotlin.srcDir("src/commonJvmAndroid/kotlin")
dependencies {
implementation(compose.desktop.currentOs)
implementation(Network.okHttp)

implementation(Commonmark.core)
implementation(Commonmark.tables)
implementation(Commonmark.strikethrough)
Expand Down
Loading
Loading