@salesforcedevs/docs-components 1.17.0-hack-alpha3 → 1.17.0-hack-alpha5

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.
@@ -3,20 +3,34 @@ 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
+ // Local storage key for comments
24
+ const COMMENTS_STORAGE_KEY = "dsc_comments";
25
+
11
26
  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;
27
+ iconSymbol = "chat";
28
+ iconSize = "medium";
29
+ popupTitle = "Leave a Comment";
30
+ submitButtonLabel = "Post Comment";
31
+ emailPlaceholder = "Enter your email";
32
+ commentPlaceholder = "Enter your comment";
33
+
20
34
  @api headingTitle?: string;
21
35
  @api filePath?: string;
22
36
  @api startLine?: string;
@@ -37,10 +51,11 @@ export default class CommentPopup extends LightningElement {
37
51
  @track private emailError = "";
38
52
  @track private commentError = "";
39
53
  @track private isSubmitting = false;
54
+ @track private isLoading = false;
55
+ @track private apiError = "";
56
+ @track private submitSuccess = false;
40
57
 
41
58
  get showComments() {
42
- const unwrapped = JSON.parse(JSON.stringify(this.comments));
43
- console.log("Comments (JSON unwrapped):", unwrapped);
44
59
  return this.comments.length > 0;
45
60
  }
46
61
 
@@ -54,7 +69,9 @@ export default class CommentPopup extends LightningElement {
54
69
 
55
70
  get sortedComments() {
56
71
  return [...this.comments].sort(
57
- (a, b) => b.timestamp.getTime() - a.timestamp.getTime()
72
+ (a, b) =>
73
+ new Date(b.timestamp).getTime() -
74
+ new Date(a.timestamp).getTime()
58
75
  );
59
76
  }
60
77
 
@@ -68,31 +85,49 @@ export default class CommentPopup extends LightningElement {
68
85
 
69
86
  handleIconClick() {
70
87
  this._open = !this._open;
88
+ if (this._open) {
89
+ this.fetchComments();
90
+ }
71
91
  }
72
92
 
73
93
  handleEmailChange(event: Event) {
74
94
  this.email = (event.target as HTMLInputElement).value;
75
95
  this.emailError = "";
96
+ this.apiError = "";
97
+ this.submitSuccess = false;
76
98
  }
77
99
 
78
100
  handleCommentChange(event: Event) {
79
101
  this.comment = (event.target as HTMLTextAreaElement).value;
80
102
  this.commentError = "";
103
+ this.apiError = "";
104
+ this.submitSuccess = false;
81
105
  }
82
106
 
83
- handleSubmit() {
107
+ async handleSubmit() {
84
108
  if (!this.validateForm()) {
85
109
  return;
86
110
  }
87
111
 
88
112
  this.isSubmitting = true;
113
+ this.apiError = "";
114
+ this.submitSuccess = false;
89
115
 
90
- // Simulate API call
91
- setTimeout(() => {
92
- this.addComment();
116
+ try {
117
+ await this.addComment();
93
118
  this.resetForm();
119
+ this.submitSuccess = true;
120
+
121
+ // Auto-hide success message after 3 seconds
122
+ setTimeout(() => {
123
+ this.submitSuccess = false;
124
+ }, 3000);
125
+ } catch (error) {
126
+ console.error("Error submitting comment:", error);
127
+ this.apiError = "Failed to post comment. Please try again.";
128
+ } finally {
94
129
  this.isSubmitting = false;
95
- }, 500);
130
+ }
96
131
  }
97
132
 
98
133
  private validateForm(): boolean {
@@ -119,22 +154,115 @@ export default class CommentPopup extends LightningElement {
119
154
  return emailRegex.test(email);
120
155
  }
121
156
 
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()
157
+ private async addComment() {
158
+ // Validate required fields before creating payload
159
+ if (!this.currentBranch || !this.filePath || !this.headingTitle) {
160
+ throw new Error(
161
+ "Missing required fields: branch, file_path, or heading_title"
162
+ );
163
+ }
164
+
165
+ const payload: ApiCommentPayload = {
166
+ branch: this.currentBranch,
167
+ file_path: this.filePath,
168
+ heading_title: this.headingTitle,
169
+ start_line: this.startLine!,
170
+ end_line: this.endLine!,
171
+ comment: {
172
+ comment_text: this.comment.trim(),
173
+ email: this.email.trim(),
174
+ timestamp: new Date().toISOString()
175
+ }
128
176
  };
129
177
 
130
- this.comments = [newComment, ...this.comments];
178
+ console.log(
179
+ "Posting comment with payload:",
180
+ JSON.stringify(payload, null, 2)
181
+ );
182
+
183
+ // LOCAL STORAGE IMPLEMENTATION (Temporary until backend is ready)
184
+ try {
185
+ await this.saveCommentToLocalStorage(payload);
186
+ console.log("Comment saved to local storage successfully");
187
+
188
+ // Refresh comments after successful save
189
+ await this.fetchComments();
190
+
191
+ // Dispatch custom event
192
+ this.dispatchEvent(
193
+ new CustomEvent("commentadded", {
194
+ detail: {
195
+ ...payload.comment,
196
+ id: Date.now().toString() // Generate temporary ID
197
+ }
198
+ })
199
+ );
200
+ } catch (error) {
201
+ console.error("Error saving comment to local storage:", error);
202
+ throw error;
203
+ }
204
+
205
+ // API IMPLEMENTATION (Commented until backend is ready)
206
+ /*
207
+ const response = await fetch('/post-comment', {
208
+ method: 'POST',
209
+ headers: {
210
+ 'Content-Type': 'application/json'
211
+ },
212
+ body: JSON.stringify(payload)
213
+ });
214
+
215
+ if (!response.ok) {
216
+ const errorText = await response.text();
217
+ throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
218
+ }
219
+
220
+ const responseData = await response.json();
221
+ console.log('Comment posted successfully:', responseData);
222
+
223
+ // Refresh comments after successful post
224
+ await this.fetchComments();
131
225
 
132
226
  // Dispatch custom event
133
227
  this.dispatchEvent(
134
228
  new CustomEvent("commentadded", {
135
- detail: newComment
229
+ detail: {
230
+ ...payload.comment,
231
+ id: responseData.id // Include any ID returned by the API
232
+ }
136
233
  })
137
234
  );
235
+ */
236
+ }
237
+
238
+ private async saveCommentToLocalStorage(payload: ApiCommentPayload) {
239
+ try {
240
+ // Get existing comments from localStorage
241
+ const existingData = localStorage.getItem(COMMENTS_STORAGE_KEY);
242
+ const allComments = existingData ? JSON.parse(existingData) : {};
243
+
244
+ // Create a unique key for this specific comment location
245
+ const commentKey = `${payload.branch}_${payload.file_path}_${payload.heading_title}`;
246
+
247
+ // Initialize array for this location if it doesn't exist
248
+ if (!allComments[commentKey]) {
249
+ allComments[commentKey] = [];
250
+ }
251
+
252
+ // Add the new comment
253
+ allComments[commentKey].push(payload.comment);
254
+
255
+ // Save back to localStorage
256
+ localStorage.setItem(
257
+ COMMENTS_STORAGE_KEY,
258
+ JSON.stringify(allComments)
259
+ );
260
+
261
+ console.log("Comments saved to localStorage:", allComments);
262
+ } catch (error) {
263
+ console.error("Error saving to localStorage:", error);
264
+ throw new Error("Failed to save comment to local storage");
265
+ }
138
266
  }
139
267
 
140
268
  private resetForm() {
@@ -162,6 +290,8 @@ export default class CommentPopup extends LightningElement {
162
290
  handleClose() {
163
291
  this._open = false;
164
292
  this.resetForm();
293
+ this.apiError = "";
294
+ this.submitSuccess = false;
165
295
  }
166
296
 
167
297
  handleOverlayClick(event: Event) {
@@ -180,13 +310,83 @@ export default class CommentPopup extends LightningElement {
180
310
  document.addEventListener("keydown", this.handleKeyDown.bind(this));
181
311
  }
182
312
 
313
+ private async fetchComments() {
314
+ if (!this.currentBranch || !this.filePath || !this.headingTitle) {
315
+ console.warn("Cannot fetch comments: missing required parameters");
316
+ return;
317
+ }
318
+
319
+ this.isLoading = true;
320
+ this.apiError = "";
321
+
322
+ try {
323
+ // LOCAL STORAGE IMPLEMENTATION (Temporary until backend is ready)
324
+ const comments = await this.getCommentsFromLocalStorage();
325
+ console.log("Fetched comments from localStorage:", comments);
326
+ this.comments = comments;
327
+
328
+ // API IMPLEMENTATION (Commented until backend is ready)
329
+ /*
330
+ const params = new URLSearchParams({
331
+ branch: this.currentBranch,
332
+ file_path: this.filePath,
333
+ heading_title: this.headingTitle
334
+ });
335
+
336
+ console.log('Fetching comments with params:', params.toString());
337
+
338
+ const response = await fetch(`/api/comments?${params.toString()}`);
339
+
340
+ if (!response.ok) {
341
+ throw new Error(`Failed to fetch comments: ${response.status} ${response.statusText}`);
342
+ }
343
+
344
+ const data = await response.json();
345
+ console.log('Fetched comments:', data);
346
+
347
+ // Handle different response formats
348
+ if (Array.isArray(data)) {
349
+ this.comments = data;
350
+ } else if (data && Array.isArray(data.comments)) {
351
+ this.comments = data.comments;
352
+ } else {
353
+ this.comments = [];
354
+ }
355
+ */
356
+ } catch (error) {
357
+ console.error("Error fetching comments:", error);
358
+ this.apiError = "Failed to load comments. Please try again later.";
359
+ this.comments = [];
360
+ } finally {
361
+ this.isLoading = false;
362
+ }
363
+ }
364
+
365
+ private async getCommentsFromLocalStorage(): Promise<Comment[]> {
366
+ try {
367
+ const existingData = localStorage.getItem(COMMENTS_STORAGE_KEY);
368
+ if (!existingData) {
369
+ return [];
370
+ }
371
+
372
+ const allComments = JSON.parse(existingData);
373
+ const commentKey = `${this.currentBranch}_${this.filePath}_${this.headingTitle}`;
374
+
375
+ return allComments[commentKey] || [];
376
+ } catch (error) {
377
+ console.error("Error reading from localStorage:", error);
378
+ return [];
379
+ }
380
+ }
381
+
183
382
  disconnectedCallback() {
184
383
  document.removeEventListener("keydown", this.handleKeyDown.bind(this));
185
384
  }
186
385
 
187
- private formatTimestamp(timestamp: Date): string {
386
+ private formatTimestamp(timestamp: string): string {
188
387
  const now = new Date();
189
- const diff = now.getTime() - timestamp.getTime();
388
+ const commentDate = new Date(timestamp);
389
+ const diff = now.getTime() - commentDate.getTime();
190
390
  const minutes = Math.floor(diff / 60000);
191
391
  const hours = Math.floor(diff / 3600000);
192
392
  const days = Math.floor(diff / 86400000);
@@ -204,6 +404,7 @@ export default class CommentPopup extends LightningElement {
204
404
  get formattedComments() {
205
405
  return this.sortedComments.map((comment) => ({
206
406
  ...comment,
407
+ comment: comment.comment_text, // Map API field to template expectation
207
408
  formattedTimestamp: this.formatTimestamp(comment.timestamp),
208
409
  maskedEmail: this.maskEmail(comment.email)
209
410
  }));
@@ -0,0 +1,250 @@
1
+ // Utility functions for managing comments in localStorage
2
+ // This file provides helper functions for comment management
3
+
4
+ export interface Comment {
5
+ email: string;
6
+ comment_text: string;
7
+ timestamp: string;
8
+ }
9
+
10
+ export 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
+ const COMMENTS_STORAGE_KEY = "dsc_comments";
24
+
25
+ /**
26
+ * Export all comments from localStorage to a JSON file
27
+ */
28
+ export function exportCommentsToFile(): void {
29
+ try {
30
+ const commentsData = localStorage.getItem(COMMENTS_STORAGE_KEY);
31
+ if (!commentsData) {
32
+ console.log("No comments found in localStorage");
33
+ return;
34
+ }
35
+
36
+ const comments = JSON.parse(commentsData);
37
+ const dataStr = JSON.stringify(comments, null, 2);
38
+ const dataBlob = new Blob([dataStr], { type: "application/json" });
39
+
40
+ const link = document.createElement("a");
41
+ link.href = URL.createObjectURL(dataBlob);
42
+ link.download = `dsc-comments-${
43
+ new Date().toISOString().split("T")[0]
44
+ }.json`;
45
+ link.click();
46
+
47
+ console.log("Comments exported successfully");
48
+ } catch (error) {
49
+ console.error("Error exporting comments:", error);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Import comments from a JSON file to localStorage
55
+ */
56
+ export function importCommentsFromFile(file: File): Promise<void> {
57
+ return new Promise((resolve, reject) => {
58
+ const reader = new FileReader();
59
+
60
+ reader.onload = (event) => {
61
+ try {
62
+ const content = event.target?.result as string;
63
+ const comments = JSON.parse(content);
64
+
65
+ // Validate the structure
66
+ if (typeof comments === "object" && comments !== null) {
67
+ localStorage.setItem(
68
+ COMMENTS_STORAGE_KEY,
69
+ JSON.stringify(comments)
70
+ );
71
+ console.log("Comments imported successfully");
72
+ resolve();
73
+ } else {
74
+ reject(new Error("Invalid comments file format"));
75
+ }
76
+ } catch (error) {
77
+ reject(new Error("Failed to parse comments file"));
78
+ }
79
+ };
80
+
81
+ reader.onerror = () => {
82
+ reject(new Error("Failed to read file"));
83
+ };
84
+
85
+ reader.readAsText(file);
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Get all comments from localStorage
91
+ */
92
+ export function getAllComments(): Record<string, Comment[]> {
93
+ try {
94
+ const commentsData = localStorage.getItem(COMMENTS_STORAGE_KEY);
95
+ return commentsData ? JSON.parse(commentsData) : {};
96
+ } catch (error) {
97
+ console.error("Error reading comments from localStorage:", error);
98
+ return {};
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get comments for a specific location
104
+ */
105
+ export function getCommentsForLocation(
106
+ branch: string,
107
+ filePath: string,
108
+ headingTitle: string
109
+ ): Comment[] {
110
+ try {
111
+ const allComments = getAllComments();
112
+ const commentKey = `${branch}_${filePath}_${headingTitle}`;
113
+ return allComments[commentKey] || [];
114
+ } catch (error) {
115
+ console.error("Error getting comments for location:", error);
116
+ return [];
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Clear all comments from localStorage
122
+ */
123
+ export function clearAllComments(): void {
124
+ try {
125
+ localStorage.removeItem(COMMENTS_STORAGE_KEY);
126
+ console.log("All comments cleared from localStorage");
127
+ } catch (error) {
128
+ console.error("Error clearing comments:", error);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Get statistics about stored comments
134
+ */
135
+ export function getCommentsStats(): {
136
+ totalLocations: number;
137
+ totalComments: number;
138
+ locations: Array<{
139
+ key: string;
140
+ commentCount: number;
141
+ lastComment?: string;
142
+ }>;
143
+ } {
144
+ try {
145
+ const allComments = getAllComments();
146
+ const locations = Object.keys(allComments);
147
+ let totalComments = 0;
148
+
149
+ const locationStats = locations.map((key) => {
150
+ const comments = allComments[key];
151
+ totalComments += comments.length;
152
+
153
+ return {
154
+ key,
155
+ commentCount: comments.length,
156
+ lastComment:
157
+ comments.length > 0
158
+ ? comments[comments.length - 1].timestamp
159
+ : undefined
160
+ };
161
+ });
162
+
163
+ return {
164
+ totalLocations: locations.length,
165
+ totalComments,
166
+ locations: locationStats
167
+ };
168
+ } catch (error) {
169
+ console.error("Error getting comments stats:", error);
170
+ return {
171
+ totalLocations: 0,
172
+ totalComments: 0,
173
+ locations: []
174
+ };
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Convert localStorage comments to API format for backend migration
180
+ */
181
+ export function convertToApiFormat(): Array<{
182
+ branch: string;
183
+ file_path: string;
184
+ heading_title: string;
185
+ start_line: string;
186
+ end_line: string;
187
+ comments: Comment[];
188
+ }> {
189
+ try {
190
+ const allComments = getAllComments();
191
+ const apiFormat: Array<{
192
+ branch: string;
193
+ file_path: string;
194
+ heading_title: string;
195
+ start_line: string;
196
+ end_line: string;
197
+ comments: Comment[];
198
+ }> = [];
199
+
200
+ Object.keys(allComments).forEach((key) => {
201
+ const [branch, file_path, heading_title, start_line, end_line] =
202
+ key.split("_", 5);
203
+ if (
204
+ branch &&
205
+ file_path &&
206
+ heading_title &&
207
+ start_line &&
208
+ end_line
209
+ ) {
210
+ apiFormat.push({
211
+ branch,
212
+ file_path,
213
+ heading_title,
214
+ start_line,
215
+ end_line,
216
+ comments: allComments[key]
217
+ });
218
+ }
219
+ });
220
+
221
+ return apiFormat;
222
+ } catch (error) {
223
+ console.error("Error converting to API format:", error);
224
+ return [];
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Add a comment to localStorage
230
+ */
231
+ export function addComment(payload: ApiCommentPayload): void {
232
+ try {
233
+ const existingData = localStorage.getItem(COMMENTS_STORAGE_KEY);
234
+ const allComments = existingData ? JSON.parse(existingData) : {};
235
+
236
+ const commentKey = `${payload.branch}_${payload.file_path}_${payload.heading_title}`;
237
+
238
+ if (!allComments[commentKey]) {
239
+ allComments[commentKey] = [];
240
+ }
241
+
242
+ allComments[commentKey].push(payload.comment);
243
+ localStorage.setItem(COMMENTS_STORAGE_KEY, JSON.stringify(allComments));
244
+
245
+ console.log("Comment added successfully");
246
+ } catch (error) {
247
+ console.error("Error adding comment:", error);
248
+ throw error;
249
+ }
250
+ }
@@ -59,7 +59,6 @@
59
59
  value={tocValue}
60
60
  ></dx-toc>
61
61
  </div>
62
- <doc-comment-popup></doc-comment-popup>
63
62
  </div>
64
63
  <div lwc:if={showFooter} class="footer-container">
65
64
  <dx-footer variant="no-signup"></dx-footer>