From ed429a749b3a9494615c726fb33b96e64fb9657a Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Fri, 24 Jun 2016 10:51:40 -0700 Subject: [PATCH 1/2] improved 401/403 detection (token validation) --- app/app/app.js | 14 +++++++------- app/app/routes/application.js | 12 +++++++----- app/app/services/session.js | 11 +---------- app/app/utils/net.js | 17 +++++++++++++---- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/app/app/app.js b/app/app/app.js index 39e6de86..24f3e5cc 100644 --- a/app/app/app.js +++ b/app/app/app.js @@ -1,11 +1,11 @@ // Copyright 2016 Documize Inc. . All rights reserved. // -// This software (Documize Community Edition) is licensed under +// This software (Documize Community Edition) is licensed under // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html // // You can operate outside the AGPL restrictions by purchasing // Documize Enterprise Edition and obtaining a commercial license -// by contacting . +// by contacting . // // https://documize.com @@ -18,10 +18,10 @@ let App; Ember.MODEL_FACTORY_INJECTIONS = true; -Ember.RSVP.on('error', function(error) { - console.log("App:"); - console.log(error); -}); +// Ember.RSVP.on('error', function(error) { +// console.log("App:"); +// console.log(error); +// }); App = Ember.Application.extend({ modulePrefix: config.modulePrefix, @@ -31,4 +31,4 @@ App = Ember.Application.extend({ loadInitializers(App, config.modulePrefix); -export default App; \ No newline at end of file +export default App; diff --git a/app/app/routes/application.js b/app/app/routes/application.js index da384933..af1ef9b8 100644 --- a/app/app/routes/application.js +++ b/app/app/routes/application.js @@ -1,15 +1,16 @@ // Copyright 2016 Documize Inc. . All rights reserved. // -// This software (Documize Community Edition) is licensed under +// This software (Documize Community Edition) is licensed under // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html // // You can operate outside the AGPL restrictions by purchasing // Documize Enterprise Edition and obtaining a commercial license -// by contacting . +// by contacting . // // https://documize.com import Ember from 'ember'; +import netUtil from '../utils/net'; export default Ember.Route.extend({ userService: Ember.inject.service('user'), @@ -43,9 +44,10 @@ export default Ember.Route.extend({ error(error, transition) { // jshint ignore: line if (error) { - if (error.status === 401 || error.status === 403) { - return this.transitionTo('auth.login'); - } + if (netUtil.isAjaxAccessError(error)) { + localStorage.clear(); + return this.transitionTo('auth.login'); + } } // Return true to bubble this event to any parent route. diff --git a/app/app/services/session.js b/app/app/services/session.js index 1de1fa04..53599916 100644 --- a/app/app/services/session.js +++ b/app/app/services/session.js @@ -164,15 +164,6 @@ export default Ember.Service.extend({ }); } - // var blockedPopupTest = window.open("http://maintenance.documize.com", "directories=no,height=1,width=1,menubar=no,resizable=no,scrollbars=no,status=no,titlebar=no,top=0,location=no"); - // - // if (!blockedPopupTest) { - // this.set('popupBlocked', true); - // } else { - // blockedPopupTest.close(); - // this.set('popupBlocked', false); - // } - let url = this.get('appMeta').getUrl("public/meta"); return this.get('ajax').request(url) @@ -196,7 +187,7 @@ export default Ember.Service.extend({ this.setSession(token, models.UserModel.create(user)); this.set('ready', true); }).catch((reason) => { - if (reason.status === 401 || reason.status === 403) { + if (netUtil.isAjaxAccessError(reason)) { localStorage.clear(); window.location.href = "/auth/login"; } diff --git a/app/app/utils/net.js b/app/app/utils/net.js index cc75ee1f..0bce88e1 100644 --- a/app/app/utils/net.js +++ b/app/app/utils/net.js @@ -1,11 +1,11 @@ // Copyright 2016 Documize Inc. . All rights reserved. // -// This software (Documize Community Edition) is licensed under +// This software (Documize Community Edition) is licensed under // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html // // You can operate outside the AGPL restrictions by purchasing // Documize Enterprise Edition and obtaining a commercial license -// by contacting . +// by contacting . // // https://documize.com @@ -39,7 +39,16 @@ function getAppUrl(domain) { return window.location.protocol + "//" + domain + leftOvers; } +function isAjaxAccessError(reason) { + if (reason.errors.length > 0 && (reason.errors[0].status === "401" || reason.errors[0].status === "403")) { + return true; + } + + return false; +} + export default { getSubdomain, - getAppUrl -}; \ No newline at end of file + getAppUrl, + isAjaxAccessError, +}; From a213ff85b73ed5d939bfc47714f7f65d5caec2f7 Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Fri, 24 Jun 2016 11:57:37 -0700 Subject: [PATCH 2/2] updated to latest tether.js lib --- app/app/utils/net.js | 5 + app/tests/acceptance/documents-space-test.js | 46 ++--- app/vendor/tether.js | 204 +++++++++++-------- 3 files changed, 150 insertions(+), 105 deletions(-) diff --git a/app/app/utils/net.js b/app/app/utils/net.js index 0bce88e1..4c36cb32 100644 --- a/app/app/utils/net.js +++ b/app/app/utils/net.js @@ -40,6 +40,11 @@ function getAppUrl(domain) { } function isAjaxAccessError(reason) { + console.log(reason); + if (typeof reason === "undefined") { + return false; + } + if (reason.errors.length > 0 && (reason.errors[0].status === "401" || reason.errors[0].status === "403")) { return true; } diff --git a/app/tests/acceptance/documents-space-test.js b/app/tests/acceptance/documents-space-test.js index ab2a60e9..dc7051ef 100644 --- a/app/tests/acceptance/documents-space-test.js +++ b/app/tests/acceptance/documents-space-test.js @@ -3,29 +3,29 @@ import moduleForAcceptance from 'documize/tests/helpers/module-for-acceptance'; moduleForAcceptance('Acceptance | Documents space'); -// test('Adding a new folder space', function(assert) { -// server.create('meta', { allowAnonymousAccess: false }); -// server.createList('folder', 2); -// server.createList('permission', 4); -// userLogin(); -// visit('/s/VzMuyEw_3WqiafcG/my-project'); -// -// andThen(function() { -// let personalSpaces = find('.section div:contains(PERSONAL)').length; -// assert.equal(currentURL(), '/s/VzMuyEw_3WqiafcG/my-project'); -// assert.equal(personalSpaces, 1, '1 personal space is listed'); -// }); -// -// click('#add-folder-button'); -// -// fillIn('#new-folder-name', 'body', 'Test Folder'); -// -// click('.actions div:contains(Add)', 'body'); -// -// andThen(function() { -// assert.equal(currentURL(), '/s/V0Vy5Uw_3QeDAMW9/test-folder'); -// }); -// }); +test('Adding a new folder space', function(assert) { + server.create('meta', { allowAnonymousAccess: false }); + server.createList('folder', 2); + server.createList('permission', 4); + userLogin(); + visit('/s/VzMuyEw_3WqiafcG/my-project'); + + andThen(function() { + let personalSpaces = find('.section div:contains(PERSONAL)').length; + assert.equal(currentURL(), '/s/VzMuyEw_3WqiafcG/my-project'); + assert.equal(personalSpaces, 1, '1 personal space is listed'); + }); + + click('#add-folder-button'); + + fillIn('#new-folder-name', 'body', 'Test Folder'); + + click('.actions div:contains(Add)', 'body'); + + andThen(function() { + assert.equal(currentURL(), '/s/V0Vy5Uw_3QeDAMW9/test-folder'); + }); +}); // test('Adding a document to a space', function(assert) { // server.create('meta', { allowAnonymousAccess: false }); diff --git a/app/vendor/tether.js b/app/vendor/tether.js index cdfc1abc..7df866bc 100644 --- a/app/vendor/tether.js +++ b/app/vendor/tether.js @@ -1,5 +1,4 @@ -// https://github.com/HubSpot/tether -/*! tether 1.1.1 */ +/*! tether 1.3.2 */ (function(root, factory) { if (typeof define === 'function' && define.amd) { @@ -22,24 +21,29 @@ if (typeof TetherBase === 'undefined') { TetherBase = { modules: [] }; } -function getScrollParent(el) { - var _getComputedStyle = getComputedStyle(el); +var zeroElement = null; - var position = _getComputedStyle.position; +function getScrollParents(el) { + // In firefox if the el is inside an iframe with display: none; window.getComputedStyle() will return null; + // https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + var computedStyle = getComputedStyle(el) || {}; + var position = computedStyle.position; + var parents = []; if (position === 'fixed') { - return el; + return [el]; } var parent = el; - while (parent = parent.parentNode) { + while ((parent = parent.parentNode) && parent && parent.nodeType === 1) { var style = undefined; try { style = getComputedStyle(parent); } catch (err) {} if (typeof style === 'undefined' || style === null) { - return parent; + parents.push(parent); + return parents; } var _style = style; @@ -49,12 +53,13 @@ function getScrollParent(el) { if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) { if (position !== 'absolute' || ['relative', 'absolute', 'fixed'].indexOf(style.position) >= 0) { - return parent; + parents.push(parent); } } } - return document.body; + parents.push(document.body); + return parents; } var uniqueId = (function () { @@ -65,14 +70,14 @@ var uniqueId = (function () { })(); var zeroPosCache = {}; -var getOrigin = function getOrigin(doc) { +var getOrigin = function getOrigin() { // getBoundingClientRect is unfortunately too accurate. It introduces a pixel or two of // jitter as the user scrolls that messes with our ability to detect if two positions // are equivilant or not. We place an element at the top left of the page that will // get the same jitter, so we can cancel the two out. - var node = doc._tetherZeroElement; - if (typeof node === 'undefined') { - node = doc.createElement('div'); + var node = zeroElement; + if (!node) { + node = document.createElement('div'); node.setAttribute('data-tether-id', uniqueId()); extend(node.style, { top: 0, @@ -80,9 +85,9 @@ var getOrigin = function getOrigin(doc) { position: 'absolute' }); - doc.body.appendChild(node); + document.body.appendChild(node); - doc._tetherZeroElement = node; + zeroElement = node; } var id = node.getAttribute('data-tether-id'); @@ -104,6 +109,13 @@ var getOrigin = function getOrigin(doc) { return zeroPosCache[id]; }; +function removeUtilElements() { + if (zeroElement) { + document.body.removeChild(zeroElement); + } + zeroElement = null; +}; + function getBounds(el) { var doc = undefined; if (el === document) { @@ -123,7 +135,7 @@ function getBounds(el) { box[k] = rect[k]; } - var origin = getOrigin(doc); + var origin = getOrigin(); box.top -= origin.top; box.left -= origin.left; @@ -305,7 +317,7 @@ var Evented = (function () { }, { key: 'off', value: function off(event, handler) { - if (typeof this.bindings !== 'undefined' && typeof this.bindings[event] !== 'undefined') { + if (typeof this.bindings === 'undefined' || typeof this.bindings[event] === 'undefined') { return; } @@ -359,7 +371,7 @@ var Evented = (function () { })(); TetherBase.Utils = { - getScrollParent: getScrollParent, + getScrollParents: getScrollParents, getBounds: getBounds, getOffsetParent: getOffsetParent, extend: extend, @@ -371,7 +383,8 @@ TetherBase.Utils = { flush: flush, uniqueId: uniqueId, Evented: Evented, - getScrollBarSize: getScrollBarSize + getScrollBarSize: getScrollBarSize, + removeUtilElements: removeUtilElements }; /* globals TetherBase, performance */ @@ -381,14 +394,18 @@ var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = 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 _get = function get(_x6, _x7, _x8) { var _again = true; _function: while (_again) { var object = _x6, property = _x7, receiver = _x8; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x6 = parent; _x7 = property; _x8 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } +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; } + if (typeof TetherBase === 'undefined') { throw new Error('You must include the utils.js file before tether.js'); } var _TetherBase$Utils = TetherBase.Utils; -var getScrollParent = _TetherBase$Utils.getScrollParent; +var getScrollParents = _TetherBase$Utils.getScrollParents; var getBounds = _TetherBase$Utils.getBounds; var getOffsetParent = _TetherBase$Utils.getOffsetParent; var extend = _TetherBase$Utils.extend; @@ -398,6 +415,7 @@ var updateClasses = _TetherBase$Utils.updateClasses; var defer = _TetherBase$Utils.defer; var flush = _TetherBase$Utils.flush; var getScrollBarSize = _TetherBase$Utils.getScrollBarSize; +var removeUtilElements = _TetherBase$Utils.removeUtilElements; function within(a, b) { var diff = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2]; @@ -456,7 +474,7 @@ function now() { return; } - if (typeof pendingTimeout !== 'undefined') { + if (pendingTimeout != null) { clearTimeout(pendingTimeout); pendingTimeout = null; } @@ -466,7 +484,7 @@ function now() { lastDuration = now() - lastCall; }; - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && typeof window.addEventListener !== 'undefined') { ['resize', 'scroll', 'touchmove'].forEach(function (event) { window.addEventListener(event, tick); }); @@ -572,12 +590,15 @@ var parseOffset = function parseOffset(value) { }; var parseAttachment = parseOffset; -var TetherClass = (function () { +var TetherClass = (function (_Evented) { + _inherits(TetherClass, _Evented); + function TetherClass(options) { var _this = this; _classCallCheck(this, TetherClass); + _get(Object.getPrototypeOf(TetherClass.prototype), 'constructor', this).call(this); this.position = this.position.bind(this); tethers.push(this); @@ -668,14 +689,14 @@ var TetherClass = (function () { this.offset = parseOffset(this.options.offset); this.targetOffset = parseOffset(this.options.targetOffset); - if (typeof this.scrollParent !== 'undefined') { + if (typeof this.scrollParents !== 'undefined') { this.disable(); } if (this.targetModifier === 'scroll-handle') { - this.scrollParent = this.target; + this.scrollParents = [this.target]; } else { - this.scrollParent = getScrollParent(this.target); + this.scrollParents = getScrollParents(this.target); } if (!(this.options.enabled === false)) { @@ -796,6 +817,8 @@ var TetherClass = (function () { }, { key: 'enable', value: function enable() { + var _this3 = this; + var pos = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; if (!(this.options.addTargetClasses === false)) { @@ -804,9 +827,11 @@ var TetherClass = (function () { addClass(this.element, this.getClass('enabled')); this.enabled = true; - if (this.scrollParent !== document) { - this.scrollParent.addEventListener('scroll', this.position); - } + this.scrollParents.forEach(function (parent) { + if (parent !== document) { + parent.addEventListener('scroll', _this3.position); + } + }); if (pos) { this.position(); @@ -815,32 +840,40 @@ var TetherClass = (function () { }, { key: 'disable', value: function disable() { + var _this4 = this; + removeClass(this.target, this.getClass('enabled')); removeClass(this.element, this.getClass('enabled')); this.enabled = false; - if (typeof this.scrollParent !== 'undefined') { - this.scrollParent.removeEventListener('scroll', this.position); + if (typeof this.scrollParents !== 'undefined') { + this.scrollParents.forEach(function (parent) { + parent.removeEventListener('scroll', _this4.position); + }); } } }, { key: 'destroy', value: function destroy() { - var _this3 = this; + var _this5 = this; this.disable(); tethers.forEach(function (tether, i) { - if (tether === _this3) { + if (tether === _this5) { tethers.splice(i, 1); - return; } }); + + // Remove any elements we were using for convenience from the DOM + if (tethers.length === 0) { + removeUtilElements(); + } } }, { key: 'updateAttachClasses', value: function updateAttachClasses(elementAttach, targetAttach) { - var _this4 = this; + var _this6 = this; elementAttach = elementAttach || this.attachment; targetAttach = targetAttach || this.targetAttachment; @@ -873,27 +906,27 @@ var TetherClass = (function () { var all = []; sides.forEach(function (side) { - all.push(_this4.getClass('element-attached') + '-' + side); - all.push(_this4.getClass('target-attached') + '-' + side); + all.push(_this6.getClass('element-attached') + '-' + side); + all.push(_this6.getClass('target-attached') + '-' + side); }); defer(function () { - if (!(typeof _this4._addAttachClasses !== 'undefined')) { + if (!(typeof _this6._addAttachClasses !== 'undefined')) { return; } - updateClasses(_this4.element, _this4._addAttachClasses, all); - if (!(_this4.options.addTargetClasses === false)) { - updateClasses(_this4.target, _this4._addAttachClasses, all); + updateClasses(_this6.element, _this6._addAttachClasses, all); + if (!(_this6.options.addTargetClasses === false)) { + updateClasses(_this6.target, _this6._addAttachClasses, all); } - delete _this4._addAttachClasses; + delete _this6._addAttachClasses; }); } }, { key: 'position', value: function position() { - var _this5 = this; + var _this7 = this; var flushChanges = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; @@ -912,7 +945,7 @@ var TetherClass = (function () { this.updateAttachClasses(this.attachment, targetAttachment); var elementPos = this.cache('element-bounds', function () { - return getBounds(_this5.element); + return getBounds(_this7.element); }); var width = elementPos.width; @@ -930,7 +963,7 @@ var TetherClass = (function () { } var targetPos = this.cache('target-bounds', function () { - return _this5.getTargetBounds(); + return _this7.getTargetBounds(); }); var targetSize = targetPos; @@ -1014,10 +1047,10 @@ var TetherClass = (function () { if (typeof this.options.optimizations !== 'undefined' && this.options.optimizations.moveElement !== false && !(typeof this.targetModifier !== 'undefined')) { (function () { - var offsetParent = _this5.cache('target-offsetparent', function () { - return getOffsetParent(_this5.target); + var offsetParent = _this7.cache('target-offsetparent', function () { + return getOffsetParent(_this7.target); }); - var offsetPosition = _this5.cache('target-offsetparent-bounds', function () { + var offsetPosition = _this7.cache('target-offsetparent-bounds', function () { return getBounds(offsetParent); }); var offsetParentStyle = getComputedStyle(offsetParent); @@ -1070,7 +1103,7 @@ var TetherClass = (function () { }, { key: 'move', value: function move(pos) { - var _this6 = this; + var _this8 = this; if (!(typeof this.element.parentNode !== 'undefined')) { return; @@ -1101,8 +1134,8 @@ var TetherClass = (function () { var css = { top: '', left: '', right: '', bottom: '' }; var transcribe = function transcribe(_same, _pos) { - var hasOptimizations = typeof _this6.options.optimizations !== 'undefined'; - var gpu = hasOptimizations ? _this6.options.optimizations.gpu : null; + var hasOptimizations = typeof _this8.options.optimizations !== 'undefined'; + var gpu = hasOptimizations ? _this8.options.optimizations.gpu : null; if (gpu !== false) { var yPos = undefined, xPos = undefined; @@ -1154,14 +1187,14 @@ var TetherClass = (function () { } else if (typeof same.offset !== 'undefined' && same.offset.top && same.offset.left) { (function () { css.position = 'absolute'; - var offsetParent = _this6.cache('target-offsetparent', function () { - return getOffsetParent(_this6.target); + var offsetParent = _this8.cache('target-offsetparent', function () { + return getOffsetParent(_this8.target); }); - if (getOffsetParent(_this6.element) !== offsetParent) { + if (getOffsetParent(_this8.element) !== offsetParent) { defer(function () { - _this6.element.parentNode.removeChild(_this6.element); - offsetParent.appendChild(_this6.element); + _this8.element.parentNode.removeChild(_this8.element); + offsetParent.appendChild(_this8.element); }); } @@ -1176,7 +1209,7 @@ var TetherClass = (function () { if (!moved) { var offsetParentIsBody = true; var currentNode = this.element.parentNode; - while (currentNode && currentNode.tagName !== 'BODY') { + while (currentNode && currentNode.nodeType === 1 && currentNode.tagName !== 'BODY') { if (getComputedStyle(currentNode).position !== 'static') { offsetParentIsBody = false; break; @@ -1198,11 +1231,6 @@ var TetherClass = (function () { var val = css[key]; var elVal = this.element.style[key]; - if (elVal !== '' && val !== '' && ['top', 'left', 'bottom', 'right'].indexOf(key) >= 0) { - elVal = parseFloat(elVal); - val = parseFloat(val); - } - if (elVal !== val) { write = true; writeCSS[key] = val; @@ -1211,14 +1239,14 @@ var TetherClass = (function () { if (write) { defer(function () { - extend(_this6.element.style, writeCSS); + extend(_this8.element.style, writeCSS); }); } } }]); return TetherClass; -})(); +})(Evented); TetherClass.modules = []; @@ -1241,7 +1269,7 @@ var BOUNDS_FORMAT = ['left', 'top', 'right', 'bottom']; function getBoundingRect(tether, to) { if (to === 'scrollParent') { - to = tether.scrollParent; + to = tether.scrollParents[0]; } else if (to === 'window') { to = [pageXOffset, pageYOffset, innerWidth + pageXOffset, innerHeight + pageYOffset]; } @@ -1369,34 +1397,32 @@ TetherBase.modules.push({ } if (changeAttachY === 'together') { - if (top < bounds[1] && tAttachment.top === 'top') { - if (eAttachment.top === 'bottom') { + if (tAttachment.top === 'top') { + if (eAttachment.top === 'bottom' && top < bounds[1]) { top += targetHeight; tAttachment.top = 'bottom'; top += height; eAttachment.top = 'top'; - } else if (eAttachment.top === 'top') { - top += targetHeight; + } else if (eAttachment.top === 'top' && top + height > bounds[3] && top - (height - targetHeight) >= bounds[1]) { + top -= height - targetHeight; tAttachment.top = 'bottom'; - top -= height; eAttachment.top = 'bottom'; } } - if (top + height > bounds[3] && tAttachment.top === 'bottom') { - if (eAttachment.top === 'top') { + if (tAttachment.top === 'bottom') { + if (eAttachment.top === 'top' && top + height > bounds[3]) { top -= targetHeight; tAttachment.top = 'top'; top -= height; eAttachment.top = 'bottom'; - } else if (eAttachment.top === 'bottom') { - top -= targetHeight; + } else if (eAttachment.top === 'bottom' && top < bounds[1] && top + (height * 2 - targetHeight) <= bounds[3]) { + top += height - targetHeight; tAttachment.top = 'top'; - top += height; eAttachment.top = 'top'; } } @@ -1477,14 +1503,24 @@ TetherBase.modules.push({ } if (changeAttachX === 'element' || changeAttachX === 'both') { - if (left < bounds[0] && eAttachment.left === 'right') { - left += width; - eAttachment.left = 'left'; + if (left < bounds[0]) { + if (eAttachment.left === 'right') { + left += width; + eAttachment.left = 'left'; + } else if (eAttachment.left === 'center') { + left += width / 2; + eAttachment.left = 'left'; + } } - if (left + width > bounds[2] && eAttachment.left === 'left') { - left -= width; - eAttachment.left = 'right'; + if (left + width > bounds[2]) { + if (eAttachment.left === 'left') { + left -= width; + eAttachment.left = 'right'; + } else if (eAttachment.left === 'center') { + left -= width / 2; + eAttachment.left = 'right'; + } } } @@ -1578,6 +1614,10 @@ TetherBase.modules.push({ if (tAttachment.top !== targetAttachment.top || tAttachment.left !== targetAttachment.left || eAttachment.top !== _this.attachment.top || eAttachment.left !== _this.attachment.left) { _this.updateAttachClasses(eAttachment, tAttachment); + _this.trigger('update', { + attachment: eAttachment, + targetAttachment: tAttachment + }); } });