Improved new AddressBook system

This commit is contained in:
the-djmaze 2022-09-06 14:26:07 +02:00
parent 497193750b
commit c9ad0ef170
53 changed files with 589 additions and 459 deletions

View file

@ -79,12 +79,12 @@ export class JCard {
* @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
* @param type the type for the VCardProperty object
*/
set(arg, value, params, group)
set(arg, value, params, type)
{
if (typeof arg === 'string') {
arg = new VCardProperty(String(arg), value, params, group);
arg = new VCardProperty(String(arg), value, params, type);
}
if (!(arg instanceof VCardProperty)) {
throw new Error('invalid argument of VCard.set(), expects string arguments or a VCardProperty');
@ -94,11 +94,11 @@ export class JCard {
return arg;
}
add(arg, value, params, group)
add(arg, value, params, type)
{
// string arguments
if (typeof arg === 'string') {
arg = new VCardProperty(String(arg), value, params, group);
arg = new VCardProperty(String(arg), value, params, type);
}
if (!(arg instanceof VCardProperty)) {
throw new Error('invalid argument of VCard.add(), expects string arguments or a VCardProperty');

View file

@ -108,12 +108,16 @@ export class ContactModel extends AbstractModel {
middleName: '', // MiddleName
namePrefix: '', // NamePrefix
nameSuffix: '', // NameSuffix
nickname: null
nickname: null,
encryptpref: '',
signpref: ''
});
// this.email = koArrayWithDestroy();
this.email = ko.observableArray();
this.tel = ko.observableArray();
this.url = ko.observableArray();
this.adr = ko.observableArray();
this.addComputables({
hasValidName: () => !!(this.givenName() || this.surName()),
@ -156,7 +160,7 @@ export class ContactModel extends AbstractModel {
/**
* @static
* @param {FetchJsonContact} json
* @param {jCard} json
* @returns {?ContactModel}
*/
static reviveFromJson(json) {
@ -181,6 +185,26 @@ export class ContactModel extends AbstractModel {
});
});
props = jCard.get('adr');
props && props.forEach(prop => {
contact.adr.push({
street: ko.observable(prop.value[2]),
street_ext: ko.observable(prop.value[1]),
locality: ko.observable(prop.value[3]),
region: ko.observable(prop.value[4]),
postcode: ko.observable(prop.value[5]),
pobox: ko.observable(prop.value[0]),
country: ko.observable(prop.value[6]),
preferred: ko.observable(prop.params.pref),
type: ko.observable(prop.params.type) // HOME | WORK
});
});
props = jCard.getOne('x-Crypto');
contact.signpref(props?.params.signpref || 'Ask');
contact.encryptpref(props?.params.encryptpref || 'Ask');
// contact.encryptpref(props?.params.allowed || 'PGP/INLINE,PGP/MIME,S/MIME,S/MIMEOpaque');
contact.jCard = jCard;
}
return contact;
@ -249,6 +273,12 @@ export class ContactModel extends AbstractModel {
values.forEach(value => value && jCard.add(field, value));
});
jCard.set('x-Crypto', '', {
allowed: 'PGP/INLINE,PGP/MIME,S/MIME,S/MIMEOpaque',
signpref: this.signpref(),
encryptpref: this.encryptpref()
}, 'x-crypto');
// Done by server
// jCard.set('rev', '2022-05-21T10:59:52Z')

View file

@ -1,86 +0,0 @@
import { pInt, pString } from 'Common/Utils';
import { i18n } from 'Common/Translator';
import { AbstractModel } from 'Knoin/AbstractModel';
const trim = text => null == text ? "" : (text + "").trim();
/**
* @enum {number}
*/
export const ContactPropertyType = {
Unknown: 0,
FullName: 10,
FirstName: 15,
LastName: 16,
MiddleName: 17,
Nick: 18,
NamePrefix: 20,
NameSuffix: 21,
Email: 30,
Phone: 31,
Web: 32,
Birthday: 40,
Facebook: 90,
Skype: 91,
GitHub: 92,
Note: 110,
Custom: 250
};
export class ContactPropertyModel extends AbstractModel {
/**
* @param {number=} type = Enums.ContactPropertyType.Unknown
* @param {string=} typeStr = ''
* @param {string=} value = ''
* @param {boolean=} focused = false
* @param {string=} placeholder = ''
*/
constructor(type = ContactPropertyType.Unknown, typeStr = '', value = '', focused = false, placeholder = '') {
super();
this.addObservables({
type: pInt(type),
typeStr: pString(typeStr),
focused: !!focused,
value: pString(value),
placeholder: placeholder
});
this.addComputables({
placeholderValue: () => {
const v = this.placeholder();
return v ? i18n(v) : '';
},
largeValue: () => ContactPropertyType.Note === this.type()
});
}
isType(type) {
return this.type && type === this.type();
}
isValid() {
return this.value && !!trim(this.value());
}
toJSON() {
return {
type: this.type(),
typeStr: this.typeStr(),
value: this.value()
};
}
// static reviveFromJson(json) {}
}

View file

@ -99,9 +99,10 @@ tr:hover .drag-handle {
.tabs {
display: grid;
grid-auto-columns: minmax(0, 1fr);
}
.tabs input[type="radio"] {
.tabs > input[type="radio"] {
position: absolute;
top: 0;
left: -9999px;
@ -109,7 +110,7 @@ tr:hover .drag-handle {
}
// Actual tabs
.tabs label {
.tabs > label {
border: 1px solid transparent;
border-radius: 4px 4px 0 0;
cursor: pointer;
@ -124,9 +125,9 @@ tr:hover .drag-handle {
opacity: 0.8;
}
}
.tabs [id^="tab"]:checked + label {
.tabs > [id^="tab"]:checked + label {
background-color: @white;
border-color: #ddd #ddd transparent #ddd;
border-color: rgba(128,128,128,.5) rgba(128,128,128,.5) transparent rgba(128,128,128,.5);
opacity: 1;
z-index: 1;
}
@ -140,6 +141,6 @@ tr:hover .drag-handle {
visibility: hidden;
}
.tabs [id^="tab"]:checked + label + .tab-content {
.tabs > [id^="tab"]:checked + label + .tab-content {
visibility: visible;
}

View file

@ -11,7 +11,7 @@
max-height: 700px;
.control-group {
label {
label.iconsize24 {
padding-top: 0;
width: 50px;
}
@ -135,12 +135,12 @@
.b-view-content-toolbar {
padding: 7px;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: @contacts-popup-left-width;
height: 30px;
text-align: right;
border-bottom: 1px solid rgba(128,128,128,0.4);
border-top: 1px solid rgba(128,128,128,0.4);
.button-save-contact {
&.dirty:enabled {
@ -148,12 +148,17 @@
font-weight: bold;
}
}
.dropdown-menu.right-edge {
top: auto;
bottom: 100%;
}
}
.b-view-content {
position: absolute;
top: 45px;
bottom: 0;
top: 0;
bottom: 45px;
left: @contacts-popup-left-width;
right: 0;
overflow: auto;
@ -176,19 +181,10 @@
color: #999;
}
.top-part {
padding-top: 20px;
}
.property-line {
margin-bottom: 5px;
}
.top-row {
padding: 10px 0;
height: 30px;
}
input, textarea {
box-shadow: none;
border-color: #fff;
@ -206,43 +202,28 @@
border-color: #999;
}
}
}
.hasError {
input {
color: #ee5f5b;
border-color: #ee5f5b;
}
.read-only-sign {
display: none;
position: absolute;
top: 20px;
right: 40px;
}
.read-only {
span, .e-read-only-sign {
display: inline-block;
}
.e-save-trigger {
position: absolute;
top: 25px;
left: 10px;
}
.e-read-only-sign {
input, textarea {
display: none;
position: absolute;
top: 20px;
right: 40px;
}
}
.e-share-sign {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
&.read-only {
span, .e-read-only-sign {
display: inline-block;
}
input, textarea, .e-share-sign {
display: none;
}
}
.tab-content {
grid-column-end: 3;
padding-top: 20px;
}
}

View file

@ -6,6 +6,6 @@
}
.tab-content {
grid-column-end: 6;
grid-column-end: 5;
}
}

View file

@ -107,7 +107,7 @@ export class ContactsPopupView extends AbstractViewPopup {
}
newContact() {
this.populateViewContact(null);
this.populateViewContact();
this.selectorContact(null);
}
@ -252,11 +252,7 @@ export class ContactsPopupView extends AbstractViewPopup {
* @param {?ContactModel} contact
*/
populateViewContact(contact) {
if (!contact) {
contact = new ContactModel;
}
this.contact(contact);
this.contact(contact || new ContactModel);
this.hasChanges(false);
}

View file

@ -1,8 +1,6 @@
<?php
use RainLoop\Providers\AddressBook\Classes\Contact;
use RainLoop\Providers\AddressBook\Classes\Property;
use RainLoop\Providers\AddressBook\Enumerations\PropertyType;
class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInterface
{
@ -297,6 +295,7 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
$oParams->sSearch = 'from='.$sSearch;
}
$oParams->sSort = 'FROM';
$oParams->bUseSortIfSupported = !!\RainLoop\Api::Actions()->Config()->Get('labs', 'use_imap_sort', true);
// $oParams->iPrevUidNext = $this->GetActionParam('UidNext', 0);
// $oParams->bUseThreads = false;

View file

@ -4,11 +4,11 @@ class KolabPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'Kolab',
VERSION = '1.1',
RELEASE = '2022-05-20',
VERSION = '2.0',
RELEASE = '2022-09-06',
CATEGORY = 'Contacts',
DESCRIPTION = 'Use an Address Book of Kolab.',
REQUIRED = '2.16.2';
REQUIRED = '2.18.0';
public function Init() : void
{

View file

@ -2,8 +2,6 @@
namespace RainLoop\Providers;
use RainLoop\Providers\AddressBook\Enumerations\PropertyType as PropertyType;
class AddressBook extends AbstractProvider
{
/**

View file

@ -2,11 +2,6 @@
namespace RainLoop\Providers\AddressBook\Classes;
use
RainLoop\Providers\AddressBook\Enumerations\PropertyType,
RainLoop\Providers\AddressBook\Classes\Property
;
class Contact implements \JsonSerializable
{
/**

View file

@ -26,16 +26,6 @@ class Property implements \JsonSerializable
*/
public $Value;
/**
* @var string
*/
public $ValueLower = '';
/**
* @var string
*/
public $ValueCustom = '';
/**
* @var int
*/
@ -48,101 +38,9 @@ class Property implements \JsonSerializable
$this->TypeStr = $sTypeStr;
}
public function IsName() : bool
{
return \in_array($this->Type, array(PropertyType::FULLNAME, PropertyType::FIRST_NAME,
PropertyType::LAST_NAME, PropertyType::MIDDLE_NAME, PropertyType::NICK_NAME));
}
public function IsEmail() : bool
{
return PropertyType::EMAIl === $this->Type;
}
public function IsPhone() : bool
{
return PropertyType::PHONE === $this->Type;
}
public function IsWeb() : bool
{
return PropertyType::WEB_PAGE === $this->Type;
}
public function IsValueForLower() : bool
{
return $this->IsEmail() || $this->IsName() || $this->IsWeb();
}
public function TypesAsArray() : array
{
$aResult = array();
if (!empty($this->TypeStr))
{
$sTypeStr = \preg_replace('/[\s]+/', '', $this->TypeStr);
$aResult = \explode(',', $sTypeStr);
}
return $aResult;
}
public function TypesUpperAsArray() : array
{
return \array_map('strtoupper', $this->TypesAsArray());
}
public function UpdateDependentValues() : void
{
$this->Value = \trim($this->Value);
$this->ValueCustom = \trim($this->ValueCustom);
$this->TypeStr = \trim($this->TypeStr);
$this->ValueLower = '';
if (\strlen($this->Value))
{
// lower
if ($this->IsEmail())
{
$this->Value = \MailSo\Base\Utils::StrMailDomainToLower($this->Value);
}
if ($this->IsName())
{
$this->Value = \MailSo\Base\Utils::StripSpaces($this->Value);
}
// lower value for searching
if ($this->IsValueForLower())
{
$this->ValueLower = (string) \mb_strtolower($this->Value, 'UTF-8');
}
// phone value for searching
if ($this->IsPhone())
{
$sPhone = \trim($this->Value);
$sPhone = \preg_replace('/^[+]+/', '', $sPhone);
$sPhone = \preg_replace('/[^\d]/', '', $sPhone);
$this->ValueCustom = \trim($sPhone);
}
}
}
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
// Simple hack
if ($this && $this->IsWeb())
{
$this->Value = \preg_replace('#(https?)\\\://#i', '$1://', $this->Value);
}
return array(
'@Object' => 'Object/ContactProperty',
'id' => $this->IdProperty,
'type' => $this->Type,
'typeStr' => $this->TypeStr,
'value' => \MailSo\Base\Utils::Utf8Clear($this->Value)
);
throw new \Exception('Obsolete ' . __CLASS__ . '::jsonSerialize()');
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace RainLoop\Providers\AddressBook;
use
RainLoop\Providers\AddressBook\Enumerations\PropertyType,
RainLoop\Providers\AddressBook\Classes\Property
;
class Legacy
{
/**
* @param mixed $oProp
*/
private static function yieldPropertyHelper($oArrayProp, int $iType) : iterable
{
$aTmp = [];
foreach ($oArrayProp as $oProp) {
$sValue = \trim($oProp->getValue());
if (\strlen($sValue)) {
$oTypes = $oProp['TYPE'];
$aTypes = $oTypes ? $oTypes->getParts() : array();
$pref = 100;
if (0 < $oProp['PREF']) {
$pref = (int) $oProp['PREF'];
}
$aTmp[\substr(1000+$pref,-3) . $sValue] = new Property($iType, $sValue, \implode(',', $aTypes));
}
}
\ksort($aTmp);
foreach ($aTmp as $oProp) {
yield $oProp;
}
}
/**
* @param mixed $oProp
*/
private static function getPropertyValueHelper($oProp, bool $bOldVersion) : string
{
$sValue = \trim($oProp);
if ($bOldVersion && !isset($oProp->parameters['CHARSET'])) {
if (\strlen($sValue)) {
$sEncValue = \utf8_encode($sValue);
if (\strlen($sEncValue)) {
$sValue = $sEncValue;
}
}
}
return \MailSo\Base\Utils::Utf8Clear($sValue);
}
public static function VCardToProperties(\Sabre\VObject\Component\VCard $oVCard) : iterable
{
yield new Property(PropertyType::JCARD, \json_encode($oVCard));
$bOldVersion = !empty($oVCard->VERSION) && \in_array((string) $oVCard->VERSION, array('2.1', '2.0', '1.0'));
if (isset($oVCard->FN) && '' !== \trim($oVCard->FN)) {
$sValue = static::getPropertyValueHelper($oVCard->FN, $bOldVersion);
yield new Property(PropertyType::FULLNAME, $sValue);
}
if (isset($oVCard->N)) {
$aNames = $oVCard->N->getParts();
foreach ($aNames as $iIndex => $sValue) {
$sValue = \trim($sValue);
if ($bOldVersion && !isset($oVCard->N->parameters['CHARSET'])) {
if (\strlen($sValue)) {
$sEncValue = \utf8_encode($sValue);
if (\strlen($sEncValue)) {
$sValue = $sEncValue;
}
}
}
$sValue = \MailSo\Base\Utils::Utf8Clear($sValue);
if ($sValue) {
switch ($iIndex) {
case 0:
yield new Property(PropertyType::LAST_NAME, $sValue);
break;
case 1:
yield new Property(PropertyType::FIRST_NAME, $sValue);
break;
case 2:
yield new Property(PropertyType::MIDDLE_NAME, $sValue);
break;
case 3:
yield new Property(PropertyType::NAME_PREFIX, $sValue);
break;
case 4:
yield new Property(PropertyType::NAME_SUFFIX, $sValue);
break;
}
}
}
}
if (isset($oVCard->EMAIL)) {
yield from static::yieldPropertyHelper($oVCard->EMAIL, PropertyType::EMAIl);
}
if (isset($oVCard->URL)) {
yield from static::yieldPropertyHelper($oVCard->URL, PropertyType::WEB_PAGE);
}
if (isset($oVCard->TEL)) {
yield from static::yieldPropertyHelper($oVCard->TEL, PropertyType::PHONE);
}
}
}

View file

@ -2,7 +2,11 @@
namespace RainLoop\Providers\AddressBook;
use RainLoop\Providers\AddressBook\Enumerations\PropertyType;
use
Sabre\VObject\Component\VCard,
RainLoop\Providers\AddressBook\Classes\Contact,
RainLoop\Providers\AddressBook\Enumerations\PropertyType
;
class PdoAddressBook
extends \RainLoop\Common\PdoAbstract
@ -265,7 +269,7 @@ class PdoAddressBook
}
}
if ($oVCard instanceof \Sabre\VObject\Component\VCard) {
if ($oVCard instanceof VCard) {
$oVCard->UID = $aData['uid'];
$oContact = null;
@ -273,7 +277,7 @@ class PdoAddressBook
$oContact = $this->GetContactByID($mExistingContactID);
}
if (!$oContact) {
$oContact = new Classes\Contact();
$oContact = new Contact();
}
$oContact->setVCard($oVCard);
@ -325,7 +329,7 @@ class PdoAddressBook
return true;
}
public function ContactSave(Classes\Contact $oContact) : bool
public function ContactSave(Contact $oContact) : bool
{
if (1 > $this->iUserID) {
return false;
@ -390,9 +394,9 @@ class PdoAddressBook
if (0 < $iIdContact) {
$aParams = array();
foreach (Utils::VCardToProperties($oContact->vCard) as /* @var $oProp Classes\Property */ $oProp) {
foreach (Legacy::VCardToProperties($oContact->vCard) as /* @var $oProp Classes\Property */ $oProp) {
$iFreq = $oProp->Frec;
if ($oProp->IsEmail() && isset($aFreq[$oProp->Value])) {
if (PropertyType::EMAIl === $oProp->Type && isset($aFreq[$oProp->Value])) {
$iFreq = $aFreq[$oProp->Value];
}
$aParams[] = array(
@ -401,8 +405,8 @@ class PdoAddressBook
':prop_type' => array($oProp->Type, \PDO::PARAM_INT),
':prop_type_str' => array($oProp->TypeStr, \PDO::PARAM_STR),
':prop_value' => array($oProp->Value, \PDO::PARAM_STR),
':prop_value_lower' => array($oProp->ValueLower, \PDO::PARAM_STR),
':prop_value_custom' => array($oProp->ValueCustom, \PDO::PARAM_STR),
':prop_value_lower' => array(\mb_strtolower($oProp->Value, 'UTF-8'), \PDO::PARAM_STR),
':prop_value_custom' => array('', \PDO::PARAM_STR),
':prop_frec' => array($iFreq, \PDO::PARAM_INT),
);
}
@ -475,7 +479,7 @@ class PdoAddressBook
foreach ($aFetch as $aItem) {
$iIdContact = $aItem && isset($aItem['id_contact']) ? (int) $aItem['id_contact'] : 0;
if (0 < $iIdContact) {
$oContact = new Classes\Contact();
$oContact = new Contact();
$oContact->id = (string) $iIdContact;
$oContact->IdContactStr = (string) $aItem['id_contact_str'];
// $oContact->Display = (string) $aItem['display'];
@ -523,7 +527,7 @@ class PdoAddressBook
$iPrevId = $iId;
}
if (!isset($aVCards[$iId])) {
$aVCards[$iId] = new \Sabre\VObject\Component\VCard;
$aVCards[$iId] = new VCard;
}
$oVCard = $aVCards[$iId];
$oVCard->VERSION = '4.0';
@ -684,7 +688,7 @@ class PdoAddressBook
/**
* @param mixed $mID
*/
public function GetContactByID($mID, bool $bIsStrID = false) : ?Classes\Contact
public function GetContactByID($mID, bool $bIsStrID = false) : ?Contact
{
$mID = \trim($mID);
@ -962,16 +966,12 @@ class PdoAddressBook
if (\count($aEmailsToCreate)) {
foreach ($aEmailsToCreate as $oEmail) {
$oContact = new Classes\Contact();
$oVCard = new VCard;
$bValid = false;
if ('' !== \trim($oEmail->GetEmail())) {
$oPropEmail = new Classes\Property();
$oPropEmail->Type = Enumerations\PropertyType::EMAIl;
$oPropEmail->Value = \trim($oEmail->GetEmail(true));
$oContact->Properties[] = $oPropEmail;
$oVCard->add('EMAIL', \trim($oEmail->GetEmail(true)));
$bValid = true;
}
if ('' !== \trim($oEmail->GetDisplayName())) {
$sFirst = $sLast = '';
$sFullName = $oEmail->GetDisplayName();
@ -982,25 +982,14 @@ class PdoAddressBook
} else {
$sFirst = $sFullName;
}
if (\strlen($sFirst)) {
$oPropName = new Classes\Property();
$oPropName->Type = Enumerations\PropertyType::FIRST_NAME;
$oPropName->Value = \trim($sFirst);
$oContact->Properties[] = $oPropName;
}
if (\strlen($sLast)) {
$oPropName = new Classes\Property();
$oPropName->Type = Enumerations\PropertyType::LAST_NAME;
$oPropName->Value = \trim($sLast);
$oContact->Properties[] = $oPropName;
if (\strlen($sFirst) || \strlen($sLast)) {
$oVCard->N = array($sLast, $sFirst, '', '', '');
$bValid = true;
}
}
if (\count($oContact->Properties)) {
if ($bValid) {
$oContact = new Contact();
$oContact->setVCard($oVCard);
$this->ContactSave($oContact);
}
}

View file

@ -2,114 +2,8 @@
namespace RainLoop\Providers\AddressBook;
use
RainLoop\Providers\AddressBook\Enumerations\PropertyType,
RainLoop\Providers\AddressBook\Classes\Property
;
class Utils
{
/**
* @param mixed $oProp
*/
private static function yieldPropertyHelper($oArrayProp, int $iType) : iterable
{
$aTmp = [];
foreach ($oArrayProp as $oProp) {
$sValue = \trim($oProp->getValue());
if (\strlen($sValue)) {
$oTypes = $oProp['TYPE'];
$aTypes = $oTypes ? $oTypes->getParts() : array();
$pref = 100;
if (0 < $oProp['PREF']) {
$pref = (int) $oProp['PREF'];
}
$aTmp[\substr(1000+$pref,-3) . $sValue] = new Property($iType, $sValue, \implode(',', $aTypes));
}
}
\ksort($aTmp);
foreach ($aTmp as $oProp) {
yield $oProp;
}
}
/**
* @param mixed $oProp
*/
private static function getPropertyValueHelper($oProp, bool $bOldVersion) : string
{
$sValue = \trim($oProp);
if ($bOldVersion && !isset($oProp->parameters['CHARSET'])) {
if (\strlen($sValue)) {
$sEncValue = \utf8_encode($sValue);
if (\strlen($sEncValue)) {
$sValue = $sEncValue;
}
}
}
return \MailSo\Base\Utils::Utf8Clear($sValue);
}
public static function VCardToProperties(\Sabre\VObject\Component\VCard $oVCard) : iterable
{
yield new Property(PropertyType::JCARD, \json_encode($oVCard));
$bOldVersion = !empty($oVCard->VERSION) && \in_array((string) $oVCard->VERSION, array('2.1', '2.0', '1.0'));
if (isset($oVCard->FN) && '' !== \trim($oVCard->FN)) {
$sValue = static::getPropertyValueHelper($oVCard->FN, $bOldVersion);
yield new Property(PropertyType::FULLNAME, $sValue);
}
if (isset($oVCard->N)) {
$aNames = $oVCard->N->getParts();
foreach ($aNames as $iIndex => $sValue) {
$sValue = \trim($sValue);
if ($bOldVersion && !isset($oVCard->N->parameters['CHARSET'])) {
if (\strlen($sValue)) {
$sEncValue = \utf8_encode($sValue);
if (\strlen($sEncValue)) {
$sValue = $sEncValue;
}
}
}
$sValue = \MailSo\Base\Utils::Utf8Clear($sValue);
if ($sValue) {
switch ($iIndex) {
case 0:
yield new Property(PropertyType::LAST_NAME, $sValue);
break;
case 1:
yield new Property(PropertyType::FIRST_NAME, $sValue);
break;
case 2:
yield new Property(PropertyType::MIDDLE_NAME, $sValue);
break;
case 3:
yield new Property(PropertyType::NAME_PREFIX, $sValue);
break;
case 4:
yield new Property(PropertyType::NAME_SUFFIX, $sValue);
break;
}
}
}
}
if (isset($oVCard->EMAIL)) {
yield from static::yieldPropertyHelper($oVCard->EMAIL, PropertyType::EMAIl);
}
if (isset($oVCard->URL)) {
yield from static::yieldPropertyHelper($oVCard->URL, PropertyType::WEB_PAGE);
}
if (isset($oVCard->TEL)) {
yield from static::yieldPropertyHelper($oVCard->TEL, PropertyType::PHONE);
}
}
private static $aMap = array(
'title' => 'FN',
'name' => 'FN',
@ -293,4 +187,11 @@ class Utils
}
}
}
/*
public static function QrCode(string $sVcfData) : string
{
MECARD:N:djmaze;ORG:SnappyMail;TEL:+31012345678;URL:https\://snappymail.eu;EMAIL:info@snappymail.eu;ADR:address line;;
VCARD
}
*/
}

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "يتم البحث..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "إضافة جهة إتصال",
"BUTTON_CREATE_CONTACT": "إنشاء",
"BUTTON_UPDATE_CONTACT": "تحديث",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Търсене..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Добави контакт",
"BUTTON_CREATE_CONTACT": "Създай",
"BUTTON_UPDATE_CONTACT": "Обнови",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Hledám..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Přidat kontakt",
"BUTTON_CREATE_CONTACT": "Vytvořit",
"BUTTON_UPDATE_CONTACT": "Aktualizovat",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Søger..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Tilføj kontakt",
"BUTTON_CREATE_CONTACT": "Ny kontakt",
"BUTTON_UPDATE_CONTACT": "Opdater kontakt",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Suche läuft..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Kontakt hinzufügen",
"BUTTON_CREATE_CONTACT": "Kontakt anlegen",
"BUTTON_UPDATE_CONTACT": "Kontakt aktualisieren",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Αναζήτηση..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Προσθήκη επαφής",
"BUTTON_CREATE_CONTACT": "Δημιουργία",
"BUTTON_UPDATE_CONTACT": "Ενημέρωση",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Searching..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Add Contact",
"BUTTON_CREATE_CONTACT": "Create",
"BUTTON_UPDATE_CONTACT": "Update",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Searching..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Add Contact",
"BUTTON_CREATE_CONTACT": "Create",
"BUTTON_UPDATE_CONTACT": "Update",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Buscando..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Añadir Contacto",
"BUTTON_CREATE_CONTACT": "Crear",
"BUTTON_UPDATE_CONTACT": "Actualizar",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Otsin..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Lisa kontakt",
"BUTTON_CREATE_CONTACT": "Lisa",
"BUTTON_UPDATE_CONTACT": "Salvesta",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "در حال جستجو..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "اضافه‌کردن تماس",
"BUTTON_CREATE_CONTACT": "ایجاد",
"BUTTON_UPDATE_CONTACT": "بروزرسانی",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Hakee..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Lisää yhteystieto",
"BUTTON_CREATE_CONTACT": "Luo",
"BUTTON_UPDATE_CONTACT": "Päivitä",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Recherche..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Ajouter un contact",
"BUTTON_CREATE_CONTACT": "Créer",
"BUTTON_UPDATE_CONTACT": "Modifier",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Keresés..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Névjegy hozzáadás",
"BUTTON_CREATE_CONTACT": "Létrehozás",
"BUTTON_UPDATE_CONTACT": "Frissítés",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Pencarian..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Tambah Kontak",
"BUTTON_CREATE_CONTACT": "Simpan",
"BUTTON_UPDATE_CONTACT": "Perbarui",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Leita..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Bæta við tengilið",
"BUTTON_CREATE_CONTACT": "Búa til",
"BUTTON_UPDATE_CONTACT": "Uppfæra",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Cerca..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Aggiungi contatto",
"BUTTON_CREATE_CONTACT": "Crea",
"BUTTON_UPDATE_CONTACT": "Aggiorna",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "検索中..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "連絡先を追加",
"BUTTON_CREATE_CONTACT": "作成",
"BUTTON_UPDATE_CONTACT": "更新",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "검색 중..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "연락처 추가",
"BUTTON_CREATE_CONTACT": "생성",
"BUTTON_UPDATE_CONTACT": "업데이트",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Ieškome..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Pridėti kontaktą",
"BUTTON_CREATE_CONTACT": "Sukurti",
"BUTTON_UPDATE_CONTACT": "Atnaujinti",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Meklē..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Pievienot kontaktu",
"BUTTON_CREATE_CONTACT": "Izveidot",
"BUTTON_UPDATE_CONTACT": "Atjaunot",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Søker …"
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Legg til kontakt",
"BUTTON_CREATE_CONTACT": "Lag",
"BUTTON_UPDATE_CONTACT": "Oppdater",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Zoeken..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Contactpersoon Toevoegen",
"BUTTON_CREATE_CONTACT": "Toevoegen",
"BUTTON_UPDATE_CONTACT": "Bijwerken",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Wyszukiwanie..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Dodaj kontakt",
"BUTTON_CREATE_CONTACT": "Utwórz",
"BUTTON_UPDATE_CONTACT": "Zaktualizuj",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Procurando..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Adicionar Contato",
"BUTTON_CREATE_CONTACT": "Criar",
"BUTTON_UPDATE_CONTACT": "Atualizar",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "A pesquisar..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Adicionar contacto",
"BUTTON_CREATE_CONTACT": "Criar",
"BUTTON_UPDATE_CONTACT": "Atualizar",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "A pesquisar..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Adicionar contacto",
"BUTTON_CREATE_CONTACT": "Criar",
"BUTTON_UPDATE_CONTACT": "Atualizar",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Căutare..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Adugă un contact",
"BUTTON_CREATE_CONTACT": "Salvează",
"BUTTON_UPDATE_CONTACT": "Actualizează",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Поиск..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Добавить контакт",
"BUTTON_CREATE_CONTACT": "Сохранить",
"BUTTON_UPDATE_CONTACT": "Обновить",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Hľadám..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Pridať kontakt",
"BUTTON_CREATE_CONTACT": "Vytvoriť",
"BUTTON_UPDATE_CONTACT": "Aktualizovať",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Iskanje..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Dodaj stik",
"BUTTON_CREATE_CONTACT": "Ustvari",
"BUTTON_UPDATE_CONTACT": "Posodobi",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Söker..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Lägg till",
"BUTTON_CREATE_CONTACT": "Ny",
"BUTTON_UPDATE_CONTACT": "Uppdatera",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Aranıyor..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Ekle",
"BUTTON_CREATE_CONTACT": "Oluştur",
"BUTTON_UPDATE_CONTACT": "Güncelle",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Пошук..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Додати контакт",
"BUTTON_CREATE_CONTACT": "Зберегти",
"BUTTON_UPDATE_CONTACT": "Оновити",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "Đang tìm kiếm..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "Thêm liên hệ mới",
"BUTTON_CREATE_CONTACT": "Tạo",
"BUTTON_UPDATE_CONTACT": "Cập nhật",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "搜索中……"
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "添加联系人",
"BUTTON_CREATE_CONTACT": "新增联系人",
"BUTTON_UPDATE_CONTACT": "更新联系人",

View file

@ -174,6 +174,13 @@
"SEARCHING_DESC": "搜索中..."
},
"CONTACTS": {
"TAB_CONTACT": "Contact",
"TAB_LOCATIONS": "Locations",
"TAB_CRYPTO": "Crypto",
"ASK": "Ask",
"NEVER": "Never",
"ALWAYS": "Always",
"ALWAYS_IF_POSSIBLE": "Always if possible",
"BUTTON_ADD_CONTACT": "添加連絡人",
"BUTTON_CREATE_CONTACT": "新增連絡人",
"BUTTON_UPDATE_CONTACT": "更新連絡人",

View file

@ -72,58 +72,19 @@
<!-- ko template: { name: 'Paginator', data: contactsPaginator } --><!-- /ko -->
</div>
</div>
<div class="b-view-content-toolbar btn-toolbar" data-bind="i18nUpdate: contact">
<!-- ko with: contact -->
<div class="btn-group">
<button class="btn button-save-contact" data-bind="visible: !readOnly(), command: $root.saveCommand, css: {'dirty': $root.hasChanges}">
<i data-bind="css: {'icon-ok': !$root.isSaving(), 'icon-spinner': $root.isSaving()}"></i>
<span data-i18n="CONTACTS/BUTTON_CREATE_CONTACT" data-bind="visible: !id()"></span>
<span data-i18n="CONTACTS/BUTTON_UPDATE_CONTACT" data-bind="visible: id"></span>
</button>
</div>
<div class="btn-group dropdown" data-bind="visible: !readOnly(), registerBootstrapDropdown: true">
<a id="button-add-prop-dropdown-id" href="#" class="btn dropdown-toggle" data-i18n="CONTACTS/ADD_MENU_LABEL"></a>
<menu class="dropdown-menu right-edge" style="text-align: left" role="menu" aria-labelledby="button-add-prop-dropdown-id">
<li role="presentation">
<a href="#" data-bind="click: addEmail">
<i class="icon-none"></i>
<span data-i18n="GLOBAL/EMAIL"></span>
</a>
</li>
<li role="presentation">
<a href="#" data-bind="click: addTel">
<i class="icon-none"></i>
<span data-i18n="CONTACTS/ADD_MENU_PHONE"></span>
</a>
</li>
<li role="presentation">
<a href="#" data-bind="click: addUrl">
<i class="icon-none"></i>
<span data-i18n="CONTACTS/ADD_MENU_URL"></span>
</a>
</li>
<li class="dividerbar" role="presentation">
<a href="#" data-bind="click: addNickname">
<i class="icon-none"></i>
<span data-i18n="CONTACTS/ADD_MENU_NICKNAME"></span>
</a>
</li>
<!--
<li role="presentation">
<a href="#" data-bind="click: addNewAddress">
<span data-i18n="CONTACTS/ADD_MENU_ADDRESS"></span>
</a>
</li>-->
</menu>
</div>
<!-- /ko -->
</div>
<div class="b-view-content">
<div class="b-contact-view-desc" data-bind="visible: !contact"
<div class="b-view-content" data-bind="css: {'read-only': contact() && contact().readOnly()}">
<div class="b-contact-view-desc" data-bind="visible: !contact()"
data-i18n="CONTACTS/CONTACT_VIEW_DESC"></div>
<div data-bind="visible: contact, i18nUpdate: contact">
<div class="tabs" data-bind="visible: contact, i18nUpdate: contact">
<!-- ko with: contact -->
<div class="form-horizontal top-part" data-bind="css: {'read-only': readOnly}">
<input type="radio" name="contacttabs" id="tab-contact" checked>
<label for="tab-contact"
role="tab"
aria-selected="true"
aria-controls="panel1"
tabindex="0"
data-i18n="CONTACTS/TAB_CONTACT"></label>
<div class="form-horizontal tab-content" role="tabpanel" aria-hidden="false">
<div class="control-group" data-bind="visible: !readOnly() || hasValidName()">
<label class="fontastic iconsize24">👤</label>
<div>
@ -210,10 +171,107 @@
</div>
</div>
</div>
<!-- /ko -->
<!--
<div class="e-read-only-sign fontastic iconsize24" data-i18n="[title]CONTACTS/LABEL_READ_ONLY">🔒</div>
<input type="radio" name="contacttabs" id="tab-contact-locations">
<label for="tab-contact-locations"
role="tab"
aria-selected="false"
aria-controls="panel3"
tabindex="0"
data-i18n="CONTACTS/TAB_LOCATIONS"></label>
<div class="form-horizontal tab-content" role="tabpanel" aria-hidden="false">
<div data-bind="foreach: adr">
<div class="control-group">
<div data-bind="text: type"></div>
<div data-bind="text: street"></div>
<div data-bind="text: street_ext"></div>
<div data-bind="text: locality"></div>
<div data-bind="text: region"></div>
<div data-bind="text: postcode"></div>
<div data-bind="text: pobox"></div>
<div data-bind="text: country"></div>
<div data-bind="text: preferred"></div>
</div>
</div>
</div>
-->
<input type="radio" name="contacttabs" id="tab-contact-crypto">
<label for="tab-contact-crypto"
role="tab"
aria-selected="false"
aria-controls="panel3"
tabindex="0"
data-i18n="CONTACTS/TAB_CRYPTO"></label>
<div class="form-horizontal tab-content" role="tabpanel" aria-hidden="false">
<div class="control-group">
<label>Sign</label>
<select name="x-crypto[signpref]" data-bind="value: signpref">
<option value="Ask" data-i18n="CONTACTS/ASK"></option>
<option value="Never" data-i18n="CONTACTS/NEVER"></option>
<option value="Always" data-i18n="CONTACTS/ALWAYS"></option>
<option value="IfPossible" data-i18n="CONTACTS/ALWAYS_IF_POSSIBLE"></option>
</select>
</div>
<div class="control-group">
<label>Encrypt</label>
<select name="x-crypto[encryptpref]" data-bind="value: encryptpref">
<option value="Ask" data-i18n="CONTACTS/ASK"></option>
<option value="Never" data-i18n="CONTACTS/NEVER"></option>
<option value="Always" data-i18n="CONTACTS/ALWAYS"></option>
<option value="IfPossible" data-i18n="CONTACTS/ALWAYS_IF_POSSIBLE"></option>
</select>
</div>
</div>
<!-- /ko -->
</div>
</div>
<div class="b-view-content-toolbar btn-toolbar" data-bind="i18nUpdate: contact">
<!-- ko with: contact -->
<div class="btn-group">
<button class="btn button-save-contact" data-bind="visible: !readOnly(), command: $root.saveCommand, css: {'dirty': $root.hasChanges}">
<i data-bind="css: {'icon-ok': !$root.isSaving(), 'icon-spinner': $root.isSaving()}"></i>
<span data-i18n="CONTACTS/BUTTON_CREATE_CONTACT" data-bind="visible: !id()"></span>
<span data-i18n="CONTACTS/BUTTON_UPDATE_CONTACT" data-bind="visible: id"></span>
</button>
</div>
<div class="btn-group dropdown" data-bind="visible: !readOnly(), registerBootstrapDropdown: true">
<a id="button-add-prop-dropdown-id" href="#" class="btn dropdown-toggle" data-i18n="CONTACTS/ADD_MENU_LABEL"></a>
<menu class="dropdown-menu right-edge" style="text-align: left" role="menu" aria-labelledby="button-add-prop-dropdown-id">
<li role="presentation">
<a href="#" data-bind="click: addEmail">
<i class="icon-none"></i>
<span data-i18n="GLOBAL/EMAIL"></span>
</a>
</li>
<li role="presentation">
<a href="#" data-bind="click: addTel">
<i class="icon-none"></i>
<span data-i18n="CONTACTS/ADD_MENU_PHONE"></span>
</a>
</li>
<li role="presentation">
<a href="#" data-bind="click: addUrl">
<i class="icon-none"></i>
<span data-i18n="CONTACTS/ADD_MENU_URL"></span>
</a>
</li>
<li class="dividerbar" role="presentation">
<a href="#" data-bind="click: addNickname">
<i class="icon-none"></i>
<span data-i18n="CONTACTS/ADD_MENU_NICKNAME"></span>
</a>
</li>
<!--
<li role="presentation">
<a href="#" data-bind="click: addNewAddress">
<span data-i18n="CONTACTS/ADD_MENU_ADDRESS"></span>
</a>
</li>-->
</menu>
</div>
<!--
<div class="read-only-sign fontastic iconsize24" data-bind="visible: readOnly" data-i18n="[title]CONTACTS/LABEL_READ_ONLY">🔒</div>
-->
<!-- /ko -->
</div>
</div>