split out a few modules, handle esc key
This commit is contained in:
+9
-3
@@ -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 {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
|||||||
<a href="#" onClick={this.handleShowQRCode}>
|
<a href="#" onClick={this.handleShowQRCode}>
|
||||||
<i className="material-icons">qr_code</i>
|
<i className="material-icons">qr_code</i>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="#" onClick={this.handleShare}>
|
||||||
|
<i className="material-icons">share</i>
|
||||||
|
</a>
|
||||||
</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
@@ -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
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
:
|
:
|
||||||
|
|||||||
@@ -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">{
|
||||||
|
|||||||
@@ -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]" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[371],{6371:function(e,t,n){n.r(t);var i=n(1594),o=n.n(i),s=n(8181),r=n(1725),a=n(8589);const l=(0,s.defineMessages)({unrecognized_video_format:{id:"unrecognized_video_format",defaultMessage:[{type:0,value:"Format of this video is not recognized"}]}});class d extends o().PureComponent{constructor(e){super(e),this.videoRef=o().createRef(),this.handleSendVideo=this.handleSendVideo.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){this.props.onSendMessage||(e.preventDefault(),"Escape"===e.key&&this.props.onClose())}handleSendVideo(e){this.props.onClose();const t={width:this.videoRef.current.videoWidth,height:this.videoRef.current.videoHeight,duration:1e3*this.videoRef.current.duration|0,mime:this.props.content.mime,name:this.props.content.filename};if(0==t.width||0==t.height)return void this.props.onError(this.props.intl.formatMessage(l.unrecognized_video_format),"err");const n=document.createElement("canvas");n.width=t.width,n.height=t.height;const i=n.getContext("2d");i.drawImage(this.videoRef.current,0,0,n.width,n.height),i.canvas.toBlob(n=>this.props.onSendMessage(e,this.props.content.blob,n,t),"image/jpeg",.75)}render(){if(!this.props.content)return null;const e=this.props.content.width||"-",t=this.props.content.height||"-",n=this.props.onSendMessage?"nodownload":"",i=!this.props.onSendMessage;return o().createElement("div",{id:"image-preview"},o().createElement("div",{id:"preview-caption-panel"},o().createElement("span",null,this.props.content.filename),o().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},o().createElement("i",{className:"material-icons gray"},"close"))),o().createElement("div",{id:"image-preview-container"},o().createElement("video",{className:"image-preview",controls:!0,controlsList:n,disablePictureInPicture:!0,ref:this.videoRef,autoPlay:i,src:this.props.tinode.authorizeURL(this.props.content.url),poster:this.props.content.preview,alt:this.props.content.filename})),this.props.onSendMessage?o().createElement(r.A,{messagePrompt:"add_image_caption",acceptBlank:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendVideo,onError:this.props.onError}):o().createElement("div",{id:"image-preview-footer"},o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))),o().createElement("div",null,o().createElement("span",{title:this.props.content.filename},this.props.content.filename))),o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))),o().createElement("div",null,this.props.content.type)),o().createElement("div",null,o().createElement("div",null,o().createElement("b",null,o().createElement(s.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))),o().createElement("div",null,e," × ",t," px; ",(0,a.lX)(this.props.content.size)))))}}t.default=(0,s.injectIntl)(d)}}]);
|
||||||
|
//# sourceMappingURL=371.prod.js.map
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[472],{4472:function(e,t,n){n.r(t),n.d(t,{default:function(){return r}});var a=n(1594),l=n.n(a),s=n(8181),o=n(1725),i=n(8589);class r extends l().PureComponent{constructor(e){super(e),this.handleSendDoc=this.handleSendDoc.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){e.preventDefault(),"Escape"===e.key&&this.props.onClose()}handleSendDoc(e){this.props.onClose(),this.props.onSendMessage(this.props.content.file)}render(){return this.props.content?l().createElement("div",{id:"image-preview"},l().createElement("div",{id:"preview-caption-panel"},l().createElement("span",null,this.props.content.name),l().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},l().createElement("i",{className:"material-icons gray"},"close"))),l().createElement("div",{id:"image-preview-container"},l().createElement("div",{className:"doc-card"},l().createElement("i",{className:"material-icons gray"},function(e){const t={default:"insert_drive_file",image:"image",text:"description",video:"theaters"};return t[e]||t[(e||"").split("/")[0]]||t.default}(this.props.content.type)),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))," ",(0,i.Tu)(this.props.content.name,24)||"-"),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))," ",this.props.content.type||"application/octet-stream"),l().createElement("div",null,l().createElement("b",null,l().createElement(s.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))," ",(0,i.lX)(this.props.content.size)))),l().createElement(o.A,{noInput:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendDoc,onError:this.props.onError})):null}}}}]);
|
||||||
|
//# sourceMappingURL=472.prod.js.map
|
||||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[889],{8889:function(e,t,n){n.r(t),n.d(t,{default:function(){return d}});var s=n(1594),i=n.n(s),a=n(8181),l=n(1725),o=n(9027),r=n(6110),p=n(8589),h=n(3540);class d extends i().PureComponent{constructor(e){super(e),this.state={width:0,height:0},this.handleSendImage=this.handleSendImage.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this)}componentDidMount(){document.addEventListener("keydown",this.handleKeyDown)}componentWillUnmount(){document.removeEventListener("keydown",this.handleKeyDown)}handleKeyDown(e){this.props.onSendMessage||(e.preventDefault(),"Escape"===e.key&&this.props.onClose())}assignWidth(e){if(e&&!this.state.width){const t=e.getBoundingClientRect();this.setState({width:0|t.width,height:0|t.height})}}handleSendImage(e){this.props.onClose(),this.props.onSendMessage(e,this.props.content.blob)}render(){if(!this.props.content)return null;const e=(0,r.ae)(this.props.content.width,this.props.content.height,this.state.width,this.state.height,!1),t=e?{width:e.dstWidth+"px",height:e.dstHeight+"px"}:this.props.content.width>this.props.content.height?{width:"100%"}:{height:"100%"};t.maxWidth="100%",t.maxHeight="100%";const n=Math.max((this.state.width/o.JS/1.5|0)-2,12),s=(0,p.Tu)(this.props.content.filename,n)||"-",d=this.props.content.width||"-",c=this.props.content.height||"-",m=(0,h.FH)(this.props.content.url);return i().createElement("div",{id:"image-preview"},i().createElement("div",{id:"preview-caption-panel"},this.props.onSendMessage?i().createElement("span",null,s):i().createElement("a",{href:m,download:this.props.content.filename},i().createElement("i",{className:"material-icons"},"file_download")," ",i().createElement(a.FormattedMessage,{id:"download_action",defaultMessage:[{type:0,value:"download"}]})),i().createElement("a",{href:"#",onClick:e=>{e.preventDefault(),this.props.onClose()}},i().createElement("i",{className:"material-icons gray"},"close"))),i().createElement("div",{id:"image-preview-container",ref:e=>this.assignWidth(e)},i().createElement("img",{src:this.props.content.url,style:t,className:"image-preview",alt:this.props.content.filename})),this.props.onSendMessage?i().createElement(l.A,{messagePrompt:"add_image_caption",acceptBlank:!0,tinode:this.props.tinode,reply:this.props.reply,onCancelReply:this.props.onCancelReply,onSendMessage:this.handleSendImage,onError:this.props.onError}):i().createElement("div",{id:"image-preview-footer"},i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_file_name",defaultMessage:[{type:0,value:"File name:"}]}))),i().createElement("div",null,i().createElement("span",{title:this.props.content.filename},s))),i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_content_type",defaultMessage:[{type:0,value:"Content type:"}]}))),i().createElement("div",null,this.props.content.type)),i().createElement("div",null,i().createElement("div",null,i().createElement("b",null,i().createElement(a.FormattedMessage,{id:"label_size",defaultMessage:[{type:0,value:"Size:"}]}))),i().createElement("div",null,d," × ",c," px; ",(0,p.lX)(this.props.content.size)))))}}}}]);
|
||||||
|
//# sourceMappingURL=889.prod.js.map
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3827
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user