Skip to content

Commit f8ed70e

Browse files
authored
Merge pull request #101 from morrisseyai/feature/autolink-markdown
Add autolink (linkify) functionality to Markdown
2 parents b056bf3 + 9556d65 commit f8ed70e

8 files changed

Lines changed: 68 additions & 9 deletions

File tree

android-sample/src/main/java/com/zachklipp/richtext/sample/MarkdownSample.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalContext
2828
import androidx.compose.ui.tooling.preview.Preview
2929
import androidx.compose.ui.unit.dp
3030
import com.halilibo.richtext.markdown.Markdown
31+
import com.halilibo.richtext.markdown.MarkdownParseOptions
3132
import com.halilibo.richtext.ui.RichTextStyle
3233
import com.halilibo.richtext.ui.material.MaterialRichText
3334
import com.halilibo.richtext.ui.resolveDefaults
@@ -41,6 +42,8 @@ import com.halilibo.richtext.ui.resolveDefaults
4142
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
4243
var isDarkModeEnabled by remember { mutableStateOf(false) }
4344
var isWordWrapEnabled by remember { mutableStateOf(true) }
45+
var markdownParseOptions by remember { mutableStateOf(MarkdownParseOptions.Default) }
46+
var isAutolinkEnabled by remember { mutableStateOf(true) }
4447

4548
LaunchedEffect(isWordWrapEnabled) {
4649
richTextStyle = richTextStyle.copy(
@@ -49,6 +52,11 @@ import com.halilibo.richtext.ui.resolveDefaults
4952
)
5053
)
5154
}
55+
LaunchedEffect(isAutolinkEnabled) {
56+
markdownParseOptions = markdownParseOptions.copy(
57+
autolink = isAutolinkEnabled
58+
)
59+
}
5260

5361
val colors = if (isDarkModeEnabled) darkColors() else lightColors()
5462
val context = LocalContext.current
@@ -75,6 +83,14 @@ import com.halilibo.richtext.ui.resolveDefaults
7583
label = "Word Wrap"
7684
)
7785

