split out a few modules, handle esc key

This commit is contained in:
or-else
2026-01-19 15:41:33 +03:00
parent 5b7565c8f8
commit a54ae70746
69 changed files with 8859 additions and 8497 deletions
+9 -3
View File
@@ -1978,7 +1978,7 @@ img.channel {
} }
#image-preview-caption-panel { #preview-caption-panel {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
background-color: var(--clr-background-blue-gray-50); background-color: var(--clr-background-blue-gray-50);
@@ -1988,15 +1988,16 @@ img.channel {
flex-grow: 0; flex-grow: 0;
height: var(--caption-panel-height); height: var(--caption-panel-height);
padding: 1rem 0.75rem 0 0.75rem; padding: 1rem 0.75rem 0 0.75rem;
font-size: 120%;
z-index: 3; z-index: 3;
} }
#image-preview-caption-panel a { #preview-caption-panel a {
color: var(--clr-text-secondary); color: var(--clr-text-secondary);
text-decoration: none; text-decoration: none;
} }
#image-preview-caption-panel a:hover { #preview-caption-panel a:hover {
color: var(--clr-dark); color: var(--clr-dark);
} }
@@ -2033,6 +2034,7 @@ img.channel {
background-color: var(--clr-std-ltgray); background-color: var(--clr-std-ltgray);
padding: 1rem; padding: 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
max-width: 30rem;
} }
.doc-card .avatar-box { .doc-card .avatar-box {
@@ -2041,6 +2043,10 @@ img.channel {
font-size: 4.5rem; font-size: 4.5rem;
} }
.doc-card .avatar-box .material-icons {
line-height: 10rem;
}
/* Video-call related styles */ /* Video-call related styles */
#video-container { #video-container {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1225,7 +1225,7 @@
"label_description": { "label_description": {
"translation": "الوصف", "translation": "الوصف",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1225,7 +1225,7 @@
"label_description": { "label_description": {
"translation": "Beschreibung", "translation": "Beschreibung",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1225,7 +1225,7 @@
"label_description": { "label_description": {
"translation": "Description", "translation": "Description",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "Descripción", "translation": "Descripción",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -452,7 +452,7 @@
}, },
"label_description": { "label_description": {
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description" "description": "Label for description of user or topic"
}, },
"label_file_name": { "label_file_name": {
"defaultMessage": "File name:", "defaultMessage": "File name:",
+1 -1
View File
@@ -1225,7 +1225,7 @@
"label_description": { "label_description": {
"translation": "Description", "translation": "Description",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "Descrizione", "translation": "Descrizione",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "설명", "translation": "설명",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "Descriere", "translation": "Descriere",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "Описание", "translation": "Описание",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1225,7 +1225,7 @@
"label_description": { "label_description": {
"translation": "คำอธิบาย", "translation": "คำอธิบาย",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "Опис", "translation": "Опис",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "說明", "translation": "說明",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -1218,7 +1218,7 @@
"label_description": { "label_description": {
"translation": "说明", "translation": "说明",
"defaultMessage": "Description", "defaultMessage": "Description",
"description": "Label for editing topic description", "description": "Label for description of user or topic",
"missing": false, "missing": false,
"obsolete": false "obsolete": false
}, },
+1 -1
View File
@@ -138,7 +138,7 @@ export default class AccountSettingsView extends React.Component {
<div className="group"> <div className="group">
<label className="small"> <label className="small">
<FormattedMessage id="label_description" defaultMessage="Description" <FormattedMessage id="label_description" defaultMessage="Description"
description="Label for editing topic description" /> description="Label for description of user or topic" />
</label> </label>
<div className="quoted">{this.state.description}</div> <div className="quoted">{this.state.description}</div>
</div> : null} </div> : null}
+13 -4
View File
@@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import { FormattedMessage, FormattedNumber, defineMessages, injectIntl } from 'react-intl'; import { FormattedMessage, FormattedNumber, defineMessages, injectIntl } from 'react-intl';
import { AccessMode, Tinode } from 'tinode-sdk'; import { AccessMode, Tinode, TheCard } from 'tinode-sdk';
import AvatarUpload from '../widgets/avatar-upload.jsx'; import AvatarUpload from '../widgets/avatar-upload.jsx';
import BadgeList from '../widgets/badge-list.jsx'; import BadgeList from '../widgets/badge-list.jsx';
@@ -20,7 +20,6 @@ import { MAX_TITLE_LENGTH, MAX_TOPIC_DESCRIPTION_LENGTH,
NO_ACCESS_MODE, TOAST_DURATION } from '../config.js'; NO_ACCESS_MODE, TOAST_DURATION } from '../config.js';
import { makeImageUrl } from '../lib/blob-helpers.js'; import { makeImageUrl } from '../lib/blob-helpers.js';
import { theCard } from '../lib/utils.js';
import { truncateString } from '../lib/strformat.js'; import { truncateString } from '../lib/strformat.js';
const messages = defineMessages({ const messages = defineMessages({
@@ -147,6 +146,7 @@ class InfoView extends React.Component {
this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this); this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
this.handleShowQRCode = this.handleShowQRCode.bind(this); this.handleShowQRCode = this.handleShowQRCode.bind(this);
this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this); this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this);
this.handleShare = this.handleShare.bind(this);
this.handleShowAddMembers = this.handleShowAddMembers.bind(this); this.handleShowAddMembers = this.handleShowAddMembers.bind(this);
this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this); this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
this.handleMemberSelected = this.handleMemberSelected.bind(this); this.handleMemberSelected = this.handleMemberSelected.bind(this);
@@ -299,7 +299,7 @@ class InfoView extends React.Component {
handleImageChanged(mime, img) { handleImageChanged(mime, img) {
this.setState({avatar: img}); this.setState({avatar: img});
this.props.onTopicDescUpdate(this.props.topic, theCard(null, img || Tinode.DEL_CHAR), null); this.props.onTopicDescUpdate(this.props.topic, new TheCard(null, img || Tinode.DEL_CHAR), null);
} }
handleMuted(ignored, checked) { handleMuted(ignored, checked) {
@@ -421,7 +421,13 @@ class InfoView extends React.Component {
this.setState({toast: this.props.intl.formatMessage(messages.text_copied)}); this.setState({toast: this.props.intl.formatMessage(messages.text_copied)});
setTimeout(_ => { this.setState({toast: ''}); }, TOAST_DURATION); setTimeout(_ => { this.setState({toast: ''}); }, TOAST_DURATION);
}); });
}
handleShare(e) {
e.preventDefault();
const card = new TheCard(this.state.fullName, this.state.avatar, null, this.state.description);
TheCard.setTinodeID(card, Tinode.URI_TOPIC_ID_PREFIX + this.props.topic);
this.props.onShareTheCard(card);
} }
handleShowAddMembers(e) { handleShowAddMembers(e) {
@@ -629,6 +635,9 @@ class InfoView extends React.Component {
&nbsp;<a href="#" onClick={this.handleShowQRCode}> &nbsp;<a href="#" onClick={this.handleShowQRCode}>
<i className="material-icons">qr_code</i> <i className="material-icons">qr_code</i>
</a>&nbsp; </a>&nbsp;
&nbsp;<a href="#" onClick={this.handleShare}>
<i className="material-icons">share</i>
</a>&nbsp;
</div> </div>
</div> </div>
{alias ? {alias ?
@@ -673,7 +682,7 @@ class InfoView extends React.Component {
<div className="group"> <div className="group">
<label className="small"> <label className="small">
<FormattedMessage id="label_description" defaultMessage="Description" <FormattedMessage id="label_description" defaultMessage="Description"
description="Label for editing topic description" /> description="Label for description of user or topic" />
</label> </label>
<div>{this.state.description}</div> <div>{this.state.description}</div>
</div> : null} </div> : null}
+102 -66
View File
@@ -1,17 +1,17 @@
// Panel with a chat. // Panel with a chat.
import React from 'react'; import React, { Suspense } from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Drafty, Tinode, TheCard } from 'tinode-sdk'; import { Drafty, Tinode, TheCard } from 'tinode-sdk';
import CallPanel from '../widgets/call-panel.jsx'; const CallPanel = React.lazy(_ => import('../widgets/call-panel.jsx'));
import ChatMessage from '../widgets/chat-message.jsx'; import ChatMessage from '../widgets/chat-message.jsx';
import ContactBadges from '../widgets/contact-badges.jsx'; import ContactBadges from '../widgets/contact-badges.jsx';
import DocPreview from '../widgets/doc-preview.jsx'; const DocPreview = React.lazy(_ => import('../widgets/doc-preview.jsx'));
import ErrorPanel from '../widgets/error-panel.jsx'; import ErrorPanel from '../widgets/error-panel.jsx';
import GroupSubs from '../widgets/group-subs.jsx'; import GroupSubs from '../widgets/group-subs.jsx';
import ImagePreview from '../widgets/image-preview.jsx'; const ImagePreview = React.lazy(_ => import('../widgets/image-preview.jsx'));
import Invitation from '../widgets/invitation.jsx'; import Invitation from '../widgets/invitation.jsx';
import LetterTile from '../widgets/letter-tile.jsx'; import LetterTile from '../widgets/letter-tile.jsx';
import LoadSpinner from '../widgets/load-spinner.jsx'; import LoadSpinner from '../widgets/load-spinner.jsx';
@@ -19,8 +19,8 @@ import LogoView from './logo-view.jsx';
import MetaMessage from '../widgets/meta-message.jsx'; import MetaMessage from '../widgets/meta-message.jsx';
import PinnedMessages from '../widgets/pinned-messages.jsx'; import PinnedMessages from '../widgets/pinned-messages.jsx';
import SendMessage from '../widgets/send-message.jsx'; import SendMessage from '../widgets/send-message.jsx';
import TheCardPreview from '../widgets/the-card-preview.jsx'; const TheCardPreview = React.lazy(_ => import('../widgets/the-card-preview.jsx'));
import VideoPreview from '../widgets/video-preview.jsx'; const VideoPreview = React.lazy(_ => import('../widgets/video-preview.jsx'));
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION, 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, DRAFTY_FR_MIME_TYPE_LEGACY, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM,
@@ -390,11 +390,18 @@ class MessagesView extends React.Component {
}; };
if (nextProps.forwardMessage) { if (nextProps.forwardMessage) {
// We are forwarding a message. Show preview. if (nextProps.forwardMessage.content) {
nextState.reply = { nextState.docPreview = {
content: nextProps.forwardMessage.preview, object: nextProps.forwardMessage.content,
seq: null type: nextProps.forwardMessage.type
}; };
} else {
// We are forwarding a message. Show preview.
nextState.reply = {
content: nextProps.forwardMessage.preview,
seq: null
};
}
} else { } else {
nextState.reply = null; nextState.reply = null;
} }
@@ -908,7 +915,14 @@ class MessagesView extends React.Component {
if (this.state.videoPreview && this.state.videoPreview.url) { if (this.state.videoPreview && this.state.videoPreview.url) {
URL.revokeObjectURL(this.state.videoPreview.url); URL.revokeObjectURL(this.state.videoPreview.url);
} }
this.setState({ imagePostview: null, imagePreview: null, docPreview: null, videoPreview: null, videoPostview: null}); this.setState({
imagePostview: null,
imagePreview: null,
docPreview: null,
videoPreview: null,
videoPostview: null,
});
this.props.onCancelForwardMessage();
} }
handleFormResponse(action, text, data) { handleFormResponse(action, text, data) {
@@ -1522,22 +1536,25 @@ class MessagesView extends React.Component {
// P2P call is an overlay, not a separate component because it cannot be // P2P call is an overlay, not a separate component because it cannot be
// re-mounted between full screen/minimized. // re-mounted between full screen/minimized.
const overlay = this.state.rtcPanel && const overlay = this.state.rtcPanel &&
<CallPanel <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
topic={this.state.topic} description="Message shown when component is loading"/></div>}>
seq={this.props.callSeq} <CallPanel
callState={this.props.callState} topic={this.state.topic}
callAudioOnly={this.props.callAudioOnly} seq={this.props.callSeq}
tinode={this.props.tinode} callState={this.props.callState}
title={this.state.title} callAudioOnly={this.props.callAudioOnly}
avatar={this.state.avatar || true} tinode={this.props.tinode}
minimized={this.state.minimizedCallPanel} title={this.state.title}
onError={this.props.onError} avatar={this.state.avatar || true}
onHangup={this.handleCallHangup} minimized={this.state.minimizedCallPanel}
onToggleMinimize={this.handleCallPanelToggle} onError={this.props.onError}
onInvite={this.props.onCallInvite} onHangup={this.handleCallHangup}
onSendOffer={this.props.onCallSendOffer} onToggleMinimize={this.handleCallPanelToggle}
onIceCandidate={this.props.onCallIceCandidate} onInvite={this.props.onCallInvite}
onSendAnswer={this.props.onCallSendAnswer} />; onSendOffer={this.props.onCallSendOffer}
onIceCandidate={this.props.onCallIceCandidate}
onSendAnswer={this.props.onCallSendAnswer} />
</Suspense>;
let component; let component;
if (!this.state.topic) { if (!this.state.topic) {
@@ -1550,57 +1567,76 @@ class MessagesView extends React.Component {
if (this.state.imagePreview) { if (this.state.imagePreview) {
// Preview image before sending. // Preview image before sending.
component2 = ( component2 = (
<ImagePreview <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
content={this.state.imagePreview} description="Message shown when component is loading"/></div>}>
tinode={this.props.tinode} <ImagePreview
reply={this.state.reply} content={this.state.imagePreview}
onCancelReply={this.handleCancelReply} tinode={this.props.tinode}
onClose={this.handleClosePreview} reply={this.state.reply}
onSendMessage={this.sendImageAttachment} /> onCancelReply={this.handleCancelReply}
onClose={this.handleClosePreview}
onSendMessage={this.sendImageAttachment} />
</Suspense>
); );
} else if (this.state.videoPreview) { } else if (this.state.videoPreview) {
// Preview video. // Preview video.
component2 = <VideoPreview component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
content={this.state.videoPreview} description="Message shown when component is loading"/></div>}>
tinode={this.props.tinode} <VideoPreview
reply={this.state.reply} content={this.state.videoPreview}
onError={this.props.onError} tinode={this.props.tinode}
onCancelReply={this.handleCancelReply} reply={this.state.reply}
onClose={this.handleClosePreview} onError={this.props.onError}
onSendMessage={this.sendVideoAttachment} />; onCancelReply={this.handleCancelReply}
onClose={this.handleClosePreview}
onSendMessage={this.sendVideoAttachment} />
</Suspense>;
} else if (this.state.imagePostview) { } else if (this.state.imagePostview) {
// Expand received image. // Expand received image.
component2 = <ImagePreview component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
content={this.state.imagePostview} description="Message shown when component is loading"/></div>}>
onClose={this.handleClosePreview} />; <ImagePreview
content={this.state.imagePostview}
onClose={this.handleClosePreview} />
</Suspense>;
} else if (this.state.videoPostview) { } else if (this.state.videoPostview) {
// Play received video. // Play received video.
component2 = <VideoPreview component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
content={this.state.videoPostview} description="Message shown when component is loading"/></div>}>
tinode={this.props.tinode} <VideoPreview
onError={this.props.onError} content={this.state.videoPostview}
onClose={this.handleClosePreview} />; tinode={this.props.tinode}
onError={this.props.onError}
onClose={this.handleClosePreview} />
</Suspense>;
} else if (this.state.docPreview) { } else if (this.state.docPreview) {
// Preview attachment before sending. // Preview attachment before sending.
if (TheCard.isFileSupported(this.state.docPreview.type, this.state.docPreview.name)) { if (TheCard.isFileSupported(this.state.docPreview.type, this.state.docPreview.name) ||
component2 = <TheCardPreview this.state.docPreview.type == TheCard.contentType) {
content={this.state.docPreview} component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
tinode={this.props.tinode} description="Message shown when component is loading"/></div>}>
reply={this.state.reply} <TheCardPreview
onCancelReply={this.handleCancelReply} content={this.state.docPreview}
onClose={this.handleClosePreview} tinode={this.props.tinode}
onSendMessage={this.sendFileAttachment} />; reply={this.state.reply}
onCancelReply={this.handleCancelReply}
onClose={this.handleClosePreview}
onSendMessage={this.sendFileAttachment} />
</Suspense>;
} else { } else {
component2 = <DocPreview component2 = <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
content={this.state.docPreview} description="Message shown when component is loading"/></div>}>
tinode={this.props.tinode} <DocPreview
reply={this.state.reply} content={this.state.docPreview}
onCancelReply={this.handleCancelReply} tinode={this.props.tinode}
onClose={this.handleClosePreview} reply={this.state.reply}
onSendMessage={this.sendFileAttachment} />; onCancelReply={this.handleCancelReply}
onClose={this.handleClosePreview}
onSendMessage={this.sendFileAttachment} />
</Suspense>;
} }
} else { } else {
const topic = this.props.tinode.getTopic(this.state.topic); const topic = this.props.tinode.getTopic(this.state.topic);
+87 -47
View File
@@ -15,7 +15,7 @@ import CallIncoming from '../widgets/call-incoming.jsx';
const PhoneCountrySelector = React.lazy(_ => import('../widgets/phone-country-selector.jsx')); const PhoneCountrySelector = React.lazy(_ => import('../widgets/phone-country-selector.jsx'));
const InfoView = React.lazy(_ => import('./info-view.jsx')); const InfoView = React.lazy(_ => import('./info-view.jsx'));
import MessagesView from './messages-view.jsx'; const MessagesView = React.lazy(_ => import('./messages-view.jsx'));
import SidepanelView from './sidepanel-view.jsx'; import SidepanelView from './sidepanel-view.jsx';
import { API_KEY, APP_NAME, DEFAULT_COLOR_SCHEME, DEFAULT_P2P_ACCESS_MODE, DEFAULT_TEXT_SIZE, FORWARDED_PREVIEW_LENGTH, import { API_KEY, APP_NAME, DEFAULT_COLOR_SCHEME, DEFAULT_P2P_ACCESS_MODE, DEFAULT_TEXT_SIZE, FORWARDED_PREVIEW_LENGTH,
@@ -108,6 +108,7 @@ class TinodeWeb extends React.Component {
this.handleResize = this.handleResize.bind(this); this.handleResize = this.handleResize.bind(this);
this.handleHashRoute = this.handleHashRoute.bind(this); this.handleHashRoute = this.handleHashRoute.bind(this);
this.handleOnline = this.handleOnline.bind(this); this.handleOnline = this.handleOnline.bind(this);
this.handleEscapeKey = this.handleEscapeKey.bind(this);
this.handleColorSchemeChange = this.handleColorSchemeChange.bind(this); this.handleColorSchemeChange = this.handleColorSchemeChange.bind(this);
this.checkForAppUpdate = this.checkForAppUpdate.bind(this); this.checkForAppUpdate = this.checkForAppUpdate.bind(this);
this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this); this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);
@@ -165,6 +166,7 @@ class TinodeWeb extends React.Component {
this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this); this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);
this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this); this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this);
this.handleReportTopic = this.handleReportTopic.bind(this); this.handleReportTopic = this.handleReportTopic.bind(this);
this.handleShareTheCard = this.handleShareTheCard.bind(this);
this.handleShowContextMenu = this.handleShowContextMenu.bind(this); this.handleShowContextMenu = this.handleShowContextMenu.bind(this);
this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this); this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this);
this.handleHideContextMenu = this.handleHideContextMenu.bind(this); this.handleHideContextMenu = this.handleHideContextMenu.bind(this);
@@ -319,6 +321,9 @@ class TinodeWeb extends React.Component {
// Window/tab visible or invisible for pausing timers. // Window/tab visible or invisible for pausing timers.
document.addEventListener('visibilitychange', this.handleVisibilityEvent); document.addEventListener('visibilitychange', this.handleVisibilityEvent);
// Listen for Escape key to clear selections.
document.addEventListener('keydown', this.handleEscapeKey);
document.documentElement.style.colorScheme = this.state.colorSchema == 'auto' ? 'light dark' : this.state.colorSchema; document.documentElement.style.colorScheme = this.state.colorSchema == 'auto' ? 'light dark' : this.state.colorSchema;
document.documentElement.style.setProperty('--message-text-size', `${this.state.textSize}pt`); document.documentElement.style.setProperty('--message-text-size', `${this.state.textSize}pt`);
@@ -407,6 +412,7 @@ class TinodeWeb extends React.Component {
window.removeEventListener('offline', this.handleOnlineOff); window.removeEventListener('offline', this.handleOnlineOff);
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.handleColorSchemeChange); window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.handleColorSchemeChange);
document.removeEventListener('visibilitychange', this.handleVisibilityEvent); document.removeEventListener('visibilitychange', this.handleVisibilityEvent);
document.removeEventListener('keydown', this.handleEscapeKey);
clearInterval(this.wakeUpTicker); clearInterval(this.wakeUpTicker);
} }
@@ -516,6 +522,27 @@ class TinodeWeb extends React.Component {
} }
} }
handleEscapeKey(e) {
if (e.key === 'Escape' || e.keyCode === 27) {
// Clear selections and navigate to base state
this.setState({
topicSelected: null,
errorText: '',
errorLevel: null,
mobilePanel: 'sidepanel',
topicSelectedOnline: false,
topicSelectedAcs: null,
infoPanel: undefined,
forwardMessage: null,
contextMenuVisible: false,
forwardDialogVisible: false,
alertVisible: false
});
// Navigate to contacts or login view
HashNavigation.navigateTo(HashNavigation.setUrlTopic('', null));
}
}
// Check if a newer version of TinodeWeb app is available at the server. // Check if a newer version of TinodeWeb app is available at the server.
checkForAppUpdate(reg) { checkForAppUpdate(reg) {
reg.onupdatefound = _ => { reg.onupdatefound = _ => {
@@ -1703,7 +1730,7 @@ class TinodeWeb extends React.Component {
// If the Find panel is active, close it. // If the Find panel is active, close it.
this.handleSidepanelCancel(); this.handleSidepanelCancel();
} }
const header = '➦ ' + params.userName; const header = params.userName ? '➦ ' + params.userName : '';
const content = typeof params.content == 'string' ? Drafty.init(params.content) : Drafty.forwardedContent(params.content); const content = typeof params.content == 'string' ? Drafty.init(params.content) : Drafty.forwardedContent(params.content);
const preview = Drafty.preview(content, FORWARDED_PREVIEW_LENGTH, true); const preview = Drafty.preview(content, FORWARDED_PREVIEW_LENGTH, true);
const msg = Drafty.append(Drafty.appendLineBreak(Drafty.mention(header, params.userFrom)), content); const msg = Drafty.append(Drafty.appendLineBreak(Drafty.mention(header, params.userFrom)), content);
@@ -1718,6 +1745,16 @@ class TinodeWeb extends React.Component {
}); });
} }
handleShareTheCard(card) {
this.setState({
forwardDialogVisible: true,
forwardMessage: {
content: card,
type: card.contentType,
}
});
}
defaultTopicContextMenu(topicName) { defaultTopicContextMenu(topicName) {
const topic = this.tinode.getTopic(topicName); const topic = this.tinode.getTopic(topicName);
@@ -2228,56 +2265,59 @@ class TinodeWeb extends React.Component {
: null} : null}
{!this.state.displayMobile || (this.state.mobilePanel == 'topic-view' && !this.state.infoPanel) ? {!this.state.displayMobile || (this.state.mobilePanel == 'topic-view' && !this.state.infoPanel) ?
<MessagesView <Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
tinode={this.tinode} description="Message shown when component is loading"/></div>}>
connected={this.state.connected} <MessagesView
ready={this.state.ready} tinode={this.tinode}
online={this.state.topicSelectedOnline} connected={this.state.connected}
acs={this.state.topicSelectedAcs} ready={this.state.ready}
displayMobile={this.state.displayMobile} online={this.state.topicSelectedOnline}
viewportWidth={this.state.viewportWidth} acs={this.state.topicSelectedAcs}
viewportHeight={this.state.viewportHeight} displayMobile={this.state.displayMobile}
topic={this.state.topicSelected} viewportWidth={this.state.viewportWidth}
myUserId={this.state.myUserId} viewportHeight={this.state.viewportHeight}
// User public.fn. topic={this.state.topicSelected}
myUserName={this.state.sidePanelTitle} myUserId={this.state.myUserId}
serverVersion={this.state.serverVersion} // User public.fn.
serverAddress={this.state.serverAddress} myUserName={this.state.sidePanelTitle}
applicationVisible={this.state.applicationVisible} serverVersion={this.state.serverVersion}
colorSchema={colorSchema} serverAddress={this.state.serverAddress}
sendOnEnter={this.state.sendOnEnter} applicationVisible={this.state.applicationVisible}
wallpaper={this.state.wallpaper} colorSchema={colorSchema}
sendOnEnter={this.state.sendOnEnter}
wallpaper={this.state.wallpaper}
forwardMessage={this.state.forwardMessage} forwardMessage={this.state.forwardMessage}
onCancelForwardMessage={this.handleHideForwardDialog} onCancelForwardMessage={this.handleHideForwardDialog}
callTopic={this.state.callTopic} callTopic={this.state.callTopic}
callSeq={this.state.callSeq} callSeq={this.state.callSeq}
callState={this.state.callState} callState={this.state.callState}
callAudioOnly={this.state.callAudioOnly} callAudioOnly={this.state.callAudioOnly}
onCallHangup={this.handleCallHangup} onCallHangup={this.handleCallHangup}
onAcceptCall={this.handleCallAccept} onAcceptCall={this.handleCallAccept}
onCallInvite={this.handleCallInvite} onCallInvite={this.handleCallInvite}
onCallSendOffer={this.handleCallSendOffer} onCallSendOffer={this.handleCallSendOffer}
onCallIceCandidate={this.handleCallIceCandidate} onCallIceCandidate={this.handleCallIceCandidate}
onCallSendAnswer={this.handleCallSendAnswer} onCallSendAnswer={this.handleCallSendAnswer}
errorText={this.state.errorText} errorText={this.state.errorText}
errorLevel={this.state.errorLevel} errorLevel={this.state.errorLevel}
errorAction={this.state.errorAction} errorAction={this.state.errorAction}
errorActionText={this.state.errorActionText} errorActionText={this.state.errorActionText}
newTopicParams={this.state.newTopicParams} newTopicParams={this.state.newTopicParams}
onHideMessagesView={this.handleHideMessagesView} onHideMessagesView={this.handleHideMessagesView}
onError={this.handleError} onError={this.handleError}
onNewTopicCreated={this.handleNewTopicCreated} onNewTopicCreated={this.handleNewTopicCreated}
showContextMenu={this.handleShowContextMenu} showContextMenu={this.handleShowContextMenu}
onChangePermissions={this.handleChangePermissions} onChangePermissions={this.handleChangePermissions}
onNewChat={this.handleNewChatInvitation} onNewChat={this.handleNewChatInvitation}
sendMessage={this.handleSendMessage} sendMessage={this.handleSendMessage}
onVideoCallClosed={this.handleCallClose} /> onVideoCallClosed={this.handleCallClose} />
</Suspense>
: null} : null}
{this.state.infoPanel ? {this.state.infoPanel ?
@@ -2307,7 +2347,7 @@ class TinodeWeb extends React.Component {
onLeaveTopic={this.handleLeaveUnsubRequest} onLeaveTopic={this.handleLeaveUnsubRequest}
onBlockTopic={this.handleBlockTopicRequest} onBlockTopic={this.handleBlockTopicRequest}
onReportTopic={this.handleReportTopic} onReportTopic={this.handleReportTopic}
onAddMember={this.handleManageGroupMembers} onShareTheCard={this.handleShareTheCard}
onTopicTagsUpdateRequest={this.handleTagsUpdateRequest} onTopicTagsUpdateRequest={this.handleTagsUpdateRequest}
onTopicUnArchive={this.handleUnarchive} onTopicUnArchive={this.handleUnarchive}
onInitFind={this.tnInitFind} onInitFind={this.tnInitFind}
+1 -1
View File
@@ -46,7 +46,7 @@ export default class DocPreview extends React.PureComponent {
} }
return ( return (
<div id="image-preview"> <div id="image-preview">
<div id="image-preview-caption-panel"> <div id="preview-caption-panel">
<span>{this.props.content.name}</span> <span>{this.props.content.name}</span>
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a> <a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
</div> </div>
+1 -1
View File
@@ -77,7 +77,7 @@ export default class ImagePreview extends React.PureComponent {
return ( return (
<div id="image-preview"> <div id="image-preview">
<div id="image-preview-caption-panel"> <div id="preview-caption-panel">
{this.props.onSendMessage ? {this.props.onSendMessage ?
<span>{fname}</span> <span>{fname}</span>
: :
+1 -1
View File
@@ -161,7 +161,7 @@ export default class NewTopicGroup extends React.PureComponent {
<div className="group"> <div className="group">
<label className="small" htmlFor="new-topic-desc"> <label className="small" htmlFor="new-topic-desc">
<FormattedMessage id="label_description" defaultMessage="Description" <FormattedMessage id="label_description" defaultMessage="Description"
description="Label for editing topic description" /> description="Label for description of user or topic" />
</label> </label>
<FormattedMessage id="description_editing_placeholder" defaultMessage="Description (optional)" <FormattedMessage id="description_editing_placeholder" defaultMessage="Description (optional)"
description="Placeholder for editing topic description">{ description="Placeholder for editing topic description">{
+1
View File
@@ -23,6 +23,7 @@ export default class ShowQRCode extends React.PureComponent {
<div className="panel-form-column"> <div className="panel-form-column">
<br /> <br />
<div className="qr-code" ref={this.qrCodeRef} /> <div className="qr-code" ref={this.qrCodeRef} />
<div><tt>{this.props.uri}</tt></div>
<div className="dialog-buttons"> <div className="dialog-buttons">
<button className="outline" onClick={this.props.onCancel}> <button className="outline" onClick={this.props.onCancel}>
<FormattedMessage id="button_ok" defaultMessage="OK" description="Button [OK]" /> <FormattedMessage id="button_ok" defaultMessage="OK" description="Button [OK]" />
+9 -7
View File
@@ -21,13 +21,15 @@ export default class TheCardPreview extends React.PureComponent {
super(props); super(props);
this.state = { this.state = {
content: null content: props.content.object
}; };
importVCard(props.content.file) if (!props.content.object) {
.then(card => { importVCard(props.content.file)
this.setState({ content: card }); .then(card => {
}); this.setState({ content: card });
});
}
this.handleSendCard = this.handleSendCard.bind(this); this.handleSendCard = this.handleSendCard.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
@@ -122,7 +124,7 @@ export default class TheCardPreview extends React.PureComponent {
return ( return (
<div id="image-preview"> <div id="image-preview">
<div id="image-preview-caption-panel"> <div id="preview-caption-panel">
<span><FormattedMessage id="label_contact_card" defaultMessage="Contact Card" <span><FormattedMessage id="label_contact_card" defaultMessage="Contact Card"
description="Label for contact card preview" /></span> description="Label for contact card preview" /></span>
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}> <a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}>
@@ -153,7 +155,7 @@ export default class TheCardPreview extends React.PureComponent {
<div className="group"> <div className="group">
<label className="small"> <label className="small">
<FormattedMessage id="label_description" defaultMessage="Description" <FormattedMessage id="label_description" defaultMessage="Description"
description="Label for description" /> description="Label for description of user or topic" />
</label> </label>
<div className="quoted">{note}</div> <div className="quoted">{note}</div>
</div> </div>
+1 -1
View File
@@ -363,7 +363,7 @@ class TopicDescEdit extends React.Component {
<div className="group"> <div className="group">
<div><label className="small"> <div><label className="small">
<FormattedMessage id="label_description" defaultMessage="Description" <FormattedMessage id="label_description" defaultMessage="Description"
description="Label for editing topic description" /> description="Label for description of user or topic" />
</label></div> </label></div>
<div> <div>
<FormattedMessage id="description_editing_placeholder" <FormattedMessage id="description_editing_placeholder"
+1 -1
View File
@@ -81,7 +81,7 @@ class VideoPreview extends React.PureComponent {
return ( return (
<div id="image-preview"> <div id="image-preview">
<div id="image-preview-caption-panel"> <div id="preview-caption-panel">
<span>{this.props.content.filename}</span> <span>{this.props.content.filename}</span>
<a href="#" onClick={e => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a> <a href="#" onClick={e => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
</div> </div>
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[371],{6371:function(e,t,n){n.r(t);var i=n(1594),o=n.n(i),s=n(8181),r=n(1725),a=n(8589);const l=(0,s.defineMessages)({unrecognized_video_format:{id:"unrecognized_video_format",defaultMessage:[{type:0,value:"Format of this video is not recognized"}]}});class d extends o().PureComponent{constructor(e){super(e),this.videoRef=o().createRef(),this.handleSendVideo=this.handleSendVideo.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){this.props.onSendMessage||(e.preventDefault(),"Escape"===e.key&&this.props.onClose())}handleSendVideo(e){this.props.onClose();const t={width:this.videoRef.current.videoWidth,height:this.videoRef.current.videoHeight,duration:1e3*this.videoRef.current.duration|0,mime:this.props.content.mime,name:this.props.content.filename};if(0==t.width||0==t.height)return void this.props.onError(this.props.intl.formatMessage(l.unrecognized_video_format),"err");const n=document.createElement("canvas");n.width=t.width,n.height=t.height;const i=n.getContext("2d");i.drawImage(this.videoRef.current,0,0,n.width,n.height),i.canvas.toBlob(n=>this.props.onSendMessage(e,this.props.content.blob,n,t),"image/jpeg",.75)}render(){if(!this.props.content)return null;const e=this.props.content.width||"-",t=this.props.content.height||"-",n=this.props.onSendMessage?"nodownload":"",i=!this.props.onSendMessage;return o().createElement("div",{id:"image-preview"},o().createElement("div",{id:"preview-caption-panel"},o().createElement("span",null,this.props.content.filename),o().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},o().createElement("i",{className:"material-icons gray"},"close"))),o().createElement("div",{id:"image-preview-container"},o().createElement("video",{className:"image-preview",controls:!0,controlsList:n,disablePictureInPicture:!0,ref:this.videoRef,autoPlay:i,src:this.props.tinode.authorizeURL(this.props.content.url),poster:this.props.content.preview,alt:this.props.content.filename})),this.props.onSendMessage?o().createElement(r.A,{messagePrompt:"add_image_caption",acceptBlank:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendVideo,onError:this.props.onError}):o().createElement("div",{id:"image-preview-footer"},o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))),o().createElement("div",null,o().createElement("span",{title:this.props.content.filename},this.props.content.filename))),o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))),o().createElement("div",null,this.props.content.type)),o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))),o().createElement("div",null,e," × ",t," px; ",(0,a.lX)(this.props.content.size)))))}}t.default=(0,s.injectIntl)(d)}}]);
//# sourceMappingURL=371.prod.js.map
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[472],{4472:function(e,t,n){n.r(t),n.d(t,{default:function(){return r}});var a=n(1594),l=n.n(a),s=n(8181),o=n(1725),i=n(8589);class r extends l().PureComponent{constructor(e){super(e),this.handleSendDoc=this.handleSendDoc.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){e.preventDefault(),"Escape"===e.key&&this.props.onClose()}handleSendDoc(e){this.props.onClose(),this.props.onSendMessage(this.props.content.file)}render(){return this.props.content?l().createElement("div",{id:"image-preview"},l().createElement("div",{id:"preview-caption-panel"},l().createElement("span",null,this.props.content.name),l().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},l().createElement("i",{className:"material-icons gray"},"close"))),l().createElement("div",{id:"image-preview-container"},l().createElement("div",{className:"doc-card"},l().createElement("i",{className:"material-icons gray"},function(e){const t={default:"insert_drive_file",image:"image",text:"description",video:"theaters"};return t[e]||t[(e||"").split("/")[0]]||t.default}(this.props.content.type)),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))," ",(0,i.Tu)(this.props.content.name,24)||"-"),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))," ",this.props.content.type||"application/octet-stream"),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))," ",(0,i.lX)(this.props.content.size)))),l().createElement(o.A,{noInput:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendDoc,onError:this.props.onError})):null}}}}]);
//# sourceMappingURL=472.prod.js.map
File diff suppressed because one or more lines are too long
+1 -1
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
+2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[889],{8889:function(e,t,n){n.r(t),n.d(t,{default:function(){return d}});var s=n(1594),i=n.n(s),a=n(8181),l=n(1725),o=n(9027),r=n(6110),p=n(8589),h=n(3540);class d extends i().PureComponent{constructor(e){super(e),this.state={width:0,height:0},this.handleSendImage=this.handleSendImage.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){this.props.onSendMessage||(e.preventDefault(),"Escape"===e.key&&this.props.onClose())}assignWidth(e){if(e&&!this.state.width){const t=e.getBoundingClientRect();this.setState({width:0|t.width,height:0|t.height})}}handleSendImage(e){this.props.onClose(),this.props.onSendMessage(e,this.props.content.blob)}render(){if(!this.props.content)return null;const e=(0,r.ae)(this.props.content.width,this.props.content.height,this.state.width,this.state.height,!1),t=e?{width:e.dstWidth+"px",height:e.dstHeight+"px"}:this.props.content.width>this.props.content.height?{width:"100%"}:{height:"100%"};t.maxWidth="100%",t.maxHeight="100%";const n=Math.max((this.state.width/o.JS/1.5|0)-2,12),s=(0,p.Tu)(this.props.content.filename,n)||"-",d=this.props.content.width||"-",c=this.props.content.height||"-",m=(0,h.FH)(this.props.content.url);return i().createElement("div",{id:"image-preview"},i().createElement("div",{id:"preview-caption-panel"},this.props.onSendMessage?i().createElement("span",null,s):i().createElement("a",{href:m,download:this.props.content.filename},i().createElement("i",{className:"material-icons"},"file_download")," ",i().createElement(a.FormattedMessage,{id:"download_action",defaultMessage:[{type:0,value:"download"}]})),i().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},i().createElement("i",{className:"material-icons gray"},"close"))),i().createElement("div",{id:"image-preview-container",ref:e=>this.assignWidth(e)},i().createElement("img",{src:this.props.content.url,style:t,className:"image-preview",alt:this.props.content.filename})),this.props.onSendMessage?i().createElement(l.A,{messagePrompt:"add_image_caption",acceptBlank:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendImage,onError:this.props.onError}):i().createElement("div",{id:"image-preview-footer"},i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))),i().createElement("div",null,i().createElement("span",{title:this.props.content.filename},s))),i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))),i().createElement("div",null,this.props.content.type)),i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))),i().createElement("div",null,d," × ",c," px; ",(0,p.lX)(this.props.content.size)))))}}}}]);
//# sourceMappingURL=889.prod.js.map
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[942],{8942:function(e,t,a){a.r(t),a.d(t,{default:function(){return p}});var n=a(1594),l=a.n(n),s=a(8181),r=a(8892),o=a(1725),c=a(6904),d=a(4532),i=a(6110);const m=(0,s.defineMessages)({unknown_name:{id:"unknown_name",defaultMessage:[{type:0,value:"Unknown"}]}});class p extends l().PureComponent{constructor(e){super(e),this.state={content:e.content.object},e.content.object||(0,i.QJ)(e.content.file).then(e=>{this.setState({content:e})}),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(),"Escape"===e.key&&this.props.onClose()}handleSendCard(){this.props.onClose(),this.props.onSendMessage(this.props.content)}render(){const e=this.state.content,t=d.TheCard.getFn(e),a=d.TheCard.getPhotoUrl(e)||null,n=e?.note,i=d.TheCard.getOrg(e),p=d.TheCard.getComm(e,"email")||[],u=d.TheCard.getComm(e,"tel")||[],h=d.TheCard.getComm(e,"tinode")||[],E=d.TheCard.getComm(e,"http")||[],v=[];return p.forEach((e,t)=>{const a=Array.isArray(e.des)?e.des.join(", "):"";v.push(l().createElement("div",{className:"group quoted",key:`email-${t}`},l().createElement("tt",null,e.value),l().createElement("span",{className:"small gray"}," ",a?`(${a})`:"")))}),u.forEach((e,t)=>{let a=e.value;try{const t=(0,r.L)(e.value);a=t?t.formatInternational():e.value}catch(e){}const n=Array.isArray(e.des)?e.des.join(", "):"";v.push(l().createElement("div",{className:"group quoted",key:`tel-${t}`},l().createElement("tt",null,a),l().createElement("span",{className:"small gray"}," ",n?`(${n})`:"")))}),h.forEach((e,t)=>{const a=Array.isArray(e.des)?e.des.join(", "):"";v.push(l().createElement("div",{className:"group quoted",key:`tinode-${t}`},l().createElement("tt",null,e.value),l().createElement("span",{className:"small gray"}," ",a?`(${a})`:"")))}),E.forEach((e,t)=>{const a=Array.isArray(e.des)?e.des.join(", "):"";v.push(l().createElement("div",{className:"group quoted",key:`url-${t}`},l().createElement("tt",null,e.value),l().createElement("span",{className:"small gray"}," ",a?`(${a})`:"")))}),l().createElement("div",{id:"image-preview"},l().createElement("div",{id:"preview-caption-panel"},l().createElement("span",null,l().createElement(s.FormattedMessage,{id:"label_contact_card",defaultMessage:[{type:0,value:"Contact Card"}]})),l().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},l().createElement("i",{className:"material-icons gray"},"close"))),l().createElement("div",{id:"image-preview-container"},l().createElement("div",{className:"doc-card"},l().createElement("div",{className:"panel-form-column"},l().createElement("center",null,l().createElement("div",{className:"avatar-box"},l().createElement(c.A,{authorizeURL:this.props.tinode.authorizeURL,avatar:a||!0,topic:"usrXXX123",title:t}))),l().createElement("div",{className:"group"},l().createElement("div",{className:"large ellipsized"},t||l().createElement(s.FormattedMessage,m.unknown_name))),i?l().createElement("div",{className:"group"},l().createElement("div",null,i)):null,n?l().createElement("div",{className:"group"},l().createElement("label",{className:"small"},l().createElement(s.FormattedMessage,{id:"label_description",defaultMessage:[{type:0,value:"Description"}]})),l().createElement("div",{className:"quoted"},n)):null,v.length>0?l().createElement("div",{className:"group"},l().createElement("label",{className:"small"},l().createElement(s.FormattedMessage,{id:"label_contacts",defaultMessage:[{type:0,value:"Contacts"}]})),v):null))),l().createElement(o.A,{noInput:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendCard,onError:this.props.onError}))}}}}]);
//# sourceMappingURL=942.prod.js.map
File diff suppressed because one or more lines are too long
+137 -8312
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+19 -9
View File
@@ -27,9 +27,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _widgets_topic_security_jsx__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../widgets/topic-security.jsx */ "./src/widgets/topic-security.jsx"); /* harmony import */ var _widgets_topic_security_jsx__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../widgets/topic-security.jsx */ "./src/widgets/topic-security.jsx");
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ../config.js */ "./src/config.js"); /* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
/* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js"); /* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js");
/* harmony import */ var _lib_utils_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ../lib/utils.js */ "./src/lib/utils.js"); /* harmony import */ var _lib_strformat_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ../lib/strformat.js */ "./src/lib/strformat.js");
/* harmony import */ var _lib_strformat_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ../lib/strformat.js */ "./src/lib/strformat.js");
@@ -198,6 +196,7 @@ class InfoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this); this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
this.handleShowQRCode = this.handleShowQRCode.bind(this); this.handleShowQRCode = this.handleShowQRCode.bind(this);
this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this); this.handleCopyToClipboard = this.handleCopyToClipboard.bind(this);
this.handleShare = this.handleShare.bind(this);
this.handleShowAddMembers = this.handleShowAddMembers.bind(this); this.handleShowAddMembers = this.handleShowAddMembers.bind(this);
this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this); this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
this.handleMemberSelected = this.handleMemberSelected.bind(this); this.handleMemberSelected = this.handleMemberSelected.bind(this);
@@ -279,11 +278,11 @@ class InfoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
sharer: acs && acs.isSharer(), sharer: acs && acs.isSharer(),
deleter: acs && acs.isDeleter(), deleter: acs && acs.isDeleter(),
muted: acs && acs.isMuted(), muted: acs && acs.isMuted(),
fullName: isSelf ? props.intl.formatMessage(messages.self_topic_name) : (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_17__.truncateString)(topic.public && topic.public.fn, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_TITLE_LENGTH), fullName: isSelf ? props.intl.formatMessage(messages.self_topic_name) : (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_16__.truncateString)(topic.public && topic.public.fn, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_TITLE_LENGTH),
description: isSelf ? props.intl.formatMessage(messages.self_topic_comment) : (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_17__.truncateString)(topic.public && topic.public.note, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_TOPIC_DESCRIPTION_LENGTH), description: isSelf ? props.intl.formatMessage(messages.self_topic_comment) : (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_16__.truncateString)(topic.public && topic.public.note, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_TOPIC_DESCRIPTION_LENGTH),
avatar: (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.makeImageUrl)(topic.public ? topic.public.photo : null), avatar: (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_15__.makeImageUrl)(topic.public ? topic.public.photo : null),
trustedBadges: badges, trustedBadges: badges,
private: (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_17__.truncateString)(topic.private && topic.private.comment, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_TITLE_LENGTH), private: (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_16__.truncateString)(topic.private && topic.private.comment, _config_js__WEBPACK_IMPORTED_MODULE_14__.MAX_TITLE_LENGTH),
archived: topic.isArchived(), archived: topic.isArchived(),
address: topic.name, address: topic.name,
groupTopic: topic.isGroupType(), groupTopic: topic.isGroupType(),
@@ -329,7 +328,7 @@ class InfoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
this.setState({ this.setState({
avatar: img avatar: img
}); });
this.props.onTopicDescUpdate(this.props.topic, (0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_16__.theCard)(null, img || tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.DEL_CHAR), null); this.props.onTopicDescUpdate(this.props.topic, new tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard(null, img || tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.DEL_CHAR), null);
} }
handleMuted(ignored, checked) { handleMuted(ignored, checked) {
this.setState({ this.setState({
@@ -457,6 +456,12 @@ class InfoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
}, _config_js__WEBPACK_IMPORTED_MODULE_14__.TOAST_DURATION); }, _config_js__WEBPACK_IMPORTED_MODULE_14__.TOAST_DURATION);
}); });
} }
handleShare(e) {
e.preventDefault();
const card = new tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard(this.state.fullName, this.state.avatar, null, this.state.description);
tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.TheCard.setTinodeID(card, tinode_sdk__WEBPACK_IMPORTED_MODULE_2__.Tinode.URI_TOPIC_ID_PREFIX + this.props.topic);
this.props.onShareTheCard(card);
}
handleShowAddMembers(e) { handleShowAddMembers(e) {
e.preventDefault(); e.preventDefault();
this.props.onInitFind(); this.props.onInitFind();
@@ -677,7 +682,12 @@ class InfoView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
onClick: this.handleShowQRCode onClick: this.handleShowQRCode
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", { }, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons" className: "material-icons"
}, "qr_code")), "\xA0")), alias ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { }, "qr_code")), "\xA0 \xA0", react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
href: "#",
onClick: this.handleShare
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons"
}, "share")), "\xA0")), alias ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "panel-form-row" className: "panel-form-row"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", { }, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
className: "small" className: "small"
@@ -1376,7 +1386,7 @@ class ShowQRCode extends (react__WEBPACK_IMPORTED_MODULE_1___default().PureCompo
}, react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { }, react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", {
className: "qr-code", className: "qr-code",
ref: this.qrCodeRef ref: this.qrCodeRef
}), react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { }), react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tt", null, this.props.uri)), react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", {
className: "dialog-buttons" className: "dialog-buttons"
}, react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { }, react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", {
className: "outline", className: "outline",
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
+663
View File
@@ -0,0 +1,663 @@
"use strict";
(self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_widgets_call-panel_jsx"],{
/***/ "./src/widgets/call-panel.jsx":
/*!************************************!*\
!*** ./src/widgets/call-panel.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 _letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./letter-tile.jsx */ "./src/widgets/letter-tile.jsx");
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../constants.js */ "./src/constants.js");
/* harmony import */ var _lib_strformat_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../lib/strformat.js */ "./src/lib/strformat.js");
const RING_SOUND = new Audio('audio/call-out.m4a');
RING_SOUND.loop = true;
const CALL_ENDED_SOUND = new Audio('audio/call-end.m4a');
CALL_ENDED_SOUND.loop = true;
const DIALING_SOUND = new Audio('audio/dialing.m4a');
const VIDEO_MUTED_EVENT = 'video:muted';
const VIDEO_UNMUTED_EVENT = 'video:unmuted';
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
already_in_call: {
id: "already_in_call",
defaultMessage: [{
"type": 0,
"value": "You already in an ongoing call!"
}]
}
});
class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
constructor(props) {
super(props);
this.state = {
localStream: undefined,
remoteStream: undefined,
pc: undefined,
dataChannel: undefined,
previousOnInfo: undefined,
waitingForPeer: false,
callInitialSetupComplete: false,
audioOnly: props.callAudioOnly,
videoToggleInProgress: false,
remoteVideoLive: false
};
this.localStreamConstraints = {
audio: true,
video: !props.callAudioOnly
};
this.isOutgoingCall = props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_OUTGOING_INITATED;
this.containerRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
this.localRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
this.remoteRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
this.remoteIceCandidatesCache = [];
this.onInfo = this.onInfo.bind(this);
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.createPeerConnection = this.createPeerConnection.bind(this);
this.canSendOffer = this.canSendOffer.bind(this);
this.drainRemoteIceCandidatesCache = this.drainRemoteIceCandidatesCache.bind(this);
this.handleNegotiationNeededEvent = this.handleNegotiationNeededEvent.bind(this);
this.handleICECandidateEvent = this.handleICECandidateEvent.bind(this);
this.handleNewICECandidateMsg = this.handleNewICECandidateMsg.bind(this);
this.handleICEConnectionStateChangeEvent = this.handleICEConnectionStateChangeEvent.bind(this);
this.handleSignalingStateChangeEvent = this.handleSignalingStateChangeEvent.bind(this);
this.handleICEGatheringStateChangeEvent = this.handleICEGatheringStateChangeEvent.bind(this);
this.handleIceCandidateErrorEvent = this.handleIceCandidateErrorEvent.bind(this);
this.handleTrackEvent = this.handleTrackEvent.bind(this);
this.handleVideoOfferMsg = this.handleVideoOfferMsg.bind(this);
this.handleVideoAnswerMsg = this.handleVideoAnswerMsg.bind(this);
this.handleNewICECandidateMsg = this.handleNewICECandidateMsg.bind(this);
this.reportError = this.reportError.bind(this);
this.handleGetUserMediaError = this.handleGetUserMediaError.bind(this);
this.stopTracks = this.stopTracks.bind(this);
this.disconnectMedia = this.disconnectMedia.bind(this);
this.handleCloseClick = this.handleCloseClick.bind(this);
this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this);
this.handleToggleMicClick = this.handleToggleMicClick.bind(this);
this.handleRemoteHangup = this.handleRemoteHangup.bind(this);
this.handleVideoCallAccepted = this.handleVideoCallAccepted.bind(this);
this.muteVideo = this.muteVideo.bind(this);
this.unmuteVideo = this.unmuteVideo.bind(this);
this.emptyVideoTrack = this.emptyVideoTrack.bind(this);
this.handleDataChannelEvent = this.handleDataChannelEvent.bind(this);
this.handleDataChannelError = this.handleDataChannelError.bind(this);
this.handleDataChannelMessage = this.handleDataChannelMessage.bind(this);
this.handleDataChannelOpen = this.handleDataChannelOpen.bind(this);
this.handleDataChannelClose = this.handleDataChannelClose.bind(this);
this.bounds = {};
this.dragStart = event => {
if (!event.target.classList.contains('draggable')) {
return;
}
if (window.matchMedia('(width<=640px)')) {
return;
}
if (this.props.minimized && this.containerRef) {
event.stopPropagation();
this.containerRef.current.setPointerCapture(event.pointerId);
this.containerRef.current.style.cursor = 'grabbing';
this.bounds.left = this.containerRef.current.offsetLeft;
this.bounds.top = this.containerRef.current.offsetTop;
this.bounds.width = this.containerRef.current.offsetWidth;
this.bounds.height = this.containerRef.current.offsetHeight;
const parent = this.containerRef.current.offsetParent;
this.bounds.maxX = parent.offsetWidth;
this.bounds.maxY = parent.offsetHeight;
}
};
this.drag = event => {
event.stopPropagation();
const el = this.containerRef.current;
if (this.props.minimized && this.containerRef && el.hasPointerCapture(event.pointerId)) {
this.bounds.left = Math.min(Math.max(this.bounds.left + event.movementX, 0), this.bounds.maxX - this.bounds.width);
this.bounds.top = Math.min(Math.max(this.bounds.top + event.movementY, 0), this.bounds.maxY - this.bounds.height);
el.style.left = `${this.bounds.left}px`;
el.style.top = `${this.bounds.top}px`;
}
};
this.dragEnd = event => {
event.stopPropagation();
if (this.props.minimized && this.containerRef) {
this.containerRef.current.releasePointerCapture(event.pointerId);
this.containerRef.current.style.cursor = 'grab';
}
};
}
componentDidMount() {
const topic = this.props.tinode.getTopic(this.props.topic);
this.previousOnInfo = topic.onInfo;
topic.onInfo = this.onInfo;
if ((this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_OUTGOING_INITATED || this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS) && this.localRef.current) {
this.start();
}
if (this.containerRef) {
this.containerRef.current.addEventListener('pointerdown', this.dragStart);
this.containerRef.current.addEventListener('pointermove', this.drag);
this.containerRef.current.addEventListener('pointerup', this.dragEnd);
}
}
componentWillUnmount() {
const topic = this.props.tinode.getTopic(this.props.topic);
topic.onInfo = this.previousOnInfo;
this.stop();
if (this.containerRef) {
this.containerRef.current.removeEventListener('pointerdown', this.dragStart);
this.containerRef.current.removeEventListener('pointermove', this.drag);
this.containerRef.current.removeEventListener('pointerup', this.dragEnd);
}
}
handleVideoCallAccepted(info) {
RING_SOUND.pause();
const pc = this.createPeerConnection(true);
const stream = this.state.localStream;
stream.getTracks().forEach(track => {
pc.addTrack(track, stream);
if (track.kind == 'video' && this.state.audioOnly) {
track.enabled = false;
track.stop();
stream.removeTrack(track);
}
});
}
onInfo(info) {
if (info.what != 'call') {
return;
}
switch (info.event) {
case 'accept':
this.handleVideoCallAccepted(info);
break;
case 'answer':
this.handleVideoAnswerMsg(info);
break;
case 'ice-candidate':
this.handleNewICECandidateMsg(info);
break;
case 'hang-up':
this.handleRemoteHangup(info);
break;
case 'offer':
this.handleVideoOfferMsg(info);
break;
case 'ringing':
RING_SOUND.play().catch(_ => {});
break;
default:
console.warn("Unknown call event", info.event);
break;
}
}
emptyVideoTrack() {
const width = 640;
const height = 480;
const canvas = Object.assign(document.createElement("canvas"), {
width,
height
});
canvas.getContext('2d').fillRect(0, 0, width, height);
const stream = canvas.captureStream(0);
return Object.assign(stream.getVideoTracks()[0], {
enabled: false
});
}
start() {
if (this.state.localStream) {
this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info');
return;
}
if (this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS) {
this.props.onInvite(this.props.topic, this.props.seq, _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS, this.props.callAudioOnly);
return;
}
navigator.mediaDevices.getUserMedia(this.localStreamConstraints).then(stream => {
if (!this.localStreamConstraints.video) {
stream.addTrack(this.emptyVideoTrack());
}
this.setState({
localStream: stream,
waitingForPeer: true
});
this.localRef.current.srcObject = stream;
DIALING_SOUND.play();
this.props.onInvite(this.props.topic, this.props.seq, this.props.callState, this.props.callAudioOnly);
}).catch(this.handleGetUserMediaError);
}
stop() {
CALL_ENDED_SOUND.pause();
CALL_ENDED_SOUND.currentTime = 0;
RING_SOUND.pause();
RING_SOUND.currentTime = 0;
this.stopTracks(this.state.localStream);
this.stopTracks(this.state.remoteStream);
this.disconnectMedia(this.localRef.current);
this.disconnectMedia(this.remoteRef.current);
if (this.state.pc) {
this.state.pc.ontrack = null;
this.state.pc.onremovetrack = null;
this.state.pc.onremovestream = null;
this.state.pc.onicecandidate = null;
this.state.pc.oniceconnectionstatechange = null;
this.state.pc.onsignalingstatechange = null;
this.state.pc.onicegatheringstatechange = null;
this.state.pc.onnegotiationneeded = null;
this.state.pc.onicecandidateerror = null;
this.state.pc.ondatachannel = null;
if (this.state.dataChannel && (this.state.dataChannel.readyState == 'open' || this.state.dataChannel.readyState == 'connecting')) {
this.state.dataChannel.close();
}
this.state.pc.close();
}
this.setState({
pc: null,
waitingForPeer: false
});
}
disconnectMedia(el) {
if (!el) {
return;
}
el.srcObject = null;
el.src = '';
}
stopTracks(stream) {
if (!stream) {
return;
}
let tracks = stream.getTracks();
if (tracks) {
tracks.forEach(track => {
track.stop();
track.enabled = false;
});
}
}
handleDataChannelError(error) {
console.error('data channel error', error);
}
handleDataChannelMessage(event) {
switch (event.data) {
case VIDEO_MUTED_EVENT:
this.setState({
remoteVideoLive: false
}, _ => {
this.remoteRef.current.srcObject = this.state.remoteStream;
});
break;
case VIDEO_UNMUTED_EVENT:
this.setState({
remoteVideoLive: true
}, _ => {
this.remoteRef.current.srcObject = this.state.remoteStream;
});
break;
default:
break;
}
}
handleDataChannelOpen(event) {
if (!this.state.audioOnly) {
event.target.send(VIDEO_UNMUTED_EVENT);
}
}
handleDataChannelClose(event) {
console.log('close data channel:', event);
}
handleDataChannelEvent(event) {
console.log('data channel event:', event);
const channel = event.channel;
channel.onerror = this.handleDataChannelError;
channel.onmessage = this.handleDataChannelMessage;
channel.onopen = this.handleDataChannelOpen;
channel.onclose = this.handleDataChannelClose;
this.setState({
dataChannel: channel
});
}
createPeerConnection(withDataChannel) {
const iceServers = this.props.tinode.getServerParam('iceServers', null);
const pc = iceServers ? new RTCPeerConnection({
iceServers: iceServers
}) : new RTCPeerConnection();
pc.onicecandidate = this.handleICECandidateEvent;
pc.oniceconnectionstatechange = this.handleICEConnectionStateChangeEvent;
pc.onicegatheringstatechange = this.handleICEGatheringStateChangeEvent;
pc.onsignalingstatechange = this.handleSignalingStateChangeEvent;
pc.onnegotiationneeded = this.handleNegotiationNeededEvent;
pc.onicecandidateerror = this.handleIceCandidateErrorEvent;
pc.ontrack = this.handleTrackEvent;
pc.ondatachannel = this.handleDataChannelEvent;
let stateUpdate = {
pc: pc,
waitingForPeer: false
};
if (withDataChannel) {
const channel = pc.createDataChannel("events", {
ordered: true
});
channel.onerror = this.handleDataChannelError;
channel.onmessage = this.handleDataChannelMessage;
channel.onopen = this.handleDataChannelOpen;
channel.onclose = this.handleDataChannelClose;
stateUpdate.dataChannel = channel;
}
this.setState(stateUpdate);
return pc;
}
handleVideoAnswerMsg(info) {
const desc = new RTCSessionDescription(info.payload);
this.state.pc.setRemoteDescription(desc).then(_ => {
this.setState({
callInitialSetupComplete: true
}, _ => this.drainRemoteIceCandidatesCache());
}).catch(err => this.reportError(err));
}
reportError(err) {
this.props.onError(err.message, 'err');
}
canSendOffer() {
return this.isOutgoingCall || this.state.callInitialSetupComplete;
}
handleNegotiationNeededEvent(event) {
const pc = event.target;
if (!this.canSendOffer()) {
return;
}
pc.createOffer().then(offer => {
return pc.setLocalDescription(offer);
}).then(_ => {
this.props.onSendOffer(this.props.topic, this.props.seq, pc.localDescription.toJSON());
}).catch(err => this.reportError(err));
}
handleIceCandidateErrorEvent(event) {
console.warn("ICE candidate error:", event);
}
handleICECandidateEvent(event) {
if (event.candidate) {
this.props.onIceCandidate(this.props.topic, this.props.seq, event.candidate.toJSON());
}
}
handleNewICECandidateMsg(info) {
const candidate = new RTCIceCandidate(info.payload);
if (this.state.callInitialSetupComplete) {
this.state.pc.addIceCandidate(candidate).catch(err => {
if (candidate.candidate) {
this.reportError(err);
}
console.warn("Error adding new ice candidate", candidate, err);
});
} else {
this.remoteIceCandidatesCache.push(candidate);
}
}
drainRemoteIceCandidatesCache() {
this.remoteIceCandidatesCache.forEach(candidate => {
this.state.pc.addIceCandidate(candidate).catch(err => {
if (candidate.candidate) {
this.reportError(err);
}
console.warn("Error adding cached ice candidate", candidate, err);
});
});
this.remoteIceCandidatesCache = [];
}
handleICEConnectionStateChangeEvent(event) {
switch (event.target.iceConnectionState) {
case 'closed':
case 'failed':
this.handleCloseClick();
break;
}
}
handleSignalingStateChangeEvent(event) {
if (event.target.signalingState == 'closed') {
this.handleCloseClick();
}
}
handleICEGatheringStateChangeEvent(event) {}
handleTrackEvent(event) {
this.remoteRef.current.srcObject = event.streams[0];
this.setState({
remoteStream: event.streams[0]
});
}
handleGetUserMediaError(e) {
console.error("Error opening camera and/or microphone", e);
switch (e.name) {
case 'NotFoundError':
this.reportError(e);
break;
case 'SecurityError':
case 'PermissionDeniedError':
break;
default:
this.reportError(e);
break;
}
this.handleCloseClick();
}
handleVideoOfferMsg(info) {
let localStream = null;
const pc = this.state.pc ? this.state.pc : this.createPeerConnection(false);
const desc = new RTCSessionDescription(info.payload);
pc.setRemoteDescription(desc).then(_ => {
return navigator.mediaDevices.getUserMedia(this.localStreamConstraints);
}).then(stream => {
let dummyVideo;
if (!this.localStreamConstraints.video) {
dummyVideo = this.emptyVideoTrack();
stream.addTrack(dummyVideo);
}
localStream = stream;
this.localRef.current.srcObject = stream;
this.setState({
localStream: stream
});
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
if (dummyVideo) {
dummyVideo.enabled = false;
dummyVideo.stop();
stream.removeTrack(dummyVideo);
}
}).then(_ => {
return pc.createAnswer();
}).then(answer => {
return pc.setLocalDescription(answer);
}).then(_ => {
this.props.onSendAnswer(this.props.topic, this.props.seq, pc.localDescription.toJSON());
this.setState({
callInitialSetupComplete: true
}, _ => this.drainRemoteIceCandidatesCache());
}).catch(this.handleGetUserMediaError);
}
handleRemoteHangup() {
if (!this.state.waitingForPeer) {
this.handleCloseClick();
} else {
this.setState({
waitingForPeer: false
});
RING_SOUND.pause();
RING_SOUND.currentTime = 0;
CALL_ENDED_SOUND.loop = true;
CALL_ENDED_SOUND.play().catch(_ => {});
setTimeout(_ => {
this.handleCloseClick();
}, 2000);
}
}
handleCloseClick() {
this.stop();
this.props.onHangup(this.props.topic, this.props.seq);
}
muteVideo() {
if (!this.state.pc || !this.state.dataChannel) {
return;
}
const stream = this.state.localStream;
const t = stream.getVideoTracks()[0];
t.enabled = false;
t.stop();
stream.removeTrack(t);
this.state.dataChannel.send(VIDEO_MUTED_EVENT);
this.setState({
videoToggleInProgress: false
});
}
unmuteVideo() {
if (!this.state.pc || !this.state.dataChannel) {
return;
}
navigator.mediaDevices.getUserMedia({
video: true
}).then(stream => {
this.localRef.current.srcObject = null;
const sender = this.state.pc.getSenders().find(s => s.track.kind == 'video');
const track = stream.getVideoTracks()[0];
stream.removeTrack(track);
this.state.localStream.addTrack(track);
return sender.replaceTrack(track);
}).then(_ => {
this.localRef.current.srcObject = this.state.localStream;
this.state.dataChannel.send(VIDEO_UNMUTED_EVENT);
}).catch(err => this.handleGetUserMediaError(err)).finally(_ => {
this.setState({
videoToggleInProgress: false
});
});
}
handleToggleCameraClick() {
if (this.state.videoToggleInProgress) {
return;
}
const tracks = this.state.localStream.getVideoTracks();
this.setState({
videoToggleInProgress: true
}, _ => {
if (tracks && tracks.length > 0 && tracks[0].enabled && tracks[0].readyState == 'live') {
this.muteVideo();
} else {
this.unmuteVideo();
}
this.setState({
audioOnly: !this.state.audioOnly
});
});
}
handleToggleMicClick() {
const stream = this.state.localStream;
const t = stream.getAudioTracks()[0];
t.enabled = !t.enabled;
this.forceUpdate();
}
render() {
const audioTracks = this.state.localStream && this.state.localStream.getAudioTracks();
const videoTracks = !this.state.audioOnly && this.state.localStream && this.state.localStream.getVideoTracks();
const disabled = !this.state.pc || !this.state.dataChannel || !(audioTracks && audioTracks[0]);
const audioIcon = audioTracks && audioTracks[0] && audioTracks[0].enabled ? 'mic' : 'mic_off';
const videoIcon = videoTracks && videoTracks[0] && videoTracks[0].enabled && videoTracks[0].readyState == 'live' ? 'videocam' : 'videocam_off';
const peerTitle = (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_5__.truncateString)(this.props.title, _config_js__WEBPACK_IMPORTED_MODULE_3__.MAX_PEER_TITLE_LENGTH);
const pulseAnimation = this.state.waitingForPeer ? ' pulse' : '';
let remoteActive = false;
if (this.remoteRef.current && this.remoteRef.current.srcObject && this.state.remoteVideoLive) {
const rstream = this.remoteRef.current.srcObject;
if (rstream.getVideoTracks().length > 0) {
const t = rstream.getVideoTracks()[0];
remoteActive = t.enabled && t.readyState == 'live';
}
}
const minimizedClass = this.props.minimized ? 'minimized' : null;
const fullScreen = this.props.minimized ? 'fullscreen' : 'fullscreen_exit';
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "video-container",
className: minimizedClass,
ref: this.containerRef
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "video-container-panel"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "call-party self",
disabled: this.state.audioOnly || this.props.minimized
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("video", {
ref: this.localRef,
autoPlay: true,
muted: true,
playsInline: true
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "caller-name inactive"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "calls_you_label",
defaultMessage: [{
"type": 0,
"value": "You"
}]
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "call-party peer",
disabled: !remoteActive
}, remoteActive ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("video", {
ref: this.remoteRef,
autoPlay: true,
playsInline: true
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "caller-name inactive"
}, peerTitle)) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("audio", {
ref: this.remoteRef,
autoPlay: true
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: `caller-card${pulseAnimation} draggable`
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
authorizeURL: this.props.tinode.authorizeURL,
avatar: this.props.avatar,
topic: this.props.topic,
title: this.props.title
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "caller-name"
}, peerTitle))))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
className: "full-screen",
onClick: this.props.onToggleMinimize
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons"
}, fullScreen)), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "controls",
className: minimizedClass
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
className: "danger",
onClick: this.handleCloseClick
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons"
}, "call_end")), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
className: "secondary",
onClick: this.handleToggleCameraClick,
disabled: disabled
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons"
}, videoIcon)), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
className: "secondary",
onClick: this.handleToggleMicClick,
disabled: disabled
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons"
}, audioIcon))));
}
}
;
/* harmony default export */ __webpack_exports__["default"] = ((0,react_intl__WEBPACK_IMPORTED_MODULE_1__.injectIntl)(CallPanel));
/***/ })
}]);
//# sourceMappingURL=src_widgets_call-panel_jsx.dev.js.map
File diff suppressed because one or more lines are too long
+110
View File
@@ -0,0 +1,110 @@
"use strict";
(self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_widgets_doc-preview_jsx"],{
/***/ "./src/widgets/doc-preview.jsx":
/*!*************************************!*\
!*** ./src/widgets/doc-preview.jsx ***!
\*************************************/
/***/ (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 */ DocPreview; }
/* harmony export */ });
/* 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 _widgets_send_message_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../widgets/send-message.jsx */ "./src/widgets/send-message.jsx");
/* harmony import */ var _lib_strformat_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../lib/strformat.js */ "./src/lib/strformat.js");
function iconFromMime(mime) {
const mimeToIcon = {
default: 'insert_drive_file',
image: 'image',
text: 'description',
video: 'theaters'
};
return mimeToIcon[mime] || mimeToIcon[(mime || '').split('/')[0]] || mimeToIcon['default'];
}
class DocPreview extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
constructor(props) {
super(props);
this.handleSendDoc = this.handleSendDoc.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(e) {
e.preventDefault();
if (e.key === 'Escape') {
this.props.onClose();
}
}
handleSendDoc(caption) {
this.props.onClose();
this.props.onSendMessage(this.props.content.file);
}
render() {
if (!this.props.content) {
return null;
}
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "preview-caption-panel"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, this.props.content.name), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
href: "#",
onClick: e => {
e.preventDefault();
this.props.onClose();
}
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons gray"
}, "close"))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview-container"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "doc-card"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons gray"
}, iconFromMime(this.props.content.type)), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_file_name",
defaultMessage: [{
"type": 0,
"value": "File name:"
}]
})), " ", (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_3__.shortenFileName)(this.props.content.name, 24) || '-'), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_content_type",
defaultMessage: [{
"type": 0,
"value": "Content type:"
}]
})), " ", this.props.content.type || 'application/octet-stream'), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_size",
defaultMessage: [{
"type": 0,
"value": "Size:"
}]
})), " ", (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_3__.bytesToHumanSize)(this.props.content.size)))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_send_message_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
noInput: true,
tinode: this.props.tinode,
reply: this.props.reply,
onCancelReply: this.props.onCancelReply,
onSendMessage: this.handleSendDoc,
onError: this.props.onError
}));
}
}
;
/***/ })
}]);
//# sourceMappingURL=src_widgets_doc-preview_jsx.dev.js.map
File diff suppressed because one or more lines are too long
+157
View File
@@ -0,0 +1,157 @@
"use strict";
(self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_widgets_image-preview_jsx"],{
/***/ "./src/widgets/image-preview.jsx":
/*!***************************************!*\
!*** ./src/widgets/image-preview.jsx ***!
\***************************************/
/***/ (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 */ ImagePreview; }
/* harmony export */ });
/* 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 _send_message_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./send-message.jsx */ "./src/widgets/send-message.jsx");
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../config.js */ "./src/config.js");
/* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js");
/* harmony import */ var _lib_strformat_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../lib/strformat.js */ "./src/lib/strformat.js");
/* harmony import */ var _lib_utils_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../lib/utils.js */ "./src/lib/utils.js");
class ImagePreview extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
constructor(props) {
super(props);
this.state = {
width: 0,
height: 0
};
this.handleSendImage = this.handleSendImage.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(e) {
if (this.props.onSendMessage) {
return;
}
e.preventDefault();
if (e.key === 'Escape') {
this.props.onClose();
}
}
assignWidth(node) {
if (node && !this.state.width) {
const bounds = node.getBoundingClientRect();
this.setState({
width: bounds.width | 0,
height: bounds.height | 0
});
}
}
handleSendImage(caption) {
this.props.onClose();
this.props.onSendMessage(caption, this.props.content.blob);
}
render() {
if (!this.props.content) {
return null;
}
const dim = (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__.fitImageSize)(this.props.content.width, this.props.content.height, this.state.width, this.state.height, false);
const size = dim ? {
width: dim.dstWidth + 'px',
height: dim.dstHeight + 'px'
} : this.props.content.width > this.props.content.height ? {
width: '100%'
} : {
height: '100%'
};
size.maxWidth = '100%';
size.maxHeight = '100%';
const maxlength = Math.max((this.state.width / _config_js__WEBPACK_IMPORTED_MODULE_3__.REM_SIZE / 1.5 | 0) - 2, 12);
const fname = (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_5__.shortenFileName)(this.props.content.filename, maxlength) || '-';
const width = this.props.content.width || '-';
const height = this.props.content.height || '-';
const download_url = (0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_6__.urlAsAttachment)(this.props.content.url);
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "preview-caption-panel"
}, this.props.onSendMessage ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, fname) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
href: download_url,
download: this.props.content.filename
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons"
}, "file_download"), " ", react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "download_action",
defaultMessage: [{
"type": 0,
"value": "download"
}]
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
href: "#",
onClick: e => {
e.preventDefault();
this.props.onClose();
}
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons gray"
}, "close"))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview-container",
ref: node => this.assignWidth(node)
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("img", {
src: this.props.content.url,
style: size,
className: "image-preview",
alt: this.props.content.filename
})), this.props.onSendMessage ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_send_message_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
messagePrompt: "add_image_caption",
acceptBlank: true,
tinode: this.props.tinode,
reply: this.props.reply,
onCancelReply: this.props.onCancelReply,
onSendMessage: this.handleSendImage,
onError: this.props.onError
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview-footer"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_file_name",
defaultMessage: [{
"type": 0,
"value": "File name:"
}]
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
title: this.props.content.filename
}, fname))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_content_type",
defaultMessage: [{
"type": 0,
"value": "Content type:"
}]
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, this.props.content.type)), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_size",
defaultMessage: [{
"type": 0,
"value": "Size:"
}]
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, width, " \xD7 ", height, " px; ", (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_5__.bytesToHumanSize)(this.props.content.size)))));
}
}
;
/***/ })
}]);
//# sourceMappingURL=src_widgets_image-preview_jsx.dev.js.map
File diff suppressed because one or more lines are too long
+197
View File
@@ -0,0 +1,197 @@
"use strict";
(self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_widgets_the-card-preview_jsx"],{
/***/ "./src/widgets/the-card-preview.jsx":
/*!******************************************!*\
!*** ./src/widgets/the-card-preview.jsx ***!
\******************************************/
/***/ (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 */ TheCardPreview; }
/* harmony export */ });
/* 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 libphonenumber_js_mobile__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! libphonenumber-js/mobile */ "./node_modules/libphonenumber-js/mobile/exports/parsePhoneNumberWithError.js");
/* harmony import */ var _send_message_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./send-message.jsx */ "./src/widgets/send-message.jsx");
/* harmony import */ var _letter_tile_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./letter-tile.jsx */ "./src/widgets/letter-tile.jsx");
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! tinode-sdk */ "tinode-sdk");
/* harmony import */ var tinode_sdk__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(tinode_sdk__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js");
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
unknown_name: {
id: "unknown_name",
defaultMessage: [{
"type": 0,
"value": "Unknown"
}]
}
});
class TheCardPreview extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
constructor(props) {
super(props);
this.state = {
content: props.content.object
};
if (!props.content.object) {
(0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_6__.importVCard)(props.content.file).then(card => {
this.setState({
content: card
});
});
}
this.handleSendCard = this.handleSendCard.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(e) {
e.preventDefault();
if (e.key === 'Escape') {
this.props.onClose();
}
}
handleSendCard() {
this.props.onClose();
this.props.onSendMessage(this.props.content);
}
render() {
const card = this.state.content;
const fullName = tinode_sdk__WEBPACK_IMPORTED_MODULE_5__.TheCard.getFn(card);
const photoUrl = tinode_sdk__WEBPACK_IMPORTED_MODULE_5__.TheCard.getPhotoUrl(card) || null;
const note = card?.note;
const org = tinode_sdk__WEBPACK_IMPORTED_MODULE_5__.TheCard.getOrg(card);
const emails = tinode_sdk__WEBPACK_IMPORTED_MODULE_5__.TheCard.getComm(card, 'email') || [];
const phones = tinode_sdk__WEBPACK_IMPORTED_MODULE_5__.TheCard.getComm(card, 'tel') || [];
const tinodeIds = tinode_sdk__WEBPACK_IMPORTED_MODULE_5__.TheCard.getComm(card, 'tinode') || [];
const urls = tinode_sdk__WEBPACK_IMPORTED_MODULE_5__.TheCard.getComm(card, 'http') || [];
const contacts = [];
emails.forEach((email, idx) => {
const types = Array.isArray(email.des) ? email.des.join(', ') : '';
contacts.push(react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "group quoted",
key: `email-${idx}`
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tt", null, email.value), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
className: "small gray"
}, " ", types ? `(${types})` : '')));
});
phones.forEach((phone, idx) => {
let val = phone.value;
try {
const number = (0,libphonenumber_js_mobile__WEBPACK_IMPORTED_MODULE_2__.parsePhoneNumberWithError)(phone.value);
val = number ? number.formatInternational() : phone.value;
} catch (err) {}
const types = Array.isArray(phone.des) ? phone.des.join(', ') : '';
contacts.push(react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "group quoted",
key: `tel-${idx}`
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tt", null, val), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
className: "small gray"
}, " ", types ? `(${types})` : '')));
});
tinodeIds.forEach((tid, idx) => {
const types = Array.isArray(tid.des) ? tid.des.join(', ') : '';
contacts.push(react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "group quoted",
key: `tinode-${idx}`
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tt", null, tid.value), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
className: "small gray"
}, " ", types ? `(${types})` : '')));
});
urls.forEach((url, idx) => {
const types = Array.isArray(url.des) ? url.des.join(', ') : '';
contacts.push(react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "group quoted",
key: `url-${idx}`
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tt", null, url.value), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
className: "small gray"
}, " ", types ? `(${types})` : '')));
});
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "preview-caption-panel"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_contact_card",
defaultMessage: [{
"type": 0,
"value": "Contact Card"
}]
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
href: "#",
onClick: e => {
e.preventDefault();
this.props.onClose();
}
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons gray"
}, "close"))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview-container"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "doc-card"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "panel-form-column"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("center", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "avatar-box"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_4__["default"], {
authorizeURL: this.props.tinode.authorizeURL,
avatar: photoUrl || true,
topic: "usrXXX123",
title: fullName
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "group"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "large ellipsized"
}, fullName || 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: "group"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, org)) : null, note ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "group"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
className: "small"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_description",
defaultMessage: [{
"type": 0,
"value": "Description"
}]
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "quoted"
}, note)) : null, contacts.length > 0 ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "group"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
className: "small"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_contacts",
defaultMessage: [{
"type": 0,
"value": "Contacts"
}]
})), contacts) : null))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_send_message_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], {
noInput: true,
tinode: this.props.tinode,
reply: this.props.reply,
onCancelReply: this.props.onCancelReply,
onSendMessage: this.handleSendCard,
onError: this.props.onError
}));
}
}
/***/ })
}]);
//# sourceMappingURL=src_widgets_the-card-preview_jsx.dev.js.map
File diff suppressed because one or more lines are too long
+143
View File
@@ -0,0 +1,143 @@
"use strict";
(self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_widgets_video-preview_jsx"],{
/***/ "./src/widgets/video-preview.jsx":
/*!***************************************!*\
!*** ./src/widgets/video-preview.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 _send_message_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./send-message.jsx */ "./src/widgets/send-message.jsx");
/* harmony import */ var _lib_strformat_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../lib/strformat.js */ "./src/lib/strformat.js");
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
unrecognized_video_format: {
id: "unrecognized_video_format",
defaultMessage: [{
"type": 0,
"value": "Format of this video is not recognized"
}]
}
});
class VideoPreview extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) {
constructor(props) {
super(props);
this.videoRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
this.handleSendVideo = this.handleSendVideo.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown);
}
handleKeyDown(e) {
if (this.props.onSendMessage) {
return;
}
e.preventDefault();
if (e.key === 'Escape') {
this.props.onClose();
}
}
handleSendVideo(caption) {
this.props.onClose();
const params = {
width: this.videoRef.current.videoWidth,
height: this.videoRef.current.videoHeight,
duration: this.videoRef.current.duration * 1000 | 0,
mime: this.props.content.mime,
name: this.props.content.filename
};
if (params.width == 0 || params.height == 0) {
this.props.onError(this.props.intl.formatMessage(messages.unrecognized_video_format), 'err');
return;
}
const canvas = document.createElement('canvas');
canvas.width = params.width;
canvas.height = params.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(this.videoRef.current, 0, 0, canvas.width, canvas.height);
ctx.canvas.toBlob(preview => this.props.onSendMessage(caption, this.props.content.blob, preview, params), 'image/jpeg', 0.75);
}
render() {
if (!this.props.content) {
return null;
}
const width = this.props.content.width || '-';
const height = this.props.content.height || '-';
const controlist = this.props.onSendMessage ? 'nodownload' : '';
const autoPlay = !this.props.onSendMessage;
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "preview-caption-panel"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null, this.props.content.filename), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
href: "#",
onClick: e => {
e.preventDefault();
this.props.onClose();
}
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
className: "material-icons gray"
}, "close"))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview-container"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("video", {
className: "image-preview",
controls: true,
controlsList: controlist,
disablePictureInPicture: true,
ref: this.videoRef,
autoPlay: autoPlay,
src: this.props.tinode.authorizeURL(this.props.content.url),
poster: this.props.content.preview,
alt: this.props.content.filename
})), this.props.onSendMessage ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_send_message_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
messagePrompt: "add_image_caption",
acceptBlank: true,
tinode: this.props.tinode,
reply: this.props.reply,
onCancelReply: this.props.onCancelReply,
onSendMessage: this.handleSendVideo,
onError: this.props.onError
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
id: "image-preview-footer"
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_file_name",
defaultMessage: [{
"type": 0,
"value": "File name:"
}]
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
title: this.props.content.filename
}, this.props.content.filename))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_content_type",
defaultMessage: [{
"type": 0,
"value": "Content type:"
}]
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, this.props.content.type)), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("b", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
id: "label_size",
defaultMessage: [{
"type": 0,
"value": "Size:"
}]
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, width, " \xD7 ", height, " px; ", (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_3__.bytesToHumanSize)(this.props.content.size)))));
}
}
;
/* harmony default export */ __webpack_exports__["default"] = ((0,react_intl__WEBPACK_IMPORTED_MODULE_1__.injectIntl)(VideoPreview));
/***/ })
}]);
//# sourceMappingURL=src_widgets_video-preview_jsx.dev.js.map
File diff suppressed because one or more lines are too long
+134 -17
View File
@@ -2607,6 +2607,7 @@ Drafty.attrValue = function (style, data) {
Drafty.getContentType = function () { Drafty.getContentType = function () {
return DRAFTY_MIME_TYPE; return DRAFTY_MIME_TYPE;
}; };
Drafty.contentType = DRAFTY_MIME_TYPE;
Drafty.isFormResponseType = function (mimeType) { Drafty.isFormResponseType = function (mimeType) {
return mimeType === DRAFTY_FR_MIME_TYPE || mimeType === DRAFTY_FR_MIME_TYPE_LEGACY; return mimeType === DRAFTY_FR_MIME_TYPE || mimeType === DRAFTY_FR_MIME_TYPE_LEGACY;
}; };
@@ -3978,16 +3979,19 @@ __webpack_require__.r(__webpack_exports__);
const THE_CARD_MIME_TYPE = 'text/x-the-card';
class TheCard { class TheCard {
constructor(fn, imageUrl, imageMimeType, note) { constructor(fn, imageUrl, imageMimeType, note) {
this.card = theCard(fn, imageUrl, imageMimeType, note); Object.assign(this, theCard(fn, imageUrl, imageMimeType, note) || {});
} }
getCard() { merge(that) {
return this.card; Object.assign(this, that);
}
get contentType() {
return THE_CARD_MIME_TYPE;
} }
} }
TheCard.init = theCard; TheCard.contentType = THE_CARD_MIME_TYPE;
TheCard.contentType = 'text/x-card';
TheCard.setFn = function (card, fn) { TheCard.setFn = function (card, fn) {
fn = fn && fn.trim(); fn = fn && fn.trim();
card = card || {}; card = card || {};
@@ -4130,6 +4134,12 @@ TheCard.exportVCard = function (card) {
if (card.note && card.note != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) { if (card.note && card.note != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
vcard += `NOTE:${card.note}\r\n`; vcard += `NOTE:${card.note}\r\n`;
} }
if (card.bday && card.bday.m && card.bday.d) {
const year = card.bday.y ? String(card.bday.y).padStart(4, '0') : '--';
const month = String(card.bday.m).padStart(2, '0');
const day = String(card.bday.d).padStart(2, '0');
vcard += `BDAY:${year}-${month}-${day}\r\n`;
}
if (card.photo) { if (card.photo) {
if (card.photo.ref && card.photo.ref != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) { if (card.photo.ref && card.photo.ref != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
vcard += `PHOTO;VALUE=URI:${card.photo.ref}\r\n`; vcard += `PHOTO;VALUE=URI:${card.photo.ref}\r\n`;
@@ -4165,6 +4175,8 @@ TheCard.importVCard = function (vcardStr) {
const line = rawLines[i]; const line = rawLines[i];
if (line.startsWith(' ') || line.startsWith('\t')) { if (line.startsWith(' ') || line.startsWith('\t')) {
currentLine += line.substring(1); currentLine += line.substring(1);
} else if (currentLine.endsWith('=')) {
currentLine = currentLine.substring(0, currentLine.length - 1) + line;
} else { } else {
if (currentLine) { if (currentLine) {
lines.push(currentLine); lines.push(currentLine);
@@ -4177,6 +4189,39 @@ TheCard.importVCard = function (vcardStr) {
} }
let card = {}; let card = {};
const commMap = new Map(); const commMap = new Map();
const unescapeValue = val => {
return val.replace(/\\([,;\\n])/g, (match, char) => {
if (char === 'n') return '\n';
return char;
});
};
const decodeQuotedPrintable = val => {
const bytes = [];
let i = 0;
while (i < val.length) {
if (val[i] === '=' && i + 2 < val.length) {
const hex = val.substring(i + 1, i + 3);
if (/^[0-9A-F]{2}$/i.test(hex)) {
bytes.push(parseInt(hex, 16));
i += 3;
} else {
bytes.push(val.charCodeAt(i));
i++;
}
} else {
bytes.push(val.charCodeAt(i));
i++;
}
}
try {
const uint8Array = new Uint8Array(bytes);
return new TextDecoder('utf-8').decode(uint8Array);
} catch (e) {
return val.replace(/=([0-9A-F]{2})/gi, (match, hex) => {
return String.fromCharCode(parseInt(hex, 16));
});
}
};
lines.forEach(line => { lines.forEach(line => {
const [keyPart, ...valueParts] = line.split(':'); const [keyPart, ...valueParts] = line.split(':');
if (!keyPart || valueParts.length === 0) { if (!keyPart || valueParts.length === 0) {
@@ -4185,32 +4230,104 @@ TheCard.importVCard = function (vcardStr) {
const value = valueParts.join(':'); const value = valueParts.join(':');
const keyParams = keyPart.split(';'); const keyParams = keyPart.split(';');
const key = keyParams[0].trim().toUpperCase(); const key = keyParams[0].trim().toUpperCase();
const isQuotedPrintable = keyParams.some(param => param.trim().toUpperCase() === 'QUOTED-PRINTABLE' || param.trim().toUpperCase() === 'ENCODING=QUOTED-PRINTABLE');
if (key === 'FN') { if (key === 'FN') {
card.fn = value; let processedValue = value;
if (isQuotedPrintable) {
processedValue = decodeQuotedPrintable(processedValue);
}
card.fn = unescapeValue(processedValue);
} else if (key === 'N') { } else if (key === 'N') {
const parts = value.split(';'); let processedValue = value;
if (isQuotedPrintable) {
processedValue = decodeQuotedPrintable(processedValue);
}
const parts = processedValue.split(';');
card.n = { card.n = {
surname: parts[0] || undefined, surname: parts[0] ? unescapeValue(parts[0]) : undefined,
given: parts[1] || undefined, given: parts[1] ? unescapeValue(parts[1]) : undefined,
additional: parts[2] || undefined, additional: parts[2] ? unescapeValue(parts[2]) : undefined,
prefix: parts[3] || undefined, prefix: parts[3] ? unescapeValue(parts[3]) : undefined,
suffix: parts[4] || undefined suffix: parts[4] ? unescapeValue(parts[4]) : undefined
}; };
Object.keys(card.n).forEach(key => card.n[key] === undefined && delete card.n[key]); Object.keys(card.n).forEach(key => card.n[key] === undefined && delete card.n[key]);
if (Object.keys(card.n).length === 0) { if (Object.keys(card.n).length === 0) {
delete card.n; delete card.n;
} }
} else if (key === 'ORG') { } else if (key === 'ORG') {
const parts = value.split(';'); let processedValue = value;
if (isQuotedPrintable) {
processedValue = decodeQuotedPrintable(processedValue);
}
const parts = processedValue.split(';');
card.org = card.org || {}; card.org = card.org || {};
card.org.fn = parts[0] || undefined; card.org.fn = parts[0] ? unescapeValue(parts[0]) : undefined;
if (!card.org.fn) delete card.org.fn; if (!card.org.fn) delete card.org.fn;
} else if (key === 'TITLE') { } else if (key === 'TITLE') {
let processedValue = value;
if (isQuotedPrintable) {
processedValue = decodeQuotedPrintable(processedValue);
}
card.org = card.org || {}; card.org = card.org || {};
card.org.title = value || undefined; card.org.title = processedValue ? unescapeValue(processedValue) : undefined;
if (!card.org.title) delete card.org.title; if (!card.org.title) delete card.org.title;
} else if (key === 'NOTE') { } else if (key === 'NOTE') {
card.note = value; let processedValue = value;
if (isQuotedPrintable) {
processedValue = decodeQuotedPrintable(processedValue);
}
card.note = unescapeValue(processedValue);
} else if (key === 'BDAY') {
let dateStr = value.split(/[T ]/)[0].trim();
let year = null;
let month = null;
let day = null;
const noHyphens = dateStr.replace(/-/g, '');
if (noHyphens.length === 6 && /^\d{6}$/.test(noHyphens)) {
const yy = parseInt(noHyphens.substring(0, 2), 10);
month = parseInt(noHyphens.substring(2, 4), 10);
day = parseInt(noHyphens.substring(4, 6), 10);
year = yy >= 35 ? 1900 + yy : 2000 + yy;
} else if (dateStr.startsWith('--') || dateStr.startsWith('----')) {
const cleaned = dateStr.replace(/^-+/, '');
if (cleaned.length === 4 && /^\d{4}$/.test(cleaned)) {
month = parseInt(cleaned.substring(0, 2), 10);
day = parseInt(cleaned.substring(2, 4), 10);
} else if (cleaned.includes('-')) {
const parts = cleaned.split('-');
if (parts.length >= 2) {
month = parseInt(parts[0], 10);
day = parseInt(parts[1], 10);
}
}
} else if (noHyphens.length === 8 && /^\d{8}$/.test(noHyphens)) {
year = parseInt(noHyphens.substring(0, 4), 10);
month = parseInt(noHyphens.substring(4, 6), 10);
day = parseInt(noHyphens.substring(6, 8), 10);
} else if (dateStr.includes('-')) {
const parts = dateStr.split('-');
if (parts[0] && parts[0] !== '' && !/^-+$/.test(parts[0])) {
year = parseInt(parts[0], 10);
}
if (parts.length >= 2) {
month = parseInt(parts[1], 10);
}
if (parts.length >= 3) {
day = parseInt(parts[2], 10);
}
}
const isValidMonth = month && month >= 1 && month <= 12;
const isValidDay = day && day >= 1 && day <= 31;
const isValidYear = !year || year >= 1800 && year <= 2200;
if (isValidMonth && isValidDay && isValidYear) {
card.bday = {
m: month,
d: day
};
if (year) {
card.bday.y = year;
}
}
} else if (key === 'PHOTO') { } else if (key === 'PHOTO') {
const params = keyPart.split(';').slice(1); const params = keyPart.split(';').slice(1);
let type = 'jpeg'; let type = 'jpeg';
@@ -4313,7 +4430,7 @@ TheCard.importVCard = function (vcardStr) {
return card; return card;
}; };
TheCard.isFileSupported = function (type, name) { TheCard.isFileSupported = function (type, name) {
return type == 'text/vcard' || (name || '').endsWith('.vcf') || (name || '').endsWith('.vcr') || (name || '').endsWith('.vcard'); return type == 'text/vcard' || (name || '').endsWith('.vcf') || (name || '').endsWith('.vcard');
}; };
function theCard(fn, imageUrl, imageMimeType, note) { function theCard(fn, imageUrl, imageMimeType, note) {
let card = null; let card = null;
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
File diff suppressed because one or more lines are too long