mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 11:48:24 +00:00
Merge branch 'main' into phase1-final
This commit is contained in:
@@ -48,6 +48,7 @@ import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStory
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
@@ -699,6 +700,51 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
wasVerified: Boolean,
|
||||
) = consumeRegularEvent(event, relay, wasVerified)
|
||||
|
||||
fun consume(
|
||||
event: NipTextEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
): Boolean {
|
||||
val version = getOrCreateNote(event.id)
|
||||
val note = getOrCreateAddressableNote(event.address())
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
val isVerified =
|
||||
if (version.event == null && (wasVerified || justVerify(event))) {
|
||||
version.loadEvent(event, author, emptyList())
|
||||
version.moveAllReferencesTo(note)
|
||||
true
|
||||
} else {
|
||||
wasVerified
|
||||
}
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event?.id == event.id) return wasVerified
|
||||
|
||||
if (antiSpam.isSpam(event, relay)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isVerified || justVerify(event)) {
|
||||
val replyTo = computeReplyTo(event)
|
||||
|
||||
if (event.createdAt > (note.createdAt() ?: 0L)) {
|
||||
note.loadEvent(event, author, replyTo)
|
||||
|
||||
refreshNewNoteObservers(note)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: LongTextNoteEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
@@ -793,7 +839,6 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
fun computeReplyTo(event: Event): List<Note> =
|
||||
when (event) {
|
||||
is PollNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
is WikiNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
is LongTextNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
is GitReplyEvent -> event.tagsWithoutCitations().filter { it != event.repository()?.toTag() }.mapNotNull { checkGetOrCreateNote(it) }
|
||||
is TextNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
@@ -2089,6 +2134,10 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
}
|
||||
|
||||
return notes.filter { _, note ->
|
||||
if (note.event is AddressableEvent) {
|
||||
return@filter false
|
||||
}
|
||||
|
||||
if (excludeNoteEventFromSearchResults(note)) {
|
||||
return@filter false
|
||||
}
|
||||
@@ -2886,6 +2935,7 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
is MetadataEvent -> consume(event, relay, wasVerified)
|
||||
is MuteListEvent -> consume(event, relay, wasVerified)
|
||||
is NNSEvent -> consume(event, relay, wasVerified)
|
||||
is NipTextEvent -> consume(event, relay, wasVerified)
|
||||
is OtsEvent -> consume(event, relay, wasVerified)
|
||||
is PictureEvent -> consume(event, relay, wasVerified)
|
||||
is PrivateDmEvent -> consume(event, relay, wasVerified)
|
||||
|
||||
+3
-6
@@ -249,15 +249,12 @@ fun observeUserFollowCount(
|
||||
remember(user) {
|
||||
user
|
||||
.flow()
|
||||
.followers.stateFlow
|
||||
.sample(200)
|
||||
.mapLatest { userState ->
|
||||
userState.user.transientFollowCount() ?: 0
|
||||
}.distinctUntilChanged()
|
||||
.follows.stateFlow
|
||||
.mapLatest { it.user.transientFollowCount() ?: 0 }
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(0)
|
||||
return flow.collectAsStateWithLifecycle(user.transientFollowCount() ?: 0)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
|
||||
+2
@@ -24,6 +24,7 @@ import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
@@ -81,6 +82,7 @@ val SearchPostsByTextKinds3 =
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
InteractiveStorySceneEvent.KIND,
|
||||
FollowListEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
)
|
||||
|
||||
fun searchPostsByText(
|
||||
|
||||
+35
-34
@@ -37,7 +37,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickAndHoldBoxComposable
|
||||
import com.vitorpamplona.amethyst.ui.components.ToggleableBox
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
@@ -52,15 +52,16 @@ fun RecordAudioBox(
|
||||
val mediaRecorder = remember { mutableStateOf<VoiceMessageRecorder?>(null) }
|
||||
val context = LocalContext.current
|
||||
var elapsedSeconds by remember { mutableIntStateOf(0) }
|
||||
var wantsToRecord by remember { mutableStateOf(false) }
|
||||
var pendingPermissionStart by remember { mutableStateOf(false) }
|
||||
|
||||
// Must be called at Composable scope, not in callback
|
||||
val recordPermissionState = rememberPermissionState(Manifest.permission.RECORD_AUDIO)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val isRecording = mediaRecorder.value != null
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
wantsToRecord = false
|
||||
mediaRecorder.value?.stop()
|
||||
mediaRecorder.value = null
|
||||
}
|
||||
@@ -74,8 +75,25 @@ fun RecordAudioBox(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(recordPermissionState.status.isGranted, wantsToRecord) {
|
||||
if (recordPermissionState.status.isGranted && wantsToRecord) {
|
||||
fun stopRecording() {
|
||||
val result = mediaRecorder.value?.stop()
|
||||
mediaRecorder.value = null
|
||||
if (result != null) {
|
||||
onRecordTaken(result)
|
||||
} else {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
stringRes(context, R.string.record_a_message_description),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
// Start recording after permission is granted
|
||||
LaunchedEffect(recordPermissionState.status.isGranted) {
|
||||
if (recordPermissionState.status.isGranted && pendingPermissionStart) {
|
||||
pendingPermissionStart = false
|
||||
startRecording()
|
||||
}
|
||||
}
|
||||
@@ -96,38 +114,21 @@ fun RecordAudioBox(
|
||||
}
|
||||
}
|
||||
|
||||
ClickAndHoldBoxComposable(
|
||||
ToggleableBox(
|
||||
modifier = modifier,
|
||||
onPress = {
|
||||
wantsToRecord = true
|
||||
if (!recordPermissionState.status.isGranted) {
|
||||
recordPermissionState.launchPermissionRequest()
|
||||
isActive = isRecording,
|
||||
onClick = {
|
||||
if (isRecording) {
|
||||
stopRecording()
|
||||
} else {
|
||||
// Start immediately for responsive UX when permission already granted
|
||||
startRecording()
|
||||
if (!recordPermissionState.status.isGranted) {
|
||||
pendingPermissionStart = true
|
||||
recordPermissionState.launchPermissionRequest()
|
||||
} else {
|
||||
startRecording()
|
||||
}
|
||||
}
|
||||
},
|
||||
onRelease = {
|
||||
wantsToRecord = false
|
||||
val result = mediaRecorder.value?.stop()
|
||||
mediaRecorder.value = null
|
||||
if (result != null) {
|
||||
onRecordTaken(result)
|
||||
} else {
|
||||
// less disruptive than error messages
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
stringRes(context, R.string.record_a_message_description),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
},
|
||||
onCancel = {
|
||||
wantsToRecord = false
|
||||
mediaRecorder.value?.stop()
|
||||
mediaRecorder.value = null
|
||||
},
|
||||
content = @Composable { isRecording -> content(isRecording, elapsedSeconds) },
|
||||
content = { active -> content(active, elapsedSeconds) },
|
||||
)
|
||||
}
|
||||
|
||||
+4
-4
@@ -35,7 +35,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FiberManualRecord
|
||||
import androidx.compose.material.icons.filled.Stop
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -206,8 +206,8 @@ fun FloatingRecordingIndicator(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = innerPadding),
|
||||
) {
|
||||
// Pulsing red dot
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "recording_dot")
|
||||
// Pulsing stop square
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "recording_stop")
|
||||
val dotAlpha by infiniteTransition.animateFloat(
|
||||
initialValue = 1f,
|
||||
targetValue = 0.5f,
|
||||
@@ -220,7 +220,7 @@ fun FloatingRecordingIndicator(
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.FiberManualRecord,
|
||||
imageVector = Icons.Default.Stop,
|
||||
contentDescription = recordingLabel,
|
||||
tint = Color.White,
|
||||
modifier =
|
||||
|
||||
+14
-4
@@ -26,7 +26,6 @@ import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
@@ -71,7 +70,6 @@ fun AudioWaveformReadOnly(
|
||||
progressBrush: Brush = SolidColor(Color.Blue),
|
||||
waveformAlignment: WaveformAlignment = WaveformAlignment.Center,
|
||||
amplitudeType: AmplitudeType = AmplitudeType.Avg,
|
||||
onProgressChangeFinished: (() -> Unit)? = null,
|
||||
spikeAnimationSpec: AnimationSpec<Float> = tween(500),
|
||||
spikeWidth: Dp = 3.dp,
|
||||
spikeRadius: Dp = 2.dp,
|
||||
@@ -80,7 +78,6 @@ fun AudioWaveformReadOnly(
|
||||
amplitudes: List<Float>,
|
||||
onProgressChange: (Float) -> Unit,
|
||||
) {
|
||||
val backgroundColor = MaterialTheme.colorScheme.background
|
||||
val progressState = remember(progress) { progress.coerceIn(MIN_PROGRESS, MAX_PROGRESS) }
|
||||
val spikeWidthState =
|
||||
remember(spikeWidth) { spikeWidth.coerceIn(MinSpikeWidthDp, MaxSpikeWidthDp) }
|
||||
@@ -195,7 +192,20 @@ internal fun <T> Iterable<T>.chunkToSize(
|
||||
internal fun Iterable<Float>.normalize(
|
||||
min: Float,
|
||||
max: Float,
|
||||
): List<Float> = map { (max - min) * ((it - min()) / (max() - min())) + min }
|
||||
): List<Float> {
|
||||
val values = toList()
|
||||
if (values.isEmpty()) return emptyList()
|
||||
|
||||
val currentMin = values.minOrNull() ?: return emptyList()
|
||||
val currentMax = values.maxOrNull() ?: return emptyList()
|
||||
val range = currentMax - currentMin
|
||||
if (!range.isFinite() || range == 0f) {
|
||||
return List(values.size) { min }
|
||||
}
|
||||
|
||||
val scale = max - min
|
||||
return values.map { scale * ((it - currentMin) / range) + min }
|
||||
}
|
||||
|
||||
private fun Int.safeDiv(value: Int): Float {
|
||||
return if (value == 0) return 0F else this / value.toFloat()
|
||||
|
||||
@@ -28,17 +28,12 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
@@ -64,116 +59,6 @@ fun ClickableBox(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClickAndHoldBox(
|
||||
modifier: Modifier = Modifier,
|
||||
onPress: () -> Unit,
|
||||
onRelease: () -> Unit,
|
||||
content: @Composable (Boolean) -> Unit,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
LaunchedEffect(isPressed) {
|
||||
if (isPressed) {
|
||||
// Button is pressed
|
||||
onPress()
|
||||
} else {
|
||||
// Button is released
|
||||
onRelease()
|
||||
}
|
||||
}
|
||||
|
||||
// Animation for the button scale
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isPressed) 1.5f else 1.0f, // Scale up when recording
|
||||
animationSpec = tween(durationMillis = 150), // Smooth animation
|
||||
)
|
||||
|
||||
// Animation for the button color
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (isPressed) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.background,
|
||||
animationSpec = tween(durationMillis = 150),
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier
|
||||
.scale(scale)
|
||||
.background(backgroundColor, CircleShape)
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = ripple24dp,
|
||||
onClick = { },
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
content(isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClickAndHoldBoxComposable(
|
||||
modifier: Modifier = Modifier,
|
||||
onPress: () -> Unit,
|
||||
onRelease: suspend () -> Unit,
|
||||
onCancel: suspend () -> Unit,
|
||||
content: @Composable (Boolean) -> Unit,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
var isPressed by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(interactionSource) {
|
||||
val pressInteractions = mutableListOf<PressInteraction.Press>()
|
||||
interactionSource.interactions.collect { interaction ->
|
||||
when (interaction) {
|
||||
is PressInteraction.Press -> {
|
||||
if (pressInteractions.isEmpty()) {
|
||||
onPress()
|
||||
}
|
||||
pressInteractions.add(interaction)
|
||||
}
|
||||
is PressInteraction.Release -> {
|
||||
onRelease()
|
||||
pressInteractions.remove(interaction.press)
|
||||
}
|
||||
is PressInteraction.Cancel -> {
|
||||
onCancel()
|
||||
pressInteractions.remove(interaction.press)
|
||||
}
|
||||
}
|
||||
isPressed = pressInteractions.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
// Animation for the button scale
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isPressed) 1.5f else 1.0f, // Scale up when recording
|
||||
animationSpec = tween(durationMillis = 150), // Smooth animation
|
||||
)
|
||||
|
||||
// Animation for the button color
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (isPressed) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.background,
|
||||
animationSpec = tween(durationMillis = 150),
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier
|
||||
.scale(scale)
|
||||
.background(backgroundColor, CircleShape)
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
interactionSource = interactionSource,
|
||||
indication = ripple24dp,
|
||||
onClick = { },
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
content(isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ClickableBox(
|
||||
@@ -195,3 +80,38 @@ fun ClickableBox(
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToggleableBox(
|
||||
modifier: Modifier = Modifier,
|
||||
isActive: Boolean,
|
||||
onClick: () -> Unit,
|
||||
content: @Composable (Boolean) -> Unit,
|
||||
) {
|
||||
// Animation for the button scale
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isActive) 1.5f else 1.0f,
|
||||
animationSpec = tween(durationMillis = 150),
|
||||
)
|
||||
|
||||
// Animation for the button color
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (isActive) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.background,
|
||||
animationSpec = tween(durationMillis = 150),
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier
|
||||
.scale(scale)
|
||||
.background(backgroundColor, CircleShape)
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple24dp,
|
||||
onClick = onClick,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
content(isActive)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90Status
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNipContent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPoll
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPostApproval
|
||||
@@ -163,15 +164,15 @@ import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.bounties.bountyBaseReward
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.isAFork
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geoHashOrScope
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip13Pow.strongPoWOrNull
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
@@ -727,6 +728,7 @@ private fun RenderNoteRow(
|
||||
is ReportEvent -> RenderReport(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||
is LongTextNoteEvent -> RenderLongFormContent(baseNote, accountViewModel, nav)
|
||||
is WikiNoteEvent -> RenderWikiContent(baseNote, accountViewModel, nav)
|
||||
is NipTextEvent -> RenderNipContent(baseNote, accountViewModel, nav)
|
||||
is BadgeAwardEvent -> RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
|
||||
is FhirResourceEvent -> RenderFhirResource(baseNote, accountViewModel, nav)
|
||||
is PeopleListEvent -> DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
||||
@@ -1085,8 +1087,8 @@ fun SecondUserInfoRow(
|
||||
modifier = UserNameMaxRowHeight,
|
||||
) {
|
||||
Column(modifier = remember(noteEvent) { Modifier.weight(1f) }) {
|
||||
if (noteEvent is BaseThreadedEvent && noteEvent.isAFork()) {
|
||||
ShowForkInformation(noteEvent, remember(noteEvent) { Modifier.weight(1f) }, accountViewModel, nav)
|
||||
if (noteEvent is IForkableEvent && noteEvent.isAFork()) {
|
||||
ShowForkInformation(noteEvent, Modifier, accountViewModel, nav)
|
||||
} else {
|
||||
ObserveDisplayNip05Status(noteAuthor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@@ -159,6 +159,7 @@ import com.vitorpamplona.amethyst.ui.theme.reactionBox
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.selectedReactionBoxModifier
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
|
||||
@@ -1394,16 +1395,20 @@ private fun BoostTypeChoicePopup(
|
||||
Text(stringRes(R.string.quote), color = Color.White, textAlign = TextAlign.Center)
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = onFork,
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
) {
|
||||
Text(stringRes(R.string.fork), color = Color.White, textAlign = TextAlign.Center)
|
||||
// removes the option to fork for now because we do not have screens for
|
||||
// LongForm, Wiki and NIP posting.
|
||||
if (baseNote.event is TextNoteEvent) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = onFork,
|
||||
shape = ButtonBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
) {
|
||||
Text(stringRes(R.string.fork), color = Color.White, textAlign = TextAlign.Center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,39 +21,29 @@
|
||||
package com.vitorpamplona.amethyst.ui.note.elements
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUser
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.appendLink
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.nip05
|
||||
import com.vitorpamplona.quartz.experimental.forks.forkFromAddress
|
||||
import com.vitorpamplona.quartz.experimental.forks.forkFromVersion
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
|
||||
@Composable
|
||||
fun ShowForkInformation(
|
||||
noteEvent: BaseThreadedEvent,
|
||||
noteEvent: IForkableEvent,
|
||||
modifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
@@ -67,7 +57,7 @@ fun ShowForkInformation(
|
||||
}
|
||||
}
|
||||
} else if (forkedEvent != null) {
|
||||
LoadNote(forkedEvent.eventId, accountViewModel) { event ->
|
||||
LoadNote(forkedEvent, accountViewModel) { event ->
|
||||
if (event != null) {
|
||||
ForkInformationRowLightColor(event, modifier, accountViewModel, nav)
|
||||
}
|
||||
@@ -83,35 +73,19 @@ fun ForkInformationRowLightColor(
|
||||
nav: INav,
|
||||
) {
|
||||
val noteState by observeNote(originalVersion, accountViewModel)
|
||||
val note = noteState?.note ?: return
|
||||
val note = noteState.note
|
||||
val author = note.author ?: return
|
||||
val route = remember(note) { routeFor(note, accountViewModel.account) }
|
||||
|
||||
if (route != null) {
|
||||
Row(modifier) {
|
||||
Text(
|
||||
text =
|
||||
buildAnnotatedString {
|
||||
appendLink(stringRes(id = R.string.forked_from) + " ") {
|
||||
nav.nav(route)
|
||||
}
|
||||
},
|
||||
style =
|
||||
LocalTextStyle.current.copy(
|
||||
color = MaterialTheme.colorScheme.nip05,
|
||||
fontSize = Font14SP,
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Visible,
|
||||
)
|
||||
|
||||
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
val userState by observeUser(author, accountViewModel)
|
||||
userState?.user?.toBestDisplayName()?.let {
|
||||
CreateClickableTextWithEmoji(
|
||||
clickablePart = it,
|
||||
clickablePart = stringRes(id = R.string.forked_from) + " " + it,
|
||||
maxLines = 1,
|
||||
route = route,
|
||||
overrideColor = MaterialTheme.colorScheme.nip05,
|
||||
overrideColor = MaterialTheme.colorScheme.primary,
|
||||
fontSize = Font14SP,
|
||||
nav = nav,
|
||||
tags = userState?.user?.info?.tags,
|
||||
@@ -120,35 +94,3 @@ fun ForkInformationRowLightColor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ForkInformationRow(
|
||||
originalVersion: Note,
|
||||
modifier: Modifier = Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteState by observeNote(originalVersion, accountViewModel)
|
||||
val note = noteState?.note ?: return
|
||||
val route = remember(note) { routeFor(note, accountViewModel.account) }
|
||||
|
||||
if (route != null) {
|
||||
Row(modifier) {
|
||||
val author = note.author ?: return
|
||||
val meta by observeUserInfo(author, accountViewModel)
|
||||
|
||||
Text(stringRes(id = R.string.forked_from))
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
|
||||
val userMetadata by observeUserInfo(author, accountViewModel)
|
||||
|
||||
CreateClickableTextWithEmoji(
|
||||
clickablePart = remember(meta) { meta?.bestName() ?: author.pubkeyDisplayHex() },
|
||||
maxLines = 1,
|
||||
route = route,
|
||||
nav = nav,
|
||||
tags = userMetadata?.tags,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) 2025 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.amethyst.ui.note.types
|
||||
|
||||
import android.R.attr.label
|
||||
import android.R.attr.maxLines
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.kinds
|
||||
|
||||
@Composable
|
||||
fun RenderNipContent(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? NipTextEvent ?: return
|
||||
|
||||
NipNoteHeader(noteEvent, note, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NipNoteHeader(
|
||||
noteEvent: NipTextEvent,
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val title = remember(noteEvent) { noteEvent.title() }
|
||||
val kinds = remember(noteEvent) { noteEvent.kinds() }
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = Size5dp)
|
||||
.clip(shape = QuoteBorder)
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.subtleBorder,
|
||||
QuoteBorder,
|
||||
),
|
||||
verticalArrangement = SpacedBy5dp,
|
||||
) {
|
||||
title?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp, top = 10.dp),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = remember(noteEvent) { noteEvent.summary() ?: noteEvent.content },
|
||||
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,
|
||||
)
|
||||
if (kinds.isNotEmpty()) {
|
||||
FlowRow(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
||||
horizontalArrangement = SpacedBy5dp,
|
||||
verticalArrangement = SpacedBy5dp,
|
||||
itemVerticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.kinds),
|
||||
)
|
||||
kinds.forEach {
|
||||
NoPaddingSuggestionChip(
|
||||
label = it.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun NipNoteHeaderPreview() {
|
||||
val event =
|
||||
NipTextEvent(
|
||||
id = "eb2b05394ff0014bb6a79c2eacfd1c80696821592f4dbf86c950c4bf16614aa0",
|
||||
pubKey = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
createdAt = 1767978398,
|
||||
content = "Trusted Translations\n--------------------\n\n`draft` `optional`\n\nThis NIP allows anyone to post a translation for any event on a `kind:76`.\n\n```js\n{\n \"kind\": 76,\n \"tags\": [\n [\"e\", \"\u003coriginal_event_id\u003e\", \"\u003crelay\u003e\"]\n [\"k\", \"\u003coriginal_event_kind\u003e\"]\n [\"l\", \"\u003ctranslated_to_language_country\u003e\"], // ISO 639-1: \"en\", \"es\", ...\n [\"l\", \"\u003ctranslated_to_language_code\u003e\"], // ISO 639-1: \"en-us\", \"en-br\"\n [\"s\", \"title\", \"translated title tag\"]\n [\"s\", \"summary\", \"translated summary tag\"]\n ],\n \"content\": \"this is a translated version of the original content\",\n // ...other fields\n}\n```\n\n`e` tag points to the event being translated, `k` tag points to the kind of that event.\n\n`l` tags define the language this was translated to in lowercase codes as defined by ISO 639-1. \n\n`s` tags are the translations for tags in the original event. \n\n`.content` contains the translation of the original `.content`\n\nClients SHOULD request translations by `e` and `l` tags spoken by their user. For every tag being rendered, Clients SHOULD look for their translated versions.\n\nProviders SHOULD use the event id in the filter to know which events need translations.\n\nProviders MAY store their translations behind a paid relay with NIP-42 auth.\n\n## Declaring Translation Providers\n\nKind `10041` lists the user's authorized translation providers. Each `p` tag is followed by the `pubkey` of the service publishing kind 76s, and the relay translations can be found. Users can specify these publicly or privately by JSON-stringifying and encrypting the tag list in the `.content` using NIP-44. \n\n```js\n{\n \"kind\": 10041,\n \"tags\": [\n [\"p\", \"4fd5e210530e4f6b2cb083795834bfe5108324f1ed9f00ab73b9e8fcfe5f12fe\", \"wss://translations.nostr.com\"],\n [\"l\", \"\u003ctranslated_to_language_country\u003e\"], // ISO 639-1: \"en\", \"es\", ...\n [\"l\", \"\u003ctranslated_to_language_code\u003e\"], // ISO 639-1: \"en-us\", \"en-br\"\n //...\n}\n```\n\n`l` tags in this event are the languages the user understands and wants translations to.\n\nProviders SHOULD create the `10041` event and post to the user's outbox relay.",
|
||||
sig = "7fa9f1d49c41c7bfbdad6d089d1c6685777c86de8fbda7662a630888658839f0444cb1398385f759461c09191b287fa222eec0ffe22b3fa8cf740f71aab9dc21",
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("d", "trusted-translations"),
|
||||
arrayOf("title", "Trusted Translations"),
|
||||
arrayOf("k", "1011"),
|
||||
arrayOf("k", "10041"),
|
||||
arrayOf("k", "1011"),
|
||||
arrayOf("k", "10041"),
|
||||
arrayOf("k", "1011"),
|
||||
arrayOf("k", "10041"),
|
||||
arrayOf("k", "1011"),
|
||||
arrayOf("k", "10041"),
|
||||
arrayOf("client", "nostrhub.io"),
|
||||
),
|
||||
)
|
||||
|
||||
LocalCache.justConsume(event, null, true)
|
||||
val note = LocalCache.getOrCreateNote(event.id)
|
||||
|
||||
ThemeComparisonColumn(
|
||||
toPreview = {
|
||||
NipNoteHeader(
|
||||
noteEvent = event,
|
||||
note = note,
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NoPaddingSuggestionChip(
|
||||
label: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.extraSmall, // Use a small shape for chip look
|
||||
color = MaterialTheme.colorScheme.secondaryContainer, // Default chip color
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
// Apply desired internal padding to the Text itself
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -392,6 +393,7 @@ val DEFAULT_FEED_KINDS =
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
LiveActivitiesEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
)
|
||||
|
||||
@@ -406,6 +408,7 @@ val DEFAULT_COMMUNITY_FEEDS =
|
||||
AudioTrackEvent.KIND,
|
||||
PinListEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
)
|
||||
|
||||
+3
-1
@@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
@@ -49,12 +50,13 @@ class FollowPackFeedNewThreadFeedFilter(
|
||||
val followPackNote: AddressableNote,
|
||||
val account: Account,
|
||||
) : AdditiveFeedFilter<Note>() {
|
||||
companion object Companion {
|
||||
companion object {
|
||||
val ADDRESSABLE_KINDS =
|
||||
listOf(
|
||||
AudioTrackEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
ClassifiedsEvent.KIND,
|
||||
LongTextNoteEvent.KIND,
|
||||
)
|
||||
|
||||
+2
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip22Commen
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
@@ -58,6 +59,7 @@ val PostsByHashtagKinds2 =
|
||||
InteractiveStorySceneEvent.KIND,
|
||||
AudioTrackEvent.KIND,
|
||||
AudioHeaderEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
)
|
||||
|
||||
fun filterPostsByHashtags(
|
||||
|
||||
+33
-16
@@ -35,6 +35,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Mic
|
||||
import androidx.compose.material.icons.filled.Stop
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -53,6 +54,7 @@ import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordAudioBox
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceMessagePreview
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.formatSecondsToTime
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.Nav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.PostingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
@@ -212,29 +214,44 @@ private fun ReRecordButton(viewModel: VoiceReplyViewModel) {
|
||||
onRecordTaken = { recording ->
|
||||
viewModel.selectRecording(recording)
|
||||
},
|
||||
) { isRecording, _ ->
|
||||
) { isRecording, elapsedSeconds ->
|
||||
val contentColor =
|
||||
if (isRecording) {
|
||||
MaterialTheme.colorScheme.onPrimary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
}
|
||||
val icon =
|
||||
if (isRecording) {
|
||||
Icons.Default.Stop
|
||||
} else {
|
||||
Icons.Default.Mic
|
||||
}
|
||||
val label =
|
||||
if (isRecording) {
|
||||
formatSecondsToTime(elapsedSeconds)
|
||||
} else {
|
||||
stringRes(id = R.string.re_record)
|
||||
}
|
||||
val iconDescription =
|
||||
if (isRecording) {
|
||||
stringRes(id = R.string.recording_indicator_description)
|
||||
} else {
|
||||
stringRes(id = R.string.record_a_message)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Mic,
|
||||
contentDescription = stringRes(id = R.string.record_a_message),
|
||||
tint =
|
||||
if (isRecording) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
},
|
||||
imageVector = icon,
|
||||
contentDescription = iconDescription,
|
||||
tint = contentColor,
|
||||
)
|
||||
Text(
|
||||
text = stringRes(id = R.string.re_record),
|
||||
color =
|
||||
if (isRecording) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
},
|
||||
text = label,
|
||||
color = contentColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip22Comments.filterHomePostsByScopes
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -52,6 +53,7 @@ val HomePostsBuHashtagsKinds =
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
VoiceEvent.KIND,
|
||||
VoiceReplyEvent.KIND,
|
||||
)
|
||||
|
||||
+2
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthors
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
@@ -53,6 +54,7 @@ val HomePostsNewThreadKinds =
|
||||
LongTextNoteEvent.KIND,
|
||||
HighlightEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
)
|
||||
|
||||
+3
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip72Commu
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.community.SingleCommunityTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -41,6 +42,7 @@ val HomePostsFromCommunityKinds =
|
||||
ClassifiedsEvent.KIND,
|
||||
HighlightEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
@@ -53,6 +55,7 @@ val HomePostsFromCommunityKindsStr =
|
||||
ClassifiedsEvent.KIND.toString(),
|
||||
HighlightEvent.KIND.toString(),
|
||||
WikiNoteEvent.KIND.toString(),
|
||||
NipTextEvent.KIND.toString(),
|
||||
CommunityPostApprovalEvent.KIND.toString(),
|
||||
CommentEvent.KIND.toString(),
|
||||
InteractiveStoryPrologueEvent.KIND.toString(),
|
||||
|
||||
+21
-8
@@ -29,13 +29,13 @@ import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.forkFromVersion
|
||||
import com.vitorpamplona.quartz.experimental.forks.isForkFromAddressWithPubkey
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
@@ -77,6 +77,7 @@ class NotificationFeedFilter(
|
||||
listOf(
|
||||
AudioTrackEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
ClassifiedsEvent.KIND,
|
||||
LongTextNoteEvent.KIND,
|
||||
CalendarTimeSlotEvent.KIND,
|
||||
@@ -185,7 +186,7 @@ class NotificationFeedFilter(
|
||||
return true
|
||||
}
|
||||
|
||||
if (event is BaseThreadedEvent) {
|
||||
if (event is BaseNoteEvent) {
|
||||
if (note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true) {
|
||||
return true
|
||||
}
|
||||
@@ -208,11 +209,23 @@ class NotificationFeedFilter(
|
||||
}
|
||||
|
||||
val isAuthoredPostCited = event.findCitations().any { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex == authorHex }
|
||||
val isAuthorDirectlyCited = event.citedUsers().contains(authorHex)
|
||||
val isAuthorOfAFork =
|
||||
event.isForkFromAddressWithPubkey(authorHex) || (event.forkFromVersion()?.let { LocalCache.getNoteIfExists(it.eventId)?.author?.pubkeyHex == authorHex } == true)
|
||||
|
||||
return isAuthoredPostCited || isAuthorDirectlyCited || isAuthorOfAFork
|
||||
if (isAuthoredPostCited) return true
|
||||
|
||||
val isAuthorDirectlyCited = event.citedUsers().contains(authorHex)
|
||||
|
||||
if (isAuthorDirectlyCited) return true
|
||||
|
||||
return if (event is IForkableEvent && event.isAFork()) {
|
||||
val address = event.forkFromAddress()
|
||||
val version = event.forkFromVersion()
|
||||
|
||||
// Displays notifications about forks
|
||||
address?.pubKeyHex == authorHex ||
|
||||
(version?.let { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex == authorHex } == true)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
if (event is ReactionEvent) {
|
||||
|
||||
+2
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
@@ -58,6 +59,7 @@ val UserProfilePostKinds2 =
|
||||
listOf(
|
||||
TorrentEvent.KIND,
|
||||
TorrentCommentEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
VoiceReplyEvent.KIND,
|
||||
|
||||
+2
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
@@ -76,6 +77,7 @@ class UserProfileMutualFeedFilter(
|
||||
it.event is GenericRepostEvent ||
|
||||
it.event is LongTextNoteEvent ||
|
||||
it.event is WikiNoteEvent ||
|
||||
it.event is NipTextEvent ||
|
||||
it.event is PollNoteEvent ||
|
||||
it.event is HighlightEvent ||
|
||||
it.event is InteractiveStoryPrologueEvent ||
|
||||
|
||||
+2
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -77,6 +78,7 @@ class UserProfileNewThreadFeedFilter(
|
||||
it.event is GenericRepostEvent ||
|
||||
it.event is LongTextNoteEvent ||
|
||||
it.event is WikiNoteEvent ||
|
||||
it.event is NipTextEvent ||
|
||||
it.event is PollNoteEvent ||
|
||||
it.event is HighlightEvent ||
|
||||
it.event is InteractiveStoryPrologueEvent ||
|
||||
|
||||
+11
-17
@@ -111,9 +111,9 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayFollowingHashtagsInPos
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayLocation
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayPoW
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayReward
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ForkInformationRow
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.Reward
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ShowForkInformation
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.note.observeEdits
|
||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||
@@ -185,7 +185,7 @@ import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.bounties.bountyBaseReward
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.forkFromAddress
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
@@ -478,11 +478,15 @@ private fun FullBleedNoteCompose(
|
||||
Column(
|
||||
remember { Modifier.weight(1f) },
|
||||
) {
|
||||
ObserveDisplayNip05Status(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
if (noteEvent is IForkableEvent && noteEvent.isAFork()) {
|
||||
ShowForkInformation(noteEvent, Modifier, accountViewModel, nav)
|
||||
} else {
|
||||
ObserveDisplayNip05Status(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val geo = remember { noteEvent.geoHashOrScope() }
|
||||
@@ -1078,8 +1082,6 @@ private fun RenderWikiHeaderForThread(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val forkedAddress = remember(noteEvent) { noteEvent.forkFromAddress() }
|
||||
|
||||
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
|
||||
Column {
|
||||
noteEvent.image()?.let {
|
||||
@@ -1105,14 +1107,6 @@ private fun RenderWikiHeaderForThread(
|
||||
)
|
||||
}
|
||||
|
||||
forkedAddress?.let {
|
||||
LoadAddressableNote(it, accountViewModel) { originalVersion ->
|
||||
if (originalVersion != null) {
|
||||
ForkInformationRow(originalVersion, Modifier.fillMaxWidth(), accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noteEvent
|
||||
.summary()
|
||||
?.ifBlank { null }
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<string name="could_not_decrypt_the_message">Nepodařilo se dešifrovat zprávu</string>
|
||||
<string name="group_picture">Obrázek skupiny</string>
|
||||
<string name="explicit_content">Explicitní obsah</string>
|
||||
<string name="relay_notice">Oznámení relé</string>
|
||||
<string name="duplicated_post">Duplicitní příspěvek</string>
|
||||
<string name="spam">Nevyžádaná pošta</string>
|
||||
<string name="spam_description">Počet spamových událostí z tohoto relé</string>
|
||||
<string name="impersonation">Zneužívání identity</string>
|
||||
@@ -121,6 +123,7 @@
|
||||
<string name="relay_address">Adresa relé</string>
|
||||
<string name="posts">Příspěvky</string>
|
||||
<string name="bytes">Byty</string>
|
||||
<string name="error">Chyba</string>
|
||||
<string name="errors">Chyby</string>
|
||||
<string name="errors_description">Počet chyb připojení v této relaci</string>
|
||||
<string name="home_feed">Domovský kanál</string>
|
||||
@@ -132,6 +135,8 @@
|
||||
<string name="add_a_user">Přidat uživatele</string>
|
||||
<string name="add_a_relay">Přidat přeposílání</string>
|
||||
<string name="profile_name">Jméno</string>
|
||||
<string name="profile_name_with_explainer">Jméno (pro @tagování)</string>
|
||||
<string name="my_name">Moje @tag jméno</string>
|
||||
<string name="display_name">Zobrazované jméno</string>
|
||||
<string name="my_display_name">Moje zobrazované jméno</string>
|
||||
<string name="my_awesome_name">Ostrich McAwesome</string>
|
||||
@@ -358,6 +363,8 @@
|
||||
<string name="block_only">Blokovat</string>
|
||||
<string name="manual_zaps">Manuální zap rozdělení</string>
|
||||
<string name="bookmarks">Záložky</string>
|
||||
<string name="bookmarks_title">Výchozí záložky</string>
|
||||
<string name="bookmarks_explainer">Vaše výchozí záložky, které mnoho klientů podporuje</string>
|
||||
<string name="drafts">Koncepty</string>
|
||||
<string name="private_bookmarks">Soukromé záložky</string>
|
||||
<string name="public_bookmarks">Veřejné záložky</string>
|
||||
@@ -639,28 +646,64 @@
|
||||
<string name="read_from_relay_description">Množství bajtů, které bylo přijato z tohoto relé, včetně filtrů a událostí</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Při pokusu o získání informací z Relay se vyskytla chyba z %1$s</string>
|
||||
<string name="owner">Vlastník</string>
|
||||
<string name="self">Servisní klíč</string>
|
||||
<string name="running_software">Spuštěno %1$s</string>
|
||||
<string name="running_software_version">Spuštěno %1$s (%2$s)</string>
|
||||
<string name="version">Verze</string>
|
||||
<string name="software">Programové vybavení</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="supports">Podporované NIPs</string>
|
||||
<string name="supported_grasps">Podporované GRASPy</string>
|
||||
<string name="admission_fees">Vstupné</string>
|
||||
<string name="admission">Vstup</string>
|
||||
<string name="subscription">Předplatné</string>
|
||||
<string name="publication">Publikování</string>
|
||||
<string name="payment_link">Platby %1$s</string>
|
||||
<string name="payments_url">URL plateb</string>
|
||||
<string name="target_audience">Cílové publikum</string>
|
||||
<string name="policies_and_links">Zásady & odkazy</string>
|
||||
<string name="fees_and_payments">Poplatky & platby</string>
|
||||
<string name="limitations">Omezení</string>
|
||||
<string name="countries">Země</string>
|
||||
<string name="languages">Jazyky</string>
|
||||
<string name="tags">Tagy</string>
|
||||
<string name="topics">Témata</string>
|
||||
<string name="all_countries">Všechny země</string>
|
||||
<string name="all_languages">Všechny jazyky</string>
|
||||
<string name="posting_policy">Politika příspěvků</string>
|
||||
<string name="privacy_policy">Zásady ochrany soukromí</string>
|
||||
<string name="terms_and_conditions">Podmínky & ujednání</string>
|
||||
<string name="not_available_acronym">N/A</string>
|
||||
<string name="relay_error_messages">Chyby a upozornění z tohoto relé</string>
|
||||
<string name="message_length">Délka zprávy</string>
|
||||
<string name="subscriptions">Předplatné</string>
|
||||
<string name="filters">Filtry</string>
|
||||
<string name="subscription_id_length">Délka ID předplatného</string>
|
||||
<string name="minimum_prefix">Minimální předpona</string>
|
||||
<string name="maximum_event_tags">Maximální počet značek události</string>
|
||||
<string name="minimum_prefix">Min. předpona</string>
|
||||
<string name="maximum_event_tags">Max. značek události</string>
|
||||
<string name="content_length">Délka obsahu</string>
|
||||
<string name="minimum_pow">Minimální PoW</string>
|
||||
<string name="max_content_length">Max. délka obsahu</string>
|
||||
<string name="discards_older_than">Zahazuje starší než</string>
|
||||
<string name="accepts_up_to">Přijímá až</string>
|
||||
<string name="time_in_the_future">%1$s v budoucnu</string>
|
||||
<string name="time_in_the_past">před %1$s</string>
|
||||
<string name="amount_in_zeros">%1$s nul</string>
|
||||
<string name="amount_in_bits">%1$s bitů</string>
|
||||
<string name="event_retention">Uchovávání událostí</string>
|
||||
<string name="content_size">Velikost obsahu</string>
|
||||
<string name="connectivity">Připojení</string>
|
||||
<string name="access_control">Řízení přístupu</string>
|
||||
<string name="minimum_pow">Min. obtížnost PoW</string>
|
||||
<string name="auth">Ověření</string>
|
||||
<string name="auth_required">Vyžaduje se autentizace</string>
|
||||
<string name="payment">Platba</string>
|
||||
<string name="payment_required">Vyžaduje se platba</string>
|
||||
<string name="max_message_length">Max. délka zprávy</string>
|
||||
<string name="max_subs">Max. odběrů</string>
|
||||
<string name="max_filters_per_sub">Max. filtrů na odběr</string>
|
||||
<string name="max_limit_events_returning">Max. limit (vracené události)</string>
|
||||
<string name="default_limit">Výchozí limit (vracené události)</string>
|
||||
<string name="max_subid_length">Max. délka SubID</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Vyměnit</string>
|
||||
<string name="cashu_redeem_to_zap">Poslat do Zap peněženky</string>
|
||||
@@ -1184,4 +1227,5 @@
|
||||
<string name="follow_pack_broadcast">Broadcast sady doporučení</string>
|
||||
<string name="follow_set_delete">Smazat seznam</string>
|
||||
<string name="follow_pack_delete">Smazat sadu doporučení</string>
|
||||
<string name="kinds">Typy</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<string name="could_not_decrypt_the_message">Nachricht konnte nicht entschlüsselt werden</string>
|
||||
<string name="group_picture">Gruppenbild</string>
|
||||
<string name="explicit_content">Anstößiger Inhalt</string>
|
||||
<string name="relay_notice">Relay-Hinweis</string>
|
||||
<string name="duplicated_post">Doppelter Beitrag</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="spam_description">Anzahl der Spam-Ereignisse von diesem Relais</string>
|
||||
<string name="impersonation">Vortäuschung</string>
|
||||
@@ -121,6 +123,7 @@
|
||||
<string name="relay_address">Relay-Adresse</string>
|
||||
<string name="posts">Beiträge</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="error">Fehler</string>
|
||||
<string name="errors">Fehler</string>
|
||||
<string name="errors_description">Anzahl der Verbindungsfehler in dieser Sitzung</string>
|
||||
<string name="home_feed">Startseite</string>
|
||||
@@ -132,6 +135,8 @@
|
||||
<string name="add_a_user">Benutzer hinzufügen</string>
|
||||
<string name="add_a_relay">Relay hinzufügen</string>
|
||||
<string name="profile_name">Name</string>
|
||||
<string name="profile_name_with_explainer">Name (zum @Taggen)</string>
|
||||
<string name="my_name">Mein @tag-Name</string>
|
||||
<string name="display_name">Anzeigename</string>
|
||||
<string name="my_display_name">Mein Anzeigename</string>
|
||||
<string name="my_awesome_name">Ostrich McAwesome</string>
|
||||
@@ -364,6 +369,8 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="block_only">Blockieren</string>
|
||||
<string name="manual_zaps">Manuelle Zap-Splits</string>
|
||||
<string name="bookmarks">Lesezeichen</string>
|
||||
<string name="bookmarks_title">Standard-Lesezeichen</string>
|
||||
<string name="bookmarks_explainer">Deine Standard-Lesezeichen, die viele Clients unterstützen</string>
|
||||
<string name="drafts">Entwürfe</string>
|
||||
<string name="private_bookmarks">Private Lesezeichen</string>
|
||||
<string name="public_bookmarks">Öffentliche Lesezeichen</string>
|
||||
@@ -644,28 +651,64 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="read_from_relay_description">Die Menge in Bytes, die von diesem Relais empfangen wurde, einschließlich Filter und Ereignisse</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten</string>
|
||||
<string name="owner">Inhaber</string>
|
||||
<string name="self">Service-Schlüssel</string>
|
||||
<string name="running_software">%1$s wird ausgeführt</string>
|
||||
<string name="running_software_version">%1$s wird ausgeführt (%2$s)</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="software">Programme</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="supports">Unterstützte NIPs</string>
|
||||
<string name="supported_grasps">Unterstützte GRASPs</string>
|
||||
<string name="admission_fees">Eintrittsgebühren</string>
|
||||
<string name="admission">Zutritt</string>
|
||||
<string name="subscription">Abonnement</string>
|
||||
<string name="publication">Veröffentlichung</string>
|
||||
<string name="payment_link">Zahlungen %1$s</string>
|
||||
<string name="payments_url">Zahlungs-URL</string>
|
||||
<string name="target_audience">Zielgruppe</string>
|
||||
<string name="policies_and_links">Richtlinien & Links</string>
|
||||
<string name="fees_and_payments">Gebühren & Zahlungen</string>
|
||||
<string name="limitations">Einschränkungen</string>
|
||||
<string name="countries">Länder</string>
|
||||
<string name="languages">Sprachen</string>
|
||||
<string name="tags">Tags</string>
|
||||
<string name="topics">Themen</string>
|
||||
<string name="all_countries">Alle Länder</string>
|
||||
<string name="all_languages">Alle Sprachen</string>
|
||||
<string name="posting_policy">Veröffentlichungsrichtlinie</string>
|
||||
<string name="privacy_policy">Datenschutzerklärung</string>
|
||||
<string name="terms_and_conditions">Allgemeine Geschäftsbedingungen</string>
|
||||
<string name="not_available_acronym">N/A</string>
|
||||
<string name="relay_error_messages">Fehler und Hinweise von diesem Relais</string>
|
||||
<string name="message_length">Nachrichtenlänge</string>
|
||||
<string name="subscriptions">Abonnements</string>
|
||||
<string name="filters">Filter</string>
|
||||
<string name="subscription_id_length">Länge der Abonnement-ID</string>
|
||||
<string name="minimum_prefix">Mindestpräfix</string>
|
||||
<string name="maximum_event_tags">Maximale Anzahl von Ereignis-Tags</string>
|
||||
<string name="maximum_event_tags">Max. Event-Tags</string>
|
||||
<string name="content_length">Inhaltslänge</string>
|
||||
<string name="minimum_pow">Mindest-PoW</string>
|
||||
<string name="max_content_length">Max. Inhaltslänge</string>
|
||||
<string name="discards_older_than">Verwirft älter als</string>
|
||||
<string name="accepts_up_to">Akzeptiert bis zu</string>
|
||||
<string name="time_in_the_future">%1$s in der Zukunft</string>
|
||||
<string name="time_in_the_past">vor %1$s</string>
|
||||
<string name="amount_in_zeros">%1$s Nullen</string>
|
||||
<string name="amount_in_bits">%1$s Bits</string>
|
||||
<string name="event_retention">Ereignis-Aufbewahrung</string>
|
||||
<string name="content_size">Inhaltsgröße</string>
|
||||
<string name="connectivity">Verbindung</string>
|
||||
<string name="access_control">Zugriffskontrolle</string>
|
||||
<string name="minimum_pow">Min. PoW-Schwierigkeit</string>
|
||||
<string name="auth">Authentifizierung</string>
|
||||
<string name="auth_required">Authentifizierung erforderlich</string>
|
||||
<string name="payment">Zahlung</string>
|
||||
<string name="payment_required">Zahlung erforderlich</string>
|
||||
<string name="max_message_length">Max. Nachrichtenlänge</string>
|
||||
<string name="max_subs">Max. Abonnements</string>
|
||||
<string name="max_filters_per_sub">Max. Filter pro Abo</string>
|
||||
<string name="max_limit_events_returning">Max. Limit (zurückgegebene Ereignisse)</string>
|
||||
<string name="default_limit">Standardlimit (zurückgegebene Ereignisse)</string>
|
||||
<string name="max_subid_length">Max. SubID-Länge</string>
|
||||
<string name="cashu">Cashu-Token</string>
|
||||
<string name="cashu_redeem">Einlösen</string>
|
||||
<string name="cashu_redeem_to_zap">An Zap Wallet senden</string>
|
||||
@@ -1189,4 +1232,5 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="follow_pack_broadcast">Empfehlungspaket veröffentlichen</string>
|
||||
<string name="follow_set_delete">Liste löschen</string>
|
||||
<string name="follow_pack_delete">Empfehlungspaket löschen</string>
|
||||
<string name="kinds">Typen</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<string name="could_not_decrypt_the_message">सन्देश का अरहस्यीकरण असफल</string>
|
||||
<string name="group_picture">समूह चित्र</string>
|
||||
<string name="explicit_content">अभद्र विषयवस्तु</string>
|
||||
<string name="relay_notice">पुनःप्रसारक सूचना</string>
|
||||
<string name="duplicated_post">दोहराया गया पत्र</string>
|
||||
<string name="spam">कचरालेख</string>
|
||||
<string name="spam_description">इस पुनःप्रसारक से कचरालेख घटनाओं की संख्या</string>
|
||||
<string name="impersonation">पररूपण</string>
|
||||
@@ -121,6 +123,7 @@
|
||||
<string name="relay_address">पुनःप्रसारक पता</string>
|
||||
<string name="posts">प्रकाशित पत्र</string>
|
||||
<string name="bytes">अष्टक</string>
|
||||
<string name="error">अपक्रम</string>
|
||||
<string name="errors">अपक्रम</string>
|
||||
<string name="errors_description">संयोजन अपक्रमों की संख्या इस सत्र में</string>
|
||||
<string name="home_feed">मुख्य सूचनावली</string>
|
||||
@@ -132,6 +135,8 @@
|
||||
<string name="add_a_user">प्रयोक्ता जोडें</string>
|
||||
<string name="add_a_relay">पुनःप्रसारक जोडें</string>
|
||||
<string name="profile_name">नाम</string>
|
||||
<string name="profile_name_with_explainer">नाम (@सूचक के लिए)</string>
|
||||
<string name="my_name">मेरा @सूचक नाम</string>
|
||||
<string name="display_name">प्रदर्शन नाम</string>
|
||||
<string name="my_display_name">मेरा प्रदर्शन नाम</string>
|
||||
<string name="my_awesome_name">उष्ट्रपक्षी मक्बढिया</string>
|
||||
@@ -158,6 +163,7 @@
|
||||
<string name="record_a_message">ऐक संदेश का अभिलेखन करें</string>
|
||||
<string name="record_a_message_title">ऐक संदेश का अभिलेखन करें</string>
|
||||
<string name="record_a_message_description">टाँकें तथा दबाए रखें संदेश का अभिलेखन करने के लिए</string>
|
||||
<string name="re_record">पुनरभिलेखन</string>
|
||||
<string name="recording_indicator_description">ध्वन्यभिलेखन</string>
|
||||
<string name="recording_indicator_with_time">ध्वन्यभिलेखन %1$s</string>
|
||||
<string name="uploading">आरोहण चल रहा है…</string>
|
||||
@@ -359,6 +365,8 @@
|
||||
<string name="block_only">बाधित करें</string>
|
||||
<string name="manual_zaps">मनुष्यकृत ज्साप विभाजन</string>
|
||||
<string name="bookmarks">स्मर्तव्य</string>
|
||||
<string name="bookmarks_title">मूलविकल्प स्मर्त्तव्य सूची</string>
|
||||
<string name="bookmarks_explainer">आपका मूलविकल्प स्मर्त्तव्य सूची जिसका अवलम्बन अनेक ग्राहक करते हैं</string>
|
||||
<string name="drafts">पाण्डुलिपियाँ</string>
|
||||
<string name="private_bookmarks">निजी स्मर्तव्य</string>
|
||||
<string name="public_bookmarks">सार्वजनिक स्मर्तव्य</string>
|
||||
@@ -642,17 +650,34 @@
|
||||
<string name="read_from_relay_description">अष्टकों में मात्रा जो इस पुनःप्रसारक से प्राप्त हुआ था छलनियाँ तथा घटनाएँ समेत</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">%1$s से पुनःप्रसारक जानकारी प्राप्त करने के प्रयास में अपक्रम हुआ</string>
|
||||
<string name="owner">अधिपति</string>
|
||||
<string name="self">सेवा कुंजी</string>
|
||||
<string name="running_software">%1$s चलाया जा रहा है</string>
|
||||
<string name="running_software_version">%1$s (%2$s) चलाया जा रहा है</string>
|
||||
<string name="version">संस्करण</string>
|
||||
<string name="software">क्रमक</string>
|
||||
<string name="contact">सम्पर्क</string>
|
||||
<string name="supports">अवलम्बन किए हुए निप॰ सूची</string>
|
||||
<string name="supported_grasps">अवलम्बित पकड</string>
|
||||
<string name="admission_fees">प्रवेश शुल्क</string>
|
||||
<string name="admission">प्रवेशन</string>
|
||||
<string name="subscription">ग्राहकता</string>
|
||||
<string name="publication">प्रकाशन</string>
|
||||
<string name="payment_link">भुगतान %1$s</string>
|
||||
<string name="payments_url">भुगतान जालपता</string>
|
||||
<string name="target_audience">लक्ष्य दर्शक</string>
|
||||
<string name="policies_and_links">नीतियाँ तथा जालयोजक</string>
|
||||
<string name="fees_and_payments">शुल्क तथा भुगतान</string>
|
||||
<string name="limitations">परिसीमाएँ</string>
|
||||
<string name="countries">देश</string>
|
||||
<string name="languages">भाषाएँ</string>
|
||||
<string name="tags">विषयसूचक</string>
|
||||
<string name="topics">विषय सूची</string>
|
||||
<string name="all_countries">सभी देश</string>
|
||||
<string name="all_languages">सभी भाषाएँ</string>
|
||||
<string name="posting_policy">पत्र प्रकाशन नीति</string>
|
||||
<string name="privacy_policy">गोपनीयता नीति</string>
|
||||
<string name="terms_and_conditions">नियम तथा अवस्था</string>
|
||||
<string name="not_available_acronym">अनुपलब्ध</string>
|
||||
<string name="relay_error_messages">अपक्रम तथा सूचनाएँ इस पुनःप्रसारक से</string>
|
||||
<string name="message_length">संदेश लम्बाई</string>
|
||||
<string name="subscriptions">ग्राहकताएँ</string>
|
||||
@@ -661,9 +686,28 @@
|
||||
<string name="minimum_prefix">न्यूनतम उपसर्ग</string>
|
||||
<string name="maximum_event_tags">अधिकतम घटना विषयसूचक</string>
|
||||
<string name="content_length">विषयवस्तु लम्बाई</string>
|
||||
<string name="max_content_length">अधिकतम विषयवस्तु लम्बाई</string>
|
||||
<string name="discards_older_than">इससे पुराना हटाता है</string>
|
||||
<string name="accepts_up_to">इस तक स्वीकारता है</string>
|
||||
<string name="time_in_the_future">%1$s भविष्य में</string>
|
||||
<string name="time_in_the_past">%1$s पूर्व</string>
|
||||
<string name="amount_in_zeros">%1$s शून्यांक</string>
|
||||
<string name="amount_in_bits">%1$s द्वयंक</string>
|
||||
<string name="event_retention">घटना रखाई</string>
|
||||
<string name="content_size">विषयवस्तु आकार</string>
|
||||
<string name="connectivity">संयोजकता</string>
|
||||
<string name="access_control">अभिगमन नियन्त्रण</string>
|
||||
<string name="minimum_pow">न्यूनतम श्रमप्रमाण</string>
|
||||
<string name="auth">प्रमाणीकरण</string>
|
||||
<string name="auth_required">प्रमाणीकरण आवश्यक</string>
|
||||
<string name="payment">भुगतान</string>
|
||||
<string name="payment_required">भुगतान आवश्यक</string>
|
||||
<string name="max_message_length">अधिकतम संदेश लम्बाई</string>
|
||||
<string name="max_subs">अधिकतम ग्राहकताएँ</string>
|
||||
<string name="max_filters_per_sub">अधिकतम छलनियाँ प्रति ग्राहकता</string>
|
||||
<string name="max_limit_events_returning">अधिकतम सीमा (घटनाएँ प्रतिफलित)</string>
|
||||
<string name="default_limit">मूलविकल्प सीमा (घटनाएँ प्रतिफलित)</string>
|
||||
<string name="max_subid_length">अधिकतम ग्राहकताविभेदक लम्बाई</string>
|
||||
<string name="cashu">काशयु राशिखण्ड</string>
|
||||
<string name="cashu_redeem">चुकाएँ</string>
|
||||
<string name="cashu_redeem_to_zap">ज्साप धनकोष को भेजें</string>
|
||||
@@ -685,7 +729,7 @@
|
||||
<string name="discover_follows_explainer">ये प्रयोक्ता सूचियाँ हैं जो आप अन्य लोगों के लिए अनुशंसित करते हैं। केवल सार्वजनिक प्रयोक्ता अनुमत।</string>
|
||||
<string name="discover_reads">पठितव्य</string>
|
||||
<string name="discover_content_v2">सूचनावली विधियाँ</string>
|
||||
<string name="discover_marketplace">पण्यक्षेत्र</string>
|
||||
<string name="discover_marketplace">व्यापारक्षेत्र</string>
|
||||
<string name="discover_live_v2">तत्क्षणप्रसार</string>
|
||||
<string name="discover_community_v2">समुदाय</string>
|
||||
<string name="discover_chat">चर्चाएँ</string>
|
||||
|
||||
@@ -135,6 +135,8 @@
|
||||
<string name="add_a_user">Felhasználó ozzáadása</string>
|
||||
<string name="add_a_relay">Egy átjátszó hozzáadása</string>
|
||||
<string name="profile_name">Név</string>
|
||||
<string name="profile_name_with_explainer">Név (az @említéshez)</string>
|
||||
<string name="my_name">Saját @említési név</string>
|
||||
<string name="display_name">Megjelenítendő név</string>
|
||||
<string name="my_display_name">Saját megjelenítendő név</string>
|
||||
<string name="my_awesome_name">Strucc McNagyszerű</string>
|
||||
@@ -1230,4 +1232,5 @@
|
||||
<string name="follow_pack_broadcast">Közvetítési csomag</string>
|
||||
<string name="follow_set_delete">Lista törlése</string>
|
||||
<string name="follow_pack_delete">Csomag törlése</string>
|
||||
<string name="kinds">Változatok</string>
|
||||
</resources>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<string name="zaps">Zapy</string>
|
||||
<string name="view_count">Liczba wyświetleń</string>
|
||||
<string name="boost">Powtórz</string>
|
||||
<string name="boosted">powtórzony</string>
|
||||
<string name="boosted">powtórzono</string>
|
||||
<string name="edited">edytowano</string>
|
||||
<string name="edited_number">edytuj #%1$s</string>
|
||||
<string name="original">oryginalny</string>
|
||||
@@ -135,6 +135,8 @@
|
||||
<string name="add_a_user">Dodaj użytkownika</string>
|
||||
<string name="add_a_relay">Dodaj Transmiter</string>
|
||||
<string name="profile_name">Imię</string>
|
||||
<string name="profile_name_with_explainer">Imię (dla @tagging)</string>
|
||||
<string name="my_name">Moje imię @tag</string>
|
||||
<string name="display_name">Nazwa użytkownika</string>
|
||||
<string name="my_display_name">Mój nick</string>
|
||||
<string name="my_awesome_name">G Braun</string>
|
||||
@@ -1227,4 +1229,5 @@
|
||||
<string name="follow_pack_broadcast">Upowszechnij kategorię</string>
|
||||
<string name="follow_set_delete">Usuń listę</string>
|
||||
<string name="follow_pack_delete">Usuń kategorię</string>
|
||||
<string name="kinds">Typy</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<string name="could_not_decrypt_the_message">Não foi possível descriptografar a mensagem</string>
|
||||
<string name="group_picture">Imagem do grupo</string>
|
||||
<string name="explicit_content">Conteúdo explícito</string>
|
||||
<string name="relay_notice">Aviso do relay</string>
|
||||
<string name="duplicated_post">Post duplicado</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="spam_description">O número de eventos de spam vindo deste relé</string>
|
||||
<string name="impersonation">Representação</string>
|
||||
@@ -121,6 +123,7 @@
|
||||
<string name="relay_address">Endereço do Relay</string>
|
||||
<string name="posts">Postagens</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="error">Erro</string>
|
||||
<string name="errors">Erros</string>
|
||||
<string name="errors_description">O número de erros de conexão nesta sessão</string>
|
||||
<string name="home_feed">Feed principal</string>
|
||||
@@ -132,6 +135,8 @@
|
||||
<string name="add_a_user">Adicionar um usuário</string>
|
||||
<string name="add_a_relay">Adicionar um Relay</string>
|
||||
<string name="profile_name">Nome</string>
|
||||
<string name="profile_name_with_explainer">Nome (para @marcar)</string>
|
||||
<string name="my_name">Meu nome de @tag</string>
|
||||
<string name="display_name">Nome de Exibição</string>
|
||||
<string name="my_display_name">Meu nome de exibição</string>
|
||||
<string name="my_awesome_name">McAwesome Ostrich</string>
|
||||
@@ -358,6 +363,8 @@
|
||||
<string name="block_only">Bloquear</string>
|
||||
<string name="manual_zaps">Divisão manual de Zap</string>
|
||||
<string name="bookmarks">Itens Salvos</string>
|
||||
<string name="bookmarks_title">Marcadores padrão</string>
|
||||
<string name="bookmarks_explainer">Seus marcadores padrão que muitos clientes suportam</string>
|
||||
<string name="drafts">Rascunhos</string>
|
||||
<string name="private_bookmarks">Itens Salvos Privados</string>
|
||||
<string name="public_bookmarks">Itens Salvos Públicos</string>
|
||||
@@ -639,28 +646,64 @@
|
||||
<string name="read_from_relay_description">A quantidade em bytes que foi recebida deste relé, incluindo filtros e eventos</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Ocorreu um erro ao tentar obter informações do relay de %1$s</string>
|
||||
<string name="owner">Proprietário</string>
|
||||
<string name="self">Chave de serviço</string>
|
||||
<string name="running_software">Executando %1$s</string>
|
||||
<string name="running_software_version">Executando %1$s (%2$s)</string>
|
||||
<string name="version">Versão</string>
|
||||
<string name="software">Programa</string>
|
||||
<string name="contact">Contato</string>
|
||||
<string name="supports">NIPs Suportados</string>
|
||||
<string name="supported_grasps">GRASPs compatíveis</string>
|
||||
<string name="admission_fees">Taxas de admissão</string>
|
||||
<string name="admission">Entrada</string>
|
||||
<string name="subscription">Assinatura</string>
|
||||
<string name="publication">Publicação</string>
|
||||
<string name="payment_link">Pagamentos %1$s</string>
|
||||
<string name="payments_url">URL de pagamentos</string>
|
||||
<string name="target_audience">Público-alvo</string>
|
||||
<string name="policies_and_links">Políticas & links</string>
|
||||
<string name="fees_and_payments">Taxas & pagamentos</string>
|
||||
<string name="limitations">Limitações</string>
|
||||
<string name="countries">Países</string>
|
||||
<string name="languages">Línguas</string>
|
||||
<string name="tags">Cerquilhas</string>
|
||||
<string name="topics">Tópicos</string>
|
||||
<string name="all_countries">Todos os países</string>
|
||||
<string name="all_languages">Todos os idiomas</string>
|
||||
<string name="posting_policy">Política de postagem</string>
|
||||
<string name="privacy_policy">Política de privacidade</string>
|
||||
<string name="terms_and_conditions">Termos & condições</string>
|
||||
<string name="not_available_acronym">N/A</string>
|
||||
<string name="relay_error_messages">Erros e Avisos deste Relé</string>
|
||||
<string name="message_length">Tamanho da mensagem</string>
|
||||
<string name="subscriptions">Assinaturas</string>
|
||||
<string name="filters">Filtros</string>
|
||||
<string name="subscription_id_length">Comprimento do ID da assinatura</string>
|
||||
<string name="minimum_prefix">Prefixo mínimo</string>
|
||||
<string name="maximum_event_tags">Máximo de tags de evento</string>
|
||||
<string name="minimum_prefix">Prefixo mín.</string>
|
||||
<string name="maximum_event_tags">Máx. tags de evento</string>
|
||||
<string name="content_length">Tamanho do conteúdo</string>
|
||||
<string name="minimum_pow">PoW mínimo</string>
|
||||
<string name="max_content_length">Máx. tamanho do conteúdo</string>
|
||||
<string name="discards_older_than">Descarta mais antigos que</string>
|
||||
<string name="accepts_up_to">Aceita até</string>
|
||||
<string name="time_in_the_future">%1$s no futuro</string>
|
||||
<string name="time_in_the_past">há %1$s</string>
|
||||
<string name="amount_in_zeros">%1$s zeros</string>
|
||||
<string name="amount_in_bits">%1$s bits</string>
|
||||
<string name="event_retention">Retenção de eventos</string>
|
||||
<string name="content_size">Tamanho do conteúdo</string>
|
||||
<string name="connectivity">Conectividade</string>
|
||||
<string name="access_control">Controle de acesso</string>
|
||||
<string name="minimum_pow">Dificuldade mínima de PoW</string>
|
||||
<string name="auth">Autenticação</string>
|
||||
<string name="auth_required">Autenticação obrigatória</string>
|
||||
<string name="payment">Pagamento</string>
|
||||
<string name="payment_required">Pagamento obrigatório</string>
|
||||
<string name="max_message_length">Máx. tamanho da mensagem</string>
|
||||
<string name="max_subs">Máx. inscrições</string>
|
||||
<string name="max_filters_per_sub">Máx. filtros por inscrição</string>
|
||||
<string name="max_limit_events_returning">Limite máx. (eventos retornados)</string>
|
||||
<string name="default_limit">Limite padrão (eventos retornados)</string>
|
||||
<string name="max_subid_length">Comprimento máx. do SubID</string>
|
||||
<string name="cashu">Token Cashu</string>
|
||||
<string name="cashu_redeem">Resgatar</string>
|
||||
<string name="cashu_redeem_to_zap">Enviar para Zap Wallet</string>
|
||||
@@ -1184,4 +1227,5 @@
|
||||
<string name="follow_pack_broadcast">Transmitir pacote de recomendações</string>
|
||||
<string name="follow_set_delete">Excluir lista</string>
|
||||
<string name="follow_pack_delete">Excluir pacote de recomendações</string>
|
||||
<string name="kinds">Tipos</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<string name="could_not_decrypt_the_message">Kunde inte dekryptera meddelandet</string>
|
||||
<string name="group_picture">Grupp bild</string>
|
||||
<string name="explicit_content">Explicit Innehåll</string>
|
||||
<string name="relay_notice">Relay-meddelande</string>
|
||||
<string name="duplicated_post">Duplicerat inlägg</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="spam_description">Antal skräpposthändelser från detta relä</string>
|
||||
<string name="impersonation">Imitation</string>
|
||||
@@ -121,6 +123,7 @@
|
||||
<string name="relay_address">Relä Adress</string>
|
||||
<string name="posts">Inlägg</string>
|
||||
<string name="bytes">Bytes</string>
|
||||
<string name="error">Fel</string>
|
||||
<string name="errors">Fel</string>
|
||||
<string name="errors_description">Antal anslutningsfel under denna session</string>
|
||||
<string name="home_feed">Hem Flöde</string>
|
||||
@@ -132,6 +135,8 @@
|
||||
<string name="add_a_user">Lägg till en användare</string>
|
||||
<string name="add_a_relay">Lägg till Relä</string>
|
||||
<string name="profile_name">Namn</string>
|
||||
<string name="profile_name_with_explainer">Namn (för @taggning)</string>
|
||||
<string name="my_name">Mitt @tag-namn</string>
|
||||
<string name="display_name">Visningsnamn</string>
|
||||
<string name="my_display_name">Mitt visningsnamn</string>
|
||||
<string name="my_awesome_name">Struts McAwesome</string>
|
||||
@@ -358,6 +363,8 @@
|
||||
<string name="block_only">Blockera</string>
|
||||
<string name="manual_zaps">Manuell Zap delning</string>
|
||||
<string name="bookmarks">Bokmärken</string>
|
||||
<string name="bookmarks_title">Standardbokmärken</string>
|
||||
<string name="bookmarks_explainer">Dina standardbokmärken som många klienter stödjer</string>
|
||||
<string name="drafts">Utkast</string>
|
||||
<string name="private_bookmarks">Privata Bokmärken</string>
|
||||
<string name="public_bookmarks">Publika Bokmärken</string>
|
||||
@@ -638,28 +645,64 @@
|
||||
<string name="read_from_relay_description">Mängden data i byte som mottogs från detta relä, inklusive filter och händelser</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Ett fel inträffade vid försök att hämta information från Relay %1$s</string>
|
||||
<string name="owner">Ägare</string>
|
||||
<string name="self">Service-nyckel</string>
|
||||
<string name="running_software">Kör %1$s</string>
|
||||
<string name="running_software_version">Kör %1$s (%2$s)</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="software">Programvara</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="supports">Stödda NIPs</string>
|
||||
<string name="supported_grasps">Stödda GRASPs</string>
|
||||
<string name="admission_fees">Entréavgifter</string>
|
||||
<string name="admission">Inträde</string>
|
||||
<string name="subscription">Prenumeration</string>
|
||||
<string name="publication">Publicering</string>
|
||||
<string name="payment_link">Betalningar %1$s</string>
|
||||
<string name="payments_url">Betalnings-URL</string>
|
||||
<string name="target_audience">Målgrupp</string>
|
||||
<string name="policies_and_links">Policyer & länkar</string>
|
||||
<string name="fees_and_payments">Avgifter & betalningar</string>
|
||||
<string name="limitations">Begränsningar</string>
|
||||
<string name="countries">Länder</string>
|
||||
<string name="languages">Språk</string>
|
||||
<string name="tags">Taggar</string>
|
||||
<string name="topics">Ämnen</string>
|
||||
<string name="all_countries">Alla länder</string>
|
||||
<string name="all_languages">Alla språk</string>
|
||||
<string name="posting_policy">Publiceringspolicy</string>
|
||||
<string name="privacy_policy">Integritetspolicy</string>
|
||||
<string name="terms_and_conditions">Villkor & bestämmelser</string>
|
||||
<string name="not_available_acronym">N/A</string>
|
||||
<string name="relay_error_messages">Fel och meddelanden från detta relä</string>
|
||||
<string name="message_length">Meddelandelängd</string>
|
||||
<string name="subscriptions">Prenumerationer</string>
|
||||
<string name="filters">Filter</string>
|
||||
<string name="subscription_id_length">Längd på prenumerations-ID</string>
|
||||
<string name="minimum_prefix">Minsta prefix</string>
|
||||
<string name="maximum_event_tags">Maximalt antal händelse-taggar</string>
|
||||
<string name="minimum_prefix">Min. prefix</string>
|
||||
<string name="maximum_event_tags">Max. eventtaggar</string>
|
||||
<string name="content_length">Innehållslängd</string>
|
||||
<string name="minimum_pow">Minsta PoW</string>
|
||||
<string name="max_content_length">Max. innehållslängd</string>
|
||||
<string name="discards_older_than">Kastar bort äldre än</string>
|
||||
<string name="accepts_up_to">Accepterar upp till</string>
|
||||
<string name="time_in_the_future">%1$s i framtiden</string>
|
||||
<string name="time_in_the_past">för %1$s sedan</string>
|
||||
<string name="amount_in_zeros">%1$s nollor</string>
|
||||
<string name="amount_in_bits">%1$s bitar</string>
|
||||
<string name="event_retention">Lagringstid för händelser</string>
|
||||
<string name="content_size">Innehållsstorlek</string>
|
||||
<string name="connectivity">Anslutning</string>
|
||||
<string name="access_control">Åtkomstkontroll</string>
|
||||
<string name="minimum_pow">Min. PoW-svårighet</string>
|
||||
<string name="auth">Autentisering</string>
|
||||
<string name="auth_required">Autentisering krävs</string>
|
||||
<string name="payment">Betalning</string>
|
||||
<string name="payment_required">Betalning krävs</string>
|
||||
<string name="max_message_length">Max. meddelandelängd</string>
|
||||
<string name="max_subs">Max. prenumerationer</string>
|
||||
<string name="max_filters_per_sub">Max. filter per prenumeration</string>
|
||||
<string name="max_limit_events_returning">Maxgräns (returnerade händelser)</string>
|
||||
<string name="default_limit">Standardgräns (returnerade händelser)</string>
|
||||
<string name="max_subid_length">Max. SubID-längd</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Inlösen</string>
|
||||
<string name="cashu_redeem_to_zap">Skicka till Zap Plånbok</string>
|
||||
@@ -1183,4 +1226,5 @@
|
||||
<string name="follow_pack_broadcast">Sänd rekommendationspaket</string>
|
||||
<string name="follow_set_delete">Ta bort lista</string>
|
||||
<string name="follow_pack_delete">Ta bort rekommendationspaket</string>
|
||||
<string name="kinds">Typer</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<string name="could_not_decrypt_the_message">无法解密消息</string>
|
||||
<string name="group_picture">群聊图片</string>
|
||||
<string name="explicit_content">明确内容</string>
|
||||
<string name="relay_notice">中继通知</string>
|
||||
<string name="duplicated_post">重复的贴文</string>
|
||||
<string name="spam">垃圾信息</string>
|
||||
<string name="spam_description">来自该中继的垃圾事件数量</string>
|
||||
<string name="impersonation">冒充</string>
|
||||
@@ -121,6 +123,7 @@
|
||||
<string name="relay_address">中继器地址</string>
|
||||
<string name="posts">帖文</string>
|
||||
<string name="bytes">字节</string>
|
||||
<string name="error">错误</string>
|
||||
<string name="errors">错误</string>
|
||||
<string name="errors_description">该会话中产生的连接错误数量</string>
|
||||
<string name="home_feed">主页</string>
|
||||
@@ -132,6 +135,8 @@
|
||||
<string name="add_a_user">添加用户</string>
|
||||
<string name="add_a_relay">添加中继器</string>
|
||||
<string name="profile_name">名称</string>
|
||||
<string name="profile_name_with_explainer">名称 (用于 @ 标记时显示)</string>
|
||||
<string name="my_name">我的 @ 名称</string>
|
||||
<string name="display_name">显示名称</string>
|
||||
<string name="my_display_name">我的显示名称</string>
|
||||
<string name="my_awesome_name">Nostr 太酷了</string>
|
||||
@@ -360,6 +365,8 @@
|
||||
<string name="block_only">仅阻止</string>
|
||||
<string name="manual_zaps">手动 Zap 拆分</string>
|
||||
<string name="bookmarks">书签</string>
|
||||
<string name="bookmarks_title">默认书签</string>
|
||||
<string name="bookmarks_explainer">许多客户端支持默认书签</string>
|
||||
<string name="drafts">草稿</string>
|
||||
<string name="private_bookmarks">私人书签</string>
|
||||
<string name="public_bookmarks">公开书签</string>
|
||||
@@ -643,17 +650,34 @@
|
||||
<string name="read_from_relay_description">从该中继接收事件和过滤响应所使用的数据量</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">尝试从 %1$s 获取中继器信息时出错</string>
|
||||
<string name="owner">机主</string>
|
||||
<string name="self">服务密钥</string>
|
||||
<string name="running_software">正在运行 %1$s</string>
|
||||
<string name="running_software_version">正在运行 %1$s (%2$s)</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="software">软件</string>
|
||||
<string name="contact">联络</string>
|
||||
<string name="supports">支持的 NIPs</string>
|
||||
<string name="supported_grasps">支持 Grasps</string>
|
||||
<string name="admission_fees">接纳费</string>
|
||||
<string name="admission">接纳</string>
|
||||
<string name="subscription">订阅</string>
|
||||
<string name="publication">发布</string>
|
||||
<string name="payment_link">付款 %1$s</string>
|
||||
<string name="payments_url">付款网址</string>
|
||||
<string name="target_audience">目标受众</string>
|
||||
<string name="policies_and_links">策略 & 链接</string>
|
||||
<string name="fees_and_payments">费用 & 付款</string>
|
||||
<string name="limitations">限制</string>
|
||||
<string name="countries">国家</string>
|
||||
<string name="languages">语言</string>
|
||||
<string name="tags">标签</string>
|
||||
<string name="topics">话题</string>
|
||||
<string name="all_countries">所有国家和地区</string>
|
||||
<string name="all_languages">所有语言</string>
|
||||
<string name="posting_policy">发布政策</string>
|
||||
<string name="privacy_policy">隐私政策</string>
|
||||
<string name="terms_and_conditions">使用条款</string>
|
||||
<string name="not_available_acronym">N/A</string>
|
||||
<string name="relay_error_messages">该中继的错误和通知</string>
|
||||
<string name="message_length">消息长度</string>
|
||||
<string name="subscriptions">订阅</string>
|
||||
@@ -662,9 +686,28 @@
|
||||
<string name="minimum_prefix">最少前缀</string>
|
||||
<string name="maximum_event_tags">最多事件标签</string>
|
||||
<string name="content_length">内容长度</string>
|
||||
<string name="max_content_length">最大内容长度</string>
|
||||
<string name="discards_older_than">丢弃旧于</string>
|
||||
<string name="accepts_up_to">最多接受</string>
|
||||
<string name="time_in_the_future">未来 %1$s</string>
|
||||
<string name="time_in_the_past">%1$s 之前</string>
|
||||
<string name="amount_in_zeros">%1$s 整</string>
|
||||
<string name="amount_in_bits">%1$s 位</string>
|
||||
<string name="event_retention">事件保留</string>
|
||||
<string name="content_size">内容大小</string>
|
||||
<string name="connectivity">连通性</string>
|
||||
<string name="access_control">访问控制</string>
|
||||
<string name="minimum_pow">最低工作量证明</string>
|
||||
<string name="auth">认证</string>
|
||||
<string name="auth_required">需要认证</string>
|
||||
<string name="payment">支付</string>
|
||||
<string name="payment_required">需要付费</string>
|
||||
<string name="max_message_length">最大消息长度</string>
|
||||
<string name="max_subs">最大订阅连接数</string>
|
||||
<string name="max_filters_per_sub">最多订阅过滤器</string>
|
||||
<string name="max_limit_events_returning">最大数量(返回事件)</string>
|
||||
<string name="default_limit">默认限制(返回事件)</string>
|
||||
<string name="max_subid_length">最大 SubID 长度</string>
|
||||
<string name="cashu">Cashu 代币</string>
|
||||
<string name="cashu_redeem">兑换</string>
|
||||
<string name="cashu_redeem_to_zap">发送到打闪钱包</string>
|
||||
|
||||
@@ -1465,4 +1465,5 @@
|
||||
|
||||
<string name="follow_set_delete">Delete List</string>
|
||||
<string name="follow_pack_delete">Delete Pack</string>
|
||||
<string name="kinds">Kinds</string>
|
||||
</resources>
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
|
||||
-1
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.util
|
||||
|
||||
import com.sun.org.apache.xalan.internal.lib.ExsltDatetime.time
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
+7
-11
@@ -20,19 +20,15 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.experimental.forks
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||
|
||||
fun BaseThreadedEvent.isAFork() = tags.any { it.size > 3 && (it[0] == "a" || it[0] == "e") && it[3] == "fork" }
|
||||
@Stable
|
||||
interface IForkableEvent {
|
||||
fun isAFork(): Boolean
|
||||
|
||||
fun BaseThreadedEvent.forkFromAddress() =
|
||||
tags.firstOrNull { it.size > 3 && it[0] == "a" && it[3] == "fork" }?.let {
|
||||
val aTagValue = it[1]
|
||||
Address.parse(aTagValue)
|
||||
}
|
||||
fun forkFromAddress(): Address?
|
||||
|
||||
fun BaseThreadedEvent.forkFromVersion() = tags.firstNotNullOfOrNull(MarkedETag::parseFork)
|
||||
|
||||
fun BaseThreadedEvent.isForkFromAddressWithPubkey(authorHex: HexKey) = tags.any { it.size > 3 && it[0] == "a" && it[3] == "fork" && it[1].contains(authorHex) }
|
||||
fun forkFromVersion(): HexKey?
|
||||
}
|
||||
+8
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.experimental.forks
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag.MARKER
|
||||
@@ -37,3 +38,10 @@ fun MarkedETag.Companion.parseFork(tag: Array<String>): MarkedETag? {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun MarkedETag.Companion.parseForkedEventId(tag: Array<String>): HexKey? {
|
||||
if (tag.size < 4 || tag[0] != "e") return null
|
||||
if (tag[ORDER_MARKER] != MARKER.FORK.code) return null
|
||||
// ["e", id hex, relay hint, marker, pubkey]
|
||||
return tag[ORDER_EVT_ID]
|
||||
}
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Copyright (c) 2025 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.experimental.nipsOnNostr
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.parseForkedEventId
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.tags.ForkTag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.kinds
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QTag
|
||||
import com.vitorpamplona.quartz.nip19Bech32.addressHints
|
||||
import com.vitorpamplona.quartz.nip19Bech32.addressIds
|
||||
import com.vitorpamplona.quartz.nip19Bech32.eventHints
|
||||
import com.vitorpamplona.quartz.nip19Bech32.eventIds
|
||||
import com.vitorpamplona.quartz.nip22Comments.RootScope
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@Immutable
|
||||
class NipTextEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseNoteEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
AddressableEvent,
|
||||
RootScope,
|
||||
EventHintProvider,
|
||||
AddressHintProvider,
|
||||
IForkableEvent,
|
||||
SearchableEvent {
|
||||
override fun indexableContent() = "title: " + title() + "\n" + content
|
||||
|
||||
override fun dTag() = tags.dTag()
|
||||
|
||||
override fun aTag(relayHint: NormalizedRelayUrl?) = ATag(kind, pubKey, dTag(), relayHint)
|
||||
|
||||
override fun address() = Address(kind, pubKey, dTag())
|
||||
|
||||
override fun addressTag() = Address.assemble(kind, pubKey, dTag())
|
||||
|
||||
override fun eventHints(): List<EventIdHint> {
|
||||
val qHints = tags.mapNotNull(QTag::parseEventAsHint)
|
||||
val nip19Hints = citedNIP19().eventHints()
|
||||
|
||||
return qHints + nip19Hints
|
||||
}
|
||||
|
||||
override fun linkedEventIds(): List<HexKey> {
|
||||
val qHints = tags.mapNotNull(QTag::parseEventId)
|
||||
val nip19Hints = citedNIP19().eventIds()
|
||||
|
||||
return qHints + nip19Hints
|
||||
}
|
||||
|
||||
override fun addressHints(): List<AddressHint> {
|
||||
val aHints = tags.mapNotNull(ATag::parseAsHint)
|
||||
val qHints = tags.mapNotNull(QTag::parseAddressAsHint)
|
||||
val nip19Hints = citedNIP19().addressHints()
|
||||
|
||||
return aHints + qHints + nip19Hints
|
||||
}
|
||||
|
||||
override fun linkedAddressIds(): List<String> {
|
||||
val aHints = tags.mapNotNull(ATag::parseAddressId)
|
||||
val qHints = tags.mapNotNull(QTag::parseAddressId)
|
||||
val nip19Hints = citedNIP19().addressIds()
|
||||
|
||||
return aHints + qHints + nip19Hints
|
||||
}
|
||||
|
||||
override fun isAFork() = tags.any { it.size > 3 && (it[0] == "a" || it[0] == "e") && it[3] == "fork" }
|
||||
|
||||
override fun forkFromAddress() = tags.firstNotNullOfOrNull(ForkTag::parseAddress)
|
||||
|
||||
override fun forkFromVersion() = tags.firstNotNullOfOrNull(MarkedETag::parseForkedEventId)
|
||||
|
||||
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
|
||||
|
||||
fun summary() =
|
||||
content.lineSequence().drop(1).firstNotNullOfOrNull {
|
||||
if (it.isBlank() ||
|
||||
it.startsWith("---") ||
|
||||
it.startsWith("`draft") ||
|
||||
it.startsWith("#")
|
||||
) {
|
||||
null
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND = 30817
|
||||
const val KIND_STR = "30817"
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
fun build(
|
||||
description: String,
|
||||
title: String,
|
||||
kinds: List<Int>,
|
||||
dTag: String = Uuid.random().toString(),
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
initializer: TagArrayBuilder<NipTextEvent>.() -> Unit = {},
|
||||
) = eventTemplate(KIND, description, createdAt) {
|
||||
dTag(dTag)
|
||||
alt("NIP: $title")
|
||||
|
||||
title(title)
|
||||
kinds(kinds)
|
||||
|
||||
initializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2025 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.experimental.nipsOnNostr
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.kinds.KindTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
|
||||
|
||||
fun TagArrayBuilder<NipTextEvent>.title(title: String) = addUnique(TitleTag.assemble(title))
|
||||
|
||||
fun TagArrayBuilder<NipTextEvent>.kinds(kinds: List<Int>) = addAll(KindTag.assemble(kinds))
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2025 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.experimental.nipsOnNostr.tags
|
||||
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.utils.arrayOfNotNull
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class ForkTag(
|
||||
val address: Address,
|
||||
val relayHint: NormalizedRelayUrl? = null,
|
||||
) {
|
||||
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||
|
||||
fun toTagArray() = assemble(address, relayHint)
|
||||
|
||||
fun toTagIdOnly() = assemble(address, null)
|
||||
|
||||
companion object {
|
||||
const val TAG_NAME = "a"
|
||||
|
||||
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && Address.isOfKind(tag[1], NipTextEvent.KIND_STR)
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
addressId: String,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId
|
||||
|
||||
fun isTagged(
|
||||
tag: Array<String>,
|
||||
address: ForkTag,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag()
|
||||
|
||||
fun isIn(
|
||||
tag: Array<String>,
|
||||
addressIds: Set<String>,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
|
||||
|
||||
fun parse(tag: Array<String>): ForkTag? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(
|
||||
Address.Companion.isOfKind(
|
||||
tag[1],
|
||||
CommunityDefinitionEvent.Companion.KIND_STR,
|
||||
),
|
||||
) { return null }
|
||||
|
||||
val address = Address.Companion.parse(tag[1]) ?: return null
|
||||
val relayHint = tag.getOrNull(2)?.let { RelayUrlNormalizer.Companion.normalizeOrNull(it) }
|
||||
return ForkTag(address, relayHint)
|
||||
}
|
||||
|
||||
fun parseValidAddress(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(
|
||||
Address.Companion.isOfKind(
|
||||
tag[1],
|
||||
CommunityDefinitionEvent.Companion.KIND_STR,
|
||||
),
|
||||
) { return null }
|
||||
return Address.Companion.parse(tag[1])?.toValue()
|
||||
}
|
||||
|
||||
fun parseAddress(tag: Array<String>): Address? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
val address = Address.parse(tag[1]) ?: return null
|
||||
ensure(address.kind == NipTextEvent.KIND) { return null }
|
||||
return address
|
||||
}
|
||||
|
||||
fun parseAddressId(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(
|
||||
Address.isOfKind(
|
||||
tag[1],
|
||||
NipTextEvent.KIND_STR,
|
||||
),
|
||||
) { return null }
|
||||
return tag[1]
|
||||
}
|
||||
|
||||
fun parseAsHint(tag: Array<String>): AddressHint? {
|
||||
ensure(tag.has(2)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(
|
||||
Address.isOfKind(
|
||||
tag[1],
|
||||
NipTextEvent.KIND_STR,
|
||||
),
|
||||
) { return null }
|
||||
ensure(tag[2].isNotEmpty()) { return null }
|
||||
|
||||
val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2])
|
||||
ensure(relayHint != null) { return null }
|
||||
|
||||
return AddressHint(tag[1], relayHint)
|
||||
}
|
||||
|
||||
fun assemble(
|
||||
aTagId: String,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) = arrayOfNotNull(TAG_NAME, aTagId, relay?.url, "fork")
|
||||
|
||||
fun assemble(
|
||||
address: Address,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) = assemble(address.toValue(), relay)
|
||||
|
||||
fun assemble(
|
||||
kind: Int,
|
||||
pubKey: String,
|
||||
dTag: String,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) = assemble(Address.assemble(kind, pubKey, dTag), relay)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2025 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.experimental.nipsOnNostr.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
class TitleTag {
|
||||
companion object {
|
||||
const val TAG_NAME = "title"
|
||||
|
||||
fun parse(tag: Array<String>): String? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return tag[1]
|
||||
}
|
||||
|
||||
fun assemble(title: String) = arrayOf(TAG_NAME, title)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -44,7 +44,7 @@ class KindTag {
|
||||
ensure(tag.has(1)) { return null }
|
||||
ensure(tag[0] == TAG_NAME) { return null }
|
||||
ensure(tag[1].isNotEmpty()) { return null }
|
||||
return tag[1].toInt()
|
||||
return tag[1].toIntOrNull()
|
||||
}
|
||||
|
||||
fun assemble(kind: Int) = arrayOf(TAG_NAME, kind.toString())
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.quartz.nip10Notes
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.taggedATags
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
@@ -79,7 +80,7 @@ open class BaseThreadedEvent(
|
||||
val tagAddresses =
|
||||
taggedATags()
|
||||
.filter { aTag ->
|
||||
aTag.kind != CommunityDefinitionEvent.KIND && (kind != WikiNoteEvent.KIND || aTag.kind != WikiNoteEvent.KIND)
|
||||
aTag.kind != CommunityDefinitionEvent.KIND && (kind != WikiNoteEvent.KIND || aTag.kind != WikiNoteEvent.KIND) && (kind != NipTextEvent.KIND || aTag.kind != NipTextEvent.KIND)
|
||||
// removes forks from itself.
|
||||
}.map {
|
||||
it.toTag()
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
package com.vitorpamplona.quartz.nip10Notes
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.parseForkedEventId
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
|
||||
@@ -47,6 +49,7 @@ import com.vitorpamplona.quartz.nip19Bech32.pubKeys
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.serialization.json.JsonNull.content
|
||||
|
||||
@Immutable
|
||||
class TextNoteEvent(
|
||||
@@ -60,6 +63,7 @@ class TextNoteEvent(
|
||||
EventHintProvider,
|
||||
AddressHintProvider,
|
||||
PubKeyHintProvider,
|
||||
IForkableEvent,
|
||||
SearchableEvent {
|
||||
override fun indexableContent() = "Subject: " + subject() + "\n" + content
|
||||
|
||||
@@ -109,6 +113,12 @@ class TextNoteEvent(
|
||||
return pHints + nip19Hints
|
||||
}
|
||||
|
||||
override fun isAFork() = tags.any { it.size > 3 && (it[0] == "a" || it[0] == "e") && it[3] == "fork" }
|
||||
|
||||
override fun forkFromAddress() = tags.firstNotNullOfOrNull(ATag::parseAddress)
|
||||
|
||||
override fun forkFromVersion() = tags.firstNotNullOfOrNull(MarkedETag::parseForkedEventId)
|
||||
|
||||
companion object {
|
||||
const val KIND = 1
|
||||
const val ALT = "A short note: "
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2025 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.nip54Wiki
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.ImageTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.SummaryTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
|
||||
|
||||
fun TagArrayBuilder<WikiNoteEvent>.title(title: String) = addUnique(TitleTag.assemble(title))
|
||||
|
||||
fun TagArrayBuilder<WikiNoteEvent>.summary(summary: String) = addUnique(SummaryTag.assemble(summary))
|
||||
|
||||
fun TagArrayBuilder<WikiNoteEvent>.image(imageUrl: String) = addUnique(ImageTag.assemble(imageUrl))
|
||||
|
||||
fun TagArrayBuilder<WikiNoteEvent>.publishedAt(publishedAt: Long) = addUnique(PublishedAtTag.assemble(publishedAt))
|
||||
@@ -21,9 +21,13 @@
|
||||
package com.vitorpamplona.quartz.nip54Wiki
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.parseForkedEventId
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.tags.ForkTag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||
@@ -31,13 +35,14 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.quotes.QTag
|
||||
import com.vitorpamplona.quartz.nip19Bech32.addressHints
|
||||
@@ -46,10 +51,16 @@ import com.vitorpamplona.quartz.nip19Bech32.eventHints
|
||||
import com.vitorpamplona.quartz.nip19Bech32.eventIds
|
||||
import com.vitorpamplona.quartz.nip19Bech32.pubKeyHints
|
||||
import com.vitorpamplona.quartz.nip19Bech32.pubKeys
|
||||
import com.vitorpamplona.quartz.nip22Comments.RootScope
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.ImageTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
|
||||
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.SummaryTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@Immutable
|
||||
class WikiNoteEvent(
|
||||
@@ -59,12 +70,14 @@ class WikiNoteEvent(
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseThreadedEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
) : BaseNoteEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||
AddressableEvent,
|
||||
EventHintProvider,
|
||||
AddressHintProvider,
|
||||
PubKeyHintProvider,
|
||||
PublishedAtProvider,
|
||||
IForkableEvent,
|
||||
RootScope,
|
||||
SearchableEvent {
|
||||
override fun indexableContent() = "title: " + title() + "\nsummary: " + summary() + "\n" + content
|
||||
|
||||
@@ -124,16 +137,20 @@ class WikiNoteEvent(
|
||||
|
||||
fun topics() = hashtags()
|
||||
|
||||
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
||||
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
|
||||
|
||||
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == "summary" }?.get(1)
|
||||
fun image() = tags.firstNotNullOfOrNull(ImageTag::parse)
|
||||
|
||||
fun image() = tags.firstOrNull { it.size > 1 && it[0] == "image" }?.get(1)
|
||||
fun summary() = tags.firstNotNullOfOrNull(SummaryTag::parse)
|
||||
|
||||
override fun isAFork() = tags.any { it.size > 3 && (it[0] == "a" || it[0] == "e") && it[3] == "fork" }
|
||||
|
||||
override fun forkFromAddress() = tags.firstNotNullOfOrNull(ForkTag::parseAddress)
|
||||
|
||||
override fun forkFromVersion() = tags.firstNotNullOfOrNull(MarkedETag::parseForkedEventId)
|
||||
|
||||
override fun publishedAt(): Long? {
|
||||
val publishedAt = tags.firstNotNullOfOrNull(PublishedAtTag::parse)
|
||||
|
||||
if (publishedAt == null) return null
|
||||
val publishedAt = tags.firstNotNullOfOrNull(PublishedAtTag::parse) ?: return null
|
||||
|
||||
// removes posts in the future.
|
||||
return if (publishedAt <= createdAt) {
|
||||
@@ -146,20 +163,27 @@ class WikiNoteEvent(
|
||||
companion object {
|
||||
const val KIND = 30818
|
||||
|
||||
suspend fun create(
|
||||
msg: String,
|
||||
title: String?,
|
||||
replyTos: List<String>?,
|
||||
mentions: List<String>?,
|
||||
signer: NostrSigner,
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
fun build(
|
||||
description: String,
|
||||
title: String,
|
||||
summary: String? = null,
|
||||
image: String? = null,
|
||||
publishedAt: Long? = null,
|
||||
dTag: String = Uuid.random().toString(),
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): WikiNoteEvent {
|
||||
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(AltTag.assemble("Wiki Post: $title"))
|
||||
return signer.sign(createdAt, KIND, tags.toTypedArray(), msg)
|
||||
}
|
||||
initializer: TagArrayBuilder<WikiNoteEvent>.() -> Unit = {},
|
||||
): EventTemplate<WikiNoteEvent> =
|
||||
eventTemplate(KIND, description, createdAt) {
|
||||
dTag(dTag)
|
||||
alt("Wiki entry: $title")
|
||||
|
||||
title(title)
|
||||
summary?.let { summary(it) }
|
||||
image?.let { image(it) }
|
||||
publishedAt?.let { publishedAt(it) }
|
||||
|
||||
initializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipA3.PaymentTargetsEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
@@ -266,6 +267,7 @@ class EventFactory {
|
||||
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NipTextEvent.KIND -> NipTextEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NostrConnectEvent.KIND -> NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
||||
Reference in New Issue
Block a user