From 533f6aaf00f382b59bcdbee8dbaa386b4b98ed5b Mon Sep 17 00:00:00 2001
From: Mike Feranda <26969-mferanda@users.noreply.drupalcode.org>
Date: Thu, 19 Sep 2024 02:45:43 +0000
Subject: [PATCH] Issue #3475416 by mferanda: Split off files, rename, and code
 clean-up

---
 README.md                                   |  19 +++
 aidmi.libraries.yml                         |   3 +-
 config/install/aidmi.settings.yml           |   2 +-
 css/aidmi.css                               |  19 ++-
 js/{aidmi_ckeditor.js => aidmi.ckeditor.js} | 170 ++++++--------------
 js/aidmi.dialog.js                          |  88 ++++++++++
 6 files changed, 166 insertions(+), 135 deletions(-)
 create mode 100644 README.md
 rename js/{aidmi_ckeditor.js => aidmi.ckeditor.js} (63%)
 create mode 100644 js/aidmi.dialog.js

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4d22d1e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+## AIDmi - AI, describe my image!
+
+AIDmi provides a button that will use an AI API to send an image to return a detailed description.
+
+## Installation
+
+Make sure you have gemini-api-php/client installed.
+
+composer require gemini-api-php/client
+
+Enable aidmi.
+
+drush en aidmi
+
+- Configure the settings under Configure > Web services > AIDmi Settings
+    - Select API: Only Gemini at the moment.
+    - API Key Input Method: API key in Settings or File Path
+        - File Path is recommended. Make sure you save the file outside of web/ (Example on page)
+    - API Instructions: Default text provided that is sent to AI for image description.
diff --git a/aidmi.libraries.yml b/aidmi.libraries.yml
index fd1b6e9..3c30b13 100644
--- a/aidmi.libraries.yml
+++ b/aidmi.libraries.yml
@@ -3,7 +3,8 @@ aidmi_ckeditor:
     theme:
       css/aidmi.css: {}
   js:
-    js/aidmi_ckeditor.js: { preprocess: false }
+    js/aidmi.ckeditor.js: { preprocess: false }
+    js/aidmi.dialog.js: { preprocess: false }
   dependencies:
     - core/ckeditor5
     
\ No newline at end of file
diff --git a/config/install/aidmi.settings.yml b/config/install/aidmi.settings.yml
index 1bdf5cf..00fe043 100644
--- a/config/install/aidmi.settings.yml
+++ b/config/install/aidmi.settings.yml
@@ -2,4 +2,4 @@ selected_api: 'gemini'  # Set the default API to 'gemini'
 api_input_method: 'api_key'
 api_key: ''
 api_key_file_path: '/api_keys/aidmi.key'
-api_instructions: 'Describe this image in detail, following the latest WCAG standards, without HTML, for insertion into an image alt tag.'
+api_instructions: 'Describe the image in context of a web document. For graphs, provide the story being shown as well. This is to meet web accessibility requirements.'
diff --git a/css/aidmi.css b/css/aidmi.css
index bc36f3c..d1fbeb1 100644
--- a/css/aidmi.css
+++ b/css/aidmi.css
@@ -1,5 +1,4 @@
 .aidmi-dialog {
-    max-width: 400px;
     height: auto;
   }
 
@@ -7,15 +6,19 @@
     margin-right: 10px;
 }
 
