TheCard handling in messages
This commit is contained in:
@@ -790,6 +790,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;
|
||||
@@ -3784,3 +3792,71 @@ div.toast.show {
|
||||
border-radius: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
position: relative;
|
||||
background-color: var(--clr-background-off-white);
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,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 +211,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."
|
||||
@@ -327,6 +335,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"
|
||||
@@ -880,6 +892,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 +1072,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"
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,5 +1906,40 @@
|
||||
"description": "Label [Scan my ID:]",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"unknown_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Unknown",
|
||||
"description": "Name to show when the name is missing",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"save_action": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Save",
|
||||
"description": "Label for Save button",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"cannot_parse_vcard": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Cannot parse vCard file.",
|
||||
"description": "Error message when vCard file cannot be parsed",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"chat_now": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Chat",
|
||||
"description": "Label for message button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"find_user": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Find",
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
}
|
||||
}
|
||||
|
||||
+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 TheCardViewer from '../widgets/the-card-viewer.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 = TheCardViewer;
|
||||
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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React 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';
|
||||
import ChatMessage from '../widgets/chat-message.jsx';
|
||||
@@ -104,6 +104,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 +149,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);
|
||||
@@ -1039,7 +1045,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 +1053,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)) {
|
||||
// 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 +1096,26 @@ 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;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const card = TheCard.importVCard(e.target.result);
|
||||
if (!card) {
|
||||
// Failed to parse vCard.
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_parse_vcard), 'err');
|
||||
}
|
||||
this.sendMessage(Drafty.appendTheCard(null, card));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
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);
|
||||
@@ -1777,7 +1812,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}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -26,22 +26,46 @@ 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':
|
||||
case 'contact_find':
|
||||
e.preventDefault();
|
||||
console.log('Contact click handling not implemented yet.', action, e.target.dataset);
|
||||
break;
|
||||
default:
|
||||
// No special handling; let the browser deal with it.
|
||||
console.log('Unhandled drafty action.', action, e.target.dataset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleExpandImage(e) {
|
||||
e.preventDefault();
|
||||
this.props.onExpandMedia({
|
||||
@@ -99,7 +123,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 +224,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,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} />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
+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
+254
-67
@@ -7679,11 +7679,13 @@ __webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var _widgets_call_status_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../widgets/call-status.jsx */ "./src/widgets/call-status.jsx");
|
||||
/* harmony import */ var _widgets_inline_video_jsx__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../widgets/inline-video.jsx */ "./src/widgets/inline-video.jsx");
|
||||
/* harmony import */ var _widgets_lazy_image_jsx__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../widgets/lazy-image.jsx */ "./src/widgets/lazy-image.jsx");
|
||||
/* harmony import */ var _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../widgets/uploading-image.jsx */ "./src/widgets/uploading-image.jsx");
|
||||
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
|
||||
/* harmony import */ var _blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./blob-helpers.js */ "./src/lib/blob-helpers.js");
|
||||
/* harmony import */ var _strformat_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./strformat.js */ "./src/lib/strformat.js");
|
||||
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./utils.js */ "./src/lib/utils.js");
|
||||
/* harmony import */ var _widgets_the_card_viewer_jsx__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../widgets/the-card-viewer.jsx */ "./src/widgets/the-card-viewer.jsx");
|
||||
/* harmony import */ var _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../widgets/uploading-image.jsx */ "./src/widgets/uploading-image.jsx");
|
||||
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
|
||||
/* harmony import */ var _blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./blob-helpers.js */ "./src/lib/blob-helpers.js");
|
||||
/* harmony import */ var _strformat_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./strformat.js */ "./src/lib/strformat.js");
|
||||
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./utils.js */ "./src/lib/utils.js");
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7747,7 +7749,7 @@ function fullFormatter(style, data, values, key, stack) {
|
||||
switch (style) {
|
||||
case 'AU':
|
||||
if (attr.src) {
|
||||
attr.src = this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.sanitizeUrlForMime)(attr.src, 'audio'));
|
||||
attr.src = this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_13__.sanitizeUrlForMime)(attr.src, 'audio'));
|
||||
attr.duration = data.duration > 0 ? data.duration | 0 : undefined;
|
||||
attr.preview = data.preview;
|
||||
attr.loading = 'lazy';
|
||||
@@ -7772,7 +7774,7 @@ function fullFormatter(style, data, values, key, stack) {
|
||||
values = null;
|
||||
break;
|
||||
case 'BN':
|
||||
attr.onClick = this.onFormButtonClick;
|
||||
attr.onClick = e => this.onHandleClick(e, 'form_button');
|
||||
let inner = react__WEBPACK_IMPORTED_MODULE_0___default().Children.map(values, child => {
|
||||
return typeof child == 'string' ? child : undefined;
|
||||
});
|
||||
@@ -7783,13 +7785,13 @@ function fullFormatter(style, data, values, key, stack) {
|
||||
break;
|
||||
case 'LN':
|
||||
if (attr) {
|
||||
attr.href = typeof attr.href == 'string' ? (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.sanitizeUrl)(attr.href, _config_js__WEBPACK_IMPORTED_MODULE_9__.CLICKABLE_URL_SCHEMES) : '';
|
||||
attr.href = typeof attr.href == 'string' ? (0,_utils_js__WEBPACK_IMPORTED_MODULE_13__.sanitizeUrl)(attr.href, _config_js__WEBPACK_IMPORTED_MODULE_10__.CLICKABLE_URL_SCHEMES) : '';
|
||||
}
|
||||
break;
|
||||
case 'MN':
|
||||
attr.className = 'mention';
|
||||
if (data) {
|
||||
attr.className += ' ' + (0,_strformat_js__WEBPACK_IMPORTED_MODULE_11__.idToColorClass)(data.val, false, true);
|
||||
attr.className += ' ' + (0,_strformat_js__WEBPACK_IMPORTED_MODULE_12__.idToColorClass)(data.val, false, true);
|
||||
}
|
||||
break;
|
||||
case 'FM':
|
||||
@@ -7799,7 +7801,15 @@ function fullFormatter(style, data, values, key, stack) {
|
||||
break;
|
||||
case 'QQ':
|
||||
attr.className = 'reply-quote';
|
||||
attr.onClick = this.onQuoteClick;
|
||||
attr.onClick = e => this.onHandleClick(e, 'quote');
|
||||
break;
|
||||
case 'TC':
|
||||
el = _widgets_the_card_viewer_jsx__WEBPACK_IMPORTED_MODULE_8__["default"];
|
||||
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 = _widgets_call_message_jsx__WEBPACK_IMPORTED_MODULE_4__["default"];
|
||||
@@ -7843,15 +7853,15 @@ function handleImageData(el, data, attr) {
|
||||
if (!data) {
|
||||
attr.src = 'img/broken_image.png';
|
||||
attr.style = {
|
||||
width: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
height: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
width: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
height: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
};
|
||||
return el;
|
||||
}
|
||||
attr.className = 'inline-image';
|
||||
const dim = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__.fitImageSize)(data.width, data.height, this.viewportWidth > 0 ? Math.min(this.viewportWidth - _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 6.5, _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 34.5) : _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 34.5, _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 24, false) || {
|
||||
dstWidth: _config_js__WEBPACK_IMPORTED_MODULE_9__.BROKEN_IMAGE_SIZE,
|
||||
dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.BROKEN_IMAGE_SIZE
|
||||
const dim = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__.fitImageSize)(data.width, data.height, this.viewportWidth > 0 ? Math.min(this.viewportWidth - _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 6.5, _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 34.5) : _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 34.5, _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 24, false) || {
|
||||
dstWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.BROKEN_IMAGE_SIZE,
|
||||
dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.BROKEN_IMAGE_SIZE
|
||||
};
|
||||
attr.style = {
|
||||
width: dim.dstWidth + 'px',
|
||||
@@ -7860,11 +7870,11 @@ function handleImageData(el, data, attr) {
|
||||
minHeight: dim.dstHeight + 'px'
|
||||
};
|
||||
if (!tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.isProcessing(data)) {
|
||||
attr.src = this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.sanitizeUrlForMime)(attr.src, 'image'));
|
||||
attr.src = this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_13__.sanitizeUrlForMime)(attr.src, 'image'));
|
||||
attr.alt = data.name;
|
||||
if (attr.src) {
|
||||
if (Math.max(data.width || 0, data.height || 0) > _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM) {
|
||||
attr.onClick = this.onImagePreview;
|
||||
if (Math.max(data.width || 0, data.height || 0) > _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM) {
|
||||
attr.onClick = e => this.onHandleClick(e, 'image');
|
||||
attr.className += ' image-clickable';
|
||||
}
|
||||
attr.loading = 'lazy';
|
||||
@@ -7872,7 +7882,7 @@ function handleImageData(el, data, attr) {
|
||||
attr.src = null;
|
||||
}
|
||||
} else {
|
||||
el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_8__["default"];
|
||||
el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_9__["default"];
|
||||
}
|
||||
return el;
|
||||
}
|
||||
@@ -7880,15 +7890,15 @@ function handleVideoData(el, data, attr) {
|
||||
if (!data) {
|
||||
attr.src = 'img/broken_video.png';
|
||||
attr.style = {
|
||||
width: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
height: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
width: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
height: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
};
|
||||
return el;
|
||||
}
|
||||
attr.className = 'inline-image';
|
||||
const dim = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__.fitImageSize)(data.width, data.height, this.viewportWidth > 0 ? Math.min(this.viewportWidth - _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 6.5, _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 34.5) : _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 34.5, _config_js__WEBPACK_IMPORTED_MODULE_9__.REM_SIZE * 24, false) || {
|
||||
dstWidth: _config_js__WEBPACK_IMPORTED_MODULE_9__.NO_DIMENSIONS_VIDEO,
|
||||
dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.NO_DIMENSIONS_VIDEO
|
||||
const dim = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__.fitImageSize)(data.width, data.height, this.viewportWidth > 0 ? Math.min(this.viewportWidth - _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 6.5, _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 34.5) : _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 34.5, _config_js__WEBPACK_IMPORTED_MODULE_10__.REM_SIZE * 24, false) || {
|
||||
dstWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.NO_DIMENSIONS_VIDEO,
|
||||
dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.NO_DIMENSIONS_VIDEO
|
||||
};
|
||||
attr.style = {
|
||||
width: dim.dstWidth + 'px',
|
||||
@@ -7897,15 +7907,15 @@ function handleVideoData(el, data, attr) {
|
||||
minHeight: dim.dstHeight + 'px'
|
||||
};
|
||||
if (!tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.isProcessing(data)) {
|
||||
attr.src = this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.sanitizeUrlForMime)(attr.src, 'image'));
|
||||
attr.src = this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_13__.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 = _widgets_inline_video_jsx__WEBPACK_IMPORTED_MODULE_6__["default"];
|
||||
} else {
|
||||
el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_8__["default"];
|
||||
el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_9__["default"];
|
||||
}
|
||||
return el;
|
||||
}
|
||||
@@ -7923,7 +7933,7 @@ function previewFormatter(style, data, values, key) {
|
||||
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||
key: "au",
|
||||
className: "material-icons"
|
||||
}, "mic"), ' ', (0,_strformat_js__WEBPACK_IMPORTED_MODULE_11__.secondsToTime)(data.duration / 1000)];
|
||||
}, "mic"), ' ', (0,_strformat_js__WEBPACK_IMPORTED_MODULE_12__.secondsToTime)(data.duration / 1000)];
|
||||
break;
|
||||
case 'BR':
|
||||
el = (react__WEBPACK_IMPORTED_MODULE_0___default().Fragment);
|
||||
@@ -7985,6 +7995,13 @@ function previewFormatter(style, data, values, key) {
|
||||
el = null;
|
||||
values = null;
|
||||
break;
|
||||
case 'TC':
|
||||
el = (react__WEBPACK_IMPORTED_MODULE_0___default().Fragment);
|
||||
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||
key: "tc",
|
||||
className: "material-icons"
|
||||
}, "contact_mail"), ' ', tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.getFn(data) || this.formatMessage(messages.drafty_unknown)];
|
||||
break;
|
||||
case 'VD':
|
||||
el = (react__WEBPACK_IMPORTED_MODULE_0___default().Fragment);
|
||||
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||
@@ -8010,10 +8027,10 @@ function previewFormatter(style, data, values, key) {
|
||||
;
|
||||
function inlineImageAttr(attr, data) {
|
||||
attr.style = {
|
||||
width: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
height: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
width: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
height: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px',
|
||||
maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
};
|
||||
attr.className = 'inline-image';
|
||||
attr.alt = this.formatMessage(messages.drafty_image);
|
||||
@@ -8026,12 +8043,12 @@ function inlineImageAttr(attr, data) {
|
||||
return attr;
|
||||
}
|
||||
function inlineVideoAttr(attr, data) {
|
||||
const dim = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__.fitImageSize)(data.width, data.height, _config_js__WEBPACK_IMPORTED_MODULE_9__.VIDEO_THUMBNAIL_WIDTH, _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM);
|
||||
const dim = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__.fitImageSize)(data.width, data.height, _config_js__WEBPACK_IMPORTED_MODULE_10__.VIDEO_THUMBNAIL_WIDTH, _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM);
|
||||
attr.style = {
|
||||
width: dim.width + 'px',
|
||||
height: dim.height + 'px',
|
||||
maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_9__.VIDEO_THUMBNAIL_WIDTH + 'px',
|
||||
maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.VIDEO_THUMBNAIL_WIDTH + 'px',
|
||||
maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
|
||||
};
|
||||
attr.className = 'inline-image';
|
||||
attr.alt = this.formatMessage(messages.drafty_video);
|
||||
@@ -8072,7 +8089,7 @@ function quoteFormatter(style, data, values, key) {
|
||||
el = 'span';
|
||||
attr.className = 'mention';
|
||||
if (data) {
|
||||
attr.className += ' ' + (0,_strformat_js__WEBPACK_IMPORTED_MODULE_11__.idToColorClass)(data.val, false, true);
|
||||
attr.className += ' ' + (0,_strformat_js__WEBPACK_IMPORTED_MODULE_12__.idToColorClass)(data.val, false, true);
|
||||
}
|
||||
break;
|
||||
case 'EX':
|
||||
@@ -8089,7 +8106,7 @@ function quoteFormatter(style, data, values, key) {
|
||||
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||
key: "ex",
|
||||
className: "material-icons"
|
||||
}, "attachment"), (0,_strformat_js__WEBPACK_IMPORTED_MODULE_11__.shortenFileName)(fname, 16) || this.formatMessage(messages.drafty_attachment)];
|
||||
}, "attachment"), (0,_strformat_js__WEBPACK_IMPORTED_MODULE_12__.shortenFileName)(fname, 16) || this.formatMessage(messages.drafty_attachment)];
|
||||
break;
|
||||
}
|
||||
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(el, attr, values);
|
||||
@@ -8109,13 +8126,13 @@ function quoteImageOrVideo(data, isVideo) {
|
||||
ref = data.ref;
|
||||
}
|
||||
if (bits) {
|
||||
const blob = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__.base64ToBlob)(bits, mime);
|
||||
const blob = (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__.base64ToBlob)(bits, mime);
|
||||
if (!blob) {
|
||||
throw new Error("Invalid image");
|
||||
}
|
||||
promise = Promise.resolve(blob);
|
||||
} else if (ref) {
|
||||
promise = fetch(this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.sanitizeUrlForMime)(ref, 'image'))).then(evt => {
|
||||
promise = fetch(this.authorizeURL((0,_utils_js__WEBPACK_IMPORTED_MODULE_13__.sanitizeUrlForMime)(ref, 'image'))).then(evt => {
|
||||
if (evt.ok) {
|
||||
return evt.blob();
|
||||
} else {
|
||||
@@ -8126,7 +8143,7 @@ function quoteImageOrVideo(data, isVideo) {
|
||||
throw new Error("Missing image data");
|
||||
}
|
||||
return promise.then(blob => {
|
||||
return (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__.imageScaled)(blob, isVideo ? _config_js__WEBPACK_IMPORTED_MODULE_9__.VIDEO_THUMBNAIL_WIDTH : _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM, _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM, -1, !isVideo);
|
||||
return (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__.imageScaled)(blob, isVideo ? _config_js__WEBPACK_IMPORTED_MODULE_10__.VIDEO_THUMBNAIL_WIDTH : _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM, _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM, -1, !isVideo);
|
||||
}).then(scaled => {
|
||||
if (isVideo) {
|
||||
data.premime = scaled.mime;
|
||||
@@ -8139,7 +8156,7 @@ function quoteImageOrVideo(data, isVideo) {
|
||||
delete data.ref;
|
||||
delete data.preref;
|
||||
data.src = URL.createObjectURL(scaled.blob);
|
||||
return (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__.blobToBase64)(scaled.blob);
|
||||
return (0,_blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__.blobToBase64)(scaled.blob);
|
||||
}).then(b64 => {
|
||||
if (isVideo) {
|
||||
data.preview = b64.bits;
|
||||
@@ -8151,8 +8168,8 @@ function quoteImageOrVideo(data, isVideo) {
|
||||
delete data.val;
|
||||
delete data.preview;
|
||||
delete data.src;
|
||||
data.width = _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM;
|
||||
data.height = _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM;
|
||||
data.width = _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM;
|
||||
data.height = _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
@@ -8166,10 +8183,10 @@ function replyFormatter(style, data, values, key, stack) {
|
||||
}, data);
|
||||
let loadedPromise;
|
||||
try {
|
||||
loadedPromise = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.cancelablePromise)(quoteImageOrVideo.call(this, data, style == 'VD'));
|
||||
loadedPromise = (0,_utils_js__WEBPACK_IMPORTED_MODULE_13__.cancelablePromise)(quoteImageOrVideo.call(this, data, style == 'VD'));
|
||||
} catch (error) {
|
||||
console.warn("Failed to quote image:", error.message);
|
||||
loadedPromise = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.cancelablePromise)(error);
|
||||
loadedPromise = (0,_utils_js__WEBPACK_IMPORTED_MODULE_13__.cancelablePromise)(error);
|
||||
}
|
||||
attr.whenDone = loadedPromise;
|
||||
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_lazy_image_jsx__WEBPACK_IMPORTED_MODULE_7__["default"], attr, null), ' ', attr.alt];
|
||||
@@ -9771,6 +9788,13 @@ const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
||||
"offset": 0,
|
||||
"pluralType": "cardinal"
|
||||
}]
|
||||
},
|
||||
cannot_parse_vcard: {
|
||||
id: "cannot_parse_vcard",
|
||||
defaultMessage: [{
|
||||
"type": 0,
|
||||
"value": "Cannot parse vCard file."
|
||||
}]
|
||||
}
|
||||
});
|
||||
function isUnconfirmed(acs) {
|
||||
@@ -9802,6 +9826,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
||||
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);
|
||||
@@ -10605,6 +10630,11 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
||||
}
|
||||
sendFileAttachment(file) {
|
||||
const maxInbandAttachmentSize = this.props.tinode.getServerParam('maxMessageSize', _config_js__WEBPACK_IMPORTED_MODULE_18__.MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024 | 0;
|
||||
if (tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.isFileSupported(file.type, file.name)) {
|
||||
if (this.sendTheCardAttachment(file, maxInbandAttachmentSize)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const jsonMimeConverter = fileType => fileType === _config_js__WEBPACK_IMPORTED_MODULE_18__.DRAFTY_FR_MIME_TYPE_LEGACY ? 'application/octet-stream' : fileType;
|
||||
if (file.size > maxInbandAttachmentSize) {
|
||||
const uploader = this.props.tinode.getLargeFileHelper();
|
||||
@@ -10629,6 +10659,21 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
||||
}))).catch(err => this.props.onError(err.message, 'err'));
|
||||
}
|
||||
}
|
||||
sendTheCardAttachment(file, maxInbandAttachmentSize) {
|
||||
if (file.size > maxInbandAttachmentSize) {
|
||||
return false;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
const card = tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.importVCard(e.target.result);
|
||||
if (!card) {
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_parse_vcard), 'err');
|
||||
}
|
||||
this.sendMessage(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.appendTheCard(null, card));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
return true;
|
||||
}
|
||||
handleAttachFile(file) {
|
||||
const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', _config_js__WEBPACK_IMPORTED_MODULE_18__.MAX_EXTERN_ATTACHMENT_SIZE);
|
||||
if (file.size > maxExternAttachmentSize) {
|
||||
@@ -11260,7 +11305,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
||||
}, "arrow_back")) : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_11__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
avatar: avatar,
|
||||
topic: this.state.topic,
|
||||
title: this.state.title,
|
||||
@@ -14938,7 +14983,7 @@ class AvatarUpload extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
||||
}) : this.props.readOnly && this.props.uid ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_1__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
avatar: true,
|
||||
topic: this.props.uid,
|
||||
title: this.props.title
|
||||
@@ -15191,7 +15236,7 @@ class CallIncoming extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
avatar: this.state.avatar || true,
|
||||
topic: this.props.topic,
|
||||
title: this.state.fullName
|
||||
@@ -15939,7 +15984,7 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
avatar: this.props.avatar,
|
||||
topic: this.props.topic,
|
||||
title: this.props.title
|
||||
@@ -16109,19 +16154,41 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
||||
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':
|
||||
case 'contact_find':
|
||||
e.preventDefault();
|
||||
console.log('Contact click handling not implemented yet.', action, e.target.dataset);
|
||||
break;
|
||||
default:
|
||||
console.log('Unhandled drafty action.', action, e.target.dataset);
|
||||
break;
|
||||
}
|
||||
};
|
||||
handleExpandImage(e) {
|
||||
e.preventDefault();
|
||||
this.props.onExpandMedia({
|
||||
@@ -16175,7 +16242,7 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
||||
if (!this.props.response) {
|
||||
let immutable = false;
|
||||
tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.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) {
|
||||
@@ -16274,7 +16341,7 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
|
||||
}, this.props.isGroup && this.props.response ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, fullDisplay ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_4__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
topic: this.props.userFrom,
|
||||
title: this.props.userName,
|
||||
avatar: avatar
|
||||
@@ -16563,7 +16630,7 @@ class Chip extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent)
|
||||
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_1__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
avatar: this.props.avatar || true,
|
||||
topic: this.props.topic,
|
||||
title: this.props.title
|
||||
@@ -16998,7 +17065,7 @@ class Contact extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component) {
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_4__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
avatar: avatar,
|
||||
title: this.props.title,
|
||||
topic: this.props.item,
|
||||
@@ -18216,7 +18283,7 @@ class GroupSubs extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
||||
className: "avatar-box",
|
||||
key: sub.user
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
topic: sub.user,
|
||||
avatar: (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__.makeImageUrl)(sub.public ? sub.public.photo : null) || true,
|
||||
title: sub.public ? sub.public.fn : null
|
||||
@@ -18894,7 +18961,7 @@ class LetterTile extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompo
|
||||
}
|
||||
}
|
||||
} else if (this.props.avatar) {
|
||||
const url = this.props.tinode.authorizeURL((0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_3__.sanitizeUrlForMime)(this.props.avatar, 'image'));
|
||||
const url = this.props.authorizeURL((0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_3__.sanitizeUrlForMime)(this.props.avatar, 'image'));
|
||||
const className = 'avatar' + (this.props.deleted ? ' deleted' : '');
|
||||
avatar = react__WEBPACK_IMPORTED_MODULE_0___default().createElement("img", {
|
||||
className: className,
|
||||
@@ -20247,7 +20314,7 @@ class SideNavbar extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompo
|
||||
id: "self-avatar",
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_1__["default"], {
|
||||
tinode: this.props.tinode,
|
||||
authorizeURL: this.props.tinode.authorizeURL,
|
||||
avatar: avatar,
|
||||
topic: this.props.myUserId,
|
||||
title: this.props.title
|
||||
@@ -20481,6 +20548,132 @@ class TagManager extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/widgets/the-card-viewer.jsx":
|
||||
/*!*****************************************!*\
|
||||
!*** ./src/widgets/the-card-viewer.jsx ***!
|
||||
\*****************************************/
|
||||
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
||||
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-intl */ "react-intl");
|
||||
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
||||
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tinode-sdk */ "tinode-sdk");
|
||||
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__);
|
||||
/* harmony import */ var _letter_tile_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./letter-tile.jsx */ "./src/widgets/letter-tile.jsx");
|
||||
|
||||
|
||||
|
||||
|
||||
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
||||
unknown_name: {
|
||||
id: "unknown_name",
|
||||
defaultMessage: [{
|
||||
"type": 0,
|
||||
"value": "Unknown"
|
||||
}]
|
||||
}
|
||||
});
|
||||
class TheCardViewer extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleDownload = this.handleDownload.bind(this);
|
||||
this.handleMessage = this.handleMessage.bind(this);
|
||||
}
|
||||
handleDownload(e) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const vcard = tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.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) {}
|
||||
render() {
|
||||
const card = this.props.content;
|
||||
if (!card) {
|
||||
return null;
|
||||
}
|
||||
const uid = tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.getFirstTinodeID(card);
|
||||
const contacts = tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.getEmails(card).concat(tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.getPhones(card));
|
||||
const org = tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.getOrg(card);
|
||||
console.log('Rendering TheCardViewer', JSON.stringify(contacts), uid);
|
||||
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "contact-card"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "contact-body"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "avatar-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], {
|
||||
authorizeURL: this.props.authorizeURL,
|
||||
topic: uid || 'usr123XXX',
|
||||
avatar: tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.getPhotoUrl(card) || true,
|
||||
title: card.fn
|
||||
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "name-box"
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "name"
|
||||
}, card.fn || react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, messages.unknown_name)), org && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "org"
|
||||
}, org))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "contact-actions"
|
||||
}, uid && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||
className: "flat-button",
|
||||
"data-val": uid,
|
||||
onClick: this.props.onChatClick
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||
id: "chat_now",
|
||||
defaultMessage: [{
|
||||
"type": 0,
|
||||
"value": "Chat"
|
||||
}]
|
||||
})), uid && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "divider"
|
||||
}), contacts.length > 0 && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||
className: "flat-button",
|
||||
"data-val": contacts.join(','),
|
||||
onClick: this.props.onChatClick
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||
id: "find_user",
|
||||
defaultMessage: [{
|
||||
"type": 0,
|
||||
"value": "Find"
|
||||
}]
|
||||
})), contacts.length > 0 && react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "divider"
|
||||
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||
className: "flat-button",
|
||||
onClick: this.handleDownload
|
||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||
id: "save_action",
|
||||
defaultMessage: [{
|
||||
"type": 0,
|
||||
"value": "Save"
|
||||
}]
|
||||
}))));
|
||||
}
|
||||
}
|
||||
;
|
||||
/* harmony default export */ __webpack_exports__["default"] = ((0,react_intl__WEBPACK_IMPORTED_MODULE_1__.injectIntl)(TheCardViewer));
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/widgets/topic-desc-edit.jsx":
|
||||
/*!*****************************************!*\
|
||||
!*** ./src/widgets/topic-desc-edit.jsx ***!
|
||||
@@ -21263,12 +21456,6 @@ module.exports = tinode;
|
||||
/******/ if (cachedModule !== undefined) {
|
||||
/******/ return cachedModule.exports;
|
||||
/******/ }
|
||||
/******/ // Check if module exists (development only)
|
||||
/******/ if (__webpack_modules__[moduleId] === undefined) {
|
||||
/******/ var e = new Error("Cannot find module '" + moduleId + "'");
|
||||
/******/ e.code = 'MODULE_NOT_FOUND';
|
||||
/******/ throw e;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = __webpack_module_cache__[moduleId] = {
|
||||
/******/ // no module.id needed
|
||||
|
||||
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
+384
-16
@@ -1400,7 +1400,7 @@ class DB {
|
||||
/***/ (function(module) {
|
||||
|
||||
/**
|
||||
* @copyright 2015-2024 Tinode LLC.
|
||||
* @copyright 2015-2026 Tinode LLC.
|
||||
* @summary Minimally rich text representation and formatting for Tinode.
|
||||
* @license Apache 2.0
|
||||
*
|
||||
@@ -1460,7 +1460,7 @@ const MAX_PREVIEW_DATA_SIZE = 64;
|
||||
const DRAFTY_MIME_TYPE = 'text/x-drafty';
|
||||
const DRAFTY_FR_MIME_TYPE = 'text/x-drafty-fr';
|
||||
const DRAFTY_FR_MIME_TYPE_LEGACY = 'application/json';
|
||||
const ALLOWED_ENT_FIELDS = ['act', 'height', 'duration', 'incoming', 'mime', 'name', 'premime', 'preref', 'preview', 'ref', 'size', 'state', 'url', 'val', 'width'];
|
||||
const ALLOWED_ENT_FIELDS = ['act', 'height', 'duration', 'fn', 'incoming', 'mime', 'name', 'premime', 'preref', 'preview', 'ref', 'size', 'state', 'url', 'val', 'width'];
|
||||
const segmenter = new Intl.Segmenter();
|
||||
const INLINE_STYLES = [{
|
||||
name: 'ST',
|
||||
@@ -1597,6 +1597,11 @@ const FORMAT_TAGS = {
|
||||
md_tag: '*',
|
||||
isVoid: false
|
||||
},
|
||||
TC: {
|
||||
html_tag: 'div',
|
||||
md_tag: undefined,
|
||||
isVoid: false
|
||||
},
|
||||
VC: {
|
||||
html_tag: 'div',
|
||||
md_tag: undefined,
|
||||
@@ -1769,6 +1774,17 @@ const DECORATORS = {
|
||||
return data ? {} : null;
|
||||
}
|
||||
},
|
||||
TC: {
|
||||
open: _ => '<div>',
|
||||
close: _ => '</div>',
|
||||
props: data => {
|
||||
if (!data) return {};
|
||||
return {
|
||||
'data-fn': data.fn,
|
||||
'data-title': data.title
|
||||
};
|
||||
}
|
||||
},
|
||||
VC: {
|
||||
open: _ => '<div>',
|
||||
close: _ => '</div>',
|
||||
@@ -2314,6 +2330,24 @@ Drafty.appendLineBreak = function (content) {
|
||||
content.txt += ' ';
|
||||
return content;
|
||||
};
|
||||
Drafty.appendTheCard = function (content, theCardData) {
|
||||
content = content || {
|
||||
txt: ''
|
||||
};
|
||||
content.ent = content.ent || [];
|
||||
content.fmt = content.fmt || [];
|
||||
content.fmt.push({
|
||||
at: stringToGraphemes(content.txt).length,
|
||||
len: 1,
|
||||
key: content.ent.length
|
||||
});
|
||||
content.txt += ' ';
|
||||
content.ent.push({
|
||||
tp: 'TC',
|
||||
data: theCardData
|
||||
});
|
||||
return content;
|
||||
};
|
||||
Drafty.UNSAFE_toHTML = function (doc) {
|
||||
const tree = draftyToTree(doc);
|
||||
const htmlFormatter = function (type, data, values) {
|
||||
@@ -3930,6 +3964,336 @@ class MetaGetBuilder {
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/the-card.js":
|
||||
/*!*************************!*\
|
||||
!*** ./src/the-card.js ***!
|
||||
\*************************/
|
||||
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||
/* harmony export */ "default": function() { return /* binding */ TheCard; }
|
||||
/* harmony export */ });
|
||||
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./config.js */ "./src/config.js");
|
||||
|
||||
|
||||
|
||||
class TheCard {
|
||||
constructor(fn, imageUrl, imageMimeType, note) {
|
||||
this.card = theCard(fn, imageUrl, imageMimeType, note);
|
||||
}
|
||||
getCard() {
|
||||
return this.card;
|
||||
}
|
||||
}
|
||||
TheCard.init = theCard;
|
||||
TheCard.contentType = 'text/x-card';
|
||||
TheCard.setFn = function (card, fn) {
|
||||
fn = fn && fn.trim();
|
||||
card = card || {};
|
||||
card.fn = fn || undefined;
|
||||
return card;
|
||||
};
|
||||
TheCard.getFn = function (card) {
|
||||
if (card && card.fn) {
|
||||
return card.fn;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
TheCard.setNote = function (card, note) {
|
||||
note = note && note.trim();
|
||||
card = card || {};
|
||||
card.note = note ? note : _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR;
|
||||
return card;
|
||||
};
|
||||
TheCard.setPhoto = function (card, imageUrl, imageMimeType) {
|
||||
if (imageUrl) {
|
||||
card = card || {};
|
||||
let mimeType = imageMimeType;
|
||||
const matches = /^data:(image\/[-a-z0-9+.]+)?(;base64)?,/i.exec(imageUrl);
|
||||
if (matches) {
|
||||
mimeType = matches[1];
|
||||
card.photo = {
|
||||
data: imageUrl.substring(imageUrl.indexOf(',') + 1),
|
||||
ref: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR
|
||||
};
|
||||
} else {
|
||||
card.photo = {
|
||||
data: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR,
|
||||
ref: imageUrl
|
||||
};
|
||||
if (!mimeType) {
|
||||
const ext = /\.([a-z0-9]+)$/i.exec(imageUrl);
|
||||
if (ext) {
|
||||
const extLower = ext[1].toLowerCase();
|
||||
const mimeMap = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'bmp': 'image/bmp',
|
||||
'webp': 'image/webp',
|
||||
'svg': 'image/svg+xml'
|
||||
};
|
||||
mimeType = mimeMap[extLower] || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
card.photo.type = (mimeType || 'image/jpeg').substring('image/'.length);
|
||||
} else {
|
||||
card = card || {};
|
||||
card.photo = _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR;
|
||||
}
|
||||
return card;
|
||||
};
|
||||
TheCard.getPhotoUrl = function (card) {
|
||||
if (card && card.photo) {
|
||||
if (card.photo.ref && card.photo.ref != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
|
||||
return card.photo.ref;
|
||||
} else if (card.photo.data && card.photo.data != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
|
||||
return 'data:image/' + (card.photo.type || 'jpeg') + ';base64,' + card.photo.data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
TheCard.getOrg = function (card) {
|
||||
if (card && card.org) {
|
||||
return card.org.fn || null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
TheCard.setPhone = function (card, phone, type = 'voice') {
|
||||
return addOrSetComm(card, 'tel', phone, type, true);
|
||||
};
|
||||
TheCard.setEmail = function (card, email, type = 'home') {
|
||||
return addOrSetComm(card, 'email', email, type, true);
|
||||
};
|
||||
TheCard.setTinodeID = function (card, tinodeID, type = 'home') {
|
||||
return addOrSetComm(card, 'tinode', tinodeID, type, true);
|
||||
};
|
||||
TheCard.addPhone = function (card, phone, type = 'voice') {
|
||||
return addOrSetComm(card, 'tel', phone, type, false);
|
||||
};
|
||||
TheCard.addEmail = function (card, email, type = 'home') {
|
||||
return addOrSetComm(card, 'email', email, type, false);
|
||||
};
|
||||
TheCard.addTinodeID = function (card, tinodeID, type = 'home') {
|
||||
return addOrSetComm(card, 'tinode', tinodeID, type, false);
|
||||
};
|
||||
TheCard.clearPhone = function (card, phone, type) {
|
||||
return clearComm(card, 'tel', phone, type);
|
||||
};
|
||||
TheCard.clearEmail = function (card, email, type) {
|
||||
return clearComm(card, 'email', email, type);
|
||||
};
|
||||
TheCard.clearTinodeID = function (card, tinodeID, type) {
|
||||
return clearComm(card, 'tinode', tinodeID, type);
|
||||
};
|
||||
TheCard.getComm = function (card, proto) {
|
||||
if (card && Array.isArray(card.comm)) {
|
||||
return card.comm.filter(c => c.proto == proto);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
TheCard.getEmails = function (card) {
|
||||
return TheCard.getComm(card, 'email').map(c => c.value);
|
||||
};
|
||||
TheCard.getPhones = function (card) {
|
||||
return TheCard.getComm(card, 'tel').map(c => c.value);
|
||||
};
|
||||
TheCard.getFirstTinodeID = function (card) {
|
||||
const comms = TheCard.getComm(card, 'tinode');
|
||||
if (comms.length > 0) {
|
||||
return comms[0].value;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
TheCard.exportVCard = function (card) {
|
||||
if (!card) {
|
||||
return null;
|
||||
}
|
||||
let vcard = 'BEGIN:VCARD\r\nVERSION:3.0\r\n';
|
||||
if (card.fn) {
|
||||
vcard += `FN:${card.fn}\r\n`;
|
||||
}
|
||||
if (card.n) {
|
||||
vcard += `N:${card.n.surname || ''};${card.n.given || ''};${card.n.additional || ''};${card.n.prefix || ''};${card.n.suffix || ''}\r\n`;
|
||||
}
|
||||
if (card.org) {
|
||||
if (card.org.fn) {
|
||||
vcard += `ORG:${card.org.fn}\r\n`;
|
||||
}
|
||||
if (card.org.title) {
|
||||
vcard += `TITLE:${card.org.title}\r\n`;
|
||||
}
|
||||
}
|
||||
if (card.note && card.note != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
|
||||
vcard += `NOTE:${card.note}\r\n`;
|
||||
}
|
||||
if (card.photo) {
|
||||
if (card.photo.ref && card.photo.ref != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
|
||||
vcard += `PHOTO;VALUE=URI:${card.photo.ref}\r\n`;
|
||||
} else if (card.photo.data && card.photo.data != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
|
||||
vcard += `PHOTO;TYPE=${card.photo.type.toUpperCase()};ENCODING=b:${card.photo.data}\r\n`;
|
||||
}
|
||||
}
|
||||
if (Array.isArray(card.comm)) {
|
||||
card.comm.forEach(comm => {
|
||||
if (comm.proto === 'tel') {
|
||||
vcard += `TEL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
|
||||
} else if (comm.proto === 'email') {
|
||||
vcard += `EMAIL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
|
||||
} else if (comm.proto === 'tinode') {
|
||||
vcard += `IMPP;TYPE=${comm.des.toUpperCase()};tinode:${comm.value}\r\n`;
|
||||
} else if (comm.proto === 'http') {
|
||||
vcard += `URL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
vcard += 'END:VCARD\r\n';
|
||||
return vcard;
|
||||
};
|
||||
TheCard.importVCard = function (vcardStr) {
|
||||
if (!vcardStr || typeof vcardStr !== 'string') {
|
||||
return null;
|
||||
}
|
||||
const lines = vcardStr.split(/\r\n|\n/);
|
||||
let card = {};
|
||||
lines.forEach(line => {
|
||||
const [keyPart, ...valueParts] = line.split(':');
|
||||
if (!keyPart || valueParts.length === 0) {
|
||||
return;
|
||||
}
|
||||
const value = valueParts.join(':');
|
||||
const keyParams = keyPart.split(';');
|
||||
const key = keyParams[0].trim().toUpperCase();
|
||||
if (key === 'FN') {
|
||||
card.fn = value;
|
||||
} else if (key === 'N') {
|
||||
const parts = value.split(';');
|
||||
card.n = {
|
||||
surname: parts[0] || undefined,
|
||||
given: parts[1] || undefined,
|
||||
additional: parts[2] || undefined,
|
||||
prefix: parts[3] || undefined,
|
||||
suffix: parts[4] || undefined
|
||||
};
|
||||
Object.keys(card.n).forEach(key => card.n[key] === undefined && delete card.n[key]);
|
||||
if (Object.keys(card.n).length === 0) {
|
||||
delete card.n;
|
||||
}
|
||||
} else if (key === 'ORG') {
|
||||
const parts = value.split(';');
|
||||
card.org = card.org || {};
|
||||
card.org.fn = parts[0] || undefined;
|
||||
if (!card.org.fn) delete card.org.fn;
|
||||
} else if (key === 'TITLE') {
|
||||
card.org = card.org || {};
|
||||
card.org.title = value || undefined;
|
||||
if (!card.org.title) delete card.org.title;
|
||||
} else if (key === 'NOTE') {
|
||||
card.note = value;
|
||||
} else if (key === 'PHOTO') {
|
||||
const params = keyPart.split(';').slice(1);
|
||||
let type = 'jpeg';
|
||||
let encoding = null;
|
||||
params.forEach(param => {
|
||||
const [pKey, pValue] = param.split('=');
|
||||
if (pKey && pKey.trim().toUpperCase() === 'TYPE') {
|
||||
type = pValue ? pValue.trim().toLowerCase() : 'jpeg';
|
||||
} else if (pKey && pKey.trim().toUpperCase() === 'ENCODING') {
|
||||
encoding = pValue ? pValue.trim().toLowerCase() : null;
|
||||
}
|
||||
});
|
||||
if (encoding === 'b') {
|
||||
card.photo = {
|
||||
type: type,
|
||||
data: value,
|
||||
ref: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR
|
||||
};
|
||||
} else {
|
||||
card.photo = {
|
||||
type: type,
|
||||
data: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR,
|
||||
ref: value
|
||||
};
|
||||
}
|
||||
} else if (key === 'TEL') {
|
||||
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'voice';
|
||||
card = TheCard.addPhone(card, value, des);
|
||||
} else if (key === 'EMAIL') {
|
||||
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'home';
|
||||
card = TheCard.addEmail(card, value, des);
|
||||
} else if (key === 'IMPP') {
|
||||
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'home';
|
||||
const tinodeID = value.replace(/^tinode:/, '');
|
||||
card = TheCard.addTinodeID(card, tinodeID, des);
|
||||
}
|
||||
});
|
||||
return card;
|
||||
};
|
||||
TheCard.isFileSupported = function (type, name) {
|
||||
return type == 'text/vcard' || (name || '').endsWith('.vcf') || (name || '').endsWith('.vcard');
|
||||
};
|
||||
function theCard(fn, imageUrl, imageMimeType, note) {
|
||||
let card = null;
|
||||
fn = fn && fn.trim();
|
||||
note = note && note.trim();
|
||||
if (fn) {
|
||||
card = {
|
||||
fn: fn
|
||||
};
|
||||
}
|
||||
if (typeof note == 'string') {
|
||||
card = card || {};
|
||||
card.note = note ? note : _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR;
|
||||
}
|
||||
if (imageUrl) {
|
||||
card = card || {};
|
||||
let mimeType = imageMimeType;
|
||||
const matches = /^data:(image\/[-a-z0-9+.]+)?(;base64)?,/i.exec(imageUrl);
|
||||
if (matches) {
|
||||
mimeType = matches[1];
|
||||
card.photo = {
|
||||
data: imageUrl.substring(imageUrl.indexOf(',') + 1),
|
||||
ref: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR
|
||||
};
|
||||
} else {
|
||||
card.photo = {
|
||||
data: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR,
|
||||
ref: imageUrl
|
||||
};
|
||||
}
|
||||
card.photo.type = (mimeType || 'image/jpeg').substring('image/'.length);
|
||||
}
|
||||
return card;
|
||||
}
|
||||
function addOrSetComm(card, proto, value, type, setOnly) {
|
||||
proto = proto && proto.trim();
|
||||
value = value && value.trim();
|
||||
if (proto && value) {
|
||||
card = card || {};
|
||||
card.comm = card.comm || [];
|
||||
if (setOnly) {
|
||||
card.comm = card.comm.filter(c => !(c.proto == proto && c.des == type));
|
||||
}
|
||||
card.comm.push({
|
||||
proto: proto,
|
||||
des: type,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
return card;
|
||||
}
|
||||
function clearComm(card, proto, value, type) {
|
||||
if (card && Array.isArray(card.comm)) {
|
||||
card.comm = card.comm.filter(c => !(c.proto == proto && (!type || c.des == type) && (!value || c.value == value)));
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/topic.js":
|
||||
/*!**********************!*\
|
||||
!*** ./src/topic.js ***!
|
||||
@@ -5549,6 +5913,7 @@ __webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||
/* harmony export */ AccessMode: function() { return /* reexport safe */ _access_mode_js__WEBPACK_IMPORTED_MODULE_0__["default"]; },
|
||||
/* harmony export */ Drafty: function() { return /* reexport default from dynamic */ _drafty_js__WEBPACK_IMPORTED_MODULE_5___default.a; },
|
||||
/* harmony export */ TheCard: function() { return /* reexport safe */ _the_card_js__WEBPACK_IMPORTED_MODULE_11__["default"]; },
|
||||
/* harmony export */ Tinode: function() { return /* binding */ Tinode; }
|
||||
/* harmony export */ });
|
||||
/* harmony import */ var _access_mode_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./access-mode.js */ "./src/access-mode.js");
|
||||
@@ -5563,11 +5928,12 @@ __webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var _topic_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./topic.js */ "./src/topic.js");
|
||||
/* harmony import */ var _fnd_topic_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./fnd-topic.js */ "./src/fnd-topic.js");
|
||||
/* harmony import */ var _me_topic_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./me-topic.js */ "./src/me-topic.js");
|
||||
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./utils.js */ "./src/utils.js");
|
||||
/* harmony import */ var _the_card_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./the-card.js */ "./src/the-card.js");
|
||||
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./utils.js */ "./src/utils.js");
|
||||
/**
|
||||
* @module tinode-sdk
|
||||
*
|
||||
* @copyright 2015-2025 Tinode LLC.
|
||||
* @copyright 2015-2026 Tinode LLC.
|
||||
* @summary Javascript bindings for Tinode.
|
||||
* @license Apache 2.0
|
||||
* @version 0.25
|
||||
@@ -5622,6 +5988,8 @@ __webpack_require__.r(__webpack_exports__);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let WebSocketProvider;
|
||||
if (typeof WebSocket != 'undefined') {
|
||||
WebSocketProvider = WebSocket;
|
||||
@@ -5698,7 +6066,7 @@ function b64EncodeUnicode(str) {
|
||||
}
|
||||
function jsonBuildHelper(key, val) {
|
||||
if (val instanceof Date) {
|
||||
val = (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.rfc3339DateString)(val);
|
||||
val = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.rfc3339DateString)(val);
|
||||
} else if (val instanceof _access_mode_js__WEBPACK_IMPORTED_MODULE_0__["default"]) {
|
||||
val = val.jsonHelper();
|
||||
} else if (val === undefined || val === null || val === false || Array.isArray(val) && val.length == 0 || typeof val == 'object' && Object.keys(val).length == 0) {
|
||||
@@ -5858,7 +6226,7 @@ class Tinode {
|
||||
});
|
||||
}).then(_ => {
|
||||
return this._db.mapUsers(data => {
|
||||
this.#cachePut('user', data.uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)({}, data.public));
|
||||
this.#cachePut('user', data.uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)({}, data.public));
|
||||
});
|
||||
}).then(_ => {
|
||||
return Promise.all(prom);
|
||||
@@ -5919,7 +6287,7 @@ class Tinode {
|
||||
if (id) {
|
||||
promise = this.#makePromise(id);
|
||||
}
|
||||
pkt = (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.simplify)(pkt);
|
||||
pkt = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.simplify)(pkt);
|
||||
let msg = JSON.stringify(pkt);
|
||||
this.logger("out: " + (this._trimLongStrings ? JSON.stringify(pkt, jsonLoggerHelper) : msg));
|
||||
try {
|
||||
@@ -5945,7 +6313,7 @@ class Tinode {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let pkt = JSON.parse(data, _utils_js__WEBPACK_IMPORTED_MODULE_11__.jsonParseHelper);
|
||||
let pkt = JSON.parse(data, _utils_js__WEBPACK_IMPORTED_MODULE_12__.jsonParseHelper);
|
||||
if (!pkt) {
|
||||
this.logger("in: " + data);
|
||||
this.logger("ERROR: failed to parse data");
|
||||
@@ -6206,13 +6574,13 @@ class Tinode {
|
||||
if (pub) {
|
||||
return {
|
||||
user: uid,
|
||||
public: (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)({}, pub)
|
||||
public: (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)({}, pub)
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
topic._cachePutUser = (uid, user) => {
|
||||
this.#cachePut('user', uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)({}, user.public));
|
||||
this.#cachePut('user', uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)({}, user.public));
|
||||
};
|
||||
topic._cacheDelUser = uid => {
|
||||
this.#cacheDel('user', uid);
|
||||
@@ -6391,7 +6759,7 @@ class Tinode {
|
||||
if (typeof url != 'string') {
|
||||
return url;
|
||||
}
|
||||
if ((0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(url)) {
|
||||
if ((0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(url)) {
|
||||
const base = 'scheme://host/';
|
||||
const parsed = new URL(url, base);
|
||||
if (this._apiKey) {
|
||||
@@ -6422,7 +6790,7 @@ class Tinode {
|
||||
pkt.acc.tmpsecret = params.secret;
|
||||
if (Array.isArray(params.attachments) && params.attachments.length > 0) {
|
||||
pkt.extra = {
|
||||
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
|
||||
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6531,7 +6899,7 @@ class Tinode {
|
||||
}
|
||||
if (Array.isArray(setParams.attachments) && setParams.attachments.length > 0) {
|
||||
pkt.extra = {
|
||||
attachments: setParams.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
|
||||
attachments: setParams.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
|
||||
};
|
||||
}
|
||||
if (setParams.tags) {
|
||||
@@ -6574,7 +6942,7 @@ class Tinode {
|
||||
};
|
||||
if (attachments) {
|
||||
msg.extra = {
|
||||
attachments: attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
|
||||
attachments: attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
|
||||
};
|
||||
}
|
||||
return this.#send(msg, pub.id);
|
||||
@@ -6647,7 +7015,7 @@ class Tinode {
|
||||
}
|
||||
getMeta(topic, params) {
|
||||
const pkt = this.#initPacket('get', topic);
|
||||
pkt.get = (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)(pkt.get, params);
|
||||
pkt.get = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)(pkt.get, params);
|
||||
return this.#send(pkt, pkt.get.id);
|
||||
}
|
||||
setMeta(topic, params) {
|
||||
@@ -6662,7 +7030,7 @@ class Tinode {
|
||||
});
|
||||
if (Array.isArray(params.attachments) && params.attachments.length > 0) {
|
||||
pkt.extra = {
|
||||
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
|
||||
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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
+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
Reference in New Issue
Block a user