@salesforcedevs/docs-components 1.17.0-hack-alpha4 → 1.17.0-hack-alpha6

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.
@@ -133,6 +133,72 @@
133
133
  padding: 0 24px 24px;
134
134
  }
135
135
 
136
+ /* Success Message */
137
+ .success-message {
138
+ display: flex;
139
+ align-items: center;
140
+ gap: 8px;
141
+ background: #e8f5e8;
142
+ color: #2e7d32;
143
+ padding: 12px 16px;
144
+ border-radius: 8px;
145
+ margin-bottom: 16px;
146
+ border-left: 4px solid #4caf50;
147
+ font-size: 14px;
148
+ }
149
+
150
+ .success-message dx-icon {
151
+ --sds-c-icon-color-foreground: #4caf50;
152
+ }
153
+
154
+ /* API Error Message */
155
+ .api-error {
156
+ display: flex;
157
+ align-items: center;
158
+ gap: 8px;
159
+ background: #ffebee;
160
+ color: #c62828;
161
+ padding: 12px 16px;
162
+ border-radius: 8px;
163
+ margin-bottom: 16px;
164
+ border-left: 4px solid #f44336;
165
+ font-size: 14px;
166
+ }
167
+
168
+ .api-error dx-icon {
169
+ --sds-c-icon-color-foreground: #f44336;
170
+ }
171
+
172
+ /* Loading Message */
173
+ .loading-message {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 8px;
177
+ background: #e3f2fd;
178
+ color: #1565c0;
179
+ padding: 12px 16px;
180
+ border-radius: 8px;
181
+ margin-bottom: 16px;
182
+ border-left: 4px solid #2196f3;
183
+ font-size: 14px;
184
+ }
185
+
186
+ .loading-message dx-icon {
187
+ --sds-c-icon-color-foreground: #2196f3;
188
+
189
+ animation: spin 1s linear infinite;
190
+ }
191
+
192
+ @keyframes spin {
193
+ from {
194
+ transform: rotate(0deg);
195
+ }
196
+
197
+ to {
198
+ transform: rotate(360deg);
199
+ }
200
+ }
201
+
136
202
  /* Comment Form */
137
203
  .comment-form {
138
204
  margin-bottom: 24px;
@@ -277,6 +343,13 @@
277
343
  padding: 0 16px 16px;
278
344
  }
279
345
 
346
+ .success-message,
347
+ .api-error,
348
+ .loading-message {
349
+ padding: 10px 12px;
350
+ font-size: 13px;
351
+ }
352
+
280
353
  .comments-section {
281
354
  padding-top: 16px;
282
355
  }
@@ -297,6 +370,24 @@
297
370
  border-bottom-color: #424242;
298
371
  }
299
372
 
373
+ .success-message {
374
+ background: #1b5e20;
375
+ color: #a5d6a7;
376
+ border-left-color: #4caf50;
377
+ }
378
+
379
+ .api-error {
380
+ background: #b71c1c;
381
+ color: #ef9a9a;
382
+ border-left-color: #f44336;
383
+ }
384
+
385
+ .loading-message {
386
+ background: #0d47a1;
387
+ color: #90caf9;
388
+ border-left-color: #2196f3;
389
+ }
390
+
300
391
  .form-label {
301
392
  color: #e0e0e0;
302
393
  }
@@ -35,8 +35,27 @@
35
35
  ></dx-button>
36
36
  </div>
37
37
 
38
- <!-- Comment Form -->
38
+ <!-- Popup Content -->
39
39
  <div class="popup-content">
40
+ <!-- Success Message -->
41
+ <div if:true={submitSuccess} class="success-message">
42
+ <dx-icon icon-symbol="success" icon-size="small"></dx-icon>
43
+ <span>Comment posted successfully!</span>
44
+ </div>
45
+
46
+ <!-- API Error Message -->
47
+ <div if:true={apiError} class="error-message api-error">
48
+ <dx-icon icon-symbol="error" icon-size="small"></dx-icon>
49
+ <span>{apiError}</span>
50
+ </div>
51
+
52
+ <!-- Loading State -->
53
+ <div if:true={isLoading} class="loading-message">
54
+ <dx-icon icon-symbol="spinner" icon-size="small"></dx-icon>
55
+ <span>Loading comments...</span>
56
+ </div>
57
+
58
+ <!-- Comment Form -->
40
59
  <form class="comment-form" onsubmit={handleSubmit}>