-.aidmi-dialog-content-scrollable {
-    max-height: 300px;    /* Adjust the max height based on how tall you want the scrollable area */
-    overflow-y: auto;     /* Adds a vertical scrollbar if content exceeds the max height */
-    padding: 10px;        /* Optional: Add padding inside the scrollable content */
+.aidmi-dialog-content-scrollable {    
+    max-height: 300px;    
+    overflow-y: auto;     
+    padding: 10px;        
 }
 
 .aidmi-dialog-subtitle {
     font-size: 0.9em;
-    color: #666; /* Use a color that ensures sufficient contrast */
-    margin-top: -10px; /* Adjust spacing as needed */
+    color: #666; 
+    margin-top: -10px; 
     margin-bottom: 10px;
-}
\ No newline at end of file
+}
+
+.aidmi-dialog-textarea {
+    resize:vertical;
+}
diff --git a/js/aidmi_ckeditor.js b/js/aidmi.ckeditor.js
similarity index 63%
rename from js/aidmi_ckeditor.js
rename to js/aidmi.ckeditor.js
index 2c8ddf6..9f97930 100644
--- a/js/aidmi_ckeditor.js
+++ b/js/aidmi.ckeditor.js
@@ -1,46 +1,18 @@
-let aidmiActiveEditorInstance;
-let aidmiLastSelection;
-let aidmiEditedText;
-
 (function ($, Drupal, once) {
+                
+  Drupal.aidmi = Drupal.aidmi || {};
+  
   Drupal.behaviors.ckeditorImageUploadMonitor = {
     attach: function (context, settings) {
 
-      // Capture the selection before opening the dialog.
-      function captureSelection(editorInstance) {
-        const selection = editorInstance.model.document.selection;
-      
-        // If there's a selection, clone it to restore later.
-        if (selection) {
-          aidmiLastSelection = selection.getFirstRange().clone();
-        }
-      }
-
-      // Restore that last selection.
-      function restoreSelection(editorInstance) {
-        if (aidmiLastSelection) {
-          editorInstance.model.change(writer => {
-            // Restore the selection.
-            writer.setSelection(aidmiLastSelection);
-          });
-      
-          // Focus the editor view.
-          editorInstance.editing.view.focus();
-        }
-      }
-
       // Set the last active CKEditor.
       once('getActiveCKEditorInstance', '.ck-editor__editable', context).forEach(function (editorElement) {
         // Attach a focus event listener to detect when an editor becomes active.
         editorElement.addEventListener('focus', function () {
-          aidmiActiveEditorInstance = editorElement.ckeditorInstance;
+          Drupal.aidmi.activeEditorInstance = editorElement.ckeditorInstance;
         });
       });
 
-
-      // Select the target node (element) you want to observe.
-      const targetNode = document.body; // You can change this to any specific element.
-
       // Define the configuration for the observer (which types of changes to monitor).
       const config = {
         childList: true,       // Monitor additions or removals of child elements.
@@ -68,9 +40,9 @@ let aidmiEditedText;
               // Check if the alternative text form exists.
               altTextForm = document.querySelector('.ck-text-alternative-form');
               // Get the selected image.
-              imgTag = aidmiActiveEditorInstance.data.stringify(aidmiActiveEditorInstance.model.getSelectedContent(aidmiActiveEditorInstance.model.document.selection));
+              imgTag = Drupal.aidmi.activeEditorInstance.data.stringify(Drupal.aidmi.activeEditorInstance.model.getSelectedContent(Drupal.aidmi.activeEditorInstance.model.document.selection));
               if ((altTextForm) && (altTextField) && (imgTag)) {
-                aidmiButton(altTextField, aidmiActiveEditorInstance, imgTag);
+                Drupal.aidmi.aidmiButton(altTextField, Drupal.aidmi.activeEditorInstance, imgTag);
               }
             }
           }
@@ -78,19 +50,27 @@ let aidmiEditedText;
       };
 
       // Create an instance of MutationObserver and pass the callback function.
-      const observer = new MutationObserver(callback);    
+      const observer = new MutationObserver(callback);
+      // Select the target node (element) you want to observe.
+      const targetNode = document.body; // You can change this to any specific element.
       // Start observing the target node with the provided configuration.
       observer.observe(targetNode, config);
+    }
+  }
+  
+  Drupal.behaviors.aidmiCKEditorFunctions = {
+    attach: function (context, settings) {     
+      Drupal.aidmi.lastSelection = '';
 
       // Function to add the AIDmi Button.
-      function aidmiButton(altTextField, ckEditorInstance, imgTag) {
+      Drupal.aidmi.aidmiButton = function (altTextField, ckEditorInstance, imgTag) {
         // Check if the image has a data-entity-uuid.
         uuidMatch = imgTag.match(/^<img[^>]*data-entity-uuid="([^"]*)"/);
         if (uuidMatch) {
           imgUuid = uuidMatch[1];
         }
         const existingButton = document.querySelector('.aidmi-button');  // Check if the button already exists.
-        aidmiEditedText = ''; // Make sure previous text is wiped out.
+        let aidmiEditedText = '';
 
         if (!existingButton) {
           const button = document.createElement('button');
@@ -119,15 +99,19 @@ let aidmiEditedText;
             button.disabled = true;
             button.textContent = 'AIDmi Processing...';
             // Call the aidmiAjax function and handle the result with .then().
-            imgDesc = aidmiAjax(imgUuid)
+            imgDesc = Drupal.aidmi.aidmiAjax(imgUuid)
                         .then((data) => {
+                          // Capture the current selection before opening the dialog.
+                          captureSelection(Drupal.aidmi.activeEditorInstance);
                           // Open the Off-Canvas Dialog and show the retrieved description.
-                          openOffCanvasDialog(data, (isConfirmed) => {
+                          Drupal.aidmi.aidmiDialog(data, (isConfirmed) => {
                             if (isConfirmed) {
                               // Check if the imgTag has an alt attribute.   
                               updateImageAltInCKEditor(ckEditorInstance, imgUuid, aidmiEditedText);
                             }
-                          });
+                          });                     
+                          // Restore the CKEditor selection after closing the dialog.
+                          restoreSelection(Drupal.aidmi.activeEditorInstance);
                         })
                         .catch((error) => {
                           console.log('Error:', error);  // Handle errors here.
@@ -147,9 +131,7 @@ let aidmiEditedText;
         }
       }
 
-      function aidmiAjax(uuid) {
-        // Initially clear the field or show a placeholder/loading message.
-        altTextField.value = '';  // Keep the field empty.
+      Drupal.aidmi.aidmiAjax = function (uuid) {
         // Return a Promise that resolves when the AJAX call succeeds.
         return new Promise((resolve, reject) => {
           $.ajax({
@@ -167,89 +149,27 @@ let aidmiEditedText;
         });
       }
 
-      // Open dialog for AIDmi.
-      function openOffCanvasDialog(content, callback) {
-        // Capture the current selection before opening the dialog.
-        captureSelection(aidmiActiveEditorInstance);
-
-        // Create a temporary container to hold the content.
-        const tempElement = document.createElement('div');
-        tempElement.setAttribute('role', 'dialog');
-        tempElement.setAttribute('aria-labelledby', 'aidmi-dialog-title');
-        tempElement.setAttribute('aria-describedby', 'aidmi-dialog-description');
-        tempElement.setAttribute('aria-modal', 'true'); // Ensures screen readers treat the dialog as a modal.
-
-        // Set title text.
-        let tempETitle = 'AIDmi Description';
-        let tempESubTitle = 'Please evaluate and edit the description as needed.';
-
-        // Create dialog content with a textarea for editing.
-        tempElement.innerHTML = `
-          <div>
-            <h2 id="aidmi-dialog-title">${Drupal.t(tempETitle)}</h2>
-            <p id="aidmi-dialog-subtitle" class="aidmi-dialog-subtitle">${Drupal.t(tempESubTitle)}</p>
-            <textarea id="aidmi-dialog-textarea" class="aidmi-dialog-textarea" rows="6" style="width: 100%;">${Drupal.t(content)}</textarea>
-          </div>`;
-
-        // Store the element that triggered the dialog, to return focus later.
-        const previousFocus = document.activeElement;
-
-        // Use the Drupal off-canvas dialog to show the content.
-        const options = {
-          dialogClass: 'aidmi-dialog',
-          title: tempETitle,
-          width: '400px',
-          buttons: [
-            {
-              text: Drupal.t('Insert Text'),
-              click: function () {
-                // Get the value from the textarea.
-                aidmiEditedText = document.getElementById('aidmi-dialog-textarea').value;
-
-                // Pass the modified text back through the callback.
-                callback(aidmiEditedText);
-
-                // Close the dialog.
-                $(tempElement).dialog('close');
-                $(tempElement).dialog('destroy').remove(); // Properly remove the dialog from the DOM.
-              }
-            },
-            {
-              text: Drupal.t('Cancel'),
-              click: function () {
-                // Return false when Cancel is clicked.
-                callback(false);
-
-                // Close the dialog.
-                $(tempElement).dialog('close');
-                $(tempElement).dialog('destroy').remove(); // Properly remove the dialog from the DOM.
-              }
-            }
-          ],
-          close: function () {
-            // Return focus to the original element when the dialog is closed.
-            previousFocus.focus();
-
-            // Restore the CKEditor selection after closing the dialog.
-            restoreSelection(aidmiActiveEditorInstance);
-          }
-        };
-
-        // Open the dialog using Drupal's dialog API.
-        const dialogInstance = Drupal.dialog(tempElement, options);
-        dialogInstance.showModal();
-
-        // Set focus on the textarea for screen readers.
-        const textareaElement = document.getElementById('aidmi-dialog-textarea');
-        textareaElement.setAttribute('tabindex', '-1');
-        textareaElement.focus();
+      // Capture the selection before opening the dialog.
+      function captureSelection(editorInstance) {
+        const selection = editorInstance.model.document.selection;
+      
+        // If there's a selection, clone it to restore later.
+        if (selection) {
+          Drupal.aidmi.lastSelection = selection.getFirstRange().clone();
+        }
+      }
 
-        // Ensure the Esc key closes the dialog.
-        document.addEventListener('keydown', function (event) {
-          if (event.key === 'Escape') {
-            $(tempElement).dialog('close');
-          }
-        });
+      // Restore that last selection.
+      function restoreSelection(editorInstance) {
+        if (Drupal.aidmi.lastSelection) {
+          editorInstance.model.change(writer => {
+            // Restore the selection.
+            writer.setSelection(Drupal.aidmi.lastSelection);
+          });
+      
+          // Focus the editor view.
+          editorInstance.editing.view.focus();
+        }
       }
 
       // Update image alt tag in CKEditor
diff --git a/js/aidmi.dialog.js b/js/aidmi.dialog.js
new file mode 100644
index 0000000..af8b9ce
--- /dev/null
+++ b/js/aidmi.dialog.js
@@ -0,0 +1,88 @@
+(function ($, Drupal) {
+    Drupal.behaviors.aidmiDialog = {
+        attach: function (context, settings) {    
+        Drupal.aidmi = Drupal.aidmi || {};            
+        
+        Drupal.aidmi.aidmiDialog = function (content, callback) {
+            let aidmiEditedText;
+            // Create a temporary container to hold the content.
+            const tempElement = document.createElement('div');
+            tempElement.setAttribute('role', 'dialog');
+            tempElement.setAttribute('aria-labelledby', 'aidmi-dialog-title');
+            tempElement.setAttribute('aria-describedby', 'aidmi-dialog-description');
+            tempElement.setAttribute('aria-modal', 'true'); // Ensures screen readers treat the dialog as a modal.
+
+            // Set title text.
+            let tempETitle = 'AIDmi Description';
+            let tempESubTitle = 'Please evaluate and edit the description as needed.';
+
+            // Create dialog content with a textarea for editing.
+            tempElement.innerHTML = `
+            <div>
+                <h2 id="aidmi-dialog-title">${Drupal.t(tempETitle)}</h2>
+                <p id="aidmi-dialog-subtitle" class="aidmi-dialog-subtitle">${Drupal.t(tempESubTitle)}</p>
+                <textarea id="aidmi-dialog-textarea" class="aidmi-dialog-textarea" rows="6" style="width: 100%;">${Drupal.t(content)}</textarea>
+            </div>`;
+
+            // Store the element that triggered the dialog, to return focus later.
+            const previousFocus = document.activeElement;
+
+            // Use the Drupal off-canvas dialog to show the content.
+            const options = {
+            dialogClass: 'aidmi-dialog',
+            title: tempETitle,
+            width: '400px',
+            buttons: [
+                {
+                text: Drupal.t('Insert Text'),
+                click: function () {
+                    // Get the value from the textarea.
+                    aidmiEditedText = document.getElementById('aidmi-dialog-textarea').value;
+
+                    // Pass the modified text back through the callback.
+                    callback(aidmiEditedText);
+
+                    // Close the dialog.
+                    $(tempElement).dialog('close');
+                    $(tempElement).dialog('destroy').remove(); // Properly remove the dialog from the DOM.
+                }
+                },
+                {
+                text: Drupal.t('Cancel'),
+                click: function () {
+                    // Return false when Cancel is clicked.
+                    callback(false);
+
+                    // Close the dialog.
+                    $(tempElement).dialog('close');
+                    $(tempElement).dialog('destroy').remove(); // Properly remove the dialog from the DOM.
+                }
+                }
+            ],
+            close: function () {
+                // Return focus to the original element when the dialog is closed.
+                previousFocus.focus();
+            }
+            };
+
+            // Open the dialog using Drupal's dialog API.
+            const dialogInstance = Drupal.dialog(tempElement, options);
+            dialogInstance.showModal();
+
+            // Set focus on the textarea for screen readers.
+            const textareaElement = document.getElementById('aidmi-dialog-textarea');
+            textareaElement.setAttribute('tabindex', '-1');
+            textareaElement.focus();
+
+            // Ensure the Esc key closes the dialog.
+            document.addEventListener('keydown', function (event) {
+            if (event.key === 'Escape') {
+                // Close the dialog.
+                $(tempElement).dialog('close');
+                $(tempElement).dialog('destroy').remove(); // Properly remove the dialog from the DOM.
+            }
+            });
+        }
+    }
+  };
+})(jQuery, Drupal);
\ No newline at end of file
-- 
GitLab