merge devel
This commit is contained in:
@@ -53,6 +53,7 @@ The app is currently available in the following languages:
|
||||
* Spanish
|
||||
* Thai
|
||||
* Ukrainian
|
||||
* Vietnamese
|
||||
|
||||
More translations are welcome. See [instructions](https://github.com/tinode/chat/blob/devel/docs/translations.md#webapp).
|
||||
|
||||
|
||||
+110
-3
@@ -299,6 +299,8 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100dvh;
|
||||
width: 100dvw;
|
||||
font-size: 10pt; /* 13.33 px/rem */
|
||||
color: var(--clr-text-primary);
|
||||
overflow: hidden;
|
||||
@@ -790,6 +792,14 @@ div.trusted-badge {
|
||||
height: 0.015rem;
|
||||
}
|
||||
|
||||
/* Vertical divider line */
|
||||
.divider {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
margin: 0 0.25rem;
|
||||
border-left: var(--clr-border-muted) 1.5px groove;
|
||||
}
|
||||
|
||||
.ellipsized {
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
@@ -816,6 +826,15 @@ div.trusted-badge {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.flat-button.disabled,
|
||||
.flat-button.disabled:hover,
|
||||
.flat-button.disabled:focus {
|
||||
color: var(--clr-primary-accent);
|
||||
cursor: default;
|
||||
opacity: 0.75;
|
||||
filter: grayscale(75%);
|
||||
}
|
||||
|
||||
.flat-button.faux {
|
||||
display: inline-block;
|
||||
height: 1.4em;
|
||||
@@ -1427,6 +1446,9 @@ i.search {
|
||||
font-size: 2.4rem;
|
||||
line-height: 3rem;
|
||||
}
|
||||
.contact-box .avatar-box > .checkmark.material-icons {
|
||||
line-height: unset;
|
||||
}
|
||||
.contact-box.small .avatar-box > .material-icons {
|
||||
font-size: 1.6rem;
|
||||
line-height: 2rem;
|
||||
@@ -1961,7 +1983,7 @@ img.channel {
|
||||
}
|
||||
|
||||
|
||||
#image-preview-caption-panel {
|
||||
#preview-caption-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: var(--clr-background-blue-gray-50);
|
||||
@@ -1971,15 +1993,16 @@ img.channel {
|
||||
flex-grow: 0;
|
||||
height: var(--caption-panel-height);
|
||||
padding: 1rem 0.75rem 0 0.75rem;
|
||||
font-size: 120%;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
#image-preview-caption-panel a {
|
||||
#preview-caption-panel a {
|
||||
color: var(--clr-text-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#image-preview-caption-panel a:hover {
|
||||
#preview-caption-panel a:hover {
|
||||
color: var(--clr-dark);
|
||||
}
|
||||
|
||||
@@ -2016,6 +2039,17 @@ img.channel {
|
||||
background-color: var(--clr-std-ltgray);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
.doc-card .avatar-box {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
|
||||
.doc-card .avatar-box .material-icons {
|
||||
line-height: 10rem;
|
||||
}
|
||||
|
||||
/* Video-call related styles */
|
||||
@@ -3533,6 +3567,79 @@ ul.tabbar li.active > a {
|
||||
position: relative;
|
||||
right: 0;
|
||||
}
|
||||
/* END of pinned messages */
|
||||
|
||||
/* Contact attachments */
|
||||
.contact-card {
|
||||
position: relative;
|
||||
background-color: var(--clr-background-off-white);
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 14rem;
|
||||
}
|
||||
|
||||
.contact-card .avatar-box {
|
||||
position: relative;
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
font-size: 1rem;
|
||||
margin-right: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.contact-card .contact-body {
|
||||
align-items: center;
|
||||
background-color: var(--clr-background-blue-gray-50);
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box .name {
|
||||
color: var(--clr-text-primary);
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box .org {
|
||||
color: var(--clr-text-secondary);
|
||||
font-size: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.contact-card .contact-actions {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
border-top: none;
|
||||
background-color: var(--clr-bright);
|
||||
}
|
||||
|
||||
.contact-card .contact-actions a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-card .contact-actions .divider {
|
||||
height: 1.8rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* END of contact attachments */
|
||||
|
||||
/*
|
||||
Drag-and-drop area.
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+3
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tinode-webapp",
|
||||
"description": "Tinode messenger for the web",
|
||||
"version": "0.25.1",
|
||||
"version": "0.25.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tinode/webapp.git"
|
||||
@@ -62,6 +62,7 @@
|
||||
"ru",
|
||||
"th",
|
||||
"uk",
|
||||
"vi",
|
||||
"zh",
|
||||
"zh-TW"
|
||||
],
|
||||
@@ -80,7 +81,7 @@
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-intl": "^7.0.0",
|
||||
"tinode-sdk": "^0.25.1",
|
||||
"tinode-sdk": "^0.25.2",
|
||||
"webm-duration-fix": "^1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -8,7 +8,7 @@ function writeVersion(fileName, useExport) {
|
||||
{ flag: 'w' },
|
||||
(err) => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
return console.error(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
+8
-4
@@ -10,6 +10,10 @@ const webAppChannel = (typeof BroadcastChannel == 'function') ?
|
||||
|
||||
// Basic internationalization.
|
||||
const i18n = {
|
||||
'ar': {
|
||||
'new_message': "رسالة جديدة",
|
||||
'new_chat': "محادثة جديدة",
|
||||
},
|
||||
'de': {
|
||||
'new_message': "Neue Nachricht",
|
||||
'new_chat': "Neuer Chat",
|
||||
@@ -50,6 +54,10 @@ const i18n = {
|
||||
'new_message': "Нове повідомлення",
|
||||
'new_chat': "Новий чат",
|
||||
},
|
||||
'vi': {
|
||||
'new_message': "Tin nhắn mới",
|
||||
'new_chat': "Cuộc trò chuyện mới",
|
||||
},
|
||||
'zh': {
|
||||
'new_message': "新讯息",
|
||||
'new_chat': "新聊天",
|
||||
@@ -57,10 +65,6 @@ const i18n = {
|
||||
'zh-TW': {
|
||||
'new_message': "新訊息",
|
||||
'new_chat': "新聊天",
|
||||
},
|
||||
'ar': {
|
||||
'new_message': "رسالة جديدة",
|
||||
'new_chat': "محادثة جديدة",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "ليس لديك محادثات<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "ليس لديك محادثات\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1225,7 +1225,7 @@
|
||||
"label_description": {
|
||||
"translation": "الوصف",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "غير معروف",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "حفظ",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "تعذّر تحليل ملف vCard.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "محادثة",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "بحث",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "بطاقة الاتصال",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "جهات الاتصال",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "إضافة أعضاء",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "Sie haben keine Unterhaltungen<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "Sie haben keine Unterhaltungen\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1225,7 +1225,7 @@
|
||||
"label_description": {
|
||||
"translation": "Beschreibung",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Unbekannt",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Speichern",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "vCard-Datei konnte nicht verarbeitet werden.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Chatten",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Suchen",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Kontaktkarte",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Kontakte",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "Mitglieder hinzufügen",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "You have no chats\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1225,7 +1225,7 @@
|
||||
"label_description": {
|
||||
"translation": "Description",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Unknown",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Save",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "Cannot parse vCard file.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Chat",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Find",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Contact Card",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Contacts",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "add members",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "No tienes chats<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "No tienes chats\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "Descripción",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Desconocido",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Guardar",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "No se puede analizar el archivo vCard.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Chatear",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Buscar",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Tarjeta de contacto",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Contactos",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "añadir miembros",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
"defaultMessage": "Report Conversation",
|
||||
"description": "Flat button [Report Group]"
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field"
|
||||
},
|
||||
"alias_already_taken": {
|
||||
"defaultMessage": "(already taken)",
|
||||
"description": "Error message for alias already taken"
|
||||
@@ -175,6 +179,10 @@
|
||||
"defaultMessage": "Cannot initiate file upload.",
|
||||
"description": "Generic error messagewhen attachment fails"
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed"
|
||||
},
|
||||
"change_email": {
|
||||
"defaultMessage": "Change email",
|
||||
"description": "Prompt to change current email"
|
||||
@@ -207,6 +215,10 @@
|
||||
"defaultMessage": "Ignore",
|
||||
"description": "Action [Ignore] for chat invitation."
|
||||
},
|
||||
"chat_now": {
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card"
|
||||
},
|
||||
"clear_messages_warning": {
|
||||
"defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
|
||||
"description": "Alert dialog warning when deleting all messages."
|
||||
@@ -228,7 +240,7 @@
|
||||
"description": "Name of the color schema"
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found"
|
||||
},
|
||||
"contacts_not_found_short": {
|
||||
@@ -327,6 +339,10 @@
|
||||
"defaultMessage": "The file size {size} exceeds the {limit} limit.",
|
||||
"description": "Error message when attachment is too large"
|
||||
},
|
||||
"find_user": {
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card"
|
||||
},
|
||||
"forgot_password_link": {
|
||||
"defaultMessage": "Forgot password?",
|
||||
"description": "Link to Reset password form"
|
||||
@@ -422,6 +438,14 @@
|
||||
"defaultMessage": "Theme:",
|
||||
"description": "Label for selecting color scheme (dark, light) in Settings"
|
||||
},
|
||||
"label_contact_card": {
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview"
|
||||
},
|
||||
"label_contacts": {
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information"
|
||||
},
|
||||
"label_content_type": {
|
||||
"defaultMessage": "Content type:",
|
||||
"description": "Label for file content type (mime)"
|
||||
@@ -432,7 +456,7 @@
|
||||
},
|
||||
"label_description": {
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description"
|
||||
"description": "Label for description of user or topic"
|
||||
},
|
||||
"label_file_name": {
|
||||
"defaultMessage": "File name:",
|
||||
@@ -880,6 +904,10 @@
|
||||
"defaultMessage": "Requested",
|
||||
"description": "Title for permissions"
|
||||
},
|
||||
"save_action": {
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button"
|
||||
},
|
||||
"save_attachment": {
|
||||
"defaultMessage": "save",
|
||||
"description": "Call to save an attachment"
|
||||
@@ -1056,6 +1084,10 @@
|
||||
"defaultMessage": "Freeform name of the group",
|
||||
"description": "Prompt for entering topic name"
|
||||
},
|
||||
"unknown_name": {
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing"
|
||||
},
|
||||
"unnamed_topic": {
|
||||
"defaultMessage": "Unnamed",
|
||||
"description": "Title shown when the topic has no name"
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "Vous n'avez aucune conversation<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "Vous n'avez aucune conversation\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1225,7 +1225,7 @@
|
||||
"label_description": {
|
||||
"translation": "Description",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Inconnu",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Enregistrer",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "Impossible d'analyser le fichier vCard.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Discuter",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Rechercher",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Carte de contact",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Contacts",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "ajouter des membres",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "Non hai chats<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "Non hai chats\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "Descrizione",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Sconosciuto",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Salva",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "Impossibile analizzare il file vCard.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Chatta",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Cerca",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Scheda contatto",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Contatti",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "aggiungi membri",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "채팅이없습니다<br/>¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "채팅이없습니다\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "설명",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "알 수 없음",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "저장",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "vCard 파일을 파싱할 수 없습니다.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "채팅",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "찾기",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "연락처 카드",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "연락처",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "멤버 추가",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "Nu ai contacte salvate<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "Nu ai contacte salvate\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "Descriere",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Necunoscut",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Salvează",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "Nu se poate procesa fișierul vCard.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Conversație",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Caută",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Card de contact",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Contacte",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "adaugă membri",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "Чатов нет<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "Чатов нет\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "Описание",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Неизвестный",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Сохранить",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "Не удалось обработать файл vCard.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Написать",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Найти",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Карточка контакта",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Контакты",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "добавить участников",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "ไม่พบแชท <br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "ไม่พบแชท \n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1225,7 +1225,7 @@
|
||||
"label_description": {
|
||||
"translation": "คำอธิบาย",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "ไม่ทราบ",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "บันทึก",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "ไม่สามารถอ่านไฟล์ vCard ได้",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "แชท",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "ค้นหา",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "นามบัตร",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "ผู้ติดต่อ",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "เพิ่มสมาชิก",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "Чатів немає<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "Чатів немає\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "Опис",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "Невідомий",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "Зберегти",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "Не вдалося обробити файл vCard.",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "Написати",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "Знайти",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Картка контакту",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Контакти",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "додати учасників",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+1966
File diff suppressed because it is too large
Load Diff
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "您沒有任何對話<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "您沒有任何對話\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "說明",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "未知",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "儲存",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "無法解析 vCard 檔案。",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "聊天",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "尋找",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "聯絡人卡片",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "聯絡人",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "新增成員",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+59
-3
@@ -259,8 +259,8 @@
|
||||
"obsolete": false
|
||||
},
|
||||
"contacts_not_found": {
|
||||
"translation": "你尚无会话<br />¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||
"translation": "你尚无会话\n¯∖_(ツ)_/¯",
|
||||
"defaultMessage": "You have no chats\\n¯∖_(ツ)_/¯",
|
||||
"description": "HTML message shown in ContactList when no contacts are found",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
@@ -1218,7 +1218,7 @@
|
||||
"label_description": {
|
||||
"translation": "说明",
|
||||
"defaultMessage": "Description",
|
||||
"description": "Label for editing topic description",
|
||||
"description": "Label for description of user or topic",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
@@ -1906,5 +1906,61 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "未知",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "保存",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "无法解析 vCard 文件。",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "聊天",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "查找",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "联系人卡片",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "联系人",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"add_members_prompt": {
|
||||
"translation": "添加成员",
|
||||
"defaultMessage": "add members",
|
||||
"description": "Placeholder in group members input field",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ const messageLoader = {
|
||||
'ru': _ => import('./i18n.min/ru.json'),
|
||||
'th': _ => import('./i18n.min/th.json'),
|
||||
'uk': _ => import('./i18n.min/uk.json'),
|
||||
'vi': _ => import('./i18n.min/vi.json'),
|
||||
'zh': _ => import('./i18n.min/zh.json'),
|
||||
'zh-TW': _ => import('./i18n.min/zh-TW.json')
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// File and image helper functions.
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
|
||||
// Supported image MIME types and corresponding file extensions.
|
||||
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||
@@ -305,3 +306,21 @@ export function base64ToIntArray(b64) {
|
||||
} catch (err) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function importVCard(fileOrBlob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = _ => {
|
||||
reject(reader.error);
|
||||
};
|
||||
reader.onload = _ => {
|
||||
const card = TheCard.importVCard(reader.result);
|
||||
if (!card) {
|
||||
reject(new Error("Failed to parse vCard"));
|
||||
return;
|
||||
}
|
||||
resolve(card);
|
||||
};
|
||||
reader.readAsText(fileOrBlob);
|
||||
});
|
||||
}
|
||||
|
||||
+24
-11
@@ -1,13 +1,14 @@
|
||||
import React from 'react';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { Drafty } from 'tinode-sdk';
|
||||
import { Drafty, TheCard } from 'tinode-sdk';
|
||||
|
||||
import AudioPlayer from '../widgets/audio-player.jsx'
|
||||
import CallMessage from '../widgets/call-message.jsx'
|
||||
import CallStatus from '../widgets/call-status.jsx';
|
||||
import InlineVideo from '../widgets/inline-video.jsx';
|
||||
import LazyImage from '../widgets/lazy-image.jsx'
|
||||
import LazyImage from '../widgets/lazy-image.jsx';
|
||||
import TheCardMini from '../widgets/the-card-mini.jsx';
|
||||
import UploadingImage from '../widgets/uploading-image.jsx'
|
||||
|
||||
import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO,
|
||||
@@ -48,10 +49,7 @@ const messages = defineMessages({
|
||||
// 'this' must contain:
|
||||
// viewportWidth:
|
||||
// authorizeURL:
|
||||
// onImagePreview:
|
||||
// onVideoPreview:
|
||||
// onFormButtonClick:
|
||||
// onQuoteClick:
|
||||
// onHandleClick:
|
||||
export function fullFormatter(style, data, values, key, stack) {
|
||||
if (stack.includes('QQ')) {
|
||||
return quoteFormatter.call(this, style, data, values, key);
|
||||
@@ -100,7 +98,7 @@ export function fullFormatter(style, data, values, key, stack) {
|
||||
break;
|
||||
case 'BN':
|
||||
// Button
|
||||
attr.onClick = this.onFormButtonClick;
|
||||
attr.onClick = (e) => this.onHandleClick(e, 'form_button');
|
||||
let inner = React.Children.map(values, (child) => {
|
||||
return typeof child == 'string' ? child : undefined;
|
||||
});
|
||||
@@ -133,7 +131,16 @@ export function fullFormatter(style, data, values, key, stack) {
|
||||
case 'QQ':
|
||||
// Quote/citation.
|
||||
attr.className = 'reply-quote'
|
||||
attr.onClick = this.onQuoteClick;
|
||||
attr.onClick = (e) => this.onHandleClick(e, 'quote');
|
||||
break;
|
||||
case 'TC':
|
||||
// TheCard
|
||||
el = TheCardMini;
|
||||
attr.content = data;
|
||||
attr.authorizeURL = this.authorizeURL;
|
||||
attr.onChatClick = (e) => this.onHandleClick(e, 'contact_chat');
|
||||
attr.onFindClick = (e) => this.onHandleClick(e, 'contact_find');
|
||||
values = null;
|
||||
break;
|
||||
case 'VC':
|
||||
el = CallMessage;
|
||||
@@ -200,7 +207,7 @@ function handleImageData(el, data, attr) {
|
||||
if (attr.src) {
|
||||
if (Math.max(data.width || 0, data.height || 0) > IMAGE_THUMBNAIL_DIM) {
|
||||
// Allow previews for large enough images.
|
||||
attr.onClick = this.onImagePreview;
|
||||
attr.onClick = (e) => this.onHandleClick(e, 'image');
|
||||
attr.className += ' image-clickable';
|
||||
}
|
||||
attr.loading = 'lazy';
|
||||
@@ -241,7 +248,7 @@ function handleVideoData(el, data, attr) {
|
||||
attr.src = this.authorizeURL(sanitizeUrlForMime(attr.src, 'image'));
|
||||
attr.alt = data.name;
|
||||
if (data.ref || data.val) {
|
||||
attr.onClick = this.onVideoPreview;
|
||||
attr.onClick = (e) => this.onHandleClick(e, 'video');
|
||||
attr.loading = 'lazy';
|
||||
}
|
||||
el = InlineVideo;
|
||||
@@ -329,6 +336,12 @@ export function previewFormatter(style, data, values, key) {
|
||||
el = null;
|
||||
values = null;
|
||||
break;
|
||||
case 'TC':
|
||||
// TheCard
|
||||
el = React.Fragment;
|
||||
values = [<i key="tc" className="material-icons">contact_mail</i>, ' ',
|
||||
TheCard.getFn(data) || this.formatMessage(messages.drafty_unknown)];
|
||||
break;
|
||||
case 'VD':
|
||||
// Replace image with '[icon] Video'.
|
||||
el = React.Fragment;
|
||||
@@ -392,7 +405,7 @@ function inlineVideoAttr(attr, data) {
|
||||
// formatMessage: this.props.intl.formatMessage
|
||||
// messages: formatjs messages defined with defineMessages.
|
||||
// authorizeURL: this.props.tinode.authorizeURL
|
||||
// onQuoteClick: this.handleQuoteClick (optional)
|
||||
// onHandleClick: this.handleDraftyClick
|
||||
function quoteFormatter(style, data, values, key) {
|
||||
if (['BR', 'EX', 'IM', 'MN', 'VD'].includes(style)) {
|
||||
let el = Drafty.tagName(style);
|
||||
|
||||
@@ -55,6 +55,17 @@ export default class HashNavigation {
|
||||
return HashNavigation.composeUrlHash(parsed.path, parsed.params);
|
||||
}
|
||||
|
||||
static removeUrlParams(hash, keys) {
|
||||
if (!Array.isArray(keys)) {
|
||||
keys = [keys];
|
||||
}
|
||||
const parsed = HashNavigation.parseUrlHash(hash);
|
||||
for (const key of keys) {
|
||||
delete parsed.params[key];
|
||||
}
|
||||
return HashNavigation.composeUrlHash(parsed.path, parsed.params);
|
||||
}
|
||||
|
||||
static setUrlSidePanel(hash, sidepanel) {
|
||||
const parsed = HashNavigation.parseUrlHash(hash);
|
||||
parsed.path[0] = sidepanel;
|
||||
|
||||
@@ -254,3 +254,23 @@ export function defaultWallpaper() {
|
||||
const wp = wallpaperIndex[WALLPAPER_DEFAULTS.type][WALLPAPER_DEFAULTS.index];
|
||||
return {name: `../${WALLPAPER_DEFAULTS.path}${wp.name}`, size: wp.size};
|
||||
}
|
||||
|
||||
export function wallpaperNameFromUrl(wallpaperUrl) {
|
||||
// Get filename from URL.
|
||||
const parts = wallpaperUrl.split('/');
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
export function wallpaperTypeFromUrl(wallpaperUrl) {
|
||||
// Get filename from URL.
|
||||
const fname = wallpaperNameFromUrl(wallpaperUrl);
|
||||
const wallpaperIndex = require('../../img/bkg/index.json');
|
||||
for (const type in wallpaperIndex) {
|
||||
for (let i = 0; i < wallpaperIndex[type].length; i++) {
|
||||
if (wallpaperIndex[type][i].name == fname) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
// This is a generated file. Don't edit.
|
||||
|
||||
export const PACKAGE_VERSION = "0.25.1";
|
||||
export const PACKAGE_VERSION = "0.25.2";
|
||||
|
||||
@@ -138,7 +138,7 @@ export default class AccountSettingsView extends React.Component {
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
description="Label for description of user or topic" />
|
||||
</label>
|
||||
<div className="quoted">{this.state.description}</div>
|
||||
</div> : null}
|
||||
|
||||
@@ -91,7 +91,7 @@ export default class ContactsView extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FormattedMessage id="contacts_not_found"
|
||||
defaultMessage="You have no chats<br />¯∖_(ツ)_/¯"
|
||||
defaultMessage="You have no chats\n¯∖_(ツ)_/¯"
|
||||
description="HTML message shown in ContactList when no contacts are found">{
|
||||
(no_contacts) => <ContactList
|
||||
tinode={this.props.tinode}
|
||||
|
||||
+13
-4
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage, FormattedNumber, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { AccessMode, Tinode } from 'tinode-sdk';
|
||||
import { AccessMode, Tinode, TheCard } from 'tinode-sdk';
|
||||
|
||||
import AvatarUpload from '../widgets/avatar-upload.jsx';
|
||||
import BadgeList from '../widgets/badge-list.jsx';
|
||||
@@ -20,7 +20,6 @@ import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH,
|
||||
NO_ACCESS_MODE, TOAST_DURATION } from '../config.js';
|
||||
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { theCard } from '../lib/utils.js';
|
||||
import { truncateString } from '../lib/strformat.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -147,6 +146,7 @@ class InfoView extends React.Component {
|
||||
this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
|
||||
this.handleShowQRCode = this.handleShowQRCode.bind(this);
|
||||
this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this);
|
||||
this.handleShare = this.handleShare.bind(this);
|
||||
this.handleShowAddMembers = this.handleShowAddMembers.bind(this);
|
||||
this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
|
||||
this.handleMemberSelected = this.handleMemberSelected.bind(this);
|
||||
@@ -299,7 +299,7 @@ class InfoView extends React.Component {
|
||||
|
||||
handleImageChanged(mime, img) {
|
||||
this.setState({avatar: img});
|
||||
this.props.onTopicDescUpdate(this.props.topic, theCard(null, img || Tinode.DEL_CHAR), null);
|
||||
this.props.onTopicDescUpdate(this.props.topic, new TheCard(null, img || Tinode.DEL_CHAR), null);
|
||||
}
|
||||
|
||||
handleMuted(ignored, checked) {
|
||||
@@ -421,7 +421,13 @@ class InfoView extends React.Component {
|
||||
this.setState({toast: this.props.intl.formatMessage(messages.text_copied)});
|
||||
setTimeout(_ => { this.setState({toast: ''}); }, TOAST_DURATION);
|
||||
});
|
||||
}
|
||||
|
||||
handleShare(e) {
|
||||
e.preventDefault();
|
||||
const card = new TheCard(this.state.fullName, this.state.avatar, null, this.state.description);
|
||||
TheCard.setTinodeID(card, Tinode.URI_TOPIC_ID_PREFIX + this.props.topic);
|
||||
this.props.onShareTheCard(card);
|
||||
}
|
||||
|
||||
handleShowAddMembers(e) {
|
||||
@@ -629,6 +635,9 @@ class InfoView extends React.Component {
|
||||
<a href="#" onClick={this.handleShowQRCode}>
|
||||
<i className="material-icons">qr_code</i>
|
||||
</a>
|
||||
<a href="#" onClick={this.handleShare}>
|
||||
<i className="material-icons">share</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{alias ?
|
||||
@@ -673,7 +682,7 @@ class InfoView extends React.Component {
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
description="Label for description of user or topic" />
|
||||
</label>
|
||||
<div>{this.state.description}</div>
|
||||
</div> : null}
|
||||
|
||||
+109
-26
@@ -1,17 +1,17 @@
|
||||
// Panel with a chat.
|
||||
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { Drafty, Tinode } from 'tinode-sdk';
|
||||
import { Drafty, Tinode, TheCard } from 'tinode-sdk';
|
||||
|
||||
import CallPanel from '../widgets/call-panel.jsx';
|
||||
const CallPanel = React.lazy(_ => import('../widgets/call-panel.jsx'));
|
||||
import ChatMessage from '../widgets/chat-message.jsx';
|
||||
import ContactBadges from '../widgets/contact-badges.jsx';
|
||||
import DocPreview from '../widgets/doc-preview.jsx';
|
||||
const DocPreview = React.lazy(_ => import('../widgets/doc-preview.jsx'));
|
||||
import ErrorPanel from '../widgets/error-panel.jsx';
|
||||
import GroupSubs from '../widgets/group-subs.jsx';
|
||||
import ImagePreview from '../widgets/image-preview.jsx';
|
||||
const ImagePreview = React.lazy(_ => import('../widgets/image-preview.jsx'));
|
||||
import Invitation from '../widgets/invitation.jsx';
|
||||
import LetterTile from '../widgets/letter-tile.jsx';
|
||||
import LoadSpinner from '../widgets/load-spinner.jsx';
|
||||
@@ -19,13 +19,14 @@ import LogoView from './logo-view.jsx';
|
||||
import MetaMessage from '../widgets/meta-message.jsx';
|
||||
import PinnedMessages from '../widgets/pinned-messages.jsx';
|
||||
import SendMessage from '../widgets/send-message.jsx';
|
||||
import VideoPreview from '../widgets/video-preview.jsx';
|
||||
const TheCardPreview = React.lazy(_ => import('../widgets/the-card-preview.jsx'));
|
||||
const VideoPreview = React.lazy(_ => import('../widgets/video-preview.jsx'));
|
||||
|
||||
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
|
||||
DRAFTY_FR_MIME_TYPE_LEGACY, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM,
|
||||
MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY, QUOTED_REPLY_LENGTH, VIDEO_PREVIEW_DIM } from '../config.js';
|
||||
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
||||
import { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { blobToBase64, fileToBase64, imageScaled, importVCard, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/strformat.js';
|
||||
|
||||
@@ -104,6 +105,11 @@ const messages = defineMessages({
|
||||
'other {{count, number} members}}',
|
||||
description: 'Count of group topic members'
|
||||
},
|
||||
cannot_parse_vcard: {
|
||||
id: 'cannot_parse_vcard',
|
||||
defaultMessage: 'Cannot parse vCard file.',
|
||||
description: 'Error message when vCard file cannot be parsed'
|
||||
}
|
||||
});
|
||||
|
||||
// Checks if the access permissions are granted but not yet accepted.
|
||||
@@ -144,6 +150,7 @@ class MessagesView extends React.Component {
|
||||
this.sendVideoAttachment = this.sendVideoAttachment.bind(this);
|
||||
this.sendFileAttachment = this.sendFileAttachment.bind(this);
|
||||
this.sendAudioAttachment = this.sendAudioAttachment.bind(this);
|
||||
this.sendTheCardAttachment = this.sendTheCardAttachment.bind(this);
|
||||
this.sendKeyPress = this.sendKeyPress.bind(this);
|
||||
this.subscribe = this.subscribe.bind(this);
|
||||
this.handleScrollReference = this.handleScrollReference.bind(this);
|
||||
@@ -319,6 +326,9 @@ class MessagesView extends React.Component {
|
||||
} else if (topic.isSubscribed() && this.state.isReader && !prevState.isReader) {
|
||||
// If reader status has changed and data became available.
|
||||
topic.getMeta(topic.startMetaQuery().withLaterData().build());
|
||||
} else if (!topic.isSubscribed() && this.props.ready && !prevProps.ready) {
|
||||
// Connection restored (e.g. wake from sleep): resubscribe to the currently open topic.
|
||||
this.subscribe(topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -383,11 +393,21 @@ class MessagesView extends React.Component {
|
||||
};
|
||||
|
||||
if (nextProps.forwardMessage) {
|
||||
if (nextProps.forwardMessage.content) {
|
||||
const content = nextProps.forwardMessage.content;
|
||||
const size = content?.length || (content?.size || 0);
|
||||
nextState.docPreview = {
|
||||
object: content,
|
||||
type: nextProps.forwardMessage.type,
|
||||
size: size
|
||||
};
|
||||
} else {
|
||||
// We are forwarding a message. Show preview.
|
||||
nextState.reply = {
|
||||
content: nextProps.forwardMessage.preview,
|
||||
seq: null
|
||||
};
|
||||
}
|
||||
} else {
|
||||
nextState.reply = null;
|
||||
}
|
||||
@@ -901,7 +921,14 @@ class MessagesView extends React.Component {
|
||||
if (this.state.videoPreview && this.state.videoPreview.url) {
|
||||
URL.revokeObjectURL(this.state.videoPreview.url);
|
||||
}
|
||||
this.setState({ imagePostview: null, imagePreview: null, docPreview: null, videoPreview: null, videoPostview: null});
|
||||
this.setState({
|
||||
imagePostview: null,
|
||||
imagePreview: null,
|
||||
docPreview: null,
|
||||
videoPreview: null,
|
||||
videoPostview: null,
|
||||
});
|
||||
this.props.onCancelForwardMessage();
|
||||
}
|
||||
|
||||
handleFormResponse(action, text, data) {
|
||||
@@ -1001,7 +1028,7 @@ class MessagesView extends React.Component {
|
||||
// sendMessage sends the message with an optional subscription to topic first.
|
||||
sendMessage(msg, uploadCompletionPromise, uploader) {
|
||||
let head;
|
||||
if (this.props.forwardMessage) {
|
||||
if (!msg && this.props.forwardMessage) {
|
||||
// We are forwarding a message.
|
||||
msg = this.props.forwardMessage.msg;
|
||||
head = this.props.forwardMessage.head;
|
||||
@@ -1039,7 +1066,7 @@ class MessagesView extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
// Send attachment as Drafty message:
|
||||
// sendFileAttachment sends the file as Drafty message:
|
||||
// - if file is too large, upload it and send a s link.
|
||||
// - if file is small enough, just send it in-band.
|
||||
sendFileAttachment(file) {
|
||||
@@ -1047,6 +1074,15 @@ class MessagesView extends React.Component {
|
||||
const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
|
||||
MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
|
||||
|
||||
if (TheCard.isFileSupported(file.type, file.name) || file.type == TheCard.contentType) {
|
||||
// This is a vCard file, try to convert and send as TheCard.
|
||||
if (this.sendTheCardAttachment(file, maxInbandAttachmentSize)) {
|
||||
// Successfully sent as TheCard.
|
||||
return;
|
||||
}
|
||||
// Sending as TheCard failed, fall through to sending as generic file attachment.
|
||||
}
|
||||
|
||||
// If the attachment is a JSON file, then use 'application/octet-stream' instead of 'application/json'.
|
||||
// This is a temporary workaround for the collision with the 'application/json' MIME type of form responses.
|
||||
// Remove this code in 2026 or so.
|
||||
@@ -1081,6 +1117,29 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert attached vCard to TheCard and send.
|
||||
sendTheCardAttachment(file, maxInbandAttachmentSize) {
|
||||
if (file.size > maxInbandAttachmentSize) {
|
||||
// Cannot convert to TheCard, send as a generic file attachment instead.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.type == TheCard.contentType) {
|
||||
// Already TheCard, no need to convert.
|
||||
this.sendMessage(Drafty.appendTheCard(null, file.object));
|
||||
return true;
|
||||
}
|
||||
|
||||
importVCard(file)
|
||||
.then(card => {
|
||||
this.sendMessage(Drafty.appendTheCard(null, card));
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_parse_vcard, {error: err.message}), 'err');
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// handleAttachFile method is called when [Attach file] button is clicked: launch attachment preview.
|
||||
handleAttachFile(file) {
|
||||
const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);
|
||||
@@ -1488,7 +1547,9 @@ class MessagesView extends React.Component {
|
||||
|
||||
// P2P call is an overlay, not a separate component because it cannot be
|
||||
// re-mounted between full screen/minimized.
|
||||
const overlay = this.state.rtcPanel ? (
|
||||
const overlay = this.state.rtcPanel &&
|
||||
<Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<CallPanel
|
||||
topic={this.state.topic}
|
||||
seq={this.props.callSeq}
|
||||
@@ -1498,7 +1559,6 @@ class MessagesView extends React.Component {
|
||||
title={this.state.title}
|
||||
avatar={this.state.avatar || true}
|
||||
minimized={this.state.minimizedCallPanel}
|
||||
|
||||
onError={this.props.onError}
|
||||
onHangup={this.handleCallHangup}
|
||||
onToggleMinimize={this.handleCallPanelToggle}
|
||||
@@ -1506,20 +1566,21 @@ class MessagesView extends React.Component {
|
||||
onSendOffer={this.props.onCallSendOffer}
|
||||
onIceCandidate={this.props.onCallIceCandidate}
|
||||
onSendAnswer={this.props.onCallSendAnswer} />
|
||||
) : null;
|
||||
</Suspense>;
|
||||
|
||||
let component;
|
||||
if (!this.state.topic) {
|
||||
component = (
|
||||
<LogoView
|
||||
component = <LogoView
|
||||
serverVersion={this.props.serverVersion}
|
||||
serverAddress={this.props.serverAddress} />
|
||||
);
|
||||
serverAddress={this.props.serverAddress} />;
|
||||
|
||||
} else {
|
||||
let component2;
|
||||
if (this.state.imagePreview) {
|
||||
// Preview image before sending.
|
||||
component2 = (
|
||||
<Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<ImagePreview
|
||||
content={this.state.imagePreview}
|
||||
tinode={this.props.tinode}
|
||||
@@ -1527,10 +1588,12 @@ class MessagesView extends React.Component {
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendImageAttachment} />
|
||||
</Suspense>
|
||||
);
|
||||
} else if (this.state.videoPreview) {
|
||||
// Preview video.
|
||||
component2 = (
|
||||
component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<VideoPreview
|
||||
content={this.state.videoPreview}
|
||||
tinode={this.props.tinode}
|
||||
@@ -1539,26 +1602,45 @@ class MessagesView extends React.Component {
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendVideoAttachment} />
|
||||
);
|
||||
</Suspense>;
|
||||
|
||||
} else if (this.state.imagePostview) {
|
||||
// Expand received image.
|
||||
component2 = (
|
||||
component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<ImagePreview
|
||||
content={this.state.imagePostview}
|
||||
onClose={this.handleClosePreview} />
|
||||
);
|
||||
</Suspense>;
|
||||
|
||||
} else if (this.state.videoPostview) {
|
||||
// Play received video.
|
||||
component2 = (
|
||||
component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<VideoPreview
|
||||
content={this.state.videoPostview}
|
||||
tinode={this.props.tinode}
|
||||
onError={this.props.onError}
|
||||
onClose={this.handleClosePreview} />
|
||||
);
|
||||
</Suspense>;
|
||||
|
||||
} else if (this.state.docPreview) {
|
||||
// Preview attachment before sending.
|
||||
component2 = (
|
||||
if (TheCard.isFileSupported(this.state.docPreview.type, this.state.docPreview.name) ||
|
||||
this.state.docPreview.type == TheCard.contentType) {
|
||||
component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<TheCardPreview
|
||||
content={this.state.docPreview}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.state.reply}
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendFileAttachment} />
|
||||
</Suspense>;
|
||||
} else {
|
||||
component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<DocPreview
|
||||
content={this.state.docPreview}
|
||||
tinode={this.props.tinode}
|
||||
@@ -1566,7 +1648,8 @@ class MessagesView extends React.Component {
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendFileAttachment} />
|
||||
);
|
||||
</Suspense>;
|
||||
}
|
||||
} else {
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
const isChannel = topic.isChannelType() || topic.chan;
|
||||
@@ -1777,7 +1860,7 @@ class MessagesView extends React.Component {
|
||||
null}
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={avatar}
|
||||
topic={this.state.topic}
|
||||
title={this.state.title}
|
||||
|
||||
@@ -34,9 +34,13 @@ class NewTopicView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const urlParams = HashNavigation.parseUrlHash(window.location.hash).params;
|
||||
const initialTab = urlParams['tab'] || 'find';
|
||||
const initialQuery = initialTab == 'find' ? (urlParams['q'] || null) : null;
|
||||
|
||||
this.state = {
|
||||
tabSelected: 'find',
|
||||
searchQuery: null
|
||||
tabSelected: initialTab,
|
||||
searchQuery: initialQuery
|
||||
};
|
||||
|
||||
this.handleTabClick = this.handleTabClick.bind(this);
|
||||
@@ -83,19 +87,19 @@ class NewTopicView extends React.Component {
|
||||
|
||||
handleSearchResultSelected(topicName) {
|
||||
if (this.state.tabSelected == 'find') {
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParams(window.location.hash, ['tab', 'q']));
|
||||
this.props.onCreateTopic(topicName);
|
||||
}
|
||||
}
|
||||
|
||||
handleNewGroupSubmit(name, description, dataUrl, priv, tags, isChannel) {
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParams(window.location.hash, ['tab', 'q']));
|
||||
this.props.onCreateTopic(undefined,
|
||||
{public: theCard(name, dataUrl, null, description), private: priv, tags: tags}, isChannel);
|
||||
}
|
||||
|
||||
handleGroupByID(topicName) {
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParam(window.location.hash, 'tab'));
|
||||
HashNavigation.navigateTo(HashNavigation.removeUrlParams(window.location.hash, ['tab', 'q']));
|
||||
this.props.onCreateTopic(topicName);
|
||||
}
|
||||
|
||||
@@ -131,14 +135,17 @@ class NewTopicView extends React.Component {
|
||||
tinode={this.props.tinode}
|
||||
onSubmit={this.handleNewGroupSubmit}
|
||||
onError={this.props.onError} /> :
|
||||
|
||||
this.state.tabSelected === 'byid' ?
|
||||
<NewTopicById
|
||||
myURI={Tinode.URI_TOPIC_ID_PREFIX + this.props.tinode.getCurrentUserID()}
|
||||
onSubmit={this.handleGroupByID}
|
||||
onError={this.props.onError} /> :
|
||||
|
||||
<div className="flex-column">
|
||||
<SearchContacts
|
||||
placeholder={search_placeholder}
|
||||
initialQuery={this.state.searchQuery || ''}
|
||||
onSearchContacts={this.handleSearchContacts} />
|
||||
<ContactList
|
||||
tinode={this.props.tinode}
|
||||
|
||||
@@ -254,6 +254,9 @@ class SidepanelView extends React.PureComponent {
|
||||
<Suspense fallback={<div className="panel-form-row"><FormattedMessage id="loading_note"
|
||||
defaultMessage="Loading..." description="Message shown when component is loading"/></div>}>
|
||||
<WallpapersView
|
||||
wallpaper={this.props.wallpaper}
|
||||
wallpaperSize={this.props.wallpaperSize}
|
||||
wallpaperBlur={this.props.wallpaperBlur}
|
||||
colorSchema={this.props.colorSchema !== 'auto' ? this.props.colorSchema : this.props.systemColorSchema}
|
||||
onWallpaperSelected={this.props.onWallpaperSelected}
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,7 @@ import CallIncoming from '../widgets/call-incoming.jsx';
|
||||
const PhoneCountrySelector = React.lazy(_ => import('../widgets/phone-country-selector.jsx'));
|
||||
|
||||
const InfoView = React.lazy(_ => import('./info-view.jsx'));
|
||||
import MessagesView from './messages-view.jsx';
|
||||
const MessagesView = React.lazy(_ => import('./messages-view.jsx'));
|
||||
import SidepanelView from './sidepanel-view.jsx';
|
||||
|
||||
import { API_KEY, APP_NAME, DEFAULT_COLOR_SCHEME, DEFAULT_P2P_ACCESS_MODE, DEFAULT_TEXT_SIZE, FORWARDED_PREVIEW_LENGTH,
|
||||
@@ -108,6 +108,7 @@ class TinodeWeb extends React.Component {
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleHashRoute = this.handleHashRoute.bind(this);
|
||||
this.handleOnline = this.handleOnline.bind(this);
|
||||
this.handleEscapeKey = this.handleEscapeKey.bind(this);
|
||||
this.handleColorSchemeChange = this.handleColorSchemeChange.bind(this);
|
||||
this.checkForAppUpdate = this.checkForAppUpdate.bind(this);
|
||||
this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);
|
||||
@@ -165,6 +166,7 @@ class TinodeWeb extends React.Component {
|
||||
this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);
|
||||
this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this);
|
||||
this.handleReportTopic = this.handleReportTopic.bind(this);
|
||||
this.handleShareTheCard = this.handleShareTheCard.bind(this);
|
||||
this.handleShowContextMenu = this.handleShowContextMenu.bind(this);
|
||||
this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this);
|
||||
this.handleHideContextMenu = this.handleHideContextMenu.bind(this);
|
||||
@@ -319,6 +321,9 @@ class TinodeWeb extends React.Component {
|
||||
// Window/tab visible or invisible for pausing timers.
|
||||
document.addEventListener('visibilitychange', this.handleVisibilityEvent);
|
||||
|
||||
// Listen for Escape key to clear selections.
|
||||
document.addEventListener('keydown', this.handleEscapeKey);
|
||||
|
||||
document.documentElement.style.colorScheme = this.state.colorSchema == 'auto' ? 'light dark' : this.state.colorSchema;
|
||||
document.documentElement.style.setProperty('--message-text-size', `${this.state.textSize}pt`);
|
||||
|
||||
@@ -407,6 +412,7 @@ class TinodeWeb extends React.Component {
|
||||
window.removeEventListener('offline', this.handleOnlineOff);
|
||||
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.handleColorSchemeChange);
|
||||
document.removeEventListener('visibilitychange', this.handleVisibilityEvent);
|
||||
document.removeEventListener('keydown', this.handleEscapeKey);
|
||||
|
||||
clearInterval(this.wakeUpTicker);
|
||||
}
|
||||
@@ -516,6 +522,27 @@ class TinodeWeb extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleEscapeKey(e) {
|
||||
if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
// Clear selections and navigate to base state
|
||||
this.setState({
|
||||
topicSelected: null,
|
||||
errorText: '',
|
||||
errorLevel: null,
|
||||
mobilePanel: 'sidepanel',
|
||||
topicSelectedOnline: false,
|
||||
topicSelectedAcs: null,
|
||||
infoPanel: undefined,
|
||||
forwardMessage: null,
|
||||
contextMenuVisible: false,
|
||||
forwardDialogVisible: false,
|
||||
alertVisible: false
|
||||
});
|
||||
// Navigate to contacts or login view
|
||||
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a newer version of TinodeWeb app is available at the server.
|
||||
checkForAppUpdate(reg) {
|
||||
reg.onupdatefound = _ => {
|
||||
@@ -826,7 +853,9 @@ class TinodeWeb extends React.Component {
|
||||
LocalStorageUtil.setObject('auth-token', this.tinode.getAuthToken());
|
||||
}
|
||||
|
||||
const goToTopic = this.state.requestedTopic;
|
||||
// On reconnect, requestedTopic is undefined but topicSelected may still be set.
|
||||
// Preserve the open topic so it gets restored after re-login.
|
||||
const goToTopic = this.state.requestedTopic || this.state.topicSelected;
|
||||
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
|
||||
const me = this.tinode.getMeTopic();
|
||||
me.onMetaDesc = this.tnMeMetaDesc;
|
||||
@@ -1123,7 +1152,6 @@ class TinodeWeb extends React.Component {
|
||||
// - head - head dictionary to be attached to the message
|
||||
handleSendMessage(msg, uploadCompletionPromise, uploader, head) {
|
||||
const topic = this.tinode.getTopic(this.state.topicSelected);
|
||||
/* TODO: check if return is required */
|
||||
return this.sendMessageToTopic(topic, msg, uploadCompletionPromise, uploader, head);
|
||||
}
|
||||
|
||||
@@ -1703,7 +1731,7 @@ class TinodeWeb extends React.Component {
|
||||
// If the Find panel is active, close it.
|
||||
this.handleSidepanelCancel();
|
||||
}
|
||||
const header = '➦ ' + params.userName;
|
||||
const header = params.userName ? '➦ ' + params.userName : '';
|
||||
const content = typeof params.content == 'string' ? Drafty.init(params.content) : Drafty.forwardedContent(params.content);
|
||||
const preview = Drafty.preview(content, FORWARDED_PREVIEW_LENGTH, true);
|
||||
const msg = Drafty.append(Drafty.appendLineBreak(Drafty.mention(header, params.userFrom)), content);
|
||||
@@ -1718,6 +1746,16 @@ class TinodeWeb extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
handleShareTheCard(card) {
|
||||
this.setState({
|
||||
forwardDialogVisible: true,
|
||||
forwardMessage: {
|
||||
content: card,
|
||||
type: card.contentType,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
defaultTopicContextMenu(topicName) {
|
||||
const topic = this.tinode.getTopic(topicName);
|
||||
|
||||
@@ -2179,6 +2217,9 @@ class TinodeWeb extends React.Component {
|
||||
reqCredMethod={this.state.reqCredMethod}
|
||||
textSize={this.state.textSize}
|
||||
colorSchema={this.state.colorSchema}
|
||||
wallpaper={this.state.wallpaper}
|
||||
wallpaperSize={this.state.wallpaperSize}
|
||||
wallpaperBlur={this.state.wallpaperBlur}
|
||||
systemColorSchema={this.state.systemColorSchema}
|
||||
sendOnEnter={this.state.sendOnEnter}
|
||||
|
||||
@@ -2225,6 +2266,8 @@ class TinodeWeb extends React.Component {
|
||||
: null}
|
||||
|
||||
{!this.state.displayMobile || (this.state.mobilePanel == 'topic-view' && !this.state.infoPanel) ?
|
||||
<Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||
description="Message shown when component is loading"/></div>}>
|
||||
<MessagesView
|
||||
tinode={this.tinode}
|
||||
connected={this.state.connected}
|
||||
@@ -2275,6 +2318,7 @@ class TinodeWeb extends React.Component {
|
||||
onNewChat={this.handleNewChatInvitation}
|
||||
sendMessage={this.handleSendMessage}
|
||||
onVideoCallClosed={this.handleCallClose} />
|
||||
</Suspense>
|
||||
: null}
|
||||
|
||||
{this.state.infoPanel ?
|
||||
@@ -2304,7 +2348,7 @@ class TinodeWeb extends React.Component {
|
||||
onLeaveTopic={this.handleLeaveUnsubRequest}
|
||||
onBlockTopic={this.handleBlockTopicRequest}
|
||||
onReportTopic={this.handleReportTopic}
|
||||
onAddMember={this.handleManageGroupMembers}
|
||||
onShareTheCard={this.handleShareTheCard}
|
||||
onTopicTagsUpdateRequest={this.handleTagsUpdateRequest}
|
||||
onTopicUnArchive={this.handleUnarchive}
|
||||
onInitFind={this.tnInitFind}
|
||||
|
||||
@@ -3,17 +3,20 @@ import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
import { WALLPAPER_DEFAULTS } from '../config.js';
|
||||
import { wallpaperNameFromUrl, wallpaperTypeFromUrl } from '../lib/utils.js';
|
||||
import imageIndex from '../../img/bkg/index.json';
|
||||
|
||||
export default class WallpapersView extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const type = wallpaperTypeFromUrl(this.props.wallpaper);
|
||||
const name = wallpaperNameFromUrl(this.props.wallpaper);
|
||||
this.state = {
|
||||
tab: WALLPAPER_DEFAULTS.type, // currently selected tab: 'patt' | 'img'
|
||||
selectedType: WALLPAPER_DEFAULTS.type, // type of the selected wallpaper.
|
||||
wallpaper: imageIndex[WALLPAPER_DEFAULTS.type][WALLPAPER_DEFAULTS.index].name,
|
||||
blur: 0,
|
||||
tab: type || WALLPAPER_DEFAULTS.type, // currently selected tab: 'patt' | 'img'
|
||||
selectedType: type || WALLPAPER_DEFAULTS.type, // type of the selected wallpaper.
|
||||
wallpaper: name || imageIndex[WALLPAPER_DEFAULTS.type][WALLPAPER_DEFAULTS.index].name,
|
||||
blur: this.props.wallpaperBlur | 0,
|
||||
};
|
||||
|
||||
this.blurValues = [0, 1, 2, 4, 8, 16];
|
||||
@@ -21,6 +24,7 @@ export default class WallpapersView extends React.PureComponent {
|
||||
this.handleTabClick = this.handleTabClick.bind(this);
|
||||
this.handleWallpaperSelected = this.handleWallpaperSelected.bind(this);
|
||||
this.handleBlurChanged = this.handleBlurChanged.bind(this);
|
||||
this.hasChanged = this.hasChanged.bind(this);
|
||||
}
|
||||
|
||||
handleTabClick(e) {
|
||||
@@ -32,6 +36,10 @@ export default class WallpapersView extends React.PureComponent {
|
||||
handleWallpaperSelected(e) {
|
||||
let index, type, blur
|
||||
if (!e) {
|
||||
if (!this.hasChanged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore default.
|
||||
type = WALLPAPER_DEFAULTS.type;
|
||||
index = WALLPAPER_DEFAULTS.index;
|
||||
@@ -58,11 +66,18 @@ export default class WallpapersView extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
hasChanged() {
|
||||
return this.state.selectedType != WALLPAPER_DEFAULTS.type ||
|
||||
this.state.wallpaper != imageIndex[WALLPAPER_DEFAULTS.type][WALLPAPER_DEFAULTS.index].name ||
|
||||
this.state.blur != 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="flex-column">
|
||||
<div className="panel-form-row clean-clickable">
|
||||
<a className="flat-button" onClick={() => this.handleWallpaperSelected(null)}>
|
||||
<div className={`panel-form-row${this.hasChanged() ? ' clean-clickable' : ''}`}>
|
||||
<a className={`flat-button${this.hasChanged() ? '' : ' disabled'}`}
|
||||
onClick={() => this.handleWallpaperSelected(null)}>
|
||||
<i className="material-icons">undo</i>
|
||||
<FormattedMessage id="button_restore" defaultMessage="Restore default" description="Reset setting to default"/>
|
||||
</a>
|
||||
|
||||
@@ -48,7 +48,7 @@ export default class AvatarUpload extends React.Component {
|
||||
this.props.readOnly && this.props.uid ?
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={true}
|
||||
topic={this.props.uid}
|
||||
title={this.props.title} />
|
||||
|
||||
@@ -122,7 +122,7 @@ export default class CallIncoming extends React.Component {
|
||||
<div className="caller-card incoming pulse">
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={this.state.avatar || true}
|
||||
topic={this.props.topic}
|
||||
title={this.state.fullName} />
|
||||
|
||||
@@ -346,11 +346,11 @@ class CallPanel extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleDataChannelClose(event) {
|
||||
console.log('close data channel:', event);
|
||||
console.info('close data channel:', event);
|
||||
}
|
||||
|
||||
handleDataChannelEvent(event) {
|
||||
console.log('data channel event:', event);
|
||||
console.info('data channel event:', event);
|
||||
const channel = event.channel;
|
||||
channel.onerror = this.handleDataChannelError;
|
||||
channel.onmessage = this.handleDataChannelMessage;
|
||||
@@ -680,7 +680,7 @@ class CallPanel extends React.PureComponent {
|
||||
<div className={`caller-card${pulseAnimation} draggable`}>
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={this.props.avatar}
|
||||
topic={this.props.topic}
|
||||
title={this.props.title} />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Drafty, Tinode } from 'tinode-sdk';
|
||||
import { Drafty, Tinode, TheCard } from 'tinode-sdk';
|
||||
|
||||
import Attachment from './attachment.jsx';
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
@@ -10,6 +10,7 @@ import ReceivedMarker from './received-marker.jsx'
|
||||
|
||||
import { fullFormatter } from '../lib/formatters.js';
|
||||
import { sanitizeUrl } from '../lib/utils.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
|
||||
class BaseChatMessage extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@@ -26,22 +27,59 @@ class BaseChatMessage extends React.PureComponent {
|
||||
this.handleExpandImage = this.handleExpandImage.bind(this);
|
||||
this.handlePlayVideo = this.handlePlayVideo.bind(this);
|
||||
this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
|
||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||
this.handleContextClick = this.handleContextClick.bind(this);
|
||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||
this.handleDraftyClick = this.handleDraftyClick.bind(this);
|
||||
|
||||
this.formatterContext = {
|
||||
formatMessage: props.intl.formatMessage.bind(props.intl),
|
||||
viewportWidth: props.viewportWidth,
|
||||
authorizeURL: props.tinode.authorizeURL.bind(props.tinode),
|
||||
|
||||
onImagePreview: this.handleExpandImage,
|
||||
onVideoPreview: this.handlePlayVideo,
|
||||
onFormButtonClick: this.handleFormButtonClick,
|
||||
onQuoteClick: this.handleQuoteClick
|
||||
onHandleClick: this.handleDraftyClick
|
||||
};
|
||||
}
|
||||
|
||||
handleDraftyClick = (e, action) => {
|
||||
switch (action) {
|
||||
case 'image':
|
||||
this.handleExpandImage(e);
|
||||
break;
|
||||
case 'video':
|
||||
this.handlePlayVideo(e);
|
||||
break;
|
||||
case 'form_button':
|
||||
this.handleFormButtonClick(e);
|
||||
break;
|
||||
case 'quote':
|
||||
this.handleQuoteClick(e);
|
||||
break;
|
||||
case 'contact_chat':
|
||||
e.preventDefault();
|
||||
try {
|
||||
// tinode:topic/usr123abc -> usr123abc
|
||||
const pathname = new URL(e.target.dataset.val)?.pathname;
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', parts.pop() || ''));
|
||||
} catch (error) {
|
||||
console.error("Invalid URL:", error);
|
||||
}
|
||||
break;
|
||||
case 'contact_find':
|
||||
e.preventDefault();
|
||||
let hashUrl = HashNavigation.setUrlSidePanel(window.location.hash, 'newtpk');
|
||||
hashUrl = HashNavigation.addUrlParam(hashUrl, 'q', e.target.dataset.val);
|
||||
hashUrl = HashNavigation.addUrlParam(hashUrl, 'tab', 'find');
|
||||
HashNavigation.navigateTo(hashUrl);
|
||||
break;
|
||||
default:
|
||||
// No special handling; let the browser deal with it.
|
||||
console.info('Unhandled drafty action.', action, e.target.dataset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleExpandImage(e) {
|
||||
e.preventDefault();
|
||||
this.props.onExpandMedia({
|
||||
@@ -99,7 +137,7 @@ class BaseChatMessage extends React.PureComponent {
|
||||
if (!this.props.response) {
|
||||
let immutable = false;
|
||||
Drafty.entities(this.props.content, (_0, _1, tp) => {
|
||||
immutable = ['AU', 'EX', 'FM', 'IM', 'VC', 'VD'].includes(tp);
|
||||
immutable = ['AU', 'EX', 'FM', 'IM', 'TC', 'VC', 'VD'].includes(tp);
|
||||
return immutable;
|
||||
});
|
||||
if (!immutable) {
|
||||
@@ -200,7 +238,7 @@ class BaseChatMessage extends React.PureComponent {
|
||||
<div className="avatar-box">
|
||||
{fullDisplay ?
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
topic={this.props.userFrom}
|
||||
title={this.props.userName}
|
||||
avatar={avatar} /> :
|
||||
|
||||
@@ -23,7 +23,6 @@ export default class ChipInput extends React.Component {
|
||||
|
||||
static deriveStateFromProps(props) {
|
||||
return {
|
||||
placeholder: props.chips ? '' : props.prompt,
|
||||
sortedChips: ChipInput.sortChips(props.chips, props.staticMembers),
|
||||
chipIndex: ChipInput.indexChips(props.chips)
|
||||
};
|
||||
@@ -137,7 +136,7 @@ export default class ChipInput extends React.Component {
|
||||
<div className={className}>
|
||||
{chips}
|
||||
<input type="text"
|
||||
placeholder={this.state.placeholder}
|
||||
placeholder={this.props.prompt}
|
||||
onChange={this.handleTextInput}
|
||||
onFocus={this.handleFocusGained}
|
||||
onBlur={this.handleFocusLost}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default class Chip extends React.PureComponent {
|
||||
<span className="spacer" /> :
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={this.props.avatar || true}
|
||||
topic={this.props.topic}
|
||||
title={this.props.title} />
|
||||
|
||||
@@ -25,7 +25,7 @@ const messages = defineMessages({
|
||||
}
|
||||
});
|
||||
|
||||
class ContactList extends React.Component {
|
||||
class ContactList extends React.PureComponent {
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl;
|
||||
const showCheckmark = Array.isArray(this.props.topicSelected);
|
||||
@@ -53,6 +53,8 @@ class ContactList extends React.Component {
|
||||
contactsCount ++;
|
||||
}
|
||||
if (this.props.contacts && this.props.contacts.length > 0) {
|
||||
// Used keys to prevent duplicates when the user comes both from the 'fnd' and the list of contacts.
|
||||
const usedKeys = {};
|
||||
this.props.contacts.forEach(c => {
|
||||
if (c.action) {
|
||||
// Action item
|
||||
@@ -65,6 +67,10 @@ class ContactList extends React.Component {
|
||||
} else {
|
||||
// Normal contact
|
||||
const key = this.props.showMode ? c.user : (c.topic || c.user);
|
||||
if (usedKeys[key]) {
|
||||
return;
|
||||
}
|
||||
usedKeys[key] = true;
|
||||
// If filter function is provided, filter out the items
|
||||
// which don't satisfy the condition.
|
||||
if (this.props.filterFunc && this.props.filter) {
|
||||
@@ -154,8 +160,9 @@ class ContactList extends React.Component {
|
||||
return (
|
||||
<div className={this.props.noScroll ? null : "scrollable-panel"}>
|
||||
{contactsCount == 0 ?
|
||||
<div className="center-medium-text"
|
||||
dangerouslySetInnerHTML={{__html: this.props.emptyListMessage}} />
|
||||
<div className="center-medium-text" style={{whiteSpace: 'pre-line'}}>
|
||||
{this.props.emptyListMessage}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ class Contact extends React.Component {
|
||||
<li className={bgColor} onClick={this.handleClick}>
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={avatar}
|
||||
title={this.props.title}
|
||||
topic={this.props.item}
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class DocPreview extends React.PureComponent {
|
||||
}
|
||||
return (
|
||||
<div id="image-preview">
|
||||
<div id="image-preview-caption-panel">
|
||||
<div id="preview-caption-panel">
|
||||
<span>{this.props.content.name}</span>
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,11 @@ const messages = defineMessages({
|
||||
id: 'contacts_not_found_short',
|
||||
defaultMessage: "No contacts match ''{query}''",
|
||||
description: 'Shown in ContactsView when search returned no results'
|
||||
},
|
||||
add_members_prompt: {
|
||||
id: 'add_members_prompt',
|
||||
defaultMessage: 'add members',
|
||||
description: 'Placeholder in group members input field'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,7 +171,7 @@ class GroupManager extends React.Component {
|
||||
tinode={this.props.tinode}
|
||||
chips={this.state.members}
|
||||
staticMembers={this.state.staticMembers}
|
||||
prompt="add members"
|
||||
prompt={this.props.intl.formatMessage(messages.add_members_prompt)}
|
||||
filterFunc={this.handleContactFilter}
|
||||
onChipRemoved={this.handleMemberRemoved} />
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class GroupSubs extends React.Component {
|
||||
usersOnline.push(
|
||||
<div className="avatar-box" key={sub.user}>
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
topic={sub.user}
|
||||
avatar={makeImageUrl(sub.public ? sub.public.photo : null) || true}
|
||||
title={sub.public ? sub.public.fn : null} />
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class ImagePreview extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div id="image-preview">
|
||||
<div id="image-preview-caption-panel">
|
||||
<div id="preview-caption-panel">
|
||||
{this.props.onSendMessage ?
|
||||
<span>{fname}</span>
|
||||
:
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class LetterTile extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
} else if (this.props.avatar) {
|
||||
const url = this.props.tinode.authorizeURL(sanitizeUrlForMime(this.props.avatar, 'image'));
|
||||
const url = this.props.authorizeURL(sanitizeUrlForMime(this.props.avatar, 'image'));
|
||||
// If avatar image is invalid, show a placeholder.
|
||||
const className = 'avatar' + (this.props.deleted ? ' deleted' : '');
|
||||
avatar = <img className={className} alt="avatar" src={url}
|
||||
|
||||
@@ -161,7 +161,7 @@ export default class NewTopicGroup extends React.PureComponent {
|
||||
<div className="group">
|
||||
<label className="small" htmlFor="new-topic-desc">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
description="Label for description of user or topic" />
|
||||
</label>
|
||||
<FormattedMessage id="description_editing_placeholder" defaultMessage="Description (optional)"
|
||||
description="Placeholder for editing topic description">{
|
||||
|
||||
@@ -8,7 +8,7 @@ export default class SearchContacts extends React.PureComponent {
|
||||
|
||||
this.state = {
|
||||
edited: false,
|
||||
search: ''
|
||||
search: this.props.initialQuery || ''
|
||||
};
|
||||
|
||||
this.handleSearchChange = this.handleSearchChange.bind(this);
|
||||
@@ -47,7 +47,8 @@ export default class SearchContacts extends React.PureComponent {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSearch(e);
|
||||
} else if (e.key === 'Escape') {
|
||||
this.handleClear();
|
||||
e.stopPropagation();
|
||||
this.handleClear(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export default class ShowQRCode extends React.PureComponent {
|
||||
<div className="panel-form-column">
|
||||
<br />
|
||||
<div className="qr-code" ref={this.qrCodeRef} />
|
||||
<div><tt>{this.props.uri}</tt></div>
|
||||
<div className="dialog-buttons">
|
||||
<button className="outline" onClick={this.props.onCancel}>
|
||||
<FormattedMessage id="button_ok" defaultMessage="OK" description="Button [OK]" />
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class SideNavbar extends React.PureComponent {
|
||||
{avatar ?
|
||||
<div id="self-avatar" className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={avatar}
|
||||
topic={this.props.myUserId}
|
||||
title={this.props.title} />
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
|
||||
const messages = defineMessages({
|
||||
unknown_name: {
|
||||
id: 'unknown_name',
|
||||
defaultMessage: 'Unknown',
|
||||
description: 'Name to show when the name is missing'
|
||||
}
|
||||
});
|
||||
|
||||
class TheCardMini extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleDownload = this.handleDownload.bind(this);
|
||||
this.handleMessage = this.handleMessage.bind(this);
|
||||
}
|
||||
|
||||
handleDownload(e) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const vcard = TheCard.exportVCard(this.props.content);
|
||||
const blob = new Blob([vcard], { type: 'text/vcard' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = (this.props.content.fn || 'contact') + '.vcf';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (err) {
|
||||
console.error("Failed to download vcard", err);
|
||||
if (this.props.onError) {
|
||||
this.props.onError("Failed to download vcard", 'err');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(e) {
|
||||
// This is handled by the parent component via props.onAction which expects a click event.
|
||||
}
|
||||
|
||||
render() {
|
||||
const card = this.props.content;
|
||||
if (!card) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const uid = TheCard.getFirstTinodeID(card);
|
||||
const contacts = TheCard.getEmails(card).concat(TheCard.getPhones(card));
|
||||
const org = TheCard.getOrg(card);
|
||||
return (
|
||||
<div className="contact-card">
|
||||
<div className="contact-body">
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
authorizeURL={this.props.authorizeURL}
|
||||
topic={uid || 'usr123XXX'}
|
||||
avatar={TheCard.getPhotoUrl(card) || true}
|
||||
title={card.fn} />
|
||||
</div>
|
||||
<div className="name-box">
|
||||
<div className="name">{card.fn || <FormattedMessage {...messages.unknown_name} />}</div>
|
||||
{org && <div className="org">{org}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="contact-actions">
|
||||
{uid ?
|
||||
<>
|
||||
<a className="flat-button" data-val={uid} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="chat_now" defaultMessage="Chat"
|
||||
description="Label for message button in contact card" />
|
||||
</a>
|
||||
<div className="divider"></div>
|
||||
</>
|
||||
: contacts.length > 0 ?
|
||||
<>
|
||||
<a className="flat-button" data-val={contacts.join(',')} onClick={this.props.onFindClick}>
|
||||
<FormattedMessage id="find_user" defaultMessage="Find"
|
||||
description="Label for search button in contact card" />
|
||||
</a>
|
||||
<div className="divider"></div>
|
||||
</>
|
||||
: null
|
||||
}
|
||||
<a className="flat-button" onClick={this.handleDownload}>
|
||||
<FormattedMessage id="save_action" defaultMessage="Save" description="Label for Save button" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(TheCardMini);
|
||||
@@ -0,0 +1,185 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { parsePhoneNumber } from 'libphonenumber-js/mobile';
|
||||
|
||||
import SendMessage from './send-message.jsx';
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
import { importVCard } from '../lib/blob-helpers.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
unknown_name: {
|
||||
id: 'unknown_name',
|
||||
defaultMessage: 'Unknown',
|
||||
description: 'Name to show when the name is missing'
|
||||
}
|
||||
});
|
||||
|
||||
export default class TheCardPreview extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
content: props.content.object
|
||||
};
|
||||
|
||||
if (!props.content.object) {
|
||||
importVCard(props.content.file)
|
||||
.then(card => {
|
||||
this.setState({ content: card });
|
||||
});
|
||||
}
|
||||
|
||||
this.handleSendCard = this.handleSendCard.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
e.preventDefault();
|
||||
if (e.key === 'Escape') {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleSendCard() {
|
||||
this.props.onClose();
|
||||
this.props.onSendMessage(this.props.content);
|
||||
}
|
||||
|
||||
render() {
|
||||
const card = this.state.content;
|
||||
|
||||
const fullName = TheCard.getFn(card);
|
||||
const photoUrl = TheCard.getPhotoUrl(card) || null;
|
||||
const note = card?.note;
|
||||
const org = TheCard.getOrg(card);
|
||||
|
||||
// Get communication methods
|
||||
const emails = TheCard.getComm(card, 'email') || [];
|
||||
const phones = TheCard.getComm(card, 'tel') || [];
|
||||
const tinodeIds = TheCard.getComm(card, 'tinode') || [];
|
||||
const urls = TheCard.getComm(card, 'http') || [];
|
||||
|
||||
const contacts = [];
|
||||
|
||||
// Add emails
|
||||
emails.forEach((email, idx) => {
|
||||
const types = Array.isArray(email.des) ? email.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`email-${idx}`}>
|
||||
<tt>{email.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add phones
|
||||
phones.forEach((phone, idx) => {
|
||||
let val = phone.value;
|
||||
try {
|
||||
const number = parsePhoneNumber(phone.value);
|
||||
val = number ? number.formatInternational() : phone.value;
|
||||
} catch (err) {
|
||||
// Use original value if parsing fails
|
||||
}
|
||||
const types = Array.isArray(phone.des) ? phone.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`tel-${idx}`}>
|
||||
<tt>{val}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add Tinode IDs
|
||||
tinodeIds.forEach((tid, idx) => {
|
||||
const types = Array.isArray(tid.des) ? tid.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`tinode-${idx}`}>
|
||||
<tt>{tid.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add URLs
|
||||
urls.forEach((url, idx) => {
|
||||
const types = Array.isArray(url.des) ? url.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`url-${idx}`}>
|
||||
<tt>{url.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="image-preview">
|
||||
<div id="preview-caption-panel">
|
||||
<span><FormattedMessage id="label_contact_card" defaultMessage="Contact Card"
|
||||
description="Label for contact card preview" /></span>
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}>
|
||||
<i className="material-icons gray">close</i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="image-preview-container">
|
||||
<div className="doc-card">
|
||||
<div className="panel-form-column">
|
||||
<center>
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={photoUrl || true}
|
||||
topic="usrXXX123"
|
||||
title={fullName} />
|
||||
</div>
|
||||
</center>
|
||||
<div className="group">
|
||||
<div className="large ellipsized">{fullName || <FormattedMessage {...messages.unknown_name} />}</div>
|
||||
</div>
|
||||
{org ?
|
||||
<div className="group">
|
||||
<div>{org}</div>
|
||||
</div>
|
||||
: null}
|
||||
{note ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for description of user or topic" />
|
||||
</label>
|
||||
<div className="quoted">{note}</div>
|
||||
</div>
|
||||
: null}
|
||||
{contacts.length > 0 ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_contacts" defaultMessage="Contacts"
|
||||
description="Label for contact information" />
|
||||
</label>
|
||||
{contacts}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SendMessage
|
||||
noInput={true}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.props.reply}
|
||||
onCancelReply={this.props.onCancelReply}
|
||||
onSendMessage={this.handleSendCard}
|
||||
onError={this.props.onError} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -363,7 +363,7 @@ class TopicDescEdit extends React.Component {
|
||||
<div className="group">
|
||||
<div><label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for editing topic description" />
|
||||
description="Label for description of user or topic" />
|
||||
</label></div>
|
||||
<div>
|
||||
<FormattedMessage id="description_editing_placeholder"
|
||||
|
||||
@@ -81,7 +81,7 @@ class VideoPreview extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div id="image-preview">
|
||||
<div id="image-preview-caption-panel">
|
||||
<div id="preview-caption-panel">
|
||||
<span>{this.props.content.filename}</span>
|
||||
<a href="#" onClick={e => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
|
||||
</div>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[371],{6371:function(e,t,n){n.r(t);var i=n(1594),o=n.n(i),s=n(8181),r=n(1725),a=n(8589);const l=(0,s.defineMessages)({unrecognized_video_format:{id:"unrecognized_video_format",defaultMessage:[{type:0,value:"Format of this video is not recognized"}]}});class d extends o().PureComponent{constructor(e){super(e),this.videoRef=o().createRef(),this.handleSendVideo=this.handleSendVideo.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){this.props.onSendMessage||(e.preventDefault(),"Escape"===e.key&&this.props.onClose())}handleSendVideo(e){this.props.onClose();const t={width:this.videoRef.current.videoWidth,height:this.videoRef.current.videoHeight,duration:1e3*this.videoRef.current.duration|0,mime:this.props.content.mime,name:this.props.content.filename};if(0==t.width||0==t.height)return void this.props.onError(this.props.intl.formatMessage(l.unrecognized_video_format),"err");const n=document.createElement("canvas");n.width=t.width,n.height=t.height;const i=n.getContext("2d");i.drawImage(this.videoRef.current,0,0,n.width,n.height),i.canvas.toBlob(n=>this.props.onSendMessage(e,this.props.content.blob,n,t),"image/jpeg",.75)}render(){if(!this.props.content)return null;const e=this.props.content.width||"-",t=this.props.content.height||"-",n=this.props.onSendMessage?"nodownload":"",i=!this.props.onSendMessage;return o().createElement("div",{id:"image-preview"},o().createElement("div",{id:"preview-caption-panel"},o().createElement("span",null,this.props.content.filename),o().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},o().createElement("i",{className:"material-icons gray"},"close"))),o().createElement("div",{id:"image-preview-container"},o().createElement("video",{className:"image-preview",controls:!0,controlsList:n,disablePictureInPicture:!0,ref:this.videoRef,autoPlay:i,src:this.props.tinode.authorizeURL(this.props.content.url),poster:this.props.content.preview,alt:this.props.content.filename})),this.props.onSendMessage?o().createElement(r.A,{messagePrompt:"add_image_caption",acceptBlank:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendVideo,onError:this.props.onError}):o().createElement("div",{id:"image-preview-footer"},o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))),o().createElement("div",null,o().createElement("span",{title:this.props.content.filename},this.props.content.filename))),o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))),o().createElement("div",null,this.props.content.type)),o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))),o().createElement("div",null,e," × ",t," px; ",(0,a.lX)(this.props.content.size)))))}}t.default=(0,s.injectIntl)(d)}}]);
|
||||
//# sourceMappingURL=371.prod.js.map
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[472],{4472:function(e,t,n){n.r(t),n.d(t,{default:function(){return r}});var a=n(1594),l=n.n(a),s=n(8181),o=n(1725),i=n(8589);class r extends l().PureComponent{constructor(e){super(e),this.handleSendDoc=this.handleSendDoc.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){e.preventDefault(),"Escape"===e.key&&this.props.onClose()}handleSendDoc(e){this.props.onClose(),this.props.onSendMessage(this.props.content.file)}render(){return this.props.content?l().createElement("div",{id:"image-preview"},l().createElement("div",{id:"preview-caption-panel"},l().createElement("span",null,this.props.content.name),l().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},l().createElement("i",{className:"material-icons gray"},"close"))),l().createElement("div",{id:"image-preview-container"},l().createElement("div",{className:"doc-card"},l().createElement("i",{className:"material-icons gray"},function(e){const t={default:"insert_drive_file",image:"image",text:"description",video:"theaters"};return t[e]||t[(e||"").split("/")[0]]||t.default}(this.props.content.type)),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))," ",(0,i.Tu)(this.props.content.name,24)||"-"),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))," ",this.props.content.type||"application/octet-stream"),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))," ",(0,i.lX)(this.props.content.size)))),l().createElement(o.A,{noInput:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendDoc,onError:this.props.onError})):null}}}}]);
|
||||
//# sourceMappingURL=472.prod.js.map
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,2 +1,2 @@
|
||||
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[827],{3827:function(e,t,a){a.r(t),a.d(t,{default:function(){return d}});var l=a(1594),s=a.n(l),r=a(8181),n=a(7151),i=a(9027),c=a(1781);class d extends s().PureComponent{constructor(e){super(e),this.state={tab:i.S.type,selectedType:i.S.type,wallpaper:c[i.S.type][i.S.index].name,blur:0},this.blurValues=[0,1,2,4,8,16],this.handleTabClick=this.handleTabClick.bind(this),this.handleWallpaperSelected=this.handleWallpaperSelected.bind(this),this.handleBlurChanged=this.handleBlurChanged.bind(this)}handleTabClick(e){e.preventDefault(),n.A.navigateTo(n.A.addUrlParam(window.location.hash,"tab",e.currentTarget.dataset.id)),this.setState({tab:e.currentTarget.dataset.id})}handleWallpaperSelected(e){let t,a,l;e?(e.preventDefault(),a=this.state.tab,t=e.currentTarget.dataset.id,l="patt"==a?0:this.state.blur):(a=i.S.type,t=i.S.index,l=0);const s=c[a][t].name,r="patt"==a?c[a][t].size:0;this.setState({tab:a,wallpaper:s,selectedType:a,blur:l}),this.props.onWallpaperSelected(`../${i.S.path}${s}`,r,this.blurValues[l])}handleBlurChanged(e){e.preventDefault();const t=e.currentTarget.value;this.setState({blur:t}),"img"==this.state.selectedType&&this.props.onWallpaperSelected(`../${i.S.path}${this.state.wallpaper}`,0,this.blurValues[t])}render(){return s().createElement("div",{className:"flex-column"},s().createElement("div",{className:"panel-form-row clean-clickable"},s().createElement("a",{className:"flat-button",onClick:()=>this.handleWallpaperSelected(null)},s().createElement("i",{className:"material-icons"},"undo")," ",s().createElement(r.FormattedMessage,{id:"button_restore",defaultMessage:[{type:0,value:"Restore default"}]}))),s().createElement("ul",{className:"tabbar"},s().createElement("li",{className:"patt"===this.state.tab?"active":null},s().createElement("a",{href:"#","data-id":"patt",onClick:this.handleTabClick},s().createElement(r.FormattedMessage,{id:"tabtitle_pattern",defaultMessage:[{type:0,value:"pattern"}]}))),s().createElement("li",{className:"img"===this.state.tab?"active":null},s().createElement("a",{href:"#","data-id":"img",onClick:this.handleTabClick},s().createElement(r.FormattedMessage,{id:"tabtitle_image",defaultMessage:[{type:0,value:"image"}]})))),s().createElement("div",{id:"settings-form",className:"scrollable-panel"},"patt"===this.state.tab?s().createElement("div",{className:"image-grid"},c.patt.map((e,t)=>{const a=this.state.wallpaper===e.name?" selected":"",l="dark"==this.props.colorSchema?"inverted":null;return s().createElement("div",{key:t,"data-id":t,className:`image-grid-cell${a}`,onClick:this.handleWallpaperSelected},s().createElement("img",{src:i.S.path+e.name,alt:e.name,className:l,style:{width:`${e.size}px`,height:`${e.size}px`}}))})):s().createElement(s().Fragment,null,s().createElement("div",{className:"panel-form-column"},s().createElement("div",{className:"panel-form-row"},s().createElement("label",{className:"small"},s().createElement(r.FormattedMessage,{id:"label_blur_wallpaper",defaultMessage:[{type:0,value:"Blur:"}]}))),s().createElement("div",{className:"panel-form-row"},s().createElement("input",{type:"range",id:"blur",name:"blur",min:"0",max:"5",step:"1",value:this.state.blur,onChange:this.handleBlurChanged}))),s().createElement("div",{className:"image-grid"},c.img.map((e,t)=>{const a=this.state.wallpaper===e.name?" selected":"";return s().createElement("div",{key:t,"data-id":t,className:`image-grid-cell${a}`,onClick:this.handleWallpaperSelected},s().createElement("img",{src:i.S.path+e.pr,alt:e.name,style:{width:"100%",height:"100%"}}))})))))}}}}]);
|
||||
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[827],{3827:function(e,t,a){a.r(t),a.d(t,{default:function(){return p}});var l=a(1594),s=a.n(l),r=a(8181),n=a(7151),i=a(9027),d=a(3540),c=a(1781);class p extends s().PureComponent{constructor(e){super(e);const t=(0,d.MC)(this.props.wallpaper),a=(0,d.t7)(this.props.wallpaper);this.state={tab:t||i.S.type,selectedType:t||i.S.type,wallpaper:a||c[i.S.type][i.S.index].name,blur:0|this.props.wallpaperBlur},this.blurValues=[0,1,2,4,8,16],this.handleTabClick=this.handleTabClick.bind(this),this.handleWallpaperSelected=this.handleWallpaperSelected.bind(this),this.handleBlurChanged=this.handleBlurChanged.bind(this),this.hasChanged=this.hasChanged.bind(this)}handleTabClick(e){e.preventDefault(),n.A.navigateTo(n.A.addUrlParam(window.location.hash,"tab",e.currentTarget.dataset.id)),this.setState({tab:e.currentTarget.dataset.id})}handleWallpaperSelected(e){let t,a,l;if(e)e.preventDefault(),a=this.state.tab,t=e.currentTarget.dataset.id,l="patt"==a?0:this.state.blur;else{if(!this.hasChanged())return;a=i.S.type,t=i.S.index,l=0}const s=c[a][t].name,r="patt"==a?c[a][t].size:0;this.setState({tab:a,wallpaper:s,selectedType:a,blur:l}),this.props.onWallpaperSelected(`../${i.S.path}${s}`,r,this.blurValues[l])}handleBlurChanged(e){e.preventDefault();const t=e.currentTarget.value;this.setState({blur:t}),"img"==this.state.selectedType&&this.props.onWallpaperSelected(`../${i.S.path}${this.state.wallpaper}`,0,this.blurValues[t])}hasChanged(){return this.state.selectedType!=i.S.type||this.state.wallpaper!=c[i.S.type][i.S.index].name||0!=this.state.blur}render(){return s().createElement("div",{className:"flex-column"},s().createElement("div",{className:"panel-form-row"+(this.hasChanged()?" clean-clickable":"")},s().createElement("a",{className:"flat-button"+(this.hasChanged()?"":" disabled"),onClick:()=>this.handleWallpaperSelected(null)},s().createElement("i",{className:"material-icons"},"undo")," ",s().createElement(r.FormattedMessage,{id:"button_restore",defaultMessage:[{type:0,value:"Restore default"}]}))),s().createElement("ul",{className:"tabbar"},s().createElement("li",{className:"patt"===this.state.tab?"active":null},s().createElement("a",{href:"#","data-id":"patt",onClick:this.handleTabClick},s().createElement(r.FormattedMessage,{id:"tabtitle_pattern",defaultMessage:[{type:0,value:"pattern"}]}))),s().createElement("li",{className:"img"===this.state.tab?"active":null},s().createElement("a",{href:"#","data-id":"img",onClick:this.handleTabClick},s().createElement(r.FormattedMessage,{id:"tabtitle_image",defaultMessage:[{type:0,value:"image"}]})))),s().createElement("div",{id:"settings-form",className:"scrollable-panel"},"patt"===this.state.tab?s().createElement("div",{className:"image-grid"},c.patt.map((e,t)=>{const a=this.state.wallpaper===e.name?" selected":"",l="dark"==this.props.colorSchema?"inverted":null;return s().createElement("div",{key:t,"data-id":t,className:`image-grid-cell${a}`,onClick:this.handleWallpaperSelected},s().createElement("img",{src:i.S.path+e.name,alt:e.name,className:l,style:{width:`${e.size}px`,height:`${e.size}px`}}))})):s().createElement(s().Fragment,null,s().createElement("div",{className:"panel-form-column"},s().createElement("div",{className:"panel-form-row"},s().createElement("label",{className:"small"},s().createElement(r.FormattedMessage,{id:"label_blur_wallpaper",defaultMessage:[{type:0,value:"Blur:"}]}))),s().createElement("div",{className:"panel-form-row"},s().createElement("input",{type:"range",id:"blur",name:"blur",min:"0",max:"5",step:"1",value:this.state.blur,onChange:this.handleBlurChanged}))),s().createElement("div",{className:"image-grid"},c.img.map((e,t)=>{const a=this.state.wallpaper===e.name?" selected":"";return s().createElement("div",{key:t,"data-id":t,className:`image-grid-cell${a}`,onClick:this.handleWallpaperSelected},s().createElement("img",{src:i.S.path+e.pr,alt:e.name,style:{width:"100%",height:"100%"}}))})))))}}}}]);
|
||||
//# sourceMappingURL=827.prod.js.map
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[889],{8889:function(e,t,n){n.r(t),n.d(t,{default:function(){return d}});var s=n(1594),i=n.n(s),a=n(8181),l=n(1725),o=n(9027),r=n(6110),p=n(8589),h=n(3540);class d extends i().PureComponent{constructor(e){super(e),this.state={width:0,height:0},this.handleSendImage=this.handleSendImage.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){this.props.onSendMessage||(e.preventDefault(),"Escape"===e.key&&this.props.onClose())}assignWidth(e){if(e&&!this.state.width){const t=e.getBoundingClientRect();this.setState({width:0|t.width,height:0|t.height})}}handleSendImage(e){this.props.onClose(),this.props.onSendMessage(e,this.props.content.blob)}render(){if(!this.props.content)return null;const e=(0,r.ae)(this.props.content.width,this.props.content.height,this.state.width,this.state.height,!1),t=e?{width:e.dstWidth+"px",height:e.dstHeight+"px"}:this.props.content.width>this.props.content.height?{width:"100%"}:{height:"100%"};t.maxWidth="100%",t.maxHeight="100%";const n=Math.max((this.state.width/o.JS/1.5|0)-2,12),s=(0,p.Tu)(this.props.content.filename,n)||"-",d=this.props.content.width||"-",c=this.props.content.height||"-",m=(0,h.FH)(this.props.content.url);return i().createElement("div",{id:"image-preview"},i().createElement("div",{id:"preview-caption-panel"},this.props.onSendMessage?i().createElement("span",null,s):i().createElement("a",{href:m,download:this.props.content.filename},i().createElement("i",{className:"material-icons"},"file_download")," ",i().createElement(a.FormattedMessage,{id:"download_action",defaultMessage:[{type:0,value:"download"}]})),i().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},i().createElement("i",{className:"material-icons gray"},"close"))),i().createElement("div",{id:"image-preview-container",ref:e=>this.assignWidth(e)},i().createElement("img",{src:this.props.content.url,style:t,className:"image-preview",alt:this.props.content.filename})),this.props.onSendMessage?i().createElement(l.A,{messagePrompt:"add_image_caption",acceptBlank:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendImage,onError:this.props.onError}):i().createElement("div",{id:"image-preview-footer"},i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))),i().createElement("div",null,i().createElement("span",{title:this.props.content.filename},s))),i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))),i().createElement("div",null,this.props.content.type)),i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))),i().createElement("div",null,d," × ",c," px; ",(0,p.lX)(this.props.content.size)))))}}}}]);
|
||||
//# sourceMappingURL=889.prod.js.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user