25 Commits
v0.4 ... v1.6.3

Author SHA1 Message Date
cb9e70a105 README: More-plugins section -> Dr. Beco heading + full sibling list (v1.6.3) 2026-07-13 08:50:14 -03:00
63178ed529 ShrinkVertically v1.6.2: move config auto-adjust dim into external JS (CSP blocks inline script) 2026-07-13 08:06:42 -03:00
630e289b6f auto-adjust bottom margin 20px v1.6.1 2026-07-12 08:41:49 -03:00
355104cf26 Auto-adjust fits collapsed columns to the screen; dims manual offset (v1.6.0) 2026-07-12 08:37:18 -03:00
5560c42bcc add cross-promo plugin section to READMEs; bump patch (v*.*.+1) 2026-07-10 09:15:39 -03:00
86f40ff6ae drive top-scrollbar layout() from a single ResizeObserver 2026-07-08 10:01:19 -03:00
89292492db empty track (no knob) when board fits, add fit tolerance (v1.5.1) 2026-07-08 09:48:49 -03:00
f0d9974a31 enlarge empty-column drop area when collapsed: min-height 100px -> 250px, still under max; v1.5 2026-07-06 21:11:39 -03:00
1743f9b868 fix top scrollbar freezing after card drag: re-resolve #board-container on rebuild, sync on parent; v1.4.2 2026-07-06 07:46:45 -03:00
045bae1e4c empty columns need 100px for drop 2026-07-04 09:50:45 -03:00
8ae373b4e7 bar from scratch VERSION v1.4 2026-07-01 10:06:22 -03:00
174b6ad31a bar from scratch Template 2026-07-01 10:06:10 -03:00
4ab761c03e bar from scratch plugin 2026-07-01 10:06:01 -03:00
b8308782f8 bar from scratch Assets 2026-07-01 10:05:52 -03:00
5cccbef7d7 title fixed in config 2026-07-01 10:00:33 -03:00
9b78882835 plugin focused on the vertical size and horizontal bars v1.3 2026-07-01 09:45:30 -03:00
51eba0ad5e growth until hit maximum feature bloat, now decoupling into TweakDrag new plugin 2026-07-01 09:42:51 -03:00
54b9fa93fb starting v1.2 with new tweaks 2026-07-01 07:31:52 -03:00
65b4771e51 gitignore PLAN-* files 2026-07-01 07:31:14 -03:00
dbfe98b3c7 drag-scroll checkbox VERSION 1.1 2026-06-28 17:19:42 -03:00
3beaa88fe9 drag-scroll checkbox 2026-06-28 17:19:04 -03:00
dcb672777d drag-scroll behavior new feature 2026-06-28 17:18:53 -03:00
5712d055d3 feature add top horizontal scroll bar done VERSION v1.0 release 2026-06-28 17:12:25 -03:00
5fcf1ffeb1 js feature add top horizontal scroll bar t2 2026-06-28 17:09:56 -03:00
fc961be3c1 new feature checkbox add top horizontal scroll bar 2026-06-28 17:09:22 -03:00
11 changed files with 537 additions and 23 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
PLAN-*

View File

