N°7243 - Add toast notifications to iTop (#614)

* N°7243 - Add toast notifications to iTop

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update js/pages/backoffice/toolbox.js

* Update js/utils.js

* N°7243 - Move some rules to a dedicated partial and use spacing variables

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
This commit is contained in:
Stephen Abello
2024-02-16 10:59:05 +01:00
parent d775658980
commit 1dfb2e0a1a
26 changed files with 2151 additions and 7 deletions

View File

@@ -33,3 +33,4 @@
@import "field-badge";
@import "file-select";
@import "medallion-icon";
@import "toast";

View File

@@ -0,0 +1,62 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-toast--padding-y: $ibo-spacing-400 !default;
$ibo-toast--padding-right: $ibo-spacing-300 !default;
$ibo-toast--padding-left: $ibo-spacing-500 !default;
$ibo-toast--border-radius: $ibo-border-radius-300 !default;
$ibo-toast--box-shadow: $ibo-elevation-200 !default;
$ibo-toast--max-width: calc(50% - 20px) !default;
@keyframes decreaseHighlight {
0% {
height: 100%;
}
8%{
border-radius: 0 0 0 3px;
}
100% {
height: 0;
}
}
.ibo-toast {
display: inline-flex;
position: fixed;
align-items: center;
max-width: $ibo-toast--max-width ;
padding: $ibo-toast--padding-y $ibo-toast--padding-right $ibo-toast--padding-y $ibo-toast--padding-left;
border-radius: $ibo-toast--border-radius;
box-shadow: $ibo-toast--box-shadow;
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
z-index: 2147483647;
&::before {
@include ibo-vertical-highlight;
top: initial;
bottom: 0;
border-radius: $ibo-toast--border-radius 0 0 $ibo-toast--border-radius;
}
&.ibo-is-auto-closeable::before{
animation: decreaseHighlight 5s linear forwards;
}
&:hover::before {
animation: none; /* Pause animation on hover */
}
&.ibo-is-error{
@extend .ibo-alert.ibo-is-danger;
}
&.ibo-is-warning{
@extend .ibo-alert.ibo-is-warning;
}
&.ibo-is-success{
@extend .ibo-alert.ibo-is-success;
}
&.ibo-is-information{
@extend .ibo-alert.ibo-is-information;
}
}

View File

@@ -18,3 +18,4 @@
@import "jquery-blockui";
@import "magnific-popup";
@import "selectize";
@import "toastify";

76
css/backoffice/vendors/_toastify.scss vendored Normal file
View File

@@ -0,0 +1,76 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-vendors-toastify--right--right: $ibo-spacing-500 !default;
$ibo-vendors-toastify--left--left: $ibo-spacing-500 !default;
$ibo-vendors-toastify--top--top: -150px !default;
$ibo-vendors-toastify--bottom--bottom: -150px !default;
$ibo-vendors-toastify--close--background: transparent !default;
$ibo-vendors-toastify--close--padding: 0 !default;
$ibo-vendors-toastify--close--margin-left: $ibo-spacing-300 !default;
.toastify.on {
opacity: 1;
}
.toast-close {
background: $ibo-vendors-toastify--close--background;
border: 0;
color: inherit;
cursor: pointer;
font-family: inherit;
padding: $ibo-vendors-toastify--close--padding;
margin-left: $ibo-vendors-toastify--close--margin-left;
}
.toastify-right {
right: $ibo-vendors-toastify--right--right;
}
.toastify-left {
left: $ibo-vendors-toastify--left--left;
}
.toastify-top {
top: $ibo-vendors-toastify--top--top;
}
.toastify-bottom {
bottom: $ibo-vendors-toastify--bottom--bottom;
}
.toastify-rounded {
border-radius: 25px;
}
.toastify-avatar {
width: 1.5em;
height: 1.5em;
margin: -7px 5px;
border-radius: 2px;
}
.toastify-center {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
max-width: -moz-fit-content;
}
@media only screen and (max-width: 360px) {
.toastify-right, .toastify-left {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
}
}

View File

@@ -46,6 +46,9 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Preferences:Tabs:Scrollable:Label' => 'Navigation',
'UI:Preferences:Tabs:Scrollable:Classic' => 'Classic',
'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Scrollable',
'UI:Preferences:General:Toasts' => 'Toast notifications position',
'UI:Preferences:General:Toasts:Bottom' => 'Bottom',
'UI:Preferences:General:Toasts:Top' => 'Top',
'UI:Preferences:ChooseAPlaceholder' => 'User placeholder image',
'UI:Preferences:ChooseAPlaceholder+' => 'Choose a placeholder image that will be displayed if the contact linked to your user doesn\'t have one',
));

View File

@@ -458,6 +458,27 @@ CombodoModal.OpenInformativeModal = function(sMessage, sSeverity, oOptions) {
// Open modal
CombodoModal.OpenModal(oOptions);
}
/**
* @override
* @inheritDoc
*/
CombodoToast.OpenToast = function(sMessage, sSeverity, aOptions) {
aOptions = $.extend({
text: sMessage,
className: "ibo-toast ibo-is-" + sSeverity,
duration: 6000,
close: true,
gravity: GetUserPreference('toasts_vertical_position', 'bottom'),
position: "right",
stopOnFocus: true,
}, aOptions);
if(aOptions.duration !== -1){
aOptions.className += ' ibo-is-auto-closeable';
}
Toastify(aOptions).showToast();
};
// Processing on each pages of the backoffice
$(document).ready(function(){

View File

@@ -1575,3 +1575,62 @@ let CombodoModal = {
CombodoModal.OpenInformativeModal(sMessage, CombodoModal.INFORMATIVE_MODAL_SEVERITY_ERROR, oOptions);
},
};
/**
* Abstract wrapper to manage toasts in iTop.
* Implementations for the various GUIs may vary but APIs are the same.
*
* @since 3.2.0
*/
let CombodoToast = {
/**
* Open a standard toast and put the content into it.
*
* @param sMessage {String} Message to be displayed in the toast
* @param sSeverity {String} Severity of the information. Default values are success, information, warning, error.
* @param aOptions {Object} {@see CombodoModal.OpenModal
*/
OpenToast: function(sMessage, sSeverity, aOptions = {}) {
// Meant for overloading
CombodoJSConsole.Debug('CombodoToast.OpenToast not implemented');
},
/**
* Open a standard toast for success messages.
*
* @param sMessage {String} Success message to be displayed in the toast
* @param aOptions {Object} {@see CombodoModal.OpenModal
*/
OpenSuccessToast: function(sMessage, aOptions = {}) {
CombodoToast.OpenToast(sMessage, 'success', aOptions);
},
/**
* Open a standard toast for information messages.
*
* @param sMessage {String} Information message to be displayed in the toast
* @param aOptions {Object} {@see CombodoModal.OpenModal
*/
OpenInformationToast: function(sMessage, aOptions = {}) {
CombodoToast.OpenToast(sMessage, 'information', aOptions);
},
/**
* Open a standard toast for warning messages.
*
* @param sMessage {String} Warning message to be displayed in the toast
* @param aOptions {Object} {@see CombodoModal.OpenModal
*/
OpenWarningToast: function(sMessage, aOptions = {}) {
CombodoToast.OpenToast(sMessage, 'warning', aOptions);
},
/**
* Open a standard toast for error messages.
*
* @param sMessage {String} Error message to be displayed in the toast
* @param aOptions {Object} {@see CombodoModal.OpenModal
*/
OpenErrorToast: function(sMessage, aOptions = {}) {
CombodoToast.OpenToast(sMessage, 'error', aOptions);
}
};

9
node_modules/.package-lock.json generated vendored
View File

@@ -1,6 +1,6 @@
{
"name": "dev-trunk",
"lockfileVersion": 2,
"name": "iTop",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@fontsource/raleway": {
@@ -88,6 +88,11 @@
"dependencies": {
"@popperjs/core": "^2.4.4"
}
},
"node_modules/toastify-js": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz",
"integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ=="
}
}
}

