PageSourceSearch

https://globaljournals.org/core/modules/29d03b1ecd/assets/js/modules/response-handler.js?rnd=27577

js globaljournals.org collected 2026-10-01 11:48:11 UTC 22,808 bytes, 598 lines download raw bytes

1// Response Handler Module
2const ResponseHandler = (function ($) {
3  /**
4   * Helper function to extract HTML content from various response structures
5   */
6  function extractHtmlContent(responseData) {
7    if (!responseData) return "";
8
9    // Try various paths where HTML data might be
10    let htmlData =
11      responseData.response?.response?.data.html ||   // Nested structure from API
12      responseData.response?.data.html ||              // Direct API response
13      responseData.data.html ||                        // Simple structure
14      "";
15
16    // If we still have an object, try to stringify it or extract 'data' property
17    if (typeof htmlData === 'object' && htmlData !== null) {
18      if (htmlData.data) {
19        htmlData = htmlData.data.html;
20      } else {
21        // Last resort: stringify the object
22        htmlData = JSON.stringify(htmlData, null, 2);
23      }
24    }
25
26    return htmlData || "";
27  }
28
29  function showResponseDialog(data) {
30    const responses = data.responses || [];
31    const bulkEmailPayloads = Array.isArray(data.bulk_email_payloads)
32      ? data.bulk_email_payloads
33      : [];
34    let htmlContent = "";
35    let pdfResponses = [];
36
37    // First pass: categorize responses by type
38    responses.forEach((response) => {
39      const responseData = response.response;
40      if (responseData?.type === "html") {
41        const htmlData = extractHtmlContent(responseData);
42        htmlContent +=
43          (htmlContent ? "\n\n<!-- Next Response -->\n\n" : "") + htmlData;
44      } else if (responseData?.type === "pdf") {
45        pdfResponses.push(response);
46      }
47    });
48
49    if (!htmlContent && bulkEmailPayloads.length > 0) {
50      htmlContent = bulkEmailPayloads
51        .map((payload, index) => {
52          const subject = payload.email_subject || `Recipient ${index + 1}`;
53          const recipient = payload.email || "";
54          const body = payload.email_content || "";
55          if (!body) {
56            return "";
57          }
58
59          return `
60            <div class="bulk-preview-item" style="margin-bottom:24px;">
61              <div style="padding:10px 12px; background:#f6f7f7; border:1px solid #dcdcde; border-radius:4px; margin-bottom:12px;">
62                <strong>Preview ${index + 1}</strong><br>
63                <span>To: ${recipient}</span><br>
64                <span>Subject: ${subject}</span>
65              </div>
66              <div>${body}</div>
67            </div>
68          `;
69        })
70        .filter(Boolean)
71        .join("\n<hr style=\"margin:24px 0; border-top:1px solid #ddd;\">\n");
72    }
73
74    // Create single dialog with combined content
75    const dialogHtml = createDialogHtml({
76      hasHtml: htmlContent !== "",
77      htmlContent: htmlContent,
78      allResponses: responses,
79      pdfResponses: pdfResponses,
80    });
81
82    updatePopupContent(dialogHtml);
83
84    if (htmlContent) {
85      initializePreview();
86    }
87
88    setupEventHandlers({
89      responses: responses,
90      button_id: data.button_id,
91      post_id: data.post_id,
92      params: data.params,
93      session_id: data.session_id,
94      bulk_recipients: data.bulk_recipients || [],
95      bulk_email_payloads: bulkEmailPayloads,
96    });
97  }
98
99  function createDialogHtml(data) {
100    const { hasHtml, htmlContent, pdfResponses } = data;
101
102    return `
103            <div class="outliny-response-dialog" style="height: 90%; display: flex; flex-direction: column;">
104                <h3>Response</h3>
105                
106                <!-- Content Section (70% height) -->
107                <div class="content-section" style="height: 70%; margin-bottom: 15px;">
108                    ${hasHtml
109        ? createHtmlEditorHtml(htmlContent)
110        : createPdfViewerHtml()
111      }
112                </div>
113
114                <!-- Horizontal Divider -->
115                <hr style="margin: 0 -20px 15px -20px; border-top: 1px solid #ddd;">
116
117                <!-- Attachments Section (30% height) -->
118                <div class="attachments-section" style="height: 30%;">
119                    ${createAttachmentsSectionHtml(pdfResponses)}
120                </div>
121                <div style="display: flex; justify-content: center; margin: 10px; padding: 10px;">
122                    <button class="button button-close" style="margin-right: 10px;">Close</button>
123                    ${hasHtml
124        ? '<button class="button button-primary execute-final-action">Perform Final Action</button>'
125        : ""
126      }
127                </div>
128            </div>
129        `;
130  }
131
132  function createHtmlEditorHtml(html) {
133    return `
134            <div class="email-editor" style="display: flex; height: 50vh; !important">
135                <div class="text-editor" style="flex: 1; padding-right: 10px; display: flex; flex-direction: column;">
136                    <h4 style="margin: 0 0 10px 0;">Email Content</h4>
137                    <textarea 
138                        class="email-content-editor" 
139                        style="width: 100%; flex: 1; resize: none; min-height: 0;"
140                    >${html}</textarea>
141                </div>
142                <div class="html-preview" style="flex: 1; display: flex; flex-direction: column;">
143                    <h4 style="margin: 0 0 10px 0;">Preview</h4>
144                    <iframe 
145                        class="preview-frame" 
146                        style="width: 100%; flex: 1; border: 1px solid #ddd; background: white; min-height: 0;"
147                    ></iframe>
148                </div>
149            </div>
150        `;
151  }
152
153  function createPdfViewerHtml() {
154    return `
155            <div class="pdf-viewer" style="height: 100%;">
156                <h4>PDF Document</h4>
157                <div style="height: calc(100% - 40px); border: 1px solid #ddd; padding: 10px; background: #f9f9f9;">
158                    <p>PDF file will be available in attachments section</p>
159                </div>
160            </div>
161        `;
162  }
163
164  function createAttachmentsSectionHtml(pdfResponses) {
165    return `
166            <div class="attachments-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
167                <h4 style="margin: 0;">Attachments</h4>
168                <div class="attachment-actions">
169                    <input type="file" id="add-attachment" style="display: none;" multiple>
170                    <button class="button add-attachment-btn">Add Attachment</button>
171                </div>
172            </div>
173            <div class="attachments-list" style="height: calc(100% - 50px); overflow-y: auto; border: 1px solid #ddd; padding: 10px; background: #f9f9f9;">
174                ${generateAttachmentsList(pdfResponses)}
175            </div>
176        `;
177  }
178
179  function generateAttachmentsList(pdfResponses) {
180    if (!pdfResponses || pdfResponses.length === 0) {
181      return '<p class="no-attachments" style="text-align: center; color: #666;">No attachments added</p>';
182    }
183
184    const attachmentItems = pdfResponses.map((response, index) => {
185      const responseData = response.response;
186      // Ensure we have a proper URL by checking both response structures
187      const fileUrl =
188        responseData?.file_url || responseData?.response?.file_url;
189      let filename =
190        responseData?.file_name || responseData?.response?.file_name;
191      filename = filename ?? "File " + (index + 1);
192      // Make sure we have an absolute URL
193      const url = fileUrl.startsWith("http")
194        ? fileUrl
195        : window.location.origin + fileUrl;
196
197      return {
198        name: `${filename || "Response"}.pdf`,
199        url: url,
200        isPdf: true,
201        actionId: response.action_id,
202        originalName: filename, // Use the actual filename from the server
203      };
204    });
205
206    return generateAttachmentsListHtml(attachmentItems);
207  }
208
209  function generateAttachmentsListHtml(attachmentItems) {
210    return `
211            <ul class="attachments-items" style="list-style: none; padding: 0; margin: 0;">
212                ${attachmentItems
213        .map(
214          (attachment, index) => `
215                    <li class="attachment-item" 
216                        data-original-name="${attachment.originalName}" 
217                        data-url="${attachment.url}"
218                        data-display-name="${attachment.name}"
219                        style="display: flex; justify-content: space-between; align-items: center; padding: 8px; background: white; margin-bottom: 5px; border: 1px solid #eee; border-radius: 3px;">
220                        <span class="attachment-name" style="display: flex; align-items: center;">
221                            <svg style="width: 16px; height: 16px; margin-right: 8px;" viewBox="0 0 24 24">
222                                <path fill="currentColor" d="M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M9.5 11.5C9.5 12.3 8.8 13 8 13H7V15H5.5V9H8C8.8 9 9.5 9.7 9.5 10.5V11.5M14.5 13.5C14.5 14.3 13.8 15 13 15H10.5V9H13C13.8 9 14.5 9.7 14.5 10.5V13.5M18.5 10.5H17V11.5H18.5V13H17V15H15.5V9H18.5V10.5M7 11.5H8V10.5H7V11.5M13 13.5V10.5H12V13.5H13Z"/>
223                            </svg>
224                            ${attachment.name}
225                        </span>
226                        <div class="attachment-actions">
227                            <button class="button rename-attachment" data-index="${index}" style="margin-right: 8px;">
228                                Rename
229                            </button>
230                            <a href="${attachment.url}" 
231                               download="${attachment.name}" 
232                               class="button button-primary download-attachment" 
233                               style="margin-right: 8px;"
234                               target="_blank">
235                                Download
236                            </a>
237                            <button class="button button-link-delete remove-attachment" data-index="${index}" style="color: #dc3545;">
238                                Remove
239                            </button>
240                        </div>
241                    </li>
242                `
243        )
244        .join("")}
245            </ul>
246        `;
247  }
248
249  function updatePopupContent(dialogHtml) {
250    const popupOverlay = $(".outliny-popup-overlay");
251    const outlinyPopup = popupOverlay.find(".outliny-popup");
252    const popupContent = popupOverlay.find(".outliny-popup-content");
253    const loadingDiv = popupContent.find(".outliny-popup-loading");
254    const responseDiv = popupContent.find(".outliny-popup-response");
255
256    loadingDiv.hide();
257    responseDiv.html(dialogHtml).show();
258
259    updatePopupStyles(popupOverlay, popupContent, outlinyPopup);
260  }
261
262  function updatePopupStyles(popupOverlay, popupContent, outlinyPopup) {
263    outlinyPopup.css({
264      width: "90vw",
265      height: "90vh",
266      "max-width": "90vw",
267      "max-height": "90vh",
268    });
269    popupOverlay.css({
270      display: "flex",
271      "align-items": "center",
272      "justify-content": "center",
273      background: "rgba(0, 0, 0, 0.5)",
274      position: "fixed",
275      top: 0,
276      left: 0,
277      right: 0,
278      bottom: 0,
279      "z-index": 999999,
280      padding: "20px",
281    });
282
283    popupContent.css({
284      height: "90vh",
285      background: "white",
286      padding: "20px",
287      "border-radius": "4px",
288      "box-shadow": "0 2px 8px rgba(0, 0, 0, 0.2)",
289      overflow: "hidden",
290      position: "relative",
291      margin: "0 auto",
292    });
293  }
294
295  function initializePreview() {
296    const textarea = $(".email-content-editor");
297    const iframe = $(".preview-frame");
298    const iframeDoc = iframe[0].contentWindow.document;
299
300    updatePreview(textarea.val(), iframeDoc);
301
302    let timeout;
303    textarea.on("input", function () {
304      clearTimeout(timeout);
305      timeout = setTimeout(() => {
306        updatePreview($(this).val(), iframeDoc);
307      }, 300);
308    });
309  }
310
311  function updatePreview(content, iframeDoc) {
312    iframeDoc.open();
313    iframeDoc.write(`
314            <!DOCTYPE html>
315            <html>
316                <head>
317                    <meta charset="UTF-8">
318                    <style>
319                        body { 
320                            font-family: Arial, sans-serif; 
321                            padding: 20px; 
322                            margin: 0;
323                            line-height: 1.6;
324                        }
325                        img { max-width: 100%; height: auto; }
326                        table { border-collapse: collapse; }
327                        td, th { padding: 8px; border: 1px solid #ddd; }
328                    </style>
329                </head>
330                <body>${content}</body>
331            </html>
332        `);
333    iframeDoc.close();
334  }
335
336  function setupEventHandlers(data) {
337    // Add attachment button handler
338    $(".add-attachment-btn").on("click", function () {
339      $("#add-attachment").trigger("click");
340    });
341
342    // File selection handler
343    $("#add-attachment").on("change", handleFileSelection);
344
345    // Remove attachment handler
346    $(document).on("click", ".remove-attachment", function () {
347      const item = $(this).closest(".attachment-item");
348      handleAttachmentRemoval(item);
349    });
350
351    // Rename attachment handler
352    $(document).on("click", ".rename-attachment", function () {
353      const item = $(this).closest(".attachment-item");
354      const originalName = item.data("original-name");
355      const currentName = item.find(".attachment-name").text().trim();
356      showRenameDialog(item, originalName, currentName, data);
357    });
358
359    // Download attachment handler
360    $(document).on("click", ".download-attachment", handleDownload);
361
362    // Final action handler - ensure older handlers are removed before binding
363    $(document)
364      .off("click.responseHandler", ".execute-final-action")
365      .on("click.responseHandler", ".execute-final-action", function () {
366        handleFinalAction(data);
367      });
368
369    // Add close button handler
370    $(".button-close").on("click", function () {
371      const popupOverlay = $(".outliny-popup-overlay");
372      const popupContent = popupOverlay.find(".outliny-popup-content");
373      const loadingDiv = popupContent.find(".outliny-popup-loading");
374      const responseDiv = popupContent.find(".outliny-popup-response");
375
376      // Hide the overlay
377      popupOverlay.css("display", "none");
378
379      // Reset popup styles for future use
380      const outlinyPopup = popupOverlay.find(".outliny-popup");
381      outlinyPopup.css({
382        width: "90%",
383        "max-width": "600px",
384        height: "auto",
385        "max-height": "unset",
386      });
387
388      popupContent.css({
389        height: "auto",
390        "min-height": "200px",
391        "max-width": "600px",
392        width: "unset",
393      });
394
395      // Reset content sections
396      loadingDiv.show();
397      responseDiv.hide().empty();
398    });
399  }
400
401  function handleFinalAction(data) {
402    const emailContent = $(".email-content-editor").val();
403    const attachments = [];
404
405    // Collect all attachments
406    $(".attachment-item").each(function () {
407      const $item = $(this);
408      const displayName = $item.data("display-name");
409      const originalName = $item.data("original-name");
410      const url = $item.data("url");
411
412      attachments.push({
413        name: displayName,
414        originalName: originalName,
415        url: url,
416        isPdf: true,
417      });
418    });
419
420    // Get references to UI elements
421    const popupOverlay = $(".outliny-popup-overlay");
422    const popupContent = popupOverlay.find(".outliny-popup-content");
423    const loadingDiv = popupContent.find(".outliny-popup-loading");
424    const responseDiv = popupContent.find(".outliny-popup-response");
425
426    // Show loading state with improved visibility
427    popupOverlay.show();
428    popupContent.css({
429      height: "auto",
430      "min-height": "200px",
431      width: "unset",
432    });
433    // Update loading message to indicate email is being sent
434    loadingDiv.find("p").text("Sending email, please wait...");
435    loadingDiv.show();
436    responseDiv.hide().empty();
437
438    // Add a spinner if it doesn't exist
439    if (loadingDiv.find(".spinner").length === 0) {
440      loadingDiv.prepend(
441        '<span class="spinner is-active" style="float: none; margin: 0 auto 10px; display: block;"></span>'
442      );
443    }
444
445    // Process the action - with a slight delay to ensure the UI updates are visible
446    setTimeout(() => {
447      window.OutlinyProcessor.processButtonAction(
448        data.button_id,
449        data.post_id,
450        {
451          email_content: encodeURIComponent(emailContent),
452          attachments: JSON.stringify(attachments),
453          bulk_recipients: JSON.stringify(data.bulk_recipients || []),
454          bulk_email_payloads: JSON.stringify(data.bulk_email_payloads || []),
455          stage: "response_shown",
456          params: data.params,
457          session_id: data.session_id,
458        }
459      );
460    }, 100);
461  }
462
463  function showRenameDialog(item, originalName, currentName, data) {
464    // Validate originalName
465    if (!originalName) {
466      console.error("Original filename is missing:", {
467        item,
468        originalName,
469        currentName,
470      });
471      alert("Cannot rename file: Original filename is missing");
472      return;
473    }
474
475    // Extract the current name without extension
476    const nameWithoutExt = currentName.replace(/\.pdf$/i, "");
477
478    const dialog = $(`
479            <div class="rename-dialog" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); z-index: 1000000;">
480                <h3>Rename File</h3>
481                <div style="margin-bottom: 15px;">
482                    <input type="text" class="new-filename" value="${nameWithoutExt}" style="width: calc(100% - 45px); padding: 5px;">
483                    <span style="padding: 5px; background: #f0f0f0; border: 1px solid #ddd; margin-left: -1px;">.pdf</span>
484                </div>
485                <div style="display: flex; justify-content: flex-end;">
486                    <button class="button cancel-rename" style="margin-right: 8px;">Cancel</button>
487                    <button class="button button-primary confirm-rename">Rename</button>
488                </div>
489            </div>
490        `);
491
492    const overlay = $(
493      '<div class="rename-overlay" style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 999999;"></div>'
494    );
495
496    $("body").append(overlay).append(dialog);
497
498    dialog.find(".cancel-rename").on("click", function () {
499      dialog.remove();
500      overlay.remove();
501    });
502
503    dialog.find(".confirm-rename").on("click", function () {
504      const newNameWithoutExt = dialog.find(".new-filename").val().trim();
505      if (!newNameWithoutExt) {
506        alert("Please enter a new filename");
507        return;
508      }
509
510      const newName = newNameWithoutExt + ".pdf";
511      console.log("Updating display name:", { originalName, newName });
512
513      // Update only the display elements, keep the original file
514      item.find(".attachment-name").html(`
515                <svg style="width: 16px; height: 16px; margin-right: 8px;" viewBox="0 0 24 24">
516                    <path fill="currentColor" d="M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M9.5 11.5C9.5 12.3 8.8 13 8 13H7V15H5.5V9H8C8.8 9 9.5 9.7 9.5 10.5V11.5M14.5 13.5C14.5 14.3 13.8 15 13 15H10.5V9H13C13.8 9 14.5 9.7 14.5 10.5V13.5M18.5 10.5H17V11.5H18.5V13H17V15H15.5V9H18.5V10.5M7 11.5H8V10.5H7V11.5M13 13.5V10.5H12V13.5H13Z"/>
517                </svg>
518                ${newName}
519            `);
520      item.find(".download-attachment").attr("download", newName);
521      item.data("display-name", newName);
522
523      dialog.remove();
524      overlay.remove();
525    });
526  }
527
528  function handleFileSelection(e) {
529    const files = e.target.files;
530    const attachmentsList = $(".attachments-list");
531
532    attachmentsList.find(".no-attachments").remove();
533
534    if (!attachmentsList.find(".attachments-items").length) {
535      attachmentsList.append(
536        '<ul class="attachments-items" style="list-style: none; padding: 0; margin: 0;"></ul>'
537      );
538    }
539
540    Array.from(files).forEach((file) => {
541      const newItem = createAttachmentItem(file);
542      attachmentsList.find(".attachments-items").append(newItem);
543    });
544  }
545
546  function createAttachmentItem(file) {
547    return $(`
548            <li class="attachment-item" style="display: flex; justify-content: space-between; align-items: center; padding: 8px; background: white; margin-bottom: 5px; border: 1px solid #eee; border-radius: 3px;">
549                <span class="attachment-name" style="display: flex; align-items: center;">
550                    <svg style="width: 16px; height: 16px; margin-right: 8px;" viewBox="0 0 24 24">
551                        <path fill="currentColor" d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
552                    </svg>
553                    ${file.name}
554                </span>
555                <button class="button-link-delete remove-attachment" style="color: #dc3545;">Remove</button>
556            </li>
557        `);
558  }
559
560  function handleAttachmentRemoval(item) {
561    item.fadeOut(200, function () {
562      $(this).remove();
563
564      const attachmentsList = $(".attachments-list");
565      if (!attachmentsList.find(".attachment-item").length) {
566        attachmentsList.html(
567          '<p class="no-attachments" style="text-align: center; color: #666;">No attachments added</p>'
568        );
569      }
570    });
571  }
572
573  // Add this new function to handle downloads
574  function handleDownload(e) {
575    e.preventDefault();
576    const link = $(this);
577    const url = link.attr("href");
578    const filename = link.attr("download");
579
580    // Create a temporary link and trigger download
581    const tempLink = document.createElement("a");
582    tempLink.style.display = "none";
583    tempLink.href = url;
584    tempLink.setAttribute("download", filename);
585    document.body.appendChild(tempLink);
586    tempLink.click();
587    document.body.removeChild(tempLink);
588  }
589
590  return {
591    showResponseDialog: showResponseDialog,
592    initializePreview: initializePreview,
593    handleDownload: handleDownload,
594  };
595})(jQuery);
596
597// Export the module
598window.OutlinyResponseHandler = ResponseHandler;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.