Fixing custom emoji and markdown issues (#5)
This commit is contained in:
parent
dc2699e089
commit
406c017696
13
README.md
13
README.md
|
|
@ -28,13 +28,13 @@ You can download the latest APK [here](https://github.com/alexjyong/android/rele
|
|||
## Features Added
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<td width="50%" valign="top" align="center">
|
||||
<strong>Emojis next to channel names</strong><br>
|
||||
<img src="https://github.com/user-attachments/assets/6c37f059-47ef-4028-aac9-ef233f7a85e4" width="300" alt="Channel Emojis">
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<td width="50%" valign="top" align="center">
|
||||
<strong>Jump to replied message</strong><br>
|
||||
<img src="https://github.com/user-attachments/assets/1dc0fb6b-521d-40e2-a833-1ada19bd207b" width="220" alt="Jump to Reply">
|
||||
<img src="https://github.com/user-attachments/assets/1dc0fb6b-521d-40e2-a833-1ada19bd207b" width="300" alt="Jump to Reply">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -43,10 +43,17 @@ You can download the latest APK [here](https://github.com/alexjyong/android/rele
|
|||
<img src="https://github.com/user-attachments/assets/1450ea4c-a283-4ac3-8a0a-8e9f1adb436b" width="300" alt="Server Context Menu">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center" valign="top">
|
||||
<strong>Recently used emojis</strong><br>
|
||||
<img src="https://github.com/user-attachments/assets/6f1e126a-8e73-49ee-a5d1-c4841907e14a" width="600" alt="Recently Used Emojis">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
The codebase includes the app itself, as well as an internal library for interacting with the Revolt
|
||||
API. The app is written in Kotlin, and wholly
|
||||
uses [Jetpack Compose](https://developer.android.com/jetpack/compose).
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ class ExperimentInstance(default: Boolean) {
|
|||
* - All experiments can be disabled at once with a single toggle.
|
||||
*/
|
||||
object Experiments {
|
||||
val useKotlinBasedMarkdownRenderer = ExperimentInstance(true)
|
||||
val useKotlinBasedMarkdownRenderer = ExperimentInstance(false)
|
||||
val useEnhancedMarkdownRenderer = ExperimentInstance(true)
|
||||
val usePolar = ExperimentInstance(false)
|
||||
val enableServerIdentityOptions = ExperimentInstance(false)
|
||||
val useFinalMarkdownRenderer = ExperimentInstance(false)
|
||||
|
|
@ -43,6 +44,9 @@ object Experiments {
|
|||
useKotlinBasedMarkdownRenderer.setEnabled(
|
||||
kvStorage.getBoolean("exp/useKotlinBasedMarkdownRenderer") == true
|
||||
)
|
||||
useEnhancedMarkdownRenderer.setEnabled(
|
||||
kvStorage.getBoolean("exp/useEnhancedMarkdownRenderer") == true
|
||||
)
|
||||
usePolar.setEnabled(
|
||||
kvStorage.getBoolean("exp/usePolar") == true
|
||||
)
|
||||
|
|
@ -53,8 +57,16 @@ object Experiments {
|
|||
kvStorage.getBoolean("exp/useFinalMarkdownRenderer") == true
|
||||
)
|
||||
|
||||
if (useFinalMarkdownRenderer.isEnabled && useKotlinBasedMarkdownRenderer.isEnabled) {
|
||||
// if jbm and fm are enabled, fm takes precedence. this should not be possible in practice
|
||||
if (useFinalMarkdownRenderer.isEnabled) {
|
||||
if (useEnhancedMarkdownRenderer.isEnabled) {
|
||||
useEnhancedMarkdownRenderer.setEnabled(false)
|
||||
kvStorage.set("exp/useEnhancedMarkdownRenderer", false)
|
||||
}
|
||||
if (useKotlinBasedMarkdownRenderer.isEnabled) {
|
||||
useKotlinBasedMarkdownRenderer.setEnabled(false)
|
||||
kvStorage.set("exp/useKotlinBasedMarkdownRenderer", false)
|
||||
}
|
||||
} else if (useEnhancedMarkdownRenderer.isEnabled && useKotlinBasedMarkdownRenderer.isEnabled) {
|
||||
useKotlinBasedMarkdownRenderer.setEnabled(false)
|
||||
kvStorage.set("exp/useKotlinBasedMarkdownRenderer", false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
package chat.revolt.composables.generic
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.unit.isUnspecified
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
|
||||
/**
|
||||
* Text composable that automatically detects and renders custom emojis in the format :ULID:
|
||||
* This bypasses all parser-level emoji handling and works at the Compose rendering level.
|
||||
*/
|
||||
@Composable
|
||||
fun EmojiAwareText(
|
||||
text: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
inlineContent: Map<String, InlineTextContent> = emptyMap(),
|
||||
onTextLayout: ((androidx.compose.ui.text.TextLayoutResult) -> Unit)? = null
|
||||
) {
|
||||
val fontSize = LocalTextStyle.current.fontSize
|
||||
val emojiSize = if (fontSize.isUnspecified) 16.sp else fontSize
|
||||
|
||||
val customEmojiRegex = Regex(":([0-9A-HJKMNP-TV-Z]{26}):")
|
||||
val matches = customEmojiRegex.findAll(text.text).toList()
|
||||
|
||||
if (matches.isEmpty()) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
maxLines = maxLines,
|
||||
inlineContent = inlineContent,
|
||||
onTextLayout = onTextLayout ?: {}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val processedText = buildAnnotatedString {
|
||||
val sourceText = text.text
|
||||
var lastIndex = 0
|
||||
|
||||
matches.forEach { match ->
|
||||
val emojiId = match.groupValues[1]
|
||||
val placeholderKey = "emoji_$emojiId"
|
||||
|
||||
if (match.range.first > lastIndex) {
|
||||
val beforeEmoji = sourceText.substring(lastIndex, match.range.first)
|
||||
append(beforeEmoji)
|
||||
|
||||
text.spanStyles.forEach { spanStyle ->
|
||||
if (spanStyle.start < match.range.first && spanStyle.end > lastIndex) {
|
||||
val spanStart = maxOf(spanStyle.start - lastIndex, 0)
|
||||
val spanEnd = minOf(spanStyle.end - lastIndex, beforeEmoji.length)
|
||||
if (spanStart < spanEnd) {
|
||||
addStyle(spanStyle.item, length - beforeEmoji.length + spanStart, length - beforeEmoji.length + spanEnd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appendInlineContent(placeholderKey, "[emoji]")
|
||||
|
||||
lastIndex = match.range.last + 1
|
||||
}
|
||||
|
||||
if (lastIndex < sourceText.length) {
|
||||
val remainingText = sourceText.substring(lastIndex)
|
||||
append(remainingText)
|
||||
|
||||
text.spanStyles.forEach { spanStyle ->
|
||||
if (spanStyle.start < sourceText.length && spanStyle.end > lastIndex) {
|
||||
val spanStart = maxOf(spanStyle.start - lastIndex, 0)
|
||||
val spanEnd = minOf(spanStyle.end - lastIndex, remainingText.length)
|
||||
if (spanStart < spanEnd) {
|
||||
addStyle(spanStyle.item, length - remainingText.length + spanStart, length - remainingText.length + spanEnd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val emojiInlineContent = matches.associate { match ->
|
||||
val emojiId = match.groupValues[1]
|
||||
val placeholderKey = "emoji_$emojiId"
|
||||
|
||||
placeholderKey to InlineTextContent(
|
||||
placeholder = Placeholder(
|
||||
width = emojiSize,
|
||||
height = emojiSize,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
|
||||
)
|
||||
) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/emojis/$emojiId",
|
||||
description = ":$emojiId:",
|
||||
modifier = Modifier
|
||||
.size(emojiSize.value.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val mergedInlineContent = inlineContent + emojiInlineContent
|
||||
|
||||
Text(
|
||||
text = processedText,
|
||||
inlineContent = mergedInlineContent,
|
||||
modifier = modifier,
|
||||
maxLines = maxLines,
|
||||
onTextLayout = onTextLayout ?: {}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload for plain string input
|
||||
*/
|
||||
@Composable
|
||||
fun EmojiAwareText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
inlineContent: Map<String, InlineTextContent> = emptyMap(),
|
||||
onTextLayout: ((androidx.compose.ui.text.TextLayoutResult) -> Unit)? = null
|
||||
) {
|
||||
EmojiAwareText(
|
||||
text = AnnotatedString(text),
|
||||
modifier = modifier,
|
||||
maxLines = maxLines,
|
||||
inlineContent = inlineContent,
|
||||
onTextLayout = onTextLayout
|
||||
)
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ import chat.revolt.api.routes.custom.fetchEmoji
|
|||
import chat.revolt.api.schemas.isInviteUri
|
||||
import chat.revolt.callbacks.Action
|
||||
import chat.revolt.callbacks.ActionChannel
|
||||
import chat.revolt.composables.generic.EmojiAwareText
|
||||
import chat.revolt.composables.generic.RemoteImage
|
||||
import chat.revolt.composables.utils.detectTapGesturesConditionalConsume
|
||||
import chat.revolt.internals.EmojiRepository
|
||||
|
|
@ -484,43 +485,9 @@ fun MarkdownText(textNode: AstNode, modifier: Modifier = Modifier) {
|
|||
false
|
||||
}
|
||||
|
||||
Text(
|
||||
EmojiAwareText(
|
||||
text = annotatedText,
|
||||
style = LocalTextStyle.current,
|
||||
color = LocalContentColor.current,
|
||||
onTextLayout = { layoutResult = it },
|
||||
inlineContent = mapOf(
|
||||
"CustomEmote" to InlineTextContent(
|
||||
placeholder = Placeholder(
|
||||
width = LocalTextStyle.current.fontSize * 1.5,
|
||||
height = LocalTextStyle.current.fontSize * 1.5,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
|
||||
),
|
||||
) { id ->
|
||||
val emote = RevoltAPI.emojiCache[id]
|
||||
if (emote == null) {
|
||||
scope.launch {
|
||||
try {
|
||||
RevoltAPI.emojiCache[id] = fetchEmoji(id)
|
||||
} catch (e: Exception) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
return@InlineTextContent
|
||||
} else {
|
||||
with(LocalDensity.current) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/emojis/${id}",
|
||||
description = emote.name,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.width((LocalTextStyle.current.fontSize * 1.5).toDp())
|
||||
.height((LocalTextStyle.current.fontSize * 1.5).toDp())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
modifier = modifier.pointerInput(onClick, onLongClick) {
|
||||
detectTapGesturesConditionalConsume(
|
||||
onTap = { pos ->
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
|
|||
import chat.revolt.api.settings.Experiments
|
||||
import chat.revolt.markdown.jbm.JBM
|
||||
import chat.revolt.markdown.jbm.JBMRenderer
|
||||
import chat.revolt.markdown.jbm.JBMEnhancedRenderer
|
||||
import chat.revolt.ndk.Stendal
|
||||
|
||||
@OptIn(JBM::class)
|
||||
|
|
@ -16,10 +17,15 @@ fun RichMarkdown(input: String, modifier: Modifier = Modifier) {
|
|||
Experiments.useFinalMarkdownRenderer.isEnabled -> {
|
||||
JBMRenderer(input)
|
||||
}
|
||||
else -> {
|
||||
Experiments.useEnhancedMarkdownRenderer.isEnabled -> {
|
||||
JBMEnhancedRenderer(input)
|
||||
}
|
||||
Experiments.useKotlinBasedMarkdownRenderer.isEnabled -> {
|
||||
JBMRenderer(input)
|
||||
}
|
||||
else -> {
|
||||
JBMEnhancedRenderer(input)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ annotation class JBM
|
|||
|
||||
@JBM
|
||||
object JBMApi {
|
||||
fun parse(src: String): ASTNode {
|
||||
return MarkdownParser(RSMFlavourDescriptor()).buildMarkdownTreeFromString(src)
|
||||
fun parse(src: String, flavor: RSMFlavourDescriptor = RSMFlavourDescriptor()): ASTNode {
|
||||
return MarkdownParser(flavor).buildMarkdownTreeFromString(src)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package chat.revolt.markdown.jbm
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
/**
|
||||
* Enhanced JBM Renderer that provides additional markdown features.
|
||||
* This renderer inherits all functionality from the original JBMRenderer while adding support
|
||||
* for spoilers and other enhancements through a simple preprocessing approach.
|
||||
*/
|
||||
|
||||
@JBM
|
||||
@Composable
|
||||
fun JBMEnhancedRenderer(content: String, modifier: Modifier = Modifier) {
|
||||
CompositionLocalProvider(
|
||||
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
|
||||
enhanced = true
|
||||
)
|
||||
) {
|
||||
JBMRenderer(content, modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,6 +83,7 @@ import chat.revolt.api.schemas.isInviteUri
|
|||
import chat.revolt.api.settings.LoadedSettings
|
||||
import chat.revolt.callbacks.Action
|
||||
import chat.revolt.callbacks.ActionChannel
|
||||
import chat.revolt.composables.generic.EmojiAwareText
|
||||
import chat.revolt.composables.generic.RemoteImage
|
||||
import chat.revolt.composables.generic.UserAvatar
|
||||
import chat.revolt.composables.markdown.Annotations
|
||||
|
|
@ -138,6 +139,7 @@ data class JBMarkdownTreeState(
|
|||
val currentServer: String? = null,
|
||||
val embedded: Boolean = false,
|
||||
val singleLine: Boolean = false,
|
||||
val enhanced: Boolean = false,
|
||||
val colors: JBMColors = JBMColors(
|
||||
clickable = Color(0xFFFF00FF),
|
||||
clickableBackground = Color(0x2000FF00)
|
||||
|
|
@ -153,11 +155,14 @@ val avatarPadding = 2.dp
|
|||
@Composable
|
||||
@JBM
|
||||
fun JBMRenderer(content: String, modifier: Modifier = Modifier) {
|
||||
var tree by remember { mutableStateOf(JBMApi.parse(content)) }
|
||||
val state = LocalJBMarkdownTreeState.current
|
||||
val flavor = if (state.enhanced) RSMEnhancedFlavourDescriptor() else RSMFlavourDescriptor()
|
||||
|
||||
var tree by remember { mutableStateOf(JBMApi.parse(content, flavor)) }
|
||||
var revealedSpoilers by remember { mutableStateOf(setOf<String>()) }
|
||||
|
||||
LaunchedEffect(content) {
|
||||
tree = JBMApi.parse(content)
|
||||
LaunchedEffect(content, state.enhanced) {
|
||||
tree = JBMApi.parse(content, flavor)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
|
|
@ -324,23 +329,6 @@ private fun annotateText(
|
|||
}
|
||||
}
|
||||
|
||||
RSMElementTypes.CUSTOM_EMOTE -> {
|
||||
val contents = node.getTextInNode(sourceText).toString()
|
||||
val emoteId = contents.removeSurrounding(":", ":")
|
||||
if (emoteId == contents || !emoteId.isUlid()) {
|
||||
// Invalid custom emote. Append as if it were regular text.
|
||||
for (child in node.children) {
|
||||
append(annotateText(state, child, revealedSpoilers))
|
||||
}
|
||||
} else {
|
||||
pushStringAnnotation(
|
||||
tag = JBMAnnotations.CustomEmote.tag,
|
||||
annotation = emoteId
|
||||
)
|
||||
appendInlineContent(JBMAnnotations.CustomEmote.tag, emoteId)
|
||||
pop()
|
||||
}
|
||||
}
|
||||
|
||||
RSMElementTypes.SPOILER -> {
|
||||
android.util.Log.d("JBMRenderer", "Found SPOILER node with ${node.children.size} children")
|
||||
|
|
@ -714,14 +702,6 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
|
|||
return@handler true
|
||||
}
|
||||
|
||||
JBMAnnotations.CustomEmote.tag -> {
|
||||
scope.launch {
|
||||
ActionChannel.send(
|
||||
Action.EmoteInfo(item)
|
||||
)
|
||||
}
|
||||
return@handler true
|
||||
}
|
||||
|
||||
JBMAnnotations.Spoiler.tag -> {
|
||||
mdState.onSpoilerToggle?.invoke(item)
|
||||
|
|
@ -753,11 +733,10 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
|
|||
return@handler false
|
||||
}
|
||||
|
||||
Text(
|
||||
EmojiAwareText(
|
||||
text = annotatedText,
|
||||
onTextLayout = { layoutResult = it },
|
||||
maxLines = if (mdState.singleLine) 1 else Int.MAX_VALUE,
|
||||
overflow = if (mdState.singleLine) TextOverflow.Ellipsis else TextOverflow.Clip,
|
||||
modifier = modifier.pointerInput(onClick, onLongClick) {
|
||||
detectTapGesturesConditionalConsume(
|
||||
onTap = { pos ->
|
||||
|
|
@ -851,36 +830,6 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
|
|||
}
|
||||
}
|
||||
},
|
||||
JBMAnnotations.CustomEmote.tag to InlineTextContent(
|
||||
placeholder = Placeholder(
|
||||
width = LocalTextStyle.current.fontSize * 1.5,
|
||||
height = LocalTextStyle.current.fontSize * 1.5,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
|
||||
),
|
||||
) { id ->
|
||||
val emote = RevoltAPI.emojiCache[id]
|
||||
if (emote == null) {
|
||||
scope.launch {
|
||||
try {
|
||||
RevoltAPI.emojiCache[id] = fetchEmoji(id)
|
||||
} catch (e: Exception) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
return@InlineTextContent
|
||||
} else {
|
||||
with(LocalDensity.current) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/emojis/${id}",
|
||||
description = emote.name,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.width((LocalTextStyle.current.fontSize * 1.5).toDp())
|
||||
.height((LocalTextStyle.current.fontSize * 1.5).toDp())
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
JBMAnnotations.UserAvatar.tag to with(LocalDensity.current) {
|
||||
val placeholderBaseWidth =
|
||||
(LocalTextStyle.current.fontSize * 1.5).toPx() - (avatarPadding * 2).toPx()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package chat.revolt.markdown.jbm
|
||||
|
||||
import chat.revolt.markdown.jbm.sequentialparsers.SpoilerParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParserManager
|
||||
|
||||
/**
|
||||
* Enhanced RSM Flavour Descriptor that inherits from the original working RSMFlavourDescriptor.
|
||||
* This enhanced version adds support for spoilers by carefully adding SpoilerParser
|
||||
* at the end of the parsing sequence to avoid conflicts with existing parsers.
|
||||
*/
|
||||
class RSMEnhancedFlavourDescriptor : RSMFlavourDescriptor() {
|
||||
override val sequentialParserManager = object : SequentialParserManager() {
|
||||
override fun getParserSequence(): List<SequentialParser> {
|
||||
val originalParsers = super@RSMEnhancedFlavourDescriptor.sequentialParserManager.getParserSequence()
|
||||
|
||||
return originalParsers + SpoilerParser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
package chat.revolt.markdown.jbm
|
||||
|
||||
import chat.revolt.markdown.jbm.sequentialparsers.ChannelMentionParser
|
||||
import chat.revolt.markdown.jbm.sequentialparsers.CustomEmoteParser
|
||||
import chat.revolt.markdown.jbm.sequentialparsers.MassMentionParser
|
||||
import chat.revolt.markdown.jbm.sequentialparsers.RoleMentionParser
|
||||
import chat.revolt.markdown.jbm.sequentialparsers.SpoilerParser
|
||||
import chat.revolt.markdown.jbm.sequentialparsers.UserMentionParser
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
|
|
@ -21,7 +19,7 @@ import org.intellij.markdown.parser.sequentialparsers.impl.InlineLinkParser
|
|||
import org.intellij.markdown.parser.sequentialparsers.impl.MathParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.impl.ReferenceLinkParser
|
||||
|
||||
class RSMFlavourDescriptor : GFMFlavourDescriptor() {
|
||||
open class RSMFlavourDescriptor : GFMFlavourDescriptor() {
|
||||
override val sequentialParserManager = object : SequentialParserManager() {
|
||||
override fun getParserSequence(): List<SequentialParser> {
|
||||
return listOf(
|
||||
|
|
@ -35,9 +33,7 @@ class RSMFlavourDescriptor : GFMFlavourDescriptor() {
|
|||
UserMentionParser(),
|
||||
ChannelMentionParser(),
|
||||
RoleMentionParser(),
|
||||
MassMentionParser(),
|
||||
CustomEmoteParser(),
|
||||
SpoilerParser()
|
||||
MassMentionParser()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@ class CustomEmoteParser : SequentialParser {
|
|||
var iterator: TokensCache.Iterator = tokens.RangesListIterator(rangesToGlue)
|
||||
|
||||
while (iterator.type != null) {
|
||||
android.util.Log.d("CustomEmoteParser", "Processing token: type=${iterator.type}, text='${iterator.toString()}'")
|
||||
|
||||
if (iterator.type == MarkdownTokenTypes.COLON) {
|
||||
|
||||
android.util.Log.d("CustomEmoteParser", "Found COLON token")
|
||||
val endIterator = findNextColon(iterator.advance())
|
||||
|
||||
if (endIterator != null) {
|
||||
android.util.Log.d("CustomEmoteParser", "Found matching closing colon, creating CUSTOM_EMOTE node")
|
||||
result.withNode(
|
||||
SequentialParser.Node(
|
||||
iterator.index..endIterator.index + 1,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import chat.revolt.settings.dsl.SubcategoryContentInsets
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class MarkdownRenderer {
|
||||
JetBrains, FinalMarkdown
|
||||
JetBrains, JetBrainsEnhanced, FinalMarkdown
|
||||
}
|
||||
|
||||
class ExperimentsSettingsScreenViewModel : ViewModel() {
|
||||
|
|
@ -56,17 +56,21 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
|
|||
fun init() {
|
||||
viewModelScope.launch {
|
||||
when {
|
||||
Experiments.useKotlinBasedMarkdownRenderer.isEnabled -> {
|
||||
mdRenderer.value = MarkdownRenderer.JetBrains
|
||||
}
|
||||
|
||||
Experiments.useFinalMarkdownRenderer.isEnabled -> {
|
||||
mdRenderer.value = MarkdownRenderer.FinalMarkdown
|
||||
}
|
||||
|
||||
else -> {
|
||||
Experiments.useEnhancedMarkdownRenderer.isEnabled -> {
|
||||
mdRenderer.value = MarkdownRenderer.JetBrainsEnhanced
|
||||
}
|
||||
|
||||
Experiments.useKotlinBasedMarkdownRenderer.isEnabled -> {
|
||||
mdRenderer.value = MarkdownRenderer.JetBrains
|
||||
}
|
||||
|
||||
else -> {
|
||||
mdRenderer.value = MarkdownRenderer.JetBrainsEnhanced
|
||||
}
|
||||
}
|
||||
usePolarChecked.value = Experiments.usePolar.isEnabled
|
||||
enableServerIdentityOptionsChecked.value =
|
||||
|
|
@ -98,7 +102,7 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
val mdRenderer = mutableStateOf(MarkdownRenderer.JetBrains)
|
||||
val mdRenderer = mutableStateOf(MarkdownRenderer.JetBrainsEnhanced)
|
||||
|
||||
fun setMdRenderer(value: MarkdownRenderer) {
|
||||
viewModelScope.launch {
|
||||
|
|
@ -106,6 +110,17 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
|
|||
MarkdownRenderer.JetBrains -> {
|
||||
kv.set("exp/useKotlinBasedMarkdownRenderer", true)
|
||||
Experiments.useKotlinBasedMarkdownRenderer.setEnabled(true)
|
||||
kv.set("exp/useEnhancedMarkdownRenderer", false)
|
||||
Experiments.useEnhancedMarkdownRenderer.setEnabled(false)
|
||||
kv.set("exp/useFinalMarkdownRenderer", false)
|
||||
Experiments.useFinalMarkdownRenderer.setEnabled(false)
|
||||
}
|
||||
|
||||
MarkdownRenderer.JetBrainsEnhanced -> {
|
||||
kv.set("exp/useKotlinBasedMarkdownRenderer", false)
|
||||
Experiments.useKotlinBasedMarkdownRenderer.setEnabled(false)
|
||||
kv.set("exp/useEnhancedMarkdownRenderer", true)
|
||||
Experiments.useEnhancedMarkdownRenderer.setEnabled(true)
|
||||
kv.set("exp/useFinalMarkdownRenderer", false)
|
||||
Experiments.useFinalMarkdownRenderer.setEnabled(false)
|
||||
}
|
||||
|
|
@ -113,6 +128,8 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
|
|||
MarkdownRenderer.FinalMarkdown -> {
|
||||
kv.set("exp/useKotlinBasedMarkdownRenderer", false)
|
||||
Experiments.useKotlinBasedMarkdownRenderer.setEnabled(false)
|
||||
kv.set("exp/useEnhancedMarkdownRenderer", false)
|
||||
Experiments.useEnhancedMarkdownRenderer.setEnabled(false)
|
||||
kv.set("exp/useFinalMarkdownRenderer", true)
|
||||
Experiments.useFinalMarkdownRenderer.setEnabled(true)
|
||||
}
|
||||
|
|
@ -203,7 +220,8 @@ fun ExperimentsSettingsScreen(
|
|||
},
|
||||
supportingContent = {
|
||||
when (viewModel.mdRenderer.value) {
|
||||
MarkdownRenderer.JetBrains -> Text("Use the Kotlin-based JetBrains Markdown renderer for messages. This renderer is feature-complete with support for strikethrough, spoilers, and more. (Default)")
|
||||
MarkdownRenderer.JetBrains -> Text("Use the original Kotlin-based JetBrains Markdown renderer for messages. Stable but without spoiler support.")
|
||||
MarkdownRenderer.JetBrainsEnhanced -> Text("Use the enhanced JetBrains Markdown renderer with spoiler support and other improvements. Recommended for best experience. (Default)")
|
||||
MarkdownRenderer.FinalMarkdown -> Text("Use a new blazingly fast markdown renderer for messages. This renderer is experimental and may have missing features.")
|
||||
}
|
||||
},
|
||||
|
|
@ -226,6 +244,15 @@ fun ExperimentsSettingsScreen(
|
|||
) {
|
||||
Text("JetBrains")
|
||||
}
|
||||
ToggleButton(
|
||||
checked = viewModel.mdRenderer.value == MarkdownRenderer.JetBrainsEnhanced,
|
||||
onCheckedChange = { viewModel.setMdRenderer(MarkdownRenderer.JetBrainsEnhanced) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.semantics { role = Role.RadioButton }
|
||||
) {
|
||||
Text("JetBrains Enhanced")
|
||||
}
|
||||
if (FeatureFlags.finalMarkdownGranted || viewModel.mdRenderer.value == MarkdownRenderer.FinalMarkdown) {
|
||||
ToggleButton(
|
||||
checked = viewModel.mdRenderer.value == MarkdownRenderer.FinalMarkdown,
|
||||
|
|
|
|||
Loading…
Reference in New Issue