feat(markdown): support timestamps

Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
Infi 2023-07-03 16:38:22 +02:00
parent 10a40f75e7
commit 33fd3c6bd4
4 changed files with 96 additions and 0 deletions

View File

@ -29,6 +29,7 @@ import chat.revolt.internals.markdown.CustomEmoteRule
import chat.revolt.internals.markdown.MarkdownContext
import chat.revolt.internals.markdown.MarkdownParser
import chat.revolt.internals.markdown.MarkdownState
import chat.revolt.internals.markdown.TimestampRule
import chat.revolt.internals.markdown.UserMentionRule
import chat.revolt.internals.markdown.createCodeRule
import chat.revolt.internals.markdown.createInlineCodeRule
@ -61,6 +62,7 @@ fun UIMarkdown(
UserMentionRule(),
ChannelMentionRule(),
CustomEmoteRule(),
TimestampRule(),
)
.addRules(
createCodeRule(context, codeBlockColor.toArgb()),

View File

@ -1,7 +1,15 @@
package chat.revolt.internals.markdown
import android.text.SpannableStringBuilder
import android.text.format.DateUtils
import android.util.Log
import com.discord.simpleast.core.node.Node
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
@ -30,3 +38,67 @@ class CustomEmoteNode(private val emoteId: String) : Node<MarkdownContext>() {
)
}
}
class TimestampNode(private val timestamp: Long, private val modifier: String? = null) :
Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
val normalisedModifier = modifier.orEmpty().removePrefix(":")
val instant = Instant.fromEpochSeconds(timestamp)
val javaInstant = instant.toJavaInstant()
try {
if (timestamp < 0) {
builder.append("<invalid timestamp>")
return
}
val outString = when (normalisedModifier) {
// 22:22
"t" -> DateTimeFormatter.ofPattern("HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22:22:22
"T" -> DateTimeFormatter.ofPattern("HH:mm:ss")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22 September 2022
"D" -> DateTimeFormatter.ofPattern("dd MMMM yyyy")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22 September 2022 22:22
"f" -> DateTimeFormatter.ofPattern("dd MMMM yyyy HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// Thursday, 22 September 2022 22:22
"F" -> DateTimeFormatter.ofPattern("EEEE, dd MMMM yyyy HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 9 months ago
"R" -> DateUtils.getRelativeTimeSpanString(
timestamp * 1000,
Clock.System.now().toEpochMilliseconds(),
DateUtils.MINUTE_IN_MILLIS
)
// Fallback. Shouldn't happen
else -> timestamp.toString()
}
builder.append(outString)
} catch (e: Exception) {
Log.e("TimestampNode", "Failed to parse timestamp", e)
builder.append("<invalid timestamp>")
}
}
}

View File

@ -47,6 +47,26 @@ class CustomEmoteRule<S> :
}
}
class TimestampRule<S> :
Rule<MarkdownContext, TimestampNode, S>(Pattern.compile("^<t:([0-9]+?)(:[tTDfFR])?>")) {
override fun parse(
matcher: Matcher,
parser: Parser<MarkdownContext, in TimestampNode, S>,
state: S
): ParseSpec<MarkdownContext, S> {
return ParseSpec.createTerminal(
TimestampNode(
try {
matcher.group(1)!!.toLong()
} catch (e: NumberFormatException) {
-1
},
matcher.group(2)
), state
)
}
}
fun <RC, S> createInlineCodeRule(context: Context, backgroundColor: Int): Rule<RC, Node<RC>, S> {
return CodeRules.createInlineCodeRule(
{ listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) },

View File

@ -70,6 +70,7 @@ import chat.revolt.internals.markdown.CustomEmoteRule
import chat.revolt.internals.markdown.MarkdownContext
import chat.revolt.internals.markdown.MarkdownParser
import chat.revolt.internals.markdown.MarkdownState
import chat.revolt.internals.markdown.TimestampRule
import chat.revolt.internals.markdown.UserMentionRule
import chat.revolt.internals.markdown.createCodeRule
import chat.revolt.internals.markdown.createInlineCodeRule
@ -256,6 +257,7 @@ fun ChannelScreen(
UserMentionRule(),
ChannelMentionRule(),
CustomEmoteRule(),
TimestampRule(),
)
.addRules(
createCodeRule(context, codeBlockColor.toArgb()),