Improved Kolab AddressBook/Contacts

This commit is contained in:
the-djmaze 2022-05-13 16:17:13 +02:00
parent 5c9b7d7000
commit 824311937a
42 changed files with 299 additions and 54 deletions

View file

@ -4,6 +4,7 @@ import { koComputable } from 'External/ko';
import { SettingsGet } from 'Common/Globals';
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
import { ContactUserStore } from 'Stores/User/Contact';
import { FolderUserStore } from 'Stores/User/Folder';
import Remote from 'Remote/User/Fetch';
export class UserSettingsContacts /*extends AbstractViewSettings*/ {
@ -48,5 +49,33 @@ export class UserSettingsContacts /*extends AbstractViewSettings*/ {
Password: ContactUserStore.syncPass()
})
);
this.kolabContactFolder = ko.observable(SettingsGet('KolabContactFolder'));
this.kolabContactFolder.subscribe(value =>
Remote.saveSettings(null, { KolabContactFolder: value })
);
this.showKolab = FolderUserStore.allowKolab();
this.folderSelectList = koComputable(() => {
const
aResult = [{
id: '',
name: '',
}],
foldersWalk = folders => {
folders.forEach(oItem => {
if ('contact' === oItem.kolabType()) {
aResult.push({
id: oItem.fullName,
name: oItem.fullName
});
}
if (oItem.subFolders.length) {
foldersWalk(oItem.subFolders());
}
});
};
foldersWalk(FolderUserStore.folderList());
return aResult;
});
}
}

View file

