Skip to content

Commit 1048d0d

Browse files
Merge pull request #49 from openai/bjd/rich-text-decoration
Dotted and dashed underline support for annotated links
2 parents 43f61c1 + 492637f commit 1048d0d

File tree

9 files changed

+355
-22
lines changed

9 files changed

+355
-22
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import androidx.compose.runtime.Composable
1313
import androidx.compose.runtime.CompositionLocalProvider
1414
import androidx.compose.ui.Modifier
1515
import androidx.compose.ui.graphics.Color
16+
import androidx.compose.ui.text.SpanStyle
17+
import androidx.compose.ui.text.TextLinkStyles
1618
import androidx.compose.ui.tooling.preview.Preview
1719
import androidx.compose.ui.unit.dp
1820
import com.halilibo.richtext.ui.BlockQuote
@@ -32,6 +34,13 @@ import com.halilibo.richtext.ui.RichTextStyle
3234
import com.halilibo.richtext.ui.Table
3335
import com.halilibo.richtext.ui.WithStyle
3436
import com.halilibo.richtext.ui.material3.RichText
37+
import com.halilibo.richtext.ui.string.LinkDecoration
38+
import com.halilibo.richtext.ui.string.RichTextDecorations
39+
import com.halilibo.richtext.ui.string.RichTextString
40+
import com.halilibo.richtext.ui.string.Text as RichTextText
41+
import com.halilibo.richtext.ui.string.UnderlineStyle
42+
import com.halilibo.richtext.ui.string.richTextString
43+
import com.halilibo.richtext.ui.string.withFormat
3544

3645
@Preview(widthDp = 300, heightDp = 1000)
3746
@Composable fun RichTextDemoOnWhite() {
@@ -61,6 +70,36 @@ import com.halilibo.richtext.ui.material3.RichText
6170
Text("Simple paragraph.")
6271
Text("Paragraph with\nmultiple lines.")
6372
Text("Paragraph with really long line that should be getting wrapped.")
73+
val bodyTextColor = LocalContentColor.current
74+
val dottedLinkDecorations = RichTextDecorations(
75+
linkDecorations = listOf(
76+
LinkDecoration(
77+
matcher = { destination, _ -> destination.contains("dotted") },
78+
underlineStyle = UnderlineStyle.Dotted(),
79+
linkStyleOverride = { base ->
80+
TextLinkStyles(
81+
style = (base?.style ?: SpanStyle()).copy(color = bodyTextColor),
82+
focusedStyle = base?.focusedStyle?.copy(color = bodyTextColor),
83+
hoveredStyle = base?.hoveredStyle?.copy(color = bodyTextColor),
84+
pressedStyle = base?.pressedStyle?.copy(color = bodyTextColor),
85+
)
86+
},
87+
),
88+
),
89+
)
90+
val dottedUnderlineText = richTextString {
91+
append("Dotted underline with wrapping: ")
92+
withFormat(RichTextString.Format.Link("https://example.com/dotted")) {
93+
append(
94+
"This is a long link that should wrap across multiple lines to show the " +
95+
"dotted underline.",
96+
)
97+
}
98+
}
99+
RichTextText(
100+
text = dottedUnderlineText,
101+
decorations = dottedLinkDecorations,
102+
)
64103
TextPreview()
65104

66105
Heading(0, "Lists")

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ import com.halilibo.richtext.ui.RichTextStyle
4848
import com.halilibo.richtext.ui.material3.RichText
4949
import com.halilibo.richtext.ui.resolveDefaults
5050
import com.halilibo.richtext.ui.string.MarkdownAnimationState
51+
import com.halilibo.richtext.ui.string.LinkDecoration
52+
import com.halilibo.richtext.ui.string.RichTextDecorations
5153
import com.halilibo.richtext.ui.string.RichTextRenderOptions
54+
import com.halilibo.richtext.ui.string.UnderlineStyle
5255

5356
@Preview
5457
@Composable private fun MarkdownSamplePreview() {
@@ -125,13 +128,31 @@ import com.halilibo.richtext.ui.string.RichTextRenderOptions
125128
val astNode = remember(parser) {
126129
parser.parse(sampleMarkdown)
127130
}
131+
val richTextDecorations = remember {
132+
RichTextDecorations(
133+
linkDecorations = listOf(
134+
LinkDecoration(
135+
matcher = { destination, _ -> destination.contains("dotted") },
136+
underlineStyle = UnderlineStyle.Dotted(),
137+
),
138+
LinkDecoration(
139+
matcher = { destination, _ -> destination.contains("dashed") },
140+
underlineStyle = UnderlineStyle.Dashed(),
141+
),
142+
),
143+
)
144+
}
128145

129146
ProvideToastUriHandler(context) {
130147
RichText(
131148
style = richTextStyle,
132149
modifier = Modifier.padding(8.dp),
133150
) {
134-
BasicMarkdown(astNode, astBlockNodeComposer = HeadingAstBlockNodeComposer)
151+
BasicMarkdown(
152+
astNode = astNode,
153+
richTextDecorations = richTextDecorations,
154+
astBlockNodeComposer = HeadingAstBlockNodeComposer,
155+
)
135156
}
136157
}
137158
}
@@ -150,6 +171,7 @@ val HeadingAstBlockNodeComposer = object : AstBlockNodeComposer {
150171
contentOverride: ContentOverride?,
151172
inlineContentOverride: InlineContentOverride?,
152173
richTextRenderOptions: RichTextRenderOptions,
174+
richTextDecorations: RichTextDecorations,
153175
markdownAnimationState: MarkdownAnimationState,
154176
visitChildren: @Composable (AstNode) -> Unit
155177
) {
@@ -186,6 +208,11 @@ private val sampleMarkdown = """
186208
# Demo
187209
Based on [this cheatsheet][cheatsheet]
188210
211+
## Link underline styles
212+
- [Dotted underline example](https://example.com/dotted)
213+
- [Dashed underline example](https://example.com/dashed)
214+
- [Default underline example](https://example.com/solid)
215+
189216
---
190217
191218
## Headers

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import com.halilibo.richtext.markdown.ContentOverride
1212
import com.halilibo.richtext.markdown.InlineContentOverride
1313
import com.halilibo.richtext.markdown.node.AstNode
1414
import com.halilibo.richtext.ui.RichTextScope
15-
import com.halilibo.richtext.ui.string.MarkdownAnimationState
15+
import com.halilibo.richtext.ui.string.RichTextDecorations
1616
import com.halilibo.richtext.ui.string.RichTextRenderOptions
1717
import org.commonmark.node.Node
1818

@@ -29,6 +29,7 @@ public fun RichTextScope.Markdown(
2929
content: String,
3030
markdownParseOptions: CommonMarkdownParseOptions = CommonMarkdownParseOptions.Default,
3131
richtextRenderOptions: RichTextRenderOptions = RichTextRenderOptions.Default,
32+
richTextDecorations: RichTextDecorations = RichTextDecorations(),
3233
contentOverride: ContentOverride? = null,
3334
inlineContentOverride: InlineContentOverride? = null,
3435
astBlockNodeComposer: AstBlockNodeComposer? = null
@@ -51,6 +52,7 @@ public fun RichTextScope.Markdown(
5152
contentOverride = contentOverride,
5253
inlineContentOverride = inlineContentOverride,
5354
richTextRenderOptions = richtextRenderOptions,
55+
richTextDecorations = richTextDecorations,
5456
astBlockNodeComposer = astBlockNodeComposer,
5557
)
5658
}
@@ -66,6 +68,7 @@ public fun RichTextScope.Markdown(
6668
public fun RichTextScope.Markdown(
6769
content: Node,
6870
richtextRenderOptions: RichTextRenderOptions = RichTextRenderOptions.Default,
71+
richTextDecorations: RichTextDecorations = RichTextDecorations(),
6972
contentOverride: ContentOverride? = null,
7073
inlineContentOverride: InlineContentOverride? = null,
7174
astBlockNodeComposer: AstBlockNodeComposer? = null
@@ -76,6 +79,7 @@ public fun RichTextScope.Markdown(
7679
contentOverride,
7780
inlineContentOverride,
7881
richtextRenderOptions,
82+
richTextDecorations,
7983
astBlockNodeComposer,
8084
)
8185
}

richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/BasicMarkdown.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.halilibo.richtext.ui.ListType.Unordered
3737
import com.halilibo.richtext.ui.RichTextScope
3838
import com.halilibo.richtext.ui.string.InlineContent
3939
import com.halilibo.richtext.ui.string.MarkdownAnimationState
40+
import com.halilibo.richtext.ui.string.RichTextDecorations
4041
import com.halilibo.richtext.ui.string.RichTextRenderOptions
4142
import com.halilibo.richtext.ui.string.RichTextRenderOptions.Companion
4243
import com.halilibo.richtext.ui.string.RichTextString
@@ -80,13 +81,15 @@ public fun RichTextScope.BasicMarkdown(
8081
contentOverride: ContentOverride? = null,
8182
inlineContentOverride: InlineContentOverride? = null,
8283
richTextRenderOptions: RichTextRenderOptions = RichTextRenderOptions.Default,
84+
richTextDecorations: RichTextDecorations = RichTextDecorations(),
8385
astBlockNodeComposer: AstBlockNodeComposer? = null,
8486
) {
8587
RecursiveRenderMarkdownAst(
8688
astNode = astNode,
8789
contentOverride = contentOverride,
8890
inlineContentOverride = inlineContentOverride,
8991
richTextRenderOptions = richTextRenderOptions,
92+
richTextDecorations = richTextDecorations,
9093
markdownAnimationState = remember { MarkdownAnimationState() },
9194
astNodeComposer = astBlockNodeComposer,
9295
)
@@ -116,6 +119,7 @@ public interface AstBlockNodeComposer {
116119
contentOverride: ContentOverride?,
117120
inlineContentOverride: InlineContentOverride?,
118121
richTextRenderOptions: RichTextRenderOptions,
122+
richTextDecorations: RichTextDecorations,
119123
markdownAnimationState: MarkdownAnimationState,
120124
visitChildren: @Composable (AstNode) -> Unit
121125
)
@@ -152,6 +156,7 @@ internal fun RichTextScope.RecursiveRenderMarkdownAst(
152156
contentOverride: ContentOverride?,
153157
inlineContentOverride: InlineContentOverride?,
154158
richTextRenderOptions: RichTextRenderOptions,
159+
richTextDecorations: RichTextDecorations,
155160
markdownAnimationState: MarkdownAnimationState,
156161
astNodeComposer: AstBlockNodeComposer?
157162
) {
@@ -163,6 +168,7 @@ internal fun RichTextScope.RecursiveRenderMarkdownAst(
163168
contentOverride,
164169
inlineContentOverride,
165170
richTextRenderOptions,
171+
richTextDecorations,
166172
markdownAnimationState,
167173
astNodeComposer,
168174
)
@@ -180,13 +186,15 @@ internal fun RichTextScope.RecursiveRenderMarkdownAst(
180186
contentOverride,
181187
inlineContentOverride,
182188
richTextRenderOptions,
189+
richTextDecorations,
183190
markdownAnimationState,
184191
) {
185192
renderChildren(
186193
node = it,
187194
contentOverride,
188195
inlineContentOverride = inlineContentOverride,
189196
richTextRenderOptions = richTextRenderOptions,
197+
richTextDecorations = richTextDecorations,
190198
markdownAnimationState = markdownAnimationState,
191199
astNodeComposer = astNodeComposer
192200
)
@@ -199,13 +207,15 @@ internal fun RichTextScope.RecursiveRenderMarkdownAst(
199207
contentOverride,
200208
inlineContentOverride = inlineContentOverride,
201209
richTextRenderOptions = richTextRenderOptions,
210+
richTextDecorations = richTextDecorations,
202211
markdownAnimationState = markdownAnimationState,
203212
visitChildren = {
204213
renderChildren(
205214
node = it,
206215
contentOverride,
207216
inlineContentOverride = inlineContentOverride,
208217
richTextRenderOptions = richTextRenderOptions,
218+
richTextDecorations = richTextDecorations,
209219
markdownAnimationState = markdownAnimationState,
210220
astNodeComposer = astNodeComposer
211221
)
@@ -224,6 +234,7 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer {
224234
contentOverride: ContentOverride?,
225235
inlineContentOverride: InlineContentOverride?,
226236
richTextRenderOptions: RichTextRenderOptions,
237+
richTextDecorations: RichTextDecorations,
227238
markdownAnimationState: MarkdownAnimationState,
228239
visitChildren: @Composable (AstNode) -> Unit
229240
) {
@@ -284,6 +295,7 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer {
284295
astNode,
285296
inlineContentOverride,
286297
richTextRenderOptions,
298+
richTextDecorations,
287299
markdownAnimationState,
288300
modifier = Modifier.semantics { heading() },
289301
)
@@ -324,12 +336,19 @@ private val DefaultAstNodeComposer = object : AstBlockNodeComposer {
324336
astNode,
325337
inlineContentOverride,
326338
richTextRenderOptions,
339+
richTextDecorations,
327340
markdownAnimationState,
328341
)
329342
}
330343

331344
is AstTableRoot -> {
332-
RenderTable(astNode, inlineContentOverride, richTextRenderOptions, markdownAnimationState)
345+
RenderTable(
346+
astNode,
347+
inlineContentOverride,
348+
richTextRenderOptions,
349+
richTextDecorations,
350+
markdownAnimationState,
351+
)
333352
}
334353
// This should almost never happen. All the possible text
335354
// nodes must be under either Heading, Paragraph or CustomNode
@@ -371,6 +390,7 @@ internal fun RichTextScope.renderChildren(
371390
contentOverride: ContentOverride?,
372391
inlineContentOverride: InlineContentOverride?,
373392
richTextRenderOptions: RichTextRenderOptions,
393+
richTextDecorations: RichTextDecorations,
374394
markdownAnimationState: MarkdownAnimationState,
375395
astNodeComposer: AstBlockNodeComposer?
376396
) {
@@ -380,6 +400,7 @@ internal fun RichTextScope.renderChildren(
380400
contentOverride = contentOverride,
381401
inlineContentOverride = inlineContentOverride,
382402
richTextRenderOptions = richTextRenderOptions,
403+
richTextDecorations = richTextDecorations,
383404
markdownAnimationState = markdownAnimationState,
384405
astNodeComposer = astNodeComposer,
385406
)

richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.halilibo.richtext.ui.FormattedList
3030
import com.halilibo.richtext.ui.RichTextScope
3131
import com.halilibo.richtext.ui.string.InlineContent
3232
import com.halilibo.richtext.ui.string.MarkdownAnimationState
33+
import com.halilibo.richtext.ui.string.RichTextDecorations
3334
import com.halilibo.richtext.ui.string.RichTextRenderOptions
3435
import com.halilibo.richtext.ui.string.RichTextString
3536
import com.halilibo.richtext.ui.string.Text
@@ -63,6 +64,7 @@ internal fun RichTextScope.MarkdownRichText(
6364
astNode: AstNode,
6465
inlineContentOverride: InlineContentOverride?,
6566
richTextRenderOptions: RichTextRenderOptions,
67+
richTextDecorations: RichTextDecorations,
6668
markdownAnimationState: MarkdownAnimationState,
6769
modifier: Modifier = Modifier,
6870
) {
@@ -77,6 +79,7 @@ internal fun RichTextScope.MarkdownRichText(
7779
isLeafText = astNode.isLastInTree(),
7880
renderOptions = richTextRenderOptions,
7981
sharedAnimationState = markdownAnimationState,
82+
decorations = richTextDecorations,
8083
)
8184
}
8285

richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/RenderTable.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.halilibo.richtext.markdown
22

33
import androidx.compose.runtime.Composable
4-
import androidx.compose.runtime.MutableState
54
import com.halilibo.richtext.markdown.node.AstNode
65
import com.halilibo.richtext.markdown.node.AstTableBody
76
import com.halilibo.richtext.markdown.node.AstTableCell
@@ -10,13 +9,15 @@ import com.halilibo.richtext.markdown.node.AstTableRow
109
import com.halilibo.richtext.ui.RichTextScope
1110
import com.halilibo.richtext.ui.Table
1211
import com.halilibo.richtext.ui.string.MarkdownAnimationState
12+
import com.halilibo.richtext.ui.string.RichTextDecorations
1313
import com.halilibo.richtext.ui.string.RichTextRenderOptions
1414

1515
@Composable
1616
internal fun RichTextScope.RenderTable(
1717
node: AstNode,
1818
inlineContentOverride: InlineContentOverride?,
1919
richtextRenderOptions: RichTextRenderOptions,
20+
richTextDecorations: RichTextDecorations,
2021
markdownAnimationState: MarkdownAnimationState,
2122
) {
2223
Table(
@@ -34,6 +35,7 @@ internal fun RichTextScope.RenderTable(
3435
tableCell,
3536
inlineContentOverride,
3637
richtextRenderOptions,
38+
richTextDecorations,
3739
markdownAnimationState,
3840
)
3941
}
@@ -52,11 +54,12 @@ internal fun RichTextScope.RenderTable(
5254
tableCell,
5355
inlineContentOverride,
5456
richtextRenderOptions,
57+
richTextDecorations,
5558
markdownAnimationState,
5659
)
5760
}
5861
}
5962
}
60-
}
63+
}
6164
}
6265
}

0 commit comments

Comments
 (0)