Adds Wikipage Kind
This commit is contained in:
@@ -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\\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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user