Contacts tags support (unstable - step 1).

+ Small fixes
This commit is contained in:
RainLoop Team 2014-05-13 12:29:36 +04:00
parent 74fa12c000
commit 0ca00dec40
34 changed files with 1790 additions and 801 deletions

View file

@ -184,6 +184,7 @@ module.exports = function (grunt) {
"dev/Knoin/Knoin.js",
"dev/Models/EmailModel.js",
"dev/Models/ContactTagModel.js",
"dev/ViewModels/PopupsDomainViewModel.js",
"dev/ViewModels/PopupsPluginViewModel.js",
@ -264,6 +265,7 @@ module.exports = function (grunt) {
"dev/Models/EmailModel.js",
"dev/Models/ContactModel.js",
"dev/Models/ContactPropertyModel.js",
"dev/Models/ContactTagModel.js",
"dev/Models/AttachmentModel.js",
"dev/Models/ComposeAttachmentModel.js",
"dev/Models/MessageModel.js",

View file

@ -974,6 +974,17 @@ RainLoopApp.prototype.getAutocomplete = function (sQuery, fCallback)
}, sQuery);
};
/**
* @param {string} sQuery
* @param {Function} fCallback
*/
RainLoopApp.prototype.getContactsTagsAutocomplete = function (sQuery, fCallback)
{
fCallback(_.filter(RL.data().contactTags(), function (oContactTag) {
return oContactTag && oContactTag.filterHelper(sQuery);
}));
};
RainLoopApp.prototype.emailsPicsHashes = function ()
{
RL.remote().emailsPicsHashes(function (sResult, oData) {

View file

@ -536,11 +536,19 @@ ko.bindingHandlers.emailsTags = {
'init': function(oElement, fValueAccessor) {
var
$oEl = $(oElement),
fValue = fValueAccessor()
fValue = fValueAccessor(),
fFocusCallback = function (bValue) {
if (fValue && fValue.focusTrigger)
{
fValue.focusTrigger(bValue);
}
}
;
$oEl.inputosaurus({
'parseOnBlur': true,
'allowDragAndDrop': true,
'focusCallback': fFocusCallback,
'inputDelimiters': [',', ';'],
'autoCompleteSource': function (oData, fResponse) {
RL.getAutocomplete(oData.term, function (aData) {
@ -586,8 +594,83 @@ ko.bindingHandlers.emailsTags = {
if (fValue.focusTrigger)
{
fValue.focusTrigger.subscribe(function () {
fValue.focusTrigger.subscribe(function (bValue) {
if (bValue)
{
$oEl.inputosaurus('focus');
}
});
}
}
};
ko.bindingHandlers.contactTags = {
'init': function(oElement, fValueAccessor) {
var
$oEl = $(oElement),
fValue = fValueAccessor(),
fFocusCallback = function (bValue) {
if (fValue && fValue.focusTrigger)
{
fValue.focusTrigger(bValue);
}
}
;
$oEl.inputosaurus({
'parseOnBlur': true,
'allowDragAndDrop': false,
'focusCallback': fFocusCallback,
'inputDelimiters': [';'],
'outputDelimiter': ';',
'autoCompleteSource': function (oData, fResponse) {
RL.getContactsTagsAutocomplete(oData.term, function (aData) {
fResponse(_.map(aData, function (oTagItem) {
return oTagItem.toLine(false);
}));
});
},
'parseHook': function (aInput) {
return _.map(aInput, function (sInputValue) {
var
sValue = Utils.trim(sInputValue),
oTag = null
;
if ('' !== sValue)
{
oTag = new ContactTagModel();
oTag.name(sValue);
return [oTag.toLine(false), oTag];
}
return [sValue, null];
});
},
'change': _.bind(function (oEvent) {
$oEl.data('ContactsTagsValue', oEvent.target.value);
fValue(oEvent.target.value);
}, this)
});
fValue.subscribe(function (sValue) {
if ($oEl.data('ContactsTagsValue') !== sValue)
{
$oEl.val(sValue);
$oEl.data('ContactsTagsValue', sValue);
$oEl.inputosaurus('refresh');
}
});
if (fValue.focusTrigger)
{
fValue.focusTrigger.subscribe(function (bValue) {
if (bValue)
{
$oEl.inputosaurus('focus');
}
});
}
}

View file

@ -1075,7 +1075,7 @@ Utils.setExpandedFolder = function (sFullNameHash, bExpanded)
Utils.initLayoutResizer = function (sLeft, sRight, sClientSideKeyName)
{
var
iDisabledWidth = 65,
iDisabledWidth = 60,
iMinWidth = 155,
oLeft = $(sLeft),
oRight = $(sRight),

View file

@ -72,15 +72,22 @@ KnoinAbstractViewModel.prototype.restoreKeyScope = function ()
RL.data().keyScope(this.sCurrentKeyScope);
};
KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function ()
KnoinAbstractViewModel.prototype.registerPopupKeyDown = function ()
{
var self = this;
$window.on('keydown', function (oEvent) {
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility && self.modalVisibility())
if (oEvent && self.modalVisibility && self.modalVisibility())
{
if (!this.bDisabeCloseOnEsc && Enums.EventKeyCode.Esc === oEvent.keyCode)
{
Utils.delegateRun(self, 'cancelCommand');
return false;
}
else if (Enums.EventKeyCode.Backspace === oEvent.keyCode && !Utils.inFocus())
{
return false;
}
}
return true;
});

View file

@ -134,9 +134,9 @@ Knoin.prototype.buildViewModel = function (ViewModelClass, oScreen)
ko.applyBindings(oViewModel, oViewModelDom[0]);
Utils.delegateRun(oViewModel, 'onBuild', [oViewModelDom]);
if (oViewModel && 'Popups' === sPosition && !oViewModel.bDisabeCloseOnEsc)
if (oViewModel && 'Popups' === sPosition)
{
oViewModel.registerPopupEscapeKey();
oViewModel.registerPopupKeyDown();
}
Plugins.runHook('view-model-post-build', [ViewModelClass.__name, oViewModel, oViewModelDom]);

View file

@ -8,6 +8,7 @@ function ContactModel()
this.idContact = 0;
this.display = '';
this.properties = [];
this.tags = '';
this.readOnly = false;
this.focused = ko.observable(false);
@ -58,6 +59,7 @@ ContactModel.prototype.parse = function (oItem)
this.idContact = Utils.pInt(oItem['IdContact']);
this.display = Utils.pString(oItem['Display']);
this.readOnly = !!oItem['ReadOnly'];
this.tags = '';
if (Utils.isNonEmptyArray(oItem['Properties']))
{
@ -69,6 +71,11 @@ ContactModel.prototype.parse = function (oItem)
}, this);
}
if (Utils.isNonEmptyArray(oItem['Tags']))
{
this.tags = oItem['Tags'].join(';');
}
bResult = true;
}

View file

@ -0,0 +1,45 @@
/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
/**
* @constructor
*/
function ContactTagModel()
{
this.idContactTag = 0;
this.name = ko.observable('');
this.readOnly = false;
}
ContactTagModel.prototype.parse = function (oItem)
{
var bResult = false;
if (oItem && 'Object/Tag' === oItem['@Object'])
{
this.idContact = Utils.pInt(oItem['IdContactTag']);
this.name(Utils.pString(oItem['Name']));
this.readOnly = !!oItem['ReadOnly'];
bResult = true;
}
return bResult;
};
/**
* @param {string} sSearch
* @return {boolean}
*/
ContactTagModel.prototype.filterHelper = function (sSearch)
{
return this.name().toLowerCase().indexOf(sSearch.toLowerCase()) !== -1;
};
/**
* @param {boolean=} bEncodeHtml = false
* @return {string}
*/
ContactTagModel.prototype.toLine = function (bEncodeHtml)
{
return (Utils.isUnd(bEncodeHtml) ? false : !!bEncodeHtml) ?
Utils.encodeHtml(this.name()) : this.name();
};

View file

@ -686,11 +686,12 @@ WebMailAjaxRemoteStorage.prototype.contacts = function (fCallback, iOffset, iLim
/**
* @param {?Function} fCallback
*/
WebMailAjaxRemoteStorage.prototype.contactSave = function (fCallback, sRequestUid, sUid, aProperties)
WebMailAjaxRemoteStorage.prototype.contactSave = function (fCallback, sRequestUid, sUid, sTags, aProperties)
{
this.defaultRequest(fCallback, 'ContactSave', {
'RequestUid': sRequestUid,
'Uid': Utils.trim(sUid),
'Tags': Utils.trim(sTags),
'Properties': aProperties
});
};

View file

@ -83,6 +83,7 @@ function WebMailDataStorage()
this.identitiesLoading = ko.observable(false).extend({'throttle': 100});
// contacts
this.contactTags = ko.observableArray([]);
this.contacts = ko.observableArray([]);
this.contacts.loading = ko.observable(false).extend({'throttle': 200});
this.contacts.importing = ko.observable(false).extend({'throttle': 200});

View file

@ -279,6 +279,27 @@
-webkit-overflow-scrolling: touch;
}
.tags-property-container {
font-size: 18px;
width: 400px;
.inputosaurus-container {
border-width: 1px;
border-color: transparent;
.box-shadow(none);
&:hover {
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.075));
border-color: #ccc;
}
&.inputosaurus-focused {
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.075));
border-color: #999;
}
}
}
.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic {
height: 20px;
line-height: 20px;

View file

@ -177,7 +177,7 @@ html.ssm-state-tablet {
html.rl-left-panel-disabled {
#rl-left {
width: 65px !important;
width: 60px !important;
.show-on-panel-disabled {
display: block;
@ -201,7 +201,7 @@ html.rl-left-panel-disabled {
}
#rl-right {
left: 65px !important;
left: 60px !important;
}
}

View file

@ -1,5 +1,6 @@
.inputosaurus-container {
width: 99%;
line-height: 20px;
padding: 2px;
@ -9,6 +10,12 @@
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.075));
.transition(~"border linear .2s, box-shadow linear .2s");
&.inputosaurus-focused {
background-color: #fff;
border: @rlInputBorderSize solid darken(@inputBorder, 20%);
.box-shadow(none);
}
li {
max-width: 500px;
background-color: #eee;

View file

@ -120,6 +120,7 @@ function MailBoxMessageViewViewModel()
}, this.messageVisibility);
// viewer
this.viewHash = '';
this.viewSubject = ko.observable('');
this.viewFromShort = ko.observable('');
this.viewToShort = ko.observable('');
@ -152,6 +153,12 @@ function MailBoxMessageViewViewModel()
if (oMessage)
{
if (this.viewHash !== oMessage.hash)
{
this.scrollMessageToTop();
}
this.viewHash = oMessage.hash;
this.viewSubject(oMessage.subject());
this.viewFromShort(oMessage.fromToLine(true, true));
this.viewToShort(oMessage.toToLine(true, true));
@ -179,6 +186,11 @@ function MailBoxMessageViewViewModel()
}
});
}
else
{
this.viewHash = '';
this.scrollMessageToTop();
}
}, this);
@ -202,10 +214,6 @@ function MailBoxMessageViewViewModel()
}
});
this.messageActiveDom.subscribe(function () {
this.scrollMessageToTop();
}, this);
this.goUpCommand = Utils.createCommand(this, function () {
RL.pub('mailbox.message-list.selector.go-up');
});

View file

@ -24,6 +24,7 @@ function PopupsContactsViewModel()
this.search = ko.observable('');
this.contactsCount = ko.observable(0);
this.contacts = RL.data().contacts;
this.contactTags = RL.data().contactTags;
this.currentContact = ko.observable(null);
@ -44,6 +45,21 @@ function PopupsContactsViewModel()
this.viewReadOnly = ko.observable(false);
this.viewProperties = ko.observableArray([]);
this.viewTags = ko.observable('');
this.viewTags.visibility = ko.observable(false);
this.viewTags.focusTrigger = ko.observable(false);
this.viewTags.focusTrigger.subscribe(function (bValue) {
if (!bValue && '' === this.viewTags())
{
this.viewTags.visibility(false);
}
else if (bValue)
{
this.viewTags.visibility(true);
}
}, this);
this.viewSaveTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
this.viewPropertiesNames = this.viewProperties.filter(function(oProperty) {
@ -291,7 +307,7 @@ function PopupsContactsViewModel()
}, 1000);
}
}, sRequestUid, this.viewID(), aProperties);
}, sRequestUid, this.viewID(), this.viewTags(), aProperties);
}, function () {
var
@ -324,7 +340,7 @@ function PopupsContactsViewModel()
this.watchHash = ko.observable(false);
this.viewHash = ko.computed(function () {
return '' + _.map(self.viewProperties(), function (oItem) {
return '' + self.viewTags() + '|' + _.map(self.viewProperties(), function (oItem) {
return oItem.value();
}).join('');
});
@ -385,6 +401,12 @@ PopupsContactsViewModel.prototype.addNewOrFocusProperty = function (sType, sType
}
};
PopupsContactsViewModel.prototype.addNewTag = function ()
{
this.viewTags.visibility(true);
this.viewTags.focusTrigger(true);
};
PopupsContactsViewModel.prototype.addNewEmail = function ()
{
this.addNewProperty(Enums.ContactPropertyType.Email, 'Home');
@ -562,6 +584,7 @@ PopupsContactsViewModel.prototype.populateViewContact = function (oContact)
this.emptySelection(false);
this.viewReadOnly(false);
this.viewTags('');
if (oContact)
{
@ -587,9 +610,14 @@ PopupsContactsViewModel.prototype.populateViewContact = function (oContact)
});
}
this.viewTags(oContact.tags);
this.viewReadOnly(!!oContact.readOnly);
}
this.viewTags.focusTrigger.valueHasMutated();
this.viewTags.visibility('' !== this.viewTags());
aList.unshift(new ContactPropertyModel(Enums.ContactPropertyType.LastName, '', sLastName, false,
this.getPropertyPlceholder(Enums.ContactPropertyType.LastName)));
@ -626,7 +654,8 @@ PopupsContactsViewModel.prototype.reloadContactList = function (bDropPagePositio
RL.remote().contacts(function (sResult, oData) {
var
iCount = 0,
aList = []
aList = [],
aTagsList = []
;
if (Enums.StorageResultType.Success === sResult && oData && oData.Result && oData.Result.List)
@ -643,14 +672,26 @@ PopupsContactsViewModel.prototype.reloadContactList = function (bDropPagePositio
iCount = Utils.pInt(oData.Result.Count);
iCount = 0 < iCount ? iCount : 0;
}
if (Utils.isNonEmptyArray(oData.Result.Tags))
{
aTagsList = _.map(oData.Result.Tags, function (oItem) {
var oContactTag = new ContactTagModel();
return oContactTag.parse(oItem) ? oContactTag : null;
});
aTagsList = _.compact(aTagsList);
}
}
self.contactsCount(iCount);
self.contacts(aList);
self.viewClearSearch('' !== self.search());
self.contactTags(aTagsList);
self.contacts.loading(false);
self.viewClearSearch('' !== self.search());
}, iOffset, Consts.Defaults.ContactsPerPage, this.search());
};

View file

@ -4973,11 +4973,17 @@ class Actions
$iLimit = 0 > $iLimit ? 20 : $iLimit;
$iResultCount = 0;
if ($this->AddressBookProvider($oAccount)->IsActive())
$mResult = array();
$aTags = array();
$oAbp = $this->AddressBookProvider($oAccount);
if ($oAbp->IsActive())
{
$iResultCount = 0;
$mResult = $this->AddressBookProvider($oAccount)->GetContacts($oAccount->ParentEmailHelper(),
$mResult = $oAbp->GetContacts($oAccount->ParentEmailHelper(),
$iOffset, $iLimit, $sSearch, $iResultCount);
$aTags = $oAbp->GetContactTags($oAccount->ParentEmailHelper());
}
return $this->DefaultResponse(__FUNCTION__, array(
@ -4985,6 +4991,26 @@ class Actions
'Limit' => $iLimit,
'Count' => $iResultCount,
'Search' => $sSearch,
'List' => $mResult,
'Tags' => $aTags
));
}
/**
* @return array
*/
public function DoContactTags()
{
$oAccount = $this->getAccountFromToken();
$mResult = false;
if ($this->AddressBookProvider($oAccount)->IsActive())
{
$mResult = $this->AddressBookProvider($oAccount)->GetContactTags($oAccount->ParentEmailHelper());
}
return $this->DefaultResponse(__FUNCTION__, array(
'List' => $mResult
));
}
@ -5025,6 +5051,11 @@ class Actions
if ($oAddressBookProvider && $oAddressBookProvider->IsActive() && 0 < \strlen($sRequestUid))
{
$sUid = \trim($this->GetActionParam('Uid', ''));
$sTags = \trim($this->GetActionParam('Tags', ''));
$aTags = \explode(';', $sTags);
$aTags = \array_map('trim', $aTags);
$aTags = \array_unique($aTags);
$oContact = null;
if (0 < \strlen($sUid))
{
@ -5040,6 +5071,7 @@ class Actions
}
}
$oContact->Tags = $aTags;
$oContact->Properties = array();
$aProperties = $this->GetActionParam('Properties', array());
if (\is_array($aProperties))
@ -6933,9 +6965,19 @@ class Actions
'Display' => \MailSo\Base\Utils::Utf8Clear($mResponse->Display),
'ReadOnly' => $mResponse->ReadOnly,
'IdPropertyFromSearch' => $mResponse->IdPropertyFromSearch,
'Tags' => $this->responseObject($mResponse->Tags, $sParent, $aParameters),
'Properties' => $this->responseObject($mResponse->Properties, $sParent, $aParameters)
));
}
else if ('RainLoop\Providers\AddressBook\Classes\Tag' === $sClassName)
{
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array(
/* @var $mResponse \RainLoop\Providers\AddressBook\Classes\Tag */
'IdContactTag' => $mResponse->IdContactTag,
'Name' => \MailSo\Base\Utils::Utf8Clear($mResponse->Name),
'ReadOnly' => $mResponse->ReadOnly
));
}
else if ('RainLoop\Providers\AddressBook\Classes\Property' === $sClassName)
{
// Simple hack

View file

@ -224,13 +224,13 @@ abstract class PdoAbstract
}
/**
* @param string $sSql
* @param mixed $mData
*/
protected function writeLog($sSql)
protected function writeLog($mData)
{
if ($this->oLogger)
{
$this->oLogger->WriteMixed($sSql, \MailSo\Log\Enumerations\Type::INFO, 'SQL');
$this->oLogger->WriteMixed($mData, \MailSo\Log\Enumerations\Type::INFO, 'SQL');
}
}

View file

@ -123,6 +123,16 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider
$iOffset, $iLimit, $sSearch, $iResultCount) : array();
}
/**
* @param string $sEmail
*
* @return array
*/
public function GetContactTags($sEmail)
{
return $this->IsActive() ? $this->oDriver->GetContactTags($sEmail) : array();
}
/**
* @param string $sEmail
* @param string $mID

View file

@ -37,6 +37,11 @@ class Contact
/**
* @var array
*/
public $Tags;
/**
* @var bool
*/
public $ReadOnly;
/**
@ -58,10 +63,10 @@ class Contact
{
$this->IdContact = '';
$this->IdContactStr = '';
$this->IdUser = 0;
$this->Display = '';
$this->Changed = \time();
$this->Properties = array();
$this->Tags = array();
$this->ReadOnly = false;
$this->IdPropertyFromSearch = 0;
$this->Etag = '';

View file

@ -0,0 +1,33 @@
<?php
namespace RainLoop\Providers\AddressBook\Classes;
class Tag
{
/**
* @var string
*/
public $IdContactTag;
/**
* @var string
*/
public $Name;
/**
* @var bool
*/
public $ReadOnly;
public function __construct()
{
$this->Clear();
}
public function Clear()
{
$this->IdContactTag = '';
$this->Name = '';
$this->ReadOnly = false;
}
}

View file

