mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-26 00:29:38 +01:00
Refactor email address formatting and use it in Et2EmailTag and Et2UrlEmailReadonly
This commit is contained in:
parent
0b20751602
commit
84fb37214a
166
api/js/etemplate/Et2Email/utils.ts
Normal file
166
api/js/etemplate/Et2Email/utils.ts
Normal file
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Email address UI utilities
|
||||
*
|
||||
* You probably want formatEmailAddress(address)
|
||||
*/
|
||||
|
||||
function _getEmailDisplayPreference()
|
||||
{
|
||||
const pref = window.egw.preference("emailTag", "mail") ?? "";
|
||||
switch(pref)
|
||||
{
|
||||
case "fullemail":
|
||||
return "full"
|
||||
default:
|
||||
case "onlyname":
|
||||
return "name";
|
||||
case "onlyemail":
|
||||
return "email";
|
||||
case "domain":
|
||||
return "domain";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const email_cache : { [address : string] : ContactInfo | false } = {};
|
||||
|
||||
let contact_request : Promise<void | boolean>;
|
||||
let contact_requests : { [key : string] : Array<Function>; } = {};
|
||||
|
||||
/**
|
||||
* Cache information about a contact
|
||||
*/
|
||||
export interface ContactInfo
|
||||
{
|
||||
id : number,
|
||||
n_fn : string,
|
||||
lname? : string,
|
||||
fname? : string,
|
||||
photo? : string,
|
||||
email? : string,
|
||||
email_home : string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contact information using an email address
|
||||
*
|
||||
* @param {string} email
|
||||
* @returns {Promise<boolean | ContactInfo>}
|
||||
*/
|
||||
export function checkContact(email : string) : Promise<false | ContactInfo>
|
||||
{
|
||||
if(typeof email_cache[email] !== "undefined")
|
||||
{
|
||||
return Promise.resolve(email_cache[email]);
|
||||
}
|
||||
if(!contact_request && window.egw)
|
||||
{
|
||||
contact_request = window.egw.jsonq('EGroupware\\Api\\Etemplate\\Widget\\Url::ajax_contact', [[]], null, null,
|
||||
(parameters) =>
|
||||
{
|
||||
for(const email in contact_requests)
|
||||
{
|
||||
parameters[0].push(email);
|
||||
}
|
||||
}).then((result) =>
|
||||
{
|
||||
for(const email in contact_requests)
|
||||
{
|
||||
email_cache[email] = result[email];
|
||||
contact_requests[email].forEach((resolve) =>
|
||||
{
|
||||
resolve(result[email]);
|
||||
});
|
||||
}
|
||||
contact_request = null;
|
||||
contact_requests = {};
|
||||
});
|
||||
}
|
||||
if(typeof contact_requests[email] === 'undefined')
|
||||
{
|
||||
contact_requests[email] = [];
|
||||
}
|
||||
return new Promise(resolve =>
|
||||
{
|
||||
contact_requests[email].push(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* if we have a "name <email>" value split it into name & email
|
||||
* @param email_string
|
||||
*
|
||||
* @return {name:string, email:string}
|
||||
*/
|
||||
export function splitEmail(email_string) : { name : string, email : string }
|
||||
{
|
||||
let split = {name: "", email: email_string};
|
||||
if(email_string && email_string.indexOf('<') !== -1)
|
||||
{
|
||||
const parts = email_string.split('<');
|
||||
if(parts[0])
|
||||
{
|
||||
split.email = parts[1].substring(0, parts[1].length - 1).trim();
|
||||
split.name = parts[0].trim();
|
||||
// remove quotes
|
||||
while((split.name[0] === '"' || split.name[0] === "'") && split.name[0] === split.name.substr(-1))
|
||||
{
|
||||
split.name = split.name.substring(1, split.name.length - 1);
|
||||
}
|
||||
}
|
||||
else // <email> --> email
|
||||
{
|
||||
split.email = parts[1].substring(0, email_string.length - 1);
|
||||
}
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an email address according to user preference
|
||||
*
|
||||
* @param address
|
||||
* @param {"full" | "email" | "name" | "domain"} emailDisplayFormat
|
||||
* @returns {any}
|
||||
*/
|
||||
export async function formatEmailAddress(address : string, emailDisplayFormat? : "full" | "email" | "name" | "domain") : Promise<string>
|
||||
{
|
||||
if(!address || !address.trim())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!emailDisplayFormat)
|
||||
{
|
||||
emailDisplayFormat = _getEmailDisplayPreference();
|
||||
}
|
||||
|
||||
const split = splitEmail(address);
|
||||
let content = address;
|
||||
let contact;
|
||||
if(!split.name && (contact = await checkContact(address)))
|
||||
{
|
||||
split.name = contact.n_fn;
|
||||
}
|
||||
|
||||
if(split.name)
|
||||
{
|
||||
switch(emailDisplayFormat)
|
||||
{
|
||||
case "full":
|
||||
content = split.name + " <" + split.email + ">";
|
||||
break;
|
||||
case "email":
|
||||
content = split.email;
|
||||
break;
|
||||
case "name":
|
||||
default:
|
||||
content = split.name;
|
||||
break;
|
||||
case "domain":
|
||||
content = split.name + " (" + split.email.split("@").pop() + ")";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
@ -6,11 +6,13 @@
|
||||
* @link https://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
*/
|
||||
import {css, html, nothing, PropertyValues, TemplateResult} from "lit";
|
||||
import {css, html, nothing, TemplateResult} from "lit";
|
||||
import {property} from "lit/decorators/property.js";
|
||||
import {classMap} from "lit/directives/class-map.js";
|
||||
import shoelace from "../../Styles/shoelace";
|
||||
import {Et2Tag} from "./Et2Tag";
|
||||
import {checkContact, ContactInfo, formatEmailAddress} from "../../Et2Email/utils";
|
||||
import {until} from "lit/directives/until.js";
|
||||
|
||||
/**
|
||||
* Display a single email address
|
||||
@ -23,8 +25,6 @@ import {Et2Tag} from "./Et2Tag";
|
||||
export class Et2EmailTag extends Et2Tag
|
||||
{
|
||||
|
||||
private static email_cache : { [address : string] : ContactInfo | false } = {};
|
||||
|
||||
static get styles()
|
||||
{
|
||||
return [
|
||||
@ -113,45 +113,6 @@ export class Et2EmailTag extends Et2Tag
|
||||
this.removeEventListener("mouseleave", this.handleMouseLeave);
|
||||
}
|
||||
|
||||
static contact_request : Promise<any>;
|
||||
static contact_requests : { [key: string]: Array<Function>; } = {};
|
||||
|
||||
public checkContact(email : string) : Promise<boolean | ContactInfo>
|
||||
{
|
||||
if(typeof Et2EmailTag.email_cache[email] !== "undefined")
|
||||
{
|
||||
return Promise.resolve(Et2EmailTag.email_cache[email]);
|
||||
}
|
||||
if (!Et2EmailTag.contact_request)
|
||||
{
|
||||
Et2EmailTag.contact_request = this.egw().jsonq('EGroupware\\Api\\Etemplate\\Widget\\Url::ajax_contact', [[]], null, null,
|
||||
(parameters) => {
|
||||
for(const email in Et2EmailTag.contact_requests)
|
||||
{
|
||||
parameters[0].push(email);
|
||||
}
|
||||
}).then((result) =>
|
||||
{
|
||||
for(const email in Et2EmailTag.contact_requests)
|
||||
{
|
||||
Et2EmailTag.email_cache[email] = result[email];
|
||||
Et2EmailTag.contact_requests[email].forEach((resolve) => {
|
||||
resolve(result[email]);
|
||||
});
|
||||
}
|
||||
Et2EmailTag.contact_request = null;
|
||||
Et2EmailTag.contact_requests = {};
|
||||
});
|
||||
}
|
||||
if (typeof Et2EmailTag.contact_requests[email] === 'undefined')
|
||||
{
|
||||
Et2EmailTag.contact_requests[email] = [];
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
Et2EmailTag.contact_requests[email].push(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseEnter(e : MouseEvent)
|
||||
{
|
||||
this.shadowRoot.querySelector(".tag").classList.add("contact_plus");
|
||||
@ -176,7 +137,7 @@ export class Et2EmailTag extends Et2Tag
|
||||
handleContactMouseDown(e : MouseEvent)
|
||||
{
|
||||
e.stopPropagation();
|
||||
this.checkContact(this.value).then((result) =>
|
||||
checkContact(this.value).then((result) =>
|
||||
{
|
||||
this.egw().open((<ContactInfo>result).id, 'addressbook', 'view', {
|
||||
title: (<ContactInfo>result).n_fn,
|
||||
@ -194,49 +155,13 @@ export class Et2EmailTag extends Et2Tag
|
||||
{
|
||||
return this.shadowRoot.querySelector(".tag__prefix");
|
||||
}
|
||||
|
||||
protected update(changedProperties : PropertyValues)
|
||||
{
|
||||
super.update(changedProperties);
|
||||
|
||||
if(changedProperties.has("value") && this.value)
|
||||
{
|
||||
// Send the request
|
||||
this.checkContact(this.value).then((result) =>
|
||||
{
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public _contentTemplate() : TemplateResult
|
||||
{
|
||||
const split = Et2EmailTag.splitEmail(this.value);
|
||||
|
||||
let content = this.value;
|
||||
if(split.name)
|
||||
{
|
||||
switch(this.emailDisplay)
|
||||
{
|
||||
case "full":
|
||||
content = split.name + " <" + split.email + ">";
|
||||
break;
|
||||
case "email":
|
||||
content = split.email;
|
||||
break;
|
||||
case "name":
|
||||
default:
|
||||
content = split.name;
|
||||
break;
|
||||
case "domain":
|
||||
content = split.name + " (" + split.email.split("@").pop() + ")";
|
||||
break;
|
||||
}
|
||||
}
|
||||
const content = formatEmailAddress(this.value, this.emailDisplay);
|
||||
|
||||
return html`
|
||||
<span part="content" class="tag__content" title="${this.value}">
|
||||
${content}
|
||||
${until(content, this.value)}
|
||||
</span>`;
|
||||
}
|
||||
|
||||
@ -245,17 +170,18 @@ export class Et2EmailTag extends Et2Tag
|
||||
let classes = {
|
||||
"tag__prefix": true,
|
||||
}
|
||||
let button_or_avatar;
|
||||
|
||||
// Show the lavatar for the contact
|
||||
if(this.value && Et2EmailTag.email_cache[this.value])
|
||||
let button_or_avatar = checkContact(this.value).then((option) =>
|
||||
{
|
||||
classes['tag__has_contact'] = true;
|
||||
// lavatar uses a size property, not a CSS variable
|
||||
let style = getComputedStyle(this);
|
||||
const option = Et2EmailTag.email_cache[this.value];
|
||||
let button_or_avatar;
|
||||
if(typeof option == "object")
|
||||
{
|
||||
// Show the lavatar for the contact
|
||||
classes['tag__has_contact'] = true;
|
||||
|
||||
button_or_avatar = html`
|
||||
// lavatar uses a size property, not a CSS variable
|
||||
let style = getComputedStyle(this);
|
||||
button_or_avatar = html`
|
||||
<et2-lavatar slot="prefix" exportparts="image" part="icon" tabindex="-1"
|
||||
@mousedown=${this.handleContactMouseDown}
|
||||
.size=${style.getPropertyValue("--icon-width")}
|
||||
@ -265,61 +191,32 @@ export class Et2EmailTag extends Et2Tag
|
||||
statustext="${this.egw().lang("Open existing contact") + ": " + option.n_fn}"
|
||||
>
|
||||
</et2-lavatar>`;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show a button to add as new contact
|
||||
classes['tag__has_plus'] = true;
|
||||
button_or_avatar = html`
|
||||
<et2-button-icon image="add" tabindex="-1" @click=${this.handleMouseClick}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show a button to add as new contact
|
||||
classes['tag__has_plus'] = true;
|
||||
button_or_avatar = html`
|
||||
<et2-button-icon image="add" tabindex="-1" @click=${this.handleMouseClick} .noSubmit=${true}
|
||||
label="${this.egw().lang("Add a new contact")}"
|
||||
statustext="${this.egw().lang("Add a new contact")}">
|
||||
</et2-button-icon>`;
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<span part="prefix" class=${classMap(classes)}>
|
||||
return html`<span part="prefix" class=${classMap(classes)}>
|
||||
<slot name="prefix">
|
||||
</slot>
|
||||
${button_or_avatar}
|
||||
</span>`;
|
||||
}
|
||||
</span>`;
|
||||
});
|
||||
|
||||
/**
|
||||
* if we have a "name <email>" value split it into name & email
|
||||
* @param email_string
|
||||
*
|
||||
* @return {name:string, email:string}
|
||||
*/
|
||||
public static splitEmail(email_string) : { name : string, email : string }
|
||||
{
|
||||
let split = {name: "", email: email_string};
|
||||
if(email_string && email_string.indexOf('<') !== -1)
|
||||
{
|
||||
const parts = email_string.split('<');
|
||||
if(parts[0])
|
||||
{
|
||||
split.email = parts[1].substring(0, parts[1].length - 1).trim();
|
||||
split.name = parts[0].trim();
|
||||
// remove quotes
|
||||
while((split.name[0] === '"' || split.name[0] === "'") && split.name[0] === split.name.substr(-1))
|
||||
{
|
||||
split.name = split.name.substring(1, split.name.length - 1);
|
||||
}
|
||||
}
|
||||
else // <email> --> email
|
||||
{
|
||||
split.email = parts[1].substring(0, email_string.length - 1);
|
||||
}
|
||||
}
|
||||
return split;
|
||||
return html`
|
||||
${until(button_or_avatar, html`
|
||||
<span part="prefix" class=${classMap(classes)}>
|
||||
<slot name="prefix"></slot>
|
||||
<sl-spinner></sl-spinner>
|
||||
</span>`)}`;
|
||||
}
|
||||
}
|
||||
|
||||
interface ContactInfo
|
||||
{
|
||||
id : number,
|
||||
n_fn : string,
|
||||
photo? : string
|
||||
}
|
||||
customElements.define("et2-email-tag", Et2EmailTag);
|
@ -11,68 +11,33 @@
|
||||
import {IsEmail} from "../Validators/IsEmail";
|
||||
import {Et2UrlEmail} from "./Et2UrlEmail";
|
||||
import {Et2UrlReadonly} from "./Et2UrlReadonly";
|
||||
import {property} from "lit/decorators/property.js";
|
||||
import {formatEmailAddress, splitEmail} from "../Et2Email/utils";
|
||||
|
||||
/**
|
||||
* @customElement et2-url-email_ro
|
||||
*/
|
||||
export class Et2UrlEmailReadonly extends Et2UrlReadonly
|
||||
{
|
||||
/** @type {any} */
|
||||
static get properties()
|
||||
{
|
||||
return {
|
||||
...super.properties,
|
||||
/**
|
||||
* Show full email address if true otherwise show only name and put full address as statustext/tooltip
|
||||
*/
|
||||
fullEmail: {
|
||||
type: Boolean,
|
||||
},
|
||||
/**
|
||||
* Show icon to add email as contact to addressbook
|
||||
* @ToDo
|
||||
*/
|
||||
contactPlus: {
|
||||
type: Boolean,
|
||||
},
|
||||
/**
|
||||
* set to true to always show just the email
|
||||
* Mutually exclusive with fullEmail!
|
||||
*/
|
||||
onlyEmail: {
|
||||
type: Boolean
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* What to display for the selected email addresses
|
||||
*
|
||||
* - full: "Mr Test User <test@example.com>
|
||||
* - name: "Mr Test User"
|
||||
* - domain: "Mr Test User (example.com)"
|
||||
* - email: "test@example.com"
|
||||
*
|
||||
* If name is unknown, we'll use the email instead.
|
||||
*/
|
||||
@property({type: String})
|
||||
emailDisplay : "full" | "email" | "name" | "domain";
|
||||
|
||||
set value(val : string)
|
||||
{
|
||||
this._value = val;
|
||||
// check if we have a "name <email>" value and only show name
|
||||
if (!this.fullEmail && val && val.indexOf('<') !== -1)
|
||||
{
|
||||
const parts = val.split('<');
|
||||
if (parts[0] && !this.onlyEmail)
|
||||
{
|
||||
super.statustext = parts[1].substring(0, parts[1].length-1);
|
||||
val = parts[0].trim();
|
||||
// remove quotes
|
||||
if ((val[0] === '"' || val[0] === "'" ) && val[0] === val.substr(-1))
|
||||
{
|
||||
val = val.substring(1, val.length-1);
|
||||
}
|
||||
}
|
||||
else // <email> --> email
|
||||
{
|
||||
super.statustext = '';
|
||||
val = parts[1].substring(0, val.length-1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
super.statustext = '';
|
||||
}
|
||||
super.value = val;
|
||||
const split = splitEmail(this._value);
|
||||
super.statustext = split.name ? split.email : "";
|
||||
formatEmailAddress(val, this.emailDisplay).then((value) => super.value = value);
|
||||
}
|
||||
|
||||
get value()
|
||||
|
Loading…
Reference in New Issue
Block a user