cax-design-system 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/commentbox/commentbox.d.ts +111 -0
  3. package/commentbox/commentbox.module.d.ts +17 -0
  4. package/commentbox/index.d.ts +5 -0
  5. package/commentbox/public_api.d.ts +2 -0
  6. package/esm2022/card/card.mjs +2 -2
  7. package/esm2022/commentbox/cax-design-system-commentbox.mjs +5 -0
  8. package/esm2022/commentbox/commentbox.mjs +541 -0
  9. package/esm2022/commentbox/commentbox.module.mjs +27 -0
  10. package/esm2022/commentbox/public_api.mjs +3 -0
  11. package/esm2022/inputtext/inputtext.component.mjs +13 -4
  12. package/esm2022/inputtextarea/inputtextarea.component.mjs +8 -4
  13. package/esm2022/table/components/column-filter/column-filter.mjs +2 -2
  14. package/esm2022/upload/cax-design-system-upload.mjs +5 -0
  15. package/esm2022/upload/public_api.mjs +3 -0
  16. package/esm2022/upload/upload.component.mjs +355 -0
  17. package/esm2022/upload/upload.component.module.mjs +21 -0
  18. package/fesm2022/cax-design-system-card.mjs +2 -2
  19. package/fesm2022/cax-design-system-card.mjs.map +1 -1
  20. package/fesm2022/cax-design-system-commentbox.mjs +572 -0
  21. package/fesm2022/cax-design-system-commentbox.mjs.map +1 -0
  22. package/fesm2022/cax-design-system-inputtext.mjs +12 -3
  23. package/fesm2022/cax-design-system-inputtext.mjs.map +1 -1
  24. package/fesm2022/cax-design-system-inputtextarea.mjs +7 -3
  25. package/fesm2022/cax-design-system-inputtextarea.mjs.map +1 -1
  26. package/fesm2022/cax-design-system-table.mjs +1 -1
  27. package/fesm2022/cax-design-system-table.mjs.map +1 -1
  28. package/fesm2022/cax-design-system-upload.mjs +380 -0
  29. package/fesm2022/cax-design-system-upload.mjs.map +1 -0
  30. package/inputtext/inputtext.component.d.ts +4 -1
  31. package/inputtextarea/inputtextarea.component.d.ts +2 -2
  32. package/package.json +156 -144
  33. package/resources/cax.min.scss +1 -1
  34. package/resources/cax.scss +83 -37
  35. package/resources/components/card/card.scss +1 -1
  36. package/resources/components/commentbox/commentbox.scss +604 -0
  37. package/resources/components/upload/upload.component.scss +147 -0
  38. package/upload/index.d.ts +5 -0
  39. package/upload/public_api.d.ts +2 -0
  40. package/upload/upload.component.d.ts +61 -0
  41. package/upload/upload.component.module.d.ts +11 -0
