From b17e1554918f503f4d9fb825e270e3bae0db269e Mon Sep 17 00:00:00 2001
From: Artem  Dmitriiev <a.dmitriiev@1xinternet.de>
Date: Fri, 20 Sep 2024 14:28:14 +0200
Subject: [PATCH] Issue #3440870: Keep action links in the viewport

---
 config/install/frontend_editing.settings.yml |  1 +
 config/schema/frontend_editing.schema.yml    |  3 +
 css/frontend_editing.css                     |  9 +++
 frontend_editing.install                     | 11 ++++
 frontend_editing.libraries.yml               |  2 +
 frontend_editing.module                      |  6 +-
 js/editing_actions.js                        | 64 ++++++++++----------
 src/Form/UiSettingsForm.php                  |  8 +++
 8 files changed, 70 insertions(+), 34 deletions(-)

diff --git a/config/install/frontend_editing.settings.yml b/config/install/frontend_editing.settings.yml
index 222e85d..a9f4cd7 100644
--- a/config/install/frontend_editing.settings.yml
+++ b/config/install/frontend_editing.settings.yml
@@ -12,3 +12,4 @@ ui_toggle_settings:
   offset_bottom: 50
   offset_right: 50
   offset_left: ''
+actions_in_viewport: false
diff --git a/config/schema/frontend_editing.schema.yml b/config/schema/frontend_editing.schema.yml
index f8985c2..446dcce 100644
--- a/config/schema/frontend_editing.schema.yml
+++ b/config/schema/frontend_editing.schema.yml
@@ -53,3 +53,6 @@ frontend_editing.settings:
         offset_right:
           type: string
           label: 'Right Offset'
+    actions_in_viewport:
+      type: boolean
+      label: 'Keep actions in viewport'
diff --git a/css/frontend_editing.css b/css/frontend_editing.css
index 4bd68ea..2c3ea5a 100644
--- a/css/frontend_editing.css
+++ b/css/frontend_editing.css
@@ -137,6 +137,15 @@ body:not(.frontend-editing--hidden) .frontend-editing:has(.add-paragraphs.hover-
   z-index: 2;
 }
 
+.common-actions-container.place-bottom {
+  top: auto;
+  bottom: 10px;
+}
+
+.common-actions-container.place-fixed {
+  position: fixed;
+}
+
 .title-edit-container,
 .icons-container {
   margin-left: 0.5rem;
diff --git a/frontend_editing.install b/frontend_editing.install
index b8bfb6b..721ad07 100644
--- a/frontend_editing.install
+++ b/frontend_editing.install
@@ -122,3 +122,14 @@ function frontend_editing_update_11007() {
   ]);
   $config->save(TRUE);
 }
+
+/**
+ * Set default 'actions in viewport' setting.
+ */
+function frontend_editing_update_11008() {
+  /** @var \Drupal\Core\Config\Config $config */
+  $config = \Drupal::service('config.factory')
+    ->getEditable('frontend_editing.settings');
+  $config->set('actions_in_viewport', FALSE);
+  $config->save(TRUE);
+}
diff --git a/frontend_editing.libraries.yml b/frontend_editing.libraries.yml
index d1f2121..eca7059 100644
--- a/frontend_editing.libraries.yml
+++ b/frontend_editing.libraries.yml
@@ -58,3 +58,5 @@ editing_actions:
   version: 1.6.0
   js:
     js/editing_actions.js: {}
