TheCard handling in messages

This commit is contained in:
or-else
2026-01-16 20:51:57 +03:00
parent 16489432cf
commit 930f4f0656
42 changed files with 1304 additions and 126 deletions
+384 -16
View File
@@ -1400,7 +1400,7 @@ class DB {
/***/ (function(module) {
/**
* @copyright 2015-2024 Tinode LLC.
* @copyright 2015-2026 Tinode LLC.
* @summary Minimally rich text representation and formatting for Tinode.
* @license Apache 2.0
*
@@ -1460,7 +1460,7 @@ const MAX_PREVIEW_DATA_SIZE = 64;
const DRAFTY_MIME_TYPE = 'text/x-drafty';
const DRAFTY_FR_MIME_TYPE = 'text/x-drafty-fr';
const DRAFTY_FR_MIME_TYPE_LEGACY = 'application/json';
const ALLOWED_ENT_FIELDS = ['act', 'height', 'duration', 'incoming', 'mime', 'name', 'premime', 'preref', 'preview', 'ref', 'size', 'state', 'url', 'val', 'width'];
const ALLOWED_ENT_FIELDS = ['act', 'height', 'duration', 'fn', 'incoming', 'mime', 'name', 'premime', 'preref', 'preview', 'ref', 'size', 'state', 'url', 'val', 'width'];
const segmenter = new Intl.Segmenter();
const INLINE_STYLES = [{
name: 'ST',
@@ -1597,6 +1597,11 @@ const FORMAT_TAGS = {
md_tag: '*',
isVoid: false
},
TC: {
html_tag: 'div',
md_tag: undefined,
isVoid: false
},
VC: {
html_tag: 'div',
md_tag: undefined,
@@ -1769,6 +1774,17 @@ const DECORATORS = {
return data ? {} : null;
}
},
TC: {
open: _ => '<div>',
close: _ => '</div>',
props: data => {
if (!data) return {};
return {
'data-fn': data.fn,
'data-title': data.title
};
}
},
VC: {
open: _ => '<div>',
close: _ => '</div>',
@@ -2314,6 +2330,24 @@ Drafty.appendLineBreak = function (content) {
content.txt += ' ';
return content;
};
Drafty.appendTheCard = function (content, theCardData) {
content = content || {
txt: ''
};
content.ent = content.ent || [];
content.fmt = content.fmt || [];
content.fmt.push({
at: stringToGraphemes(content.txt).length,
len: 1,
key: content.ent.length
});
content.txt += ' ';
content.ent.push({
tp: 'TC',
data: theCardData
});
return content;
};
Drafty.UNSAFE_toHTML = function (doc) {
const tree = draftyToTree(doc);
const htmlFormatter = function (type, data, values) {
@@ -3930,6 +3964,336 @@ class MetaGetBuilder {
/***/ }),
/***/ "./src/the-card.js":
/*!*************************!*\
!*** ./src/the-card.js ***!
\*************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": function() { return /* binding */ TheCard; }
/* harmony export */ });
/* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./config.js */ "./src/config.js");
class TheCard {
constructor(fn, imageUrl, imageMimeType, note) {
this.card = theCard(fn, imageUrl, imageMimeType, note);
}
getCard() {
return this.card;
}
}
TheCard.init = theCard;
TheCard.contentType = 'text/x-card';
TheCard.setFn = function (card, fn) {
fn = fn && fn.trim();
card = card || {};
card.fn = fn || undefined;
return card;
};
TheCard.getFn = function (card) {
if (card && card.fn) {
return card.fn;
}
return null;
};
TheCard.setNote = function (card, note) {
note = note && note.trim();
card = card || {};
card.note = note ? note : _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR;
return card;
};
TheCard.setPhoto = function (card, imageUrl, imageMimeType) {
if (imageUrl) {
card = card || {};
let mimeType = imageMimeType;
const matches = /^data:(image\/[-a-z0-9+.]+)?(;base64)?,/i.exec(imageUrl);
if (matches) {
mimeType = matches[1];
card.photo = {
data: imageUrl.substring(imageUrl.indexOf(',') + 1),
ref: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR
};
} else {
card.photo = {
data: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR,
ref: imageUrl
};
if (!mimeType) {
const ext = /\.([a-z0-9]+)$/i.exec(imageUrl);
if (ext) {
const extLower = ext[1].toLowerCase();
const mimeMap = {
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'bmp': 'image/bmp',
'webp': 'image/webp',
'svg': 'image/svg+xml'
};
mimeType = mimeMap[extLower] || null;
}
}
}
card.photo.type = (mimeType || 'image/jpeg').substring('image/'.length);
} else {
card = card || {};
card.photo = _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR;
}
return card;
};
TheCard.getPhotoUrl = function (card) {
if (card && card.photo) {
if (card.photo.ref && card.photo.ref != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
return card.photo.ref;
} else if (card.photo.data && card.photo.data != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
return 'data:image/' + (card.photo.type || 'jpeg') + ';base64,' + card.photo.data;
}
}
return null;
};
TheCard.getOrg = function (card) {
if (card && card.org) {
return card.org.fn || null;
}
return null;
};
TheCard.setPhone = function (card, phone, type = 'voice') {
return addOrSetComm(card, 'tel', phone, type, true);
};
TheCard.setEmail = function (card, email, type = 'home') {
return addOrSetComm(card, 'email', email, type, true);
};
TheCard.setTinodeID = function (card, tinodeID, type = 'home') {
return addOrSetComm(card, 'tinode', tinodeID, type, true);
};
TheCard.addPhone = function (card, phone, type = 'voice') {
return addOrSetComm(card, 'tel', phone, type, false);
};
TheCard.addEmail = function (card, email, type = 'home') {
return addOrSetComm(card, 'email', email, type, false);
};
TheCard.addTinodeID = function (card, tinodeID, type = 'home') {
return addOrSetComm(card, 'tinode', tinodeID, type, false);
};
TheCard.clearPhone = function (card, phone, type) {
return clearComm(card, 'tel', phone, type);
};
TheCard.clearEmail = function (card, email, type) {
return clearComm(card, 'email', email, type);
};
TheCard.clearTinodeID = function (card, tinodeID, type) {
return clearComm(card, 'tinode', tinodeID, type);
};
TheCard.getComm = function (card, proto) {
if (card && Array.isArray(card.comm)) {
return card.comm.filter(c => c.proto == proto);
}
return [];
};
TheCard.getEmails = function (card) {
return TheCard.getComm(card, 'email').map(c => c.value);
};
TheCard.getPhones = function (card) {
return TheCard.getComm(card, 'tel').map(c => c.value);
};
TheCard.getFirstTinodeID = function (card) {
const comms = TheCard.getComm(card, 'tinode');
if (comms.length > 0) {
return comms[0].value;
}
return null;
};
TheCard.exportVCard = function (card) {
if (!card) {
return null;
}
let vcard = 'BEGIN:VCARD\r\nVERSION:3.0\r\n';
if (card.fn) {
vcard += `FN:${card.fn}\r\n`;
}
if (card.n) {
vcard += `N:${card.n.surname || ''};${card.n.given || ''};${card.n.additional || ''};${card.n.prefix || ''};${card.n.suffix || ''}\r\n`;
}
if (card.org) {
if (card.org.fn) {
vcard += `ORG:${card.org.fn}\r\n`;
}
if (card.org.title) {
vcard += `TITLE:${card.org.title}\r\n`;
}
}
if (card.note && card.note != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
vcard += `NOTE:${card.note}\r\n`;
}
if (card.photo) {
if (card.photo.ref && card.photo.ref != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
vcard += `PHOTO;VALUE=URI:${card.photo.ref}\r\n`;
} else if (card.photo.data && card.photo.data != _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR) {
vcard += `PHOTO;TYPE=${card.photo.type.toUpperCase()};ENCODING=b:${card.photo.data}\r\n`;
}
}
if (Array.isArray(card.comm)) {
card.comm.forEach(comm => {
if (comm.proto === 'tel') {
vcard += `TEL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
} else if (comm.proto === 'email') {
vcard += `EMAIL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
} else if (comm.proto === 'tinode') {
vcard += `IMPP;TYPE=${comm.des.toUpperCase()};tinode:${comm.value}\r\n`;
} else if (comm.proto === 'http') {
vcard += `URL;TYPE=${comm.des.toUpperCase()}:${comm.value}\r\n`;
}
});
}
vcard += 'END:VCARD\r\n';
return vcard;
};
TheCard.importVCard = function (vcardStr) {
if (!vcardStr || typeof vcardStr !== 'string') {
return null;
}
const lines = vcardStr.split(/\r\n|\n/);
let card = {};
lines.forEach(line => {
const [keyPart, ...valueParts] = line.split(':');
if (!keyPart || valueParts.length === 0) {
return;
}
const value = valueParts.join(':');
const keyParams = keyPart.split(';');
const key = keyParams[0].trim().toUpperCase();
if (key === 'FN') {
card.fn = value;
} else if (key === 'N') {
const parts = value.split(';');
card.n = {
surname: parts[0] || undefined,
given: parts[1] || undefined,
additional: parts[2] || undefined,
prefix: parts[3] || undefined,
suffix: parts[4] || undefined
};
Object.keys(card.n).forEach(key => card.n[key] === undefined && delete card.n[key]);
if (Object.keys(card.n).length === 0) {
delete card.n;
}
} else if (key === 'ORG') {
const parts = value.split(';');
card.org = card.org || {};
card.org.fn = parts[0] || undefined;
if (!card.org.fn) delete card.org.fn;
} else if (key === 'TITLE') {
card.org = card.org || {};
card.org.title = value || undefined;
if (!card.org.title) delete card.org.title;
} else if (key === 'NOTE') {
card.note = value;
} else if (key === 'PHOTO') {
const params = keyPart.split(';').slice(1);
let type = 'jpeg';
let encoding = null;
params.forEach(param => {
const [pKey, pValue] = param.split('=');
if (pKey && pKey.trim().toUpperCase() === 'TYPE') {
type = pValue ? pValue.trim().toLowerCase() : 'jpeg';
} else if (pKey && pKey.trim().toUpperCase() === 'ENCODING') {
encoding = pValue ? pValue.trim().toLowerCase() : null;
}
});
if (encoding === 'b') {
card.photo = {
type: type,
data: value,
ref: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR
};
} else {
card.photo = {
type: type,
data: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR,
ref: value
};
}
} else if (key === 'TEL') {
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'voice';
card = TheCard.addPhone(card, value, des);
} else if (key === 'EMAIL') {
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'home';
card = TheCard.addEmail(card, value, des);
} else if (key === 'IMPP') {
const des = keyPart.split(';').find(param => param.trim().toUpperCase().startsWith('TYPE='))?.split('=')[1].trim().toLowerCase() || 'home';
const tinodeID = value.replace(/^tinode:/, '');
card = TheCard.addTinodeID(card, tinodeID, des);
}
});
return card;
};
TheCard.isFileSupported = function (type, name) {
return type == 'text/vcard' || (name || '').endsWith('.vcf') || (name || '').endsWith('.vcard');
};
function theCard(fn, imageUrl, imageMimeType, note) {
let card = null;
fn = fn && fn.trim();
note = note && note.trim();
if (fn) {
card = {
fn: fn
};
}
if (typeof note == 'string') {
card = card || {};
card.note = note ? note : _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR;
}
if (imageUrl) {
card = card || {};
let mimeType = imageMimeType;
const matches = /^data:(image\/[-a-z0-9+.]+)?(;base64)?,/i.exec(imageUrl);
if (matches) {
mimeType = matches[1];
card.photo = {
data: imageUrl.substring(imageUrl.indexOf(',') + 1),
ref: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR
};
} else {
card.photo = {
data: _config_js__WEBPACK_IMPORTED_MODULE_0__.DEL_CHAR,
ref: imageUrl
};
}
card.photo.type = (mimeType || 'image/jpeg').substring('image/'.length);
}
return card;
}
function addOrSetComm(card, proto, value, type, setOnly) {
proto = proto && proto.trim();
value = value && value.trim();
if (proto && value) {
card = card || {};
card.comm = card.comm || [];
if (setOnly) {
card.comm = card.comm.filter(c => !(c.proto == proto && c.des == type));
}
card.comm.push({
proto: proto,
des: type,
value: value
});
}
return card;
}
function clearComm(card, proto, value, type) {
if (card && Array.isArray(card.comm)) {
card.comm = card.comm.filter(c => !(c.proto == proto && (!type || c.des == type) && (!value || c.value == value)));
}
return card;
}
/***/ }),
/***/ "./src/topic.js":
/*!**********************!*\
!*** ./src/topic.js ***!
@@ -5549,6 +5913,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ AccessMode: function() { return /* reexport safe */ _access_mode_js__WEBPACK_IMPORTED_MODULE_0__["default"]; },
/* harmony export */ Drafty: function() { return /* reexport default from dynamic */ _drafty_js__WEBPACK_IMPORTED_MODULE_5___default.a; },
/* harmony export */ TheCard: function() { return /* reexport safe */ _the_card_js__WEBPACK_IMPORTED_MODULE_11__["default"]; },
/* harmony export */ Tinode: function() { return /* binding */ Tinode; }
/* harmony export */ });
/* harmony import */ var _access_mode_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./access-mode.js */ "./src/access-mode.js");
@@ -5563,11 +5928,12 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _topic_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./topic.js */ "./src/topic.js");
/* harmony import */ var _fnd_topic_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./fnd-topic.js */ "./src/fnd-topic.js");
/* harmony import */ var _me_topic_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./me-topic.js */ "./src/me-topic.js");
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./utils.js */ "./src/utils.js");
/* harmony import */ var _the_card_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./the-card.js */ "./src/the-card.js");
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./utils.js */ "./src/utils.js");
/**
* @module tinode-sdk
*
* @copyright 2015-2025 Tinode LLC.
* @copyright 2015-2026 Tinode LLC.
* @summary Javascript bindings for Tinode.
* @license Apache 2.0
* @version 0.25
@@ -5622,6 +5988,8 @@ __webpack_require__.r(__webpack_exports__);
let WebSocketProvider;
if (typeof WebSocket != 'undefined') {
WebSocketProvider = WebSocket;
@@ -5698,7 +6066,7 @@ function b64EncodeUnicode(str) {
}
function jsonBuildHelper(key, val) {
if (val instanceof Date) {
val = (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.rfc3339DateString)(val);
val = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.rfc3339DateString)(val);
} else if (val instanceof _access_mode_js__WEBPACK_IMPORTED_MODULE_0__["default"]) {
val = val.jsonHelper();
} else if (val === undefined || val === null || val === false || Array.isArray(val) && val.length == 0 || typeof val == 'object' && Object.keys(val).length == 0) {
@@ -5858,7 +6226,7 @@ class Tinode {
});
}).then(_ => {
return this._db.mapUsers(data => {
this.#cachePut('user', data.uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)({}, data.public));
this.#cachePut('user', data.uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)({}, data.public));
});
}).then(_ => {
return Promise.all(prom);
@@ -5919,7 +6287,7 @@ class Tinode {
if (id) {
promise = this.#makePromise(id);
}
pkt = (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.simplify)(pkt);
pkt = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.simplify)(pkt);
let msg = JSON.stringify(pkt);
this.logger("out: " + (this._trimLongStrings ? JSON.stringify(pkt, jsonLoggerHelper) : msg));
try {
@@ -5945,7 +6313,7 @@ class Tinode {
}
return;
}
let pkt = JSON.parse(data, _utils_js__WEBPACK_IMPORTED_MODULE_11__.jsonParseHelper);
let pkt = JSON.parse(data, _utils_js__WEBPACK_IMPORTED_MODULE_12__.jsonParseHelper);
if (!pkt) {
this.logger("in: " + data);
this.logger("ERROR: failed to parse data");
@@ -6206,13 +6574,13 @@ class Tinode {
if (pub) {
return {
user: uid,
public: (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)({}, pub)
public: (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)({}, pub)
};
}
return undefined;
};
topic._cachePutUser = (uid, user) => {
this.#cachePut('user', uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)({}, user.public));
this.#cachePut('user', uid, (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)({}, user.public));
};
topic._cacheDelUser = uid => {
this.#cacheDel('user', uid);
@@ -6391,7 +6759,7 @@ class Tinode {
if (typeof url != 'string') {
return url;
}
if ((0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(url)) {
if ((0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(url)) {
const base = 'scheme://host/';
const parsed = new URL(url, base);
if (this._apiKey) {
@@ -6422,7 +6790,7 @@ class Tinode {
pkt.acc.tmpsecret = params.secret;
if (Array.isArray(params.attachments) && params.attachments.length > 0) {
pkt.extra = {
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
};
}
}
@@ -6531,7 +6899,7 @@ class Tinode {
}
if (Array.isArray(setParams.attachments) && setParams.attachments.length > 0) {
pkt.extra = {
attachments: setParams.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
attachments: setParams.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
};
}
if (setParams.tags) {
@@ -6574,7 +6942,7 @@ class Tinode {
};
if (attachments) {
msg.extra = {
attachments: attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
attachments: attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
};
}
return this.#send(msg, pub.id);
@@ -6647,7 +7015,7 @@ class Tinode {
}
getMeta(topic, params) {
const pkt = this.#initPacket('get', topic);
pkt.get = (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.mergeObj)(pkt.get, params);
pkt.get = (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.mergeObj)(pkt.get, params);
return this.#send(pkt, pkt.get.id);
}
setMeta(topic, params) {
@@ -6662,7 +7030,7 @@ class Tinode {
});
if (Array.isArray(params.attachments) && params.attachments.length > 0) {
pkt.extra = {
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_11__.isUrlRelative)(ref))
attachments: params.attachments.filter(ref => (0,_utils_js__WEBPACK_IMPORTED_MODULE_12__.isUrlRelative)(ref))
};
}
}