cryptpad/www/common/common-interface.js

737 lines
23 KiB
JavaScript
Raw Normal View History

define([
'jquery',
'/customize/messages.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-notifier.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js',
2018-04-12 17:08:08 +00:00
'/common/tippy/tippy.min.js',
2017-09-07 14:41:11 +00:00
'/customize/pages.js',
2017-09-07 10:45:07 +00:00
'/common/hyperscript.js',
'/customize/loading.js',
'/common/test.js',
2017-09-07 10:45:07 +00:00
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
2018-04-12 17:08:08 +00:00
'css!/common/tippy/tippy.css',
], function ($, Messages, Util, Hash, Notifier, AppConfig,
Alertify, Tippy, Pages, h, Loading, Test) {
var UI = {};
/*
* Alertifyjs
*/
UI.Alertify = Alertify;
// set notification timeout
2018-04-16 14:07:40 +00:00
Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000;
2017-09-07 10:45:07 +00:00
var findCancelButton = UI.findCancelButton = function (root) {
if (root) {
return $(root).find('button.cancel').last();
}
return $('button.cancel').last();
};
2017-09-07 10:45:07 +00:00
var findOKButton = UI.findOKButton = function (root) {
if (root) {
return $(root).find('button.ok').last();
}
return $('button.ok').last();
};
var listenForKeys = UI.listenForKeys = function (yes, no, el) {
var handler = function (e) {
e.stopPropagation();
switch (e.which) {
case 27: // cancel
if (typeof(no) === 'function') { no(e); }
break;
case 13: // enter
if (typeof(yes) === 'function') { yes(e); }
break;
}
};
$(el || window).keydown(handler);
return handler;
};
2018-01-17 17:39:45 +00:00
var customListenForKeys = function (keys, cb, el) {
2018-01-18 09:53:36 +00:00
if (!keys || !keys.length || typeof cb !== "function") { return; }
2018-01-17 17:39:45 +00:00
var handler = function (e) {
e.stopPropagation();
2018-01-18 09:53:36 +00:00
keys.some(function (k) {
// k is number or array
// if it's an array, it should be [keyCode, "{ctrl|alt|shift|meta}"]
if (Array.isArray(k) && e.which === k[0] && e[k[1] + 'Key']) {
cb();
return true;
}
if (e.which === k && !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey) {
cb();
return true;
}
});
2018-01-17 17:39:45 +00:00
};
$(el || window).keydown(handler);
return handler;
};
var stopListening = UI.stopListening = function (handler) {
if (!handler) { return; } // we don't want to stop all the 'keyup' listeners
$(window).off('keyup', handler);
};
2017-09-07 16:54:58 +00:00
var dialog = UI.dialog = {};
var merge = function (a, b) {
var c = {};
if (a) {
Object.keys(a).forEach(function (k) {
c[k] = a[k];
});
}
if (b) {
Object.keys(b).forEach(function (k) {
c[k] = b[k];
});
}
return c;
};
dialog.selectable = function (value, opt) {
var attrs = merge({
2017-09-07 16:54:58 +00:00
type: 'text',
readonly: 'readonly',
}, opt);
var input = h('input', attrs);
2017-09-07 16:54:58 +00:00
$(input).val(value).click(function () {
input.select();
});
return input;
};
2018-01-24 10:01:15 +00:00
dialog.okButton = function (content, classString) {
2018-01-24 10:35:05 +00:00
var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary';
2018-01-24 10:01:15 +00:00
return h(sel, { tabindex: '2', }, content || Messages.okButton);
2017-09-07 16:54:58 +00:00
};
2018-01-24 10:01:15 +00:00
dialog.cancelButton = function (content, classString) {
2018-01-24 10:35:05 +00:00
var sel = typeof(classString) === 'string'? 'button.' + classString:'button.cancel';
2018-01-24 10:01:15 +00:00
return h(sel, { tabindex: '1'}, content || Messages.cancelButton);
2017-09-07 16:54:58 +00:00
};
dialog.message = function (text) {
return h('p.msg', text);
2017-09-07 16:54:58 +00:00
};
dialog.textInput = function (opt) {
2017-09-08 15:40:25 +00:00
var attrs = merge({
2017-09-07 16:54:58 +00:00
type: 'text',
'class': 'cp-text-input',
2017-09-08 15:40:25 +00:00
}, opt);
return h('input', attrs);
2017-09-07 16:54:58 +00:00
};
dialog.nav = function (content) {
return h('nav', content || [
dialog.cancelButton(),
dialog.okButton(),
]);
};
dialog.frame = function (content) {
return h('div.alertify', {
tabindex: 1,
}, [
2017-09-07 16:54:58 +00:00
h('div.dialog', [
h('div', content),
])
]);
};
/**
* tabs is an array containing objects
* each object must have the following attributes:
* - title: String
* - content: DOMElement
*/
dialog.tabs = function (tabs) {
var contents = [];
var titles = [];
tabs.forEach(function (tab) {
if (!tab.content || !tab.title) { return; }
2018-01-17 17:39:45 +00:00
var content = h('div.alertify-tabs-content', tab.content);
var title = h('span.alertify-tabs-title', tab.title);
$(title).click(function () {
titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); });
contents.forEach(function (c) { $(c).removeClass('alertify-tabs-content-active'); });
$(title).addClass('alertify-tabs-active');
$(content).addClass('alertify-tabs-content-active');
});
titles.push(title);
contents.push(content);
});
if (contents.length) {
$(contents[0]).addClass('alertify-tabs-content-active');
$(titles[0]).addClass('alertify-tabs-active');
}
return h('div.alertify-tabs', [
h('div.alertify-tabs-titles', titles),
h('div.alertify-tabs-contents', contents),
]);
};
2017-09-07 16:54:58 +00:00
UI.tokenField = function (target) {
var t = {
element: target || h('input'),
};
2017-09-07 16:54:58 +00:00
var $t = t.tokenfield = $(t.element).tokenfield();
t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
2017-09-19 13:30:08 +00:00
return token.value.toLowerCase();
2017-08-04 13:54:15 +00:00
});
if (ignorePending) { return tokens; }
var $pendingEl = $($t.parent().find('.token-input')[0]);
var val = ($pendingEl.val() || "").trim();
if (val && tokens.indexOf(val) === -1) {
return tokens.concat(val);
}
return tokens;
2017-09-07 16:54:58 +00:00
};
2017-09-20 15:55:05 +00:00
var $root = $t.parent();
2017-09-21 09:19:23 +00:00
$t.on('tokenfield:removetoken', function () {
2017-09-20 15:55:05 +00:00
$root.find('.token-input').focus();
});
2017-09-07 16:54:58 +00:00
t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) {
var val;
2017-09-19 13:30:08 +00:00
ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens(true).some(function (t) {
2017-09-07 16:54:58 +00:00
if (t === ev.attrs.value) { return ((val = t)); }
})) {
ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); }
}
});
return t;
};
t.setTokens = function (tokens) {
$t.tokenfield('setTokens',
tokens.map(function (token) {
return {
2017-09-19 13:30:08 +00:00
value: token.toLowerCase(),
label: token.toLowerCase(),
2017-09-07 16:54:58 +00:00
};
}));
};
t.focus = function () {
var $temp = $t.closest('.tokenfield').find('.token-input');
$temp.css('width', '20%');
$t.tokenfield('focusInput', $temp[0]);
};
return t;
};
dialog.tagPrompt = function (tags, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
2017-10-03 16:36:34 +00:00
dialog.message([
Messages.tags_add,
2017-10-09 14:57:30 +00:00
h('br'),
Messages.tags_searchHint,
2017-10-03 16:36:34 +00:00
]),
2017-09-07 16:54:58 +00:00
input,
2017-10-09 14:51:57 +00:00
h('center', h('small', Messages.tags_notShared)),
2017-09-07 16:54:58 +00:00
dialog.nav(),
]);
var field = UI.tokenField(input).preventDuplicates(function (val) {
2017-09-19 13:30:08 +00:00
UI.warn(Messages._getKey('tags_duplicate', [val]));
2017-09-07 16:54:58 +00:00
});
2017-09-20 15:55:05 +00:00
var listener;
var close = Util.once(function (result, ev) {
2017-10-05 13:08:34 +00:00
ev.stopPropagation();
ev.preventDefault();
2017-09-20 15:55:05 +00:00
var $frame = $(tagger).fadeOut(150, function () {
stopListening(listener);
$frame.remove();
cb(result, ev);
});
2017-09-07 16:54:58 +00:00
});
2017-10-05 13:08:34 +00:00
var $ok = findOKButton(tagger).click(function (e) {
2017-09-07 16:54:58 +00:00
var tokens = field.getTokens();
2017-10-05 13:08:34 +00:00
close(tokens, e);
2017-09-20 15:55:05 +00:00
});
2017-10-05 13:08:34 +00:00
var $cancel = findCancelButton(tagger).click(function (e) {
close(null, e);
2017-09-07 16:54:58 +00:00
});
listener = listenForKeys(function () {
2017-09-20 15:55:05 +00:00
$ok.click();
}, function () {
$cancel.click();
2017-10-05 13:08:34 +00:00
}, tagger);
$(tagger).on('click submit', function (e) {
e.stopPropagation();
2017-09-07 16:54:58 +00:00
});
2017-09-20 15:55:05 +00:00
document.body.appendChild(tagger);
2017-09-07 16:54:58 +00:00
// :(
setTimeout(function () {
field.setTokens(tags);
field.focus();
});
return tagger;
};
2018-01-17 17:39:45 +00:00
dialog.customModal = function (msg, opt) {
var force = false;
if (typeof(opt) === 'object') {
force = opt.force || false;
} else if (typeof(opt) === 'boolean') {
force = opt;
}
if (typeof(opt) !== 'object') {
opt = {};
}
var message;
if (typeof(msg) === 'string') {
// sanitize
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
}
var close = function (el) {
2018-01-17 17:39:45 +00:00
var $el = $(el).fadeOut(150, function () {
$el.detach();
2018-01-17 17:39:45 +00:00
});
};
2018-01-17 17:39:45 +00:00
var navs = [];
opt.buttons.forEach(function (b) {
if (!b.name || !b.onClick) { return; }
var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name);
2018-01-17 17:39:45 +00:00
$(button).click(function () {
b.onClick();
close($(button).parents('.alertify').first());
2018-01-17 17:39:45 +00:00
});
2018-01-18 09:53:36 +00:00
if (b.keys && b.keys.length) { $(button).attr('data-keys', JSON.stringify(b.keys)); }
2018-01-17 17:39:45 +00:00
navs.push(button);
});
var frame = h('div', [
message,
dialog.nav(navs),
]);
if (opt.forefront) { $(frame).addClass('forefront'); }
return frame;
};
UI.openCustomModal = function (content) {
var frame = dialog.frame([
content
]);
$(frame).find('button[data-keys]').each(function (i, el) {
2018-01-18 09:53:36 +00:00
var keys = JSON.parse($(el).attr('data-keys'));
2018-01-17 17:39:45 +00:00
customListenForKeys(keys, function () {
if (!$(el).is(':visible')) { return; }
$(el).click();
}, frame);
});
document.body.appendChild(frame);
$(frame).focus();
setTimeout(function () {
Notifier.notify();
});
};
2017-09-13 10:32:30 +00:00
UI.alert = function (msg, cb, opt) {
var force = false;
if (typeof(opt) === 'object') {
force = opt.force || false;
} else if (typeof(opt) === 'boolean') {
force = opt;
}
if (typeof(opt) !== 'object') {
2017-09-13 10:32:30 +00:00
opt = {};
}
2017-09-07 16:54:58 +00:00
cb = cb || function () {};
var message;
if (typeof(msg) === 'string') {
// sanitize
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
2017-09-07 16:54:58 +00:00
}
2017-09-07 16:54:58 +00:00
var ok = dialog.okButton();
var frame = dialog.frame([
message,
2017-09-07 16:54:58 +00:00
dialog.nav(ok),
]);
2017-09-13 10:32:30 +00:00
if (opt.forefront) { $(frame).addClass('forefront'); }
2017-09-07 16:54:58 +00:00
var listener;
var close = Util.once(function () {
$(frame).fadeOut(150, function () { $(this).remove(); });
stopListening(listener);
cb();
2017-09-07 16:54:58 +00:00
});
2017-09-21 15:56:24 +00:00
listener = listenForKeys(close, close, ok);
2017-09-07 16:54:58 +00:00
var $ok = $(ok).click(close);
document.body.appendChild(frame);
setTimeout(function () {
$ok.focus();
Notifier.notify();
});
};
UI.prompt = function (msg, def, cb, opt, force) {
cb = cb || function () {};
2017-09-08 15:40:25 +00:00
opt = opt || {};
var input = dialog.textInput();
input.value = typeof(def) === 'string'? def: '';
var message;
if (typeof(msg) === 'string') {
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
}
2017-09-08 15:40:25 +00:00
var ok = dialog.okButton(opt.ok);
var cancel = dialog.cancelButton(opt.cancel);
var frame = dialog.frame([
message,
input,
dialog.nav([ cancel, ok, ]),
]);
var listener;
var close = Util.once(function (result, ev) {
var $frame = $(frame).fadeOut(150, function () {
stopListening(listener);
$frame.remove();
cb(result, ev);
});
2017-09-08 15:40:25 +00:00
});
var $ok = $(ok).click(function (ev) { close(input.value, ev); });
var $cancel = $(cancel).click(function (ev) { close(null, ev); });
2017-09-08 15:40:25 +00:00
listener = listenForKeys(function () { // yes
$ok.click();
2017-05-04 14:16:09 +00:00
}, function () { // no
$cancel.click();
2017-09-21 15:56:24 +00:00
}, input);
2017-09-08 15:40:25 +00:00
document.body.appendChild(frame);
setTimeout(function () {
$(input).select().focus();
Notifier.notify();
2017-09-08 15:40:25 +00:00
});
};
2017-09-11 14:24:43 +00:00
UI.confirm = function (msg, cb, opt, force) {
cb = cb || function () {};
2017-09-08 15:40:25 +00:00
opt = opt || {};
2017-09-08 15:40:25 +00:00
var message;
if (typeof(msg) === 'string') {
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
}
2018-01-24 10:01:15 +00:00
var ok = dialog.okButton(opt.ok, opt.okClass);
var cancel = dialog.cancelButton(opt.cancel, opt.cancelClass);
2017-09-08 15:40:25 +00:00
var frame = dialog.frame([
message,
dialog.nav(opt.reverseOrder?
[ok, cancel]: [cancel, ok]),
]);
var listener;
var close = Util.once(function (bool, ev) {
2017-09-08 15:40:25 +00:00
$(frame).fadeOut(150, function () { $(this).remove(); });
stopListening(listener);
cb(bool, ev);
});
var $ok = $(ok).click(function (ev) { close(true, ev); });
var $cancel = $(cancel).click(function (ev) { close(false, ev); });
2017-09-08 15:40:25 +00:00
if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); }
if (opt.okClass) { $ok.addClass(opt.okClass); }
listener = listenForKeys(function () {
$ok.click();
}, function () {
$cancel.click();
2017-09-22 17:35:06 +00:00
}, ok);
2017-09-08 15:40:25 +00:00
document.body.appendChild(frame);
setTimeout(function () {
Notifier.notify();
$(frame).find('.ok').focus();
2017-09-11 14:24:43 +00:00
if (typeof(opt.done) === 'function') {
opt.done($ok.closest('.dialog'));
}
2017-09-08 15:40:25 +00:00
});
};
UI.log = function (msg) {
Alertify.success(Util.fixHTML(msg));
};
UI.warn = function (msg) {
Alertify.error(Util.fixHTML(msg));
};
/*
* spinner
*/
UI.spinner = function (parent) {
var $target = $('<span>', {
2017-09-07 14:41:11 +00:00
'class': 'fa fa-circle-o-notch fa-spin fa-4x fa-fw',
}).hide();
$(parent).append($target);
return {
show: function () {
$target.css('display', 'inline');
return this;
},
hide: function () {
$target.hide();
return this;
},
get: function () {
return $target;
},
};
};
2017-09-13 14:19:26 +00:00
var LOADING = 'cp-loading';
2018-04-16 13:57:18 +00:00
/*var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
2018-04-16 13:57:18 +00:00
};*/
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
var todo = function () {
2018-04-19 12:22:10 +00:00
var $loading = $('#' + LOADING); //.show();
$loading.css('display', '');
2017-12-21 17:21:10 +00:00
$loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
if (loadingText) {
$('#' + LOADING).find('p').show().text(loadingText);
} else {
$('#' + LOADING).find('p').hide().text('');
}
};
if ($('#' + LOADING).length) {
todo();
} else {
Loading();
todo();
}
};
UI.removeLoadingScreen = function (cb) {
// Release the test blocker, hopefully every test has been registered.
// This test is created in sframe-boot2.js
cb = cb || function () {};
if (Test.__ASYNC_BLOCKER__) { Test.__ASYNC_BLOCKER__.pass(); }
2017-12-21 17:21:10 +00:00
$('#' + LOADING).addClass("cp-loading-hidden");
2017-12-22 13:42:18 +00:00
setTimeout(cb, 750);
2017-09-13 14:19:26 +00:00
var $tip = $('#cp-loading-tip').css('top', '')
2017-06-02 10:13:11 +00:00
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
.css({
'opacity': 0,
'pointer-events': 'none',
});
window.setTimeout(function () {
$tip.remove();
}, 3750);
2017-06-02 10:13:11 +00:00
// jquery.fadeout can get stuck
};
2018-02-13 17:20:13 +00:00
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
2017-09-13 14:19:26 +00:00
$('.cp-loading-spinner-container').hide();
2018-02-13 17:20:13 +00:00
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.9); }
var $error = $('#' + LOADING).find('p').show();
if (error instanceof Element) {
$error.html('').append(error);
} else {
$error.html(error || Messages.error);
}
2018-02-13 17:20:13 +00:00
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
$('#' + LOADING).hide();
if (typeof(exitable) === "function") { exitable(); }
}
2018-02-13 17:20:13 +00:00
});
}
};
2017-09-04 13:09:54 +00:00
var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});
2017-06-29 13:15:40 +00:00
UI.getIcon = function (type) {
2017-09-04 13:09:54 +00:00
var $icon = $defaultIcon.clone();
if (AppConfig.applicationsIcon && AppConfig.applicationsIcon[type]) {
var appClass = ' cp-icon cp-icon-color-'+type;
2017-09-04 13:09:54 +00:00
$icon = $('<span>', {'class': 'fa ' + AppConfig.applicationsIcon[type] + appClass});
2017-06-29 13:15:40 +00:00
}
return $icon;
};
UI.getFileIcon = function (data) {
var $icon = UI.getIcon();
if (!data) { return $icon; }
var href = data.href;
2018-03-13 10:31:08 +00:00
var type = data.type;
if (!href && !type) { return $icon; }
2018-03-13 10:31:08 +00:00
if (!type) { type = Hash.parsePadUrl(href).type; }
$icon = UI.getIcon(type);
return $icon;
};
2017-06-29 13:15:40 +00:00
2017-07-19 15:14:10 +00:00
// Tooltips
2017-07-31 10:29:41 +00:00
UI.clearTooltips = function () {
// If an element is removed from the UI while a tooltip is applied on that element, the tooltip will get hung
// forever, this is a solution which just searches for tooltips which have no corrisponding element and removes
// them.
$('.tippy-popper').each(function (i, el) {
if ($('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
el.remove();
}
});
2017-07-31 10:29:41 +00:00
};
2018-04-12 17:08:08 +00:00
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
$.extend(true, Tippy.defaults, {
placement: 'bottom',
performance: true,
delay: [delay, 0],
//sticky: true,
theme: 'cryptpad',
arrow: true,
maxWidth: '200px',
2018-04-13 13:25:14 +00:00
flip: true,
2018-04-12 17:08:08 +00:00
popperOptions: {
modifiers: {
preventOverflow: { boundariesElement: 'window' }
}
},
//arrowType: 'round',
arrowTransform: 'scale(2)',
zIndex: 100000001
2018-04-12 17:08:08 +00:00
});
2017-07-19 15:14:10 +00:00
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var addTippy = function (i, el) {
2017-07-19 15:14:10 +00:00
if (el.nodeName === 'IFRAME') { return; }
2018-04-12 17:08:08 +00:00
var opts = {
distance: 15
};
Array.prototype.slice.apply(el.attributes).filter(function (obj) {
return /^data-tippy-/.test(obj.name);
}).forEach(function (obj) {
opts[obj.name.slice(11)] = obj.value;
2017-07-19 15:14:10 +00:00
});
2018-04-12 17:08:08 +00:00
Tippy(el, opts);
2017-07-19 15:14:10 +00:00
};
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
//setInterval(UI.clearTooltips, delay);
var checkRemoved = function (x) {
var out = false;
var xId = $(x).attr('aria-describedby');
if (xId) {
if (xId.indexOf('tippy-tooltip-') === 0) {
return true;
}
}
$(x).find('[aria-describedby]').each(function (i, el) {
var id = el.getAttribute('aria-describedby');
if (id.indexOf('tippy-tooltip-') !== 0) { return; }
out = true;
});
return out;
};
$('[title]').each(addTippy);
2017-07-19 15:14:10 +00:00
var observer = new MutationObserver(function(mutations) {
var removed = false;
2017-07-19 15:14:10 +00:00
mutations.forEach(function(mutation) {
2017-11-15 15:31:26 +00:00
if (mutation.type === "childList") {
for (var i = 0; i < mutation.addedNodes.length; i++) {
2018-03-02 17:33:43 +00:00
if ($(mutation.addedNodes[i]).attr('title')) {
addTippy(0, mutation.addedNodes[i]);
}
2017-11-15 15:31:26 +00:00
$(mutation.addedNodes[i]).find('[title]').each(addTippy);
}
for (var j = 0; j < mutation.removedNodes.length; j++) {
removed |= checkRemoved(mutation.removedNodes[j]);
}
}
2017-11-15 15:31:26 +00:00
if (mutation.type === "attributes" && mutation.attributeName === "title") {
addTippy(0, mutation.target);
2017-07-19 15:14:10 +00:00
}
});
if (removed) { UI.clearTooltips(); }
2017-07-19 15:14:10 +00:00
});
observer.observe($('body')[0], {
2017-11-15 15:31:26 +00:00
attributes: true,
2017-07-19 15:14:10 +00:00
childList: true,
characterData: false,
subtree: true
});
};
UI.createCheckbox = Pages.createCheckbox;
2018-04-16 17:07:54 +00:00
UI.createRadio = Pages.createRadio;
2018-04-16 17:07:54 +00:00
return UI;
});