"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', function ($http, $templateCache, $compile, $window, events, attachmentService, $filter, smartRecorderModel, $timeout, metadataModel, systemAlertService, feedModel, userModel, i18nService, configurationModel) { 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: '=?' }, link: function (scope, $elem) { var textAreaElement; var defaultWorknoteTypeName = 'General Information'; 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: !_.contains([EntityVO.TYPE_KNOWLEDGE, EntityVO.TYPE_SERVICEREQUEST], scope.type), isAttachEnabled: !scope.attachmentDisabled, isPostButtonVisible: !scope.isDraft, noteFormIsActive: false, savingNote: false, access: false, shareWithVendor: 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.state.isMultipleVendorTickets = _.isArray(vendorInfo) && vendorInfo.length > 1; scope.updateShareWithVendorFlag(); } }); scope.updateVendorTicketList = function (filterOption) { filterOption.selected = !filterOption.selected; scope.updateShareWithVendorFlag(); }; scope.updateShareWithVendorFlag = function () { scope.state.shareWithVendor = _.some(scope.parentContext.vendorInfo, { 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.selectedWorknoteType = _.find(scope.worknoteTypes, { name: defaultWorknoteTypeName }) || {}; scope.attachments = []; scope.addFlagNote = true; scope.flag = data.flag; 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.selectedWorknoteType = _.find(scope.worknoteTypes, { name: defaultWorknoteTypeName }) || {}; scope.toggleNoteForm(); scope.$apply(); } function keyDownKeyPressHandler(event) { if (event.which === 13) { 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) { if (elem && elem.target && (elem.target.innerText === $filter('i18n')('timeline.note.post.label') || elem.target.innerText === $filter('i18n')('timeline.note.cancel'))) { textAreaElement && textAreaElement.empty(); scope.commentFormNode.hide(); scope.state.noteFormIsActive = false; scope.inputText = ''; } typeAheadMode = false; scope.dismissPopup(); scope.state.shareWithVendor = false; if (scope.parentContext && scope.parentContext.vendorInfo && _.isArray(scope.parentContext.vendorInfo)) { _.forEach(scope.parentContext.vendorInfo, function (vendorTicket) { vendorTicket.selected = false; }); } scope.state.unflagging = false; scope.attachments = []; 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 angular.element('#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); }).catch(function (error) { if (error) { systemAlertService.error({ text: error.data.detailMessage || error.data.error || error, clear: false }); } }) .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');
return cleanUpTextBeforePost(textAreaElm.text());
};
/**
* Get note data
* @returns {} worknote descriptor
*/
function mapVendorUrl(vendorInfo) {
if (vendorInfo.id && vendorInfo.vendorTicketUrl) {
return '@[' + vendorInfo.id + ']|' + vendorInfo.vendorTicketUrl + '|(url)';
}
else {
return vendorInfo.id || '';
}
}
function getWorkNoteData() {
if (textAreaElement) {
var noteData = {
noteText: processWorknoteText(textAreaElement.clone(true)),
attachments: scope.attachments,
access: !scope.state.access,
workInfoType: scope.selectedWorknoteType.index,
addFlagNote: scope.addFlagNote,
flag: scope.flag
};
if (scope.state.shareWithVendor && scope.parentContext && scope.parentContext.brokerVendorName) {
if (scope.type === EntityVO.TYPE_INCIDENT) {
noteData.shareWithVendor = true;
}
else if (_.isArray(scope.parentContext.vendorInfo)) {
if (scope.parentContext.vendorInfo.length === 1) {
noteData.vendorTicketId = _.map(scope.parentContext.vendorInfo, mapVendorUrl).join() || '';
}
else if (scope.parentContext.vendorInfo.length > 1) {
noteData.vendorTicketId = _(scope.parentContext.vendorInfo)
.filter({ selected: true })
.map(mapVendorUrl)
.join(', ');
}
}
noteData.brokerVendorName = scope.parentContext.brokerVendorName;
}
if (scope.isFlagThread && scope.state.isThreadUnflagged) {
noteData.addFlagNote = true;
noteData.flag = false;
noteData.isCommentOnly = true;
noteData.workNoteGuid = scope.timelineItem.note.workNoteGuid;
}
else if (scope.isFlagThread) {
noteData.addFlagNote = true;
noteData.flag = !scope.state.unflagging;
noteData.isCommentOnly = !scope.state.unflagging;
noteData.workNoteGuid = scope.timelineItem.note.workNoteGuid;
}
return noteData;
}
else {
return {};
}
}
///////////////////////////// @ MENTION
var typeAheadMode = false;
var highlightSelected = false;
var typeAheadSearchString = '';
var pauseInterval = 2000; //2 sec pause
var wordCountToStartSmartSearch = 8;
var searchChunkSize = 20;
var assetTotalMatches = 0;
var personTotalMatches = 0;
var timerPromise;
var selectedElement = null;
var browserIsMSIE = ($window.navigator.userAgent.indexOf('MSIE') > -1 || $window.navigator.userAgent.indexOf('Edge') > -1) || ($window.navigator.userAgent.indexOf('Trident\/7.0') > -1);
// once again MSIE is opposite to the rest in terms of contenteditable setting
var contenteditableSetting = browserIsMSIE ? 'true' : 'false';
var keyCodes = {
enter: 13,
leftArrow: 37,
rightArrow: 39,
upArrow: 38,
downArrow: 40
};
scope.personProfileList = [];
scope.assetProfileList = [];
scope.smartResultItems = []; // This list stores items that were highlighted at the text input area
scope.personProfileListFilteredLength = 0;
scope.assetProfileListFilteredLength = 0;
scope.popupType = '';
scope.selectedText = '';
scope.typeAheadListPos = 0;
scope.typeAheadText = '';
scope.actualTypeAheadText = '';
scope.showPopup = false;
scope.smartRecorderModel = smartRecorderModel;
scope.showAssetCount = true;
scope.showPopupHeader = false;
scope.toggleMentioning = function () {
var edtiElm = scope.commentFormNode.find('.timeline-note__text');
if (!scope.showPopup) {
pasteHtmlAtCaret('@', edtiElm[0]);
scope.handleSmartInputChange($.Event('keyup'));
}
else {
scope.dismissPopup();
}
};
function setEndOfContenteditable(contentEditableElement) {
var range, selection;
if (document.createRange) { //Firefox, Chrome, Opera, Safari, IE 9+
range = document.createRange(); //Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement); //Select the entire contents of the element with the range
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection(); //get the selection object (allows you to change selection)
selection.removeAllRanges(); //remove any selections already made
selection.addRange(range); //make the range you have just created the visible selection
}
else if (document.selection) { //IE 8 and lower
range = document.body.createTextRange(); //Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement); //Select the entire contents of the element with the range
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
range.select(); //Select the range (make it the visible selection
}
}
function elementContainsSelection(el) {
var sel;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
for (var i = 0; i < sel.rangeCount; ++i) {
if (!isOrContains(sel.getRangeAt(i).commonAncestorContainer, el)) {
return false;
}
}
return true;
}
}
else if ((sel = document.selection) && sel.type !== 'Control') {
return isOrContains(sel.createRange().parentElement(), el);
}
return false;
}
function isOrContains(node, container) {
while (node) {
if (node === container) {
return true;
}
node = node.parentNode;
}
return false;
}
function pasteHtmlAtCaret(html, el) {
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (elementContainsSelection(el)) {
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
el = document.createElement('div');
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
else if (document.selection && document.selection.type !== 'Control') {
// IE < 9
document.selection.createRange().pasteHTML(html);
}
}
else {
setEndOfContenteditable(el);
pasteHtmlAtCaret(html, el);
}
}
}
scope.handleSmartInputKeyDown = function ($event) {
// prevent the cursor from moving on the text input field if
// type-ahead popup is active. The up, down and enter keys will be used exclusive for selection purposes
if (($event.keyCode === keyCodes.upArrow || $event.keyCode === keyCodes.downArrow || $event.keyCode === keyCodes.enter) && scope.showPopup === true) {
$event.preventDefault();
}
// Chrome and Safari insert
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('insertText')) {
document.execCommand('insertText', false, content);
}
else if (document.queryCommandSupported('paste')) {
document.execCommand('paste', false, content);
}
else {
return; // not support, let the default handle it
}
}
scope.handleSmartInputChange();
};
scope.handleSmartInputChange = function ($event) {
var typeAheadStartPos;
var textArea = textAreaElement[0];
// 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();
}
scope.inputTextHtml = textAreaElement.html();
scope.inputText = getInputText();
// ensure there is already a child
at the end to make non IE work properly
if (!browserIsMSIE) {
if (!textArea.lastChild || textArea.lastChild.nodeName.toLowerCase() !== 'br') {
textArea.appendChild(document.createElement('br'));
}
}
if ($event) {
// ignore if control or alternate key was pressed
if ($event.ctrlKey === true || $event.altKey === true) {
return;
}
else if ($event.keyCode === keyCodes.upArrow && scope.showPopup === true) {
if (scope.typeAheadListPos > 0) {
scope.typeAheadListPos--;
}
return;
}
else if ($event.keyCode === keyCodes.downArrow && scope.showPopup === true) {
if (scope.typeAheadListPos < (scope.personProfileListFilteredLength + scope.assetProfileListFilteredLength - 1)) {
scope.typeAheadListPos++;
}
return;
}
else if ($event.keyCode === keyCodes.enter) {
scope.handleSmartInputEnter();
return;
}
}
// check @ or ! character
if (scope.inputText.length > 0) {
typeAheadStartPos = scope.inputText.indexOf(' @');
if (typeAheadStartPos === -1) {
typeAheadStartPos = scope.inputText.indexOf('\n@');
}
if (typeAheadStartPos > -1) {
typeAheadStartPos += 2;
scope.popupType = 'profile';
}
else {
// this is for Chrome and IE
typeAheadStartPos = scope.inputText.indexOf(String.fromCharCode(160, 64));
if (typeAheadStartPos > -1) {
typeAheadStartPos += 2;
scope.popupType = 'profile';
}
else if (scope.inputText[0] === '@') {
typeAheadStartPos = 1;
scope.popupType = 'profile';
}
}
if (typeAheadStartPos > -1) {
scope.actualTypeAheadText = scope.inputText.substring(typeAheadStartPos);
if (typeAheadMode === false) {
typeAheadMode = true;
scope.typeAheadListPos = 0;
}
}
else if (typeAheadMode === true) {
typeAheadMode = false;
scope.dismissPopup();
}
}
else if (typeAheadMode === true) {
typeAheadMode = false;
scope.dismissPopup();
}
if (typeAheadMode === true) {
if (scope.actualTypeAheadText.length >= 3 && scope.actualTypeAheadText !== scope.typeAheadText) {
var personProfileListFilteredLength = $filter('filter')(scope.personProfileList, scope.actualTypeAheadText).length;
var assetProfileListFilteredLength = $filter('filter')(scope.assetProfileList, scope.actualTypeAheadText).length;
scope.showPopup = true;
scope.showPopupHeader = true;
if ((personProfileListFilteredLength + assetProfileListFilteredLength) > 0 || scope.typeAheadText.length === 0) {
scope.typeAheadText = scope.actualTypeAheadText;
scope.personProfileListFilteredLength = personProfileListFilteredLength;
scope.assetProfileListFilteredLength = assetProfileListFilteredLength;
}
// If scope.actualTypeAheadText result in no match, keep the last non-empty list on the screen instead of showing nothing
// This is the prefer behaviour
else {
// also check if more than 3 words has already been typed after the @ sign
var newTypeAheadWords = scope.actualTypeAheadText.split(' ');
if (newTypeAheadWords.length > 3) {
// check any potential match before quiting. In the ideal world, we shouldn't need to do this check.
// The NLP suppose to pick it up and highlight item accordingly. But we don't have that yet, so this
// is the workaround.
// check potential full name match first
var name = newTypeAheadWords[0] + ' ' + newTypeAheadWords[1];
personProfileListFilteredLength = $filter('filter')(scope.personProfileList, name).length;
if ((personProfileListFilteredLength) > 0) {
highlightPotentialItem('@' + name, name);
}
// if full name not match, try lower the qualification with first or last name
else {
name = newTypeAheadWords[0];
personProfileListFilteredLength = $filter('filter')(scope.personProfileList, name).length;
if ((personProfileListFilteredLength) > 0) {
highlightPotentialItem('@' + name, name);
}
else {
// check if there is any asset match
assetProfileListFilteredLength = $filter('filter')(scope.assetProfileList, newTypeAheadWords[0]).length;
if ((assetProfileListFilteredLength) > 0) {
highlightPotentialItem('@' + newTypeAheadWords[0], newTypeAheadWords[0]);
}
}
}
// three words typed so far and user not selecting anything, automatically quit typeahead
typeAheadMode = false;
scope.dismissPopup();
return;
}
}
scope.typeAheadListPos = 0;
if ((typeAheadSearchString.length === 0) || (scope.actualTypeAheadText.indexOf(typeAheadSearchString) !== 0)) {
typeAheadSearchString = scope.actualTypeAheadText;
scope.typeAheadText = scope.actualTypeAheadText;
if (scope.showPopup && scope.popupType === 'profile') {
smartRecorderModel.getListOfPersons(typeAheadSearchString, searchChunkSize).then(onListOfPersonLoaded);
smartRecorderModel.getListOfAssets(typeAheadSearchString, searchChunkSize).then(onListOfAssetLoaded);
}
return;
}
if (personTotalMatches > searchChunkSize) {
typeAheadSearchString = scope.actualTypeAheadText;
scope.typeAheadText = scope.actualTypeAheadText;
if (scope.showPopup && scope.popupType === 'profile') {
smartRecorderModel.getListOfPersons(typeAheadSearchString, searchChunkSize).then(onListOfPersonLoaded);
}
}
if (assetTotalMatches > searchChunkSize) {
typeAheadSearchString = scope.actualTypeAheadText;
scope.typeAheadText = scope.actualTypeAheadText;
if (scope.showPopup && scope.popupType === 'profile') {
smartRecorderModel.getListOfAssets(typeAheadSearchString, searchChunkSize).then(onListOfAssetLoaded);
}
}
}
else if (scope.actualTypeAheadText.length < 3) {
scope.personProfileList = [];
scope.assetProfileList = [];
scope.typeAheadListPos = 0;
scope.personProfileListFilteredLength = 0;
scope.assetProfileListFilteredLength = 0;
scope.typeAheadText = '';
scope.showPopupHeader = false;
scope.actualTypeAheadText = '';
typeAheadSearchString = '';
assetTotalMatches = 0;
personTotalMatches = 0;
}
}
else if (scope.inputText.length === 0) {
scope.dismissPopup();
}
else if (scope.customerName) {
$timeout.cancel(timerPromise);
// the replace('\u00a0',' ') call is used to replace the ' ' character in Chrome and Safari
var numWords = jQuery.trim(scope.inputText).replace('\u00a0', ' ').split(' ').length - scope.customerName.split(' ').length;
if (numWords > 0) {
if (numWords % wordCountToStartSmartSearch === 0) {
scope.smartSearch();
}
else {
timerPromise = $timeout(function () {
if (typeAheadMode === false) {
scope.smartSearch();
}
}, pauseInterval);
}
}
}
};
scope.handleSmartInputEnter = function () {
if (scope.showPopup === true) {
if (scope.typeAheadListPos < scope.personProfileListFilteredLength && scope.personProfileListFilteredLength > 0) {
var personProfileFilteredList = $filter('filter')(scope.personProfileList, scope.typeAheadText);
var personProfile = personProfileFilteredList[scope.typeAheadListPos];
if (scope.popupType === 'profile') {
scope.profileSelected(personProfile, 'person', personProfile.fullName, personProfile.id, 'customer', 'common.label.customer');
}
else {
scope.templateSelected(personProfile, 'incidentTemplate');
}
}
else if (scope.typeAheadListPos >= scope.personProfileListFilteredLength && scope.assetProfileListFilteredLength > 0) {
var assetProfileFilteredList = $filter('filter')(scope.assetProfileList, scope.typeAheadText);
var assetProfile = assetProfileFilteredList[scope.typeAheadListPos - scope.personProfileListFilteredLength];
if (scope.popupType === 'profile') {
scope.profileSelected(assetProfile, 'asset', assetProfile.name, assetProfile.reconciliationId, 'affectedasset', 'common.label.asset');
}
else {
scope.templateSelected(assetProfile, 'servicerequestTemplate');
}
}
}
};
scope.handleSmartInputHighlightSelected = function ($event) {
if (typeAheadMode) {
return;
}
// dismiss popup if it already opened
highlightSelected = true;
if (scope.showPopup) {
scope.dismissPopup();
}
var itemName = $event.currentTarget.firstChild.data;
selectedElement = $event.currentTarget;
scope.selectedText = itemName;
scope.personProfileList = [];
scope.assetProfileList = [];
scope.showPopup = true;
// get the list of potential match people records
smartRecorderModel.getListOfPersons(itemName).then(onListOfPersonLoaded);
smartRecorderModel.getListOfAssets(itemName).then(onListOfAssetLoaded);
};
scope.profileSelected = function (profile, type, displayName, id) {
if (typeAheadMode) {
scope.selectedText = '@' + scope.actualTypeAheadText;
highlightConfirmItem(scope.selectedText, profile, type, displayName, id);
typeAheadMode = false;
}
else if (selectedElement) {
highlightConfirmElement(selectedElement, profile, type, displayName, id);
}
scope.dismissPopup();
};
scope.dismissPopup = function () {
if (highlightSelected || typeAheadMode) {
return;
}
scope.showPopup = false;
scope.personProfileList = [];
scope.assetProfileList = [];
scope.typeAheadText = '';
scope.typeAheadListPos = 0;
scope.personProfileListFilteredLength = 0;
scope.assetProfileListFilteredLength = 0;
scope.showPopupHeader = false;
typeAheadSearchString = '';
assetTotalMatches = 0;
personTotalMatches = 0;
};
//Unused function
scope.personProfileMouseover = function ($index) {
scope.typeAheadListPos = $index;
};
//Unused function
scope.assetProfileMouseover = function ($index) {
scope.typeAheadListPos = scope.personProfileListFilteredLength + $index;
};
function highlightPotentialItem(originalName, displayName) {
var textArea = textAreaElement[0];
$.each(textArea.childNodes, function (nodeIndex, node) {
// check every text node for matching NLP result in smartResultItem
if (node.nodeName.toLowerCase() === '#text') {
if (node.data.length > 1) {
var startIndex = node.data.indexOf(originalName);
if (startIndex > -1) {
// found match, prepare range object for DOM update
var rangeObj = document.createRange();
rangeObj.setStart(node, startIndex);
rangeObj.setEnd(node, startIndex + originalName.length);
// prepare span object to highlight matching object
var elementToInsert = angular.element('' + displayName + '');
elementToInsert.addClass('smart-recorder-highlight');
elementToInsert.attr('ng-click', 'handleSmartInputHighlightSelected($event)');
elementToInsert.attr('contenteditable', contenteditableSetting);
// compile the element to register the ng-click callback
var linkFn = $compile(elementToInsert);
var element = linkFn(scope);
// delete the existing text
rangeObj.deleteContents();
// replace it with the span object
rangeObj.insertNode(element[0]);
}
}
}
});
}
function highlightConfirmItem(originalName, profile, type, displayName, id) {
var textArea = textAreaElement[0];
// 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();
}
$.each(textArea.childNodes, function (nodeIndex, node) {
// check every text node for matching
if (node.nodeName.toLowerCase() === '#text') {
if (node.data.length > 1) {
var startIndex = node.data.indexOf(originalName);
if (startIndex > -1) {
// found match, prepare range object for DOM update
var rangeObj = document.createRange();
rangeObj.setStart(node, startIndex);
rangeObj.setEnd(node, startIndex + originalName.length);
// prepare span object to highlight matching object
var elementToInsert = angular.element('' + displayName + '');
elementToInsert
.addClass('smart-recorder-highlightPerfectMatch')
.attr('contenteditable', contenteditableSetting)
.data({
displayName: displayName,
id: id + ((type === EntityVO.TYPE_ASSET) ? '*' + profile.classId : ''),
type: (type === EntityVO.TYPE_ASSET) ? type : 'user'
});
// compile the element to register the ng directive
var linkFn = $compile(elementToInsert);
var element = linkFn(scope);
// delete the existing matching text
rangeObj.deleteContents();
// and replace it with the span object with correct style
rangeObj.insertNode(element[0]);
scope.inputText = getInputText();
scope.inputTextHtml = textAreaElement.html();
// set focus
var range = document.createRange();
var sel = window.getSelection();
var lastChild = textArea.lastChild;
if (lastChild.nodeName.toLowerCase() === 'br') {
lastChild = lastChild.previousSibling;
}
// (SW00467508) To fix issue with FF and IE, insert a ' ' character at the end.
else if (lastChild.nodeName.toLowerCase() === '#text' && lastChild.data === '') {
lastChild.data = '\u00a0';
}
else if (lastChild.nodeName.toLowerCase() === 'span') {
lastChild = document.createTextNode('\u00a0');
textArea.appendChild(lastChild);
}
range.setStart(lastChild, lastChild.length);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
textAreaElement.focus();
}
}
}
});
}
function highlightConfirmElement(selectedElement, profile, type, displayName, id) {
var elmToReplace = $('' + displayName + '');
elmToReplace
.addClass('smart-recorder-highlightPerfectMatch')
.attr('contenteditable', contenteditableSetting)
.data({
displayName: displayName,
id: id + ((type === EntityVO.TYPE_ASSET) ? '*' + profile.classId : ''),
type: (type === EntityVO.TYPE_ASSET) ? type : 'user'
});
$(selectedElement).replaceWith(elmToReplace);
scope.inputTextHtml = textAreaElement.html();
}
function getInputText() {
var textArea = textAreaElement[0];
var textAreaText = '';
$.each(textArea.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]+$/, '');
}
function onListOfPersonLoaded(response) {
var data = response.list;
// Todo: backend currently not returning total match count, set it based on length of array for now
if (((data.length >= searchChunkSize && scope.actualTypeAheadText !== smartRecorderModel.personSearchString) || (scope.actualTypeAheadText.length < smartRecorderModel.personSearchString.length)) && scope.actualTypeAheadText.length >= 3) {
typeAheadSearchString = scope.actualTypeAheadText;
scope.typeAheadText = scope.actualTypeAheadText;
smartRecorderModel.getListOfPersons(typeAheadSearchString, searchChunkSize).then(onListOfPersonLoaded);
}
else {
scope.personProfileList = data;
personTotalMatches = data.length + 1;
scope.personProfileListFilteredLength = $filter('filter')(scope.personProfileList, scope.typeAheadText).length;
scope.typeAheadListPos = 0;
highlightSelected = false;
if (scope.personProfileList.length > 0 && typeAheadMode) {
//back track the last type ahead text that show non-empty result
var i = scope.actualTypeAheadText.length;
var endPosition = scope.typeAheadText.length;
while (i > 3 || i > endPosition) {
var longestTypeAheadTextWithNonEmptyList = scope.actualTypeAheadText.substr(0, i);
var filteredLength = $filter('filter')(scope.personProfileList, longestTypeAheadTextWithNonEmptyList).length;
if (filteredLength > 0) {
scope.personProfileListFilteredLength = filteredLength;
scope.typeAheadText = longestTypeAheadTextWithNonEmptyList;
break;
}
i--;
}
}
}
}
function onListOfAssetLoaded(data) {
if (((data.totalMatches > searchChunkSize && scope.actualTypeAheadText !== smartRecorderModel.assetSearchString) || (scope.actualTypeAheadText.length < smartRecorderModel.assetSearchString.length)) && scope.actualTypeAheadText.length >= 3) {
typeAheadSearchString = scope.actualTypeAheadText;
scope.typeAheadText = scope.actualTypeAheadText;
smartRecorderModel.getListOfAssets(typeAheadSearchString, searchChunkSize).then(onListOfAssetLoaded);
}
else {
scope.assetProfileList = data.objects;
assetTotalMatches = data.totalMatches;
scope.assetProfileListFilteredLength = $filter('filter')(scope.assetProfileList, scope.typeAheadText).length;
scope.typeAheadListPos = 0;
highlightSelected = false;
if (scope.assetProfileList.length > 0 && typeAheadMode) {
//back track the last type ahead text that show non-empty result
var i = scope.actualTypeAheadText.length;
var endPosition = scope.typeAheadText.length;
while (i > 3 || i > endPosition) {
var longestTypeAheadTextWithNonEmptyList = scope.actualTypeAheadText.substr(0, i);
var filteredLength = $filter('filter')(scope.assetProfileList, longestTypeAheadTextWithNonEmptyList).length;
if (filteredLength > 0) {
scope.assetProfileListFilteredLength = filteredLength;
scope.typeAheadText = longestTypeAheadTextWithNonEmptyList;
break;
}
i--;
}
}
}
}
scope.$on(events.HIDE_TICKET_DRAFT_LOADER, hideTicketDraftLoader);
function hideTicketDraftLoader() {
scope.state.savingNote = false;
}
}
};
}]);
})();