"use strict"; (function () { 'use strict'; angular.module('myitsmApp') .directive('feedCommentThread', ['$http', '$templateCache', '$compile', '$window', 'events', 'attachmentService', '$filter', 'smartRecorderModel', '$timeout', 'metadataModel', 'systemAlertService', 'feedModel', 'userModel', 'i18nService', 'configurationModel', 'browser', 'utilityFunctions', function ($http, $templateCache, $compile, $window, events, attachmentService, $filter, smartRecorderModel, $timeout, metadataModel, systemAlertService, feedModel, userModel, i18nService, configurationModel, browser, utilityFunctions) { return { restrict: 'A', priority: 99, template: angular.noop, replace: false, scope: { nestingLevel: '@', runSaveNoteHandler: '&savenote', opened: '@', isClosed: '=', data: '@', parentContext: '=', type: '=', isDraft: '=', timelineItem: '=', attachmentDisabled: '=', isUnflagEditAllowed: '=', inputDisabled: '=', inputText: '=?', isRequired: '=?' }, link: function (scope, $elem) { var textAreaElement; var defaultWorknoteTypeName = 'General Information'; var allowSearchByCompany = configurationModel.smartRecorderSearchByCompany; var defaultPlaceHolder = (scope.timelineItem && scope.timelineItem.isFlag()) ? $filter('i18n')('timeline.note.respondFlag.placeholder') : $filter('i18n')('timeline.note.addNote.placeholder'); function handleSaveTicketDraftRequest() { console.log('handling SAVE_DRAFT event in feed-comment-thread '); var workNote = getWorkNoteData(); if (workNote.noteText || (workNote.attachments && workNote.attachments.length)) { feedModel.pendingWorkNote = workNote; console.log('initialized pending note'); // block ui to prevent changes if (!(scope.type === EntityVO.TYPE_CHANGE || scope.type === EntityVO.TYPE_RELEASE)) { scope.state.savingNote = true; } } } scope.$on(events.SAVE_TICKET_DRAFT, handleSaveTicketDraftRequest); scope.$on(events.SAVE_COPY_CHANGE_ACTIVITY, handleSaveTicketDraftRequest); function handleTicketDraftMentionedNote() { scope.selectedWorknoteType = _.find(scope.worknoteTypes, { name: defaultWorknoteTypeName }) || {}; scope.addNoteForm(true); } function showMentionedNote() { var textArea = textAreaElement[0]; var mentionedPersons = _.filter(smartRecorderModel.smartRecorderData.confirmedItems, { relationship: 'mentioned' }); if (mentionedPersons && mentionedPersons.length) { var mentionedPerson; for (var i = 0; i < mentionedPersons.length; i++) { mentionedPerson = mentionedPersons[i]; if (i > 0) { textArea.lastChild.data = ', '; } textArea.appendChild(document.createTextNode(mentionedPerson.originalName)); highlightConfirmItem(mentionedPerson.originalName, mentionedPerson.content, mentionedPerson.type, mentionedPerson.displayName, mentionedPerson.id); } var mentionedText = mentionedPersons.length === 1 ? i18nService.getLocalizedString('createNew.ticket.mentionedPerson') : i18nService.getLocalizedString('createNew.ticket.mentionedPersons'); textArea.appendChild(document.createTextNode(mentionedText)); var range = document.createRange(); var sel = window.getSelection(); var lastChild = textArea.lastChild; range.setStart(lastChild, lastChild.length); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); textAreaElement.focus(); } } scope.$on(events.SET_TICKET_DRAFT_MENTIONED, handleTicketDraftMentionedNote); scope.state = { isPublicEnabled: !_.includes([EntityVO.TYPE_KNOWLEDGE, EntityVO.TYPE_SERVICEREQUEST], scope.type), isAttachEnabled: !scope.attachmentDisabled, isPostButtonVisible: !scope.isDraft, noteFormIsActive: false, savingNote: false, access: false, shareWithVendor: false, atLeastOneShareableVendorTickets: false }; if (scope.timelineItem) { scope.state.isThreadUnflagged = scope.timelineItem.hasUnflaggingResponse(); scope.isFlagThread = scope.timelineItem.isFlag(); } scope.$watch('parentContext.brokerVendorName', function (brokerVendorName) { scope.state.isVendorEnabled = !!brokerVendorName; }); scope.$watch('parentContext.vendorInfo', function (vendorInfo) { if (_.isArray(vendorInfo)) { scope.shareableVendorTickets = _.filter(vendorInfo, function (vendorTicket) { return vendorTicket.vendor.doNotShareWorkLogToVendor !== true; }); scope.state.atLeastOneShareableVendorTickets = _.isArray(scope.shareableVendorTickets) && scope.shareableVendorTickets.length > 0; scope.state.isMultipleVendorTickets = _.isArray(scope.shareableVendorTickets) && scope.shareableVendorTickets.length > 1; scope.updateShareWithVendorFlag(); } }); scope.updateVendorTicketList = function (filterOption) { filterOption.selected = !filterOption.selected; scope.updateShareWithVendorFlag(); }; scope.updateShareWithVendorFlag = function () { scope.state.shareWithVendor = _.some(scope.shareableVendorTickets, { selected: true }); }; scope.commentFormNode = angular.element(''); scope.attachments = []; scope.selectedWorknoteType = {}; scope.placeholderText = defaultPlaceHolder; if (EntityVO.hasMetadata(scope.type)) { metadataModel.getMetadataByType(scope.type).then(function (metadata) { scope.worknoteTypes = metadata.workinfoTypes; }); } scope.$on(events.SET_FOCUS_TO_ACTIVITY_INPUT, function () { scope.selectedWorknoteType = _.find(scope.worknoteTypes, { name: defaultWorknoteTypeName }) || {}; scope.addFlagNote = false; scope.placeholderText = defaultPlaceHolder; scope.addNoteForm(); }); if (_.isUndefined(scope.timelineItem)) { scope.$on(events.ADD_FLAG_NOTE, function (event, data) { scope.isNeedAttentionFlag = !!data.isNeedAttentionFlag; scope.selectedWorknoteType = _.find(scope.worknoteTypes, { name: defaultWorknoteTypeName }) || {}; scope.attachments = []; scope.addFlagNote = true; scope.flag = data.flag; if (scope.isNeedAttentionFlag) { scope.placeholderText = data.flag ? $filter('i18n')('ticket.needsAttention.flag.inputPlaceholder') : $filter('i18n')('ticket.needsAttention.unflag.inputPlaceholder'); } else { scope.placeholderText = data.flag ? $filter('i18n')('timeline.note.flag.placeholder') : $filter('i18n')('timeline.note.removeFlag.placeholder'); } scope.addNoteForm(); }); } scope.$on(events.DISMISS_NOTE_FORM, function () { scope.dismissNoteForm(); }); function clickFocusInHandler() { scope.isNeedAttentionFlag = false; scope.selectedWorknoteType = _.find(scope.worknoteTypes, { name: defaultWorknoteTypeName }) || {}; scope.toggleNoteForm(); scope.$apply(); } function keyDownKeyPressHandler(event) { if (event.which === 13) { scope.isNeedAttentionFlag = false; scope.selectedWorknoteType = _.find(scope.worknoteTypes, { name: defaultWorknoteTypeName }) || {}; scope.toggleNoteForm(); scope.$apply(); } } $elem.on('click focusin', clickFocusInHandler); $elem.on('keydown keypress', keyDownKeyPressHandler); scope.$on("$destroy", function () { console.log("feedCommentThread: unbind events"); $elem.off('click', clickFocusInHandler); $elem.off('focusin', clickFocusInHandler); $elem.off('keydown', keyDownKeyPressHandler); $elem.off('keypress', keyDownKeyPressHandler); $elem.off(); }); scope.handleKeydown = function ($event, type) { if ($event.keyCode === 32) { // space key scope.selectWorknoteType(type); $event.preventDefault(); $event.stopPropagation(); } }; scope.handleKeydownOnUpdate = function ($event, option) { if ($event.keyCode === 32) { // space key scope.updateVendorTicketList(option); $event.preventDefault(); $event.stopPropagation(); } }; scope.selectWorknoteType = function (type) { if (scope.selectedWorknoteType.index !== type.index) { scope.selectedWorknoteType = type; } }; scope.expandWorknoteTypeSection = function ($event, worknoteType) { if ($event) { $event.preventDefault(); $event.stopPropagation(); } if (worknoteType.expanded) { return; } _.forEach(scope.worknoteTypes, function (item) { item.expanded = false; }); worknoteType.expanded = true; }; scope.addNoteForm = function (mentionedFlag) { $http.get('views/feed/feed-add-note-form.html', { cache: $templateCache }).then(function (template) { scope.state.noteFormIsActive = true; scope.editMode = true; scope.state.unflagging = false; if (scope.type === EntityVO.TYPE_SERVICEREQUEST) { scope.state.access = true; } else if (scope.type !== EntityVO.TYPE_KNOWLEDGE && configurationModel.socialWorklogAccessSetting) { scope.state.access = configurationModel.socialWorklogAccessSetting; } else { scope.state.access = false; } if (scope.commentFormNode[0]) { scope.commentFormNode.show(); if (textAreaElement !== undefined) { $('.resource-preview__body').scrollTop($(textAreaElement).offset().top); textAreaElement.focus(); } return; } var nestingLevels = scope.nestingLevel, appendAfterDOM = $elem, formTemplate; if (template.data) { formTemplate = angular.element(template.data); scope.commentFormNode = $compile(formTemplate)(scope); } while (nestingLevels--) { appendAfterDOM = appendAfterDOM.parent(); } appendAfterDOM.after(scope.commentFormNode); textAreaElement = scope.commentFormNode.find('.timeline-note__text'); textAreaElement.focus(); if (mentionedFlag) { showMentionedNote(); } }); }; scope.dismissNoteForm = function (elem) { scope.attachments = []; typeAheadMode = false; scope.dismissPopup(); textAreaElement && textAreaElement.empty(); scope.commentFormNode.hide(); scope.state.noteFormIsActive = false; scope.inputText = ''; scope.state.shareWithVendor = false; if (scope.shareableVendorTickets && _.isArray(scope.shareableVendorTickets)) { _.forEach(scope.shareableVendorTickets, function (vendorTicket) { vendorTicket.selected = false; }); } scope.state.unflagging = false; scope.addFlagNote = false; scope.placeholderText = defaultPlaceHolder; if (scope.opened) { scope.isClosed = false; } }; scope.toggleNoteForm = function () { scope.state.noteFormIsActive ? scope.dismissNoteForm() : scope.addNoteForm(); }; scope.handleFileChange = function (fileInput) { var newAttachment = attachmentService.prepareFileToUpload({ fileInput: fileInput }); if (newAttachment && newAttachment.size <= 0) { systemAlertService.warning({ text: $filter('i18n')('attachment.file_empty'), icon: 'icon-exclamation_triangle', clear: true, hide: 5000 }); return; } if (newAttachment) { scope.attachments ? scope.attachments.push(newAttachment) : scope.attachments = [newAttachment]; //clear the file input value, otherwise the onChange event will not trigger, // if you are attaching the same file again in the same or next activity $elem.parent().find('#uploadAttachment').val(''); } }; scope.dismissAttachment = function ($e, attachment) { var index = scope.attachments.indexOf(attachment); if (index >= 0) { scope.attachments.splice(index, 1); } $e.stopImmediatePropagation(); }; scope.submitNote = function (elem) { if (!textAreaElement.text() && !scope.attachments.length) { //TODO: handle case when no text is entered and no attachments return false; } scope.state.savingNote = true; scope.noteData = getWorkNoteData(); scope.runSaveNoteHandler({ noteData: scope.noteData }) .then(function () { scope.dismissNoteForm(elem); }) .finally(function () { scope.state.savingNote = false; }); }; var cleanUpTextBeforePost = function (text) { return text .replace(/\n/g, ' ') // replace line breaks with special character .replace(/\s/g, ' ') // replace any space with regular one .replace(/ /g, '\n') // replace special line-break character with normal line-break .replace(/(\n{2})\n+/g, '$1'); // replace more that two line-breaks with just two }; var processWorknoteText = function (textAreaElm) { textAreaElm.find('.smart-recorder-highlightPerfectMatch').each(function () { var span = $(this); var textToReplace = '@[' + span.data('displayName') + ']|' + span.data('id') + '|(' + span.data('type') + ')'; // if mention is not at the start of text, put a space before it so that Angular's linky filter would not be confused if (this.previousSibling) { textToReplace = ' ' + textToReplace; } span.replaceWith(textToReplace); }); if (browserIsMSIE) { textAreaElm[0].innerHTML = textAreaElm[0].innerHTML.replace(/
/g, '').replace(/<\/p>/g, '
');
}
textAreaElm[0].innerHTML = textAreaElm[0].innerHTML.replace(/
/g, '\n');
textAreaElm[0].innerHTML = textAreaElm[0].innerHTML.trimRight(); //Logic to delete the
added at the end of note for non IE.
var text;
if (browser.isSafari) {
//There is no \n after converting it to innerText in safari if data present in different divs.
// Replacing div with \n. This use case is applied while pasting data in activity note.
text = $('
instead of
else if ($event.keyCode === keyCodes.enter && !scope.showPopup && window.getSelection) {
var selection = window.getSelection(), range = selection.getRangeAt(0), br = document.createElement('br');
range.deleteContents();
range.insertNode(br);
range.setStartAfter(br);
range.setEndAfter(br);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
$event.preventDefault();
}
};
/*Unused function*/
scope.hidePopupHeader = function () {
scope.showPopupHeader = false;
};
scope.handleSmartInputPaste = function ($event) {
$event.preventDefault();
var content;
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
}
if (content) {
if (document.queryCommandSupported('insertHTML')) {
content = utilityFunctions.escapeHTML(content);
content = content.replace(/(?:\r\n|\r|\n)/g, '
');
document.execCommand('insertHTML', false, content);
textAreaElement.find('div').each(function (index, item) {
if (item.innerHTML.trim().length === 0) {
$(item).addClass('empty-div');
}
});
}
else if (document.queryCommandSupported('paste')) {
document.execCommand('paste', false, content);
}
else {
return; // not support, let the default handle it
}
}
scope.handleSmartInputChange($event);
};
function getTextWithLineBreak(htmlNode) {
var textAreaText = '';
$.each(htmlNode.childNodes, function (nodeIndex, node) {
// check every text node for matching
if (node.nodeName.toLowerCase() === 'br') {
textAreaText += '\n';
}
else {
textAreaText += node.innerText || node.textContent;
}
if (browserIsMSIE) {
if (node.nodeName.toLowerCase() === 'p') {
textAreaText += '\n';
}
}
});
return textAreaText.replace(/[\r\n]+$/, '');
}
scope.getCaretPosition = function ($event) {
var caretOffset = 0;
var activityWindow = $event.currentTarget.ownerDocument.defaultView;
var selection = activityWindow.getSelection();
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents($event.currentTarget);
preCaretRange.setEnd(range.endContainer, range.endOffset);
var dummyContent = $("