@salesforcedevs/docs-components 1.17.0-hack-alpha8 → 1.17.2-accessfix-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,565 +0,0 @@
1
- import { LightningElement, api, track } from "lwc";
2
- import { normalizeBoolean } from "dxUtils/normalizers";
3
-
4
- interface Comment {
5
- email: string;
6
- comment_text: string;
7
- timestamp: string;
8
- }
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
- const API_URL = "https://cx-helper-engine-2-356c8dd6c7cc.herokuapp.com";
37
-
38
- export default class CommentPopup extends LightningElement {
39
- iconSymbol = "chat";
40
- iconSize = "medium";
41
- popupTitle = "Leave a Comment";
42
- submitButtonLabel = "Post Comment";
43
- emailPlaceholder = "Enter your email";
44
- commentPlaceholder = "Enter your comment";
45
-
46
- private _headingTitle?: string;
47
- private _filePath?: string;
48
- private _startLine?: string;
49
- private _endLine?: string;
50
- private _currentBranch?: string;
51
-
52
- @api get headingTitle() {
53
- return this._headingTitle;
54
- }
55
- set headingTitle(value) {
56
- this._headingTitle = value;
57
- this.handlePropertyChange();
58
- }
59
-
60
- @api get filePath() {
61
- return this._filePath;
62
- }
63
- set filePath(value) {
64
- this._filePath = value;
65
- this.handlePropertyChange();
66
- }
67
-
68
- @api get startLine() {
69
- return this._startLine;
70
- }
71
- set startLine(value) {
72
- this._startLine = value;
73
- }
74
-
75
- @api get endLine() {
76
- return this._endLine;
77
- }
78
- set endLine(value) {
79
- this._endLine = value;
80
- }
81
-
82
- @api get currentBranch() {
83
- return this._currentBranch;
84
- }
85
- set currentBranch(value) {
86
- this._currentBranch = value;
87
- this.handlePropertyChange();
88
- }
89
-
90
- private _open = false;
91
- @api get open() {
92
- return this._open;
93
- }
94
- set open(value) {
95
- this._open = normalizeBoolean(value);
96
- }
97
-
98
- @track private comments: Comment[] = [];
99
- @track private email = "";
100
- @track private comment = "";
101
- @track private emailError = "";
102
- @track private commentError = "";
103
- @track private isSubmitting = false;
104
- @track private isLoading = false;
105
- @track private apiError = "";
106
- @track private submitSuccess = false;
107
-
108
- get showComments() {
109
- return this.comments.length > 0;
110
- }
111
-
112
- get commentCount() {
113
- return this.comments.length;
114
- }
115
-
116
- get showCommentCount() {
117
- return this.commentCount > 0;
118
- }
119
-
120
- get sortedComments() {
121
- return [...this.comments].sort(
122
- (a, b) =>
123
- new Date(b.timestamp).getTime() -
124
- new Date(a.timestamp).getTime()
125
- );
126
- }
127
-
128
- get isFormValid() {
129
- return this.email.trim() !== "" && this.comment.trim() !== "";
130
- }
131
-
132
- get submitButtonDisabled() {
133
- return !this.isFormValid || this.isSubmitting;
134
- }
135
-
136
- handleIconClick() {
137
- this._open = !this._open;
138
- // Comments are already loaded when component is connected, no need to fetch again
139
- }
140
-
141
- private handlePropertyChange() {
142
- // Only fetch comments if component has required properties
143
- if (this._currentBranch && this._filePath && this._headingTitle) {
144
- this.fetchComments();
145
- }
146
- }
147
-
148
- handleEmailChange(event: Event) {
149
- this.email = (event.target as HTMLInputElement).value;
150
- this.emailError = "";
151
- this.apiError = "";
152
- this.submitSuccess = false;
153
- }
154
-
155
- handleCommentChange(event: Event) {
156
- this.comment = (event.target as HTMLTextAreaElement).value;
157
- this.commentError = "";
158
- this.apiError = "";
159
- this.submitSuccess = false;
160
- }
161
-
162
- async handleSubmit() {
163
- if (!this.validateForm()) {
164
- return;
165
- }
166
-
167
- this.isSubmitting = true;
168
- this.apiError = "";
169
- this.submitSuccess = false;
170
-
171
- try {
172
- await this.addComment();
173
- this.resetForm();
174
- this.submitSuccess = true;
175
-
176
- // Auto-hide success message after 3 seconds
177
- setTimeout(() => {
178
- this.submitSuccess = false;
179
- }, 3000);
180
- } catch (error) {
181
- console.error("Error submitting comment:", error);
182
- this.apiError = "Failed to post comment. Please try again.";
183
- } finally {
184
- this.isSubmitting = false;
185
- }
186
- }
187
-
188
- private validateForm(): boolean {
189
- let isValid = true;
190
-
191
- if (!this.email.trim()) {
192
- this.emailError = "Email is required";
193
- isValid = false;
194
- } else if (!this.isValidEmail(this.email)) {
195
- this.emailError = "Please enter a valid email address";
196
- isValid = false;
197
- }
198
-
199
- if (!this.comment.trim()) {
200
- this.commentError = "Comment is required";
201
- isValid = false;
202
- }
203
-
204
- return isValid;
205
- }
206
-
207
- private isValidEmail(email: string): boolean {
208
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
209
- return emailRegex.test(email);
210
- }
211
-
212
- private async addComment() {
213
- // Validate required fields before creating payload
214
- if (!this._currentBranch || !this._filePath || !this._headingTitle) {
215
- throw new Error(
216
- "Missing required fields: branch, file_path, or heading_title"
217
- );
218
- }
219
-
220
- const payload: ApiCommentPayload = {
221
- branch: this._currentBranch,
222
- file_path: this._filePath,
223
- heading_title: this._headingTitle,
224
- start_line: this._startLine || "",
225
- end_line: this._endLine || "",
226
- comment: {
227
- comment_text: this.comment.trim(),
228
- email: this.email.trim(),
229
- timestamp: new Date().toISOString()
230
- }
231
- };
232
-
233
- console.log(
234
- "Posting comment with payload:",
235
- JSON.stringify(payload, null, 2)
236
- );
237
-
238
- // Try API first, fallback to localStorage
239
- try {
240
- const response = await fetch(`${API_URL}/post-comment`, {
241
- method: "POST",
242
- headers: {
243
- "Content-Type": "application/json"
244
- },
245
- body: JSON.stringify(payload)
246
- });
247
-
248
- if (response.ok) {
249
- const responseData = await response.json();
250
- console.log(
251
- "Comment posted successfully via API:",
252
- responseData
253
- );
254
-
255
- // Refresh comments after successful post
256
- await this.fetchComments();
257
-
258
- // Dispatch custom event
259
- this.dispatchEvent(
260
- new CustomEvent("commentadded", {
261
- detail: {
262
- ...payload.comment,
263
- id: responseData.id // Include any ID returned by the API
264
- }
265
- })
266
- );
267
- } else {
268
- console.warn(
269
- `API call failed (${response.status}): ${response.statusText}, falling back to localStorage`
270
- );
271
- // Fallback to localStorage if API fails
272
- await this.saveCommentToLocalStorage(payload);
273
- console.log(
274
- "Comment saved to local storage successfully (fallback)"
275
- );
276
-
277
- // Refresh comments after successful save
278
- await this.fetchComments();
279
-
280
- // Dispatch custom event
281
- this.dispatchEvent(
282
- new CustomEvent("commentadded", {
283
- detail: {
284
- ...payload.comment,
285
- id: Date.now().toString() // Generate temporary ID
286
- }
287
- })
288
- );
289
- }
290
- } catch (error) {
291
- console.error(
292
- "Error posting comment via API, falling back to localStorage:",
293
- error
294
- );
295
- // Fallback to localStorage if API call throws an error
296
- try {
297
- await this.saveCommentToLocalStorage(payload);
298
- console.log(
299
- "Comment saved to local storage successfully (fallback)"
300
- );
301
-
302
- // Refresh comments after successful save
303
- await this.fetchComments();
304
-
305
- // Dispatch custom event
306
- this.dispatchEvent(
307
- new CustomEvent("commentadded", {
308
- detail: {
309
- ...payload.comment,
310
- id: Date.now().toString() // Generate temporary ID
311
- }
312
- })
313
- );
314
- } catch (localStorageError) {
315
- console.error(
316
- "Error saving comment to local storage:",
317
- localStorageError
318
- );
319
- throw localStorageError;
320
- }
321
- }
322
- }
323
-
324
- private async saveCommentToLocalStorage(payload: ApiCommentPayload) {
325
- try {
326
- // Get existing comments from localStorage
327
- const existingData = localStorage.getItem(COMMENTS_STORAGE_KEY);
328
- const allComments = existingData ? JSON.parse(existingData) : {};
329
-
330
- // Create a unique key for this specific comment location
331
- const commentKey = `${payload.branch}_${payload.file_path}_${payload.heading_title}`;
332
-
333
- // Initialize array for this location if it doesn't exist
334
- if (!allComments[commentKey]) {
335
- allComments[commentKey] = [];
336
- }
337
-
338
- // Add the new comment
339
- allComments[commentKey].push(payload.comment);
340
-
341
- // Save back to localStorage
342
- localStorage.setItem(
343
- COMMENTS_STORAGE_KEY,
344
- JSON.stringify(allComments)
345
- );
346
-
347
- console.log("Comments saved to localStorage:", allComments);
348
- } catch (error) {
349
- console.error("Error saving to localStorage:", error);
350
- throw new Error("Failed to save comment to local storage");
351
- }
352
- }
353
-
354
- private resetForm() {
355
- this.email = "";
356
- this.comment = "";
357
- this.emailError = "";
358
- this.commentError = "";
359
-
360
- // Force DOM update by accessing the form elements
361
- const emailInput = this.template.querySelector(
362
- "#email-input"
363
- ) as HTMLInputElement;
364
- const commentInput = this.template.querySelector(
365
- "#comment-input"
366
- ) as HTMLTextAreaElement;
367
-
368
- if (emailInput) {
369
- emailInput.value = "";
370
- }
371
- if (commentInput) {
372
- commentInput.value = "";
373
- }
374
- }
375
-
376
- handleClose() {
377
- this._open = false;
378
- this.resetForm();
379
- this.apiError = "";
380
- this.submitSuccess = false;
381
- }
382
-
383
- handleOverlayClick(event: Event) {
384
- if (event.target === event.currentTarget) {
385
- this.handleClose();
386
- }
387
- }
388
-
389
- handleKeyDown(event: KeyboardEvent) {
390
- if (event.key === "Escape") {
391
- this.handleClose();
392
- }
393
- }
394
-
395
- connectedCallback() {
396
- document.addEventListener("keydown", this.handleKeyDown.bind(this));
397
- // Fetch comments when component is connected
398
- this.fetchComments();
399
- }
400
-
401
- private async fetchComments() {
402
- if (!this._currentBranch || !this._filePath || !this._headingTitle) {
403
- console.warn("Cannot fetch comments: missing required parameters");
404
- return;
405
- }
406
-
407
- this.isLoading = true;
408
- this.apiError = "";
409
-
410
- try {
411
- // API IMPLEMENTATION - Try to fetch from /get-comments endpoint
412
- const params = new URLSearchParams({
413
- branch: this._currentBranch
414
- });
415
-
416
- console.log("Fetching comments with params:", params.toString());
417
-
418
- const response = await fetch(
419
- `${API_URL}/get-comments?${params.toString()}`
420
- );
421
-
422
- if (response.ok) {
423
- const data: ApiCommentResponse = await response.json();
424
- console.log("Fetched comments from API:", data);
425
-
426
- // Find comments for this specific file path and heading title
427
- const comments = this.extractCommentsFromApiResponse(data);
428
- this.comments = comments;
429
- } else {
430
- console.warn(
431
- `API call failed (${response.status}): ${response.statusText}, falling back to localStorage`
432
- );
433
- // Fallback to localStorage if API fails
434
- const comments = await this.getCommentsFromLocalStorage();
435
- console.log(
436
- "Fetched comments from localStorage (fallback):",
437
- comments
438
- );
439
- this.comments = comments;
440
- }
441
- } catch (error) {
442
- console.error(
443
- "Error fetching comments from API, falling back to localStorage:",
444
- error
445
- );
446
- // Fallback to localStorage if API call throws an error
447
- try {
448
- const comments = await this.getCommentsFromLocalStorage();
449
- console.log(
450
- "Fetched comments from localStorage (fallback):",
451
- comments
452
- );
453
- this.comments = comments;
454
- } catch (localStorageError) {
455
- console.error(
456
- "Error fetching comments from localStorage:",
457
- localStorageError
458
- );
459
- this.apiError =
460
- "Failed to load comments. Please try again later.";
461
- this.comments = [];
462
- }
463
- } finally {
464
- this.isLoading = false;
465
- }
466
- }
467
-
468
- private extractCommentsFromApiResponse(
469
- data: ApiCommentResponse
470
- ): Comment[] {
471
- try {
472
- // Find the path that matches our file path
473
- const matchingPath = data.paths.find(
474
- (path) => path.path === this._filePath
475
- );
476
-
477
- if (!matchingPath) {
478
- console.log(
479
- `No comments found for file path: ${this._filePath}`
480
- );
481
- return [];
482
- }
483
-
484
- // Find the title that matches our heading title
485
- const matchingTitle = matchingPath.titles.find(
486
- (title) => title.title === this._headingTitle
487
- );
488
-
489
- if (!matchingTitle) {
490
- console.log(
491
- `No comments found for heading title: ${this._headingTitle}`
492
- );
493
- return [];
494
- }
495
-
496
- console.log(
497
- `Found ${matchingTitle.comments.length} comments for ${this._filePath} - ${this._headingTitle}`
498
- );
499
- return matchingTitle.comments;
500
- } catch (error) {
501
- console.error(
502
- "Error extracting comments from API response:",
503
- error
504
- );
505
- return [];
506
- }
507
- }
508
-
509
- private async getCommentsFromLocalStorage(): Promise<Comment[]> {
510
- try {
511
- const existingData = localStorage.getItem(COMMENTS_STORAGE_KEY);
512
- if (!existingData) {
513
- return [];
514
- }
515
-
516
- const allComments = JSON.parse(existingData);
517
- const commentKey = `${this._currentBranch}_${this._filePath}_${this._headingTitle}`;
518
-
519
- return allComments[commentKey] || [];
520
- } catch (error) {
521
- console.error("Error reading from localStorage:", error);
522
- return [];
523
- }
524
- }
525
-
526
- disconnectedCallback() {
527
- document.removeEventListener("keydown", this.handleKeyDown.bind(this));
528
- }
529
-
530
- private formatTimestamp(timestamp: string): string {
531
- const now = new Date();
532
- const commentDate = new Date(timestamp);
533
- const diff = now.getTime() - commentDate.getTime();
534
- const minutes = Math.floor(diff / 60000);
535
- const hours = Math.floor(diff / 3600000);
536
- const days = Math.floor(diff / 86400000);
537
-
538
- if (minutes < 1) {
539
- return "Just now";
540
- } else if (minutes < 60) {
541
- return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
542
- } else if (hours < 24) {
543
- return `${hours} hour${hours > 1 ? "s" : ""} ago`;
544
- }
545
- return `${days} day${days > 1 ? "s" : ""} ago`;
546
- }
547
-
548
- get formattedComments() {
549
- return this.sortedComments.map((comment) => ({
550
- ...comment,
551
- comment: comment.comment_text, // Map API field to template expectation
552
- formattedTimestamp: this.formatTimestamp(comment.timestamp),
553
- maskedEmail: this.maskEmail(comment.email)
554
- }));
555
- }
556
-
557
- private maskEmail(email: string): string {
558
- const [localPart, domain] = email.split("@");
559
- const maskedLocal =
560
- localPart.length > 2
561
- ? localPart.substring(0, 2) + "*".repeat(localPart.length - 2)
562
- : localPart;
563
- return `${maskedLocal}@${domain}`;
564
- }
565
- }