From 14330b70fa7081fc297da82a8f39e1cfe408c215 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 2 May 2017 09:22:59 +0200 Subject: [PATCH 1/2] Vendorize angular-inview Contains a patch for page visiblity support --- dist/package.sh | 1 - gather-licenses.sh | 2 +- index.html | 2 +- npm-shrinkwrap.json | 5 - package.json | 1 - public/libs/angular-inview/LICENSE | 21 + public/libs/angular-inview/README.md | 3 + public/libs/angular-inview/angular-inview.js | 388 +++++++++++++++++++ 8 files changed, 414 insertions(+), 9 deletions(-) create mode 100644 public/libs/angular-inview/LICENSE create mode 100644 public/libs/angular-inview/README.md create mode 100644 public/libs/angular-inview/angular-inview.js diff --git a/dist/package.sh b/dist/package.sh index b1b5ea731..2724fa5db 100755 --- a/dist/package.sh +++ b/dist/package.sh @@ -74,7 +74,6 @@ targets=( angular-translate/dist/angular-translate.min.js angular-translate/dist/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js - angular-inview/angular-inview.js angular-messages/angular-messages.min.js sdp/sdp.js ) diff --git a/gather-licenses.sh b/gather-licenses.sh index 9d7b09307..aa684da71 100644 --- a/gather-licenses.sh +++ b/gather-licenses.sh @@ -6,7 +6,7 @@ LICENSE_FILES=( 'angular' 'node_modules/angular/LICENSE.md' 'angular-animate' 'node_modules/angular-animate/LICENSE.md' 'angular-aria' 'node_modules/angular-aria/LICENSE.md' - 'angular-inview' 'node_modules/angular-inview/LICENSE' + 'angular-inview' 'public/libs/angular-inview/LICENSE' 'angular-material' 'node_modules/angular-material/LICENSE' 'angular-messages' 'node_modules/angular-messages/LICENSE.md' 'angular-qrcode' '.licenses/angular-qrcode' diff --git a/index.html b/index.html index 42b1c2ee4..bcb91bb54 100644 --- a/index.html +++ b/index.html @@ -109,7 +109,7 @@

- + diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d01ae34d3..181ab3f0c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -89,11 +89,6 @@ "from": "angular-aria@>=1.5.10 <1.6.0", "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.5.11.tgz" }, - "angular-inview": { - "version": "2.1.0", - "from": "angular-inview@>=2.1.0 <2.2.0", - "resolved": "https://registry.npmjs.org/angular-inview/-/angular-inview-2.1.0.tgz" - }, "angular-material": { "version": "1.1.1", "from": "angular-material@>=1.1.1 <1.2.0", diff --git a/package.json b/package.json index 29620a8da..ba51453dd 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "angular": "~1.5.10", "angular-animate": "~1.5.10", "angular-aria": "~1.5.10", - "angular-inview": "~2.1.0", "angular-material": "~1.1.1", "angular-messages": "~1.6.1", "angular-qrcode": "~6.2.1", diff --git a/public/libs/angular-inview/LICENSE b/public/libs/angular-inview/LICENSE new file mode 100644 index 000000000..7190e3a94 --- /dev/null +++ b/public/libs/angular-inview/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Nicola Peduzzi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/public/libs/angular-inview/README.md b/public/libs/angular-inview/README.md new file mode 100644 index 000000000..051f64ecb --- /dev/null +++ b/public/libs/angular-inview/README.md @@ -0,0 +1,3 @@ +angular-inview library with pull request 122 applied (commit e7c57e031489b2e7c2aaeed6dff09455495fcb51) + +https://github.com/thenikso/angular-inview/pull/122 diff --git a/public/libs/angular-inview/angular-inview.js b/public/libs/angular-inview/angular-inview.js new file mode 100644 index 000000000..5dadbe804 --- /dev/null +++ b/public/libs/angular-inview/angular-inview.js @@ -0,0 +1,388 @@ +// # Angular-Inview +// - Author: [Nicola Peduzzi](https://github.com/thenikso) +// - Repository: https://github.com/thenikso/angular-inview +// - Install with: `npm install angular-inview` +// - Version: **2.2.0** +(function() { +'use strict'; + +// An [angular.js](https://angularjs.org) directive to evaluate an expression if +// a DOM element is or not in the current visible browser viewport. +// Use it in your AngularJS app by including the javascript and requireing it: +// +// `angular.module('myApp', ['angular-inview'])` +var angularInviewModule = angular.module('angular-inview', []) + +// ## in-view directive +// +// ### Usage +// ```html +// +// ``` +.directive('inView', ['$parse', inViewDirective]) + +// ## in-view-container directive +.directive('inViewContainer', inViewContainerDirective); + +// ## Implementation +function inViewDirective ($parse) { + return { + // Evaluate the expression passet to the attribute `in-view` when the DOM + // element is visible in the viewport. + restrict: 'A', + require: '?^^inViewContainer', + link: function inViewDirectiveLink (scope, element, attrs, container) { + // in-view-options attribute can be specified with an object expression + // containing: + // - `offset`: An array of values to offset the element position. + // Offsets are expressed as arrays of 4 numbers [top, right, bottom, left]. + // Like CSS, you can also specify only 2 numbers [top/bottom, left/right]. + // Instead of numbers, some array elements can be a string with a percentage. + // Positive numbers are offsets outside the element rectangle and + // negative numbers are offsets to the inside. + // - `viewportOffset`: Like the element offset but appied to the viewport. + // - `generateDirection`: Indicate if the `direction` information should + // be included in `$inviewInfo` (default false). + // - `generateParts`: Indicate if the `parts` information should + // be included in `$inviewInfo` (default false). + // - `throttle`: Specify a number of milliseconds by which to limit the + // number of incoming events. + var options = {}; + if (attrs.inViewOptions) { + options = scope.$eval(attrs.inViewOptions); + } + if (options.offset) { + options.offset = normalizeOffset(options.offset); + } + if (options.viewportOffset) { + options.viewportOffset = normalizeOffset(options.viewportOffset); + } + + // Build reactive chain from an initial event + var viewportEventSignal = signalSingle({ type: 'initial' }) + + // Merged with the window events + .merge(signalFromEvent(window, 'checkInView click ready wheel mousewheel DomMouseScroll MozMousePixelScroll resize scroll touchmove mouseup keydown')); + + // Merged with the page visibility events + if (options.considerPageVisibility) { + viewportEventSignal = viewportEventSignal.merge(signalFromEvent(document, 'visibilitychange')); + } + + // Merge with container's events signal + if (container) { + viewportEventSignal = viewportEventSignal.merge(container.eventsSignal); + } + + // Throttle if option specified + if (options.throttle) { + viewportEventSignal = viewportEventSignal.throttle(options.throttle); + } + + // Map to viewport intersection and in-view informations + var inviewInfoSignal = viewportEventSignal + + // Inview information structure contains: + // - `inView`: a boolean value indicating if the element is + // visible in the viewport; + // - `changed`: a boolean value indicating if the inview status + // changed after the last event; + // - `event`: the event that initiated the in-view check; + .map(function(event) { + var viewportRect; + if (container) { + viewportRect = container.getViewportRect(); + // TODO merge with actual window! + } else { + viewportRect = getViewportRect(); + } + viewportRect = offsetRect(viewportRect, options.viewportOffset); + var elementRect = offsetRect(element[0].getBoundingClientRect(), options.offset); + var isVisible = !!(element[0].offsetWidth || element[0].offsetHeight || element[0].getClientRects().length); + var documentVisible = !options.considerPageVisibility || document.visibilityState === 'visible' || document.hidden === false; + var info = { + inView: documentVisible && isVisible && intersectRect(elementRect, viewportRect), + event: event, + element: element, + elementRect: elementRect, + viewportRect: viewportRect + }; + // Add inview parts + if (options.generateParts && info.inView) { + info.parts = {}; + info.parts.top = elementRect.top >= viewportRect.top; + info.parts.left = elementRect.left >= viewportRect.left; + info.parts.bottom = elementRect.bottom <= viewportRect.bottom; + info.parts.right = elementRect.right <= viewportRect.right; + } + return info; + }) + + // Add the changed information to the inview structure. + .scan({}, function (lastInfo, newInfo) { + // Add inview direction info + if (options.generateDirection && newInfo.inView && lastInfo.elementRect) { + newInfo.direction = { + horizontal: newInfo.elementRect.left - lastInfo.elementRect.left, + vertical: newInfo.elementRect.top - lastInfo.elementRect.top + }; + } + // Calculate changed flag + newInfo.changed = + newInfo.inView !== lastInfo.inView || + !angular.equals(newInfo.parts, lastInfo.parts) || + !angular.equals(newInfo.direction, lastInfo.direction); + return newInfo; + }) + + // Filters only informations that should be forwarded to the callback + .filter(function (info) { + // Don't forward if no relevant infomation changed + if (!info.changed) { + return false; + } + // Don't forward if not initially in-view + if (info.event.type === 'initial' && !info.inView) { + return false; + } + return true; + }); + + // Execute in-view callback + var inViewExpression = $parse(attrs.inView); + var dispose = inviewInfoSignal.subscribe(function (info) { + scope.$applyAsync(function () { + inViewExpression(scope, { + '$inview': info.inView, + '$inviewInfo': info + }); + }); + }); + + // Dispose of reactive chain + scope.$on('$destroy', dispose); + } + } +} + +function inViewContainerDirective () { + return { + restrict: 'A', + controller: ['$element', function ($element) { + this.element = $element; + this.eventsSignal = signalFromEvent($element, 'scroll'); + this.getViewportRect = function () { + return $element[0].getBoundingClientRect(); + }; + }] + } +} + +// ## Utilities + +function getViewportRect () { + var result = { + top: 0, + left: 0, + width: window.innerWidth, + right: window.innerWidth, + height: window.innerHeight, + bottom: window.innerHeight + }; + if (result.height) { + return result; + } + var mode = document.compatMode; + if (mode === 'CSS1Compat') { + result.width = result.right = document.documentElement.clientWidth; + result.height = result.bottom = document.documentElement.clientHeight; + } else { + result.width = result.right = document.body.clientWidth; + result.height = result.bottom = document.body.clientHeight; + } + return result; +} + +function intersectRect (r1, r2) { + return !(r2.left > r1.right || + r2.right < r1.left || + r2.top > r1.bottom || + r2.bottom < r1.top); +} + +function normalizeOffset (offset) { + if (!angular.isArray(offset)) { + return [offset, offset, offset, offset]; + } + if (offset.length == 2) { + return offset.concat(offset); + } + else if (offset.length == 3) { + return offset.concat([offset[1]]); + } + return offset; +} + +function offsetRect (rect, offset) { + if (!offset) { + return rect; + } + var offsetObject = { + top: isPercent(offset[0]) ? (parseFloat(offset[0]) * rect.height) : offset[0], + right: isPercent(offset[1]) ? (parseFloat(offset[1]) * rect.width) : offset[1], + bottom: isPercent(offset[2]) ? (parseFloat(offset[2]) * rect.height) : offset[2], + left: isPercent(offset[3]) ? (parseFloat(offset[3]) * rect.width) : offset[3] + }; + // Note: ClientRect object does not allow its properties to be written to therefore a new object has to be created. + return { + top: rect.top - offsetObject.top, + left: rect.left - offsetObject.left, + bottom: rect.bottom + offsetObject.bottom, + right: rect.right + offsetObject.right, + height: rect.height + offsetObject.top + offsetObject.bottom, + width: rect.width + offsetObject.left + offsetObject.right + }; +} + +function isPercent (n) { + return angular.isString(n) && n.indexOf('%') > 0; +} + +// ## QuickSignal FRP +// A quick and dirty implementation of Rx to have a streamlined code in the +// directives. + +// ### QuickSignal +// +// - `didSubscribeFunc`: a function receiving a `subscriber` as described below +// +// Usage: +// var mySignal = new QuickSignal(function(subscriber) { ... }) +function QuickSignal (didSubscribeFunc) { + this.didSubscribeFunc = didSubscribeFunc; +} + +// Subscribe to a signal and consume the steam of data. +// +// Returns a function that can be called to stop the signal stream of data and +// perform cleanup. +// +// A `subscriber` is a function that will be called when a new value arrives. +// a `subscriber.$dispose` property can be set to a function to be called uppon +// disposal. When setting the `$dispose` function, the previously set function +// should be chained. +QuickSignal.prototype.subscribe = function (subscriber) { + this.didSubscribeFunc(subscriber); + var dispose = function () { + if (subscriber.$dispose) { + subscriber.$dispose(); + subscriber.$dispose = null; + } + } + return dispose; +} + +QuickSignal.prototype.map = function (f) { + var s = this; + return new QuickSignal(function (subscriber) { + subscriber.$dispose = s.subscribe(function (nextValue) { + subscriber(f(nextValue)); + }); + }); +}; + +QuickSignal.prototype.filter = function (f) { + var s = this; + return new QuickSignal(function (subscriber) { + subscriber.$dispose = s.subscribe(function (nextValue) { + if (f(nextValue)) { + subscriber(nextValue); + } + }); + }); +}; + +QuickSignal.prototype.scan = function (initial, scanFunc) { + var s = this; + return new QuickSignal(function (subscriber) { + var last = initial; + subscriber.$dispose = s.subscribe(function (nextValue) { + last = scanFunc(last, nextValue); + subscriber(last); + }); + }); +} + +QuickSignal.prototype.merge = function (signal) { + return signalMerge(this, signal); +}; + +QuickSignal.prototype.throttle = function (threshhold) { + var s = this, last, deferTimer; + return new QuickSignal(function (subscriber) { + var chainDisposable = s.subscribe(function () { + var now = +new Date, + args = arguments; + if (last && now < last + threshhold) { + clearTimeout(deferTimer); + deferTimer = setTimeout(function () { + last = now; + subscriber.apply(null, args); + }, threshhold); + } else { + last = now; + subscriber.apply(null, args); + } + }); + subscriber.$dispose = function () { + clearTimeout(deferTimer); + if (chainDisposable) chainDisposable(); + }; + }); +}; + +function signalMerge () { + var signals = arguments; + return new QuickSignal(function (subscriber) { + var disposables = []; + for (var i = signals.length - 1; i >= 0; i--) { + disposables.push(signals[i].subscribe(function () { + subscriber.apply(null, arguments); + })); + } + subscriber.$dispose = function () { + for (var i = disposables.length - 1; i >= 0; i--) { + if (disposables[i]) disposables[i](); + } + } + }); +} + +// Returns a signal from DOM events of a target. +function signalFromEvent (target, event) { + return new QuickSignal(function (subscriber) { + var handler = function (e) { + subscriber(e); + }; + var el = angular.element(target); + el.on(event, handler); + subscriber.$dispose = function () { + el.off(event, handler); + }; + }); +} + +function signalSingle (value) { + return new QuickSignal(function (subscriber) { + setTimeout(function() { subscriber(value); }); + }); +} + +// Module loaders exports +if (typeof define === 'function' && define.amd) { + define(['angular'], angularInviewModule); +} else if (typeof module !== 'undefined' && module && module.exports) { + module.exports = angularInviewModule; +} + +})(); From f97fea26785bf48483b98c716a809cfca2d5b8d6 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Tue, 2 May 2017 09:45:46 +0200 Subject: [PATCH 2/2] Consider page visibility when marking messages as read --- src/partials/messenger.conversation.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/partials/messenger.conversation.html b/src/partials/messenger.conversation.html index 1258a7bcf..744fc1ccd 100644 --- a/src/partials/messenger.conversation.html +++ b/src/partials/messenger.conversation.html @@ -48,7 +48,8 @@

messenger.PRIVATE_CHAT

  • + in-view="$inview && !ctrl.locked && ctrl.msgRead(message.id)" + in-view-options="{ considerPageVisibility: true }">