feat(jbm): continue development
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
5497933589
commit
7cfbc3fdac
|
|
@ -269,6 +269,7 @@ dependencies {
|
|||
|
||||
// Markup
|
||||
implementation "org.jetbrains:markdown:0.7.3"
|
||||
implementation "dev.snipme:highlights:0.9.1"
|
||||
|
||||
// Livekit
|
||||
// FIXME temporarily not included, re-add when realtime media is to be implemented
|
||||
|
|
|
|||
|
|
@ -34,13 +34,17 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -78,6 +82,9 @@ import chat.revolt.components.generic.UserAvatar
|
|||
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
||||
import chat.revolt.components.markdown.LocalMarkdownTreeConfig
|
||||
import chat.revolt.components.markdown.RichMarkdown
|
||||
import chat.revolt.components.markdown.jbm.JBM
|
||||
import chat.revolt.components.markdown.jbm.JBMRenderer
|
||||
import chat.revolt.components.markdown.jbm.LocalJBMarkdownTreeState
|
||||
import chat.revolt.internals.text.Gigamoji
|
||||
import kotlinx.coroutines.launch
|
||||
import chat.revolt.api.schemas.Message as MessageSchema
|
||||
|
|
@ -167,7 +174,7 @@ fun formatLongAsTime(time: Long): String {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class, JBM::class)
|
||||
@Composable
|
||||
fun Message(
|
||||
message: MessageSchema,
|
||||
|
|
@ -192,6 +199,8 @@ fun Message(
|
|||
|
||||
val authorIsBlocked = remember(author) { author.relationship == "Blocked" }
|
||||
|
||||
var __TEMPORARY_useJbm by remember { mutableStateOf(false) }
|
||||
|
||||
Column(Modifier.animateContentSize()) {
|
||||
if (message.tail == false) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
|
@ -361,17 +370,40 @@ fun Message(
|
|||
message.content?.let {
|
||||
if (message.content.isBlank()) return@let // if only an attachment is sent
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalMarkdownTreeConfig provides LocalMarkdownTreeConfig.current.copy(
|
||||
currentServer = RevoltAPI.channelCache[message.channel]?.server,
|
||||
fontSizeMultiplier = Gigamoji.useGigamojiForMessage(message.content)
|
||||
.let {
|
||||
if (it) 2f else 1f
|
||||
}
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
RichMarkdown(input = message.content)
|
||||
Switch(
|
||||
checked = __TEMPORARY_useJbm,
|
||||
onCheckedChange = { __TEMPORARY_useJbm = it },
|
||||
)
|
||||
|
||||
if (__TEMPORARY_useJbm == false) {
|
||||
CompositionLocalProvider(
|
||||
LocalMarkdownTreeConfig provides LocalMarkdownTreeConfig.current.copy(
|
||||
currentServer = RevoltAPI.channelCache[message.channel]?.server,
|
||||
fontSizeMultiplier = Gigamoji.useGigamojiForMessage(
|
||||
message.content
|
||||
)
|
||||
.let {
|
||||
if (it) 2f else 1f
|
||||
}
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
RichMarkdown(input = message.content)
|
||||
}
|
||||
} else {
|
||||
CompositionLocalProvider(
|
||||
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
|
||||
fontSizeMultiplier = Gigamoji.useGigamojiForMessage(
|
||||
message.content
|
||||
)
|
||||
.let {
|
||||
if (it) 2f else 1f
|
||||
}
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
JBMRenderer(message.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
package chat.revolt.components.markdown.jbm
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
|
|
@ -20,12 +28,14 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.structuralEqualityPolicy
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
|
|
@ -39,8 +49,17 @@ import androidx.compose.ui.text.style.TextDecoration
|
|||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.components.markdown.Annotations
|
||||
import chat.revolt.components.utils.detectTapGesturesConditionalConsume
|
||||
import chat.revolt.ui.theme.FragmentMono
|
||||
import chat.revolt.ui.theme.isThemeDark
|
||||
import dev.snipme.highlights.Highlights
|
||||
import dev.snipme.highlights.model.BoldHighlight
|
||||
import dev.snipme.highlights.model.CodeHighlight
|
||||
import dev.snipme.highlights.model.ColorHighlight
|
||||
import dev.snipme.highlights.model.SyntaxLanguage
|
||||
import dev.snipme.highlights.model.SyntaxThemes
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.ast.ASTNode
|
||||
|
|
@ -50,10 +69,11 @@ import org.intellij.markdown.flavours.gfm.GFMTokenTypes
|
|||
|
||||
data class JBMarkdownTreeState(
|
||||
val sourceText: String = "",
|
||||
val ignoreLineBreaks: Boolean = false,
|
||||
val listDepth: Int = 0,
|
||||
val fontSizeMultiplier: Float = 1f,
|
||||
val linksClickable: Boolean = true
|
||||
val linksClickable: Boolean = true,
|
||||
val currentServer: String? = null,
|
||||
val embedded: Boolean = false,
|
||||
)
|
||||
|
||||
val LocalJBMarkdownTreeState =
|
||||
|
|
@ -66,15 +86,21 @@ fun JBMRenderer(content: String, modifier: Modifier = Modifier) {
|
|||
|
||||
LaunchedEffect(content) {
|
||||
tree = JBMApi.parse(content)
|
||||
|
||||
Log.d("JBMRenderer", "Parsed tree: ${tree.children.map { it.type.name }}")
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalJBMarkdownTreeState provides JBMarkdownTreeState(content)
|
||||
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
|
||||
sourceText = content
|
||||
)
|
||||
) {
|
||||
tree.children.map {
|
||||
JBMBlock(it, modifier)
|
||||
if (LocalJBMarkdownTreeState.current.embedded) {
|
||||
tree.children.getOrNull(0)?.let {
|
||||
JBMBlock(it, modifier)
|
||||
}
|
||||
} else {
|
||||
tree.children.map {
|
||||
JBMBlock(it, modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,12 +115,34 @@ private fun annotateText(
|
|||
buildAnnotatedString {
|
||||
when (node.type) {
|
||||
MarkdownTokenTypes.TEXT -> {
|
||||
append(node.getTextInNode(sourceText))
|
||||
val source = if (state.embedded) {
|
||||
node.getTextInNode(sourceText).toString().replace("\n", " ")
|
||||
} else {
|
||||
node.getTextInNode(sourceText)
|
||||
}
|
||||
append(source)
|
||||
}
|
||||
|
||||
MarkdownTokenTypes.ATX_HEADER -> {
|
||||
// Do not need to do anything
|
||||
}
|
||||
|
||||
MarkdownElementTypes.ATX_1,
|
||||
MarkdownElementTypes.ATX_2,
|
||||
MarkdownElementTypes.ATX_3,
|
||||
MarkdownElementTypes.ATX_4,
|
||||
MarkdownElementTypes.ATX_5,
|
||||
MarkdownElementTypes.ATX_6 -> {
|
||||
for (child in node.children) {
|
||||
append(annotateText(state, child))
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownElementTypes.EMPH -> {
|
||||
withStyle(SpanStyle(fontStyle = FontStyle.Italic)) {
|
||||
for (child in node.children) {
|
||||
// Skip the first child and the last child
|
||||
// because they are the asterisk characters
|
||||
for (child in node.children.subList(1, node.children.size - 1)) {
|
||||
append(annotateText(state, child))
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +150,9 @@ private fun annotateText(
|
|||
|
||||
MarkdownElementTypes.STRONG -> {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
for (child in node.children) {
|
||||
// Skip the first two children and the last two children
|
||||
// because they are the asterisk characters
|
||||
for (child in node.children.subList(2, node.children.size - 2)) {
|
||||
append(annotateText(state, child))
|
||||
}
|
||||
}
|
||||
|
|
@ -118,8 +168,24 @@ private fun annotateText(
|
|||
}
|
||||
}
|
||||
|
||||
MarkdownElementTypes.CODE_SPAN -> {
|
||||
withStyle(SpanStyle(fontFamily = FragmentMono)) {
|
||||
val startsWithTwoBackticks =
|
||||
node.children.getOrNull(1)?.type == MarkdownTokenTypes.BACKTICK
|
||||
val removeItemCount = if (startsWithTwoBackticks) 2 else 1
|
||||
// Skip the first and last 1 or 2 children
|
||||
// because they are the backtick characters
|
||||
for (child in node.children.subList(
|
||||
removeItemCount,
|
||||
node.children.size - removeItemCount
|
||||
)) {
|
||||
append(annotateText(state, child))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownTokenTypes.LIST_BULLET -> {
|
||||
append(" ".repeat(state.listDepth) + " " + if (state.listDepth % 2 == 0) "•" else "◦" + " ")
|
||||
append(" ".repeat(state.listDepth) + " • ")
|
||||
}
|
||||
|
||||
MarkdownTokenTypes.LIST_NUMBER -> {
|
||||
|
|
@ -145,7 +211,9 @@ private fun annotateText(
|
|||
append(" ")
|
||||
}
|
||||
|
||||
MarkdownElementTypes.PARAGRAPH, MarkdownElementTypes.HTML_BLOCK -> {
|
||||
MarkdownElementTypes.PARAGRAPH,
|
||||
MarkdownElementTypes.HTML_BLOCK,
|
||||
MarkdownTokenTypes.ATX_CONTENT -> {
|
||||
for (child in node.children) {
|
||||
append(annotateText(state, child))
|
||||
}
|
||||
|
|
@ -167,15 +235,11 @@ private fun annotateText(
|
|||
MarkdownTokenTypes.EOL,
|
||||
MarkdownTokenTypes.WHITE_SPACE,
|
||||
MarkdownTokenTypes.COLON,
|
||||
MarkdownTokenTypes.EMPH,
|
||||
GFMTokenTypes.TILDE -> {
|
||||
append(node.getTextInNode(sourceText))
|
||||
}
|
||||
|
||||
// no-op types
|
||||
// for example, the special characters that are used to denote the markup are here
|
||||
MarkdownTokenTypes.EMPH -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("[${node.type.name}]{\n")
|
||||
append(node.getTextInNode(sourceText))
|
||||
|
|
@ -292,14 +356,132 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
|
|||
)
|
||||
}
|
||||
|
||||
private fun annotateHighlights(
|
||||
source: String,
|
||||
highlights: List<CodeHighlight>
|
||||
): AnnotatedString {
|
||||
val highlightStyles = highlights.map {
|
||||
when (it) {
|
||||
is BoldHighlight -> AnnotatedString.Range(
|
||||
SpanStyle(fontWeight = FontWeight.Bold),
|
||||
it.location.start,
|
||||
it.location.end
|
||||
)
|
||||
|
||||
is ColorHighlight -> {
|
||||
AnnotatedString.Range(
|
||||
SpanStyle(color = Color(0xFF000000 or it.rgb.toLong())),
|
||||
it.location.start,
|
||||
it.location.end
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}.filterNotNull()
|
||||
|
||||
return AnnotatedString(source, spanStyles = highlightStyles)
|
||||
}
|
||||
|
||||
// ======== TODO ========
|
||||
// - Add aliases for languages. For example, "js" should be an alias for "javascript" and "ts" should be an alias for "typescript", etc.
|
||||
// - Better looking language name display. Ideally a dictionary of language names to display names with proper brand casing.
|
||||
@Composable
|
||||
private fun JBMCodeBlockContent(node: ASTNode, modifier: Modifier) {
|
||||
val state = LocalJBMarkdownTreeState.current
|
||||
|
||||
/*val colours = MaterialTheme.colorScheme
|
||||
val contentColour = LocalContentColor.current
|
||||
val syntaxTheme = remember {
|
||||
SyntaxTheme(
|
||||
key = "chat.revolt.M3Dynamic",
|
||||
code = contentColour.toArgb() and 0xFFFFFF,
|
||||
comment = colours.outline.toArgb() and 0xFFFFFF,
|
||||
multilineComment = colours.outline.toArgb() and 0xFFFFFF,
|
||||
keyword = colours.primary.toArgb() and 0xFFFFFF,
|
||||
string = colours.secondary.toArgb() and 0xFFFFFF,
|
||||
literal = colours.tertiary.toArgb() and 0xFFFFFF,
|
||||
mark = colours.error.toArgb() and 0xFFFFFF,
|
||||
punctuation = colours.inversePrimary.toArgb() and 0xFFFFFF,
|
||||
metadata = colours.inverseSurface.toArgb() and 0xFFFFFF,
|
||||
)
|
||||
}*/
|
||||
|
||||
val uiMode = LocalConfiguration.current.uiMode
|
||||
val systemIsDark =
|
||||
(uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
val themeIsDark = remember(GlobalState.theme) { isThemeDark(GlobalState.theme, systemIsDark) }
|
||||
|
||||
val codeFenceLanguage = remember(node) {
|
||||
node.children.firstOrNull { it.type == MarkdownTokenTypes.FENCE_LANG }
|
||||
?.getTextInNode(state.sourceText)?.toString()
|
||||
}
|
||||
val codeFenceContent = remember(node) {
|
||||
node.children
|
||||
.filter { it.type == MarkdownTokenTypes.CODE_FENCE_CONTENT || it.type == MarkdownTokenTypes.EOL }
|
||||
.joinToString("") {
|
||||
it.getTextInNode(state.sourceText).toString()
|
||||
}
|
||||
.trim()
|
||||
}
|
||||
val annotatedContent = remember(codeFenceLanguage, codeFenceContent) {
|
||||
val canAnnotate = codeFenceLanguage != null
|
||||
val language = codeFenceLanguage?.let { SyntaxLanguage.getByName(it) }
|
||||
val shouldAnnotate = language != null
|
||||
|
||||
if (canAnnotate && shouldAnnotate) {
|
||||
buildAnnotatedString {
|
||||
val highlights = Highlights.Builder().apply {
|
||||
code(codeFenceContent)
|
||||
language(language ?: SyntaxLanguage.DEFAULT)
|
||||
theme(SyntaxThemes.notepad(themeIsDark))
|
||||
//theme(syntaxTheme)
|
||||
}.build()
|
||||
append(annotateHighlights(codeFenceContent, highlights.getHighlights()))
|
||||
}
|
||||
} else {
|
||||
buildAnnotatedString {
|
||||
append(codeFenceContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
if (codeFenceLanguage != null) {
|
||||
Text(
|
||||
text = codeFenceLanguage,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.horizontalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(
|
||||
text = annotatedContent,
|
||||
fontFamily = FragmentMono,
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JBMBlock(node: ASTNode, modifier: Modifier) {
|
||||
val state = LocalJBMarkdownTreeState.current
|
||||
|
||||
when (node.type) {
|
||||
MarkdownElementTypes.PARAGRAPH,
|
||||
MarkdownElementTypes.HTML_BLOCK -> {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides LocalTextStyle.current.copy(
|
||||
fontSize = LocalTextStyle.current.fontSize * LocalJBMarkdownTreeState.current.fontSizeMultiplier
|
||||
fontSize = LocalTextStyle.current.fontSize * state.fontSizeMultiplier
|
||||
)
|
||||
) {
|
||||
JBMText(node, modifier)
|
||||
|
|
@ -315,42 +497,44 @@ private fun JBMBlock(node: ASTNode, modifier: Modifier) {
|
|||
CompositionLocalProvider(
|
||||
LocalTextStyle provides LocalTextStyle.current.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = when (node.type) {
|
||||
MarkdownElementTypes.ATX_1 -> 32.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_2 -> 24.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_3 -> 20.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_4 -> 16.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_5 -> 14.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier
|
||||
else -> 12.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier
|
||||
},
|
||||
color = when (node.type) {
|
||||
MarkdownElementTypes.ATX_1 -> Color(0xFFFF0000)
|
||||
MarkdownElementTypes.ATX_2 -> Color(0xFF00FF00)
|
||||
MarkdownElementTypes.ATX_3 -> Color(0xFF0000FF)
|
||||
MarkdownElementTypes.ATX_4 -> Color(0xFFFF00FF)
|
||||
MarkdownElementTypes.ATX_5 -> Color(0xFF00FFFF)
|
||||
else -> Color(0xFFFFFF00)
|
||||
fontSize = if (state.embedded) LocalTextStyle.current.fontSize
|
||||
else when (node.type) {
|
||||
MarkdownElementTypes.ATX_1 -> 32.sp * state.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_2 -> 24.sp * state.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_3 -> 20.sp * state.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_4 -> 16.sp * state.fontSizeMultiplier
|
||||
MarkdownElementTypes.ATX_5 -> 14.sp * state.fontSizeMultiplier
|
||||
else -> 12.sp * state.fontSizeMultiplier
|
||||
}
|
||||
)
|
||||
) {
|
||||
if (node.startOffset != 0) {
|
||||
Box(Modifier.padding(top = 8.dp))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
}
|
||||
JBMText(node, modifier)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownElementTypes.ORDERED_LIST,
|
||||
MarkdownElementTypes.UNORDERED_LIST -> {
|
||||
CompositionLocalProvider(
|
||||
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
|
||||
listDepth = LocalJBMarkdownTreeState.current.listDepth + 1
|
||||
LocalJBMarkdownTreeState provides state.copy(
|
||||
listDepth = state.listDepth + 1
|
||||
)
|
||||
) {
|
||||
JBMText(node, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownTokenTypes.EOL -> {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
}
|
||||
|
||||
MarkdownElementTypes.CODE_FENCE -> {
|
||||
JBMCodeBlockContent(node, modifier)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,27 @@
|
|||
package chat.revolt.screens.labs.ui.sandbox
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.components.markdown.jbm.JBM
|
||||
import chat.revolt.components.markdown.jbm.JBMRenderer
|
||||
import chat.revolt.components.markdown.jbm.LocalJBMarkdownTreeState
|
||||
import chat.revolt.settings.dsl.SettingsPage
|
||||
|
||||
@OptIn(JBM::class)
|
||||
|
|
@ -21,6 +29,7 @@ import chat.revolt.settings.dsl.SettingsPage
|
|||
fun JBMSandbox(navController: NavController) {
|
||||
var mdSource by remember { mutableStateOf("") }
|
||||
var submitMdSource by remember { mutableStateOf<String?>(null) }
|
||||
var isEmbedded by remember { mutableStateOf(false) }
|
||||
|
||||
SettingsPage(
|
||||
navController = navController,
|
||||
|
|
@ -32,6 +41,20 @@ fun JBMSandbox(navController: NavController) {
|
|||
)
|
||||
}
|
||||
) {
|
||||
Subcategory(
|
||||
title = { Text("Options", maxLines = 1, overflow = TextOverflow.Ellipsis) },
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = isEmbedded,
|
||||
onCheckedChange = { isEmbedded = it }
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Embedded", maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
Subcategory(
|
||||
title = { Text("Source", maxLines = 1, overflow = TextOverflow.Ellipsis) },
|
||||
) {
|
||||
|
|
@ -47,12 +70,43 @@ fun JBMSandbox(navController: NavController) {
|
|||
}) {
|
||||
Text("Submit")
|
||||
}
|
||||
TextButton(onClick = {
|
||||
submitMdSource = """# Full range of MD now supported!
|
||||
1. Text with **bold**, *italics*, and ***both***!
|
||||
2. You ~~can't see me~~.
|
||||
3. [I'm a link to another website.](<https://revolt.chat>)
|
||||
4. I'm a spoiler with ||**bold text inside it**||
|
||||
- I'm a sub-item on this list...
|
||||
- Let's go even deeper...
|
||||
|
||||
`Inline code`
|
||||
|
||||
```js
|
||||
let x = "I'm a multi-line code block!";
|
||||
```
|
||||
|
||||
> > ${'$'}${'$'}E = mc^2${'$'}${'$'}
|
||||
>
|
||||
> — Albert Einstein
|
||||
|
||||
| Timestamp | Mention | Channel Link | Message Link |
|
||||
|:-:|:-:|:-:|:-:|
|
||||
| <t:1663846662:f> | <@01EX2NCWQ0CHS3QJF0FEQS1GR4> | <#01H73F4RAHTPBHKJ1XBQDXK3NQ> | https://revolt.chat/server/01F7ZSBSFHQ8TA81725KQCSDDP/channel/01F92C5ZXBQWQ8KY7J8KY917NM/01J25XZM9JXVVJDDKFPB7Q48HZ |"""
|
||||
}) {
|
||||
Text("Submit test document")
|
||||
}
|
||||
}
|
||||
Subcategory(
|
||||
title = { Text("Output", maxLines = 1, overflow = TextOverflow.Ellipsis) },
|
||||
) {
|
||||
submitMdSource?.let { JBMRenderer(it, Modifier) }
|
||||
?: Text("Submit some Markdown and see the output.")
|
||||
CompositionLocalProvider(
|
||||
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
|
||||
embedded = isEmbedded
|
||||
)
|
||||
) {
|
||||
submitMdSource?.let { JBMRenderer(it, Modifier) }
|
||||
?: Text("Submit some Markdown and see the output.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -171,4 +171,15 @@ object ClearRippleTheme : RippleTheme {
|
|||
hoveredAlpha = 0.0f,
|
||||
pressedAlpha = 0.0f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun isThemeDark(theme: Theme, systemIsDark: Boolean): Boolean {
|
||||
return when (theme) {
|
||||
Theme.Revolt, Theme.Amoled -> true
|
||||
Theme.Light -> false
|
||||
Theme.M3Dynamic, Theme.None -> systemIsDark
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun isThemeDark(theme: Theme) = isThemeDark(theme, isSystemInDarkTheme())
|
||||
Loading…
Reference in New Issue