mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-30 00:40:49 +00:00
Adds new Highlight event kind from https://highlighter.com/
This commit is contained in:
parent
d1dc06a467
commit
1496f012a5
@ -396,10 +396,10 @@ class Account(
|
||||
)
|
||||
|
||||
Client.send(data)
|
||||
LocalCache.consume(data)
|
||||
LocalCache.consume(data, null)
|
||||
|
||||
Client.send(signedEvent)
|
||||
LocalCache.consume(signedEvent)
|
||||
LocalCache.consume(signedEvent, null)
|
||||
|
||||
return LocalCache.notes[signedEvent.id]
|
||||
}
|
||||
@ -418,7 +418,7 @@ class Account(
|
||||
)
|
||||
|
||||
Client.send(signedEvent)
|
||||
LocalCache.consume(signedEvent)
|
||||
LocalCache.consume(signedEvent, null)
|
||||
|
||||
return LocalCache.notes[signedEvent.id]
|
||||
}
|
||||
|
@ -653,40 +653,72 @@ object LocalCache {
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(event: FileHeaderEvent) {
|
||||
fun consume(event: FileHeaderEvent, relay: Relay?) {
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(event: FileStorageHeaderEvent) {
|
||||
fun consume(event: FileStorageHeaderEvent, relay: Relay?) {
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(event: FileStorageEvent) {
|
||||
fun consume(event: HighlightEvent, relay: Relay?) {
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
// Adds to user profile
|
||||
author.addNote(note)
|
||||
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(event: FileStorageEvent, relay: Relay?) {
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
|
@ -76,9 +76,10 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
is ContactListEvent -> LocalCache.consume(event)
|
||||
is DeletionEvent -> LocalCache.consume(event)
|
||||
|
||||
is FileHeaderEvent -> LocalCache.consume(event)
|
||||
is FileStorageEvent -> LocalCache.consume(event)
|
||||
is FileStorageHeaderEvent -> LocalCache.consume(event)
|
||||
is FileHeaderEvent -> LocalCache.consume(event, relay)
|
||||
is FileStorageEvent -> LocalCache.consume(event, relay)
|
||||
is FileStorageHeaderEvent -> LocalCache.consume(event, relay)
|
||||
is HighlightEvent -> LocalCache.consume(event, relay)
|
||||
is LnZapEvent -> {
|
||||
event.zapRequest?.let { onEvent(it, subscriptionId, relay) }
|
||||
LocalCache.consume(event)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||
@ -12,7 +13,7 @@ object NostrGlobalDataSource : NostrDataSource("GlobalFeed") {
|
||||
fun createGlobalFilter() = TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind, PollNoteEvent.kind, ChannelMessageEvent.kind, LongTextNoteEvent.kind),
|
||||
kinds = listOf(TextNoteEvent.kind, PollNoteEvent.kind, ChannelMessageEvent.kind, LongTextNoteEvent.kind, HighlightEvent.kind),
|
||||
limit = 200
|
||||
)
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.UserState
|
||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||
@ -55,7 +56,7 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
|
||||
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, HighlightEvent.kind),
|
||||
authors = followSet,
|
||||
limit = 400,
|
||||
since = latestEOSEs.users[account.userProfile()]?.relayList
|
||||
@ -71,7 +72,7 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
||||
return TypedFilter(
|
||||
types = setOf(FeedType.FOLLOWS),
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind),
|
||||
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, HighlightEvent.kind),
|
||||
tags = mapOf(
|
||||
"t" to hashToLoad.map {
|
||||
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||
|
@ -60,7 +60,7 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, ChannelMetadataEvent.kind, ChannelCreateEvent.kind, ChannelMessageEvent.kind),
|
||||
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, ChannelMetadataEvent.kind, ChannelCreateEvent.kind, ChannelMessageEvent.kind, HighlightEvent.kind),
|
||||
search = mySearchString,
|
||||
limit = 20
|
||||
)
|
||||
|
@ -79,7 +79,8 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
ReportEvent.kind,
|
||||
LnZapEvent.kind,
|
||||
LnZapRequestEvent.kind,
|
||||
PollNoteEvent.kind
|
||||
PollNoteEvent.kind,
|
||||
HighlightEvent.kind
|
||||
),
|
||||
tags = mapOf("e" to listOf(it.idHex)),
|
||||
since = it.lastReactionsDownloadTime
|
||||
@ -117,7 +118,8 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
ChannelMessageEvent.kind, ChannelCreateEvent.kind, ChannelMetadataEvent.kind,
|
||||
BadgeDefinitionEvent.kind, BadgeAwardEvent.kind, BadgeProfilesEvent.kind,
|
||||
PrivateDmEvent.kind,
|
||||
FileHeaderEvent.kind, FileStorageEvent.kind, FileStorageHeaderEvent.kind
|
||||
FileHeaderEvent.kind, FileStorageEvent.kind, FileStorageHeaderEvent.kind,
|
||||
HighlightEvent.kind
|
||||
),
|
||||
ids = interestedEvents.toList()
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
|
||||
TypedFilter(
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
|
||||
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, HighlightEvent.kind),
|
||||
authors = listOf(it.pubkeyHex),
|
||||
limit = 200
|
||||
)
|
||||
|
@ -41,6 +41,8 @@ open class Event(
|
||||
fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||
fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||
|
||||
fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] }
|
||||
|
||||
override fun zapAddress() = tags.firstOrNull { it.size > 1 && it[0] == "zap" }?.get(1)
|
||||
|
||||
fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull {
|
||||
@ -227,6 +229,7 @@ open class Event(
|
||||
FileHeaderEvent.kind -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileStorageEvent.kind -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
FileStorageHeaderEvent.kind -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
HighlightEvent.kind -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPaymentRequestEvent.kind -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
LnZapPaymentResponseEvent.kind -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
@ -0,0 +1,36 @@
|
||||
package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
import com.vitorpamplona.amethyst.model.toHexKey
|
||||
import nostr.postr.Utils
|
||||
import java.util.Date
|
||||
|
||||
class HighlightEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: List<List<String>>,
|
||||
content: String,
|
||||
sig: HexKey
|
||||
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
|
||||
fun inUrl() = taggedUrls().firstOrNull()
|
||||
fun author() = taggedUsers().firstOrNull()
|
||||
fun quote() = content
|
||||
|
||||
companion object {
|
||||
const val kind = 9802
|
||||
|
||||
fun create(
|
||||
msg: String,
|
||||
privateKey: ByteArray,
|
||||
createdAt: Long = Date().time / 1000
|
||||
): PollNoteEvent {
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
val tags = mutableListOf<List<String>>()
|
||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||
val sig = Utils.sign(id, privateKey)
|
||||
return PollNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
|
||||
}
|
||||
}
|
||||
}
|
@ -86,7 +86,8 @@ fun RichTextViewer(
|
||||
navController: NavController
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (content.startsWith("# ") ||
|
||||
if (content.startsWith("> ") ||
|
||||
content.startsWith("# ") ||
|
||||
content.contains("##") ||
|
||||
content.contains("**") ||
|
||||
content.contains("__") ||
|
||||
|
@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
@ -30,7 +31,7 @@ object HomeNewThreadFeedFilter : AdditiveFeedFilter<Note>() {
|
||||
return collection
|
||||
.asSequence()
|
||||
.filter { it ->
|
||||
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent || it.event is PollNoteEvent) &&
|
||||
(it.event is TextNoteEvent || it.event is RepostEvent || it.event is LongTextNoteEvent || it.event is PollNoteEvent || it.event is HighlightEvent) &&
|
||||
(it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false)) &&
|
||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||
it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true &&
|
||||
|
@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.theme.Following
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
import java.net.URL
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
@ -517,6 +518,26 @@ fun NoteComposeInner(
|
||||
ReactionsRow(note, accountViewModel, navController)
|
||||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
} else if (noteEvent is HighlightEvent) {
|
||||
DisplayHighlight(
|
||||
noteEvent.quote(),
|
||||
noteEvent.author(),
|
||||
noteEvent.inUrl(),
|
||||
makeItShort,
|
||||
canPreview,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
|
||||
if (!makeItShort) {
|
||||
ReactionsRow(note, accountViewModel, navController)
|
||||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
@ -574,6 +595,66 @@ fun NoteComposeInner(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayHighlight(
|
||||
highlight: String,
|
||||
authorHex: String?,
|
||||
url: String?,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
navController: NavController
|
||||
) {
|
||||
val quote = highlight.split("\n").map { "> *${it.removeSuffix(" ")}*" }.joinToString("\n")
|
||||
|
||||
if (quote != null) {
|
||||
TranslatableRichTextViewer(
|
||||
quote,
|
||||
canPreview = canPreview && !makeItShort,
|
||||
Modifier.fillMaxWidth(),
|
||||
emptyList(),
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
}
|
||||
|
||||
FlowRow() {
|
||||
authorHex?.let { authorHex ->
|
||||
val userBase = LocalCache.checkGetOrCreateUser(authorHex)
|
||||
|
||||
if (userBase != null) {
|
||||
val userState by userBase.live().metadata.observeAsState()
|
||||
val user = userState?.user
|
||||
|
||||
if (user != null) {
|
||||
CreateClickableText(
|
||||
user.toBestDisplayName(),
|
||||
"",
|
||||
"User/${user.pubkeyHex}",
|
||||
navController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
url?.let { url ->
|
||||
val validatedUrl = try {
|
||||
URL(url)
|
||||
} catch (e: Exception) {
|
||||
Log.w("Note Compose", "Invalid URI: $url")
|
||||
null
|
||||
}
|
||||
|
||||
validatedUrl?.host?.let { host ->
|
||||
Text("on ")
|
||||
ClickableUrl(urlText = host, url = url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayFollowingHashtagsInPost(
|
||||
noteEvent: EventInterface,
|
||||
|
@ -54,6 +54,7 @@ import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
@ -348,6 +349,18 @@ fun NoteMaster(
|
||||
)
|
||||
) {
|
||||
Column() {
|
||||
if (noteEvent is HighlightEvent) {
|
||||
DisplayHighlight(
|
||||
noteEvent.quote(),
|
||||
noteEvent.author(),
|
||||
noteEvent.inUrl(),
|
||||
false,
|
||||
true,
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
} else {
|
||||
val eventContent = note.event?.content()
|
||||
|
||||
val canPreview = note.author == account.userProfile() ||
|
||||
@ -377,6 +390,7 @@ fun NoteMaster(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReactionsRow(note, accountViewModel, navController)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user