mesauth-angular 1.21.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, Injectable, InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgModule, afterNextRender, input, booleanAttribute, computed, HostBinding, Component, output, effect, HostListener, Injector, Pipe, viewChild, ViewChild,
|
|
2
|
+
import { signal, Injectable, InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgModule, afterNextRender, input, booleanAttribute, computed, HostBinding, Component, output, effect, HostListener, ElementRef, Injector, Pipe, viewChild, ViewChild, DestroyRef, Directive } from '@angular/core';
|
|
3
3
|
import { toObservable, toSignal, takeUntilDestroyed, rxResource } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { catchError, of, Subject, EMPTY, timer, throwError, firstValueFrom, distinctUntilChanged, switchMap as switchMap$1, forkJoin } from 'rxjs';
|
|
5
5
|
import { HttpClient, HttpResponse } from '@angular/common/http';
|
|
@@ -704,7 +704,8 @@ const DEFAULT_AI_CONFIG = {
|
|
|
704
704
|
enabled: true,
|
|
705
705
|
tools: [],
|
|
706
706
|
systemPromptExtensions: [],
|
|
707
|
-
appName: undefined
|
|
707
|
+
appName: undefined,
|
|
708
|
+
panelMode: 'static'
|
|
708
709
|
};
|
|
709
710
|
/**
|
|
710
711
|
* Provide AI assistant configuration to mesauth-angular.
|
|
@@ -1052,6 +1053,7 @@ class NotificationPanelComponent {
|
|
|
1052
1053
|
authService = inject(MesAuthService);
|
|
1053
1054
|
toastService = inject(ToastService);
|
|
1054
1055
|
themeService = inject(ThemeService);
|
|
1056
|
+
host = inject(ElementRef);
|
|
1055
1057
|
constructor() {
|
|
1056
1058
|
this.loadNotifications();
|
|
1057
1059
|
// Refresh time-ago labels every 30s - signal mutation triggers CD in zoneless
|
|
@@ -1064,6 +1066,22 @@ class NotificationPanelComponent {
|
|
|
1064
1066
|
this.toastService.show(notification.message || '', notification.title, this.toastType(notification.type), 5000);
|
|
1065
1067
|
this.loadNotifications();
|
|
1066
1068
|
});
|
|
1069
|
+
// Close when the user clicks outside the panel - matches ma-approval-panel UX.
|
|
1070
|
+
effect((onCleanup) => {
|
|
1071
|
+
if (!this.isOpen())
|
|
1072
|
+
return;
|
|
1073
|
+
const onDocClick = (ev) => {
|
|
1074
|
+
const panel = this.host.nativeElement.querySelector('.notification-panel');
|
|
1075
|
+
if (panel && !panel.contains(ev.target))
|
|
1076
|
+
this.close();
|
|
1077
|
+
};
|
|
1078
|
+
// Defer to skip the same click that opened the panel.
|
|
1079
|
+
const tid = setTimeout(() => document.addEventListener('mousedown', onDocClick), 0);
|
|
1080
|
+
onCleanup(() => {
|
|
1081
|
+
clearTimeout(tid);
|
|
1082
|
+
document.removeEventListener('mousedown', onDocClick);
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1067
1085
|
}
|
|
1068
1086
|
ngOnDestroy() {
|
|
1069
1087
|
if (this.dateTimer !== null) {
|
|
@@ -1208,11 +1226,11 @@ class NotificationPanelComponent {
|
|
|
1208
1226
|
return normalized;
|
|
1209
1227
|
}
|
|
1210
1228
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: NotificationPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1211
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: "<div class=\"notification-panel\" [class.open]=\"isOpen()\">\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <h3>Notifications</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\" aria-label=\"Close notifications\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'unread'\" (click)=\"switchTab('unread')\">\n Unread\n @if (unreadNotifications().length > 0) {\n <span class=\"tab-badge\">{{ unreadNotifications().length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'read'\" (click)=\"switchTab('read')\">\n Read\n @if (readNotifications().length > 0) {\n <span class=\"tab-badge read-badge\">{{ readNotifications().length }}</span>\n }\n </button>\n </div>\n\n <!-- Notifications List -->\n <div class=\"notifications-list\">\n @if (currentNotifications().length > 0) {\n @for (notification of currentNotifications(); track notification.id) {\n <div\n class=\"notification-item\"\n [class.unread]=\"!notification.isRead\"\n (click)=\"openDetails(notification)\"\n >\n @let t = typeOf(notification);\n <div class=\"notif-accent\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\"></div>\n <div class=\"notif-type-icon\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\">\n @if (t === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (t === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (t === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (t === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-title\">{{ notification.title }}</div>\n <div class=\"notification-message\">{{ notification.message }}</div>\n <div class=\"notification-meta\">\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\n <span class=\"time\">{{ dateLabels().get(notification.id) }}</span>\n </div>\n </div>\n @if (!notification.isRead) {\n <button class=\"icon-btn read-btn\" (click)=\"markAsRead(notification.id, $event)\" title=\"Mark as read\" aria-label=\"Mark as read\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n </button>\n }\n @if (notification.isRead) {\n <button class=\"icon-btn delete-btn\" (click)=\"delete(notification.id, $event)\" title=\"Delete\" aria-label=\"Delete notification\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M10 11v6\"/><path d=\"M14 11v6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <svg class=\"empty-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <p>No {{ activeTab() }} notifications</p>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (currentNotifications().length > 0) {\n <div class=\"panel-footer\">\n @if (activeTab() === 'unread') {\n <div class=\"footer-actions\">\n @if (unreadNotifications().length > 0) {\n <button class=\"action-btn\" (click)=\"markAllAsRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark all read\n </button>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n @if (activeTab() === 'read' && readNotifications().length > 0) {\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n</div>\n\n<!-- Details Modal -->\n@if (selectedNotification()) {\n <div class=\"modal-overlay\" (click)=\"closeDetails()\">\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\"\n [class.modal-type-info]=\"typeOf(selectedNotification()!) === 'Info'\"\n [class.modal-type-success]=\"typeOf(selectedNotification()!) === 'Success'\"\n [class.modal-type-warning]=\"typeOf(selectedNotification()!) === 'Warning'\"\n [class.modal-type-error]=\"typeOf(selectedNotification()!) === 'Error'\">\n <div class=\"modal-header-left\">\n <div class=\"modal-type-icon\">\n @if (typeOf(selectedNotification()!) === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <h3>{{ selectedNotification()!.title }}</h3>\n </div>\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\" aria-label=\"Close notification detail\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"modal-meta\">\n <span class=\"app-name\">{{ selectedNotification()!.sourceAppName }}</span>\n <span class=\"time\">{{ selectedNotificationDate() }}</span>\n </div>\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml()\"></div>\n <div class=\"modal-footer\">\n @if (selectedNotification()?.url?.trim()) {\n <button class=\"action-btn see-details-btn\" (click)=\"openUrl()\">See Details</button>\n }\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\n </div>\n </div>\n </div>\n}\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:92%;max-width:860px;max-height:88vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] });
|
|
1229
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: "<div class=\"notification-panel\" [class.open]=\"isOpen()\">\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <h3>Notifications</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\" aria-label=\"Close notifications\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'unread'\" (click)=\"switchTab('unread')\">\n Unread\n @if (unreadNotifications().length > 0) {\n <span class=\"tab-badge\">{{ unreadNotifications().length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'read'\" (click)=\"switchTab('read')\">\n Read\n @if (readNotifications().length > 0) {\n <span class=\"tab-badge read-badge\">{{ readNotifications().length }}</span>\n }\n </button>\n </div>\n\n <!-- Notifications List -->\n <div class=\"notifications-list\">\n @if (currentNotifications().length > 0) {\n @for (notification of currentNotifications(); track notification.id) {\n <div\n class=\"notification-item\"\n [class.unread]=\"!notification.isRead\"\n (click)=\"openDetails(notification)\"\n >\n @let t = typeOf(notification);\n <div class=\"notif-accent\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\"></div>\n <div class=\"notif-type-icon\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\">\n @if (t === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (t === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (t === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (t === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-title\">{{ notification.title }}</div>\n <div class=\"notification-message\">{{ notification.message }}</div>\n <div class=\"notification-meta\">\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\n <span class=\"time\">{{ dateLabels().get(notification.id) }}</span>\n </div>\n </div>\n @if (!notification.isRead) {\n <button class=\"icon-btn read-btn\" (click)=\"markAsRead(notification.id, $event)\" title=\"Mark as read\" aria-label=\"Mark as read\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n </button>\n }\n @if (notification.isRead) {\n <button class=\"icon-btn delete-btn\" (click)=\"delete(notification.id, $event)\" title=\"Delete\" aria-label=\"Delete notification\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M10 11v6\"/><path d=\"M14 11v6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <svg class=\"empty-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <p>No {{ activeTab() }} notifications</p>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (currentNotifications().length > 0) {\n <div class=\"panel-footer\">\n @if (activeTab() === 'unread') {\n <div class=\"footer-actions\">\n @if (unreadNotifications().length > 0) {\n <button class=\"action-btn\" (click)=\"markAllAsRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark all read\n </button>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n @if (activeTab() === 'read' && readNotifications().length > 0) {\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n</div>\n\n<!-- Details Modal -->\n@if (selectedNotification()) {\n <div class=\"modal-overlay\" (click)=\"closeDetails()\">\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\"\n [class.modal-type-info]=\"typeOf(selectedNotification()!) === 'Info'\"\n [class.modal-type-success]=\"typeOf(selectedNotification()!) === 'Success'\"\n [class.modal-type-warning]=\"typeOf(selectedNotification()!) === 'Warning'\"\n [class.modal-type-error]=\"typeOf(selectedNotification()!) === 'Error'\">\n <div class=\"modal-header-left\">\n <div class=\"modal-type-icon\">\n @if (typeOf(selectedNotification()!) === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <h3>{{ selectedNotification()!.title }}</h3>\n </div>\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\" aria-label=\"Close notification detail\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"modal-meta\">\n <span class=\"app-name\">{{ selectedNotification()!.sourceAppName }}</span>\n <span class=\"time\">{{ selectedNotificationDate() }}</span>\n </div>\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml()\"></div>\n <div class=\"modal-footer\">\n @if (selectedNotification()?.url?.trim()) {\n <button class=\"action-btn see-details-btn\" (click)=\"openUrl()\">See Details</button>\n }\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\n </div>\n </div>\n </div>\n}\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:var(--ma-ai-panel-width, 0px)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:92%;max-width:860px;max-height:88vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] });
|
|
1212
1230
|
}
|
|
1213
1231
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
1214
1232
|
type: Component,
|
|
1215
|
-
args: [{ selector: 'ma-notification-panel', imports: [], template: "<div class=\"notification-panel\" [class.open]=\"isOpen()\">\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <h3>Notifications</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\" aria-label=\"Close notifications\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'unread'\" (click)=\"switchTab('unread')\">\n Unread\n @if (unreadNotifications().length > 0) {\n <span class=\"tab-badge\">{{ unreadNotifications().length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'read'\" (click)=\"switchTab('read')\">\n Read\n @if (readNotifications().length > 0) {\n <span class=\"tab-badge read-badge\">{{ readNotifications().length }}</span>\n }\n </button>\n </div>\n\n <!-- Notifications List -->\n <div class=\"notifications-list\">\n @if (currentNotifications().length > 0) {\n @for (notification of currentNotifications(); track notification.id) {\n <div\n class=\"notification-item\"\n [class.unread]=\"!notification.isRead\"\n (click)=\"openDetails(notification)\"\n >\n @let t = typeOf(notification);\n <div class=\"notif-accent\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\"></div>\n <div class=\"notif-type-icon\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\">\n @if (t === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (t === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (t === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (t === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-title\">{{ notification.title }}</div>\n <div class=\"notification-message\">{{ notification.message }}</div>\n <div class=\"notification-meta\">\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\n <span class=\"time\">{{ dateLabels().get(notification.id) }}</span>\n </div>\n </div>\n @if (!notification.isRead) {\n <button class=\"icon-btn read-btn\" (click)=\"markAsRead(notification.id, $event)\" title=\"Mark as read\" aria-label=\"Mark as read\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n </button>\n }\n @if (notification.isRead) {\n <button class=\"icon-btn delete-btn\" (click)=\"delete(notification.id, $event)\" title=\"Delete\" aria-label=\"Delete notification\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M10 11v6\"/><path d=\"M14 11v6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <svg class=\"empty-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <p>No {{ activeTab() }} notifications</p>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (currentNotifications().length > 0) {\n <div class=\"panel-footer\">\n @if (activeTab() === 'unread') {\n <div class=\"footer-actions\">\n @if (unreadNotifications().length > 0) {\n <button class=\"action-btn\" (click)=\"markAllAsRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark all read\n </button>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n @if (activeTab() === 'read' && readNotifications().length > 0) {\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n</div>\n\n<!-- Details Modal -->\n@if (selectedNotification()) {\n <div class=\"modal-overlay\" (click)=\"closeDetails()\">\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\"\n [class.modal-type-info]=\"typeOf(selectedNotification()!) === 'Info'\"\n [class.modal-type-success]=\"typeOf(selectedNotification()!) === 'Success'\"\n [class.modal-type-warning]=\"typeOf(selectedNotification()!) === 'Warning'\"\n [class.modal-type-error]=\"typeOf(selectedNotification()!) === 'Error'\">\n <div class=\"modal-header-left\">\n <div class=\"modal-type-icon\">\n @if (typeOf(selectedNotification()!) === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <h3>{{ selectedNotification()!.title }}</h3>\n </div>\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\" aria-label=\"Close notification detail\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"modal-meta\">\n <span class=\"app-name\">{{ selectedNotification()!.sourceAppName }}</span>\n <span class=\"time\">{{ selectedNotificationDate() }}</span>\n </div>\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml()\"></div>\n <div class=\"modal-footer\">\n @if (selectedNotification()?.url?.trim()) {\n <button class=\"action-btn see-details-btn\" (click)=\"openUrl()\">See Details</button>\n }\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\n </div>\n </div>\n </div>\n}\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:92%;max-width:860px;max-height:88vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
1233
|
+
args: [{ selector: 'ma-notification-panel', imports: [], template: "<div class=\"notification-panel\" [class.open]=\"isOpen()\">\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <h3>Notifications</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\" aria-label=\"Close notifications\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'unread'\" (click)=\"switchTab('unread')\">\n Unread\n @if (unreadNotifications().length > 0) {\n <span class=\"tab-badge\">{{ unreadNotifications().length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'read'\" (click)=\"switchTab('read')\">\n Read\n @if (readNotifications().length > 0) {\n <span class=\"tab-badge read-badge\">{{ readNotifications().length }}</span>\n }\n </button>\n </div>\n\n <!-- Notifications List -->\n <div class=\"notifications-list\">\n @if (currentNotifications().length > 0) {\n @for (notification of currentNotifications(); track notification.id) {\n <div\n class=\"notification-item\"\n [class.unread]=\"!notification.isRead\"\n (click)=\"openDetails(notification)\"\n >\n @let t = typeOf(notification);\n <div class=\"notif-accent\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\"></div>\n <div class=\"notif-type-icon\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\">\n @if (t === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (t === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (t === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (t === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-title\">{{ notification.title }}</div>\n <div class=\"notification-message\">{{ notification.message }}</div>\n <div class=\"notification-meta\">\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\n <span class=\"time\">{{ dateLabels().get(notification.id) }}</span>\n </div>\n </div>\n @if (!notification.isRead) {\n <button class=\"icon-btn read-btn\" (click)=\"markAsRead(notification.id, $event)\" title=\"Mark as read\" aria-label=\"Mark as read\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n </button>\n }\n @if (notification.isRead) {\n <button class=\"icon-btn delete-btn\" (click)=\"delete(notification.id, $event)\" title=\"Delete\" aria-label=\"Delete notification\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M10 11v6\"/><path d=\"M14 11v6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <svg class=\"empty-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <p>No {{ activeTab() }} notifications</p>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (currentNotifications().length > 0) {\n <div class=\"panel-footer\">\n @if (activeTab() === 'unread') {\n <div class=\"footer-actions\">\n @if (unreadNotifications().length > 0) {\n <button class=\"action-btn\" (click)=\"markAllAsRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark all read\n </button>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n @if (activeTab() === 'read' && readNotifications().length > 0) {\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n</div>\n\n<!-- Details Modal -->\n@if (selectedNotification()) {\n <div class=\"modal-overlay\" (click)=\"closeDetails()\">\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\"\n [class.modal-type-info]=\"typeOf(selectedNotification()!) === 'Info'\"\n [class.modal-type-success]=\"typeOf(selectedNotification()!) === 'Success'\"\n [class.modal-type-warning]=\"typeOf(selectedNotification()!) === 'Warning'\"\n [class.modal-type-error]=\"typeOf(selectedNotification()!) === 'Error'\">\n <div class=\"modal-header-left\">\n <div class=\"modal-type-icon\">\n @if (typeOf(selectedNotification()!) === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n </svg>\n }\n @if (typeOf(selectedNotification()!) === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <h3>{{ selectedNotification()!.title }}</h3>\n </div>\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\" aria-label=\"Close notification detail\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"modal-meta\">\n <span class=\"app-name\">{{ selectedNotification()!.sourceAppName }}</span>\n <span class=\"time\">{{ selectedNotificationDate() }}</span>\n </div>\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml()\"></div>\n <div class=\"modal-footer\">\n @if (selectedNotification()?.url?.trim()) {\n <button class=\"action-btn see-details-btn\" (click)=\"openUrl()\">See Details</button>\n }\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\n </div>\n </div>\n </div>\n}\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:var(--ma-ai-panel-width, 0px)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:92%;max-width:860px;max-height:88vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
1216
1234
|
}], ctorParameters: () => [], propDecorators: { notificationRead: [{ type: i0.Output, args: ["notificationRead"] }], themeClass: [{
|
|
1217
1235
|
type: HostBinding,
|
|
1218
1236
|
args: ['class']
|
|
@@ -1359,6 +1377,7 @@ class MaApprovalPanelComponent {
|
|
|
1359
1377
|
mesAuth = inject(MesAuthService);
|
|
1360
1378
|
http = inject(HttpClient);
|
|
1361
1379
|
router = inject(Router);
|
|
1380
|
+
host = inject(ElementRef);
|
|
1362
1381
|
approvalSvc = null;
|
|
1363
1382
|
constructor() {
|
|
1364
1383
|
const config = this.mesAuth.getConfig();
|
|
@@ -1372,6 +1391,23 @@ class MaApprovalPanelComponent {
|
|
|
1372
1391
|
if (this.isOpen())
|
|
1373
1392
|
this.loadCurrentTab();
|
|
1374
1393
|
});
|
|
1394
|
+
// Close when the user clicks outside the panel (mirrors the old backdrop UX
|
|
1395
|
+
// now that the backdrop has been removed).
|
|
1396
|
+
effect((onCleanup) => {
|
|
1397
|
+
if (!this.isOpen())
|
|
1398
|
+
return;
|
|
1399
|
+
const onDocClick = (ev) => {
|
|
1400
|
+
const panel = this.host.nativeElement.querySelector('.approval-panel');
|
|
1401
|
+
if (panel && !panel.contains(ev.target))
|
|
1402
|
+
this.close();
|
|
1403
|
+
};
|
|
1404
|
+
// Defer to skip the same click that opened the panel.
|
|
1405
|
+
const tid = setTimeout(() => document.addEventListener('mousedown', onDocClick), 0);
|
|
1406
|
+
onCleanup(() => {
|
|
1407
|
+
clearTimeout(tid);
|
|
1408
|
+
document.removeEventListener('mousedown', onDocClick);
|
|
1409
|
+
});
|
|
1410
|
+
});
|
|
1375
1411
|
}
|
|
1376
1412
|
open() {
|
|
1377
1413
|
this.isOpen.set(true);
|
|
@@ -1444,11 +1480,11 @@ class MaApprovalPanelComponent {
|
|
|
1444
1480
|
this.router.navigate(['/auth/approval/my-requests'], { queryParams: { status } });
|
|
1445
1481
|
}
|
|
1446
1482
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: MaApprovalPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1447
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: MaApprovalPanelComponent, isStandalone: true, selector: "ma-approval-panel", outputs: { approvalActioned: "approvalActioned" }, ngImport: i0, template: "<div class=\"approval-
|
|
1483
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: MaApprovalPanelComponent, isStandalone: true, selector: "ma-approval-panel", outputs: { approvalActioned: "approvalActioned" }, ngImport: i0, template: "<div class=\"approval-panel\" [class.open]=\"isOpen()\">\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n <h3>Approvals</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'processing'\" (click)=\"switchTab('processing')\">\n Processing\n @if (processingItems().length > 0) {\n <span class=\"tab-badge\">{{ processingItems().length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'approved'\" (click)=\"switchTab('approved')\">\n Approved\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'rejected'\" (click)=\"switchTab('rejected')\">\n Rejected\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"panel-content\">\n <!-- Loading -->\n @if (loading()) {\n <div class=\"loading-state\">\n <div class=\"spinner\"></div>\n <span>Loading...</span>\n </div>\n }\n\n <!-- Processing tab -->\n @if (!loading() && activeTab() === 'processing') {\n @if (processingItems().length === 0) {\n <div class=\"empty-state\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.4\"><path d=\"M9 11l3 3L22 4\"/><path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/></svg>\n <p>No pending approvals</p>\n </div>\n }\n @for (item of processingItems(); track item.id) {\n <div class=\"approval-item\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n @if (item.currentStepName) {\n <span class=\"item-step\">\u00B7 {{ item.currentStepName }}</span>\n }\n </div>\n <div class=\"item-footer\">\n <span class=\"item-time\">{{ item.createdAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n }\n\n <!-- Approved tab -->\n @if (!loading() && activeTab() === 'approved') {\n @if (approvedItems().length === 0) {\n <div class=\"empty-state\">\n <p>No approved documents</p>\n </div>\n }\n @for (item of approvedItems(); track item.id) {\n <div class=\"approval-item approved\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge approved-badge\">Approved</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (approvedItems().length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('approved')\">Show more →</div>\n }\n }\n\n <!-- Rejected tab -->\n @if (!loading() && activeTab() === 'rejected') {\n @if (rejectedItems().length === 0) {\n <div class=\"empty-state\">\n <p>No rejected documents</p>\n </div>\n }\n @for (item of rejectedItems(); track item.id) {\n <div class=\"approval-item rejected\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge rejected-badge\">Rejected</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (rejectedItems().length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('rejected')\">Show more →</div>\n }\n }\n </div>\n</div>\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{--primary: #90caf9;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #1565c0;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f5f5;--bg-hover: #e8eaf6;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.approval-panel{position:fixed;top:0;right:-380px;width:380px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.approval-panel.open{right:var(--ma-ai-panel-width, 0px)}.panel-content{flex:1;overflow-y:auto;padding:8px 0}.approval-item{padding:14px 18px;border-bottom:1px solid var(--border-color);cursor:pointer;transition:background .15s}.approval-item:hover{background:var(--bg-hover)}.approval-item:last-child{border-bottom:none}.item-title{font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item-meta{font-size:12px;color:var(--text-muted);margin-bottom:8px;display:flex;gap:4px;flex-wrap:wrap}.item-footer{display:flex;align-items:center;gap:8px}.item-time{font-size:11px;color:var(--text-muted);margin-right:auto}.item-link{font-size:12px;color:var(--primary);font-weight:500}.status-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;letter-spacing:.3px}.approved-badge{background:#66bb6a26;color:var(--success)}.rejected-badge{background:#ef53501f;color:var(--error)}.show-more{text-align:center;padding:14px;font-size:13px;color:var(--primary);cursor:pointer;font-weight:500}.show-more:hover{text-decoration:underline}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }] });
|
|
1448
1484
|
}
|
|
1449
1485
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: MaApprovalPanelComponent, decorators: [{
|
|
1450
1486
|
type: Component,
|
|
1451
|
-
args: [{ selector: 'ma-approval-panel', imports: [DatePipe], template: "<div class=\"approval-
|
|
1487
|
+
args: [{ selector: 'ma-approval-panel', imports: [DatePipe], template: "<div class=\"approval-panel\" [class.open]=\"isOpen()\">\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n <h3>Approvals</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'processing'\" (click)=\"switchTab('processing')\">\n Processing\n @if (processingItems().length > 0) {\n <span class=\"tab-badge\">{{ processingItems().length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'approved'\" (click)=\"switchTab('approved')\">\n Approved\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab() === 'rejected'\" (click)=\"switchTab('rejected')\">\n Rejected\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"panel-content\">\n <!-- Loading -->\n @if (loading()) {\n <div class=\"loading-state\">\n <div class=\"spinner\"></div>\n <span>Loading...</span>\n </div>\n }\n\n <!-- Processing tab -->\n @if (!loading() && activeTab() === 'processing') {\n @if (processingItems().length === 0) {\n <div class=\"empty-state\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.4\"><path d=\"M9 11l3 3L22 4\"/><path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/></svg>\n <p>No pending approvals</p>\n </div>\n }\n @for (item of processingItems(); track item.id) {\n <div class=\"approval-item\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n @if (item.currentStepName) {\n <span class=\"item-step\">\u00B7 {{ item.currentStepName }}</span>\n }\n </div>\n <div class=\"item-footer\">\n <span class=\"item-time\">{{ item.createdAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n }\n\n <!-- Approved tab -->\n @if (!loading() && activeTab() === 'approved') {\n @if (approvedItems().length === 0) {\n <div class=\"empty-state\">\n <p>No approved documents</p>\n </div>\n }\n @for (item of approvedItems(); track item.id) {\n <div class=\"approval-item approved\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge approved-badge\">Approved</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (approvedItems().length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('approved')\">Show more →</div>\n }\n }\n\n <!-- Rejected tab -->\n @if (!loading() && activeTab() === 'rejected') {\n @if (rejectedItems().length === 0) {\n <div class=\"empty-state\">\n <p>No rejected documents</p>\n </div>\n }\n @for (item of rejectedItems(); track item.id) {\n <div class=\"approval-item rejected\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge rejected-badge\">Rejected</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (rejectedItems().length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('rejected')\">Show more →</div>\n }\n }\n </div>\n</div>\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{--primary: #90caf9;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #1565c0;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f5f5;--bg-hover: #e8eaf6;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.approval-panel{position:fixed;top:0;right:-380px;width:380px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.approval-panel.open{right:var(--ma-ai-panel-width, 0px)}.panel-content{flex:1;overflow-y:auto;padding:8px 0}.approval-item{padding:14px 18px;border-bottom:1px solid var(--border-color);cursor:pointer;transition:background .15s}.approval-item:hover{background:var(--bg-hover)}.approval-item:last-child{border-bottom:none}.item-title{font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item-meta{font-size:12px;color:var(--text-muted);margin-bottom:8px;display:flex;gap:4px;flex-wrap:wrap}.item-footer{display:flex;align-items:center;gap:8px}.item-time{font-size:11px;color:var(--text-muted);margin-right:auto}.item-link{font-size:12px;color:var(--primary);font-weight:500}.status-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;letter-spacing:.3px}.approved-badge{background:#66bb6a26;color:var(--success)}.rejected-badge{background:#ef53501f;color:var(--error)}.show-more{text-align:center;padding:14px;font-size:13px;color:var(--primary);cursor:pointer;font-weight:500}.show-more:hover{text-decoration:underline}\n"] }]
|
|
1452
1488
|
}], ctorParameters: () => [], propDecorators: { approvalActioned: [{ type: i0.Output, args: ["approvalActioned"] }] } });
|
|
1453
1489
|
|
|
1454
1490
|
/**
|
|
@@ -2032,6 +2068,36 @@ const STORAGE_KEY = 'ma-ai-panel-width';
|
|
|
2032
2068
|
const MIN_WIDTH = 320;
|
|
2033
2069
|
const MAX_WIDTH = 800;
|
|
2034
2070
|
const DEFAULT_WIDTH = 420;
|
|
2071
|
+
const LAYOUT_STYLE_ID = 'ma-ai-panel-layout-style';
|
|
2072
|
+
const LAYOUT_CSS_VAR = '--ma-ai-panel-width';
|
|
2073
|
+
let layoutStyleInstalled = false;
|
|
2074
|
+
/**
|
|
2075
|
+
* Inject (once per page) a global stylesheet that lets the host app's <body> reflow
|
|
2076
|
+
* when the AI panel opens. Driven by the `--ma-ai-panel-width` custom property which
|
|
2077
|
+
* the panel writes on every open/close/resize.
|
|
2078
|
+
*/
|
|
2079
|
+
function ensureGlobalLayoutStyle() {
|
|
2080
|
+
if (layoutStyleInstalled)
|
|
2081
|
+
return;
|
|
2082
|
+
if (typeof document === 'undefined')
|
|
2083
|
+
return;
|
|
2084
|
+
if (document.getElementById(LAYOUT_STYLE_ID)) {
|
|
2085
|
+
layoutStyleInstalled = true;
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
const style = document.createElement('style');
|
|
2089
|
+
style.id = LAYOUT_STYLE_ID;
|
|
2090
|
+
style.textContent = `
|
|
2091
|
+
:root { ${LAYOUT_CSS_VAR}: 0px; }
|
|
2092
|
+
body {
|
|
2093
|
+
padding-inline-end: var(${LAYOUT_CSS_VAR}, 0px);
|
|
2094
|
+
transition: padding-inline-end 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
2095
|
+
box-sizing: border-box;
|
|
2096
|
+
}
|
|
2097
|
+
`;
|
|
2098
|
+
document.head.appendChild(style);
|
|
2099
|
+
layoutStyleInstalled = true;
|
|
2100
|
+
}
|
|
2035
2101
|
/**
|
|
2036
2102
|
* Right-side, resizable chat drawer. Open/close via the public open/close/toggle methods.
|
|
2037
2103
|
* Uses panel-shared.css for the header/empty-state styling so it looks like the approval
|
|
@@ -2045,6 +2111,8 @@ class MaAiChatPanelComponent {
|
|
|
2045
2111
|
router = inject(Router, { optional: true });
|
|
2046
2112
|
themeService = inject(ThemeService);
|
|
2047
2113
|
uiConfig = inject(MaUiConfigService);
|
|
2114
|
+
aiConfig = inject(MES_AUTH_AI_CONFIG, { optional: true }) ?? DEFAULT_AI_CONFIG;
|
|
2115
|
+
panelMode = this.aiConfig.panelMode ?? 'static';
|
|
2048
2116
|
/** Suggested prompts shown in the empty state — manifest overridable. */
|
|
2049
2117
|
quickPrompts = computed(() => {
|
|
2050
2118
|
const fromManifest = this.uiConfig.quickPrompts();
|
|
@@ -2077,10 +2145,28 @@ class MaAiChatPanelComponent {
|
|
|
2077
2145
|
this.streaming();
|
|
2078
2146
|
queueMicrotask(() => this.scrollToBottom());
|
|
2079
2147
|
});
|
|
2148
|
+
// Static mode: install the global push-layout stylesheet and keep the CSS
|
|
2149
|
+
// variable in sync with open/close + live drag-resize.
|
|
2150
|
+
if (this.panelMode === 'static') {
|
|
2151
|
+
ensureGlobalLayoutStyle();
|
|
2152
|
+
effect(() => {
|
|
2153
|
+
const px = this.isOpen() ? this.width() : 0;
|
|
2154
|
+
this.setLayoutVar(px);
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2080
2157
|
}
|
|
2081
2158
|
ngAfterViewInit() {
|
|
2082
2159
|
// pointer listeners attached on demand during drag
|
|
2083
2160
|
}
|
|
2161
|
+
ngOnDestroy() {
|
|
2162
|
+
if (this.panelMode === 'static')
|
|
2163
|
+
this.setLayoutVar(0);
|
|
2164
|
+
}
|
|
2165
|
+
setLayoutVar(px) {
|
|
2166
|
+
if (typeof document === 'undefined')
|
|
2167
|
+
return;
|
|
2168
|
+
document.documentElement.style.setProperty(LAYOUT_CSS_VAR, `${px}px`);
|
|
2169
|
+
}
|
|
2084
2170
|
open() {
|
|
2085
2171
|
this.isOpen.set(true);
|
|
2086
2172
|
queueMicrotask(() => this.textArea()?.nativeElement.focus());
|
|
@@ -2170,11 +2256,11 @@ class MaAiChatPanelComponent {
|
|
|
2170
2256
|
el.scrollTop = el.scrollHeight;
|
|
2171
2257
|
}
|
|
2172
2258
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: MaAiChatPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2173
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: MaAiChatPanelComponent, isStandalone: true, selector: "ma-ai-chat-panel", host: { properties: { "class": "this.themeClass" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }, { propertyName: "textArea", first: true, predicate: ["textArea"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"ai-panel\" [class.open]=\"isOpen()\" [style.width.px]=\"width()\">\n\n <!-- Resize handle (left edge) -->\n <div class=\"ai-resize-handle\"\n (pointerdown)=\"startResize($event)\"\n (pointermove)=\"onResizeMove($event)\"\n (pointerup)=\"endResize($event)\"\n (pointercancel)=\"endResize($event)\"\n aria-label=\"Resize panel\"\n title=\"Drag to resize\"></div>\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n <path d=\"M19 16 L19.8 18.2 L22 19 L19.8 19.8 L19 22 L18.2 19.8 L16 19 L18.2 18.2 Z\"/>\n </svg>\n <h3>AI Assistant</h3>\n </div>\n <div class=\"header-actions\">\n <button class=\"icon-btn\" (click)=\"newConversation()\" title=\"New conversation\" aria-label=\"New conversation\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 1 1-3-6.7L21 8\"/><path d=\"M21 3v5h-5\"/>\n </svg>\n </button>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Conversation -->\n <div class=\"panel-content\" #scrollContainer>\n @if (!hasMessages()) {\n <div class=\"empty-state\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"36\" height=\"36\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n </svg>\n <p>Ask anything about your account, approvals, notifications, or how to use the app.</p>\n <div class=\"quick-prompts\">\n @for (q of quickPrompts(); track q) {\n <button class=\"quick-prompt\" (click)=\"draft.set(q)\">{{ q }}</button>\n }\n </div>\n </div>\n }\n\n @for (m of messages(); track m.id) {\n <div class=\"bubble\" [class.user]=\"m.role === 'user'\" [class.assistant]=\"m.role === 'assistant'\">\n @if (m.role === 'assistant' && m.toolEvents && m.toolEvents.length > 0) {\n <div class=\"tool-strip\">\n @for (t of m.toolEvents; track t.id) {\n <div class=\"tool-chip\" [class.ok]=\"t.status === 'ok'\" [class.err]=\"t.status === 'error'\"\n [class.running]=\"t.status === 'running'\" [class.await]=\"t.status === 'awaiting-approval'\"\n [class.declined]=\"t.status === 'declined'\"\n [title]=\"t.summary ?? ''\">\n <span class=\"dot\"></span>\n <span class=\"tool-name\">{{ t.side === 'server' ? '\u2699' : '\u2318' }} {{ t.name }}</span>\n @if (t.status === 'running') { <span class=\"muted\">\u2026</span> }\n @if (t.status === 'ok' && t.summary) { <span class=\"muted\">\u00B7 {{ t.summary }}</span> }\n @if (t.status === 'error') { <span class=\"muted\">\u00B7 failed</span> }\n @if (t.status === 'awaiting-approval') { <span class=\"muted\">\u00B7 awaiting approval</span> }\n @if (t.status === 'declined') { <span class=\"muted\">\u00B7 declined</span> }\n </div>\n }\n </div>\n }\n @if (m.text) {\n @if (m.role === 'assistant') {\n <div class=\"bubble-text md-body\" [innerHTML]=\"m.text | maAiMarkdown\"></div>\n } @else {\n <div class=\"bubble-text\">{{ m.text }}</div>\n }\n }\n @if (m.pending && !m.text) {\n <div class=\"thinking\"><span class=\"spinner-small\"></span> Thinking\u2026</div>\n }\n </div>\n }\n </div>\n\n <!-- Pending approval card -->\n @if (pendingApproval(); as p) {\n <div class=\"approval-card\">\n <div class=\"approval-card-head\">\n <strong>Approve action?</strong>\n <code class=\"tool-name\">{{ p.name }}</code>\n </div>\n @if (p.args && (p.args | json) !== '{}') {\n <pre class=\"approval-args\">{{ p.args | json }}</pre>\n }\n <div class=\"approval-actions\">\n <button class=\"btn primary\" (click)=\"approve()\">Approve</button>\n <button class=\"btn\" (click)=\"alwaysApprove()\">Always</button>\n <button class=\"btn danger\" (click)=\"decline()\">Decline</button>\n </div>\n </div>\n }\n\n <!-- Composer -->\n <div class=\"composer\">\n @if (lastError()) {\n <div class=\"composer-error\">{{ lastError() }}</div>\n }\n <div class=\"composer-row\">\n <textarea #textArea\n rows=\"1\"\n placeholder=\"Ask the AI\u2026\"\n [value]=\"draft()\"\n (input)=\"onTextareaInput($event)\"\n (keydown)=\"onTextareaKey($event)\"\n [disabled]=\"streaming()\"></textarea>\n @if (streaming()) {\n <button class=\"send-btn cancel\" (click)=\"cancel()\" title=\"Cancel\" aria-label=\"Cancel\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\"/></svg>\n </button>\n } @else {\n <button class=\"send-btn\" (click)=\"send()\" [disabled]=\"!draft().trim()\" title=\"Send\" aria-label=\"Send\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/></svg>\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{--primary: #c4b5fd;--primary-strong: #a78bfa;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-bubble-user: #312e81;--bg-bubble-assistant: #232336;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #7c3aed;--primary-strong: #6d28d9;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f3ff;--bg-hover: #ede9fe;--bg-bubble-user: #ede9fe;--bg-bubble-assistant: #f5f5f5;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.ai-panel{position:fixed;top:0;right:0;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1040;transform:translate(100%);transition:transform .3s cubic-bezier(.16,1,.3,1);will-change:transform,width;pointer-events:none}.ai-panel.open{transform:translate(0);pointer-events:auto}.ai-resize-handle{position:absolute;top:0;left:-4px;width:8px;height:100%;cursor:ew-resize;z-index:1;background:transparent}.ai-resize-handle:hover{background:linear-gradient(to right,transparent,rgba(124,58,237,.25),transparent)}.header-actions{display:flex;align-items:center;gap:4px}.icon-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.icon-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.panel-content{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}.quick-prompts{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-top:8px}.quick-prompt{background:var(--bg-secondary);border:1px solid var(--border-color);color:var(--text-secondary);padding:6px 10px;border-radius:14px;font-size:12px;cursor:pointer;transition:background .15s,color .15s}.quick-prompt:hover{background:var(--bg-hover);color:var(--text-primary)}.bubble{max-width:90%;padding:10px 12px;border-radius:10px;font-size:13.5px;line-height:1.5;word-wrap:break-word;white-space:pre-wrap}.bubble.user{align-self:flex-end;background:var(--bg-bubble-user);color:var(--text-primary)}.bubble.assistant{align-self:flex-start;background:var(--bg-bubble-assistant);color:var(--text-primary)}.bubble-text{display:block}.thinking{display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12.5px}.spinner-small{width:12px;height:12px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}.tool-strip{display:flex;flex-direction:column;gap:4px;margin-bottom:6px}.tool-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:12px;background:var(--bg-hover);font-size:11.5px;color:var(--text-secondary);border:1px solid var(--border-color);align-self:flex-start}.tool-chip .dot{width:6px;height:6px;border-radius:50%;background:var(--text-muted)}.tool-chip.running .dot{background:var(--primary);animation:pulse 1s ease-in-out infinite}.tool-chip.ok .dot{background:var(--success)}.tool-chip.err .dot{background:var(--error)}.tool-chip.await .dot{background:#f59e0b}.tool-chip.declined .dot{background:var(--text-muted)}.tool-chip .tool-name{font-weight:600;color:var(--text-primary)}.tool-chip .muted{color:var(--text-muted)}@keyframes pulse{50%{opacity:.4}}.approval-card{flex-shrink:0;margin:8px 14px;padding:12px 14px;border:1px solid var(--primary);border-radius:10px;background:var(--bg-secondary)}.approval-card-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;color:var(--text-primary)}.approval-card-head code{background:var(--bg-hover);padding:2px 6px;border-radius:6px;font-size:12px;color:var(--primary)}.approval-args{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:6px;padding:8px;font-size:11.5px;max-height:100px;overflow:auto;margin:0 0 8px;color:var(--text-secondary)}.approval-actions{display:flex;gap:6px;flex-wrap:wrap}.btn{flex:1;padding:6px 10px;font-size:12.5px;font-weight:600;border-radius:6px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);cursor:pointer;transition:background .15s,border-color .15s}.btn:hover{background:var(--bg-hover)}.btn.primary{background:var(--primary);color:#fff;border-color:var(--primary)}.btn.primary:hover{background:var(--primary-strong)}.btn.danger{color:var(--error)}.btn.danger:hover{background:#ef53501a}.composer{flex-shrink:0;border-top:1px solid var(--border-color);padding:10px 12px;background:var(--bg-secondary)}.composer-error{color:var(--error);font-size:12px;margin-bottom:6px}.composer-row{display:flex;align-items:flex-end;gap:8px}.composer textarea{flex:1;resize:none;padding:8px 10px;border-radius:8px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);font-size:13px;font-family:inherit;outline:none;min-height:36px;max-height:160px;line-height:1.4}.composer textarea:focus{border-color:var(--primary)}.composer textarea:disabled{opacity:.6}.send-btn{width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;transition:background .15s}.send-btn:hover:not(:disabled){background:var(--primary-strong)}.send-btn:disabled{opacity:.5;cursor:not-allowed}.send-btn.cancel{background:var(--error)}.send-btn.cancel:hover{filter:brightness(1.1)}.md-body{font-size:13.5px;line-height:1.55}.md-body .md-p{margin:0 0 8px}.md-body .md-p:last-child{margin-bottom:0}.md-body .md-h1,.md-body .md-h2,.md-body .md-h3,.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{margin:12px 0 6px;font-weight:700;color:var(--text-primary);line-height:1.3}.md-body .md-h1{font-size:17px}.md-body .md-h2{font-size:15.5px}.md-body .md-h3{font-size:14.5px}.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{font-size:13.5px}.md-body strong{font-weight:700}.md-body em{font-style:italic}.md-body .md-link{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.md-body .md-link:hover{color:var(--primary-strong)}.md-body .md-list{margin:0 0 8px;padding-left:22px}.md-body .md-list li{margin:2px 0}.md-body .md-list li::marker{color:var(--text-muted)}.md-body .md-quote{margin:0 0 8px;padding:6px 10px;border-left:3px solid var(--primary);background:var(--bg-hover);color:var(--text-secondary);border-radius:0 6px 6px 0}.md-body .md-code{background:var(--bg-hover);border:1px solid var(--border-color);border-radius:4px;padding:1px 5px;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--primary)}.md-body .md-pre{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:8px;padding:10px 12px;margin:0 0 8px;overflow-x:auto;font-size:12px;line-height:1.5}.md-body .md-pre code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--text-primary);background:none;border:none;padding:0}.md-body .md-table-wrap{margin:0 0 8px;overflow-x:auto;border:1px solid var(--border-color);border-radius:8px}.md-body .md-table{border-collapse:collapse;width:100%;font-size:12.5px}.md-body .md-table th,.md-body .md-table td{padding:6px 10px;border-bottom:1px solid var(--border-color);vertical-align:top;text-align:left}.md-body .md-table th{background:var(--bg-secondary);color:var(--text-primary);font-weight:600}.md-body .md-table tr:last-child td{border-bottom:none}.md-body .md-table tbody tr:hover{background:var(--bg-hover)}\n"], dependencies: [{ kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: MaAiMarkdownPipe, name: "maAiMarkdown" }] });
|
|
2259
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: MaAiChatPanelComponent, isStandalone: true, selector: "ma-ai-chat-panel", host: { properties: { "class": "this.themeClass" } }, viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }, { propertyName: "textArea", first: true, predicate: ["textArea"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"ai-panel\" [class.open]=\"isOpen()\" [style.width.px]=\"width()\">\n\n <!-- Resize handle (left edge) -->\n <div class=\"ai-resize-handle\"\n (pointerdown)=\"startResize($event)\"\n (pointermove)=\"onResizeMove($event)\"\n (pointerup)=\"endResize($event)\"\n (pointercancel)=\"endResize($event)\"\n aria-label=\"Resize panel\"\n title=\"Drag to resize\"></div>\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n <path d=\"M19 16 L19.8 18.2 L22 19 L19.8 19.8 L19 22 L18.2 19.8 L16 19 L18.2 18.2 Z\"/>\n </svg>\n <h3>AI Assistant</h3>\n </div>\n <div class=\"header-actions\">\n <button class=\"icon-btn\" (click)=\"newConversation()\" title=\"New conversation\" aria-label=\"New conversation\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 1 1-3-6.7L21 8\"/><path d=\"M21 3v5h-5\"/>\n </svg>\n </button>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Conversation -->\n <div class=\"panel-content\" #scrollContainer>\n @if (!hasMessages()) {\n <div class=\"empty-state\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"36\" height=\"36\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n </svg>\n <p>Ask anything about your account, approvals, notifications, or how to use the app.</p>\n <div class=\"quick-prompts\">\n @for (q of quickPrompts(); track q) {\n <button class=\"quick-prompt\" (click)=\"draft.set(q)\">{{ q }}</button>\n }\n </div>\n </div>\n }\n\n @for (m of messages(); track m.id) {\n <div class=\"bubble\" [class.user]=\"m.role === 'user'\" [class.assistant]=\"m.role === 'assistant'\">\n @if (m.role === 'assistant' && m.toolEvents && m.toolEvents.length > 0) {\n <div class=\"tool-strip\">\n @for (t of m.toolEvents; track t.id) {\n <div class=\"tool-chip\" [class.ok]=\"t.status === 'ok'\" [class.err]=\"t.status === 'error'\"\n [class.running]=\"t.status === 'running'\" [class.await]=\"t.status === 'awaiting-approval'\"\n [class.declined]=\"t.status === 'declined'\"\n [title]=\"t.summary ?? ''\">\n <span class=\"dot\"></span>\n <span class=\"tool-name\">{{ t.side === 'server' ? '\u2699' : '\u2318' }} {{ t.name }}</span>\n @if (t.status === 'running') { <span class=\"muted\">\u2026</span> }\n @if (t.status === 'ok' && t.summary) { <span class=\"muted\">\u00B7 {{ t.summary }}</span> }\n @if (t.status === 'error') { <span class=\"muted\">\u00B7 failed</span> }\n @if (t.status === 'awaiting-approval') { <span class=\"muted\">\u00B7 awaiting approval</span> }\n @if (t.status === 'declined') { <span class=\"muted\">\u00B7 declined</span> }\n </div>\n }\n </div>\n }\n @if (m.text) {\n @if (m.role === 'assistant') {\n <div class=\"bubble-text md-body\" [innerHTML]=\"m.text | maAiMarkdown\"></div>\n } @else {\n <div class=\"bubble-text\">{{ m.text }}</div>\n }\n }\n @if (m.pending && !m.text) {\n <div class=\"thinking\"><span class=\"spinner-small\"></span> Thinking\u2026</div>\n }\n </div>\n }\n </div>\n\n <!-- Pending approval card -->\n @if (pendingApproval(); as p) {\n <div class=\"approval-card\">\n <div class=\"approval-card-head\">\n <strong>Approve action?</strong>\n <code class=\"tool-name\">{{ p.name }}</code>\n </div>\n @if (p.args && (p.args | json) !== '{}') {\n <pre class=\"approval-args\">{{ p.args | json }}</pre>\n }\n <div class=\"approval-actions\">\n <button class=\"btn primary\" (click)=\"approve()\">Approve</button>\n <button class=\"btn\" (click)=\"alwaysApprove()\">Always</button>\n <button class=\"btn danger\" (click)=\"decline()\">Decline</button>\n </div>\n </div>\n }\n\n <!-- Composer -->\n <div class=\"composer\">\n @if (lastError()) {\n <div class=\"composer-error\">{{ lastError() }}</div>\n }\n <div class=\"composer-row\">\n <textarea #textArea\n rows=\"1\"\n placeholder=\"Ask the AI\u2026\"\n [value]=\"draft()\"\n (input)=\"onTextareaInput($event)\"\n (keydown)=\"onTextareaKey($event)\"\n [disabled]=\"streaming()\"></textarea>\n @if (streaming()) {\n <button class=\"send-btn cancel\" (click)=\"cancel()\" title=\"Cancel\" aria-label=\"Cancel\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\"/></svg>\n </button>\n } @else {\n <button class=\"send-btn\" (click)=\"send()\" [disabled]=\"!draft().trim()\" title=\"Send\" aria-label=\"Send\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/></svg>\n </button>\n }\n </div>\n </div>\n\n <!-- Footer with hints (lifts the composer off the absolute bottom of the viewport) -->\n <div class=\"ai-footer\">\n <span class=\"hint\">\n <kbd>Enter</kbd> send \u00B7 <kbd>Shift</kbd>+<kbd>Enter</kbd> new line\n </span>\n <span class=\"hint dim\">AI can make mistakes — verify important info</span>\n </div>\n</div>\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{--primary: #c4b5fd;--primary-strong: #a78bfa;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-bubble-user: #312e81;--bg-bubble-assistant: #232336;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #7c3aed;--primary-strong: #6d28d9;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f3ff;--bg-hover: #ede9fe;--bg-bubble-user: #ede9fe;--bg-bubble-assistant: #f5f5f5;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.ai-panel{position:fixed;top:0;right:0;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1040;transform:translate(100%);transition:transform .3s cubic-bezier(.16,1,.3,1);will-change:transform,width;pointer-events:none}.ai-panel.open{transform:translate(0);pointer-events:auto}.ai-resize-handle{position:absolute;top:0;left:-4px;width:8px;height:100%;cursor:ew-resize;z-index:1;background:transparent}.ai-resize-handle:hover{background:linear-gradient(to right,transparent,rgba(124,58,237,.25),transparent)}.header-actions{display:flex;align-items:center;gap:4px}.icon-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.icon-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.panel-content{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}.quick-prompts{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-top:8px}.quick-prompt{background:var(--bg-secondary);border:1px solid var(--border-color);color:var(--text-secondary);padding:6px 10px;border-radius:14px;font-size:12px;cursor:pointer;transition:background .15s,color .15s}.quick-prompt:hover{background:var(--bg-hover);color:var(--text-primary)}.bubble{max-width:90%;padding:10px 12px;border-radius:10px;font-size:13.5px;line-height:1.5;word-wrap:break-word;white-space:pre-wrap}.bubble.user{align-self:flex-end;background:var(--bg-bubble-user);color:var(--text-primary)}.bubble.assistant{align-self:flex-start;background:var(--bg-bubble-assistant);color:var(--text-primary)}.bubble-text{display:block}.thinking{display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12.5px}.spinner-small{width:12px;height:12px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}.tool-strip{display:flex;flex-direction:column;gap:4px;margin-bottom:6px}.tool-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:12px;background:var(--bg-hover);font-size:11.5px;color:var(--text-secondary);border:1px solid var(--border-color);align-self:flex-start}.tool-chip .dot{width:6px;height:6px;border-radius:50%;background:var(--text-muted)}.tool-chip.running .dot{background:var(--primary);animation:pulse 1s ease-in-out infinite}.tool-chip.ok .dot{background:var(--success)}.tool-chip.err .dot{background:var(--error)}.tool-chip.await .dot{background:#f59e0b}.tool-chip.declined .dot{background:var(--text-muted)}.tool-chip .tool-name{font-weight:600;color:var(--text-primary)}.tool-chip .muted{color:var(--text-muted)}@keyframes pulse{50%{opacity:.4}}.approval-card{flex-shrink:0;margin:8px 14px;padding:12px 14px;border:1px solid var(--primary);border-radius:10px;background:var(--bg-secondary)}.approval-card-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;color:var(--text-primary)}.approval-card-head code{background:var(--bg-hover);padding:2px 6px;border-radius:6px;font-size:12px;color:var(--primary)}.approval-args{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:6px;padding:8px;font-size:11.5px;max-height:100px;overflow:auto;margin:0 0 8px;color:var(--text-secondary)}.approval-actions{display:flex;gap:6px;flex-wrap:wrap}.btn{flex:1;padding:6px 10px;font-size:12.5px;font-weight:600;border-radius:6px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);cursor:pointer;transition:background .15s,border-color .15s}.btn:hover{background:var(--bg-hover)}.btn.primary{background:var(--primary);color:#fff;border-color:var(--primary)}.btn.primary:hover{background:var(--primary-strong)}.btn.danger{color:var(--error)}.btn.danger:hover{background:#ef53501a}.composer{flex-shrink:0;border-top:1px solid var(--border-color);padding:10px 12px;background:var(--bg-secondary)}.composer-error{color:var(--error);font-size:12px;margin-bottom:6px}.composer-row{display:flex;align-items:flex-end;gap:8px}.composer textarea{flex:1;resize:none;padding:8px 10px;border-radius:8px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);font-size:13px;font-family:inherit;outline:none;min-height:36px;max-height:160px;line-height:1.4}.composer textarea:focus{border-color:var(--primary)}.composer textarea:disabled{opacity:.6}.send-btn{width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;transition:background .15s}.send-btn:hover:not(:disabled){background:var(--primary-strong)}.send-btn:disabled{opacity:.5;cursor:not-allowed}.send-btn.cancel{background:var(--error)}.send-btn.cancel:hover{filter:brightness(1.1)}.ai-footer{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 12px 10px;background:var(--bg-secondary);border-top:1px solid var(--border-color);font-size:11px;color:var(--text-secondary);line-height:1.4;text-align:center}.ai-footer .hint{display:inline-block}.ai-footer .hint.dim{color:var(--text-muted);font-size:10.5px}.ai-footer kbd{display:inline-block;padding:0 5px;margin:0 1px;border:1px solid var(--border-color);border-bottom-width:2px;border-radius:4px;background:var(--bg-primary);color:var(--text-primary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;line-height:1.4}.md-body{font-size:13.5px;line-height:1.55}.md-body .md-p{margin:0 0 8px}.md-body .md-p:last-child{margin-bottom:0}.md-body .md-h1,.md-body .md-h2,.md-body .md-h3,.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{margin:12px 0 6px;font-weight:700;color:var(--text-primary);line-height:1.3}.md-body .md-h1{font-size:17px}.md-body .md-h2{font-size:15.5px}.md-body .md-h3{font-size:14.5px}.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{font-size:13.5px}.md-body strong{font-weight:700}.md-body em{font-style:italic}.md-body .md-link{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.md-body .md-link:hover{color:var(--primary-strong)}.md-body .md-list{margin:0 0 8px;padding-left:22px}.md-body .md-list li{margin:2px 0}.md-body .md-list li::marker{color:var(--text-muted)}.md-body .md-quote{margin:0 0 8px;padding:6px 10px;border-left:3px solid var(--primary);background:var(--bg-hover);color:var(--text-secondary);border-radius:0 6px 6px 0}.md-body .md-code{background:var(--bg-hover);border:1px solid var(--border-color);border-radius:4px;padding:1px 5px;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--primary)}.md-body .md-pre{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:8px;padding:10px 12px;margin:0 0 8px;overflow-x:auto;font-size:12px;line-height:1.5}.md-body .md-pre code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--text-primary);background:none;border:none;padding:0}.md-body .md-table-wrap{margin:0 0 8px;overflow-x:auto;border:1px solid var(--border-color);border-radius:8px}.md-body .md-table{border-collapse:collapse;width:100%;font-size:12.5px}.md-body .md-table th,.md-body .md-table td{padding:6px 10px;border-bottom:1px solid var(--border-color);vertical-align:top;text-align:left}.md-body .md-table th{background:var(--bg-secondary);color:var(--text-primary);font-weight:600}.md-body .md-table tr:last-child td{border-bottom:none}.md-body .md-table tbody tr:hover{background:var(--bg-hover)}\n"], dependencies: [{ kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: MaAiMarkdownPipe, name: "maAiMarkdown" }] });
|
|
2174
2260
|
}
|
|
2175
2261
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: MaAiChatPanelComponent, decorators: [{
|
|
2176
2262
|
type: Component,
|
|
2177
|
-
args: [{ selector: 'ma-ai-chat-panel', imports: [JsonPipe, MaAiMarkdownPipe], template: "<div class=\"ai-panel\" [class.open]=\"isOpen()\" [style.width.px]=\"width()\">\n\n <!-- Resize handle (left edge) -->\n <div class=\"ai-resize-handle\"\n (pointerdown)=\"startResize($event)\"\n (pointermove)=\"onResizeMove($event)\"\n (pointerup)=\"endResize($event)\"\n (pointercancel)=\"endResize($event)\"\n aria-label=\"Resize panel\"\n title=\"Drag to resize\"></div>\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n <path d=\"M19 16 L19.8 18.2 L22 19 L19.8 19.8 L19 22 L18.2 19.8 L16 19 L18.2 18.2 Z\"/>\n </svg>\n <h3>AI Assistant</h3>\n </div>\n <div class=\"header-actions\">\n <button class=\"icon-btn\" (click)=\"newConversation()\" title=\"New conversation\" aria-label=\"New conversation\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 1 1-3-6.7L21 8\"/><path d=\"M21 3v5h-5\"/>\n </svg>\n </button>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Conversation -->\n <div class=\"panel-content\" #scrollContainer>\n @if (!hasMessages()) {\n <div class=\"empty-state\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"36\" height=\"36\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n </svg>\n <p>Ask anything about your account, approvals, notifications, or how to use the app.</p>\n <div class=\"quick-prompts\">\n @for (q of quickPrompts(); track q) {\n <button class=\"quick-prompt\" (click)=\"draft.set(q)\">{{ q }}</button>\n }\n </div>\n </div>\n }\n\n @for (m of messages(); track m.id) {\n <div class=\"bubble\" [class.user]=\"m.role === 'user'\" [class.assistant]=\"m.role === 'assistant'\">\n @if (m.role === 'assistant' && m.toolEvents && m.toolEvents.length > 0) {\n <div class=\"tool-strip\">\n @for (t of m.toolEvents; track t.id) {\n <div class=\"tool-chip\" [class.ok]=\"t.status === 'ok'\" [class.err]=\"t.status === 'error'\"\n [class.running]=\"t.status === 'running'\" [class.await]=\"t.status === 'awaiting-approval'\"\n [class.declined]=\"t.status === 'declined'\"\n [title]=\"t.summary ?? ''\">\n <span class=\"dot\"></span>\n <span class=\"tool-name\">{{ t.side === 'server' ? '\u2699' : '\u2318' }} {{ t.name }}</span>\n @if (t.status === 'running') { <span class=\"muted\">\u2026</span> }\n @if (t.status === 'ok' && t.summary) { <span class=\"muted\">\u00B7 {{ t.summary }}</span> }\n @if (t.status === 'error') { <span class=\"muted\">\u00B7 failed</span> }\n @if (t.status === 'awaiting-approval') { <span class=\"muted\">\u00B7 awaiting approval</span> }\n @if (t.status === 'declined') { <span class=\"muted\">\u00B7 declined</span> }\n </div>\n }\n </div>\n }\n @if (m.text) {\n @if (m.role === 'assistant') {\n <div class=\"bubble-text md-body\" [innerHTML]=\"m.text | maAiMarkdown\"></div>\n } @else {\n <div class=\"bubble-text\">{{ m.text }}</div>\n }\n }\n @if (m.pending && !m.text) {\n <div class=\"thinking\"><span class=\"spinner-small\"></span> Thinking\u2026</div>\n }\n </div>\n }\n </div>\n\n <!-- Pending approval card -->\n @if (pendingApproval(); as p) {\n <div class=\"approval-card\">\n <div class=\"approval-card-head\">\n <strong>Approve action?</strong>\n <code class=\"tool-name\">{{ p.name }}</code>\n </div>\n @if (p.args && (p.args | json) !== '{}') {\n <pre class=\"approval-args\">{{ p.args | json }}</pre>\n }\n <div class=\"approval-actions\">\n <button class=\"btn primary\" (click)=\"approve()\">Approve</button>\n <button class=\"btn\" (click)=\"alwaysApprove()\">Always</button>\n <button class=\"btn danger\" (click)=\"decline()\">Decline</button>\n </div>\n </div>\n }\n\n <!-- Composer -->\n <div class=\"composer\">\n @if (lastError()) {\n <div class=\"composer-error\">{{ lastError() }}</div>\n }\n <div class=\"composer-row\">\n <textarea #textArea\n rows=\"1\"\n placeholder=\"Ask the AI\u2026\"\n [value]=\"draft()\"\n (input)=\"onTextareaInput($event)\"\n (keydown)=\"onTextareaKey($event)\"\n [disabled]=\"streaming()\"></textarea>\n @if (streaming()) {\n <button class=\"send-btn cancel\" (click)=\"cancel()\" title=\"Cancel\" aria-label=\"Cancel\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\"/></svg>\n </button>\n } @else {\n <button class=\"send-btn\" (click)=\"send()\" [disabled]=\"!draft().trim()\" title=\"Send\" aria-label=\"Send\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/></svg>\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{--primary: #c4b5fd;--primary-strong: #a78bfa;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-bubble-user: #312e81;--bg-bubble-assistant: #232336;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #7c3aed;--primary-strong: #6d28d9;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f3ff;--bg-hover: #ede9fe;--bg-bubble-user: #ede9fe;--bg-bubble-assistant: #f5f5f5;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.ai-panel{position:fixed;top:0;right:0;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1040;transform:translate(100%);transition:transform .3s cubic-bezier(.16,1,.3,1);will-change:transform,width;pointer-events:none}.ai-panel.open{transform:translate(0);pointer-events:auto}.ai-resize-handle{position:absolute;top:0;left:-4px;width:8px;height:100%;cursor:ew-resize;z-index:1;background:transparent}.ai-resize-handle:hover{background:linear-gradient(to right,transparent,rgba(124,58,237,.25),transparent)}.header-actions{display:flex;align-items:center;gap:4px}.icon-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.icon-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.panel-content{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}.quick-prompts{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-top:8px}.quick-prompt{background:var(--bg-secondary);border:1px solid var(--border-color);color:var(--text-secondary);padding:6px 10px;border-radius:14px;font-size:12px;cursor:pointer;transition:background .15s,color .15s}.quick-prompt:hover{background:var(--bg-hover);color:var(--text-primary)}.bubble{max-width:90%;padding:10px 12px;border-radius:10px;font-size:13.5px;line-height:1.5;word-wrap:break-word;white-space:pre-wrap}.bubble.user{align-self:flex-end;background:var(--bg-bubble-user);color:var(--text-primary)}.bubble.assistant{align-self:flex-start;background:var(--bg-bubble-assistant);color:var(--text-primary)}.bubble-text{display:block}.thinking{display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12.5px}.spinner-small{width:12px;height:12px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}.tool-strip{display:flex;flex-direction:column;gap:4px;margin-bottom:6px}.tool-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:12px;background:var(--bg-hover);font-size:11.5px;color:var(--text-secondary);border:1px solid var(--border-color);align-self:flex-start}.tool-chip .dot{width:6px;height:6px;border-radius:50%;background:var(--text-muted)}.tool-chip.running .dot{background:var(--primary);animation:pulse 1s ease-in-out infinite}.tool-chip.ok .dot{background:var(--success)}.tool-chip.err .dot{background:var(--error)}.tool-chip.await .dot{background:#f59e0b}.tool-chip.declined .dot{background:var(--text-muted)}.tool-chip .tool-name{font-weight:600;color:var(--text-primary)}.tool-chip .muted{color:var(--text-muted)}@keyframes pulse{50%{opacity:.4}}.approval-card{flex-shrink:0;margin:8px 14px;padding:12px 14px;border:1px solid var(--primary);border-radius:10px;background:var(--bg-secondary)}.approval-card-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;color:var(--text-primary)}.approval-card-head code{background:var(--bg-hover);padding:2px 6px;border-radius:6px;font-size:12px;color:var(--primary)}.approval-args{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:6px;padding:8px;font-size:11.5px;max-height:100px;overflow:auto;margin:0 0 8px;color:var(--text-secondary)}.approval-actions{display:flex;gap:6px;flex-wrap:wrap}.btn{flex:1;padding:6px 10px;font-size:12.5px;font-weight:600;border-radius:6px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);cursor:pointer;transition:background .15s,border-color .15s}.btn:hover{background:var(--bg-hover)}.btn.primary{background:var(--primary);color:#fff;border-color:var(--primary)}.btn.primary:hover{background:var(--primary-strong)}.btn.danger{color:var(--error)}.btn.danger:hover{background:#ef53501a}.composer{flex-shrink:0;border-top:1px solid var(--border-color);padding:10px 12px;background:var(--bg-secondary)}.composer-error{color:var(--error);font-size:12px;margin-bottom:6px}.composer-row{display:flex;align-items:flex-end;gap:8px}.composer textarea{flex:1;resize:none;padding:8px 10px;border-radius:8px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);font-size:13px;font-family:inherit;outline:none;min-height:36px;max-height:160px;line-height:1.4}.composer textarea:focus{border-color:var(--primary)}.composer textarea:disabled{opacity:.6}.send-btn{width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;transition:background .15s}.send-btn:hover:not(:disabled){background:var(--primary-strong)}.send-btn:disabled{opacity:.5;cursor:not-allowed}.send-btn.cancel{background:var(--error)}.send-btn.cancel:hover{filter:brightness(1.1)}.md-body{font-size:13.5px;line-height:1.55}.md-body .md-p{margin:0 0 8px}.md-body .md-p:last-child{margin-bottom:0}.md-body .md-h1,.md-body .md-h2,.md-body .md-h3,.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{margin:12px 0 6px;font-weight:700;color:var(--text-primary);line-height:1.3}.md-body .md-h1{font-size:17px}.md-body .md-h2{font-size:15.5px}.md-body .md-h3{font-size:14.5px}.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{font-size:13.5px}.md-body strong{font-weight:700}.md-body em{font-style:italic}.md-body .md-link{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.md-body .md-link:hover{color:var(--primary-strong)}.md-body .md-list{margin:0 0 8px;padding-left:22px}.md-body .md-list li{margin:2px 0}.md-body .md-list li::marker{color:var(--text-muted)}.md-body .md-quote{margin:0 0 8px;padding:6px 10px;border-left:3px solid var(--primary);background:var(--bg-hover);color:var(--text-secondary);border-radius:0 6px 6px 0}.md-body .md-code{background:var(--bg-hover);border:1px solid var(--border-color);border-radius:4px;padding:1px 5px;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--primary)}.md-body .md-pre{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:8px;padding:10px 12px;margin:0 0 8px;overflow-x:auto;font-size:12px;line-height:1.5}.md-body .md-pre code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--text-primary);background:none;border:none;padding:0}.md-body .md-table-wrap{margin:0 0 8px;overflow-x:auto;border:1px solid var(--border-color);border-radius:8px}.md-body .md-table{border-collapse:collapse;width:100%;font-size:12.5px}.md-body .md-table th,.md-body .md-table td{padding:6px 10px;border-bottom:1px solid var(--border-color);vertical-align:top;text-align:left}.md-body .md-table th{background:var(--bg-secondary);color:var(--text-primary);font-weight:600}.md-body .md-table tr:last-child td{border-bottom:none}.md-body .md-table tbody tr:hover{background:var(--bg-hover)}\n"] }]
|
|
2263
|
+
args: [{ selector: 'ma-ai-chat-panel', imports: [JsonPipe, MaAiMarkdownPipe], template: "<div class=\"ai-panel\" [class.open]=\"isOpen()\" [style.width.px]=\"width()\">\n\n <!-- Resize handle (left edge) -->\n <div class=\"ai-resize-handle\"\n (pointerdown)=\"startResize($event)\"\n (pointermove)=\"onResizeMove($event)\"\n (pointerup)=\"endResize($event)\"\n (pointercancel)=\"endResize($event)\"\n aria-label=\"Resize panel\"\n title=\"Drag to resize\"></div>\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n <path d=\"M19 16 L19.8 18.2 L22 19 L19.8 19.8 L19 22 L18.2 19.8 L16 19 L18.2 18.2 Z\"/>\n </svg>\n <h3>AI Assistant</h3>\n </div>\n <div class=\"header-actions\">\n <button class=\"icon-btn\" (click)=\"newConversation()\" title=\"New conversation\" aria-label=\"New conversation\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12a9 9 0 1 1-3-6.7L21 8\"/><path d=\"M21 3v5h-5\"/>\n </svg>\n </button>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Conversation -->\n <div class=\"panel-content\" #scrollContainer>\n @if (!hasMessages()) {\n <div class=\"empty-state\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"36\" height=\"36\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" opacity=\"0.5\">\n <path d=\"M12 2 L13.5 7.5 L19 9 L13.5 10.5 L12 16 L10.5 10.5 L5 9 L10.5 7.5 Z\"/>\n </svg>\n <p>Ask anything about your account, approvals, notifications, or how to use the app.</p>\n <div class=\"quick-prompts\">\n @for (q of quickPrompts(); track q) {\n <button class=\"quick-prompt\" (click)=\"draft.set(q)\">{{ q }}</button>\n }\n </div>\n </div>\n }\n\n @for (m of messages(); track m.id) {\n <div class=\"bubble\" [class.user]=\"m.role === 'user'\" [class.assistant]=\"m.role === 'assistant'\">\n @if (m.role === 'assistant' && m.toolEvents && m.toolEvents.length > 0) {\n <div class=\"tool-strip\">\n @for (t of m.toolEvents; track t.id) {\n <div class=\"tool-chip\" [class.ok]=\"t.status === 'ok'\" [class.err]=\"t.status === 'error'\"\n [class.running]=\"t.status === 'running'\" [class.await]=\"t.status === 'awaiting-approval'\"\n [class.declined]=\"t.status === 'declined'\"\n [title]=\"t.summary ?? ''\">\n <span class=\"dot\"></span>\n <span class=\"tool-name\">{{ t.side === 'server' ? '\u2699' : '\u2318' }} {{ t.name }}</span>\n @if (t.status === 'running') { <span class=\"muted\">\u2026</span> }\n @if (t.status === 'ok' && t.summary) { <span class=\"muted\">\u00B7 {{ t.summary }}</span> }\n @if (t.status === 'error') { <span class=\"muted\">\u00B7 failed</span> }\n @if (t.status === 'awaiting-approval') { <span class=\"muted\">\u00B7 awaiting approval</span> }\n @if (t.status === 'declined') { <span class=\"muted\">\u00B7 declined</span> }\n </div>\n }\n </div>\n }\n @if (m.text) {\n @if (m.role === 'assistant') {\n <div class=\"bubble-text md-body\" [innerHTML]=\"m.text | maAiMarkdown\"></div>\n } @else {\n <div class=\"bubble-text\">{{ m.text }}</div>\n }\n }\n @if (m.pending && !m.text) {\n <div class=\"thinking\"><span class=\"spinner-small\"></span> Thinking\u2026</div>\n }\n </div>\n }\n </div>\n\n <!-- Pending approval card -->\n @if (pendingApproval(); as p) {\n <div class=\"approval-card\">\n <div class=\"approval-card-head\">\n <strong>Approve action?</strong>\n <code class=\"tool-name\">{{ p.name }}</code>\n </div>\n @if (p.args && (p.args | json) !== '{}') {\n <pre class=\"approval-args\">{{ p.args | json }}</pre>\n }\n <div class=\"approval-actions\">\n <button class=\"btn primary\" (click)=\"approve()\">Approve</button>\n <button class=\"btn\" (click)=\"alwaysApprove()\">Always</button>\n <button class=\"btn danger\" (click)=\"decline()\">Decline</button>\n </div>\n </div>\n }\n\n <!-- Composer -->\n <div class=\"composer\">\n @if (lastError()) {\n <div class=\"composer-error\">{{ lastError() }}</div>\n }\n <div class=\"composer-row\">\n <textarea #textArea\n rows=\"1\"\n placeholder=\"Ask the AI\u2026\"\n [value]=\"draft()\"\n (input)=\"onTextareaInput($event)\"\n (keydown)=\"onTextareaKey($event)\"\n [disabled]=\"streaming()\"></textarea>\n @if (streaming()) {\n <button class=\"send-btn cancel\" (click)=\"cancel()\" title=\"Cancel\" aria-label=\"Cancel\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\"/></svg>\n </button>\n } @else {\n <button class=\"send-btn\" (click)=\"send()\" [disabled]=\"!draft().trim()\" title=\"Send\" aria-label=\"Send\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/></svg>\n </button>\n }\n </div>\n </div>\n\n <!-- Footer with hints (lifts the composer off the absolute bottom of the viewport) -->\n <div class=\"ai-footer\">\n <span class=\"hint\">\n <kbd>Enter</kbd> send \u00B7 <kbd>Shift</kbd>+<kbd>Enter</kbd> new line\n </span>\n <span class=\"hint dim\">AI can make mistakes — verify important info</span>\n </div>\n</div>\n", styles: [".panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n", ":host{--primary: #c4b5fd;--primary-strong: #a78bfa;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-bubble-user: #312e81;--bg-bubble-assistant: #232336;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #7c3aed;--primary-strong: #6d28d9;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f3ff;--bg-hover: #ede9fe;--bg-bubble-user: #ede9fe;--bg-bubble-assistant: #f5f5f5;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.ai-panel{position:fixed;top:0;right:0;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1040;transform:translate(100%);transition:transform .3s cubic-bezier(.16,1,.3,1);will-change:transform,width;pointer-events:none}.ai-panel.open{transform:translate(0);pointer-events:auto}.ai-resize-handle{position:absolute;top:0;left:-4px;width:8px;height:100%;cursor:ew-resize;z-index:1;background:transparent}.ai-resize-handle:hover{background:linear-gradient(to right,transparent,rgba(124,58,237,.25),transparent)}.header-actions{display:flex;align-items:center;gap:4px}.icon-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.icon-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.panel-content{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}.quick-prompts{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin-top:8px}.quick-prompt{background:var(--bg-secondary);border:1px solid var(--border-color);color:var(--text-secondary);padding:6px 10px;border-radius:14px;font-size:12px;cursor:pointer;transition:background .15s,color .15s}.quick-prompt:hover{background:var(--bg-hover);color:var(--text-primary)}.bubble{max-width:90%;padding:10px 12px;border-radius:10px;font-size:13.5px;line-height:1.5;word-wrap:break-word;white-space:pre-wrap}.bubble.user{align-self:flex-end;background:var(--bg-bubble-user);color:var(--text-primary)}.bubble.assistant{align-self:flex-start;background:var(--bg-bubble-assistant);color:var(--text-primary)}.bubble-text{display:block}.thinking{display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12.5px}.spinner-small{width:12px;height:12px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}.tool-strip{display:flex;flex-direction:column;gap:4px;margin-bottom:6px}.tool-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:12px;background:var(--bg-hover);font-size:11.5px;color:var(--text-secondary);border:1px solid var(--border-color);align-self:flex-start}.tool-chip .dot{width:6px;height:6px;border-radius:50%;background:var(--text-muted)}.tool-chip.running .dot{background:var(--primary);animation:pulse 1s ease-in-out infinite}.tool-chip.ok .dot{background:var(--success)}.tool-chip.err .dot{background:var(--error)}.tool-chip.await .dot{background:#f59e0b}.tool-chip.declined .dot{background:var(--text-muted)}.tool-chip .tool-name{font-weight:600;color:var(--text-primary)}.tool-chip .muted{color:var(--text-muted)}@keyframes pulse{50%{opacity:.4}}.approval-card{flex-shrink:0;margin:8px 14px;padding:12px 14px;border:1px solid var(--primary);border-radius:10px;background:var(--bg-secondary)}.approval-card-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;color:var(--text-primary)}.approval-card-head code{background:var(--bg-hover);padding:2px 6px;border-radius:6px;font-size:12px;color:var(--primary)}.approval-args{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:6px;padding:8px;font-size:11.5px;max-height:100px;overflow:auto;margin:0 0 8px;color:var(--text-secondary)}.approval-actions{display:flex;gap:6px;flex-wrap:wrap}.btn{flex:1;padding:6px 10px;font-size:12.5px;font-weight:600;border-radius:6px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);cursor:pointer;transition:background .15s,border-color .15s}.btn:hover{background:var(--bg-hover)}.btn.primary{background:var(--primary);color:#fff;border-color:var(--primary)}.btn.primary:hover{background:var(--primary-strong)}.btn.danger{color:var(--error)}.btn.danger:hover{background:#ef53501a}.composer{flex-shrink:0;border-top:1px solid var(--border-color);padding:10px 12px;background:var(--bg-secondary)}.composer-error{color:var(--error);font-size:12px;margin-bottom:6px}.composer-row{display:flex;align-items:flex-end;gap:8px}.composer textarea{flex:1;resize:none;padding:8px 10px;border-radius:8px;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);font-size:13px;font-family:inherit;outline:none;min-height:36px;max-height:160px;line-height:1.4}.composer textarea:focus{border-color:var(--primary)}.composer textarea:disabled{opacity:.6}.send-btn{width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer;transition:background .15s}.send-btn:hover:not(:disabled){background:var(--primary-strong)}.send-btn:disabled{opacity:.5;cursor:not-allowed}.send-btn.cancel{background:var(--error)}.send-btn.cancel:hover{filter:brightness(1.1)}.ai-footer{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 12px 10px;background:var(--bg-secondary);border-top:1px solid var(--border-color);font-size:11px;color:var(--text-secondary);line-height:1.4;text-align:center}.ai-footer .hint{display:inline-block}.ai-footer .hint.dim{color:var(--text-muted);font-size:10.5px}.ai-footer kbd{display:inline-block;padding:0 5px;margin:0 1px;border:1px solid var(--border-color);border-bottom-width:2px;border-radius:4px;background:var(--bg-primary);color:var(--text-primary);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;line-height:1.4}.md-body{font-size:13.5px;line-height:1.55}.md-body .md-p{margin:0 0 8px}.md-body .md-p:last-child{margin-bottom:0}.md-body .md-h1,.md-body .md-h2,.md-body .md-h3,.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{margin:12px 0 6px;font-weight:700;color:var(--text-primary);line-height:1.3}.md-body .md-h1{font-size:17px}.md-body .md-h2{font-size:15.5px}.md-body .md-h3{font-size:14.5px}.md-body .md-h4,.md-body .md-h5,.md-body .md-h6{font-size:13.5px}.md-body strong{font-weight:700}.md-body em{font-style:italic}.md-body .md-link{color:var(--primary);text-decoration:underline;text-underline-offset:2px}.md-body .md-link:hover{color:var(--primary-strong)}.md-body .md-list{margin:0 0 8px;padding-left:22px}.md-body .md-list li{margin:2px 0}.md-body .md-list li::marker{color:var(--text-muted)}.md-body .md-quote{margin:0 0 8px;padding:6px 10px;border-left:3px solid var(--primary);background:var(--bg-hover);color:var(--text-secondary);border-radius:0 6px 6px 0}.md-body .md-code{background:var(--bg-hover);border:1px solid var(--border-color);border-radius:4px;padding:1px 5px;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--primary)}.md-body .md-pre{background:var(--bg-primary);border:1px solid var(--border-color);border-radius:8px;padding:10px 12px;margin:0 0 8px;overflow-x:auto;font-size:12px;line-height:1.5}.md-body .md-pre code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--text-primary);background:none;border:none;padding:0}.md-body .md-table-wrap{margin:0 0 8px;overflow-x:auto;border:1px solid var(--border-color);border-radius:8px}.md-body .md-table{border-collapse:collapse;width:100%;font-size:12.5px}.md-body .md-table th,.md-body .md-table td{padding:6px 10px;border-bottom:1px solid var(--border-color);vertical-align:top;text-align:left}.md-body .md-table th{background:var(--bg-secondary);color:var(--text-primary);font-weight:600}.md-body .md-table tr:last-child td{border-bottom:none}.md-body .md-table tbody tr:hover{background:var(--bg-hover)}\n"] }]
|
|
2178
2264
|
}], ctorParameters: () => [], propDecorators: { scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }], textArea: [{ type: i0.ViewChild, args: ['textArea', { isSignal: true }] }], themeClass: [{
|
|
2179
2265
|
type: HostBinding,
|
|
2180
2266
|
args: ['class']
|