@ -3,7 +3,6 @@ import { koComputable } from 'External/ko';
import { Notification } from 'Common/Enums';
import { FolderMetadataKeys } from 'Common/EnumsUser';
import { SettingsCapa } from 'Common/Globals';
import { getNotification } from 'Common/Translator';
import { setFolder, getFolderFromCacheList, removeFolderFromCacheList } from 'Common/Cache';
@ -26,7 +25,7 @@ const folderForDeletion = ko.observable(null).askDeleteHelper();
export class UserSettingsFolders /*extends AbstractViewSettings*/ {
constructor() {
this.showKolab = koComputable(() => FolderUserStore.hasCapability('METADATA') && SettingsCapa('Kolab'));
this.showKolab = FolderUserStore.allowKolab();
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
this.kolabTypeOptions = ko.observableArray();
let i18nFilter = key => i18n('SETTINGS_FOLDERS/TYPE_' + key);

View file

@ -6,7 +6,7 @@ import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { forEachObjectEntry } from 'Common/Utils';
import { addObservablesTo, addSubscribablesTo, addComputablesTo } from 'External/ko';
import { getFolderInboxName, getFolderFromCacheList } from 'Common/Cache';
import { Settings } from 'Common/Globals';
import { Settings, SettingsCapa } from 'Common/Globals';
//import Remote from 'Remote/User/Fetch'; // Circular dependency
export const FolderUserStore = new class {
@ -117,6 +117,10 @@ export const FolderUserStore = new class {
return this.capabilities().includes(name);
}
allowKolab() {
return FolderUserStore.hasCapability('METADATA') && SettingsCapa('Kolab');
}
/**
* @returns {Array}
*/

View file

@ -248,26 +248,8 @@ class Actions
break;
case 'address-book':
// Providers\AddressBook\AddressBookInterface
$sDsn = \trim($this->oConfig->Get('contacts', 'pdo_dsn', ''));
$sUser = \trim($this->oConfig->Get('contacts', 'pdo_user', ''));
$sPassword = (string)$this->oConfig->Get('contacts', 'pdo_password', '');
$sDsnType = Providers\AddressBook\PdoAddressBook::validPdoType($this->oConfig->Get('contacts', 'type', 'sqlite'));
if ('sqlite' === $sDsnType) {
$sUser = $sPassword = '';
$sDsn = 'sqlite:' . APP_PRIVATE_DATA . 'AddressBook.sqlite';
/*
// TODO: use local db?
$homedir = $this->StorageProvider()->GenerateFilePath(
$oAccount,
\RainLoop\Providers\Storage\Enumerations\StorageType::ROOT
);
$sDsn = 'sqlite:' . $homedir . '/AddressBook.sqlite';
*/
} else {
$sDsn = $sDsnType . ':' . \preg_replace('/^[a-z]+:/', '', $sDsn);
}
$mResult = new Providers\AddressBook\PdoAddressBook($sDsn, $sUser, $sPassword, $sDsnType);
// $mResult = new Providers\AddressBook\KolabAddressBook($this->MailClient()->ImapClient());
$mResult = new Providers\AddressBook\PdoAddressBook();
// $mResult = new Providers\AddressBook\KolabAddressBook();
break;
case 'identities':
case 'suggestions':
@ -750,7 +732,7 @@ class Actions
'ContactsAutosave' => (bool) $oConfig->Get('defaults', 'contacts_autosave', true),
'HideUnsubscribed' => false,
'MainEmail' => '',
'InterfaceAnimation' => true,
'KolabContactFolder' => '',
'UserBackgroundName' => '',
'UserBackgroundHash' => ''
);
@ -838,6 +820,7 @@ class Actions
$aResult['HideUnsubscribed'] = (bool)$oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']);
$aResult['UseThreads'] = (bool)$oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']);
$aResult['ReplySameFolder'] = (bool)$oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']);
$aResult['KolabContactFolder'] = (string)$oSettingsLocal->GetConf('KolabContactFolder', $aResult['KolabContactFolder']);
}
if ($oConfig->Get('login', 'determine_user_language', true)) {
@ -1166,7 +1149,7 @@ class Actions
'DangerousActions' => (bool) $oConfig->Get('capa', 'dangerous_actions', true),
'GnuPG' => (bool) $oConfig->Get('security', 'openpgp', false) && \SnappyMail\PGP\GnuPG::isSupported(),
'Identities' => (bool) $oConfig->Get('webmail', 'allow_additional_identities', false),
'Kolab' => (bool) $oConfig->Get('labs', 'kolab_enabled', false),
'Kolab' => (bool) $oConfig->Get('labs', 'kolab_enabled', false) /* && ImapClient->IsSupported('METADATA')*/,
'MessageActions' => (bool) $oConfig->Get('capa', 'message_actions', true),
'OpenPGP' => (bool) $oConfig->Get('security', 'openpgp', false),
'Prefetch' => (bool) $oConfig->Get('labs', 'allow_prefetch', false),

View file

@ -345,6 +345,7 @@ trait User
$this->setSettingsFromParams($oSettingsLocal, 'UseThreads', 'bool');
$this->setSettingsFromParams($oSettingsLocal, 'ReplySameFolder', 'bool');
$this->setSettingsFromParams($oSettingsLocal, 'HideUnsubscribed', 'bool');
$this->setSettingsFromParams($oSettingsLocal, 'KolabContactFolder', 'string');
return $this->DefaultResponse(__FUNCTION__,
$this->SettingsProvider()->Save($oAccount, $oSettings) &&

View file

@ -4,7 +4,7 @@ namespace RainLoop\Providers\AddressBook;
use RainLoop\Providers\AddressBook\Enumerations\PropertyType;
class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInterface
class KolabAddressBook implements AddressBookInterface
{
use CardDAV;
@ -12,27 +12,54 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
$oImapClient,
$sFolderName;
function __construct(\MailSo\Imap\ImapClient $oImapClient)
protected function ImapClient() : \MailSo\Imap\ImapClient
{
$this->oImapClient = $oImapClient;
if (!$this->oImapClient /*&&\RainLoop\Api::Config()->Get('labs', 'kolab_enabled', false)*/) {
$oActions = \RainLoop\Api::Actions();
$oMailClient = $oActions->MailClient();
if (!$oMailClient->IsLoggined()) {
$oActions->getAccountFromToken()->IncConnectAndLoginHelper($oActions->Plugins(), $oMailClient, $oActions->Config());
}
$this->oImapClient = $oMailClient->ImapClient();
}
return $this->oImapClient;
}
public function SetFolder(string $sFolderName) : bool
public function FolderName() : string
{
$metadata = $this->oImapClient->FolderGetMetadata($sFolderName, [\MailSo\Imap\Enumerations\MetadataKeys::KOLAB_CTYPE]);
if ($metadata && 'contact' !== \array_shift($metadata)) {
// Throw error
// $this->oImapClient->FolderList() : array
return false;
if (!\is_string($this->sFolderName)) {
$oActions = \RainLoop\Api::Actions();
$oAccount = $oActions->getAccountFromToken();
$this->sFolderName = (string) $oActions->SettingsProvider(true)->Load($oAccount)->GetConf('KolabContactFolder', '');
}
$this->oImapClient->FolderSelect($sFolderName);
$this->sFolderName = $sFolderName;
return true;
return $this->sFolderName;
}
public function SelectFolder() : bool
{
try {
$sFolderName = $this->FolderName();
if (!$sFolderName) {
return false;
}
$metadata = $this->ImapClient()->FolderGetMetadata($sFolderName, [\MailSo\Imap\Enumerations\MetadataKeys::KOLAB_CTYPE]);
if (!$metadata || 'contact' !== \array_shift($metadata)) {
throw new \Exception("Invalid kolab contact folder: {$sFolderName}");
}
$this->ImapClient()->FolderSelect($sFolderName);
$this->sFolderName = $sFolderName;
return true;
} catch (\Throwable $e) {
\trigger_error("KolabAddressBook {$sFolderName} error: {$e->getMessage()}");
}
return false;
}
public function IsSupported() : bool
{
// Check $this->oImapClient->IsSupported('METADATA')
// Check $this->ImapClient()->IsSupported('METADATA')
return true;
}
@ -58,6 +85,10 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
// TODO
// $emails = $oContact->GetEmails();
if (!$this->SelectFolder()) {
return false;
}
$oContact->PopulateDisplayAndFullNameValue();
$sUID = $oContact->GetUID();
@ -88,27 +119,27 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
$oMessage->SubParts->append($oPart);
// Search in IMAP folder:
$aUids = $this->oImapClient->MessageSimpleSearch("SUBJECT {$sUID}");
$aUids = $this->ImapClient()->MessageSimpleSearch("SUBJECT {$sUID}");
/*
$email = \MailSo\Imap\SearchCriterias::escapeSearchString($this->oImapClient, $sEmail);
$aUids = $this->oImapClient->MessageSimpleSearch("OR SUBJECT {$sUID} FROM {$email}");
$aUids = $this->oImapClient->MessageSimpleSearch("OR SUBJECT {$sUID} FROM {$email} BODY {$email}");
$email = \MailSo\Imap\SearchCriterias::escapeSearchString($this->ImapClient(), $sEmail);
$aUids = $this->ImapClient()->MessageSimpleSearch("OR SUBJECT {$sUID} FROM {$email}");
$aUids = $this->ImapClient()->MessageSimpleSearch("OR SUBJECT {$sUID} FROM {$email} BODY {$email}");
$aUids = $this->oImapClient->MessageSimpleSearch('HEADER Subject '.$sUID);
$aUids = $this->ImapClient()->MessageSimpleSearch('HEADER Subject '.$sUID);
return 1 === \count($aUids) && \is_numeric($aUids[0]) ? (int) $aUids[0] : null;
*/
if ($aUids) {
// Replace Message
if (false && $this->oImapClient->IsSupported('REPLACE')) {
if (false && $this->ImapClient()->IsSupported('REPLACE')) {
// UID REPLACE
} else {
$oRange = new \MailSo\Imap\SequenceSet($aUids[0]);
$this->oImapClient->MessageStoreFlag($oRange,
$this->ImapClient()->MessageStoreFlag($oRange,
array(\MailSo\Imap\Enumerations\MessageFlag::DELETED),
\MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
);
$this->oImapClient->FolderExpunge($oRange);
$this->ImapClient()->FolderExpunge($oRange);
}
}
@ -118,7 +149,7 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
$oMessage->ToStream(false), array($rMessageStream), 8192, true, true);
if (false !== $iMessageStreamSize) {
\rewind($rMessageStream);
$this->oImapClient->MessageAppendStream($this->sFolderName, $rMessageStream, $iMessageStreamSize);
$this->ImapClient()->MessageAppendStream($this->sFolderName, $rMessageStream, $iMessageStreamSize);
}
return true;
@ -138,8 +169,76 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
public function GetContacts(string $sEmail, int $iOffset = 0, int $iLimit = 20, string $sSearch = '', int &$iResultCount = 0) : array
{
// TODO
return [];
$oParams = new \MailSo\Mail\MessageListParams;
$oParams->sFolderName = $this->FolderName();
$oParams->iOffset = $iOffset;
$oParams->iLimit = $iLimit;
if ($sSearch) {
$oParams->sSearch = 'from='.$sSearch;
}
$oParams->sSort = 'FROM';
// $oParams->iPrevUidNext = $this->GetActionParam('UidNext', 0);
// $oParams->bUseThreads = false;
if (!\strlen($oParams->sFolderName)) {
// return [];
throw new ClientException(Notifications::CantGetMessageList);
}
$this->ImapClient();
$aResult = [];
try
{
$oMessageList = \RainLoop\Api::Actions()->MailClient()->MessageList($oParams);
foreach ($oMessageList as $oMessage) {
$oContact = new Classes\Contact;
$oContact->IdContact = $oMessage->Uid();
$oContact->IdContactStr = $oMessage->Subject();
// $oContact->Display = isset($aItem['display']) ? (string) $aItem['display'] : '';
$oContact->Changed = $oMessage->HeaderTimeStampInUTC();
$oFrom = $oMessage->From();
if ($oFrom) {
$oMail = $oFrom[0];
$oProperty = new Classes\Property(PropertyType::EMAIl, $oMail->GetEmail());
$oContact->Properties[] = $oProperty;
$oProperty = new Classes\Property(PropertyType::FULLNAME, $oMail->GetDisplayName());
// $oProperty = new Classes\Property(PropertyType::FULLNAME, $oMail->ToString());
$oContact->Properties[] = $oProperty;
// $oProperty = new Classes\Property(PropertyType::NICK_NAME, $oMail->GetDisplayName());
// $oContact->Properties[] = $oProperty;
$oContact->UpdateDependentValues();
$aResult[] = $oContact;
/*
// TODO extract xCard attachment
$oMessage->ContentType() = multipart/mixed
$oMessage->Attachments() : ?AttachmentCollection
[0] => MailSo\Mail\Attachment(
[oBodyStructure:MailSo\Mail\Attachment:private] => MailSo\Imap\BodyStructure(
[sContentType:MailSo\Imap\BodyStructure:private] => application/vcard+xml
[sCharset:MailSo\Imap\BodyStructure:private] =>
[aBodyParams:MailSo\Imap\BodyStructure:private] => Array(
[name] => kolab.xml
)
[sMailEncodingName:MailSo\Imap\BodyStructure:private] => quoted-printable
[sDisposition:MailSo\Imap\BodyStructure:private] => attachment
[sFileName:MailSo\Imap\BodyStructure:private] => kolab.xml
[iSize:MailSo\Imap\BodyStructure:private] => 1043
[sPartID:MailSo\Imap\BodyStructure:private] => 2
*/
}
}
}
catch (\Throwable $oException)
{
throw $oException;
throw new ClientException(Notifications::CantGetMessageList, $oException);
}
return $aResult;
}
public function GetContactByID(string $sEmail, $mID, bool $bIsStrID = false) : ?Classes\Contact
@ -151,18 +250,18 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
public function GetSuggestions(string $sEmail, string $sSearch, int $iLimit = 20) : array
{
$sSearch = \trim($sSearch);
if (2 > \strlen($sSearch) || !$this->SetFolder(/*TODO 'Contacts'*/)) {
if (2 > \strlen($sSearch) || !$this->SelectFolder()) {
return [];
}
$sSearch = \MailSo\Imap\SearchCriterias::escapeSearchString($this->oImapClient, $sSearch);
$sSearch = \MailSo\Imap\SearchCriterias::escapeSearchString($this->ImapClient(), $sSearch);
$aUids = \array_slice(
$this->oImapClient->MessageSimpleSearch("FROM {$sSearch}"),
$this->ImapClient()->MessageSimpleSearch("FROM {$sSearch}"),
0, $iLimit
);
$aResult = [];
foreach ($this->oImapClient->Fetch(['BODY.PEEK[HEADER.FIELDS (FROM)]'], \implode(',', $aUids), true) as $oFetchResponse) {
foreach ($this->ImapClient()->Fetch(['BODY.PEEK[HEADER.FIELDS (FROM)]'], \implode(',', $aUids), true) as $oFetchResponse) {
$oHeaders = new \MailSo\Mime\HeaderCollection($oFetchResponse->GetHeaderFieldsValue());
$oFrom = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, true);
foreach ($oFrom as $oMail) {

View file

@ -30,8 +30,28 @@ class PdoAddressBook
*/
private $sPassword;
public function __construct(string $sDsn, string $sUser = '', string $sPassword = '', string $sDsnType = 'mysql')
public function __construct()
{
$oConfig = \RainLoop\Api::Config();
$sDsnType = static::validPdoType($oConfig->Get('contacts', 'type', 'sqlite'));
if ('sqlite' === $sDsnType) {
$sUser = $sPassword = '';
$sDsn = 'sqlite:' . APP_PRIVATE_DATA . 'AddressBook.sqlite';
/*
// TODO: use local db?
$homedir = \RainLoop\Api::Actions()->StorageProvider()->GenerateFilePath(
$oAccount,
\RainLoop\Providers\Storage\Enumerations\StorageType::ROOT
);
$sDsn = 'sqlite:' . $homedir . '/AddressBook.sqlite';
*/
} else {
$sDsn = \trim($oConfig->Get('contacts', 'pdo_dsn', ''));
$sUser = \trim($oConfig->Get('contacts', 'pdo_user', ''));
$sPassword = (string)$oConfig->Get('contacts', 'pdo_password', '');
$sDsn = $sDsnType . ':' . \preg_replace('/^[a-z]+:/', '', $sDsn);
}
$this->sDsn = $sDsn;
$this->sUser = $sUser;
$this->sPassword = $sPassword;

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "إرسال الرسالة",
"LABEL_CLOSE_COMPOSE": "إغلاق الرسالة"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "خاطىء token",
"AUTH_ERROR": "خطأ في كلمة السر \\اسم المستخدم",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Изпрати съобщението",
"LABEL_CLOSE_COMPOSE": "Затвори създаването"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Невалиден токен",
"AUTH_ERROR": "Неуспешно удостоверяване",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Odeslat zprávu",
"LABEL_CLOSE_COMPOSE": "Zavřít okno se zprávou"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Neplatný token",
"AUTH_ERROR": "Ověření selhalo",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send meddelelse",
"LABEL_CLOSE_COMPOSE": "Luk forfatter mode"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Ugyldigt tegn",
"AUTH_ERROR": "Godkendelse fejlede",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Nachricht senden",
"LABEL_CLOSE_COMPOSE": "Popup schließen"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Ungültiger Token",
"AUTH_ERROR": "Authentifizierung fehlgeschlagen",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send message",
"LABEL_CLOSE_COMPOSE": "Close compose"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Invalid token",
"AUTH_ERROR": "Authentication failed",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send message",
"LABEL_CLOSE_COMPOSE": "Close compose"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Invalid token",
"AUTH_ERROR": "Authentication failed",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Enviar mensaje",
"LABEL_CLOSE_COMPOSE": "Cerrar componer"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Elemento no válido",
"AUTH_ERROR": "Autentificación fallida",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Saada kiri",
"LABEL_CLOSE_COMPOSE": "Sulge kirja koostamise aken"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Puudulik tõend",
"AUTH_ERROR": "Autentimine ebaõnnestus",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "فرستادن پیام",
"LABEL_CLOSE_COMPOSE": "بستن صفحه ایجاد"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "توکن نامعتبر",
"AUTH_ERROR": "احراز هویت با موفقیت همراه نبود",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Lähetä viesti",
"LABEL_CLOSE_COMPOSE": "Sule luo viesti"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Väärä suojaustunnus",
"AUTH_ERROR": "Tunnistusvirhe",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Envoyer le message",
"LABEL_CLOSE_COMPOSE": "Fermer la fenêtre d'édition"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Jeton invalide",
"AUTH_ERROR": "L'authentification a échoué",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Üzenet küldés",
"LABEL_CLOSE_COMPOSE": "Levélírás bezárása"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Érvénytelen kulcs",
"AUTH_ERROR": "Érvénytelen hitelesítés",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Kirim pesan",
"LABEL_CLOSE_COMPOSE": "Tutup buat pesan"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Token tidak sah",
"AUTH_ERROR": "Otentikasi gagal",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Senda skilaboð",
"LABEL_CLOSE_COMPOSE": "Loka ritli"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Rangt tákn",
"AUTH_ERROR": "Auðkenning mistókst",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Invia messaggio",
"LABEL_CLOSE_COMPOSE": "Chiudi finestra di scrittura"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Token invalido",
"AUTH_ERROR": "Autenticazione fallita",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "メッセージを送信",
"LABEL_CLOSE_COMPOSE": "作成画面を閉じる"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "無効なトークン",
"AUTH_ERROR": "認証に失敗しました",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "메시지 보내기",
"LABEL_CLOSE_COMPOSE": "쓰기 창 닫기"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "잘못된 토큰입니다.",
"AUTH_ERROR": "인증에 실패했습니다",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Siųsti laišką",
"LABEL_CLOSE_COMPOSE": "Uždaryti laišką"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Neteisingas raktas",
"AUTH_ERROR": "Autorizacija nepavyko",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send message",
"LABEL_CLOSE_COMPOSE": "Close compose"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Nepareizs tokens",
"AUTH_ERROR": "Autorizācija neizdevās",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send melding",
"LABEL_CLOSE_COMPOSE": "Lukk ny melding"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Ugyldig info",
"AUTH_ERROR": "Autentisering mislyktes",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Bericht versturen",
"LABEL_CLOSE_COMPOSE": "Nieuw bericht venster sluiten"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Ongeldige token",
"AUTH_ERROR": "Authenticatie mislukt",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Wyślij wiadomość",
"LABEL_CLOSE_COMPOSE": "Zamknij okno tworzenia wiadomości"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Nieprawidłowy token",
"AUTH_ERROR": "Błąd autoryzacji",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Enviar mensagem",
"LABEL_CLOSE_COMPOSE": "Fechar composição"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Senha inválida",
"AUTH_ERROR": "Falha na autenticação",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Enviar mensagem",
"LABEL_CLOSE_COMPOSE": "Fechar janela de composição"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Token inválido",
"AUTH_ERROR": "Falha na autenticação",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send message",
"LABEL_CLOSE_COMPOSE": "Close compose"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Semn invalid",
"AUTH_ERROR": "Nu se poate realiza conectarea",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Отправить сообщение",
"LABEL_CLOSE_COMPOSE": "Закрыть сообщение"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Неверный токен запроса",
"AUTH_ERROR": "Не удалось авторизоваться",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send message",
"LABEL_CLOSE_COMPOSE": "Close compose"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Neplatný token",
"AUTH_ERROR": "Overenie zlyhalo",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Pošlji sporočilo",
"LABEL_CLOSE_COMPOSE": "Zapri sestavljanje"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Neveljaven žeton",
"AUTH_ERROR": "Spodletelo overjanje",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Skicka meddelande",
"LABEL_CLOSE_COMPOSE": "Stäng fönster"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Ogiltigt tecken",
"AUTH_ERROR": "Autentisering misslyckades",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Mesajı Gönder",
"LABEL_CLOSE_COMPOSE": "Oluşturulanı Kapat"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Geçersiz token",
"AUTH_ERROR": "Kimlik doğrulama başarısız oldu",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Надіслати повідомлення",
"LABEL_CLOSE_COMPOSE": "Закрити повідомлення"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "Невірний токен запиту",
"AUTH_ERROR": "Не вдалося авторизуватися",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "发送邮件",
"LABEL_CLOSE_COMPOSE": "退出新邮件"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "无效标记",
"AUTH_ERROR": "认证失败",

View file

@ -500,6 +500,9 @@
"LABEL_SEND_MESSAGE": "Send message",
"LABEL_CLOSE_COMPOSE": "Close compose"
},
"KOLAB": {
"CONTACTS_FOLDER": "Contacts Folder"
},
"NOTIFICATIONS": {
"INVALID_TOKEN": "無效標記",
"AUTH_ERROR": "認證失敗",

View file

@ -40,3 +40,11 @@
spellcheck="false" data-bind="value: syncPass">
</div>
</div>
<div class="form-horizontal" data-bind="visible: showKolab">
<div class="legend">Kolab</div>
<div class="control-group">
<label data-i18n="KOLAB/CONTACTS_FOLDER"></label>
<select data-bind="options: folderSelectList, value: kolabContactFolder,
optionsText: 'name', optionsValue: 'id'"></select>
</div>
</div>