mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-05-15 08:45:06 +00:00
feat(lxmf): update conversation previews to include image, audio, and file attachment notifications and add related tests
This commit is contained in:
@@ -152,7 +152,7 @@ def lxmf_sidebar_preview_for_conversation_latest_row(
|
||||
local_hash: str,
|
||||
peer_display_name: str,
|
||||
) -> str:
|
||||
"""Single-line preview for conversation list APIs (reactions have empty body)."""
|
||||
"""Single-line preview for conversation list APIs (reactions and some media have empty body)."""
|
||||
content = row.get("content")
|
||||
if content is not None and str(content).strip():
|
||||
return str(content)
|
||||
@@ -202,6 +202,29 @@ def lxmf_sidebar_preview_for_conversation_latest_row(
|
||||
return f"{actor} requested your location"
|
||||
return f"{actor} sent a location request"
|
||||
|
||||
image = fields.get("image")
|
||||
if isinstance(image, dict) and image:
|
||||
if actor == "You":
|
||||
return "You sent an image"
|
||||
return f"{actor} sent an image"
|
||||
|
||||
audio = fields.get("audio")
|
||||
if isinstance(audio, dict) and audio:
|
||||
if actor == "You":
|
||||
return "You sent a voice note"
|
||||
return f"{actor} sent a voice note"
|
||||
|
||||
file_attachments = fields.get("file_attachments")
|
||||
if isinstance(file_attachments, list) and len(file_attachments) > 0:
|
||||
n = len(file_attachments)
|
||||
if n == 1:
|
||||
if actor == "You":
|
||||
return "You sent a file"
|
||||
return f"{actor} sent a file"
|
||||
if actor == "You":
|
||||
return f"You sent {n} files"
|
||||
return f"{actor} sent {n} files"
|
||||
|
||||
return str(content or "")
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
v-if="
|
||||
selectedPeerPath ||
|
||||
selectedPeerSignalMetrics?.snr != null ||
|
||||
selectedPeerLxmfStampInfo?.stamp_cost
|
||||
selectedPeerLxmfStampInfo?.stamp_cost ||
|
||||
lxmfHasOutboundTicket
|
||||
"
|
||||
class="flex items-center gap-2 min-w-0"
|
||||
>
|
||||
@@ -77,9 +78,30 @@
|
||||
>
|
||||
</span>
|
||||
|
||||
<span v-if="selectedPeerLxmfStampInfo?.stamp_cost" class="flex items-center gap-2 shrink-0">
|
||||
<span
|
||||
v-if="selectedPeerLxmfStampInfo?.stamp_cost || lxmfHasOutboundTicket"
|
||||
class="flex items-center gap-1 shrink-0"
|
||||
>
|
||||
<span class="text-gray-300 dark:text-zinc-700 opacity-50">•</span>
|
||||
<MaterialDesignIcon
|
||||
v-if="lxmfHasOutboundTicket"
|
||||
icon-name="ticket-confirmation"
|
||||
class="size-3.5 shrink-0"
|
||||
:class="
|
||||
lxmfStampTicketValid
|
||||
? 'text-emerald-600 dark:text-emerald-400'
|
||||
: 'text-amber-600 dark:text-amber-500'
|
||||
"
|
||||
:title="
|
||||
lxmfStampTicketValid
|
||||
? $t('messages.stamp_ticket_valid', {
|
||||
expires: lxmfStampTicketExpiresRelative,
|
||||
})
|
||||
: $t('messages.stamp_ticket_expired')
|
||||
"
|
||||
/>
|
||||
<span
|
||||
v-if="selectedPeerLxmfStampInfo?.stamp_cost"
|
||||
class="cursor-pointer hover:text-gray-700 dark:hover:text-zinc-200"
|
||||
title="LXMF stamp requirement"
|
||||
@click="$emit('stamp-info-click', selectedPeerLxmfStampInfo)"
|
||||
@@ -115,11 +137,15 @@
|
||||
|
||||
<script>
|
||||
import Utils from "../../js/Utils";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
|
||||
import IconButton from "../IconButton.vue";
|
||||
import LxmfUserIcon from "../LxmfUserIcon.vue";
|
||||
import ConversationDropDownMenu from "./ConversationDropDownMenu.vue";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export default {
|
||||
name: "ConversationPeerHeader",
|
||||
components: {
|
||||
@@ -176,6 +202,34 @@ export default {
|
||||
destinationDisplay() {
|
||||
return Utils.formatDestinationHash(this.selectedPeer?.destination_hash);
|
||||
},
|
||||
lxmfHasOutboundTicket() {
|
||||
return this.selectedPeerLxmfStampInfo?.outbound_ticket_expiry != null;
|
||||
},
|
||||
lxmfStampTicketExpiryMs() {
|
||||
const e = this.selectedPeerLxmfStampInfo?.outbound_ticket_expiry;
|
||||
if (e == null) {
|
||||
return null;
|
||||
}
|
||||
const n = Number(e);
|
||||
if (!Number.isFinite(n)) {
|
||||
return null;
|
||||
}
|
||||
return n * 1000;
|
||||
},
|
||||
lxmfStampTicketValid() {
|
||||
const ms = this.lxmfStampTicketExpiryMs;
|
||||
if (ms == null) {
|
||||
return false;
|
||||
}
|
||||
return ms > Date.now();
|
||||
},
|
||||
lxmfStampTicketExpiresRelative() {
|
||||
const ms = this.lxmfStampTicketExpiryMs;
|
||||
if (ms == null) {
|
||||
return "";
|
||||
}
|
||||
return dayjs(ms).fromNow();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -3130,9 +3130,7 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
// do nothing if response is for a previous request
|
||||
if (seq !== this.lxmfMessagesRequestSequence) {
|
||||
console.log("ignoring response for previous lxmf messages request");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4153,12 +4151,35 @@ export default {
|
||||
lxmfImageUrl(hash) {
|
||||
return `/api/v1/lxmf-messages/attachment/${hash}/image`;
|
||||
},
|
||||
lxmfDataUrlFromOutboundJobImage(img) {
|
||||
if (!img?.image_bytes || typeof img.image_bytes !== "string") {
|
||||
return null;
|
||||
}
|
||||
const raw = (img.image_type || "png").toLowerCase().replace(/^image\//, "");
|
||||
let mime = "image/png";
|
||||
if (raw === "jpg" || raw === "jpeg") {
|
||||
mime = "image/jpeg";
|
||||
} else if (raw === "png" || raw === "gif" || raw === "webp" || raw === "bmp") {
|
||||
mime = `image/${raw}`;
|
||||
} else if (raw === "webm") {
|
||||
mime = "video/webm";
|
||||
} else if (raw === "svg" || raw === "svg+xml") {
|
||||
mime = "image/svg+xml";
|
||||
} else {
|
||||
mime = `image/${raw}`;
|
||||
}
|
||||
return `data:${mime};base64,${img.image_bytes}`;
|
||||
},
|
||||
pendingOutboundImageSrc(chatItem) {
|
||||
const prev = chatItem.lxmf_message?.fields?.image?._preview_url;
|
||||
if (prev) {
|
||||
return prev;
|
||||
}
|
||||
return this.lxmfImageUrl(chatItem.lxmf_message.hash);
|
||||
const h = chatItem.lxmf_message?.hash;
|
||||
if (typeof h === "string" && h.startsWith("pending-")) {
|
||||
return "";
|
||||
}
|
||||
return this.lxmfImageUrl(h);
|
||||
},
|
||||
removePendingOutboundPlaceholder(hash) {
|
||||
if (!hash) {
|
||||
@@ -4836,10 +4857,12 @@ export default {
|
||||
job.pendingHash = pendingHash;
|
||||
const pendingFields = {};
|
||||
if (job.images.length > 0) {
|
||||
const previewUrl =
|
||||
job.imagePreviewUrls[0] || this.lxmfDataUrlFromOutboundJobImage(job.images[0]);
|
||||
pendingFields.image = {
|
||||
image_type: job.images[0].image_type,
|
||||
image_size: job.images[0].image_size,
|
||||
_preview_url: job.imagePreviewUrls[0],
|
||||
_preview_url: previewUrl,
|
||||
};
|
||||
}
|
||||
this.chatItems.push({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* One-line preview text for the conversation list / sidebar (LXMF latest row).
|
||||
* Handles plain content, Columba reactions, location/telemetry fields, and
|
||||
* Sideband location-request commands. Pairs with the Python helper
|
||||
* Handles plain content, Columba reactions, location/telemetry fields,
|
||||
* Sideband location-request commands, and media-only payloads (image, audio,
|
||||
* file attachments). Pairs with the Python helper
|
||||
* lxmf_sidebar_preview_for_conversation_latest_row.
|
||||
*/
|
||||
|
||||
@@ -102,5 +103,41 @@ export function lxmfConversationListPreview(msg, { myLxmfAddressHash, peerDispla
|
||||
return msg?.is_incoming ? `${name} requested your location` : `${name} sent a location request`;
|
||||
}
|
||||
|
||||
const imageField = fields?.image;
|
||||
if (imageField && typeof imageField === "object" && Object.keys(imageField).length > 0) {
|
||||
const fromSelf = isOutboundFromSelf(msg, myLxmfAddressHash);
|
||||
if (typeof t === "function") {
|
||||
return fromSelf ? t("messages.conversation_image_you") : t("messages.conversation_image_other", { name });
|
||||
}
|
||||
return fromSelf ? "You sent an image" : `${name} sent an image`;
|
||||
}
|
||||
|
||||
const audioField = fields?.audio;
|
||||
if (audioField && typeof audioField === "object" && Object.keys(audioField).length > 0) {
|
||||
const fromSelf = isOutboundFromSelf(msg, myLxmfAddressHash);
|
||||
if (typeof t === "function") {
|
||||
return fromSelf ? t("messages.conversation_voice_you") : t("messages.conversation_voice_other", { name });
|
||||
}
|
||||
return fromSelf ? "You sent a voice note" : `${name} sent a voice note`;
|
||||
}
|
||||
|
||||
const files = fields?.file_attachments;
|
||||
if (Array.isArray(files) && files.length > 0) {
|
||||
const fromSelf = isOutboundFromSelf(msg, myLxmfAddressHash);
|
||||
const n = files.length;
|
||||
if (typeof t === "function") {
|
||||
if (n === 1) {
|
||||
return fromSelf ? t("messages.conversation_file_you") : t("messages.conversation_file_other", { name });
|
||||
}
|
||||
return fromSelf
|
||||
? t("messages.conversation_files_you", { count: n })
|
||||
: t("messages.conversation_files_other", { name, count: n });
|
||||
}
|
||||
if (n === 1) {
|
||||
return fromSelf ? "You sent a file" : `${name} sent a file`;
|
||||
}
|
||||
return fromSelf ? `You sent ${n} files` : `${name} sent ${n} files`;
|
||||
}
|
||||
|
||||
return raw ?? "";
|
||||
}
|
||||
|
||||
@@ -1175,6 +1175,8 @@
|
||||
"hops_away": "{count} Hops entfernt",
|
||||
"snr": "SNR {snr}",
|
||||
"stamp_cost": "Stempelkosten {cost}",
|
||||
"stamp_ticket_valid": "Ausgehendes Stempel-Ticket aktiv (sofortiger Versand). Läuft ab {expires}.",
|
||||
"stamp_ticket_expired": "Stempel-Ticket abgelaufen; die nächste Nachricht kann einen Proof-of-Work erfordern.",
|
||||
"pop_out_chat": "Chat auslagern",
|
||||
"custom_display_name": "Benutzerdefinierter Anzeigename",
|
||||
"stranger_banner_text": "Dieser Peer ist nicht in Ihren Kontakten. Anhänge von Fremden werden blockiert.",
|
||||
@@ -1379,6 +1381,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} hat einen Telemetrie-Datenstrom gesendet",
|
||||
"conversation_location_request_in_preview": "{name} hat den Standort angefordert",
|
||||
"conversation_location_request_out_preview": "{name} hat eine Standortanfrage gesendet",
|
||||
"conversation_image_you": "Du hast ein Bild gesendet",
|
||||
"conversation_image_other": "{name} hat ein Bild gesendet",
|
||||
"conversation_voice_you": "Du hast eine Sprachnachricht gesendet",
|
||||
"conversation_voice_other": "{name} hat eine Sprachnachricht gesendet",
|
||||
"conversation_file_you": "Du hast eine Datei gesendet",
|
||||
"conversation_file_other": "{name} hat eine Datei gesendet",
|
||||
"conversation_files_you": "Du hast {count} Dateien gesendet",
|
||||
"conversation_files_other": "{name} hat {count} Dateien gesendet",
|
||||
"message_not_found_in_cache": "Nachricht nicht im Cache gefunden"
|
||||
},
|
||||
"nomadnet": {
|
||||
|
||||
@@ -1123,6 +1123,8 @@
|
||||
"hops_away": "{count} hops away",
|
||||
"snr": "SNR {snr}",
|
||||
"stamp_cost": "Stamp Cost {cost}",
|
||||
"stamp_ticket_valid": "Outbound stamp ticket active (instant send). Expires {expires}.",
|
||||
"stamp_ticket_expired": "Stamp ticket expired; the next message may require proof-of-work.",
|
||||
"pop_out_chat": "Pop out chat",
|
||||
"more_actions": "More actions",
|
||||
"retry_failed": "Retry failed messages",
|
||||
@@ -1327,6 +1329,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} sent a telemetry stream",
|
||||
"conversation_location_request_in_preview": "{name} requested your location",
|
||||
"conversation_location_request_out_preview": "{name} sent a location request",
|
||||
"conversation_image_you": "You sent an image",
|
||||
"conversation_image_other": "{name} sent an image",
|
||||
"conversation_voice_you": "You sent a voice note",
|
||||
"conversation_voice_other": "{name} sent a voice note",
|
||||
"conversation_file_you": "You sent a file",
|
||||
"conversation_file_other": "{name} sent a file",
|
||||
"conversation_files_you": "You sent {count} files",
|
||||
"conversation_files_other": "{name} sent {count} files",
|
||||
"message_not_found_in_cache": "Message not found in cache"
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1123,6 +1123,8 @@
|
||||
"hops_away": "{count}salta lejos",
|
||||
"snr": "SNR{snr}",
|
||||
"stamp_cost": "Costo de sello{cost}",
|
||||
"stamp_ticket_valid": "Boleto de sello saliente activo (envío instantáneo). Caduca {expires}.",
|
||||
"stamp_ticket_expired": "Boleto de sello caducado; el próximo mensaje puede requerir prueba de trabajo.",
|
||||
"pop_out_chat": "Apágalo.",
|
||||
"more_actions": "Más acciones",
|
||||
"retry_failed": "Retry falló mensajes",
|
||||
@@ -1327,6 +1329,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} envió un flujo de telemetría",
|
||||
"conversation_location_request_in_preview": "{name} solicitó tu ubicación",
|
||||
"conversation_location_request_out_preview": "{name} envió una solicitud de ubicación",
|
||||
"conversation_image_you": "Enviaste una imagen",
|
||||
"conversation_image_other": "{name} envió una imagen",
|
||||
"conversation_voice_you": "Enviaste una nota de voz",
|
||||
"conversation_voice_other": "{name} envió una nota de voz",
|
||||
"conversation_file_you": "Enviaste un archivo",
|
||||
"conversation_file_other": "{name} envió un archivo",
|
||||
"conversation_files_you": "Enviaste {count} archivos",
|
||||
"conversation_files_other": "{name} envió {count} archivos",
|
||||
"message_not_found_in_cache": "Mensaje no encontrado en caché"
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1123,6 +1123,8 @@
|
||||
"hops_away": "{count}houblon loin",
|
||||
"snr": "SNR{snr}",
|
||||
"stamp_cost": "Coût du timbre{cost}",
|
||||
"stamp_ticket_valid": "Ticket de timbre sortant actif (envoi instantané). Expire {expires}.",
|
||||
"stamp_ticket_expired": "Ticket de timbre expiré ; le prochain message peut nécessiter une preuve de travail.",
|
||||
"pop_out_chat": "Clavardage",
|
||||
"more_actions": "Autres actions",
|
||||
"retry_failed": "Réessayer les messages échoués",
|
||||
@@ -1327,6 +1329,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} a envoyé un flux de télémétrie",
|
||||
"conversation_location_request_in_preview": "{name} a demandé votre position",
|
||||
"conversation_location_request_out_preview": "{name} a envoyé une demande de position",
|
||||
"conversation_image_you": "Vous avez envoyé une image",
|
||||
"conversation_image_other": "{name} a envoyé une image",
|
||||
"conversation_voice_you": "Vous avez envoyé une note vocale",
|
||||
"conversation_voice_other": "{name} a envoyé une note vocale",
|
||||
"conversation_file_you": "Vous avez envoyé un fichier",
|
||||
"conversation_file_other": "{name} a envoyé un fichier",
|
||||
"conversation_files_you": "Vous avez envoyé {count} fichiers",
|
||||
"conversation_files_other": "{name} a envoyé {count} fichiers",
|
||||
"message_not_found_in_cache": "Message non trouvé dans cache"
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1175,6 +1175,8 @@
|
||||
"hops_away": "{count} salti di distanza",
|
||||
"snr": "SNR {snr}",
|
||||
"stamp_cost": "Costo Francobollo {cost}",
|
||||
"stamp_ticket_valid": "Biglietto di francobollo in uscita attivo (invio istantaneo). Scade {expires}.",
|
||||
"stamp_ticket_expired": "Biglietto scaduto; il prossimo messaggio potrebbe richiedere proof-of-work.",
|
||||
"pop_out_chat": "Estrai chat",
|
||||
"custom_display_name": "Nome Visualizzato Personalizzato",
|
||||
"stranger_banner_text": "Questo peer non è nei tuoi contatti. Gli allegati da sconosciuti sono bloccati.",
|
||||
@@ -1379,6 +1381,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} ha inviato un flusso di telemetria",
|
||||
"conversation_location_request_in_preview": "{name} ha richiesto la tua posizione",
|
||||
"conversation_location_request_out_preview": "{name} ha inviato una richiesta di posizione",
|
||||
"conversation_image_you": "Hai inviato un'immagine",
|
||||
"conversation_image_other": "{name} ha inviato un'immagine",
|
||||
"conversation_voice_you": "Hai inviato una nota vocale",
|
||||
"conversation_voice_other": "{name} ha inviato una nota vocale",
|
||||
"conversation_file_you": "Hai inviato un file",
|
||||
"conversation_file_other": "{name} ha inviato un file",
|
||||
"conversation_files_you": "Hai inviato {count} file",
|
||||
"conversation_files_other": "{name} ha inviato {count} file",
|
||||
"message_not_found_in_cache": "Messaggio non trovato nella cache"
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1123,6 +1123,8 @@
|
||||
"hops_away": "{count}hop weg",
|
||||
"snr": "SNR{snr}",
|
||||
"stamp_cost": "Postzegelkosten{cost}",
|
||||
"stamp_ticket_valid": "Uitgaand postzegelticket actief (direct verzenden). Verloopt {expires}.",
|
||||
"stamp_ticket_expired": "Postzegelticket verlopen; het volgende bericht kan proof-of-work vereisen.",
|
||||
"pop_out_chat": "Pop out chat",
|
||||
"more_actions": "Meer acties",
|
||||
"retry_failed": "Opnieuw proberen van mislukte berichten",
|
||||
@@ -1327,6 +1329,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} stuurde een telemetriestroom",
|
||||
"conversation_location_request_in_preview": "{name} vroeg om jouw locatie",
|
||||
"conversation_location_request_out_preview": "{name} stuurde een locatieverzoek",
|
||||
"conversation_image_you": "Je hebt een afbeelding gestuurd",
|
||||
"conversation_image_other": "{name} heeft een afbeelding gestuurd",
|
||||
"conversation_voice_you": "Je hebt een spraakbericht gestuurd",
|
||||
"conversation_voice_other": "{name} heeft een spraakbericht gestuurd",
|
||||
"conversation_file_you": "Je hebt een bestand gestuurd",
|
||||
"conversation_file_other": "{name} heeft een bestand gestuurd",
|
||||
"conversation_files_you": "Je hebt {count} bestanden gestuurd",
|
||||
"conversation_files_other": "{name} heeft {count} bestanden gestuurd",
|
||||
"message_not_found_in_cache": "Bericht niet gevonden in cache"
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1175,6 +1175,8 @@
|
||||
"hops_away": "в {count} прыжках",
|
||||
"snr": "SNR {snr}",
|
||||
"stamp_cost": "Стоимость штампа {cost}",
|
||||
"stamp_ticket_valid": "Активный исходящий билет штампа (мгновенная отправка). Истекает {expires}.",
|
||||
"stamp_ticket_expired": "Билет штампа истёк; следующее сообщение может потребовать proof-of-work.",
|
||||
"pop_out_chat": "Открыть в отдельном окне",
|
||||
"custom_display_name": "Свое имя для контакта",
|
||||
"stranger_banner_text": "Этот узел не в ваших контактах. Вложения от незнакомцев заблокированы.",
|
||||
@@ -1379,6 +1381,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} отправил(а) поток телеметрии",
|
||||
"conversation_location_request_in_preview": "{name} запросил(а) ваше местоположение",
|
||||
"conversation_location_request_out_preview": "{name} отправил(а) запрос местоположения",
|
||||
"conversation_image_you": "Вы отправили изображение",
|
||||
"conversation_image_other": "{name} отправил(а) изображение",
|
||||
"conversation_voice_you": "Вы отправили голосовое сообщение",
|
||||
"conversation_voice_other": "{name} отправил(а) голосовое сообщение",
|
||||
"conversation_file_you": "Вы отправили файл",
|
||||
"conversation_file_other": "{name} отправил(а) файл",
|
||||
"conversation_files_you": "Вы отправили файлов: {count}",
|
||||
"conversation_files_other": "{name} отправил(а) файлов: {count}",
|
||||
"message_not_found_in_cache": "Сообщение не найдено в кэше"
|
||||
},
|
||||
"nomadnet": {
|
||||
|
||||
@@ -1123,6 +1123,8 @@
|
||||
"hops_away": "{count} 跳远",
|
||||
"snr": "SNR {snr} dB",
|
||||
"stamp_cost": "邮戳成本 {cost}",
|
||||
"stamp_ticket_valid": "出站邮戳票据有效(即时发送)。将于 {expires} 过期。",
|
||||
"stamp_ticket_expired": "邮戳票据已过期;下一条消息可能需要工作量证明。",
|
||||
"pop_out_chat": "弹出聊天",
|
||||
"more_actions": "更多操作",
|
||||
"retry_failed": "重试失败的消息",
|
||||
@@ -1327,6 +1329,14 @@
|
||||
"conversation_telemetry_stream_preview": "{name} 发送了遥测流",
|
||||
"conversation_location_request_in_preview": "{name} 请求你的位置",
|
||||
"conversation_location_request_out_preview": "{name} 发送了位置请求",
|
||||
"conversation_image_you": "你发送了一张图片",
|
||||
"conversation_image_other": "{name} 发送了一张图片",
|
||||
"conversation_voice_you": "你发送了一条语音",
|
||||
"conversation_voice_other": "{name} 发送了一条语音",
|
||||
"conversation_file_you": "你发送了一个文件",
|
||||
"conversation_file_other": "{name} 发送了一个文件",
|
||||
"conversation_files_you": "你发送了 {count} 个文件",
|
||||
"conversation_files_other": "{name} 发送了 {count} 个文件",
|
||||
"message_not_found_in_cache": "缓存中未找到消息"
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -416,3 +416,71 @@ def test_sidebar_preview_telemetry_stream():
|
||||
peer_display_name="Jordan",
|
||||
)
|
||||
assert out == "Jordan sent a telemetry stream"
|
||||
|
||||
|
||||
def test_sidebar_preview_image_incoming():
|
||||
row = {
|
||||
"content": "",
|
||||
"fields": json.dumps({"image": {"image_type": "png", "image_size": 12}}),
|
||||
"is_incoming": 1,
|
||||
"source_hash": "b" * 32,
|
||||
}
|
||||
out = lxmf_sidebar_preview_for_conversation_latest_row(
|
||||
row,
|
||||
local_hash="a" * 32,
|
||||
peer_display_name="Quinn",
|
||||
)
|
||||
assert out == "Quinn sent an image"
|
||||
|
||||
|
||||
def test_sidebar_preview_image_outbound_you():
|
||||
me = "c" * 32
|
||||
row = {
|
||||
"content": "",
|
||||
"fields": json.dumps({"image": {"image_type": "jpeg", "image_size": 99}}),
|
||||
"is_incoming": 0,
|
||||
"source_hash": me,
|
||||
}
|
||||
out = lxmf_sidebar_preview_for_conversation_latest_row(
|
||||
row,
|
||||
local_hash=me,
|
||||
peer_display_name="Pat",
|
||||
)
|
||||
assert out == "You sent an image"
|
||||
|
||||
|
||||
def test_sidebar_preview_audio_voice_note():
|
||||
row = {
|
||||
"content": "",
|
||||
"fields": json.dumps({"audio": {"audio_mode": "opus", "audio_size": 500}}),
|
||||
"is_incoming": 1,
|
||||
"source_hash": "b" * 32,
|
||||
}
|
||||
out = lxmf_sidebar_preview_for_conversation_latest_row(
|
||||
row,
|
||||
local_hash="a" * 32,
|
||||
peer_display_name="Morgan",
|
||||
)
|
||||
assert out == "Morgan sent a voice note"
|
||||
|
||||
|
||||
def test_sidebar_preview_file_attachments_plural():
|
||||
row = {
|
||||
"content": "",
|
||||
"fields": json.dumps(
|
||||
{
|
||||
"file_attachments": [
|
||||
{"file_name": "a.txt", "file_size": 1},
|
||||
{"file_name": "b.txt", "file_size": 2},
|
||||
],
|
||||
},
|
||||
),
|
||||
"is_incoming": 0,
|
||||
"source_hash": "d" * 32,
|
||||
}
|
||||
out = lxmf_sidebar_preview_for_conversation_latest_row(
|
||||
row,
|
||||
local_hash="d" * 32,
|
||||
peer_display_name="Casey",
|
||||
)
|
||||
assert out == "You sent 2 files"
|
||||
|
||||
@@ -653,6 +653,10 @@ class TestNotificationsGetUserFacingFilter:
|
||||
body = await self._get(bell_app, unread="true", limit=10)
|
||||
assert body["unread_count"] == 1
|
||||
assert len(body["notifications"]) == 1
|
||||
peer_label = f"peer-{PEER_HASH[:6]}"
|
||||
assert body["notifications"][0]["latest_message_preview"] == (
|
||||
f"{peer_label} sent an image"
|
||||
)
|
||||
|
||||
async def test_badge_count_matches_dropdown_items(self, bell_app):
|
||||
# Mix of user-facing and silent messages across multiple peers must
|
||||
|
||||
@@ -96,4 +96,61 @@ describe("lxmfConversationListPreview", () => {
|
||||
);
|
||||
expect(s).toBe("Riley requested your location");
|
||||
});
|
||||
|
||||
it("shows image preview when body is empty", () => {
|
||||
const s = lxmfConversationListPreview(
|
||||
{
|
||||
content: "",
|
||||
is_incoming: true,
|
||||
source_hash: peer,
|
||||
fields: { image: { image_type: "png", image_size: 10 } },
|
||||
},
|
||||
{ myLxmfAddressHash: me, peerDisplayName: "Jo" }
|
||||
);
|
||||
expect(s).toBe("Jo sent an image");
|
||||
});
|
||||
|
||||
it("shows outbound image preview as You", () => {
|
||||
const s = lxmfConversationListPreview(
|
||||
{
|
||||
content: "",
|
||||
is_incoming: false,
|
||||
source_hash: me,
|
||||
fields: { image: { image_type: "webp", image_size: 20 } },
|
||||
},
|
||||
{ myLxmfAddressHash: me, peerDisplayName: "Jo" }
|
||||
);
|
||||
expect(s).toBe("You sent an image");
|
||||
});
|
||||
|
||||
it("shows voice note preview for audio field", () => {
|
||||
const s = lxmfConversationListPreview(
|
||||
{
|
||||
content: "",
|
||||
is_incoming: true,
|
||||
source_hash: peer,
|
||||
fields: { audio: { audio_mode: "opus", audio_size: 100 } },
|
||||
},
|
||||
{ myLxmfAddressHash: me, peerDisplayName: "Max" }
|
||||
);
|
||||
expect(s).toBe("Max sent a voice note");
|
||||
});
|
||||
|
||||
it("shows multiple file attachment preview", () => {
|
||||
const s = lxmfConversationListPreview(
|
||||
{
|
||||
content: "",
|
||||
is_incoming: false,
|
||||
source_hash: me,
|
||||
fields: {
|
||||
file_attachments: [
|
||||
{ file_name: "a.bin", file_size: 1 },
|
||||
{ file_name: "b.bin", file_size: 2 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ myLxmfAddressHash: me, peerDisplayName: "Jo" }
|
||||
);
|
||||
expect(s).toBe("You sent 2 files");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user