@@ -26,9 +26,56 @@
* (BoardDragAndDrop.js: css("min-height", parent().height())). In CSS, min-height
* beats max-height, so that inline value keeps tall columns from shrinking and our
* max-height alone does nothing. A stylesheet !important overrides the inline
* (non-important) min-height, so we reset it to 0 and let max-height win.
* (non-important) min-height so max-height can win.
*
* We override it to a NON-ZERO value, not 0: Kanboard relies on that same inline
* min-height to keep EMPTY columns tall enough to be a drop target, so zeroing it makes
* you unable to drop a card into an empty collapsed column. 250px gives a comfortable drop
* target (well above Kanboard's 70px .draggable-placeholder) while staying below the
* collapsed max-height, so the board never overflows the viewport (which would re-hide the
* horizontal scrollbar). Non-empty columns are unaffected -- their content is taller -- and
* short columns simply get this min-height, which is only cosmetic (no scrollbar).
*/
#board td .board-task-list.board-task-list-compact {
max-height: calc(100vh - var(--sv-shrink-offset, 240px)) !important;
min-height: 0 !important;
min-height: 250px !important;
}
/*
* Optional custom top horizontal scrollbar (built by Asset/js/top-scrollbar.js when the
* "Add a top horizontal scrollbar" setting is enabled). It is a track + draggable thumb,
* not a native scrollbar, so it renders and can be sized identically in every browser
* (Chrome, Firefox, Safari) and on touch. Thickness follows the --sv-bar-size variable
* (Settings -> Shrink Vertically), injected in the page head; falls back to 25px.
* touch-action: none lets a finger drag the thumb without scrolling the page.
*/
.sv-top-scroll {
position: relative;
width: 100%;
height: var(--sv-bar-size, 25px);
background: rgba(0, 0, 0, 0.06);
border-radius: 4px;
overflow: hidden;
touch-action: none;
}
.sv-top-scroll-thumb {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 100%;
background: rgba(0, 0, 0, 0.35);
border-radius: 4px;
cursor: grab;
touch-action: none;
}
.sv-top-scroll.sv-dragging .sv-top-scroll-thumb {
cursor: grabbing;
background: rgba(0, 0, 0, 0.5);
}
.sv-dragging {
user-select: none;
}

86
Asset/js/auto-adjust.js Normal file
View File

