mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-11-16 22:25:30 +08:00
524 lines
17 KiB
Text
524 lines
17 KiB
Text
var SmartAnnotation = (function() {
|
|
'use strict';
|
|
|
|
// utilities
|
|
var Util = (function() {
|
|
// helper method that binds show/hidden action
|
|
function showHideBinding() {
|
|
$.each(['show', 'hide'], function (i, ev) {
|
|
var el = $.fn[ev];
|
|
$.fn[ev] = function () {
|
|
this.trigger(ev);
|
|
return el.apply(this, arguments);
|
|
};
|
|
});
|
|
}
|
|
|
|
var publicApi = {
|
|
showHideBinding: showHideBinding
|
|
};
|
|
|
|
return publicApi;
|
|
})();
|
|
|
|
// stop the user annotation popover on click propagation
|
|
function atwhoStopPropagation(element) {
|
|
$(element).on('click', function(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
|
|
function setAtWho(field) {
|
|
var FilterTypeEnum = Object.freeze({
|
|
USER: {tag: "users",
|
|
dataUrl: $(document.body).attr('data-atwho-users-url')},
|
|
TASK: {tag: "tsk",
|
|
dataUrl: $(document.body).attr('data-atwho-task-url')},
|
|
PROJECT: {tag: "prj",
|
|
dataUrl: $(document.body).attr('data-atwho-project-url')},
|
|
EXPERIMENT: {tag: "exp",
|
|
dataUrl: $(document.body).attr('data-atwho-experiment-url')},
|
|
REPOSITORY: {tag: "rep",
|
|
dataUrl: $(document.body).attr('data-atwho-rep-items-url')},
|
|
MENU: {tag: "menu",
|
|
dataUrl: $(document.body).attr('data-atwho-menu-items')}
|
|
});
|
|
var prevAt,
|
|
// Default selected filter when using '#'
|
|
DEFAULT_SEARCH_FILTER = FilterTypeEnum.REPOSITORY,
|
|
atWhoUpdating = false;
|
|
|
|
var defaultRepId;
|
|
|
|
// helper methods for AtWho callback
|
|
function _templateEval(_tpl, map) {
|
|
var res;
|
|
try {
|
|
if (map.no_results) {
|
|
res = noResultsTemplate();
|
|
} else {
|
|
res = generateTemplate(map);
|
|
}
|
|
} catch (_error) {
|
|
res = '';
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function _matchHighlighter(li, query, filterType) {
|
|
var $li, re;
|
|
|
|
function highlight(el, sel, re) {
|
|
var prevVal, newVal;
|
|
prevVal = el.find(sel).html();
|
|
newVal = prevVal.replace(re, '<strong>$&</strong>');
|
|
el.find(sel).html(newVal);
|
|
}
|
|
|
|
if (!query || $(li).data('no-results')) {
|
|
return li;
|
|
}
|
|
|
|
$li = $(li);
|
|
re = new RegExp(query, 'gi');
|
|
// search_filter is not passed for the user
|
|
if(filterType) {
|
|
highlight($li, '[data-val=name]', re);
|
|
} else {
|
|
highlight($li, '[data-val=full-name]', re);
|
|
highlight($li, '[data-val=email]', re);
|
|
}
|
|
|
|
return $li[0].outerHTML
|
|
}
|
|
|
|
function _generateInputTag(value, li) {
|
|
var res = '';
|
|
res += '[#' + li.attr('data-name');
|
|
res += '~' + li.attr('data-type');
|
|
res += '~' + li.attr('data-id') + ']';
|
|
return res;
|
|
}
|
|
|
|
// initialise dropdown dismiss button
|
|
function initDismissButton($currentAtWho) {
|
|
$currentAtWho.find('.dismiss').off('click')
|
|
.on('click', function() {
|
|
$(field).atwho('destroy');
|
|
init();
|
|
});
|
|
}
|
|
|
|
// Initialize or update dropdown header buttons
|
|
function updateHeaderButtons(query, filterType) {
|
|
var $currentAtWho = $('.atwho-view[style]');
|
|
initDismissButton($currentAtWho);
|
|
|
|
// Update the selected filter button when changing smart annotation type
|
|
$currentAtWho.find('[data-filter]')
|
|
.removeClass('btn-primary')
|
|
.addClass('btn-default');
|
|
if(filterType.tag === 'rep') {
|
|
$currentAtWho.find('[data-rep-id="' + filterType.repository_id + '"]')
|
|
.removeClass('btn-default')
|
|
.addClass('btn-primary');
|
|
} else {
|
|
$currentAtWho.find('[data-filter="' + filterType.tag + '"]')
|
|
.removeClass('btn-default')
|
|
.addClass('btn-primary');
|
|
}
|
|
|
|
// Update the selected filter button when clicking on one of them
|
|
$currentAtWho.find('[data-filter]').off()
|
|
.on('click', function(e) {
|
|
if($(this).hasClass('btn-primary')) {
|
|
return;
|
|
}
|
|
var $selectedBtn = $(this);
|
|
var $prevBtn = $selectedBtn.closest('.atwho-header-res')
|
|
.children('.btn-primary');
|
|
$selectedBtn.removeClass('btn-default').addClass('btn-primary');
|
|
$prevBtn.removeClass('btn-primary').addClass('btn-default');
|
|
|
|
// Updates query and dropdown elements; focuses input
|
|
$(field).click().focus();
|
|
});
|
|
}
|
|
|
|
// Generates suggestion dropdown filter
|
|
function generateFilterMenu(active, res_data) {
|
|
var rep_buttons = '';
|
|
$.ajax({
|
|
async: false,
|
|
dataType: 'json',
|
|
url: $(document.body).attr('data-atwho-repositories-url'),
|
|
success: function(data) {
|
|
$.each(data['repositories'], function(id, name) {
|
|
if(defaultRepId === undefined){
|
|
defaultRepId = id;
|
|
}
|
|
rep_buttons += '<button data-filter="rep" data-rep-id="' + id +
|
|
'" class="btn btn-xs btn-primary">' + name + '</button>';
|
|
});
|
|
}
|
|
});
|
|
var header = '<div class="atwho-header-res">' +
|
|
'<button data-filter="prj" class="btn btn-xs btn-primary' +
|
|
'">Projects</button>' +
|
|
'<button data-filter="exp" class="btn btn-xs btn-primary' +
|
|
'">Experiments</button>' +
|
|
'<button data-filter="tsk" class="btn btn-xs btn-primary' +
|
|
'">Tasks</button>' +
|
|
rep_buttons +
|
|
'<div class="dismiss">' +
|
|
'<span class="fas fa-times"></span>' +
|
|
'</div>' +
|
|
'<div class="help">' +
|
|
'<div>' +
|
|
'<strong><%= I18n.t("atwho.users.navigate_1") %></strong> ' +
|
|
'<%= I18n.t("atwho.users.navigate_2") %>' +
|
|
'</div>' +
|
|
'<div><strong><%= I18n.t("atwho.users.confirm_1") %></strong> ' +
|
|
'<%= I18n.t("atwho.users.confirm_2") %>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<strong><%= I18n.t("atwho.users.dismiss_1") %></strong> ' +
|
|
'<%= I18n.t("atwho.users.dismiss_2") %>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
return header;
|
|
}
|
|
|
|
function noResultsTemplate() {
|
|
var res = '<div class="atwho-no-results" data-no-results="1">';
|
|
res += '<span><%= I18n.t("atwho.no_results") %></span>';
|
|
res += '</div>';
|
|
return res;
|
|
}
|
|
|
|
// Generates resources list items
|
|
function generateTemplate(map) {
|
|
var res = '';
|
|
res += '<li class="atwho-li atwho-li-res" data-name="' +
|
|
truncateLongString(map.name,
|
|
<%= Constants::NAME_TRUNCATION_LENGTH %>) +
|
|
'" data-id="' + map.id + '" data-type="' +
|
|
map.type + '">';
|
|
switch(map.type) {
|
|
case 'tsk':
|
|
res += '<span data-type class="res-type">' + map.type + '</span>';
|
|
break;
|
|
case 'prj':
|
|
res += '<span data-type class="res-type">' + map.type + '</span>';
|
|
break;
|
|
case 'exp':
|
|
res += '<span data-type class="res-type">' + map.type + '</span>';
|
|
break;
|
|
case 'rep_item':
|
|
res += '<span data-type class="res-type">' +
|
|
map.repository_tag + '</span>';
|
|
break;
|
|
}
|
|
|
|
res += ' ';
|
|
res += '<span data-val="name" class="res-name">';
|
|
res += truncateLongString(map.name,
|
|
<%= Constants::NAME_TRUNCATION_LENGTH %>);
|
|
res += '</span>';
|
|
if(map.archived) {
|
|
res += '<%= I18n.t("atwho.res.archived") %></span>';
|
|
} else {
|
|
res += '</span>';
|
|
}
|
|
res += ' ';
|
|
|
|
switch (map.type) {
|
|
case 'tsk':
|
|
|
|
res += '<span class="res-description">< ' + map.experimentName +
|
|
' < ' + map.projectName + '</span>';
|
|
break;
|
|
case 'exp':
|
|
res += '<span class="res-description">< ' + map.projectName + '</span>';
|
|
break;
|
|
case 'sam':
|
|
res += '<span class="res-description">' + map.description + '</span>';
|
|
break;
|
|
}
|
|
|
|
res += '</li>';
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Hackish wrapper function to make AtWho work when switching between
|
|
* multiple AtWho instances (e.g. from # to task#).
|
|
*
|
|
* Prevents second execution of AtWho update callback, triggered when user
|
|
* switches to different AtWho instance (e.g. from # to task#), which causes
|
|
* both of them to be called. In such case, AtWhO modal needs to be
|
|
* rerendered.
|
|
*/
|
|
function atWhoSwitchHack(filterTypeTag, remoteFilterCb) {
|
|
if(atWhoUpdating || (!$(field).length && _.isUndefined(filterTypeTag))) {
|
|
setTimeout(function() {
|
|
$(field).click();
|
|
}, 100);
|
|
return;
|
|
}
|
|
|
|
atWhoUpdating = true;
|
|
setTimeout(function() {
|
|
remoteFilterCb();
|
|
atWhoUpdating = false;
|
|
}, 100);
|
|
}
|
|
|
|
function atWhoSettings(at, defaultFilterType) {
|
|
return {
|
|
at: at,
|
|
callbacks: {
|
|
remoteFilter: function(query, callback) {
|
|
var $currentAtWho = $('.atwho-view[style]');
|
|
var filterTypeTag = $currentAtWho
|
|
.find('.btn-primary')
|
|
.data('filter');
|
|
|
|
atWhoSwitchHack(filterTypeTag, function() {
|
|
var filterType;
|
|
if (_.isUndefined(filterTypeTag)) {
|
|
// Switched smart annotation type (i.e. changed input)
|
|
filterType = defaultFilterType;
|
|
} else {
|
|
// Switched filtering type (i.e. different filter button
|
|
// pressed; works also for specific annotation types, e.g.
|
|
// task#, and coverts to the correct annotation type on confirm)
|
|
$.each(FilterTypeEnum, function(k, v) {
|
|
if (v.tag == filterTypeTag) {
|
|
filterType = FilterTypeEnum[k];
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
if (prevAt != at) {
|
|
// Switching smart annotation type (i.e. chaned input)
|
|
|
|
prevAt = at;
|
|
filterType = defaultFilterType;
|
|
// Hide current AtWho
|
|
$currentAtWho.removeAttr("style");
|
|
}
|
|
|
|
var params = { query: query };
|
|
|
|
if(filterType.tag === 'rep') {
|
|
params.repository_id = $currentAtWho
|
|
.find('.btn-primary')
|
|
.data('rep-id');
|
|
if(params.repository_id === undefined) {
|
|
params.repository_id = defaultRepId;
|
|
}
|
|
filterType.repository_id = params.repository_id;
|
|
}
|
|
|
|
$.getJSON(
|
|
filterType.dataUrl,
|
|
params,
|
|
function(data) {
|
|
// Updates dropdown
|
|
if (data.res.length < 1) {
|
|
callback([{no_results: 1}]);
|
|
} else {
|
|
callback(data.res);
|
|
}
|
|
|
|
updateHeaderButtons(query, filterType);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
sorter: function(query, items, _searchKey) {
|
|
// Sorting is already done on server-side
|
|
return items;
|
|
},
|
|
tplEval: function(_tpl, map) {
|
|
return _templateEval(_tpl, map);
|
|
},
|
|
highlighter: function(li, query) {
|
|
return _matchHighlighter(li, query, true);
|
|
},
|
|
beforeInsert: function(value, li) {
|
|
return _generateInputTag(value, li);
|
|
},
|
|
matcher:function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
|
|
var _a, _y, match, regexp, space;
|
|
flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
|
if (should_startWithSpace) {
|
|
flag = '(?:^|\\s)' + flag;
|
|
}
|
|
_a = decodeURI("%C3%80");
|
|
_y = decodeURI("%C3%BF");
|
|
regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_/:\\s\+\-\]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
|
|
match = regexp.exec(subtext);
|
|
if (match) {
|
|
return match[2] || match[1];
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
headerTpl: generateFilterMenu(defaultFilterType),
|
|
limit: <%= Constants::ATWHO_SEARCH_LIMIT %>,
|
|
startWithSpace: true,
|
|
acceptSpaceBar: true,
|
|
displayTimeout: 120000
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
$(field)
|
|
.on("reposition.atwho", function(event, flag, query) {
|
|
let inputFieldLeft = query.$inputor.offset().left;
|
|
if (inputFieldLeft > $(window).width()) {
|
|
let leftPosition;
|
|
if (inputFieldLeft < flag.left + $(window).scrollLeft()) {
|
|
leftPosition = inputFieldLeft;
|
|
} else {
|
|
leftPosition = flag.left + $(window).scrollLeft();
|
|
}
|
|
query.$el.find('.atwho-view').css('left', leftPosition + 'px');
|
|
}
|
|
if ($('.repository-show').length) {
|
|
query.$el.find('.atwho-view').css('top', flag.top + 'px');
|
|
}
|
|
})
|
|
.atwho({
|
|
at: '@',
|
|
callbacks: {
|
|
remoteFilter: function(query, callback) {
|
|
$.getJSON(
|
|
FilterTypeEnum.USER.dataUrl,
|
|
{query: query},
|
|
function(data) {
|
|
if (data.users.length < 1) {
|
|
callback([{no_results: 1}]);
|
|
} else {
|
|
callback(data.users);
|
|
}
|
|
initDismissButton($('.atwho-view[style]'));
|
|
}
|
|
);
|
|
},
|
|
sorter: function(query, items, _searchKey) {
|
|
// Sorting is already done on server-side
|
|
return items;
|
|
},
|
|
tplEval: function(_tpl, map) {
|
|
var res;
|
|
try {
|
|
if (map.no_results) {
|
|
res = noResultsTemplate();
|
|
} else {
|
|
res = '';
|
|
res += '<li class="atwho-li atwho-li-user" ';
|
|
res += 'data-id="' + map.id + '" ';
|
|
res += 'data-full-name="' + map.full_name + '">';
|
|
res += '<span class="global-avatar-container"><img src="' + map.img_url + '" class="avatar" /></span>';
|
|
res += '<span data-val="full-name">';
|
|
res += map.full_name;
|
|
res += '</span>';
|
|
res += '<small>';
|
|
res += ' ';
|
|
res += '·';
|
|
res += ' ';
|
|
res += '<span data-val="email">';
|
|
res += map.email;
|
|
res += '</span>';
|
|
res += '</small>';
|
|
res += '</li>';
|
|
}
|
|
} catch (_error) {
|
|
res = '';
|
|
}
|
|
return res;
|
|
},
|
|
highlighter: function(li, query) {
|
|
return _matchHighlighter(li, query);
|
|
},
|
|
beforeInsert: function(value, li) {
|
|
var res = '';
|
|
res += '[@' + li.attr('data-full-name');
|
|
res += '~' + li.attr('data-id') + ']';
|
|
return res;
|
|
}
|
|
},
|
|
headerTpl:
|
|
'<div class="atwho-header-res">' +
|
|
'<div class="title-user"><%= I18n.t("atwho.users.title") %></div>' +
|
|
'<div class="help">' +
|
|
'<div>' +
|
|
'<strong><%= I18n.t("atwho.users.navigate_1") %></strong> ' +
|
|
'<%= I18n.t("atwho.users.navigate_2") %>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<strong><%= I18n.t("atwho.users.confirm_1") %></strong> ' +
|
|
'<%= I18n.t("atwho.users.confirm_2") %>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<strong><%= I18n.t("atwho.users.dismiss_1") %></strong> ' +
|
|
'<%= I18n.t("atwho.users.dismiss_2") %>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="dismiss">' +
|
|
'<span class="fas fa-times"></span>' +
|
|
'</div>' +
|
|
'</div>',
|
|
limit: <%= Constants::ATWHO_SEARCH_LIMIT %>,
|
|
startsWithSpace: true,
|
|
acceptSpaceBar: true,
|
|
displayTimeout: 120000
|
|
})
|
|
.atwho(atWhoSettings('#', DEFAULT_SEARCH_FILTER))
|
|
// .atwho(atWhoSettings('task#', FilterTypeEnum.TASK)) Waiting for better times
|
|
// .atwho(atWhoSettings('project#', FilterTypeEnum.PROJECT))
|
|
// .atwho(atWhoSettings('experiment#', FilterTypeEnum.EXPERIMENT))
|
|
// .atwho(atWhoSettings('sample#', FilterTypeEnum.REPOSITORY));
|
|
}
|
|
|
|
return {
|
|
init: init
|
|
};
|
|
}
|
|
// Closes the atwho popup * needed in repositories to close the popup
|
|
// if nothing is selected and the user leaves the form *
|
|
function closePopup() {
|
|
$('.atwho-header-res').find('.fa-times').click();
|
|
}
|
|
|
|
function initialize(field) {
|
|
var atWho = new setAtWho(field);
|
|
atWho.init();
|
|
}
|
|
|
|
var publicApi = Object.freeze({
|
|
init: initialize,
|
|
preventPropagation: atwhoStopPropagation,
|
|
closePopup: closePopup
|
|
});
|
|
|
|
return publicApi;
|
|
|
|
})();
|
|
|
|
|
|
// initialize the smart annotations
|
|
(function initSmartAnnotation() {
|
|
$(document).on('focus', '[data-atwho-edit]', function() {
|
|
if(_.isUndefined($(this).data('atwho'))) {
|
|
SmartAnnotation.init(this);
|
|
}
|
|
});
|
|
})();
|