adding TheCard preview
This commit is contained in:
+79
-68
@@ -2035,6 +2035,12 @@ img.channel {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-card .avatar-box {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
|
||||
/* Video-call related styles */
|
||||
|
||||
#video-container {
|
||||
@@ -3550,6 +3556,79 @@ ul.tabbar li.active > a {
|
||||
position: relative;
|
||||
right: 0;
|
||||
}
|
||||
/* END of pinned messages */
|
||||
|
||||
/* Contact attachments */
|
||||
.contact-card {
|
||||
position: relative;
|
||||
background-color: var(--clr-background-off-white);
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 14rem;
|
||||
}
|
||||
|
||||
.contact-card .avatar-box {
|
||||
position: relative;
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
font-size: 1rem;
|
||||
margin-right: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.contact-card .contact-body {
|
||||
align-items: center;
|
||||
background-color: var(--clr-background-blue-gray-50);
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box .name {
|
||||
color: var(--clr-text-primary);
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box .org {
|
||||
color: var(--clr-text-secondary);
|
||||
font-size: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.contact-card .contact-actions {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
border-top: none;
|
||||
background-color: var(--clr-bright);
|
||||
}
|
||||
|
||||
.contact-card .contact-actions a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-card .contact-actions .divider {
|
||||
height: 1.8rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* END of contact attachments */
|
||||
|
||||
/*
|
||||
Drag-and-drop area.
|
||||
@@ -3792,71 +3871,3 @@ div.toast.show {
|
||||
border-radius: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
position: relative;
|
||||
background-color: var(--clr-background-off-white);
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.contact-card .avatar-box {
|
||||
position: relative;
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
font-size: 1rem;
|
||||
margin-right: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.contact-card .contact-body {
|
||||
align-items: center;
|
||||
background-color: var(--clr-background-blue-gray-50);
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box .name {
|
||||
color: var(--clr-text-primary);
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.contact-card .contact-body .name-box .org {
|
||||
color: var(--clr-text-secondary);
|
||||
font-size: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.contact-card .contact-actions {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
border-top: none;
|
||||
background-color: var(--clr-bright);
|
||||
}
|
||||
|
||||
.contact-card .contact-actions a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-card .contact-actions .divider {
|
||||
height: 1.8rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "Contact Card",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "Contacts",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": false,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "Full name",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": false,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "Organization",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": false,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +434,14 @@
|
||||
"defaultMessage": "Theme:",
|
||||
"description": "Label for selecting color scheme (dark, light) in Settings"
|
||||
},
|
||||
"label_contact_card": {
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview"
|
||||
},
|
||||
"label_contacts": {
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information"
|
||||
},
|
||||
"label_content_type": {
|
||||
"defaultMessage": "Content type:",
|
||||
"description": "Label for file content type (mime)"
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,5 +1941,33 @@
|
||||
"description": "Label for search button in contact card",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contact_card": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contact Card",
|
||||
"description": "Label for contact card preview",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_contacts": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Contacts",
|
||||
"description": "Label for contact information",
|
||||
"missing": true,
|
||||
"obsolete": false
|
||||
},
|
||||
"label_full_name": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Full name",
|
||||
"description": "Label for full name",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
},
|
||||
"label_organization": {
|
||||
"translation": "",
|
||||
"defaultMessage": "Organization",
|
||||
"description": "Label for organization",
|
||||
"missing": true,
|
||||
"obsolete": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// File and image helper functions.
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
|
||||
// Supported image MIME types and corresponding file extensions.
|
||||
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||
@@ -305,3 +306,21 @@ export function base64ToIntArray(b64) {
|
||||
} catch (err) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function importVCard(fileOrBlob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = _ => {
|
||||
reject(reader.error);
|
||||
};
|
||||
reader.onload = _ => {
|
||||
const card = TheCard.importVCard(reader.result);
|
||||
if (!card) {
|
||||
reject(new Error("Failed to parse vCard"));
|
||||
return;
|
||||
}
|
||||
resolve(card);
|
||||
};
|
||||
reader.readAsText(fileOrBlob);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import CallMessage from '../widgets/call-message.jsx'
|
||||
import CallStatus from '../widgets/call-status.jsx';
|
||||
import InlineVideo from '../widgets/inline-video.jsx';
|
||||
import LazyImage from '../widgets/lazy-image.jsx';
|
||||
import TheCardViewer from '../widgets/the-card-viewer.jsx';
|
||||
import TheCardMini from '../widgets/the-card-mini.jsx';
|
||||
import UploadingImage from '../widgets/uploading-image.jsx'
|
||||
|
||||
import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO,
|
||||
@@ -135,7 +135,7 @@ export function fullFormatter(style, data, values, key, stack) {
|
||||
break;
|
||||
case 'TC':
|
||||
// TheCard
|
||||
el = TheCardViewer;
|
||||
el = TheCardMini;
|
||||
attr.content = data;
|
||||
attr.authorizeURL = this.authorizeURL;
|
||||
attr.onChatClick = (e) => this.onHandleClick(e, 'contact_chat');
|
||||
|
||||
+35
-35
@@ -19,13 +19,14 @@ import LogoView from './logo-view.jsx';
|
||||
import MetaMessage from '../widgets/meta-message.jsx';
|
||||
import PinnedMessages from '../widgets/pinned-messages.jsx';
|
||||
import SendMessage from '../widgets/send-message.jsx';
|
||||
import TheCardPreview from '../widgets/the-card-preview.jsx';
|
||||
import VideoPreview from '../widgets/video-preview.jsx';
|
||||
|
||||
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
|
||||
DRAFTY_FR_MIME_TYPE_LEGACY, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM,
|
||||
MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY, QUOTED_REPLY_LENGTH, VIDEO_PREVIEW_DIM } from '../config.js';
|
||||
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
||||
import { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { blobToBase64, fileToBase64, imageScaled, importVCard, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
import { bytesToHumanSize, relativeDateFormat, shortDateFormat } from '../lib/strformat.js';
|
||||
|
||||
@@ -1103,16 +1104,13 @@ class MessagesView extends React.Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const card = TheCard.importVCard(e.target.result);
|
||||
if (!card) {
|
||||
// Failed to parse vCard.
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_parse_vcard), 'err');
|
||||
}
|
||||
this.sendMessage(Drafty.appendTheCard(null, card));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
importVCard(file)
|
||||
.then(card => {
|
||||
this.sendMessage(Drafty.appendTheCard(null, card));
|
||||
})
|
||||
.catch(err => {
|
||||
this.props.onError(this.props.intl.formatMessage(messages.cannot_parse_vcard, {error: err.message}), 'err');
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1523,7 +1521,7 @@ class MessagesView extends React.Component {
|
||||
|
||||
// P2P call is an overlay, not a separate component because it cannot be
|
||||
// re-mounted between full screen/minimized.
|
||||
const overlay = this.state.rtcPanel ? (
|
||||
const overlay = this.state.rtcPanel &&
|
||||
<CallPanel
|
||||
topic={this.state.topic}
|
||||
seq={this.props.callSeq}
|
||||
@@ -1533,23 +1531,20 @@ class MessagesView extends React.Component {
|
||||
title={this.state.title}
|
||||
avatar={this.state.avatar || true}
|
||||
minimized={this.state.minimizedCallPanel}
|
||||
|
||||
onError={this.props.onError}
|
||||
onHangup={this.handleCallHangup}
|
||||
onToggleMinimize={this.handleCallPanelToggle}
|
||||
onInvite={this.props.onCallInvite}
|
||||
onSendOffer={this.props.onCallSendOffer}
|
||||
onIceCandidate={this.props.onCallIceCandidate}
|
||||
onSendAnswer={this.props.onCallSendAnswer} />
|
||||
) : null;
|
||||
onSendAnswer={this.props.onCallSendAnswer} />;
|
||||
|
||||
let component;
|
||||
if (!this.state.topic) {
|
||||
component = (
|
||||
<LogoView
|
||||
component = <LogoView
|
||||
serverVersion={this.props.serverVersion}
|
||||
serverAddress={this.props.serverAddress} />
|
||||
);
|
||||
serverAddress={this.props.serverAddress} />;
|
||||
|
||||
} else {
|
||||
let component2;
|
||||
if (this.state.imagePreview) {
|
||||
@@ -1565,43 +1560,48 @@ class MessagesView extends React.Component {
|
||||
);
|
||||
} else if (this.state.videoPreview) {
|
||||
// Preview video.
|
||||
component2 = (
|
||||
<VideoPreview
|
||||
component2 = <VideoPreview
|
||||
content={this.state.videoPreview}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.state.reply}
|
||||
onError={this.props.onError}
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendVideoAttachment} />
|
||||
);
|
||||
onSendMessage={this.sendVideoAttachment} />;
|
||||
|
||||
} else if (this.state.imagePostview) {
|
||||
// Expand received image.
|
||||
component2 = (
|
||||
<ImagePreview
|
||||
component2 = <ImagePreview
|
||||
content={this.state.imagePostview}
|
||||
onClose={this.handleClosePreview} />
|
||||
);
|
||||
onClose={this.handleClosePreview} />;
|
||||
|
||||
} else if (this.state.videoPostview) {
|
||||
// Play received video.
|
||||
component2 = (
|
||||
<VideoPreview
|
||||
component2 = <VideoPreview
|
||||
content={this.state.videoPostview}
|
||||
tinode={this.props.tinode}
|
||||
onError={this.props.onError}
|
||||
onClose={this.handleClosePreview} />
|
||||
);
|
||||
onClose={this.handleClosePreview} />;
|
||||
|
||||
} else if (this.state.docPreview) {
|
||||
// Preview attachment before sending.
|
||||
component2 = (
|
||||
<DocPreview
|
||||
if (TheCard.isFileSupported(this.state.docPreview.type, this.state.docPreview.name)) {
|
||||
component2 = <TheCardPreview
|
||||
content={this.state.docPreview}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.state.reply}
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendFileAttachment} />
|
||||
);
|
||||
onSendMessage={this.sendFileAttachment} />;
|
||||
} else {
|
||||
component2 = <DocPreview
|
||||
content={this.state.docPreview}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.state.reply}
|
||||
onCancelReply={this.handleCancelReply}
|
||||
onClose={this.handleClosePreview}
|
||||
onSendMessage={this.sendFileAttachment} />;
|
||||
}
|
||||
} else {
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
const isChannel = topic.isChannelType() || topic.chan;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
|
||||
const messages = defineMessages({
|
||||
unknown_name: {
|
||||
id: 'unknown_name',
|
||||
defaultMessage: 'Unknown',
|
||||
description: 'Name to show when the name is missing'
|
||||
}
|
||||
});
|
||||
|
||||
class TheCardMini extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleDownload = this.handleDownload.bind(this);
|
||||
this.handleMessage = this.handleMessage.bind(this);
|
||||
}
|
||||
|
||||
handleDownload(e) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const vcard = TheCard.exportVCard(this.props.content);
|
||||
const blob = new Blob([vcard], { type: 'text/vcard' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = (this.props.content.fn || 'contact') + '.vcf';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (err) {
|
||||
console.error("Failed to download vcard", err);
|
||||
if (this.props.onError) {
|
||||
this.props.onError("Failed to download vcard", 'err');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(e) {
|
||||
// This is handled by the parent component via props.onAction which expects a click event.
|
||||
}
|
||||
|
||||
render() {
|
||||
const card = this.props.content;
|
||||
if (!card) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const uid = TheCard.getFirstTinodeID(card);
|
||||
const contacts = TheCard.getEmails(card).concat(TheCard.getPhones(card));
|
||||
const org = TheCard.getOrg(card);
|
||||
return (
|
||||
<div className="contact-card">
|
||||
<div className="contact-body">
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
authorizeURL={this.props.authorizeURL}
|
||||
topic={uid || 'usr123XXX'}
|
||||
avatar={TheCard.getPhotoUrl(card) || true}
|
||||
title={card.fn} />
|
||||
</div>
|
||||
<div className="name-box">
|
||||
<div className="name">{card.fn || <FormattedMessage {...messages.unknown_name} />}</div>
|
||||
{org && <div className="org">{org}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="contact-actions">
|
||||
{uid &&
|
||||
<a className="flat-button" data-val={uid} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="chat_now" defaultMessage="Chat"
|
||||
description="Label for message button in contact card" />
|
||||
</a>
|
||||
}
|
||||
{uid && <div className="divider"></div>}
|
||||
{contacts.length > 0 &&
|
||||
<a className="flat-button" data-val={contacts.join(',')} onClick={this.props.onChatClick}>
|
||||
<FormattedMessage id="find_user" defaultMessage="Find"
|
||||
description="Label for search button in contact card" />
|
||||
</a>
|
||||
}
|
||||
{contacts.length > 0 && <div className="divider"></div>}
|
||||
<a className="flat-button" onClick={this.handleDownload}>
|
||||
<FormattedMessage id="save_action" defaultMessage="Save" description="Label for Save button" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(TheCardMini);
|
||||
@@ -0,0 +1,190 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { parsePhoneNumber } from 'libphonenumber-js/mobile';
|
||||
|
||||
import SendMessage from './send-message.jsx';
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
|
||||
import { TheCard } from 'tinode-sdk';
|
||||
import { importVCard } from '../lib/blob-helpers.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
unknown_name: {
|
||||
id: 'unknown_name',
|
||||
defaultMessage: 'Unknown',
|
||||
description: 'Name to show when the name is missing'
|
||||
}
|
||||
});
|
||||
|
||||
export default class TheCardPreview extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
content: null
|
||||
};
|
||||
|
||||
importVCard(props.content.file)
|
||||
.then(card => {
|
||||
console.log('Imported vCard:', card);
|
||||
this.setState({ content: card });
|
||||
});
|
||||
|
||||
this.handleSendCard = this.handleSendCard.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
e.preventDefault();
|
||||
if (e.key === 'Escape') {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleSendCard() {
|
||||
this.props.onClose();
|
||||
this.props.onSendMessage(this.props.content);
|
||||
}
|
||||
|
||||
render() {
|
||||
const card = this.state.content;
|
||||
|
||||
console.log('Rendering TheCardPreview for card:', JSON.stringify(card));
|
||||
|
||||
const fullName = TheCard.getFn(card);
|
||||
const photoUrl = TheCard.getPhotoUrl(card) || null;
|
||||
const note = card?.note;
|
||||
const org = TheCard.getOrg(card);
|
||||
|
||||
// Get communication methods
|
||||
const emails = TheCard.getComm(card, 'email') || [];
|
||||
console.log('Emails:', emails);
|
||||
const phones = TheCard.getComm(card, 'tel') || [];
|
||||
console.log('Phones:', phones);
|
||||
const tinodeIds = TheCard.getComm(card, 'tinode') || [];
|
||||
console.log('Tinode IDs:', tinodeIds);
|
||||
const urls = TheCard.getComm(card, 'http') || [];
|
||||
console.log('URLs:', urls);
|
||||
|
||||
const contacts = [];
|
||||
|
||||
// Add emails
|
||||
emails.forEach((email, idx) => {
|
||||
const types = Array.isArray(email.des) ? email.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`email-${idx}`}>
|
||||
<tt>{email.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add phones
|
||||
phones.forEach((phone, idx) => {
|
||||
let val = phone.value;
|
||||
try {
|
||||
const number = parsePhoneNumber(phone.value);
|
||||
val = number ? number.formatInternational() : phone.value;
|
||||
} catch (err) {
|
||||
// Use original value if parsing fails
|
||||
}
|
||||
const types = Array.isArray(phone.des) ? phone.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`tel-${idx}`}>
|
||||
<tt>{val}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add Tinode IDs
|
||||
tinodeIds.forEach((tid, idx) => {
|
||||
const types = Array.isArray(tid.des) ? tid.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`tinode-${idx}`}>
|
||||
<tt>{tid.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Add URLs
|
||||
urls.forEach((url, idx) => {
|
||||
const types = Array.isArray(url.des) ? url.des.join(', ') : '';
|
||||
contacts.push(
|
||||
<div className="group quoted" key={`url-${idx}`}>
|
||||
<tt>{url.value}</tt>
|
||||
<span className="small gray"> {types ? `(${types})` : ''}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="image-preview">
|
||||
<div id="image-preview-caption-panel">
|
||||
<span><FormattedMessage id="label_contact_card" defaultMessage="Contact Card"
|
||||
description="Label for contact card preview" /></span>
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}>
|
||||
<i className="material-icons gray">close</i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="image-preview-container">
|
||||
<div className="doc-card">
|
||||
<div className="panel-form-column">
|
||||
<center>
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
authorizeURL={this.props.tinode.authorizeURL}
|
||||
avatar={photoUrl || true}
|
||||
topic="usrXXX123"
|
||||
title={fullName} />
|
||||
</div>
|
||||
</center>
|
||||
<div className="group">
|
||||
<div className="large ellipsized">{fullName || <FormattedMessage {...messages.unknown_name} />}</div>
|
||||
</div>
|
||||
{org ?
|
||||
<div className="group">
|
||||
<div>{org}</div>
|
||||
</div>
|
||||
: null}
|
||||
{note ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_description" defaultMessage="Description"
|
||||
description="Label for description" />
|
||||
</label>
|
||||
<div className="quoted">{note}</div>
|
||||
</div>
|
||||
: null}
|
||||
{contacts.length > 0 ?
|
||||
<div className="group">
|
||||
<label className="small">
|
||||
<FormattedMessage id="label_contacts" defaultMessage="Contacts"
|
||||
description="Label for contact information" />
|
||||
</label>
|
||||
{contacts}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SendMessage
|
||||
noInput={true}
|
||||
tinode={this.props.tinode}
|
||||
reply={this.props.reply}
|
||||
onCancelReply={this.props.onCancelReply}
|
||||
onSendMessage={this.handleSendCard}
|
||||
onError={this.props.onError} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
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
+4120
-71
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
+105
-15
@@ -4139,14 +4139,15 @@ TheCard.exportVCard = function (card) {
|
||||
}
|
||||
if (Array.isArray(card.comm)) {
|
||||
card.comm.forEach(comm => {
|
||||
const types = comm.des.join(',').toUpperCase();
|
||||
if (comm.proto === 'tel') {
|
||||
vcard += `TEL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
|
||||
vcard += `TEL;TYPE=${types}:${comm.value}\r\n`;
|
||||
} else if (comm.proto === 'email') {
|
||||
vcard += `EMAIL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
|
||||
vcard += `EMAIL;TYPE=${types}:${comm.value}\r\n`;
|
||||
} else if (comm.proto === 'tinode') {
|
||||
vcard += `IMPP;TYPE=${comm.des.toUpperCase()};tinode:${comm.value}\r\n`;
|
||||
vcard += `IMPP;TYPE=${types};tinode:${comm.value}\r\n`;
|
||||
} else if (comm.proto === 'http') {
|
||||
vcard += `URL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
|
||||
vcard += `URL;TYPE=${types}:${comm.value}\r\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4157,8 +4158,25 @@ TheCard.importVCard = function (vcardStr) {
|
||||
if (!vcardStr || typeof vcardStr !== 'string') {
|
||||
return null;
|
||||
}
|
||||
const lines = vcardStr.split(/\r\n|\n/);
|
||||
const rawLines = vcardStr.split(/\r\n|\n/);
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
for (let i = 0; i < rawLines.length; i++) {
|
||||
const line = rawLines[i];
|
||||
if (line.startsWith(' ') || line.startsWith('\t')) {
|
||||
currentLine += line.substring(1);
|
||||
} else {
|
||||
if (currentLine) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
currentLine = line;
|
||||
}
|
||||
}
|
||||
if (currentLine) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
let card = {};
|
||||
const commMap = new Map();
|
||||
lines.forEach(line => {
|
||||
const [keyPart, ...valueParts] = line.split(':');
|
||||
if (!keyPart || valueParts.length === 0) {
|
||||
@@ -4219,21 +4237,83 @@ TheCard.importVCard = function (vcardStr) {
|
||||
};
|
||||
}
|
||||
} else if (key === 'TEL') {
|
||||
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'voice';
|
||||
card = TheCard.addPhone(card, value, des);
|
||||
const typeParams = keyPart.split(';').filter(param => param.trim().toUpperCase().startsWith('TYPE='));
|
||||
const types = typeParams.flatMap(param => {
|
||||
const equalIndex = param.indexOf('=');
|
||||
const valuesPart = param.substring(equalIndex + 1);
|
||||
return valuesPart.split(',').map(t => {
|
||||
const cleaned = t.trim().toLowerCase();
|
||||
return cleaned.startsWith('type=') ? cleaned.substring(5) : cleaned;
|
||||
});
|
||||
}).filter(t => t !== 'internet');
|
||||
const mapKey = `tel|${value}`;
|
||||
if (!commMap.has(mapKey)) {
|
||||
commMap.set(mapKey, new Set());
|
||||
}
|
||||
types.forEach(t => commMap.get(mapKey).add(t));
|
||||
} else if (key === 'EMAIL') {
|
||||
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'home';
|
||||
card = TheCard.addEmail(card, value, des);
|
||||
const typeParams = keyPart.split(';').filter(param => param.trim().toUpperCase().startsWith('TYPE='));
|
||||
const types = typeParams.flatMap(param => {
|
||||
const equalIndex = param.indexOf('=');
|
||||
const valuesPart = param.substring(equalIndex + 1);
|
||||
return valuesPart.split(',').map(t => {
|
||||
const cleaned = t.trim().toLowerCase();
|
||||
return cleaned.startsWith('type=') ? cleaned.substring(5) : cleaned;
|
||||
});
|
||||
}).filter(t => t !== 'internet');
|
||||
const mapKey = `email|${value}`;
|
||||
if (!commMap.has(mapKey)) {
|
||||
commMap.set(mapKey, new Set());
|
||||
}
|
||||
types.forEach(t => commMap.get(mapKey).add(t));
|
||||
} else if (key === 'IMPP') {
|
||||
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'home';
|
||||
const typeParams = keyPart.split(';').filter(param => param.trim().toUpperCase().startsWith('TYPE='));
|
||||
const types = typeParams.flatMap(param => {
|
||||
const equalIndex = param.indexOf('=');
|
||||
const valuesPart = param.substring(equalIndex + 1);
|
||||
return valuesPart.split(',').map(t => {
|
||||
const cleaned = t.trim().toLowerCase();
|
||||
return cleaned.startsWith('type=') ? cleaned.substring(5) : cleaned;
|
||||
});
|
||||
}).filter(t => t !== 'internet');
|
||||
const tinodeID = value.replace(/^tinode:/, '');
|
||||
card = TheCard.addTinodeID(card, tinodeID, des);
|
||||
const mapKey = `tinode|${tinodeID}`;
|
||||
if (!commMap.has(mapKey)) {
|
||||
commMap.set(mapKey, new Set());
|
||||
}
|
||||
types.forEach(t => commMap.get(mapKey).add(t));
|
||||
} else if (key === 'URL') {
|
||||
const typeParams = keyPart.split(';').filter(param => param.trim().toUpperCase().startsWith('TYPE='));
|
||||
const types = typeParams.flatMap(param => {
|
||||
const equalIndex = param.indexOf('=');
|
||||
const valuesPart = param.substring(equalIndex + 1);
|
||||
return valuesPart.split(',').map(t => {
|
||||
const cleaned = t.trim().toLowerCase();
|
||||
return cleaned.startsWith('type=') ? cleaned.substring(5) : cleaned;
|
||||
});
|
||||
}).filter(t => t !== 'internet');
|
||||
const mapKey = `http|${value}`;
|
||||
if (!commMap.has(mapKey)) {
|
||||
commMap.set(mapKey, new Set());
|
||||
}
|
||||
types.forEach(t => commMap.get(mapKey).add(t));
|
||||
}
|
||||
});
|
||||
if (commMap.size > 0) {
|
||||
card.comm = [];
|
||||
commMap.forEach((types, key) => {
|
||||
const [proto, value] = key.split('|', 2);
|
||||
card.comm.push({
|
||||
proto: proto,
|
||||
des: Array.from(types),
|
||||
value: value
|
||||
});
|
||||
});
|
||||
}
|
||||
return card;
|
||||
};
|
||||
TheCard.isFileSupported = function (type, name) {
|
||||
return type == 'text/vcard' || (name || '').endsWith('.vcf') || (name || '').endsWith('.vcard');
|
||||
return type == 'text/vcard' || (name || '').endsWith('.vcf') || (name || '').endsWith('.vcr') || (name || '').endsWith('.vcard');
|
||||
};
|
||||
function theCard(fn, imageUrl, imageMimeType, note) {
|
||||
let card = null;
|
||||
@@ -4275,11 +4355,14 @@ function addOrSetComm(card, proto, value, type, setOnly) {
|
||||
card = card || {};
|
||||
card.comm = card.comm || [];
|
||||
if (setOnly) {
|
||||
card.comm = card.comm.filter(c => !(c.proto == proto && c.des == type));
|
||||
card.comm = card.comm.filter(c => {
|
||||
if (c.proto != proto) return true;
|
||||
return !c.des.includes(type);
|
||||
});
|
||||
}
|
||||
card.comm.push({
|
||||
proto: proto,
|
||||
des: type,
|
||||
des: [type],
|
||||
value: value
|
||||
});
|
||||
}
|
||||
@@ -4287,7 +4370,14 @@ function addOrSetComm(card, proto, value, type, setOnly) {
|
||||
}
|
||||
function clearComm(card, proto, value, type) {
|
||||
if (card && Array.isArray(card.comm)) {
|
||||
card.comm = card.comm.filter(c => !(c.proto == proto && (!type || c.des == type) && (!value || c.value == value)));
|
||||
card.comm = card.comm.filter(c => {
|
||||
if (c.proto != proto) return true;
|
||||
if (value && c.value != value) return true;
|
||||
if (type) {
|
||||
return !c.des.includes(type);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
||||
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
+2687
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user