feat(markdown): support timestamps
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
10a40f75e7
commit
33fd3c6bd4
|
|
@ -29,6 +29,7 @@ import chat.revolt.internals.markdown.CustomEmoteRule
|
||||||
import chat.revolt.internals.markdown.MarkdownContext
|
import chat.revolt.internals.markdown.MarkdownContext
|
||||||
import chat.revolt.internals.markdown.MarkdownParser
|
import chat.revolt.internals.markdown.MarkdownParser
|
||||||
import chat.revolt.internals.markdown.MarkdownState
|
import chat.revolt.internals.markdown.MarkdownState
|
||||||
|
import chat.revolt.internals.markdown.TimestampRule
|
||||||
import chat.revolt.internals.markdown.UserMentionRule
|
import chat.revolt.internals.markdown.UserMentionRule
|
||||||
import chat.revolt.internals.markdown.createCodeRule
|
import chat.revolt.internals.markdown.createCodeRule
|
||||||
import chat.revolt.internals.markdown.createInlineCodeRule
|
import chat.revolt.internals.markdown.createInlineCodeRule
|
||||||
|
|
@ -61,6 +62,7 @@ fun UIMarkdown(
|
||||||
UserMentionRule(),
|
UserMentionRule(),
|
||||||
ChannelMentionRule(),
|
ChannelMentionRule(),
|
||||||
CustomEmoteRule(),
|
CustomEmoteRule(),
|
||||||
|
TimestampRule(),
|
||||||
)
|
)
|
||||||
.addRules(
|
.addRules(
|
||||||
createCodeRule(context, codeBlockColor.toArgb()),
|
createCodeRule(context, codeBlockColor.toArgb()),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
package chat.revolt.internals.markdown
|
package chat.revolt.internals.markdown
|
||||||
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.util.Log
|
||||||
import com.discord.simpleast.core.node.Node
|
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>() {
|
class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
|
||||||
override fun render(builder: SpannableStringBuilder, renderContext: 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>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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> {
|
fun <RC, S> createInlineCodeRule(context: Context, backgroundColor: Int): Rule<RC, Node<RC>, S> {
|
||||||
return CodeRules.createInlineCodeRule(
|
return CodeRules.createInlineCodeRule(
|
||||||
{ listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) },
|
{ listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) },
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ import chat.revolt.internals.markdown.CustomEmoteRule
|
||||||
import chat.revolt.internals.markdown.MarkdownContext
|
import chat.revolt.internals.markdown.MarkdownContext
|
||||||
import chat.revolt.internals.markdown.MarkdownParser
|
import chat.revolt.internals.markdown.MarkdownParser
|
||||||
import chat.revolt.internals.markdown.MarkdownState
|
import chat.revolt.internals.markdown.MarkdownState
|
||||||
|
import chat.revolt.internals.markdown.TimestampRule
|
||||||
import chat.revolt.internals.markdown.UserMentionRule
|
import chat.revolt.internals.markdown.UserMentionRule
|
||||||
import chat.revolt.internals.markdown.createCodeRule
|
import chat.revolt.internals.markdown.createCodeRule
|
||||||
import chat.revolt.internals.markdown.createInlineCodeRule
|
import chat.revolt.internals.markdown.createInlineCodeRule
|
||||||
|
|
@ -256,6 +257,7 @@ fun ChannelScreen(
|
||||||
UserMentionRule(),
|
UserMentionRule(),
|
||||||
ChannelMentionRule(),
|
ChannelMentionRule(),
|
||||||
CustomEmoteRule(),
|
CustomEmoteRule(),
|
||||||
|
TimestampRule(),
|
||||||
)
|
)
|
||||||
.addRules(
|
.addRules(
|
||||||
createCodeRule(context, codeBlockColor.toArgb()),
|
createCodeRule(context, codeBlockColor.toArgb()),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue