parent
22762b3bd4
commit
ca886aa662
6
NEWS
6
NEWS
|
@ -1,3 +1,9 @@
|
|||
4.0.6 (YYYY-MM-DD)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- [web] create card from sender or recipient address (#3002, #4610)
|
||||
|
||||
4.0.5 (2019-01-09)
|
||||
------------------
|
||||
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
|
||||
/* Address Popup menu */
|
||||
"Add to Address Book..." = "Add to Address Book...";
|
||||
"Successfully created card" = "Successfully created card";
|
||||
"Compose Mail To" = "Compose Mail To";
|
||||
"Create Filter From Message..." = "Create Filter From Message...";
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<var:string label:value="Search subfolders"/>
|
||||
</sg-checkmark>
|
||||
</md-menu-item>
|
||||
<md-menu-divider> <!-- divider --></md-menu-divider>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="app.search.match='AND'">
|
||||
<md-icon ng-class="{ 'icon-check': app.search.match == 'AND'}"
|
||||
|
@ -376,4 +376,42 @@
|
|||
</md-content>
|
||||
</div>
|
||||
|
||||
<!-- template of contextual menu for a recipient -->
|
||||
<script type="text/ng-template" id="UIxMailViewRecipientMenu">
|
||||
<div md-whiteframe="3">
|
||||
<md-menu-content class="md-dense" width="3">
|
||||
<md-menu-item>
|
||||
<md-button disabled="disabled" md-menu-align-target="md-menu-align-target">
|
||||
{{ $menuCtrl.recipient.full }}
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button type="button"
|
||||
ng-click="$menuCtrl.newMessage($event, { to: [$menuCtrl.recipient.full] })">
|
||||
<var:string label:value="Write a new message"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-menu md-position-mode="cascade cascade">
|
||||
<md-button
|
||||
label:aria-label="Add to Address Book..."
|
||||
ng-mouseenter="$mdMenu.open($event)"
|
||||
ng-keydown="$mdMenu.onKeyDown($event)">
|
||||
<var:string label:value="Add to Address Book..."/>
|
||||
</md-button>
|
||||
<md-menu-content class="md-dense" width="4">
|
||||
<md-menu-item ng-repeat="addressbook in $menuCtrl.addressbooks track by addressbook.id">
|
||||
<md-button ng-click="$menuCtrl.newCard($menuCtrl.recipient, addressbook.id)">{{addressbook.name}}</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item ng-repeat="addressbook in $menuCtrl.subscriptions track by addressbook.id">
|
||||
<md-button ng-click="$menuCtrl.newCard($menuCtrl.recipient, addressbook.id)"
|
||||
ng-disabled="!addressbook.acls.objectCreator">{{addressbook.name}}</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
</container>
|
||||
|
|
|
@ -127,56 +127,70 @@
|
|||
<h5 class="sg-md-headline" ng-bind="viewer.message.subject"><!-- subject --></h5>
|
||||
<time class="msg-date" datetime="viewer.message.date" ng-bind="::viewer.message.date"><!-- date --></time>
|
||||
</div>
|
||||
<div>
|
||||
<div layout="row" layout-wrap="layout-wrap">
|
||||
<div class="pseudo-input-container--compact" flex="50" flex-xs="100">
|
||||
<div layout="row" layout-align="start center">
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
sg-email="::viewer.message.from[0].email"
|
||||
size="40">person</sg-avatar-image>
|
||||
<div>
|
||||
<span ng-bind="::viewer.message.from[0].name"><!-- from --></span>
|
||||
<br/>
|
||||
<a href="#" class="md-caption"
|
||||
ng-bind="::viewer.message.from[0].email"
|
||||
ng-click="viewer.newMessage($event, { to: [viewer.message.from[0].full] })"><!-- from --></a>
|
||||
</div>
|
||||
<div layout="row" layout-wrap="layout-wrap">
|
||||
<div class="pseudo-input-container--compact" flex="50" flex-xs="100">
|
||||
<div layout="row" layout-align="start center">
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
sg-email="::viewer.message.from[0].email"
|
||||
size="40">person</sg-avatar-image>
|
||||
<div>
|
||||
<span ng-bind="::viewer.message.from[0].name"><!-- from --></span>
|
||||
<a href="#" class="md-caption"
|
||||
ng-bind="::viewer.message.from[0].email"
|
||||
ng-click="viewer.selectRecipient(viewer.message.from[0], $event)"><!-- from --></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-recipients" layout="column" flex="50" flex-xs="100">
|
||||
<div class="pseudo-input-container--compact">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="To"/>
|
||||
</label>
|
||||
<div class="pseudo-input-field"
|
||||
ng-hide="viewer.$showDetailedRecipients">
|
||||
<a href="#" ng-click="viewer.toggleDetailedRecipients($event)"
|
||||
ng-bind="::viewer.message.$shortRecipients(5)"><!-- to --></a>
|
||||
</div>
|
||||
<div class="pseudo-input-field" ng-show="viewer.$showDetailedRecipients">
|
||||
<span ng-repeat="recipient in viewer.message.to">
|
||||
<a href="#" ng-bind="::recipient.full"
|
||||
ng-click="viewer.newMessage($event, { to: [recipient.full] })"><!-- recipient --></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-recipients" layout="column" flex="50" flex-xs="100">
|
||||
<div class="pseudo-input-container--compact">
|
||||
<label class="pseudo-input-label">
|
||||
<var:string label:value="To"/>
|
||||
</label>
|
||||
<div class="pseudo-input-field" ng-hide="viewer.$showDetailedRecipients">
|
||||
<a href="#" ng-click="viewer.toggleDetailedRecipients($event)"
|
||||
ng-bind="::viewer.message.$shortRecipients(5)"><!-- to --></a>
|
||||
</div>
|
||||
<div class="pseudo-input-container--compact" ng-show="viewer.$showDetailedRecipients">
|
||||
<label class="pseudo-input-label" ng-show="::viewer.message.cc.length > 0">
|
||||
<var:string label:value="Cc"/>
|
||||
</label>
|
||||
<div class="pseudo-input-field">
|
||||
<span ng-repeat="recipient in ::viewer.message.cc">
|
||||
<a href="#" ng-bind="::recipient.full"
|
||||
ng-click="viewer.newMessage($event, { to: [recipient.full] })"><!-- recipient --></a>
|
||||
</span>
|
||||
<md-button style="float: right"
|
||||
label:aria-label="Hide"
|
||||
ng-click="viewer.toggleDetailedRecipients($event)">
|
||||
<var:string label:value="Hide"/>
|
||||
</md-button>
|
||||
</div>
|
||||
<div class="pseudo-input-field" ng-show="viewer.$showDetailedRecipients">
|
||||
<md-chips
|
||||
class="sg-dense sg-readonly"
|
||||
ng-model="::viewer.message.to"
|
||||
md-removable="::false"
|
||||
readonly="::true">
|
||||
<md-chip-template
|
||||
ng-click="viewer.selectRecipient($chip, $event)"
|
||||
ng-focus="viewer.focusChip($event)"
|
||||
ng-blur="viewer.blurChip($event)">
|
||||
{{ $chip.name || $chip.email }}
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pseudo-input-container--compact" ng-show="viewer.$showDetailedRecipients">
|
||||
<label class="pseudo-input-label" ng-show="::viewer.message.cc.length > 0">
|
||||
<var:string label:value="Cc"/>
|
||||
</label>
|
||||
<div class="pseudo-input-field" ng-show="::viewer.message.cc.length > 0">
|
||||
<md-chips
|
||||
class="sg-dense sg-readonly"
|
||||
ng-model="::viewer.message.cc"
|
||||
md-removable="::false"
|
||||
readonly="::true">
|
||||
<md-chip-template
|
||||
ng-click="viewer.selectRecipient($chip, $event)"
|
||||
ng-focus="viewer.focusChip($event)"
|
||||
ng-blur="viewer.blurChip($event)">
|
||||
{{ $chip.name || $chip.email }}
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
<md-button
|
||||
ng-hide="viewer.$alwaysShowDetailedRecipients || !viewer.$showDetailedRecipients"
|
||||
style="float: right"
|
||||
label:aria-label="Hide"
|
||||
ng-click="viewer.toggleDetailedRecipients($event)">
|
||||
<var:string label:value="Hide"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sg-padded" ng-show="viewer.showFlags">
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
$Preferences: Preferences,
|
||||
$query: {value: '', sort: 'c_cn', asc: 1},
|
||||
activeUser: Settings.activeUser(),
|
||||
$addressbooks: [],
|
||||
$subscriptions: [],
|
||||
$remotes: [],
|
||||
selectedFolder: null,
|
||||
$refreshTimeout: null
|
||||
});
|
||||
|
@ -159,10 +162,10 @@
|
|||
*/
|
||||
AddressBook.$findAll = function(data) {
|
||||
var _this = this;
|
||||
if (data) {
|
||||
this.$addressbooks = [];
|
||||
this.$subscriptions = [];
|
||||
this.$remotes = [];
|
||||
if (data && data.length) {
|
||||
this.$addressbooks.splice(0, this.$addressbooks.length);
|
||||
this.$subscriptions.splice(0, this.$subscriptions.length);
|
||||
this.$remotes.splice(0, this.$remotes.length);
|
||||
// Instanciate AddressBook objects
|
||||
angular.forEach(data, function(o, i) {
|
||||
var addressbook = new AddressBook(o);
|
||||
|
@ -174,6 +177,12 @@
|
|||
_this.$addressbooks.push(addressbook);
|
||||
});
|
||||
}
|
||||
else if (angular.isArray(data)) { // empty array
|
||||
return AddressBook.$$resource.fetch('addressbooksList').then(function(data) {
|
||||
return AddressBook.$findAll(data.addressbooks);
|
||||
});
|
||||
}
|
||||
|
||||
return _.union(this.$addressbooks, this.$subscriptions, this.$remotes);
|
||||
};
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
/**
|
||||
* @ngInject
|
||||
*/
|
||||
MessageController.$inject = ['$window', '$scope', '$q', '$state', '$mdMedia', '$mdDialog', 'sgConstant', 'stateAccounts', 'stateAccount', 'stateMailbox', 'stateMessage', 'sgHotkeys', 'encodeUriFilter', 'sgSettings', 'ImageGallery', 'sgFocus', 'Dialog', 'Preferences', 'Calendar', 'Component', 'Account', 'Mailbox', 'Message'];
|
||||
function MessageController($window, $scope, $q, $state, $mdMedia, $mdDialog, sgConstant, stateAccounts, stateAccount, stateMailbox, stateMessage, sgHotkeys, encodeUriFilter, sgSettings, ImageGallery, focus, Dialog, Preferences, Calendar, Component, Account, Mailbox, Message) {
|
||||
MessageController.$inject = ['$window', '$scope', '$q', '$state', '$mdMedia', '$mdDialog', '$mdPanel', 'sgConstant', 'stateAccounts', 'stateAccount', 'stateMailbox', 'stateMessage', 'sgHotkeys', 'encodeUriFilter', 'sgSettings', 'ImageGallery', 'sgFocus', 'Dialog', 'Preferences', 'Calendar', 'Component', 'Account', 'Mailbox', 'Message', 'AddressBook', 'Card'];
|
||||
function MessageController($window, $scope, $q, $state, $mdMedia, $mdDialog, $mdPanel, sgConstant, stateAccounts, stateAccount, stateMailbox, stateMessage, sgHotkeys, encodeUriFilter, sgSettings, ImageGallery, focus, Dialog, Preferences, Calendar, Component, Account, Mailbox, Message, AddressBook, Card) {
|
||||
var vm = this, popupWindow = null, hotkeys = [];
|
||||
|
||||
this.$onInit = function() {
|
||||
|
@ -27,7 +27,8 @@
|
|||
this.service = Message;
|
||||
this.tags = { searchText: '', selected: '' };
|
||||
this.showFlags = stateMessage.flags && stateMessage.flags.length > 0;
|
||||
this.$showDetailedRecipients = false;
|
||||
this.$alwaysShowDetailedRecipients = (!stateMessage.to || stateMessage.to.length < 5) && (!stateMessage.cc || stateMessage.cc.length < 5);
|
||||
this.$showDetailedRecipients = this.$alwaysShowDetailedRecipients;
|
||||
this.showRawSource = false;
|
||||
|
||||
_registerHotkeys(hotkeys);
|
||||
|
@ -197,6 +198,108 @@
|
|||
$event.preventDefault();
|
||||
};
|
||||
|
||||
this.focusChip = function($event) {
|
||||
var chipElement = $event.target;
|
||||
while (chipElement.tagName !== 'MD-CHIP') {
|
||||
chipElement = chipElement.parentNode;
|
||||
}
|
||||
chipElement.classList.add('md-focused');
|
||||
};
|
||||
|
||||
this.blurChip = function($event) {
|
||||
var chipElement = $event.target;
|
||||
while (chipElement.tagName !== 'MD-CHIP') {
|
||||
chipElement = chipElement.parentNode;
|
||||
}
|
||||
chipElement.classList.remove('md-focused');
|
||||
if ($event.relatedTarget && $event.relatedTarget.tagName === 'MD-CHIP-TEMPLATE') {
|
||||
// Moving to another chip; close menu
|
||||
vm.panel.close();
|
||||
}
|
||||
};
|
||||
|
||||
this.selectRecipient = function(recipient, $event) {
|
||||
// Fetch addressbooks list
|
||||
AddressBook.$findAll([]);
|
||||
|
||||
var targetElement = $event.target;
|
||||
|
||||
var panelPosition = $mdPanel.newPanelPosition()
|
||||
.relativeTo(targetElement)
|
||||
.addPanelPosition(
|
||||
$mdPanel.xPosition.ALIGN_START,
|
||||
$mdPanel.yPosition.ALIGN_TOPS
|
||||
);
|
||||
|
||||
var panelAnimation = $mdPanel.newPanelAnimation()
|
||||
.openFrom(targetElement)
|
||||
.duration(100)
|
||||
.withAnimation($mdPanel.animation.FADE);
|
||||
|
||||
var config = {
|
||||
attachTo: angular.element(document.body),
|
||||
locals: {
|
||||
recipient: recipient,
|
||||
addressbooks: AddressBook.$addressbooks,
|
||||
subscriptions: AddressBook.$subscriptions,
|
||||
newMessage: angular.bind(this, this.newMessage)
|
||||
},
|
||||
bindToController: true,
|
||||
controller: MenuController,
|
||||
controllerAs: '$menuCtrl',
|
||||
position: panelPosition,
|
||||
animation: panelAnimation,
|
||||
targetEvent: $event,
|
||||
templateUrl: 'UIxMailViewRecipientMenu',
|
||||
trapFocus: true,
|
||||
clickOutsideToClose: true,
|
||||
escapeToClose: true,
|
||||
focusOnOpen: false
|
||||
};
|
||||
|
||||
$mdPanel.open(config)
|
||||
.then(function(panelRef) {
|
||||
vm.panel = panelRef;
|
||||
// Automatically close panel when clicking inside of it
|
||||
panelRef.panelEl.one('click', function() {
|
||||
panelRef.close();
|
||||
});
|
||||
});
|
||||
|
||||
MenuController.$inject = ['mdPanelRef', '$state', '$mdToast'];
|
||||
function MenuController(mdPanelRef, $state, $mdToast) {
|
||||
this.onKeyDown = function($event) {
|
||||
if ($event.which === 9) { // Tab
|
||||
mdPanelRef.close();
|
||||
}
|
||||
};
|
||||
|
||||
this.newCard = function(recipient, addressbookId) {
|
||||
var card = new Card({
|
||||
pid: addressbookId,
|
||||
c_cn: recipient.name,
|
||||
emails: [{ value: recipient.email }]
|
||||
});
|
||||
card.$id().then(function(id) {
|
||||
card.$save().then(function() {
|
||||
// Show success toast when action succeeds
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.content(l('Successfully created card'))
|
||||
.position('top right')
|
||||
.hideDelay(2000));
|
||||
});
|
||||
});
|
||||
mdPanelRef.close();
|
||||
};
|
||||
}
|
||||
|
||||
if (targetElement.tagName === 'A') {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
this.filterMailtoLinks = function($event) {
|
||||
var href, match, to, cc, bcc, subject, body, data;
|
||||
if ($event.target.tagName == 'A' && 'href' in $event.target.attributes) {
|
||||
|
@ -382,8 +485,10 @@
|
|||
};
|
||||
|
||||
this.newMessage = function($event, mailto) {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
if ($event.target.tagName === 'A') {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
}
|
||||
this.account.$newMessage({ mailto: mailto }).then(function(message) {
|
||||
_showMailEditor($event, message);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
/// chips.scss -*- Mode: scss; indent-tabs-mode: nil; basic-offset: 2 -*-
|
||||
@import 'extends';
|
||||
|
||||
$chip-dense-font-size: rem(1.2) !default;
|
||||
$chip-dense-height: rem(2.4) !default;
|
||||
$chip-dense-padding: 0 rem(0.8) 0 rem(0.8) !default;
|
||||
$chip-dense-margin: rem(0.6) rem(0.6) 0 0 !default;
|
||||
|
||||
md-chips {
|
||||
// Remove the line under the tags of the message viewer
|
||||
&.sg-readonly {
|
||||
|
@ -9,6 +14,9 @@ md-chips {
|
|||
&.md-focused {
|
||||
box-shadow: none;
|
||||
}
|
||||
md-chip-template:focus {
|
||||
outline: 0;
|
||||
}
|
||||
.md-chip-content {
|
||||
//max-width: initial; // fix bug in ng-material
|
||||
}
|
||||
|
@ -25,6 +33,15 @@ md-chips {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Small, compact chip
|
||||
&.sg-dense md-chip {
|
||||
height: $chip-dense-height;
|
||||
padding: $chip-dense-padding;
|
||||
@include rtl(margin, $chip-dense-margin, rtl-value($chip-dense-margin));
|
||||
font-size: $chip-dense-font-size;
|
||||
line-height: $chip-dense-height;
|
||||
}
|
||||
.sg-chip-progress {
|
||||
border-radius: $chip-height / 2;
|
||||
bottom: 0;
|
||||
|
|
|
@ -61,6 +61,9 @@
|
|||
padding-left: $mg;
|
||||
padding-right: $mg;
|
||||
}
|
||||
md-chip {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical buttons in header area of mail composer dialog
|
||||
|
|
Loading…
Reference in New Issue