Compare commits

...

3 Commits

Author SHA1 Message Date
Claude
de85f6d472 feat: surface pasted user/event references in the post notify section
When an npub/nprofile (a user) or a note/nevent/naddr (an event, whose
author is notified) is pasted or typed into the post body, add the resolved
user to the notify list live, mirroring the @ autocomplete path. These
authors were already added to the p tags at send time by NewMessageTagger;
this keeps the notify chips in sync so the user can review who will be
tagged.

Extracts the mention-collection loop from NewMessageTagger.run() into a
reusable collectMentions() and adds a cheap mightContainNostrReference()
guard so plain text isn't reparsed on every keystroke. Applied to both
screens that render the Notifying() section (short note + comment).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HSBvxobGdB6VrqwAtkFwi
2026-06-18 15:29:13 +00:00
Claude
aaf1a0e588 feat: add mentioned users to the notify list in the comment composer
Mirror the short-note composer behavior: when a user is mentioned via the @
autocomplete in a comment body, also add them to the notify (notifying)
section so they appear as a removable chip. This is the other screen that
renders the Notifying() section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HSBvxobGdB6VrqwAtkFwi
2026-06-18 03:30:30 +00:00
Claude
d88889f653 feat: add mentioned users to the notify list when composing a post
When a user is mentioned via the @ autocomplete in the post body, also add
them to the notify (pTags) section so they show up as a notify chip and can
be reviewed/removed before sending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HSBvxobGdB6VrqwAtkFwi
2026-06-18 03:18:29 +00:00
3 changed files with 72 additions and 4 deletions

View File

@@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.nip19Bech32.entities.NNote
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay
@@ -50,6 +51,13 @@ class NewMessageTagger(
val directMentionsNotes = mutableSetOf<Note>()
val directMentionsUsers = mutableSetOf<User>()
companion object {
private val bechPrefixes = listOf("npub1", "nprofile1", "nevent1", "note1", "naddr1", "nsec1")
/** Cheap pre-check so callers can skip parsing plain text that cannot hold a reference. */
fun mightContainNostrReference(text: String): Boolean = bechPrefixes.any { text.contains(it, ignoreCase = true) }
}
fun addUserToMentions(user: User) {
directMentionsUsers.add(user)
directMentions.add(user.pubkeyHex)
@@ -64,8 +72,15 @@ class NewMessageTagger(
eTags = if (eTags?.contains(note) == true) eTags else eTags?.plus(note) ?: listOf(note)
}
suspend fun run() {
// adds all references to mentions and reply tos
/**
* Scans the message for user (npub/nprofile) and event (note/nevent/naddr)
* references, resolving each into the users that should be notified — the
* referenced user itself, or the referenced event's author. Populates the
* mention/reply-to sets (and [pTags]/[eTags]) as a side effect and returns
* the resolved users. Does not rewrite the message, so it is cheap enough
* to call live as the user types or pastes.
*/
suspend fun collectMentions(): Set<User> {
message.split('\n').forEach { paragraph: String ->
paragraph.split(' ').forEach { word: String ->
val results = parseDirtyWordForKey(word)
@@ -79,7 +94,7 @@ class NewMessageTagger(
addUserToMentions(dao.getOrCreateUser(entity.hex))
}
is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> {
is NNote -> {
addNoteToReplyTos(dao.getOrCreateNote(entity.hex))
}
@@ -105,6 +120,13 @@ class NewMessageTagger(
}
}
return directMentionsUsers
}
suspend fun run() {
// adds all references to mentions and reply tos
collectMentions()
// Tags the text in the correct order.
message =
message
@@ -123,7 +145,7 @@ class NewMessageTagger(
getNostrAddress(dao.getOrCreateUser(entity.hex).toNProfile(), results.restOfWord)
}
is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> {
is NNote -> {
getNostrAddress(dao.getOrCreateNote(entity.hex).toNEvent(), results.restOfWord)
}

View File

@@ -786,6 +786,12 @@ open class CommentPostViewModel :
notifying = notifying?.filter { it != userToRemove }
}
fun addToReplyList(user: User) {
if (notifying?.contains(user) != true) {
notifying = (notifying ?: emptyList()).plus(user)
}
}
override fun onMessageChanged() {
urlPreviews.update(message.text.toString())
revalidateDraft()
@@ -803,9 +809,28 @@ open class CommentPostViewModel :
emojiSuggestions?.processCurrentWord(lastWord)
}
addNotifiedUsersFromMessage()
draftTag.newVersion()
}
/**
* Surfaces users referenced in the message body (pasted or typed npub/nprofile,
* or the author of a pasted note/nevent/naddr) in the notify section, mirroring
* what [autocompleteWithUser] does for the @ dropdown. The comment already
* notifies these authors at send time via [NewMessageTagger]; this keeps the
* notify chips in sync so the user can see (and review) who will be tagged.
*/
private fun addNotifiedUsersFromMessage() {
val text = message.text.toString()
if (!NewMessageTagger.mightContainNostrReference(text)) return
viewModelScope.launch(Dispatchers.IO) {
val mentioned = NewMessageTagger(message = text, dao = accountViewModel).collectMentions()
mentioned.forEach { addToReplyList(it) }
}
}
override fun onForwardZapTextChanged() {
if (forwardZapToEditting.selection.collapsed) {
val lastWord = forwardZapToEditting.text.toString()
@@ -820,6 +845,7 @@ open class CommentPostViewModel :
val lastWord = message.currentWord()
userSuggestions.replaceCurrentWord(message, lastWord, item)
urlPreviews.update(message.text.toString())
addToReplyList(item)
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
forwardZapTo.value.addItem(item)
forwardZapToEditting.clearText()

View File

@@ -1352,10 +1352,29 @@ open class ShortNotePostViewModel :
emojiSuggestions?.processCurrentWord(lastWord)
}
addNotifiedUsersFromMessage()
precomputeAiResults()
draftTag.newVersion()
}
/**
* Surfaces users referenced in the message body (pasted or typed npub/nprofile,
* or the author of a pasted note/nevent/naddr) in the notify section, mirroring
* what [autocompleteWithUser] does for the @ dropdown. The post already notifies
* these authors at send time via [NewMessageTagger]; this keeps the notify chips
* in sync so the user can see (and review) who will be tagged.
*/
private fun addNotifiedUsersFromMessage() {
val text = message.text.toString()
if (!NewMessageTagger.mightContainNostrReference(text)) return
viewModelScope.launch(Dispatchers.IO) {
val mentioned = NewMessageTagger(message = text, dao = accountViewModel).collectMentions()
mentioned.forEach { addToReplyList(it) }
}
}
override fun onForwardZapTextChanged() {
if (forwardZapToEditting.selection.collapsed) {
val lastWord = forwardZapToEditting.text.toString()
@@ -1370,6 +1389,7 @@ open class ShortNotePostViewModel :
val lastWord = message.currentWord()
userSuggestions.replaceCurrentWord(message, lastWord, item)
urlPreviews.update(message.text.toString())
addToReplyList(item)
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
forwardZapTo.value.addItem(item)
forwardZapToEditting.clearText()