TheCard handling in messages

This commit is contained in:
or-else
2026-01-16 20:51:57 +03:00
parent 16489432cf
commit 930f4f0656
42 changed files with 1304 additions and 126 deletions
+76
View File
@@ -790,6 +790,14 @@ div.trusted-badge {
height: 0.015rem; 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 { .ellipsized {
overflow-x: hidden; overflow-x: hidden;
white-space: nowrap; white-space: nowrap;
@@ -3784,3 +3792,71 @@ div.toast.show {
border-radius: unset; 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;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+20
View File
@@ -175,6 +175,10 @@
"defaultMessage": "Cannot initiate file upload.", "defaultMessage": "Cannot initiate file upload.",
"description": "Generic error messagewhen attachment fails" "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": { "change_email": {
"defaultMessage": "Change email", "defaultMessage": "Change email",
"description": "Prompt to change current email" "description": "Prompt to change current email"
@@ -207,6 +211,10 @@
"defaultMessage": "Ignore", "defaultMessage": "Ignore",
"description": "Action [Ignore] for chat invitation." "description": "Action [Ignore] for chat invitation."
}, },
"chat_now": {
"defaultMessage": "Chat",
"description": "Label for message button in contact card"
},
"clear_messages_warning": { "clear_messages_warning": {
"defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.", "defaultMessage": "Are you sure you want to clear all messages? It cannot be undone.",
"description": "Alert dialog warning when deleting all messages." "description": "Alert dialog warning when deleting all messages."
@@ -327,6 +335,10 @@
"defaultMessage": "The file size {size} exceeds the {limit} limit.", "defaultMessage": "The file size {size} exceeds the {limit} limit.",
"description": "Error message when attachment is too large" "description": "Error message when attachment is too large"
}, },
"find_user": {
"defaultMessage": "Find",
"description": "Label for search button in contact card"
},
"forgot_password_link": { "forgot_password_link": {
"defaultMessage": "Forgot password?", "defaultMessage": "Forgot password?",
"description": "Link to Reset password form" "description": "Link to Reset password form"
@@ -880,6 +892,10 @@
"defaultMessage": "Requested", "defaultMessage": "Requested",
"description": "Title for permissions" "description": "Title for permissions"
}, },
"save_action": {
"defaultMessage": "Save",
"description": "Label for Save button"
},
"save_attachment": { "save_attachment": {
"defaultMessage": "save", "defaultMessage": "save",
"description": "Call to save an attachment" "description": "Call to save an attachment"
@@ -1056,6 +1072,10 @@
"defaultMessage": "Freeform name of the group", "defaultMessage": "Freeform name of the group",
"description": "Prompt for entering topic name" "description": "Prompt for entering topic name"
}, },
"unknown_name": {
"defaultMessage": "Unknown",
"description": "Name to show when the name is missing"
},
"unnamed_topic": { "unnamed_topic": {
"defaultMessage": "Unnamed", "defaultMessage": "Unnamed",
"description": "Title shown when the topic has no name" "description": "Title shown when the topic has no name"
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
} }
} }
+35
View File
@@ -1906,5 +1906,40 @@
"description": "Label [Scan my ID:]", "description": "Label [Scan my ID:]",
"missing": false, "missing": false,
"obsolete": 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
View File
@@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import { defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
import { Drafty } from 'tinode-sdk'; import { Drafty, TheCard } from 'tinode-sdk';
import AudioPlayer from '../widgets/audio-player.jsx' import AudioPlayer from '../widgets/audio-player.jsx'
import CallMessage from '../widgets/call-message.jsx' import CallMessage from '../widgets/call-message.jsx'
import CallStatus from '../widgets/call-status.jsx'; import CallStatus from '../widgets/call-status.jsx';
import InlineVideo from '../widgets/inline-video.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 UploadingImage from '../widgets/uploading-image.jsx'
import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO, import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO,
@@ -48,10 +49,7 @@ const messages = defineMessages({
// 'this' must contain: // 'this' must contain:
// viewportWidth: // viewportWidth:
// authorizeURL: // authorizeURL:
// onImagePreview: // onHandleClick:
// onVideoPreview:
// onFormButtonClick:
// onQuoteClick:
export function fullFormatter(style, data, values, key, stack) { export function fullFormatter(style, data, values, key, stack) {
if (stack.includes('QQ')) { if (stack.includes('QQ')) {
return quoteFormatter.call(this, style, data, values, key); return quoteFormatter.call(this, style, data, values, key);
@@ -100,7 +98,7 @@ export function fullFormatter(style, data, values, key, stack) {
break; break;
case 'BN': case 'BN':
// Button // Button
attr.onClick = this.onFormButtonClick; attr.onClick = (e) => this.onHandleClick(e, 'form_button');
let inner = React.Children.map(values, (child) => { let inner = React.Children.map(values, (child) => {
return typeof child == 'string' ? child : undefined; return typeof child == 'string' ? child : undefined;
}); });
@@ -133,7 +131,16 @@ export function fullFormatter(style, data, values, key, stack) {
case 'QQ': case 'QQ':
// Quote/citation. // Quote/citation.
attr.className = 'reply-quote' 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; break;
case 'VC': case 'VC':
el = CallMessage; el = CallMessage;
@@ -200,7 +207,7 @@ function handleImageData(el, data, attr) {
if (attr.src) { if (attr.src) {
if (Math.max(data.width || 0, data.height || 0) > IMAGE_THUMBNAIL_DIM) { if (Math.max(data.width || 0, data.height || 0) > IMAGE_THUMBNAIL_DIM) {
// Allow previews for large enough images. // Allow previews for large enough images.
attr.onClick = this.onImagePreview; attr.onClick = (e) => this.onHandleClick(e, 'image');
attr.className += ' image-clickable'; attr.className += ' image-clickable';
} }
attr.loading = 'lazy'; attr.loading = 'lazy';
@@ -241,7 +248,7 @@ function handleVideoData(el, data, attr) {
attr.src = this.authorizeURL(sanitizeUrlForMime(attr.src, 'image')); attr.src = this.authorizeURL(sanitizeUrlForMime(attr.src, 'image'));
attr.alt = data.name; attr.alt = data.name;
if (data.ref || data.val) { if (data.ref || data.val) {
attr.onClick = this.onVideoPreview; attr.onClick = (e) => this.onHandleClick(e, 'video');
attr.loading = 'lazy'; attr.loading = 'lazy';
} }
el = InlineVideo; el = InlineVideo;
@@ -329,6 +336,12 @@ export function previewFormatter(style, data, values, key) {
el = null; el = null;
values = null; values = null;
break; 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': case 'VD':
// Replace image with '[icon] Video'. // Replace image with '[icon] Video'.
el = React.Fragment; el = React.Fragment;
@@ -392,7 +405,7 @@ function inlineVideoAttr(attr, data) {
// formatMessage: this.props.intl.formatMessage // formatMessage: this.props.intl.formatMessage
// messages: formatjs messages defined with defineMessages. // messages: formatjs messages defined with defineMessages.
// authorizeURL: this.props.tinode.authorizeURL // authorizeURL: this.props.tinode.authorizeURL
// onQuoteClick: this.handleQuoteClick (optional) // onHandleClick: this.handleDraftyClick
function quoteFormatter(style, data, values, key) { function quoteFormatter(style, data, values, key) {
if (['BR', 'EX', 'IM', 'MN', 'VD'].includes(style)) { if (['BR', 'EX', 'IM', 'MN', 'VD'].includes(style)) {
let el = Drafty.tagName(style); let el = Drafty.tagName(style);
+38 -3
View File
@@ -3,7 +3,7 @@
import React from 'react'; import React from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; 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 CallPanel from '../widgets/call-panel.jsx';
import ChatMessage from '../widgets/chat-message.jsx'; import ChatMessage from '../widgets/chat-message.jsx';
@@ -104,6 +104,11 @@ const messages = defineMessages({
'other {{count, number} members}}', 'other {{count, number} members}}',
description: 'Count of group topic 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. // 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.sendVideoAttachment = this.sendVideoAttachment.bind(this);
this.sendFileAttachment = this.sendFileAttachment.bind(this); this.sendFileAttachment = this.sendFileAttachment.bind(this);
this.sendAudioAttachment = this.sendAudioAttachment.bind(this); this.sendAudioAttachment = this.sendAudioAttachment.bind(this);
this.sendTheCardAttachment = this.sendTheCardAttachment.bind(this);
this.sendKeyPress = this.sendKeyPress.bind(this); this.sendKeyPress = this.sendKeyPress.bind(this);
this.subscribe = this.subscribe.bind(this); this.subscribe = this.subscribe.bind(this);
this.handleScrollReference = this.handleScrollReference.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 too large, upload it and send a s link.
// - if file is small enough, just send it in-band. // - if file is small enough, just send it in-band.
sendFileAttachment(file) { sendFileAttachment(file) {
@@ -1047,6 +1053,15 @@ class MessagesView extends React.Component {
const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize', const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0; 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'. // 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. // This is a temporary workaround for the collision with the 'application/json' MIME type of form responses.
// Remove this code in 2026 or so. // 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 method is called when [Attach file] button is clicked: launch attachment preview.
handleAttachFile(file) { handleAttachFile(file) {
const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE); const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);
@@ -1777,7 +1812,7 @@ class MessagesView extends React.Component {
null} null}
<div className="avatar-box"> <div className="avatar-box">
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
avatar={avatar} avatar={avatar}
topic={this.state.topic} topic={this.state.topic}
title={this.state.title} title={this.state.title}
+1 -1
View File
@@ -48,7 +48,7 @@ export default class AvatarUpload extends React.Component {
this.props.readOnly && this.props.uid ? this.props.readOnly && this.props.uid ?
<div className="avatar-box"> <div className="avatar-box">
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
avatar={true} avatar={true}
topic={this.props.uid} topic={this.props.uid}
title={this.props.title} /> title={this.props.title} />
+1 -1
View File
@@ -122,7 +122,7 @@ export default class CallIncoming extends React.Component {
<div className="caller-card incoming pulse"> <div className="caller-card incoming pulse">
<div className="avatar-box"> <div className="avatar-box">
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
avatar={this.state.avatar || true} avatar={this.state.avatar || true}
topic={this.props.topic} topic={this.props.topic}
title={this.state.fullName} /> title={this.state.fullName} />
+1 -1
View File
@@ -680,7 +680,7 @@ class CallPanel extends React.PureComponent {
<div className={`caller-card${pulseAnimation} draggable`}> <div className={`caller-card${pulseAnimation} draggable`}>
<div className="avatar-box"> <div className="avatar-box">
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
avatar={this.props.avatar} avatar={this.props.avatar}
topic={this.props.topic} topic={this.props.topic}
title={this.props.title} /> title={this.props.title} />
+31 -7
View File
@@ -26,22 +26,46 @@ class BaseChatMessage extends React.PureComponent {
this.handleExpandImage = this.handleExpandImage.bind(this); this.handleExpandImage = this.handleExpandImage.bind(this);
this.handlePlayVideo = this.handlePlayVideo.bind(this); this.handlePlayVideo = this.handlePlayVideo.bind(this);
this.handleFormButtonClick = this.handleFormButtonClick.bind(this); this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
this.handleQuoteClick = this.handleQuoteClick.bind(this);
this.handleContextClick = this.handleContextClick.bind(this); this.handleContextClick = this.handleContextClick.bind(this);
this.handleCancelUpload = this.handleCancelUpload.bind(this); this.handleCancelUpload = this.handleCancelUpload.bind(this);
this.handleQuoteClick = this.handleQuoteClick.bind(this); this.handleDraftyClick = this.handleDraftyClick.bind(this);
this.formatterContext = { this.formatterContext = {
formatMessage: props.intl.formatMessage.bind(props.intl), formatMessage: props.intl.formatMessage.bind(props.intl),
viewportWidth: props.viewportWidth, viewportWidth: props.viewportWidth,
authorizeURL: props.tinode.authorizeURL.bind(props.tinode), authorizeURL: props.tinode.authorizeURL.bind(props.tinode),
onImagePreview: this.handleExpandImage, onHandleClick: this.handleDraftyClick
onVideoPreview: this.handlePlayVideo,
onFormButtonClick: this.handleFormButtonClick,
onQuoteClick: this.handleQuoteClick
}; };
} }
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) { handleExpandImage(e) {
e.preventDefault(); e.preventDefault();
this.props.onExpandMedia({ this.props.onExpandMedia({
@@ -99,7 +123,7 @@ class BaseChatMessage extends React.PureComponent {
if (!this.props.response) { if (!this.props.response) {
let immutable = false; let immutable = false;
Drafty.entities(this.props.content, (_0, _1, tp) => { 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; return immutable;
}); });
if (!immutable) { if (!immutable) {
@@ -200,7 +224,7 @@ class BaseChatMessage extends React.PureComponent {
<div className="avatar-box"> <div className="avatar-box">
{fullDisplay ? {fullDisplay ?
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
topic={this.props.userFrom} topic={this.props.userFrom}
title={this.props.userName} title={this.props.userName}
avatar={avatar} /> : avatar={avatar} /> :
+1 -1
View File
@@ -23,7 +23,7 @@ export default class Chip extends React.PureComponent {
<span className="spacer" /> : <span className="spacer" /> :
<div className="avatar-box"> <div className="avatar-box">
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
avatar={this.props.avatar || true} avatar={this.props.avatar || true}
topic={this.props.topic} topic={this.props.topic}
title={this.props.title} /> title={this.props.title} />
+1 -1
View File
@@ -122,7 +122,7 @@ class Contact extends React.Component {
<li className={bgColor} onClick={this.handleClick}> <li className={bgColor} onClick={this.handleClick}>
<div className="avatar-box"> <div className="avatar-box">
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
avatar={avatar} avatar={avatar}
title={this.props.title} title={this.props.title}
topic={this.props.item} topic={this.props.item}
+1 -1
View File
@@ -22,7 +22,7 @@ export default class GroupSubs extends React.Component {
usersOnline.push( usersOnline.push(
<div className="avatar-box" key={sub.user}> <div className="avatar-box" key={sub.user}>
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
topic={sub.user} topic={sub.user}
avatar={makeImageUrl(sub.public ? sub.public.photo : null) || true} avatar={makeImageUrl(sub.public ? sub.public.photo : null) || true}
title={sub.public ? sub.public.fn : null} /> title={sub.public ? sub.public.fn : null} />
+1 -1
View File
@@ -27,7 +27,7 @@ export default class LetterTile extends React.PureComponent {
} }
} }
} else if (this.props.avatar) { } 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. // If avatar image is invalid, show a placeholder.
const className = 'avatar' + (this.props.deleted ? ' deleted' : ''); const className = 'avatar' + (this.props.deleted ? ' deleted' : '');
avatar = <img className={className} alt="avatar" src={url} avatar = <img className={className} alt="avatar" src={url}
+1 -1
View File
@@ -26,7 +26,7 @@ export default class SideNavbar extends React.PureComponent {
{avatar ? {avatar ?
<div id="self-avatar" className="avatar-box"> <div id="self-avatar" className="avatar-box">
<LetterTile <LetterTile
tinode={this.props.tinode} authorizeURL={this.props.tinode.authorizeURL}
avatar={avatar} avatar={avatar}
topic={this.props.myUserId} topic={this.props.myUserId}
title={this.props.title} /> title={this.props.title} />
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+254 -67
View File
@@ -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_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_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_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 _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 _config_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../config.js */ "./src/config.js"); /* 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 _blob_helpers_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./blob-helpers.js */ "./src/lib/blob-helpers.js"); /* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
/* harmony import */ var _strformat_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./strformat.js */ "./src/lib/strformat.js"); /* harmony import */ var _blob_helpers_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./blob-helpers.js */ "./src/lib/blob-helpers.js");
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./utils.js */ "./src/lib/utils.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) { switch (style) {
case 'AU': case 'AU':
if (attr.src) { 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.duration = data.duration > 0 ? data.duration | 0 : undefined;
attr.preview = data.preview; attr.preview = data.preview;
attr.loading = 'lazy'; attr.loading = 'lazy';
@@ -7772,7 +7774,7 @@ function fullFormatter(style, data, values, key, stack) {
values = null; values = null;
break; break;
case 'BN': 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 => { let inner = react__WEBPACK_IMPORTED_MODULE_0___default().Children.map(values, child => {
return typeof child == 'string' ? child : undefined; return typeof child == 'string' ? child : undefined;
}); });
@@ -7783,13 +7785,13 @@ function fullFormatter(style, data, values, key, stack) {
break; break;
case 'LN': case 'LN':
if (attr) { 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; break;
case 'MN': case 'MN':
attr.className = 'mention'; attr.className = 'mention';
if (data) { 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; break;
case 'FM': case 'FM':
@@ -7799,7 +7801,15 @@ function fullFormatter(style, data, values, key, stack) {
break; break;
case 'QQ': case 'QQ':
attr.className = 'reply-quote'; 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; break;
case 'VC': case 'VC':
el = _widgets_call_message_jsx__WEBPACK_IMPORTED_MODULE_4__["default"]; el = _widgets_call_message_jsx__WEBPACK_IMPORTED_MODULE_4__["default"];
@@ -7843,15 +7853,15 @@ function handleImageData(el, data, attr) {
if (!data) { if (!data) {
attr.src = 'img/broken_image.png'; attr.src = 'img/broken_image.png';
attr.style = { attr.style = {
width: _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_9__.IMAGE_THUMBNAIL_DIM + 'px' height: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
}; };
return el; return el;
} }
attr.className = 'inline-image'; 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) || { 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_9__.BROKEN_IMAGE_SIZE, dstWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.BROKEN_IMAGE_SIZE,
dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.BROKEN_IMAGE_SIZE dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.BROKEN_IMAGE_SIZE
}; };
attr.style = { attr.style = {
width: dim.dstWidth + 'px', width: dim.dstWidth + 'px',
@@ -7860,11 +7870,11 @@ function handleImageData(el, data, attr) {
minHeight: dim.dstHeight + 'px' minHeight: dim.dstHeight + 'px'
}; };
if (!tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.isProcessing(data)) { 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; attr.alt = data.name;
if (attr.src) { if (attr.src) {
if (Math.max(data.width || 0, data.height || 0) > _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM) { if (Math.max(data.width || 0, data.height || 0) > _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM) {
attr.onClick = this.onImagePreview; attr.onClick = e => this.onHandleClick(e, 'image');
attr.className += ' image-clickable'; attr.className += ' image-clickable';
} }
attr.loading = 'lazy'; attr.loading = 'lazy';
@@ -7872,7 +7882,7 @@ function handleImageData(el, data, attr) {
attr.src = null; attr.src = null;
} }
} else { } else {
el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_8__["default"]; el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_9__["default"];
} }
return el; return el;
} }
@@ -7880,15 +7890,15 @@ function handleVideoData(el, data, attr) {
if (!data) { if (!data) {
attr.src = 'img/broken_video.png'; attr.src = 'img/broken_video.png';
attr.style = { attr.style = {
width: _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_9__.IMAGE_THUMBNAIL_DIM + 'px' height: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
}; };
return el; return el;
} }
attr.className = 'inline-image'; 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) || { 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_9__.NO_DIMENSIONS_VIDEO, dstWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.NO_DIMENSIONS_VIDEO,
dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.NO_DIMENSIONS_VIDEO dstHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.NO_DIMENSIONS_VIDEO
}; };
attr.style = { attr.style = {
width: dim.dstWidth + 'px', width: dim.dstWidth + 'px',
@@ -7897,15 +7907,15 @@ function handleVideoData(el, data, attr) {
minHeight: dim.dstHeight + 'px' minHeight: dim.dstHeight + 'px'
}; };
if (!tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.isProcessing(data)) { 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; attr.alt = data.name;
if (data.ref || data.val) { if (data.ref || data.val) {
attr.onClick = this.onVideoPreview; attr.onClick = e => this.onHandleClick(e, 'video');
attr.loading = 'lazy'; attr.loading = 'lazy';
} }
el = _widgets_inline_video_jsx__WEBPACK_IMPORTED_MODULE_6__["default"]; el = _widgets_inline_video_jsx__WEBPACK_IMPORTED_MODULE_6__["default"];
} else { } else {
el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_8__["default"]; el = _widgets_uploading_image_jsx__WEBPACK_IMPORTED_MODULE_9__["default"];
} }
return el; return el;
} }
@@ -7923,7 +7933,7 @@ function previewFormatter(style, data, values, key) {
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", { values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
key: "au", key: "au",
className: "material-icons" 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; break;
case 'BR': case 'BR':
el = (react__WEBPACK_IMPORTED_MODULE_0___default().Fragment); el = (react__WEBPACK_IMPORTED_MODULE_0___default().Fragment);
@@ -7985,6 +7995,13 @@ function previewFormatter(style, data, values, key) {
el = null; el = null;
values = null; values = null;
break; 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': case 'VD':
el = (react__WEBPACK_IMPORTED_MODULE_0___default().Fragment); el = (react__WEBPACK_IMPORTED_MODULE_0___default().Fragment);
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", { values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
@@ -8010,10 +8027,10 @@ function previewFormatter(style, data, values, key) {
; ;
function inlineImageAttr(attr, data) { function inlineImageAttr(attr, data) {
attr.style = { attr.style = {
width: _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_9__.IMAGE_THUMBNAIL_DIM + 'px', height: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px',
maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px', maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px',
maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px' maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
}; };
attr.className = 'inline-image'; attr.className = 'inline-image';
attr.alt = this.formatMessage(messages.drafty_image); attr.alt = this.formatMessage(messages.drafty_image);
@@ -8026,12 +8043,12 @@ function inlineImageAttr(attr, data) {
return attr; return attr;
} }
function inlineVideoAttr(attr, data) { 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 = { attr.style = {
width: dim.width + 'px', width: dim.width + 'px',
height: dim.height + 'px', height: dim.height + 'px',
maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_9__.VIDEO_THUMBNAIL_WIDTH + 'px', maxWidth: _config_js__WEBPACK_IMPORTED_MODULE_10__.VIDEO_THUMBNAIL_WIDTH + 'px',
maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_9__.IMAGE_THUMBNAIL_DIM + 'px' maxHeight: _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM + 'px'
}; };
attr.className = 'inline-image'; attr.className = 'inline-image';
attr.alt = this.formatMessage(messages.drafty_video); attr.alt = this.formatMessage(messages.drafty_video);
@@ -8072,7 +8089,7 @@ function quoteFormatter(style, data, values, key) {
el = 'span'; el = 'span';
attr.className = 'mention'; attr.className = 'mention';
if (data) { 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; break;
case 'EX': case 'EX':
@@ -8089,7 +8106,7 @@ function quoteFormatter(style, data, values, key) {
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", { values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
key: "ex", key: "ex",
className: "material-icons" 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; break;
} }
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(el, attr, values); return react__WEBPACK_IMPORTED_MODULE_0___default().createElement(el, attr, values);
@@ -8109,13 +8126,13 @@ function quoteImageOrVideo(data, isVideo) {
ref = data.ref; ref = data.ref;
} }
if (bits) { 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) { if (!blob) {
throw new Error("Invalid image"); throw new Error("Invalid image");
} }
promise = Promise.resolve(blob); promise = Promise.resolve(blob);
} else if (ref) { } 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) { if (evt.ok) {
return evt.blob(); return evt.blob();
} else { } else {
@@ -8126,7 +8143,7 @@ function quoteImageOrVideo(data, isVideo) {
throw new Error("Missing image data"); throw new Error("Missing image data");
} }
return promise.then(blob => { 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 => { }).then(scaled => {
if (isVideo) { if (isVideo) {
data.premime = scaled.mime; data.premime = scaled.mime;
@@ -8139,7 +8156,7 @@ function quoteImageOrVideo(data, isVideo) {
delete data.ref; delete data.ref;
delete data.preref; delete data.preref;
data.src = URL.createObjectURL(scaled.blob); 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 => { }).then(b64 => {
if (isVideo) { if (isVideo) {
data.preview = b64.bits; data.preview = b64.bits;
@@ -8151,8 +8168,8 @@ function quoteImageOrVideo(data, isVideo) {
delete data.val; delete data.val;
delete data.preview; delete data.preview;
delete data.src; delete data.src;
data.width = _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_9__.IMAGE_THUMBNAIL_DIM; data.height = _config_js__WEBPACK_IMPORTED_MODULE_10__.IMAGE_THUMBNAIL_DIM;
throw err; throw err;
}); });
} }
@@ -8166,10 +8183,10 @@ function replyFormatter(style, data, values, key, stack) {
}, data); }, data);
let loadedPromise; let loadedPromise;
try { 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) { } catch (error) {
console.warn("Failed to quote image:", error.message); 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; attr.whenDone = loadedPromise;
values = [react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_lazy_image_jsx__WEBPACK_IMPORTED_MODULE_7__["default"], attr, null), ' ', attr.alt]; 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, "offset": 0,
"pluralType": "cardinal" "pluralType": "cardinal"
}] }]
},
cannot_parse_vcard: {
id: "cannot_parse_vcard",
defaultMessage: [{
"type": 0,
"value": "Cannot parse vCard file."
}]
} }
}); });
function isUnconfirmed(acs) { function isUnconfirmed(acs) {
@@ -9802,6 +9826,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
this.sendVideoAttachment = this.sendVideoAttachment.bind(this); this.sendVideoAttachment = this.sendVideoAttachment.bind(this);
this.sendFileAttachment = this.sendFileAttachment.bind(this); this.sendFileAttachment = this.sendFileAttachment.bind(this);
this.sendAudioAttachment = this.sendAudioAttachment.bind(this); this.sendAudioAttachment = this.sendAudioAttachment.bind(this);
this.sendTheCardAttachment = this.sendTheCardAttachment.bind(this);
this.sendKeyPress = this.sendKeyPress.bind(this); this.sendKeyPress = this.sendKeyPress.bind(this);
this.subscribe = this.subscribe.bind(this); this.subscribe = this.subscribe.bind(this);
this.handleScrollReference = this.handleScrollReference.bind(this); this.handleScrollReference = this.handleScrollReference.bind(this);
@@ -10605,6 +10630,11 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
} }
sendFileAttachment(file) { sendFileAttachment(file) {
const maxInbandAttachmentSize = this.props.tinode.getServerParam('maxMessageSize', _config_js__WEBPACK_IMPORTED_MODULE_18__.MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024 | 0; 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; const jsonMimeConverter = fileType => fileType === _config_js__WEBPACK_IMPORTED_MODULE_18__.DRAFTY_FR_MIME_TYPE_LEGACY ? 'application/octet-stream' : fileType;
if (file.size > maxInbandAttachmentSize) { if (file.size > maxInbandAttachmentSize) {
const uploader = this.props.tinode.getLargeFileHelper(); 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')); }))).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) { handleAttachFile(file) {
const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', _config_js__WEBPACK_IMPORTED_MODULE_18__.MAX_EXTERN_ATTACHMENT_SIZE); const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', _config_js__WEBPACK_IMPORTED_MODULE_18__.MAX_EXTERN_ATTACHMENT_SIZE);
if (file.size > maxExternAttachmentSize) { 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", { }, "arrow_back")) : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box" className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_11__["default"], { }, 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, avatar: avatar,
topic: this.state.topic, topic: this.state.topic,
title: this.state.title, 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", { }) : this.props.readOnly && this.props.uid ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box" className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_1__["default"], { }, 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, avatar: true,
topic: this.props.uid, topic: this.props.uid,
title: this.props.title 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", { }, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box" className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], { }, 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, avatar: this.state.avatar || true,
topic: this.props.topic, topic: this.props.topic,
title: this.state.fullName 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", { }, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box" className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], { }, 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, avatar: this.props.avatar,
topic: this.props.topic, topic: this.props.topic,
title: this.props.title 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.handleExpandImage = this.handleExpandImage.bind(this);
this.handlePlayVideo = this.handlePlayVideo.bind(this); this.handlePlayVideo = this.handlePlayVideo.bind(this);
this.handleFormButtonClick = this.handleFormButtonClick.bind(this); this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
this.handleQuoteClick = this.handleQuoteClick.bind(this);
this.handleContextClick = this.handleContextClick.bind(this); this.handleContextClick = this.handleContextClick.bind(this);
this.handleCancelUpload = this.handleCancelUpload.bind(this); this.handleCancelUpload = this.handleCancelUpload.bind(this);
this.handleQuoteClick = this.handleQuoteClick.bind(this); this.handleDraftyClick = this.handleDraftyClick.bind(this);
this.formatterContext = { this.formatterContext = {
formatMessage: props.intl.formatMessage.bind(props.intl), formatMessage: props.intl.formatMessage.bind(props.intl),
viewportWidth: props.viewportWidth, viewportWidth: props.viewportWidth,
authorizeURL: props.tinode.authorizeURL.bind(props.tinode), authorizeURL: props.tinode.authorizeURL.bind(props.tinode),
onImagePreview: this.handleExpandImage, onHandleClick: this.handleDraftyClick
onVideoPreview: this.handlePlayVideo,
onFormButtonClick: this.handleFormButtonClick,
onQuoteClick: this.handleQuoteClick
}; };
} }
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) { handleExpandImage(e) {
e.preventDefault(); e.preventDefault();
this.props.onExpandMedia({ this.props.onExpandMedia({
@@ -16175,7 +16242,7 @@ class BaseChatMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().Pure
if (!this.props.response) { if (!this.props.response) {
let immutable = false; let immutable = false;
tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Drafty.entities(this.props.content, (_0, _1, tp) => { 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; return immutable;
}); });
if (!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", { }, this.props.isGroup && this.props.response ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box" className: "avatar-box"
}, fullDisplay ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_4__["default"], { }, 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, topic: this.props.userFrom,
title: this.props.userName, title: this.props.userName,
avatar: avatar avatar: avatar
@@ -16563,7 +16630,7 @@ class Chip extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent)
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { }) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box" className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_1__["default"], { }, 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, avatar: this.props.avatar || true,
topic: this.props.topic, topic: this.props.topic,
title: this.props.title 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", { }, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box" className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_4__["default"], { }, 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, avatar: avatar,
title: this.props.title, title: this.props.title,
topic: this.props.item, topic: this.props.item,
@@ -18216,7 +18283,7 @@ class GroupSubs extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
className: "avatar-box", className: "avatar-box",
key: sub.user key: sub.user
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], { }, 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, topic: sub.user,
avatar: (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__.makeImageUrl)(sub.public ? sub.public.photo : null) || true, 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 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) { } 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' : ''); const className = 'avatar' + (this.props.deleted ? ' deleted' : '');
avatar = react__WEBPACK_IMPORTED_MODULE_0___default().createElement("img", { avatar = react__WEBPACK_IMPORTED_MODULE_0___default().createElement("img", {
className: className, className: className,
@@ -20247,7 +20314,7 @@ class SideNavbar extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompo
id: "self-avatar", id: "self-avatar",
className: "avatar-box" className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_1__["default"], { }, 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, avatar: avatar,
topic: this.props.myUserId, topic: this.props.myUserId,
title: this.props.title 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":
/*!*****************************************!*\ /*!*****************************************!*\
!*** ./src/widgets/topic-desc-edit.jsx ***! !*** ./src/widgets/topic-desc-edit.jsx ***!
@@ -21263,12 +21456,6 @@ module.exports = tinode;
/******/ if (cachedModule !== undefined) { /******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports; /******/ 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) /******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = { /******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed /******/ // no module.id needed
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
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
View File
@@ -1400,7 +1400,7 @@ class DB {
/***/ (function(module) { /***/ (function(module) {
/** /**
* @copyright 2015-2024 Tinode LLC. * @copyright 2015-2026 Tinode LLC.
* @summary Minimally rich text representation and formatting for Tinode. * @summary Minimally rich text representation and formatting for Tinode.
* @license Apache 2.0 * @license Apache 2.0
* *
@@ -1460,7 +1460,7 @@ const MAX_PREVIEW_DATA_SIZE = 64;
const DRAFTY_MIME_TYPE = 'text/x-drafty'; const DRAFTY_MIME_TYPE = 'text/x-drafty';
const DRAFTY_FR_MIME_TYPE = 'text/x-drafty-fr'; const DRAFTY_FR_MIME_TYPE = 'text/x-drafty-fr';
const DRAFTY_FR_MIME_TYPE_LEGACY = 'application/json'; 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 segmenter = new Intl.Segmenter();
const INLINE_STYLES = [{ const INLINE_STYLES = [{
name: 'ST', name: 'ST',
@@ -1597,6 +1597,11 @@ const FORMAT_TAGS = {
md_tag: '*', md_tag: '*',
isVoid: false isVoid: false
}, },
TC: {
html_tag: 'div',
md_tag: undefined,
isVoid: false
},
VC: { VC: {
html_tag: 'div', html_tag: 'div',
md_tag: undefined, md_tag: undefined,
@@ -1769,6 +1774,17 @@ const DECORATORS = {
return data ? {} : null; return data ? {} : null;
} }
}, },
TC: {
open: _ => '<div>',
close: _ => '</div>',
props: data => {
if (!data) return {};
return {
'data-fn': data.fn,
'data-title': data.title
};
}
},
VC: { VC: {
open: _ => '<div>', open: _ => '<div>',
close: _ => '</div>', close: _ => '</div>',
@@ -2314,6 +2330,24 @@ Drafty.appendLineBreak = function (content) {
content.txt += ' '; content.txt += ' ';
return content; 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) { Drafty.UNSAFE_toHTML = function (doc) {
const tree = draftyToTree(doc); const tree = draftyToTree(doc);
const htmlFormatter = function (type, data, values) { 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":
/*!**********************!*\ /*!**********************!*\
!*** ./src/topic.js ***! !*** ./src/topic.js ***!
@@ -5549,6 +5913,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__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 */ 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 */ 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 */ Tinode: function() { return /* binding */ Tinode; }
/* harmony export */ }); /* harmony export */ });
/* harmony import */ var _access_mode_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./access-mode.js */ "./src/access-mode.js"); /* 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 _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 _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 _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 * @module tinode-sdk
* *
* @copyright 2015-2025 Tinode LLC. * @copyright 2015-2026 Tinode LLC.
* @summary Javascript bindings for Tinode. * @summary Javascript bindings for Tinode.
* @license Apache 2.0 * @license Apache 2.0
* @version 0.25 * @version 0.25
@@ -5622,6 +5988,8 @@ __webpack_require__.r(__webpack_exports__);
let WebSocketProvider; let WebSocketProvider;
if (typeof WebSocket != 'undefined') { if (typeof WebSocket != 'undefined') {
WebSocketProvider = WebSocket; WebSocketProvider = WebSocket;
@@ -5698,7 +6066,7 @@ function b64EncodeUnicode(str) {
} }
function jsonBuildHelper(key, val) { function jsonBuildHelper(key, val) {
if (val instanceof Date) { 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"]) { } else if (val instanceof _access_mode_js__WEBPACK_IMPORTED_MODULE_0__["default"]) {
val = val.jsonHelper(); 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) { } 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(_ => { }).then(_ => {
return this._db.mapUsers(data => { 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(_ => { }).then(_ => {
return Promise.all(prom); return Promise.all(prom);
@@ -5919,7 +6287,7 @@ class Tinode {
if (id) { if (id) {
promise = this.#makePromise(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); let msg = JSON.stringify(pkt);
this.logger("out: " + (this._trimLongStrings ? JSON.stringify(pkt, jsonLoggerHelper) : msg)); this.logger("out: " + (this._trimLongStrings ? JSON.stringify(pkt, jsonLoggerHelper) : msg));
try { try {
@@ -5945,7 +6313,7 @@ class Tinode {
} }
return; 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) { if (!pkt) {
this.logger("in: " + data); this.logger("in: " + data);
this.logger("ERROR: failed to parse data"); this.logger("ERROR: failed to parse data");
@@ -6206,13 +6574,13 @@ class Tinode {
if (pub) { if (pub) {
return { return {
user: uid, user: uid,
public: (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)({}, pub) public: (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)({}, pub)
}; };
} }
return undefined; return undefined;
}; };
topic._cachePutUser = (uid, user) => { 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 => { topic._cacheDelUser = uid => {
this.#cacheDel('user', uid); this.#cacheDel('user', uid);
@@ -6391,7 +6759,7 @@ class Tinode {
if (typeof url != 'string') { if (typeof url != 'string') {
return url; 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 base = 'scheme://host/';
const parsed = new URL(url, base); const parsed = new URL(url, base);
if (this._apiKey) { if (this._apiKey) {
@@ -6422,7 +6790,7 @@ class Tinode {
pkt.acc.tmpsecret = params.secret; pkt.acc.tmpsecret = params.secret;
if (Array.isArray(params.attachments) && params.attachments.length > 0) { if (Array.isArray(params.attachments) && params.attachments.length > 0) {
pkt.extra = { 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) { if (Array.isArray(setParams.attachments) && setParams.attachments.length > 0) {
pkt.extra = { 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) { if (setParams.tags) {
@@ -6574,7 +6942,7 @@ class Tinode {
}; };
if (attachments) { if (attachments) {
msg.extra = { 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); return this.#send(msg, pub.id);
@@ -6647,7 +7015,7 @@ class Tinode {
} }
getMeta(topic, params) { getMeta(topic, params) {
const pkt = this.#initPacket('get', topic); 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); return this.#send(pkt, pkt.get.id);
} }
setMeta(topic, params) { setMeta(topic, params) {
@@ -6662,7 +7030,7 @@ class Tinode {
}); });
if (Array.isArray(params.attachments) && params.attachments.length > 0) { if (Array.isArray(params.attachments) && params.attachments.length > 0) {
pkt.extra = { 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
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long