@masterteam/discussion 0.0.1 → 0.0.2

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.
@@ -1626,7 +1626,7 @@ class DiscussionThread {
1626
1626
  return `${fileName}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1627
1627
  }
1628
1628
  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\n class=\"mt-discussion-card h-full min-h-0 w-full overflow-hidden shadow-sm\"\n [paddingless]=\"true\"\n [ngClass]=\"styleClass()\"\n>\n <div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\n <input\n #attachmentInput\n type=\"file\"\n class=\"hidden\"\n multiple\n (change)=\"onAttachmentSelected($event)\"\n />\n\n <header\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-200 px-4 py-3\"\n >\n <div class=\"min-w-0\">\n <h3 class=\"truncate text-sm font-semibold uppercase tracking-[0.08em]\">\n {{ title() }}\n </h3>\n @if (subtitle()) {\n <p class=\"mt-1 text-xs text-surface-500\">{{ subtitle() }}</p>\n }\n <p class=\"mt-1 text-xs text-surface-500\">\n {{ threadKey() || \"Discussion\" }}\n </p>\n </div>\n\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (unreadCount() > 0) {\n <span\n class=\"inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-semibold text-red-700\"\n >\n {{ unreadCount() }} unread\n </span>\n } @else {\n <span\n class=\"inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-semibold text-emerald-700\"\n >\n Up to date\n </span>\n }\n\n <mt-button\n icon=\"arrow.refresh-cw-01\"\n [label]=\"refreshing() ? 'Refreshing...' : 'Refresh'\"\n size=\"small\"\n [outlined]=\"true\"\n [loading]=\"refreshing()\"\n [disabled]=\"loadingInitial()\"\n (onClick)=\"refreshThread()\"\n />\n <mt-button\n icon=\"general.check\"\n label=\"Mark read\"\n size=\"small\"\n [outlined]=\"true\"\n [disabled]=\"markingRead() || !hasUnread()\"\n (onClick)=\"markRead()\"\n />\n <mt-button\n icon=\"communication.mail-01\"\n label=\"Mark unread\"\n size=\"small\"\n [outlined]=\"true\"\n [disabled]=\"markingRead()\"\n (onClick)=\"markUnread()\"\n />\n @if (showParticipants()) {\n <mt-button\n icon=\"user.users-01\"\n [label]=\"'Participants (' + participants().length + ')'\"\n size=\"small\"\n [outlined]=\"true\"\n (onClick)=\"toggleParticipantsPanel()\"\n />\n }\n </div>\n </header>\n\n @if (showParticipants() && participantsExpanded()) {\n <section class=\"border-b border-surface-200 px-4 py-3\">\n @if (participantsLoading()) {\n <span class=\"text-xs text-surface-500\">Loading participants...</span>\n } @else if (participantEntities().length === 0) {\n <span class=\"text-xs text-surface-500\">No participants yet.</span>\n } @else {\n <div class=\"max-h-48 overflow-y-auto pr-1\">\n <mt-entities-preview [entities]=\"participantEntities()\" />\n </div>\n }\n </section>\n }\n\n @if (errorMessage(); as error) {\n <div\n class=\"border-b border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700\"\n >\n {{ error }}\n </div>\n }\n\n <div class=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\n <div\n #viewport\n class=\"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-3 py-3\"\n (scroll)=\"onViewportScroll()\"\n >\n @if (hasMore()) {\n <div class=\"flex justify-center py-1\">\n <mt-button\n icon=\"arrow.chevron-up\"\n [label]=\"loadingMore() ? 'Loading...' : 'Load older messages'\"\n size=\"small\"\n [outlined]=\"true\"\n [loading]=\"loadingMore()\"\n (onClick)=\"loadOlder()\"\n />\n </div>\n }\n\n @if (loadingInitial()) {\n <div class=\"space-y-2\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"h-16 animate-pulse rounded-xl bg-surface-100\"></div>\n }\n </div>\n } @else if (commentsAsc().length === 0) {\n <div\n class=\"rounded-2xl border border-dashed border-surface-300 p-8 text-center\"\n >\n <h4 class=\"text-sm font-semibold text-surface-700\">\n No comments yet\n </h4>\n <p class=\"mt-1 text-xs text-surface-500\">Start the conversation.</p>\n </div>\n } @else {\n @for (comment of commentsAsc(); track trackComment($index, comment)) {\n @if (firstUnreadCommentId() === comment.id) {\n <div class=\"my-1 flex items-center justify-center gap-2\">\n <span\n class=\"flex-1 border-t border-dashed border-red-300\"\n ></span>\n <span\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\"\n >Unread messages</span\n >\n <span\n class=\"flex-1 border-t border-dashed border-red-300\"\n ></span>\n </div>\n }\n\n <article\n class=\"group flex gap-2\"\n [class.justify-end]=\"isOwnComment(comment)\"\n >\n @if (!isOwnComment(comment)) {\n <div\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)]\"\n >\n {{ getAvatarText(comment) }}\n </div>\n }\n\n <div\n class=\"max-w-[88%] min-w-[16rem] rounded-2xl border border-surface-200 px-3 py-2\"\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_7%,white)]]=\"\n isOwnComment(comment)\n \"\n >\n <div\n class=\"mb-1 flex flex-wrap items-center justify-between gap-2\"\n >\n <div class=\"text-xs text-surface-600\">\n <span class=\"font-semibold text-surface-900\">{{\n comment.createdBy\n }}</span>\n <span class=\"mx-1\">&bull;</span>\n <span>{{\n comment.createdAt | date: \"MMM d, y h:mm a\"\n }}</span>\n @if (comment.updatedAt) {\n <span class=\"ml-1 text-[11px] text-surface-500\"\n >(edited)</span\n >\n }\n </div>\n </div>\n\n @if (getParentComment(comment); as parentComment) {\n <button\n type=\"button\"\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\"\n (click)=\"openReply(parentComment)\"\n >\n <span class=\"font-semibold\">{{\n parentComment.createdBy\n }}</span>\n <span class=\"mx-1\">:</span>\n <span class=\"line-clamp-1\">{{\n parentComment.comment\n }}</span>\n </button>\n }\n\n @if (editingCommentId() === comment.id) {\n <div class=\"relative\">\n <textarea\n rows=\"3\"\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\"\n [ngModel]=\"editText()\"\n (ngModelChange)=\"editText.set($event)\"\n (input)=\"onEditInput($event)\"\n (keyup)=\"onEditCaretEvent($event)\"\n (click)=\"onEditCaretEvent($event)\"\n (scroll)=\"onEditCaretEvent($event)\"\n (keydown)=\"onEditKeydown($event)\"\n ></textarea>\n\n @if (\n mentionSession()?.mode === \"edit\" &&\n mentionSession()?.editCommentId === comment.id\n ) {\n <div\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)]\"\n [ngStyle]=\"mentionMenuStyle()\"\n [class.origin-bottom-left]=\"\n mentionMenuPosition()?.placement === 'above'\n \"\n >\n @if (mentionLoading()) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n Searching...\n </div>\n } @else if (mentionCandidates().length === 0) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n No matches\n </div>\n } @else {\n @for (\n candidate of mentionCandidates();\n track candidate.userId;\n let i = $index\n ) {\n <button\n type=\"button\"\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)]\"\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\n mentionActiveIndex() === i\n \"\n (click)=\"selectMention(candidate)\"\n >\n <span\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)]\"\n >\n {{ getMentionAvatarText(candidate) }}\n </span>\n <span\n class=\"flex min-w-0 flex-1 flex-col gap-0.5\"\n >\n <span\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\n >{{\n candidate.displayName ||\n candidate.userName ||\n candidate.userId\n }}</span\n >\n <span\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\n >\n @{{ candidate.userId }}\n </span>\n </span>\n </button>\n }\n }\n </div>\n }\n </div>\n\n <div\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2\"\n >\n <span class=\"text-xs text-surface-500\"\n >{{ editText().length }}/10000</span\n >\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\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\"\n (click)=\"cancelEdit()\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\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\"\n [disabled]=\"!canSaveEdit()\"\n (click)=\"saveEdit()\"\n >\n {{ savingEdit() ? \"Saving...\" : \"Save\" }}\n </button>\n </div>\n </div>\n } @else {\n <div\n class=\"whitespace-pre-wrap break-words text-sm leading-6\"\n >\n @for (\n segment of getCommentSegments(comment);\n track $index\n ) {\n <span\n [ngClass]=\"\n segment.isMention\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\n : ''\n \"\n [attr.data-user-id]=\"segment.userId || null\"\n >\n {{ segment.text }}\n </span>\n }\n </div>\n\n @if (comment.attachments.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-2\">\n @for (\n attachment of comment.attachments;\n track attachment.id\n ) {\n <button\n type=\"button\"\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\"\n (click)=\"downloadAttachment(comment, attachment)\"\n >\n <span class=\"truncate\">{{\n attachment.fileName\n }}</span>\n <span class=\"text-[11px] text-surface-500\"\n >({{ attachment.size | number }} bytes)</span\n >\n </button>\n }\n </div>\n }\n\n <div\n class=\"mt-2 flex flex-wrap items-center gap-1 opacity-100 md:opacity-0 md:group-hover:opacity-100\"\n >\n <button\n type=\"button\"\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\"\n (click)=\"openReply(comment)\"\n >\n Reply\n </button>\n @if (canEditComment(comment)) {\n <button\n type=\"button\"\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\"\n (click)=\"startEdit(comment)\"\n >\n Edit\n </button>\n }\n @if (canDeleteComment(comment)) {\n <button\n type=\"button\"\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\"\n [disabled]=\"isDeleting(comment.id)\"\n (click)=\"deleteComment(comment)\"\n >\n {{ isDeleting(comment.id) ? \"Deleting...\" : \"Delete\" }}\n </button>\n }\n @if (comment.updatedAt) {\n <button\n type=\"button\"\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\"\n (click)=\"openRevisions(comment)\"\n >\n History\n </button>\n }\n </div>\n }\n </div>\n\n @if (isOwnComment(comment)) {\n <div\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)]\"\n >\n {{ getAvatarText(comment) }}\n </div>\n }\n </article>\n }\n }\n </div>\n\n <footer\n class=\"z-10 shrink-0 border-t border-surface-200 bg-content px-3 py-3\"\n >\n @if (replyToComment(); as replyComment) {\n <div\n class=\"mb-2 flex items-center justify-between rounded-xl border border-surface-200 bg-surface-50 px-2 py-1\"\n >\n <div class=\"min-w-0 text-xs text-surface-600\">\n <span class=\"font-semibold\"\n >Replying to {{ replyComment.createdBy }}</span\n >\n <p class=\"truncate\">{{ replyComment.comment }}</p>\n </div>\n <button\n type=\"button\"\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\"\n (click)=\"clearReply()\"\n >\n Cancel\n </button>\n </div>\n }\n\n <div\n class=\"rounded-2xl border border-surface-300 bg-white p-2 shadow-sm\"\n >\n <div class=\"relative\">\n <textarea\n #composerInput\n rows=\"3\"\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\"\n [disabled]=\"disabled() || posting()\"\n [ngModel]=\"composerText()\"\n (ngModelChange)=\"composerText.set($event)\"\n (input)=\"onComposerInput($event)\"\n (keyup)=\"onComposerCaretEvent($event)\"\n (click)=\"onComposerCaretEvent($event)\"\n (scroll)=\"onComposerCaretEvent($event)\"\n (keydown)=\"onComposerKeydown($event)\"\n [placeholder]=\"placeholder()\"\n ></textarea>\n\n @if (mentionSession()?.mode === \"composer\") {\n <div\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)]\"\n [ngStyle]=\"mentionMenuStyle()\"\n [class.origin-bottom-left]=\"\n mentionMenuPosition()?.placement === 'above'\n \"\n >\n @if (mentionLoading()) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n Searching...\n </div>\n } @else if (mentionCandidates().length === 0) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n No matches\n </div>\n } @else {\n @for (\n candidate of mentionCandidates();\n track candidate.userId;\n let i = $index\n ) {\n <button\n type=\"button\"\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)]\"\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\n mentionActiveIndex() === i\n \"\n (click)=\"selectMention(candidate)\"\n >\n <span\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)]\"\n >\n {{ getMentionAvatarText(candidate) }}\n </span>\n <span class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\n >{{\n candidate.displayName ||\n candidate.userName ||\n candidate.userId\n }}</span\n >\n <span\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\n >\n @{{ candidate.userId }}\n </span>\n </span>\n </button>\n }\n }\n </div>\n }\n </div>\n\n @if (composerAttachments().length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-2 px-1\">\n @for (attachment of composerAttachments(); track attachment.id) {\n <div\n class=\"rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-xs\"\n >\n <div class=\"flex items-center gap-2\">\n <span class=\"max-w-[12rem] truncate\">{{\n attachment.fileName\n }}</span>\n @if (attachment.status === \"uploading\") {\n <span class=\"text-surface-500\"\n >{{ attachment.progress }}%</span\n >\n } @else if (attachment.status === \"failed\") {\n <span class=\"text-red-600\">Failed</span>\n } @else {\n <span class=\"text-emerald-600\">Ready</span>\n }\n <button\n type=\"button\"\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\"\n (click)=\"removeAttachment(attachment.id)\"\n >\n Remove\n </button>\n </div>\n @if (attachment.error) {\n <p class=\"text-[11px] text-red-600\">\n {{ attachment.error }}\n </p>\n }\n </div>\n }\n </div>\n }\n\n <div\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2 border-t border-surface-200 px-1 pt-2\"\n >\n <div class=\"flex items-center gap-2 text-xs text-surface-500\">\n <span>Type @ to mention people</span>\n <span>&bull;</span>\n <span>{{ composerText().length }}/10000</span>\n @if (charactersLeft() < 0) {\n <span class=\"font-semibold text-red-600\">Too long</span>\n }\n </div>\n\n <div class=\"flex items-center gap-2\">\n @if (allowAttachments()) {\n <mt-button\n icon=\"file.paperclip\"\n label=\"Attach\"\n size=\"small\"\n [outlined]=\"true\"\n [disabled]=\"disabled() || posting()\"\n (onClick)=\"browseAttachments()\"\n />\n }\n <mt-button\n icon=\"communication.send-01\"\n [label]=\"posting() ? 'Sending...' : 'Send'\"\n size=\"small\"\n [loading]=\"posting()\"\n [disabled]=\"!canSend()\"\n (onClick)=\"sendComment()\"\n />\n </div>\n </div>\n </div>\n </footer>\n </div>\n </div>\n</mt-card>\n\n<p-dialog\n [visible]=\"revisionsDialogVisible()\"\n (visibleChange)=\"revisionsDialogVisible.set($event)\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n [style]=\"{ width: 'min(42rem, 92vw)' }\"\n header=\"Comment history\"\n>\n @if (revisionLoading()) {\n <div class=\"space-y-2\">\n @for (row of [1, 2, 3]; track row) {\n <div class=\"h-12 animate-pulse rounded-xl bg-surface-100\"></div>\n }\n </div>\n } @else if (visibleRevisions().length === 0) {\n <p class=\"text-sm text-surface-500\">No revision snapshots.</p>\n } @else {\n <div class=\"max-h-[55vh] space-y-2 overflow-y-auto pr-1\">\n @for (revision of visibleRevisions(); track revision.id) {\n <article class=\"rounded-xl border border-surface-200 p-3\">\n <div class=\"mb-1 text-xs text-surface-500\">\n Revision #{{ revision.revisionNumber }} &bull;\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} &bull;\n {{ revision.createdBy }}\n </div>\n <div class=\"whitespace-pre-wrap break-words text-sm leading-6\">\n @for (\n segment of getCommentSegments({\n id: revision.id,\n moduleType: selectedRevisionComment()?.moduleType || \"\",\n recordId: selectedRevisionComment()?.recordId || 0,\n parentCommentId: null,\n comment: revision.comment,\n isSystem: false,\n createdAt: revision.createdAt,\n updatedAt: null,\n createdBy: revision.createdBy,\n updatedBy: null,\n attachments: [],\n mentions: revision.mentions,\n });\n track $index\n ) {\n <span\n [ngClass]=\"\n segment.isMention\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\n : ''\n \"\n >{{ segment.text }}</span\n >\n }\n </div>\n </article>\n }\n </div>\n }\n</p-dialog>\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 });
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\">&bull;</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>&bull;</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 }} &bull;\r\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} &bull;\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 });
1630
1630
  }
1631
1631
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: DiscussionThread, decorators: [{
1632
1632
  type: Component,
@@ -1640,7 +1640,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
1640
1640
  EntitiesPreview,
1641
1641
  ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1642
1642
  class: 'block h-full min-h-0',
1643
- }, template: "<mt-card\n class=\"mt-discussion-card h-full min-h-0 w-full overflow-hidden shadow-sm\"\n [paddingless]=\"true\"\n [ngClass]=\"styleClass()\"\n>\n <div class=\"flex h-full min-h-0 flex-col overflow-hidden\">\n <input\n #attachmentInput\n type=\"file\"\n class=\"hidden\"\n multiple\n (change)=\"onAttachmentSelected($event)\"\n />\n\n <header\n class=\"flex flex-wrap items-start justify-between gap-3 border-b border-surface-200 px-4 py-3\"\n >\n <div class=\"min-w-0\">\n <h3 class=\"truncate text-sm font-semibold uppercase tracking-[0.08em]\">\n {{ title() }}\n </h3>\n @if (subtitle()) {\n <p class=\"mt-1 text-xs text-surface-500\">{{ subtitle() }}</p>\n }\n <p class=\"mt-1 text-xs text-surface-500\">\n {{ threadKey() || \"Discussion\" }}\n </p>\n </div>\n\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (unreadCount() > 0) {\n <span\n class=\"inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-semibold text-red-700\"\n >\n {{ unreadCount() }} unread\n </span>\n } @else {\n <span\n class=\"inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-semibold text-emerald-700\"\n >\n Up to date\n </span>\n }\n\n <mt-button\n icon=\"arrow.refresh-cw-01\"\n [label]=\"refreshing() ? 'Refreshing...' : 'Refresh'\"\n size=\"small\"\n [outlined]=\"true\"\n [loading]=\"refreshing()\"\n [disabled]=\"loadingInitial()\"\n (onClick)=\"refreshThread()\"\n />\n <mt-button\n icon=\"general.check\"\n label=\"Mark read\"\n size=\"small\"\n [outlined]=\"true\"\n [disabled]=\"markingRead() || !hasUnread()\"\n (onClick)=\"markRead()\"\n />\n <mt-button\n icon=\"communication.mail-01\"\n label=\"Mark unread\"\n size=\"small\"\n [outlined]=\"true\"\n [disabled]=\"markingRead()\"\n (onClick)=\"markUnread()\"\n />\n @if (showParticipants()) {\n <mt-button\n icon=\"user.users-01\"\n [label]=\"'Participants (' + participants().length + ')'\"\n size=\"small\"\n [outlined]=\"true\"\n (onClick)=\"toggleParticipantsPanel()\"\n />\n }\n </div>\n </header>\n\n @if (showParticipants() && participantsExpanded()) {\n <section class=\"border-b border-surface-200 px-4 py-3\">\n @if (participantsLoading()) {\n <span class=\"text-xs text-surface-500\">Loading participants...</span>\n } @else if (participantEntities().length === 0) {\n <span class=\"text-xs text-surface-500\">No participants yet.</span>\n } @else {\n <div class=\"max-h-48 overflow-y-auto pr-1\">\n <mt-entities-preview [entities]=\"participantEntities()\" />\n </div>\n }\n </section>\n }\n\n @if (errorMessage(); as error) {\n <div\n class=\"border-b border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700\"\n >\n {{ error }}\n </div>\n }\n\n <div class=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\n <div\n #viewport\n class=\"min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-3 py-3\"\n (scroll)=\"onViewportScroll()\"\n >\n @if (hasMore()) {\n <div class=\"flex justify-center py-1\">\n <mt-button\n icon=\"arrow.chevron-up\"\n [label]=\"loadingMore() ? 'Loading...' : 'Load older messages'\"\n size=\"small\"\n [outlined]=\"true\"\n [loading]=\"loadingMore()\"\n (onClick)=\"loadOlder()\"\n />\n </div>\n }\n\n @if (loadingInitial()) {\n <div class=\"space-y-2\">\n @for (item of [1, 2, 3, 4]; track item) {\n <div class=\"h-16 animate-pulse rounded-xl bg-surface-100\"></div>\n }\n </div>\n } @else if (commentsAsc().length === 0) {\n <div\n class=\"rounded-2xl border border-dashed border-surface-300 p-8 text-center\"\n >\n <h4 class=\"text-sm font-semibold text-surface-700\">\n No comments yet\n </h4>\n <p class=\"mt-1 text-xs text-surface-500\">Start the conversation.</p>\n </div>\n } @else {\n @for (comment of commentsAsc(); track trackComment($index, comment)) {\n @if (firstUnreadCommentId() === comment.id) {\n <div class=\"my-1 flex items-center justify-center gap-2\">\n <span\n class=\"flex-1 border-t border-dashed border-red-300\"\n ></span>\n <span\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\"\n >Unread messages</span\n >\n <span\n class=\"flex-1 border-t border-dashed border-red-300\"\n ></span>\n </div>\n }\n\n <article\n class=\"group flex gap-2\"\n [class.justify-end]=\"isOwnComment(comment)\"\n >\n @if (!isOwnComment(comment)) {\n <div\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)]\"\n >\n {{ getAvatarText(comment) }}\n </div>\n }\n\n <div\n class=\"max-w-[88%] min-w-[16rem] rounded-2xl border border-surface-200 px-3 py-2\"\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_7%,white)]]=\"\n isOwnComment(comment)\n \"\n >\n <div\n class=\"mb-1 flex flex-wrap items-center justify-between gap-2\"\n >\n <div class=\"text-xs text-surface-600\">\n <span class=\"font-semibold text-surface-900\">{{\n comment.createdBy\n }}</span>\n <span class=\"mx-1\">&bull;</span>\n <span>{{\n comment.createdAt | date: \"MMM d, y h:mm a\"\n }}</span>\n @if (comment.updatedAt) {\n <span class=\"ml-1 text-[11px] text-surface-500\"\n >(edited)</span\n >\n }\n </div>\n </div>\n\n @if (getParentComment(comment); as parentComment) {\n <button\n type=\"button\"\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\"\n (click)=\"openReply(parentComment)\"\n >\n <span class=\"font-semibold\">{{\n parentComment.createdBy\n }}</span>\n <span class=\"mx-1\">:</span>\n <span class=\"line-clamp-1\">{{\n parentComment.comment\n }}</span>\n </button>\n }\n\n @if (editingCommentId() === comment.id) {\n <div class=\"relative\">\n <textarea\n rows=\"3\"\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\"\n [ngModel]=\"editText()\"\n (ngModelChange)=\"editText.set($event)\"\n (input)=\"onEditInput($event)\"\n (keyup)=\"onEditCaretEvent($event)\"\n (click)=\"onEditCaretEvent($event)\"\n (scroll)=\"onEditCaretEvent($event)\"\n (keydown)=\"onEditKeydown($event)\"\n ></textarea>\n\n @if (\n mentionSession()?.mode === \"edit\" &&\n mentionSession()?.editCommentId === comment.id\n ) {\n <div\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)]\"\n [ngStyle]=\"mentionMenuStyle()\"\n [class.origin-bottom-left]=\"\n mentionMenuPosition()?.placement === 'above'\n \"\n >\n @if (mentionLoading()) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n Searching...\n </div>\n } @else if (mentionCandidates().length === 0) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n No matches\n </div>\n } @else {\n @for (\n candidate of mentionCandidates();\n track candidate.userId;\n let i = $index\n ) {\n <button\n type=\"button\"\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)]\"\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\n mentionActiveIndex() === i\n \"\n (click)=\"selectMention(candidate)\"\n >\n <span\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)]\"\n >\n {{ getMentionAvatarText(candidate) }}\n </span>\n <span\n class=\"flex min-w-0 flex-1 flex-col gap-0.5\"\n >\n <span\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\n >{{\n candidate.displayName ||\n candidate.userName ||\n candidate.userId\n }}</span\n >\n <span\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\n >\n @{{ candidate.userId }}\n </span>\n </span>\n </button>\n }\n }\n </div>\n }\n </div>\n\n <div\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2\"\n >\n <span class=\"text-xs text-surface-500\"\n >{{ editText().length }}/10000</span\n >\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\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\"\n (click)=\"cancelEdit()\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\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\"\n [disabled]=\"!canSaveEdit()\"\n (click)=\"saveEdit()\"\n >\n {{ savingEdit() ? \"Saving...\" : \"Save\" }}\n </button>\n </div>\n </div>\n } @else {\n <div\n class=\"whitespace-pre-wrap break-words text-sm leading-6\"\n >\n @for (\n segment of getCommentSegments(comment);\n track $index\n ) {\n <span\n [ngClass]=\"\n segment.isMention\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\n : ''\n \"\n [attr.data-user-id]=\"segment.userId || null\"\n >\n {{ segment.text }}\n </span>\n }\n </div>\n\n @if (comment.attachments.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-2\">\n @for (\n attachment of comment.attachments;\n track attachment.id\n ) {\n <button\n type=\"button\"\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\"\n (click)=\"downloadAttachment(comment, attachment)\"\n >\n <span class=\"truncate\">{{\n attachment.fileName\n }}</span>\n <span class=\"text-[11px] text-surface-500\"\n >({{ attachment.size | number }} bytes)</span\n >\n </button>\n }\n </div>\n }\n\n <div\n class=\"mt-2 flex flex-wrap items-center gap-1 opacity-100 md:opacity-0 md:group-hover:opacity-100\"\n >\n <button\n type=\"button\"\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\"\n (click)=\"openReply(comment)\"\n >\n Reply\n </button>\n @if (canEditComment(comment)) {\n <button\n type=\"button\"\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\"\n (click)=\"startEdit(comment)\"\n >\n Edit\n </button>\n }\n @if (canDeleteComment(comment)) {\n <button\n type=\"button\"\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\"\n [disabled]=\"isDeleting(comment.id)\"\n (click)=\"deleteComment(comment)\"\n >\n {{ isDeleting(comment.id) ? \"Deleting...\" : \"Delete\" }}\n </button>\n }\n @if (comment.updatedAt) {\n <button\n type=\"button\"\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\"\n (click)=\"openRevisions(comment)\"\n >\n History\n </button>\n }\n </div>\n }\n </div>\n\n @if (isOwnComment(comment)) {\n <div\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)]\"\n >\n {{ getAvatarText(comment) }}\n </div>\n }\n </article>\n }\n }\n </div>\n\n <footer\n class=\"z-10 shrink-0 border-t border-surface-200 bg-content px-3 py-3\"\n >\n @if (replyToComment(); as replyComment) {\n <div\n class=\"mb-2 flex items-center justify-between rounded-xl border border-surface-200 bg-surface-50 px-2 py-1\"\n >\n <div class=\"min-w-0 text-xs text-surface-600\">\n <span class=\"font-semibold\"\n >Replying to {{ replyComment.createdBy }}</span\n >\n <p class=\"truncate\">{{ replyComment.comment }}</p>\n </div>\n <button\n type=\"button\"\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\"\n (click)=\"clearReply()\"\n >\n Cancel\n </button>\n </div>\n }\n\n <div\n class=\"rounded-2xl border border-surface-300 bg-white p-2 shadow-sm\"\n >\n <div class=\"relative\">\n <textarea\n #composerInput\n rows=\"3\"\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\"\n [disabled]=\"disabled() || posting()\"\n [ngModel]=\"composerText()\"\n (ngModelChange)=\"composerText.set($event)\"\n (input)=\"onComposerInput($event)\"\n (keyup)=\"onComposerCaretEvent($event)\"\n (click)=\"onComposerCaretEvent($event)\"\n (scroll)=\"onComposerCaretEvent($event)\"\n (keydown)=\"onComposerKeydown($event)\"\n [placeholder]=\"placeholder()\"\n ></textarea>\n\n @if (mentionSession()?.mode === \"composer\") {\n <div\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)]\"\n [ngStyle]=\"mentionMenuStyle()\"\n [class.origin-bottom-left]=\"\n mentionMenuPosition()?.placement === 'above'\n \"\n >\n @if (mentionLoading()) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n Searching...\n </div>\n } @else if (mentionCandidates().length === 0) {\n <div class=\"px-3 py-2 text-xs text-surface-500\">\n No matches\n </div>\n } @else {\n @for (\n candidate of mentionCandidates();\n track candidate.userId;\n let i = $index\n ) {\n <button\n type=\"button\"\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)]\"\n [class.bg-[color-mix(in_srgb,var(--p-primary-color)_11%,white)]]=\"\n mentionActiveIndex() === i\n \"\n (click)=\"selectMention(candidate)\"\n >\n <span\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)]\"\n >\n {{ getMentionAvatarText(candidate) }}\n </span>\n <span class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n class=\"text-[0.79rem] leading-[1.25] font-semibold text-surface-900\"\n >{{\n candidate.displayName ||\n candidate.userName ||\n candidate.userId\n }}</span\n >\n <span\n class=\"inline-flex items-center text-[0.73rem] leading-[1.2] text-surface-500\"\n >\n @{{ candidate.userId }}\n </span>\n </span>\n </button>\n }\n }\n </div>\n }\n </div>\n\n @if (composerAttachments().length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-2 px-1\">\n @for (attachment of composerAttachments(); track attachment.id) {\n <div\n class=\"rounded-xl border border-surface-200 bg-surface-50 px-2 py-1 text-xs\"\n >\n <div class=\"flex items-center gap-2\">\n <span class=\"max-w-[12rem] truncate\">{{\n attachment.fileName\n }}</span>\n @if (attachment.status === \"uploading\") {\n <span class=\"text-surface-500\"\n >{{ attachment.progress }}%</span\n >\n } @else if (attachment.status === \"failed\") {\n <span class=\"text-red-600\">Failed</span>\n } @else {\n <span class=\"text-emerald-600\">Ready</span>\n }\n <button\n type=\"button\"\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\"\n (click)=\"removeAttachment(attachment.id)\"\n >\n Remove\n </button>\n </div>\n @if (attachment.error) {\n <p class=\"text-[11px] text-red-600\">\n {{ attachment.error }}\n </p>\n }\n </div>\n }\n </div>\n }\n\n <div\n class=\"mt-2 flex flex-wrap items-center justify-between gap-2 border-t border-surface-200 px-1 pt-2\"\n >\n <div class=\"flex items-center gap-2 text-xs text-surface-500\">\n <span>Type @ to mention people</span>\n <span>&bull;</span>\n <span>{{ composerText().length }}/10000</span>\n @if (charactersLeft() < 0) {\n <span class=\"font-semibold text-red-600\">Too long</span>\n }\n </div>\n\n <div class=\"flex items-center gap-2\">\n @if (allowAttachments()) {\n <mt-button\n icon=\"file.paperclip\"\n label=\"Attach\"\n size=\"small\"\n [outlined]=\"true\"\n [disabled]=\"disabled() || posting()\"\n (onClick)=\"browseAttachments()\"\n />\n }\n <mt-button\n icon=\"communication.send-01\"\n [label]=\"posting() ? 'Sending...' : 'Send'\"\n size=\"small\"\n [loading]=\"posting()\"\n [disabled]=\"!canSend()\"\n (onClick)=\"sendComment()\"\n />\n </div>\n </div>\n </div>\n </footer>\n </div>\n </div>\n</mt-card>\n\n<p-dialog\n [visible]=\"revisionsDialogVisible()\"\n (visibleChange)=\"revisionsDialogVisible.set($event)\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n [style]=\"{ width: 'min(42rem, 92vw)' }\"\n header=\"Comment history\"\n>\n @if (revisionLoading()) {\n <div class=\"space-y-2\">\n @for (row of [1, 2, 3]; track row) {\n <div class=\"h-12 animate-pulse rounded-xl bg-surface-100\"></div>\n }\n </div>\n } @else if (visibleRevisions().length === 0) {\n <p class=\"text-sm text-surface-500\">No revision snapshots.</p>\n } @else {\n <div class=\"max-h-[55vh] space-y-2 overflow-y-auto pr-1\">\n @for (revision of visibleRevisions(); track revision.id) {\n <article class=\"rounded-xl border border-surface-200 p-3\">\n <div class=\"mb-1 text-xs text-surface-500\">\n Revision #{{ revision.revisionNumber }} &bull;\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} &bull;\n {{ revision.createdBy }}\n </div>\n <div class=\"whitespace-pre-wrap break-words text-sm leading-6\">\n @for (\n segment of getCommentSegments({\n id: revision.id,\n moduleType: selectedRevisionComment()?.moduleType || \"\",\n recordId: selectedRevisionComment()?.recordId || 0,\n parentCommentId: null,\n comment: revision.comment,\n isSystem: false,\n createdAt: revision.createdAt,\n updatedAt: null,\n createdBy: revision.createdBy,\n updatedBy: null,\n attachments: [],\n mentions: revision.mentions,\n });\n track $index\n ) {\n <span\n [ngClass]=\"\n segment.isMention\n ? 'rounded-sm bg-primary-50 px-0.5 font-semibold text-primary-700'\n : ''\n \"\n >{{ segment.text }}</span\n >\n }\n </div>\n </article>\n }\n </div>\n }\n</p-dialog>\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"] }]
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\">&bull;</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>&bull;</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 }} &bull;\r\n {{ revision.createdAt | date: \"MMM d, y h:mm a\" }} &bull;\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"] }]
1644
1644
  }], 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
1645
 
1646
1646
  /**