Merge branch 'dev'
This commit is contained in:
commit
0068dd9dff
43 changed files with 1013 additions and 177 deletions
|
|
@ -1,5 +1,5 @@
|
|||
build:
|
||||
image: node:20
|
||||
image: node:20.8.1
|
||||
stage: build
|
||||
before_script:
|
||||
# - ./update_version.sh
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
var periodicSyncNewMsgReminderText;
|
||||
|
||||
// Notification click event listener
|
||||
self.addEventListener("notificationclick", (e) => {
|
||||
e.notification.close();
|
||||
|
|
@ -19,6 +21,10 @@ self.addEventListener("notificationclick", (e) => {
|
|||
);
|
||||
});
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
periodicSyncNewMsgReminderText = event.data;
|
||||
});
|
||||
|
||||
async function checkNewMessages() {
|
||||
const cachedCredentials = await caches.open('cachedCredentials');
|
||||
// Todo...
|
||||
|
|
@ -28,7 +34,8 @@ async function checkNewMessages() {
|
|||
// see browser compatibility: https://developer.mozilla.org/en-US/docs/Web/API/Web_Periodic_Background_Synchronization_API#browser_compatibility
|
||||
self.addEventListener('periodicsync', (event) => {
|
||||
if (event.tag === 'check-new-messages') {
|
||||
self.registration.showNotification("Notification via periodicSync");
|
||||
let notificationTitle = periodicSyncNewMsgReminderText || "You may have new messages";
|
||||
self.registration.showNotification(notificationTitle);
|
||||
|
||||
event.waitUntil(checkNewMessages());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export default {
|
|||
this.setDefaultLanguage();
|
||||
},
|
||||
mounted() {
|
||||
registerServiceWorker();
|
||||
registerServiceWorker(this.$t('notification.periodicSync_new_msg_reminder'));
|
||||
/**
|
||||
if (
|
||||
window.location.protocol == "http" &&
|
||||
|
|
|
|||
|
|
@ -18,4 +18,6 @@ $voice-recording-color: red;
|
|||
$voice-recorded-color: #3ae17d;
|
||||
$poll-hilite-color: #6360f0;
|
||||
$poll-hilite-color-bg: #d6d5fc;
|
||||
$alert-bg-color: #FF3300;
|
||||
$alert-bg-color: #FF3300;
|
||||
|
||||
$min-touch-target: 48px;
|
||||
|
|
@ -286,7 +286,7 @@ body {
|
|||
.input-area-button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 48px;
|
||||
min-width: $min-touch-target;
|
||||
|
||||
&.input-more-icon {
|
||||
svg {
|
||||
|
|
@ -430,6 +430,10 @@ body {
|
|||
display: inline-block;
|
||||
position: relative;
|
||||
max-width: 70%;
|
||||
|
||||
@media #{map-get($display-breakpoints, 'sm-and-down')} {
|
||||
min-height: $min-touch-target;
|
||||
}
|
||||
}
|
||||
&.from-admin .bubble {
|
||||
background-color: rgba($admin-bg,0.8);
|
||||
|
|
@ -520,6 +524,10 @@ body {
|
|||
display: inline-block;
|
||||
position: relative;
|
||||
max-width: 70%;
|
||||
|
||||
@media #{map-get($display-breakpoints, 'sm-and-down')} {
|
||||
min-height: $min-touch-target;
|
||||
}
|
||||
}
|
||||
.audio-bubble {
|
||||
background-color: rgba(#e5e5e5,0.8);
|
||||
|
|
@ -1409,7 +1417,7 @@ body {
|
|||
bottom: 68px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 48px;
|
||||
height: $min-touch-target;
|
||||
background: rgba(0, 0, 0, 0.69);
|
||||
border: 1px solid #000000;
|
||||
border-radius: 5px;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
border-radius: 4px;
|
||||
padding: 15px 14px;
|
||||
margin: 0px;
|
||||
height: 48px;
|
||||
height: $min-touch-target;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -225,7 +225,7 @@
|
|||
|
||||
.add-answer-button {
|
||||
border-radius: 4px;
|
||||
height: 48px;
|
||||
height: $min-touch-target;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid #242424;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
$large-button-height: 48px;
|
||||
$large-button-height: $min-touch-target;
|
||||
$small-button-height: 36px;
|
||||
|
||||
.file-drop-root {
|
||||
|
|
@ -295,11 +295,11 @@ $small-button-height: 36px;
|
|||
position: relative;
|
||||
padding: 8px;
|
||||
.v-image {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: $min-touch-target;
|
||||
height: $min-touch-target;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
flex: 0 0 48px;
|
||||
flex: 0 0 $min-touch-target;
|
||||
margin-right: 8px;
|
||||
}
|
||||
margin-bottom: 8px;
|
||||
|
|
|
|||
BIN
src/assets/heart.png
Normal file
BIN
src/assets/heart.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -19,7 +19,11 @@
|
|||
"loading": "{appName} wird geladen",
|
||||
"done": "Fertig",
|
||||
"user_kick_and_ban": "Hinauswerfen",
|
||||
"direct_chat": "Privater Chat"
|
||||
"direct_chat": "Privater Chat",
|
||||
"delete_now": "Jetzt löschen",
|
||||
"user_make_admin": "Admin erstellen",
|
||||
"user_revoke_moderator": "Moderator widerrufen",
|
||||
"user_make_moderator": "Moderator erstellen"
|
||||
},
|
||||
"message": {
|
||||
"you": "Du",
|
||||
|
|
@ -68,7 +72,18 @@
|
|||
"someone": "Jemand",
|
||||
"file": "Datei",
|
||||
"files": "Dateien",
|
||||
"download_all": "Alle herunterladen"
|
||||
"download_all": "Alle herunterladen",
|
||||
"outgoing_message_deleted_text": "Du hast diese Nachricht gelöscht.",
|
||||
"upload_file_too_large": "Datei ist zu groß zum Hochladen!",
|
||||
"user_was_kicked": "{user} wurde aus dem Chat geworfen.",
|
||||
"user_was_kicked_by_you": "Du hast {user} aus dem Chat geworfen.",
|
||||
"user_was_kicked_you": "Du wurdest aus dem Chat geworfen.",
|
||||
"incoming_message_deleted_text": "Diese Nachricht wurde gelöscht.",
|
||||
"sent_media": "{count} Medienelemente gesendet.",
|
||||
"user_was_banned_by_you": "Du hast {user} aus dem Chat geworfen und gesperrt.",
|
||||
"user_was_banned_you": "Du wurdest aus dem Chat rausgeschmissen und gesperrt.",
|
||||
"user_was_banned": "{user} wurde aus dem Chat geworfen und gesperrt.",
|
||||
"preparing_to_upload": "Vorbereitungen zum Hochladen..."
|
||||
},
|
||||
"room": {
|
||||
"leave": "Verlassen",
|
||||
|
|
@ -89,7 +104,9 @@
|
|||
"room_history_is": "Der Raumverlauf ist {type}.",
|
||||
"room_history_joined": "Die Teilnehmer können nur die Nachrichten sehen, die nach ihrem Beitritt gesendet wurden.",
|
||||
"got_it": "Verstanden",
|
||||
"encrypted": "Die Nachrichten werden Ende-zu-Ende verschlüsselt."
|
||||
"encrypted": "Die Nachrichten werden Ende-zu-Ende verschlüsselt.",
|
||||
"change": "Ändern",
|
||||
"direct_private_chat": "Direktnachricht"
|
||||
},
|
||||
"new_room": {
|
||||
"join_permissions": "Beitrittsberechtigungen",
|
||||
|
|
@ -130,7 +147,10 @@
|
|||
"invalid_message": "Benutzername oder Passwort falsch",
|
||||
"send_verification": "Sende Bestätigungs-E-Mail",
|
||||
"resend_verification": "Bestätigungsmail erneut senden",
|
||||
"accept_terms": "Akzeptiere."
|
||||
"accept_terms": "Akzeptiere.",
|
||||
"token_not_valid": "Ungültiges Token",
|
||||
"email_not_valid": "E-Mail-Adresse ist nicht gültig",
|
||||
"send_token": "Token senden"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Mein Profil",
|
||||
|
|
@ -146,7 +166,8 @@
|
|||
"set_language": "Stelle deine Sprache ein",
|
||||
"language_description": "Convene ist in vielen Sprachen verfügbar.",
|
||||
"tell_us": "Teile uns mit.",
|
||||
"notification_label": "Benachrichtigung"
|
||||
"notification_label": "Benachrichtigung",
|
||||
"display_name_required": "Anzeigename ist erforderlich"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"you_are": "Du bist",
|
||||
|
|
@ -154,7 +175,7 @@
|
|||
"edit_profile": "Profil bearbeiten",
|
||||
"logout": "Abmelden",
|
||||
"powered_by": "Dieser Raum wird von {product} betrieben. Erfahre mehr unter {productLink} oder erstelle einen weiteren Raum!",
|
||||
"new_room": "+ neuer Raum",
|
||||
"new_room": "Neuer Raum",
|
||||
"identity_temporary": "{displayName}",
|
||||
"want_more": "Willst du mehr?"
|
||||
},
|
||||
|
|
@ -168,7 +189,9 @@
|
|||
"title": "Willkommen in {roomName}",
|
||||
"enter_room": "Raum betreten",
|
||||
"remember_me": "Mich merken",
|
||||
"choose_name": "Wähle einen zu nutzenden Namen"
|
||||
"choose_name": "Wähle einen zu nutzenden Namen",
|
||||
"join_user": "Chat starten",
|
||||
"enter_room_user": "Chat starten"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Freunde hinzufügen",
|
||||
|
|
@ -228,7 +251,19 @@
|
|||
"message_retention_1_week": "1 Woche",
|
||||
"message_retention_1_day": "1 Tag",
|
||||
"message_retention_8_hours": "8 Stunden",
|
||||
"message_retention_1_hour": "1 Stunde"
|
||||
"message_retention_1_hour": "1 Stunde",
|
||||
"read_only_room": "Schreibgeschützt",
|
||||
"moderation": "Moderation",
|
||||
"experimental_features": "Experimentelle Funktionen",
|
||||
"file_mode": "Dateimodus",
|
||||
"message_retention_4_week": "4 Wochen",
|
||||
"make_public": "Öffentlich machen",
|
||||
"direct_link": "Mein Direktlink",
|
||||
"voice_mode": "Sprachmodus",
|
||||
"download_chat": "Chat herunterladen",
|
||||
"message_retention": "Nachrichtenverlauf",
|
||||
"message_retention_none": "Aus",
|
||||
"room_type": "Raumart"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Dieser Raum",
|
||||
|
|
@ -278,7 +313,9 @@
|
|||
"poll_status_open_not_voted": "Umfrage ist offen – stimme ab, um die Ergebnisse zu sehen",
|
||||
"close_poll": "Umfrage schließen",
|
||||
"answer_label_n": "Antwort",
|
||||
"view_results": "Ergebnisse ansehen"
|
||||
"view_results": "Ergebnisse ansehen",
|
||||
"answer_label_1": "Antwort*",
|
||||
"please_complete": "Bitte vervollständigen"
|
||||
},
|
||||
"export": {
|
||||
"fetched_n_of_total_events": "{count} von {total} Ereignissen geladen",
|
||||
|
|
@ -294,10 +331,15 @@
|
|||
"add_reaction": "Reaktion hinzufügen",
|
||||
"show_less": "Weniger anzeigen",
|
||||
"time": {
|
||||
"recently": "im Moment"
|
||||
"recently": "im Moment",
|
||||
"hours": "vor 1 Stunde | vor {n} Stunden",
|
||||
"days": "vor 1 Tag | vor {n} Tagen",
|
||||
"minutes": "vor 1 Minute | vor {n} Minuten"
|
||||
},
|
||||
"notify": "Benachrichtigung",
|
||||
"close": "schlieen"
|
||||
"close": "schlieen",
|
||||
"click_to_remove": "Zum Entfernen klicken",
|
||||
"password_hint": "Mindestens 12 Zeichen, davon mindestens eine Ziffer, ein Großbuchstabe und ein Kleinbuchstabe"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
|
|
@ -306,7 +348,8 @@
|
|||
"flags": "Kennzeichnungen",
|
||||
"objects": "Objekte",
|
||||
"symbols": "Symbole",
|
||||
"places": "Orte"
|
||||
"places": "Orte",
|
||||
"frequently": "Häufig genutzt"
|
||||
},
|
||||
"search": "Suche ..."
|
||||
},
|
||||
|
|
@ -314,15 +357,25 @@
|
|||
"sending": "Sende",
|
||||
"sending_progress": "Senden…",
|
||||
"close": "Schließen",
|
||||
"files": "Dateien"
|
||||
"files": "Dateien",
|
||||
"files_sent": "1 Datei gesendet! | {count} Dateien gesendet!",
|
||||
"choose_files": "Dateien auswählen",
|
||||
"send_more_files": "Weitere Dateien senden"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Aktiviere"
|
||||
}
|
||||
},
|
||||
"title": "Neue Nachricht erhalten"
|
||||
},
|
||||
"getlink": {
|
||||
"next": "Nächster",
|
||||
"continue": "Fortfahren"
|
||||
"continue": "Fortfahren",
|
||||
"hello": "Hallo {user},\nhier ist dein Direktlink",
|
||||
"share_qr": "QR teilen",
|
||||
"qr_image_copied": "Bild in die Zwischenablage kopiert"
|
||||
},
|
||||
"createchannel": {
|
||||
"name_required": "Kanalname ist erforderlich"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,8 @@
|
|||
"files": "Files",
|
||||
"images": "Images",
|
||||
"send_attachements_dialog_title": "Do you want to send following attachments ?",
|
||||
"download_all": "Download all"
|
||||
"download_all": "Download all",
|
||||
"failed_to_render": "Failed to render event"
|
||||
},
|
||||
"room": {
|
||||
"invitations": "You have no invitations | You have 1 invitation | You have {count} invitations",
|
||||
|
|
@ -140,6 +141,7 @@
|
|||
"direct_private_chat": "Direct Message",
|
||||
"join_channel": "All set! Invite people to join you: {link}",
|
||||
"info_retention": "🕓 Messages sent within {time} are viewable by anyone with the link.",
|
||||
"info_retention_user": "🕓 Messages older than {time} will be deleted from the history.",
|
||||
"change": "Change"
|
||||
},
|
||||
"new_room": {
|
||||
|
|
@ -406,7 +408,8 @@
|
|||
"enable": "Enable"
|
||||
},
|
||||
"blocked_message": "Notification is blocked. Go to your device or browser settings to enable Notification",
|
||||
"not_supported": "Notification is not yet supported in Mobile"
|
||||
"not_supported": "Notification is not yet supported in Mobile",
|
||||
"periodicSync_new_msg_reminder": "You may have new messages"
|
||||
},
|
||||
"emoji": {
|
||||
"search": "Search...",
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@
|
|||
"room_type_default": "Por Defecto",
|
||||
"copy_link": "Copiar enlace",
|
||||
"direct_link": "Mi enlace directo",
|
||||
"read_only_room": "Sala de sólo lectura",
|
||||
"read_only_room": "Solo lectura",
|
||||
"voice_mode": "Por voz",
|
||||
"make_public_warning": "Advertencia: El historial completo de mensajes será visible para los nuevos participantes",
|
||||
"room_type": "Genero de la sala",
|
||||
"experimental_features": "Funciones experimentales",
|
||||
"file_mode_info": "Cambia la interfaz del chat al modo \"soltar archivos\"",
|
||||
"read_only_room_info": "Sólo los administradores y moderadores pueden escribir en la sala",
|
||||
"read_only_room_info": "Solo los administradores y moderadores pueden enviar mensajes a la sala.",
|
||||
"file_mode": "Modo del archivo",
|
||||
"direct_link_desc": "¡Ya está listo para compartir! Se abrirá una nueva sala privada cada vez que alguien abra el enlace.",
|
||||
"download_chat": "Descargar el chat",
|
||||
|
|
@ -49,7 +49,8 @@
|
|||
"shared_room_number": "Compartes {count} salas con {name}",
|
||||
"shared_room_number_more": "Compartes más de {count} y salas con {name}",
|
||||
"message_retention": "Historial de mensajes",
|
||||
"message_retention_info": "Los mensajes enviados dentro de este plazo pueden ser vistos por cualquiera que tenga el enlace."
|
||||
"message_retention_info": "Los mensajes enviados dentro de este plazo pueden ser vistos por cualquiera que tenga el enlace.",
|
||||
"moderation": "Moderado"
|
||||
},
|
||||
"purge_room": {
|
||||
"button": "Borrar",
|
||||
|
|
@ -173,7 +174,11 @@
|
|||
"room_history_joined": "La gente sólo puede ver los mensajes enviados después de unirse.",
|
||||
"no_past_messages": "¡Bienvenido! Por su seguridad, los mensajes anteriores no están disponibles.",
|
||||
"direct_info": "Hola, {you}. Estás en un chat privado con {user}.",
|
||||
"direct_private_chat": "Mensaje directo"
|
||||
"direct_private_chat": "Mensaje directo",
|
||||
"change": "Cambiar",
|
||||
"join_channel": "¡Listo! Invita a la gente a unirse a: {link}",
|
||||
"info_retention": "🕓 Los mensajes enviados dentro de {time} pueden ser vistos por cualquiera que tenga el enlace.",
|
||||
"info_retention_user": "🕓 Los mensajes anteriores a {time} serán eliminados del historial."
|
||||
},
|
||||
"room": {
|
||||
"leave": "Salir",
|
||||
|
|
@ -253,7 +258,8 @@
|
|||
"time_ago": "Hoy | Ayer | Hace {count} días",
|
||||
"upload_exceeded_file_limit": "Se ha superado el tamaño máximo para el archivo ({configFormattedUploadSize}). ",
|
||||
"someone": "Alguien",
|
||||
"sent_media": "Enviados {count} elementos multimedia."
|
||||
"sent_media": "Enviados {count} elementos multimedia.",
|
||||
"failed_to_render": "No se pudo representar el evento"
|
||||
},
|
||||
"menu": {
|
||||
"login": "Iniciar sesión",
|
||||
|
|
@ -411,7 +417,8 @@
|
|||
},
|
||||
"title": "Nuevo mensaje recibido",
|
||||
"blocked_message": "La notificación está bloqueada. Vaya a la configuración de su dispositivo o navegador para habilitar la Notificación",
|
||||
"not_supported": "La notificación aún no es compatible con dispositivos móviles"
|
||||
"not_supported": "La notificación aún no es compatible con dispositivos móviles",
|
||||
"periodicSync_new_msg_reminder": "Es posible que tengas nuevos mensajes"
|
||||
},
|
||||
"export": {
|
||||
"fetched_n_of_total_events": "{count} de {total} eventos recuperados",
|
||||
|
|
@ -422,5 +429,13 @@
|
|||
},
|
||||
"logout": {
|
||||
"confirm_text": "¿Seguro que quieres cerrar la sesión?"
|
||||
},
|
||||
"createchannel": {
|
||||
"title": "Crear un canal",
|
||||
"info": "Difunde noticias o conocimientos en cualquier formato: vídeo, podcast, texto, imágenes o PDF.",
|
||||
"channel_name": "Nombre de tu canal",
|
||||
"channel_topic": "Descríbelo",
|
||||
"name_required": "El nombre del canal es obligatorio",
|
||||
"error_channel": "Error al crear el canal"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@
|
|||
"password": "Anna salasana",
|
||||
"password_required": "Salasana vaaditaan",
|
||||
"create_room": "Rekisteröidy ja luo huone",
|
||||
"accept_terms": "Hyväksy"
|
||||
"accept_terms": "Hyväksy",
|
||||
"token_not_valid": "Virheellinen koodi"
|
||||
},
|
||||
"join": {
|
||||
"title": "Tervetuloa huoneen {roomName}",
|
||||
|
|
@ -106,7 +107,8 @@
|
|||
"reply_poll": "Kysely",
|
||||
"file": "Tiedosto",
|
||||
"someone": "Joku",
|
||||
"images": "Kuvat"
|
||||
"images": "Kuvat",
|
||||
"room_joinrule_public": "julkinen"
|
||||
},
|
||||
"room": {
|
||||
"leave": "Poistu",
|
||||
|
|
@ -118,7 +120,8 @@
|
|||
"info": "Tervetuloa! Seuraavassa on muutamia asioita, jotka sinun on hyvä tietää huoneestasi:",
|
||||
"info_permissions": "Voit muuttaa liittymisoikeuksia milloin tahansa huoneen asetuksissa.",
|
||||
"encrypted": "Viestit ovat päästä päähän salattuja.",
|
||||
"got_it": "Selvä"
|
||||
"got_it": "Selvä",
|
||||
"change": "Vaihda"
|
||||
},
|
||||
"profile": {
|
||||
"temporary_identity": "Tämä identiteetti on väliaikainen. Aseta salasana käyttääksesi sitä uudelleen",
|
||||
|
|
@ -170,7 +173,8 @@
|
|||
"message_retention_none": "Pois päältä",
|
||||
"message_retention_1_day": "1 päivä",
|
||||
"message_retention_8_hours": "8 tuntia",
|
||||
"message_retention_1_hour": "1 tunti"
|
||||
"message_retention_1_hour": "1 tunti",
|
||||
"read_only_room": "Lue Ainoastaan"
|
||||
},
|
||||
"power_level": {
|
||||
"restricted": "rajoitettu",
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@
|
|||
"join_invite": "Seules les personnes que vous invitez peuvent s’y joindre.",
|
||||
"info_permissions": "Vous pouvez modifier les « autorisations de participation » à tout moment dans les paramètres du salon.",
|
||||
"room_history_joined": "Les personnes ne peuvent voir que les messages envoyés après leur adhésion.",
|
||||
"got_it": "Compris"
|
||||
"got_it": "Compris",
|
||||
"change": "Changer"
|
||||
},
|
||||
"new_room": {
|
||||
"next": "Suivant",
|
||||
|
|
@ -220,7 +221,8 @@
|
|||
"message_retention_1_week": "1 semaine",
|
||||
"message_retention_1_day": "1 jour",
|
||||
"message_retention_8_hours": "8 heures",
|
||||
"message_retention_1_hour": "1 heure"
|
||||
"message_retention_1_hour": "1 heure",
|
||||
"read_only_room": "Lecture seule"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Ce salon",
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@
|
|||
"join_invite": "Solo le persone che inviti possono partecipare.",
|
||||
"info_permissions": "Puoi cambiare i «permessi di adesione» in qualsiasi momento nelle impostazioni della stanza.",
|
||||
"got_it": "Capito",
|
||||
"room_history_joined": "Le persone possono vedere solo i messaggi inviati dopo la loro adesione."
|
||||
"room_history_joined": "Le persone possono vedere solo i messaggi inviati dopo la loro adesione.",
|
||||
"change": "Cambia"
|
||||
},
|
||||
"new_room": {
|
||||
"next": "Successivo",
|
||||
|
|
@ -216,7 +217,8 @@
|
|||
"message_retention_none": "Off",
|
||||
"message_retention_1_day": "1 giorno",
|
||||
"message_retention_8_hours": "8 ore",
|
||||
"message_retention_1_hour": "1 ora"
|
||||
"message_retention_1_hour": "1 ora",
|
||||
"read_only_room": "Sola lettura"
|
||||
},
|
||||
"voice_recorder": {
|
||||
"failed_to_record": "Impossibile registrare l’audio",
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@
|
|||
"message_retention_1_week": "1 uke",
|
||||
"message_retention_1_day": "1 dag",
|
||||
"message_retention_8_hours": "åtte timer",
|
||||
"message_retention_1_hour": "Én time"
|
||||
"message_retention_1_hour": "Én time",
|
||||
"read_only_room": "Skrivebeskyttet"
|
||||
},
|
||||
"goodbye": {
|
||||
"view_other_rooms": "Vis andre rom",
|
||||
|
|
@ -137,7 +138,8 @@
|
|||
"room_history_is": "Romhistorikken er {type}.",
|
||||
"encrypted": "Meldinger er ende-til-ende -kryptert.",
|
||||
"got_it": "Skjønner",
|
||||
"join_invite": "Kun folk du inviterer kan ta del."
|
||||
"join_invite": "Kun folk du inviterer kan ta del.",
|
||||
"change": "Endre"
|
||||
},
|
||||
"room": {
|
||||
"room_list_rooms": "Rom",
|
||||
|
|
|
|||
|
|
@ -122,7 +122,8 @@
|
|||
"upload_exceeded_file_limit": "O tamanho máximo do arquivo ({configFormattedUploadSize}) foi excedido. ",
|
||||
"preparing_to_upload": "Preparando para enviar...",
|
||||
"someone": "Alguém",
|
||||
"sent_media": "Enviou {count} itens de mídia."
|
||||
"sent_media": "Enviou {count} itens de mídia.",
|
||||
"failed_to_render": "Houve uma falha ao renderizar o evento"
|
||||
},
|
||||
"room": {
|
||||
"members": "sem membros | 1 membro | {count} membros",
|
||||
|
|
@ -150,7 +151,11 @@
|
|||
"got_it": "Entendi",
|
||||
"no_past_messages": "Bem-vindo! Para a sua segurança, as mensagens anteriores não estão disponíveis.",
|
||||
"direct_info": "Olá {you}. Você está num chat privado com {user}.",
|
||||
"direct_private_chat": "Mensagem direta"
|
||||
"direct_private_chat": "Mensagem direta",
|
||||
"change": "Mudança",
|
||||
"join_channel": "Tudo pronto! Convide pessoas para se juntarem a você: {link}",
|
||||
"info_retention": "🕓 As mensagens enviadas dentro de {time} podem ser visualizadas por qualquer pessoa com o link.",
|
||||
"info_retention_user": "As mensagens mais antigas que {time} serão excluídas do histórico."
|
||||
},
|
||||
"new_room": {
|
||||
"new_room": "Nova sala",
|
||||
|
|
@ -295,11 +300,11 @@
|
|||
"voice_mode": "Modo de voz",
|
||||
"user_admin": "Administrador",
|
||||
"voice_mode_info": "Alterna a interface de chat para um modo de 'ouvir e gravar'",
|
||||
"read_only_room": "Sala somente leitura",
|
||||
"read_only_room": "Somente leitura",
|
||||
"user_moderator": "Moderador",
|
||||
"experimental_features": "Recursos experimentais",
|
||||
"download_chat": "Baixar o chat",
|
||||
"read_only_room_info": "Apenas administradores e moderadores podem postar na sala",
|
||||
"read_only_room_info": "Apenas os administradores e os moderadores podem postar na sala.",
|
||||
"copy_link": "Copiar o link",
|
||||
"make_public": "Tornar público",
|
||||
"room_type": "Tipo de sala",
|
||||
|
|
@ -319,7 +324,8 @@
|
|||
"message_retention_info": "As mensagens enviadas dentro desse período podem ser visualizadas por qualquer pessoa que tenha o link.",
|
||||
"message_retention_4_week": "4 semanas",
|
||||
"shared_room_number": "Você compartilha {count} quartos com {name}",
|
||||
"shared_room_number_more": "Você compartilha mais de {count} quartos com {name}"
|
||||
"shared_room_number_more": "Você compartilha mais de {count} quartos com {name}",
|
||||
"moderation": "Moderação"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Esta sala",
|
||||
|
|
@ -408,7 +414,8 @@
|
|||
"enable": "Ativar"
|
||||
},
|
||||
"blocked_message": "A notificação está bloqueada. Vá para as configurações do dispositivo ou do navegador para ativar a notificação",
|
||||
"not_supported": "A notificação ainda não é suportada no mobile"
|
||||
"not_supported": "A notificação ainda não é suportada no mobile",
|
||||
"periodicSync_new_msg_reminder": "Talvez você tenha novas mensagens"
|
||||
},
|
||||
"getlink": {
|
||||
"title": "Obter um link direto",
|
||||
|
|
@ -422,5 +429,13 @@
|
|||
"username": "Insira um nome (ex: waku)",
|
||||
"different_link": "Obter um link diferente",
|
||||
"next": "Próximo"
|
||||
},
|
||||
"createchannel": {
|
||||
"channel_topic": "Descreva-o",
|
||||
"name_required": "O nome do canal é obrigatório",
|
||||
"info": "Transmita notícias ou conhecimento em qualquer formato: vídeo, podcast, texto, imagens ou PDFs.",
|
||||
"error_channel": "Houve uma falha ao criar o canal",
|
||||
"title": "Criar um canal",
|
||||
"channel_name": "Dê um nome ao seu canal"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@
|
|||
"room_type_default": "Predefinit",
|
||||
"message_retention_none": "Dezactivat",
|
||||
"message_retention_1_day": "1 zi",
|
||||
"message_retention_1_hour": "O oră"
|
||||
"message_retention_1_hour": "O oră",
|
||||
"message_retention_8_hours": "8 ore"
|
||||
},
|
||||
"goodbye": {
|
||||
"view_other_rooms": "Vezi alte camere",
|
||||
|
|
@ -181,7 +182,8 @@
|
|||
"room_history_joined": "Oamenii pot vedea doar mesajele trimise după ce se înscriu.",
|
||||
"room_history_is": "Istoricul camerei este {type}.",
|
||||
"encrypted": "Mesajele sunt criptate de la un capăt la altul.",
|
||||
"info": "Bine ați venit! Iată câteva lucruri pe care trebuie să le știți despre camera dumneavoastră:"
|
||||
"info": "Bine ați venit! Iată câteva lucruri pe care trebuie să le știți despre camera dumneavoastră:",
|
||||
"change": "Schimbă"
|
||||
},
|
||||
"room": {
|
||||
"room_list_rooms": "Camere",
|
||||
|
|
|
|||
441
src/assets/translations/ru.json
Normal file
441
src/assets/translations/ru.json
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
{
|
||||
"login": {
|
||||
"invalid_message": "Неверное имя пользователя или пароль",
|
||||
"title": "Имя пользователя",
|
||||
"password": "Введите пароль",
|
||||
"login": "Имя пользователя",
|
||||
"resend_verification": "Отправить электронное письмо-подтверждение снова",
|
||||
"token_not_valid": "Недействительный токен",
|
||||
"accept_terms": "Принять",
|
||||
"username": "Имя пользователя (например: marta)",
|
||||
"username_required": "Имя пользователя обязательно",
|
||||
"password_required": "Требуется пароль",
|
||||
"create_room": "Регистрация и создание комнаты",
|
||||
"no_supported_flow": "Приложение не может войти на указанный сервер",
|
||||
"email_not_valid": "Адрес электронной почты недействителен",
|
||||
"or": "ИЛИ",
|
||||
"email": "Вам необходимо подтвердить свой адрес электронной почты",
|
||||
"send_verification": "Отправить письмо с подтверждением",
|
||||
"send_token": "Отправить токен",
|
||||
"terms": "Домашний сервер требует, чтобы вы ознакомились со следующими правилами и приняли их:",
|
||||
"sent_verification": "На адрес {email} отправлено письмо. Пожалуйста, используйте свой обычный почтовый клиент для проверки адреса.",
|
||||
"registration_token": "Пожалуйста, введите регистрационный токен"
|
||||
},
|
||||
"room_info": {
|
||||
"copy_link": "Копировать ссылку",
|
||||
"message_retention_1_day": "1 день",
|
||||
"message_retention_8_hours": "8 часов",
|
||||
"message_retention_1_hour": "1 час",
|
||||
"hide_all": "Скрыть",
|
||||
"leave_room": "Оставить",
|
||||
"user_admin": "Администратор",
|
||||
"user_moderator": "Модератор",
|
||||
"room_type_default": "По умолчанию",
|
||||
"members": "Элементов",
|
||||
"read_only_room": "Только чтение",
|
||||
"message_retention_none": "Выключить",
|
||||
"message_retention_2_week": "2 недели",
|
||||
"message_retention_1_week": "1 неделя",
|
||||
"join_public": "У кого-нибудь есть ссылка",
|
||||
"copy_invite_link": "Скопируйте ссылку на приглашение",
|
||||
"scan_code": "Сканировать, чтобы присоединиться к комнате",
|
||||
"message_retention": "История сообщений",
|
||||
"message_retention_info": "Сообщения, отправленные в течение этого срока, могут просматривать все, у кого есть ссылка.",
|
||||
"direct_link": "Моя прямая ссылка",
|
||||
"title": "Детали комнаты",
|
||||
"created_by": "Создано {user}",
|
||||
"permissions": "Разрешения на присоединение",
|
||||
"join_invite": "Только добавленные люди",
|
||||
"link_copied": "Ссылка скопирована!",
|
||||
"purge": "Удалить комнату",
|
||||
"user": "{user}",
|
||||
"user_you": "{user} (вы)",
|
||||
"show_all": "Показать все >",
|
||||
"export_room": "Экспорт чата",
|
||||
"moderation": "Модерация",
|
||||
"room_type": "Тип комнаты",
|
||||
"voice_mode": "Голосовой режим",
|
||||
"voice_mode_info": "Переключает интерфейс чата в режим \"слушать и записывать\"",
|
||||
"file_mode": "Файловый режим",
|
||||
"download_chat": "Скачать чат",
|
||||
"read_only_room_info": "Отправлять сообщения в комнату могут только администраторы и модераторы.",
|
||||
"message_retention_4_week": "4 недели",
|
||||
"make_public_warning": "предупреждение: Полная история сообщений будет видна новым участникам",
|
||||
"shared_room_number": "Вы делите {count} комнат с {name}",
|
||||
"file_mode_info": "Переключает интерфейс чата в режим \"сброса файлов\"",
|
||||
"make_public": "Сделать публичным",
|
||||
"direct_link_desc": "Он готов к обмену! Каждый раз, когда кто-то открывает ссылку, будет открываться новая прямая комната.",
|
||||
"version_info": "Работает под управлением Guardian Project. Версия: {version}",
|
||||
"shared_room_number_more": "Вы делите более {count} комнат с {name}",
|
||||
"experimental_features": "Экспериментальная функция"
|
||||
},
|
||||
"file_mode": {
|
||||
"sending": "Отправляем",
|
||||
"sending_progress": "Отправка...",
|
||||
"close": "Закрыть",
|
||||
"files": "Файлы",
|
||||
"files_sent": "Отправлен 1 файл! | {count} отправленных файлов!",
|
||||
"any_file_format_accepted": "Принимаются файлы любого формата",
|
||||
"send_more_files": "Отправить больше файлов",
|
||||
"secure_file_send": "безопасная отправка файлов",
|
||||
"add_a_message": "Добавить сообщение",
|
||||
"files_sent_with_note": "1 файл отправлен с примечанием! | {count} файлов, отправленных с примечанием!",
|
||||
"choose_files": "Выбрать файл"
|
||||
},
|
||||
"global": {
|
||||
"save": "Сохранить",
|
||||
"show_less": "Показать меньше",
|
||||
"show_more": "Показать больше",
|
||||
"time": {
|
||||
"recently": "только что",
|
||||
"minutes": "1 минуту назад | {n} минут назад",
|
||||
"hours": "1 час назад | {n} часов назад",
|
||||
"days": "1 день назад | {n} дней назад"
|
||||
},
|
||||
"notify": "Оповестить",
|
||||
"password_didnot_match": "Пароль не совпадает",
|
||||
"password_hint": "Минимум 12 символов, содержащих как минимум одну цифру, одну заглавную и одну строчную буквы",
|
||||
"add_reaction": "Добавить реакцию",
|
||||
"click_to_remove": "Нажмите, чтобы удалить",
|
||||
"close": "закрыть"
|
||||
},
|
||||
"message": {
|
||||
"download_all": "Скачать все",
|
||||
"someone": "Кто-то",
|
||||
"file_prefix": "Файл: ",
|
||||
"edited": "(отредактировано)",
|
||||
"reply_image": "Изображение",
|
||||
"reply_video": "Видео",
|
||||
"reply_poll": "Опрос",
|
||||
"seen_by": "Увиденное",
|
||||
"file": "Файл",
|
||||
"files": "Файлы",
|
||||
"images": "Изображения",
|
||||
"you": "Вы",
|
||||
"user_aliased_room": "{user} создал псевдоним {alias} для комнаты",
|
||||
"user_changed_display_name": "{user} изменил отображаемое имя на {displayName}",
|
||||
"user_changed_avatar": "{user} изменил аватар",
|
||||
"user_changed_room_avatar": "{user} изменил аватар комнаты",
|
||||
"user_was_invited": "{user} был приглашен в чат...",
|
||||
"user_was_kicked": "{user} был удален из чата.",
|
||||
"user_was_banned": "{user} был удален и заблокирован в чате.",
|
||||
"user_was_banned_you": "Вы были удалены из чата и заблокированы.",
|
||||
"user_joined": "{user} присоединился к чату",
|
||||
"user_left": "{user} покинул чат",
|
||||
"user_said": "{user} сказал:",
|
||||
"sent_media": "Отправлено {count} медиаэлементов.",
|
||||
"preparing_to_upload": "Подготовка к загрузке...",
|
||||
"upload_file_too_large": "Файл слишком велик для загрузки!",
|
||||
"upload_progress": "Загружено {count}",
|
||||
"upload_progress_with_total": "Загружено {count} из {total}",
|
||||
"user_changed_room_history": "{user} сделал историю номера {type}",
|
||||
"room_history_world_readable": "читаемый всеми",
|
||||
"room_history_shared": "читаемый всеми участниками в комнате",
|
||||
"user_changed_join_rules": "{user} сделал комнату {type}",
|
||||
"room_joinrule_invite": "только по приглашению",
|
||||
"user_changed_room_topic": "{user} изменил тему комнаты на {topic}",
|
||||
"unread_messages": "Непрочитанные сообщения",
|
||||
"user_is_typing": "{user} набирает",
|
||||
"users_are_typing": "{count} членов набирают",
|
||||
"room_powerlevel_change": "{user} изменил уровень мощности {changes}",
|
||||
"user_powerlevel_change_from_to": "{user} из {powerOld} в {powerNew}",
|
||||
"user_changed_guest_access_closed": "{user} запретил гостям входить в комнату",
|
||||
"user_changed_guest_access_open": "{user} разрешил гостям присоединиться к комнате",
|
||||
"reply_audio_message": "Аудиосообщение",
|
||||
"outgoing_message_deleted_text": "Вы удалили это сообщение.",
|
||||
"incoming_message_deleted_text": "Это сообщение было удалено.",
|
||||
"user_created_room": "{user} создал комнату",
|
||||
"user_encrypted_room": "{user} сделал комнату зашифрованной",
|
||||
"user_was_kicked_by_you": "Вы выгнали {user} из чата.",
|
||||
"user_was_kicked_you": "Вы были удалены из чата.",
|
||||
"user_was_banned_by_you": "Вы выгнали и забанили {user} из чата.",
|
||||
"download_progress": "{percentage}% скачанных",
|
||||
"upload_exceeded_file_limit": "Превышен максимальный размер файла ({configFormattedUploadSize}). ",
|
||||
"room_history_invited": "доступны для чтения членам клуба с того момента, когда они были приглашены",
|
||||
"room_history_joined": "доступны для чтения членам клуба с момента их присоединения",
|
||||
"room_joinrule_public": "публичный",
|
||||
"user_changed_room_name": "{user} изменил название комнаты на {name}",
|
||||
"replying_to": "Отвечая на {user}",
|
||||
"your_message": "Ваше сообщение...",
|
||||
"scale_image": "Масштаб изображения",
|
||||
"time_ago": "Сегодня | Вчера | {count} дней назад",
|
||||
"not_allowed_to_send": "Только администраторы и модераторы могут отправлять сообщения в комнату",
|
||||
"reaction_count_more": "{reactionCount} больше",
|
||||
"seen_by_count": "Видели ни один участник | Видел 1 участник | Видели {count} участников",
|
||||
"send_attachements_dialog_title": "Вы хотите отправить следующие вложения?",
|
||||
"failed_to_render": "Не удалось отрендерить событие"
|
||||
},
|
||||
"new_room": {
|
||||
"create": "Создание",
|
||||
"next": "Вперёд",
|
||||
"options": "Параметры",
|
||||
"new_room": "Новая комната",
|
||||
"name_room": "Название комнаты",
|
||||
"join_permissions_info": "Эти разрешения определяют, как люди могут присоединиться к комнате и как легко пригласить других. Они могут быть изменены в любое время.",
|
||||
"set_join_permissions": "Установка разрешений на присоединение",
|
||||
"room_topic": "Добавьте описание, если хотите",
|
||||
"join_permissions": "Разрешения на присоединение",
|
||||
"get_link": "Получить ссылку",
|
||||
"add_people": "Добавить людей",
|
||||
"link_copied": "Ссылка скопирована!",
|
||||
"colon_not_allowed": "Двоеточие запрещено",
|
||||
"public_info": "У кого-нибудь есть ссылка",
|
||||
"public_description": "Получите ссылку, чтобы поделиться",
|
||||
"invite_info": "Добавлены только люди",
|
||||
"invite_description": "Выберите из списка или выполните поиск по идентификатору счета",
|
||||
"status_creating": "Создание комнаты",
|
||||
"status_avatar_total": "Загрузка аватара: {count} из {total}",
|
||||
"status_avatar": "Загрузка аватара: {count}",
|
||||
"room_name_limit_error_msg": "Разрешено не более 50 символов"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"view_details": "Подробнее",
|
||||
"this_room": "Эта комната"
|
||||
},
|
||||
"power_level": {
|
||||
"default": "по умолчанию",
|
||||
"admin": "администратор",
|
||||
"moderator": "модератор",
|
||||
"custom": "пользовательский ({level})",
|
||||
"restricted": "Ограничено"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"places": "Места",
|
||||
"activity": "Активность",
|
||||
"flags": "Метки",
|
||||
"objects": "Объекты",
|
||||
"nature": "Природа",
|
||||
"symbols": "Символы",
|
||||
"foods": "Продукция",
|
||||
"peoples": "Люди",
|
||||
"frequently": "Часто используемое"
|
||||
},
|
||||
"search": "Найти..."
|
||||
},
|
||||
"menu": {
|
||||
"direct_chat": "Личный чат",
|
||||
"reply": "Ответная",
|
||||
"edit": "Редактировать",
|
||||
"delete": "Удалить",
|
||||
"download": "Загрузить",
|
||||
"ok": "Ок",
|
||||
"send": "Отправить",
|
||||
"login": "Имя пользователя",
|
||||
"logout": "Выйти из системы",
|
||||
"undo": "Отменить",
|
||||
"done": "Готово",
|
||||
"cancel": "Отмена",
|
||||
"join": "Присоединяйтесь к",
|
||||
"ignore": "Игнорировать",
|
||||
"user_kick_and_ban": "Выгнать",
|
||||
"start_private_chat": "Прямое сообщение с этим пользователем",
|
||||
"back": "НАЗАД",
|
||||
"new_room": "Новая комната",
|
||||
"delete_now": "Удалить сейчас",
|
||||
"loading": "Загрузка {appName}",
|
||||
"user_revoke_moderator": "Отозвать модератора",
|
||||
"user_make_moderator": "Сделать модератором",
|
||||
"user_make_admin": "Сделать администратором"
|
||||
},
|
||||
"room": {
|
||||
"leave": "Оставить",
|
||||
"room_list_invites": "Приглашения",
|
||||
"room_list_rooms": "Комнаты",
|
||||
"unseen_messages": "У вас нет непросмотренных сообщений | У вас 1 непросмотренное сообщение | У вас {count} непросмотренных сообщений",
|
||||
"members": "нет членов | 1 член | {count} членов",
|
||||
"purge_set_room_state": "Установка состояния комнаты",
|
||||
"purge_removing_members": "Удаление членов ({count} из {total})",
|
||||
"purge_failed": "Не удалось очистить комнату!",
|
||||
"room_name_required": "Название комнаты обязательно",
|
||||
"room_topic_required": "Описание номера обязательно",
|
||||
"invitations": "У вас нет приглашений | У вас есть 1 приглашение | У вас есть {count} приглашений",
|
||||
"purge_redacting_events": "Сокращение событий ({count} из {total})",
|
||||
"room_list_new_messages": "{count} новых сообщений"
|
||||
},
|
||||
"room_welcome": {
|
||||
"got_it": "Есть",
|
||||
"change": "Изменить",
|
||||
"encrypted": "Сообщения шифруются из конца в конец.",
|
||||
"room_history_is": "История комнаты - {type}.",
|
||||
"join_invite": "Присоединиться могут только те, кого вы пригласили.",
|
||||
"no_past_messages": "Добро пожаловать! Для вашей безопасности прошлые сообщения недоступны.",
|
||||
"direct_info": "Привет, {you}. Вы находитесь в приватном чате с {user}.",
|
||||
"join_channel": "Все готово! Пригласите людей присоединиться к вам: {link}",
|
||||
"info_retention": "🕓 Сообщения, отправленные в течение {time}, могут просматривать все, у кого есть ссылка.",
|
||||
"room_history_joined": "Люди могут видеть сообщения, отправленные только после того, как они присоединились.",
|
||||
"join_public": "Любой желающий может присоединиться, открыв эту ссылку: {link}.",
|
||||
"info_permissions": "Вы можете в любой момент изменить \"разрешение на присоединение\" в настройках комнаты.",
|
||||
"info": "Добро пожаловать! Вот несколько вещей, которые нужно знать о вашей комнате:",
|
||||
"direct_private_chat": "Личное сообщение",
|
||||
"info_retention_user": "🕓 Сообщения старше {time} будут удалены из истории."
|
||||
},
|
||||
"device_list": {
|
||||
"blocked": "Заблокированный",
|
||||
"verified": "Проверенные",
|
||||
"not_verified": "Не проверенные"
|
||||
},
|
||||
"getlink": {
|
||||
"next": "Вперёд",
|
||||
"continue": "Продолжить",
|
||||
"hello": "Здравствуйте {user},\nВот ваша прямая ссылка",
|
||||
"scan_title": "Отсканируйте этот код, чтобы начать прямой разговор",
|
||||
"different_link": "Получите другую ссылку",
|
||||
"share_qr": "Поделиться QR",
|
||||
"qr_image_copied": "Изображение копируется в буфер обмена",
|
||||
"title": "Получить прямую ссылку",
|
||||
"info": "Прямые ссылки дают людям возможность безопасно общаться с вами. Для начала выберите экранное имя, которое будет отображаться при входе в чат с вами.",
|
||||
"ready_to_share": "Он готов к обмену! Каждый раз, когда кто-то открывает ссылку, будет открываться новая прямая комната.",
|
||||
"username": "Введите имя экрана (например: waku)"
|
||||
},
|
||||
"profile": {
|
||||
"password_old": "Старый пароль",
|
||||
"password_new": "Новый пароль",
|
||||
"password_repeat": "Повторите новый пароль",
|
||||
"display_name": "Отображаемое имя",
|
||||
"notification_label": "Уведомления",
|
||||
"set_password": "Задать пароль",
|
||||
"change_password": "Изменить пароль",
|
||||
"select_language": "Язык",
|
||||
"title": "Мой профиль",
|
||||
"temporary_identity": "Этот идентификатор является временным. Установите пароль, чтобы использовать ее снова",
|
||||
"change_name": "Изменить имя",
|
||||
"set_language": "Установите свой язык",
|
||||
"language_description": "Convene доступен на многих языках.",
|
||||
"dont_see_yours": "Не видите своего?",
|
||||
"tell_us": "Расскажите нам.",
|
||||
"display_name_required": "Отображаемое имя обязательно"
|
||||
},
|
||||
"join": {
|
||||
"user_name_label": "Имя пользователя",
|
||||
"remember_me": "Запомнить меня",
|
||||
"join": "Присоединиться к комнате",
|
||||
"enter_room": "Войти в комнату",
|
||||
"joining_as": "Вы присоединяетесь как:",
|
||||
"join_user": "Начать общение",
|
||||
"you_have_been_banned": "Вам запрещено посещать эту комнату.",
|
||||
"title": "Добро пожаловать, вы приглашены присоединиться",
|
||||
"title_user": "Добро пожаловать, вас пригласили пообщаться с",
|
||||
"enter_room_user": "Начать общение",
|
||||
"status_joining": "Присоединяюсь к комнате...",
|
||||
"join_failed": "Не удалось присоединиться к комнате.",
|
||||
"choose_name": "Выберите имя для использования",
|
||||
"status_logging_in": "Вход…"
|
||||
},
|
||||
"leave": {
|
||||
"title_invite": "Вы уверены, что хотите выйти?",
|
||||
"leave": "Оставить",
|
||||
"go_back": "Назад",
|
||||
"text_invite": "Эта комната заперта. Вы не сможете войти в нее без специального разрешения.",
|
||||
"create_account": "создать аккаунт",
|
||||
"title_public": "До свидания, {user}",
|
||||
"text_public": "Вы всегда можете присоединиться к этой комнате снова, если знаете ссылку.",
|
||||
"text_public_lastroom": "Если вы хотите присоединиться к этой комнате снова, вы можете присоединиться под новым именем. Чтобы сохранить {user}, {action}."
|
||||
},
|
||||
"purge_room": {
|
||||
"button": "Удалить",
|
||||
"n_seconds": "{seconds} секунды",
|
||||
"title": "Удалить комнату?",
|
||||
"info": "Все пользователи и сообщения будут удалены. Это действие нельзя отменить.",
|
||||
"self_destruct": "Ваша комната самоуничтожится в считанные секунды.",
|
||||
"deleting": "Удаление комнаты:",
|
||||
"notified": "Мы уведомили членов клуба.",
|
||||
"room_deletion_notice": "Время прощаться! Эта комната была удалена {user}. Она самоуничтожится через несколько секунд."
|
||||
},
|
||||
"poll_create": {
|
||||
"poll_submit": "Отправить",
|
||||
"create": "Опубликовать",
|
||||
"answer_label_n": "Ответ",
|
||||
"title": "Создать новый опрос",
|
||||
"answer_label_1": "Ответ*",
|
||||
"tip_title": "СОВЕТ ПРОФЕССИОНАЛА",
|
||||
"creating": "Создайте опрос",
|
||||
"poll_disclosed": "Открыто - текущие результаты отображаются постоянно.",
|
||||
"poll_undisclosed": "Закрыто - пользователи увидят результаты, когда опрос будет закрыт.",
|
||||
"add_answer": "Добавить ответ",
|
||||
"failed": "Не удалось создать опрос, повторите попытку позже.",
|
||||
"question_label": "Задайте свой вопрос*",
|
||||
"please_complete": "Пожалуйста, заполните",
|
||||
"create_poll_menu_option": "Создать опрос",
|
||||
"poll_status_closed": "Опрос закрыт",
|
||||
"poll_status_disclosed": "Результаты будут показаны после закрытия опроса.",
|
||||
"poll_status_open": "Опрос открыт",
|
||||
"poll_status_open_not_voted": "Опрос открыт - проголосуйте, чтобы увидеть результаты",
|
||||
"view_results": "Посмотреть результаты",
|
||||
"close_poll": "Закрытый опрос",
|
||||
"question_required": "Вам нужно ввести вопрос!",
|
||||
"answer_required": "Ответ не может быть пустым. Пожалуйста, введите текст или удалите этот вариант.",
|
||||
"tip_text": "Участники увидят результаты опроса после того, как ответят. Закройте опрос, когда закончите, чтобы показать результаты всем присутствующим в комнате.",
|
||||
"num_answered": "{count} ответов",
|
||||
"results_shared": "Результаты переданы в зал."
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"edit_profile": "Редактировать профиль",
|
||||
"logout": "Выйти из системы",
|
||||
"you_are": "Вы",
|
||||
"identity": "{displayName}",
|
||||
"identity_temporary": "{displayName}",
|
||||
"want_more": "Хотите больше?",
|
||||
"powered_by": "В этой комнате используется {product}. Узнайте больше на {productLink} или создайте другую комнату!",
|
||||
"new_room": "Новая комната"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Добавить друзей",
|
||||
"done": "Готово",
|
||||
"send_invites_to": "Присылайте приглашения по адресу",
|
||||
"status_error": "Не удалось пригласить одного или нескольких друзей!",
|
||||
"status_inviting": "Приглашение друга {index} из {count}"
|
||||
},
|
||||
"voice_recorder": {
|
||||
"not_supported_title": "Не поддерживается",
|
||||
"not_supported_text": "К сожалению, этот браузер не поддерживает запись звука.",
|
||||
"swipe_to_cancel": "Проведите пальцем, чтобы отменить",
|
||||
"release_to_cancel": "Отпустить, чтобы отменить",
|
||||
"failed_to_record": "Не удалось записать звук"
|
||||
},
|
||||
"fallbacks": {
|
||||
"download_name": "Загрузить",
|
||||
"original_text": "<Оригинальный текст>",
|
||||
"audio_file": "Аудиофайлы",
|
||||
"video_file": "Видеофайлы"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Включить",
|
||||
"body": "Никогда больше не пропустите ни одного сообщения или важного разговора! Получайте уведомления, когда кто-то отправляет вам сообщение или отвечает в вашем чате.",
|
||||
"title": "Оставайтесь на связи с уведомлениями в чате!"
|
||||
},
|
||||
"blocked_message": "Уведомление заблокировано. Перейдите в настройки устройства или браузера, чтобы включить уведомление",
|
||||
"not_supported": "Уведомления пока не поддерживаются в мобильной версии",
|
||||
"title": "Получено новое сообщение",
|
||||
"periodicSync_new_msg_reminder": "У вас могут появиться новые сообщения"
|
||||
},
|
||||
"language_display_name": "Русский",
|
||||
"project": {
|
||||
"name": "Созыв",
|
||||
"tag_line": "Просто подключите"
|
||||
},
|
||||
"logout": {
|
||||
"confirm_text": "Вы уверены, что хотите выйти из системы?"
|
||||
},
|
||||
"goodbye": {
|
||||
"room_deleted": "Комната удалена.",
|
||||
"close_tab": "Закрыть вкладку браузера",
|
||||
"view_other_rooms": "Посмотреть другие комнаты"
|
||||
},
|
||||
"createchannel": {
|
||||
"title": "Создать канал",
|
||||
"info": "Транслируйте новости или знания в любом формате - видео, подкаст, текст, картинки или PDF-файлы.",
|
||||
"channel_name": "Назовите свой канал",
|
||||
"channel_topic": "Опишите его",
|
||||
"name_required": "Имя канала обязательно",
|
||||
"error_channel": "Не удалось создать канал"
|
||||
},
|
||||
"export": {
|
||||
"fetched_n_events": "Найдено {count} событий",
|
||||
"fetched_n_of_total_events": "Получено {count} из {total} событий",
|
||||
"export_filename": "Экспортированный чат {date}",
|
||||
"processed_n_of_total_events": "Обработка носителей для {count} из {total} событий",
|
||||
"exported_date": "Экспортировано в {date}"
|
||||
}
|
||||
}
|
||||
|
|
@ -113,5 +113,8 @@
|
|||
"dialog": {
|
||||
"enable": "සක්රිය කරන්න"
|
||||
}
|
||||
},
|
||||
"room_welcome": {
|
||||
"change": "වෙනස් කරන්න"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
"voice_mode": "语音模块",
|
||||
"voice_mode_info": "将聊天界面切换到“收听和录音”模式",
|
||||
"download_chat": "下载聊天",
|
||||
"read_only_room": "只读聊天室",
|
||||
"read_only_room": "只读",
|
||||
"read_only_room_info": "只允许管理员和版主发送到聊天室",
|
||||
"export_room": "导出聊天",
|
||||
"user_moderator": "版主",
|
||||
|
|
@ -47,7 +47,9 @@
|
|||
"message_retention_none": "关闭",
|
||||
"message_retention_1_day": "1 天",
|
||||
"message_retention_8_hours": "8 小时",
|
||||
"message_retention_1_hour": "1 小时"
|
||||
"message_retention_1_hour": "1 小时",
|
||||
"message_retention_2_week": "2周",
|
||||
"message_retention_1_week": "1周"
|
||||
},
|
||||
"leave": {
|
||||
"leave": "离开",
|
||||
|
|
@ -292,7 +294,8 @@
|
|||
"encrypted": "信息是端到端加密的。",
|
||||
"no_past_messages": "欢迎! 为了您的安全,过去的信息不提供。",
|
||||
"direct_info": "你好,{you}.正在与 {user} 进行私人聊天。",
|
||||
"direct_private_chat": "私信"
|
||||
"direct_private_chat": "私信",
|
||||
"change": "更改"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"new_room": "新的聊天室",
|
||||
|
|
|
|||
|
|
@ -54,39 +54,45 @@
|
|||
|
||||
<component :is="roomWelcomeHeader" v-on:close="closeRoomWelcomeHeader"></component>
|
||||
|
||||
<!-- If we have a retention timer, it means we have active message retention. Show header. -->
|
||||
<WelcomeHeaderChannelUser v-if="retentionTimer && !roomWelcomeHeader" />
|
||||
|
||||
<div v-for="(event, index) in filteredEvents" :key="event.getId()" :eventId="event.getId()">
|
||||
<!-- DAY Marker, shown for every new day in the timeline -->
|
||||
<div v-if="showDayMarkerBeforeEvent(event) && !!componentForEvent(event, isForExport = false)" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
|
||||
|
||||
<div v-if="!event.isRelation() && !event.isRedaction()" :ref="event.getId()">
|
||||
<div class="message-wrapper" v-on:touchstart="
|
||||
(e) => {
|
||||
touchStart(e, event);
|
||||
}
|
||||
" v-on:touchend="touchEnd" v-on:touchcancel="touchCancel" v-on:touchmove="touchMove">
|
||||
<!-- Note: For threaded media messages, IF there is only one item we show that media item as a single component.
|
||||
We might therefore get calls to v-on:context-menu that has the event set to that single media item, not the top level thread event
|
||||
that is really displayed in the flow. Therefore, we rewrite these events with "{event: event, anchor: $event.anchor}",
|
||||
see below. Otherwise things like context menus won't work as designed.
|
||||
-->
|
||||
<component :is="componentForEvent(event)" :room="room" :originalEvent="event" :nextEvent="filteredEvents[index + 1]"
|
||||
:timelineSet="timelineSet" v-on:send-quick-reaction.stop="sendQuickReaction"
|
||||
:componentFn="componentForEvent"
|
||||
v-on:context-menu="showContextMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:own-avatar-clicked="viewProfile"
|
||||
v-on:other-avatar-clicked="showAvatarMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:download="download(event)"
|
||||
v-on:poll-closed="pollWasClosed(event)"
|
||||
v-on:more="
|
||||
isEmojiQuickReaction = true
|
||||
showMoreMessageOperations({event: event, anchor: $event.anchor})
|
||||
"
|
||||
v-on:layout-change="onLayoutChange"
|
||||
/>
|
||||
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
|
||||
<!-- <div v-if="debugging" style="user-select:text">Event: {{ JSON.stringify(event) }}</div> -->
|
||||
<div v-if="event.getId() == readMarker && index < filteredEvents.length - 1" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
|
||||
</div>
|
||||
<MessageErrorHandler>
|
||||
<div class="message-wrapper" v-on:touchstart="
|
||||
(e) => {
|
||||
touchStart(e, event);
|
||||
}
|
||||
" v-on:touchend="touchEnd" v-on:touchcancel="touchCancel" v-on:touchmove="touchMove">
|
||||
<!-- Note: For threaded media messages, IF there is only one item we show that media item as a single component.
|
||||
We might therefore get calls to v-on:context-menu that has the event set to that single media item, not the top level thread event
|
||||
that is really displayed in the flow. Therefore, we rewrite these events with "{event: event, anchor: $event.anchor}",
|
||||
see below. Otherwise things like context menus won't work as designed.
|
||||
-->
|
||||
<component :is="componentForEvent(event)" :room="room" :originalEvent="event" :nextEvent="filteredEvents[index + 1]"
|
||||
:timelineSet="timelineSet" v-on:send-quick-reaction.stop="sendQuickReaction"
|
||||
:componentFn="componentForEvent"
|
||||
v-on:context-menu="showContextMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:own-avatar-clicked="viewProfile"
|
||||
v-on:other-avatar-clicked="showAvatarMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:download="download(event)"
|
||||
v-on:poll-closed="pollWasClosed(event)"
|
||||
v-on:more="
|
||||
isEmojiQuickReaction = true
|
||||
showMoreMessageOperations({event: event, anchor: $event.anchor})
|
||||
"
|
||||
v-on:layout-change="onLayoutChange"
|
||||
v-on:addQuickHeartReaction="addQuickHeartReaction({event, position: $event.position})"
|
||||
/>
|
||||
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
|
||||
<!-- <div v-if="debugging" style="user-select:text">Event: {{ JSON.stringify(event) }}<br /><br /></div> -->
|
||||
<div v-if="event.getId() == readMarker && index < filteredEvents.length - 1" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
|
||||
</div>
|
||||
</MessageErrorHandler>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -109,7 +115,7 @@
|
|||
<div v-if="replyToContentType === 'm.text'" class="reply-text" :title="replyToEvent.getContent().body">
|
||||
{{ replyToEvent.getContent().body | latestReply }}
|
||||
</div>
|
||||
<div v-if="replyToContentType === 'm.thread'">{{ replyToThreadMessage }}</div>
|
||||
<div v-if="replyToContentType === 'm.thread' || replyToContentType === 'io.element.thread'">{{ replyToThreadMessage }}</div>
|
||||
<div v-if="replyToContentType === 'm.image'">{{ $t("message.reply_image") }}</div>
|
||||
<div v-if="replyToContentType === 'm.audio'">{{ $t("message.reply_audio_message") }}</div>
|
||||
<div v-if="replyToContentType === 'm.video'">{{ $t("message.reply_video") }}</div>
|
||||
|
|
@ -209,7 +215,7 @@
|
|||
</v-container>
|
||||
|
||||
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
||||
accept="image/*,audio/*,video/*,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
|
||||
accept="image/*,audio/*,video/*,.mp3,.mp4,.wav,.m4a,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
|
||||
|
||||
<div v-if="currentFileInputsDialog && !useFileModeNonAdmin">
|
||||
<v-dialog v-model="currentFileInputsDialog" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'" persistent scrollable>
|
||||
|
|
@ -251,7 +257,7 @@
|
|||
</span>
|
||||
|
||||
<v-switch v-if="currentImageInput && currentImageInput.scaled" :label="$t('message.scale_image')"
|
||||
v-model="currentImageInput.useScaled" :disabled="currentImageInput.sendInfo" />
|
||||
v-model="currentImageInput.useScaled" :disabled="currentImageInput && currentImageInput.sendInfo !== undefined" />
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
|
@ -277,7 +283,7 @@
|
|||
{{ $t("menu.cancel") }}
|
||||
</v-btn>
|
||||
<v-btn id="btn-attachment-send" color="primary" text @click="sendAttachment(undefined)"
|
||||
v-if="currentSendShowSendButton" :disabled="sendingStatus != sendStatuses.INITIAL">{{ $t("menu.send") }}</v-btn>
|
||||
v-if="currentSendShowSendButton" :disabled="currentSendShowSendButton && sendingStatus != sendStatuses.INITIAL">{{ $t("menu.send") }}</v-btn>
|
||||
</v-card-actions>
|
||||
</template>
|
||||
</v-card>
|
||||
|
|
@ -327,6 +333,11 @@
|
|||
|
||||
<!-- PURGE ROOM POPUP -->
|
||||
<PurgeRoomDialog :show="showPurgeConfirmation" :room="room" @close="showPurgeConfirmation = false" />
|
||||
|
||||
<!-- Heart animation -->
|
||||
<div :class="['heart-wrapper', { 'is-active': heartAnimation }]" :style="hearAnimationPosition">
|
||||
<div :class="['heart', { 'is-active': heartAnimation }]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -342,6 +353,7 @@ import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
|
|||
import WelcomeHeaderRoom from "./welcome_headers/WelcomeHeaderRoom";
|
||||
import WelcomeHeaderDirectChat from "./welcome_headers/WelcomeHeaderDirectChat";
|
||||
import WelcomeHeaderChannel from "./welcome_headers/WelcomeHeaderChannel";
|
||||
import WelcomeHeaderChannelUser from "./welcome_headers/WelcomeHeaderChannelUser";
|
||||
import NoHistoryRoomWelcomeHeader from "./NoHistoryRoomWelcomeHeader.vue";
|
||||
import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet";
|
||||
import StickerPickerBottomSheet from "./StickerPickerBottomSheet";
|
||||
|
|
@ -356,6 +368,7 @@ import FileDropLayout from "./file_mode/FileDropLayout";
|
|||
import roomTypeMixin from "./roomTypeMixin";
|
||||
import roomMembersMixin from "./roomMembersMixin";
|
||||
import PurgeRoomDialog from "../components/PurgeRoomDialog";
|
||||
import MessageErrorHandler from "./MessageErrorHandler";
|
||||
|
||||
const sizeOf = require("image-size");
|
||||
const dataUriToBuffer = require("data-uri-to-buffer");
|
||||
|
|
@ -409,6 +422,8 @@ export default {
|
|||
FileDropLayout,
|
||||
UserProfileDialog,
|
||||
PurgeRoomDialog,
|
||||
WelcomeHeaderChannelUser,
|
||||
MessageErrorHandler
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -493,6 +508,11 @@ export default {
|
|||
retentionTimer: null,
|
||||
showProfileDialog: false,
|
||||
showPurgeConfirmation: false,
|
||||
heartAnimation: false,
|
||||
heartPosition: {
|
||||
top: 0,
|
||||
left: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -533,6 +553,9 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
heartEmoji() {
|
||||
return this.$refs.emojiPicker.mapEmojis["Symbols"].find(({ aliases }) => aliases.includes('heart')).data;
|
||||
},
|
||||
compActiveMember() {
|
||||
const currentUserId= this.selectedEvent?.sender.userId || this.$matrix.currentUserId
|
||||
return this.joinedAndInvitedMembers.find(({userId}) => userId === currentUserId)
|
||||
|
|
@ -764,6 +787,12 @@ export default {
|
|||
.filter((e) => util.downloadableTypes().includes(e.getContent().msgtype)).length});
|
||||
}
|
||||
return "";
|
||||
},
|
||||
hearAnimationPosition() {
|
||||
return {
|
||||
'--top': this.heartPosition.top,
|
||||
'--left': this.heartPosition.left
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -869,13 +898,10 @@ export default {
|
|||
|
||||
methods: {
|
||||
/**
|
||||
* Set initialLoadDone to 'true'. First process all events, setting threadParent and replyEvent if needed.
|
||||
* Set initialLoadDone to 'true'. This will process all events, setting threadParent and replyEvent if needed (see watcher for "initialLoadDone")
|
||||
*/
|
||||
setInitialLoadDone() {
|
||||
this.events.filter(event => (event.threadRootId && !event.parentThread)).forEach(event => this.setParentThread(event));
|
||||
this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => this.setReplyToEvent(event));
|
||||
this.initialLoadDone = true;
|
||||
console.log("Loading finished!");
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -998,7 +1024,7 @@ export default {
|
|||
})
|
||||
.catch((err) => {
|
||||
console.log("Error fetching events!", err, this);
|
||||
if (err.errcode == "M_UNKNOWN" && initialEventId) {
|
||||
if (initialEventId) {
|
||||
// Try again without initial event!
|
||||
this.onRoomJoined(null);
|
||||
} else {
|
||||
|
|
@ -1146,7 +1172,8 @@ export default {
|
|||
Vue.set(event, "parentThread", parentEvent);
|
||||
} else {
|
||||
// Try to load from server.
|
||||
this.$matrix.matrixClient.getEventTimeline(this.timelineSet, event.threadRootId).then((tl) => {
|
||||
this.$matrix.matrixClient.getEventTimeline(this.timelineSet, event.threadRootId)
|
||||
.then((tl) => {
|
||||
if (tl) {
|
||||
const parentEvent = tl.getEvents().find((e) => e.getId() === event.threadRootId);
|
||||
if (parentEvent) {
|
||||
|
|
@ -1168,7 +1195,7 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}).catch(e => console.error(e));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -1569,6 +1596,11 @@ export default {
|
|||
this.sendQuickReaction({ reaction: e.emoji, event: e.event });
|
||||
},
|
||||
|
||||
addQuickHeartReaction(e) {
|
||||
this.heartPosition = e.position
|
||||
this.sendQuickReaction({ reaction: this.heartEmoji, event: e.event }, true);
|
||||
},
|
||||
|
||||
setReplyToImage(event) {
|
||||
util
|
||||
.getThumbnail(this.$matrix.matrixClient, event, this.$config)
|
||||
|
|
@ -1584,7 +1616,7 @@ export default {
|
|||
this.replyToEvent = event;
|
||||
this.$refs.messageInput.focus();
|
||||
if (event.parentThread || event.isThreadRoot || event.isMxThread) {
|
||||
this.replyToContentType = 'm.thread';
|
||||
this.replyToContentType = util.threadMessageType();
|
||||
} else {
|
||||
this.replyToContentType = event.getContent().msgtype || 'm.poll';
|
||||
}
|
||||
|
|
@ -1598,8 +1630,16 @@ export default {
|
|||
},
|
||||
|
||||
redact(event) {
|
||||
this.$matrix.matrixClient
|
||||
.redactEvent(event.getRoomId(), event.getId())
|
||||
let promises = [];
|
||||
if ((event.isThreadRoot || event.isMxThread) && this.timelineSet) {
|
||||
// If this is a thread message, make sure to redact all children as well.
|
||||
const children = this.timelineSet.relations.getAllChildEventsForEvent(event.getId()).filter(e => util.downloadableTypes().includes(e.getContent().msgtype));
|
||||
promises = children.map((c) => {
|
||||
return this.$matrix.matrixClient.redactEvent(c.getRoomId(), c.getId(), undefined, { reason: "redactedMedia"});
|
||||
});
|
||||
}
|
||||
promises.push(this.$matrix.matrixClient.redactEvent(event.getRoomId(), event.getId(), undefined, { reason: "redactedThread"}));
|
||||
Promise.allSettled(promises)
|
||||
.then(() => {
|
||||
console.log("Message redacted");
|
||||
})
|
||||
|
|
@ -1652,7 +1692,15 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
sendQuickReaction(e) {
|
||||
showHeartAnimation() {
|
||||
const self = this;
|
||||
this.heartAnimation = true;
|
||||
setTimeout(() => {
|
||||
self.heartAnimation = false;
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
sendQuickReaction(e, heartAnimationFlag = false) {
|
||||
let previousReaction = null;
|
||||
|
||||
// Figure out if we have already sent this emoji, in that case redact it again (toggle)
|
||||
|
|
@ -1679,6 +1727,10 @@ export default {
|
|||
.catch((err) => {
|
||||
console.log("Failed to send quick reaction:", err);
|
||||
});
|
||||
|
||||
if(heartAnimationFlag) {
|
||||
this.showHeartAnimation();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -1909,4 +1961,32 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
.heart-wrapper {
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
|
||||
.heart {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: url("../assets/heart.png") no-repeat;
|
||||
background-position: 0 0;
|
||||
cursor: pointer;
|
||||
transition: background-position 1s steps(28);
|
||||
transition-duration: 0s;
|
||||
visibility: hidden;
|
||||
|
||||
|
||||
&.is-active {
|
||||
transition-duration: 1s;
|
||||
background-position: -2800px 0;
|
||||
visibility: visible;
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
&.is-active {
|
||||
z-index: 1000;
|
||||
top: var(--top);
|
||||
left: var(--left);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -145,18 +145,18 @@ export default {
|
|||
preset: "public_chat",
|
||||
initial_state:
|
||||
[
|
||||
{
|
||||
type: "m.room.encryption",
|
||||
state_key: "",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
},
|
||||
// {
|
||||
// type: "m.room.encryption",
|
||||
// state_key: "",
|
||||
// content: {
|
||||
// algorithm: "m.megolm.v1.aes-sha2",
|
||||
// },
|
||||
// },
|
||||
{
|
||||
type: "m.room.history_visibility",
|
||||
state_key: "",
|
||||
content: {
|
||||
history_visibility: "joined"
|
||||
history_visibility: "shared"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default {
|
|||
width: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
min-height: 48px;
|
||||
min-height: $min-touch-target;
|
||||
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
|
|
|
|||
29
src/components/MessageErrorHandler.vue
Normal file
29
src/components/MessageErrorHandler.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<slot
|
||||
v-if="err"
|
||||
name="error"
|
||||
v-bind:err="err"
|
||||
><div class="text-center">{{ $t('message.failed_to_render') }}</div></slot>
|
||||
<slot v-else></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MessageErrorHandler",
|
||||
data() {
|
||||
return {
|
||||
err: false,
|
||||
};
|
||||
},
|
||||
errorCaptured(err, ignoredvm, ignoredinfo) {
|
||||
this.err = err;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
@ -117,6 +117,6 @@ export default {
|
|||
}
|
||||
|
||||
.v-radio.flex-row-reverse {
|
||||
height: 48px;
|
||||
height: $min-touch-target;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -125,13 +125,6 @@ export default {
|
|||
},
|
||||
|
||||
componentForEvent(event, isForExport = false) {
|
||||
if (!event.isRelation() && !event.isRedaction() && event.isRedacted()) {
|
||||
const redaction = event.getRedactionEvent();
|
||||
if (redaction && redaction.content && redaction.content.reason === "cancel") {
|
||||
return null; // Show nothing, it was canceled!
|
||||
}
|
||||
}
|
||||
|
||||
switch (event.getType()) {
|
||||
case "m.room.member":
|
||||
if (event.getContent().membership == "join") {
|
||||
|
|
@ -161,6 +154,13 @@ export default {
|
|||
|
||||
case "m.room.message":
|
||||
if (event.getSender() != this.$matrix.currentUserId) {
|
||||
if (event.isRedacted()) {
|
||||
// Redacted thread, show as text (and hide all media)!
|
||||
if (event.getUnsigned().redacted_because.content.reason == "redactedThread") {
|
||||
return MessageIncomingText;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (event.isMxThread) {
|
||||
// Incoming thread, e.g. a file drop!
|
||||
return isForExport ? MessageIncomingThreadExport : MessageIncomingThread;
|
||||
|
|
@ -201,6 +201,13 @@ export default {
|
|||
}
|
||||
return MessageIncomingText;
|
||||
} else {
|
||||
if (event.isRedacted()) {
|
||||
// Redacted thread, show as text (and hide all media)!
|
||||
if (event.getUnsigned().redacted_because.content.reason == "redactedThread") {
|
||||
return MessageOutgoingText;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (event.isMxThread) {
|
||||
// Outgoing thread
|
||||
return isForExport ? MessageOutgoingThreadExport : MessageOutgoingThread;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<v-responsive v-if="item.event.getContent().msgtype == 'm.video' && item.src" :class="{'thumbnail-item': true, 'preview': previewOnly}"
|
||||
@click.stop="$emit('itemclick', {item: item})">
|
||||
<video :src="item.src" :controls="!previewOnly" class="w-100 h-100">
|
||||
{{ $t('fallbacks.video_file') }}
|
||||
</video>
|
||||
</v-responsive>
|
||||
<v-img v-else-if="item.event.getContent().msgtype == 'm.image' && item.src" :aspect-ratio="previewOnly ? (16 / 9) : undefined" :class="{'thumbnail-item': true, 'preview': previewOnly}" :src="item.src" :contain="!previewOnly" :cover="previewOnly"
|
||||
@click.stop="$emit('itemclick', {item: item})" />
|
||||
<div v-else :class="{'thumbnail-item': true, 'preview': previewOnly, 'file-item': true}" @click.stop="$emit('itemclick', {item: item})">
|
||||
<v-icon>{{ fileTypeIcon }}</v-icon>
|
||||
<b>{{ $sanitize(fileName) }}</b>
|
||||
<div>{{ fileSize }}</div>
|
||||
<div ref="thumbnailRef">
|
||||
<v-responsive v-if="item.event.getContent().msgtype == 'm.video' && item.src" :class="{'thumbnail-item': true, 'preview': previewOnly}">
|
||||
<video :src="item.src" :controls="!previewOnly" class="w-100 h-100">
|
||||
{{ $t('fallbacks.video_file') }}
|
||||
</video>
|
||||
</v-responsive>
|
||||
<v-img v-else-if="item.event.getContent().msgtype == 'm.image' && item.src" :aspect-ratio="previewOnly ? (16 / 9) : undefined" :class="{'thumbnail-item': true, 'preview': previewOnly}" :src="item.src" :contain="!previewOnly" :cover="previewOnly" />
|
||||
<div v-else :class="{'thumbnail-item': true, 'preview': previewOnly, 'file-item': true}" >
|
||||
<v-icon>{{ fileTypeIcon }}</v-icon>
|
||||
<b>{{ $sanitize(fileName) }}</b>
|
||||
<div>{{ fileSize }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
|
@ -20,7 +20,7 @@ import util from "../../plugins/utils";
|
|||
export default {
|
||||
props: {
|
||||
/**
|
||||
* Item is an object of { event: MXEvent, src: URL }
|
||||
* Item is an object of { event: MXEvent, src: URL }
|
||||
*/
|
||||
item: {
|
||||
type: Object,
|
||||
|
|
@ -54,7 +54,24 @@ export default {
|
|||
fileSize() {
|
||||
return util.getFileSizeFormatted(this.item.event);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// listen for custom hammerJs singletab click to differentiate it from double click(heart animation).
|
||||
initThumbnailHammerJs(element) {
|
||||
const hammerInstance = util.singleOrDoubleTabRecognizer(element)
|
||||
|
||||
hammerInstance.on("singletap doubletap", (ev) => {
|
||||
if(ev.type === 'singletap') {
|
||||
this.$emit('itemclick', { item: this.item })
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if(this.$refs.thumbnailRef) {
|
||||
this.initThumbnailHammerJs(this.$refs.thumbnailRef);
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
}}</span>
|
||||
</v-avatar>
|
||||
<!-- SLOT FOR CONTENT -->
|
||||
<slot></slot>
|
||||
<span ref="messageInOutRef">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<div class="op-button" ref="opbutton" v-if="!event.isRedacted()">
|
||||
<v-btn id="btn-more" icon @click.stop="showContextMenu($refs.opbutton)">
|
||||
<v-icon>more_vert</v-icon>
|
||||
|
|
@ -28,10 +30,16 @@
|
|||
<script>
|
||||
import SeenBy from "./SeenBy.vue";
|
||||
import messageMixin from "./messageMixin";
|
||||
import util from "../../plugins/utils";
|
||||
|
||||
export default {
|
||||
mixins: [messageMixin],
|
||||
components: { SeenBy }
|
||||
components: { SeenBy },
|
||||
mounted() {
|
||||
if(util.isMobileOrTabletBrowser() && this.$refs.messageInOutRef) {
|
||||
this.initMsgHammerJs(this.$refs.messageInOutRef);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
<template>
|
||||
<message-incoming v-bind="{...$props, ...$attrs}" v-on="$listeners">
|
||||
<div class="bubble image-bubble">
|
||||
<div class="bubble image-bubble" ref="imageRef">
|
||||
<v-img
|
||||
:aspect-ratio="16 / 9"
|
||||
ref="image"
|
||||
:src="src"
|
||||
:cover="cover"
|
||||
:contain="contain"
|
||||
@click.stop="dialog = true"
|
||||
/>
|
||||
</div>
|
||||
<v-dialog
|
||||
|
|
@ -28,12 +27,24 @@ export default {
|
|||
components: { MessageIncoming },
|
||||
data() {
|
||||
return {
|
||||
src: null,
|
||||
src: undefined,
|
||||
cover: true,
|
||||
contain: false,
|
||||
dialog: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// listen for custom hammerJs singletab click to differentiate it from double click(heart animation).
|
||||
initMessageInImageHammerJs(element) {
|
||||
const hammerInstance = util.singleOrDoubleTabRecognizer(element);
|
||||
|
||||
hammerInstance.on("singletap doubletap", (ev) => {
|
||||
if(ev.type === 'singletap') {
|
||||
this.dialog = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//console.log("Mounted with event:", JSON.stringify(this.event.getContent()));
|
||||
const width = this.$refs.image.$el.clientWidth;
|
||||
|
|
@ -52,6 +63,9 @@ export default {
|
|||
this.contain = true;
|
||||
}
|
||||
this.src = url;
|
||||
if(this.$refs.imageRef) {
|
||||
this.initMessageInImageHammerJs(this.$refs.imageRef);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to fetch thumbnail: ", err);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<div class="message">
|
||||
<i v-if="event.isRedacted()" class="deleted-text">
|
||||
<v-icon :color="this.senderIsAdminOrModerator(this.event)?'white':''" size="small">block</v-icon>
|
||||
{{ $t('message.incoming_message_deleted_text')}}
|
||||
{{ redactedBySomeoneElse(event) ? $t('message.incoming_message_deleted_text') : $t('message.outgoing_message_deleted_text')}}
|
||||
</i>
|
||||
<span v-html="linkify($sanitize(messageText))" v-else/>
|
||||
<span class="edit-marker" v-if="event.replacingEventId() && !event.isRedacted()">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<message-incoming v-bind="{ ...$props, ...$attrs }" v-on="$listeners" v-if="items.length > 1">
|
||||
<message-incoming v-bind="{ ...$props, ...$attrs }" v-on="$listeners" v-if="items.length > 1 || event.isRedacted()">
|
||||
<div class="bubble">
|
||||
<div class="original-message" v-if="inReplyToText">
|
||||
<div class="original-message-sender">{{ inReplyToSender }}</div>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
|
||||
<div class="message">
|
||||
<v-container fluid class="imageCollection">
|
||||
<v-container v-if="!event.isRedacted()" fluid class="imageCollection">
|
||||
<v-row wrap>
|
||||
<v-col v-for="({ size, item }) in layoutedItems()" :key="item.event.getId()" :cols="size">
|
||||
<ThumbnailView :item="item" :previewOnly="true" v-on:itemclick="onItemClick($event)" />
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
</v-container>
|
||||
<i v-if="event.isRedacted()" class="deleted-text">
|
||||
<v-icon :color="this.senderIsAdminOrModerator(this.event) ? 'white' : ''" size="small">block</v-icon>
|
||||
{{ $t('message.incoming_message_deleted_text') }}
|
||||
{{ redactedBySomeoneElse(event) ? $t('message.incoming_message_deleted_text') : $t('message.outgoing_message_deleted_text')}}
|
||||
</i>
|
||||
<span v-html="linkify($sanitize(messageText))" v-else />
|
||||
<span class="edit-marker" v-if="event.replacingEventId() && !event.isRedacted()">
|
||||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
if (!this.thread) {
|
||||
this.event.on("Event.relationsCreated", this.onRelationsCreated);
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onRelationsCreated() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
this.event.off("Event.relationsCreated", this.onRelationsCreated);
|
||||
},
|
||||
onItemClick(event) {
|
||||
|
|
@ -72,7 +72,7 @@ export default {
|
|||
processThread() {
|
||||
this.$emit('layout-change', () => {
|
||||
this.items = this.timelineSet.relations.getAllChildEventsForEvent(this.event.getId())
|
||||
.filter(e => util.downloadableTypes().includes(e.getContent().msgtype))
|
||||
.filter(e => !e.isRedacted() && util.downloadableTypes().includes(e.getContent().msgtype))
|
||||
.map(e => {
|
||||
let ret = {
|
||||
event: e,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
</v-btn>
|
||||
</div>
|
||||
<!-- SLOT FOR CONTENT -->
|
||||
<slot></slot>
|
||||
<span ref="messageInOutRef">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<v-avatar
|
||||
class="avatar"
|
||||
size="32"
|
||||
|
|
@ -32,10 +34,16 @@
|
|||
<script>
|
||||
import SeenBy from "./SeenBy.vue";
|
||||
import messageMixin from "./messageMixin";
|
||||
import util from "../../plugins/utils";
|
||||
|
||||
export default {
|
||||
mixins: [messageMixin],
|
||||
components: { SeenBy }
|
||||
components: { SeenBy },
|
||||
mounted() {
|
||||
if(util.isMobileOrTabletBrowser() && this.$refs.messageInOutRef) {
|
||||
this.initMsgHammerJs(this.$refs.messageInOutRef);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
<template>
|
||||
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<div class="bubble image-bubble">
|
||||
<div class="bubble image-bubble" ref="imageRef">
|
||||
<v-img
|
||||
:aspect-ratio="16 / 9"
|
||||
ref="image"
|
||||
:src="src"
|
||||
:cover="cover"
|
||||
:contain="contain"
|
||||
@click.stop="dialog = true"
|
||||
/>
|
||||
</div>
|
||||
<v-dialog
|
||||
|
|
@ -28,12 +27,24 @@ export default {
|
|||
components: { MessageOutgoing },
|
||||
data() {
|
||||
return {
|
||||
src: null,
|
||||
src: undefined,
|
||||
cover: true,
|
||||
contain: false,
|
||||
dialog: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// listen for custom hammerJs singletab click to differentiate it from double click(heart animation).
|
||||
initMessageOutImageHammerJs(element) {
|
||||
const hammerInstance = util.singleOrDoubleTabRecognizer(element);
|
||||
|
||||
hammerInstance.on("singletap doubletap", (ev) => {
|
||||
if(ev.type === 'singletap') {
|
||||
this.dialog = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const width = this.$refs.image.$el.clientWidth;
|
||||
const height = (width * 9) / 16;
|
||||
|
|
@ -51,6 +62,9 @@ export default {
|
|||
this.contain = true;
|
||||
}
|
||||
this.src = url;
|
||||
if(this.$refs.imageRef) {
|
||||
this.initMessageOutImageHammerJs(this.$refs.imageRef);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to fetch thumbnail: ", err);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<div class="message">
|
||||
<i v-if="event.isRedacted()" class="deleted-text">
|
||||
<v-icon size="small">block</v-icon>
|
||||
{{ $t('message.outgoing_message_deleted_text')}}
|
||||
{{ redactedBySomeoneElse(event) ? $t('message.incoming_message_deleted_text') : $t('message.outgoing_message_deleted_text')}}
|
||||
</i>
|
||||
<span v-html="linkify($sanitize(messageText))" v-else/>
|
||||
<span class="edit-marker" v-if="event.replacingEventId() && !event.isRedacted()">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners" v-if="items.length > 1">
|
||||
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners" v-if="items.length > 1 || event.isRedacted()">
|
||||
<div class="bubble">
|
||||
<div class="original-message" v-if="inReplyToText">
|
||||
<div class="original-message-sender">{{ inReplyToSender }}</div>
|
||||
|
|
@ -11,16 +11,16 @@
|
|||
|
||||
|
||||
<div class="message">
|
||||
<v-container fluid class="imageCollection">
|
||||
<v-container v-if="!event.isRedacted()" fluid class="imageCollection">
|
||||
<v-row wrap>
|
||||
<v-col v-for="({ size, item }) in layoutedItems()" :key="item.event.getId()" :cols="size">
|
||||
<ThumbnailView :item="item" :previewOnly="true" v-on:itemclick="onItemClick($event)" />
|
||||
<ThumbnailView :item="item" :previewOnly="true" v-on:itemclick="onItemClick($event)" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<i v-if="event.isRedacted()" class="deleted-text">
|
||||
<v-icon size="small">block</v-icon>
|
||||
{{ $t('message.outgoing_message_deleted_text') }}
|
||||
{{ redactedBySomeoneElse(event) ? $t('message.incoming_message_deleted_text') : $t('message.outgoing_message_deleted_text')}}
|
||||
</i>
|
||||
<span v-html="linkify($sanitize(messageText))" v-else />
|
||||
<span class="edit-marker" v-if="event.replacingEventId() && !event.isRedacted()">
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
<GalleryItemsView :originalEvent="originalEvent" :items="items" :initialItem="showItem" v-if="!!showItem" v-on:close="showItem = null" />
|
||||
</message-outgoing>
|
||||
<component v-else-if="items.length == 1" :is="componentFn(items[0].event)"
|
||||
<component v-else-if="items.length == 1" :is="componentFn(items[0].event)"
|
||||
:originalEvent="items[0].event"
|
||||
v-bind="{...$props, ...$attrs}" v-on="$listeners"
|
||||
/>
|
||||
|
|
@ -54,7 +54,7 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
if (!this.thread) {
|
||||
this.event.on("Event.relationsCreated", this.onRelationsCreated);
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onRelationsCreated() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
this.event.off("Event.relationsCreated", this.onRelationsCreated);
|
||||
},
|
||||
onItemClick(event) {
|
||||
|
|
@ -73,7 +73,7 @@ export default {
|
|||
processThread() {
|
||||
this.$emit('layout-change', () => {
|
||||
this.items = this.timelineSet.relations.getAllChildEventsForEvent(this.event.getId())
|
||||
.filter(e => util.downloadableTypes().includes(e.getContent().msgtype))
|
||||
.filter(e => !e.isRedacted() && util.downloadableTypes().includes(e.getContent().msgtype))
|
||||
.map(e => {
|
||||
let ret = {
|
||||
event: e,
|
||||
|
|
@ -147,7 +147,7 @@ export default {
|
|||
.col {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
if (!this.thread) {
|
||||
this.event.on("Event.relationsCreated", this.onRelationsCreated);
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onRelationsCreated() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
this.event.off("Event.relationsCreated", this.onRelationsCreated);
|
||||
},
|
||||
processThread() {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
if (!this.thread) {
|
||||
this.event.on("Event.relationsCreated", this.onRelationsCreated);
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onRelationsCreated() {
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
|
||||
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), util.threadMessageType(), "m.room.message");
|
||||
this.event.off("Event.relationsCreated", this.onRelationsCreated);
|
||||
},
|
||||
processThread() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import * as linkify from 'linkifyjs';
|
|||
import linkifyHtml from 'linkify-html';
|
||||
import utils from "../../plugins/utils"
|
||||
import util from "../../plugins/utils";
|
||||
import Hammer from "hammerjs";
|
||||
|
||||
linkify.options.defaults.className = "link";
|
||||
linkify.options.defaults.target = { url: "_blank" };
|
||||
|
|
@ -48,10 +49,10 @@ export default {
|
|||
event: {},
|
||||
thread: null,
|
||||
utils,
|
||||
mc: null,
|
||||
mcCustom: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.thread = null;
|
||||
},
|
||||
|
|
@ -278,6 +279,15 @@ export default {
|
|||
return false;
|
||||
},
|
||||
|
||||
redactedBySomeoneElse(event) {
|
||||
if (!event.isRedacted()) return false;
|
||||
const redactionEvent = event.getUnsigned().redacted_because;
|
||||
if (redactionEvent) {
|
||||
return redactionEvent.sender !== this.$matrix.currentUserId;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
formatTimeAgo(time) {
|
||||
const date = new Date();
|
||||
date.setTime(time);
|
||||
|
|
@ -326,5 +336,22 @@ export default {
|
|||
* Override this to handle updates to (the) message thread.
|
||||
*/
|
||||
processThread() {},
|
||||
initMsgHammerJs(element) {
|
||||
this.mc = new Hammer(element);
|
||||
this.mcCustom = new Hammer.Manager(element);
|
||||
|
||||
this.mcCustom.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));
|
||||
|
||||
this.mcCustom.on("doubletap", (evt) => {
|
||||
var { top, left } = evt.target.getBoundingClientRect();
|
||||
var position = { top: `${top}px`, left: `${left}px`};
|
||||
this.$emit("addQuickHeartReaction", { position });
|
||||
});
|
||||
|
||||
this.mc.on("press", () => {
|
||||
this.showContextMenu(this.$refs.opbutton);
|
||||
});
|
||||
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export default {
|
|||
id: attachment.name,
|
||||
status: this.sendStatuses.INITIAL,
|
||||
statusDate: Date.now,
|
||||
mediaEventId: undefined,
|
||||
attachment: file,
|
||||
preview: attachment.image,
|
||||
progress: 0,
|
||||
|
|
@ -74,6 +75,7 @@ export default {
|
|||
if (item.status !== this.sendStatuses.INITIAL) {
|
||||
return getItemPromise(++index);
|
||||
}
|
||||
item.status = this.sendStatuses.SENDING;
|
||||
const itemPromise = util.sendFile(this.$matrix.matrixClient, this.room.roomId, item.attachment, ({ loaded, total }) => {
|
||||
if (loaded == total) {
|
||||
item.progress = 100;
|
||||
|
|
@ -81,7 +83,7 @@ export default {
|
|||
item.progress = 100 * loaded / total;
|
||||
}
|
||||
}, eventId)
|
||||
.then(() => {
|
||||
.then((mediaEventId) => {
|
||||
// Look at last item rotation, flipping the sign on this, so looks more like a true stack
|
||||
let signR = 1;
|
||||
let signX = 1;
|
||||
|
|
@ -100,6 +102,7 @@ export default {
|
|||
item.randomRotation = signR * (2 + Math.random() * 10);
|
||||
item.randomTranslationX = signX * Math.random() * 20;
|
||||
item.randomTranslationY = signY * Math.random() * 20;
|
||||
item.mediaEventId = mediaEventId;
|
||||
item.status = this.sendStatuses.SENT;
|
||||
item.statusDate = Date.now;
|
||||
}).catch(ignorederr => {
|
||||
|
|
@ -133,15 +136,17 @@ export default {
|
|||
});
|
||||
this.sendingStatus = this.sendStatuses.CANCELED;
|
||||
if (this.sendingRootEventId && this.room) {
|
||||
// Redact the root event.
|
||||
this.$matrix.matrixClient
|
||||
.redactEvent(this.room.roomId, this.sendingRootEventId, undefined, { reason: "cancel" })
|
||||
.then(() => {
|
||||
console.log("Message redacted");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Redaction failed: ", err);
|
||||
});
|
||||
|
||||
// Redact all media we already sent, plus the root event
|
||||
let promises = this.sendingAttachments.filter((item) => item.mediaEventId !== undefined).map((item) => this.$matrix.matrixClient.redactEvent(this.room.roomId, item.mediaEventId, undefined, { reason: "cancel" }));
|
||||
promises.push(this.$matrix.matrixClient.redactEvent(this.room.roomId, this.sendingRootEventId, undefined, { reason: "cancel" }));
|
||||
Promise.allSettled(promises)
|
||||
.then(() => {
|
||||
console.log("Message redacted");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Redaction failed: ", err);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
37
src/components/welcome_headers/WelcomeHeaderChannelUser.vue
Normal file
37
src/components/welcome_headers/WelcomeHeaderChannelUser.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="created-room-welcome-header">
|
||||
<div class="mt-2" v-if="roomMessageRetention() > 0">
|
||||
<i18n path="room_welcome.info_retention_user" tag="span">
|
||||
<template v-slot:time>
|
||||
<b>{{ messageRetentionDisplay }}</b>
|
||||
</template>
|
||||
</i18n>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import roomInfoMixin from "../roomInfoMixin";
|
||||
|
||||
export default {
|
||||
name: "WelcomeHeaderChannelUser",
|
||||
mixins: [roomInfoMixin],
|
||||
components: {
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMessageRetention(ignoredretention) {
|
||||
this.updateMessageRetention();
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
|
|
@ -33,11 +33,12 @@ const registerPeriodicBackgroundSync = async (registration) => {
|
|||
}
|
||||
}
|
||||
|
||||
export function registerServiceWorker() {
|
||||
export function registerServiceWorker(periodicSyncNewMsgReminderTxt) {
|
||||
if("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("./sw.js")
|
||||
.then(async registration => {
|
||||
console.log('Service Worker registered with scope:', registration.scope);
|
||||
registration.active.postMessage(periodicSyncNewMsgReminderTxt);
|
||||
await registerPeriodicBackgroundSync(registration);
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import ImageResize from "image-resize";
|
|||
import { AutoDiscovery } from 'matrix-js-sdk';
|
||||
import User from '../models/user';
|
||||
const prettyBytes = require("pretty-bytes");
|
||||
import Hammer from "hammerjs";
|
||||
import { Thread } from 'matrix-js-sdk/lib/models/thread';
|
||||
|
||||
export const STATE_EVENT_ROOM_DELETION_NOTICE = "im.keanu.room_deletion_notice";
|
||||
export const STATE_EVENT_ROOM_DELETED = "im.keanu.room_deleted";
|
||||
|
|
@ -66,6 +68,11 @@ class UploadPromise {
|
|||
}
|
||||
|
||||
class Util {
|
||||
|
||||
threadMessageType() {
|
||||
return Thread.hasServerSideSupport ? "m.thread" : "io.element.thread"
|
||||
}
|
||||
|
||||
getAttachmentUrlAndDuration(event) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const content = event.getContent();
|
||||
|
|
@ -398,7 +405,7 @@ class Util {
|
|||
// If thread root (an eventId) is set, add that here
|
||||
if (threadRoot) {
|
||||
messageContent["m.relates_to"] = {
|
||||
"rel_type": "m.thread",
|
||||
"rel_type": this.threadMessageType(),
|
||||
"event_id": threadRoot
|
||||
};
|
||||
}
|
||||
|
|
@ -669,7 +676,8 @@ class Util {
|
|||
name = name.slice(0, name.indexOf("."));
|
||||
name = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
const image = r(res);
|
||||
images.push({ id: res, image: image, name: "Guest " + name });
|
||||
const randomNumber = parseInt(this.randomString(4, "0123456789")).toFixed();
|
||||
images.push({ id: res, image: image, name: "Guest " + name + " " + randomNumber});
|
||||
});
|
||||
return images;
|
||||
}
|
||||
|
|
@ -1014,6 +1022,25 @@ class Util {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isMobileOrTabletBrowser() {
|
||||
// Regular expression to match common mobile and tablet browser user agent strings
|
||||
const mobileTabletPattern = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Tablet|Mobile|CriOS/i;
|
||||
const userAgent = navigator.userAgent;
|
||||
return mobileTabletPattern.test(userAgent);
|
||||
}
|
||||
|
||||
singleOrDoubleTabRecognizer(element) {
|
||||
// reference: https://codepen.io/jtangelder/pen/xxYyJQ
|
||||
const hm = new Hammer.Manager(element);
|
||||
|
||||
hm.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));
|
||||
hm.add(new Hammer.Tap({ event: 'singletap' }) );
|
||||
|
||||
hm.get('doubletap').recognizeWith('singletap');
|
||||
hm.get('singletap').requireFailure('doubletap');
|
||||
return hm
|
||||
}
|
||||
}
|
||||
export default new Util();
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const vuexPersistSessionStorage = new VuexPersist({
|
|||
const defaultUseSessionStorage = (sessionStorage.getItem(USER) != null);
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: { language: null, currentRoomId: null, auth: null, tempuser: null, useLocalStorage: !defaultUseSessionStorage },
|
||||
state: { language: null, currentRoomId: null, auth: null, tempuser: null, useLocalStorage: !defaultUseSessionStorage, globalNotification: false },
|
||||
mutations: {
|
||||
loginSuccess(state, user) {
|
||||
state.auth.status.loggedIn = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue