[IMP] web: improve traceback reporting
* Do not assault the user with a full traceback on bug, but indicates an error occured and allow him to toggle the traceback * Automatically scroll the traceback to the end (better for python errors) * Propose the user to report the traceback by using a new "Copy" button which correctly copies the full error + traceback
This commit is contained in:
@@ -0,0 +1,753 @@
|
||||
/*!
|
||||
* clipboard.js v1.5.15
|
||||
* https://zenorocha.github.io/clipboard.js
|
||||
*
|
||||
* Licensed MIT © Zeno Rocha
|
||||
*/
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
/**
|
||||
* A polyfill for Element.matches()
|
||||
*/
|
||||
if (Element && !Element.prototype.matches) {
|
||||
var proto = Element.prototype;
|
||||
|
||||
proto.matches = proto.matchesSelector ||
|
||||
proto.mozMatchesSelector ||
|
||||
proto.msMatchesSelector ||
|
||||
proto.oMatchesSelector ||
|
||||
proto.webkitMatchesSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest parent that matches a selector.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {String} selector
|
||||
* @return {Function}
|
||||
*/
|
||||
function closest (element, selector) {
|
||||
while (element && element !== document) {
|
||||
if (element.matches(selector)) return element;
|
||||
element = element.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = closest;
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
var closest = require('./closest');
|
||||
|
||||
/**
|
||||
* Delegates event to a selector.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {String} selector
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @param {Boolean} useCapture
|
||||
* @return {Object}
|
||||
*/
|
||||
function delegate(element, selector, type, callback, useCapture) {
|
||||
var listenerFn = listener.apply(this, arguments);
|
||||
|
||||
element.addEventListener(type, listenerFn, useCapture);
|
||||
|
||||
return {
|
||||
destroy: function() {
|
||||
element.removeEventListener(type, listenerFn, useCapture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds closest match and invokes callback.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {String} selector
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Function}
|
||||
*/
|
||||
function listener(element, selector, type, callback) {
|
||||
return function(e) {
|
||||
e.delegateTarget = closest(e.target, selector);
|
||||
|
||||
if (e.delegateTarget) {
|
||||
callback.call(element, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = delegate;
|
||||
|
||||
},{"./closest":1}],3:[function(require,module,exports){
|
||||
/**
|
||||
* Check if argument is a HTML element.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.node = function(value) {
|
||||
return value !== undefined
|
||||
&& value instanceof HTMLElement
|
||||
&& value.nodeType === 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if argument is a list of HTML elements.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.nodeList = function(value) {
|
||||
var type = Object.prototype.toString.call(value);
|
||||
|
||||
return value !== undefined
|
||||
&& (type === '[object NodeList]' || type === '[object HTMLCollection]')
|
||||
&& ('length' in value)
|
||||
&& (value.length === 0 || exports.node(value[0]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if argument is a string.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.string = function(value) {
|
||||
return typeof value === 'string'
|
||||
|| value instanceof String;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if argument is a function.
|
||||
*
|
||||
* @param {Object} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
exports.fn = function(value) {
|
||||
var type = Object.prototype.toString.call(value);
|
||||
|
||||
return type === '[object Function]';
|
||||
};
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
var is = require('./is');
|
||||
var delegate = require('delegate');
|
||||
|
||||
/**
|
||||
* Validates all params and calls the right
|
||||
* listener function based on its target type.
|
||||
*
|
||||
* @param {String|HTMLElement|HTMLCollection|NodeList} target
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listen(target, type, callback) {
|
||||
if (!target && !type && !callback) {
|
||||
throw new Error('Missing required arguments');
|
||||
}
|
||||
|
||||
if (!is.string(type)) {
|
||||
throw new TypeError('Second argument must be a String');
|
||||
}
|
||||
|
||||
if (!is.fn(callback)) {
|
||||
throw new TypeError('Third argument must be a Function');
|
||||
}
|
||||
|
||||
if (is.node(target)) {
|
||||
return listenNode(target, type, callback);
|
||||
}
|
||||
else if (is.nodeList(target)) {
|
||||
return listenNodeList(target, type, callback);
|
||||
}
|
||||
else if (is.string(target)) {
|
||||
return listenSelector(target, type, callback);
|
||||
}
|
||||
else {
|
||||
throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener to a HTML element
|
||||
* and returns a remove listener function.
|
||||
*
|
||||
* @param {HTMLElement} node
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listenNode(node, type, callback) {
|
||||
node.addEventListener(type, callback);
|
||||
|
||||
return {
|
||||
destroy: function() {
|
||||
node.removeEventListener(type, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener to a list of HTML elements
|
||||
* and returns a remove listener function.
|
||||
*
|
||||
* @param {NodeList|HTMLCollection} nodeList
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listenNodeList(nodeList, type, callback) {
|
||||
Array.prototype.forEach.call(nodeList, function(node) {
|
||||
node.addEventListener(type, callback);
|
||||
});
|
||||
|
||||
return {
|
||||
destroy: function() {
|
||||
Array.prototype.forEach.call(nodeList, function(node) {
|
||||
node.removeEventListener(type, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener to a selector
|
||||
* and returns a remove listener function.
|
||||
*
|
||||
* @param {String} selector
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {Object}
|
||||
*/
|
||||
function listenSelector(selector, type, callback) {
|
||||
return delegate(document.body, selector, type, callback);
|
||||
}
|
||||
|
||||
module.exports = listen;
|
||||
|
||||
},{"./is":3,"delegate":2}],5:[function(require,module,exports){
|
||||
function select(element) {
|
||||
var selectedText;
|
||||
|
||||
if (element.nodeName === 'SELECT') {
|
||||
element.focus();
|
||||
|
||||
selectedText = element.value;
|
||||
}
|
||||
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
||||
element.focus();
|
||||
element.setSelectionRange(0, element.value.length);
|
||||
|
||||
selectedText = element.value;
|
||||
}
|
||||
else {
|
||||
if (element.hasAttribute('contenteditable')) {
|
||||
element.focus();
|
||||
}
|
||||
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
selectedText = selection.toString();
|
||||
}
|
||||
|
||||
return selectedText;
|
||||
}
|
||||
|
||||
module.exports = select;
|
||||
|
||||
},{}],6:[function(require,module,exports){
|
||||
function E () {
|
||||
// Keep this empty so it's easier to inherit from
|
||||
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
|
||||
}
|
||||
|
||||
E.prototype = {
|
||||
on: function (name, callback, ctx) {
|
||||
var e = this.e || (this.e = {});
|
||||
|
||||
(e[name] || (e[name] = [])).push({
|
||||
fn: callback,
|
||||
ctx: ctx
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
once: function (name, callback, ctx) {
|
||||
var self = this;
|
||||
function listener () {
|
||||
self.off(name, listener);
|
||||
callback.apply(ctx, arguments);
|
||||
};
|
||||
|
||||
listener._ = callback
|
||||
return this.on(name, listener, ctx);
|
||||
},
|
||||
|
||||
emit: function (name) {
|
||||
var data = [].slice.call(arguments, 1);
|
||||
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
||||
var i = 0;
|
||||
var len = evtArr.length;
|
||||
|
||||
for (i; i < len; i++) {
|
||||
evtArr[i].fn.apply(evtArr[i].ctx, data);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
off: function (name, callback) {
|
||||
var e = this.e || (this.e = {});
|
||||
var evts = e[name];
|
||||
var liveEvents = [];
|
||||
|
||||
if (evts && callback) {
|
||||
for (var i = 0, len = evts.length; i < len; i++) {
|
||||
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
|
||||
liveEvents.push(evts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove event from queue to prevent memory leak
|
||||
// Suggested by https://github.com/lazd
|
||||
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
|
||||
|
||||
(liveEvents.length)
|
||||
? e[name] = liveEvents
|
||||
: delete e[name];
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = E;
|
||||
|
||||
},{}],7:[function(require,module,exports){
|
||||
(function (global, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(['module', 'select'], factory);
|
||||
} else if (typeof exports !== "undefined") {
|
||||
factory(module, require('select'));
|
||||
} else {
|
||||
var mod = {
|
||||
exports: {}
|
||||
};
|
||||
factory(mod, global.select);
|
||||
global.clipboardAction = mod.exports;
|
||||
}
|
||||
})(this, function (module, _select) {
|
||||
'use strict';
|
||||
|
||||
var _select2 = _interopRequireDefault(_select);
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||||
return typeof obj;
|
||||
} : function (obj) {
|
||||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||||
};
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
}
|
||||
|
||||
var _createClass = function () {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i];
|
||||
descriptor.enumerable = descriptor.enumerable || false;
|
||||
descriptor.configurable = true;
|
||||
if ("value" in descriptor) descriptor.writable = true;
|
||||
Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
};
|
||||
}();
|
||||
|
||||
var ClipboardAction = function () {
|
||||
/**
|
||||
* @param {Object} options
|
||||
*/
|
||||
function ClipboardAction(options) {
|
||||
_classCallCheck(this, ClipboardAction);
|
||||
|
||||
this.resolveOptions(options);
|
||||
this.initSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines base properties passed from constructor.
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
|
||||
_createClass(ClipboardAction, [{
|
||||
key: 'resolveOptions',
|
||||
value: function resolveOptions() {
|
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
this.action = options.action;
|
||||
this.emitter = options.emitter;
|
||||
this.target = options.target;
|
||||
this.text = options.text;
|
||||
this.trigger = options.trigger;
|
||||
|
||||
this.selectedText = '';
|
||||
}
|
||||
}, {
|
||||
key: 'initSelection',
|
||||
value: function initSelection() {
|
||||
if (this.text) {
|
||||
this.selectFake();
|
||||
} else if (this.target) {
|
||||
this.selectTarget();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'selectFake',
|
||||
value: function selectFake() {
|
||||
var _this = this;
|
||||
|
||||
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
|
||||
|
||||
this.removeFake();
|
||||
|
||||
this.fakeHandlerCallback = function () {
|
||||
return _this.removeFake();
|
||||
};
|
||||
this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || true;
|
||||
|
||||
this.fakeElem = document.createElement('textarea');
|
||||
// Prevent zooming on iOS
|
||||
this.fakeElem.style.fontSize = '12pt';
|
||||
// Reset box model
|
||||
this.fakeElem.style.border = '0';
|
||||
this.fakeElem.style.padding = '0';
|
||||
this.fakeElem.style.margin = '0';
|
||||
// Move element out of screen horizontally
|
||||
this.fakeElem.style.position = 'absolute';
|
||||
this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
|
||||
// Move element to the same position vertically
|
||||
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
|
||||
this.fakeElem.addEventListener('focus', window.scrollTo(0, yPosition));
|
||||
this.fakeElem.style.top = yPosition + 'px';
|
||||
|
||||
this.fakeElem.setAttribute('readonly', '');
|
||||
this.fakeElem.value = this.text;
|
||||
|
||||
document.body.appendChild(this.fakeElem);
|
||||
|
||||
this.selectedText = (0, _select2.default)(this.fakeElem);
|
||||
this.copyText();
|
||||
}
|
||||
}, {
|
||||
key: 'removeFake',
|
||||
value: function removeFake() {
|
||||
if (this.fakeHandler) {
|
||||
document.body.removeEventListener('click', this.fakeHandlerCallback);
|
||||
this.fakeHandler = null;
|
||||
this.fakeHandlerCallback = null;
|
||||
}
|
||||
|
||||
if (this.fakeElem) {
|
||||
document.body.removeChild(this.fakeElem);
|
||||
this.fakeElem = null;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'selectTarget',
|
||||
value: function selectTarget() {
|
||||
this.selectedText = (0, _select2.default)(this.target);
|
||||
this.copyText();
|
||||
}
|
||||
}, {
|
||||
key: 'copyText',
|
||||
value: function copyText() {
|
||||
var succeeded = void 0;
|
||||
|
||||
try {
|
||||
succeeded = document.execCommand(this.action);
|
||||
} catch (err) {
|
||||
succeeded = false;
|
||||
}
|
||||
|
||||
this.handleResult(succeeded);
|
||||
}
|
||||
}, {
|
||||
key: 'handleResult',
|
||||
value: function handleResult(succeeded) {
|
||||
this.emitter.emit(succeeded ? 'success' : 'error', {
|
||||
action: this.action,
|
||||
text: this.selectedText,
|
||||
trigger: this.trigger,
|
||||
clearSelection: this.clearSelection.bind(this)
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'clearSelection',
|
||||
value: function clearSelection() {
|
||||
if (this.target) {
|
||||
this.target.blur();
|
||||
}
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
}, {
|
||||
key: 'destroy',
|
||||
value: function destroy() {
|
||||
this.removeFake();
|
||||
}
|
||||
}, {
|
||||
key: 'action',
|
||||
set: function set() {
|
||||
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
|
||||
|
||||
this._action = action;
|
||||
|
||||
if (this._action !== 'copy' && this._action !== 'cut') {
|
||||
throw new Error('Invalid "action" value, use either "copy" or "cut"');
|
||||
}
|
||||
},
|
||||
get: function get() {
|
||||
return this._action;
|
||||
}
|
||||
}, {
|
||||
key: 'target',
|
||||
set: function set(target) {
|
||||
if (target !== undefined) {
|
||||
if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
|
||||
if (this.action === 'copy' && target.hasAttribute('disabled')) {
|
||||
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
|
||||
}
|
||||
|
||||
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
|
||||
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
|
||||
}
|
||||
|
||||
this._target = target;
|
||||
} else {
|
||||
throw new Error('Invalid "target" value, use a valid Element');
|
||||
}
|
||||
}
|
||||
},
|
||||
get: function get() {
|
||||
return this._target;
|
||||
}
|
||||
}]);
|
||||
|
||||
return ClipboardAction;
|
||||
}();
|
||||
|
||||
module.exports = ClipboardAction;
|
||||
});
|
||||
|
||||
},{"select":5}],8:[function(require,module,exports){
|
||||
(function (global, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory);
|
||||
} else if (typeof exports !== "undefined") {
|
||||
factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener'));
|
||||
} else {
|
||||
var mod = {
|
||||
exports: {}
|
||||
};
|
||||
factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener);
|
||||
global.clipboard = mod.exports;
|
||||
}
|
||||
})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) {
|
||||
'use strict';
|
||||
|
||||
var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
|
||||
|
||||
var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
|
||||
|
||||
var _goodListener2 = _interopRequireDefault(_goodListener);
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
}
|
||||
|
||||
var _createClass = function () {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i];
|
||||
descriptor.enumerable = descriptor.enumerable || false;
|
||||
descriptor.configurable = true;
|
||||
if ("value" in descriptor) descriptor.writable = true;
|
||||
Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
};
|
||||
}();
|
||||
|
||||
function _possibleConstructorReturn(self, call) {
|
||||
if (!self) {
|
||||
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||
}
|
||||
|
||||
return call && (typeof call === "object" || typeof call === "function") ? call : self;
|
||||
}
|
||||
|
||||
function _inherits(subClass, superClass) {
|
||||
if (typeof superClass !== "function" && superClass !== null) {
|
||||
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
|
||||
}
|
||||
|
||||
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
||||
constructor: {
|
||||
value: subClass,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
|
||||
}
|
||||
|
||||
var Clipboard = function (_Emitter) {
|
||||
_inherits(Clipboard, _Emitter);
|
||||
|
||||
/**
|
||||
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
|
||||
* @param {Object} options
|
||||
*/
|
||||
function Clipboard(trigger, options) {
|
||||
_classCallCheck(this, Clipboard);
|
||||
|
||||
var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));
|
||||
|
||||
_this.resolveOptions(options);
|
||||
_this.listenClick(trigger);
|
||||
return _this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if attributes would be resolved using internal setter functions
|
||||
* or custom functions that were passed in the constructor.
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
|
||||
_createClass(Clipboard, [{
|
||||
key: 'resolveOptions',
|
||||
value: function resolveOptions() {
|
||||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
|
||||
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
|
||||
this.text = typeof options.text === 'function' ? options.text : this.defaultText;
|
||||
}
|
||||
}, {
|
||||
key: 'listenClick',
|
||||
value: function listenClick(trigger) {
|
||||
var _this2 = this;
|
||||
|
||||
this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
|
||||
return _this2.onClick(e);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'onClick',
|
||||
value: function onClick(e) {
|
||||
var trigger = e.delegateTarget || e.currentTarget;
|
||||
|
||||
if (this.clipboardAction) {
|
||||
this.clipboardAction = null;
|
||||
}
|
||||
|
||||
this.clipboardAction = new _clipboardAction2.default({
|
||||
action: this.action(trigger),
|
||||
target: this.target(trigger),
|
||||
text: this.text(trigger),
|
||||
trigger: trigger,
|
||||
emitter: this
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'defaultAction',
|
||||
value: function defaultAction(trigger) {
|
||||
return getAttributeValue('action', trigger);
|
||||
}
|
||||
}, {
|
||||
key: 'defaultTarget',
|
||||
value: function defaultTarget(trigger) {
|
||||
var selector = getAttributeValue('target', trigger);
|
||||
|
||||
if (selector) {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'defaultText',
|
||||
value: function defaultText(trigger) {
|
||||
return getAttributeValue('text', trigger);
|
||||
}
|
||||
}, {
|
||||
key: 'destroy',
|
||||
value: function destroy() {
|
||||
this.listener.destroy();
|
||||
|
||||
if (this.clipboardAction) {
|
||||
this.clipboardAction.destroy();
|
||||
this.clipboardAction = null;
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
return Clipboard;
|
||||
}(_tinyEmitter2.default);
|
||||
|
||||
/**
|
||||
* Helper function to retrieve attribute value.
|
||||
* @param {String} suffix
|
||||
* @param {Element} element
|
||||
*/
|
||||
function getAttributeValue(suffix, element) {
|
||||
var attribute = 'data-clipboard-' + suffix;
|
||||
|
||||
if (!element.hasAttribute(attribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return element.getAttribute(attribute);
|
||||
}
|
||||
|
||||
module.exports = Clipboard;
|
||||
});
|
||||
|
||||
},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)
|
||||
});
|
||||
@@ -122,7 +122,7 @@ var WebClient = Widget.extend({
|
||||
window.onerror = function (message, file, line, col, error) {
|
||||
var traceback = error ? error.stack : '';
|
||||
crash_manager.show_error({
|
||||
type: _t("Client Error"),
|
||||
type: _t("Odoo Client Error"),
|
||||
message: message,
|
||||
data: {debug: file + ':' + line + "\n" + _t('Traceback:') + "\n" + traceback}
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ var CrashManager = core.Class.extend({
|
||||
if (this.connection_lost) {
|
||||
return;
|
||||
}
|
||||
if (error.code == -32098) {
|
||||
if (error.code === -32098) {
|
||||
core.bus.trigger('connection_lost');
|
||||
this.connection_lost = true;
|
||||
var timeinterval = setInterval(function() {
|
||||
@@ -55,11 +55,11 @@ var CrashManager = core.Class.extend({
|
||||
return;
|
||||
}
|
||||
if (error.data.name === "openerp.http.SessionExpiredException" || error.data.name === "werkzeug.exceptions.Forbidden") {
|
||||
this.show_warning({type: "Session Expired", data: { message: _t("Your Odoo session expired. Please refresh the current web page.") }});
|
||||
this.show_warning({type: _t("Odoo Session Expired"), data: {message: _t("Your Odoo session expired. Please refresh the current web page.")}});
|
||||
return;
|
||||
}
|
||||
if (_.has(map_title, error.data.exception_type)) {
|
||||
if(error.data.exception_type == 'except_orm'){
|
||||
if(error.data.exception_type === 'except_orm'){
|
||||
if(error.data.arguments[1]) {
|
||||
error = _.extend({}, error,
|
||||
{
|
||||
@@ -93,7 +93,7 @@ var CrashManager = core.Class.extend({
|
||||
}
|
||||
|
||||
this.show_warning(error);
|
||||
//InternalError
|
||||
//InternalError
|
||||
|
||||
} else {
|
||||
this.show_error(error);
|
||||
@@ -105,23 +105,53 @@ var CrashManager = core.Class.extend({
|
||||
}
|
||||
new Dialog(this, {
|
||||
size: 'medium',
|
||||
title: "Odoo " + (_.str.capitalize(error.type) || _t("Warning")),
|
||||
title: _.str.capitalize(error.type || error.message) || _t("Odoo Warning"),
|
||||
subtitle: error.data.title,
|
||||
$content: $('<div>').html(QWeb.render('CrashManager.warning', {error: error}))
|
||||
$content: $(QWeb.render('CrashManager.warning', {error: error}))
|
||||
}).open();
|
||||
},
|
||||
show_error: function(error) {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
new Dialog(this, {
|
||||
title: "Odoo " + _.str.capitalize(error.type),
|
||||
$content: QWeb.render('CrashManager.error', {error: error})
|
||||
var dialog = new Dialog(this, {
|
||||
title: _.str.capitalize(error.type || error.message) || _t("Odoo Error"),
|
||||
$content: $(QWeb.render('CrashManager.error', {error: error}))
|
||||
}).open();
|
||||
|
||||
// When the dialog opens, initialize the copy feature and destroy it when the dialog is closed
|
||||
var $clipboardBtn;
|
||||
var clipboard;
|
||||
dialog.opened(function () {
|
||||
$clipboardBtn = dialog.$(".o_clipboard_button");
|
||||
$clipboardBtn.tooltip({title: _t("Copied !"), trigger: "manual", placement: "left"});
|
||||
clipboard = new window.Clipboard($clipboardBtn[0], {
|
||||
text: function () {
|
||||
return (_t("Error") + ":\n" + error.message + "\n\n" + error.data.debug).trim();
|
||||
}
|
||||
});
|
||||
clipboard.on("success", function (e) {
|
||||
_.defer(function () {
|
||||
$clipboardBtn.tooltip("show");
|
||||
_.delay(function () {
|
||||
$clipboardBtn.tooltip("hide");
|
||||
}, 800);
|
||||
});
|
||||
});
|
||||
});
|
||||
dialog.on("closed", this, function () {
|
||||
$clipboardBtn.tooltip("destroy");
|
||||
clipboard.destroy();
|
||||
});
|
||||
|
||||
// When the full traceback is shown, scroll it to the end (useful for better python error reporting)
|
||||
dialog.$(".o_error_detail").on("shown.bs.collapse", function (e) {
|
||||
e.target.scrollTop = e.target.scrollHeight;
|
||||
});
|
||||
},
|
||||
show_message: function(exception) {
|
||||
this.show_error({
|
||||
type: _t("Client Error"),
|
||||
type: _t("Odoo Client Error"),
|
||||
message: exception,
|
||||
data: {debug: ""}
|
||||
});
|
||||
@@ -162,7 +192,7 @@ var RedirectWarningHandler = Dialog.extend(ExceptionHandler, {
|
||||
|
||||
new Dialog(this, {
|
||||
size: 'medium',
|
||||
title: "Odoo " + (_.str.capitalize(error.type) || "Warning"),
|
||||
title: _.str.capitalize(error.type) || _t("Odoo Warning"),
|
||||
buttons: [
|
||||
{text: error.data.arguments[2], classes : "btn-primary", click: function() {
|
||||
window.location.href = '#action='+error.data.arguments[1];
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (min-width: @screen-sm-min) {
|
||||
.modal-dialog {
|
||||
@@ -52,6 +52,23 @@
|
||||
|
||||
.modal-body {
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
|
||||
&.o_dialog_error {
|
||||
overflow: visible;
|
||||
.o-flex-display();
|
||||
.o-flex-flow(column, nowrap);
|
||||
|
||||
> .alert, > button {
|
||||
.o-flex(0, 0, auto);
|
||||
}
|
||||
|
||||
> .o_error_detail {
|
||||
.o-flex(1, 1, auto);
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,21 +19,30 @@
|
||||
<span class="o_icon fa fa-3x fa-exclamation"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="CrashManager.warning">
|
||||
<div class="o_dialog_warning">
|
||||
<t t-js="d">
|
||||
var message = d.message ? d.message : d.error.data.message;
|
||||
d.html_error = context.engine.tools.html_escape(message).replace(/\n/g, '<br/>');
|
||||
</t>
|
||||
<t t-raw="html_error"/>
|
||||
|
||||
<div t-name="CrashManager.warning" class="o_dialog_warning">
|
||||
<t t-js="d">
|
||||
var message = d.message ? d.message : d.error.data.message;
|
||||
d.html_error = context.engine.tools.html_escape(message).replace(/\n/g, '<br/>');
|
||||
</t>
|
||||
<t t-raw="html_error"/>
|
||||
</div>
|
||||
<div t-name="CrashManager.error" class="o_dialog_error">
|
||||
<div class="alert alert-warning clearfix" role="alert">
|
||||
<button class="btn btn-sm btn-primary pull-right ml8 o_clipboard_button">
|
||||
<i class="fa fa-clipboard mr8"/>Copy the full error to clipboard
|
||||
</button>
|
||||
<p><b>An error occurred</b></p>
|
||||
<p>Please use the copy button to report the error to your support service.</p>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="CrashManager.error">
|
||||
<div class="o_error_detail">
|
||||
|
||||
<t t-set="errUID" t-value="_.uniqueId()"/>
|
||||
<button class="btn btn-sm btn-link" t-att-data-target="'#o_error_detail' + errUID" data-toggle="collapse">See details</button>
|
||||
<div t-att-id="'o_error_detail' + errUID" class="collapse alert alert-danger o_error_detail">
|
||||
<pre><t t-esc="error.message"/></pre>
|
||||
<pre><t t-esc="error.data.debug"/></pre>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<form t-name="ChangePassword" name="change_password_form" method="POST">
|
||||
<div class="o_form_view">
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
<script src="/web/static/lib/bootstrap/js/tab.js"></script>
|
||||
<script src="/web/static/lib/bootstrap/js/transition.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/clipboard/clipboard.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/boot.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/config.js"></script>
|
||||
@@ -73,9 +77,6 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/services/core.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/dialog.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/framework/local_storage.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/select2/select2.js"></script>
|
||||
</template>
|
||||
|
||||
<template id="web.assets_backend">
|
||||
|
||||
Reference in New Issue
Block a user