2 Commits
v0.3 ... v1.0

8 changed files with 148 additions and 14 deletions

55
Asset/js/keep-scroll.js Normal file
View File

@@ -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();
}
})();

View File

@@ -1,17 +1,23 @@
/* /*
* OrganonTweaks -- place the "Remove this Column" entry inside the native column menu. * OrganonTweaks -- move plugin-injected DOM bits into their native positions on the board.
* *
* Kanboard only exposes the template:board:column:dropdown hook AFTER the column menu * Both fixes are needed because Kanboard's relevant hooks render at the END of their
* <ul>, and its dropdown JS clones just that <ul> when the menu opens. So the hook item * container, not where the item belongs:
* cannot reach the menu on its own. column_dropdown.php renders the item hidden next to *
* the menu; this script moves each such item into that column's real <ul> so it appears * 1. "Remove this Column": the template:board:column:dropdown hook renders AFTER the column
* alongside the native entries. It re-runs when the board is redrawn (AJAX polling / * menu <ul> (and the dropdown JS clones only that <ul>), so we move the hidden <li> into
* drag-and-drop refresh). * the column's real <ul>.
* 2. "Always show comment icon": the template:board:task:icons hook renders at the end of
* the card icons row, so the "+" bubble lands after priority instead of in the native
* comment slot. We move it just before the description icon (where the native comment
* icon sits), so the order matches whether or not the card has comments.
*
* Both re-run when the board is redrawn (AJAX polling / drag-and-drop refresh).
*/ */
(function () { (function () {
"use strict"; "use strict";
function relocate() { function relocateColumnMenuItems() {
var items = document.querySelectorAll(".organontweaks-remove-column-item"); var items = document.querySelectorAll(".organontweaks-remove-column-item");
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {
@@ -34,17 +40,53 @@
} }
} }
function repositionCommentIcons() {
var items = document.querySelectorAll(".organontweaks-comment-add");
for (var i = 0; i < items.length; i++) {
var item = items[i];
var row = item.parentNode;
if (!row) {
continue;
}
// Native comment icon sits just before the description icon; if there is no
// description, before the age / closed block. That is the slot we move into.
var anchor = null;
var descIcon = row.querySelector("i.fa-file-text-o");
if (descIcon) {
anchor = descIcon.closest("a");
}
if (!anchor) {
anchor = row.querySelector(".task-icon-age") || row.querySelector(".task-board-closed");
}
// Move only if needed (keeps the MutationObserver from looping).
if (anchor && anchor.parentNode === row && item.nextElementSibling !== anchor) {
row.insertBefore(item, anchor);
}
}
}
function apply() {
relocateColumnMenuItems();
repositionCommentIcons();
}
function init() { function init() {
if (!document.getElementById("board")) { if (!document.getElementById("board")) {
return; return;
} }
relocate(); apply();
var container = document.getElementById("board-container"); var container = document.getElementById("board-container");
if (container && window.MutationObserver) { if (container && window.MutationObserver) {
new MutationObserver(relocate).observe(container, { childList: true, subtree: true }); new MutationObserver(apply).observe(container, { childList: true, subtree: true });
} }
} }

View File

@@ -15,6 +15,7 @@ class ConfigController extends BaseController
'title' => t('Settings').' &gt; '.t('Organon Tweaks'), 'title' => t('Settings').' &gt; '.t('Organon Tweaks'),
'values' => array( 'values' => array(
'organon_tweaks_always_comment_icon' => (int) $this->configModel->get('organon_tweaks_always_comment_icon', 1), '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(), 'errors' => array(),
))); )));
@@ -25,8 +26,12 @@ class ConfigController extends BaseController
$values = $this->request->getValues(); $values = $this->request->getValues();
$alwaysCommentIcon = isset($values['organon_tweaks_always_comment_icon']) ? 1 : 0; $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.')); $this->flash->success(t('Settings saved successfully.'));
} else { } else {
$this->flash->failure(t('Unable to save your settings.')); $this->flash->failure(t('Unable to save your settings.'));

View File

@@ -26,6 +26,14 @@ class Plugin extends Base
if ((int) $this->configModel->get('organon_tweaks_always_comment_icon', 1) === 1) { 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'); $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() public function getHelpers()
@@ -52,7 +60,7 @@ class Plugin extends Base
public function getPluginVersion() public function getPluginVersion()
{ {
return '0.3.0'; return '1.0.0';
} }
public function getPluginHomepage() public function getPluginHomepage()

View File

@@ -35,11 +35,24 @@ have comments keep showing the count as before.
read-only viewers). read-only viewers).
- **On by default.** Toggle it under "Settings -> Organon Tweaks". - **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 ## Settings
Global (per Kanboard instance) and admin-only, under "Settings -> Organon Tweaks": Global (per Kanboard instance) and admin-only, under "Settings -> Organon Tweaks":
- **Always show the comment icon on board cards** -- default on. - **Always show the comment icon on board cards** -- default on.
- **Keep the board scroll position across refreshes** -- default on.
## Requirements ## Requirements

View File

@@ -10,5 +10,9 @@
* so it does not appear for read-only viewers. * so it does not appear for read-only viewers.
*/ ?> */ ?>
<?php if (empty($task['nb_comments']) && $this->user->hasProjectAccess('CommentController', 'save', $task['project_id'])): ?> <?php if (empty($task['nb_comments']) && $this->user->hasProjectAccess('CommentController', 'save', $task['project_id'])): ?>
<?php /* The hook renders at the END of the icons row; relocate.js moves this span up to
the native comment slot (before the description icon) so the order matches. */ ?>
<span class="organontweaks-comment-add">
<?= $this->modal->medium('comments-o', '+', 'CommentListController', 'show', array('task_id' => $task['id']), t('Add a comment')) ?> <?= $this->modal->medium('comments-o', '+', 'CommentListController', 'show', array('task_id' => $task['id']), t('Add a comment')) ?>
</span>
<?php endif ?> <?php endif ?>

View File

@@ -12,6 +12,13 @@
</p> </p>
</fieldset> </fieldset>
<fieldset>
<?= $this->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) ?>
<p class="form-help">
<?= t('When a card is dropped (or the board auto-refreshes), Kanboard rebuilds the board and jumps back to the first column. This restores the horizontal scroll position so the board stays where you were.') ?>
</p>
</fieldset>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button> <button type="submit" class="btn btn-blue"><?= t('Save') ?></button>
</div> </div>

View File

@@ -1 +1 @@
OrganonTweaks v0.3 OrganonTweaks v1.0