Skip to content

Commit 9556d65

Browse files
committed
Add autolink (linkify) functionality to Markdown
Adds MarkdownParseOptions which allows consumers of the library to control whether or not the autolink functionality is enabled. Note the behaviour change: autolink is enabled with the new MarkdownParseOptions defaults.
1 parent 4a60b78 commit 9556d65

8 files changed

Lines changed: 67 additions & 8 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ object Commonmark {
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)