+  dependencies:
+    - core/drupal.debounce
diff --git a/frontend_editing.module b/frontend_editing.module
index dea21ed..0f43d08 100644
--- a/frontend_editing.module
+++ b/frontend_editing.module
@@ -247,8 +247,10 @@ function frontend_editing_entity_view_alter(&$build, EntityInterface $entity, En
   // Library for updating content.and listen to the messages from frontend
   // editing iframe that is in the sidebar.
   $build['frontend_editing']['#attached']['library'][] = 'frontend_editing/update_content';
-  // Library for keeping editing actions in viewport.
-  $build['frontend_editing']['#attached']['library'][] = 'frontend_editing/editing_actions';
+  if ($config->get('actions_in_viewport')) {
+    // Library for keeping editing actions in viewport.
+    $build['frontend_editing']['#attached']['library'][] = 'frontend_editing/editing_actions';
+  }
   // Add sidebar size configuration to drupal settings.
   $build['frontend_editing']['#attached']['drupalSettings']['frontend_editing'] = [
     'sidebar_width' => $config->get('sidebar_width') ?? 30,
diff --git a/js/editing_actions.js b/js/editing_actions.js
index 72d4feb..1f8baf9 100644
--- a/js/editing_actions.js
+++ b/js/editing_actions.js
@@ -2,38 +2,38 @@
  * @file
  * Keeps editing actions in viewport.
  */
-window.addEventListener('load', () => {
-  let timer
-  window.addEventListener('scroll', () => {
-    clearTimeout(timer);
-    const clientHeight = document.documentElement.clientHeight
-    timer = setTimeout(() => {
-      document.querySelectorAll(".frontend-editing").forEach((element) => {
-        const {top, bottom, left } = element.getBoundingClientRect()
-        const actions = element.querySelector('.common-actions-container')
+(function (Drupal) {
 
-        // Reset
-        // element.style.backgroundColor = 'transparent'
-        actions.style.position = 'absolute'
-        actions.style.top = '10px'
-        actions.style.bottom = 'auto'
-        actions.style.left = '0'
+  const floatingLinks = function () {
+    const clientHeight = document.documentElement.clientHeight;
+    document.querySelectorAll(".frontend-editing").forEach((element) => {
+      const { top, bottom, left } = element.getBoundingClientRect()
+      const actions = element.querySelector('.common-actions-container')
 
-        // Top intersecting sections
-        if (top < 0 && bottom > 0 && bottom < clientHeight) {
-          // element.style.backgroundColor = 'pink'
-          actions.style.top = 'auto'
-          actions.style.bottom = '10px'
-        }
+      // Reset
+      actions.removeAttribute('style');
+      actions.classList.remove('place-bottom');
+      actions.classList.remove('place-fixed');
 
-        // Top + bottom intersecting sections
-        if (top < 0 && bottom > clientHeight) {
-          // element.style.backgroundColor = 'yellow'
-          actions.style.position = 'fixed'
-          actions.style.top = clientHeight - actions.offsetHeight - 10 + 'px'
-          actions.style.left = left + 'px'
-        }
-      })
-    }, 1000) // execute one second after user stops scrolling
-  })
-})
+      // Top intersecting sections
+      if (top < 0 && bottom > 0 && bottom < clientHeight) {
+        actions.classList.add('place-bottom');
+      }
+
+      // Top + bottom intersecting sections
+      if (top < 0 && bottom > clientHeight) {
+        actions.classList.add('place-fixed');
+        actions.style.position = 'fixed'
+        actions.style.top = clientHeight - actions.offsetHeight - 10 + 'px'
+        actions.style.left = left + 'px'
+      }
+    })
+  }
+
+  Drupal.behaviors.editingActionsPosition = {
+    attach(context, settings) {
+      window.addEventListener('scroll', Drupal.debounce(floatingLinks, 250, false));
+    }
+  }
+
+})(Drupal);
diff --git a/src/Form/UiSettingsForm.php b/src/Form/UiSettingsForm.php
index baf1cb6..11090c5 100644
--- a/src/Form/UiSettingsForm.php
+++ b/src/Form/UiSettingsForm.php
@@ -122,6 +122,13 @@ class UiSettingsForm extends ConfigFormBase {
       '#description' => $this->t('Choose the primary color for the frontend editing interface. Be sure to choose a color that has enough contrast with the background color of your site.'),
     ];
 
+    $form['actions_in_viewport'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Keeps action links in viewport'),
+      '#description' => $this->t('If element to edit is too long, this helps to keep the actions link visible.'),
+      '#default_value' => $config->get('actions_in_viewport'),
+    ];
+
     // Enable on/off toggle in UI.
     $form['ui_toggle'] = [
       '#type' => 'checkbox',
@@ -188,6 +195,7 @@ class UiSettingsForm extends ConfigFormBase {
     $config->set('hover_highlight', (bool) $form_state->getValue('hover_highlight'));
     $config->set('primary_color', $form_state->getValue('primary_color'));
     $config->set('ui_toggle', (bool) $form_state->getValue('ui_toggle'));
+    $config->set('actions_in_viewport', (bool) $form_state->getValue('actions_in_viewport'));
     $config->set('ui_toggle_settings', $form_state->getValue('ui_toggle_settings'));
     $exclude_fields = $form_state->getValue('exclude_fields');
     $exclude_fields = explode("\r\n", $exclude_fields);
-- 
GitLab