493 lines
30 KiB
JavaScript
493 lines
30 KiB
JavaScript
"use strict";
|
|
(function () {
|
|
'use strict';
|
|
/**
|
|
* @ngdoc directive
|
|
* @name myitsmApp:emailWindow
|
|
* @restrict E
|
|
* @desription Email windows are used for user to send email based on context of current ticket. It's a draggable window which contains
|
|
* email controls bar. From email window one can search and include email recipients from ITSM DB . Once email is sent, it will also be logged to instance timeline
|
|
*/
|
|
angular.module('myitsmApp')
|
|
//TODO: remove not use dependency
|
|
.directive('emailWindow', ['$window', '$filter', '$timeout', '$state', 'ticketModel', 'emailModel', 'systemAlertService', 'events', 'accelerators', 'i18nService', 'relationModel', 'browser', 'elementFocus',
|
|
function ($window, $filter, $timeout, $state, ticketModel, emailModel, systemAlertService, events, accelerators, i18nService, relationModel, browser, elementFocus) {
|
|
return {
|
|
restrict: 'E',
|
|
templateUrl: 'components/email/email-window.html',
|
|
replace: true,
|
|
scope: {
|
|
emailInstance: '=',
|
|
isPopupWindow: '@'
|
|
},
|
|
link: function (scope, $element) {
|
|
var $textArea = $element.find('textarea.email__message-editor'), typeAheadMode = false, gotFullMatch = false, actualTypeAheadText, expressionStart = 0, expressionEnd = 0;
|
|
var keyCodes = {
|
|
enter: 13,
|
|
escape: 27,
|
|
tab: 9,
|
|
leftArrow: 37,
|
|
rightArrow: 39,
|
|
upArrow: 38,
|
|
downArrow: 40,
|
|
space: 32
|
|
};
|
|
var preventDefaultForKeys = [keyCodes.leftArrow, keyCodes.rightArrow, keyCodes.enter, keyCodes.upArrow, keyCodes.downArrow, keyCodes.tab];
|
|
var popupMode = !!scope.isPopupWindow;
|
|
if (popupMode) {
|
|
$window.document.body.style.minWidth = 0;
|
|
$window.document.body.style.overflow = 'hidden';
|
|
scope.emailContext = scope.emailInstance.parentEmailContext;
|
|
}
|
|
else {
|
|
$element.draggable({ containment: 'parent', scroll: false, handle: '.drag-handle' });
|
|
scope.emailContext = {
|
|
headerText: '',
|
|
recipientInputText: '',
|
|
senderList: scope.emailInstance.senderList || [],
|
|
subjectText: '',
|
|
messageBody: ''
|
|
};
|
|
if (scope.emailInstance.ticketData.length > 1) {
|
|
scope.emailContext.ticketList = _.map(scope.emailInstance.ticketData, function (ticket) {
|
|
return {
|
|
id: ticket.id,
|
|
type: ticket.type
|
|
};
|
|
});
|
|
}
|
|
else {
|
|
scope.emailContext.ticketType = scope.emailInstance.ticketData[0].type;
|
|
scope.emailContext.ticketId = scope.emailInstance.ticketData[0].id;
|
|
}
|
|
}
|
|
scope.sendingEmail = false;
|
|
scope.showArticles = false;
|
|
scope.articlesLoading = false;
|
|
scope.emailActions = {};
|
|
scope.accelerators = {
|
|
expression: '',
|
|
showAcceleratorsList: false,
|
|
availableExpressions: []
|
|
};
|
|
if (!scope.emailContext.ticketList) {
|
|
scope.accelerators.availableExpressions = _.filter(accelerators.ticket, function (accelerator) {
|
|
if (accelerator.onlyFor && accelerator.onlyFor.indexOf(scope.emailInstance.ticketData[0].type) < 0) {
|
|
return false;
|
|
}
|
|
accelerator.expr = '!' + i18nService.getLocalizedString(accelerator.key);
|
|
accelerator.text = scope.$eval(accelerator.valueExpr, { exprContext: scope.emailInstance.ticketData[0] });
|
|
if (accelerator.type) {
|
|
accelerator.text = $filter('localizeLabel')(accelerator.text, accelerator.type, scope.emailInstance.ticketData[0].type);
|
|
}
|
|
return accelerator.text ? accelerator.text.trim() : false;
|
|
});
|
|
}
|
|
/*List of available email window actions*/
|
|
scope.emailControls = [
|
|
{
|
|
iconClass: 'icon-trash',
|
|
name: 'deleteEmail',
|
|
inactive: false
|
|
},
|
|
{
|
|
iconClass: 'icon-pop_up',
|
|
name: 'popoutEmail',
|
|
inactive: !!scope.isPopupWindow
|
|
}
|
|
];
|
|
/*Email controls click event transmitter. Uses controls item, to execute applicable click handler */
|
|
scope.handleEmailControlsClick = function ($e, action) {
|
|
return scope.emailActions[action.name]();
|
|
};
|
|
//Backend allows 255 bytes, not 255 characters, so checking input length in bytes.
|
|
scope.checkByteLength = function (maxSize) {
|
|
if (scope.emailContext.subjectText.length > maxSize) {
|
|
scope.emailContext.subjectText = scope.emailContext.subjectText.substring(0, maxSize);
|
|
}
|
|
var byteLength = new Blob([scope.emailContext.subjectText]).size, strLength = scope.emailContext.subjectText.length;
|
|
while (byteLength > maxSize) {
|
|
strLength = strLength - Math.ceil((byteLength - strLength) / 4); //Max length of a character in UTF-8 is 4 byte currently
|
|
scope.emailContext.subjectText = scope.emailContext.subjectText.substring(0, strLength);
|
|
byteLength = new Blob([scope.emailContext.subjectText]).size;
|
|
}
|
|
};
|
|
/**
|
|
* @ngdoc method
|
|
* @name myitsmApp.emailWindow#leaveEmail
|
|
* @methodOf myitsmApp.emailWindow
|
|
*
|
|
* @description
|
|
* Action can be triggered from email controls, by clicking leave email button. It prompts
|
|
* confirmation modal to ensure user is intended to delete the email.
|
|
*
|
|
*/
|
|
scope.emailActions.deleteEmail = function () {
|
|
var modalSettings = {
|
|
title: $filter('i18n')('email.deleteEmailModal.title'),
|
|
text: $filter('i18n')('email.deleteEmailModal.label'),
|
|
buttons: [
|
|
{
|
|
text: $filter('i18n')('email.button.keepEmail')
|
|
},
|
|
{
|
|
text: $filter('i18n')('email.button.deleteEmail'),
|
|
data: { delete: true }
|
|
}
|
|
]
|
|
};
|
|
var modalInstance = systemAlertService.modal(modalSettings);
|
|
modalInstance.result.then(function (data) {
|
|
if (!_.isEmpty(data)) {
|
|
if (data.delete) {
|
|
if (scope.isPopupWindow) {
|
|
$window.close();
|
|
}
|
|
emailModel.deleteEmailWindow(scope.emailInstance);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
scope.emailActions.popoutEmail = function () {
|
|
scope.emailInstance.parentEmailContext = scope.emailContext;
|
|
$window.popupDetails = { emailWindow: scope.emailInstance };
|
|
var width = Math.round((screen.width / 3) / 10) * 10, height = Math.round(screen.height * 70 / 100);
|
|
$window.open('#/emailPopupWindow', '', 'location=no, scrollbars=no, resizable=yes, width=' + width + ', height=' + height);
|
|
emailModel.deleteEmailWindow(scope.emailInstance);
|
|
};
|
|
scope.sendEmail = function () {
|
|
var inputValue = $element.find('.email__recipient-list span').text(), rootScope = scope.$root, validEmailInput = false;
|
|
if (inputValue) {
|
|
validEmailInput = validateEmail(inputValue);
|
|
}
|
|
var internalRecip = _.filter(scope.emailContext.senderList, { internal: true }), externalRecip = _.filter(scope.emailContext.senderList, { internal: false });
|
|
if (validEmailInput) {
|
|
externalRecip.push({ email: inputValue, internal: false });
|
|
}
|
|
try {
|
|
rootScope = window.opener.angular.element('body').scope();
|
|
}
|
|
catch (e) {
|
|
}
|
|
scope.sendingEmail = true;
|
|
emailModel.sendEmail(scope.emailContext, internalRecip, externalRecip)
|
|
.finally(function () {
|
|
_.delay(function () {
|
|
rootScope.$broadcast(events.REFRESH_ACTIVITY_FEED);
|
|
}, 400);
|
|
emailModel.deleteEmailWindow(scope.emailInstance);
|
|
if (scope.isPopupWindow) {
|
|
window.close();
|
|
}
|
|
});
|
|
};
|
|
scope.handleEmailBodyKeyDown = function ($event) {
|
|
// disable for bulk email
|
|
if (scope.emailContext.ticketList) {
|
|
return;
|
|
}
|
|
if (($event.which === 49 || $event.keyCode === 49) && $event.shiftKey) {
|
|
scope.accelerators.showAcceleratorsList = true;
|
|
expressionStart = $event.target.selectionStart;
|
|
typeAheadMode = true;
|
|
// Tere is a open defect on IE 11, for incorrect definition of selectionStart propery after Enter click
|
|
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14692189/
|
|
// Workaround for IE
|
|
if (browser.isIE11 && $textArea[0].value.length < expressionStart) {
|
|
expressionStart = $textArea[0].value.length;
|
|
}
|
|
}
|
|
if (scope.accelerators.showAcceleratorsList && (preventDefaultForKeys.indexOf($event.keyCode) >= 0 || ($event.keyCode == keyCodes.space && gotFullMatch))) {
|
|
$event.preventDefault();
|
|
}
|
|
};
|
|
scope.handleEmailBodyChange = function ($event) {
|
|
// disable for bulk email
|
|
if (scope.emailContext.ticketList) {
|
|
return;
|
|
}
|
|
expressionEnd = $event.target.selectionEnd;
|
|
$textArea = $element.find('textarea.email__message-editor');
|
|
var textArea = $textArea[0];
|
|
if (textArea.value.length > 1 && typeAheadMode && (expressionStart > expressionEnd)) {
|
|
scope.accelerators.showAcceleratorsList = false;
|
|
return;
|
|
}
|
|
// When text blocks were cut and paste to the text area, individual adjacent text block could be formed
|
|
// Need to normalize textArea to merge adjacent text nodes before processing
|
|
if (textArea.normalize) {
|
|
textArea.normalize();
|
|
}
|
|
if (scope.accelerators.showAcceleratorsList) {
|
|
if (gotFullMatch && [keyCodes.tab, keyCodes.rightArrow, keyCodes.space].indexOf($event.keyCode) >= 0) {
|
|
scope.insertAcceleratorText(scope.acceleratorsList[0]);
|
|
gotFullMatch = false;
|
|
return;
|
|
}
|
|
if (typeAheadMode && ($event.keyCode == keyCodes.tab)) {
|
|
var emailText = textArea.value;
|
|
(scope.typeAheadListPos == scope.acceleratorsList.length - 1) ? scope.typeAheadListPos = 0 : scope.typeAheadListPos++;
|
|
var selectedItem = scope.acceleratorsList[scope.typeAheadListPos] ? scope.acceleratorsList[scope.typeAheadListPos].expr : '';
|
|
var updatedText = [emailText.slice(0, expressionStart), selectedItem, emailText.slice(expressionEnd)].join('');
|
|
expressionEnd = parseInt(expressionStart + selectedItem.length);
|
|
actualTypeAheadText = updatedText.substring(expressionStart, expressionEnd);
|
|
scope.emailContext.messageBody = updatedText;
|
|
$timeout(function () {
|
|
textArea.setSelectionRange(expressionStart, expressionEnd);
|
|
textArea.selectionStart = expressionStart;
|
|
textArea.selectionEnd = expressionEnd;
|
|
});
|
|
return;
|
|
}
|
|
// ignore if control or alternate key was pressed
|
|
if ($event.ctrlKey === true || $event.altKey === true || $event.keyCode === keyCodes.leftArrow || $event.keyCode === keyCodes.rightArrow) {
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.upArrow) { // up key
|
|
if (scope.typeAheadListPos > 0) {
|
|
scope.typeAheadListPos--;
|
|
}
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.downArrow) { // down key
|
|
if (scope.typeAheadListPos < (scope.acceleratorsList.length - 1)) {
|
|
scope.typeAheadListPos++;
|
|
}
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.enter) { // enter key
|
|
scope.accelerators.expression = actualTypeAheadText;
|
|
var selectedItem = _.filter(scope.accelerators.availableExpressions, { expr: scope.accelerators.expression })[0];
|
|
if (selectedItem) {
|
|
scope.insertAcceleratorText(selectedItem);
|
|
}
|
|
else {
|
|
scope.insertAcceleratorText(scope.acceleratorsList[scope.typeAheadListPos]);
|
|
}
|
|
return;
|
|
}
|
|
else if ($event.keyCode === keyCodes.escape) {
|
|
scope.disableTypeAheadMode();
|
|
return;
|
|
}
|
|
}
|
|
if (scope.emailContext.messageBody.length > 0) {
|
|
if (typeAheadMode) {
|
|
actualTypeAheadText = textArea.value.substring(expressionStart, expressionEnd);
|
|
scope.typeAheadListPos = 0;
|
|
}
|
|
}
|
|
else if (typeAheadMode) {
|
|
scope.hideTypeAheadPopup();
|
|
}
|
|
if (typeAheadMode) {
|
|
if (actualTypeAheadText.length > 0 && actualTypeAheadText != scope.accelerators.expression) {
|
|
scope.acceleratorsList = $filter('filter')(scope.accelerators.availableExpressions, { expr: actualTypeAheadText.substring(1) });
|
|
if (scope.acceleratorsList.length > 0 || scope.accelerators.expression.length == 0) {
|
|
(scope.acceleratorsList.length > 0) ? scope.accelerators.showAcceleratorsList = true : angular.noop();
|
|
scope.accelerators.expression = actualTypeAheadText;
|
|
scope.popupLeftPosition = 0;
|
|
scope.popupTopPosition = 0;
|
|
if (scope.acceleratorsList.length === 1) {
|
|
var accelerator = scope.acceleratorsList[0], regExpr = new RegExp(accelerator.expr, 'gi');
|
|
gotFullMatch = regExpr.test(actualTypeAheadText);
|
|
if (gotFullMatch) {
|
|
scope.emailContext.messageBody.replace(regExpr, accelerator.expr);
|
|
textArea.setSelectionRange(expressionStart, expressionEnd);
|
|
textArea.selectionStart = expressionStart;
|
|
textArea.selectionEnd = expressionEnd;
|
|
}
|
|
}
|
|
}
|
|
else if (scope.acceleratorsList.length === 0 && scope.accelerators.expression.length >= 1) {
|
|
scope.hideTypeAheadPopup();
|
|
scope.accelerators.expression = "";
|
|
}
|
|
}
|
|
}
|
|
};
|
|
scope.handleEmailBodyClick = function ($event) {
|
|
expressionEnd = $event.target.selectionEnd;
|
|
$textArea = $element.find('textarea.email__message-editor');
|
|
if ($textArea[0] && $textArea[0].value.length > 1 && scope.accelerators.showAcceleratorsList && expressionStart > expressionEnd) {
|
|
scope.accelerators.showAcceleratorsList = false;
|
|
return;
|
|
}
|
|
};
|
|
scope.handleTextPaste = function ($event) {
|
|
$textArea = $element.find('textarea.email__message-editor');
|
|
var content;
|
|
$event.preventDefault();
|
|
$event.stopImmediatePropagation();
|
|
if ($event.originalEvent && $event.originalEvent.clipboardData) {
|
|
content = $event.originalEvent.clipboardData.getData('text/plain');
|
|
}
|
|
else if ($window.clipboardData) {
|
|
content = $window.clipboardData.getData('Text');
|
|
}
|
|
else {
|
|
return; // not support, let the default handle it
|
|
}
|
|
var parsedContentVersions = [content];
|
|
_(scope.accelerators.availableExpressions).forEach(function (accelerator, index) {
|
|
var updatedText = parsedContentVersions[index];
|
|
var regExpr = new RegExp(accelerator.expr, 'gi'), replaceText = accelerator.text;
|
|
parsedContentVersions.push(updatedText.replace(regExpr, replaceText));
|
|
});
|
|
var emailText = $textArea[0].value, start = 0, end = 0;
|
|
if ($event.target.selectionStart == $event.target.selectionEnd && $event.target.selectionStart > 0) {
|
|
start = end = $event.target.selectionStart;
|
|
}
|
|
else if ($event.target.selectionStart != $event.target.selectionEnd) {
|
|
if ($event.target.selectionStart > $event.target.selectionEnd) {
|
|
start = $event.target.selectionEnd;
|
|
end = $event.target.selectionStart;
|
|
}
|
|
else {
|
|
start = $event.target.selectionStart;
|
|
end = $event.target.selectionEnd;
|
|
}
|
|
}
|
|
var pasteText = parsedContentVersions.pop();
|
|
scope.emailContext.messageBody = emailText.substring(0, start) + pasteText + emailText.substring(end, emailText.length);
|
|
};
|
|
scope.acceleratorMouseover = function ($index) {
|
|
scope.typeAheadListPos = $index;
|
|
};
|
|
scope.insertAcceleratorText = function (selectedItem) {
|
|
var emailText = $textArea[0].value, accelerator = selectedItem || scope.acceleratorsList[scope.typeAheadListPos], insertText = accelerator.text, updatedText = emailText;
|
|
if (scope.accelerators.expression) {
|
|
if (expressionStart === 0) {
|
|
updatedText = insertText + ' ';
|
|
}
|
|
else {
|
|
insertText += ' ';
|
|
updatedText = [emailText.slice(0, expressionStart), insertText, emailText.slice(expressionEnd)].join('');
|
|
}
|
|
}
|
|
else {
|
|
if (emailText.length === 0) {
|
|
updatedText = insertText;
|
|
}
|
|
else {
|
|
if (expressionEnd > 0) {
|
|
updatedText = [emailText.slice(0, expressionEnd), ' ' + insertText + ' ', emailText.slice(expressionEnd)].join('');
|
|
}
|
|
else {
|
|
updatedText += ' ' + insertText;
|
|
}
|
|
}
|
|
}
|
|
scope.disableTypeAheadMode();
|
|
scope.emailContext.messageBody = updatedText;
|
|
};
|
|
scope.hideTypeAheadPopup = function () {
|
|
scope.accelerators.showAcceleratorsList = false;
|
|
};
|
|
scope.disableTypeAheadMode = function () {
|
|
actualTypeAheadText = '';
|
|
scope.accelerators.expression = '';
|
|
scope.accelerators.showAcceleratorsList = false;
|
|
scope.typeAheadListPos = 0;
|
|
scope.acceleratorsList = null;
|
|
typeAheadMode = false;
|
|
expressionStart = 0;
|
|
expressionEnd = 0;
|
|
};
|
|
scope.acceleratorsHelp = function () {
|
|
actualTypeAheadText = '!';
|
|
scope.accelerators.expression = '';
|
|
scope.accelerators.showAcceleratorsList = true;
|
|
scope.typeAheadListPos = 0;
|
|
scope.acceleratorsList = scope.accelerators.availableExpressions;
|
|
expressionStart = scope.emailContext.messageBody.length;
|
|
$textArea = $element.find('textarea.email__message-editor');
|
|
$textArea.focus();
|
|
};
|
|
scope.addEmailAttachment = function (fileInput) {
|
|
if (scope.emailContext.attachment) {
|
|
return false;
|
|
}
|
|
scope.emailContext.attachment = emailModel.addAttachment(fileInput);
|
|
elementFocus('emailBtn');
|
|
};
|
|
scope.openPinnedArticles = function () {
|
|
//scope.articlesLoading = true;
|
|
scope.showArticles = true;
|
|
};
|
|
scope.selectResource = function ($event, resource) {
|
|
$event.stopPropagation();
|
|
scope.emailContext.kba = (scope.emailContext.kba === resource) ? null : resource;
|
|
};
|
|
scope.isResourceSelected = function (resource) {
|
|
return (scope.emailContext.kba && scope.emailContext.kba.displayId === resource.displayId);
|
|
};
|
|
scope.attachKBA = function () {
|
|
scope.showArticles = false;
|
|
};
|
|
scope.showEmailBody = function () {
|
|
scope.emailContext.kba = null;
|
|
scope.showArticles = false;
|
|
};
|
|
scope.removeKBAttachment = function () {
|
|
scope.emailContext.kba = null;
|
|
};
|
|
scope.removeEmailAttachment = function () {
|
|
if (scope.emailContext.attachment) {
|
|
scope.emailContext.attachment = null;
|
|
}
|
|
};
|
|
scope.handleAttachClick = function () {
|
|
setTimeout(function () {
|
|
document.getElementById('email-ka-attach-btn').click();
|
|
}, 0);
|
|
};
|
|
function init() {
|
|
scope.showPopOver = false;
|
|
var idField, summaryField;
|
|
if (scope.emailInstance.ticketData) {
|
|
if (scope.emailInstance.ticketData.length === 1 && scope.emailInstance.ticketData[0].type === EntityVO.TYPE_INCIDENT) {
|
|
scope.showPopOver = true;
|
|
relationModel.getRelations(scope.emailInstance.ticketData[0].id, scope.emailInstance.ticketData[0].type).then(function () {
|
|
scope.pinnedKBA = _.filter(relationModel.cache[scope.emailInstance.ticketData[0].id], function (item) {
|
|
return item.type === EntityVO.TYPE_KNOWLEDGE && item.realObject.internalUse === false && !item.isDecisionTree();
|
|
});
|
|
scope.disableKBAttachment = (scope.pinnedKBA.length) ? false : true;
|
|
scope.showArticles = false;
|
|
});
|
|
}
|
|
if (scope.emailInstance.ticketData[0].type === EntityVO.TYPE_KNOWLEDGE) {
|
|
idField = 'articleId';
|
|
summaryField = 'title';
|
|
}
|
|
else if (scope.emailInstance.ticketData[0].type === EntityVO.TYPE_SBEREQUEST) {
|
|
idField = 'id';
|
|
summaryField = 'serviceName';
|
|
}
|
|
else {
|
|
idField = 'displayId';
|
|
summaryField = 'summary';
|
|
}
|
|
if (scope.emailInstance.ticketData.length > 1) {
|
|
scope.emailContext.headerText = _.map(scope.emailInstance.ticketData, function (ticket) {
|
|
return ticket[idField];
|
|
}).join('; ');
|
|
}
|
|
else {
|
|
scope.emailContext.headerText = scope.emailInstance.ticketData[0][idField] + ' : ' + scope.emailInstance.ticketData[0][summaryField];
|
|
}
|
|
//scope.emailContext.headerText = scope.emailInstance.ticketData.displayId + ': ' + scope.emailInstance.ticketData.summary;
|
|
scope.emailContext.subjectText = scope.emailContext.headerText;
|
|
scope.checkByteLength(255); //TicketID + Summary should not be greater than 255 bytes
|
|
}
|
|
}
|
|
function validateEmail(email) {
|
|
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
return re.test(email);
|
|
}
|
|
init();
|
|
}
|
|
};
|
|
}]);
|
|
}());
|