@ -233,6 +233,9 @@ class PdoAddressBook
$oClient = new \Sabre\DAV\Client($aSettings);
$oClient->setVerifyPeer(false);
$this->oLogger->AddSecret($sPassword);
$this->oLogger->Write('User: '.$aSettings['userName'].', Url: '.$sUrl, \MailSo\Log\Enumerations\Type::INFO, 'DAV');
$aRemoteSyncData = $this->prepearRemoteSyncData($oClient, $sPath);
if (false === $aRemoteSyncData)
{
@ -465,6 +468,14 @@ class PdoAddressBook
':id_contact' => array($iIdContact, \PDO::PARAM_INT)
)
);
// clear previos tags
$this->prepareAndExecute(
'DELETE FROM rainloop_ab_tags_contacts WHERE id_contact = :id_contact',
array(
':id_contact' => array($iIdContact, \PDO::PARAM_INT)
)
);
}
else
{
@ -517,6 +528,44 @@ class PdoAddressBook
'(:id_contact, :id_user, :prop_type, :prop_type_str, :prop_value, :prop_value_custom, :prop_frec)';
$this->prepareAndExecute($sSql, $aParams, true);
$aTags = array();
$aContactTags = $this->GetContactTags($sEmail);
$fFindFunc = function ($sName) use ($aContactTags) {
$iResult = 0;
foreach ($aContactTags as $oItem)
{
if ($oItem && $oItem->Name === $sName)
{
$iResult = (int) $oItem->IdContactTag;
break;
}
}
return $iResult;
};
foreach ($oContact->Tags as $sTagName)
{
$iTagID = $fFindFunc($sTagName);
if (0 >= $iTagID)
{
$iTagID = $this->CreateTag($sEmail, $sTagName, false);
}
if (\is_int($iTagID) && 0 < $iTagID)
{
$aTags[] = array(
':id_tag' => array($iTagID, \PDO::PARAM_INT),
':id_contact' => array($iIdContact, \PDO::PARAM_INT)
);
}
}
$sSql = 'INSERT INTO rainloop_ab_tags_contacts (id_tag, id_contact) VALUES (:id_tag, :id_contact)';
$this->prepareAndExecute($sSql, $aTags, true);
}
}
catch (\Exception $oException)
@ -537,6 +586,64 @@ class PdoAddressBook
return 0 < $iIdContact;
}
/**
* @param string $sEmail
* @param string $sName
* @param bool $bSyncDb = true
*
* @return bool|int
*/
public function CreateTag($sEmail, $sName, $bSyncDb = true)
{
if ($bSyncDb)
{
$this->SyncDatabase();
}
$iUserID = $this->getUserId($sEmail);
$mResult = false;
try
{
if ($this->isTransactionSupported())
{
$this->beginTransaction();
}
$sSql = 'INSERT INTO rainloop_ab_tags '.
'(id_user, tag_name) VALUES (:id_user, :tag_name)';
$this->prepareAndExecute($sSql,
array(
':id_user' => array($iUserID, \PDO::PARAM_INT),
':tag_name' => array($sName, \PDO::PARAM_STR)
)
);
$sLast = $this->lastInsertId('rainloop_ab_contacts', 'id_contact');
if (\is_numeric($sLast) && 0 < (int) $sLast)
{
$mResult = (int) $sLast;
}
}
catch (\Exception $oException)
{
if ($this->isTransactionSupported())
{
$this->rollBack();
}
throw $oException;
}
if ($this->isTransactionSupported())
{
$this->commit();
}
return $mResult;
}
/**
* @param string $sEmail
* @param array $aContactIds
@ -704,6 +811,7 @@ class PdoAddressBook
$iResultCount = $iCount;
$aResult = array();
if (0 < $iCount)
{
$sSql = 'SELECT * FROM rainloop_ab_contacts WHERE deleted = 0 AND id_user = :id_user';
@ -794,13 +902,136 @@ class PdoAddressBook
$oItem->UpdateDependentValues();
}
return \array_values($aContacts);
$aResult = \array_values($aContacts);
}
}
}
}
return array();
if (\is_array($aResult) && 0 < \count($aResult))
{
$aIdContacts = array();
$aIdTagsContacts = array();
$aTags = $this->GetContactTags($sEmail);
if (\is_array($aTags) && 0 < \count($aTags))
{
foreach ($aResult as $oItem)
{
$aIdContacts[$oItem->IdContact] = true;
}
if (0 < \count($aIdContacts))
{
$sSql = 'SELECT id_tag, id_contact FROM rainloop_ab_tags_contacts WHERE id_contact IN ('.\implode(',', \array_keys($aIdContacts)).')';
$oStmt = $this->prepareAndExecute($sSql);
if ($oStmt)
{
$aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
if (\is_array($aFetch) && 0 < \count($aFetch))
{
foreach ($aFetch as $aItem)
{
if ($aItem && isset($aItem['id_tag'], $aItem['id_contact']))
{
$sID = (string) $aItem['id_contact'];
if (!isset($aIdTagsContacts[$sID]))
{
$aIdTagsContacts[$sID] = array();
}
$aIdTagsContacts[$sID][] = (string) $aItem['id_tag'];
}
}
}
unset($aFetch);
}
if (0 < \count($aIdTagsContacts))
{
$fFindFunc = function ($sID) use ($aTags) {
foreach ($aTags as $oItem)
{
if ($oItem && (string) $oItem->IdContactTag === $sID)
{
return\trim($oItem->Name);
}
}
return '';
};
foreach ($aResult as &$oItem)
{
$sID = $oItem ? (string) $oItem->IdContact : '';
if (0 < \strlen($sID) && isset($aIdTagsContacts[$sID]) && \is_array($aIdTagsContacts[$sID]))
{
$aNames = array();
foreach ($aIdTagsContacts[$sID] as $sSubID)
{
$sName = $fFindFunc($sSubID);
if (0 < \strlen($sName))
{
$aNames[] = $sName;
}
}
$oItem->Tags = $aNames;
}
}
}
}
}
}
return $aResult;
}
/**
* @param string $sEmail
*
* @return array
*/
public function GetContactTags($sEmail)
{
$this->SyncDatabase();
$iUserID = $this->getUserId($sEmail);
$sSql = 'SELECT id_tag, id_user, tag_name FROM rainloop_ab_tags WHERE id_user = :id_user ORDER BY tag_name ASC';
$aParams = array(
':id_user' => array($iUserID, \PDO::PARAM_INT)
);
$aResult = array();
$oStmt = $this->prepareAndExecute($sSql, $aParams);
if ($oStmt)
{
$aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
if (\is_array($aFetch) && 0 < \count($aFetch))
{
foreach ($aFetch as $aItem)
{
$iIdContactTag = $aItem && isset($aItem['id_tag']) ? (int) $aItem['id_tag'] : 0;
if (0 < $iIdContactTag)
{
$oContactTag = new \RainLoop\Providers\AddressBook\Classes\Tag();
$oContactTag->IdContactTag = (string) $iIdContactTag;
$oContactTag->Name = isset($aItem['tag_name']) ? (string) $aItem['tag_name'] : '';
$oContactTag->ReadOnly = $iUserID !== (isset($aItem['id_user']) ? (int) $aItem['id_user'] : 0);
$aResult[] = $oContactTag;
}
}
}
}
return $aResult;
}
/**
@ -1506,7 +1737,7 @@ SQLITEINITIAL;
break;
}
return false;
return $mCache;
}
/**

View file

@ -315,7 +315,7 @@ class Client {
// Return headers as part of the response
CURLOPT_HEADER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_USERAGENT => 'RainLoop DAV Client',
CURLOPT_USERAGENT => 'RainLoop DAV Client', // TODO rainloop
// Automatically follow redirects
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
@ -323,6 +323,10 @@ class Client {
if($this->verifyPeer !== null) {
$curlSettings[CURLOPT_SSL_VERIFYPEER] = $this->verifyPeer;
// TODO rainloop
if (!$this->verifyPeer) {
$curlSettings[CURLOPT_SSL_VERIFYHOST] = 0;
} // ---
}
if($this->trustedCertificates) {
@ -459,6 +463,7 @@ class Client {
// @codeCoverageIgnoreStart
protected function curlRequest($url, $settings) {
// TODO rainloop
$curl = curl_init($url);
$sSafeMode = strtolower(trim(@ini_get('safe_mode')));
$bSafeMode = 'on' === $sSafeMode || '1' === $sSafeMode;

View file

@ -126,25 +126,41 @@
<ul class="dropdown-menu g-ui-menu" style="text-align: left" tabindex="-1" role="menu" aria-labelledby="button-add-prop-dropdown-id">
<li class="e-item" role="presentation">
<a class="e-link menuitem" href="#" tabindex="-1" data-bind="click: addNewEmail">
<i class="icon-none"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n-text="CONTACTS/ADD_MENU_EMAIL"></span>
</a>
</li>
<li class="e-item" role="presentation">
<a class="e-link menuitem" href="#" tabindex="-1" data-bind="click: addNewPhone">
<i class="icon-none"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n-text="CONTACTS/ADD_MENU_PHONE"></span>
</a>
</li>
<li class="e-item" role="presentation">
<a class="e-link menuitem" href="#" tabindex="-1" data-bind="click: addNewWeb">
<i class="icon-none"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n-text="CONTACTS/ADD_MENU_URL"></span>
</a>
</li>
<li class="divider" role="presentation"></li>
<li class="e-item" role="presentation">
<a class="e-link menuitem" href="#" tabindex="-1" data-bind="click: addNewNickname">
<i class="icon-none"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n-text="CONTACTS/ADD_MENU_NICKNAME"></span>
</a>
</li>
<li class="divider" role="presentation"></li>
<li class="e-item" role="presentation">
<a class="e-link menuitem" href="#" tabindex="-1" data-bind="click: addNewTag">
<i class="icon-tags"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n-text="CONTACTS/ADD_MENU_TAGS"></span>
</a>
</li>
<!-- <li class="e-item" role="presentation">
<a class="e-link menuitem" href="#" tabindex="-1" data-bind="click: addNewAddress">
<span class="i18n" data-i18n-text="CONTACTS/ADD_MENU_ADDRESS"></span>
@ -247,6 +263,18 @@
</div>
</div>
</div>
<div class="control-group" data-bind="visible: !viewReadOnly() && viewTags.visibility()">
<label class="control-label remove-padding-top fix-width">
<i class="icon-tags iconsize24" data-tooltip-placement="left" data-bind="tooltip: 'CONTACTS/LABEL_TAGS'"></i>
</label>
<div class="controls fix-width">
<div class="property-line">
<div class="tags-property-container">
<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" data-bind="contactTags: viewTags" />
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="controls fix-width">
<br />

View file

@ -39,8 +39,8 @@
<br />
<select class="span2" data-bind="value: imapSecure">
<option value="0">None</option>
<option value="1">SSL</option>
<option value="2">TLS</option>
<option value="1">SSL/TLS</option>
<option value="2">STARTTLS</option>
</select>
<br />
<br />
@ -72,8 +72,8 @@
<br />
<select class="span2" data-bind="value: smtpSecure">
<option value="0">None</option>
<option value="1">SSL</option>
<option value="2">TLS</option>
<option value="1">SSL/TLS</option>
<option value="2">STARTTLS</option>
</select>
<br />
<br />

View file

@ -167,6 +167,7 @@ LABEL_EMAIL = "Email"
LABEL_PHONE = "Phone"
LABEL_WEB = "Web"
LABEL_BIRTHDAY = "Birthday"
LABEL_TAGS = "Tags"
LINK_ADD_EMAIL = "Add an email address"
LINK_ADD_PHONE = "Add a phone"
LINK_BIRTHDAY = "Birthday"
@ -184,6 +185,7 @@ ADD_MENU_PHONE = "Phone"
ADD_MENU_URL = "URL"
ADD_MENU_ADDRESS = "Address"
ADD_MENU_BIRTHDAY = "Birthday"
ADD_MENU_TAGS = "Tags"
BUTTON_SHARE_NONE = "None"
BUTTON_SHARE_ALL = "Everyone"
BUTTON_SYNC = "Synchronization (CardDAV)"

View file

@ -6522,6 +6522,13 @@ html.no-rgba .modal {
-o-transition: border linear .2s, box-shadow linear .2s;
transition: border linear .2s, box-shadow linear .2s;
}
.inputosaurus-container.inputosaurus-focused {
background-color: #fff;
border: 1px solid #999999;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.inputosaurus-container li {
max-width: 500px;
background-color: #eee;
@ -7101,7 +7108,7 @@ html.ssm-state-tablet .b-contacts-content.modal .contactValueInput {
display: none;
}
html.rl-left-panel-disabled #rl-left {
width: 65px !important;
width: 60px !important;
}
html.rl-left-panel-disabled #rl-left .show-on-panel-disabled {
display: block;
@ -7121,7 +7128,7 @@ html.rl-left-panel-disabled #rl-left.ui-state-disabled {
filter: alpha(opacity=100);
}
html.rl-left-panel-disabled #rl-right {
left: 65px !important;
left: 60px !important;
}
.ui-resizable-helper {
border-right: 5px solid #777;
@ -8637,6 +8644,29 @@ html.rl-message-fullscreen .messageView .b-content .buttonFull {
.b-contacts-content.modal .b-view-content .content {
-webkit-overflow-scrolling: touch;
}
.b-contacts-content.modal .b-view-content .tags-property-container {
font-size: 18px;
width: 400px;
}
.b-contacts-content.modal .b-view-content .tags-property-container .inputosaurus-container {
border-width: 1px;
border-color: transparent;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.b-contacts-content.modal .b-view-content .tags-property-container .inputosaurus-container:hover {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
border-color: #ccc;
}
.b-contacts-content.modal .b-view-content .tags-property-container .inputosaurus-container.inputosaurus-focused {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
border-color: #999;
}
.b-contacts-content.modal .b-view-content .contactValueStatic,
.b-contacts-content.modal .b-view-content .contactValueLargeStatic,
.b-contacts-content.modal .b-view-content .contactValueTextAreaStatic {

File diff suppressed because one or more lines are too long

View file

@ -1802,7 +1802,7 @@ Utils.setExpandedFolder = function (sFullNameHash, bExpanded)
Utils.initLayoutResizer = function (sLeft, sRight, sClientSideKeyName)
{
var
iDisabledWidth = 65,
iDisabledWidth = 60,
iMinWidth = 155,
oLeft = $(sLeft),
oRight = $(sRight),
@ -3169,11 +3169,19 @@ ko.bindingHandlers.emailsTags = {
'init': function(oElement, fValueAccessor) {
var
$oEl = $(oElement),
fValue = fValueAccessor()
fValue = fValueAccessor(),
fFocusCallback = function (bValue) {
if (fValue && fValue.focusTrigger)
{
fValue.focusTrigger(bValue);
}
}
;
$oEl.inputosaurus({
'parseOnBlur': true,
'allowDragAndDrop': true,
'focusCallback': fFocusCallback,
'inputDelimiters': [',', ';'],
'autoCompleteSource': function (oData, fResponse) {
RL.getAutocomplete(oData.term, function (aData) {
@ -3219,8 +3227,83 @@ ko.bindingHandlers.emailsTags = {
if (fValue.focusTrigger)
{
fValue.focusTrigger.subscribe(function () {
fValue.focusTrigger.subscribe(function (bValue) {
if (bValue)
{
$oEl.inputosaurus('focus');
}
});
}
}
};
ko.bindingHandlers.contactTags = {
'init': function(oElement, fValueAccessor) {
var
$oEl = $(oElement),
fValue = fValueAccessor(),
fFocusCallback = function (bValue) {
if (fValue && fValue.focusTrigger)
{
fValue.focusTrigger(bValue);
}
}
;
$oEl.inputosaurus({
'parseOnBlur': true,
'allowDragAndDrop': false,
'focusCallback': fFocusCallback,
'inputDelimiters': [';'],
'outputDelimiter': ';',
'autoCompleteSource': function (oData, fResponse) {
RL.getContactsTagsAutocomplete(oData.term, function (aData) {
fResponse(_.map(aData, function (oTagItem) {
return oTagItem.toLine(false);
}));
});
},
'parseHook': function (aInput) {
return _.map(aInput, function (sInputValue) {
var
sValue = Utils.trim(sInputValue),
oTag = null
;
if ('' !== sValue)
{
oTag = new ContactTagModel();
oTag.name(sValue);
return [oTag.toLine(false), oTag];
}
return [sValue, null];
});
},
'change': _.bind(function (oEvent) {
$oEl.data('ContactsTagsValue', oEvent.target.value);
fValue(oEvent.target.value);
}, this)
});
fValue.subscribe(function (sValue) {
if ($oEl.data('ContactsTagsValue') !== sValue)
{
$oEl.val(sValue);
$oEl.data('ContactsTagsValue', sValue);
$oEl.inputosaurus('refresh');
}
});
if (fValue.focusTrigger)
{
fValue.focusTrigger.subscribe(function (bValue) {
if (bValue)
{
$oEl.inputosaurus('focus');
}
});
}
}
@ -4057,15 +4140,22 @@ KnoinAbstractViewModel.prototype.restoreKeyScope = function ()
RL.data().keyScope(this.sCurrentKeyScope);
};
KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function ()
KnoinAbstractViewModel.prototype.registerPopupKeyDown = function ()
{
var self = this;
$window.on('keydown', function (oEvent) {
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility && self.modalVisibility())
if (oEvent && self.modalVisibility && self.modalVisibility())
{
if (!this.bDisabeCloseOnEsc && Enums.EventKeyCode.Esc === oEvent.keyCode)
{
Utils.delegateRun(self, 'cancelCommand');
return false;
}
else if (Enums.EventKeyCode.Backspace === oEvent.keyCode && !Utils.inFocus())
{
return false;
}
}
return true;
});
@ -4281,9 +4371,9 @@ Knoin.prototype.buildViewModel = function (ViewModelClass, oScreen)
ko.applyBindings(oViewModel, oViewModelDom[0]);
Utils.delegateRun(oViewModel, 'onBuild', [oViewModelDom]);
if (oViewModel && 'Popups' === sPosition && !oViewModel.bDisabeCloseOnEsc)
if (oViewModel && 'Popups' === sPosition)
{
oViewModel.registerPopupEscapeKey();
oViewModel.registerPopupKeyDown();
}
Plugins.runHook('view-model-post-build', [ViewModelClass.__name, oViewModel, oViewModelDom]);
@ -4911,6 +5001,50 @@ EmailModel.prototype.inputoTagLine = function ()
return 0 < this.name.length ? this.name + ' (' + this.email + ')' : this.email;
};
/**
* @constructor
*/
function ContactTagModel()
{
this.idContactTag = 0;
this.name = ko.observable('');
this.readOnly = false;
}
ContactTagModel.prototype.parse = function (oItem)
{
var bResult = false;
if (oItem && 'Object/Tag' === oItem['@Object'])
{
this.idContact = Utils.pInt(oItem['IdContactTag']);
this.name(Utils.pString(oItem['Name']));
this.readOnly = !!oItem['ReadOnly'];
bResult = true;
}
return bResult;
};
/**
* @param {string} sSearch
* @return {boolean}
*/
ContactTagModel.prototype.filterHelper = function (sSearch)
{
return this.name().toLowerCase().indexOf(sSearch.toLowerCase()) !== -1;
};
/**
* @param {boolean=} bEncodeHtml = false
* @return {string}
*/
ContactTagModel.prototype.toLine = function (bEncodeHtml)
{
return (Utils.isUnd(bEncodeHtml) ? false : !!bEncodeHtml) ?
Utils.encodeHtml(this.name()) : this.name();
};
/**
* @constructor
* @extends KnoinAbstractViewModel

File diff suppressed because one or more lines are too long

View file

@ -1806,7 +1806,7 @@ Utils.setExpandedFolder = function (sFullNameHash, bExpanded)
Utils.initLayoutResizer = function (sLeft, sRight, sClientSideKeyName)
{
var
iDisabledWidth = 65,
iDisabledWidth = 60,
iMinWidth = 155,
oLeft = $(sLeft),
oRight = $(sRight),
@ -3173,11 +3173,19 @@ ko.bindingHandlers.emailsTags = {
'init': function(oElement, fValueAccessor) {
var
$oEl = $(oElement),
fValue = fValueAccessor()
fValue = fValueAccessor(),
fFocusCallback = function (bValue) {
if (fValue && fValue.focusTrigger)
{
fValue.focusTrigger(bValue);
}
}
;
$oEl.inputosaurus({
'parseOnBlur': true,
'allowDragAndDrop': true,
'focusCallback': fFocusCallback,
'inputDelimiters': [',', ';'],
'autoCompleteSource': function (oData, fResponse) {
RL.getAutocomplete(oData.term, function (aData) {
@ -3223,8 +3231,83 @@ ko.bindingHandlers.emailsTags = {
if (fValue.focusTrigger)
{
fValue.focusTrigger.subscribe(function () {
fValue.focusTrigger.subscribe(function (bValue) {
if (bValue)
{
$oEl.inputosaurus('focus');
}
});
}
}
};
ko.bindingHandlers.contactTags = {
'init': function(oElement, fValueAccessor) {
var
$oEl = $(oElement),
fValue = fValueAccessor(),
fFocusCallback = function (bValue) {
if (fValue && fValue.focusTrigger)
{
fValue.focusTrigger(bValue);
}
}
;
$oEl.inputosaurus({
'parseOnBlur': true,
'allowDragAndDrop': false,
'focusCallback': fFocusCallback,
'inputDelimiters': [';'],
'outputDelimiter': ';',
'autoCompleteSource': function (oData, fResponse) {
RL.getContactsTagsAutocomplete(oData.term, function (aData) {
fResponse(_.map(aData, function (oTagItem) {
return oTagItem.toLine(false);
}));
});
},
'parseHook': function (aInput) {
return _.map(aInput, function (sInputValue) {
var
sValue = Utils.trim(sInputValue),
oTag = null
;
if ('' !== sValue)
{
oTag = new ContactTagModel();
oTag.name(sValue);
return [oTag.toLine(false), oTag];
}
return [sValue, null];
});
},
'change': _.bind(function (oEvent) {
$oEl.data('ContactsTagsValue', oEvent.target.value);
fValue(oEvent.target.value);
}, this)
});
fValue.subscribe(function (sValue) {
if ($oEl.data('ContactsTagsValue') !== sValue)
{
$oEl.val(sValue);
$oEl.data('ContactsTagsValue', sValue);
$oEl.inputosaurus('refresh');
}
});
if (fValue.focusTrigger)
{
fValue.focusTrigger.subscribe(function (bValue) {
if (bValue)
{
$oEl.inputosaurus('focus');
}
});
}
}
@ -4990,15 +5073,22 @@ KnoinAbstractViewModel.prototype.restoreKeyScope = function ()
RL.data().keyScope(this.sCurrentKeyScope);
};
KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function ()
KnoinAbstractViewModel.prototype.registerPopupKeyDown = function ()
{
var self = this;
$window.on('keydown', function (oEvent) {
if (oEvent && Enums.EventKeyCode.Esc === oEvent.keyCode && self.modalVisibility && self.modalVisibility())
if (oEvent && self.modalVisibility && self.modalVisibility())
{
if (!this.bDisabeCloseOnEsc && Enums.EventKeyCode.Esc === oEvent.keyCode)
{
Utils.delegateRun(self, 'cancelCommand');
return false;
}
else if (Enums.EventKeyCode.Backspace === oEvent.keyCode && !Utils.inFocus())
{
return false;
}
}
return true;
});
@ -5214,9 +5304,9 @@ Knoin.prototype.buildViewModel = function (ViewModelClass, oScreen)
ko.applyBindings(oViewModel, oViewModelDom[0]);
Utils.delegateRun(oViewModel, 'onBuild', [oViewModelDom]);
if (oViewModel && 'Popups' === sPosition && !oViewModel.bDisabeCloseOnEsc)
if (oViewModel && 'Popups' === sPosition)
{
oViewModel.registerPopupEscapeKey();
oViewModel.registerPopupKeyDown();
}
Plugins.runHook('view-model-post-build', [ViewModelClass.__name, oViewModel, oViewModelDom]);
@ -5852,6 +5942,7 @@ function ContactModel()
this.idContact = 0;
this.display = '';
this.properties = [];
this.tags = '';
this.readOnly = false;
this.focused = ko.observable(false);
@ -5902,6 +5993,7 @@ ContactModel.prototype.parse = function (oItem)
this.idContact = Utils.pInt(oItem['IdContact']);
this.display = Utils.pString(oItem['Display']);
this.readOnly = !!oItem['ReadOnly'];
this.tags = '';
if (Utils.isNonEmptyArray(oItem['Properties']))
{
@ -5913,6 +6005,11 @@ ContactModel.prototype.parse = function (oItem)
}, this);
}
if (Utils.isNonEmptyArray(oItem['Tags']))
{
this.tags = oItem['Tags'].join(';');
}
bResult = true;
}
@ -5990,6 +6087,50 @@ function ContactPropertyModel(iType, sTypeStr, sValue, bFocused, sPlaceholder)
}
/**
* @constructor
*/
function ContactTagModel()
{
this.idContactTag = 0;
this.name = ko.observable('');
this.readOnly = false;
}
ContactTagModel.prototype.parse = function (oItem)
{
var bResult = false;
if (oItem && 'Object/Tag' === oItem['@Object'])
{
this.idContact = Utils.pInt(oItem['IdContactTag']);
this.name(Utils.pString(oItem['Name']));
this.readOnly = !!oItem['ReadOnly'];
bResult = true;
}
return bResult;
};
/**
* @param {string} sSearch
* @return {boolean}
*/
ContactTagModel.prototype.filterHelper = function (sSearch)
{
return this.name().toLowerCase().indexOf(sSearch.toLowerCase()) !== -1;
};
/**
* @param {boolean=} bEncodeHtml = false
* @return {string}
*/
ContactTagModel.prototype.toLine = function (bEncodeHtml)
{
return (Utils.isUnd(bEncodeHtml) ? false : !!bEncodeHtml) ?
Utils.encodeHtml(this.name()) : this.name();
};
/**
* @constructor
*/
@ -9732,6 +9873,7 @@ function PopupsContactsViewModel()
this.search = ko.observable('');
this.contactsCount = ko.observable(0);
this.contacts = RL.data().contacts;
this.contactTags = RL.data().contactTags;
this.currentContact = ko.observable(null);
@ -9752,6 +9894,21 @@ function PopupsContactsViewModel()
this.viewReadOnly = ko.observable(false);
this.viewProperties = ko.observableArray([]);
this.viewTags = ko.observable('');
this.viewTags.visibility = ko.observable(false);
this.viewTags.focusTrigger = ko.observable(false);
this.viewTags.focusTrigger.subscribe(function (bValue) {
if (!bValue && '' === this.viewTags())
{
this.viewTags.visibility(false);
}
else if (bValue)
{
this.viewTags.visibility(true);
}
}, this);
this.viewSaveTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
this.viewPropertiesNames = this.viewProperties.filter(function(oProperty) {
@ -9999,7 +10156,7 @@ function PopupsContactsViewModel()
}, 1000);
}
}, sRequestUid, this.viewID(), aProperties);
}, sRequestUid, this.viewID(), this.viewTags(), aProperties);
}, function () {
var
@ -10032,7 +10189,7 @@ function PopupsContactsViewModel()
this.watchHash = ko.observable(false);
this.viewHash = ko.computed(function () {
return '' + _.map(self.viewProperties(), function (oItem) {
return '' + self.viewTags() + '|' + _.map(self.viewProperties(), function (oItem) {
return oItem.value();
}).join('');
});
@ -10093,6 +10250,12 @@ PopupsContactsViewModel.prototype.addNewOrFocusProperty = function (sType, sType
}
};
PopupsContactsViewModel.prototype.addNewTag = function ()
{
this.viewTags.visibility(true);
this.viewTags.focusTrigger(true);
};
PopupsContactsViewModel.prototype.addNewEmail = function ()
{
this.addNewProperty(Enums.ContactPropertyType.Email, 'Home');
@ -10270,6 +10433,7 @@ PopupsContactsViewModel.prototype.populateViewContact = function (oContact)
this.emptySelection(false);
this.viewReadOnly(false);
this.viewTags('');
if (oContact)
{
@ -10295,9 +10459,14 @@ PopupsContactsViewModel.prototype.populateViewContact = function (oContact)
});
}
this.viewTags(oContact.tags);
this.viewReadOnly(!!oContact.readOnly);
}
this.viewTags.focusTrigger.valueHasMutated();
this.viewTags.visibility('' !== this.viewTags());
aList.unshift(new ContactPropertyModel(Enums.ContactPropertyType.LastName, '', sLastName, false,
this.getPropertyPlceholder(Enums.ContactPropertyType.LastName)));
@ -10334,7 +10503,8 @@ PopupsContactsViewModel.prototype.reloadContactList = function (bDropPagePositio
RL.remote().contacts(function (sResult, oData) {
var
iCount = 0,
aList = []
aList = [],
aTagsList = []
;
if (Enums.StorageResultType.Success === sResult && oData && oData.Result && oData.Result.List)
@ -10351,14 +10521,26 @@ PopupsContactsViewModel.prototype.reloadContactList = function (bDropPagePositio
iCount = Utils.pInt(oData.Result.Count);
iCount = 0 < iCount ? iCount : 0;
}
if (Utils.isNonEmptyArray(oData.Result.Tags))
{
aTagsList = _.map(oData.Result.Tags, function (oItem) {
var oContactTag = new ContactTagModel();
return oContactTag.parse(oItem) ? oContactTag : null;
});
aTagsList = _.compact(aTagsList);
}
}
self.contactsCount(iCount);
self.contacts(aList);
self.viewClearSearch('' !== self.search());
self.contactTags(aTagsList);
self.contacts.loading(false);
self.viewClearSearch('' !== self.search());
}, iOffset, Consts.Defaults.ContactsPerPage, this.search());
};
@ -13187,6 +13369,7 @@ function MailBoxMessageViewViewModel()
}, this.messageVisibility);
// viewer
this.viewHash = '';
this.viewSubject = ko.observable('');
this.viewFromShort = ko.observable('');
this.viewToShort = ko.observable('');
@ -13219,6 +13402,12 @@ function MailBoxMessageViewViewModel()
if (oMessage)
{
if (this.viewHash !== oMessage.hash)
{
this.scrollMessageToTop();
}
this.viewHash = oMessage.hash;
this.viewSubject(oMessage.subject());
this.viewFromShort(oMessage.fromToLine(true, true));
this.viewToShort(oMessage.toToLine(true, true));
@ -13246,6 +13435,11 @@ function MailBoxMessageViewViewModel()
}
});
}
else
{
this.viewHash = '';
this.scrollMessageToTop();
}
}, this);
@ -13269,10 +13463,6 @@ function MailBoxMessageViewViewModel()
}
});
this.messageActiveDom.subscribe(function () {
this.scrollMessageToTop();
}, this);
this.goUpCommand = Utils.createCommand(this, function () {
RL.pub('mailbox.message-list.selector.go-up');
});
@ -15260,6 +15450,7 @@ function WebMailDataStorage()
this.identitiesLoading = ko.observable(false).extend({'throttle': 100});
// contacts
this.contactTags = ko.observableArray([]);
this.contacts = ko.observableArray([]);
this.contacts.loading = ko.observable(false).extend({'throttle': 200});
this.contacts.importing = ko.observable(false).extend({'throttle': 200});
@ -17390,11 +17581,12 @@ WebMailAjaxRemoteStorage.prototype.contacts = function (fCallback, iOffset, iLim
/**
* @param {?Function} fCallback
*/
WebMailAjaxRemoteStorage.prototype.contactSave = function (fCallback, sRequestUid, sUid, aProperties)
WebMailAjaxRemoteStorage.prototype.contactSave = function (fCallback, sRequestUid, sUid, sTags, aProperties)
{
this.defaultRequest(fCallback, 'ContactSave', {
'RequestUid': sRequestUid,
'Uid': Utils.trim(sUid),
'Tags': Utils.trim(sTags),
'Properties': aProperties
});
};
@ -19612,6 +19804,17 @@ RainLoopApp.prototype.getAutocomplete = function (sQuery, fCallback)
}, sQuery);
};
/**
* @param {string} sQuery
* @param {Function} fCallback
*/
RainLoopApp.prototype.getContactsTagsAutocomplete = function (sQuery, fCallback)
{
fCallback(_.filter(RL.data().contactTags(), function (oContactTag) {
return oContactTag && oContactTag.filterHelper(sQuery);
}));
};
RainLoopApp.prototype.emailsPicsHashes = function ()
{
RL.remote().emailsPicsHashes(function (sResult, oData) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -43,6 +43,10 @@
allowDuplicates : false,
allowDragAndDrop : true,
focusCallback : null,
parseOnBlur : false,
// optional wrapper for widget
@ -74,6 +78,9 @@
// Create the elements
els.ul = $('<ul class="inputosaurus-container"></ul>');
if (this.options.allowDragAndDrop)
{
els.ul.droppable({
'drop': function(event, ui) {
@ -88,13 +95,20 @@
widget.parseInput();
}
});
}
els.input = $('<input type="text" />');
// els.input = $('<input type="email" />');
els.input = $('<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />');
// els.input = $('<input type="email" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />');
els.inputCont = $('<li class="inputosaurus-input inputosaurus-required"></li>');
els.origInputCont = $('<li class="inputosaurus-input-hidden inputosaurus-required"></li>');
els.lastEdit = '';
els.input.on('focus', function () {
widget._focusTrigger(true);
}).on('blur', function () {
widget._focusTrigger(false);
});
// define starting placeholder
if (placeholder) {
o.placeholder = placeholder;
@ -127,6 +141,20 @@
this._instAutocomplete();
},
_focusTriggerTimer : 0,
_focusTrigger : function (bValue) {
var widget = this;
window.clearTimeout(this._focusTriggerTimer);
this._focusTriggerTimer = window.setTimeout(function () {
widget.elements.ul[!bValue ? 'removeClass' : 'addClass']('inputosaurus-focused');
if (widget.options.focusCallback)
{
widget.options.focusCallback(bValue);
}
}, 10);
},
_instAutocomplete : function() {
if(this.options.autoCompleteSource){
var widget = this;
@ -496,6 +524,9 @@
;
$li.data('inputosaurus-value', obj.toLine(false, false, false));
if (this.options.allowDragAndDrop)
{
$li.draggable({
'revert': 'invalid',
'revertDuration': 200,
@ -503,6 +534,7 @@
ui.helper.__widget = widget;
}
});
}
return $li;
}
@ -607,12 +639,14 @@
},
_attachEvents : function() {
var widget = this;
this.elements.input.on('keyup.inputosaurus', {widget : widget}, this._inputKeypress);
this.elements.input.on('keydown.inputosaurus', {widget : widget}, this._inputKeypress);
this.elements.input.on('change.inputosaurus', {widget : widget}, this._inputKeypress);
this.elements.input.on('focus.inputosaurus', {widget : widget}, this._inputFocus);
this.options.parseOnBlur && this.elements.input.on('blur.inputosaurus', {widget : widget}, this.parseInput);
this.elements.ul.on('click.inputosaurus', {widget : widget}, this._focus);
@ -626,14 +660,12 @@
_destroy: function() {
this.elements.input.unbind('.inputosaurus');
this.elements.ul.replaceWith(this.element);
}
};
$('body').append(inputosaurustext.fakeSpan);
$.widget("ui.inputosaurus", inputosaurustext);
})(jQuery);

File diff suppressed because one or more lines are too long