Adds Wikipage Kind

This commit is contained in:
Vitor Pamplona
2024-02-23 17:55:39 -05:00
parent 2819c71e85
commit 173e82d236
7 changed files with 378 additions and 4 deletions
@@ -96,6 +96,7 @@ import com.vitorpamplona.quartz.events.StatusEvent
import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent import com.vitorpamplona.quartz.events.VideoHorizontalEvent
import com.vitorpamplona.quartz.events.VideoVerticalEvent import com.vitorpamplona.quartz.events.VideoVerticalEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -395,6 +396,41 @@ object LocalCache {
} }
} }
fun consume(
event: WikiNoteEvent,
relay: Relay?,
) {
val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address())
val author = getOrCreateUser(event.pubKey)
if (version.event == null) {
version.loadEvent(event, author, emptyList())
version.moveAllReferencesTo(note)
}
if (relay != null) {
author.addRelayBeingUsed(relay, event.createdAt)
note.addRelay(relay)
}
// Already processed this event.
if (note.event?.id() == event.id()) return
if (antiSpam.isSpam(event, relay)) {
relay?.let { it.spamCounter++ }
return
}
val replyTo = event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
if (event.createdAt > (note.createdAt() ?: 0)) {
note.loadEvent(event, author, replyTo)
refreshObservers(note)
}
}
fun consume( fun consume(
event: PollNoteEvent, event: PollNoteEvent,
relay: Relay? = null, relay: Relay? = null,
@@ -1848,6 +1884,7 @@ object LocalCache {
is TextNoteEvent -> consume(event, relay) is TextNoteEvent -> consume(event, relay)
is VideoHorizontalEvent -> consume(event, relay) is VideoHorizontalEvent -> consume(event, relay)
is VideoVerticalEvent -> consume(event, relay) is VideoVerticalEvent -> consume(event, relay)
is WikiNoteEvent -> consume(event, relay)
else -> { else -> {
Log.w("Event Not Supported", event.toJson()) Log.w("Event Not Supported", event.toJson())
} }
@@ -225,6 +225,7 @@ import com.vitorpamplona.quartz.events.UserMetadata
import com.vitorpamplona.quartz.events.VideoEvent import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent import com.vitorpamplona.quartz.events.VideoHorizontalEvent
import com.vitorpamplona.quartz.events.VideoVerticalEvent import com.vitorpamplona.quartz.events.VideoVerticalEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
import com.vitorpamplona.quartz.events.toImmutableListOfLists import com.vitorpamplona.quartz.events.toImmutableListOfLists
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@@ -1160,6 +1161,9 @@ private fun RenderNoteRow(
is LongTextNoteEvent -> { is LongTextNoteEvent -> {
RenderLongFormContent(baseNote, accountViewModel, nav) RenderLongFormContent(baseNote, accountViewModel, nav)
} }
is WikiNoteEvent -> {
RenderWikiContent(baseNote, accountViewModel, nav)
}
is BadgeAwardEvent -> { is BadgeAwardEvent -> {
RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav) RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
} }
@@ -2916,12 +2920,19 @@ private fun LoadAndDisplayUrl(url: String) {
} }
@Composable @Composable
private fun LoadAndDisplayUser( fun LoadAndDisplayUser(
userBase: User, userBase: User,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
val route = remember { "User/${userBase.pubkeyHex}" } LoadAndDisplayUser(userBase, "User/${userBase.pubkeyHex}", nav)
}
@Composable
fun LoadAndDisplayUser(
userBase: User,
route: String,
nav: (String) -> Unit,
) {
val userState by userBase.live().metadata.observeAsState() val userState by userBase.live().metadata.observeAsState()
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() } val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
val userTags = val userTags =
@@ -3660,6 +3671,92 @@ private fun LongFormHeader(
} }
summary?.let { summary?.let {
Spacer(modifier = StdVertSpacer)
Text(
text = it,
style = MaterialTheme.typography.bodySmall,
modifier =
Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
color = Color.Gray,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
@Composable
private fun RenderWikiContent(
note: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteEvent = note.event as? WikiNoteEvent ?: return
WikiNoteHeader(noteEvent, note, accountViewModel, nav)
}
@Composable
private fun WikiNoteHeader(
noteEvent: WikiNoteEvent,
note: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val title = remember(noteEvent) { noteEvent.title() }
val forkedAddress = remember(noteEvent) { noteEvent.forkFromAddress() }
val summary =
remember(noteEvent) {
noteEvent.summary()?.ifBlank { null } ?: noteEvent.content.take(200).ifBlank { null }
}
val image = remember(noteEvent) { noteEvent.image() }
Row(
modifier =
Modifier
.padding(top = Size5dp)
.clip(shape = QuoteBorder)
.border(
1.dp,
MaterialTheme.colorScheme.subtleBorder,
QuoteBorder,
),
) {
Column {
val automaticallyShowUrlPreview = remember { accountViewModel.settings.showUrlPreview.value }
if (automaticallyShowUrlPreview) {
image?.let {
AsyncImage(
model = it,
contentDescription =
stringResource(
R.string.preview_card_image_for,
it,
),
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(),
)
}
?: CreateImageHeader(note, accountViewModel)
}
title?.let {
Text(
text = it,
style = MaterialTheme.typography.bodyLarge,
modifier =
Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, top = 10.dp),
)
}
summary?.let {
Spacer(modifier = StdVertSpacer)
Text( Text(
text = it, text = it,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
@@ -76,21 +76,27 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.InlineCarrousel import com.vitorpamplona.amethyst.ui.components.InlineCarrousel
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost
import com.vitorpamplona.amethyst.ui.elements.DisplayPoW import com.vitorpamplona.amethyst.ui.elements.DisplayPoW
import com.vitorpamplona.amethyst.ui.elements.DisplayReward import com.vitorpamplona.amethyst.ui.elements.DisplayReward
import com.vitorpamplona.amethyst.ui.elements.DisplayZapSplits import com.vitorpamplona.amethyst.ui.elements.DisplayZapSplits
import com.vitorpamplona.amethyst.ui.elements.Reward import com.vitorpamplona.amethyst.ui.elements.Reward
import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
import com.vitorpamplona.amethyst.ui.note.AudioHeader import com.vitorpamplona.amethyst.ui.note.AudioHeader
import com.vitorpamplona.amethyst.ui.note.AudioTrackHeader import com.vitorpamplona.amethyst.ui.note.AudioTrackHeader
@@ -104,6 +110,8 @@ import com.vitorpamplona.amethyst.ui.note.DisplayRelaySet
import com.vitorpamplona.amethyst.ui.note.FileHeaderDisplay import com.vitorpamplona.amethyst.ui.note.FileHeaderDisplay
import com.vitorpamplona.amethyst.ui.note.FileStorageHeaderDisplay import com.vitorpamplona.amethyst.ui.note.FileStorageHeaderDisplay
import com.vitorpamplona.amethyst.ui.note.HiddenNote import com.vitorpamplona.amethyst.ui.note.HiddenNote
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.ui.note.LoadAndDisplayUser
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.NoteDropDownMenu import com.vitorpamplona.amethyst.ui.note.NoteDropDownMenu
@@ -131,6 +139,7 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.selectedNote import com.vitorpamplona.amethyst.ui.theme.selectedNote
@@ -144,6 +153,7 @@ import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.events.EmojiPackEvent import com.vitorpamplona.quartz.events.EmojiPackEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
import com.vitorpamplona.quartz.events.GenericRepostEvent import com.vitorpamplona.quartz.events.GenericRepostEvent
@@ -155,11 +165,13 @@ import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.RelaySetEvent import com.vitorpamplona.quartz.events.RelaySetEvent
import com.vitorpamplona.quartz.events.RepostEvent import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.VideoEvent import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
@@ -444,6 +456,8 @@ fun NoteMaster(
BadgeDisplay(baseNote = note) BadgeDisplay(baseNote = note)
} else if (noteEvent is LongTextNoteEvent) { } else if (noteEvent is LongTextNoteEvent) {
RenderLongFormHeaderForThread(noteEvent) RenderLongFormHeaderForThread(noteEvent)
} else if (noteEvent is WikiNoteEvent) {
RenderWikiHeaderForThread(noteEvent, accountViewModel, nav)
} else if (noteEvent is ClassifiedsEvent) { } else if (noteEvent is ClassifiedsEvent) {
RenderClassifiedsReaderForThread(noteEvent, note, accountViewModel, nav) RenderClassifiedsReaderForThread(noteEvent, note, accountViewModel, nav)
} }
@@ -797,3 +811,137 @@ private fun RenderLongFormHeaderForThread(noteEvent: LongTextNoteEvent) {
} }
} }
} }
@Preview
@Composable
private fun RenderWikiHeaderForThreadPreview() {
val event = Event.fromJson("{\"id\":\"277f982a4cd3f67cc47ad9282176acabee1713848f547d6021e0c155572078e1\",\"pubkey\":\"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c\",\"created_at\":1708695717,\"kind\":30818,\"tags\":[[\"d\",\"amethyst\"],[\"a\",\"30818:f03e7c5262648e0b7823dfb49f8f17309cfec9cb14711413dcabdf3d7fc6369a:amethyst\",\"wss://relay.nostr.band\",\"fork\"],[\"e\",\"ceabc60c8022c472c727aa25ae7691885964366386ce265c47e5a78be6cb00be\",\"wss://relay.nostr.band\",\"fork\"],[\"title\",\"Amethyst\"],[\"published_at\",\"1708707133\"]],\"content\":\"An Android-only app written in Kotlin with support for over 90 event kinds. \\n\\n![](https://play-lh.googleusercontent.com/lvZlAm9dBrpHeOo7sIPKCsiKOLYLhR2b0FiOT4tyiwWO2dvsR2gDS0xk9tOOr9U-6uM=w240-h480-rw)\\n\",\"sig\":\"6748126a909a20dbdb67947a09d64e41d7140a79335a4ad675c6173d7dd5dbcab9c360dec617bd67bbbc20dfad416b15056eda2e20716cd6c425a84301a125a0\"}") as WikiNoteEvent
val accountViewModel = mockAccountViewModel()
val nav: (String) -> Unit = {}
runBlocking {
withContext(Dispatchers.IO) {
LocalCache.justConsume(event, null)
}
}
LoadNote(baseNoteHex = "277f982a4cd3f67cc47ad9282176acabee1713848f547d6021e0c155572078e1", accountViewModel = accountViewModel) { baseNote ->
ThemeComparisonColumn(
onDark = {
val bg = MaterialTheme.colorScheme.background
val backgroundColor =
remember {
mutableStateOf(bg)
}
Column {
RenderWikiHeaderForThread(noteEvent = event, accountViewModel = accountViewModel, nav)
RenderTextEvent(
baseNote!!,
false,
true,
backgroundColor,
accountViewModel,
nav,
)
}
},
onLight = {
val bg = MaterialTheme.colorScheme.background
val backgroundColor =
remember {
mutableStateOf(bg)
}
Column {
RenderWikiHeaderForThread(noteEvent = event, accountViewModel = accountViewModel, nav)
RenderTextEvent(
baseNote!!,
false,
true,
backgroundColor,
accountViewModel,
nav,
)
}
},
)
}
}
@Composable
private fun RenderWikiHeaderForThread(
noteEvent: WikiNoteEvent,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val forkedAddress = remember(noteEvent) { noteEvent.forkFromAddress() }
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
Column {
noteEvent.image()?.let {
AsyncImage(
model = it,
contentDescription =
stringResource(
R.string.preview_card_image_for,
it,
),
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(),
)
}
noteEvent.title()?.let {
Spacer(modifier = DoubleVertSpacer)
Text(
text = it,
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.fillMaxWidth(),
)
}
forkedAddress?.let {
LoadAddressableNote(aTag = it, accountViewModel = accountViewModel) { originalVersion ->
if (originalVersion != null) {
ShowForkInformation(originalVersion, Modifier.fillMaxWidth(), accountViewModel, nav)
}
}
}
noteEvent
.summary()
?.ifBlank { null }
?.let {
Spacer(modifier = DoubleVertSpacer)
Text(
text = it,
modifier = Modifier.fillMaxWidth(),
color = Color.Gray,
)
}
}
}
}
@Composable
fun ShowForkInformation(
originalVersion: AddressableNote,
modifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteState by originalVersion.live().metadata.observeAsState()
val note = noteState?.note ?: return
val author = note.author ?: return
val route = remember(note) { routeFor(note, accountViewModel.userProfile()) }
if (route != null) {
Row(modifier) {
Text(stringResource(id = R.string.forked_from))
Spacer(modifier = StdHorzSpacer)
LoadAndDisplayUser(author, route, nav)
}
}
}
+1
View File
@@ -776,4 +776,5 @@
<string name="thank_you">Thank you!</string> <string name="thank_you">Thank you!</string>
<string name="max_limit">Max Limit</string> <string name="max_limit">Max Limit</string>
<string name="restricted_writes">Restricted Writes</string> <string name="restricted_writes">Restricted Writes</string>
<string name="forked_from">Forked from</string>
</resources> </resources>
@@ -43,7 +43,7 @@ open class BaseTextNoteEvent(
fun mentions() = taggedUsers() fun mentions() = taggedUsers()
open fun replyTos(): List<HexKey> { open fun replyTos(): List<HexKey> {
val oldStylePositional = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] } val oldStylePositional = tags.filter { it.size > 1 && it.size <= 3 && it[0] == "e" }.map { it[1] }
val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1) val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1)
val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1) val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1)
@@ -153,7 +153,10 @@ open class BaseTextNoteEvent(
fun tagsWithoutCitations(): List<String> { fun tagsWithoutCitations(): List<String> {
val repliesTo = replyTos() val repliesTo = replyTos()
val tagAddresses = val tagAddresses =
taggedAddresses().filter { it.kind != CommunityDefinitionEvent.KIND }.map { it.toTag() } taggedAddresses().filter {
it.kind != CommunityDefinitionEvent.KIND &&
it.kind != WikiNoteEvent.KIND
}.map { it.toTag() }
if (repliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList() if (repliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList()
val citations = findCitations() val citations = findCitations()
@@ -123,6 +123,7 @@ class EventFactory {
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig) VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig) VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
VideoViewEvent.KIND -> VideoViewEvent(id, pubKey, createdAt, tags, content, sig) VideoViewEvent.KIND -> VideoViewEvent(id, pubKey, createdAt, tags, content, sig)
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
else -> Event(id, pubKey, createdAt, kind, tags, content, sig) else -> Event(id, pubKey, createdAt, kind, tags, content, sig)
} }
} }
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.events
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
class WikiNoteEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseTextNoteEvent(id, pubKey, createdAt, KIND, tags, content, sig), AddressableEvent {
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
override fun address() = ATag(kind, pubKey, dTag(), null)
fun topics() = hashtags()
fun forkFromAddress() =
tags.firstOrNull { it.size > 3 && it[0] == "a" && it[3] == "fork" }?.let {
val aTagValue = it[1]
val relay = it.getOrNull(2)
ATag.parse(aTagValue, relay)
}
fun forkFromVersion() = tags.firstOrNull { it.size > 3 && it[0] == "e" && it[3] == "fork" }?.get(1)
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == "summary" }?.get(1)
fun image() = tags.firstOrNull { it.size > 1 && it[0] == "image" }?.get(1)
fun publishedAt() =
try {
tags.firstOrNull { it.size > 1 && it[0] == "published_at" }?.get(1)?.toLongOrNull()
} catch (_: Exception) {
null
}
companion object {
const val KIND = 30818
fun create(
msg: String,
title: String?,
replyTos: List<String>?,
mentions: List<String>?,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (WikiNoteEvent) -> Unit,
) {
val tags = mutableListOf<Array<String>>()
replyTos?.forEach { tags.add(arrayOf("e", it)) }
mentions?.forEach { tags.add(arrayOf("p", it)) }
title?.let { tags.add(arrayOf("title", it)) }
tags.add(arrayOf("alt", "Wiki Post: $title"))
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
}
}
}