Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,18 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withLink
import com.moveagency.markymark.model.annotated.*
import com.moveagency.markymark.model.annotated.AnnotatedStableNode
import com.moveagency.markymark.model.annotated.Bold
import com.moveagency.markymark.model.annotated.Code
import com.moveagency.markymark.model.annotated.EmailLink
import com.moveagency.markymark.model.annotated.Italic
import com.moveagency.markymark.model.annotated.Link
import com.moveagency.markymark.model.annotated.ParagraphText
import com.moveagency.markymark.model.annotated.SoftLineBreak
import com.moveagency.markymark.model.annotated.Strikethrough
import com.moveagency.markymark.model.annotated.Subscript
import com.moveagency.markymark.model.annotated.Superscript
import com.moveagency.markymark.model.annotated.Text
import com.moveagency.markymark.theme.AnnotatedStyles
import kotlinx.collections.immutable.ImmutableList

Expand Down Expand Up @@ -88,15 +99,15 @@ open class DefaultMarkyMarkAnnotator : MarkyMarkAnnotator {

protected open fun AnnotatedString.Builder.annotateLink(link: Link, styles: AnnotatedStyles) {
pushStyle(styles.link)
withLink(LinkAnnotation.Url(link.url)) {
withLink(linkToAnnotation(link)) {
annotateChildren(nodes = link.children, styles = styles)
}
pop()
}

protected open fun AnnotatedString.Builder.annotateEmailLink(link: EmailLink, styles: AnnotatedStyles) {
pushStyle(styles.link)
withLink(LinkAnnotation.Url("$MailToPrefix${link.email}")) {
withLink(emailLinkToAnnotation(link)) {
append(link.email)
}
pop()
Expand All @@ -114,6 +125,26 @@ open class DefaultMarkyMarkAnnotator : MarkyMarkAnnotator {
pop()
}

private fun linkToAnnotation(link: Link) = when (link) {
is Link.BrowserLink -> LinkAnnotation.Url(url = link.url)
is Link.CustomLink -> {
LinkAnnotation.Clickable(
tag = link.title.orEmpty(),
linkInteractionListener = { link.clickListener.onClick(link.url) },
)
}
}

private fun emailLinkToAnnotation(link: EmailLink): LinkAnnotation {
val url = "$MailToPrefix${link.email}"
return link.linkInteractionListener?.let { listener ->
LinkAnnotation.Clickable(
tag = link.email,
linkInteractionListener = { listener.onClick(url) },
)
} ?: LinkAnnotation.Url(url)
}

companion object {

private const val MailToPrefix = "mailto:"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.moveagency.markymark.MarkyMark
import com.moveagency.markymark.composer.paddingVertical
Expand All @@ -45,7 +51,10 @@ fun Markdown(
val document = remember(parser, markdown) { parser.parse(markdown) }

var nodes by remember { mutableStateOf<ImmutableList<ComposableStableNode>>(persistentListOf()) }
LaunchedEffect(parser, document) { nodes = convertToStableNodes(document) }

LaunchedEffect(parser, document) {
nodes = convertToStableNodes(document = document)
}

val composer = MarkyMark.options.composer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,25 @@ package com.moveagency.markymark.converter
import android.util.Log
import com.moveagency.markymark.converter.MarkyMarkConverter.ConverterTag
import com.moveagency.markymark.converter.MarkyMarkConverter.convertToAnnotatedNodes
import com.moveagency.markymark.model.LinkInteractionListener
import com.moveagency.markymark.model.NodeMetadata
import com.moveagency.markymark.model.annotated.*
import com.vladsch.flexmark.ast.*
import com.moveagency.markymark.model.annotated.Bold
import com.moveagency.markymark.model.annotated.Code
import com.moveagency.markymark.model.annotated.EmailLink
import com.moveagency.markymark.model.annotated.Italic
import com.moveagency.markymark.model.annotated.Link
import com.moveagency.markymark.model.annotated.ParagraphText
import com.moveagency.markymark.model.annotated.SoftLineBreak
import com.moveagency.markymark.model.annotated.Strikethrough
import com.moveagency.markymark.model.annotated.Subscript
import com.moveagency.markymark.model.annotated.Superscript
import com.moveagency.markymark.model.annotated.Text
import com.vladsch.flexmark.ast.AutoLink
import com.vladsch.flexmark.ast.Emphasis
import com.vladsch.flexmark.ast.LinkRef
import com.vladsch.flexmark.ast.MailLink
import com.vladsch.flexmark.ast.StrongEmphasis
import com.vladsch.flexmark.ast.TextBase
import com.vladsch.flexmark.util.ast.Node
import com.vladsch.flexmark.util.sequence.BasedSequence
import com.vladsch.flexmark.util.sequence.Escaping
Expand All @@ -40,20 +56,24 @@ import com.vladsch.flexmark.ext.superscript.Superscript as FlexSuperscript
object AnnotatedStableNodeConverter {

@Suppress("ComplexMethod")
internal suspend fun convertToAnnotatedNode(metadata: NodeMetadata, node: Node) = when (node) {
internal suspend fun convertToAnnotatedNode(
metadata: NodeMetadata,
node: Node,
linkInteractionListener: LinkInteractionListener?,
) = when (node) {
is FlexText -> convertTextNode(metadata, node)
is Emphasis -> convertEmphasisNode(metadata, node)
is StrongEmphasis -> convertStrongEmphasisNode(metadata, node)
is FlexStrikethrough -> convertStrikeThroughNode(metadata, node)
is Emphasis -> convertEmphasisNode(metadata, node, linkInteractionListener)
is StrongEmphasis -> convertStrongEmphasisNode(metadata, node, linkInteractionListener)
is FlexStrikethrough -> convertStrikeThroughNode(metadata, node, linkInteractionListener)
is FlexCode -> convertCodeNode(metadata, node)
is FlexLink -> convertLinkNode(metadata, node)
is AutoLink -> convertAutoLinkNode(metadata, node)
is FlexLink -> convertLinkNode(metadata, node, linkInteractionListener)
is AutoLink -> convertAutoLinkNode(metadata, node, linkInteractionListener)
is LinkRef -> convertLinkRefNode(metadata, node)
is MailLink -> convertMailLinkNode(metadata, node)
is MailLink -> convertMailLinkNode(metadata, node, linkInteractionListener)
is FlexSoftLineBreak -> SoftLineBreak(metadata)
is FlexSubscript -> convertSubscriptNode(metadata, node)
is FlexSuperscript -> convertSuperscriptNode(metadata, node)
is TextBase -> convertTextBaseNode(metadata, node)
is TextBase -> convertTextBaseNode(metadata, node, linkInteractionListener)
else -> {
Log.w(ConverterTag, "Found unknown node, $node")
null
Expand All @@ -64,49 +84,106 @@ object AnnotatedStableNodeConverter {
return Text(metadata = metadata, content = text.chars.unescapeHtml())
}

private suspend fun convertEmphasisNode(metadata: NodeMetadata, emphasis: Emphasis): Italic {
private suspend fun convertEmphasisNode(
metadata: NodeMetadata,
emphasis: Emphasis,
linkInteractionListener: LinkInteractionListener?,
): Italic {
return Italic(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, nodes = emphasis.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = emphasis.children,
linkInteractionListener = linkInteractionListener,
),
)
}

private suspend fun convertStrongEmphasisNode(metadata: NodeMetadata, strongEmphasis: StrongEmphasis): Bold {
private suspend fun convertStrongEmphasisNode(
metadata: NodeMetadata,
strongEmphasis: StrongEmphasis,
linkInteractionListener: LinkInteractionListener?,
): Bold {
return Bold(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, strongEmphasis.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = strongEmphasis.children,
linkInteractionListener = linkInteractionListener,
),
)
}

private suspend fun convertStrikeThroughNode(
metadata: NodeMetadata,
strikethrough: FlexStrikethrough,
linkInteractionListener: LinkInteractionListener?,
): Strikethrough {
return Strikethrough(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, nodes = strikethrough.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = strikethrough.children,
linkInteractionListener = linkInteractionListener,
),
)
}

private suspend fun convertCodeNode(metadata: NodeMetadata, code: FlexCode): Code {
return Code(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, nodes = code.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = code.children,
linkInteractionListener = null,
),
)
}

private suspend fun convertLinkNode(metadata: NodeMetadata, link: FlexLink): Link {
return Link(
private suspend fun convertLinkNode(
metadata: NodeMetadata,
link: FlexLink,
clickListener: LinkInteractionListener?,
): Link {
return clickListener?.let { listener ->
Link.CustomLink(
metadata = metadata,
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = link.children,
linkInteractionListener = null,
),
url = link.url.unescapeHtml(),
title = link.title.unescapeHtml().takeUnless { it.isBlank() },
clickListener = listener,
)
} ?: Link.BrowserLink(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, nodes = link.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = link.children,
linkInteractionListener = null,
),
url = link.url.unescapeHtml(),
title = link.title.unescapeHtml().takeUnless { it.isBlank() },
)
}

private fun convertAutoLinkNode(metadata: NodeMetadata, autoLink: AutoLink): Link {
private fun convertAutoLinkNode(
metadata: NodeMetadata,
autoLink: AutoLink,
clickListener: LinkInteractionListener?,
): Link {
val url = autoLink.url.unescapeHtml()
return Link(
return clickListener?.let {
Link.CustomLink(
metadata = metadata,
children = persistentListOf(Text(metadata = metadata, content = url)),
url = url,
title = null,
clickListener = it,
)
} ?: Link.BrowserLink(
metadata = metadata,
children = persistentListOf(Text(metadata = metadata, content = url)),
url = url,
Expand All @@ -118,28 +195,52 @@ object AnnotatedStableNodeConverter {
return Text(metadata = metadata, content = linkRef.chars.unescapeHtml())
}

private fun convertMailLinkNode(metadata: NodeMetadata, emailLink: MailLink): EmailLink {
return EmailLink(metadata = metadata, email = emailLink.text.unescapeHtml())
private fun convertMailLinkNode(
metadata: NodeMetadata,
emailLink: MailLink,
linkInteractionListener: LinkInteractionListener?,
): EmailLink {
return EmailLink(
metadata = metadata,
email = emailLink.text.unescapeHtml(),
linkInteractionListener = linkInteractionListener,
)
}

private suspend fun convertSubscriptNode(metadata: NodeMetadata, subscript: FlexSubscript): Subscript {
return Subscript(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, nodes = subscript.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = subscript.children,
linkInteractionListener = null,
),
)
}

private suspend fun convertSuperscriptNode(metadata: NodeMetadata, superscript: FlexSuperscript): Superscript {
return Superscript(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, nodes = superscript.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = superscript.children,
linkInteractionListener = null,
),
)
}

private suspend fun convertTextBaseNode(metadata: NodeMetadata, textBase: TextBase): ParagraphText {
private suspend fun convertTextBaseNode(
metadata: NodeMetadata,
textBase: TextBase,
linkInteractionListener: LinkInteractionListener?,
): ParagraphText {
return ParagraphText(
metadata = metadata,
children = convertToAnnotatedNodes(metadata = metadata, nodes = textBase.children),
children = convertToAnnotatedNodes(
metadata = metadata,
nodes = textBase.children,
linkInteractionListener = linkInteractionListener,
),
)
}

Expand Down
Loading
Loading