mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-02-01 19:09:30 +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
|
* @link https://www.egroupware.org
|
||||||
* @author Nathan Gray
|
* @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 {property} from "lit/decorators/property.js";
|
||||||
import {classMap} from "lit/directives/class-map.js";
|
import {classMap} from "lit/directives/class-map.js";
|
||||||
import shoelace from "../../Styles/shoelace";
|
import shoelace from "../../Styles/shoelace";
|
||||||
import {Et2Tag} from "./Et2Tag";
|
import {Et2Tag} from "./Et2Tag";
|
||||||
|
import {checkContact, ContactInfo, formatEmailAddress} from "../../Et2Email/utils";
|
||||||
|
import {until} from "lit/directives/until.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a single email address
|
* Display a single email address
|
||||||
@ -23,8 +25,6 @@ import {Et2Tag} from "./Et2Tag";
|
|||||||
export class Et2EmailTag extends Et2Tag
|
export class Et2EmailTag extends Et2Tag
|
||||||
{
|
{
|
||||||
|
|
||||||
private static email_cache : { [address : string] : ContactInfo | false } = {};
|
|
||||||
|
|
||||||
static get styles()
|
static get styles()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -113,45 +113,6 @@ export class Et2EmailTag extends Et2Tag
|
|||||||
this.removeEventListener("mouseleave", this.handleMouseLeave);
|
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)
|
handleMouseEnter(e : MouseEvent)
|
||||||
{
|
{
|
||||||
this.shadowRoot.querySelector(".tag").classList.add("contact_plus");
|
this.shadowRoot.querySelector(".tag").classList.add("contact_plus");
|
||||||
@ -176,7 +137,7 @@ export class Et2EmailTag extends Et2Tag
|
|||||||
handleContactMouseDown(e : MouseEvent)
|
handleContactMouseDown(e : MouseEvent)
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.checkContact(this.value).then((result) =>
|
checkContact(this.value).then((result) =>
|
||||||
{
|
{
|
||||||
this.egw().open((<ContactInfo>result).id, 'addressbook', 'view', {
|
this.egw().open((<ContactInfo>result).id, 'addressbook', 'view', {
|
||||||
title: (<ContactInfo>result).n_fn,
|
title: (<ContactInfo>result).n_fn,
|
||||||
@ -194,49 +155,13 @@ export class Et2EmailTag extends Et2Tag
|
|||||||
{
|
{
|
||||||
return this.shadowRoot.querySelector(".tag__prefix");
|
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
|
public _contentTemplate() : TemplateResult
|
||||||
{
|
{
|
||||||
const split = Et2EmailTag.splitEmail(this.value);
|
const content = formatEmailAddress(this.value, this.emailDisplay);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<span part="content" class="tag__content" title="${this.value}">
|
<span part="content" class="tag__content" title="${this.value}">
|
||||||
${content}
|
${until(content, this.value)}
|
||||||
</span>`;
|
</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,16 +170,17 @@ export class Et2EmailTag extends Et2Tag
|
|||||||
let classes = {
|
let classes = {
|
||||||
"tag__prefix": true,
|
"tag__prefix": true,
|
||||||
}
|
}
|
||||||
let button_or_avatar;
|
|
||||||
|
|
||||||
// Show the lavatar for the contact
|
let button_or_avatar = checkContact(this.value).then((option) =>
|
||||||
if(this.value && Et2EmailTag.email_cache[this.value])
|
|
||||||
{
|
{
|
||||||
|
let button_or_avatar;
|
||||||
|
if(typeof option == "object")
|
||||||
|
{
|
||||||
|
// Show the lavatar for the contact
|
||||||
classes['tag__has_contact'] = true;
|
classes['tag__has_contact'] = true;
|
||||||
|
|
||||||
// lavatar uses a size property, not a CSS variable
|
// lavatar uses a size property, not a CSS variable
|
||||||
let style = getComputedStyle(this);
|
let style = getComputedStyle(this);
|
||||||
const option = Et2EmailTag.email_cache[this.value];
|
|
||||||
|
|
||||||
button_or_avatar = html`
|
button_or_avatar = html`
|
||||||
<et2-lavatar slot="prefix" exportparts="image" part="icon" tabindex="-1"
|
<et2-lavatar slot="prefix" exportparts="image" part="icon" tabindex="-1"
|
||||||
@mousedown=${this.handleContactMouseDown}
|
@mousedown=${this.handleContactMouseDown}
|
||||||
@ -271,55 +197,26 @@ export class Et2EmailTag extends Et2Tag
|
|||||||
// Show a button to add as new contact
|
// Show a button to add as new contact
|
||||||
classes['tag__has_plus'] = true;
|
classes['tag__has_plus'] = true;
|
||||||
button_or_avatar = html`
|
button_or_avatar = html`
|
||||||
<et2-button-icon image="add" tabindex="-1" @click=${this.handleMouseClick}
|
<et2-button-icon image="add" tabindex="-1" @click=${this.handleMouseClick} .noSubmit=${true}
|
||||||
label="${this.egw().lang("Add a new contact")}"
|
label="${this.egw().lang("Add a new contact")}"
|
||||||
statustext="${this.egw().lang("Add a new contact")}">
|
statustext="${this.egw().lang("Add a new contact")}">
|
||||||
</et2-button-icon>`;
|
</et2-button-icon>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`<span part="prefix" class=${classMap(classes)}>
|
||||||
<span part="prefix" class=${classMap(classes)}>
|
|
||||||
<slot name="prefix">
|
<slot name="prefix">
|
||||||
</slot>
|
</slot>
|
||||||
${button_or_avatar}
|
${button_or_avatar}
|
||||||
</span>`;
|
</span>`;
|
||||||
}
|
});
|
||||||
|
|
||||||
/**
|
return html`
|
||||||
* if we have a "name <email>" value split it into name & email
|
${until(button_or_avatar, html`
|
||||||
* @param email_string
|
<span part="prefix" class=${classMap(classes)}>
|
||||||
*
|
<slot name="prefix"></slot>
|
||||||
* @return {name:string, email:string}
|
<sl-spinner></sl-spinner>
|
||||||
*/
|
</span>`)}`;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContactInfo
|
|
||||||
{
|
|
||||||
id : number,
|
|
||||||
n_fn : string,
|
|
||||||
photo? : string
|
|
||||||
}
|
|
||||||
customElements.define("et2-email-tag", Et2EmailTag);
|
customElements.define("et2-email-tag", Et2EmailTag);
|
@ -11,68 +11,33 @@
|
|||||||
import {IsEmail} from "../Validators/IsEmail";
|
import {IsEmail} from "../Validators/IsEmail";
|
||||||
import {Et2UrlEmail} from "./Et2UrlEmail";
|
import {Et2UrlEmail} from "./Et2UrlEmail";
|
||||||
import {Et2UrlReadonly} from "./Et2UrlReadonly";
|
import {Et2UrlReadonly} from "./Et2UrlReadonly";
|
||||||
|
import {property} from "lit/decorators/property.js";
|
||||||
|
import {formatEmailAddress, splitEmail} from "../Et2Email/utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @customElement et2-url-email_ro
|
* @customElement et2-url-email_ro
|
||||||
*/
|
*/
|
||||||
export class Et2UrlEmailReadonly extends Et2UrlReadonly
|
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
|
* 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.
|
||||||
*/
|
*/
|
||||||
fullEmail: {
|
@property({type: String})
|
||||||
type: Boolean,
|
emailDisplay : "full" | "email" | "name" | "domain";
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
set value(val : string)
|
set value(val : string)
|
||||||
{
|
{
|
||||||
this._value = val;
|
this._value = val;
|
||||||
// check if we have a "name <email>" value and only show name
|
const split = splitEmail(this._value);
|
||||||
if (!this.fullEmail && val && val.indexOf('<') !== -1)
|
super.statustext = split.name ? split.email : "";
|
||||||
{
|
formatEmailAddress(val, this.emailDisplay).then((value) => super.value = value);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get value()
|
get value()
|
||||||
|
Loading…
Reference in New Issue
Block a user