@@ -0,0 +1,572 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, Component, Input, Output, ViewChild, HostListener, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/platform-browser';
4
+ import * as i2 from '@angular/common';
5
+ import { CommonModule } from '@angular/common';
6
+ import * as i3 from '@angular/forms';
7
+ import { FormsModule } from '@angular/forms';
8
+ import * as i4 from 'cax-design-system/sidebar';
9
+ import { Sidebar } from 'cax-design-system/sidebar';
10
+ import * as i5 from 'cax-design-system/inputtext';
11
+ import { InputTextModule } from 'cax-design-system/inputtext';
12
+ import * as i6 from 'cax-design-system/button';
13
+ import { ButtonModule } from 'cax-design-system/button';
14
+ import * as i7 from 'cax-design-system/api';
15
+ import { SharedModule } from 'cax-design-system/api';
16
+ import * as i8 from 'cax-design-system/chip';
17
+ import { ChipModule } from 'cax-design-system/chip';
18
+ import * as i9 from 'cax-design-system/overlay';
19
+ import { OverlayModule } from 'cax-design-system/overlay';
20
+ import * as i10 from 'cax-design-system/avatar';
21
+ import { AvatarModule } from 'cax-design-system/avatar';
22
+ import { RippleModule } from 'cax-design-system/ripple';
23
+
24
+ class CommentboxComponent {
25
+ sanitizer;
26
+ comments = [];
27
+ mentionSuggestions = [];
28
+ hashtagSuggestions = [];
29
+ sidebarHeader;
30
+ isAdmin = true;
31
+ commentAdded = new EventEmitter();
32
+ fileSelected = new EventEmitter();
33
+ fileDownload = new EventEmitter();
34
+ visibleChange = new EventEmitter();
35
+ newComment = '';
36
+ isMentioning = false;
37
+ isHashtagging = false;
38
+ suggestions = [];
39
+ selectedSuggestion = null;
40
+ selectedSuggestionIndex = -1;
41
+ filteredSuggestions = [];
42
+ currentTokenType = null;
43
+ cursorPosition = 0;
44
+ fileInput;
45
+ suggestionPanel;
46
+ inputText;
47
+ commentsList;
48
+ countChip;
49
+ selectedFiles = [];
50
+ selectedFilesMap = new Map();
51
+ today = new Date();
52
+ overlayVisible = false;
53
+ filesOverlayVisible = false;
54
+ overlayPosition = { top: 0, left: 0 };
55
+ dateUpdateInterval;
56
+ // Keep track of should auto scroll
57
+ shouldAutoScroll = true;
58
+ isUserScrolling = false;
59
+ // Add new property for mutation observer
60
+ observer;
61
+ constructor(sanitizer) {
62
+ this.sanitizer = sanitizer;
63
+ this.updateCurrentDate();
64
+ this.dateUpdateInterval = setInterval(() => this.updateCurrentDate(), 60000);
65
+ // Initialize observer
66
+ this.observer = new MutationObserver(() => this.forceScrollToBottom());
67
+ }
68
+ ngAfterViewInit() {
69
+ setTimeout(() => {
70
+ this.forceScrollToBottom();
71
+ }, 100);
72
+ // Set up observer to auto-scroll when content changes
73
+ if (this.commentsList?.nativeElement) {
74
+ this.observer.observe(this.commentsList.nativeElement, {
75
+ childList: true,
76
+ subtree: true,
77
+ characterData: true
78
+ });
79
+ }
80
+ }
81
+ ngOnDestroy() {
82
+ if (this.dateUpdateInterval) {
83
+ clearInterval(this.dateUpdateInterval);
84
+ }
85
+ // Disconnect the observer
86
+ this.observer.disconnect();
87
+ // Remove event listeners
88
+ document.removeEventListener('click', this.handleClickOutside);
89
+ }
90
+ updateCurrentDate() {
91
+ this.today = new Date();
92
+ }
93
+ formatDate(date) {
94
+ return date.toLocaleDateString(undefined, {
95
+ day: '2-digit',
96
+ month: '2-digit',
97
+ year: 'numeric',
98
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
99
+ });
100
+ }
101
+ formatMessageWithTags(text) {
102
+ if (!text)
103
+ return '';
104
+ const sanitizedText = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
105
+ const htmlContent = sanitizedText.replace(/(@\w+|#\w+)/g, (match) => {
106
+ if (match.startsWith('@')) {
107
+ return `<span class="mention" style="color: #0C4AF3; font-weight: 600;">${match}</span>`;
108
+ }
109
+ else if (match.startsWith('#')) {
110
+ return `<span class="hashtag" style="color: #0C4AF3; font-weight: 600;">${match}</span>`;
111
+ }
112
+ return match;
113
+ });
114
+ return this.sanitizer.bypassSecurityTrustHtml(htmlContent);
115
+ }
116
+ addComment() {
117
+ if (this.newComment.trim() || this.selectedFiles.length > 0) {
118
+ const newComment = {
119
+ text: this.newComment,
120
+ date: new Date(),
121
+ files: this.selectedFiles.length > 0 ? [...this.selectedFiles] : undefined,
122
+ isAdmin: this.isAdmin,
123
+ sender: this.isAdmin ? 'System Admin' : 'User'
124
+ };
125
+ this.comments = [...this.comments, newComment];
126
+ this.commentAdded.emit(newComment);
127
+ this.newComment = '';
128
+ this.selectedFiles = [];
129
+ this.selectedFilesMap.clear();
130
+ this.filesOverlayVisible = false;
131
+ if (this.suggestionPanel?.nativeElement) {
132
+ this.suggestionPanel.nativeElement.style.display = 'none';
133
+ }
134
+ // Always scroll to bottom immediately
135
+ this.scrollToBottom(true);
136
+ }
137
+ }
138
+ forceScrollToBottom() {
139
+ if (!this.commentsList?.nativeElement)
140
+ return;
141
+ const element = this.commentsList.nativeElement;
142
+ requestAnimationFrame(() => {
143
+ try {
144
+ // Force immediate scroll
145
+ element.scrollTop = element.scrollHeight;
146
+ }
147
+ catch (err) {
148
+ console.error('Scroll error:', err);
149
+ }
150
+ });
151
+ }
152
+ scrollToBottom(force = false) {
153
+ if (force) {
154
+ this.forceScrollToBottom();
155
+ return;
156
+ }
157
+ if (!this.commentsList?.nativeElement)
158
+ return;
159
+ const element = this.commentsList.nativeElement;
160
+ // Use double RAF for reliable scrolling
161
+ requestAnimationFrame(() => {
162
+ requestAnimationFrame(() => {
163
+ try {
164
+ // Force immediate scroll for new comments
165
+ element.scrollTop = element.scrollHeight;
166
+ // Use smooth scrolling after initial jump
167
+ element.scrollTo({
168
+ top: element.scrollHeight,
169
+ behavior: 'smooth'
170
+ });
171
+ }
172
+ catch (err) {
173
+ element.scrollTop = element.scrollHeight;
174
+ }
175
+ });
176
+ });
177
+ }
178
+ set visible(value) {
179
+ if (this._visible !== value) {
180
+ this._visible = value;
181
+ this.visibleChange.emit(this._visible);
182
+ if (this._visible) {
183
+ this.shouldAutoScroll = true;
184
+ setTimeout(() => this.scrollToBottom(true), 300);
185
+ }
186
+ }
187
+ }
188
+ get visible() {
189
+ return this._visible;
190
+ }
191
+ _visible = false;
192
+ toggleComments() {
193
+ this.visible = !this.visible;
194
+ }
195
+ onSidebarHide() {
196
+ this._visible = false;
197
+ this.visibleChange.emit(false);
198
+ }
199
+ onDocumentClick(event) {
200
+ const target = event.target;
201
+ const isInputClick = this.inputText?.nativeElement?.contains(target);
202
+ const isPanelClick = this.suggestionPanel?.nativeElement?.contains(target);
203
+ if (!isInputClick && !isPanelClick) {
204
+ this.overlayVisible = false;
205
+ this.currentTokenType = null;
206
+ this.filteredSuggestions = [];
207
+ }
208
+ }
209
+ handleKeyUp(event) {
210
+ // Update cursor position on every key press
211
+ const input = event.target;
212
+ this.cursorPosition = input.selectionStart || 0;
213
+ if (event.key === 'Escape') {
214
+ this.overlayVisible = false;
215
+ this.currentTokenType = null;
216
+ this.filteredSuggestions = [];
217
+ return;
218
+ }
219
+ if (event.key === '@' || event.key === '#') {
220
+ this.currentTokenType = event.key === '@' ? 'mention' : 'hashtag';
221
+ this.filteredSuggestions = this.currentTokenType === 'mention' ? this.mentionSuggestions : this.hashtagSuggestions;
222
+ this.selectedSuggestionIndex = 0;
223
+ const rect = input.getBoundingClientRect();
224
+ const cursorPosition = this.getCursorPosition(input);
225
+ this.overlayVisible = true;
226
+ setTimeout(() => {
227
+ const panel = this.suggestionPanel?.nativeElement;
228
+ if (panel) {
229
+ panel.style.left = `${rect.left + cursorPosition.left}px`;
230
+ panel.style.top = `${rect.top - 10}px`;
231
+ }
232
+ });
233
+ }
234
+ if (this.currentTokenType) {
235
+ const token = this.getTokenAtCursor(input.value, this.cursorPosition);
236
+ if (token) {
237
+ const searchTerm = token.slice(1).toLowerCase();
238
+ this.filteredSuggestions = (this.currentTokenType === 'mention' ? this.mentionSuggestions : this.hashtagSuggestions).filter((item) => item.name.toLowerCase().includes(searchTerm) || item.id.toLowerCase().includes(searchTerm));
239
+ if (this.filteredSuggestions.length > 0) {
240
+ this.overlayVisible = true;
241
+ // Only reset selection index if we have new suggestions
242
+ if (this.selectedSuggestionIndex < 0 || this.selectedSuggestionIndex >= this.filteredSuggestions.length) {
243
+ this.selectedSuggestionIndex = 0;
244
+ }
245
+ }
246
+ else {
247
+ this.overlayVisible = false;
248
+ this.selectedSuggestionIndex = -1;
249
+ }
250
+ }
251
+ else {
252
+ this.overlayVisible = false;
253
+ this.currentTokenType = null;
254
+ }
255
+ }
256
+ }
257
+ getTokenAtCursor(text, cursorPos) {
258
+ const beforeCursor = text.slice(0, cursorPos);
259
+ const match = beforeCursor.match(/[@#][^\s]*$/);
260
+ return match ? match[0] : null;
261
+ }
262
+ getWordAtPosition(text, position) {
263
+ const left = text.slice(0, position).search(/[@#][^\s]*$/);
264
+ if (left === -1)
265
+ return '';
266
+ return text.slice(left, position);
267
+ }
268
+ selectSuggestion(suggestion) {
269
+ const input = this.newComment;
270
+ const wordStart = input.slice(0, this.cursorPosition).search(/[@#][^\s]*$/);
271
+ this.newComment = input.slice(0, wordStart) + suggestion.value + ' ' + input.slice(this.cursorPosition);
272
+ this.isMentioning = false;
273
+ this.isHashtagging = false;
274
+ }
275
+ onSuggestionSelect(item) {
276
+ const input = this.newComment;
277
+ const beforeCursor = input.slice(0, this.cursorPosition);
278
+ const afterCursor = input.slice(this.cursorPosition);
279
+ const lastTokenIndex = Math.max(beforeCursor.lastIndexOf('@'), beforeCursor.lastIndexOf('#'));
280
+ if (lastTokenIndex >= 0) {
281
+ // Insert the suggestion at the cursor position
282
+ this.newComment = beforeCursor.slice(0, lastTokenIndex) + item.id + ' ' + afterCursor;
283
+ }
284
+ this.suggestionPanel.nativeElement.style.display = 'none';
285
+ this.overlayVisible = false;
286
+ this.currentTokenType = null;
287
+ this.filteredSuggestions = [];
288
+ this.selectedSuggestionIndex = -1;
289
+ setTimeout(() => {
290
+ const inputEl = this.inputText.nativeElement.querySelector('input');
291
+ if (inputEl) {
292
+ inputEl.focus();
293
+ }
294
+ });
295
+ }
296
+ onSuggestionPanelHide() {
297
+ this.overlayVisible = false;
298
+ this.currentTokenType = null;
299
+ this.filteredSuggestions = [];
300
+ this.selectedSuggestionIndex = -1;
301
+ }
302
+ getCursorPosition(input) {
303
+ const selectionStart = input.selectionStart || 0;
304
+ const textBeforeCursor = input.value.substring(0, selectionStart);
305
+ const span = document.createElement('span');
306
+ span.textContent = textBeforeCursor;
307
+ span.style.cssText = window.getComputedStyle(input, null).cssText;
308
+ span.style.visibility = 'hidden';
309
+ document.body.appendChild(span);
310
+ const position = {
311
+ left: span.offsetWidth,
312
+ top: span.offsetHeight
313
+ };
314
+ document.body.removeChild(span);
315
+ return position;
316
+ }
317
+ onFileIconClick(event) {
318
+ event?.preventDefault();
319
+ event?.stopPropagation();
320
+ if (this.fileInput?.nativeElement) {
321
+ this.fileInput.nativeElement.click();
322
+ }
323
+ }
324
+ onFileSelect(event) {
325
+ const input = event.target;
326
+ if (input.files && input.files.length > 0) {
327
+ Array.from(input.files).forEach((file) => {
328
+ // Check if file already exists in selection
329
+ const fileExists = this.selectedFiles.some((existingFile) => existingFile.name === file.name && existingFile.size === this.formatFileSize(file.size));
330
+ if (!fileExists) {
331
+ const fileInfo = {
332
+ name: file.name,
333
+ size: this.formatFileSize(file.size)
334
+ };
335
+ this.selectedFiles.push(fileInfo);
336
+ this.selectedFilesMap.set(this.selectedFiles.length - 1, fileInfo);
337
+ this.fileSelected.emit(file);
338
+ }
339
+ });
340
+ input.value = '';
341
+ const inputEl = document.querySelector('.chat-input input');
342
+ if (inputEl instanceof HTMLElement) {
343
+ inputEl.focus();
344
+ }
345
+ }
346
+ }
347
+ updateFileState(index, fromChip = true) {
348
+ const actualIndex = fromChip ? index : index + 4;
349
+ this.selectedFiles = this.selectedFiles.filter((_, i) => i !== actualIndex);
350
+ this.selectedFilesMap.clear();
351
+ this.selectedFiles.forEach((file, i) => {
352
+ this.selectedFilesMap.set(i, file);
353
+ });
354
+ }
355
+ removeFile(index, fromChip = true) {
356
+ this.updateFileState(index, fromChip);
357
+ if (this.selectedFiles.length <= 4) {
358
+ this.hideFilesOverlay();
359
+ }
360
+ }
361
+ getOverflowCount() {
362
+ // Return count of files beyond the first 4
363
+ return Math.max(0, this.selectedFiles.length - 4);
364
+ }
365
+ showFilesOverlay(event) {
366
+ const target = event.currentTarget;
367
+ const rect = target.getBoundingClientRect();
368
+ // Position the overlay below the count chip
369
+ this.overlayPosition = {
370
+ top: rect.bottom + 5,
371
+ left: rect.left - 250 + rect.width / 2 // Center the overlay horizontally
372
+ };
373
+ this.filesOverlayVisible = true;
374
+ // Add a click outside listener to close the overlay
375
+ setTimeout(() => {
376
+ document.addEventListener('click', this.handleClickOutside);
377
+ });
378
+ }
379
+ hideFilesOverlay() {
380
+ this.filesOverlayVisible = false;
381
+ document.removeEventListener('click', this.handleClickOutside);
382
+ }
383
+ handleClickOutside = (event) => {
384
+ const target = event.target;
385
+ const overlay = document.querySelector('.files-overlay');
386
+ const countChip = document.querySelector('.count-chip');
387
+ if (overlay && !overlay.contains(target) && countChip && !countChip.contains(target)) {
388
+ this.hideFilesOverlay();
389
+ }
390
+ };
391
+ formatFileSize(bytes) {
392
+ if (bytes === 0)
393
+ return '0 Bytes';
394
+ const k = 1024;
395
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
396
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
397
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
398
+ }
399
+ showSuggestions(_event, type) {
400
+ this.currentTokenType = type;
401
+ this.filteredSuggestions = type === 'mention' ? this.mentionSuggestions : this.hashtagSuggestions;
402
+ if (this.suggestionPanel) {
403
+ this.suggestionPanel.nativeElement.style.display = 'block';
404
+ }
405
+ }
406
+ onKeyDown(event) {
407
+ // If suggestions are visible, handle keyboard navigation
408
+ if (this.overlayVisible && this.filteredSuggestions.length > 0) {
409
+ switch (event.key) {
410
+ case 'ArrowDown':
411
+ event.preventDefault();
412
+ this.selectedSuggestionIndex = Math.min(this.selectedSuggestionIndex + 1, this.filteredSuggestions.length - 1);
413
+ this.scrollSuggestionIntoView();
414
+ break;
415
+ case 'ArrowUp':
416
+ event.preventDefault();
417
+ this.selectedSuggestionIndex = Math.max(this.selectedSuggestionIndex - 1, 0);
418
+ this.scrollSuggestionIntoView();
419
+ break;
420
+ case 'Tab':
421
+ event.preventDefault();
422
+ if (event.shiftKey) {
423
+ // Shift+Tab - move up
424
+ this.selectedSuggestionIndex = Math.max(this.selectedSuggestionIndex - 1, 0);
425
+ }
426
+ else {
427
+ // Tab - move down
428
+ this.selectedSuggestionIndex = Math.min(this.selectedSuggestionIndex + 1, this.filteredSuggestions.length - 1);
429
+ }
430
+ this.scrollSuggestionIntoView();
431
+ break;
432
+ case 'Enter':
433
+ event.preventDefault();
434
+ if (this.selectedSuggestionIndex >= 0) {
435
+ this.onSuggestionSelect(this.filteredSuggestions[this.selectedSuggestionIndex]);
436
+ }
437
+ break;
438
+ case 'Escape':
439
+ event.preventDefault();
440
+ this.onSuggestionPanelHide();
441
+ break;
442
+ }
443
+ return;
444
+ }
445
+ // If no suggestions are visible, handle normal enter key press
446
+ if (event.key === 'Enter' && !event.shiftKey) {
447
+ event.preventDefault();
448
+ this.addComment();
449
+ }
450
+ }
451
+ // Helper method to scroll the selected suggestion into view
452
+ scrollSuggestionIntoView() {
453
+ setTimeout(() => {
454
+ const suggestionList = this.suggestionPanel?.nativeElement.querySelector('.suggestion-list');
455
+ const selectedItem = suggestionList?.querySelector('.suggestion-item.selected');
456
+ if (suggestionList && selectedItem) {
457
+ const listRect = suggestionList.getBoundingClientRect();
458
+ const itemRect = selectedItem.getBoundingClientRect();
459
+ // Check if the selected item is outside the visible area
460
+ if (itemRect.bottom > listRect.bottom) {
461
+ // Item is below the visible area
462
+ selectedItem.scrollIntoView({ block: 'end', behavior: 'smooth' });
463
+ }
464
+ else if (itemRect.top < listRect.top) {
465
+ // Item is above the visible area
466
+ selectedItem.scrollIntoView({ block: 'start', behavior: 'smooth' });
467
+ }
468
+ }
469
+ });
470
+ }
471
+ onFileDownload(file) {
472
+ this.fileDownload.emit(file);
473
+ }
474
+ get groupedComments() {
475
+ return this.comments
476
+ .reduce((groups, comment) => {
477
+ const commentDate = new Date(comment.date);
478
+ commentDate.setHours(0, 0, 0, 0);
479
+ const existingGroup = groups.find((group) => {
480
+ const groupDate = new Date(group.date);
481
+ groupDate.setHours(0, 0, 0, 0);
482
+ return groupDate.getTime() === commentDate.getTime();
483
+ });
484
+ if (existingGroup) {
485
+ existingGroup.items.push(comment);
486
+ }
487
+ else {
488
+ groups.push({
489
+ date: commentDate,
490
+ items: [comment]
491
+ });
492
+ }
493
+ return groups;
494
+ }, [])
495
+ .sort((a, b) => a.date.getTime() - b.date.getTime());
496
+ }
497
+ getShortFileName(filename) {
498
+ if (filename.length <= 4)
499
+ return filename;
500
+ // Get file extension
501
+ const lastDotIndex = filename.lastIndexOf('.');
502
+ const extension = lastDotIndex !== -1 ? filename.substring(lastDotIndex) : '';
503
+ // Truncate the name part and add the extension
504
+ const namePart = filename.substring(0, lastDotIndex !== -1 ? lastDotIndex : filename.length);
505
+ return namePart.substring(0, 4) + '..' + extension;
506
+ }
507
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: CommentboxComponent, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
508
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.9", type: CommentboxComponent, selector: "cax-comments", inputs: { comments: "comments", mentionSuggestions: "mentionSuggestions", hashtagSuggestions: "hashtagSuggestions", sidebarHeader: "sidebarHeader", isAdmin: "isAdmin", visible: "visible" }, outputs: { commentAdded: "commentAdded", fileSelected: "fileSelected", fileDownload: "fileDownload", visibleChange: "visibleChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "suggestionPanel", first: true, predicate: ["suggestionPanel"], descendants: true }, { propertyName: "inputText", first: true, predicate: ["inputText"], descendants: true }, { propertyName: "commentsList", first: true, predicate: ["commentsList"], descendants: true }, { propertyName: "countChip", first: true, predicate: ["countChip"], descendants: true }], ngImport: i0, template: "<cax-sidebar [(visible)]=\"visible\" [position]=\"'right'\" [baseZIndex]=\"10000\" (onHide)=\"onSidebarHide()\" [headerText]=\"sidebarHeader\">\r\n <ng-template caxTemplate=\"content\">\r\n <div class=\"cax-comments-container\">\r\n <div class=\"cax-comments-list\" #commentsList>\r\n <ng-container *ngFor=\"let group of groupedComments\">\r\n <div class=\"cax-date-header\">{{ formatDate(group.date) }}</div>\r\n <div\r\n class=\"cax-comment-item\"\r\n *ngFor=\"let comment of group.items; let i = index\"\r\n [ngClass]=\"{\r\n 'cax-user-comment': !comment.isAdmin,\r\n 'cax-admin-comment': comment.isAdmin,\r\n 'consecutive-message': i > 0 && comment.isAdmin === group.items[i-1].isAdmin && comment.sender === group.items[i-1].sender,\r\n 'new-sender': i > 0 && comment.sender !== group.items[i-1].sender,\r\n 'new-date': i === 0 && group.items.length > 0\r\n }\"\r\n >\r\n <!-- User Message (Left Aligned) -->\r\n <ng-container *ngIf=\"!comment.isAdmin\">\r\n <div class=\"cax-commentbox-avatar-container\" *ngIf=\"i === 0 || group.items[i-1].sender !== comment.sender\">\r\n <cax-avatar [label]=\"comment.sender?.substring(0, 2).toUpperCase() || 'U'\" [avatarSize]=\"'xs'\" [shape]=\"'circle'\" styleclass=\"cax-commentbox-avatar\"> </cax-avatar>\r\n </div>\r\n <div class=\"cax-comment-content\" [class.no-avatar]=\"i > 0 && group.items[i-1].sender === comment.sender\">\r\n <div class=\"cax-comment-sender\" *ngIf=\"i === 0 || group.items[i-1].sender !== comment.sender\">{{comment.sender}}</div>\r\n <div class=\"cax-file-attachment\" *ngIf=\"comment.files?.length\">\r\n <div *ngFor=\"let file of comment.files\" class=\"file-item\">\r\n <div class=\"file-details\">\r\n <span class=\"file-name\">{{ file.name.slice(0, 20) }}{{ file.name.includes('.') ? '.' + file.name.split('.').pop() : '' }}</span>\r\n <span class=\"file-size\">{{file.size}}</span>\r\n </div>\r\n <i class=\"cax cax-download\" (click)=\"onFileDownload(file)\" role=\"button\" tabindex=\"0\"></i>\r\n </div>\r\n </div>\r\n <div class=\"cax-comment-text\" [innerHTML]=\"formatMessageWithTags(comment.text)\"></div>\r\n <span class=\"cax-comment-date\">{{ comment.date | date: 'shortTime' }}</span>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Admin Message (Right Aligned) -->\r\n <ng-container *ngIf=\"comment.isAdmin\">\r\n <div class=\"cax-comment-content admin\">\r\n <div class=\"cax-file-attachment\" *ngIf=\"comment.files?.length\">\r\n <div *ngFor=\"let file of comment.files\" class=\"file-item\">\r\n <div class=\"file-details\">\r\n <span class=\"file-name\">{{ file.name.slice(0, 20) }}{{ file.name.includes('.') ? '.' + file.name.split('.').pop() : '' }}</span>\r\n <span class=\"file-size\">{{file.size}}</span>\r\n </div>\r\n <i class=\"cax cax-download\" (click)=\"onFileDownload(file)\" role=\"button\" tabindex=\"0\"></i>\r\n </div>\r\n </div>\r\n <div class=\"cax-comment-text\" [innerHTML]=\"formatMessageWithTags(comment.text)\"></div>\r\n <span class=\"cax-comment-date\">{{ comment.date | date: 'shortTime' }}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-template>\r\n <cax-overlay #suggestionPanel [(visible)]=\"overlayVisible\" [appendTo]=\"inputText\" (onHide)=\"onSuggestionPanelHide()\" [styleClass]=\"'suggestion-panel'\">\r\n <div class=\"suggestion-list\">\r\n <div *ngFor=\"let item of filteredSuggestions; let i = index\" class=\"suggestion-item\" [ngClass]=\"{'selected': selectedSuggestionIndex === i}\" (click)=\"onSuggestionSelect(item)\" (mouseenter)=\"selectedSuggestionIndex = i\">\r\n <span class=\"suggestion-label\">{{item.name}}</span>\r\n <small class=\"suggestion-id\" *ngIf=\"currentTokenType === 'mention'\">{{item.id}}</small>\r\n </div>\r\n </div>\r\n </cax-overlay>\r\n <cax-overlay\r\n [(visible)]=\"filesOverlayVisible\"\r\n [appendTo]=\"countChip\"\r\n [showTransitionOptions]=\"'0.12s ease-out'\"\r\n [hideTransitionOptions]=\"'0.1s ease-in'\"\r\n [style]=\"{ width: '224px', right: '5%' }\"\r\n styleClass=\"files-list-overlay\"\r\n (onHide)=\"hideFilesOverlay()\"\r\n >\r\n <div class=\"files-overlay-content\">\r\n <div *ngFor=\"let file of selectedFiles.slice(4); let i = index\" class=\"file-item\">\r\n <span class=\"file-name\">{{getShortFileName(file.name)}}</span>\r\n <i class=\"cax cax-close-circle\" (click)=\"removeFile(i + 4)\"></i>\r\n </div>\r\n </div>\r\n </cax-overlay>\r\n\r\n <ng-template caxTemplate=\"footer\">\r\n <div class=\"chat-footer\">\r\n <div class=\"selected-files\" *ngIf=\"selectedFiles.length > 0\">\r\n <cax-chip *ngFor=\"let file of selectedFiles.slice(0, 4); let i = index\" [label]=\"getShortFileName(file.name)\" [removable]=\"true\" [size]=\"'lg'\" (onRemove)=\"removeFile(i, true)\">\r\n <ng-template caxTemplate=\"content\">\r\n <i class=\"cax cax-paperclip\"></i>\r\n </ng-template>\r\n </cax-chip>\r\n <cax-chip #countChip *ngIf=\"selectedFiles.length > 4\" [label]=\"'+' + getOverflowCount()\" [size]=\"'lg'\" (mouseenter)=\"showFilesOverlay($event)\"> </cax-chip>\r\n </div>\r\n <div class=\"cax-chat-input-container\">\r\n <div class=\"input-wrapper\">\r\n <cax-inputtext\r\n [(ngModel)]=\"newComment\"\r\n [size]=\"'lg'\"\r\n [rightIcon]=\"true\"\r\n [clearIcon]=\"false\"\r\n [rightIconClass]=\"'cax cax-paperclip'\"\r\n [rightIconClickable]=\"true\"\r\n (rightIconClick)=\"onFileIconClick($event)\"\r\n placeholder=\"Comment or mention others with @, \u2018#\u2019 for columns\"\r\n (keyup)=\"handleKeyUp($event)\"\r\n (keydown)=\"onKeyDown($event)\"\r\n [class]=\"{'chat-input': true, 'admin-input': isAdminSender}\"\r\n [style]=\"{'border-radius': '8px', background: '#ffffff'}\"\r\n #inputText\r\n >\r\n </cax-inputtext>\r\n </div>\r\n\r\n <cax-button [icon]=\"'cax cax-plain'\" [severity]=\"'primary'\" [size]=\"'large'\" (click)=\"addComment()\" [disabled]=\"!newComment.trim() && selectedFiles.length === 0\" class=\"send-button\"> </cax-button>\r\n </div>\r\n\r\n <input #fileInput type=\"file\" (change)=\"onFileSelect($event)\" style=\"display: none\" accept=\"*/*\" multiple />\r\n </div>\r\n </ng-template>\r\n</cax-sidebar>\r\n", styles: ["@layer cax{.cax-comments-list{display:flex;flex-direction:column;gap:4px;flex:1;overflow-y:auto;overflow-x:hidden;height:100%;padding-bottom:0;will-change:transform;transform:translateZ(0);backface-visibility:hidden;-webkit-backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:rgba(94,92,92,.3) transparent}.cax-comments-list:after{content:\"\";display:block;min-height:20px;scroll-snap-align:end}.cax-comments-list::-webkit-scrollbar-track{background:transparent}.cax-comments-list::-webkit-scrollbar-thumb{background-color:#5553531a;border-radius:4px;border:2px solid transparent;background-clip:padding-box}.cax-comment-item{display:flex;align-items:flex-end;max-width:85%;gap:4px}.cax-comment-item.consecutive-message{margin-top:0}.cax-comment-item.new-sender{margin-top:12px}.cax-comment-item.new-date{margin-top:0}.cax-user-comment{align-self:flex-start;display:flex;flex-direction:row;text-align:left}.cax-user-comment .cax-commentbox-avatar-container{display:flex;align-items:flex-end}.cax-user-comment .cax-comment-content{margin-bottom:0;display:flex;flex-direction:column;border-radius:12px 12px 12px 0}.cax-user-comment .cax-comment-content.no-avatar{border-radius:12px;margin-left:25px}.cax-user-comment .cax-comment-content .cax-comment-sender{font-size:14px;font-weight:600;color:var(--neutral-900);margin-bottom:0;line-height:20px}.cax-user-comment.consecutive-message .cax-comment-content{border-radius:12px}.cax-admin-comment{align-self:flex-end;display:flex;flex-direction:row;justify-content:flex-end;text-align:right}.cax-admin-comment .cax-commentbox-avatar-container{display:flex;align-items:flex-end}.cax-admin-comment .cax-comment-content{margin-bottom:0;display:flex;flex-direction:column;border-radius:12px 12px 0;text-align:left}.cax-admin-comment .cax-comment-content .cax-comment-sender{font-size:14px;font-weight:600;color:var(--neutral-900);margin-bottom:0;line-height:20px}.cax-admin-comment.consecutive-message .cax-comment-content{border-radius:12px}.cax-commentbox-avatar{width:32px;height:32px;border-radius:50%;margin:0 8px}.cax-comment-content{background:var(--white-100);border-radius:12px 12px 12px 0;padding:8px;max-width:100%;word-wrap:break-word;display:flex;flex-direction:column;box-shadow:0 2px 4px #0000000d}.cax-comment-content .cax-comment-text{font-weight:400;font-size:14px;line-height:20px}.cax-comment-content .cax-comment-text ::ng-deep .mention{display:inline-block;font-weight:600;color:var(--primary-500)}.cax-comment-content .cax-comment-text ::ng-deep .hashtag{display:inline-block;font-weight:600;color:var(--primary-500)}.cax-comment-content .cax-comment-sender{font-size:14px;font-weight:600;color:var(--neutral-900);margin-bottom:0;line-height:20px}.cax-file-attachment{margin-bottom:12px}.cax-file-attachment .file-item{display:flex;align-items:center;gap:12px;border-color:1px solid var(--neutral-150);padding:10px 16px;background:var(--neutral-75);border-radius:8px;cursor:pointer;margin-bottom:8px}.cax-file-attachment .file-item .file-details{display:flex;flex:1;align-items:center;gap:12px}.cax-file-attachment .file-item .file-name{color:var(--neutral-900);font-weight:500;font-size:14px;line-height:20px;flex:1;white-space:nowrap;overflow:hidden}.cax-file-attachment .file-item .file-size{color:var(--neutral-600);font-weight:400;font-size:12px;line-height:18px;margin-right:8px}.cax-file-attachment .file-item i{color:var(--neutral-900);font-size:1.1rem;margin-left:auto;order:2}.cax-file-attachment .file-item i.cax-download{cursor:pointer;transition:color .2s ease}.cax-file-attachment .file-item i.cax-download:hover{color:var(--primary-600)}.cax-file-attachment .file-item i.cax-download:active{color:var(--primary-700)}.cax-file-attachment .file-item:last-child{margin-bottom:0}.cax-file-attachment:last-child{margin-bottom:0}.cax-comment-date{font-weight:400;font-size:12px;line-height:18px;color:var(--neutral-600);align-self:flex-end}.cax-chat-input-container{display:flex;gap:.5rem}.cax-chat-input-container .chat-input{flex:1}.cax-chat-input-container .send-button{cursor:pointer}.input-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.cax-date-header{text-align:center;font-size:12px;font-weight:500;line-height:18px;color:var(--neutral-700);background:var(--neutral-100);align-self:center;width:fit-content;padding:4px 12px;border-radius:4px;margin:11px 0;position:relative;z-index:1}.cax-date-header:first-child{margin-top:0}.cax-comments-container{display:flex;flex-direction:column;height:calc(100vh - 200px);overflow:hidden;position:relative;transform:translateZ(0);backface-visibility:hidden}.selected-files{display:flex;flex-wrap:wrap;gap:.5rem;padding:0 0 5px;position:relative}.selected-files .cax-chip{background:var(--neutral-100)}.selected-files .cax-chip i{margin-right:.5rem}.selected-files .files-overlay{position:absolute;background:var(--white-100);border-radius:8px;box-shadow:0 4px 12px #00000026;width:300px;max-height:400px;z-index:1000;overflow:hidden;border:1px solid var(--neutral-200)}.selected-files .files-overlay .files-overlay-header{padding:12px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--neutral-100);background:var(--neutral-50)}.selected-files .files-overlay .files-overlay-content{max-height:350px;overflow-y:auto;padding:8px 0}.selected-files .files-overlay .files-overlay-content .file-item{display:flex;align-items:center;padding:8px 16px;transition:background-color .2s ease}.selected-files .files-overlay .files-overlay-content .file-item:hover{background-color:var(--neutral-50)}.selected-files .files-overlay .files-overlay-content .file-item i.cax-paperclip{color:var(--neutral-600);margin-right:8px}.selected-files .files-overlay .files-overlay-content .file-item .file-name{flex:1;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:8px}.selected-files .files-overlay .files-overlay-content .file-item .file-size{color:var(--neutral-600);font-size:12px;margin-right:8px}.selected-files .files-overlay .files-overlay-content .file-item i.cax-times{color:var(--neutral-600);cursor:pointer;transition:color .2s ease}.selected-files .files-overlay .files-overlay-content .file-item i.cax-times:hover{color:var --error-500}.selected-files :deep(.file-chip){background:var(--neutral-50);border:1px solid var(--neutral-100)}.selected-files :deep(.file-chip) .file-details{display:flex;align-items:center;gap:8px}.selected-files :deep(.file-chip) i{color:var(--primary-500);margin-right:8px}.selected-files :deep(.file-chip) .file-name{font-weight:500}.selected-files :deep(.file-chip) .file-size{color:var(--neutral-600);font-size:.8rem}:host ::ng-deep .cax-sidebar{width:589px!important;border:1px solid var(--neutral-200)!important}:host ::ng-deep .cax-sidebar .cax-sidebar-content{display:flex;flex-direction:column;background:var(--neutral-25);padding:24px 8px!important}:host ::ng-deep .cax-sidebar .cax-sidebar-footer{padding:12px!important;background:var(--neutral-50);border-top:1px solid var(--neutral-200)}:host ::ng-deep .cax-sidebar .cax-sidebar-header{border-bottom:1px solid var(--neutral-200);background:var(--neutral-50);padding:16px 24px!important}:host ::ng-deep .cax-overlay{position:absolute;bottom:0!important;top:unset!important;left:unset!important}:host ::ng-deep .suggestion-panel{background:var(--white-100);box-shadow:0 2px 8px #0000001a;z-index:9999;padding:12px 4px;margin-left:20px;width:224px;border-radius:8px;border:1px solid var(--neutral-200)}:host ::ng-deep .suggestion-panel .suggestion-list{max-height:200px;overflow-y:auto;will-change:transform;transform:translateZ(0);backface-visibility:hidden;-webkit-backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:rgba(94,92,92,.3) transparent}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item{padding:6px 8px;cursor:pointer;display:flex;justify-content:space-between;transition:background-color .2s ease;align-items:center}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item:hover{background-color:var(--neutral-50)}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item.selected{background-color:var(--neutral-50)}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item .suggestion-label{font-weight:500}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item .suggestion-id{color:var(--neutral-600);font-size:.85em}:host ::ng-deep .suggestion-panel .suggestion-list:after{content:\"\";display:block;min-height:20px;scroll-snap-align:end}:host ::ng-deep .suggestion-panel .suggestion-list::-webkit-scrollbar-track{background:transparent}:host ::ng-deep .suggestion-panel .suggestion-list::-webkit-scrollbar-thumb{background-color:#5553531a;border-radius:4px;border:2px solid transparent;background-clip:padding-box}:host ::ng-deep .chat-footer{position:relative;z-index:1}:host ::ng-deep .cax-chat-input-container .chat-input .cax-paperclip{color:var(--neutral-900)!important}:host ::ng-deep .files-list-overlay{border:1px solid var(--neutral-200);background:var(--white-100);box-shadow:0 2px 8px #0000001a;z-index:9999;padding:12px 4px;width:224px;border-radius:8px}:host ::ng-deep .files-list-overlay.cax-overlay{transform-origin:right center!important}:host ::ng-deep .files-list-overlay .files-overlay-content{max-height:200px;overflow-y:auto}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item{transition:background-color .2s ease;padding:6px 8px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;will-change:transform;transform:translateZ(0);backface-visibility:hidden;-webkit-backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:rgba(94,92,92,.3) transparent}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item:hover{background-color:var(--neutral-50)}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item .file-name{flex:1;font-size:14px;font-weight:500;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:8px}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item .file-size{color:var(--neutral-600);font-size:12px;margin-right:8px}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item i.cax-times{color:var(--neutral-600);cursor:pointer;border:1.1rem solid var(--neutral-200);transition:color .2s ease}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item:after{content:\"\";display:block;min-height:20px;scroll-snap-align:end}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item::-webkit-scrollbar-track{background:transparent}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item::-webkit-scrollbar-thumb{background-color:#5553531a;border-radius:4px;border:2px solid transparent;background-clip:padding-box}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4.Sidebar, selector: "cax-sidebar", inputs: ["headerText", "appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "contentTemplate", "headerTemplate", "headlessTemplate", "footerTemplate", "closeIconTemplate", "headerStyle"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "component", type: i5.InputTextComponent, selector: "cax-inputtext", inputs: ["value", "placeholder", "disabled", "maxlength", "successText", "errorText", "showLabel", "leftIcon", "rightIcon", "rightIconClickable", "clearIcon", "label", "iconPath", "disabledIcon", "showIcon", "iconClass", "leftIconClass", "rightIconClass", "invalid", "required", "style", "size", "styleClass"], outputs: ["valueChange", "rightIconClick"] }, { kind: "component", type: i6.Button, selector: "cax-button", inputs: ["type", "iconPos", "icon", "badge", "rightIcon", "leftIcon", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "style", "styleClass", "badgeClass", "ariaLabel", "autofocus"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i7.CaxTemplate, selector: "[caxTemplate]", inputs: ["type", "caxTemplate"] }, { kind: "component", type: i8.Chip, selector: "cax-chip", inputs: ["label", "icon", "image", "alt", "style", "styleClass", "removable", "removeIcon", "size", "severity"], outputs: ["onRemove", "onImageError"] }, { kind: "component", type: i9.Overlay, selector: "cax-overlay", inputs: ["visible", "mode", "style", "styleClass", "contentStyle", "contentStyleClass", "target", "appendTo", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "listener", "responsive", "options"], outputs: ["visibleChange", "onBeforeShow", "onShow", "onBeforeHide", "onHide", "onAnimationStart", "onAnimationDone"] }, { kind: "component", type: i10.Avatar, selector: "cax-avatar", inputs: ["label", "icon", "image", "avatarSize", "shape", "style", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }] });
509
+ }
510
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: CommentboxComponent, decorators: [{
511
+ type: Component,
512
+ args: [{ selector: 'cax-comments', template: "<cax-sidebar [(visible)]=\"visible\" [position]=\"'right'\" [baseZIndex]=\"10000\" (onHide)=\"onSidebarHide()\" [headerText]=\"sidebarHeader\">\r\n <ng-template caxTemplate=\"content\">\r\n <div class=\"cax-comments-container\">\r\n <div class=\"cax-comments-list\" #commentsList>\r\n <ng-container *ngFor=\"let group of groupedComments\">\r\n <div class=\"cax-date-header\">{{ formatDate(group.date) }}</div>\r\n <div\r\n class=\"cax-comment-item\"\r\n *ngFor=\"let comment of group.items; let i = index\"\r\n [ngClass]=\"{\r\n 'cax-user-comment': !comment.isAdmin,\r\n 'cax-admin-comment': comment.isAdmin,\r\n 'consecutive-message': i > 0 && comment.isAdmin === group.items[i-1].isAdmin && comment.sender === group.items[i-1].sender,\r\n 'new-sender': i > 0 && comment.sender !== group.items[i-1].sender,\r\n 'new-date': i === 0 && group.items.length > 0\r\n }\"\r\n >\r\n <!-- User Message (Left Aligned) -->\r\n <ng-container *ngIf=\"!comment.isAdmin\">\r\n <div class=\"cax-commentbox-avatar-container\" *ngIf=\"i === 0 || group.items[i-1].sender !== comment.sender\">\r\n <cax-avatar [label]=\"comment.sender?.substring(0, 2).toUpperCase() || 'U'\" [avatarSize]=\"'xs'\" [shape]=\"'circle'\" styleclass=\"cax-commentbox-avatar\"> </cax-avatar>\r\n </div>\r\n <div class=\"cax-comment-content\" [class.no-avatar]=\"i > 0 && group.items[i-1].sender === comment.sender\">\r\n <div class=\"cax-comment-sender\" *ngIf=\"i === 0 || group.items[i-1].sender !== comment.sender\">{{comment.sender}}</div>\r\n <div class=\"cax-file-attachment\" *ngIf=\"comment.files?.length\">\r\n <div *ngFor=\"let file of comment.files\" class=\"file-item\">\r\n <div class=\"file-details\">\r\n <span class=\"file-name\">{{ file.name.slice(0, 20) }}{{ file.name.includes('.') ? '.' + file.name.split('.').pop() : '' }}</span>\r\n <span class=\"file-size\">{{file.size}}</span>\r\n </div>\r\n <i class=\"cax cax-download\" (click)=\"onFileDownload(file)\" role=\"button\" tabindex=\"0\"></i>\r\n </div>\r\n </div>\r\n <div class=\"cax-comment-text\" [innerHTML]=\"formatMessageWithTags(comment.text)\"></div>\r\n <span class=\"cax-comment-date\">{{ comment.date | date: 'shortTime' }}</span>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Admin Message (Right Aligned) -->\r\n <ng-container *ngIf=\"comment.isAdmin\">\r\n <div class=\"cax-comment-content admin\">\r\n <div class=\"cax-file-attachment\" *ngIf=\"comment.files?.length\">\r\n <div *ngFor=\"let file of comment.files\" class=\"file-item\">\r\n <div class=\"file-details\">\r\n <span class=\"file-name\">{{ file.name.slice(0, 20) }}{{ file.name.includes('.') ? '.' + file.name.split('.').pop() : '' }}</span>\r\n <span class=\"file-size\">{{file.size}}</span>\r\n </div>\r\n <i class=\"cax cax-download\" (click)=\"onFileDownload(file)\" role=\"button\" tabindex=\"0\"></i>\r\n </div>\r\n </div>\r\n <div class=\"cax-comment-text\" [innerHTML]=\"formatMessageWithTags(comment.text)\"></div>\r\n <span class=\"cax-comment-date\">{{ comment.date | date: 'shortTime' }}</span>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-template>\r\n <cax-overlay #suggestionPanel [(visible)]=\"overlayVisible\" [appendTo]=\"inputText\" (onHide)=\"onSuggestionPanelHide()\" [styleClass]=\"'suggestion-panel'\">\r\n <div class=\"suggestion-list\">\r\n <div *ngFor=\"let item of filteredSuggestions; let i = index\" class=\"suggestion-item\" [ngClass]=\"{'selected': selectedSuggestionIndex === i}\" (click)=\"onSuggestionSelect(item)\" (mouseenter)=\"selectedSuggestionIndex = i\">\r\n <span class=\"suggestion-label\">{{item.name}}</span>\r\n <small class=\"suggestion-id\" *ngIf=\"currentTokenType === 'mention'\">{{item.id}}</small>\r\n </div>\r\n </div>\r\n </cax-overlay>\r\n <cax-overlay\r\n [(visible)]=\"filesOverlayVisible\"\r\n [appendTo]=\"countChip\"\r\n [showTransitionOptions]=\"'0.12s ease-out'\"\r\n [hideTransitionOptions]=\"'0.1s ease-in'\"\r\n [style]=\"{ width: '224px', right: '5%' }\"\r\n styleClass=\"files-list-overlay\"\r\n (onHide)=\"hideFilesOverlay()\"\r\n >\r\n <div class=\"files-overlay-content\">\r\n <div *ngFor=\"let file of selectedFiles.slice(4); let i = index\" class=\"file-item\">\r\n <span class=\"file-name\">{{getShortFileName(file.name)}}</span>\r\n <i class=\"cax cax-close-circle\" (click)=\"removeFile(i + 4)\"></i>\r\n </div>\r\n </div>\r\n </cax-overlay>\r\n\r\n <ng-template caxTemplate=\"footer\">\r\n <div class=\"chat-footer\">\r\n <div class=\"selected-files\" *ngIf=\"selectedFiles.length > 0\">\r\n <cax-chip *ngFor=\"let file of selectedFiles.slice(0, 4); let i = index\" [label]=\"getShortFileName(file.name)\" [removable]=\"true\" [size]=\"'lg'\" (onRemove)=\"removeFile(i, true)\">\r\n <ng-template caxTemplate=\"content\">\r\n <i class=\"cax cax-paperclip\"></i>\r\n </ng-template>\r\n </cax-chip>\r\n <cax-chip #countChip *ngIf=\"selectedFiles.length > 4\" [label]=\"'+' + getOverflowCount()\" [size]=\"'lg'\" (mouseenter)=\"showFilesOverlay($event)\"> </cax-chip>\r\n </div>\r\n <div class=\"cax-chat-input-container\">\r\n <div class=\"input-wrapper\">\r\n <cax-inputtext\r\n [(ngModel)]=\"newComment\"\r\n [size]=\"'lg'\"\r\n [rightIcon]=\"true\"\r\n [clearIcon]=\"false\"\r\n [rightIconClass]=\"'cax cax-paperclip'\"\r\n [rightIconClickable]=\"true\"\r\n (rightIconClick)=\"onFileIconClick($event)\"\r\n placeholder=\"Comment or mention others with @, \u2018#\u2019 for columns\"\r\n (keyup)=\"handleKeyUp($event)\"\r\n (keydown)=\"onKeyDown($event)\"\r\n [class]=\"{'chat-input': true, 'admin-input': isAdminSender}\"\r\n [style]=\"{'border-radius': '8px', background: '#ffffff'}\"\r\n #inputText\r\n >\r\n </cax-inputtext>\r\n </div>\r\n\r\n <cax-button [icon]=\"'cax cax-plain'\" [severity]=\"'primary'\" [size]=\"'large'\" (click)=\"addComment()\" [disabled]=\"!newComment.trim() && selectedFiles.length === 0\" class=\"send-button\"> </cax-button>\r\n </div>\r\n\r\n <input #fileInput type=\"file\" (change)=\"onFileSelect($event)\" style=\"display: none\" accept=\"*/*\" multiple />\r\n </div>\r\n </ng-template>\r\n</cax-sidebar>\r\n", styles: ["@layer cax{.cax-comments-list{display:flex;flex-direction:column;gap:4px;flex:1;overflow-y:auto;overflow-x:hidden;height:100%;padding-bottom:0;will-change:transform;transform:translateZ(0);backface-visibility:hidden;-webkit-backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:rgba(94,92,92,.3) transparent}.cax-comments-list:after{content:\"\";display:block;min-height:20px;scroll-snap-align:end}.cax-comments-list::-webkit-scrollbar-track{background:transparent}.cax-comments-list::-webkit-scrollbar-thumb{background-color:#5553531a;border-radius:4px;border:2px solid transparent;background-clip:padding-box}.cax-comment-item{display:flex;align-items:flex-end;max-width:85%;gap:4px}.cax-comment-item.consecutive-message{margin-top:0}.cax-comment-item.new-sender{margin-top:12px}.cax-comment-item.new-date{margin-top:0}.cax-user-comment{align-self:flex-start;display:flex;flex-direction:row;text-align:left}.cax-user-comment .cax-commentbox-avatar-container{display:flex;align-items:flex-end}.cax-user-comment .cax-comment-content{margin-bottom:0;display:flex;flex-direction:column;border-radius:12px 12px 12px 0}.cax-user-comment .cax-comment-content.no-avatar{border-radius:12px;margin-left:25px}.cax-user-comment .cax-comment-content .cax-comment-sender{font-size:14px;font-weight:600;color:var(--neutral-900);margin-bottom:0;line-height:20px}.cax-user-comment.consecutive-message .cax-comment-content{border-radius:12px}.cax-admin-comment{align-self:flex-end;display:flex;flex-direction:row;justify-content:flex-end;text-align:right}.cax-admin-comment .cax-commentbox-avatar-container{display:flex;align-items:flex-end}.cax-admin-comment .cax-comment-content{margin-bottom:0;display:flex;flex-direction:column;border-radius:12px 12px 0;text-align:left}.cax-admin-comment .cax-comment-content .cax-comment-sender{font-size:14px;font-weight:600;color:var(--neutral-900);margin-bottom:0;line-height:20px}.cax-admin-comment.consecutive-message .cax-comment-content{border-radius:12px}.cax-commentbox-avatar{width:32px;height:32px;border-radius:50%;margin:0 8px}.cax-comment-content{background:var(--white-100);border-radius:12px 12px 12px 0;padding:8px;max-width:100%;word-wrap:break-word;display:flex;flex-direction:column;box-shadow:0 2px 4px #0000000d}.cax-comment-content .cax-comment-text{font-weight:400;font-size:14px;line-height:20px}.cax-comment-content .cax-comment-text ::ng-deep .mention{display:inline-block;font-weight:600;color:var(--primary-500)}.cax-comment-content .cax-comment-text ::ng-deep .hashtag{display:inline-block;font-weight:600;color:var(--primary-500)}.cax-comment-content .cax-comment-sender{font-size:14px;font-weight:600;color:var(--neutral-900);margin-bottom:0;line-height:20px}.cax-file-attachment{margin-bottom:12px}.cax-file-attachment .file-item{display:flex;align-items:center;gap:12px;border-color:1px solid var(--neutral-150);padding:10px 16px;background:var(--neutral-75);border-radius:8px;cursor:pointer;margin-bottom:8px}.cax-file-attachment .file-item .file-details{display:flex;flex:1;align-items:center;gap:12px}.cax-file-attachment .file-item .file-name{color:var(--neutral-900);font-weight:500;font-size:14px;line-height:20px;flex:1;white-space:nowrap;overflow:hidden}.cax-file-attachment .file-item .file-size{color:var(--neutral-600);font-weight:400;font-size:12px;line-height:18px;margin-right:8px}.cax-file-attachment .file-item i{color:var(--neutral-900);font-size:1.1rem;margin-left:auto;order:2}.cax-file-attachment .file-item i.cax-download{cursor:pointer;transition:color .2s ease}.cax-file-attachment .file-item i.cax-download:hover{color:var(--primary-600)}.cax-file-attachment .file-item i.cax-download:active{color:var(--primary-700)}.cax-file-attachment .file-item:last-child{margin-bottom:0}.cax-file-attachment:last-child{margin-bottom:0}.cax-comment-date{font-weight:400;font-size:12px;line-height:18px;color:var(--neutral-600);align-self:flex-end}.cax-chat-input-container{display:flex;gap:.5rem}.cax-chat-input-container .chat-input{flex:1}.cax-chat-input-container .send-button{cursor:pointer}.input-wrapper{position:relative;flex:1;display:flex;flex-direction:column}.cax-date-header{text-align:center;font-size:12px;font-weight:500;line-height:18px;color:var(--neutral-700);background:var(--neutral-100);align-self:center;width:fit-content;padding:4px 12px;border-radius:4px;margin:11px 0;position:relative;z-index:1}.cax-date-header:first-child{margin-top:0}.cax-comments-container{display:flex;flex-direction:column;height:calc(100vh - 200px);overflow:hidden;position:relative;transform:translateZ(0);backface-visibility:hidden}.selected-files{display:flex;flex-wrap:wrap;gap:.5rem;padding:0 0 5px;position:relative}.selected-files .cax-chip{background:var(--neutral-100)}.selected-files .cax-chip i{margin-right:.5rem}.selected-files .files-overlay{position:absolute;background:var(--white-100);border-radius:8px;box-shadow:0 4px 12px #00000026;width:300px;max-height:400px;z-index:1000;overflow:hidden;border:1px solid var(--neutral-200)}.selected-files .files-overlay .files-overlay-header{padding:12px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--neutral-100);background:var(--neutral-50)}.selected-files .files-overlay .files-overlay-content{max-height:350px;overflow-y:auto;padding:8px 0}.selected-files .files-overlay .files-overlay-content .file-item{display:flex;align-items:center;padding:8px 16px;transition:background-color .2s ease}.selected-files .files-overlay .files-overlay-content .file-item:hover{background-color:var(--neutral-50)}.selected-files .files-overlay .files-overlay-content .file-item i.cax-paperclip{color:var(--neutral-600);margin-right:8px}.selected-files .files-overlay .files-overlay-content .file-item .file-name{flex:1;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:8px}.selected-files .files-overlay .files-overlay-content .file-item .file-size{color:var(--neutral-600);font-size:12px;margin-right:8px}.selected-files .files-overlay .files-overlay-content .file-item i.cax-times{color:var(--neutral-600);cursor:pointer;transition:color .2s ease}.selected-files .files-overlay .files-overlay-content .file-item i.cax-times:hover{color:var --error-500}.selected-files :deep(.file-chip){background:var(--neutral-50);border:1px solid var(--neutral-100)}.selected-files :deep(.file-chip) .file-details{display:flex;align-items:center;gap:8px}.selected-files :deep(.file-chip) i{color:var(--primary-500);margin-right:8px}.selected-files :deep(.file-chip) .file-name{font-weight:500}.selected-files :deep(.file-chip) .file-size{color:var(--neutral-600);font-size:.8rem}:host ::ng-deep .cax-sidebar{width:589px!important;border:1px solid var(--neutral-200)!important}:host ::ng-deep .cax-sidebar .cax-sidebar-content{display:flex;flex-direction:column;background:var(--neutral-25);padding:24px 8px!important}:host ::ng-deep .cax-sidebar .cax-sidebar-footer{padding:12px!important;background:var(--neutral-50);border-top:1px solid var(--neutral-200)}:host ::ng-deep .cax-sidebar .cax-sidebar-header{border-bottom:1px solid var(--neutral-200);background:var(--neutral-50);padding:16px 24px!important}:host ::ng-deep .cax-overlay{position:absolute;bottom:0!important;top:unset!important;left:unset!important}:host ::ng-deep .suggestion-panel{background:var(--white-100);box-shadow:0 2px 8px #0000001a;z-index:9999;padding:12px 4px;margin-left:20px;width:224px;border-radius:8px;border:1px solid var(--neutral-200)}:host ::ng-deep .suggestion-panel .suggestion-list{max-height:200px;overflow-y:auto;will-change:transform;transform:translateZ(0);backface-visibility:hidden;-webkit-backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:rgba(94,92,92,.3) transparent}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item{padding:6px 8px;cursor:pointer;display:flex;justify-content:space-between;transition:background-color .2s ease;align-items:center}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item:hover{background-color:var(--neutral-50)}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item.selected{background-color:var(--neutral-50)}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item .suggestion-label{font-weight:500}:host ::ng-deep .suggestion-panel .suggestion-list .suggestion-item .suggestion-id{color:var(--neutral-600);font-size:.85em}:host ::ng-deep .suggestion-panel .suggestion-list:after{content:\"\";display:block;min-height:20px;scroll-snap-align:end}:host ::ng-deep .suggestion-panel .suggestion-list::-webkit-scrollbar-track{background:transparent}:host ::ng-deep .suggestion-panel .suggestion-list::-webkit-scrollbar-thumb{background-color:#5553531a;border-radius:4px;border:2px solid transparent;background-clip:padding-box}:host ::ng-deep .chat-footer{position:relative;z-index:1}:host ::ng-deep .cax-chat-input-container .chat-input .cax-paperclip{color:var(--neutral-900)!important}:host ::ng-deep .files-list-overlay{border:1px solid var(--neutral-200);background:var(--white-100);box-shadow:0 2px 8px #0000001a;z-index:9999;padding:12px 4px;width:224px;border-radius:8px}:host ::ng-deep .files-list-overlay.cax-overlay{transform-origin:right center!important}:host ::ng-deep .files-list-overlay .files-overlay-content{max-height:200px;overflow-y:auto}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item{transition:background-color .2s ease;padding:6px 8px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;will-change:transform;transform:translateZ(0);backface-visibility:hidden;-webkit-backface-visibility:hidden;scrollbar-width:thin;scrollbar-color:rgba(94,92,92,.3) transparent}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item:hover{background-color:var(--neutral-50)}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item .file-name{flex:1;font-size:14px;font-weight:500;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:8px}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item .file-size{color:var(--neutral-600);font-size:12px;margin-right:8px}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item i.cax-times{color:var(--neutral-600);cursor:pointer;border:1.1rem solid var(--neutral-200);transition:color .2s ease}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item:after{content:\"\";display:block;min-height:20px;scroll-snap-align:end}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item::-webkit-scrollbar-track{background:transparent}:host ::ng-deep .files-list-overlay .files-overlay-content .file-item::-webkit-scrollbar-thumb{background-color:#5553531a;border-radius:4px;border:2px solid transparent;background-clip:padding-box}}\n"] }]
513
+ }], ctorParameters: () => [{ type: i1.DomSanitizer }], propDecorators: { comments: [{
514
+ type: Input
515
+ }], mentionSuggestions: [{
516
+ type: Input
517
+ }], hashtagSuggestions: [{
518
+ type: Input
519
+ }], sidebarHeader: [{
520
+ type: Input
521
+ }], isAdmin: [{
522
+ type: Input
523
+ }], commentAdded: [{
524
+ type: Output
525
+ }], fileSelected: [{
526
+ type: Output
527
+ }], fileDownload: [{
528
+ type: Output
529
+ }], visibleChange: [{
530
+ type: Output
531
+ }], fileInput: [{
532
+ type: ViewChild,
533
+ args: ['fileInput']
534
+ }], suggestionPanel: [{
535
+ type: ViewChild,
536
+ args: ['suggestionPanel']
537
+ }], inputText: [{
538
+ type: ViewChild,
539
+ args: ['inputText']
540
+ }], commentsList: [{
541
+ type: ViewChild,
542
+ args: ['commentsList']
543
+ }], countChip: [{
544
+ type: ViewChild,
545
+ args: ['countChip']
546
+ }], visible: [{
547
+ type: Input
548
+ }], onDocumentClick: [{
549
+ type: HostListener,
550
+ args: ['document:click', ['$event']]
551
+ }] } });
552
+
553
+ class CommentboxModule {
554
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: CommentboxModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
555
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.9", ngImport: i0, type: CommentboxModule, declarations: [CommentboxComponent], imports: [CommonModule, FormsModule, Sidebar, InputTextModule, ButtonModule, ChipModule, OverlayModule, AvatarModule, RippleModule, SharedModule], exports: [CommentboxComponent] });
556
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: CommentboxModule, imports: [CommonModule, FormsModule, Sidebar, InputTextModule, ButtonModule, ChipModule, OverlayModule, AvatarModule, RippleModule, SharedModule] });
557
+ }
558
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.9", ngImport: i0, type: CommentboxModule, decorators: [{
559
+ type: NgModule,
560
+ args: [{
561
+ declarations: [CommentboxComponent],
562
+ imports: [CommonModule, FormsModule, Sidebar, InputTextModule, ButtonModule, ChipModule, OverlayModule, AvatarModule, RippleModule, SharedModule],
563
+ exports: [CommentboxComponent]
564
+ }]
565
+ }] });
566
+
567
+ /**
568
+ * Generated bundle index. Do not edit.
569
+ */
570
+
571
+ export { CommentboxComponent, CommentboxModule };
572
+ //# sourceMappingURL=cax-design-system-commentbox.mjs.map