feat(jbm): include sequential parsers for emoji, channel, mention
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
dbdd5556bf
commit
f47b21aeec
|
|
@ -33,9 +33,9 @@ import chat.revolt.api.routes.channel.fetchSingleMessage
|
|||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.api.settings.Experiments
|
||||
import chat.revolt.components.generic.UserAvatar
|
||||
import chat.revolt.components.markdown.jbm.JBM
|
||||
import chat.revolt.components.markdown.jbm.JBMRenderer
|
||||
import chat.revolt.components.markdown.jbm.LocalJBMarkdownTreeState
|
||||
import chat.revolt.markdown.jbm.JBM
|
||||
import chat.revolt.markdown.jbm.JBMRenderer
|
||||
import chat.revolt.markdown.jbm.LocalJBMarkdownTreeState
|
||||
|
||||
@OptIn(JBM::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -78,9 +78,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.markdown.jbm.JBM
|
||||
import chat.revolt.markdown.jbm.JBMRenderer
|
||||
import chat.revolt.markdown.jbm.LocalJBMarkdownTreeState
|
||||
import chat.revolt.internals.text.Gigamoji
|
||||
import kotlinx.coroutines.launch
|
||||
import chat.revolt.api.schemas.Message as MessageSchema
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package chat.revolt.components.markdown.jbm
|
||||
package chat.revolt.markdown.jbm
|
||||
|
||||
import org.intellij.markdown.ast.ASTNode
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
|
||||
@RequiresOptIn(message = "This API is experimental and has many TODOs.")
|
||||
|
|
@ -12,6 +11,6 @@ annotation class JBM
|
|||
@JBM
|
||||
object JBMApi {
|
||||
fun parse(src: String): ASTNode {
|
||||
return MarkdownParser(GFMFlavourDescriptor()).buildMarkdownTreeFromString(src)
|
||||
return MarkdownParser(RSMFlavourDescriptor()).buildMarkdownTreeFromString(src)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package chat.revolt.components.markdown.jbm
|
||||
package chat.revolt.markdown.jbm
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
|
|
@ -67,6 +67,7 @@ import chat.revolt.R
|
|||
import chat.revolt.activities.InviteActivity
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.internals.isUlid
|
||||
import chat.revolt.api.routes.custom.fetchEmoji
|
||||
import chat.revolt.api.schemas.isInviteUri
|
||||
import chat.revolt.api.settings.LoadedSettings
|
||||
|
|
@ -177,6 +178,86 @@ private fun annotateText(
|
|||
append(source)
|
||||
}
|
||||
|
||||
RSMElementTypes.USER_MENTION -> {
|
||||
val contents = node.getTextInNode(sourceText).toString()
|
||||
val userId = contents.removeSurrounding("<@", ">")
|
||||
if (userId == contents || !userId.isUlid()) {
|
||||
// Invalid user mention. Append as if it were regular text.
|
||||
for (child in node.children) {
|
||||
append(annotateText(state, child))
|
||||
}
|
||||
} else {
|
||||
// Now we're getting somewhere
|
||||
pushStringAnnotation(
|
||||
tag = JBMAnnotations.UserMention.tag,
|
||||
annotation = userId
|
||||
)
|
||||
pushStyle(
|
||||
SpanStyle(
|
||||
color = state.colors.clickable,
|
||||
background = state.colors.clickableBackground
|
||||
)
|
||||
)
|
||||
val member = state.currentServer?.let { serverId ->
|
||||
RevoltAPI.members.getMember(serverId, userId)
|
||||
}
|
||||
val mentionDisplay = member?.nickname?.let { nick -> "@$nick" }
|
||||
?: RevoltAPI.userCache[userId]?.username?.let { username -> "@$username" }
|
||||
?: "<@$userId>"
|
||||
append(mentionDisplay)
|
||||
pop()
|
||||
pop()
|
||||
}
|
||||
}
|
||||
|
||||
RSMElementTypes.CHANNEL_MENTION -> {
|
||||
val contents = node.getTextInNode(sourceText).toString()
|
||||
val channelId = contents.removeSurrounding("<#", ">")
|
||||
if (channelId == contents || !channelId.isUlid()) {
|
||||
// Invalid channel mention. Append as if it were regular text.
|
||||
for (child in node.children) {
|
||||
append(annotateText(state, child))
|
||||
}
|
||||
} else {
|
||||
// Now we're getting somewhere
|
||||
pushStringAnnotation(
|
||||
tag = JBMAnnotations.ChannelMention.tag,
|
||||
annotation = channelId
|
||||
)
|
||||
pushStyle(
|
||||
SpanStyle(
|
||||
color = state.colors.clickable,
|
||||
background = state.colors.clickableBackground
|
||||
)
|
||||
)
|
||||
val channel = RevoltAPI.channelCache[channelId]
|
||||
val mentionDisplay = channel?.name?.let { name -> "#$name" }
|
||||
?: "<#$channelId>"
|
||||
append(mentionDisplay)
|
||||
pop()
|
||||
pop()
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
} else {
|
||||
// Now we're getting somewhere
|
||||
pushStringAnnotation(
|
||||
tag = JBMAnnotations.CustomEmote.tag,
|
||||
annotation = emoteId
|
||||
)
|
||||
appendInlineContent(JBMAnnotations.CustomEmote.tag, emoteId)
|
||||
pop()
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownTokenTypes.ATX_HEADER -> {
|
||||
// Do not need to do anything
|
||||
}
|
||||
|
|
@ -443,6 +524,36 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
|
|||
|
||||
return@handler true
|
||||
}
|
||||
|
||||
JBMAnnotations.UserMention.tag -> {
|
||||
scope.launch {
|
||||
ActionChannel.send(
|
||||
Action.OpenUserSheet(
|
||||
item,
|
||||
mdState.currentServer
|
||||
)
|
||||
)
|
||||
}
|
||||
return@handler true
|
||||
}
|
||||
|
||||
JBMAnnotations.ChannelMention.tag -> {
|
||||
scope.launch {
|
||||
ActionChannel.send(
|
||||
Action.SwitchChannel(item)
|
||||
)
|
||||
}
|
||||
return@handler true
|
||||
}
|
||||
|
||||
JBMAnnotations.CustomEmote.tag -> {
|
||||
scope.launch {
|
||||
ActionChannel.send(
|
||||
Action.EmoteInfo(item)
|
||||
)
|
||||
}
|
||||
return@handler true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -464,47 +575,6 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
|
|||
|
||||
return@handler true
|
||||
}
|
||||
|
||||
annotatedText.getStringAnnotations(
|
||||
tag = Annotations.UserMention.tag,
|
||||
start = offset,
|
||||
end = offset
|
||||
).firstOrNull()?.let { annotation ->
|
||||
scope.launch {
|
||||
ActionChannel.send(
|
||||
Action.OpenUserSheet(
|
||||
annotation.item,
|
||||
mdState.currentServer
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return@handler true
|
||||
}
|
||||
|
||||
annotatedText.getStringAnnotations(
|
||||
tag = Annotations.ChannelMention.tag,
|
||||
start = offset,
|
||||
end = offset
|
||||
).firstOrNull()?.let { annotation ->
|
||||
scope.launch {
|
||||
ActionChannel.send(Action.SwitchChannel(annotation.item))
|
||||
}
|
||||
|
||||
return@handler true
|
||||
}
|
||||
|
||||
annotatedText.getStringAnnotations(
|
||||
tag = Annotations.CustomEmote.tag,
|
||||
start = offset,
|
||||
end = offset
|
||||
).firstOrNull()?.let { annotation ->
|
||||
scope.launch {
|
||||
ActionChannel.send(Action.EmoteInfo(annotation.item))
|
||||
}
|
||||
|
||||
return@handler true
|
||||
}
|
||||
}
|
||||
|
||||
return@handler false
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package chat.revolt.markdown.jbm
|
||||
|
||||
import org.intellij.markdown.IElementType
|
||||
import org.intellij.markdown.MarkdownElementType
|
||||
|
||||
object RSMElementTypes {
|
||||
@JvmField
|
||||
val USER_MENTION: IElementType = MarkdownElementType("USER_MENTION")
|
||||
|
||||
@JvmField
|
||||
val CHANNEL_MENTION: IElementType = MarkdownElementType("CHANNEL_MENTION")
|
||||
|
||||
@JvmField
|
||||
val CUSTOM_EMOTE: IElementType = MarkdownElementType("EMOJI")
|
||||
|
||||
@JvmField
|
||||
val TIMESTAMP: IElementType = MarkdownElementType("TIMESTAMP")
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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.UserMentionParser
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import org.intellij.markdown.flavours.gfm.GFMTokenTypes
|
||||
import org.intellij.markdown.flavours.gfm.StrikeThroughDelimiterParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.EmphasisLikeParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParserManager
|
||||
import org.intellij.markdown.parser.sequentialparsers.impl.AutolinkParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.impl.BacktickParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.impl.EmphStrongDelimiterParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.impl.ImageParser
|
||||
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() {
|
||||
override val sequentialParserManager = object : SequentialParserManager() {
|
||||
override fun getParserSequence(): List<SequentialParser> {
|
||||
return listOf(
|
||||
UserMentionParser(),
|
||||
ChannelMentionParser(),
|
||||
CustomEmoteParser(),
|
||||
AutolinkParser(listOf(MarkdownTokenTypes.AUTOLINK, GFMTokenTypes.GFM_AUTOLINK)),
|
||||
BacktickParser(),
|
||||
MathParser(),
|
||||
ImageParser(),
|
||||
InlineLinkParser(),
|
||||
ReferenceLinkParser(),
|
||||
EmphasisLikeParser(EmphStrongDelimiterParser(), StrikeThroughDelimiterParser())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package chat.revolt.markdown.jbm.sequentialparsers
|
||||
|
||||
import chat.revolt.markdown.jbm.RSMElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.parser.sequentialparsers.RangesListBuilder
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.TokensCache
|
||||
|
||||
class ChannelMentionParser : SequentialParser {
|
||||
override fun parse(
|
||||
tokens: TokensCache,
|
||||
rangesToGlue: List<IntRange>
|
||||
): SequentialParser.ParsingResult {
|
||||
val result = SequentialParser.ParsingResultBuilder()
|
||||
val delegateIndices = RangesListBuilder()
|
||||
var iterator: TokensCache.Iterator = tokens.RangesListIterator(rangesToGlue)
|
||||
|
||||
while (iterator.type != null) {
|
||||
if (iterator.type == MarkdownTokenTypes.LT && iterator.charLookup(1) == '#') {
|
||||
val start = iterator.index
|
||||
while (iterator.type != MarkdownTokenTypes.GT && iterator.type != null) {
|
||||
iterator = iterator.advance()
|
||||
}
|
||||
if (iterator.type == MarkdownTokenTypes.GT) {
|
||||
result.withNode(
|
||||
SequentialParser.Node(
|
||||
start..iterator.index + 1,
|
||||
RSMElementTypes.USER_MENTION
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
delegateIndices.put(iterator.index)
|
||||
}
|
||||
iterator = iterator.advance()
|
||||
}
|
||||
|
||||
return result.withFurtherProcessing(delegateIndices.get())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package chat.revolt.markdown.jbm.sequentialparsers
|
||||
|
||||
import chat.revolt.markdown.jbm.RSMElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.parser.sequentialparsers.RangesListBuilder
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.TokensCache
|
||||
|
||||
class CustomEmoteParser : SequentialParser {
|
||||
override fun parse(
|
||||
tokens: TokensCache,
|
||||
rangesToGlue: List<IntRange>
|
||||
): SequentialParser.ParsingResult {
|
||||
val result = SequentialParser.ParsingResultBuilder()
|
||||
val delegateIndices = RangesListBuilder()
|
||||
var iterator: TokensCache.Iterator = tokens.RangesListIterator(rangesToGlue)
|
||||
|
||||
while (iterator.type != null) {
|
||||
if (iterator.type == MarkdownTokenTypes.COLON) {
|
||||
|
||||
val endIterator = findNextColon(iterator.advance())
|
||||
|
||||
if (endIterator != null) {
|
||||
result.withNode(
|
||||
SequentialParser.Node(
|
||||
iterator.index..endIterator.index + 1,
|
||||
RSMElementTypes.CUSTOM_EMOTE
|
||||
)
|
||||
)
|
||||
iterator = endIterator.advance()
|
||||
continue
|
||||
}
|
||||
}
|
||||
delegateIndices.put(iterator.index)
|
||||
iterator = iterator.advance()
|
||||
}
|
||||
|
||||
return result.withFurtherProcessing(delegateIndices.get())
|
||||
}
|
||||
|
||||
private fun findNextColon(it: TokensCache.Iterator): TokensCache.Iterator? {
|
||||
var iterator = it
|
||||
while (iterator.type != null) {
|
||||
if (iterator.type == MarkdownTokenTypes.COLON) {
|
||||
return iterator
|
||||
}
|
||||
|
||||
iterator = iterator.advance()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package chat.revolt.markdown.jbm.sequentialparsers
|
||||
|
||||
import chat.revolt.markdown.jbm.RSMElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.parser.sequentialparsers.RangesListBuilder
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.TokensCache
|
||||
|
||||
class UserMentionParser : SequentialParser {
|
||||
override fun parse(
|
||||
tokens: TokensCache,
|
||||
rangesToGlue: List<IntRange>
|
||||
): SequentialParser.ParsingResult {
|
||||
val result = SequentialParser.ParsingResultBuilder()
|
||||
val delegateIndices = RangesListBuilder()
|
||||
var iterator: TokensCache.Iterator = tokens.RangesListIterator(rangesToGlue)
|
||||
|
||||
while (iterator.type != null) {
|
||||
if (iterator.type == MarkdownTokenTypes.LT && iterator.charLookup(1) == '@') {
|
||||
val start = iterator.index
|
||||
while (iterator.type != MarkdownTokenTypes.GT && iterator.type != null) {
|
||||
iterator = iterator.advance()
|
||||
}
|
||||
if (iterator.type == MarkdownTokenTypes.GT) {
|
||||
result.withNode(
|
||||
SequentialParser.Node(
|
||||
start..iterator.index + 1,
|
||||
RSMElementTypes.USER_MENTION
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
delegateIndices.put(iterator.index)
|
||||
}
|
||||
iterator = iterator.advance()
|
||||
}
|
||||
|
||||
return result.withFurtherProcessing(delegateIndices.get())
|
||||
}
|
||||
}
|
||||
|
|
@ -19,9 +19,9 @@ 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.markdown.jbm.JBM
|
||||
import chat.revolt.markdown.jbm.JBMRenderer
|
||||
import chat.revolt.markdown.jbm.LocalJBMarkdownTreeState
|
||||
import chat.revolt.settings.dsl.SettingsPage
|
||||
|
||||
@OptIn(JBM::class)
|
||||
|
|
|
|||
Loading…
Reference in New Issue