Compare commits
36 Commits
2025-09-04
...
translatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
e3e7d54142
|
|||
|
|
a6d91c43e4 | ||
|
|
19fe3703d9 | ||
|
|
ca67977b82 | ||
|
|
559e9577fc | ||
|
|
563fbb9c4b | ||
|
|
391900d393 | ||
|
|
11700d6217 | ||
|
|
50293a6f34 | ||
|
|
d6182ed7c3 | ||
|
|
a0e9c8b434 | ||
|
|
4ac2e59983 | ||
|
|
3f1a194983 | ||
|
|
a8eaea6509 | ||
|
|
9278c90802 | ||
|
|
02a90eccd1 | ||
|
|
c0fcf53ff6 | ||
|
|
f889b54ed9 | ||
|
|
7b4c96df91 | ||
|
|
eb44637601 | ||
|
|
ea14713b58 | ||
|
|
a5e7880e25 | ||
|
|
409ca68567 | ||
|
|
6cf193b7e3 | ||
|
|
5bb17cd810 | ||
|
|
ba359c95c2 | ||
|
|
e0ed122951 | ||
|
|
e1ad2e231f | ||
|
|
91028929b2 | ||
|
|
2eef34fa1c | ||
|
|
b8eecf0c9a | ||
|
|
1b9e77a1ff | ||
|
|
28634301b8 | ||
|
|
ce0d3e8e88 | ||
|
|
0b4545d598 | ||
|
|
6db03364fd |
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -193,7 +193,7 @@ dependencies = [
|
|||||||
"objc2-foundation 0.3.1",
|
"objc2-foundation 0.3.1",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
"x11rb",
|
"x11rb",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ dependencies = [
|
|||||||
"enumflags2",
|
"enumflags2",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@@ -3542,6 +3542,7 @@ dependencies = [
|
|||||||
"profiling",
|
"profiling",
|
||||||
"puffin",
|
"puffin",
|
||||||
"puffin_egui",
|
"puffin_egui",
|
||||||
|
"rand 0.9.2",
|
||||||
"regex",
|
"regex",
|
||||||
"secp256k1 0.30.0",
|
"secp256k1 0.30.0",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3683,7 +3684,7 @@ dependencies = [
|
|||||||
"nostrdb",
|
"nostrdb",
|
||||||
"notedeck",
|
"notedeck",
|
||||||
"notedeck_ui",
|
"notedeck_ui",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
@@ -4603,7 +4604,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"getrandom 0.3.3",
|
"getrandom 0.3.3",
|
||||||
"lru-slab",
|
"lru-slab",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"ring",
|
"ring",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"rustls",
|
"rustls",
|
||||||
@@ -4657,9 +4658,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.9.1"
|
version = "0.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
"rand_core 0.9.3",
|
"rand_core 0.9.3",
|
||||||
@@ -6278,7 +6279,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.9.1",
|
"rand 0.9.2",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ chrono = "0.4.40"
|
|||||||
base32 = "0.4.0"
|
base32 = "0.4.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
rmpv = "1.3.0"
|
rmpv = "1.3.0"
|
||||||
|
rand = "0.9.2"
|
||||||
bech32 = { version = "0.11", default-features = false }
|
bech32 = { version = "0.11", default-features = false }
|
||||||
bitflags = "2.5.0"
|
bitflags = "2.5.0"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ Add_External_Notifications_Column_41ae = Agregar columna de notificaciones exter
|
|||||||
Add_Hashtag_Column_ebf4 = Agregar columna de hashtags
|
Add_Hashtag_Column_ebf4 = Agregar columna de hashtags
|
||||||
# Column title for adding last notes column
|
# Column title for adding last notes column
|
||||||
Add_Last_Notes_Column_bbad = Agregar columna de últimas notas
|
Add_Last_Notes_Column_bbad = Agregar columna de últimas notas
|
||||||
|
# Tooltip text for adding a new deck button
|
||||||
|
Add_new_deck_f2fc = Agregar nuevo deck
|
||||||
# Column title for adding notifications column
|
# Column title for adding notifications column
|
||||||
Add_Notifications_Column_79f8 = Agregar columna de notificaciones
|
Add_Notifications_Column_79f8 = Agregar columna de notificaciones
|
||||||
# Button label to add a relay
|
# Button label to add a relay
|
||||||
@@ -93,6 +95,8 @@ Copy_Link_dc7c = Copiar enlace
|
|||||||
Copy_Note_ID_6b45 = Copiar ID de nota
|
Copy_Note_ID_6b45 = Copiar ID de nota
|
||||||
# Copy the raw note data in JSON format to clipboard
|
# Copy the raw note data in JSON format to clipboard
|
||||||
Copy_Note_JSON_9e4e = Copiar JSON de nota
|
Copy_Note_JSON_9e4e = Copiar JSON de nota
|
||||||
|
# Tooltip text for copying npub to clipboard
|
||||||
|
Copy_npub_to_clipboard_c105 = Copiar npub al portapapeles
|
||||||
# Copy the author's public key to clipboard
|
# Copy the author's public key to clipboard
|
||||||
Copy_Pubkey_9cc4 = Copiar pubkey
|
Copy_Pubkey_9cc4 = Copiar pubkey
|
||||||
# Copy the text content of the note to clipboard
|
# Copy the text content of the note to clipboard
|
||||||
@@ -141,6 +145,8 @@ Delete_Wallet_d1d4 = Eliminar billetera
|
|||||||
Display_name_f9d9 = Nombre para mostrar
|
Display_name_f9d9 = Nombre para mostrar
|
||||||
# Domain identification message
|
# Domain identification message
|
||||||
domain___will_be_used_for_identification_b67e = "{ $domain }" se utilizará para la identificación
|
domain___will_be_used_for_identification_b67e = "{ $domain }" se utilizará para la identificación
|
||||||
|
# Button to indicate that the user is done going through the onboarding process.
|
||||||
|
Done_50dd = Listo
|
||||||
# Column title for editing deck
|
# Column title for editing deck
|
||||||
Edit_Deck_4018 = Editar deck
|
Edit_Deck_4018 = Editar deck
|
||||||
# Button label to edit a deck
|
# Button label to edit a deck
|
||||||
@@ -162,7 +168,7 @@ Enter_your_public_key__npub___nostr_address__e_g___address____or_private_key__ns
|
|||||||
# Label for find user button
|
# Label for find user button
|
||||||
Find_User_bd12 = Buscar usuario
|
Find_User_bd12 = Buscar usuario
|
||||||
# Label for font size, Appearance settings section
|
# Label for font size, Appearance settings section
|
||||||
Font_size_dd73 = Font size:
|
Font_size_dd73 = Tamaño de la fuente:
|
||||||
# Title for hashtags column
|
# Title for hashtags column
|
||||||
Hashtags_f8e0 = Hashtags
|
Hashtags_f8e0 = Hashtags
|
||||||
# Title for Home column
|
# Title for Home column
|
||||||
@@ -191,6 +197,8 @@ k_50K_c2dc = 50.000
|
|||||||
k_5K_f7e6 = 5.000
|
k_5K_f7e6 = 5.000
|
||||||
# Description for your notes column
|
# Description for your notes column
|
||||||
Keep_track_of_your_notes___replies_a334 = Haz seguimiento de tus notas y respuestas
|
Keep_track_of_your_notes___replies_a334 = Haz seguimiento de tus notas y respuestas
|
||||||
|
# label for keys setting section
|
||||||
|
Keys_435f = Claves
|
||||||
# Label for language, Appearance settings section
|
# Label for language, Appearance settings section
|
||||||
Language_e264 = Idioma:
|
Language_e264 = Idioma:
|
||||||
# Title for last note per user column
|
# Title for last note per user column
|
||||||
@@ -209,6 +217,14 @@ Media_from_someone_you_don_t_follow_5611 = Contenido multimedia de alguien que n
|
|||||||
Moves_this_column_to_another_position_0d4b = Mueve esta columna a otra posición
|
Moves_this_column_to_another_position_0d4b = Mueve esta columna a otra posición
|
||||||
# Title for the user's deck
|
# Title for the user's deck
|
||||||
My_Deck_4ac5 = Mi deck
|
My_Deck_4ac5 = Mi deck
|
||||||
|
# reaction from user to a note you were tagged in
|
||||||
|
name__reacted_to_a_note_you_were_tagged_in_4b62 = { $name } reaccionó a una nota en la que te etiquetaron
|
||||||
|
# reaction from user to your note
|
||||||
|
name__reacted_to_your_note_ead9 = { $name } reaccionó a tu nota
|
||||||
|
# repost from user
|
||||||
|
name__reposted_a_note_you_were_tagged_in_1379 = { $name } volvió a publicar una nota en la que te etiquetaron
|
||||||
|
# repost from user
|
||||||
|
name__reposted_your_note_1379 = { $name } volvió a publicar tu nota
|
||||||
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
||||||
New_to_Nostr_a2fd = ¿Primera vez en Nostr?
|
New_to_Nostr_a2fd = ¿Primera vez en Nostr?
|
||||||
# NIP-05 identity field label
|
# NIP-05 identity field label
|
||||||
@@ -236,7 +252,9 @@ Notifications_ef56 = Notificaciones
|
|||||||
# Relative time for very recent events (less than 3 seconds)
|
# Relative time for very recent events (less than 3 seconds)
|
||||||
now_2181 = ahora
|
now_2181 = ahora
|
||||||
# Setting to turn on sorting replies so that the newest are shown first
|
# Setting to turn on sorting replies so that the newest are shown first
|
||||||
On_f412 = On
|
On_f412 = Activado
|
||||||
|
# Column title for finding users to follow
|
||||||
|
Onboarding_4a25 = Incorporación
|
||||||
# Button label to open email client
|
# Button label to open email client
|
||||||
Open_Email_25e9 = Abrir correo electrónico
|
Open_Email_25e9 = Abrir correo electrónico
|
||||||
# Instruction to open email client
|
# Instruction to open email client
|
||||||
@@ -257,6 +275,8 @@ Post_now_8a49 = Publicar ahora
|
|||||||
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Presiona el siguiente botón para copiar los registros más recientes al portapapeles del sistema. A continuación, pégalos en tu correo electrónico.
|
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Presiona el siguiente botón para copiar los registros más recientes al portapapeles del sistema. A continuación, pégalos en tu correo electrónico.
|
||||||
# Profile picture URL field label
|
# Profile picture URL field label
|
||||||
Profile_picture_81ff = Imagen de perfil
|
Profile_picture_81ff = Imagen de perfil
|
||||||
|
# label describing public key
|
||||||
|
PUBLIC_ACCOUNT_ID_4394 = ID DE CUENTA PÚBLICA
|
||||||
# Column title for quote composition
|
# Column title for quote composition
|
||||||
Quote_475c = Citar
|
Quote_475c = Citar
|
||||||
# Error message when quote note cannot be found
|
# Error message when quote note cannot be found
|
||||||
@@ -290,7 +310,7 @@ Repost_this_note_8e56 = Volver a publicar esta nota
|
|||||||
# Label for reposted notes
|
# Label for reposted notes
|
||||||
Reposted_61c8 = Publicadas de nuevo
|
Reposted_61c8 = Publicadas de nuevo
|
||||||
# Label for reset note body font size, Appearance settings section
|
# Label for reset note body font size, Appearance settings section
|
||||||
Reset_4e60 = Reset
|
Reset_4e60 = Restablecer
|
||||||
# Label for reset zoom level, Appearance settings section
|
# Label for reset zoom level, Appearance settings section
|
||||||
Reset_62d4 = Restablecer
|
Reset_62d4 = Restablecer
|
||||||
# Heading for support section
|
# Heading for support section
|
||||||
@@ -309,10 +329,14 @@ Search_c573 = Búsqueda
|
|||||||
Search_notes_42a6 = Buscar notas...
|
Search_notes_42a6 = Buscar notas...
|
||||||
# Search in progress message
|
# Search in progress message
|
||||||
Searching_for___query_5d18 = Buscando '{ $query }'
|
Searching_for___query_5d18 = Buscando '{ $query }'
|
||||||
|
# label describing secret key
|
||||||
|
SECRET_ACCOUNT_LOGIN_KEY_8440 = CLAVE DE INICIO DE SESIÓN DE CUENTA SECRETA
|
||||||
# Description for Home column
|
# Description for Home column
|
||||||
See_notes_from_your_contacts_ac16 = Ver notas de tus contactos
|
See_notes_from_your_contacts_ac16 = Ver notas de tus contactos
|
||||||
# Description for universe column
|
# Description for universe column
|
||||||
See_the_whole_nostr_universe_7694 = Ver todo el universo de nostr
|
See_the_whole_nostr_universe_7694 = Ver todo el universo de nostr
|
||||||
|
# Button to select all profiles in follow pack
|
||||||
|
Select_All_a319 = Seleccionar todo
|
||||||
# Button label to send a zap
|
# Button label to send a zap
|
||||||
Send_1ea4 = Enviar
|
Send_1ea4 = Enviar
|
||||||
# Column title for app settings
|
# Column title for app settings
|
||||||
@@ -326,7 +350,7 @@ Someone_else_s_Notes_7e5f = Notas de otra persona
|
|||||||
# Title for someone else's notifications column
|
# Title for someone else's notifications column
|
||||||
Someone_else_s_Notifications_82e6 = Notificaciones de otra persona
|
Someone_else_s_Notifications_82e6 = Notificaciones de otra persona
|
||||||
# Label for Sort replies newest first, others settings section
|
# Label for Sort replies newest first, others settings section
|
||||||
Sort_replies_newest_first_b6c3 = Sort replies newest first:
|
Sort_replies_newest_first_b6c3 = Ordenar las respuestas más recientes primero:
|
||||||
# Description for contact list column
|
# Description for contact list column
|
||||||
Source_the_last_note_for_each_user_in_your_contact_list_e157 = Busca la última nota de cada usuario en tu lista de contactos
|
Source_the_last_note_for_each_user_in_your_contact_list_e157 = Busca la última nota de cada usuario en tu lista de contactos
|
||||||
# Description for hashtags column
|
# Description for hashtags column
|
||||||
@@ -352,7 +376,7 @@ Subscribe_to_someone_else_s_notes_d1e9 = Suscribirse a las notas de otra persona
|
|||||||
# Column title for subscribing to individual user
|
# Column title for subscribing to individual user
|
||||||
Subscribe_to_someone_s_notes_b3c8 = Suscribirse a las notas de alguien
|
Subscribe_to_someone_s_notes_b3c8 = Suscribirse a las notas de alguien
|
||||||
# Support email address
|
# Support email address
|
||||||
Support_email_44d9 = Support email:
|
Support_email_44d9 = Correo electrónico de ayuda:
|
||||||
# Hover text for dark mode toggle button
|
# Hover text for dark mode toggle button
|
||||||
Switch_to_dark_mode_4dec = Cambiar a modo oscuro
|
Switch_to_dark_mode_4dec = Cambiar a modo oscuro
|
||||||
# Hover text for light mode toggle button
|
# Hover text for light mode toggle button
|
||||||
@@ -405,6 +429,30 @@ Zoom_Level_29a8 = Nivel de zoom:
|
|||||||
# Search results count
|
# Search results count
|
||||||
Got__count__results_for___query_85fb =
|
Got__count__results_for___query_85fb =
|
||||||
{ $count ->
|
{ $count ->
|
||||||
[uno] Obtuvo { $count } resultado para '{ $query }'
|
[uno] Se obtuvo { $count } resultado para '{ $query }'
|
||||||
*[otro] Obtuvo { $count } resultados para '{ $query }'
|
*[otro] Se obtuvieron { $count } resultados para '{ $query }'
|
||||||
|
}
|
||||||
|
# amount of reactions a note you were tagged in received
|
||||||
|
name__and__count__others_reacted_to_a_note_you_were_tagged_in_181a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona más reaccionaron a una nota en la que te etiquetaron
|
||||||
|
*[other] { $name } y { $count } personas más reaccionaron a una nota en la que te etiquetaron
|
||||||
|
}
|
||||||
|
# describing the amount of reactions your note received
|
||||||
|
name__and__count__others_reacted_to_your_note_0f6a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona más reaccionaron a tu nota
|
||||||
|
*[other] { $name } y { $count } personas más reaccionaron a tu nota
|
||||||
|
}
|
||||||
|
# describing the amount of reposts a note you were tagged in received
|
||||||
|
name__and__count__others_reposted_a_note_you_were_tagged_in_08e1 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona más volvieron a publicar una nota en la que te etiquetaron
|
||||||
|
*[other] { $name } y { $count } personas más volvieron a publicar una nota en la que te etiquetaron
|
||||||
|
}
|
||||||
|
# describing the amount of reposts your note received
|
||||||
|
name__and__count__others_reposted_your_note_70a0 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona más volvieron a publicar tu nota
|
||||||
|
*[other] { $name } y { $count } personas más volvieron a publicar tu nota
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ Add_External_Notifications_Column_41ae = Añadir columna de notificaciones exter
|
|||||||
Add_Hashtag_Column_ebf4 = Añadir columna de hashtags
|
Add_Hashtag_Column_ebf4 = Añadir columna de hashtags
|
||||||
# Column title for adding last notes column
|
# Column title for adding last notes column
|
||||||
Add_Last_Notes_Column_bbad = Añadir columna de últimas notas
|
Add_Last_Notes_Column_bbad = Añadir columna de últimas notas
|
||||||
|
# Tooltip text for adding a new deck button
|
||||||
|
Add_new_deck_f2fc = Añadir nuevo deck
|
||||||
# Column title for adding notifications column
|
# Column title for adding notifications column
|
||||||
Add_Notifications_Column_79f8 = Añadir columna de notificaciones
|
Add_Notifications_Column_79f8 = Añadir columna de notificaciones
|
||||||
# Button label to add a relay
|
# Button label to add a relay
|
||||||
@@ -93,6 +95,8 @@ Copy_Link_dc7c = Copiar enlace
|
|||||||
Copy_Note_ID_6b45 = Copiar ID de nota
|
Copy_Note_ID_6b45 = Copiar ID de nota
|
||||||
# Copy the raw note data in JSON format to clipboard
|
# Copy the raw note data in JSON format to clipboard
|
||||||
Copy_Note_JSON_9e4e = Copiar JSON de nota
|
Copy_Note_JSON_9e4e = Copiar JSON de nota
|
||||||
|
# Tooltip text for copying npub to clipboard
|
||||||
|
Copy_npub_to_clipboard_c105 = Copiar npub al portapapeles
|
||||||
# Copy the author's public key to clipboard
|
# Copy the author's public key to clipboard
|
||||||
Copy_Pubkey_9cc4 = Copiar pubkey
|
Copy_Pubkey_9cc4 = Copiar pubkey
|
||||||
# Copy the text content of the note to clipboard
|
# Copy the text content of the note to clipboard
|
||||||
@@ -141,6 +145,8 @@ Delete_Wallet_d1d4 = Eliminar monedero
|
|||||||
Display_name_f9d9 = Nombre para mostrar
|
Display_name_f9d9 = Nombre para mostrar
|
||||||
# Domain identification message
|
# Domain identification message
|
||||||
domain___will_be_used_for_identification_b67e = "{ $domain }" se utilizará para la identificación
|
domain___will_be_used_for_identification_b67e = "{ $domain }" se utilizará para la identificación
|
||||||
|
# Button to indicate that the user is done going through the onboarding process.
|
||||||
|
Done_50dd = Listo
|
||||||
# Column title for editing deck
|
# Column title for editing deck
|
||||||
Edit_Deck_4018 = Editar deck
|
Edit_Deck_4018 = Editar deck
|
||||||
# Button label to edit a deck
|
# Button label to edit a deck
|
||||||
@@ -162,7 +168,7 @@ Enter_your_public_key__npub___nostr_address__e_g___address____or_private_key__ns
|
|||||||
# Label for find user button
|
# Label for find user button
|
||||||
Find_User_bd12 = Buscar usuario
|
Find_User_bd12 = Buscar usuario
|
||||||
# Label for font size, Appearance settings section
|
# Label for font size, Appearance settings section
|
||||||
Font_size_dd73 = Font size:
|
Font_size_dd73 = Tamaño de la fuente:
|
||||||
# Title for hashtags column
|
# Title for hashtags column
|
||||||
Hashtags_f8e0 = Hashtags
|
Hashtags_f8e0 = Hashtags
|
||||||
# Title for Home column
|
# Title for Home column
|
||||||
@@ -191,6 +197,8 @@ k_50K_c2dc = 50.000
|
|||||||
k_5K_f7e6 = 5.000
|
k_5K_f7e6 = 5.000
|
||||||
# Description for your notes column
|
# Description for your notes column
|
||||||
Keep_track_of_your_notes___replies_a334 = Haz seguimiento de tus notas y respuestas
|
Keep_track_of_your_notes___replies_a334 = Haz seguimiento de tus notas y respuestas
|
||||||
|
# label for keys setting section
|
||||||
|
Keys_435f = Claves
|
||||||
# Label for language, Appearance settings section
|
# Label for language, Appearance settings section
|
||||||
Language_e264 = Idioma:
|
Language_e264 = Idioma:
|
||||||
# Title for last note per user column
|
# Title for last note per user column
|
||||||
@@ -209,6 +217,14 @@ Media_from_someone_you_don_t_follow_5611 = Contenido multimedia de alguien que n
|
|||||||
Moves_this_column_to_another_position_0d4b = Mueve esta columna a otra posición
|
Moves_this_column_to_another_position_0d4b = Mueve esta columna a otra posición
|
||||||
# Title for the user's deck
|
# Title for the user's deck
|
||||||
My_Deck_4ac5 = Mi deck
|
My_Deck_4ac5 = Mi deck
|
||||||
|
# reaction from user to a note you were tagged in
|
||||||
|
name__reacted_to_a_note_you_were_tagged_in_4b62 = { $name } ha reaccionado a una nota en la que te han etiquetado
|
||||||
|
# reaction from user to your note
|
||||||
|
name__reacted_to_your_note_ead9 = { $name } ha reaccionado a tu nota
|
||||||
|
# repost from user
|
||||||
|
name__reposted_a_note_you_were_tagged_in_1379 = { $name } ha vuelto a publicar una nota en la que te han etiquetado
|
||||||
|
# repost from user
|
||||||
|
name__reposted_your_note_1379 = { $name } ha vuelto a publicar tu nota
|
||||||
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
||||||
New_to_Nostr_a2fd = ¿Primera vez en Nostr?
|
New_to_Nostr_a2fd = ¿Primera vez en Nostr?
|
||||||
# NIP-05 identity field label
|
# NIP-05 identity field label
|
||||||
@@ -236,7 +252,9 @@ Notifications_ef56 = Notificaciones
|
|||||||
# Relative time for very recent events (less than 3 seconds)
|
# Relative time for very recent events (less than 3 seconds)
|
||||||
now_2181 = ahora
|
now_2181 = ahora
|
||||||
# Setting to turn on sorting replies so that the newest are shown first
|
# Setting to turn on sorting replies so that the newest are shown first
|
||||||
On_f412 = On
|
On_f412 = Activado
|
||||||
|
# Column title for finding users to follow
|
||||||
|
Onboarding_4a25 = Incorporación
|
||||||
# Button label to open email client
|
# Button label to open email client
|
||||||
Open_Email_25e9 = Abrir correo electrónico
|
Open_Email_25e9 = Abrir correo electrónico
|
||||||
# Instruction to open email client
|
# Instruction to open email client
|
||||||
@@ -257,6 +275,8 @@ Post_now_8a49 = Publicar ahora
|
|||||||
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Presiona el siguiente botón para copiar los registros más recientes al portapapeles del sistema. A continuación, pégalos en tu correo electrónico.
|
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Presiona el siguiente botón para copiar los registros más recientes al portapapeles del sistema. A continuación, pégalos en tu correo electrónico.
|
||||||
# Profile picture URL field label
|
# Profile picture URL field label
|
||||||
Profile_picture_81ff = Imagen de perfil
|
Profile_picture_81ff = Imagen de perfil
|
||||||
|
# label describing public key
|
||||||
|
PUBLIC_ACCOUNT_ID_4394 = ID DE CUENTA PÚBLICA
|
||||||
# Column title for quote composition
|
# Column title for quote composition
|
||||||
Quote_475c = Citar
|
Quote_475c = Citar
|
||||||
# Error message when quote note cannot be found
|
# Error message when quote note cannot be found
|
||||||
@@ -290,7 +310,7 @@ Repost_this_note_8e56 = Volver a publicar esta nota
|
|||||||
# Label for reposted notes
|
# Label for reposted notes
|
||||||
Reposted_61c8 = Publicadas de nuevo
|
Reposted_61c8 = Publicadas de nuevo
|
||||||
# Label for reset note body font size, Appearance settings section
|
# Label for reset note body font size, Appearance settings section
|
||||||
Reset_4e60 = Reset
|
Reset_4e60 = Restablecer
|
||||||
# Label for reset zoom level, Appearance settings section
|
# Label for reset zoom level, Appearance settings section
|
||||||
Reset_62d4 = Restablecer
|
Reset_62d4 = Restablecer
|
||||||
# Heading for support section
|
# Heading for support section
|
||||||
@@ -309,10 +329,14 @@ Search_c573 = Búsqueda
|
|||||||
Search_notes_42a6 = Buscar notas...
|
Search_notes_42a6 = Buscar notas...
|
||||||
# Search in progress message
|
# Search in progress message
|
||||||
Searching_for___query_5d18 = Buscando '{ $query }'
|
Searching_for___query_5d18 = Buscando '{ $query }'
|
||||||
|
# label describing secret key
|
||||||
|
SECRET_ACCOUNT_LOGIN_KEY_8440 = CLAVE DE INICIO DE SESIÓN DE CUENTA SECRETA
|
||||||
# Description for Home column
|
# Description for Home column
|
||||||
See_notes_from_your_contacts_ac16 = Ver notas de tus contactos
|
See_notes_from_your_contacts_ac16 = Ver notas de tus contactos
|
||||||
# Description for universe column
|
# Description for universe column
|
||||||
See_the_whole_nostr_universe_7694 = Ver todo el universo de nostr
|
See_the_whole_nostr_universe_7694 = Ver todo el universo de nostr
|
||||||
|
# Button to select all profiles in follow pack
|
||||||
|
Select_All_a319 = Seleccionar todo
|
||||||
# Button label to send a zap
|
# Button label to send a zap
|
||||||
Send_1ea4 = Enviar
|
Send_1ea4 = Enviar
|
||||||
# Column title for app settings
|
# Column title for app settings
|
||||||
@@ -326,7 +350,7 @@ Someone_else_s_Notes_7e5f = Notas de otra persona
|
|||||||
# Title for someone else's notifications column
|
# Title for someone else's notifications column
|
||||||
Someone_else_s_Notifications_82e6 = Notificaciones de otra persona
|
Someone_else_s_Notifications_82e6 = Notificaciones de otra persona
|
||||||
# Label for Sort replies newest first, others settings section
|
# Label for Sort replies newest first, others settings section
|
||||||
Sort_replies_newest_first_b6c3 = Sort replies newest first:
|
Sort_replies_newest_first_b6c3 = Ordenar las respuestas más recientes primero:
|
||||||
# Description for contact list column
|
# Description for contact list column
|
||||||
Source_the_last_note_for_each_user_in_your_contact_list_e157 = Busca la última nota de cada usuario en tu lista de contactos
|
Source_the_last_note_for_each_user_in_your_contact_list_e157 = Busca la última nota de cada usuario en tu lista de contactos
|
||||||
# Description for hashtags column
|
# Description for hashtags column
|
||||||
@@ -352,7 +376,7 @@ Subscribe_to_someone_else_s_notes_d1e9 = Suscribirse a las notas de otra persona
|
|||||||
# Column title for subscribing to individual user
|
# Column title for subscribing to individual user
|
||||||
Subscribe_to_someone_s_notes_b3c8 = Suscribirse a las notas de alguien
|
Subscribe_to_someone_s_notes_b3c8 = Suscribirse a las notas de alguien
|
||||||
# Support email address
|
# Support email address
|
||||||
Support_email_44d9 = Support email:
|
Support_email_44d9 = Correo electrónico de ayuda:
|
||||||
# Hover text for dark mode toggle button
|
# Hover text for dark mode toggle button
|
||||||
Switch_to_dark_mode_4dec = Cambiar a modo oscuro
|
Switch_to_dark_mode_4dec = Cambiar a modo oscuro
|
||||||
# Hover text for light mode toggle button
|
# Hover text for light mode toggle button
|
||||||
@@ -405,6 +429,30 @@ Zoom_Level_29a8 = Nivel de zoom:
|
|||||||
# Search results count
|
# Search results count
|
||||||
Got__count__results_for___query_85fb =
|
Got__count__results_for___query_85fb =
|
||||||
{ $count ->
|
{ $count ->
|
||||||
[uno] Obtuvo { $count } resultado para '{ $query }'
|
[uno] Se ha obtenido { $count } resultado para '{ $query }'
|
||||||
*[otro] Obtuvo { $count } resultados para '{ $query }'
|
*[otro] Se han obtenido { $count } resultados para '{ $query }'
|
||||||
|
}
|
||||||
|
# amount of reactions a note you were tagged in received
|
||||||
|
name__and__count__others_reacted_to_a_note_you_were_tagged_in_181a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona más han reaccionado a una nota en la que te han etiquetado
|
||||||
|
*[other] { $name } y { $count } personas más han reaccionado a una nota en la que te han etiquetado
|
||||||
|
}
|
||||||
|
# describing the amount of reactions your note received
|
||||||
|
name__and__count__others_reacted_to_your_note_0f6a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona más han reaccionado a tu nota
|
||||||
|
*[other] { $name } y { $count } personas más han reaccionado a tu nota
|
||||||
|
}
|
||||||
|
# describing the amount of reposts a note you were tagged in received
|
||||||
|
name__and__count__others_reposted_a_note_you_were_tagged_in_08e1 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona más han vuelto a publicar una nota en la que te han etiquetado
|
||||||
|
*[other] { $name } y { $count } personas más han vuelto a publicar una nota en la que te han etiquetado
|
||||||
|
}
|
||||||
|
# describing the amount of reposts your note received
|
||||||
|
name__and__count__others_reposted_your_note_70a0 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } y { $count } persona han vuelto a publicar tu nota
|
||||||
|
*[other] { $name } y { $count } personas más han vuelto a publicar tu nota
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ Add_External_Notifications_Column_41ae = Ajouter une colonne pour les notificati
|
|||||||
Add_Hashtag_Column_ebf4 = Ajouter une colonne Hashtag
|
Add_Hashtag_Column_ebf4 = Ajouter une colonne Hashtag
|
||||||
# Column title for adding last notes column
|
# Column title for adding last notes column
|
||||||
Add_Last_Notes_Column_bbad = Ajouter une colonne pour les dernières notes
|
Add_Last_Notes_Column_bbad = Ajouter une colonne pour les dernières notes
|
||||||
|
# Tooltip text for adding a new deck button
|
||||||
|
Add_new_deck_f2fc = Ajouter un nouveau deck
|
||||||
# Column title for adding notifications column
|
# Column title for adding notifications column
|
||||||
Add_Notifications_Column_79f8 = Ajouter une colonne pour les notifications
|
Add_Notifications_Column_79f8 = Ajouter une colonne pour les notifications
|
||||||
# Button label to add a relay
|
# Button label to add a relay
|
||||||
@@ -93,6 +95,8 @@ Copy_Link_dc7c = Copier le lien
|
|||||||
Copy_Note_ID_6b45 = Copier l'ID de la note
|
Copy_Note_ID_6b45 = Copier l'ID de la note
|
||||||
# Copy the raw note data in JSON format to clipboard
|
# Copy the raw note data in JSON format to clipboard
|
||||||
Copy_Note_JSON_9e4e = Copier le JSON de la note
|
Copy_Note_JSON_9e4e = Copier le JSON de la note
|
||||||
|
# Tooltip text for copying npub to clipboard
|
||||||
|
Copy_npub_to_clipboard_c105 = Copier la npub dans le presse-papiers
|
||||||
# Copy the author's public key to clipboard
|
# Copy the author's public key to clipboard
|
||||||
Copy_Pubkey_9cc4 = Copier la Pubkey
|
Copy_Pubkey_9cc4 = Copier la Pubkey
|
||||||
# Copy the text content of the note to clipboard
|
# Copy the text content of the note to clipboard
|
||||||
@@ -141,6 +145,8 @@ Delete_Wallet_d1d4 = Supprimer le portefeuille
|
|||||||
Display_name_f9d9 = Nom d'utilisateur
|
Display_name_f9d9 = Nom d'utilisateur
|
||||||
# Domain identification message
|
# Domain identification message
|
||||||
domain___will_be_used_for_identification_b67e = "{ $domain }" sera utilisé pour l'identification
|
domain___will_be_used_for_identification_b67e = "{ $domain }" sera utilisé pour l'identification
|
||||||
|
# Button to indicate that the user is done going through the onboarding process.
|
||||||
|
Done_50dd = Fait
|
||||||
# Column title for editing deck
|
# Column title for editing deck
|
||||||
Edit_Deck_4018 = Modifier le deck
|
Edit_Deck_4018 = Modifier le deck
|
||||||
# Button label to edit a deck
|
# Button label to edit a deck
|
||||||
@@ -191,6 +197,8 @@ k_50K_c2dc = 50K
|
|||||||
k_5K_f7e6 = 5K
|
k_5K_f7e6 = 5K
|
||||||
# Description for your notes column
|
# Description for your notes column
|
||||||
Keep_track_of_your_notes___replies_a334 = Gardez une trace de vos notes & réponses
|
Keep_track_of_your_notes___replies_a334 = Gardez une trace de vos notes & réponses
|
||||||
|
# label for keys setting section
|
||||||
|
Keys_435f = Clés
|
||||||
# Label for language, Appearance settings section
|
# Label for language, Appearance settings section
|
||||||
Language_e264 = Langue :
|
Language_e264 = Langue :
|
||||||
# Title for last note per user column
|
# Title for last note per user column
|
||||||
@@ -209,6 +217,14 @@ Media_from_someone_you_don_t_follow_5611 = Média d'une personne que vous ne sui
|
|||||||
Moves_this_column_to_another_position_0d4b = Déplace cette colonne vers une autre position
|
Moves_this_column_to_another_position_0d4b = Déplace cette colonne vers une autre position
|
||||||
# Title for the user's deck
|
# Title for the user's deck
|
||||||
My_Deck_4ac5 = Mon deck
|
My_Deck_4ac5 = Mon deck
|
||||||
|
# reaction from user to a note you were tagged in
|
||||||
|
name__reacted_to_a_note_you_were_tagged_in_4b62 = { $name } a réagi à une note dans laquelle vous avez été tagué
|
||||||
|
# reaction from user to your note
|
||||||
|
name__reacted_to_your_note_ead9 = { $name } a réagi à votre note
|
||||||
|
# repost from user
|
||||||
|
name__reposted_a_note_you_were_tagged_in_1379 = { $name } a reposté une note dans laquelle vous avez été tagué
|
||||||
|
# repost from user
|
||||||
|
name__reposted_your_note_1379 = { $name } a reposté votre note
|
||||||
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
||||||
New_to_Nostr_a2fd = Nouveau sur Nostr ?
|
New_to_Nostr_a2fd = Nouveau sur Nostr ?
|
||||||
# NIP-05 identity field label
|
# NIP-05 identity field label
|
||||||
@@ -259,6 +275,8 @@ Post_now_8a49 = Publier maintenant
|
|||||||
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Cliquez sur le bouton ci-dessous pour copier vos données les plus récentes dans le presse-papiers de votre système. Collez-les ensuite dans votre courrier électronique.
|
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Cliquez sur le bouton ci-dessous pour copier vos données les plus récentes dans le presse-papiers de votre système. Collez-les ensuite dans votre courrier électronique.
|
||||||
# Profile picture URL field label
|
# Profile picture URL field label
|
||||||
Profile_picture_81ff = Photo de profil
|
Profile_picture_81ff = Photo de profil
|
||||||
|
# label describing public key
|
||||||
|
PUBLIC_ACCOUNT_ID_4394 = IDENTITE PUBLIQUE DU COMPTE
|
||||||
# Column title for quote composition
|
# Column title for quote composition
|
||||||
Quote_475c = Citation
|
Quote_475c = Citation
|
||||||
# Error message when quote note cannot be found
|
# Error message when quote note cannot be found
|
||||||
@@ -311,6 +329,8 @@ Search_c573 = Rechercher
|
|||||||
Search_notes_42a6 = Rechercher des notes...
|
Search_notes_42a6 = Rechercher des notes...
|
||||||
# Search in progress message
|
# Search in progress message
|
||||||
Searching_for___query_5d18 = Recherche par '{ $query }'
|
Searching_for___query_5d18 = Recherche par '{ $query }'
|
||||||
|
# label describing secret key
|
||||||
|
SECRET_ACCOUNT_LOGIN_KEY_8440 = CLÉ SECRETE DE CONNEXION DU COMPTE
|
||||||
# Description for Home column
|
# Description for Home column
|
||||||
See_notes_from_your_contacts_ac16 = Afficher les notes de vos contacts
|
See_notes_from_your_contacts_ac16 = Afficher les notes de vos contacts
|
||||||
# Description for universe column
|
# Description for universe column
|
||||||
@@ -412,3 +432,27 @@ Got__count__results_for___query_85fb =
|
|||||||
[one] A obtenu { $count } pour '{ $query }'
|
[one] A obtenu { $count } pour '{ $query }'
|
||||||
*[other] A obtenu { $count } pour '{ $query }'
|
*[other] A obtenu { $count } pour '{ $query }'
|
||||||
}
|
}
|
||||||
|
# amount of reactions a note you were tagged in received
|
||||||
|
name__and__count__others_reacted_to_a_note_you_were_tagged_in_181a =
|
||||||
|
{ $count ->
|
||||||
|
[un] { $name } et { $count } a réagi à une note où vous êtes tagué
|
||||||
|
*[autre] { $name } et { $count } ont réagi à une note où vous êtes tagué
|
||||||
|
}
|
||||||
|
# describing the amount of reactions your note received
|
||||||
|
name__and__count__others_reacted_to_your_note_0f6a =
|
||||||
|
{ $count ->
|
||||||
|
[un] { $name } et { $count } autres ont réagi à votre note
|
||||||
|
*[autre] { $name } et { $count } autres ont réagi à votre note
|
||||||
|
}
|
||||||
|
# describing the amount of reposts a note you were tagged in received
|
||||||
|
name__and__count__others_reposted_a_note_you_were_tagged_in_08e1 =
|
||||||
|
{ $count ->
|
||||||
|
[un] { $name } et { $count } a reposté une note où vous êtes tagué
|
||||||
|
*[autre] { $name } et { $count } ont reposté une note où vous êtes tagué
|
||||||
|
}
|
||||||
|
# describing the amount of reposts your note received
|
||||||
|
name__and__count__others_reposted_your_note_70a0 =
|
||||||
|
{ $count ->
|
||||||
|
[un] { $name } et { $count } a reposté votre note
|
||||||
|
*[autre] { $name } et { $count } ont reposté votre note
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ Add_External_Notifications_Column_41ae = Adicionar coluna de notificações exte
|
|||||||
Add_Hashtag_Column_ebf4 = Adicionar coluna de marcadores
|
Add_Hashtag_Column_ebf4 = Adicionar coluna de marcadores
|
||||||
# Column title for adding last notes column
|
# Column title for adding last notes column
|
||||||
Add_Last_Notes_Column_bbad = Adicionar coluna de últimas notas
|
Add_Last_Notes_Column_bbad = Adicionar coluna de últimas notas
|
||||||
|
# Tooltip text for adding a new deck button
|
||||||
|
Add_new_deck_f2fc = Adicionar nova aba
|
||||||
# Column title for adding notifications column
|
# Column title for adding notifications column
|
||||||
Add_Notifications_Column_79f8 = Adicionar coluna de notificações
|
Add_Notifications_Column_79f8 = Adicionar coluna de notificações
|
||||||
# Button label to add a relay
|
# Button label to add a relay
|
||||||
@@ -93,6 +95,8 @@ Copy_Link_dc7c = Copiar link
|
|||||||
Copy_Note_ID_6b45 = Copiar ID da nota
|
Copy_Note_ID_6b45 = Copiar ID da nota
|
||||||
# Copy the raw note data in JSON format to clipboard
|
# Copy the raw note data in JSON format to clipboard
|
||||||
Copy_Note_JSON_9e4e = Copiar JSON da nota
|
Copy_Note_JSON_9e4e = Copiar JSON da nota
|
||||||
|
# Tooltip text for copying npub to clipboard
|
||||||
|
Copy_npub_to_clipboard_c105 = Copiar npub para área de transferência
|
||||||
# Copy the author's public key to clipboard
|
# Copy the author's public key to clipboard
|
||||||
Copy_Pubkey_9cc4 = Copiar chave pública
|
Copy_Pubkey_9cc4 = Copiar chave pública
|
||||||
# Copy the text content of the note to clipboard
|
# Copy the text content of the note to clipboard
|
||||||
@@ -141,6 +145,8 @@ Delete_Wallet_d1d4 = Eliminar carteira
|
|||||||
Display_name_f9d9 = Nome a mostrar
|
Display_name_f9d9 = Nome a mostrar
|
||||||
# Domain identification message
|
# Domain identification message
|
||||||
domain___will_be_used_for_identification_b67e = "{ $domain }" será usado para identificação
|
domain___will_be_used_for_identification_b67e = "{ $domain }" será usado para identificação
|
||||||
|
# Button to indicate that the user is done going through the onboarding process.
|
||||||
|
Done_50dd = Concluído
|
||||||
# Column title for editing deck
|
# Column title for editing deck
|
||||||
Edit_Deck_4018 = Editar aba
|
Edit_Deck_4018 = Editar aba
|
||||||
# Button label to edit a deck
|
# Button label to edit a deck
|
||||||
@@ -191,6 +197,8 @@ k_50K_c2dc = 50K
|
|||||||
k_5K_f7e6 = 5K
|
k_5K_f7e6 = 5K
|
||||||
# Description for your notes column
|
# Description for your notes column
|
||||||
Keep_track_of_your_notes___replies_a334 = Acompanha as tuas notas e respostas
|
Keep_track_of_your_notes___replies_a334 = Acompanha as tuas notas e respostas
|
||||||
|
# label for keys setting section
|
||||||
|
Keys_435f = Chaves
|
||||||
# Label for language, Appearance settings section
|
# Label for language, Appearance settings section
|
||||||
Language_e264 = Idioma:
|
Language_e264 = Idioma:
|
||||||
# Title for last note per user column
|
# Title for last note per user column
|
||||||
@@ -209,6 +217,14 @@ Media_from_someone_you_don_t_follow_5611 = Conteúdo de alguém que não segues
|
|||||||
Moves_this_column_to_another_position_0d4b = Mover esta coluna para outra posição
|
Moves_this_column_to_another_position_0d4b = Mover esta coluna para outra posição
|
||||||
# Title for the user's deck
|
# Title for the user's deck
|
||||||
My_Deck_4ac5 = Minha aba
|
My_Deck_4ac5 = Minha aba
|
||||||
|
# reaction from user to a note you were tagged in
|
||||||
|
name__reacted_to_a_note_you_were_tagged_in_4b62 = { $name } reagiu a uma nota em que te marcaram
|
||||||
|
# reaction from user to your note
|
||||||
|
name__reacted_to_your_note_ead9 = { $name } reagiu à tua nota
|
||||||
|
# repost from user
|
||||||
|
name__reposted_a_note_you_were_tagged_in_1379 = { $name } republicou uma nota em que te marcaram
|
||||||
|
# repost from user
|
||||||
|
name__reposted_your_note_1379 = { $name } republicou a tua nota
|
||||||
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
||||||
New_to_Nostr_a2fd = Nov@ no Nostr?
|
New_to_Nostr_a2fd = Nov@ no Nostr?
|
||||||
# NIP-05 identity field label
|
# NIP-05 identity field label
|
||||||
@@ -259,6 +275,8 @@ Post_now_8a49 = Publicar agora
|
|||||||
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Prime o botão abaixo para copiar os teus registos mais recentes para a área de transferência do teu sistema. Depois cola-os no teu e-mail.
|
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = Prime o botão abaixo para copiar os teus registos mais recentes para a área de transferência do teu sistema. Depois cola-os no teu e-mail.
|
||||||
# Profile picture URL field label
|
# Profile picture URL field label
|
||||||
Profile_picture_81ff = Foto de perfil
|
Profile_picture_81ff = Foto de perfil
|
||||||
|
# label describing public key
|
||||||
|
PUBLIC_ACCOUNT_ID_4394 = ID da CONTA PÚBLICA
|
||||||
# Column title for quote composition
|
# Column title for quote composition
|
||||||
Quote_475c = Citação
|
Quote_475c = Citação
|
||||||
# Error message when quote note cannot be found
|
# Error message when quote note cannot be found
|
||||||
@@ -311,6 +329,8 @@ Search_c573 = Procurar
|
|||||||
Search_notes_42a6 = Procurar notas...
|
Search_notes_42a6 = Procurar notas...
|
||||||
# Search in progress message
|
# Search in progress message
|
||||||
Searching_for___query_5d18 = Procurando por '{ $query }'
|
Searching_for___query_5d18 = Procurando por '{ $query }'
|
||||||
|
# label describing secret key
|
||||||
|
SECRET_ACCOUNT_LOGIN_KEY_8440 = CHAVE SECRETA DE LOGIN DA CONTA
|
||||||
# Description for Home column
|
# Description for Home column
|
||||||
See_notes_from_your_contacts_ac16 = Ver notas dos meus contactos
|
See_notes_from_your_contacts_ac16 = Ver notas dos meus contactos
|
||||||
# Description for universe column
|
# Description for universe column
|
||||||
@@ -412,3 +432,27 @@ Got__count__results_for___query_85fb =
|
|||||||
[one] { $count } resultado obtido para '{ $query }'
|
[one] { $count } resultado obtido para '{ $query }'
|
||||||
*[other] { $count } resultados obtidos para '{ $query }'
|
*[other] { $count } resultados obtidos para '{ $query }'
|
||||||
}
|
}
|
||||||
|
# amount of reactions a note you were tagged in received
|
||||||
|
name__and__count__others_reacted_to_a_note_you_were_tagged_in_181a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } e { $count } outro reagiram a uma nota em que te marcaram
|
||||||
|
*[other] { $name } e { $count } outros reagiram a uma nota que te marcaram
|
||||||
|
}
|
||||||
|
# describing the amount of reactions your note received
|
||||||
|
name__and__count__others_reacted_to_your_note_0f6a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } e { $count } outro reagiram à tua nota
|
||||||
|
*[other] { $name } e { $count } outros reagiram à tua nota
|
||||||
|
}
|
||||||
|
# describing the amount of reposts a note you were tagged in received
|
||||||
|
name__and__count__others_reposted_a_note_you_were_tagged_in_08e1 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } e { $count } outro republicaram uma nota em que te marcaram
|
||||||
|
*[other] { $name } e { $count } outros republicaram uma nota que te marcaram
|
||||||
|
}
|
||||||
|
# describing the amount of reposts your note received
|
||||||
|
name__and__count__others_reposted_your_note_70a0 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } e { $count } outro republicaram a tua nota
|
||||||
|
*[other] { $name } e { $count } outros republicaram a tua nota
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ Add_External_Notifications_Column_41ae = เพิ่มคอลัมน์ก
|
|||||||
Add_Hashtag_Column_ebf4 = เพิ่มคอลัมน์แฮชแท็ก
|
Add_Hashtag_Column_ebf4 = เพิ่มคอลัมน์แฮชแท็ก
|
||||||
# Column title for adding last notes column
|
# Column title for adding last notes column
|
||||||
Add_Last_Notes_Column_bbad = เพิ่มคอลัมน์โน้ตล่าสุด
|
Add_Last_Notes_Column_bbad = เพิ่มคอลัมน์โน้ตล่าสุด
|
||||||
|
# Tooltip text for adding a new deck button
|
||||||
|
Add_new_deck_f2fc = เพิ่ม deck ใหม่
|
||||||
# Column title for adding notifications column
|
# Column title for adding notifications column
|
||||||
Add_Notifications_Column_79f8 = เพิ่มคอลัมน์การแจ้งเตือน
|
Add_Notifications_Column_79f8 = เพิ่มคอลัมน์การแจ้งเตือน
|
||||||
# Button label to add a relay
|
# Button label to add a relay
|
||||||
@@ -93,6 +95,8 @@ Copy_Link_dc7c = คัดลอกลิงก์
|
|||||||
Copy_Note_ID_6b45 = คัดลอก โน้ต ID
|
Copy_Note_ID_6b45 = คัดลอก โน้ต ID
|
||||||
# Copy the raw note data in JSON format to clipboard
|
# Copy the raw note data in JSON format to clipboard
|
||||||
Copy_Note_JSON_9e4e = คัดลอก JSON ของโน้ต
|
Copy_Note_JSON_9e4e = คัดลอก JSON ของโน้ต
|
||||||
|
# Tooltip text for copying npub to clipboard
|
||||||
|
Copy_npub_to_clipboard_c105 = คัดลอก npub ไปยังคลิปบอร์ด
|
||||||
# Copy the author's public key to clipboard
|
# Copy the author's public key to clipboard
|
||||||
Copy_Pubkey_9cc4 = คัดลอก npub
|
Copy_Pubkey_9cc4 = คัดลอก npub
|
||||||
# Copy the text content of the note to clipboard
|
# Copy the text content of the note to clipboard
|
||||||
@@ -141,6 +145,8 @@ Delete_Wallet_d1d4 = ลบวอลเล็ต
|
|||||||
Display_name_f9d9 = ชื่อที่แสดง
|
Display_name_f9d9 = ชื่อที่แสดง
|
||||||
# Domain identification message
|
# Domain identification message
|
||||||
domain___will_be_used_for_identification_b67e = { $domain } จะใช้สำหรับการระบุตัวตน
|
domain___will_be_used_for_identification_b67e = { $domain } จะใช้สำหรับการระบุตัวตน
|
||||||
|
# Button to indicate that the user is done going through the onboarding process.
|
||||||
|
Done_50dd = เสร็จ
|
||||||
# Column title for editing deck
|
# Column title for editing deck
|
||||||
Edit_Deck_4018 = แก้ไข Deck
|
Edit_Deck_4018 = แก้ไข Deck
|
||||||
# Button label to edit a deck
|
# Button label to edit a deck
|
||||||
@@ -193,6 +199,8 @@ k_50K_c2dc = 50K
|
|||||||
k_5K_f7e6 = 5K
|
k_5K_f7e6 = 5K
|
||||||
# Description for your notes column
|
# Description for your notes column
|
||||||
Keep_track_of_your_notes___replies_a334 = ติดตามโน้ตและการตอบกลับของคุณ
|
Keep_track_of_your_notes___replies_a334 = ติดตามโน้ตและการตอบกลับของคุณ
|
||||||
|
# label for keys setting section
|
||||||
|
Keys_435f = คีย์
|
||||||
# Label for language, Appearance settings section
|
# Label for language, Appearance settings section
|
||||||
Language_e264 = ภาษา:
|
Language_e264 = ภาษา:
|
||||||
# Title for last note per user column
|
# Title for last note per user column
|
||||||
@@ -211,6 +219,14 @@ Media_from_someone_you_don_t_follow_5611 = สื่อจากคนที่
|
|||||||
Moves_this_column_to_another_position_0d4b = ย้ายคอลัมน์นี้ไปยังตำแหน่งอื่น
|
Moves_this_column_to_another_position_0d4b = ย้ายคอลัมน์นี้ไปยังตำแหน่งอื่น
|
||||||
# Title for the user's deck
|
# Title for the user's deck
|
||||||
My_Deck_4ac5 = Deck ของฉัน
|
My_Deck_4ac5 = Deck ของฉัน
|
||||||
|
# reaction from user to a note you were tagged in
|
||||||
|
name__reacted_to_a_note_you_were_tagged_in_4b62 = { $name } react ต่อโน้ตที่คุณถูกแท็ก
|
||||||
|
# reaction from user to your note
|
||||||
|
name__reacted_to_your_note_ead9 = { $name } react ต่อโน้ตของคุณ
|
||||||
|
# repost from user
|
||||||
|
name__reposted_a_note_you_were_tagged_in_1379 = { $name } รีโพสต์โน้ตที่คุณถูกแท็ก
|
||||||
|
# repost from user
|
||||||
|
name__reposted_your_note_1379 = { $name } รีโพสต์โน้ตของคุณ
|
||||||
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
# Label asking if the user is new to Nostr. Underneath this label is a button to create an account.
|
||||||
New_to_Nostr_a2fd = มือใหม่สำหรับ Nostr?
|
New_to_Nostr_a2fd = มือใหม่สำหรับ Nostr?
|
||||||
# NIP-05 identity field label
|
# NIP-05 identity field label
|
||||||
@@ -261,6 +277,8 @@ Post_now_8a49 = โพสต์
|
|||||||
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = กดปุ่มด้านล่างเพื่อคัดลอกข้อมูลบันทึกล่าสุด ไปยังคลิปบอร์ด จากนั้นนำไปวางในอีเมลของคุณ
|
Press_the_button_below_to_copy_your_most_recent_logs_to_your_system_s_clipboard__Then_paste_it_into_your_email_322e = กดปุ่มด้านล่างเพื่อคัดลอกข้อมูลบันทึกล่าสุด ไปยังคลิปบอร์ด จากนั้นนำไปวางในอีเมลของคุณ
|
||||||
# Profile picture URL field label
|
# Profile picture URL field label
|
||||||
Profile_picture_81ff = รูปโปรไฟล์
|
Profile_picture_81ff = รูปโปรไฟล์
|
||||||
|
# label describing public key
|
||||||
|
PUBLIC_ACCOUNT_ID_4394 = ไอดีบัญชีสาธารณะ
|
||||||
# Column title for quote composition
|
# Column title for quote composition
|
||||||
Quote_475c = อ้างอิง
|
Quote_475c = อ้างอิง
|
||||||
# Error message when quote note cannot be found
|
# Error message when quote note cannot be found
|
||||||
@@ -313,6 +331,8 @@ Search_c573 = ค้นหา
|
|||||||
Search_notes_42a6 = ค้นหาโน้ต...
|
Search_notes_42a6 = ค้นหาโน้ต...
|
||||||
# Search in progress message
|
# Search in progress message
|
||||||
Searching_for___query_5d18 = กำลังค้นหา '{ $query }'
|
Searching_for___query_5d18 = กำลังค้นหา '{ $query }'
|
||||||
|
# label describing secret key
|
||||||
|
SECRET_ACCOUNT_LOGIN_KEY_8440 = คีย์ลับสำหรับล็อกอินบัญชี
|
||||||
# Description for Home column
|
# Description for Home column
|
||||||
See_notes_from_your_contacts_ac16 = ดูโน้ตจากผู้ติดต่อของคุณ
|
See_notes_from_your_contacts_ac16 = ดูโน้ตจากผู้ติดต่อของคุณ
|
||||||
# Description for universe column
|
# Description for universe column
|
||||||
@@ -414,3 +434,27 @@ Got__count__results_for___query_85fb =
|
|||||||
[one] ผลการค้นหา '{ $query }': พบ { $count } รายการ
|
[one] ผลการค้นหา '{ $query }': พบ { $count } รายการ
|
||||||
*[other] ผลการค้นหา '{ $query }': พบ { $count } รายการ
|
*[other] ผลการค้นหา '{ $query }': พบ { $count } รายการ
|
||||||
}
|
}
|
||||||
|
# amount of reactions a note you were tagged in received
|
||||||
|
name__and__count__others_reacted_to_a_note_you_were_tagged_in_181a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } และอีก { $count } คน reacted ต่อโน้ตที่คุณถูกแท็ก
|
||||||
|
*[other] { $name } และอีก { $count } คน reacted ต่อโน้ตที่คุณถูกแท็ก
|
||||||
|
}
|
||||||
|
# describing the amount of reactions your note received
|
||||||
|
name__and__count__others_reacted_to_your_note_0f6a =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } และอีก { $count } คน reacted ต่อโน้ตที่ของคุณ
|
||||||
|
*[other] { $name } และอีก { $count } คน reacted ต่อโน้ตของคุณ
|
||||||
|
}
|
||||||
|
# describing the amount of reposts a note you were tagged in received
|
||||||
|
name__and__count__others_reposted_a_note_you_were_tagged_in_08e1 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } และอีก { $count } คน รีโพสต์โน้ตที่คุณถูกแท็ก
|
||||||
|
*[other] { $name } และอีก { $count } คน รีโพสต์โน้ตที่คุณถูกแท็ก
|
||||||
|
}
|
||||||
|
# describing the amount of reposts your note received
|
||||||
|
name__and__count__others_reposted_your_note_70a0 =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $name } และอีก { $count } คน รีโพสต์โน้ตของคุณ
|
||||||
|
*[other] { $name } และอีก { $count } คน รีโพสต์โน้ตของคุณ
|
||||||
|
}
|
||||||
|
|||||||
@@ -152,7 +152,9 @@ impl<'a> RelayMessage<'a> {
|
|||||||
return Ok(Self::ok(event_id, status, message));
|
return Ok(Self::ok(event_id, status, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(Error::DecodeFailed("unrecognized message type".into()))
|
Err(Error::DecodeFailed(format!(
|
||||||
|
"unrecognized message type: '{msg}'"
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,15 +222,15 @@ mod tests {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
r#"["NOTICE": 404]"#,
|
r#"["NOTICE": 404]"#,
|
||||||
Err(Error::DecodeFailed("unrecognized message type".into())),
|
Err(Error::DecodeFailed("unrecognized message type: '[\"NOTICE\": 404]'".into())),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r#"["OK","event_id"]"#,
|
r#"["OK","event_id"]"#,
|
||||||
Err(Error::DecodeFailed("unrecognized message type".into())),
|
Err(Error::DecodeFailed("unrecognized message type: '[\"OK\",\"event_id\"]'".into())),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"#,
|
r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30"]"#,
|
||||||
Err(Error::DecodeFailed("unrecognized message type".into())),
|
Err(Error::DecodeFailed("unrecognized message type: '[\"OK\",\"b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30\"]'".into())),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,""]"#,
|
r#"["OK","b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30",hello,""]"#,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ bitflags = { workspace = true }
|
|||||||
regex = "1"
|
regex = "1"
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
indexmap = {workspace = true}
|
indexmap = {workspace = true}
|
||||||
|
rand = {workspace = true}
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
filter::{self, HybridFilter},
|
filter::{self, HybridFilter, ValidKind},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use nostrdb::{Filter, Note};
|
use nostrdb::{Filter, Note};
|
||||||
@@ -15,10 +15,16 @@ pub fn hybrid_contacts_filter(
|
|||||||
add_pk: Option<&[u8; 32]>,
|
add_pk: Option<&[u8; 32]>,
|
||||||
with_hashtags: bool,
|
with_hashtags: bool,
|
||||||
) -> Result<HybridFilter, Error> {
|
) -> Result<HybridFilter, Error> {
|
||||||
let local = filter::filter_from_tags(note, add_pk, with_hashtags)?
|
let local = vec![
|
||||||
.into_filter([1], filter::default_limit());
|
filter::filter_from_tags(note, add_pk, with_hashtags)?
|
||||||
|
.into_query_package(ValidKind::One, filter::default_limit()),
|
||||||
|
filter::filter_from_tags(note, add_pk, with_hashtags)?
|
||||||
|
.into_query_package(ValidKind::Six, filter::default_limit()),
|
||||||
|
filter::filter_from_tags(note, add_pk, with_hashtags)?
|
||||||
|
.into_query_package(ValidKind::Zero, filter::default_limit()),
|
||||||
|
];
|
||||||
let remote = filter::filter_from_tags(note, add_pk, with_hashtags)?
|
let remote = filter::filter_from_tags(note, add_pk, with_hashtags)?
|
||||||
.into_filter([1, 0], filter::default_remote_limit());
|
.into_filter(vec![1, 0], filter::default_remote_limit());
|
||||||
|
|
||||||
Ok(HybridFilter::split(local, remote))
|
Ok(HybridFilter::split(local, remote))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,12 +142,6 @@ impl FilterState {
|
|||||||
Self::Ready(HybridFilter::unsplit(filter))
|
Self::Ready(HybridFilter::unsplit(filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The filter is ready, but we have a different local filter from
|
|
||||||
/// our remote one
|
|
||||||
pub fn ready_split(local: Vec<Filter>, remote: Vec<Filter>) -> Self {
|
|
||||||
Self::Ready(HybridFilter::split(local, remote))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Our hybrid filter is ready (either split or unsplit)
|
/// Our hybrid filter is ready (either split or unsplit)
|
||||||
pub fn ready_hybrid(filter: HybridFilter) -> Self {
|
pub fn ready_hybrid(filter: HybridFilter) -> Self {
|
||||||
Self::Ready(filter)
|
Self::Ready(filter)
|
||||||
@@ -219,7 +213,7 @@ pub struct FilteredTags {
|
|||||||
/// The local and remote filter are related but slightly different
|
/// The local and remote filter are related but slightly different
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SplitFilter {
|
pub struct SplitFilter {
|
||||||
pub local: Vec<Filter>,
|
pub local: Vec<NdbQueryPackage>,
|
||||||
pub remote: Vec<Filter>,
|
pub remote: Vec<Filter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,16 +230,23 @@ impl HybridFilter {
|
|||||||
HybridFilter::Unsplit(filter)
|
HybridFilter::Unsplit(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn split(local: Vec<Filter>, remote: Vec<Filter>) -> Self {
|
pub fn split(local: Vec<NdbQueryPackage>, remote: Vec<Filter>) -> Self {
|
||||||
HybridFilter::Split(SplitFilter { local, remote })
|
HybridFilter::Split(SplitFilter { local, remote })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(&self) -> &[Filter] {
|
pub fn local(&self) -> NdbQueryPackages<'_> {
|
||||||
match self {
|
match self {
|
||||||
Self::Split(split) => &split.local,
|
Self::Split(split) => NdbQueryPackages {
|
||||||
|
packages: split.local.iter().map(NdbQueryPackage::borrow).collect(),
|
||||||
|
},
|
||||||
|
|
||||||
// local as the same as remote in unsplit
|
// local as the same as remote in unsplit
|
||||||
Self::Unsplit(local) => local,
|
Self::Unsplit(local) => NdbQueryPackages {
|
||||||
|
packages: vec![NdbQueryPackageUnowned {
|
||||||
|
filters: local,
|
||||||
|
kind: None,
|
||||||
|
}],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,77 +261,139 @@ impl HybridFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FilteredTags {
|
impl FilteredTags {
|
||||||
pub fn into_follow_filter(self) -> Vec<Filter> {
|
pub fn into_query_package(self, kind: ValidKind, limit: u64) -> NdbQueryPackage {
|
||||||
self.into_filter([1], default_limit())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make this more general
|
|
||||||
pub fn into_filter<I>(self, kinds: I, limit: u64) -> Vec<Filter>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = u64> + Copy,
|
|
||||||
{
|
|
||||||
let mut filters: Vec<Filter> = Vec::with_capacity(2);
|
let mut filters: Vec<Filter> = Vec::with_capacity(2);
|
||||||
|
|
||||||
if let Some(authors) = self.authors {
|
if let Some(authors) = self.authors {
|
||||||
filters.push(authors.kinds(kinds).limit(limit).build())
|
filters.push(authors.kinds(vec![kind.kind()]).limit(limit).build())
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hashtags) = self.hashtags {
|
if let Some(hashtags) = self.hashtags {
|
||||||
filters.push(hashtags.kinds(kinds).limit(limit).build())
|
if matches!(&kind, ValidKind::One | ValidKind::Zero) {
|
||||||
|
filters.push(hashtags.kinds(vec![kind.kind()]).limit(limit).build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NdbQueryPackage { filters, kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make this more general
|
||||||
|
pub fn into_filter(self, shared_kinds: Vec<u64>, limit: u64) -> Vec<Filter> {
|
||||||
|
let mut filters: Vec<Filter> = Vec::with_capacity(2);
|
||||||
|
|
||||||
|
if let Some(authors) = self.authors {
|
||||||
|
let mut author_kinds = shared_kinds.clone();
|
||||||
|
author_kinds.insert(0, 6);
|
||||||
|
|
||||||
|
filters.push(authors.kinds(author_kinds).limit(limit).build())
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(hashtags) = self.hashtags {
|
||||||
|
filters.push(hashtags.kinds(shared_kinds).limit(limit).build())
|
||||||
}
|
}
|
||||||
|
|
||||||
filters
|
filters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Ndb::query` retrieves the most recent notes of one kind until it can't find anymore THEN proceeds to the next kind.
|
||||||
|
/// This is not optimal for many scenarios, so this data structure represents data that is packaged optimally for one `Ndb::query`,
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct NdbQueryPackage {
|
||||||
|
pub kind: ValidKind,
|
||||||
|
pub filters: Vec<Filter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NdbQueryPackage {
|
||||||
|
pub fn borrow(&self) -> NdbQueryPackageUnowned<'_> {
|
||||||
|
NdbQueryPackageUnowned {
|
||||||
|
filters: &self.filters,
|
||||||
|
kind: Some(self.kind.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct NdbQueryPackageUnowned<'a> {
|
||||||
|
pub kind: Option<ValidKind>,
|
||||||
|
pub filters: &'a Vec<Filter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NdbQueryPackages<'a> {
|
||||||
|
pub packages: Vec<NdbQueryPackageUnowned<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NdbQueryPackages<'a> {
|
||||||
|
pub fn combined(&self) -> Vec<Filter> {
|
||||||
|
let mut combined = Vec::new();
|
||||||
|
for package in &self.packages {
|
||||||
|
combined.extend_from_slice(package.filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
combined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ValidKind {
|
||||||
|
Zero,
|
||||||
|
One,
|
||||||
|
Six,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValidKind {
|
||||||
|
fn kind(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
ValidKind::Zero => 0,
|
||||||
|
ValidKind::One => 1,
|
||||||
|
ValidKind::Six => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a "last N notes per pubkey" query.
|
/// Create a "last N notes per pubkey" query.
|
||||||
pub fn last_n_per_pubkey_from_tags(
|
pub fn last_n_per_pubkey_from_tags(
|
||||||
note: &Note,
|
note: &Note,
|
||||||
kind: u64,
|
kind: u64,
|
||||||
notes_per_pubkey: u64,
|
notes_per_pubkey: u64,
|
||||||
) -> Result<Vec<Filter>, Error> {
|
) -> Result<Vec<Filter>, Error> {
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
let mut filters: Vec<Filter> = vec![];
|
let mut filters: Vec<Filter> = vec![];
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
|
||||||
for tag in note.tags() {
|
// TODO: fix arbitrary MAX_FILTER limit in nostrdb
|
||||||
// TODO: fix arbitrary MAX_FILTER limit in nostrdb
|
const LIMIT: usize = 15;
|
||||||
if filters.len() == 15 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (i, tag) in note.tags().iter().enumerate() {
|
||||||
if tag.count() < 2 {
|
if tag.count() < 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let t = if let Some(t) = tag.get_unchecked(0).variant().str() {
|
let Some("p") = tag.get_str(0) else {
|
||||||
t
|
|
||||||
} else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if t == "p" {
|
let Some(author) = tag.get_id(1) else {
|
||||||
let author = if let Some(author) = tag.get_unchecked(1).variant().id() {
|
continue;
|
||||||
author
|
};
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let mk_filter = || {
|
||||||
let mut filter = Filter::new();
|
let mut filter = Filter::new();
|
||||||
filter.start_authors_field()?;
|
let _ = filter.start_authors_field();
|
||||||
filter.add_id_element(author)?;
|
let _ = filter.add_id_element(author);
|
||||||
filter.end_field();
|
filter.end_field();
|
||||||
filters.push(filter.kinds([kind]).limit(notes_per_pubkey).build());
|
filter.kinds([kind]).limit(notes_per_pubkey).build()
|
||||||
} else if t == "t" {
|
};
|
||||||
let hashtag = if let Some(hashtag) = tag.get_unchecked(1).variant().str() {
|
|
||||||
hashtag
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut filter = Filter::new();
|
// since we're limited due to a nostrdb bug, we reservoir sample to keep things interesting
|
||||||
filter.start_tags_field('t')?;
|
if filters.len() < LIMIT {
|
||||||
filter.add_str_element(hashtag)?;
|
filters.push(mk_filter());
|
||||||
filter.end_field();
|
} else {
|
||||||
filters.push(filter.kinds([kind]).limit(notes_per_pubkey).build());
|
let j = rng.random_range(0..=i);
|
||||||
|
if j < LIMIT {
|
||||||
|
filters[j] = mk_filter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ impl TexturesCache {
|
|||||||
|
|
||||||
entry.replace_entry_with(|_, v| {
|
entry.replace_entry_with(|_, v| {
|
||||||
let TextureStateInternal::Loading(textured) = v else {
|
let TextureStateInternal::Loading(textured) = v else {
|
||||||
return None;
|
return Some(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(TextureStateInternal::Loaded(textured))
|
Some(TextureStateInternal::Loaded(textured))
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ fn generate_gif(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if tex_input.send(texture_frame).is_err() {
|
if tex_input.send(texture_frame).is_err() {
|
||||||
tracing::debug!("AnimationTextureFrame mpsc stopped abruptly");
|
//tracing::debug!("AnimationTextureFrame mpsc stopped abruptly");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,73 +10,75 @@ const ONE_WEEK_IN_SECONDS: u64 = 604_800;
|
|||||||
const ONE_MONTH_IN_SECONDS: u64 = 2_592_000; // 30 days
|
const ONE_MONTH_IN_SECONDS: u64 = 2_592_000; // 30 days
|
||||||
const ONE_YEAR_IN_SECONDS: u64 = 31_536_000; // 365 days
|
const ONE_YEAR_IN_SECONDS: u64 = 31_536_000; // 365 days
|
||||||
|
|
||||||
// Range boundary constants for match patterns
|
/// Calculate relative time between two timestamps, with two units only
|
||||||
const MAX_SECONDS: u64 = ONE_MINUTE_IN_SECONDS - 1;
|
/// when the scale is large enough (e.g., "1y 6m", "5d 4h"),
|
||||||
const MAX_SECONDS_FOR_MINUTES: u64 = ONE_HOUR_IN_SECONDS - 1;
|
/// but not for hours/minutes/seconds.
|
||||||
const MAX_SECONDS_FOR_HOURS: u64 = ONE_DAY_IN_SECONDS - 1;
|
|
||||||
const MAX_SECONDS_FOR_DAYS: u64 = ONE_WEEK_IN_SECONDS - 1;
|
|
||||||
const MAX_SECONDS_FOR_WEEKS: u64 = ONE_MONTH_IN_SECONDS - 1;
|
|
||||||
const MAX_SECONDS_FOR_MONTHS: u64 = ONE_YEAR_IN_SECONDS - 1;
|
|
||||||
|
|
||||||
/// Calculate relative time between two timestamps
|
|
||||||
fn time_ago_between(i18n: &mut Localization, timestamp: u64, now: u64) -> String {
|
fn time_ago_between(i18n: &mut Localization, timestamp: u64, now: u64) -> String {
|
||||||
// Determine if the timestamp is in the future or the past
|
|
||||||
let duration = if now >= timestamp {
|
let duration = if now >= timestamp {
|
||||||
now.saturating_sub(timestamp)
|
now.saturating_sub(timestamp)
|
||||||
} else {
|
} else {
|
||||||
timestamp.saturating_sub(now)
|
timestamp.saturating_sub(now)
|
||||||
};
|
};
|
||||||
|
|
||||||
let time_str = match duration {
|
// Special-case: "now" for < 3 seconds
|
||||||
0..=2 => tr!(
|
if duration <= 2 {
|
||||||
|
let s = tr!(
|
||||||
i18n,
|
i18n,
|
||||||
"now",
|
"now",
|
||||||
"Relative time for very recent events (less than 3 seconds)"
|
"Relative time for very recent events (less than 3 seconds)"
|
||||||
),
|
);
|
||||||
3..=MAX_SECONDS => tr!(
|
return if timestamp > now { format!("+{s}") } else { s };
|
||||||
i18n,
|
}
|
||||||
"{count}s",
|
|
||||||
"Relative time in seconds",
|
// Break into buckets
|
||||||
count = duration
|
let years = duration / ONE_YEAR_IN_SECONDS;
|
||||||
),
|
let rem_y = duration % ONE_YEAR_IN_SECONDS;
|
||||||
ONE_MINUTE_IN_SECONDS..=MAX_SECONDS_FOR_MINUTES => tr!(
|
|
||||||
i18n,
|
let months = rem_y / ONE_MONTH_IN_SECONDS;
|
||||||
"{count}m",
|
let rem_m = rem_y % ONE_MONTH_IN_SECONDS;
|
||||||
"Relative time in minutes",
|
|
||||||
count = duration / ONE_MINUTE_IN_SECONDS
|
let weeks = rem_m / ONE_WEEK_IN_SECONDS;
|
||||||
),
|
let rem_w = rem_m % ONE_WEEK_IN_SECONDS;
|
||||||
ONE_HOUR_IN_SECONDS..=MAX_SECONDS_FOR_HOURS => tr!(
|
|
||||||
i18n,
|
let days = rem_w / ONE_DAY_IN_SECONDS;
|
||||||
"{count}h",
|
let rem_d = rem_w % ONE_DAY_IN_SECONDS;
|
||||||
"Relative time in hours",
|
|
||||||
count = duration / ONE_HOUR_IN_SECONDS
|
let hours = rem_d / ONE_HOUR_IN_SECONDS;
|
||||||
),
|
let rem_h = rem_d % ONE_HOUR_IN_SECONDS;
|
||||||
ONE_DAY_IN_SECONDS..=MAX_SECONDS_FOR_DAYS => tr!(
|
|
||||||
i18n,
|
let mins = rem_h / ONE_MINUTE_IN_SECONDS;
|
||||||
"{count}d",
|
let secs = rem_h % ONE_MINUTE_IN_SECONDS;
|
||||||
"Relative time in days",
|
|
||||||
count = duration / ONE_DAY_IN_SECONDS
|
let mut parts: Vec<String> = Vec::with_capacity(2);
|
||||||
),
|
|
||||||
ONE_WEEK_IN_SECONDS..=MAX_SECONDS_FOR_WEEKS => tr!(
|
let mut push_part = |count: u64, key: &str, desc: &str| {
|
||||||
i18n,
|
if count > 0 && parts.len() < 2 {
|
||||||
"{count}w",
|
parts.push(tr!(i18n, key, desc, count = count));
|
||||||
"Relative time in weeks",
|
}
|
||||||
count = duration / ONE_WEEK_IN_SECONDS
|
|
||||||
),
|
|
||||||
ONE_MONTH_IN_SECONDS..=MAX_SECONDS_FOR_MONTHS => tr!(
|
|
||||||
i18n,
|
|
||||||
"{count}mo",
|
|
||||||
"Relative time in months",
|
|
||||||
count = duration / ONE_MONTH_IN_SECONDS
|
|
||||||
),
|
|
||||||
_ => tr!(
|
|
||||||
i18n,
|
|
||||||
"{count}y",
|
|
||||||
"Relative time in years",
|
|
||||||
count = duration / ONE_YEAR_IN_SECONDS
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if years > 0 {
|
||||||
|
push_part(years, "{count}y", "Relative time in years");
|
||||||
|
push_part(months, "{count}mo", "Relative time in months");
|
||||||
|
} else if months > 0 {
|
||||||
|
push_part(months, "{count}mo", "Relative time in months");
|
||||||
|
push_part(weeks, "{count}w", "Relative time in weeks");
|
||||||
|
} else if weeks > 0 {
|
||||||
|
push_part(weeks, "{count}w", "Relative time in weeks");
|
||||||
|
push_part(days, "{count}d", "Relative time in days");
|
||||||
|
} else if days > 0 {
|
||||||
|
push_part(days, "{count}d", "Relative time in days");
|
||||||
|
push_part(hours, "{count}h", "Relative time in hours");
|
||||||
|
} else if hours > 0 {
|
||||||
|
push_part(hours, "{count}h", "Relative time in hours");
|
||||||
|
} else if mins > 0 {
|
||||||
|
push_part(mins, "{count}m", "Relative time in minutes");
|
||||||
|
} else {
|
||||||
|
push_part(secs.max(1), "{count}s", "Relative time in seconds");
|
||||||
|
}
|
||||||
|
|
||||||
|
let time_str = parts.join(" ");
|
||||||
|
|
||||||
if timestamp > now {
|
if timestamp > now {
|
||||||
format!("+{time_str}")
|
format!("+{time_str}")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ impl TimelineSub {
|
|||||||
let before = self.state.clone();
|
let before = self.state.clone();
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
SubState::NoSub { dependers } => {
|
SubState::NoSub { dependers } => {
|
||||||
let Some(sub) = ndb_sub(ndb, filter.local(), "") else {
|
let Some(sub) = ndb_sub(ndb, &filter.local().combined(), "") else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ impl TimelineSub {
|
|||||||
dependers: _,
|
dependers: _,
|
||||||
} => {}
|
} => {}
|
||||||
SubState::RemoteOnly { remote, dependers } => {
|
SubState::RemoteOnly { remote, dependers } => {
|
||||||
let Some(local) = ndb_sub(ndb, filter.local(), "") else {
|
let Some(local) = ndb_sub(ndb, &filter.local().combined(), "") else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.state = SubState::Unified {
|
self.state = SubState::Unified {
|
||||||
|
|||||||
@@ -56,11 +56,12 @@ impl NewPost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_note(&self, seckey: &[u8; 32]) -> Note<'_> {
|
/// creates a NoteBuilder with all the shared data between note, reply & quote reply
|
||||||
let mut content = self.content.clone();
|
fn builder_with_shared_tags<'a>(&self, mut content: String) -> NoteBuilder<'a> {
|
||||||
append_urls(&mut content, &self.media);
|
append_urls(&mut content, &self.media);
|
||||||
|
|
||||||
let mut builder = add_client_tag(NoteBuilder::new()).kind(1).content(&content);
|
let mut builder = NoteBuilder::new().kind(1).content(&content);
|
||||||
|
builder = add_client_tag(builder);
|
||||||
|
|
||||||
for hashtag in Self::extract_hashtags(&self.content) {
|
for hashtag in Self::extract_hashtags(&self.content) {
|
||||||
builder = builder.start_tag().tag_str("t").tag_str(&hashtag);
|
builder = builder.start_tag().tag_str("t").tag_str(&hashtag);
|
||||||
@@ -74,18 +75,21 @@ impl NewPost {
|
|||||||
builder = add_mention_tags(builder, &self.mentions);
|
builder = add_mention_tags(builder, &self.mentions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_note(&self, seckey: &[u8; 32]) -> Note<'_> {
|
||||||
|
let builder = self.builder_with_shared_tags(self.content.clone());
|
||||||
|
|
||||||
builder.sign(seckey).build().expect("note should be ok")
|
builder.sign(seckey).build().expect("note should be ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_reply(&self, seckey: &[u8; 32], replying_to: &Note) -> Note<'_> {
|
pub fn to_reply(&self, seckey: &[u8; 32], replying_to: &Note) -> Note<'_> {
|
||||||
let mut content = self.content.clone();
|
let mut builder = self.builder_with_shared_tags(self.content.clone());
|
||||||
append_urls(&mut content, &self.media);
|
|
||||||
|
|
||||||
let builder = add_client_tag(NoteBuilder::new()).kind(1).content(&content);
|
|
||||||
|
|
||||||
let nip10 = NoteReply::new(replying_to.tags());
|
let nip10 = NoteReply::new(replying_to.tags());
|
||||||
|
|
||||||
let mut builder = if let Some(root) = nip10.root() {
|
builder = if let Some(root) = nip10.root() {
|
||||||
builder
|
builder
|
||||||
.start_tag()
|
.start_tag()
|
||||||
.tag_str("e")
|
.tag_str("e")
|
||||||
@@ -143,14 +147,6 @@ impl NewPost {
|
|||||||
builder = builder.start_tag().tag_str("p").tag_str(&hex::encode(id));
|
builder = builder.start_tag().tag_str("p").tag_str(&hex::encode(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.media.is_empty() {
|
|
||||||
builder = add_imeta_tags(builder, &self.media);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.mentions.is_empty() {
|
|
||||||
builder = add_mention_tags(builder, &self.mentions);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.sign(seckey)
|
.sign(seckey)
|
||||||
.build()
|
.build()
|
||||||
@@ -158,27 +154,13 @@ impl NewPost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_quote(&self, seckey: &[u8; 32], quoting: &Note) -> Note<'_> {
|
pub fn to_quote(&self, seckey: &[u8; 32], quoting: &Note) -> Note<'_> {
|
||||||
let mut new_content = format!(
|
let new_content = format!(
|
||||||
"{}\nnostr:{}",
|
"{}\nnostr:{}",
|
||||||
self.content,
|
self.content,
|
||||||
enostr::NoteId::new(*quoting.id()).to_bech().unwrap()
|
enostr::NoteId::new(*quoting.id()).to_bech().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
append_urls(&mut new_content, &self.media);
|
let builder = self.builder_with_shared_tags(new_content);
|
||||||
|
|
||||||
let mut builder = NoteBuilder::new().kind(1).content(&new_content);
|
|
||||||
|
|
||||||
for hashtag in Self::extract_hashtags(&self.content) {
|
|
||||||
builder = builder.start_tag().tag_str("t").tag_str(&hashtag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.media.is_empty() {
|
|
||||||
builder = add_imeta_tags(builder, &self.media);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.mentions.is_empty() {
|
|
||||||
builder = add_mention_tags(builder, &self.mentions);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.start_tag()
|
.start_tag()
|
||||||
|
|||||||
@@ -134,15 +134,22 @@ impl TimelineCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let notes = if let FilterState::Ready(filters) = id.filters(txn, ndb) {
|
let notes = if let FilterState::Ready(filters) = id.filters(txn, ndb) {
|
||||||
if let Ok(results) = ndb.query(txn, filters.local(), 1000) {
|
let mut notes = Vec::new();
|
||||||
results
|
|
||||||
.into_iter()
|
for package in filters.local().packages {
|
||||||
.map(NoteRef::from_query_result)
|
if let Ok(results) = ndb.query(txn, package.filters, 1000) {
|
||||||
.collect()
|
let cur_notes: Vec<NoteRef> = results
|
||||||
} else {
|
.into_iter()
|
||||||
debug!("got no results from TimelineCache lookup for {:?}", id);
|
.map(NoteRef::from_query_result)
|
||||||
vec![]
|
.collect();
|
||||||
|
|
||||||
|
notes.extend(cur_notes);
|
||||||
|
} else {
|
||||||
|
debug!("got no results from TimelineCache lookup for {:?}", id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notes
|
||||||
} else {
|
} else {
|
||||||
// filter is not ready yet
|
// filter is not ready yet
|
||||||
vec![]
|
vec![]
|
||||||
@@ -178,12 +185,20 @@ impl TimelineCache {
|
|||||||
let (mut open_result, timeline) = match notes_resp.vitality {
|
let (mut open_result, timeline) = match notes_resp.vitality {
|
||||||
Vitality::Stale(timeline) => {
|
Vitality::Stale(timeline) => {
|
||||||
// The timeline cache is stale, let's update it
|
// The timeline cache is stale, let's update it
|
||||||
let notes = find_new_notes(
|
let notes = {
|
||||||
timeline.all_or_any_entries().latest(),
|
let mut notes = Vec::new();
|
||||||
timeline.subscription.get_filter()?.local(),
|
for package in timeline.subscription.get_filter()?.local().packages {
|
||||||
txn,
|
let cur_notes = find_new_notes(
|
||||||
ndb,
|
timeline.all_or_any_entries().latest(),
|
||||||
);
|
package.filters,
|
||||||
|
txn,
|
||||||
|
ndb,
|
||||||
|
);
|
||||||
|
notes.extend(cur_notes);
|
||||||
|
}
|
||||||
|
notes
|
||||||
|
};
|
||||||
|
|
||||||
let open_result = if notes.is_empty() {
|
let open_result = if notes.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::search::SearchQuery;
|
|||||||
use crate::timeline::{Timeline, TimelineTab};
|
use crate::timeline::{Timeline, TimelineTab};
|
||||||
use enostr::{Filter, NoteId, Pubkey};
|
use enostr::{Filter, NoteId, Pubkey};
|
||||||
use nostrdb::{Ndb, Transaction};
|
use nostrdb::{Ndb, Transaction};
|
||||||
|
use notedeck::filter::{NdbQueryPackage, ValidKind};
|
||||||
use notedeck::{
|
use notedeck::{
|
||||||
contacts::{contacts_filter, hybrid_contacts_filter},
|
contacts::{contacts_filter, hybrid_contacts_filter},
|
||||||
filter::{self, default_limit, default_remote_limit, HybridFilter},
|
filter::{self, default_limit, default_remote_limit, HybridFilter},
|
||||||
@@ -728,15 +729,29 @@ fn last_per_pubkey_filter_state(ndb: &Ndb, pk: &Pubkey) -> FilterState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn profile_filter(pk: &[u8; 32]) -> HybridFilter {
|
fn profile_filter(pk: &[u8; 32]) -> HybridFilter {
|
||||||
|
let local = vec![
|
||||||
|
NdbQueryPackage {
|
||||||
|
filters: vec![Filter::new()
|
||||||
|
.authors([pk])
|
||||||
|
.kinds([1])
|
||||||
|
.limit(default_limit())
|
||||||
|
.build()],
|
||||||
|
kind: ValidKind::One,
|
||||||
|
},
|
||||||
|
NdbQueryPackage {
|
||||||
|
filters: vec![Filter::new()
|
||||||
|
.authors([pk])
|
||||||
|
.kinds([6])
|
||||||
|
.limit(default_limit())
|
||||||
|
.build()],
|
||||||
|
kind: ValidKind::Six,
|
||||||
|
},
|
||||||
|
];
|
||||||
HybridFilter::split(
|
HybridFilter::split(
|
||||||
|
local,
|
||||||
vec![Filter::new()
|
vec![Filter::new()
|
||||||
.authors([pk])
|
.authors([pk])
|
||||||
.kinds([1])
|
.kinds([1, 6, 0])
|
||||||
.limit(default_limit())
|
|
||||||
.build()],
|
|
||||||
vec![Filter::new()
|
|
||||||
.authors([pk])
|
|
||||||
.kinds([1, 0])
|
|
||||||
.limit(default_remote_limit())
|
.limit(default_remote_limit())
|
||||||
.build()],
|
.build()],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl ViewFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter_notes(cache: &CachedNote, note: &Note) -> bool {
|
pub fn filter_notes(cache: &CachedNote, note: &Note) -> bool {
|
||||||
!cache.reply.borrow(note.tags()).is_reply()
|
note.kind() == 6 || !cache.reply.borrow(note.tags()).is_reply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identity(_cache: &CachedNote, _note: &Note) -> bool {
|
fn identity(_cache: &CachedNote, _note: &Note) -> bool {
|
||||||
@@ -133,6 +133,7 @@ impl TimelineTab {
|
|||||||
ndb: &Ndb,
|
ndb: &Ndb,
|
||||||
txn: &Transaction,
|
txn: &Transaction,
|
||||||
reversed: bool,
|
reversed: bool,
|
||||||
|
use_front_insert: bool,
|
||||||
) -> Option<UnknownPks<'a>> {
|
) -> Option<UnknownPks<'a>> {
|
||||||
if payloads.is_empty() {
|
if payloads.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
@@ -158,7 +159,11 @@ impl TimelineTab {
|
|||||||
debug!("spliced when inserting {num_refs} new notes, resetting virtual list",);
|
debug!("spliced when inserting {num_refs} new notes, resetting virtual list",);
|
||||||
list.reset();
|
list.reset();
|
||||||
}
|
}
|
||||||
MergeKind::FrontInsert => {
|
MergeKind::FrontInsert => 's: {
|
||||||
|
if !use_front_insert {
|
||||||
|
break 's;
|
||||||
|
}
|
||||||
|
|
||||||
// only run this logic if we're reverse-chronological
|
// only run this logic if we're reverse-chronological
|
||||||
// reversed in this case means chronological, since the
|
// reversed in this case means chronological, since the
|
||||||
// default is reverse-chronological. yeah it's confusing.
|
// default is reverse-chronological. yeah it's confusing.
|
||||||
@@ -210,6 +215,7 @@ pub struct Timeline {
|
|||||||
pub selected_view: usize,
|
pub selected_view: usize,
|
||||||
|
|
||||||
pub subscription: TimelineSub,
|
pub subscription: TimelineSub,
|
||||||
|
pub enable_front_insert: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timeline {
|
impl Timeline {
|
||||||
@@ -271,12 +277,16 @@ impl Timeline {
|
|||||||
let subscription = TimelineSub::default();
|
let subscription = TimelineSub::default();
|
||||||
let selected_view = 0;
|
let selected_view = 0;
|
||||||
|
|
||||||
|
// by default, disabled for profiles since they contain widgets above the list items
|
||||||
|
let enable_front_insert = !matches!(kind, TimelineKind::Profile(_));
|
||||||
|
|
||||||
Timeline {
|
Timeline {
|
||||||
kind,
|
kind,
|
||||||
filter,
|
filter,
|
||||||
views,
|
views,
|
||||||
subscription,
|
subscription,
|
||||||
selected_view,
|
selected_view,
|
||||||
|
enable_front_insert,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +412,9 @@ impl Timeline {
|
|||||||
match view.filter {
|
match view.filter {
|
||||||
ViewFilter::NotesAndReplies => {
|
ViewFilter::NotesAndReplies => {
|
||||||
let res: Vec<&NotePayload<'_>> = payloads.iter().collect();
|
let res: Vec<&NotePayload<'_>> = payloads.iter().collect();
|
||||||
if let Some(res) = view.insert(res, ndb, txn, reversed) {
|
if let Some(res) =
|
||||||
|
view.insert(res, ndb, txn, reversed, self.enable_front_insert)
|
||||||
|
{
|
||||||
res.process(unknown_ids, ndb, txn);
|
res.process(unknown_ids, ndb, txn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,7 +430,13 @@ impl Timeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(res) = view.insert(filtered_payloads, ndb, txn, reversed) {
|
if let Some(res) = view.insert(
|
||||||
|
filtered_payloads,
|
||||||
|
ndb,
|
||||||
|
txn,
|
||||||
|
reversed,
|
||||||
|
self.enable_front_insert,
|
||||||
|
) {
|
||||||
res.process(unknown_ids, ndb, txn);
|
res.process(unknown_ids, ndb, txn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,18 +694,32 @@ fn setup_initial_timeline(
|
|||||||
timeline.subscription, timeline.filter
|
timeline.subscription, timeline.filter
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut lim = 0i32;
|
let notes = {
|
||||||
for filter in filters.local() {
|
let mut notes = Vec::new();
|
||||||
lim += filter.limit().unwrap_or(1) as i32;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("setup_initial_timeline: limit for local filter is {}", lim);
|
for package in filters.local().packages {
|
||||||
|
let mut lim = 0i32;
|
||||||
|
for filter in package.filters {
|
||||||
|
lim += filter.limit().unwrap_or(1) as i32;
|
||||||
|
}
|
||||||
|
|
||||||
let notes: Vec<NoteRef> = ndb
|
debug!("setup_initial_timeline: limit for local filter is {}", lim);
|
||||||
.query(txn, filters.local(), lim)?
|
|
||||||
.into_iter()
|
let cur_notes: Vec<NoteRef> = ndb
|
||||||
.map(NoteRef::from_query_result)
|
.query(txn, package.filters, lim)?
|
||||||
.collect();
|
.into_iter()
|
||||||
|
.map(NoteRef::from_query_result)
|
||||||
|
.collect();
|
||||||
|
tracing::debug!(
|
||||||
|
"Found {} notes for kind: {:?}",
|
||||||
|
cur_notes.len(),
|
||||||
|
package.kind
|
||||||
|
);
|
||||||
|
notes.extend(&cur_notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
notes
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(pks) = timeline.insert_new(txn, ndb, note_cache, ¬es) {
|
if let Some(pks) = timeline.insert_new(txn, ndb, note_cache, ¬es) {
|
||||||
pks.process(ndb, txn, unknown_ids);
|
pks.process(ndb, txn, unknown_ids);
|
||||||
|
|||||||
@@ -101,28 +101,37 @@ impl NoteUnits {
|
|||||||
|
|
||||||
let inserted_new = new_order.len();
|
let inserted_new = new_order.len();
|
||||||
|
|
||||||
let front_insertion = inserted_new > 0
|
let front_insertion = if self.order.is_empty() || new_order.is_empty() {
|
||||||
&& if self.order.is_empty() || new_order.is_empty() {
|
!new_order.is_empty()
|
||||||
true
|
} else if self.reversed {
|
||||||
} else if !self.reversed {
|
// reversed is true, sorting should occur less recent to most recent (oldest to newest, opposite of `self.order`)
|
||||||
let first_new = *new_order.first().unwrap();
|
let first_new = *new_order.first().unwrap(); // most recent unit of the new order
|
||||||
let last_old = *self.order.last().unwrap();
|
let last_old = *self.order.last().unwrap(); // least recent unit of the current order
|
||||||
self.storage[first_new] >= self.storage[last_old]
|
|
||||||
} else {
|
// if the most recent unit of the new order is less recent than the least recent unit of the current order,
|
||||||
let last_new = *new_order.last().unwrap();
|
// all current order units are less recent than the new order units.
|
||||||
let first_old = *self.order.first().unwrap();
|
// In other words, they are all being inserted in the front
|
||||||
self.storage[last_new] <= self.storage[first_old]
|
self.storage[first_new] >= self.storage[last_old]
|
||||||
};
|
} else {
|
||||||
|
// reversed is false, sorting should occur most recent to least recent (newest to oldest, as it is in `self.order`)
|
||||||
|
let last_new = *new_order.last().unwrap(); // least recent unit of the new order
|
||||||
|
let first_old = *self.order.first().unwrap(); // most recent unit of the current order
|
||||||
|
|
||||||
|
// if the least recent unit of the new order is more recent than the most recent unit of the current order,
|
||||||
|
// all new units are more recent than the current units.
|
||||||
|
// In other words, they are all being inserted in the front
|
||||||
|
self.storage[last_new] <= self.storage[first_old]
|
||||||
|
};
|
||||||
|
|
||||||
let mut merged = Vec::with_capacity(self.order.len() + new_order.len());
|
let mut merged = Vec::with_capacity(self.order.len() + new_order.len());
|
||||||
let (mut i, mut j) = (0, 0);
|
let (mut i, mut j) = (0, 0);
|
||||||
while i < self.order.len() && j < new_order.len() {
|
while i < self.order.len() && j < new_order.len() {
|
||||||
let index_left = self.order[i];
|
let index_left = self.order[i];
|
||||||
let index_right = new_order[j];
|
let index_right = new_order[j];
|
||||||
let left_item = &self.storage[index_left];
|
let left_unit = &self.storage[index_left];
|
||||||
let right_item = &self.storage[index_right];
|
let right_unit = &self.storage[index_right];
|
||||||
if left_item <= right_item {
|
if left_unit <= right_unit {
|
||||||
// left_item is newer than right_item
|
// the left unit is more recent than (or the same recency as) the right unit
|
||||||
merged.push(index_left);
|
merged.push(index_left);
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ pub fn render_timeline_route(
|
|||||||
| TimelineKind::Generic(_) => {
|
| TimelineKind::Generic(_) => {
|
||||||
let note_action =
|
let note_action =
|
||||||
ui::TimelineView::new(kind, timeline_cache, note_context, note_options, jobs, col)
|
ui::TimelineView::new(kind, timeline_cache, note_context, note_options, jobs, col)
|
||||||
.scroll_to_top(scroll_to_top)
|
|
||||||
.ui(ui);
|
.ui(ui);
|
||||||
|
|
||||||
note_action.map(RenderNavAction::NoteAction)
|
note_action.map(RenderNavAction::NoteAction)
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ fn to_repost<'a>(
|
|||||||
let reposted_note = match get_reposted_note(ndb, txn, &payload.note) {
|
let reposted_note = match get_reposted_note(ndb, txn, &payload.note) {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => {
|
None => {
|
||||||
tracing::error!(
|
tracing::debug!(
|
||||||
"Could not get reposted note for note id {}",
|
"Could not get reposted note for note id {}",
|
||||||
enostr::NoteId::new(*payload.note.id()).hex()
|
enostr::NoteId::new(*payload.note.id()).hex()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -342,6 +342,13 @@ impl<'a, 'd> PostView<'a, 'd> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse {
|
pub fn ui(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse {
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.id_salt(PostView::scroll_id())
|
||||||
|
.show(ui, |ui| self.ui_no_scroll(txn, ui))
|
||||||
|
.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_no_scroll(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse {
|
||||||
while let Some(selected_file) = get_next_selected_file() {
|
while let Some(selected_file) = get_next_selected_file() {
|
||||||
match selected_file {
|
match selected_file {
|
||||||
Ok(selected_media) => {
|
Ok(selected_media) => {
|
||||||
@@ -358,13 +365,6 @@ impl<'a, 'd> PostView<'a, 'd> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.id_salt(PostView::scroll_id())
|
|
||||||
.show(ui, |ui| self.ui_no_scroll(txn, ui))
|
|
||||||
.inner
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ui_no_scroll(&mut self, txn: &Transaction, ui: &mut egui::Ui) -> PostResponse {
|
|
||||||
let focused = self.focused(ui);
|
let focused = self.focused(ui);
|
||||||
let stroke = if focused {
|
let stroke = if focused {
|
||||||
ui.visuals().selection.stroke
|
ui.visuals().selection.stroke
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ pub enum ProfileViewAction {
|
|||||||
Follow(Pubkey),
|
Follow(Pubkey),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ProfileScrollResponse {
|
||||||
|
body_end_pos: f32,
|
||||||
|
action: Option<ProfileViewAction>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'd> ProfileView<'a, 'd> {
|
impl<'a, 'd> ProfileView<'a, 'd> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@@ -65,15 +70,13 @@ impl<'a, 'd> ProfileView<'a, 'd> {
|
|||||||
|
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<ProfileViewAction> {
|
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<ProfileViewAction> {
|
||||||
let scroll_id = ProfileView::scroll_id(self.col_id, self.pubkey);
|
let scroll_id = ProfileView::scroll_id(self.col_id, self.pubkey);
|
||||||
let offset_id = scroll_id.with("scroll_offset");
|
let scroll_area = ScrollArea::vertical().id_salt(scroll_id).animated(false);
|
||||||
|
|
||||||
let mut scroll_area = ScrollArea::vertical().id_salt(scroll_id);
|
let profile_timeline = self
|
||||||
|
.timeline_cache
|
||||||
|
.get_mut(&TimelineKind::Profile(*self.pubkey))?;
|
||||||
|
|
||||||
if let Some(offset) = ui.data(|i| i.get_temp::<f32>(offset_id)) {
|
let output = scroll_area.show(ui, |ui| {
|
||||||
scroll_area = scroll_area.vertical_scroll_offset(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = scroll_area.show(ui, |ui| 's: {
|
|
||||||
let mut action = None;
|
let mut action = None;
|
||||||
let txn = Transaction::new(self.note_context.ndb).expect("txn");
|
let txn = Transaction::new(self.note_context.ndb).expect("txn");
|
||||||
let profile = self
|
let profile = self
|
||||||
@@ -82,23 +85,19 @@ impl<'a, 'd> ProfileView<'a, 'd> {
|
|||||||
.get_profile_by_pubkey(&txn, self.pubkey.bytes())
|
.get_profile_by_pubkey(&txn, self.pubkey.bytes())
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
if let Some(profile_view_action) = self.profile_body(ui, profile.as_ref()) {
|
if let Some(profile_view_action) =
|
||||||
|
profile_body(ui, self.pubkey, self.note_context, profile.as_ref())
|
||||||
|
{
|
||||||
action = Some(profile_view_action);
|
action = Some(profile_view_action);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(profile_timeline) = self
|
let tabs_resp = tabs_ui(
|
||||||
.timeline_cache
|
|
||||||
.get_mut(&TimelineKind::Profile(*self.pubkey))
|
|
||||||
else {
|
|
||||||
break 's action;
|
|
||||||
};
|
|
||||||
|
|
||||||
profile_timeline.selected_view = tabs_ui(
|
|
||||||
ui,
|
ui,
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
profile_timeline.selected_view,
|
profile_timeline.selected_view,
|
||||||
&profile_timeline.views,
|
&profile_timeline.views,
|
||||||
);
|
);
|
||||||
|
profile_timeline.selected_view = tabs_resp.inner;
|
||||||
|
|
||||||
let reversed = false;
|
let reversed = false;
|
||||||
// poll for new notes and insert them into our existing notes
|
// poll for new notes and insert them into our existing notes
|
||||||
@@ -124,145 +123,147 @@ impl<'a, 'd> ProfileView<'a, 'd> {
|
|||||||
action = Some(ProfileViewAction::Note(note_action));
|
action = Some(ProfileViewAction::Note(note_action));
|
||||||
}
|
}
|
||||||
|
|
||||||
action
|
ProfileScrollResponse {
|
||||||
|
body_end_pos: tabs_resp.response.rect.bottom(),
|
||||||
|
action,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.data_mut(|d| d.insert_temp(offset_id, output.state.offset.y));
|
// only allow front insert when the profile body is fully obstructed
|
||||||
|
profile_timeline.enable_front_insert = output.inner.body_end_pos < ui.clip_rect().top();
|
||||||
|
|
||||||
output.inner
|
output.inner.action
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn profile_body(
|
fn profile_body(
|
||||||
&mut self,
|
ui: &mut egui::Ui,
|
||||||
ui: &mut egui::Ui,
|
pubkey: &Pubkey,
|
||||||
profile: Option<&ProfileRecord<'_>>,
|
note_context: &mut NoteContext,
|
||||||
) -> Option<ProfileViewAction> {
|
profile: Option<&ProfileRecord<'_>>,
|
||||||
let mut action = None;
|
) -> Option<ProfileViewAction> {
|
||||||
ui.vertical(|ui| {
|
let mut action = None;
|
||||||
banner(
|
ui.vertical(|ui| {
|
||||||
ui,
|
banner(
|
||||||
profile
|
ui,
|
||||||
.map(|p| p.record().profile())
|
profile
|
||||||
.and_then(|p| p.and_then(|p| p.banner())),
|
.map(|p| p.record().profile())
|
||||||
120.0,
|
.and_then(|p| p.and_then(|p| p.banner())),
|
||||||
);
|
120.0,
|
||||||
|
);
|
||||||
|
|
||||||
let padding = 12.0;
|
let padding = 12.0;
|
||||||
notedeck_ui::padding(padding, ui, |ui| {
|
notedeck_ui::padding(padding, ui, |ui| {
|
||||||
let mut pfp_rect = ui.available_rect_before_wrap();
|
let mut pfp_rect = ui.available_rect_before_wrap();
|
||||||
let size = 80.0;
|
let size = 80.0;
|
||||||
pfp_rect.set_width(size);
|
pfp_rect.set_width(size);
|
||||||
pfp_rect.set_height(size);
|
pfp_rect.set_height(size);
|
||||||
let pfp_rect = pfp_rect.translate(egui::vec2(0.0, -(padding + 2.0 + (size / 2.0))));
|
let pfp_rect = pfp_rect.translate(egui::vec2(0.0, -(padding + 2.0 + (size / 2.0))));
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.put(
|
ui.put(
|
||||||
pfp_rect,
|
pfp_rect,
|
||||||
&mut ProfilePic::new(self.note_context.img_cache, get_profile_url(profile))
|
&mut ProfilePic::new(note_context.img_cache, get_profile_url(profile))
|
||||||
.size(size)
|
.size(size)
|
||||||
.border(ProfilePic::border_stroke(ui)),
|
.border(ProfilePic::border_stroke(ui)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
.add(copy_key_widget(&pfp_rect, self.note_context.i18n))
|
.add(copy_key_widget(&pfp_rect, note_context.i18n))
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
let to_copy = if let Some(bech) = self.pubkey.npub() {
|
let to_copy = if let Some(bech) = pubkey.npub() {
|
||||||
bech
|
bech
|
||||||
} else {
|
} else {
|
||||||
error!("Could not convert Pubkey to bech");
|
error!("Could not convert Pubkey to bech");
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
ui.ctx().copy_text(to_copy)
|
ui.ctx().copy_text(to_copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(egui::Align::RIGHT), |ui| {
|
ui.with_layout(Layout::right_to_left(egui::Align::RIGHT), |ui| {
|
||||||
ui.add_space(24.0);
|
ui.add_space(24.0);
|
||||||
|
|
||||||
let target_key = self.pubkey;
|
let target_key = pubkey;
|
||||||
let selected = self.note_context.accounts.get_selected_account();
|
let selected = note_context.accounts.get_selected_account();
|
||||||
|
|
||||||
let profile_type = if selected.key.secret_key.is_none() {
|
let profile_type = if selected.key.secret_key.is_none() {
|
||||||
ProfileType::ReadOnly
|
ProfileType::ReadOnly
|
||||||
} else if &selected.key.pubkey == self.pubkey {
|
} else if &selected.key.pubkey == pubkey {
|
||||||
ProfileType::MyProfile
|
ProfileType::MyProfile
|
||||||
} else {
|
} else {
|
||||||
ProfileType::Followable(selected.is_following(target_key.bytes()))
|
ProfileType::Followable(selected.is_following(target_key.bytes()))
|
||||||
};
|
};
|
||||||
|
|
||||||
match profile_type {
|
match profile_type {
|
||||||
ProfileType::MyProfile => {
|
ProfileType::MyProfile => {
|
||||||
if ui
|
if ui.add(edit_profile_button(note_context.i18n)).clicked() {
|
||||||
.add(edit_profile_button(self.note_context.i18n))
|
action = Some(ProfileViewAction::EditProfile);
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
action = Some(ProfileViewAction::EditProfile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ProfileType::Followable(is_following) => {
|
}
|
||||||
let follow_button = ui.add(follow_button(is_following));
|
ProfileType::Followable(is_following) => {
|
||||||
|
let follow_button = ui.add(follow_button(is_following));
|
||||||
|
|
||||||
if follow_button.clicked() {
|
if follow_button.clicked() {
|
||||||
action = match is_following {
|
action = match is_following {
|
||||||
IsFollowing::Unknown => {
|
IsFollowing::Unknown => {
|
||||||
// don't do anything, we don't have contact list
|
// don't do anything, we don't have contact list
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
IsFollowing::Yes => {
|
IsFollowing::Yes => {
|
||||||
Some(ProfileViewAction::Unfollow(target_key.to_owned()))
|
Some(ProfileViewAction::Unfollow(target_key.to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
IsFollowing::No => {
|
IsFollowing::No => {
|
||||||
Some(ProfileViewAction::Follow(target_key.to_owned()))
|
Some(ProfileViewAction::Follow(target_key.to_owned()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ProfileType::ReadOnly => {}
|
|
||||||
}
|
}
|
||||||
});
|
ProfileType::ReadOnly => {}
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(18.0);
|
|
||||||
|
|
||||||
ui.add(display_name_widget(&get_display_name(profile), false));
|
|
||||||
|
|
||||||
ui.add_space(8.0);
|
|
||||||
|
|
||||||
ui.add(about_section_widget(profile));
|
|
||||||
|
|
||||||
ui.horizontal_wrapped(|ui| {
|
|
||||||
let website_url = profile
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| p.record().profile())
|
|
||||||
.and_then(|p| p.and_then(|p| p.website()).filter(|s| !s.is_empty()));
|
|
||||||
|
|
||||||
let lud16 = profile
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| p.record().profile())
|
|
||||||
.and_then(|p| p.and_then(|p| p.lud16()).filter(|s| !s.is_empty()));
|
|
||||||
|
|
||||||
if let Some(website_url) = website_url {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
handle_link(ui, website_url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(lud16) = lud16 {
|
|
||||||
if website_url.is_some() {
|
|
||||||
ui.end_row();
|
|
||||||
}
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
handle_lud16(ui, lud16);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
action
|
ui.add_space(18.0);
|
||||||
}
|
|
||||||
|
ui.add(display_name_widget(&get_display_name(profile), false));
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
ui.add(about_section_widget(profile));
|
||||||
|
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
let website_url = profile
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.record().profile())
|
||||||
|
.and_then(|p| p.and_then(|p| p.website()).filter(|s| !s.is_empty()));
|
||||||
|
|
||||||
|
let lud16 = profile
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.record().profile())
|
||||||
|
.and_then(|p| p.and_then(|p| p.lud16()).filter(|s| !s.is_empty()));
|
||||||
|
|
||||||
|
if let Some(website_url) = website_url {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
handle_link(ui, website_url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(lud16) = lud16 {
|
||||||
|
if website_url.is_some() {
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
handle_lud16(ui, lud16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
action
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProfileType {
|
enum ProfileType {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use egui::containers::scroll_area::ScrollBarVisibility;
|
use egui::containers::scroll_area::ScrollBarVisibility;
|
||||||
use egui::{vec2, Direction, Layout, Margin, Pos2, ScrollArea, Sense, Stroke};
|
use egui::{vec2, Color32, Direction, Layout, Margin, Pos2, RichText, ScrollArea, Sense, Stroke};
|
||||||
use egui_tabs::TabColor;
|
use egui_tabs::TabColor;
|
||||||
use enostr::Pubkey;
|
use enostr::Pubkey;
|
||||||
use nostrdb::{ProfileRecord, Transaction};
|
use nostrdb::{Note, ProfileRecord, Transaction};
|
||||||
|
use notedeck::fonts::get_font_size;
|
||||||
use notedeck::name::get_display_name;
|
use notedeck::name::get_display_name;
|
||||||
use notedeck::ui::is_narrow;
|
use notedeck::ui::is_narrow;
|
||||||
use notedeck::{tr_plural, JobsCache, Muted, NoteRef};
|
use notedeck::{tr_plural, JobsCache, Muted, NotedeckTextStyle};
|
||||||
use notedeck_ui::app_images::{like_image, repost_image};
|
use notedeck_ui::app_images::{like_image, repost_image};
|
||||||
use notedeck_ui::ProfilePic;
|
use notedeck_ui::{ProfilePic, ProfilePreview};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ fn timeline_ui(
|
|||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
timeline_id: &TimelineKind,
|
timeline_id: &TimelineKind,
|
||||||
timeline_cache: &mut TimelineCache,
|
timeline_cache: &mut TimelineCache,
|
||||||
note_options: NoteOptions,
|
mut note_options: NoteOptions,
|
||||||
note_context: &mut NoteContext,
|
note_context: &mut NoteContext,
|
||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
col: usize,
|
col: usize,
|
||||||
@@ -118,7 +119,8 @@ fn timeline_ui(
|
|||||||
note_context.i18n,
|
note_context.i18n,
|
||||||
timeline.selected_view,
|
timeline.selected_view,
|
||||||
&timeline.views,
|
&timeline.views,
|
||||||
);
|
)
|
||||||
|
.inner;
|
||||||
|
|
||||||
// need this for some reason??
|
// need this for some reason??
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
@@ -151,12 +153,6 @@ fn timeline_ui(
|
|||||||
.auto_shrink([false, false])
|
.auto_shrink([false, false])
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible);
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible);
|
||||||
|
|
||||||
let offset_id = scroll_id.with("timeline_scroll_offset");
|
|
||||||
|
|
||||||
if let Some(offset) = ui.data(|i| i.get_temp::<f32>(offset_id)) {
|
|
||||||
scroll_area = scroll_area.vertical_scroll_offset(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if goto_top_resp.is_some_and(|r| r.clicked()) {
|
if goto_top_resp.is_some_and(|r| r.clicked()) {
|
||||||
scroll_area = scroll_area.vertical_scroll_offset(0.0);
|
scroll_area = scroll_area.vertical_scroll_offset(0.0);
|
||||||
}
|
}
|
||||||
@@ -181,6 +177,10 @@ fn timeline_ui(
|
|||||||
|
|
||||||
let txn = Transaction::new(note_context.ndb).expect("failed to create txn");
|
let txn = Transaction::new(note_context.ndb).expect("failed to create txn");
|
||||||
|
|
||||||
|
if matches!(timeline_id, TimelineKind::Notifications(_)) {
|
||||||
|
note_options.set(NoteOptions::Notification, true)
|
||||||
|
}
|
||||||
|
|
||||||
TimelineTabView::new(
|
TimelineTabView::new(
|
||||||
timeline.current_view(),
|
timeline.current_view(),
|
||||||
note_options,
|
note_options,
|
||||||
@@ -191,8 +191,6 @@ fn timeline_ui(
|
|||||||
.show(ui)
|
.show(ui)
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.data_mut(|d| d.insert_temp(offset_id, scroll_output.state.offset.y));
|
|
||||||
|
|
||||||
let at_top_after_scroll = scroll_output.state.offset.y == 0.0;
|
let at_top_after_scroll = scroll_output.state.offset.y == 0.0;
|
||||||
let cur_show_top_button = ui.ctx().data(|d| d.get_temp::<bool>(show_top_button_id));
|
let cur_show_top_button = ui.ctx().data(|d| d.get_temp::<bool>(show_top_button_id));
|
||||||
|
|
||||||
@@ -280,7 +278,7 @@ pub fn tabs_ui(
|
|||||||
i18n: &mut Localization,
|
i18n: &mut Localization,
|
||||||
selected: usize,
|
selected: usize,
|
||||||
views: &[TimelineTab],
|
views: &[TimelineTab],
|
||||||
) -> usize {
|
) -> egui::InnerResponse<usize> {
|
||||||
ui.spacing_mut().item_spacing.y = 0.0;
|
ui.spacing_mut().item_spacing.y = 0.0;
|
||||||
|
|
||||||
let tab_res = egui_tabs::Tabs::new(views.len() as i32)
|
let tab_res = egui_tabs::Tabs::new(views.len() as i32)
|
||||||
@@ -328,7 +326,9 @@ pub fn tabs_ui(
|
|||||||
|
|
||||||
let sel = tab_res.selected().unwrap_or_default();
|
let sel = tab_res.selected().unwrap_or_default();
|
||||||
|
|
||||||
let (underline, underline_y) = tab_res.inner()[sel as usize].inner;
|
let res_inner = &tab_res.inner()[sel as usize];
|
||||||
|
|
||||||
|
let (underline, underline_y) = res_inner.inner;
|
||||||
let underline_width = underline.span();
|
let underline_width = underline.span();
|
||||||
|
|
||||||
let tab_anim_id = ui.id().with("tab_anim");
|
let tab_anim_id = ui.id().with("tab_anim");
|
||||||
@@ -355,7 +355,7 @@ pub fn tabs_ui(
|
|||||||
|
|
||||||
ui.painter().hline(underline, underline_y, stroke);
|
ui.painter().hline(underline, underline_y, stroke);
|
||||||
|
|
||||||
sel as usize
|
egui::InnerResponse::new(sel as usize, res_inner.response.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_label_width(ui: &mut egui::Ui, text: &str) -> f32 {
|
fn get_label_width(ui: &mut egui::Ui, text: &str) -> f32 {
|
||||||
@@ -440,15 +440,46 @@ impl<'a, 'd> TimelineTabView<'a, 'd> {
|
|||||||
entry: &NoteUnit,
|
entry: &NoteUnit,
|
||||||
mute: &std::sync::Arc<Muted>,
|
mute: &std::sync::Arc<Muted>,
|
||||||
) -> RenderEntryResponse {
|
) -> RenderEntryResponse {
|
||||||
|
let underlying_note = {
|
||||||
|
let underlying_note_key = match entry {
|
||||||
|
NoteUnit::Single(note_ref) => note_ref.key,
|
||||||
|
NoteUnit::Composite(composite_unit) => match composite_unit {
|
||||||
|
CompositeUnit::Reaction(reaction_unit) => reaction_unit.note_reacted_to.key,
|
||||||
|
CompositeUnit::Repost(repost_unit) => repost_unit.note_reposted.key,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(note) = self
|
||||||
|
.note_context
|
||||||
|
.ndb
|
||||||
|
.get_note_by_key(self.txn, underlying_note_key)
|
||||||
|
else {
|
||||||
|
warn!("failed to query note {:?}", underlying_note_key);
|
||||||
|
return RenderEntryResponse::Unsuccessful;
|
||||||
|
};
|
||||||
|
|
||||||
|
note
|
||||||
|
};
|
||||||
|
|
||||||
|
let muted = root_note_id_from_selected_id(
|
||||||
|
self.note_context.ndb,
|
||||||
|
self.note_context.note_cache,
|
||||||
|
self.txn,
|
||||||
|
underlying_note.id(),
|
||||||
|
)
|
||||||
|
.is_ok_and(|root_id| mute.is_muted(&underlying_note, root_id.bytes()));
|
||||||
|
|
||||||
|
if muted {
|
||||||
|
return RenderEntryResponse::Success(None);
|
||||||
|
}
|
||||||
|
|
||||||
match entry {
|
match entry {
|
||||||
NoteUnit::Single(note_ref) => render_note(
|
NoteUnit::Single(_) => render_note(
|
||||||
ui,
|
ui,
|
||||||
self.note_context,
|
self.note_context,
|
||||||
self.note_options,
|
self.note_options,
|
||||||
self.jobs,
|
self.jobs,
|
||||||
mute,
|
&underlying_note,
|
||||||
self.txn,
|
|
||||||
note_ref,
|
|
||||||
),
|
),
|
||||||
NoteUnit::Composite(composite) => match composite {
|
NoteUnit::Composite(composite) => match composite {
|
||||||
CompositeUnit::Reaction(reaction_unit) => render_reaction_cluster(
|
CompositeUnit::Reaction(reaction_unit) => render_reaction_cluster(
|
||||||
@@ -458,6 +489,7 @@ impl<'a, 'd> TimelineTabView<'a, 'd> {
|
|||||||
self.jobs,
|
self.jobs,
|
||||||
mute,
|
mute,
|
||||||
self.txn,
|
self.txn,
|
||||||
|
&underlying_note,
|
||||||
reaction_unit,
|
reaction_unit,
|
||||||
),
|
),
|
||||||
CompositeUnit::Repost(repost_unit) => render_repost_cluster(
|
CompositeUnit::Repost(repost_unit) => render_repost_cluster(
|
||||||
@@ -467,6 +499,7 @@ impl<'a, 'd> TimelineTabView<'a, 'd> {
|
|||||||
self.jobs,
|
self.jobs,
|
||||||
mute,
|
mute,
|
||||||
self.txn,
|
self.txn,
|
||||||
|
&underlying_note,
|
||||||
repost_unit,
|
repost_unit,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -483,7 +516,9 @@ impl CompositeType {
|
|||||||
fn image(&self, darkmode: bool) -> egui::Image<'static> {
|
fn image(&self, darkmode: bool) -> egui::Image<'static> {
|
||||||
match self {
|
match self {
|
||||||
CompositeType::Reaction => like_image(),
|
CompositeType::Reaction => like_image(),
|
||||||
CompositeType::Repost => repost_image(darkmode),
|
CompositeType::Repost => {
|
||||||
|
repost_image(darkmode).tint(Color32::from_rgb(0x68, 0xC3, 0x51))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,6 +528,7 @@ impl CompositeType {
|
|||||||
first_name: &str,
|
first_name: &str,
|
||||||
total_count: usize,
|
total_count: usize,
|
||||||
referenced_type: ReferencedNoteType,
|
referenced_type: ReferencedNoteType,
|
||||||
|
notification: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
let count = total_count - 1;
|
let count = total_count - 1;
|
||||||
|
|
||||||
@@ -500,7 +536,16 @@ impl CompositeType {
|
|||||||
CompositeType::Reaction => {
|
CompositeType::Reaction => {
|
||||||
reaction_description(loc, first_name, count, referenced_type)
|
reaction_description(loc, first_name, count, referenced_type)
|
||||||
}
|
}
|
||||||
CompositeType::Repost => repost_description(loc, first_name, count, referenced_type),
|
CompositeType::Repost => repost_description(
|
||||||
|
loc,
|
||||||
|
first_name,
|
||||||
|
count,
|
||||||
|
if notification {
|
||||||
|
DescriptionType::Notification(referenced_type)
|
||||||
|
} else {
|
||||||
|
DescriptionType::Other
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,46 +598,72 @@ fn reaction_description(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DescriptionType {
|
||||||
|
Notification(ReferencedNoteType),
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
fn repost_description(
|
fn repost_description(
|
||||||
loc: &mut Localization,
|
loc: &mut Localization,
|
||||||
first_name: &str,
|
first_name: &str,
|
||||||
count: usize,
|
count: usize,
|
||||||
referenced_type: ReferencedNoteType,
|
description_type: DescriptionType,
|
||||||
) -> String {
|
) -> String {
|
||||||
match referenced_type {
|
match description_type {
|
||||||
ReferencedNoteType::Tagged => {
|
DescriptionType::Notification(referenced_type) => match referenced_type {
|
||||||
if count == 0 {
|
ReferencedNoteType::Tagged => {
|
||||||
tr!(
|
if count == 0 {
|
||||||
loc,
|
tr!(
|
||||||
"{name} reposted a note you were tagged in",
|
loc,
|
||||||
"repost from user",
|
"{name} reposted a note you were tagged in",
|
||||||
name = first_name
|
"repost from user",
|
||||||
)
|
name = first_name
|
||||||
} else {
|
)
|
||||||
tr_plural!(
|
} else {
|
||||||
loc,
|
tr_plural!(
|
||||||
"{name} and {count} other reposted a note you were tagged in",
|
loc,
|
||||||
"{name} and {count} others reposted a note you were tagged in",
|
"{name} and {count} other reposted a note you were tagged in",
|
||||||
"describing the amount of reposts a note you were tagged in received",
|
"{name} and {count} others reposted a note you were tagged in",
|
||||||
count,
|
"describing the amount of reposts a note you were tagged in received",
|
||||||
name = first_name
|
count,
|
||||||
)
|
name = first_name
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
ReferencedNoteType::Yours => {
|
||||||
ReferencedNoteType::Yours => {
|
if count == 0 {
|
||||||
|
tr!(
|
||||||
|
loc,
|
||||||
|
"{name} reposted your note",
|
||||||
|
"repost from user",
|
||||||
|
name = first_name
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
tr_plural!(
|
||||||
|
loc,
|
||||||
|
"{name} and {count} other reposted your note",
|
||||||
|
"{name} and {count} others reposted your note",
|
||||||
|
"describing the amount of reposts your note received",
|
||||||
|
count,
|
||||||
|
name = first_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DescriptionType::Other => {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
tr!(
|
tr!(
|
||||||
loc,
|
loc,
|
||||||
"{name} reposted your note",
|
"{name} reposted",
|
||||||
"repost from user",
|
"repost from user",
|
||||||
name = first_name
|
name = first_name
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
tr_plural!(
|
tr_plural!(
|
||||||
loc,
|
loc,
|
||||||
"{name} and {count} other reposted your note",
|
"{name} and {count} other reposted",
|
||||||
"{name} and {count} others reposted your note",
|
"{name} and {count} others reposted",
|
||||||
"describing the amount of reposts your note received",
|
"describing the amount of reposts a note has",
|
||||||
count,
|
count,
|
||||||
name = first_name
|
name = first_name
|
||||||
)
|
)
|
||||||
@@ -606,32 +677,11 @@ fn render_note(
|
|||||||
note_context: &mut NoteContext,
|
note_context: &mut NoteContext,
|
||||||
note_options: NoteOptions,
|
note_options: NoteOptions,
|
||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
mute: &std::sync::Arc<Muted>,
|
note: &Note,
|
||||||
txn: &Transaction,
|
|
||||||
note_ref: &NoteRef,
|
|
||||||
) -> RenderEntryResponse {
|
) -> RenderEntryResponse {
|
||||||
let note = if let Ok(note) = note_context.ndb.get_note_by_key(txn, note_ref.key) {
|
|
||||||
note
|
|
||||||
} else {
|
|
||||||
warn!("failed to query note {:?}", note_ref.key);
|
|
||||||
return RenderEntryResponse::Unsuccessful;
|
|
||||||
};
|
|
||||||
|
|
||||||
let muted = if let Ok(root_id) =
|
|
||||||
root_note_id_from_selected_id(note_context.ndb, note_context.note_cache, txn, note.id())
|
|
||||||
{
|
|
||||||
mute.is_muted(¬e, root_id.bytes())
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if muted {
|
|
||||||
return RenderEntryResponse::Success(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut action = None;
|
let mut action = None;
|
||||||
notedeck_ui::padding(8.0, ui, |ui| {
|
notedeck_ui::padding(8.0, ui, |ui| {
|
||||||
let resp = NoteView::new(note_context, ¬e, note_options, jobs).show(ui);
|
let resp = NoteView::new(note_context, note, note_options, jobs).show(ui);
|
||||||
|
|
||||||
if let Some(note_action) = resp.action {
|
if let Some(note_action) = resp.action {
|
||||||
action = Some(note_action);
|
action = Some(note_action);
|
||||||
@@ -643,6 +693,7 @@ fn render_note(
|
|||||||
RenderEntryResponse::Success(action)
|
RenderEntryResponse::Success(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn render_reaction_cluster(
|
fn render_reaction_cluster(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
note_context: &mut NoteContext,
|
note_context: &mut NoteContext,
|
||||||
@@ -650,16 +701,9 @@ fn render_reaction_cluster(
|
|||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
mute: &std::sync::Arc<Muted>,
|
mute: &std::sync::Arc<Muted>,
|
||||||
txn: &Transaction,
|
txn: &Transaction,
|
||||||
|
underlying_note: &Note,
|
||||||
reaction: &ReactionUnit,
|
reaction: &ReactionUnit,
|
||||||
) -> RenderEntryResponse {
|
) -> RenderEntryResponse {
|
||||||
let reacted_to_key = reaction.note_reacted_to.key;
|
|
||||||
let reacted_to_note = if let Ok(note) = note_context.ndb.get_note_by_key(txn, reacted_to_key) {
|
|
||||||
note
|
|
||||||
} else {
|
|
||||||
warn!("failed to query note {:?}", reacted_to_key);
|
|
||||||
return RenderEntryResponse::Unsuccessful;
|
|
||||||
};
|
|
||||||
|
|
||||||
let profiles_to_show: Vec<ProfileEntry> = reaction
|
let profiles_to_show: Vec<ProfileEntry> = reaction
|
||||||
.reactions
|
.reactions
|
||||||
.values()
|
.values()
|
||||||
@@ -674,20 +718,21 @@ fn render_reaction_cluster(
|
|||||||
render_composite_entry(
|
render_composite_entry(
|
||||||
ui,
|
ui,
|
||||||
note_context,
|
note_context,
|
||||||
note_options,
|
note_options | NoteOptions::Notification,
|
||||||
jobs,
|
jobs,
|
||||||
reacted_to_note,
|
underlying_note,
|
||||||
profiles_to_show,
|
profiles_to_show,
|
||||||
CompositeType::Reaction,
|
CompositeType::Reaction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn render_composite_entry(
|
fn render_composite_entry(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
note_context: &mut NoteContext,
|
note_context: &mut NoteContext,
|
||||||
note_options: NoteOptions,
|
mut note_options: NoteOptions,
|
||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
underlying_note: nostrdb::Note<'_>,
|
underlying_note: &nostrdb::Note<'_>,
|
||||||
profiles_to_show: Vec<ProfileEntry>,
|
profiles_to_show: Vec<ProfileEntry>,
|
||||||
composite_type: CompositeType,
|
composite_type: CompositeType,
|
||||||
) -> RenderEntryResponse {
|
) -> RenderEntryResponse {
|
||||||
@@ -697,92 +742,203 @@ fn render_composite_entry(
|
|||||||
let num_profiles = profiles_to_show.len();
|
let num_profiles = profiles_to_show.len();
|
||||||
|
|
||||||
let mut action = None;
|
let mut action = None;
|
||||||
|
|
||||||
|
let referenced_type = if note_context
|
||||||
|
.accounts
|
||||||
|
.get_selected_account()
|
||||||
|
.key
|
||||||
|
.pubkey
|
||||||
|
.bytes()
|
||||||
|
!= underlying_note.pubkey()
|
||||||
|
{
|
||||||
|
ReferencedNoteType::Tagged
|
||||||
|
} else {
|
||||||
|
ReferencedNoteType::Yours
|
||||||
|
};
|
||||||
|
|
||||||
|
if !note_options.contains(NoteOptions::TrustMedia) {
|
||||||
|
let acc = note_context.accounts.get_selected_account();
|
||||||
|
for entry in &profiles_to_show {
|
||||||
|
if matches!(acc.is_following(entry.pk), notedeck::IsFollowing::Yes) {
|
||||||
|
note_options = note_options.union(NoteOptions::TrustMedia);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
egui::Frame::new()
|
egui::Frame::new()
|
||||||
.inner_margin(Margin::symmetric(8, 4))
|
.inner_margin(Margin::symmetric(8, 4))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.allocate_ui_with_layout(
|
let show_label_newline = ui
|
||||||
vec2(ui.available_width(), 32.0),
|
.horizontal_wrapped(|ui| {
|
||||||
Layout::left_to_right(egui::Align::Center),
|
let pfps_resp = ui
|
||||||
|ui| {
|
.allocate_ui_with_layout(
|
||||||
ui.vertical(|ui| {
|
vec2(ui.available_width(), 32.0),
|
||||||
ui.add_space(4.0);
|
Layout::left_to_right(egui::Align::Center),
|
||||||
ui.add_sized(
|
|ui| {
|
||||||
vec2(28.0, 28.0),
|
render_profiles(
|
||||||
composite_type.image(ui.visuals().dark_mode),
|
ui,
|
||||||
);
|
profiles_to_show,
|
||||||
});
|
&composite_type,
|
||||||
|
note_context.img_cache,
|
||||||
|
note_options.contains(NoteOptions::Notification),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.inner;
|
||||||
|
|
||||||
ui.add_space(16.0);
|
if let Some(cur_action) = pfps_resp.action {
|
||||||
|
action = Some(cur_action);
|
||||||
|
}
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
let description = composite_type.description(
|
||||||
ScrollArea::horizontal()
|
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
for entry in profiles_to_show {
|
|
||||||
let resp = ui.add(
|
|
||||||
&mut ProfilePic::from_profile_or_default(
|
|
||||||
note_context.img_cache,
|
|
||||||
entry.record.as_ref(),
|
|
||||||
)
|
|
||||||
.size(24.0)
|
|
||||||
.sense(Sense::click()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if resp.clicked() {
|
|
||||||
action = Some(NoteAction::Profile(*entry.pk))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let referenced_type = if note_context
|
|
||||||
.accounts
|
|
||||||
.get_selected_account()
|
|
||||||
.key
|
|
||||||
.pubkey
|
|
||||||
.bytes()
|
|
||||||
!= underlying_note.pubkey()
|
|
||||||
{
|
|
||||||
ReferencedNoteType::Tagged
|
|
||||||
} else {
|
|
||||||
ReferencedNoteType::Yours
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.add_space(2.0);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.add_space(52.0);
|
|
||||||
|
|
||||||
ui.horizontal_wrapped(|ui| {
|
|
||||||
ui.label(composite_type.description(
|
|
||||||
note_context.i18n,
|
note_context.i18n,
|
||||||
&first_name,
|
&first_name,
|
||||||
num_profiles,
|
num_profiles,
|
||||||
referenced_type,
|
referenced_type,
|
||||||
))
|
note_options.contains(NoteOptions::Notification),
|
||||||
|
);
|
||||||
|
let galley = ui.painter().layout_no_wrap(
|
||||||
|
description.clone(),
|
||||||
|
NotedeckTextStyle::Small.get_font_id(ui.ctx()),
|
||||||
|
ui.visuals().text_color(),
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
let galley_pos = {
|
||||||
|
let mut galley_pos = ui.next_widget_position();
|
||||||
|
galley_pos.y = pfps_resp.resp.rect.right_center().y;
|
||||||
|
galley_pos.y -= galley.rect.height() / 2.0;
|
||||||
|
galley_pos
|
||||||
|
};
|
||||||
|
|
||||||
|
let fits_no_wrap = {
|
||||||
|
let mut rightmost_pos = galley_pos;
|
||||||
|
rightmost_pos.x += galley.rect.width();
|
||||||
|
|
||||||
|
ui.available_rect_before_wrap().contains(rightmost_pos)
|
||||||
|
};
|
||||||
|
|
||||||
|
if fits_no_wrap {
|
||||||
|
ui.painter()
|
||||||
|
.galley(galley_pos, galley, ui.visuals().text_color());
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(description)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.inner;
|
||||||
|
|
||||||
|
if let Some(desc) = show_label_newline {
|
||||||
|
ui.add_space(4.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_space(48.0);
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
ui.add(egui::Label::new(
|
||||||
|
RichText::new(desc)
|
||||||
|
.size(get_font_size(ui.ctx(), &NotedeckTextStyle::Small)),
|
||||||
|
));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
ui.add_space(16.0);
|
ui.add_space(16.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
let resp = ui
|
||||||
ui.add_space(48.0);
|
.horizontal(|ui| {
|
||||||
let options = note_options
|
if note_options.contains(NoteOptions::Notification) {
|
||||||
.difference(NoteOptions::ActionBar | NoteOptions::OptionsButton)
|
note_options = note_options
|
||||||
.union(NoteOptions::NotificationPreview);
|
.difference(NoteOptions::ActionBar | NoteOptions::OptionsButton)
|
||||||
let resp = NoteView::new(note_context, &underlying_note, options, jobs).show(ui);
|
.union(NoteOptions::NotificationPreview);
|
||||||
|
|
||||||
if let Some(note_action) = resp.action {
|
ui.add_space(48.0);
|
||||||
action = Some(note_action);
|
};
|
||||||
}
|
NoteView::new(note_context, underlying_note, note_options, jobs).show(ui)
|
||||||
});
|
})
|
||||||
|
.inner;
|
||||||
|
|
||||||
|
if let Some(note_action) = resp.action {
|
||||||
|
action.get_or_insert(note_action);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
notedeck_ui::hline(ui);
|
notedeck_ui::hline(ui);
|
||||||
RenderEntryResponse::Success(action)
|
RenderEntryResponse::Success(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_profiles(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
profiles_to_show: Vec<ProfileEntry>,
|
||||||
|
composite_type: &CompositeType,
|
||||||
|
img_cache: &mut notedeck::Images,
|
||||||
|
notification: bool,
|
||||||
|
) -> PfpsResponse {
|
||||||
|
let mut action = None;
|
||||||
|
if notification {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.add_space(9.0);
|
||||||
|
ui.add_sized(
|
||||||
|
vec2(20.0, 20.0),
|
||||||
|
composite_type.image(ui.visuals().dark_mode),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if notification {
|
||||||
|
ui.add_space(16.0);
|
||||||
|
} else {
|
||||||
|
ui.add_space(2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = ui.horizontal(|ui| {
|
||||||
|
ScrollArea::horizontal()
|
||||||
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let mut last_pfp_resp = None;
|
||||||
|
for entry in profiles_to_show {
|
||||||
|
let mut resp = ui.add(
|
||||||
|
&mut ProfilePic::from_profile_or_default(img_cache, entry.record.as_ref())
|
||||||
|
.size(24.0)
|
||||||
|
.sense(Sense::click()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(record) = entry.record.as_ref() {
|
||||||
|
resp = resp.on_hover_ui_at_pointer(|ui| {
|
||||||
|
ui.set_max_width(300.0);
|
||||||
|
ui.add(ProfilePreview::new(record, img_cache));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
last_pfp_resp = Some(resp.clone());
|
||||||
|
|
||||||
|
if resp.clicked() {
|
||||||
|
action = Some(NoteAction::Profile(*entry.pk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_pfp_resp
|
||||||
|
})
|
||||||
|
.inner
|
||||||
|
});
|
||||||
|
|
||||||
|
let resp = if let Some(r) = resp.inner {
|
||||||
|
r
|
||||||
|
} else {
|
||||||
|
resp.response
|
||||||
|
};
|
||||||
|
|
||||||
|
PfpsResponse { action, resp }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PfpsResponse {
|
||||||
|
action: Option<NoteAction>,
|
||||||
|
resp: egui::Response,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn render_repost_cluster(
|
fn render_repost_cluster(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
note_context: &mut NoteContext,
|
note_context: &mut NoteContext,
|
||||||
@@ -790,16 +946,9 @@ fn render_repost_cluster(
|
|||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
mute: &std::sync::Arc<Muted>,
|
mute: &std::sync::Arc<Muted>,
|
||||||
txn: &Transaction,
|
txn: &Transaction,
|
||||||
|
underlying_note: &Note,
|
||||||
repost: &RepostUnit,
|
repost: &RepostUnit,
|
||||||
) -> RenderEntryResponse {
|
) -> RenderEntryResponse {
|
||||||
let reposted_key = repost.note_reposted.key;
|
|
||||||
let reposted_note = if let Ok(note) = note_context.ndb.get_note_by_key(txn, reposted_key) {
|
|
||||||
note
|
|
||||||
} else {
|
|
||||||
warn!("failed to query note {:?}", reposted_key);
|
|
||||||
return RenderEntryResponse::Unsuccessful;
|
|
||||||
};
|
|
||||||
|
|
||||||
let profiles_to_show: Vec<ProfileEntry> = repost
|
let profiles_to_show: Vec<ProfileEntry> = repost
|
||||||
.reposts
|
.reposts
|
||||||
.values()
|
.values()
|
||||||
@@ -815,7 +964,7 @@ fn render_repost_cluster(
|
|||||||
note_context,
|
note_context,
|
||||||
note_options,
|
note_options,
|
||||||
jobs,
|
jobs,
|
||||||
reposted_note,
|
underlying_note,
|
||||||
profiles_to_show,
|
profiles_to_show,
|
||||||
CompositeType::Repost,
|
CompositeType::Repost,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ use crate::{
|
|||||||
use egui::{Color32, Hyperlink, Label, RichText};
|
use egui::{Color32, Hyperlink, Label, RichText};
|
||||||
use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction};
|
use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction};
|
||||||
use notedeck::Localization;
|
use notedeck::Localization;
|
||||||
use notedeck::{
|
use notedeck::{time_format, update_imeta_blurhashes, NoteCache, NoteContext, NotedeckTextStyle};
|
||||||
time_format, update_imeta_blurhashes, IsFollowing, NoteCache, NoteContext, NotedeckTextStyle,
|
|
||||||
};
|
|
||||||
use notedeck::{JobsCache, RenderableMedia};
|
use notedeck::{JobsCache, RenderableMedia};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
@@ -374,21 +372,6 @@ fn render_undecorated_note_contents<'a>(
|
|||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
let carousel_id = egui::Id::new(("carousel", note.key().expect("expected tx note")));
|
let carousel_id = egui::Id::new(("carousel", note.key().expect("expected tx note")));
|
||||||
|
|
||||||
let is_self = note.pubkey()
|
|
||||||
== note_context
|
|
||||||
.accounts
|
|
||||||
.get_selected_account()
|
|
||||||
.key
|
|
||||||
.pubkey
|
|
||||||
.bytes();
|
|
||||||
|
|
||||||
let trusted_media = is_self
|
|
||||||
|| note_context
|
|
||||||
.accounts
|
|
||||||
.get_selected_account()
|
|
||||||
.is_following(note.pubkey())
|
|
||||||
== IsFollowing::Yes;
|
|
||||||
|
|
||||||
media_action = image_carousel(
|
media_action = image_carousel(
|
||||||
ui,
|
ui,
|
||||||
note_context.img_cache,
|
note_context.img_cache,
|
||||||
@@ -396,7 +379,6 @@ fn render_undecorated_note_contents<'a>(
|
|||||||
jobs,
|
jobs,
|
||||||
&supported_medias,
|
&supported_medias,
|
||||||
carousel_id,
|
carousel_id,
|
||||||
trusted_media,
|
|
||||||
note_context.i18n,
|
note_context.i18n,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ pub fn image_carousel(
|
|||||||
jobs: &mut JobsCache,
|
jobs: &mut JobsCache,
|
||||||
medias: &[RenderableMedia],
|
medias: &[RenderableMedia],
|
||||||
carousel_id: egui::Id,
|
carousel_id: egui::Id,
|
||||||
trusted_media: bool,
|
|
||||||
i18n: &mut Localization,
|
i18n: &mut Localization,
|
||||||
note_options: NoteOptions,
|
note_options: NoteOptions,
|
||||||
) -> Option<MediaAction> {
|
) -> Option<MediaAction> {
|
||||||
@@ -68,7 +67,7 @@ pub fn image_carousel(
|
|||||||
job_pool,
|
job_pool,
|
||||||
jobs,
|
jobs,
|
||||||
media,
|
media,
|
||||||
trusted_media,
|
note_options.contains(NoteOptions::TrustMedia),
|
||||||
i18n,
|
i18n,
|
||||||
size,
|
size,
|
||||||
if note_options.contains(NoteOptions::NoAnimations) {
|
if note_options.contains(NoteOptions::NoAnimations) {
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ pub mod options;
|
|||||||
pub mod reply_description;
|
pub mod reply_description;
|
||||||
|
|
||||||
use crate::{app_images, secondary_label};
|
use crate::{app_images, secondary_label};
|
||||||
use crate::{
|
use crate::{widgets::x_button, ProfilePic, ProfilePreview, PulseAlpha, Username};
|
||||||
profile::name::one_line_display_name_widget, widgets::x_button, ProfilePic, ProfilePreview,
|
|
||||||
PulseAlpha, Username,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use contents::{render_note_preview, NoteContents};
|
pub use contents::{render_note_preview, NoteContents};
|
||||||
pub use context::NoteContextButton;
|
pub use context::NoteContextButton;
|
||||||
@@ -25,14 +22,12 @@ pub use options::NoteOptions;
|
|||||||
pub use reply_description::reply_desc;
|
pub use reply_description::reply_desc;
|
||||||
|
|
||||||
use egui::emath::{pos2, Vec2};
|
use egui::emath::{pos2, Vec2};
|
||||||
use egui::{Id, Pos2, Rect, Response, RichText, Sense};
|
use egui::{Id, Pos2, Rect, Response, Sense};
|
||||||
use enostr::{KeypairUnowned, NoteId, Pubkey};
|
use enostr::{KeypairUnowned, NoteId, Pubkey};
|
||||||
use nostrdb::{Ndb, Note, NoteKey, ProfileRecord, Transaction};
|
use nostrdb::{Ndb, Note, NoteKey, ProfileRecord, Transaction};
|
||||||
use notedeck::{
|
use notedeck::{
|
||||||
name::get_display_name,
|
|
||||||
note::{NoteAction, NoteContext, ZapAction},
|
note::{NoteAction, NoteContext, ZapAction},
|
||||||
tr, AnyZapState, ContextSelection, NoteZapTarget, NoteZapTargetOwned, NotedeckTextStyle,
|
tr, AnyZapState, ContextSelection, NoteZapTarget, NoteZapTargetOwned, ZapTarget, Zaps,
|
||||||
ZapTarget, Zaps,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct NoteView<'a, 'd> {
|
pub struct NoteView<'a, 'd> {
|
||||||
@@ -306,60 +301,19 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_repost(
|
|
||||||
&mut self,
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
txn: &Transaction,
|
|
||||||
note_to_repost: Note<'_>,
|
|
||||||
) -> NoteResponse {
|
|
||||||
let profile = self
|
|
||||||
.note_context
|
|
||||||
.ndb
|
|
||||||
.get_profile_by_pubkey(txn, self.note.pubkey());
|
|
||||||
|
|
||||||
let style = NotedeckTextStyle::Small;
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
ui.add_space(2.0);
|
|
||||||
ui.add_sized([20.0, 20.0], repost_icon(ui.visuals().dark_mode));
|
|
||||||
});
|
|
||||||
ui.add_space(6.0);
|
|
||||||
let resp = ui.add(one_line_display_name_widget(
|
|
||||||
ui.visuals(),
|
|
||||||
get_display_name(profile.as_ref().ok()),
|
|
||||||
style,
|
|
||||||
));
|
|
||||||
if let Ok(rec) = &profile {
|
|
||||||
resp.on_hover_ui_at_pointer(|ui| {
|
|
||||||
ui.set_max_width(300.0);
|
|
||||||
ui.add(ProfilePreview::new(rec, self.note_context.img_cache));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let color = ui.style().visuals.noninteractive().fg_stroke.color;
|
|
||||||
ui.add_space(4.0);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(tr!(
|
|
||||||
self.note_context.i18n,
|
|
||||||
"Reposted",
|
|
||||||
"Label for reposted notes"
|
|
||||||
))
|
|
||||||
.color(color)
|
|
||||||
.text_style(style.text_style()),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
NoteView::new(self.note_context, ¬e_to_repost, self.flags, self.jobs).show(ui)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_impl(&mut self, ui: &mut egui::Ui) -> NoteResponse {
|
|
||||||
let txn = self.note.txn().expect("txn");
|
|
||||||
if let Some(note_to_repost) = get_reposted_note(self.note_context.ndb, txn, self.note) {
|
|
||||||
self.show_repost(ui, txn, note_to_repost)
|
|
||||||
} else {
|
|
||||||
self.show_standard(ui)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show(&mut self, ui: &mut egui::Ui) -> NoteResponse {
|
pub fn show(&mut self, ui: &mut egui::Ui) -> NoteResponse {
|
||||||
|
if !self.flags.contains(NoteOptions::TrustMedia) {
|
||||||
|
let acc = self.note_context.accounts.get_selected_account();
|
||||||
|
if self.note.pubkey() == acc.key.pubkey.bytes()
|
||||||
|
|| matches!(
|
||||||
|
acc.is_following(self.note.pubkey()),
|
||||||
|
notedeck::IsFollowing::Yes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
self.flags = self.flags.union(NoteOptions::TrustMedia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.options().contains(NoteOptions::Textmode) {
|
if self.options().contains(NoteOptions::Textmode) {
|
||||||
NoteResponse::new(self.textmode_ui(ui))
|
NoteResponse::new(self.textmode_ui(ui))
|
||||||
} else if self.options().contains(NoteOptions::Framed) {
|
} else if self.options().contains(NoteOptions::Framed) {
|
||||||
@@ -376,11 +330,11 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
if is_narrow(ui.ctx()) {
|
if is_narrow(ui.ctx()) {
|
||||||
ui.set_width(ui.available_width());
|
ui.set_width(ui.available_width());
|
||||||
}
|
}
|
||||||
self.show_impl(ui)
|
self.show_standard(ui)
|
||||||
})
|
})
|
||||||
.inner
|
.inner
|
||||||
} else {
|
} else {
|
||||||
self.show_impl(ui)
|
self.show_standard(ui)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,7 +743,7 @@ fn note_hitbox_id(
|
|||||||
|
|
||||||
fn maybe_note_hitbox(ui: &mut egui::Ui, hitbox_id: egui::Id) -> Option<Response> {
|
fn maybe_note_hitbox(ui: &mut egui::Ui, hitbox_id: egui::Id) -> Option<Response> {
|
||||||
ui.ctx()
|
ui.ctx()
|
||||||
.data_mut(|d| d.get_persisted(hitbox_id))
|
.data_mut(|d| d.get_temp(hitbox_id))
|
||||||
.map(|note_size: Vec2| {
|
.map(|note_size: Vec2| {
|
||||||
// The hitbox should extend the entire width of the
|
// The hitbox should extend the entire width of the
|
||||||
// container. The hitbox height was cached last layout.
|
// container. The hitbox height was cached last layout.
|
||||||
|
|||||||
@@ -39,8 +39,14 @@ bitflags! {
|
|||||||
/// no animation override (accessibility)
|
/// no animation override (accessibility)
|
||||||
const NoAnimations = 1 << 17;
|
const NoAnimations = 1 << 17;
|
||||||
|
|
||||||
/// Styled for a notification preview
|
/// The note should be displayed as a preview of the underlying note of a composite unit
|
||||||
const NotificationPreview = 1 << 18;
|
const NotificationPreview = 1 << 18;
|
||||||
|
|
||||||
|
/// The note is a notification
|
||||||
|
const Notification = 1 << 19;
|
||||||
|
|
||||||
|
/// There is enough trust to show media in this note
|
||||||
|
const TrustMedia = 1 << 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user