@@ -0,0 +1,86 @@
/*
* ShrinkVertically -- "Auto adjust" mode. Measure the collapsed board live and set the
* --sv-shrink-offset CSS variable so the columns exactly fill the remaining viewport and the
* native right-side vertical scrollbar is not needed. Loaded only when the setting is on
* (Settings -> Shrink Vertically); it overrides the static offset injected by layout/variable.php.
*
* The collapsed column height is calc(100vh - var(--sv-shrink-offset)). To fit the screen we need:
* offset = (task-list top, i.e. the chrome above) + (horizontal scrollbar below) + a small margin
* so column_bottom + scrollbar stays within the viewport. Both terms are measured from the DOM, so
* this adapts to any screen size, theme, or header height.
*
* Re-measures on resize, on collapse/expand (a class toggle), and after a board refresh
* (BoardDragAndDrop replaces #board-container -- so, like the other plugins, the observer is bound
* to the STABLE parent, not to the container that gets swapped out).
*/
(function () {
"use strict";
var BOTTOM_MARGIN = 20; // breathing room below the columns (covers sub-pixel + borders so nothing overflows)
var scheduled = false;
function apply() {
scheduled = false;
var list = document.querySelector("#board .board-task-list.board-task-list-compact");
if (! list) {
return; // board not collapsed -> nothing to auto-size
}
var top = list.getBoundingClientRect().top;
var container = document.getElementById("board-container");
var bar = container ? (container.offsetHeight - container.clientHeight) : 0; // horizontal scrollbar
if (bar < 0) {
bar = 0;
}
var offset = Math.ceil(top + bar + BOTTOM_MARGIN);
if (offset < 0) {
offset = 0;
}
var next = offset + "px";
var root = document.documentElement;
// Only write when it actually changes, so mid-drag class churn does not thrash layout.
if (root.style.getPropertyValue("--sv-shrink-offset") !== next) {
root.style.setProperty("--sv-shrink-offset", next);
}
}
function schedule() {
if (! scheduled) {
scheduled = true;
window.requestAnimationFrame(apply);
}
}
function init() {
var container = document.getElementById("board-container");
if (! container) {
return; // not a board page
}
schedule();
window.addEventListener("resize", schedule);
if (window.MutationObserver && container.parentNode) {
new MutationObserver(schedule).observe(container.parentNode, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["class"]
});
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

View File

@@ -1,13 +1,17 @@
/*
* ShrinkVertically -- mark the two vertical-collapse menu items so it is obvious the
* behaviour comes from this plugin and not from Kanboard core.
* ShrinkVertically page scripts (loaded on every page via template:layout:js). Each runs only where
* its elements exist, so both are no-ops elsewhere:
*
* Core renders these labels in app/Template/project_header/dropdown.php:
* .filter-vert-collapse .filter-vert-toggle-collapse -> "Collapse vertically"
* .filter-vert-expand .filter-vert-toggle-collapse -> "Expand vertically"
* 1. addPlus() -- mark the two vertical-collapse gear-menu items with a trailing "+" so it is
* obvious the behaviour comes from this plugin and not core. Core renders these labels in
* app/Template/project_header/dropdown.php:
* .filter-vert-collapse .filter-vert-toggle-collapse -> "Collapse vertically"
* .filter-vert-expand .filter-vert-toggle-collapse -> "Expand vertically"
* Editing the rendered text (not the translation table) is locale-independent.
*
* We append a "+" to each so they read "Collapse vertically+" / "Expand vertically+".
* This is locale-independent (it edits the rendered text, not the translation table).
* 2. syncAutoAdjust() -- on Settings -> Shrink Vertically, dim + disable the pixel-offset input
* while "Auto adjust" is checked. This lives here (not an inline <script> on the config page)
* because Kanboard's CSP (default-src 'self') blocks inline scripts and event handlers.
*/
(function () {
"use strict";
@@ -26,9 +30,31 @@
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", addPlus);
} else {
function syncAutoAdjust() {
var auto = document.getElementById("form-shrink_vertically_auto");
var offset = document.getElementById("form-shrink_vertically_offset");
if (! auto || ! offset) {
return;
}
function sync() {
offset.disabled = auto.checked;
offset.style.opacity = auto.checked ? "0.4" : "";
}
auto.addEventListener("change", sync);
sync();
}
function init() {
addPlus();
syncAutoAdjust();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

238
Asset/js/top-scrollbar.js Normal file
View File

@@ -0,0 +1,238 @@
/*
* ShrinkVertically -- optional custom horizontal scrollbar above the board.
*
* Kanboard's board scrolls horizontally inside #board-container (overflow-x: auto), so the
* only native horizontal scrollbar sits at the very bottom of the board. On a wide board
* you have to scroll all the way down to reach it. This adds a second scrollbar just above
* the columns and keeps it in sync with the board.
*
* Unlike a native scrollbar (whose thickness cannot be sized in Firefox), this is a custom
* track + thumb widget: it renders and behaves the same in every browser (Chrome, Firefox,
* Safari) and on touch, and its thickness follows the --sv-bar-size setting.
*
* Resilience note: after a card drag Kanboard rebuilds the board with
* `$("#board-container").replaceWith(data)` (BoardDragAndDrop.refresh), so the element we
* started with is destroyed and replaced. We therefore never cache the container: we
* re-resolve it on every use, listen for its scroll on `document` in the capture phase (so
* the listener survives the swap), and observe the STABLE parent (which is not replaced) so
* the widget keeps working -- previously the observer and scroll listener died with the old
* element, which froze the bar and lost sync after each move.
*
* Loaded only when the "Add a top horizontal scrollbar" setting is enabled.
*/
(function () {
"use strict";
// Keep the thumb wide enough to grab, even on a very wide board.
var MIN_THUMB = 24;
// Ignore a few px of residual overflow (padding / sub-pixel rounding) so a board that visually
// fits is treated as "no horizontal scroll" instead of showing an immovable near-full thumb.
var FIT_TOLERANCE = 4;
function getContainer() {
return document.getElementById("board-container");
}
function init() {
var container = getContainer();
// Only on the board, and never add it twice.
if (!container || document.querySelector(".sv-top-scroll")) {
return;
}
// The parent survives the board refresh (only #board-container is replaced).
var boardParent = container.parentNode;
var track = document.createElement("div");
track.className = "sv-top-scroll";
var thumb = document.createElement("div");
thumb.className = "sv-top-scroll-thumb";
track.appendChild(thumb);
boardParent.insertBefore(track, container);
var dragging = false;
var dragStartX = 0;
var dragStartScrollLeft = 0;
function metrics() {
var c = getContainer();
if (!c) {
return null;
}
return {
c: c,
viewport: c.clientWidth,
content: c.scrollWidth,
trackW: track.clientWidth
};
}
function positionThumb(m, thumbW) {
var maxScroll = m.content - m.viewport;
var maxThumb = m.trackW - thumbW;
var left = maxScroll > 0 ? (m.c.scrollLeft / maxScroll) * maxThumb : 0;
thumb.style.transform = "translateX(" + Math.round(left) + "px)";
}
// The board needs scrolling only if its content exceeds the viewport by more than the
// tolerance (absorbs a residual px or two so a fitting board reads as "no scroll").
function needsScroll(m) {
return m && m.content - m.viewport > FIT_TOLERANCE;
}
// Recompute the thumb. The (light) track stays visible for a STABLE height; when the board
// fits, just drop the knob (thumb width 0) so it reads as "nothing to scroll" without the
// layout jump that hiding the whole bar would cause.
function layout() {
var m = metrics();
if (!m || m.trackW <= 0) {
return;
}
track.style.display = "";
if (!needsScroll(m)) {
thumb.style.width = "0px";
return;
}
var thumbW = Math.round(m.trackW * m.viewport / m.content);
if (thumbW < MIN_THUMB) { thumbW = MIN_THUMB; }
if (thumbW > m.trackW) { thumbW = m.trackW; }
thumb.style.width = thumbW + "px";
positionThumb(m, thumbW);
}
// Keep the track directly before the (current) container after a board refresh.
function reattachTrack() {
var c = getContainer();
if (c && c.parentNode && track.nextSibling !== c) {
c.parentNode.insertBefore(track, c);
}
observeSizes();
}
// Board scrolled by any other means -> keep the thumb in sync. Capture-phase on
// document catches #board-container's (non-bubbling) scroll and survives the swap.
document.addEventListener("scroll", function (e) {
if (dragging) { return; }
if (e.target !== getContainer()) { return; }
var m = metrics();
if (!needsScroll(m)) { return; }
positionThumb(m, thumb.offsetWidth);
}, true);
// Drag the thumb (mouse + touch via Pointer Events).
thumb.addEventListener("pointerdown", function (e) {
var c = getContainer();
if (!c) { return; }
e.preventDefault();
e.stopPropagation();
dragging = true;
dragStartX = e.clientX;
dragStartScrollLeft = c.scrollLeft;
track.classList.add("sv-dragging");
if (thumb.setPointerCapture) {
thumb.setPointerCapture(e.pointerId);
}
});
thumb.addEventListener("pointermove", function (e) {
if (!dragging) { return; }
var m = metrics();
if (!m) { return; }
var maxThumb = m.trackW - thumb.offsetWidth;
var maxScroll = m.content - m.viewport;
if (maxThumb <= 0) { return; }
m.c.scrollLeft = dragStartScrollLeft + (e.clientX - dragStartX) * (maxScroll / maxThumb);
positionThumb(m, thumb.offsetWidth);
});
function endDrag(e) {
if (!dragging) { return; }
dragging = false;
track.classList.remove("sv-dragging");
if (thumb.releasePointerCapture && e && e.pointerId != null) {
try { thumb.releasePointerCapture(e.pointerId); } catch (err) { /* ignore */ }
}
}
thumb.addEventListener("pointerup", endDrag);
thumb.addEventListener("pointercancel", endDrag);
// Click on the empty track -> jump so the thumb centers under the pointer.
track.addEventListener("pointerdown", function (e) {
if (e.target === thumb || dragging) { return; }
var m = metrics();
if (!needsScroll(m)) { return; }
var thumbW = thumb.offsetWidth;
var maxThumb = m.trackW - thumbW;
var maxScroll = m.content - m.viewport;
if (maxThumb <= 0) { return; }
var clickX = e.clientX - track.getBoundingClientRect().left;
var targetLeft = clickX - thumbW / 2;
if (targetLeft < 0) { targetLeft = 0; }
if (targetLeft > maxThumb) { targetLeft = maxThumb; }
m.c.scrollLeft = (targetLeft / maxThumb) * maxScroll;
positionThumb(m, thumbW);
});
// Single source of truth for recomputing the thumb: a ResizeObserver on the container
// (viewport) and #board (content). It fires exactly when the width settles/changes -- the
// right moment -- which also fixes the stale thumb from the first layout() running before the
// board finished sizing. Both are replaced on a board refresh, so re-observe the current ones
// (from reattachTrack). Without ResizeObserver, fall back to a window resize listener.
var sizeObserver = window.ResizeObserver ? new ResizeObserver(function () { layout(); }) : null;
var observedContainer = null;
var observedBoard = null;
function observeSizes() {
if (!sizeObserver) {
layout();
return;
}
var c = getContainer();
var b = document.getElementById("board");
if (c && c !== observedContainer) {
if (observedContainer) { sizeObserver.unobserve(observedContainer); }
sizeObserver.observe(c);
observedContainer = c;
}
if (b && b !== observedBoard) {
if (observedBoard) { sizeObserver.unobserve(observedBoard); }
sizeObserver.observe(b);
observedBoard = b;
}
}
if (!sizeObserver) {
window.addEventListener("resize", layout);
}
// Re-seat the track and re-observe after #board-container is replaced by a card move / AJAX
// polling (we watch the STABLE parent, since the container itself is swapped out).
if (window.MutationObserver) {
new MutationObserver(reattachTrack).observe(boardParent, { childList: true, subtree: true });
}
observeSizes();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

View File

@@ -14,14 +14,24 @@ class ConfigController extends BaseController
const MIN_OFFSET = 0;
const MAX_OFFSET = 2000;
const DEFAULT_BAR_SIZE = 25;
const MIN_BAR_SIZE = 0;
const MAX_BAR_SIZE = 200;
public function show()
{
$offset = (int) $this->configModel->get('shrink_vertically_offset', self::DEFAULT_OFFSET);
$barSize = (int) $this->configModel->get('shrink_vertically_bar_size', self::DEFAULT_BAR_SIZE);
$topScrollbar = (int) $this->configModel->get('shrink_vertically_top_scrollbar', 1);
$auto = (int) $this->configModel->get('shrink_vertically_auto', 0);
$this->response->html($this->helper->layout->config('shrinkVertically:config/show', array(
'title' => t('Settings').' &gt; '.t('Shrink Vertically'),
'values' => array(
'shrink_vertically_offset' => $offset,
'shrink_vertically_bar_size' => $barSize,
'shrink_vertically_top_scrollbar' => $topScrollbar,
'shrink_vertically_auto' => $auto,
),
'errors' => array(),
)));
@@ -31,10 +41,25 @@ class ConfigController extends BaseController
{
$values = $this->request->getValues();
$offset = isset($values['shrink_vertically_offset']) ? (int) $values['shrink_vertically_offset'] : self::DEFAULT_OFFSET;
// In auto mode the offset input is disabled client-side, so the browser does not submit it.
// Fall back to the stored value (not the default) so enabling auto never wipes the manual offset.
$offset = isset($values['shrink_vertically_offset'])
? (int) $values['shrink_vertically_offset']
: (int) $this->configModel->get('shrink_vertically_offset', self::DEFAULT_OFFSET);
$offset = max(self::MIN_OFFSET, min(self::MAX_OFFSET, $offset));
if ($this->configModel->save(array('shrink_vertically_offset' => $offset))) {
$barSize = isset($values['shrink_vertically_bar_size']) ? (int) $values['shrink_vertically_bar_size'] : self::DEFAULT_BAR_SIZE;
$barSize = max(self::MIN_BAR_SIZE, min(self::MAX_BAR_SIZE, $barSize));
$topScrollbar = isset($values['shrink_vertically_top_scrollbar']) ? 1 : 0;
$auto = isset($values['shrink_vertically_auto']) ? 1 : 0;
if ($this->configModel->save(array(
'shrink_vertically_offset' => $offset,
'shrink_vertically_bar_size' => $barSize,
'shrink_vertically_top_scrollbar' => $topScrollbar,
'shrink_vertically_auto' => $auto,
))) {
$this->flash->success(t('Settings saved successfully.'));
} else {
$this->flash->failure(t('Unable to save your settings.'));

View File

@@ -25,6 +25,21 @@ class Plugin extends Base
// Add a "Shrink Vertically" entry to the Settings sidebar.
$this->template->hook->attach('template:config:sidebar', 'shrinkVertically:config/sidebar');
// Optional: a second, mirrored horizontal scrollbar above the board columns.
if ((int) $this->configModel->get('shrink_vertically_top_scrollbar', 1) === 1) {
$this->hook->on('template:layout:js', array(
'template' => 'plugins/ShrinkVertically/Asset/js/top-scrollbar.js',
));
}
// Optional: auto-adjust the collapsed column height to the screen (measured live on the
// board), overriding the static pixel offset so the vertical scrollbar is not needed.
if ((int) $this->configModel->get('shrink_vertically_auto', 0) === 1) {
$this->hook->on('template:layout:js', array(
'template' => 'plugins/ShrinkVertically/Asset/js/auto-adjust.js',
));
}
}
public function getPluginName()
@@ -44,7 +59,7 @@ class Plugin extends Base
public function getPluginVersion()
{
return '0.4.0';
return '1.6.3';
}
public function getPluginHomepage()

View File

@@ -1,7 +1,12 @@
# ShrinkVertically -- keep the board horizontal scrollbar visible
A tiny Kanboard plugin that fixes the vertically-collapsed board view so the bottom
horizontal scrollbar stays on screen.
A small Kanboard plugin focused on the vertically-collapsed board view. It fixes the
collapse so the bottom horizontal scrollbar stays on screen, and can optionally add a
second mirrored scrollbar above the columns. Configurable under
"Settings -> Shrink Vertically".
Board drag/touch and column-gap options that used to live here have moved to the separate
`TweakDrag` plugin.
## The problem
@@ -59,13 +64,58 @@ Copy this folder into your Kanboard installation as `plugins/ShrinkVertically/`.
directory name must be exactly `ShrinkVertically` (Kanboard derives the plugin namespace
from the folder name). Reload the board; no build step, no configuration.
## Top horizontal scrollbar (optional)
Kanboard's board scrolls horizontally inside `#board-container`, so its only horizontal
scrollbar is at the very bottom of the board -- on a wide board you must scroll all the
way down to move sideways. Enable "Add a top horizontal scrollbar" in the settings to get
a second scrollbar just above the columns.
It is a custom track + draggable thumb (`Asset/js/top-scrollbar.js`, loaded only when the
option is on), not a native scrollbar, so it renders and can be sized the same in every
browser (Chrome, Firefox, Safari) and on touch. Drag the thumb or click the track to move
the board; the thumb tracks the board when you scroll it by other means. Its thickness
follows the "Top Horizontal Scrollbar thickness" setting.
## Settings
Go to "Settings -> Shrink Vertically" to set the pixel offset. The value is stored in the
Kanboard configuration and injected in the page head as the `--sv-shrink-offset` CSS
variable. Increase it if the scrollbar is still clipped (for example with a taller theme
header); decrease it to make the collapsed columns taller. Default: 240.
These settings are global (per Kanboard instance) and admin-only. Go to
"Settings -> Shrink Vertically":
- **Vertical offset in pixels** -- pixels reserved around the collapsed columns. Injected
in the page head as the `--sv-shrink-offset` CSS variable. Increase it if the scrollbar
is still clipped (for example with a taller theme header); decrease it to make the
collapsed columns taller. Default: 240.
- **Auto adjust** -- instead of a fixed offset, measure the collapsed board live and size
the columns to fit the screen automatically, so the right-side vertical scrollbar is not
needed. It re-measures on window resize, on collapse/expand, and after a board refresh.
While enabled, the pixel-offset field above is disabled and dimmed (the measured value is
used instead). Loaded as `Asset/js/auto-adjust.js` only when the option is on. Default: off.
- **Top Horizontal Scrollbar thickness in pixels** -- thickness of the custom top
scrollbar (all browsers). Injected as the `--sv-bar-size` CSS variable. Default: 25.
- **Add a top horizontal scrollbar to the board** -- toggles the mirrored top scrollbar
described above. Default: on.
## License
AGPL-3.0. See LICENSE.
## More Kanboard plugins by Dr. Bèco
All free and AGPL-3.0, at [code.beco.cc](https://code.beco.cc/beco):
- **[RecoReco](https://code.beco.cc/beco/RecoReco)** -- calendar-scheduled recurring cards (yearly,
monthly, weekly, daily), driven by the card due date.
- **[FinanceBuddy](https://code.beco.cc/beco/FinanceBuddy)** -- attach a money value (debit/credit
and installments) to cards, shown on the card and totalled per column.
- **[QualKard](https://code.beco.cc/beco/QualKard)** -- flashcard spaced repetition: grade a card by
dragging it to a column, and it comes back when due (needs RecoReco).
- **[WorkspaceOrg](https://code.beco.cc/beco/WorkspaceOrg)** -- group your projects into personal
per-user workspaces, shown on a "My workspaces" dashboard page with a badge on each project.
- **[OrganonTweaks](https://code.beco.cc/beco/OrganonTweaks)** -- an umbrella of small
quality-of-life board tweaks: remove an empty column, always show the comment icon, emphasize due
dates, extra search keywords and shared board filters, and more.
- **[BulkMoveTasks](https://code.beco.cc/beco/BulkMoveTasks)** -- move every task from one board
column to another in a single action.
- **[TweakDrag](https://code.beco.cc/beco/TweakDrag)** -- board drag and touch niceties:
drag-to-scroll, a wider column gap, and smoother card dragging.

View File

@@ -11,9 +11,31 @@
<p class="form-help">
<?= t('Pixels reserved above and below the vertically collapsed board columns (page header, column header and the horizontal scrollbar). A larger value makes the columns shorter. Default: 240.') ?>
</p>
<?= $this->form->checkbox('shrink_vertically_auto', t('Auto adjust (fit columns to the screen automatically)'), 1, isset($values['shrink_vertically_auto']) && $values['shrink_vertically_auto'] == 1, '', array('id' => 'form-shrink_vertically_auto')) ?>
<p class="form-help">
<?= t('When enabled, the collapsed column height is measured live on the board to fit the screen, so the right-side vertical scrollbar is not needed. The pixel offset above is then ignored (shown dimmed).') ?>
</p>
</fieldset>
<fieldset>
<?= $this->form->label(t('Top Horizontal Scrollbar thickness in pixels'), 'shrink_vertically_bar_size') ?>
<?= $this->form->number('shrink_vertically_bar_size', $values, $errors) ?>
<p class="form-help">
<?= t('Thickness of the top horizontal scrollbar. Default: 25.') ?>
</p>
</fieldset>
<fieldset>
<?= $this->form->checkbox('shrink_vertically_top_scrollbar', t('Add a top horizontal scrollbar to the board'), 1, isset($values['shrink_vertically_top_scrollbar']) && $values['shrink_vertically_top_scrollbar'] == 1) ?>
<p class="form-help">
<?= t('Adds a second horizontal scrollbar above the columns, mirrored with the board scrollbar, so you do not have to scroll to the bottom to move sideways.') ?>
</p>
</fieldset>
<div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button>
</div>
</form>
<?php /* The "Auto adjust" checkbox dims/disables the offset input via Asset/js/shrink-vertically.js
(loaded globally). An inline <script> here would be blocked by Kanboard's CSP. */ ?>

View File

@@ -1,3 +1,6 @@
<style type="text/css">
:root { --sv-shrink-offset: <?= (int) $this->app->config('shrink_vertically_offset', 240) ?>px; }
:root {
--sv-shrink-offset: <?= (int) $this->app->config('shrink_vertically_offset', 240) ?>px;
--sv-bar-size: <?= (int) $this->app->config('shrink_vertically_bar_size', 25) ?>px;
}
</style>

View File

@@ -1 +1 @@
ShrinkVertically v0.4
ShrinkVertically v1.6.3