Browse Source

Replace OC.Notification with toastify js (#15124)

Replace OC.Notification with toastify js
pull/13186/head
John Molakvoæ 7 years ago
committed by GitHub
parent
commit
b968987ed4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      core/css/server.scss
  2. 49
      core/css/toast.scss
  3. 26
      core/js/dist/login.js
  4. 2
      core/js/dist/login.js.map
  5. 54
      core/js/dist/main.js
  6. 2
      core/js/dist/main.js.map
  7. 20
      core/js/dist/maintenance.js
  8. 2
      core/js/dist/maintenance.js.map
  9. 147
      core/js/tests/specs/coreSpec.js
  10. 2
      core/js/tests/specs/sharedialogviewSpec.js
  11. 155
      core/src/OC/notification.js
  12. 6
      core/src/OCP/index.js
  13. 89
      core/src/OCP/toast.js
  14. 54
      package-lock.json
  15. 1
      package.json

3
core/css/server.scss

@ -8,4 +8,5 @@
@import 'fixes.scss';
@import 'mobile.scss';
@import 'tooltip.scss';
@import 'public.scss';
@import 'toast.scss';
@import 'public.scss';

49
core/css/toast.scss

@ -0,0 +1,49 @@
.toastify.toast {
min-width: 200px;
background: none;
background-color: var(--color-main-background);
color: var(--color-main-text);
box-shadow: 0 0 6px 0 var(--color-box-shadow);
padding: 12px;
padding-right: 34px;
margin-top: 45px;
position: absolute;
z-index: 9000;
border-radius: var(--border-radius);
.toast-close {
position: absolute;
top: 0;
right: 0;
width: 38px;
opacity: 0.4;
padding: 12px;
@include icon-color('close', 'actions', $color-black, 2, true);
background-position: center;
background-repeat: no-repeat;
text-indent: 200%;
white-space: nowrap;
overflow: hidden;
&:hover, &:focus, &:active {
cursor: pointer;
opacity: 1;
}
}
}
.toastify.toastify-top {
right: 10px;
}
.toast-error {
border-left: 3px solid var(--color-error);
}
.toast-info {
border-left: 3px solid var(--color-primary);
}
.toast-warning {
border-left: 3px solid var(--color-warning);
}
.toast-success {
border-left: 3px solid var(--color-success);
}

26
core/js/dist/login.js
File diff suppressed because it is too large
View File

2
core/js/dist/login.js.map
File diff suppressed because it is too large
View File

54
core/js/dist/main.js
File diff suppressed because it is too large
View File

2
core/js/dist/main.js.map
File diff suppressed because it is too large
View File

20
core/js/dist/maintenance.js
File diff suppressed because it is too large
View File

2
core/js/dist/maintenance.js.map
File diff suppressed because it is too large
View File

147
core/js/tests/specs/coreSpec.js

@ -571,7 +571,7 @@ describe('Core base tests', function() {
// fore show more apps icon since otherwise it would be hidden since no icons are available
clock.tick(1 * 1000);
$('#more-apps').show();
expect($navigation.is(':visible')).toEqual(false);
$toggle.click();
clock.tick(1 * 1000);
@ -900,75 +900,74 @@ describe('Core base tests', function() {
var hideSpy;
var clock;
var getInnerText = function($node) {
return $node.contents().filter(function(){
return this.nodeType === 3;
})[0].nodeValue;
}
beforeEach(function() {
clock = sinon.useFakeTimers();
showSpy = sinon.spy(OC.Notification, 'show');
showHtmlSpy = sinon.spy(OC.Notification, 'showHtml');
showSpy = sinon.spy(OCP.Toast, 'message');
hideSpy = sinon.spy(OC.Notification, 'hide');
$('#testArea').append('<div id="notification"></div>');
$('#testArea').append('<div id="content"></div>');
});
afterEach(function() {
showSpy.restore();
showHtmlSpy.restore();
hideSpy.restore();
// jump past animations
clock.tick(10000);
clock.restore();
$('#testArea .toastify').remove();
});
describe('showTemporary', function() {
it('shows a plain text notification with default timeout', function() {
var $row = OC.Notification.showTemporary('My notification test');
OC.Notification.showTemporary('My notification test');
expect(showSpy.calledOnce).toEqual(true);
expect(showSpy.firstCall.args[0]).toEqual('My notification test');
expect(showSpy.firstCall.args[1]).toEqual({isHTML: false, timeout: 7});
//expect(showSpy.firstCall.args[1]).toEqual({isHTML: false, timeout: 7});
var $row = $('#testArea .toastify');
expect($row).toBeDefined();
expect($row.text()).toEqual('My notification test');
expect(getInnerText($row)).toEqual('My notification test');
});
it('shows a HTML notification with default timeout', function() {
var $row = OC.Notification.showTemporary('<a>My notification test</a>', { isHTML: true });
OC.Notification.showTemporary('<a>My notification test</a>', { isHTML: true });
expect(showSpy.notCalled).toEqual(true);
expect(showHtmlSpy.calledOnce).toEqual(true);
expect(showHtmlSpy.firstCall.args[0]).toEqual('<a>My notification test</a>');
expect(showHtmlSpy.firstCall.args[1]).toEqual({isHTML: true, timeout: 7});
expect(showSpy.calledOnce).toEqual(true);
expect(showSpy.firstCall.args[0]).toEqual('<a>My notification test</a>');
expect(showSpy.firstCall.args[1].isHTML).toEqual(true)
var $row = $('#testArea .toastify');
expect($row).toBeDefined();
expect($row.text()).toEqual('My notification test');
expect(getInnerText($row)).toEqual('<a>My notification test</a>');
});
it('hides itself after 7 seconds', function() {
var $row = OC.Notification.showTemporary('');
OC.Notification.showTemporary('');
var $row = $('#testArea .toastify');
expect($row).toBeDefined();
// travel in time +7000 milliseconds
clock.tick(7000);
clock.tick(7500);
expect(hideSpy.calledOnce).toEqual(true);
expect(hideSpy.firstCall.args[0]).toEqual($row);
$row = $('#testArea .toastify');
expect($row.length).toEqual(0);
});
});
describe('show', function() {
it('hides itself after a given time', function() {
OC.Notification.show('', { timeout: 10 });
// travel in time +9 seconds
clock.tick(9000);
OC.Notification.showTemporary('', {timeout: 10});
expect(hideSpy.notCalled).toEqual(true);
var $row = $('#testArea .toastify');
expect($row).toBeDefined();
// travel in time +1 seconds
clock.tick(1000);
clock.tick(11500);
expect(hideSpy.calledOnce).toEqual(true);
});
it('does not hide itself after a given time if a timeout of 0 is defined', function() {
OC.Notification.show('', { timeout: 0 });
// travel in time +1000 seconds
clock.tick(1000000);
expect(hideSpy.notCalled).toEqual(true);
$row = $('#testArea .toastify');
expect($row.length).toEqual(0);
});
it('does not hide itself if no timeout given to show', function() {
OC.Notification.show('');
@ -984,92 +983,34 @@ describe('Core base tests', function() {
var $row2 = OC.Notification.showTemporary('Two', {timeout: 2});
var $row3 = OC.Notification.showTemporary('Three');
var $el = $('#notification');
var $rows = $el.find('.row');
var $el = $('#testArea');
var $rows = $el.find('.toastify');
expect($rows.length).toEqual(3);
expect($rows.eq(0).is($row1)).toEqual(true);
expect($rows.eq(0).is($row3)).toEqual(true);
expect($rows.eq(1).is($row2)).toEqual(true);
expect($rows.eq(2).is($row3)).toEqual(true);
clock.tick(3000);
$rows = $el.find('.row');
expect($rows.length).toEqual(2);
expect($rows.eq(0).is($row1)).toEqual(true);
expect($rows.eq(1).is($row3)).toEqual(true);
});
it('shows close button for error types', function() {
var $row = OC.Notification.showTemporary('One');
var $rowError = OC.Notification.showTemporary('Two', {type: 'error'});
expect($row.find('.close').length).toEqual(0);
expect($rowError.find('.close').length).toEqual(1);
// after clicking, row is gone
$rowError.find('.close').click();
var $rows = $('#notification').find('.row');
expect($rows.length).toEqual(1);
expect($rows.eq(0).is($row)).toEqual(true);
});
it('fades out the last notification but not the other ones', function() {
var fadeOutStub = sinon.stub($.fn, 'fadeOut');
var $row1 = OC.Notification.show('One', {type: 'error'});
var $row2 = OC.Notification.show('Two', {type: 'error'});
OC.Notification.showTemporary('Three', {timeout: 2});
var $el = $('#notification');
var $rows = $el.find('.row');
expect($rows.length).toEqual(3);
expect($rows.eq(2).is($row1)).toEqual(true);
clock.tick(3000);
$rows = $el.find('.row');
$rows = $el.find('.toastify');
expect($rows.length).toEqual(2);
$row1.find('.close').click();
clock.tick(1000);
expect(fadeOutStub.notCalled).toEqual(true);
$row2.find('.close').click();
clock.tick(1000);
expect(fadeOutStub.calledOnce).toEqual(true);
expect($el.is(':empty')).toEqual(false);
fadeOutStub.yield();
expect($el.is(':empty')).toEqual(true);
fadeOutStub.restore();
});
it('hides the first notification when calling hide without arguments', function() {
OC.Notification.show('One');
var $row2 = OC.Notification.show('Two');
spyOn(console, 'warn');
var $el = $('#notification');
var $rows = $el.find('.row');
expect($rows.length).toEqual(2);
OC.Notification.hide();
expect(console.warn).toHaveBeenCalled();
$rows = $el.find('.row');
expect($rows.length).toEqual(1);
expect($rows.eq(0).is($row2)).toEqual(true);
expect($rows.eq(0).is($row3)).toEqual(true);
expect($rows.eq(1).is($row1)).toEqual(true);
});
it('hides the given notification when calling hide with argument', function() {
var $row1 = OC.Notification.show('One');
var $row2 = OC.Notification.show('Two');
var $el = $('#notification');
var $rows = $el.find('.row');
var $el = $('#testArea');
var $rows = $el.find('.toastify');
expect($rows.length).toEqual(2);
OC.Notification.hide($row2);
clock.tick(3000);
$rows = $el.find('.row');
$rows = $el.find('.toastify');
expect($rows.length).toEqual(1);
expect($rows.eq(0).is($row1)).toEqual(true);
});

2
core/js/tests/specs/sharedialogviewSpec.js

@ -957,7 +957,7 @@ describe('OC.Share.ShareDialogView', function() {
var showTemporaryNotificationStub;
beforeEach(function() {
showTemporaryNotificationStub = sinon.stub(OC.Notification, 'show');
showTemporaryNotificationStub = sinon.stub(OC.Notification, 'showTemporary');
});
afterEach(function() {

155
core/src/OC/notification.js

@ -21,24 +21,22 @@
import _ from 'underscore'
import $ from 'jquery'
import Toastify from 'toastify-js'
/**
* @todo Write documentation
* @deprecated 17.0.0 use OCP.Toast
* @namespace OC.Notification
*/
export default {
queuedNotifications: [],
getDefaultNotificationFunction: null,
/**
* @type Array<int>
* @description array of notification timers
*/
notificationTimers: [],
updatableNotification: null,
getDefaultNotificationFunction: null,
/**
* @param callback
* @todo Write documentation
* @deprecated 17.0.0 use OCP.Toast
*/
setDefault: function (callback) {
this.getDefaultNotificationFunction = callback;
@ -52,10 +50,11 @@ export default {
*
* @param {jQuery} [$row] notification row
* @param {Function} [callback] callback
* @deprecated 17.0.0 use OCP.Toast
*/
hide: function ($row, callback) {
var self = this;
var $notification = $('#notification');
var $notification = $('#content');
if (_.isFunction($row)) {
// first arg is the callback
@ -64,46 +63,23 @@ export default {
}
if (!$row) {
console.warn('Missing argument $row in OC.Notification.hide() call, caller needs to be adjusted to only dismiss its own notification');
// assume that the row to be hidden is the first one
$row = $notification.find('.row:first');
}
if ($row && $notification.find('.row').length > 1) {
// remove the row directly
$row.remove();
if (callback) {
callback.call();
}
console.error('Missing argument $row in OC.Notification.hide() call, caller needs to be adjusted to only dismiss its own notification');
return;
}
_.defer(function () {
// fade out is supposed to only fade when there is a single row
// however, some code might call hide() and show() directly after,
// which results in more than one element
// in this case, simply delete that one element that was supposed to
// fade out
//
// FIXME: remove once all callers are adjusted to only hide their own notifications
if ($notification.find('.row').length > 1) {
$row.remove();
return;
// remove the row directly
$row.each(function () {
$(this)[0].toastify.hideToast()
if (this === this.updatableNotification) {
this.updatableNotification = null
}
// else, fade out whatever was present
$notification.fadeOut('400', function () {
if (self.isHidden()) {
if (self.getDefaultNotificationFunction) {
self.getDefaultNotificationFunction.call();
}
}
if (callback) {
callback.call();
}
$notification.empty();
});
});
})
if (callback) {
callback.call()
}
if (this.getDefaultNotificationFunction) {
this.getDefaultNotificationFunction()
}
},
/**
@ -116,45 +92,14 @@ export default {
* @param {string} [options.type] notification type
* @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent)
* @return {jQuery} jQuery element for notification row
* @deprecated 17.0.0 use OCP.Toast
*/
showHtml: function (html, options) {
options = options || {};
_.defaults(options, {
timeout: 0
});
var self = this;
var $notification = $('#notification');
if (this.isHidden()) {
$notification.fadeIn().css('display', 'inline-block');
}
var $row = $('<div class="row"></div>');
if (options.type) {
$row.addClass('type-' + options.type);
}
if (options.type === 'error') {
// add a close button
var $closeButton = $('<a class="action close icon-close" href="#"></a>');
$closeButton.attr('alt', t('core', 'Dismiss'));
$row.append($closeButton);
$closeButton.one('click', function () {
self.hide($row);
return false;
});
$row.addClass('closeable');
}
$row.prepend(html);
$notification.append($row);
if (options.timeout > 0) {
// register timeout to vanish notification
this.notificationTimers.push(setTimeout(function () {
self.hide($row);
}, (options.timeout * 1000)));
}
return $row;
options = options || {}
options.showHtml = true
options.timeout = (options.timeout === 0) ? -1 : options.timeout
const toast = window.OCP.Toast.message(html, options)
return $(toast.toastElement)
},
/**
@ -165,9 +110,11 @@ export default {
* @param {string} [options.type] notification type
* @param {int} [options.timeout=0] timeout value, defaults to 0 (permanent)
* @return {jQuery} jQuery element for notification row
* @deprecated 17.0.0 use OCP.Toast
*/
show: function (text, options) {
return this.showHtml($('<div/>').text(text).html(), options);
const toast = window.OCP.Toast.message(text, options);
return $(toast.toastElement);
},
/**
@ -175,23 +122,14 @@ export default {
*
* @param {string} text Message to display
* @return {jQuery} JQuery element for notificaiton row
* @deprecated 17.0.0 use OCP.Toast
*/
showUpdate: function (text) {
var $notification = $('#notification');
// sanitise
var $html = $('<div/>').text(text).html();
// new notification
if (text && $notification.find('.row').length == 0) {
return this.showHtml($html);
if (this.updatableNotification) {
this.updatableNotification.hideToast();
}
var $row = $('<div class="row"></div>').prepend($html);
// just update html in notification
$notification.html($row);
return $row;
this.updatableNotification = OCP.Toast.message(text, {timeout: -1})
return $(this.updatableNotification.toastElement);
},
/**
@ -203,30 +141,21 @@ export default {
* @param {int} [options.timeout=7] timeout in seconds, if this is 0 it will show the message permanently
* @param {boolean} [options.isHTML=false] an indicator for HTML notifications (true) or text (false)
* @param {string} [options.type] notification type
* @deprecated 17.0.0 use OCP.Toast
*/
showTemporary: function (text, options) {
var defaults = {
isHTML: false,
timeout: 7
};
options = options || {};
// merge defaults with passed in options
_.defaults(options, defaults);
var $row;
if (options.isHTML) {
$row = this.showHtml(text, options);
} else {
$row = this.show(text, options);
}
return $row;
options = options || {}
options.timeout = options.timeout || 7;
const toast = window.OCP.Toast.message(text, options);
return $(toast.toastElement);
},
/**
* Returns whether a notification is hidden.
* @return {boolean}
* @deprecated 17.0.0 use OCP.Toast
*/
isHidden: function () {
return !$("#notification").find('.row').length;
return !$('#content').find('.toastify').length;
}
}

6
core/src/OCP/index.js

@ -6,14 +6,16 @@ import * as Comments from './comments'
import * as InitialState from './initialstate'
import Loader from './loader'
import Collaboration from './collaboration'
import Toast from './toast'
import * as WhatsNew from './whatsnew'
/** @namespace OCP */
export default {
AppConfig,
Collaboration,
Comments,
InitialState,
Loader,
WhatsNew,
Collaboration
Toast,
WhatsNew
};

89
core/src/OCP/toast.js

@ -0,0 +1,89 @@
/*
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Toastify from 'toastify-js'
const TOAST_TYPE_CLASES = {
error: 'toast-error',
info: 'toast-info',
warning: 'toast-warning',
success: 'toast-success',
permanent: 'permanent'
}
const Toast = {
success(text, options = {}) {
options.type = 'success';
return this.message(text, options)
},
warning(text, options = {}) {
options.type = 'warning';
return this.message(text, options)
},
error(text, options = {}) {
options.type = 'error';
return this.message(text, options)
},
info(text, options = {}) {
options.type = 'info';
return this.message(text, options)
},
message(text, options) {
options = options || {};
_.defaults(options, {
timeout: 7,
showHtml: false,
type: undefined,
close: true,
callback: () => {}
});
if (!options.showHtml) {
text = $('<div/>').text(text).html()
}
let classes = ''
if (options.type) {
classes = TOAST_TYPE_CLASES[options.type]
}
const toast = Toastify({
text: text,
duration: options.timeout ? options.timeout*1000 : null,
callback: options.callback,
close: options.close,
gravity: 'top',
selector: !window.TESTING ? 'content' : 'testArea',
positionLeft: false,
backgroundColor: '',
className: 'toast ' + classes
})
toast.showToast()
// add toastify object to the element for reference in legacy OC.Notification
toast.toastElement.toastify = toast;
return toast
}
}
export default Toast

54
package-lock.json

@ -2330,6 +2330,34 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"jsesc": {
"version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
},
"regexpu-core": {
"version": "1.0.0",
"resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
"integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
"requires": {
"regenerate": "^1.2.1",
"regjsgen": "^0.2.0",
"regjsparser": "^0.1.4"
}
},
"regjsgen": {
"version": "0.2.0",
"resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
"integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc="
},
"regjsparser": {
"version": "0.1.5",
"resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
"integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
"requires": {
"jsesc": "~0.5.0"
}
}
}
},
@ -3782,6 +3810,21 @@
"optimist": "^0.6.1",
"source-map": "^0.6.1",
"uglify-js": "^3.1.4"
},
"dependencies": {
"async": {
"version": "2.6.1",
"resolved": "http://registry.npmjs.org/async/-/async-2.6.1.tgz",
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
"requires": {
"lodash": "^4.17.10"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"handlebars-loader": {
@ -4740,9 +4783,9 @@
}
},
"minimist": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
"version": "0.0.8",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"mississippi": {
"version": "3.0.0",
@ -6950,6 +6993,11 @@
"repeat-string": "^1.6.1"
}
},
"toastify-js": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.5.0.tgz",
"integrity": "sha512-tupU/X7DqwxYxTgT6n9SSEZLIGuwL1hFWg9uGQOzi8G04FLXoziw0GRF/TmuARrSQQCfIarfzoKEdDPG14Pr3Q=="
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",

1
package.json

@ -48,6 +48,7 @@
"query-string": "^5.1.1",
"snap.js": "^2.0.9",
"strengthify": "git+https://github.com/MorrisJobke/strengthify.git#0.5.8",
"toastify-js": "^1.5.0",
"underscore": "^1.9.1",
"v-tooltip": "^2.0.2",
"vue": "^2.6.10",

Loading…
Cancel
Save