@masterteam/discussion 0.0.2 → 0.0.4
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.
|
@@ -10,9 +10,12 @@ import { Card } from '@masterteam/components/card';
|
|
|
10
10
|
import { EntitiesPreview } from '@masterteam/components/entities';
|
|
11
11
|
import * as i3 from 'primeng/dialog';
|
|
12
12
|
import { DialogModule } from 'primeng/dialog';
|
|
13
|
+
import * as i4 from 'primeng/popover';
|
|
14
|
+
import { PopoverModule } from 'primeng/popover';
|
|
13
15
|
import { map, filter, Subject, firstValueFrom, debounceTime, distinctUntilChanged, switchMap, of, catchError, finalize } from 'rxjs';
|
|
14
16
|
import { HttpClient, HttpContext, HttpParams, HttpEventType, HttpHeaders } from '@angular/common/http';
|
|
15
17
|
import { REQUEST_CONTEXT } from '@masterteam/components';
|
|
18
|
+
import { Icon } from '@masterteam/icons';
|
|
16
19
|
|
|
17
20
|
const MODULE_TYPE_ALIASES = {
|
|
18
21
|
leveldata: 'Level',
|
|
@@ -482,6 +485,13 @@ class DiscussionThread {
|
|
|
482
485
|
}
|
|
483
486
|
return map;
|
|
484
487
|
}, ...(ngDevMode ? [{ debugName: "commentsById" }] : []));
|
|
488
|
+
resolvedCurrentUserId = computed(() => {
|
|
489
|
+
const readStateUserId = this.readState()?.userId?.trim();
|
|
490
|
+
if (readStateUserId) {
|
|
491
|
+
return readStateUserId;
|
|
492
|
+
}
|
|
493
|
+
return this.currentUserId().trim();
|
|
494
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedCurrentUserId" }] : []));
|
|
485
495
|
replyToComment = computed(() => {
|
|
486
496
|
const parentId = this.replyToCommentId();
|
|
487
497
|
if (parentId == null) {
|
|
@@ -498,7 +508,6 @@ class DiscussionThread {
|
|
|
498
508
|
return summary ? mapSummaryToReadState(summary) : null;
|
|
499
509
|
}, ...(ngDevMode ? [{ debugName: "readStateLike" }] : []));
|
|
500
510
|
hasUnread = computed(() => this.readStateLike()?.hasUnread ?? false, ...(ngDevMode ? [{ debugName: "hasUnread" }] : []));
|
|
501
|
-
unreadCount = computed(() => this.readStateLike()?.unreadCount ?? 0, ...(ngDevMode ? [{ debugName: "unreadCount" }] : []));
|
|
502
511
|
firstUnreadCommentId = computed(() => this.resolveFirstUnreadCommentId(this.commentsAsc(), this.readStateLike()), ...(ngDevMode ? [{ debugName: "firstUnreadCommentId" }] : []));
|
|
503
512
|
mentionPool = computed(() => dedupeMentionUsers([
|
|
504
513
|
...this.mentionableUsers(),
|
|
@@ -541,7 +550,6 @@ class DiscussionThread {
|
|
|
541
550
|
const text = this.editText();
|
|
542
551
|
return text.trim().length > 0 && text.length <= 10000;
|
|
543
552
|
}, ...(ngDevMode ? [{ debugName: "canSaveEdit" }] : []));
|
|
544
|
-
charactersLeft = computed(() => 10000 - this.composerText().length, ...(ngDevMode ? [{ debugName: "charactersLeft" }] : []));
|
|
545
553
|
visibleRevisions = computed(() => {
|
|
546
554
|
const commentId = this.selectedRevisionComment()?.id;
|
|
547
555
|
if (commentId == null) {
|
|
@@ -597,7 +605,7 @@ class DiscussionThread {
|
|
|
597
605
|
return comment.id;
|
|
598
606
|
}
|
|
599
607
|
isOwnComment(comment) {
|
|
600
|
-
const currentUserId = this.
|
|
608
|
+
const currentUserId = this.resolvedCurrentUserId().toLowerCase();
|
|
601
609
|
return !!currentUserId && comment.createdBy.toLowerCase() === currentUserId;
|
|
602
610
|
}
|
|
603
611
|
canEditComment(comment) {
|
|
@@ -840,22 +848,6 @@ class DiscussionThread {
|
|
|
840
848
|
this.markingRead.set(false);
|
|
841
849
|
}
|
|
842
850
|
}
|
|
843
|
-
async markUnread() {
|
|
844
|
-
if (this.markingRead()) {
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
this.markingRead.set(true);
|
|
848
|
-
try {
|
|
849
|
-
await firstValueFrom(this.api.clearReadState(this.resolvedModuleType(), this.resolvedRecordId(), this.requestContext()));
|
|
850
|
-
await this.refreshReadState();
|
|
851
|
-
}
|
|
852
|
-
catch (error) {
|
|
853
|
-
this.handleError(error, 'Unable to update read state.');
|
|
854
|
-
}
|
|
855
|
-
finally {
|
|
856
|
-
this.markingRead.set(false);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
851
|
onViewportScroll() {
|
|
860
852
|
const viewport = this.viewportRef()?.nativeElement;
|
|
861
853
|
if (!viewport) {
|
|
@@ -1515,7 +1507,7 @@ class DiscussionThread {
|
|
|
1515
1507
|
threadKey: buildDiscussionThreadKey(moduleType, recordId),
|
|
1516
1508
|
moduleType,
|
|
1517
1509
|
recordId,
|
|
1518
|
-
userId: this.
|
|
1510
|
+
userId: this.resolvedCurrentUserId(),
|
|
1519
1511
|
lastReadCommentId: page.lastReadCommentId,
|
|
1520
1512
|
lastReadCommentCreatedAt: page.lastReadCommentCreatedAt,
|
|
1521
1513
|
lastReadAt: page.lastReadAt,
|
|
@@ -1626,7 +1618,7 @@ class DiscussionThread {
|
|
|
1626
1618
|
return `${fileName}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1627
1619
|
}
|
|
1628
1620
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: DiscussionThread, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1629
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: DiscussionThread, isStandalone: true, selector: "mt-discussion-thread", inputs: { moduleType: { classPropertyName: "moduleType", publicName: "moduleType", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, requestContext: { classPropertyName: "requestContext", publicName: "requestContext", isSignal: true, isRequired: false, transformFunction: null }, mentionableUsers: { classPropertyName: "mentionableUsers", publicName: "mentionableUsers", isSignal: true, isRequired: false, transformFunction: null }, mentionSearchEndpoint: { classPropertyName: "mentionSearchEndpoint", publicName: "mentionSearchEndpoint", isSignal: true, isRequired: false, transformFunction: null }, mentionSearchParam: { classPropertyName: "mentionSearchParam", publicName: "mentionSearchParam", isSignal: true, isRequired: false, transformFunction: null }, mentionSearchDataPath: { classPropertyName: "mentionSearchDataPath", publicName: "mentionSearchDataPath", isSignal: true, isRequired: false, transformFunction: null }, allowAttachments: { classPropertyName: "allowAttachments", publicName: "allowAttachments", isSignal: true, isRequired: false, transformFunction: null }, uploadEndpoint: { classPropertyName: "uploadEndpoint", publicName: "uploadEndpoint", isSignal: true, isRequired: false, transformFunction: null }, attachmentDownloadEndpoint: { classPropertyName: "attachmentDownloadEndpoint", publicName: "attachmentDownloadEndpoint", isSignal: true, isRequired: false, transformFunction: null }, showParticipants: { classPropertyName: "showParticipants", publicName: "showParticipants", isSignal: true, isRequired: false, transformFunction: null }, autoMarkRead: { classPropertyName: "autoMarkRead", publicName: "autoMarkRead", isSignal: true, isRequired: false, transformFunction: null }, refreshIntervalMs: { classPropertyName: "refreshIntervalMs", publicName: "refreshIntervalMs", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored", commentCreated: "commentCreated", commentUpdated: "commentUpdated", commentDeleted: "commentDeleted", readStateChanged: "readStateChanged" }, host: { classAttribute: "block h-full min-h-0" }, viewQueries: [{ propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true, isSignal: true }, { propertyName: "composerInputRef", first: true, predicate: ["composerInput"], descendants: true, isSignal: true }, { propertyName: "attachmentInputRef", first: true, predicate: ["attachmentInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<mt-card\r\n class=\"mt-discussion-card h-full min-h-0 w-full overflow-hidden shadow-sm\"\r\n [paddingless]=\"true\"\r\n [ngClass]=\"styleClass()\"\r\n>\r\n <div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n <input\r\n #attachmentInput\r\n type=\"file\"\r\n class=\"hidden\"\r\n multiple\r\n (change)=\"onAttachmentSelected($event)\"\r\n />\r\n\r\n <header\r\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-200 px-4 py-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <h3 class=\"truncate text-sm font-semibold uppercase tracking-[0.08em]\">\r\n {{ title() }}\r\n </h3>\r\n @if (subtitle()) {\r\n <p class=\"mt-1 text-xs text-surface-500\">{{ subtitle() }}</p>\r\n }\r\n <p class=\"mt-1 text-xs text-surface-500\">\r\n {{ threadKey() || \"Discussion\" }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n @if (unreadCount() > 0) {\r\n <span\r\n class=\"inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-semibold text-red-700\"\r\n >\r\n {{ unreadCount() }} unread\r\n </span>\r\n } @else {\r\n <span\r\n class=\"inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-semibold text-emerald-700\"\r\n >\r\n Up to date\r\n </span>\r\n }\r\n\r\n <mt-button\r\n icon=\"arrow.refresh-cw-01\"\r\n [label]=\"refreshing() ? 'Refreshing...' : 'Refresh'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [loading]=\"refreshing()\"\r\n [disabled]=\"loadingInitial()\"\r\n (onClick)=\"refreshThread()\"\r\n />\r\n <mt-button\r\n icon=\"general.check\"\r\n label=\"Mark read\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [disabled]=\"markingRead() || !hasUnread()\"\r\n (onClick)=\"markRead()\"\r\n />\r\n <mt-button\r\n icon=\"communication.mail-01\"\r\n label=\"Mark unread\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [disabled]=\"markingRead()\"\r\n (onClick)=\"markUnread()\"\r\n />\r\n @if (showParticipants()) {\r\n <mt-button\r\n icon=\"user.users-01\"\r\n [label]=\"'Participants (' + participants().length + ')'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n (onClick)=\"toggleParticipantsPanel()\"\r\n />\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (showParticipants() && participantsExpanded()) {\r\n <section class=\"border-b border-surface-200 px-4 py-3\">\r\n @if (participantsLoading()) {\r\n <span class=\"text-xs text-surface-500\">Loading participants...</span>\r\n } @else if (participantEntities().length === 0) {\r\n <span class=\"text-xs text-surface-500\">No participants yet.</span>\r\n } @else {\r\n <div class=\"max-h-48 overflow-y-auto pr-1\">\r\n <mt-entities-preview [entities]=\"participantEntities()\" />\r\n </div>\r\n }\r\n </section>\r\n }\r\n\r\n @if (errorMessage(); as error) {\r\n <div\r\n class=\"border-b border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700\"\r\n >\r\n {{ error }}\r\n </div>\r\n }\r\n\r\n <div class=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\r\n <div\r\n #viewport\r\n class=\"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-3 py-3\"\r\n (scroll)=\"onViewportScroll()\"\r\n >\r\n @if (hasMore()) {\r\n <div class=\"flex justify-center py-1\">\r\n <mt-button\r\n icon=\"arrow.chevron-up\"\r\n [label]=\"loadingMore() ? 'Loading...' : 'Load older messages'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [loading]=\"loadingMore()\"\r\n (onClick)=\"loadOlder()\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (loadingInitial()) {\r\n <div class=\"space-y-2\">\r\n @for (item of [1, 2, 3, 4]; track item) {\r\n <div class=\"h-16 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (commentsAsc().length === 0) {\r\n <div\r\n class=\"rounded-2xl border border-dashed border-surface-300 p-8 text-center\"\r\n >\r\n <h4 class=\"text-sm font-semibold text-surface-700\">\r\n No comments yet\r\n </h4>\r\n <p class=\"mt-1 text-xs text-surface-500\">Start the conversation.</p>\r\n </div>\r\n } @else {\r\n @for (comment of commentsAsc(); track trackComment($index, comment)) {\r\n @if (firstUnreadCommentId() === comment.id) {\r\n <div class=\"my-1 flex items-center justify-center gap-2\">\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n <span\r\n class=\"rounded-full border border-red-200 bg-red-50 px-2 py-0.5 text-[0.67rem] font-bold leading-none text-red-700\"\r\n >Unread messages</span\r\n >\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n </div>\r\n }\r\n\r\n <article\r\n class=\"group flex gap-2\"\r\n [class.justify-end]=\"isOwnComment(comment)\"\r\n >\r\n @if (!isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"max-w-[88%] min-w-[16rem] rounded-2xl border border-surface-200 px-3 py-2\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_7%,white)]]=\"\r\n isOwnComment(comment)\r\n \"\r\n >\r\n <div\r\n class=\"mb-1 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <div class=\"text-xs text-surface-600\">\r\n <span class=\"font-semibold text-surface-900\">{{\r\n comment.createdBy\r\n }}</span>\r\n <span class=\"mx-1\">•</span>\r\n <span>{{\r\n comment.createdAt | date: \"MMM d, y h:mm a\"\r\n }}</span>\r\n @if (comment.updatedAt) {\r\n <span class=\"ml-1 text-[11px] text-surface-500\"\r\n >(edited)</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (getParentComment(comment); as parentComment) {\r\n <button\r\n type=\"button\"\r\n class=\"mb-2 block w-full rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-left text-xs text-surface-600\"\r\n (click)=\"openReply(parentComment)\"\r\n >\r\n <span class=\"font-semibold\">{{\r\n parentComment.createdBy\r\n }}</span>\r\n <span class=\"mx-1\">:</span>\r\n <span class=\"line-clamp-1\">{{\r\n parentComment.comment\r\n }}</span>\r\n </button>\r\n }\r\n\r\n @if (editingCommentId() === comment.id) {\r\n <div class=\"relative\">\r\n <textarea\r\n rows=\"3\"\r\n class=\"w-full min-h-[5.25rem] resize-y rounded-2xl border border-surface-300 bg-white px-3 py-2.5 text-[0.86rem] leading-[1.45] text-surface-900 outline-none focus-visible:outline-2 focus-visible:outline-[color-mix(in_srgb,var(--p-primary-color)_32%,transparent)] focus-visible:outline-offset-1\"\r\n [ngModel]=\"editText()\"\r\n (ngModelChange)=\"editText.set($event)\"\r\n (input)=\"onEditInput($event)\"\r\n (keyup)=\"onEditCaretEvent($event)\"\r\n (click)=\"onEditCaretEvent($event)\"\r\n (scroll)=\"onEditCaretEvent($event)\"\r\n (keydown)=\"onEditKeydown($event)\"\r\n ></textarea>\r\n\r\n @if (\r\n mentionSession()?.mode === \"edit\" &&\r\n mentionSession()?.editCommentId === comment.id\r\n ) {\r\n <div\r\n class=\"absolute top-0 left-0 z-20 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span\r\n class=\"flex min-w-0 flex-1 flex-col gap-0.5\"\r\n >\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <span class=\"text-xs text-surface-500\"\r\n >{{ editText().length }}/10000</span\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-surface-300 bg-transparent px-3 py-2 text-xs font-semibold leading-none text-surface-700 transition-colors hover:border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"cancelEdit()\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-[color-mix(in_srgb,var(--p-primary-color)_26%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_12%,white)] px-3 py-2 text-xs font-bold leading-none text-[color-mix(in_srgb,var(--p-primary-color)_76%,black)] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_17%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"!canSaveEdit()\"\r\n (click)=\"saveEdit()\"\r\n >\r\n {{ savingEdit() ? \"Saving...\" : \"Save\" }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"whitespace-pre-wrap break-words text-sm leading-6\"\r\n >\r\n @for (\r\n segment of getCommentSegments(comment);\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n [attr.data-user-id]=\"segment.userId || null\"\r\n >\r\n {{ segment.text }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (comment.attachments.length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2\">\r\n @for (\r\n attachment of comment.attachments;\r\n track attachment.id\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex max-w-full items-center gap-1.5 rounded-lg border border-surface-300 px-2 py-1 text-[0.72rem] hover:bg-surface-50\"\r\n (click)=\"downloadAttachment(comment, attachment)\"\r\n >\r\n <span class=\"truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n <span class=\"text-[11px] text-surface-500\"\r\n >({{ attachment.size | number }} bytes)</span\r\n >\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center gap-1 opacity-100 md:opacity-0 md:group-hover:opacity-100\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openReply(comment)\"\r\n >\r\n Reply\r\n </button>\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"startEdit(comment)\"\r\n >\r\n Edit\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-red-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"isDeleting(comment.id)\"\r\n (click)=\"deleteComment(comment)\"\r\n >\r\n {{ isDeleting(comment.id) ? \"Deleting...\" : \"Delete\" }}\r\n </button>\r\n }\r\n @if (comment.updatedAt) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openRevisions(comment)\"\r\n >\r\n History\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n </article>\r\n }\r\n }\r\n </div>\r\n\r\n <footer\r\n class=\"z-10 shrink-0 border-t border-surface-200 bg-content px-3 py-3\"\r\n >\r\n @if (replyToComment(); as replyComment) {\r\n <div\r\n class=\"mb-2 flex items-center justify-between rounded-xl border border-surface-200 bg-surface-50 px-2 py-1\"\r\n >\r\n <div class=\"min-w-0 text-xs text-surface-600\">\r\n <span class=\"font-semibold\"\r\n >Replying to {{ replyComment.createdBy }}</span\r\n >\r\n <p class=\"truncate\">{{ replyComment.comment }}</p>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"clearReply()\"\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"rounded-2xl border border-surface-300 bg-white p-2 shadow-sm\"\r\n >\r\n <div class=\"relative\">\r\n <textarea\r\n #composerInput\r\n rows=\"3\"\r\n class=\"w-full min-h-[5.5rem] resize-y border-0 bg-transparent px-2 py-1 text-[0.9rem] leading-[1.45] text-surface-900 outline-none\"\r\n [disabled]=\"disabled() || posting()\"\r\n [ngModel]=\"composerText()\"\r\n (ngModelChange)=\"composerText.set($event)\"\r\n (input)=\"onComposerInput($event)\"\r\n (keyup)=\"onComposerCaretEvent($event)\"\r\n (click)=\"onComposerCaretEvent($event)\"\r\n (scroll)=\"onComposerCaretEvent($event)\"\r\n (keydown)=\"onComposerKeydown($event)\"\r\n [placeholder]=\"placeholder()\"\r\n ></textarea>\r\n\r\n @if (mentionSession()?.mode === \"composer\") {\r\n <div\r\n class=\"absolute top-0 left-0 z-30 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (composerAttachments().length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2 px-1\">\r\n @for (attachment of composerAttachments(); track attachment.id) {\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-xs\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"max-w-[12rem] truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n @if (attachment.status === \"uploading\") {\r\n <span class=\"text-surface-500\"\r\n >{{ attachment.progress }}%</span\r\n >\r\n } @else if (attachment.status === \"failed\") {\r\n <span class=\"text-red-600\">Failed</span>\r\n } @else {\r\n <span class=\"text-emerald-600\">Ready</span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"removeAttachment(attachment.id)\"\r\n >\r\n Remove\r\n </button>\r\n </div>\r\n @if (attachment.error) {\r\n <p class=\"text-[11px] text-red-600\">\r\n {{ attachment.error }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2 border-t border-surface-200 px-1 pt-2\"\r\n >\r\n <div class=\"flex items-center gap-2 text-xs text-surface-500\">\r\n <span>Type @ to mention people</span>\r\n <span>•</span>\r\n <span>{{ composerText().length }}/10000</span>\r\n @if (charactersLeft() < 0) {\r\n <span class=\"font-semibold text-red-600\">Too long</span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex items-center gap-2\">\r\n @if (allowAttachments()) {\r\n <mt-button\r\n icon=\"file.paperclip\"\r\n label=\"Attach\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [disabled]=\"disabled() || posting()\"\r\n (onClick)=\"browseAttachments()\"\r\n />\r\n }\r\n <mt-button\r\n icon=\"communication.send-01\"\r\n [label]=\"posting() ? 'Sending...' : 'Send'\"\r\n size=\"small\"\r\n [loading]=\"posting()\"\r\n [disabled]=\"!canSend()\"\r\n (onClick)=\"sendComment()\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </footer>\r\n </div>\r\n </div>\r\n</mt-card>\r\n\r\n<p-dialog\r\n [visible]=\"revisionsDialogVisible()\"\r\n (visibleChange)=\"revisionsDialogVisible.set($event)\"\r\n [modal]=\"true\"\r\n [dismissableMask]=\"true\"\r\n [draggable]=\"false\"\r\n [resizable]=\"false\"\r\n [style]=\"{ width: 'min(42rem, 92vw)' }\"\r\n header=\"Comment history\"\r\n>\r\n @if (revisionLoading()) {\r\n <div class=\"space-y-2\">\r\n @for (row of [1, 2, 3]; track row) {\r\n <div class=\"h-12 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (visibleRevisions().length === 0) {\r\n <p class=\"text-sm text-surface-500\">No revision snapshots.</p>\r\n } @else {\r\n <div class=\"max-h-[55vh] space-y-2 overflow-y-auto pr-1\">\r\n @for (revision of visibleRevisions(); track revision.id) {\r\n <article class=\"rounded-xl border border-surface-200 p-3\">\r\n <div class=\"mb-1 text-xs text-surface-500\">\r\n Revision #{{ revision.revisionNumber }} •\r\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} •\r\n {{ revision.createdBy }}\r\n </div>\r\n <div class=\"whitespace-pre-wrap break-words text-sm leading-6\">\r\n @for (\r\n segment of getCommentSegments({\r\n id: revision.id,\r\n moduleType: selectedRevisionComment()?.moduleType || \"\",\r\n recordId: selectedRevisionComment()?.recordId || 0,\r\n parentCommentId: null,\r\n comment: revision.comment,\r\n isSystem: false,\r\n createdAt: revision.createdAt,\r\n updatedAt: null,\r\n createdBy: revision.createdBy,\r\n updatedBy: null,\r\n attachments: [],\r\n mentions: revision.mentions,\r\n });\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n >{{ segment.text }}</span\r\n >\r\n }\r\n </div>\r\n </article>\r\n }\r\n </div>\r\n }\r\n</p-dialog>\r\n", styles: [":host{display:block;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div{display:flex;flex-direction:column;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div>div.flex-1{display:flex;flex-direction:column;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: EntitiesPreview, selector: "mt-entities-preview", inputs: ["entities"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1621
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: DiscussionThread, isStandalone: true, selector: "mt-discussion-thread", inputs: { moduleType: { classPropertyName: "moduleType", publicName: "moduleType", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, requestContext: { classPropertyName: "requestContext", publicName: "requestContext", isSignal: true, isRequired: false, transformFunction: null }, mentionableUsers: { classPropertyName: "mentionableUsers", publicName: "mentionableUsers", isSignal: true, isRequired: false, transformFunction: null }, mentionSearchEndpoint: { classPropertyName: "mentionSearchEndpoint", publicName: "mentionSearchEndpoint", isSignal: true, isRequired: false, transformFunction: null }, mentionSearchParam: { classPropertyName: "mentionSearchParam", publicName: "mentionSearchParam", isSignal: true, isRequired: false, transformFunction: null }, mentionSearchDataPath: { classPropertyName: "mentionSearchDataPath", publicName: "mentionSearchDataPath", isSignal: true, isRequired: false, transformFunction: null }, allowAttachments: { classPropertyName: "allowAttachments", publicName: "allowAttachments", isSignal: true, isRequired: false, transformFunction: null }, uploadEndpoint: { classPropertyName: "uploadEndpoint", publicName: "uploadEndpoint", isSignal: true, isRequired: false, transformFunction: null }, attachmentDownloadEndpoint: { classPropertyName: "attachmentDownloadEndpoint", publicName: "attachmentDownloadEndpoint", isSignal: true, isRequired: false, transformFunction: null }, showParticipants: { classPropertyName: "showParticipants", publicName: "showParticipants", isSignal: true, isRequired: false, transformFunction: null }, autoMarkRead: { classPropertyName: "autoMarkRead", publicName: "autoMarkRead", isSignal: true, isRequired: false, transformFunction: null }, refreshIntervalMs: { classPropertyName: "refreshIntervalMs", publicName: "refreshIntervalMs", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", errored: "errored", commentCreated: "commentCreated", commentUpdated: "commentUpdated", commentDeleted: "commentDeleted", readStateChanged: "readStateChanged" }, host: { classAttribute: "block h-full min-h-0" }, viewQueries: [{ propertyName: "viewportRef", first: true, predicate: ["viewport"], descendants: true, isSignal: true }, { propertyName: "composerInputRef", first: true, predicate: ["composerInput"], descendants: true, isSignal: true }, { propertyName: "attachmentInputRef", first: true, predicate: ["attachmentInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<mt-card\r\n class=\"mt-discussion-card h-full min-h-0 w-full overflow-hidden shadow-sm\"\r\n [paddingless]=\"true\"\r\n [ngClass]=\"styleClass()\"\r\n>\r\n <div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n <input\r\n #attachmentInput\r\n type=\"file\"\r\n class=\"hidden\"\r\n multiple\r\n (change)=\"onAttachmentSelected($event)\"\r\n />\r\n\r\n <header\r\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-0 px-4 py-3.5\"\r\n >\r\n <div class=\"min-w-0 flex-1 flex items-center gap-2\">\r\n <div class=\"text-2xl text-surface-500 font-semibold\">\r\n <mt-icon icon=\"communication.message-chat-square\"></mt-icon>\r\n </div>\r\n <h3 class=\"truncate text-base font-semibold text-surface-900\">\r\n {{ title() }}\r\n </h3>\r\n </div>\r\n\r\n <div class=\"flex items-center gap-2\">\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n [rounded]=\"true\"\r\n styleClass=\"mt-discussion-icon-btn\"\r\n [loading]=\"refreshing()\"\r\n [disabled]=\"loadingInitial() || markingRead()\"\r\n (onClick)=\"actionsPopover.toggle($event)\"\r\n />\r\n\r\n <p-popover\r\n #actionsPopover\r\n appendTo=\"body\"\r\n styleClass=\"mt-discussion-actions-popover\"\r\n >\r\n <div class=\"flex min-w-44 flex-col py-1\">\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-surface-700 transition-colors hover:bg-surface-100 disabled:cursor-not-allowed disabled:opacity-50\"\r\n [disabled]=\"refreshing() || loadingInitial()\"\r\n (click)=\"actionsPopover.hide(); refreshThread()\"\r\n >\r\n <mt-icon icon=\"arrow.refresh-cw-01\"></mt-icon>\r\n\r\n <span>{{ refreshing() ? \"Refreshing...\" : \"Refresh\" }}</span>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-surface-700 transition-colors hover:bg-surface-100 disabled:cursor-not-allowed disabled:opacity-50\"\r\n [disabled]=\"markingRead() || !hasUnread()\"\r\n (click)=\"actionsPopover.hide(); markRead()\"\r\n >\r\n <mt-icon icon=\"general.check\"></mt-icon>\r\n\r\n <span>Mark read</span>\r\n </button>\r\n\r\n @if (showParticipants()) {\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-surface-700 transition-colors hover:bg-surface-100\"\r\n (click)=\"actionsPopover.hide(); toggleParticipantsPanel()\"\r\n >\r\n <mt-icon icon=\"user.users-01\"></mt-icon>\r\n\r\n <span>{{\r\n participantsExpanded()\r\n ? \"Hide participants\"\r\n : \"Show participants\"\r\n }}</span>\r\n </button>\r\n }\r\n </div>\r\n </p-popover>\r\n </div>\r\n </header>\r\n\r\n @if (showParticipants() && participantsExpanded()) {\r\n <section class=\"border-b border-surface-200 px-4 py-3\">\r\n @if (participantsLoading()) {\r\n <span class=\"text-xs text-surface-500\">Loading participants...</span>\r\n } @else if (participantEntities().length === 0) {\r\n <span class=\"text-xs text-surface-500\">No participants yet.</span>\r\n } @else {\r\n <div class=\"max-h-48 overflow-y-auto pr-1\">\r\n <mt-entities-preview [entities]=\"participantEntities()\" />\r\n </div>\r\n }\r\n </section>\r\n }\r\n\r\n @if (errorMessage(); as error) {\r\n <div\r\n class=\"border-b border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700\"\r\n >\r\n {{ error }}\r\n </div>\r\n }\r\n\r\n <div class=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\r\n <div\r\n #viewport\r\n class=\"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain bg-surface-50/60 px-3 py-3\"\r\n (scroll)=\"onViewportScroll()\"\r\n >\r\n @if (hasMore()) {\r\n <div class=\"flex justify-center py-1\">\r\n <mt-button\r\n icon=\"arrow.chevron-up\"\r\n [label]=\"loadingMore() ? 'Loading...' : 'Load older messages'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [loading]=\"loadingMore()\"\r\n (onClick)=\"loadOlder()\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (loadingInitial()) {\r\n <div class=\"space-y-2\">\r\n @for (item of [1, 2, 3, 4]; track item) {\r\n <div class=\"h-16 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (commentsAsc().length === 0) {\r\n <div class=\"rounded-2xl p-8 text-center\">\r\n <h4 class=\"text-sm font-semibold text-surface-700\">\r\n No comments yet\r\n </h4>\r\n <p class=\"mt-1 text-xs text-surface-500\">Start the conversation.</p>\r\n </div>\r\n } @else {\r\n @for (comment of commentsAsc(); track trackComment($index, comment)) {\r\n @if (firstUnreadCommentId() === comment.id) {\r\n <div class=\"my-1 flex items-center justify-center gap-2\">\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n <span\r\n class=\"rounded-full border border-red-200 bg-red-50 px-2 py-0.5 text-[0.67rem] font-bold leading-none text-red-700\"\r\n >Unread messages</span\r\n >\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n </div>\r\n }\r\n\r\n <article\r\n class=\"group flex gap-2\"\r\n [class.justify-end]=\"isOwnComment(comment)\"\r\n >\r\n @if (!isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"max-w-[90%] min-w-[30rem] rounded-[1.35rem] border border-surface-200 bg-white px-3 py-2.5 shadow-[0_8px_24px_color-mix(in_srgb,black_4%,transparent)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_7%,white)]]=\"\r\n isOwnComment(comment)\r\n \"\r\n >\r\n <div\r\n class=\"mb-1 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <div\r\n class=\"text-xs w-full text-surface-600 flex justify-between\"\r\n >\r\n <div class=\"font-semibold text-surface-900\">\r\n {{ comment.createdBy }}\r\n </div>\r\n <div>\r\n <span>{{\r\n comment.createdAt | date: \"MMM d, y h:mm a\"\r\n }}</span>\r\n @if (comment.updatedAt) {\r\n <span class=\"ml-1 text-[11px] text-surface-500\"\r\n >(edited)</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (getParentComment(comment); as parentComment) {\r\n <button\r\n type=\"button\"\r\n class=\"mb-2 block w-full rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-left text-xs text-surface-600\"\r\n (click)=\"openReply(parentComment)\"\r\n >\r\n <span class=\"font-semibold\">{{\r\n parentComment.createdBy\r\n }}</span>\r\n <span class=\"mx-1\">:</span>\r\n <span class=\"line-clamp-1\">{{\r\n parentComment.comment\r\n }}</span>\r\n </button>\r\n }\r\n\r\n @if (editingCommentId() === comment.id) {\r\n <div class=\"relative\">\r\n <textarea\r\n rows=\"3\"\r\n class=\"w-full min-h-[5.25rem] resize-y rounded-2xl border border-surface-300 bg-white px-3 py-2.5 text-[0.86rem] leading-[1.45] text-surface-900 outline-none focus-visible:outline-2 focus-visible:outline-[color-mix(in_srgb,var(--p-primary-color)_32%,transparent)] focus-visible:outline-offset-1\"\r\n [ngModel]=\"editText()\"\r\n (ngModelChange)=\"editText.set($event)\"\r\n (input)=\"onEditInput($event)\"\r\n (keyup)=\"onEditCaretEvent($event)\"\r\n (click)=\"onEditCaretEvent($event)\"\r\n (scroll)=\"onEditCaretEvent($event)\"\r\n (keydown)=\"onEditKeydown($event)\"\r\n ></textarea>\r\n\r\n @if (\r\n mentionSession()?.mode === \"edit\" &&\r\n mentionSession()?.editCommentId === comment.id\r\n ) {\r\n <div\r\n class=\"absolute top-0 left-0 z-20 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span\r\n class=\"flex min-w-0 flex-1 flex-col gap-0.5\"\r\n >\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <span class=\"text-xs text-surface-500\"\r\n >{{ editText().length }}/10000</span\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-surface-300 bg-transparent px-3 py-2 text-xs font-semibold leading-none text-surface-700 transition-colors hover:border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"cancelEdit()\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-[color-mix(in_srgb,var(--p-primary-color)_26%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_12%,white)] px-3 py-2 text-xs font-bold leading-none text-[color-mix(in_srgb,var(--p-primary-color)_76%,black)] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_17%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"!canSaveEdit()\"\r\n (click)=\"saveEdit()\"\r\n >\r\n {{ savingEdit() ? \"Saving...\" : \"Save\" }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"whitespace-pre-wrap pt-1 break-words text-sm leading-6\"\r\n >\r\n @for (\r\n segment of getCommentSegments(comment);\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n [attr.data-user-id]=\"segment.userId || null\"\r\n >\r\n {{ segment.text }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (comment.attachments.length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2\">\r\n @for (\r\n attachment of comment.attachments;\r\n track attachment.id\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex max-w-full items-center gap-1.5 rounded-lg border border-surface-300 px-2 py-1 text-[0.72rem] hover:bg-surface-50\"\r\n (click)=\"downloadAttachment(comment, attachment)\"\r\n >\r\n <span class=\"truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n <span class=\"text-[11px] text-surface-500\"\r\n >({{ attachment.size | number }} bytes)</span\r\n >\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mt-2 flex flex-wrap items-center gap-1\">\r\n @if (!comment.isSystem) {\r\n <button\r\n type=\"button\"\r\n class=\"cursor-pointer rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openReply(comment)\"\r\n >\r\n Reply\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"startEdit(comment)\"\r\n >\r\n Edit\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-red-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"isDeleting(comment.id)\"\r\n (click)=\"deleteComment(comment)\"\r\n >\r\n {{ isDeleting(comment.id) ? \"Deleting...\" : \"Delete\" }}\r\n </button>\r\n }\r\n @if (comment.updatedAt) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openRevisions(comment)\"\r\n >\r\n History\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n </article>\r\n }\r\n }\r\n </div>\r\n\r\n <footer\r\n class=\"z-10 shrink-0 border-t border-surface-200 bg-content px-3 py-3\"\r\n >\r\n @if (replyToComment(); as replyComment) {\r\n <div\r\n class=\"mb-2 flex items-center justify-between rounded-xl border border-surface-200 bg-surface-50 px-2 py-1\"\r\n >\r\n <div class=\"min-w-0 text-xs text-surface-600\">\r\n <span class=\"font-semibold\"\r\n >Replying to {{ replyComment.createdBy }}</span\r\n >\r\n <p class=\"truncate\">{{ replyComment.comment }}</p>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"clearReply()\"\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"rounded-[1.5rem] border border-surface-300 bg-white p-2.5 shadow-sm\"\r\n >\r\n <div class=\"relative\">\r\n <textarea\r\n #composerInput\r\n rows=\"2\"\r\n class=\"w-full min-h-[3.75rem] resize-none border-0 bg-transparent px-2 pb-10 pr-24 pt-1 text-[0.9rem] leading-[1.45] text-surface-900 outline-none\"\r\n [disabled]=\"disabled() || posting()\"\r\n [ngModel]=\"composerText()\"\r\n (ngModelChange)=\"composerText.set($event)\"\r\n (input)=\"onComposerInput($event)\"\r\n (keyup)=\"onComposerCaretEvent($event)\"\r\n (click)=\"onComposerCaretEvent($event)\"\r\n (scroll)=\"onComposerCaretEvent($event)\"\r\n (keydown)=\"onComposerKeydown($event)\"\r\n [placeholder]=\"placeholder()\"\r\n ></textarea>\r\n\r\n <div class=\"absolute right-1 bottom-1 flex items-center gap-2\">\r\n @if (allowAttachments()) {\r\n <mt-button\r\n icon=\"file.paperclip\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n [rounded]=\"true\"\r\n styleClass=\"mt-discussion-icon-btn\"\r\n [disabled]=\"disabled() || posting()\"\r\n (onClick)=\"browseAttachments()\"\r\n />\r\n }\r\n\r\n <mt-button\r\n icon=\"communication.send-01\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n styleClass=\"mt-discussion-send-btn\"\r\n [loading]=\"posting()\"\r\n [disabled]=\"!canSend()\"\r\n (onClick)=\"sendComment()\"\r\n />\r\n </div>\r\n\r\n @if (mentionSession()?.mode === \"composer\") {\r\n <div\r\n class=\"absolute top-0 left-0 z-30 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (composerAttachments().length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2 px-1\">\r\n @for (attachment of composerAttachments(); track attachment.id) {\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 px-2.5 py-1.5 text-xs\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"max-w-[12rem] truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n @if (attachment.status === \"uploading\") {\r\n <span class=\"text-surface-500\"\r\n >{{ attachment.progress }}%</span\r\n >\r\n } @else if (attachment.status === \"failed\") {\r\n <span class=\"text-red-600\">Failed</span>\r\n } @else {\r\n <span class=\"text-emerald-600\">Ready</span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"removeAttachment(attachment.id)\"\r\n >\r\n Remove\r\n </button>\r\n </div>\r\n @if (attachment.error) {\r\n <p class=\"text-[11px] text-red-600\">\r\n {{ attachment.error }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (composerText().length > 10000) {\r\n <div class=\"mt-2 px-1 text-xs font-medium text-red-600\">\r\n Comment cannot exceed 10000 characters.\r\n </div>\r\n }\r\n </div>\r\n </footer>\r\n </div>\r\n </div>\r\n</mt-card>\r\n\r\n<p-dialog\r\n [visible]=\"revisionsDialogVisible()\"\r\n (visibleChange)=\"revisionsDialogVisible.set($event)\"\r\n [modal]=\"true\"\r\n [dismissableMask]=\"true\"\r\n [draggable]=\"false\"\r\n [resizable]=\"false\"\r\n [style]=\"{ width: 'min(42rem, 92vw)' }\"\r\n header=\"Comment history\"\r\n>\r\n @if (revisionLoading()) {\r\n <div class=\"space-y-2\">\r\n @for (row of [1, 2, 3]; track row) {\r\n <div class=\"h-12 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (visibleRevisions().length === 0) {\r\n <p class=\"text-sm text-surface-500\">No revision snapshots.</p>\r\n } @else {\r\n <div class=\"max-h-[55vh] space-y-2 overflow-y-auto pr-1\">\r\n @for (revision of visibleRevisions(); track revision.id) {\r\n <article class=\"rounded-xl border border-surface-200 p-3\">\r\n <div class=\"mb-1 text-xs text-surface-500\">\r\n Revision #{{ revision.revisionNumber }} •\r\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} •\r\n {{ revision.createdBy }}\r\n </div>\r\n <div class=\"whitespace-pre-wrap break-words text-sm leading-6\">\r\n @for (\r\n segment of getCommentSegments({\r\n id: revision.id,\r\n moduleType: selectedRevisionComment()?.moduleType || \"\",\r\n recordId: selectedRevisionComment()?.recordId || 0,\r\n parentCommentId: null,\r\n comment: revision.comment,\r\n isSystem: false,\r\n createdAt: revision.createdAt,\r\n updatedAt: null,\r\n createdBy: revision.createdBy,\r\n updatedBy: null,\r\n attachments: [],\r\n mentions: revision.mentions,\r\n });\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n >{{ segment.text }}</span\r\n >\r\n }\r\n </div>\r\n </article>\r\n }\r\n </div>\r\n }\r\n</p-dialog>\r\n", styles: [":host{display:block;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div{display:flex;flex-direction:column;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div>div.flex-1{display:flex;flex-direction:column;min-height:0;overflow:hidden}:host ::ng-deep .mt-discussion-actions-popover .p-popover-content{padding:.25rem}:host ::ng-deep .mt-discussion-icon-btn,:host ::ng-deep .mt-discussion-send-btn{height:2.25rem;width:2.25rem;min-width:2.25rem}:host ::ng-deep .mt-discussion-send-btn{box-shadow:0 10px 24px color-mix(in srgb,var(--p-primary-color) 22%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i4.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: EntitiesPreview, selector: "mt-entities-preview", inputs: ["entities"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "pipe", type: i1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1630
1622
|
}
|
|
1631
1623
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: DiscussionThread, decorators: [{
|
|
1632
1624
|
type: Component,
|
|
@@ -1635,12 +1627,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
1635
1627
|
FormsModule,
|
|
1636
1628
|
DatePipe,
|
|
1637
1629
|
DialogModule,
|
|
1630
|
+
PopoverModule,
|
|
1638
1631
|
Button,
|
|
1639
1632
|
Card,
|
|
1640
1633
|
EntitiesPreview,
|
|
1634
|
+
Icon,
|
|
1641
1635
|
], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1642
1636
|
class: 'block h-full min-h-0',
|
|
1643
|
-
}, template: "<mt-card\r\n class=\"mt-discussion-card h-full min-h-0 w-full overflow-hidden shadow-sm\"\r\n [paddingless]=\"true\"\r\n [ngClass]=\"styleClass()\"\r\n>\r\n <div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n <input\r\n #attachmentInput\r\n type=\"file\"\r\n class=\"hidden\"\r\n multiple\r\n (change)=\"onAttachmentSelected($event)\"\r\n />\r\n\r\n <header\r\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-200 px-4 py-3\"\r\n >\r\n <div class=\"min-w-0\">\r\n <h3 class=\"truncate text-sm font-semibold uppercase tracking-[0.08em]\">\r\n {{ title() }}\r\n </h3>\r\n @if (subtitle()) {\r\n <p class=\"mt-1 text-xs text-surface-500\">{{ subtitle() }}</p>\r\n }\r\n <p class=\"mt-1 text-xs text-surface-500\">\r\n {{ threadKey() || \"Discussion\" }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-wrap items-center gap-2\">\r\n @if (unreadCount() > 0) {\r\n <span\r\n class=\"inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-semibold text-red-700\"\r\n >\r\n {{ unreadCount() }} unread\r\n </span>\r\n } @else {\r\n <span\r\n class=\"inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-semibold text-emerald-700\"\r\n >\r\n Up to date\r\n </span>\r\n }\r\n\r\n <mt-button\r\n icon=\"arrow.refresh-cw-01\"\r\n [label]=\"refreshing() ? 'Refreshing...' : 'Refresh'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [loading]=\"refreshing()\"\r\n [disabled]=\"loadingInitial()\"\r\n (onClick)=\"refreshThread()\"\r\n />\r\n <mt-button\r\n icon=\"general.check\"\r\n label=\"Mark read\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [disabled]=\"markingRead() || !hasUnread()\"\r\n (onClick)=\"markRead()\"\r\n />\r\n <mt-button\r\n icon=\"communication.mail-01\"\r\n label=\"Mark unread\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [disabled]=\"markingRead()\"\r\n (onClick)=\"markUnread()\"\r\n />\r\n @if (showParticipants()) {\r\n <mt-button\r\n icon=\"user.users-01\"\r\n [label]=\"'Participants (' + participants().length + ')'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n (onClick)=\"toggleParticipantsPanel()\"\r\n />\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (showParticipants() && participantsExpanded()) {\r\n <section class=\"border-b border-surface-200 px-4 py-3\">\r\n @if (participantsLoading()) {\r\n <span class=\"text-xs text-surface-500\">Loading participants...</span>\r\n } @else if (participantEntities().length === 0) {\r\n <span class=\"text-xs text-surface-500\">No participants yet.</span>\r\n } @else {\r\n <div class=\"max-h-48 overflow-y-auto pr-1\">\r\n <mt-entities-preview [entities]=\"participantEntities()\" />\r\n </div>\r\n }\r\n </section>\r\n }\r\n\r\n @if (errorMessage(); as error) {\r\n <div\r\n class=\"border-b border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700\"\r\n >\r\n {{ error }}\r\n </div>\r\n }\r\n\r\n <div class=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\r\n <div\r\n #viewport\r\n class=\"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-3 py-3\"\r\n (scroll)=\"onViewportScroll()\"\r\n >\r\n @if (hasMore()) {\r\n <div class=\"flex justify-center py-1\">\r\n <mt-button\r\n icon=\"arrow.chevron-up\"\r\n [label]=\"loadingMore() ? 'Loading...' : 'Load older messages'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [loading]=\"loadingMore()\"\r\n (onClick)=\"loadOlder()\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (loadingInitial()) {\r\n <div class=\"space-y-2\">\r\n @for (item of [1, 2, 3, 4]; track item) {\r\n <div class=\"h-16 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (commentsAsc().length === 0) {\r\n <div\r\n class=\"rounded-2xl border border-dashed border-surface-300 p-8 text-center\"\r\n >\r\n <h4 class=\"text-sm font-semibold text-surface-700\">\r\n No comments yet\r\n </h4>\r\n <p class=\"mt-1 text-xs text-surface-500\">Start the conversation.</p>\r\n </div>\r\n } @else {\r\n @for (comment of commentsAsc(); track trackComment($index, comment)) {\r\n @if (firstUnreadCommentId() === comment.id) {\r\n <div class=\"my-1 flex items-center justify-center gap-2\">\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n <span\r\n class=\"rounded-full border border-red-200 bg-red-50 px-2 py-0.5 text-[0.67rem] font-bold leading-none text-red-700\"\r\n >Unread messages</span\r\n >\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n </div>\r\n }\r\n\r\n <article\r\n class=\"group flex gap-2\"\r\n [class.justify-end]=\"isOwnComment(comment)\"\r\n >\r\n @if (!isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"max-w-[88%] min-w-[16rem] rounded-2xl border border-surface-200 px-3 py-2\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_7%,white)]]=\"\r\n isOwnComment(comment)\r\n \"\r\n >\r\n <div\r\n class=\"mb-1 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <div class=\"text-xs text-surface-600\">\r\n <span class=\"font-semibold text-surface-900\">{{\r\n comment.createdBy\r\n }}</span>\r\n <span class=\"mx-1\">•</span>\r\n <span>{{\r\n comment.createdAt | date: \"MMM d, y h:mm a\"\r\n }}</span>\r\n @if (comment.updatedAt) {\r\n <span class=\"ml-1 text-[11px] text-surface-500\"\r\n >(edited)</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (getParentComment(comment); as parentComment) {\r\n <button\r\n type=\"button\"\r\n class=\"mb-2 block w-full rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-left text-xs text-surface-600\"\r\n (click)=\"openReply(parentComment)\"\r\n >\r\n <span class=\"font-semibold\">{{\r\n parentComment.createdBy\r\n }}</span>\r\n <span class=\"mx-1\">:</span>\r\n <span class=\"line-clamp-1\">{{\r\n parentComment.comment\r\n }}</span>\r\n </button>\r\n }\r\n\r\n @if (editingCommentId() === comment.id) {\r\n <div class=\"relative\">\r\n <textarea\r\n rows=\"3\"\r\n class=\"w-full min-h-[5.25rem] resize-y rounded-2xl border border-surface-300 bg-white px-3 py-2.5 text-[0.86rem] leading-[1.45] text-surface-900 outline-none focus-visible:outline-2 focus-visible:outline-[color-mix(in_srgb,var(--p-primary-color)_32%,transparent)] focus-visible:outline-offset-1\"\r\n [ngModel]=\"editText()\"\r\n (ngModelChange)=\"editText.set($event)\"\r\n (input)=\"onEditInput($event)\"\r\n (keyup)=\"onEditCaretEvent($event)\"\r\n (click)=\"onEditCaretEvent($event)\"\r\n (scroll)=\"onEditCaretEvent($event)\"\r\n (keydown)=\"onEditKeydown($event)\"\r\n ></textarea>\r\n\r\n @if (\r\n mentionSession()?.mode === \"edit\" &&\r\n mentionSession()?.editCommentId === comment.id\r\n ) {\r\n <div\r\n class=\"absolute top-0 left-0 z-20 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span\r\n class=\"flex min-w-0 flex-1 flex-col gap-0.5\"\r\n >\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <span class=\"text-xs text-surface-500\"\r\n >{{ editText().length }}/10000</span\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-surface-300 bg-transparent px-3 py-2 text-xs font-semibold leading-none text-surface-700 transition-colors hover:border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"cancelEdit()\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-[color-mix(in_srgb,var(--p-primary-color)_26%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_12%,white)] px-3 py-2 text-xs font-bold leading-none text-[color-mix(in_srgb,var(--p-primary-color)_76%,black)] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_17%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"!canSaveEdit()\"\r\n (click)=\"saveEdit()\"\r\n >\r\n {{ savingEdit() ? \"Saving...\" : \"Save\" }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"whitespace-pre-wrap break-words text-sm leading-6\"\r\n >\r\n @for (\r\n segment of getCommentSegments(comment);\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n [attr.data-user-id]=\"segment.userId || null\"\r\n >\r\n {{ segment.text }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (comment.attachments.length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2\">\r\n @for (\r\n attachment of comment.attachments;\r\n track attachment.id\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex max-w-full items-center gap-1.5 rounded-lg border border-surface-300 px-2 py-1 text-[0.72rem] hover:bg-surface-50\"\r\n (click)=\"downloadAttachment(comment, attachment)\"\r\n >\r\n <span class=\"truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n <span class=\"text-[11px] text-surface-500\"\r\n >({{ attachment.size | number }} bytes)</span\r\n >\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center gap-1 opacity-100 md:opacity-0 md:group-hover:opacity-100\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openReply(comment)\"\r\n >\r\n Reply\r\n </button>\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"startEdit(comment)\"\r\n >\r\n Edit\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-red-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"isDeleting(comment.id)\"\r\n (click)=\"deleteComment(comment)\"\r\n >\r\n {{ isDeleting(comment.id) ? \"Deleting...\" : \"Delete\" }}\r\n </button>\r\n }\r\n @if (comment.updatedAt) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openRevisions(comment)\"\r\n >\r\n History\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n </article>\r\n }\r\n }\r\n </div>\r\n\r\n <footer\r\n class=\"z-10 shrink-0 border-t border-surface-200 bg-content px-3 py-3\"\r\n >\r\n @if (replyToComment(); as replyComment) {\r\n <div\r\n class=\"mb-2 flex items-center justify-between rounded-xl border border-surface-200 bg-surface-50 px-2 py-1\"\r\n >\r\n <div class=\"min-w-0 text-xs text-surface-600\">\r\n <span class=\"font-semibold\"\r\n >Replying to {{ replyComment.createdBy }}</span\r\n >\r\n <p class=\"truncate\">{{ replyComment.comment }}</p>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"clearReply()\"\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"rounded-2xl border border-surface-300 bg-white p-2 shadow-sm\"\r\n >\r\n <div class=\"relative\">\r\n <textarea\r\n #composerInput\r\n rows=\"3\"\r\n class=\"w-full min-h-[5.5rem] resize-y border-0 bg-transparent px-2 py-1 text-[0.9rem] leading-[1.45] text-surface-900 outline-none\"\r\n [disabled]=\"disabled() || posting()\"\r\n [ngModel]=\"composerText()\"\r\n (ngModelChange)=\"composerText.set($event)\"\r\n (input)=\"onComposerInput($event)\"\r\n (keyup)=\"onComposerCaretEvent($event)\"\r\n (click)=\"onComposerCaretEvent($event)\"\r\n (scroll)=\"onComposerCaretEvent($event)\"\r\n (keydown)=\"onComposerKeydown($event)\"\r\n [placeholder]=\"placeholder()\"\r\n ></textarea>\r\n\r\n @if (mentionSession()?.mode === \"composer\") {\r\n <div\r\n class=\"absolute top-0 left-0 z-30 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (composerAttachments().length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2 px-1\">\r\n @for (attachment of composerAttachments(); track attachment.id) {\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-xs\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"max-w-[12rem] truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n @if (attachment.status === \"uploading\") {\r\n <span class=\"text-surface-500\"\r\n >{{ attachment.progress }}%</span\r\n >\r\n } @else if (attachment.status === \"failed\") {\r\n <span class=\"text-red-600\">Failed</span>\r\n } @else {\r\n <span class=\"text-emerald-600\">Ready</span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"removeAttachment(attachment.id)\"\r\n >\r\n Remove\r\n </button>\r\n </div>\r\n @if (attachment.error) {\r\n <p class=\"text-[11px] text-red-600\">\r\n {{ attachment.error }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2 border-t border-surface-200 px-1 pt-2\"\r\n >\r\n <div class=\"flex items-center gap-2 text-xs text-surface-500\">\r\n <span>Type @ to mention people</span>\r\n <span>•</span>\r\n <span>{{ composerText().length }}/10000</span>\r\n @if (charactersLeft() < 0) {\r\n <span class=\"font-semibold text-red-600\">Too long</span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex items-center gap-2\">\r\n @if (allowAttachments()) {\r\n <mt-button\r\n icon=\"file.paperclip\"\r\n label=\"Attach\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [disabled]=\"disabled() || posting()\"\r\n (onClick)=\"browseAttachments()\"\r\n />\r\n }\r\n <mt-button\r\n icon=\"communication.send-01\"\r\n [label]=\"posting() ? 'Sending...' : 'Send'\"\r\n size=\"small\"\r\n [loading]=\"posting()\"\r\n [disabled]=\"!canSend()\"\r\n (onClick)=\"sendComment()\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </footer>\r\n </div>\r\n </div>\r\n</mt-card>\r\n\r\n<p-dialog\r\n [visible]=\"revisionsDialogVisible()\"\r\n (visibleChange)=\"revisionsDialogVisible.set($event)\"\r\n [modal]=\"true\"\r\n [dismissableMask]=\"true\"\r\n [draggable]=\"false\"\r\n [resizable]=\"false\"\r\n [style]=\"{ width: 'min(42rem, 92vw)' }\"\r\n header=\"Comment history\"\r\n>\r\n @if (revisionLoading()) {\r\n <div class=\"space-y-2\">\r\n @for (row of [1, 2, 3]; track row) {\r\n <div class=\"h-12 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (visibleRevisions().length === 0) {\r\n <p class=\"text-sm text-surface-500\">No revision snapshots.</p>\r\n } @else {\r\n <div class=\"max-h-[55vh] space-y-2 overflow-y-auto pr-1\">\r\n @for (revision of visibleRevisions(); track revision.id) {\r\n <article class=\"rounded-xl border border-surface-200 p-3\">\r\n <div class=\"mb-1 text-xs text-surface-500\">\r\n Revision #{{ revision.revisionNumber }} •\r\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} •\r\n {{ revision.createdBy }}\r\n </div>\r\n <div class=\"whitespace-pre-wrap break-words text-sm leading-6\">\r\n @for (\r\n segment of getCommentSegments({\r\n id: revision.id,\r\n moduleType: selectedRevisionComment()?.moduleType || \"\",\r\n recordId: selectedRevisionComment()?.recordId || 0,\r\n parentCommentId: null,\r\n comment: revision.comment,\r\n isSystem: false,\r\n createdAt: revision.createdAt,\r\n updatedAt: null,\r\n createdBy: revision.createdBy,\r\n updatedBy: null,\r\n attachments: [],\r\n mentions: revision.mentions,\r\n });\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n >{{ segment.text }}</span\r\n >\r\n }\r\n </div>\r\n </article>\r\n }\r\n </div>\r\n }\r\n</p-dialog>\r\n", styles: [":host{display:block;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div{display:flex;flex-direction:column;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div>div.flex-1{display:flex;flex-direction:column;min-height:0;overflow:hidden}\n"] }]
|
|
1637
|
+
}, template: "<mt-card\r\n class=\"mt-discussion-card h-full min-h-0 w-full overflow-hidden shadow-sm\"\r\n [paddingless]=\"true\"\r\n [ngClass]=\"styleClass()\"\r\n>\r\n <div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\r\n <input\r\n #attachmentInput\r\n type=\"file\"\r\n class=\"hidden\"\r\n multiple\r\n (change)=\"onAttachmentSelected($event)\"\r\n />\r\n\r\n <header\r\n class=\"flex items-center justify-between gap-3 border-b border-surface-200 bg-surface-0 px-4 py-3.5\"\r\n >\r\n <div class=\"min-w-0 flex-1 flex items-center gap-2\">\r\n <div class=\"text-2xl text-surface-500 font-semibold\">\r\n <mt-icon icon=\"communication.message-chat-square\"></mt-icon>\r\n </div>\r\n <h3 class=\"truncate text-base font-semibold text-surface-900\">\r\n {{ title() }}\r\n </h3>\r\n </div>\r\n\r\n <div class=\"flex items-center gap-2\">\r\n <mt-button\r\n icon=\"general.dots-vertical\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n [rounded]=\"true\"\r\n styleClass=\"mt-discussion-icon-btn\"\r\n [loading]=\"refreshing()\"\r\n [disabled]=\"loadingInitial() || markingRead()\"\r\n (onClick)=\"actionsPopover.toggle($event)\"\r\n />\r\n\r\n <p-popover\r\n #actionsPopover\r\n appendTo=\"body\"\r\n styleClass=\"mt-discussion-actions-popover\"\r\n >\r\n <div class=\"flex min-w-44 flex-col py-1\">\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-surface-700 transition-colors hover:bg-surface-100 disabled:cursor-not-allowed disabled:opacity-50\"\r\n [disabled]=\"refreshing() || loadingInitial()\"\r\n (click)=\"actionsPopover.hide(); refreshThread()\"\r\n >\r\n <mt-icon icon=\"arrow.refresh-cw-01\"></mt-icon>\r\n\r\n <span>{{ refreshing() ? \"Refreshing...\" : \"Refresh\" }}</span>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-surface-700 transition-colors hover:bg-surface-100 disabled:cursor-not-allowed disabled:opacity-50\"\r\n [disabled]=\"markingRead() || !hasUnread()\"\r\n (click)=\"actionsPopover.hide(); markRead()\"\r\n >\r\n <mt-icon icon=\"general.check\"></mt-icon>\r\n\r\n <span>Mark read</span>\r\n </button>\r\n\r\n @if (showParticipants()) {\r\n <button\r\n type=\"button\"\r\n class=\"flex cursor-pointer items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-surface-700 transition-colors hover:bg-surface-100\"\r\n (click)=\"actionsPopover.hide(); toggleParticipantsPanel()\"\r\n >\r\n <mt-icon icon=\"user.users-01\"></mt-icon>\r\n\r\n <span>{{\r\n participantsExpanded()\r\n ? \"Hide participants\"\r\n : \"Show participants\"\r\n }}</span>\r\n </button>\r\n }\r\n </div>\r\n </p-popover>\r\n </div>\r\n </header>\r\n\r\n @if (showParticipants() && participantsExpanded()) {\r\n <section class=\"border-b border-surface-200 px-4 py-3\">\r\n @if (participantsLoading()) {\r\n <span class=\"text-xs text-surface-500\">Loading participants...</span>\r\n } @else if (participantEntities().length === 0) {\r\n <span class=\"text-xs text-surface-500\">No participants yet.</span>\r\n } @else {\r\n <div class=\"max-h-48 overflow-y-auto pr-1\">\r\n <mt-entities-preview [entities]=\"participantEntities()\" />\r\n </div>\r\n }\r\n </section>\r\n }\r\n\r\n @if (errorMessage(); as error) {\r\n <div\r\n class=\"border-b border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700\"\r\n >\r\n {{ error }}\r\n </div>\r\n }\r\n\r\n <div class=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\r\n <div\r\n #viewport\r\n class=\"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain bg-surface-50/60 px-3 py-3\"\r\n (scroll)=\"onViewportScroll()\"\r\n >\r\n @if (hasMore()) {\r\n <div class=\"flex justify-center py-1\">\r\n <mt-button\r\n icon=\"arrow.chevron-up\"\r\n [label]=\"loadingMore() ? 'Loading...' : 'Load older messages'\"\r\n size=\"small\"\r\n [outlined]=\"true\"\r\n [loading]=\"loadingMore()\"\r\n (onClick)=\"loadOlder()\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (loadingInitial()) {\r\n <div class=\"space-y-2\">\r\n @for (item of [1, 2, 3, 4]; track item) {\r\n <div class=\"h-16 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (commentsAsc().length === 0) {\r\n <div class=\"rounded-2xl p-8 text-center\">\r\n <h4 class=\"text-sm font-semibold text-surface-700\">\r\n No comments yet\r\n </h4>\r\n <p class=\"mt-1 text-xs text-surface-500\">Start the conversation.</p>\r\n </div>\r\n } @else {\r\n @for (comment of commentsAsc(); track trackComment($index, comment)) {\r\n @if (firstUnreadCommentId() === comment.id) {\r\n <div class=\"my-1 flex items-center justify-center gap-2\">\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n <span\r\n class=\"rounded-full border border-red-200 bg-red-50 px-2 py-0.5 text-[0.67rem] font-bold leading-none text-red-700\"\r\n >Unread messages</span\r\n >\r\n <span\r\n class=\"flex-1 border-t border-dashed border-red-300\"\r\n ></span>\r\n </div>\r\n }\r\n\r\n <article\r\n class=\"group flex gap-2\"\r\n [class.justify-end]=\"isOwnComment(comment)\"\r\n >\r\n @if (!isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"max-w-[90%] min-w-[30rem] rounded-[1.35rem] border border-surface-200 bg-white px-3 py-2.5 shadow-[0_8px_24px_color-mix(in_srgb,black_4%,transparent)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_7%,white)]]=\"\r\n isOwnComment(comment)\r\n \"\r\n >\r\n <div\r\n class=\"mb-1 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <div\r\n class=\"text-xs w-full text-surface-600 flex justify-between\"\r\n >\r\n <div class=\"font-semibold text-surface-900\">\r\n {{ comment.createdBy }}\r\n </div>\r\n <div>\r\n <span>{{\r\n comment.createdAt | date: \"MMM d, y h:mm a\"\r\n }}</span>\r\n @if (comment.updatedAt) {\r\n <span class=\"ml-1 text-[11px] text-surface-500\"\r\n >(edited)</span\r\n >\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (getParentComment(comment); as parentComment) {\r\n <button\r\n type=\"button\"\r\n class=\"mb-2 block w-full rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-left text-xs text-surface-600\"\r\n (click)=\"openReply(parentComment)\"\r\n >\r\n <span class=\"font-semibold\">{{\r\n parentComment.createdBy\r\n }}</span>\r\n <span class=\"mx-1\">:</span>\r\n <span class=\"line-clamp-1\">{{\r\n parentComment.comment\r\n }}</span>\r\n </button>\r\n }\r\n\r\n @if (editingCommentId() === comment.id) {\r\n <div class=\"relative\">\r\n <textarea\r\n rows=\"3\"\r\n class=\"w-full min-h-[5.25rem] resize-y rounded-2xl border border-surface-300 bg-white px-3 py-2.5 text-[0.86rem] leading-[1.45] text-surface-900 outline-none focus-visible:outline-2 focus-visible:outline-[color-mix(in_srgb,var(--p-primary-color)_32%,transparent)] focus-visible:outline-offset-1\"\r\n [ngModel]=\"editText()\"\r\n (ngModelChange)=\"editText.set($event)\"\r\n (input)=\"onEditInput($event)\"\r\n (keyup)=\"onEditCaretEvent($event)\"\r\n (click)=\"onEditCaretEvent($event)\"\r\n (scroll)=\"onEditCaretEvent($event)\"\r\n (keydown)=\"onEditKeydown($event)\"\r\n ></textarea>\r\n\r\n @if (\r\n mentionSession()?.mode === \"edit\" &&\r\n mentionSession()?.editCommentId === comment.id\r\n ) {\r\n <div\r\n class=\"absolute top-0 left-0 z-20 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span\r\n class=\"flex min-w-0 flex-1 flex-col gap-0.5\"\r\n >\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2\"\r\n >\r\n <span class=\"text-xs text-surface-500\"\r\n >{{ editText().length }}/10000</span\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-surface-300 bg-transparent px-3 py-2 text-xs font-semibold leading-none text-surface-700 transition-colors hover:border-[color-mix(in_srgb,var(--p-primary-color)_24%,transparent)] hover:bg-[color-mix(in_srgb,var(--p-primary-color)_8%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"cancelEdit()\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-[0.65rem] border border-[color-mix(in_srgb,var(--p-primary-color)_26%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_12%,white)] px-3 py-2 text-xs font-bold leading-none text-[color-mix(in_srgb,var(--p-primary-color)_76%,black)] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_17%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"!canSaveEdit()\"\r\n (click)=\"saveEdit()\"\r\n >\r\n {{ savingEdit() ? \"Saving...\" : \"Save\" }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"whitespace-pre-wrap pt-1 break-words text-sm leading-6\"\r\n >\r\n @for (\r\n segment of getCommentSegments(comment);\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n [attr.data-user-id]=\"segment.userId || null\"\r\n >\r\n {{ segment.text }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (comment.attachments.length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2\">\r\n @for (\r\n attachment of comment.attachments;\r\n track attachment.id\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex max-w-full items-center gap-1.5 rounded-lg border border-surface-300 px-2 py-1 text-[0.72rem] hover:bg-surface-50\"\r\n (click)=\"downloadAttachment(comment, attachment)\"\r\n >\r\n <span class=\"truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n <span class=\"text-[11px] text-surface-500\"\r\n >({{ attachment.size | number }} bytes)</span\r\n >\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"mt-2 flex flex-wrap items-center gap-1\">\r\n @if (!comment.isSystem) {\r\n <button\r\n type=\"button\"\r\n class=\"cursor-pointer rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openReply(comment)\"\r\n >\r\n Reply\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"startEdit(comment)\"\r\n >\r\n Edit\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-red-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n [disabled]=\"isDeleting(comment.id)\"\r\n (click)=\"deleteComment(comment)\"\r\n >\r\n {{ isDeleting(comment.id) ? \"Deleting...\" : \"Delete\" }}\r\n </button>\r\n }\r\n @if (comment.updatedAt) {\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"openRevisions(comment)\"\r\n >\r\n History\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isOwnComment(comment)) {\r\n <div\r\n class=\"mt-0.5 inline-flex h-[1.9rem] w-[1.9rem] shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_20%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.74rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_75%,black)]\"\r\n >\r\n {{ getAvatarText(comment) }}\r\n </div>\r\n }\r\n </article>\r\n }\r\n }\r\n </div>\r\n\r\n <footer\r\n class=\"z-10 shrink-0 border-t border-surface-200 bg-content px-3 py-3\"\r\n >\r\n @if (replyToComment(); as replyComment) {\r\n <div\r\n class=\"mb-2 flex items-center justify-between rounded-xl border border-surface-200 bg-surface-50 px-2 py-1\"\r\n >\r\n <div class=\"min-w-0 text-xs text-surface-600\">\r\n <span class=\"font-semibold\"\r\n >Replying to {{ replyComment.createdBy }}</span\r\n >\r\n <p class=\"truncate\">{{ replyComment.comment }}</p>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"clearReply()\"\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"rounded-[1.5rem] border border-surface-300 bg-white p-2.5 shadow-sm\"\r\n >\r\n <div class=\"relative\">\r\n <textarea\r\n #composerInput\r\n rows=\"2\"\r\n class=\"w-full min-h-[3.75rem] resize-none border-0 bg-transparent px-2 pb-10 pr-24 pt-1 text-[0.9rem] leading-[1.45] text-surface-900 outline-none\"\r\n [disabled]=\"disabled() || posting()\"\r\n [ngModel]=\"composerText()\"\r\n (ngModelChange)=\"composerText.set($event)\"\r\n (input)=\"onComposerInput($event)\"\r\n (keyup)=\"onComposerCaretEvent($event)\"\r\n (click)=\"onComposerCaretEvent($event)\"\r\n (scroll)=\"onComposerCaretEvent($event)\"\r\n (keydown)=\"onComposerKeydown($event)\"\r\n [placeholder]=\"placeholder()\"\r\n ></textarea>\r\n\r\n <div class=\"absolute right-1 bottom-1 flex items-center gap-2\">\r\n @if (allowAttachments()) {\r\n <mt-button\r\n icon=\"file.paperclip\"\r\n size=\"small\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n [rounded]=\"true\"\r\n styleClass=\"mt-discussion-icon-btn\"\r\n [disabled]=\"disabled() || posting()\"\r\n (onClick)=\"browseAttachments()\"\r\n />\r\n }\r\n\r\n <mt-button\r\n icon=\"communication.send-01\"\r\n size=\"small\"\r\n [rounded]=\"true\"\r\n styleClass=\"mt-discussion-send-btn\"\r\n [loading]=\"posting()\"\r\n [disabled]=\"!canSend()\"\r\n (onClick)=\"sendComment()\"\r\n />\r\n </div>\r\n\r\n @if (mentionSession()?.mode === \"composer\") {\r\n <div\r\n class=\"absolute top-0 left-0 z-30 min-w-56 overflow-auto rounded-xl border border-surface-300 bg-white shadow-[0_10px_40px_color-mix(in_srgb,black_14%,transparent)]\"\r\n [ngStyle]=\"mentionMenuStyle()\"\r\n [class.origin-bottom-left]=\"\r\n mentionMenuPosition()?.placement === 'above'\r\n \"\r\n >\r\n @if (mentionLoading()) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n Searching...\r\n </div>\r\n } @else if (mentionCandidates().length === 0) {\r\n <div class=\"px-3 py-2 text-xs text-surface-500\">\r\n No matches\r\n </div>\r\n } @else {\r\n @for (\r\n candidate of mentionCandidates();\r\n track candidate.userId;\r\n let i = $index\r\n ) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-start gap-2 bg-transparent px-2.5 py-2 text-left text-[0.78rem] transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]\"\r\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\r\n mentionActiveIndex() === i\r\n \"\r\n (click)=\"selectMention(candidate)\"\r\n >\r\n <span\r\n class=\"mt-px inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--p-primary-color)_18%,transparent)] bg-[color-mix(in_srgb,var(--p-primary-color)_16%,white)] text-[0.7rem] font-bold text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)]\"\r\n >\r\n {{ getMentionAvatarText(candidate) }}\r\n </span>\r\n <span class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\r\n <span\r\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\r\n >{{\r\n candidate.displayName ||\r\n candidate.userName ||\r\n candidate.userId\r\n }}</span\r\n >\r\n <span\r\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\r\n >\r\n @{{ candidate.userId }}\r\n </span>\r\n </span>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (composerAttachments().length > 0) {\r\n <div class=\"mt-2 flex flex-wrap gap-2 px-1\">\r\n @for (attachment of composerAttachments(); track attachment.id) {\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 px-2.5 py-1.5 text-xs\"\r\n >\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"max-w-[12rem] truncate\">{{\r\n attachment.fileName\r\n }}</span>\r\n @if (attachment.status === \"uploading\") {\r\n <span class=\"text-surface-500\"\r\n >{{ attachment.progress }}%</span\r\n >\r\n } @else if (attachment.status === \"failed\") {\r\n <span class=\"text-red-600\">Failed</span>\r\n } @else {\r\n <span class=\"text-emerald-600\">Ready</span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"rounded-lg bg-transparent px-2 py-1 text-[0.72rem] font-semibold leading-none text-surface-600 transition-colors hover:bg-[color-mix(in_srgb,var(--p-primary-color)_10%,white)] hover:text-[color-mix(in_srgb,var(--p-primary-color)_74%,black)] disabled:cursor-not-allowed disabled:opacity-55\"\r\n (click)=\"removeAttachment(attachment.id)\"\r\n >\r\n Remove\r\n </button>\r\n </div>\r\n @if (attachment.error) {\r\n <p class=\"text-[11px] text-red-600\">\r\n {{ attachment.error }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (composerText().length > 10000) {\r\n <div class=\"mt-2 px-1 text-xs font-medium text-red-600\">\r\n Comment cannot exceed 10000 characters.\r\n </div>\r\n }\r\n </div>\r\n </footer>\r\n </div>\r\n </div>\r\n</mt-card>\r\n\r\n<p-dialog\r\n [visible]=\"revisionsDialogVisible()\"\r\n (visibleChange)=\"revisionsDialogVisible.set($event)\"\r\n [modal]=\"true\"\r\n [dismissableMask]=\"true\"\r\n [draggable]=\"false\"\r\n [resizable]=\"false\"\r\n [style]=\"{ width: 'min(42rem, 92vw)' }\"\r\n header=\"Comment history\"\r\n>\r\n @if (revisionLoading()) {\r\n <div class=\"space-y-2\">\r\n @for (row of [1, 2, 3]; track row) {\r\n <div class=\"h-12 animate-pulse rounded-xl bg-surface-100\"></div>\r\n }\r\n </div>\r\n } @else if (visibleRevisions().length === 0) {\r\n <p class=\"text-sm text-surface-500\">No revision snapshots.</p>\r\n } @else {\r\n <div class=\"max-h-[55vh] space-y-2 overflow-y-auto pr-1\">\r\n @for (revision of visibleRevisions(); track revision.id) {\r\n <article class=\"rounded-xl border border-surface-200 p-3\">\r\n <div class=\"mb-1 text-xs text-surface-500\">\r\n Revision #{{ revision.revisionNumber }} •\r\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} •\r\n {{ revision.createdBy }}\r\n </div>\r\n <div class=\"whitespace-pre-wrap break-words text-sm leading-6\">\r\n @for (\r\n segment of getCommentSegments({\r\n id: revision.id,\r\n moduleType: selectedRevisionComment()?.moduleType || \"\",\r\n recordId: selectedRevisionComment()?.recordId || 0,\r\n parentCommentId: null,\r\n comment: revision.comment,\r\n isSystem: false,\r\n createdAt: revision.createdAt,\r\n updatedAt: null,\r\n createdBy: revision.createdBy,\r\n updatedBy: null,\r\n attachments: [],\r\n mentions: revision.mentions,\r\n });\r\n track $index\r\n ) {\r\n <span\r\n [ngClass]=\"\r\n segment.isMention\r\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\r\n : ''\r\n \"\r\n >{{ segment.text }}</span\r\n >\r\n }\r\n </div>\r\n </article>\r\n }\r\n </div>\r\n }\r\n</p-dialog>\r\n", styles: [":host{display:block;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div{display:flex;flex-direction:column;height:100%;min-height:0}:host ::ng-deep .mt-discussion-card>div>div.flex-1{display:flex;flex-direction:column;min-height:0;overflow:hidden}:host ::ng-deep .mt-discussion-actions-popover .p-popover-content{padding:.25rem}:host ::ng-deep .mt-discussion-icon-btn,:host ::ng-deep .mt-discussion-send-btn{height:2.25rem;width:2.25rem;min-width:2.25rem}:host ::ng-deep .mt-discussion-send-btn{box-shadow:0 10px 24px color-mix(in srgb,var(--p-primary-color) 22%,transparent)}\n"] }]
|
|
1644
1638
|
}], ctorParameters: () => [], propDecorators: { moduleType: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleType", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: false }] }], requestContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestContext", required: false }] }], mentionableUsers: [{ type: i0.Input, args: [{ isSignal: true, alias: "mentionableUsers", required: false }] }], mentionSearchEndpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "mentionSearchEndpoint", required: false }] }], mentionSearchParam: [{ type: i0.Input, args: [{ isSignal: true, alias: "mentionSearchParam", required: false }] }], mentionSearchDataPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "mentionSearchDataPath", required: false }] }], allowAttachments: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowAttachments", required: false }] }], uploadEndpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadEndpoint", required: false }] }], attachmentDownloadEndpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachmentDownloadEndpoint", required: false }] }], showParticipants: [{ type: i0.Input, args: [{ isSignal: true, alias: "showParticipants", required: false }] }], autoMarkRead: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoMarkRead", required: false }] }], refreshIntervalMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "refreshIntervalMs", required: false }] }], styleClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "styleClass", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], errored: [{ type: i0.Output, args: ["errored"] }], commentCreated: [{ type: i0.Output, args: ["commentCreated"] }], commentUpdated: [{ type: i0.Output, args: ["commentUpdated"] }], commentDeleted: [{ type: i0.Output, args: ["commentDeleted"] }], readStateChanged: [{ type: i0.Output, args: ["readStateChanged"] }], viewportRef: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], composerInputRef: [{ type: i0.ViewChild, args: ['composerInput', { isSignal: true }] }], attachmentInputRef: [{ type: i0.ViewChild, args: ['attachmentInput', { isSignal: true }] }] } });
|
|
1645
1639
|
|
|
1646
1640
|
/**
|