Fixing the markdown for user mentions (#6)

This commit is contained in:
Alex Yong 2025-09-05 02:34:23 -04:00 committed by GitHub
parent cf575ab598
commit 8208981c66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 136 additions and 13 deletions

View File

@ -1,10 +1,10 @@
name: Android CI
name: Build Android APKs
on:
workflow_dispatch:
jobs:
build:
buildReleaseAPK:
runs-on: ubuntu-latest
steps:
@ -18,7 +18,6 @@ jobs:
distribution: 'oracle'
cache: gradle
- name: Setup Android SDK
uses: android-actions/setup-android@v2
@ -37,7 +36,7 @@ jobs:
- name: Add revoltbuild.properties from the revoltbuildPropertiesbase64 secret
run: echo "${{ secrets.REVOLTBUILD_PROPERTIES_BASE64 }}" | base64 --decode > revoltbuild.properties
- name: Build with Gradle
- name: Build Release APK
run: ./gradlew --no-daemon assembleRelease -x app:uploadSentryProguardMappingsRelease
- name: Archive release APK
@ -46,4 +45,41 @@ jobs:
name: release-apk
retention-days: 5
path: |
app/build/outputs/apk/release/app-release.apk
app/build/outputs/apk/release/app-release.apk
buildDebugAPK:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'oracle'
cache: gradle
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- uses: denoland/setup-deno@v2
with:
# setting `cache-hash` implies `cache: true` and will replace
# the default cache-hash of `${{ hashFiles('**/deno.lock') }}`
cache-hash: ${{ hashFiles('**/deno.json') }}
- name: Set up repo
run: chmod +x gradlew && deno run -A scripts/download_deps.ts --yes && cp revoltbuild.properties.example revoltbuild.properties && cp sentry.properties.example sentry.properties && cp app/google-services.json.example app/google-services.json && git submodule update --init --recursive
- name: Build Debug APK
run: ./gradlew --no-daemon assembleDebug
- name: Archive debug APK
uses: actions/upload-artifact@v4
with:
name: debug-apk
retention-days: 5
path: |
app/build/outputs/apk/debug/app-debug.apk

View File

@ -80,7 +80,7 @@ android {
minSdk = 24
targetSdk = 36
versionCode = Integer.parseInt("001_003_206".replace("_", ""), 10)
versionName = "1.3.6bb-forked"
versionName = "1.3.6bc-forked"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@ -156,18 +156,29 @@ val avatarPadding = 2.dp
@JBM
fun JBMRenderer(content: String, modifier: Modifier = Modifier) {
val state = LocalJBMarkdownTreeState.current
val flavor = if (state.enhanced) RSMEnhancedFlavourDescriptor() else RSMFlavourDescriptor()
android.util.Log.d("JBMRenderer", "JBMRenderer called with content: '${content.take(50)}...', enhanced: ${state.enhanced}")
var tree by remember { mutableStateOf(JBMApi.parse(content, flavor)) }
val preprocessedContent = content.replace(Regex("<@([0-9A-HJKMNP-TV-Z]{26})>")) { match ->
"ZZUSERMENTION${match.groupValues[1]}ZZ"
}
android.util.Log.d("JBMRenderer", "Preprocessed content: '${preprocessedContent.take(50)}...'")
val flavor = if (state.enhanced) RSMEnhancedFlavourDescriptor() else RSMFlavourDescriptor()
android.util.Log.d("JBMRenderer", "Using flavor: ${flavor.javaClass.simpleName}")
var tree by remember { mutableStateOf(JBMApi.parse(preprocessedContent, flavor)) }
var revealedSpoilers by remember { mutableStateOf(setOf<String>()) }
LaunchedEffect(content, state.enhanced) {
tree = JBMApi.parse(content, flavor)
val preprocessedContent = content.replace(Regex("<@([0-9A-HJKMNP-TV-Z]{26})>")) { match ->
"ZZUSERMENTION${match.groupValues[1]}ZZ"
}
tree = JBMApi.parse(preprocessedContent, flavor)
}
CompositionLocalProvider(
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
sourceText = content,
sourceText = preprocessedContent,
revealedSpoilers = revealedSpoilers,
onSpoilerToggle = { spoilerId ->
revealedSpoilers = if (revealedSpoilers.contains(spoilerId)) {
@ -213,6 +224,7 @@ private fun annotateText(
val sourceText = state.sourceText
return buildAnnotatedString {
android.util.Log.d("JBMRenderer", "annotateText called with node type: ${node.type}")
if (node.type.toString().contains("SPOILER", ignoreCase = true) ||
sourceText.contains("||")) {
android.util.Log.d("JBMRenderer", "Processing node: ${node.type}, text contains spoilers: ${sourceText.contains("||")}")
@ -224,18 +236,67 @@ private fun annotateText(
} else {
node.getTextInNode(sourceText)
}
append(source)
val text = source.toString()
val mentionRegex = Regex("(ZZUSERMENTION([0-9A-HJKMNP-TV-Z]{26})ZZ)")
android.util.Log.d("JBMRenderer", "Processing TEXT: '$text'")
android.util.Log.d("JBMRenderer", "Contains mention: ${mentionRegex.containsMatchIn(text)}")
if (mentionRegex.containsMatchIn(text)) {
android.util.Log.d("JBMRenderer", "Found mentions in text")
var lastIndex = 0
mentionRegex.findAll(text).forEach { match ->
android.util.Log.d("JBMRenderer", "Match: '${match.value}', groups: ${match.groupValues}")
if (match.range.first > lastIndex) {
append(text.substring(lastIndex, match.range.first))
}
val userId = match.groupValues[2]
android.util.Log.d("JBMRenderer", "Processing mention for userId: '$userId'")
pushStringAnnotation(
tag = JBMAnnotations.UserMention.tag,
annotation = userId
)
pushStyle(
SpanStyle(
color = state.colors.clickable,
background = state.colors.clickableBackground
)
)
append(" ")
appendInlineContent(JBMAnnotations.UserAvatar.tag, userId)
append(" ")
append(MentionResolver.resolveUser(userId, state.currentServer))
append(" ")
pop()
pop()
lastIndex = match.range.last + 1
}
if (lastIndex < text.length) {
append(text.substring(lastIndex))
}
} else {
append(text)
}
}
RSMElementTypes.USER_MENTION -> {
val contents = node.getTextInNode(sourceText).toString()
android.util.Log.d("JBMRenderer", "Processing USER_MENTION: '$contents'")
val userId = contents.removeSurrounding("<@", ">")
android.util.Log.d("JBMRenderer", "Extracted userId: '$userId', isUlid: ${userId.isUlid()}")
if (userId == contents || !userId.isUlid()) {
// Invalid user mention. Append as if it were regular text.
android.util.Log.d("JBMRenderer", "Invalid user mention, treating as text")
for (child in node.children) {
append(annotateText(state, child, revealedSpoilers))
}
} else {
android.util.Log.d("JBMRenderer", "Valid user mention, resolving to: ${MentionResolver.resolveUser(userId, state.currentServer)}")
pushStringAnnotation(
tag = JBMAnnotations.UserMention.tag,
annotation = userId
@ -329,6 +390,22 @@ private fun annotateText(
}
}
RSMElementTypes.CUSTOM_EMOTE -> {
val contents = node.getTextInNode(sourceText).toString()
val emoteId = contents.removeSurrounding(":", ":")
if (emoteId == contents || !emoteId.isUlid()) {
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")
@ -543,6 +620,7 @@ private fun annotateText(
append(node.getTextInNode(sourceText))
}
else -> {
withStyle(SpanStyle(color = Color.Cyan)) {
append("[${node.type.name}]{\n")

View File

@ -1,5 +1,6 @@
package chat.revolt.markdown.jbm
import chat.revolt.markdown.jbm.sequentialparsers.CustomEmoteParser
import chat.revolt.markdown.jbm.sequentialparsers.SpoilerParser
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
import org.intellij.markdown.parser.sequentialparsers.SequentialParserManager
@ -14,7 +15,7 @@ class RSMEnhancedFlavourDescriptor : RSMFlavourDescriptor() {
override fun getParserSequence(): List<SequentialParser> {
val originalParsers = super@RSMEnhancedFlavourDescriptor.sequentialParserManager.getParserSequence()
return originalParsers + SpoilerParser()
return originalParsers + CustomEmoteParser() + SpoilerParser()
}
}
}

View File

@ -166,6 +166,8 @@ class ChatRouterViewModel @Inject constructor(
val current = kvStorage.get("currentDestination")
setSaveDestination(ChatRouterDestination.fromString(current ?: ""))
// Disabled for forked version might use later ;)
/*
latestChangelogRead = changelogs.hasSeenCurrent()
latestChangelog = changelogs.getLatestChangelogCode()
latestChangelogBody =
@ -173,7 +175,12 @@ class ChatRouterViewModel @Inject constructor(
if (!latestChangelogRead) {
changelogs.markAsSeen()
}
*/
// Always mark changelog as read to prevent popup
latestChangelogRead = true
// Disabled for forked version
/*
val seenEarlyAccess = kvStorage.getBoolean("spark/earlyAccess/dismissed")
val seenSwipeToReply = kvStorage.getBoolean("spark/swipeToReply/dismissed")
if (seenEarlyAccess == null) {
@ -186,6 +193,7 @@ class ChatRouterViewModel @Inject constructor(
if (seenEarlyAccess == true && seenSwipeToReply != true) {
showSwipeToReplySpark = true
}
*/
val hasNotificationPermission =
NotificationManagerCompat.from(context).areNotificationsEnabled()