Skip to content

Commit

Permalink
Add autolink (linkify) functionality to Markdown
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
morrisseyai committed Oct 22, 2022
1 parent 4a60b78 commit 2eef844
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.markdown.MarkdownParseOptions
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material.MaterialRichText
import com.halilibo.richtext.ui.resolveDefaults
Expand All @@ -41,6 +42,8 @@ import com.halilibo.richtext.ui.resolveDefaults
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
var isDarkModeEnabled by remember { mutableStateOf(false) }
var isWordWrapEnabled by remember { mutableStateOf(true) }
var markdownParseOptions by remember { mutableStateOf(MarkdownParseOptions.getDefaults()) }
var isAutolinkEnabled by remember { mutableStateOf(true) }

LaunchedEffect(isWordWrapEnabled) {
richTextStyle = richTextStyle.copy(
Expand All @@ -49,6 +52,11 @@ import com.halilibo.richtext.ui.resolveDefaults
)
)
}
LaunchedEffect(isAutolinkEnabled) {
markdownParseOptions = markdownParseOptions.copy(
autolink = isAutolinkEnabled
)
}

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

CheckboxPreference(
onClick = {
isAutolinkEnabled = !isAutolinkEnabled
},
checked = isAutolinkEnabled,
label = "Autolink"
)

RichTextStyleConfig(
richTextStyle = richTextStyle,
onChanged = { richTextStyle = it }
Expand All @@ -90,6 +106,7 @@ import com.halilibo.richtext.ui.resolveDefaults
) {
Markdown(
content = sampleMarkdown,
markdownParseOptions = markdownParseOptions,
onLinkClicked = {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
}
Expand Down Expand Up @@ -178,6 +195,8 @@ private val sampleMarkdown = """
[You can use numbers for reference-style link definitions][1]
Or leave it empty and use the [link text itself].
Autolink option will detect text links like https://www.google.com and turn them into Markdown links automatically.
---
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ object Commonmark {
val core = "org.commonmark:commonmark:$version"
val tables = "org.commonmark:commonmark-ext-gfm-tables:$version"
val strikethrough = "org.commonmark:commonmark-ext-gfm-strikethrough:$version"
val autolink = "org.commonmark:commonmark-ext-autolink:$version"
}

object AndroidConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ private val sampleMarkdown = """
[You can use numbers for reference-style link definitions][1]
Or leave it empty and use the [link text itself].
Autolink option will detect text links like https://www.google.com and turn them into Markdown links automatically.
---
Expand Down
15 changes: 15 additions & 0 deletions docs/richtext-commonmark.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,18 @@ RichText(
Which produces something like this:

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

## MarkdownParseOptions

Passing `MarkdownParseOptions` into `Markdown` provides the ability to control some aspects of the markdown parser:

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

Markdown(
markdownParseOptions = markdownParseOptions,
...
)
```
2 changes: 2 additions & 0 deletions richtext-commonmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ kotlin {
implementation(Commonmark.core)
implementation(Commonmark.tables)
implementation(Commonmark.strikethrough)
implementation(Commonmark.autolink)
}
}

Expand All @@ -39,6 +40,7 @@ kotlin {
implementation(Commonmark.core)
implementation(Commonmark.tables)
implementation(Commonmark.strikethrough)
implementation(Commonmark.autolink)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.halilibo.richtext.markdown.node.AstTableRoot
import com.halilibo.richtext.markdown.node.AstTableRow
import com.halilibo.richtext.markdown.node.AstText
import com.halilibo.richtext.markdown.node.AstThematicBreak
import org.commonmark.ext.autolink.AutolinkExtension
import org.commonmark.ext.gfm.strikethrough.Strikethrough
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension
import org.commonmark.ext.gfm.tables.TableBlock
Expand Down Expand Up @@ -188,19 +189,20 @@ internal fun convert(
internal fun Node.convert() = convert(this)

@Composable
internal actual fun parsedMarkdownAst(text: String): AstNode? {
val parser = remember {
internal actual fun parsedMarkdownAst(text: String, options: MarkdownParseOptions): AstNode? {
val parser = remember(options) {
Parser.builder()
.extensions(
listOf(
listOfNotNull(
TablesExtension.create(),
StrikethroughExtension.create()
StrikethroughExtension.create(),
if (options.autolink) AutolinkExtension.create() else null
)
)
.build()
}

val astRootNode by produceState<AstNode?>(null, text) {
val astRootNode by produceState<AstNode?>(null, text, options) {
value = parser.parse(text).convert()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ import com.halilibo.richtext.ui.string.richTextString
* A composable that renders Markdown content using RichText.
*
* @param content Markdown text. No restriction on length.
* @param markdownParseOptions Options for the Markdown parser.
* @param onLinkClicked A function to invoke when a link is clicked from rendered content.
*/
@Composable
public fun RichTextScope.Markdown(
content: String,
markdownParseOptions: MarkdownParseOptions = MarkdownParseOptions.getDefaults(),
onLinkClicked: ((String) -> Unit)? = null
) {
val onLinkClickedState = rememberUpdatedState(onLinkClicked)
Expand All @@ -57,9 +59,8 @@ public fun RichTextScope.Markdown(
{ url -> it.openUri(url) }
}
}

CompositionLocalProvider(LocalOnLinkClicked provides realLinkClickedHandler) {
val markdownAst = parsedMarkdownAst(text = content)
val markdownAst = parsedMarkdownAst(text = content, options = markdownParseOptions)
RecursiveRenderMarkdownAst(astNode = markdownAst)
}
}
Expand All @@ -69,9 +70,10 @@ public fun RichTextScope.Markdown(
* Composable is efficient thanks to remember construct.
*
* @param text Markdown text to be parsed.
* @param options Options for the Markdown parser.
*/
@Composable
internal expect fun parsedMarkdownAst(text: String): AstNode?
internal expect fun parsedMarkdownAst(text: String, options: MarkdownParseOptions): AstNode?

/**
* When parsed, markdown content or any other rich text can be represented as a tree.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.halilibo.richtext.markdown

/**
* Allows configuration of the Markdown parser
*
* @param autolink Detect plain text links and turn them into Markdown links.
*/
public data class MarkdownParseOptions(
val autolink: Boolean
) {
public companion object {
public fun getDefaults(): MarkdownParseOptions = MarkdownParseOptions(
autolink = true
)
}
}

0 comments on commit 2eef844

Please sign in to comment.