From 9c27b51eedbb2447eccb46ea7e5b21ac91d9de5f Mon Sep 17 00:00:00 2001 From: Ruben Carlo Benante Date: Mon, 6 Jul 2026 21:12:26 -0300 Subject: [PATCH] keep board horizontal scroll across refresh: restore scrollLeft after #board-container rebuild; v1.0 --- Asset/js/keep-scroll.js | 55 +++++++++++++++++++++++++++++++++ Controller/ConfigController.php | 7 ++++- Plugin.php | 10 +++++- README.md | 13 ++++++++ Template/config/show.php | 7 +++++ VERSION | 2 +- 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 Asset/js/keep-scroll.js diff --git a/Asset/js/keep-scroll.js b/Asset/js/keep-scroll.js new file mode 100644 index 0000000..1cb4eac --- /dev/null +++ b/Asset/js/keep-scroll.js @@ -0,0 +1,55 @@ +/* + * OrganonTweaks -- keep the board's horizontal scroll position across refreshes. + * + * When you drop a card (and on Kanboard's periodic AJAX polling) the board is rebuilt with + * `$("#board-container").replaceWith(data)` (BoardDragAndDrop.refresh). The brand-new + * element starts at scrollLeft 0, so the board snaps back to the first column -- a jarring + * jump. This remembers the last scroll position and restores it the moment a replacement + * container appears, so the view stays put. + * + * We observe the STABLE parent (the container itself is replaced) and read the scroll from + * whichever #board-container is current, so it keeps working after every rebuild. + */ +(function () { + "use strict"; + + function init() { + var container = document.getElementById("board-container"); + if (!container) { + return; + } + + var parent = container.parentNode; + var lastScrollLeft = container.scrollLeft; + var lastContainer = container; + + // Remember the position whenever the board is scrolled (capture: scroll does not + // bubble, and this survives the container being replaced). + document.addEventListener("scroll", function (e) { + var c = document.getElementById("board-container"); + if (c && e.target === c) { + lastScrollLeft = c.scrollLeft; + } + }, true); + + // When the board is rebuilt, #board-container becomes a new element at scrollLeft 0; + // restore the remembered position before the browser paints it. + if (window.MutationObserver) { + new MutationObserver(function () { + var c = document.getElementById("board-container"); + if (c && c !== lastContainer) { + lastContainer = c; + if (c.scrollLeft !== lastScrollLeft) { + c.scrollLeft = lastScrollLeft; + } + } + }).observe(parent, { childList: true }); + } + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/Controller/ConfigController.php b/Controller/ConfigController.php index 85841f1..42d5e20 100644 --- a/Controller/ConfigController.php +++ b/Controller/ConfigController.php @@ -15,6 +15,7 @@ class ConfigController extends BaseController 'title' => t('Settings').' > '.t('Organon Tweaks'), 'values' => array( 'organon_tweaks_always_comment_icon' => (int) $this->configModel->get('organon_tweaks_always_comment_icon', 1), + 'organon_tweaks_keep_scroll' => (int) $this->configModel->get('organon_tweaks_keep_scroll', 1), ), 'errors' => array(), ))); @@ -25,8 +26,12 @@ class ConfigController extends BaseController $values = $this->request->getValues(); $alwaysCommentIcon = isset($values['organon_tweaks_always_comment_icon']) ? 1 : 0; + $keepScroll = isset($values['organon_tweaks_keep_scroll']) ? 1 : 0; - if ($this->configModel->save(array('organon_tweaks_always_comment_icon' => $alwaysCommentIcon))) { + if ($this->configModel->save(array( + 'organon_tweaks_always_comment_icon' => $alwaysCommentIcon, + 'organon_tweaks_keep_scroll' => $keepScroll, + ))) { $this->flash->success(t('Settings saved successfully.')); } else { $this->flash->failure(t('Unable to save your settings.')); diff --git a/Plugin.php b/Plugin.php index d259299..6b95185 100644 --- a/Plugin.php +++ b/Plugin.php @@ -26,6 +26,14 @@ class Plugin extends Base if ((int) $this->configModel->get('organon_tweaks_always_comment_icon', 1) === 1) { $this->template->hook->attach('template:board:task:icons', 'organonTweaks:board/task_comment_icon'); } + + // Tweak: keep the board horizontal scroll position across refreshes (Kanboard + // rebuilds #board-container on card drop / polling, resetting scrollLeft to 0). + if ((int) $this->configModel->get('organon_tweaks_keep_scroll', 1) === 1) { + $this->hook->on('template:layout:js', array( + 'template' => 'plugins/OrganonTweaks/Asset/js/keep-scroll.js', + )); + } } public function getHelpers() @@ -52,7 +60,7 @@ class Plugin extends Base public function getPluginVersion() { - return '0.3.1'; + return '1.0.0'; } public function getPluginHomepage() diff --git a/README.md b/README.md index a904b38..18a636b 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,24 @@ have comments keep showing the count as before. read-only viewers). - **On by default.** Toggle it under "Settings -> Organon Tweaks". +### Keep the board scroll position across refreshes + +When you drop a card (or Kanboard auto-refreshes the board via polling), it rebuilds the +board with `$("#board-container").replaceWith(...)`, and the new element starts scrolled to +the far left -- so the view jumps back to the first column. This tweak remembers the +horizontal scroll position and restores it as soon as the rebuilt board appears, so the +board stays where you were. + +- Implemented in `Asset/js/keep-scroll.js`: it observes the stable parent (the container + itself is replaced), reads/writes `scrollLeft` on whichever `#board-container` is current. +- **On by default.** Toggle it under "Settings -> Organon Tweaks". + ## Settings Global (per Kanboard instance) and admin-only, under "Settings -> Organon Tweaks": - **Always show the comment icon on board cards** -- default on. +- **Keep the board scroll position across refreshes** -- default on. ## Requirements diff --git a/Template/config/show.php b/Template/config/show.php index 84f995a..781e219 100644 --- a/Template/config/show.php +++ b/Template/config/show.php @@ -12,6 +12,13 @@

+
+ form->checkbox('organon_tweaks_keep_scroll', t('Keep the board scroll position across refreshes'), 1, isset($values['organon_tweaks_keep_scroll']) && $values['organon_tweaks_keep_scroll'] == 1) ?> +

+ +

+
+
diff --git a/VERSION b/VERSION index 48c54ec..1b8385b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -OrganonTweaks v0.3.1 +OrganonTweaks v1.0