17
node_modules/toastify-js/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

9
node_modules/toastify-js/.prettierrc generated vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"useTabs": false,
"printWidth": 80,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "es5",
"parser": "babylon",
"noSemi": false
}

12
node_modules/toastify-js/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,12 @@
language: node_js
node_js:
- "8"
script:
- echo "skipping tests"
deploy:
skip_cleanup: true
provider: npm
email: $NPM_USERNAME
api_key: $NPM_TOKEN
on:
branch: master

133
node_modules/toastify-js/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,133 @@
# Changelog
All the changes made to toastify-js library.
## [1.12.0] - 2022-07-21
* Accessibility fix: Support aria-live for the toast
* Accessibility fix: Add aria-label for close icon
## [1.11.2] - 2021-10-06
* Bugfix: Style Options: "backgroundColor" not working! (#81)
* Bugfix: "ShadowRoot is undefined" in older browsers (#83)
## [1.11.1] - 2021-07-15
* Bugfix: IE11 support broke since style option #77
## [1.11.0] - 2021-04-25
* New property `oldestFirst` allows to set the order of adding new toasts to page (#70 and #71)
## [1.10.0] - 2021-03-25
* `selector` now supports a DOM Node, along with ID string ([#65](https://github.com/apvarun/toastify-js/pull/65))
* New property - `escapeMarkup` - Toggle the default behavior of escaping HTML markup
* New property - `style` - Use the HTML DOM Style properties to add any style directly to toast
* Adds `toastify-es.js`, to be used from node_modules until there are no compatibility issues
### Deprecations:
* `backgroundColor` is deprecated. Use `style.background` instead
## [1.9.3] - 2020-10-10
* Offset IE11 compatibility #64
## [1.9.2] - 2020-09-24
* Bugfix: Max width problem for firefox browser #61
## [1.9.1] - 2020-08-13
* Bugfix: Avatar positioning based on toast position
## [1.9.0] - 2020-07-22
* Add support for providing toast `offset`
* Updated docs: offset
## [1.8.0] - 2020-05-29
* Add option to provide a node instead of text
* Updated docs: permanent toast duration
## [1.7.0] - 2020-03-01
* To be able to set `stopOnFocus` for toasts without close icon
* Bugfix: `duration` can be infinite by setting as `0`
* Bugfix: Prevent errors when parent node is removed from DOM while using frameworks
* Bugfix: IE 9/10 compatibility fix
## [1.6.2] - 2020-01-03
* Bugfix: Closing the toast when custom close icon from icon fonts are used
## [1.6.1] - 2019-06-29
* Bugfix: Disabling `stopOnFocus`
## [1.6.0] - 2019-06-29
* **Deprecation Warning**: Migrating from `positionLeft` property to `position`
* Property `position` to support `center` as a value along with `left` and `right` - Useful for centering toast messages in the page
## [1.5.0] - 2019-05-30
* Added persistant toast option with ability to programatically close it
## [1.4.0] - 2019-05-12
* **Breaking Change**: Manually import CSS while using as module in your modern JavaScript applications
* Ability to pause the toast dismiss timer on hover (Using `stopOnFocus` property)
## [1.3.2] - 2018-12-6
* Added z-index attribute
## [1.2.1] - 2018-05-31
* Added support for Classes. Now custom classes can be added to the toast while creating it
## [1.2.0] - 2018-03-05
* Fix issue when `destination` and `close` options is used at the same time
## [1.1.0] - 2018-02-18
* Browser support extended to IE10+ without any polyfills
## [1.0.0] - 2018-02-17
* Support for modules
## [0.0.6] - 2017-09-09
* Support for changing background [Options]
* Optimized toast positioning logic
* Added changelog for library update tracking
## [0.0.5] - 2017-09-06
* Support for toast messages on mobile screens
* Tweaked close icon
## [0.0.4] - 2017-09-05
* Support for positioning of toasts on the page
## [0.0.3] - 2017-09-05
* Close buton for toasts [Options]
## [0.0.2] - 2017-09-04
* Option to add on-click link for toasts
* Updated comments for code readability
## [0.0.1] - 2017-09-02
* Initial Release
* Added Preview page
* Optimized function structure

21
node_modules/toastify-js/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 apvarun
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.

439
node_modules/toastify-js/README.md generated vendored Normal file
View File

@@ -0,0 +1,439 @@
# Toastify
![Built with JavaScript](https://img.shields.io/badge/Built%20with-JavaScript-red?style=for-the-badge&logo=javascript)
[![toastify-js](https://img.shields.io/badge/toastify--js-1.12.0-brightgreen.svg)](https://www.npmjs.com/package/toastify-js)
![MIT License](https://img.shields.io/npm/l/toastify-js)
Toastify is a lightweight, vanilla JS toast notification library.
## Demo
[Click here](https://apvarun.github.io/toastify-js/)
## Features
* Multiple stacked notifications
* Customizable
* No blocking of execution thread
### Customization options
* Notification Text
* Duration
* Toast background color
* Close icon display
* Display position
* Offset position
## Installation
#### Toastify now supports installation via NPM
* Run the below command to add toastify-js to your exisitng or new project.
```
npm install --save toastify-js
```
or
```
yarn add toastify-js -S
```
* Import toastify-js into your module to start using it.
```
import Toastify from 'toastify-js'
```
You can use the default CSS from Toastify as below and later override it or choose to write your own CSS.
```
import "toastify-js/src/toastify.css"
```
#### Adding ToastifyJs to HTML page using the traditional method
To start using **Toastify**, add the following CSS on to your page.
```html
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
```
And the script at the bottom of the page
```html
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
```
> Files are delivered via the CDN service provided by [jsdeliver](https://www.jsdelivr.com/)
## Documentation
```javascript
Toastify({
text: "This is a toast",
duration: 3000,
destination: "https://github.com/apvarun/toastify-js",
newWindow: true,
close: true,
gravity: "top", // `top` or `bottom`
position: "left", // `left`, `center` or `right`
stopOnFocus: true, // Prevents dismissing of toast on hover
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)",
},
onClick: function(){} // Callback after click
}).showToast();
```
> Toast messages will be centered on devices with screen width less than 360px.
* See the [changelog](https://github.com/apvarun/toastify-js/blob/master/CHANGELOG.md)
### Add own custom classes
If you want to use custom classes on the toast for customizing (like info or warning for example), you can do that as follows:
```javascript
Toastify({
text: "This is a toast",
className: "info",
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)",
}
}).showToast();
```
Multiple classes also can be assigned as a string, with spaces between class names.
### Add some offset
If you want to add offset to the toast, you can do that as follows:
```javascript
Toastify({
text: "This is a toast with offset",
offset: {
x: 50, // horizontal axis - can be a number or a string indicating unity. eg: '2em'
y: 10 // vertical axis - can be a number or a string indicating unity. eg: '2em'
},
}).showToast();
```
Toast will be pushed 50px from right in x axis and 10px from top in y axis.
**Note:**
If `position` is equals to `left`, it will be pushed from left.
If `gravity` is equals to `bottom`, it will be pushed from bottom.
## API
| Option Key | type | Usage | Defaults |
|-----------------|----------------------|----------------------------------------------------------------------------|-------------|
| text | string | Message to be displayed in the toast | "Hi there!" |
| node | ELEMENT_NODE | Provide a node to be mounted inside the toast. `node` takes higher precedence over `text` | |
| duration | number | Duration for which the toast should be displayed.<br>-1 for permanent toast | 3000 |
| selector | string \| ELEMENT_NODE | ShadowRoot | CSS Selector or Element Node on which the toast should be added | body |
| destination | URL string | URL to which the browser should be navigated on click of the toast | |
| newWindow | boolean | Decides whether the `destination` should be opened in a new window or not | false |
| close | boolean | To show the close icon or not | false |
| gravity | "top" or "bottom" | To show the toast from top or bottom | "top" |
| position | "left" or "right" | To show the toast on left or right | "right" |
| backgroundColor | CSS background value | To be deprecated, use `style.background` option instead. Sets the background color of the toast | |
| avatar | URL string | Image/icon to be shown before text | |
| className | string | Ability to provide custom class name for further customization | |
| stopOnFocus | boolean | To stop timer when hovered over the toast (Only if duration is set) | true |
| callback | Function | Invoked when the toast is dismissed | |
| onClick | Function | Invoked when the toast is clicked | |
| offset | Object | Ability to add some offset to axis | |
| escapeMarkup | boolean | Toggle the default behavior of escaping HTML markup | true |
| style | object | Use the HTML DOM Style properties to add any style directly to toast | |
| ariaLive | string | Announce the toast to screen readers, see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions for options | "polite" |
| oldestFirst | boolean | Set the order in which toasts are stacked in page | true |
> Deprecated properties: `backgroundColor` - use `style.background` option instead
## Browsers support
| ![][ie]<br />IE / Edge | ![][firefox]<br />Firefox | ![][chrome]<br />Chrome | ![][safari]<br />Safari | ![][opera]<br />Opera |
| ---------------------- | ------------------------- | ----------------------- | ----------------------- | --------------------- |
| IE10, IE11, Edge | last 10 versions | last 10 versions | last 10 versions | last 10 versions |
## Contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore -->
<table>
<tr>
<td align="center">
<a href="https://github.com/AStoker">
<img
alt="AStoker"
src="https://avatars.githubusercontent.com/u/2907279?v=4"
width="117"
/>
<br />
AStoker
</a>
</td>
<td align="center">
<a href="https://github.com/caiomoura1994">
<img
alt="caiomoura1994"
src="https://avatars.githubusercontent.com/u/9389139?v=4"
width="117"
/>
<br />
caiomoura1994
</a>
</td>
<td align="center">
<a href="https://github.com/rndevfx">
<img
alt="rndevfx"
src="https://avatars.githubusercontent.com/u/5052076?v=4"
width="117"
/>
<br />
rndevfx
</a>
</td>
<td align="center">
<a href="https://github.com/1ess">
<img
alt="1ess"
src="https://avatars.githubusercontent.com/u/36092926?v=4"
width="117"
/>
<br />
1ess
</a>
</td>
<td align="center">
<a href="https://github.com/d4rn0k">
<img
alt="d4rn0k"
src="https://avatars.githubusercontent.com/u/2183269?v=4"
width="117"
/>
<br />
d4rn0k
</a>
</td>
<td align="center">
<a href="https://github.com/danielkaiser80">
<img
alt="danielkaiser80"
src="https://avatars.githubusercontent.com/u/33827555?v=4"
width="117"
/>
<br />
danielkaiser80
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/skjnldsv">
<img
alt="skjnldsv"
src="https://avatars.githubusercontent.com/u/14975046?v=4"
width="117"
/>
<br />
skjnldsv
</a>
</td>
<td align="center">
<a href="https://github.com/chasedeanda">
<img
alt="chasedeanda"
src="https://avatars.githubusercontent.com/u/8203134?v=4"
width="117"
/>
<br />
chasedeanda
</a>
</td>
<td align="center">
<a href="https://github.com/chrisgraham">
<img
alt="chrisgraham"
src="https://avatars.githubusercontent.com/u/195389?v=4"
width="117"
/>
<br />
chrisgraham
</a>
</td>
<td align="center">
<a href="https://github.com/Wachiwi">
<img
alt="Wachiwi"
src="https://avatars.githubusercontent.com/u/4199845?v=4"
width="117"
/>
<br />
Wachiwi
</a>
</td>
<td align="center">
<a href="https://github.com/FeixuRuins">
<img
alt="FeixuRuins"
src="https://avatars.githubusercontent.com/u/66232834?v=4"
width="117"
/>
<br />
FeixuRuins
</a>
</td>
<td align="center">
<a href="https://github.com/gavinhungry">
<img
alt="gavinhungry"
src="https://avatars.githubusercontent.com/u/744538?v=4"
width="117"
/>
<br />
gavinhungry
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/haydster7">
<img
alt="haydster7"
src="https://avatars.githubusercontent.com/u/4540595?v=4"
width="117"
/>
<br />
haydster7
</a>
</td>
<td align="center">
<a href="https://github.com/joaquinwojcik">
<img
alt="joaquinwojcik"
src="https://avatars.githubusercontent.com/u/3205737?v=4"
width="117"
/>
<br />
joaquinwojcik
</a>
</td>
<td align="center">
<a href="https://github.com/juliushaertl">
<img
alt="juliushaertl"
src="https://avatars.githubusercontent.com/u/3404133?v=4"
width="117"
/>
<br />
juliushaertl
</a>
</td>
<td align="center">
<a href="https://github.com/mort3za">
<img
alt="mort3za"
src="https://avatars.githubusercontent.com/u/510242?v=4"
width="117"
/>
<br />
mort3za
</a>
</td>
<td align="center">
<a href="https://github.com/Sandip124">
<img
alt="Sandip124"
src="https://avatars.githubusercontent.com/u/37034590?v=4"
width="117"
/>
<br />
Sandip124
</a>
</td>
<td align="center">
<a href="https://github.com/Tadaz">
<img
alt="Tadaz"
src="https://avatars.githubusercontent.com/u/10881931?v=4"
width="117"
/>
<br />
Tadaz
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/t12ung">
<img
alt="t12ung"
src="https://avatars.githubusercontent.com/u/9781423?v=4"
width="117"
/>
<br />
t12ung
</a>
</td>
<td align="center">
<a href="https://github.com/victorfeijo">
<img
alt="victorfeijo"
src="https://avatars.githubusercontent.com/u/8865899?v=4"
width="117"
/>
<br />
victorfeijo
</a>
</td>
<td align="center">
<a href="https://github.com/fiatjaf">
<img
alt="fiatjaf"
src="https://avatars.githubusercontent.com/u/1653275?v=4"
width="117"
/>
<br />
fiatjaf
</a>
</td>
<td align="center">
<a href="https://github.com/prousseau-korem">
<img
alt="prousseau-korem"
src="https://avatars.githubusercontent.com/u/59747802?v=4"
width="117"
/>
<br />
prousseau-korem
</a>
</td>
</tr>
</table>
<!-- ALL-CONTRIBUTORS-LIST:END -->
## License
MIT © [Varun A P](https://github.com/apvarun)
<a href="https://www.buymeacoffee.com/apvarun" target="_blank" rel="noopener"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" height="40" width="145" alt="Buy Me A Coffee"></a>
[ie]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/edge.png
[firefox]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/firefox.png
[chrome]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/chrome.png
[safari]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/safari.png
[opera]: https://raw.githubusercontent.com/godban/browsers-support-badges/master/src/images/opera.png

BIN
node_modules/toastify-js/example/pattern.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

95
node_modules/toastify-js/example/script.css generated vendored Normal file
View File

@@ -0,0 +1,95 @@
* {
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
font-family: Helvetica, Arial, sans-serif;
background-size: 100%;
margin: 0;
padding: 0;
color: #424242;
}
.container {
overflow: hidden;
display: flex;
height: 100%;
justify-content: center;
flex-direction: column;
align-items: center;
background-color: whitesmoke;
background-image: url("./pattern.png");
}
.hidden {
display: none;
}
.docs {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
background-color: white;
border: 1px solid #e3e3e3;
padding: 20px 20px;
width: 60%;
border-radius: 4px;
}
.docs h2 {
margin-top: 0px;
}
code p {
margin: 2px;
}
.pad-left {
padding-left: 20px;
}
.buttons {
margin: 20px;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.button {
overflow: hidden;
margin: 10px;
padding: 12px 12px;
cursor: pointer;
-webkit-transition: all 200ms ease-in-out;
transition: all 200ms ease-in-out;
text-align: center;
white-space: nowrap;
text-decoration: none;
text-transform: none;
text-transform: capitalize;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
line-height: 1.3;
min-width: 100px;
display: inline-block;
box-shadow: 0 5px 20px rgba(22, 22, 22, 0.15);
color: #5477f5;
background-color: Snow;
border: 1px solid #5477f5;
}
.button:hover {
color: #FFFFFF;
background: linear-gradient(135deg, #73a5ff, #5477f5);
border: 1px solid transparent;
}
.repo {
margin: 10px;
}

72
node_modules/toastify-js/example/script.js generated vendored Normal file
View File

@@ -0,0 +1,72 @@
var bgColors = [
"linear-gradient(to right, #00b09b, #96c93d)",
"linear-gradient(to right, #ff5f6d, #ffc371)",
],
i = 0;
Toastify({
text: "Hi",
duration: 4500,
destination: "https://github.com/apvarun/toastify-js",
newWindow: true,
gravity: "top",
position: 'left',
}).showToast();
setTimeout(function() {
Toastify({
text: "Simple JavaScript Toasts",
gravity: "top",
position: 'center',
style: {
background: '#0f3443'
}
}).showToast();
}, 1000);
// Options for the toast
var options = {
text: "Happy toasting!",
duration: 2500,
callback: function() {
console.log("Toast hidden");
Toastify.reposition();
},
close: true,
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)",
}
};
// Initializing the toast
var myToast = Toastify(options);
// Toast after delay
setTimeout(function() {
myToast.showToast();
}, 4500);
setTimeout(function() {
Toastify({
text: "Highly customizable",
gravity: "bottom",
position: 'left',
close: true,
style: {
background: "linear-gradient(to right, #ff5f6d, #ffc371)",
}
}).showToast();
}, 3000);
// Displaying toast on manual action `Try`
document.getElementById("new-toast").addEventListener("click", function() {
Toastify({
text: "I am a toast",
duration: 3000,
close: i % 3 ? true : false,
style: {
background: bgColors[i % 2],
}
}).showToast();
i++;
});

59
node_modules/toastify-js/index.html generated vendored Normal file
View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Toastify JS - Pure JavaScript Toast Notificaton Library</title>
<meta name="description" content="Toastify is a pure JavaScript library that lets you create notifications toasts/messages.">
<meta name="keywords" content="Javascript,Library,Toastify">
<meta name="author" content="apvarun">
<link rel="stylesheet" type="text/css" href="example/script.css">
<link rel="stylesheet" type="text/css" href="src/toastify.css">
</head>
<body id="root">
<div class="container">
<div class="lead">
<h1>Toastify JS</h1>
</div>
<p>Better notification messages</p>
<div class="buttons">
<a href="#" id="new-toast" class="button">Try</a>
<a href="https://github.com/apvarun/toastify-js/blob/master/README.md" target="_blank" class="button">Docs</a>
<a href="https://twitter.com/intent/tweet?text=Pure+JavaScript+library+for+better+notification+messages&url=https://apvarun.github.io/toastify-js/&hashtags=JavaScript,toast&via=apvarun"
target="_blank" class="button">Tweet</a>
</div>
<div class="docs">
<h2>Usage</h2>
<code>
<p>Toastify({</p>
<p class="pad-left">text: "This is a toast",</p>
<p class="pad-left">duration: 3000</p>
<p>}).showToast();</p>
</code>
</div>
<div class="repo">
<iframe src="https://ghbtns.com/github-btn.html?user=apvarun&repo=toastify-js&type=star&count=true&size=large" frameborder="0"
scrolling="0" width="160px" height="30px"></iframe>
</div>
</div>
</body>
<script type="text/javascript" src="src/toastify.js"></script>
<script type="text/javascript" src="example/script.js"></script>
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-105870436-1', 'auto');
ga('send', 'pageview');
</script>
</html>

22
node_modules/toastify-js/package.json generated vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "toastify-js",
"version": "1.12.0",
"description": "Toastify is a lightweight, vanilla JS toast notification library.",
"main": "./src/toastify.js",
"repository": {
"type": "git",
"url": "git+https://github.com/apvarun/toastify-js.git"
},
"keywords": [
"toastify",
"javascript",
"notifications",
"toast"
],
"author": "Varun A P",
"license": "MIT",
"bugs": {
"url": "https://github.com/apvarun/toastify-js/issues"
},
"homepage": "https://github.com/apvarun/toastify-js#readme"
}

466
node_modules/toastify-js/src/toastify-es.js generated vendored Normal file
View File

@@ -0,0 +1,466 @@
/*!
* Toastify js 1.12.0
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
/**
* Options used for Toastify
* @typedef {Object} ToastifyConfigurationObject
* @property {string} text - Message to be displayed in the toast
* @property {Element} node - Provide a node to be mounted inside the toast. node takes higher precedence over text
* @property {number} duration - Duration for which the toast should be displayed. -1 for permanent toast
* @property {string|Element} selector - CSS ID Selector on which the toast should be added
* @property {url} destination - URL to which the browser should be navigated on click of the toast
* @property {boolean} newWindow - Decides whether the destination should be opened in a new window or not
* @property {boolean} close - To show the close icon or not
* @property {string} gravity - To show the toast from top or bottom
* @property {string} position - To show the toast on left or right
* @property {string} backgroundColor - Deprecated: Sets the background color of the toast
* @property {url} avatar - Image/icon to be shown before text
* @property {string} className - Ability to provide custom class name for further customization
* @property {boolean} stopOnFocus - To stop timer when hovered over the toast (Only if duration is set)
* @property {Function} callback - Invoked when the toast is dismissed
* @property {Function} onClick - Invoked when the toast is clicked
* @property {Object} offset - Ability to add some offset to axis
* @property {boolean} escapeMarkup - Toggle the default behavior of escaping HTML markup
* @property {string} ariaLive - Use the HTML DOM style property to add styles to toast
* @property {Object} style - Use the HTML DOM style property to add styles to toast
*/
class Toastify {
defaults = {
oldestFirst: true,
text: "Toastify is awesome!",
node: undefined,
duration: 3000,
selector: undefined,
callback: function() {},
destination: undefined,
newWindow: false,
close: false,
gravity: "toastify-top",
positionLeft: false,
position: "",
backgroundColor: "",
avatar: "",
className: "",
stopOnFocus: true,
onClick: function() {},
offset: { x: 0, y: 0 },
escapeMarkup: true,
ariaLive: "polite",
style: { background: "" },
};
constructor(options) {
/**
* The version of Toastify
* @type {string}
* @public
*/
this.version = "1.12.0";
/**
* The configuration object to configure Toastify
* @type {ToastifyConfigurationObject}
* @public
*/
this.options = {};
/**
* The element that is the Toast
* @type {Element}
* @public
*/
this.toastElement = null;
/**
* The root element that contains all the toasts
* @type {Element}
* @private
*/
this._rootElement = document.body;
this._init(options);
}
/**
* Display the toast
* @public
*/
showToast() {
// Creating the DOM object for the toast
this.toastElement = this._buildToast();
// Getting the root element to with the toast needs to be added
if (typeof this.options.selector === "string") {
this._rootElement = document.getElementById(this.options.selector);
} else if (this.options.selector instanceof HTMLElement || this.options.selector instanceof ShadowRoot) {
this._rootElement = this.options.selector;
} else {
this._rootElement = document.body;
}
// Validating if root element is present in DOM
if (!this._rootElement) {
throw "Root element is not defined";
}
// Adding the DOM element
this._rootElement.insertBefore(this.toastElement, this._rootElement.firstChild);
// Repositioning the toasts in case multiple toasts are present
this._reposition();
if (this.options.duration > 0) {
this.toastElement.timeOutValue = window.setTimeout(
() => {
// Remove the toast from DOM
this._removeElement(this.toastElement);
},
this.options.duration
); // Binding `this` for function invocation
}
// Supporting function chaining
return this;
}
/**
* Hide the toast
* @public
*/
hideToast() {
if (this.toastElement.timeOutValue) {
clearTimeout(this.toastElement.timeOutValue);
}
this._removeElement(this.toastElement);
}
/**
* Init the Toastify class
* @param {ToastifyConfigurationObject} options - The configuration object to configure Toastify
* @param {string} [options.text=Hi there!] - Message to be displayed in the toast
* @param {Element} [options.node] - Provide a node to be mounted inside the toast. node takes higher precedence over text
* @param {number} [options.duration=3000] - Duration for which the toast should be displayed. -1 for permanent toast
* @param {string} [options.selector] - CSS Selector on which the toast should be added
* @param {url} [options.destination] - URL to which the browser should be navigated on click of the toast
* @param {boolean} [options.newWindow=false] - Decides whether the destination should be opened in a new window or not
* @param {boolean} [options.close=false] - To show the close icon or not
* @param {string} [options.gravity=toastify-top] - To show the toast from top or bottom
* @param {string} [options.position=right] - To show the toast on left or right
* @param {string} [options.backgroundColor] - Sets the background color of the toast (To be deprecated)
* @param {url} [options.avatar] - Image/icon to be shown before text
* @param {string} [options.className] - Ability to provide custom class name for further customization
* @param {boolean} [options.stopOnFocus] - To stop timer when hovered over the toast (Only if duration is set)
* @param {Function} [options.callback] - Invoked when the toast is dismissed
* @param {Function} [options.onClick] - Invoked when the toast is clicked
* @param {Object} [options.offset] - Ability to add some offset to axis
* @param {boolean} [options.escapeMarkup=true] - Toggle the default behavior of escaping HTML markup
* @param {string} [options.ariaLive] - Announce the toast to screen readers
* @param {Object} [options.style] - Use the HTML DOM style property to add styles to toast
* @private
*/
_init(options) {
// Setting defaults
this.options = Object.assign(this.defaults, options);
if (this.options.backgroundColor) {
// This is being deprecated in favor of using the style HTML DOM property
console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.');
}
this.toastElement = null;
this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : "toastify-top"; // toast position - top or bottom
this.options.stopOnFocus = options.stopOnFocus === undefined ? true : options.stopOnFocus; // stop timeout on focus
if(options.backgroundColor) {
this.options.style.background = options.backgroundColor;
}
}
/**
* Build the Toastify element
* @returns {Element}
* @private
*/
_buildToast() {
// Validating if the options are defined
if (!this.options) {
throw "Toastify is not initialized";
}
// Creating the DOM object
let divElement = document.createElement("div");
divElement.className = `toastify on ${this.options.className}`;
// Positioning toast to left or right or center (default right)
divElement.className += ` toastify-${this.options.position}`;
// Assigning gravity of element
divElement.className += ` ${this.options.gravity}`;
// Loop through our style object and apply styles to divElement
for (const property in this.options.style) {
divElement.style[property] = this.options.style[property];
}
// Announce the toast to screen readers
if (this.options.ariaLive) {
divElement.setAttribute('aria-live', this.options.ariaLive)
}
// Adding the toast message/node
if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {
// If we have a valid node, we insert it
divElement.appendChild(this.options.node)
} else {
if (this.options.escapeMarkup) {
divElement.innerText = this.options.text;
} else {
divElement.innerHTML = this.options.text;
}
if (this.options.avatar !== "") {
let avatarElement = document.createElement("img");
avatarElement.src = this.options.avatar;
avatarElement.className = "toastify-avatar";
if (this.options.position == "left") {
// Adding close icon on the left of content
divElement.appendChild(avatarElement);
} else {
// Adding close icon on the right of content
divElement.insertAdjacentElement("afterbegin", avatarElement);
}
}
}
// Adding a close icon to the toast
if (this.options.close === true) {
// Create a span for close element
let closeElement = document.createElement("button");
closeElement.type = "button";
closeElement.setAttribute("aria-label", "Close");
closeElement.className = "toast-close";
closeElement.innerHTML = "&#10006;";
// Triggering the removal of toast from DOM on close click
closeElement.addEventListener(
"click",
(event) => {
event.stopPropagation();
this._removeElement(this.toastElement);
window.clearTimeout(this.toastElement.timeOutValue);
}
);
//Calculating screen width
const width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Adding the close icon to the toast element
// Display on the right if screen width is less than or equal to 360px
if ((this.options.position == "left") && width > 360) {
// Adding close icon on the left of content
divElement.insertAdjacentElement("afterbegin", closeElement);
} else {
// Adding close icon on the right of content
divElement.appendChild(closeElement);
}
}
// Clear timeout while toast is focused
if (this.options.stopOnFocus && this.options.duration > 0) {
// stop countdown
divElement.addEventListener(
"mouseover",
(event) => {
window.clearTimeout(divElement.timeOutValue);
}
)
// add back the timeout
divElement.addEventListener(
"mouseleave",
() => {
divElement.timeOutValue = window.setTimeout(
() => {
// Remove the toast from DOM
this._removeElement(divElement);
},
this.options.duration
)
}
)
}
// Adding an on-click destination path
if (typeof this.options.destination !== "undefined") {
divElement.addEventListener(
"click",
(event) => {
event.stopPropagation();
if (this.options.newWindow === true) {
window.open(this.options.destination, "_blank");
} else {
window.location = this.options.destination;
}
}
);
}
if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
divElement.addEventListener(
"click",
(event) => {
event.stopPropagation();
this.options.onClick();
}
);
}
// Adding offset
if (typeof this.options.offset === "object") {
const x = this._getAxisOffsetAValue("x", this.options);
const y = this._getAxisOffsetAValue("y", this.options);
const xOffset = this.options.position == "left" ? x : `-${x}`;
const yOffset = this.options.gravity == "toastify-top" ? y : `-${y}`;
divElement.style.transform = `translate(${xOffset},${yOffset})`;
}
// Returning the generated element
return divElement;
}
/**
* Remove the toast from the DOM
* @param {Element} toastElement
*/
_removeElement(toastElement) {
// Hiding the element
toastElement.className = toastElement.className.replace(" on", "");
// Removing the element from DOM after transition end
window.setTimeout(
() => {
// remove options node if any
if (this.options.node && this.options.node.parentNode) {
this.options.node.parentNode.removeChild(this.options.node);
}
// Remove the element from the DOM, only when the parent node was not removed before.
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
}
// Calling the callback function
this.options.callback.call(toastElement);
// Repositioning the toasts again
this._reposition();
},
400
); // Binding `this` for function invocation
}
/**
* Position the toast on the DOM
* @private
*/
_reposition() {
// Top margins with gravity
let topLeftOffsetSize = {
top: 15,
bottom: 15,
};
let topRightOffsetSize = {
top: 15,
bottom: 15,
};
let offsetSize = {
top: 15,
bottom: 15,
};
// Get all toast messages that have been added to the container (selector)
let allToasts = this._rootElement.querySelectorAll(".toastify");
let classUsed;
// Modifying the position of each toast element
for (let i = 0; i < allToasts.length; i++) {
// Getting the applied gravity
if (allToasts[i].classList.contains("toastify-top") === true) {
classUsed = "toastify-top";
} else {
classUsed = "toastify-bottom";
}
let height = allToasts[i].offsetHeight;
classUsed = classUsed.substr(9, classUsed.length - 1)
// Spacing between toasts
let offset = 15;
let width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Show toast in center if screen with less than or equal to 360px
if (width <= 360) {
// Setting the position
allToasts[i].style[classUsed] = `${offsetSize[classUsed]}px`;
offsetSize[classUsed] += height + offset;
} else {
if (allToasts[i].classList.contains("toastify-left") === true) {
// Setting the position
allToasts[i].style[classUsed] = `${topLeftOffsetSize[classUsed]}px`;
topLeftOffsetSize[classUsed] += height + offset;
} else {
// Setting the position
allToasts[i].style[classUsed] = `${topRightOffsetSize[classUsed]}px`;
topRightOffsetSize[classUsed] += height + offset;
}
}
}
}
/**
* Helper function to get offset
* @param {string} axis - 'x' or 'y'
* @param {ToastifyConfigurationObject} options - The options object containing the offset object
*/
_getAxisOffsetAValue(axis, options) {
if (options.offset[axis]) {
if (isNaN(options.offset[axis])) {
return options.offset[axis];
} else {
return `${options.offset[axis]}px`;
}
}
return '0px';
}
}
// Returning the Toastify function to be assigned to the window object/module
function StartToastifyInstance(options) {
return new Toastify(options);
}
export default StartToastifyInstance;

85
node_modules/toastify-js/src/toastify.css generated vendored Normal file
View File

@@ -0,0 +1,85 @@
/*!
* Toastify js 1.12.0
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
.toastify {
padding: 12px 20px;
color: #ffffff;
display: inline-block;
box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(77, 96, 232, 0.3);
background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);
background: linear-gradient(135deg, #73a5ff, #5477f5);
position: fixed;
opacity: 0;
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
border-radius: 2px;
cursor: pointer;
text-decoration: none;
max-width: calc(50% - 20px);
z-index: 2147483647;
}
.toastify.on {
opacity: 1;
}
.toast-close {
background: transparent;
border: 0;
color: white;
cursor: pointer;
font-family: inherit;
font-size: 1em;
opacity: 0.4;
padding: 0 5px;
}
.toastify-right {
right: 15px;
}
.toastify-left {
left: 15px;
}
.toastify-top {
top: -150px;
}
.toastify-bottom {
bottom: -150px;
}
.toastify-rounded {
border-radius: 25px;
}
.toastify-avatar {
width: 1.5em;
height: 1.5em;
margin: -7px 5px;
border-radius: 2px;
}
.toastify-center {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
max-width: -moz-fit-content;
}
@media only screen and (max-width: 360px) {
.toastify-right, .toastify-left {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
}
}

445
node_modules/toastify-js/src/toastify.js generated vendored Normal file
View File

@@ -0,0 +1,445 @@
/*!
* Toastify js 1.12.0
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
(function(root, factory) {
if (typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.Toastify = factory();
}
})(this, function(global) {
// Object initialization
var Toastify = function(options) {
// Returning a new init object
return new Toastify.lib.init(options);
},
// Library version
version = "1.12.0";
// Set the default global options
Toastify.defaults = {
oldestFirst: true,
text: "Toastify is awesome!",
node: undefined,
duration: 3000,
selector: undefined,
callback: function () {
},
destination: undefined,
newWindow: false,
close: false,
gravity: "toastify-top",
positionLeft: false,
position: '',
backgroundColor: '',
avatar: "",
className: "",
stopOnFocus: true,
onClick: function () {
},
offset: {x: 0, y: 0},
escapeMarkup: true,
ariaLive: 'polite',
style: {background: ''}
};
// Defining the prototype of the object
Toastify.lib = Toastify.prototype = {
toastify: version,
constructor: Toastify,
// Initializing the object with required parameters
init: function(options) {
// Verifying and validating the input object
if (!options) {
options = {};
}
// Creating the options object
this.options = {};
this.toastElement = null;
// Validating the options
this.options.text = options.text || Toastify.defaults.text; // Display message
this.options.node = options.node || Toastify.defaults.node; // Display content as node
this.options.duration = options.duration === 0 ? 0 : options.duration || Toastify.defaults.duration; // Display duration
this.options.selector = options.selector || Toastify.defaults.selector; // Parent selector
this.options.callback = options.callback || Toastify.defaults.callback; // Callback after display
this.options.destination = options.destination || Toastify.defaults.destination; // On-click destination
this.options.newWindow = options.newWindow || Toastify.defaults.newWindow; // Open destination in new window
this.options.close = options.close || Toastify.defaults.close; // Show toast close icon
this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : Toastify.defaults.gravity; // toast position - top or bottom
this.options.positionLeft = options.positionLeft || Toastify.defaults.positionLeft; // toast position - left or right
this.options.position = options.position || Toastify.defaults.position; // toast position - left or right
this.options.backgroundColor = options.backgroundColor || Toastify.defaults.backgroundColor; // toast background color
this.options.avatar = options.avatar || Toastify.defaults.avatar; // img element src - url or a path
this.options.className = options.className || Toastify.defaults.className; // additional class names for the toast
this.options.stopOnFocus = options.stopOnFocus === undefined ? Toastify.defaults.stopOnFocus : options.stopOnFocus; // stop timeout on focus
this.options.onClick = options.onClick || Toastify.defaults.onClick; // Callback after click
this.options.offset = options.offset || Toastify.defaults.offset; // toast offset
this.options.escapeMarkup = options.escapeMarkup !== undefined ? options.escapeMarkup : Toastify.defaults.escapeMarkup;
this.options.ariaLive = options.ariaLive || Toastify.defaults.ariaLive;
this.options.style = options.style || Toastify.defaults.style;
if(options.backgroundColor) {
this.options.style.background = options.backgroundColor;
}
// Returning the current object for chaining functions
return this;
},
// Building the DOM element
buildToast: function() {
// Validating if the options are defined
if (!this.options) {
throw "Toastify is not initialized";
}
// Creating the DOM object
var divElement = document.createElement("div");
divElement.className = "toastify on " + this.options.className;
// Positioning toast to left or right or center
if (!!this.options.position) {
divElement.className += " toastify-" + this.options.position;
} else {
// To be depreciated in further versions
if (this.options.positionLeft === true) {
divElement.className += " toastify-left";
console.warn('Property `positionLeft` will be depreciated in further versions. Please use `position` instead.')
} else {
// Default position
divElement.className += " toastify-right";
}
}
// Assigning gravity of element
divElement.className += " " + this.options.gravity;
if (this.options.backgroundColor) {
// This is being deprecated in favor of using the style HTML DOM property
console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.');
}
// Loop through our style object and apply styles to divElement
for (var property in this.options.style) {
divElement.style[property] = this.options.style[property];
}
// Announce the toast to screen readers
if (this.options.ariaLive) {
divElement.setAttribute('aria-live', this.options.ariaLive)
}
// Adding the toast message/node
if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {
// If we have a valid node, we insert it
divElement.appendChild(this.options.node)
} else {
if (this.options.escapeMarkup) {
divElement.innerText = this.options.text;
} else {
divElement.innerHTML = this.options.text;
}
if (this.options.avatar !== "") {
var avatarElement = document.createElement("img");
avatarElement.src = this.options.avatar;
avatarElement.className = "toastify-avatar";
if (this.options.position == "left" || this.options.positionLeft === true) {
// Adding close icon on the left of content
divElement.appendChild(avatarElement);
} else {
// Adding close icon on the right of content
divElement.insertAdjacentElement("afterbegin", avatarElement);
}
}
}
// Adding a close icon to the toast
if (this.options.close === true) {
// Create a span for close element
var closeElement = document.createElement("button");
closeElement.type = "button";
closeElement.setAttribute("aria-label", "Close");
closeElement.className = "toast-close";
closeElement.innerHTML = "&#10006;";
// Triggering the removal of toast from DOM on close click
closeElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.removeElement(this.toastElement);
window.clearTimeout(this.toastElement.timeOutValue);
}.bind(this)
);
//Calculating screen width
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Adding the close icon to the toast element
// Display on the right if screen width is less than or equal to 360px
if ((this.options.position == "left" || this.options.positionLeft === true) && width > 360) {
// Adding close icon on the left of content
divElement.insertAdjacentElement("afterbegin", closeElement);
} else {
// Adding close icon on the right of content
divElement.appendChild(closeElement);
}
}
// Clear timeout while toast is focused
if (this.options.stopOnFocus && this.options.duration > 0) {
var self = this;
// stop countdown
divElement.addEventListener(
"mouseover",
function(event) {
window.clearTimeout(divElement.timeOutValue);
}
)
// add back the timeout
divElement.addEventListener(
"mouseleave",
function() {
divElement.timeOutValue = window.setTimeout(
function() {
// Remove the toast from DOM
self.removeElement(divElement);
},
self.options.duration
)
}
)
}
// Adding an on-click destination path
if (typeof this.options.destination !== "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
if (this.options.newWindow === true) {
window.open(this.options.destination, "_blank");
} else {
window.location = this.options.destination;
}
}.bind(this)
);
}
if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.options.onClick();
}.bind(this)
);
}
// Adding offset
if(typeof this.options.offset === "object") {
var x = getAxisOffsetAValue("x", this.options);
var y = getAxisOffsetAValue("y", this.options);
var xOffset = this.options.position == "left" ? x : "-" + x;
var yOffset = this.options.gravity == "toastify-top" ? y : "-" + y;
divElement.style.transform = "translate(" + xOffset + "," + yOffset + ")";
}
// Returning the generated element
return divElement;
},
// Displaying the toast
showToast: function() {
// Creating the DOM object for the toast
this.toastElement = this.buildToast();
// Getting the root element to with the toast needs to be added
var rootElement;
if (typeof this.options.selector === "string") {
rootElement = document.getElementById(this.options.selector);
} else if (this.options.selector instanceof HTMLElement || (typeof ShadowRoot !== 'undefined' && this.options.selector instanceof ShadowRoot)) {
rootElement = this.options.selector;
} else {
rootElement = document.body;
}
// Validating if root element is present in DOM
if (!rootElement) {
throw "Root element is not defined";
}
// Adding the DOM element
var elementToInsert = Toastify.defaults.oldestFirst ? rootElement.firstChild : rootElement.lastChild;
rootElement.insertBefore(this.toastElement, elementToInsert);
// Repositioning the toasts in case multiple toasts are present
Toastify.reposition();
if (this.options.duration > 0) {
this.toastElement.timeOutValue = window.setTimeout(
function() {
// Remove the toast from DOM
this.removeElement(this.toastElement);
}.bind(this),
this.options.duration
); // Binding `this` for function invocation
}
// Supporting function chaining
return this;
},
hideToast: function() {
if (this.toastElement.timeOutValue) {
clearTimeout(this.toastElement.timeOutValue);
}
this.removeElement(this.toastElement);
},
// Removing the element from the DOM
removeElement: function(toastElement) {
// Hiding the element
// toastElement.classList.remove("on");
toastElement.className = toastElement.className.replace(" on", "");
// Removing the element from DOM after transition end
window.setTimeout(
function() {
// remove options node if any
if (this.options.node && this.options.node.parentNode) {
this.options.node.parentNode.removeChild(this.options.node);
}
// Remove the element from the DOM, only when the parent node was not removed before.
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
}
// Calling the callback function
this.options.callback.call(toastElement);
// Repositioning the toasts again
Toastify.reposition();
}.bind(this),
400
); // Binding `this` for function invocation
},
};
// Positioning the toasts on the DOM
Toastify.reposition = function() {
// Top margins with gravity
var topLeftOffsetSize = {
top: 15,
bottom: 15,
};
var topRightOffsetSize = {
top: 15,
bottom: 15,
};
var offsetSize = {
top: 15,
bottom: 15,
};
// Get all toast messages on the DOM
var allToasts = document.getElementsByClassName("toastify");
var classUsed;
// Modifying the position of each toast element
for (var i = 0; i < allToasts.length; i++) {
// Getting the applied gravity
if (containsClass(allToasts[i], "toastify-top") === true) {
classUsed = "toastify-top";
} else {
classUsed = "toastify-bottom";
}
var height = allToasts[i].offsetHeight;
classUsed = classUsed.substr(9, classUsed.length-1)
// Spacing between toasts
var offset = 15;
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Show toast in center if screen with less than or equal to 360px
if (width <= 360) {
// Setting the position
allToasts[i].style[classUsed] = offsetSize[classUsed] + "px";
offsetSize[classUsed] += height + offset;
} else {
if (containsClass(allToasts[i], "toastify-left") === true) {
// Setting the position
allToasts[i].style[classUsed] = topLeftOffsetSize[classUsed] + "px";
topLeftOffsetSize[classUsed] += height + offset;
} else {
// Setting the position
allToasts[i].style[classUsed] = topRightOffsetSize[classUsed] + "px";
topRightOffsetSize[classUsed] += height + offset;
}
}
}
// Supporting function chaining
return this;
};
// Helper function to get offset.
function getAxisOffsetAValue(axis, options) {
if(options.offset[axis]) {
if(isNaN(options.offset[axis])) {
return options.offset[axis];
}
else {
return options.offset[axis] + 'px';
}
}
return '0px';
}
function containsClass(elem, yourClass) {
if (!elem || typeof yourClass !== "string") {
return false;
} else if (
elem.className &&
elem.className
.trim()
.split(/\s+/gi)
.indexOf(yourClass) > -1
) {
return true;
} else {
return false;
}
}
// Setting up the prototype for the init object
Toastify.lib.init.prototype = Toastify.lib;
// Returning the Toastify function to be assigned to the window object/module
return Toastify;
});

15
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{
"name": "dev-trunk",
"name": "iTop",
"lockfileVersion": 2,
"requires": true,
"packages": {
@@ -15,7 +15,8 @@
"datatables.net-select": "^1.2.0",
"scrollmagic": "^2.0.8",
"selectize-plugin-a11y": "^1.1.0",
"tippy.js": "^6.2.5"
"tippy.js": "^6.2.5",
"toastify-js": "^1.12.0"
}
},
"node_modules/@fontsource/raleway": {
@@ -103,6 +104,11 @@
"dependencies": {
"@popperjs/core": "^2.4.4"
}
},
"node_modules/toastify-js": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz",
"integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ=="
}
},
"dependencies": {
@@ -181,6 +187,11 @@
"requires": {
"@popperjs/core": "^2.4.4"
}
},
"toastify-js": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz",
"integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ=="
}
}
}

View File

@@ -10,6 +10,7 @@
"datatables.net-select": "^1.2.0",
"scrollmagic": "^2.0.8",
"selectize-plugin-a11y": "^1.1.0",
"tippy.js": "^6.2.5"
"tippy.js": "^6.2.5",
"toastify-js": "^1.12.0"
}
}

View File

@@ -115,6 +115,7 @@ function DisplayPreferences($oP)
$oSecondColumn->AddSubBlock($oMiscOptionsFieldset);
$oMiscOptionsFieldset->AddSubBlock(GetObsoleteDataFieldBlock());
$oMiscOptionsFieldset->AddSubBlock(GetSummaryCardsFieldBlock());
$oMiscOptionsFieldset->AddSubBlock(GetToastsPositionFieldBlock());
$oP->add_script(
<<<JS
@@ -708,6 +709,25 @@ HTML;
return new Html($sHtml);
}
/**
* @return \Combodo\iTop\Application\UI\Base\iUIBlock
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @since 3.2.0
*/
function GetToastsPositionFieldBlock(): iUIBlock
{
$sPosition = appUserPreferences::GetPref('toasts_vertical_position', "bottom");
$oSelect = SelectUIBlockFactory::MakeForSelectWithLabel('toasts_vertical_position', Dict::S('UI:Preferences:General:Toasts'));
$oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("bottom", Dict::S('UI:Preferences:General:Toasts:Bottom'), $sPosition === "bottom"));
$oSelect->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption("top", Dict::S('UI:Preferences:General:Toasts:Top'), $sPosition === "top"));
return $oSelect;
}
/////////////////////////////////////////////////////////////////////////////
//
// Main program
@@ -805,6 +825,12 @@ try {
$bShowSummaryCards = (bool)utils::ReadParam('show_summary_cards', 0);
appUserPreferences::SetPref('show_summary_cards', $bShowSummaryCards);
// - Toast notifications
$sToastsVerticalPosition = utils::ReadParam('toasts_vertical_position', "bottom");
if(utils::IsNotNullOrEmptyString($sToastsVerticalPosition) && in_array($sToastsVerticalPosition, ["bottom", "top"], true)) {
appUserPreferences::SetPref('toasts_vertical_position', $sToastsVerticalPosition);
}
// Redirect to force a reload/display of the page in case language has been changed
$oAppContext = new ApplicationContext();
$sURL = utils::GetAbsoluteUrlAppRoot().'pages/preferences.php?'.$oAppContext->GetForLink();

View File

@@ -199,6 +199,9 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/@popperjs/core/dist/umd/popper.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy-bundle.umd.min.js');
// Toasts
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/toastify-js/src/toastify.js');
// Keyboard shortcuts
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/mousetrap/mousetrap.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/mousetrap/mousetrap-record.min.js');