86+
CheckboxPreference(
87+
onClick = {
88+
isAutolinkEnabled = !isAutolinkEnabled
89+
},
90+
checked = isAutolinkEnabled,
91+
label = "Autolink"
92+
)
93+
7894
RichTextStyleConfig(
7995
richTextStyle = richTextStyle,
8096
onChanged = { richTextStyle = it }
@@ -90,6 +106,7 @@ import com.halilibo.richtext.ui.resolveDefaults
90106
) {
91107
Markdown(
92108
content = sampleMarkdown,
109+
markdownParseOptions = markdownParseOptions,
93110
onLinkClicked = {
94111
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
95112
}
@@ -178,6 +195,8 @@ private val sampleMarkdown = """
178195
[You can use numbers for reference-style link definitions][1]
179196
180197
Or leave it empty and use the [link text itself].
198+
199+
Autolink option will detect text links like https://www.google.com and turn them into Markdown links automatically.
181200
182201
---
183202

buildSrc/src/main/kotlin/Dependencies.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ object Compose {
5252
}
5353

5454
object Commonmark {
55-
private val version = "0.19.0"
55+
private val version = "0.20.0"
5656
val core = "org.commonmark:commonmark:$version"
5757
val tables = "org.commonmark:commonmark-ext-gfm-tables:$version"
5858
val strikethrough = "org.commonmark:commonmark-ext-gfm-strikethrough:$version"
59+
val autolink = "org.commonmark:commonmark-ext-autolink:$version"
5960
}
6061

6162
object AndroidConfiguration {

desktop-sample/src/main/kotlin/com/halilibo/richtext/desktop/Main.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ private val sampleMarkdown = """
218218
[You can use numbers for reference-style link definitions][1]
219219
220220
Or leave it empty and use the [link text itself].
221+
222+
Autolink option will detect text links like https://www.google.com and turn them into Markdown links automatically.
221223
222224
---
223225

docs/richtext-commonmark.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,18 @@ RichText(
6464
Which produces something like this:
6565

6666
![markdown demo](img/markdown-demo.png)
67+
68+
## MarkdownParseOptions
69+
70+
Passing `MarkdownParseOptions` into `Markdown` provides the ability to control some aspects of the markdown parser:
71+
72+
```kotlin
73+
val markdownParseOptions = MarkdownParseOptions.Default.copy(
74+
autolink = false
75+
)
76+
77+
Markdown(
78+
markdownParseOptions = markdownParseOptions,
79+
...
80+
)
81+
```

richtext-commonmark/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ kotlin {
2727
implementation(Commonmark.core)
2828
implementation(Commonmark.tables)
2929
implementation(Commonmark.strikethrough)
30+
implementation(Commonmark.autolink)
3031
}
3132
}
3233

@@ -39,6 +40,7 @@ kotlin {
3940
implementation(Commonmark.core)
4041
implementation(Commonmark.tables)
4142
implementation(Commonmark.strikethrough)
43+
implementation(Commonmark.autolink)
4244
}
4345
}
4446

richtext-commonmark/src/commonJvmAndroid/kotlin/com/halilibo/richtext/markdown/AstNodeConvert.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.halilibo.richtext.markdown.node.AstTableRoot
3535
import com.halilibo.richtext.markdown.node.AstTableRow
3636
import com.halilibo.richtext.markdown.node.AstText
3737
import com.halilibo.richtext.markdown.node.AstThematicBreak
38+
import org.commonmark.ext.autolink.AutolinkExtension
3839
import org.commonmark.ext.gfm.strikethrough.Strikethrough
3940
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension
4041
import org.commonmark.ext.gfm.tables.TableBlock
@@ -188,19 +189,20 @@ internal fun convert(
188189
internal fun Node.convert() = convert(this)
189190

190191
@Composable
191-
internal actual fun parsedMarkdownAst(text: String): AstNode? {
192-
val parser = remember {
192+
internal actual fun parsedMarkdownAst(text: String, options: MarkdownParseOptions): AstNode? {
193+
val parser = remember(options) {
193194
Parser.builder()
194195
.extensions(
195-
listOf(
196+
listOfNotNull(
196197
TablesExtension.create(),
197-
StrikethroughExtension.create()
198+
StrikethroughExtension.create(),
199+
if (options.autolink) AutolinkExtension.create() else null
198200
)
199201
)
200202
.build()
201203
}
202204

203-
val astRootNode by produceState<AstNode?>(null, text) {
205+
val astRootNode by produceState<AstNode?>(null, text, parser) {
204206
value = parser.parse(text).convert()
205207
}
206208

richtext-commonmark/src/commonMain/kotlin/com/halilibo/richtext/markdown/Markdown.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ import com.halilibo.richtext.ui.string.richTextString
4242
* A composable that renders Markdown content using RichText.
4343
*
4444
* @param content Markdown text. No restriction on length.
45+
* @param markdownParseOptions Options for the Markdown parser.
4546
* @param onLinkClicked A function to invoke when a link is clicked from rendered content.
4647
*/
4748
@Composable
4849
public fun RichTextScope.Markdown(
4950
content: String,
51+
markdownParseOptions: MarkdownParseOptions = MarkdownParseOptions.Default,
5052
onLinkClicked: ((String) -> Unit)? = null
5153
) {
5254
val onLinkClickedState = rememberUpdatedState(onLinkClicked)
@@ -57,9 +59,8 @@ public fun RichTextScope.Markdown(
5759
{ url -> it.openUri(url) }
5860
}
5961
}
60-
6162
CompositionLocalProvider(LocalOnLinkClicked provides realLinkClickedHandler) {
62-
val markdownAst = parsedMarkdownAst(text = content)
63+
val markdownAst = parsedMarkdownAst(text = content, options = markdownParseOptions)
6364
RecursiveRenderMarkdownAst(astNode = markdownAst)
6465
}
6566
}
@@ -69,9 +70,10 @@ public fun RichTextScope.Markdown(
6970
* Composable is efficient thanks to remember construct.
7071
*
7172
* @param text Markdown text to be parsed.
73+
* @param options Options for the Markdown parser.
7274
*/
7375
@Composable
74-
internal expect fun parsedMarkdownAst(text: String): AstNode?
76+
internal expect fun parsedMarkdownAst(text: String, options: MarkdownParseOptions): AstNode?
7577

7678
/**
7779
* When parsed, markdown content or any other rich text can be represented as a tree.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.halilibo.richtext.markdown
2+
3+
/**
4+
* Allows configuration of the Markdown parser
5+
*
6+
* @param autolink Detect plain text links and turn them into Markdown links.
7+
*/
8+
public data class MarkdownParseOptions(
9+
val autolink: Boolean
10+
) {
11+
public companion object {
12+
public val Default: MarkdownParseOptions = MarkdownParseOptions(
13+
autolink = true
14+
)
15+
}
16+
}

0 commit comments

Comments
 (0)