mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-02 14:07:04 +03:00
Redesign AddressBook
This commit is contained in:
parent
5f6ea3cf29
commit
a71874a8c4
18 changed files with 1403 additions and 1669 deletions
227
dev/DAV/JCard.js
Normal file
227
dev/DAV/JCard.js
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/**
|
||||
* https://datatracker.ietf.org/doc/html/rfc7095
|
||||
*
|
||||
* Inspired by https://github.com/mcpar-land/vcfer
|
||||
*/
|
||||
|
||||
import { VCardProperty } from './VCardProperty'
|
||||
|
||||
export class JCard {
|
||||
|
||||
constructor(input)
|
||||
{
|
||||
this.props = new Map()
|
||||
this.version = '4.0'
|
||||
if (input) {
|
||||
// read from jCard
|
||||
if (typeof input !== 'object') {
|
||||
throw new Error('error reading vcard')
|
||||
}
|
||||
this.parseFromJCard(input)
|
||||
}
|
||||
}
|
||||
|
||||
parseFromJCard(json)
|
||||
{
|
||||
json = JSON.parse(JSON.stringify(json));
|
||||
if (!/vcard/i.test(json[0])) {
|
||||
throw new SyntaxError('Incorrect jCard format');
|
||||
}
|
||||
json[1].forEach(jprop => this.add(new VCardProperty(jprop)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an array of {@link VCardProperty} objects under the specified field.
|
||||
* Returns [] if there are no VCardProperty objects found.
|
||||
* Properites are always stored in an array.
|
||||
* @param field to get.
|
||||
* @param type If provided, only return {@link VCardProperty}s with the specified
|
||||
* type as a param.
|
||||
*/
|
||||
get(field, type)
|
||||
{
|
||||
if (type) {
|
||||
let props = this.props.get(field);
|
||||
return props
|
||||
? props.filter(prop => {
|
||||
let types = prop.type;
|
||||
return (Array.isArray(types) ? types : [types]).includes(type);
|
||||
})
|
||||
: [];
|
||||
}
|
||||
return this.props.get(field) || [];
|
||||
// TODO with type filter-er
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a _single_ VCardProperty of the specified field. Attempts to pick based
|
||||
* on the following priorities, in order:
|
||||
* - `TYPE={type}` of the value specified in the `type` argument. Ignored
|
||||
* if the argument isn't supplied.
|
||||
* - `TYPE=pref` is present.
|
||||
* - is the VCardProperty at index 0 from get(field)
|
||||
* @param field
|
||||
* @param type
|
||||
*/
|
||||
getOne(field, type)
|
||||
{
|
||||
return this.get(field, type || 'pref')[0] || this.get(field)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the contents of a field to contain a single {@link VCardProperty}.
|
||||
*
|
||||
* Accepts either 2-4 arguments to construct a VCardProperty,
|
||||
* or 1 argument of a preexisting VCardProperty object.
|
||||
*
|
||||
* This will always overwrite all existing properties of the given
|
||||
* field. For just adding a new VCardProperty, see {@link VCard#add}
|
||||
* @param arg the field, or a VCardProperty object
|
||||
* @param value the value for the VCardProperty object
|
||||
* @param params the parameters for the VCardProperty object
|
||||
* @param group the group for the VCardProperty object
|
||||
*/
|
||||
set(arg, value, params, group)
|
||||
{
|
||||
if (typeof arg === 'string') {
|
||||
arg = new VCardProperty(String(arg), value, params, group);
|
||||
}
|
||||
if (!(arg instanceof VCardProperty)) {
|
||||
throw new Error('invalid argument of VCard.set(), expects string arguments or a VCardProperty');
|
||||
}
|
||||
let field = arg.getField();
|
||||
this.props.set(field, [arg]);
|
||||
return arg;
|
||||
}
|
||||
|
||||
add(arg, value, params, group)
|
||||
{
|
||||
// string arguments
|
||||
if (typeof arg === 'string') {
|
||||
arg = new VCardProperty(String(arg), value, params, group);
|
||||
}
|
||||
if (!(arg instanceof VCardProperty)) {
|
||||
throw new Error('invalid argument of VCard.add(), expects string arguments or a VCardProperty');
|
||||
}
|
||||
// VCardProperty arguments
|
||||
let field = arg.getField();
|
||||
if (this.props.get(field)) this.props.get(field)?.push(arg)
|
||||
else this.props.set(field, [arg])
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link VCardProperty}, or all properties of the supplied field.
|
||||
* @param arg the field, or a {@link VCardProperty} object
|
||||
* @param paramFilter (incomplete)
|
||||
*/
|
||||
remove(arg) {
|
||||
// string arguments
|
||||
if (typeof arg === 'string') {
|
||||
// TODO filter by param
|
||||
this.props.delete(arg);
|
||||
}
|
||||
// VCardProperty argument
|
||||
else if (arg instanceof VCardProperty) {
|
||||
let propArray = this.props.get(arg.getField());
|
||||
if (!(propArray === null || propArray === void 0 ? void 0 : propArray.includes(arg)))
|
||||
throw new Error("Attempted to remove VCardProperty VCard does not have: ".concat(arg));
|
||||
propArray.splice(propArray.indexOf(arg), 1);
|
||||
if (propArray.length === 0)
|
||||
this.props.delete(arg.getField());
|
||||
}
|
||||
// incorrect arguments
|
||||
else
|
||||
throw new Error('invalid argument of VCard.remove(), expects ' +
|
||||
'string and optional param filter or a VCardProperty');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the vCard has at least one @{link VCardProperty}
|
||||
* of the given field.
|
||||
* @param field The field to query
|
||||
*/
|
||||
has(field)
|
||||
{
|
||||
return (!!this.props.get(field) && this.props.get(field).length > 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns stringified JSON
|
||||
*/
|
||||
toString()
|
||||
{
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link JCard} object as a JSON array.
|
||||
*/
|
||||
toJSON()
|
||||
{
|
||||
let data = [['version', {}, 'text', '4.0']];
|
||||
/*
|
||||
this.props.forEach((props, field) =>
|
||||
(field === 'version') || props.forEach(prop => prop.isEmpty() || data.push(prop.toJSON()))
|
||||
);
|
||||
*/
|
||||
for (const [field, props] of this.props.entries()) {
|
||||
if ('version' !== field) {
|
||||
for (const prop of props) {
|
||||
prop.isEmpty() || data.push(prop.toJSON());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['vcard', data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically generate the 'fn' VCardProperty from the preferred 'n' VCardProperty.
|
||||
*
|
||||
* #### `set` (`boolean`)
|
||||
*
|
||||
* - `false`: (default) return the generated full name string without
|
||||
* modifying the VCard.
|
||||
*
|
||||
* - `true`: modify the VCard's `fn` VCardProperty directly, as specified
|
||||
* by `append`
|
||||
*
|
||||
* #### `append` (`boolean`)
|
||||
*
|
||||
* (ignored if `set` is `false`)
|
||||
*
|
||||
* - `false`: (default) replace the existing 'fn' VCardProperty/properties with
|
||||
* a new one.
|
||||
*
|
||||
* - `true`: append a new `fn` VCardProperty to the array.
|
||||
*
|
||||
* see: [RFC 6350 section 6.2.1](https://tools.ietf.org/html/rfc6350#section-6.2.1)
|
||||
*/
|
||||
parseFullName(options) {
|
||||
let n = this.getOne('n');
|
||||
if (n === undefined) {
|
||||
throw new Error('\'fn\' VCardProperty not present in card, cannot parse full name');
|
||||
}
|
||||
let fnString = '';
|
||||
// Position in n -> position in fn
|
||||
[3, 1, 2, 0, 4].forEach(pos => {
|
||||
let splitStr = n.value[pos];
|
||||
if (splitStr) {
|
||||
// comma separated values separated by spaces
|
||||
fnString += ' ' + splitStr.replace(',', ' ');
|
||||
}
|
||||
});
|
||||
fnString = fnString.trim();
|
||||
let fn = new VCardProperty('fn', fnString);
|
||||
if (options?.set) {
|
||||
if (options.append) {
|
||||
this.add(fn);
|
||||
} else {
|
||||
this.set(fn);
|
||||
}
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
152
dev/DAV/VCardProperty.js
Normal file
152
dev/DAV/VCardProperty.js
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* Inspired by https://github.com/mcpar-land/vcfer
|
||||
*/
|
||||
|
||||
const
|
||||
camelCase = str =>
|
||||
str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) =>
|
||||
index === 0 ? word.toLowerCase() : word.toUpperCase()
|
||||
).replace(/\s+/g, '');
|
||||
|
||||
export class VCardProperty {
|
||||
|
||||
/**
|
||||
* A class describing a single vCard property.
|
||||
* Will almost always be a member of a
|
||||
* {@link VCard}'s [props]{@link VCard.props} map.
|
||||
*
|
||||
* Accepts either 2-4 arguments, or 1 argument in jCard property format.
|
||||
* @param arg the field, or a jCard property
|
||||
* @param value
|
||||
* @param params
|
||||
* @param type
|
||||
*/
|
||||
constructor(arg, value, params, type = 'text')
|
||||
{
|
||||
this.field = '';
|
||||
|
||||
/**
|
||||
* the value of the property.
|
||||
* @example '(123) 456 7890'
|
||||
*/
|
||||
this.value = '';
|
||||
|
||||
/**
|
||||
* the type of the property value.
|
||||
* @example 'text'
|
||||
*/
|
||||
this.type = '';
|
||||
|
||||
/**
|
||||
* https://www.rfc-editor.org/rfc/rfc6350.html#section-5
|
||||
* An jCard parameters object.
|
||||
* @example
|
||||
* {
|
||||
* type: ['work', 'voice', 'pref'],
|
||||
* value: 'uri'
|
||||
* }
|
||||
*/
|
||||
this.params = {};
|
||||
|
||||
// Construct from arguments
|
||||
if (value !== undefined && typeof arg === 'string') {
|
||||
this.field = arg;
|
||||
this.value = value;
|
||||
this.params = params || {};
|
||||
this.type = type;
|
||||
}
|
||||
// construct from jcard
|
||||
else if (value === undefined && params === undefined && typeof arg === 'object') {
|
||||
this.parseFromJCardProperty(arg);
|
||||
}
|
||||
// invalid property
|
||||
else {
|
||||
throw new Error('invalid Property constructor');
|
||||
}
|
||||
}
|
||||
|
||||
parseFromJCardProperty(jCardProp)
|
||||
{
|
||||
jCardProp = JSON.parse(JSON.stringify(jCardProp));
|
||||
this.field = camelCase(jCardProp[0]);
|
||||
this.params = jCardProp[1];
|
||||
this.type = jCardProp[2];
|
||||
this.value = jCardProp[3];
|
||||
}
|
||||
|
||||
addParam(key, value)
|
||||
{
|
||||
if (Array.isArray(this.params[key])) {
|
||||
this.params[key].push(value);
|
||||
}
|
||||
else if (this.params[key] != null) {
|
||||
this.params[key] = [this.params[key], value];
|
||||
}
|
||||
else {
|
||||
this.params[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a copy of the Property's string value. */
|
||||
getValue()
|
||||
{
|
||||
return '' + this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://www.rfc-editor.org/rfc/rfc6350.html#section-5.3
|
||||
*/
|
||||
pref()
|
||||
{
|
||||
return this.params.pref || 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://www.rfc-editor.org/rfc/rfc6350.html#section-5.6
|
||||
*/
|
||||
tags()
|
||||
{
|
||||
return this.params.type || [];
|
||||
}
|
||||
|
||||
/** Returns `true` if all the following are true:
|
||||
* - the property's value contains charactes other than `;`
|
||||
* - the property has no parameters
|
||||
*/
|
||||
isEmpty()
|
||||
{
|
||||
return ((null == this.value || !/[^;]+/.test(this.value)) && !Object.keys(this.params).length);
|
||||
}
|
||||
|
||||
notEmpty()
|
||||
{
|
||||
return !this.isEmpty();
|
||||
}
|
||||
|
||||
/** Returns a readonly string copy of the property's field. */
|
||||
getField()
|
||||
{
|
||||
return '' + this.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stringified JSON
|
||||
*/
|
||||
toString()
|
||||
{
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON array in a [jCard property]{@link JCardProperty} format
|
||||
*/
|
||||
toJSON()
|
||||
{
|
||||
return [
|
||||
this.field,
|
||||
this.params,
|
||||
this.type || 'text',
|
||||
this.value
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue