adding TheCard preview
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Contact Card",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Contacts",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "Full name",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": false,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "Organization",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": false,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +434,14 @@
|
||||
"defaultMessage": "Theme:",
|
||||
"description": "Label for selecting color scheme (dark, light) in Settings"
|
||||
},
|
||||
"label_contact_card": {
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview"
|
||||
},
|
||||
"label_contacts": {
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information"
|
||||
},
|
||||
"label_content_type": {
|
||||
"defaultMessage": "Content type:",
|
||||
"description": "Label for file content type (mime)"
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// File and image helper functions.
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
|
||||
// Supported image MIME types and corresponding file extensions.
|
||||
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||
@@ -305,3 +306,21 @@ export function base64ToIntArray(b64) {
|
||||
} catch (err) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function importVCard(fileOrBlob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = _ => {
|
||||
reject(reader.error);
|
||||
};
|
||||
reader.onload = _ => {
|
||||
const card = TheCard.importVCard(reader.result);
|
||||
if (!card) {
|
||||
reject(new Error("Failed to parse vCard"));
|
||||
return;
|
||||
}
|
||||
resolve(card);
|
||||
};
|
||||
reader.readAsText(fileOrBlob);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import CallMessage from '../widgets/call-message.jsx'
|
||||
import CallStatus from '../widgets/call-status.jsx';
|
||||
import InlineVideo from '../widgets/inline-video.jsx';
|
||||
import LazyImage from '../widgets/lazy-image.jsx';
|
||||
import TheCardViewer from '../widgets/the-card-viewer.jsx';
|
||||
import TheCardMini from '../widgets/the-card-mini.jsx';
|
||||
import UploadingImage from '../widgets/uploading-image.jsx'
|
||||
|
||||
import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO,
|
||||
@@ -135,7 +135,7 @@ export function fullFormatter(style, data, values, key, stack) {
|
||||
break;
|
||||
case 'TC':
|
||||
// TheCard
|
||||
el = TheCardViewer;
|
||||
el = TheCardMini;
|
||||
attr.content = data;
|
||||
attr.authorizeURL = this.authorizeURL;
|
||||
attr.onChatClick = (e) => this.onHandleClick(e, 'contact_chat');
|
||||
|
||||
+35
-35
@@ -19,13 +19,14 @@ import LogoView from './logo-view.jsx';
|
||||
import MetaMessage from '../widgets/meta-message.jsx';
|
||||
import PinnedMessages from '../widgets/pinned-messages.jsx';
|
||||
import SendMessage from '../widgets/send-message.jsx';
|
||||
import TheCardPreview from '../widgets/the-card-preview.jsx';
|
||||
import VideoPreview from '../widgets/video-preview.jsx';
|
||||
|
||||
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
|
||||
DRAFTY_FR_MIME_TYPE_LEGACY, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM,
|
||||
MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY, QUOTED_REPLY_LENGTH, VIDEO_PREVIEW_DIM } from '../config.js';
|
||||
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
||||
import { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { blobToBase64, fileToBase64, imageScaled, importVCard, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/strformat.js';
|
||||
|
||||
@@ -1103,16 +1104,13 @@ class MessagesView extends React.Component {
|
||||
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);
|
||||
importVCard(file)
|
||||
.then(card => {
|
||||
this.sendMessage(Drafty.appendTheCard(null, card));
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_parse_vcard, {error: err.message}), 'err');
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1523,7 +1521,7 @@ class MessagesView extends React.Component {
|
||||
|
||||
// P2P call is an overlay, not a separate component because it cannot be
|
||||
// re-mounted between full screen/minimized.
|
||||
const overlay = this.state.rtcPanel ? (
|
||||
const overlay = this.state.rtcPanel &&
|
||||
<CallPanel
|
||||
topic={this.state.topic}
|
||||
seq={this.props.callSeq}
|
||||
@@ -1533,23 +1531,20 @@ class MessagesView extends React.Component {
|
||||
title={this.state.title}
|
||||
avatar={this.state.avatar || true}
|
||||
minimized={this.state.minimizedCallPanel}
|
||||
|
||||
onError={this.props.onError}
|
||||
onHangup={this.handleCallHangup}
|
||||
onToggleMinimize={this.handleCallPanelToggle}
|
||||
onInvite={this.props.onCallInvite}
|
||||
onSendOffer={this.props.onCallSendOffer}
|
||||
onIceCandidate={this.props.onCallIceCandidate}
|
||||
onSendAnswer={this.props.onCallSendAnswer} />
|
||||
) : null;
|
||||
onSendAnswer={this.props.onCallSendAnswer} />;
|
||||
|
||||
let component;
|
||||
if (!this.state.topic) {
|
||||
component = (
|
||||
<LogoView
|
||||
component = <LogoView
|
||||
serverVersion={this.props.serverVersion}
|
||||
serverAddress={this.props.serverAddress} />
|
||||
);
|
||||
serverAddress={this.props.serverAddress} />;
|
||||
|
||||
} else {
|
||||
let component2;
|
||||
if (this.state.imagePreview) {
|
||||
@@ -1565,43 +1560,48 @@ class MessagesView extends React.Component {
|
||||
);
|
||||
} else if (this.state.videoPreview) {
|
||||
// Preview video.
|
||||
component2 = (
|
||||
<VideoPreview
|
||||
component2 = <VideoPreview
|
||||
content={this.state.videoPreview}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.state.reply}
|
||||
onError={this.props.onError}
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendVideoAttachment} />
|
||||
);
|
||||
onSendMessage={this.sendVideoAttachment} />;
|
||||
|
||||
} else if (this.state.imagePostview) {
|
||||
// Expand received image.
|
||||
component2 = (
|
||||
<ImagePreview
|
||||
component2 = <ImagePreview
|
||||
content={this.state.imagePostview}
|
||||
onClose={this.handleClosePreview} />
|
||||
);
|
||||
onClose={this.handleClosePreview} />;
|
||||
|
||||
} else if (this.state.videoPostview) {
|
||||
// Play received video.
|
||||
component2 = (
|
||||
<VideoPreview
|
||||
component2 = <VideoPreview
|
||||
content={this.state.videoPostview}
|
||||
tinode={this.props.tinode}
|
||||
onError={this.props.onError}
|
||||
onClose={this.handleClosePreview} />
|
||||
);
|
||||
onClose={this.handleClosePreview} />;
|
||||
|
||||
} else if (this.state.docPreview) {
|
||||
// Preview attachment before sending.
|
||||
component2 = (
|
||||
<DocPreview
|
||||
if (TheCard.isFileSupported(this.state.docPreview.type, this.state.docPreview.name)) {
|
||||
component2 = <TheCardPreview
|
||||
content={this.state.docPreview}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.state.reply}
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendFileAttachment} />
|
||||
);
|
||||
onSendMessage={this.sendFileAttachment} />;
|
||||
} else {
|
||||
component2 = <DocPreview
|
||||
content={this.state.docPreview}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.state.reply}
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendFileAttachment} />;
|
||||
}
|
||||
} else {
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
const isChannel = topic.isChannelType() || topic.chan;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
|
||||
const messages = defineMessages({
|
||||
unknown_name: {
|
||||
id: 'unknown_name',
|
||||
defaultMessage: 'Unknown',
|
||||
description: 'Name to show when the name is missing'
|
||||
}
|
||||
});
|
||||
|
||||
class TheCardMini extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleDownload = this.handleDownload.bind(this);
|
||||
this.handleMessage = this.handleMessage.bind(this);
|
||||
}
|
||||
|
||||
handleDownload(e) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const vcard = TheCard.exportVCard(this.props.content);
|
||||
const blob = new Blob([vcard], { type: 'text/vcard' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = (this.props.content.fn || 'contact') + '.vcf';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (err) {
|
||||
console.error("Failed to download vcard", err);
|
||||
if (this.props.onError) {
|
||||
this.props.onError("Failed to download vcard", 'err');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(e) {
|
||||
// This is handled by the parent component via props.onAction which expects a click event.
|
||||
}
|
||||
|
||||
render() {
|
||||
const card = this.props.content;
|
||||
if (!card) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const uid = TheCard.getFirstTinodeID(card);
|
||||
const contacts = TheCard.getEmails(card).concat(TheCard.getPhones(card));
|
||||
const org = TheCard.getOrg(card);
|
||||
return (
|
||||
<div className="contact-card">
|
||||
<div className="contact-body">
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
authorizeURL={this.props.authorizeURL}
|
||||
topic={uid || 'usr123XXX'}
|
||||
avatar={TheCard.getPhotoUrl(card) || true}
|
||||
title={card.fn} />
|
||||
</div>
|
||||
<div className="name-box">
|
||||
<div className="name">{card.fn || <FormattedMessage {...messages.unknown_name} />}</div>
|
||||
{org && <div className="org">{org}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="contact-actions">
|
||||
{uid &&
|
||||
<a className="flat-button" data-val={uid} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="chat_now" defaultMessage="Chat"
|
||||
description="Label for message button in contact card" />
|
||||
</a>
|
||||
}
|
||||
{uid && <div className="divider"></div>}
|
||||
{contacts.length > 0 &&
|
||||
<a className="flat-button" data-val={contacts.join(',')} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="find_user" defaultMessage="Find"
|
||||
description="Label for search button in contact card" />
|
||||
</a>
|
||||
}
|
||||
{contacts.length > 0 && <div className="divider"></div>}
|
||||
<a className="flat-button" onClick={this.handleDownload}>
|
||||
<FormattedMessage id="save_action" defaultMessage="Save" description="Label for Save button" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(TheCardMini);
|
||||
@@ -0,0 +1,190 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { parsePhoneNumber } from 'libphonenumber-js/mobile';
|
||||
|
||||
import SendMessage from './send-message.jsx';
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
import { importVCard } from '../lib/blob-helpers.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
unknown_name: {
|
||||
id: 'unknown_name',
|
||||
defaultMessage: 'Unknown',
|
||||
description: 'Name to show when the name is missing'
|
||||
}
|
||||
});
|
||||
|
||||
export default class TheCardPreview extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
content: null
|
||||
};
|
||||
|
||||
importVCard(props.content.file)
|
||||
.then(card => {
|
||||
console.log('Imported vCard:', card);
|
||||
this.setState({ content: card });
|
||||
});
|
||||
|
||||
this.handleSendCard = this.handleSendCard.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
e.preventDefault();
|
||||
if (e.key === 'Escape') {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleSendCard() {
|
||||
this.props.onClose();
|
||||
this.props.onSendMessage(this.props.content);
|
||||
}
|
||||
|
||||
render() {
|
||||
const card = this.state.content;
|
||||
|
||||
console.log('Rendering TheCardPreview for card:', JSON.stringify(card));
|
||||
|
||||
const fullName = TheCard.getFn(card);
|
||||
const photoUrl = TheCard.getPhotoUrl(card) || null;
|
||||
const note = card?.note;
|
||||
const org = TheCard.getOrg(card);
|
||||
|
||||
// Get communication methods
|
||||
const emails = TheCard.getComm(card, 'email') || [];
|
||||
console.log('Emails:', emails);
|
||||
const phones = TheCard.getComm(card, 'tel') || [];
|
||||
console.log('Phones:', phones);
|
||||
const tinodeIds = TheCard.getComm(card, 'tinode') || [];
|
||||
console.log('Tinode IDs:', tinodeIds);
|
||||
const urls = TheCard.getComm(card, 'http') || [];
|
||||
console.log('URLs:', urls);
|
||||
|
||||
const contacts = [];
|
||||
|
||||
// Add emails
|
||||
emails.forEach((email, idx) => {
|
||||
const types = Array.isArray(email.des) ? email.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`email-${idx}`}>
|
||||
<tt>{email.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add phones
|
||||
phones.forEach((phone, idx) => {
|
||||
let val = phone.value;
|
||||
try {
|
||||
const number = parsePhoneNumber(phone.value);
|
||||
val = number ? number.formatInternational() : phone.value;
|
||||
} catch (err) {
|
||||
// Use original value if parsing fails
|
||||
}
|
||||
const types = Array.isArray(phone.des) ? phone.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`tel-${idx}`}>
|
||||
<tt>{val}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add Tinode IDs
|
||||
tinodeIds.forEach((tid, idx) => {
|
||||
const types = Array.isArray(tid.des) ? tid.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`tinode-${idx}`}>
|
||||
<tt>{tid.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add URLs
|
||||
urls.forEach((url, idx) => {
|
||||
const types = Array.isArray(url.des) ? url.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`url-${idx}`}>
|
||||
<tt>{url.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="image-preview">
|
||||
<div id="image-preview-caption-panel">
|
||||
<span><FormattedMessage id="label_contact_card" defaultMessage="Contact Card"
|
||||
description="Label for contact card preview" /></span>
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}>
|
||||
<i className="material-icons gray">close</i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="image-preview-container">
|
||||
<div className="doc-card">
|
||||
<div className="panel-form-column">
|
||||
<center>
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={photoUrl || true}
|
||||
topic="usrXXX123"
|
||||
title={fullName} />
|
||||
</div>
|
||||
</center>
|
||||
<div className="group">
|
||||
<div className="large ellipsized">{fullName || <FormattedMessage {...messages.unknown_name} />}</div>
|
||||
</div>
|
||||
{org ?
|
||||
<div className="group">
|
||||
<div>{org}</div>
|
||||
</div>
|
||||
: null}
|
||||
{note ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for description" />
|
||||
</label>
|
||||
<div className="quoted">{note}</div>
|
||||
</div>
|
||||
: null}
|
||||
{contacts.length > 0 ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_contacts" defaultMessage="Contacts"
|
||||
description="Label for contact information" />
|
||||
</label>
|
||||
{contacts}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SendMessage
|
||||
noInput={true}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.props.reply}
|
||||
onCancelReply={this.props.onCancelReply}
|
||||
onSendMessage={this.handleSendCard}
|
||||
onError={this.props.onError} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user