diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml
index 999d7f21..df7f7b6e 100644
--- a/Web/Presenters/templates/@layout.xml
+++ b/Web/Presenters/templates/@layout.xml
@@ -21,6 +21,7 @@
{if $theme->inheritDefault()}
{css "css/style.css"}
{css "css/dialog.css"}
+ {css "css/notifications.css"}
{if $isXmas}
{css "css/xmas.css"}
{/if}
@@ -33,6 +34,7 @@
{else}
{css "css/style.css"}
{css "css/dialog.css"}
+ {css "css/notifications.css"}
{if $isXmas}
{css "css/xmas.css"}
{/if}
@@ -53,6 +55,7 @@
{css "css/style.css"}
{css "css/dialog.css"}
{css "css/nsfw-posts.css"}
+ {css "css/notifications.css"}
{if $isXmas}
{css "css/xmas.css"}
@@ -71,6 +74,7 @@
⬆ Вверх
@@ -256,6 +260,7 @@
{script "js/node_modules/ky/umd.js"}
{script "js/messagebox.js"}
+ {script "js/notifications.js"}
{script "js/scroll.js"}
{script "js/al_wall.js"}
{script "js/al_api.js"}
diff --git a/Web/static/css/notifications.css b/Web/static/css/notifications.css
new file mode 100644
index 00000000..2677830e
--- /dev/null
+++ b/Web/static/css/notifications.css
@@ -0,0 +1,100 @@
+.notifications_global_wrap {
+ bottom: 0;
+ position: fixed;
+ display: flex;
+ flex-direction: column-reverse;
+ overflow: hidden;
+ width: 330px;
+ z-index: 1000;
+}
+
+.notification_ballon_wrap {
+ margin: 0 0 14px 14px;
+}
+
+.notification_ballon {
+ border-radius: 6px;
+ background-color: rgba(0, 0, 0, 0.7);
+ width: 300px;
+ height: 90px;
+ color: white !important;
+ display: inline-block;
+ animation-name: notification_ballon_appears;
+ animation-duration: 0.5s;
+ cursor: pointer;
+}
+
+.notification_ballon.disappears {
+ animation-name: notification_ballon_disappears;
+ animation-duration: 0.5s;
+}
+
+.notification_ballon notification_title {
+ margin: 6px 10px;
+ font-size: 10pt;
+ font-weight: bold;
+ display: block;
+}
+
+.notification_ballon notification_title .close {
+ width: 10px;
+ height: 16px;
+ float: right;
+ overflow: auto;
+ opacity: 0.1;
+ color: #fff;
+ transition-duration: 0.3s;
+}
+
+.notification_ballon notification_title .close:hover {
+ opacity: 0.5;
+}
+
+.notification_ballon wrap {
+ /* uuuuh */
+ display: flex;
+ flex-direction: row;
+ padding: 0 10px 0 10px;
+}
+
+.notification_ballon wrap avatar {
+ width: 50px;
+ padding-right: 10px;
+}
+
+.notification_ballon wrap avatar img {
+ width: 50px;
+ height: 50px;
+ object-fit: cover;
+ border-radius: 6px;
+}
+
+.notification_ballon wrap content {
+ font-size: 11px;
+
+}
+
+.notification_ballon wrap content a {
+ color: rgb(94, 165, 231);
+ font-weight: bold;
+}
+
+@keyframes notification_ballon_appears {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes notification_ballon_disappears {
+ 0% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0;
+ }
+}
diff --git a/Web/static/js/notifications.js b/Web/static/js/notifications.js
new file mode 100644
index 00000000..45d45fda
--- /dev/null
+++ b/Web/static/js/notifications.js
@@ -0,0 +1,59 @@
+Function.noop = () => {};
+
+var _n_counter = 0;
+
+function NewNotification(title, body, avatar = null, callback = () => {}, time = 5000) {
+ if(avatar != null) {
+ avatar = '
' +
+ '' +
+ '';
+ } else {
+ avatar = '';
+ }
+
+ _n_counter += 1;
+ let id = _n_counter;
+
+ let notification = u(
+ `
+
+ ${title}
+ X
+
+
+ ${avatar}
+
+ ${body}
+
+
+
+ `);
+
+ u(".notifications_global_wrap").append(notification);
+
+ function getPrototype() {
+ return u("#n"+id);
+ }
+
+ function __closeNotification() {
+ getPrototype().addClass('disappears');
+ setTimeout(() => {getPrototype().remove()}, 500);
+ }
+
+ setTimeout(() => {__closeNotification()}, time);
+
+ notification.children('notification_title').children('a.close').on('click', function(e) {
+ __closeNotification();
+ });
+
+ notification.on('click', function(e) {
+ if (!notification.hasClass('disappears')) {
+ Reflect.apply(callback, {
+ closeNotification: () => __closeNotification(),
+ $notification: () => getPrototype()
+ }, [e]);
+
+ __closeNotification();
+ }
+ });
+}