41
60
  <div class="form-group">
42
61
  <label for="email-input" class="form-label">
@@ -3,25 +3,88 @@ import { normalizeBoolean } from "dxUtils/normalizers";
3
3
 
4
4
  interface Comment {
5
5
  email: string;
6
- comment: string;
7
- url: string;
8
- timestamp: Date;
6
+ comment_text: string;
7
+ timestamp: string;
9
8
  }
10
9
 
10
+ interface ApiCommentPayload {
11
+ branch: string;
12
+ file_path: string;
13
+ heading_title: string;
14
+ start_line: string;
15
+ end_line: string;
16
+ comment: {
17
+ comment_text: string;
18
+ email: string;
19
+ timestamp: string;
20
+ };
21
+ }
22
+
23
+ interface ApiCommentResponse {
24
+ request_branch: string;
25
+ paths: Array<{
26
+ path: string;
27
+ titles: Array<{
28
+ title: string;
29
+ comments: Comment[];
30
+ }>;
31
+ }>;
32
+ }
33
+
34
+ // Local storage key for comments
35
+ const COMMENTS_STORAGE_KEY = "dsc_comments";
36
+
11
37
  export default class CommentPopup extends LightningElement {
12
- @api iconSymbol = "chat";
13
- @api iconSize = "medium";
14
- @api popupTitle = "Leave a Comment";
15
- @api submitButtonLabel = "Post Comment";
16
- @api emailPlaceholder = "Enter your email";
17
- @api commentPlaceholder = "Enter your comment";
18
- @api index?: string;
19
- @api contentType?: string;
20
- @api headingTitle?: string;
21
- @api filePath?: string;
22
- @api startLine?: string;
23
- @api endLine?: string;
24
- @api currentBranch?: string;
38
+ iconSymbol = "chat";
39
+ iconSize = "medium";
40
+ popupTitle = "Leave a Comment";
41
+ submitButtonLabel = "Post Comment";
42
+ emailPlaceholder = "Enter your email";
43
+ commentPlaceholder = "Enter your comment";
44
+
45
+ private _headingTitle?: string;
46
+ private _filePath?: string;
47
+ private _startLine?: string;
48
+ private _endLine?: string;
49
+ private _currentBranch?: string;
50
+
51
+ @api get headingTitle() {
52
+ return this._headingTitle;
53
+ }
54
+ set headingTitle(value) {
55
+ this._headingTitle = value;
56
+ this.handlePropertyChange();
57
+ }
58
+
59
+ @api get filePath() {
60
+ return this._filePath;
61
+ }
62
+ set filePath(value) {
63
+ this._filePath = value;
64
+ this.handlePropertyChange();
65
+ }
66
+
67
+ @api get startLine() {
68
+ return this._startLine;
69
+ }
70
+ set startLine(value) {
71
+ this._startLine = value;
72
+ }
73
+
74
+ @api get endLine() {
75
+ return this._endLine;
76
+ }
77
+ set endLine(value) {
78
+ this._endLine = value;
79
+ }
80
+
81
+ @api get currentBranch() {
82
+ return this._currentBranch;
83
+ }
84
+ set currentBranch(value) {
85
+ this._currentBranch = value;
86
+ this.handlePropertyChange();
87
+ }
25
88
 
26
89
  private _open = false;
27
90
  @api get open() {
@@ -37,10 +100,11 @@ export default class CommentPopup extends LightningElement {
37
100
  @track private emailError = "";
38
101
  @track private commentError = "";
39
102
  @track private isSubmitting = false;
103
+ @track private isLoading = false;
104
+ @track private apiError = "";
105
+ @track private submitSuccess = false;
40
106
 
41
107
  get showComments() {
42
- const unwrapped = JSON.parse(JSON.stringify(this.comments));
43
- console.log("Comments (JSON unwrapped):", unwrapped);
44
108
  return this.comments.length > 0;
45
109
  }
46
110
 
@@ -54,7 +118,9 @@ export default class CommentPopup extends LightningElement {
54
118
 
55
119
  get sortedComments() {
56
120
  return [...this.comments].sort(
57
- (a, b) => b.timestamp.getTime() - a.timestamp.getTime()
121
+ (a, b) =>
122
+ new Date(b.timestamp).getTime() -
123
+ new Date(a.timestamp).getTime()
58
124
  );
59
125
  }
60
126
 
@@ -68,31 +134,54 @@ export default class CommentPopup extends LightningElement {
68
134
 
69
135
  handleIconClick() {
70
136
  this._open = !this._open;
137
+ // Comments are already loaded when component is connected, no need to fetch again
138
+ }
139
+
140
+ private handlePropertyChange() {
141
+ // Only fetch comments if component has required properties
142
+ if (this._currentBranch && this._filePath && this._headingTitle) {
143
+ this.fetchComments();
144
+ }
71
145
  }
72
146
 
73
147
  handleEmailChange(event: Event) {
74
148
  this.email = (event.target as HTMLInputElement).value;
75
149
  this.emailError = "";
150
+ this.apiError = "";
151
+ this.submitSuccess = false;
76
152
  }
77
153
 
78
154
  handleCommentChange(event: Event) {
79
155
  this.comment = (event.target as HTMLTextAreaElement).value;
80
156
  this.commentError = "";
157
+ this.apiError = "";
158
+ this.submitSuccess = false;
81
159
  }
82
160
 
83
- handleSubmit() {
161
+ async handleSubmit() {
84
162
  if (!this.validateForm()) {
85
163
  return;
86
164
  }
87
165
 
88
166
  this.isSubmitting = true;
167
+ this.apiError = "";
168
+ this.submitSuccess = false;
89
169
 
90
- // Simulate API call
91
- setTimeout(() => {
92
- this.addComment();
170
+ try {
171
+ await this.addComment();
93
172
  this.resetForm();
173
+ this.submitSuccess = true;
174
+
175
+ // Auto-hide success message after 3 seconds
176
+ setTimeout(() => {
177
+ this.submitSuccess = false;
178
+ }, 3000);
179
+ } catch (error) {
180
+ console.error("Error submitting comment:", error);
181
+ this.apiError = "Failed to post comment. Please try again.";
182
+ } finally {
94
183
  this.isSubmitting = false;
95
- }, 500);
184
+ }
96
185
  }
97
186
 
98
187
  private validateForm(): boolean {
@@ -119,22 +208,115 @@ export default class CommentPopup extends LightningElement {
119
208
  return emailRegex.test(email);
120
209
  }
121
210
 
122
- private addComment() {
123
- const newComment: Comment = {
124
- email: this.email.trim(),
125
- comment: this.comment.trim(),
126
- url: window.location.href,
127
- timestamp: new Date()
211
+ private async addComment() {
212
+ // Validate required fields before creating payload
213
+ if (!this._currentBranch || !this._filePath || !this._headingTitle) {
214
+ throw new Error(
215
+ "Missing required fields: branch, file_path, or heading_title"
216
+ );
217
+ }
218
+
219
+ const payload: ApiCommentPayload = {
220
+ branch: this._currentBranch,
221
+ file_path: this._filePath,
222
+ heading_title: this._headingTitle,
223
+ start_line: this._startLine || "",
224
+ end_line: this._endLine || "",
225
+ comment: {
226
+ comment_text: this.comment.trim(),
227
+ email: this.email.trim(),
228
+ timestamp: new Date().toISOString()
229
+ }
128
230
  };
129
231
 
130
- this.comments = [newComment, ...this.comments];
232
+ console.log(
233
+ "Posting comment with payload:",
234
+ JSON.stringify(payload, null, 2)
235
+ );
236
+
237
+ // LOCAL STORAGE IMPLEMENTATION (Temporary until backend is ready)
238
+ try {
239
+ await this.saveCommentToLocalStorage(payload);
240
+ console.log("Comment saved to local storage successfully");
241
+
242
+ // Refresh comments after successful save
243
+ await this.fetchComments();
244
+
245
+ // Dispatch custom event
246
+ this.dispatchEvent(
247
+ new CustomEvent("commentadded", {
248
+ detail: {
249
+ ...payload.comment,
250
+ id: Date.now().toString() // Generate temporary ID
251
+ }
252
+ })
253
+ );
254
+ } catch (error) {
255
+ console.error("Error saving comment to local storage:", error);
256
+ throw error;
257
+ }
258
+
259
+ // API IMPLEMENTATION (Commented until backend is ready)
260
+ /*
261
+ const response = await fetch('/post-comment', {
262
+ method: 'POST',
263
+ headers: {
264
+ 'Content-Type': 'application/json'
265
+ },
266
+ body: JSON.stringify(payload)
267
+ });
268
+
269
+ if (!response.ok) {
270
+ const errorText = await response.text();
271
+ throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
272
+ }
273
+
274
+ const responseData = await response.json();
275
+ console.log('Comment posted successfully:', responseData);
276
+
277
+ // Refresh comments after successful post
278
+ await this.fetchComments();
131
279
 
132
280
  // Dispatch custom event
133
281
  this.dispatchEvent(
134
282
  new CustomEvent("commentadded", {
135
- detail: newComment
283
+ detail: {
284
+ ...payload.comment,
285
+ id: responseData.id // Include any ID returned by the API
286
+ }
136
287
  })
137
288
  );
289
+ */
290
+ }
291
+
292
+ private async saveCommentToLocalStorage(payload: ApiCommentPayload) {
293
+ try {
294
+ // Get existing comments from localStorage
295
+ const existingData = localStorage.getItem(COMMENTS_STORAGE_KEY);
296
+ const allComments = existingData ? JSON.parse(existingData) : {};
297
+
298
+ // Create a unique key for this specific comment location
299
+ const commentKey = `${payload.branch}_${payload.file_path}_${payload.heading_title}`;
300
+
301
+ // Initialize array for this location if it doesn't exist
302
+ if (!allComments[commentKey]) {
303
+ allComments[commentKey] = [];
304
+ }
305
+
306
+ // Add the new comment
307
+ allComments[commentKey].push(payload.comment);
308
+
309
+ // Save back to localStorage
310
+ localStorage.setItem(
311
+ COMMENTS_STORAGE_KEY,
312
+ JSON.stringify(allComments)
313
+ );
314
+
315
+ console.log("Comments saved to localStorage:", allComments);
316
+ } catch (error) {
317
+ console.error("Error saving to localStorage:", error);
318
+ throw new Error("Failed to save comment to local storage");
319
+ }
138
320
  }
139
321
 
140
322
  private resetForm() {
@@ -162,6 +344,8 @@ export default class CommentPopup extends LightningElement {
162
344
  handleClose() {
163
345
  this._open = false;
164
346
  this.resetForm();
347
+ this.apiError = "";
348
+ this.submitSuccess = false;
165
349
  }
166
350
 
167
351
  handleOverlayClick(event: Event) {
@@ -178,15 +362,121 @@ export default class CommentPopup extends LightningElement {
178
362
 
179
363
  connectedCallback() {
180
364
  document.addEventListener("keydown", this.handleKeyDown.bind(this));
365
+ // Fetch comments when component is connected
366
+ this.fetchComments();
367
+ }
368
+
369
+ private async fetchComments() {
370
+ if (!this._currentBranch || !this._filePath || !this._headingTitle) {
371
+ console.warn("Cannot fetch comments: missing required parameters");
372
+ return;
373
+ }
374
+
375
+ this.isLoading = true;
376
+ this.apiError = "";
377
+
378
+ try {
379
+ // LOCAL STORAGE IMPLEMENTATION (Temporary until backend is ready)
380
+ const comments = await this.getCommentsFromLocalStorage();
381
+ console.log("Fetched comments from localStorage:", comments);
382
+ this.comments = comments;
383
+
384
+ // API IMPLEMENTATION (Commented until backend is ready)
385
+ /*
386
+ const params = new URLSearchParams({
387
+ branch: this._currentBranch
388
+ });
389
+
390
+ console.log('Fetching comments with params:', params.toString());
391
+
392
+ const response = await fetch(`/get-comments?${params.toString()}`);
393
+
394
+ if (!response.ok) {
395
+ throw new Error(`Failed to fetch comments: ${response.status} ${response.statusText}`);
396
+ }
397
+
398
+ const data: ApiCommentResponse = await response.json();
399
+ console.log('Fetched comments from API:', data);
400
+
401
+ // Find comments for this specific file path and heading title
402
+ const comments = this.extractCommentsFromApiResponse(data);
403
+ this.comments = comments;
404
+ */
405
+ } catch (error) {
406
+ console.error("Error fetching comments:", error);
407
+ this.apiError = "Failed to load comments. Please try again later.";
408
+ this.comments = [];
409
+ } finally {
410
+ this.isLoading = false;
411
+ }
412
+ }
413
+
414
+ private extractCommentsFromApiResponse(
415
+ data: ApiCommentResponse
416
+ ): Comment[] {
417
+ try {
418
+ // Find the path that matches our file path
419
+ const matchingPath = data.paths.find(
420
+ (path) => path.path === this._filePath
421
+ );
422
+
423
+ if (!matchingPath) {
424
+ console.log(
425
+ `No comments found for file path: ${this._filePath}`
426
+ );
427
+ return [];
428
+ }
429
+
430
+ // Find the title that matches our heading title
431
+ const matchingTitle = matchingPath.titles.find(
432
+ (title) => title.title === this._headingTitle
433
+ );
434
+
435
+ if (!matchingTitle) {
436
+ console.log(
437
+ `No comments found for heading title: ${this._headingTitle}`
438
+ );
439
+ return [];
440
+ }
441
+
442
+ console.log(
443
+ `Found ${matchingTitle.comments.length} comments for ${this._filePath} - ${this._headingTitle}`
444
+ );
445
+ return matchingTitle.comments;
446
+ } catch (error) {
447
+ console.error(
448
+ "Error extracting comments from API response:",
449
+ error
450
+ );
451
+ return [];
452
+ }
453
+ }
454
+
455
+ private async getCommentsFromLocalStorage(): Promise<Comment[]> {
456
+ try {
457
+ const existingData = localStorage.getItem(COMMENTS_STORAGE_KEY);
458
+ if (!existingData) {
459
+ return [];
460
+ }
461
+
462
+ const allComments = JSON.parse(existingData);
463
+ const commentKey = `${this._currentBranch}_${this._filePath}_${this._headingTitle}`;
464
+
465
+ return allComments[commentKey] || [];
466
+ } catch (error) {
467
+ console.error("Error reading from localStorage:", error);
468
+ return [];
469
+ }
181
470
  }
182
471
 
183
472
  disconnectedCallback() {
184
473
  document.removeEventListener("keydown", this.handleKeyDown.bind(this));
185
474
  }
186
475
 
187
- private formatTimestamp(timestamp: Date): string {
476
+ private formatTimestamp(timestamp: string): string {
188
477
  const now = new Date();
189
- const diff = now.getTime() - timestamp.getTime();
478
+ const commentDate = new Date(timestamp);
479
+ const diff = now.getTime() - commentDate.getTime();
190
480
  const minutes = Math.floor(diff / 60000);
191
481
  const hours = Math.floor(diff / 3600000);
192
482
  const days = Math.floor(diff / 86400000);
@@ -204,6 +494,7 @@ export default class CommentPopup extends LightningElement {
204
494
  get formattedComments() {
205
495
  return this.sortedComments.map((comment) => ({
206
496
  ...comment,
497
+ comment: comment.comment_text, // Map API field to template expectation
207
498
  formattedTimestamp: this.formatTimestamp(comment.timestamp),
208
499
  maskedEmail: this.maskEmail(comment.email)
209
500
  }));