Compare commits
7 Commits
4e48e33b26
...
v1.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e472c388c | |||
| 8373fe2637 | |||
| fe577a674c | |||
| 8760377927 | |||
| 7f9235dbdc | |||
| bb3ac1c946 | |||
| 924e287aee |
@@ -6,16 +6,17 @@
|
|||||||
* --td-column-gap value, so it is not in this static stylesheet.
|
* --td-column-gap value, so it is not in this static stylesheet.
|
||||||
*
|
*
|
||||||
* Below: click-and-drag horizontal scrolling (added by Asset/js/drag-scroll.js when the
|
* Below: click-and-drag horizontal scrolling (added by Asset/js/drag-scroll.js when the
|
||||||
* "Drag the board background to scroll" setting is enabled). The .td-drag-scroll class is
|
* "Drag the board background to scroll" setting is enabled). The .td-drag-scroll marker is
|
||||||
* set on #board-container by the script, so these rules are inert when disabled. A grab
|
* set on <body> by the script (not on #board-container, which Kanboard replaces on every
|
||||||
|
* card move), so these rules are inert when disabled and survive board rebuilds. A grab
|
||||||
* cursor advertises the empty background; cards keep the normal cursor since pressing them
|
* cursor advertises the empty background; cards keep the normal cursor since pressing them
|
||||||
* does a card drag, not a pan.
|
* does a card drag, not a pan.
|
||||||
*/
|
*/
|
||||||
#board-container.td-drag-scroll {
|
body.td-drag-scroll #board-container {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
#board-container.td-drag-scroll .task-board {
|
body.td-drag-scroll #board-container .task-board {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,3 +25,17 @@
|
|||||||
cursor: grabbing !important;
|
cursor: grabbing !important;
|
||||||
user-select: none !important;
|
user-select: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enlarge the mobile card drag handle so it is a usable touch target.
|
||||||
|
* Kanboard reveals the .task-board-sort-handle (a FontAwesome fa-arrows-alt glyph) only
|
||||||
|
* on mobile (isMobile.any) and restricts card dragging to it; by default the glyph is
|
||||||
|
* tiny and hard to grab with a finger. FontAwesome icons are sized by font-size, so this
|
||||||
|
* is a pure font-size bump; it is inert on desktop where the handle stays display:none.
|
||||||
|
* Size comes from the --td-handle-size variable (Settings -> Tweak Drag), injected in the
|
||||||
|
* page head; falls back to 20px.
|
||||||
|
*/
|
||||||
|
.task-board-sort-handle,
|
||||||
|
.task-board-sort-handle i {
|
||||||
|
font-size: var(--td-handle-size, 20px);
|
||||||
|
}
|
||||||
|
|||||||
187
Asset/js/drag-autoscroll.js
Normal file
187
Asset/js/drag-autoscroll.js
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* TweakDrag -- auto-scroll the board while dragging a card near a screen edge.
|
||||||
|
*
|
||||||
|
* Kanboard drags cards with jQuery UI sortable. The board does not scroll during a drag
|
||||||
|
* (jQuery UI's autoscroll is unreliable, and on touch the bundled Touch Punch shim
|
||||||
|
* suppresses native scrolling while a card is held), so you can only drop where already on
|
||||||
|
* screen. While a card drag is active and the pointer nears an edge, this scrolls toward it
|
||||||
|
* with a smooth continuous speed that grows toward the edge:
|
||||||
|
*
|
||||||
|
* - Horizontal: scrolls #board-container (near its left/right edge).
|
||||||
|
* - Vertical: scrolls the window/page (near the viewport top/bottom). This matters when
|
||||||
|
* the vertical-collapse view is OFF and the whole page scrolls; when collapse is ON the
|
||||||
|
* page is not scrollable so window scrolling no-ops and the per-column scroll stays
|
||||||
|
* native.
|
||||||
|
*
|
||||||
|
* The edge zone is a percentage of each axis (clamped), so it adapts to screen size and
|
||||||
|
* orientation: a narrow phone width gets a small horizontal zone, a tall phone height a
|
||||||
|
* larger vertical one, a wide desktop the reverse -- with no device detection.
|
||||||
|
*
|
||||||
|
* It only acts during an active card drag near an edge, so it is harmless when idle. Works
|
||||||
|
* on desktop (mouse) and mobile (touch, via Touch Punch). No core changes.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EDGE_ZONE_PCT = 0.15; // edge zone = 15% of the axis length...
|
||||||
|
var EDGE_ZONE_MIN = 30; // ...clamped to at least 30px...
|
||||||
|
var EDGE_ZONE_MAX = 160; // ...and at most 160px.
|
||||||
|
var MAX_SPEED = 22; // px per animation frame at the very edge
|
||||||
|
var EASE = 1; // 1 = linear ramp, 2 = gentle-then-fast
|
||||||
|
var SCROLL_SAFETY = 200; // px of page we may scroll past the bottom of the columns
|
||||||
|
|
||||||
|
var jq = window.jQuery;
|
||||||
|
if (!jq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dragging = false;
|
||||||
|
var rafId = 0;
|
||||||
|
var lastX = 0;
|
||||||
|
var lastY = 0;
|
||||||
|
|
||||||
|
function onMove(e) {
|
||||||
|
if (e.touches && e.touches.length) {
|
||||||
|
lastX = e.touches[0].clientX;
|
||||||
|
lastY = e.touches[0].clientY;
|
||||||
|
} else {
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge zone for an axis of the given length (px), 15% clamped to [30, 160].
|
||||||
|
function zoneFor(length) {
|
||||||
|
var z = length * EDGE_ZONE_PCT;
|
||||||
|
if (z < EDGE_ZONE_MIN) { z = EDGE_ZONE_MIN; }
|
||||||
|
if (z > EDGE_ZONE_MAX) { z = EDGE_ZONE_MAX; }
|
||||||
|
return z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance is px from the edge; 0 at the zone border up to MAX_SPEED at the edge.
|
||||||
|
function speedFor(distance, zone) {
|
||||||
|
var p = (zone - distance) / zone;
|
||||||
|
if (p <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (p > 1) {
|
||||||
|
p = 1;
|
||||||
|
}
|
||||||
|
if (EASE !== 1) {
|
||||||
|
p = Math.pow(p, EASE);
|
||||||
|
}
|
||||||
|
return MAX_SPEED * p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function step() {
|
||||||
|
if (!dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var moved = false;
|
||||||
|
|
||||||
|
// Horizontal: scroll the board container near its left/right edge.
|
||||||
|
var c = document.getElementById("board-container");
|
||||||
|
if (c) {
|
||||||
|
var rect = c.getBoundingClientRect();
|
||||||
|
var zoneH = zoneFor(rect.width);
|
||||||
|
var hspeed = 0;
|
||||||
|
var dLeft = lastX - rect.left;
|
||||||
|
var dRight = rect.right - lastX;
|
||||||
|
|
||||||
|
if (dLeft < zoneH) {
|
||||||
|
hspeed = -speedFor(dLeft, zoneH);
|
||||||
|
} else if (dRight < zoneH) {
|
||||||
|
hspeed = speedFor(dRight, zoneH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hspeed !== 0) {
|
||||||
|
var beforeX = c.scrollLeft;
|
||||||
|
c.scrollLeft = beforeX + hspeed;
|
||||||
|
if (c.scrollLeft !== beforeX) {
|
||||||
|
moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical: scroll the window near the viewport top/bottom (collapse-off case;
|
||||||
|
// a natural no-op when the page is not scrollable).
|
||||||
|
var zoneV = zoneFor(window.innerHeight);
|
||||||
|
var vspeed = 0;
|
||||||
|
var dTop = lastY;
|
||||||
|
var dBottom = window.innerHeight - lastY;
|
||||||
|
|
||||||
|
if (dTop < zoneV) {
|
||||||
|
vspeed = -speedFor(dTop, zoneV);
|
||||||
|
} else if (dBottom < zoneV) {
|
||||||
|
vspeed = speedFor(dBottom, zoneV);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap downward scrolling to just past the bottom of the columns, so a drag held at
|
||||||
|
// the bottom edge cannot scroll the page forever. #board is the columns table, so
|
||||||
|
// its height is the tallest column; allow SCROLL_SAFETY px beyond it.
|
||||||
|
if (vspeed > 0) {
|
||||||
|
var boardEl = document.getElementById("board");
|
||||||
|
if (boardEl) {
|
||||||
|
var boardBottomPage = boardEl.getBoundingClientRect().bottom + window.pageYOffset;
|
||||||
|
var maxScrollY = boardBottomPage + SCROLL_SAFETY - window.innerHeight;
|
||||||
|
if (window.pageYOffset >= maxScrollY) {
|
||||||
|
vspeed = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vspeed !== 0) {
|
||||||
|
var beforeY = window.pageYOffset;
|
||||||
|
window.scrollBy(0, vspeed);
|
||||||
|
if (window.pageYOffset !== beforeY) {
|
||||||
|
moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the board/page actually moved, nudge jQuery UI to recompute drop positions
|
||||||
|
// so the placeholder follows onto the newly revealed area (either axis). Dispatch
|
||||||
|
// through jQuery with EXPLICIT page coordinates: a raw synthetic MouseEvent does not
|
||||||
|
// carry a correct pageY on mobile browsers, which made jQuery UI drop the dragged
|
||||||
|
// card to a wrong vertical position (X right, Y jumping up).
|
||||||
|
if (moved) {
|
||||||
|
jq(document).trigger(jq.Event("mousemove", {
|
||||||
|
which: 1,
|
||||||
|
clientX: lastX,
|
||||||
|
clientY: lastY,
|
||||||
|
pageX: lastX + window.pageXOffset,
|
||||||
|
pageY: lastY + window.pageYOffset
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
rafId = window.requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLoop() {
|
||||||
|
if (!document.getElementById("board-container")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dragging = true;
|
||||||
|
if (!rafId) {
|
||||||
|
rafId = window.requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopLoop() {
|
||||||
|
dragging = false;
|
||||||
|
if (rafId) {
|
||||||
|
window.cancelAnimationFrame(rafId);
|
||||||
|
rafId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the pointer at all times (cheap; only used while dragging).
|
||||||
|
document.addEventListener("mousemove", onMove, true);
|
||||||
|
document.addEventListener("touchmove", onMove, true);
|
||||||
|
document.addEventListener("pointermove", onMove, true);
|
||||||
|
|
||||||
|
// A card drag started / stopped. jQuery UI sortable fires these (on touch too, via
|
||||||
|
// Touch Punch); delegated on the document so board redraws need no rebinding.
|
||||||
|
jq(document).on("sortstart", startLoop);
|
||||||
|
jq(document).on("sortstop", stopLoop);
|
||||||
|
})();
|
||||||
@@ -6,6 +6,13 @@
|
|||||||
* pressing a task card, a drag handle, or any interactive control is ignored, so this
|
* pressing a task card, a drag handle, or any interactive control is ignored, so this
|
||||||
* never interferes with Kanboard's own card and column drag-and-drop.
|
* never interferes with Kanboard's own card and column drag-and-drop.
|
||||||
*
|
*
|
||||||
|
* Resilience note: after a card drag Kanboard rebuilds the board with
|
||||||
|
* `$("#board-container").replaceWith(data)` (BoardDragAndDrop.refresh), destroying the
|
||||||
|
* element. So we must NOT bind to #board-container directly: we listen on `document` and
|
||||||
|
* re-resolve the container on each press, and the grab-cursor marker lives on <body>
|
||||||
|
* (which is never replaced). Previously the mousedown listener and the cursor class were
|
||||||
|
* on the container, so background dragging stopped working after every card move.
|
||||||
|
*
|
||||||
* Loaded only when the "Drag the board background to scroll" setting is enabled.
|
* Loaded only when the "Drag the board background to scroll" setting is enabled.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
@@ -21,34 +28,40 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var container = document.getElementById("board-container");
|
if (!document.getElementById("board-container") || document.body.getAttribute("data-td-drag") === "1") {
|
||||||
|
|
||||||
if (!container || container.getAttribute("data-td-drag") === "1") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.setAttribute("data-td-drag", "1");
|
// Marker on <body> (stable) enables the grab cursor via CSS, surviving board rebuilds.
|
||||||
container.classList.add("td-drag-scroll");
|
document.body.setAttribute("data-td-drag", "1");
|
||||||
|
document.body.classList.add("td-drag-scroll");
|
||||||
|
|
||||||
var dragging = false;
|
var dragging = false;
|
||||||
|
var container = null;
|
||||||
var startX = 0;
|
var startX = 0;
|
||||||
var startScrollLeft = 0;
|
var startScrollLeft = 0;
|
||||||
|
|
||||||
container.addEventListener("mousedown", function (e) {
|
document.addEventListener("mousedown", function (e) {
|
||||||
// Left button only, and only on empty board background.
|
if (e.button !== 0) {
|
||||||
if (e.button !== 0 || isIgnored(e.target)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-resolve the current container and only act on its empty background.
|
||||||
|
var c = document.getElementById("board-container");
|
||||||
|
if (!c || !c.contains(e.target) || isIgnored(e.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container = c;
|
||||||
dragging = true;
|
dragging = true;
|
||||||
startX = e.clientX;
|
startX = e.clientX;
|
||||||
startScrollLeft = container.scrollLeft;
|
startScrollLeft = c.scrollLeft;
|
||||||
container.classList.add("td-grabbing");
|
document.body.classList.add("td-grabbing");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mousemove", function (e) {
|
document.addEventListener("mousemove", function (e) {
|
||||||
if (!dragging) {
|
if (!dragging || !container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
container.scrollLeft = startScrollLeft - (e.clientX - startX);
|
container.scrollLeft = startScrollLeft - (e.clientX - startX);
|
||||||
@@ -60,7 +73,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dragging = false;
|
dragging = false;
|
||||||
container.classList.remove("td-grabbing");
|
container = null;
|
||||||
|
document.body.classList.remove("td-grabbing");
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("mouseup", stop);
|
document.addEventListener("mouseup", stop);
|
||||||
|
|||||||
@@ -13,11 +13,16 @@ class ConfigController extends BaseController
|
|||||||
const MIN_GAP_SIZE = 0;
|
const MIN_GAP_SIZE = 0;
|
||||||
const MAX_GAP_SIZE = 200;
|
const MAX_GAP_SIZE = 200;
|
||||||
|
|
||||||
|
const DEFAULT_HANDLE_SIZE = 20;
|
||||||
|
const MIN_HANDLE_SIZE = 8;
|
||||||
|
const MAX_HANDLE_SIZE = 200;
|
||||||
|
|
||||||
public function show()
|
public function show()
|
||||||
{
|
{
|
||||||
$gapSize = (int) $this->configModel->get('tweakdrag_column_gap_size', self::DEFAULT_GAP_SIZE);
|
$gapSize = (int) $this->configModel->get('tweakdrag_column_gap_size', self::DEFAULT_GAP_SIZE);
|
||||||
$columnGap = (int) $this->configModel->get('tweakdrag_column_gap', 1);
|
$columnGap = (int) $this->configModel->get('tweakdrag_column_gap', 1);
|
||||||
$dragScroll = (int) $this->configModel->get('tweakdrag_drag_scroll', 1);
|
$dragScroll = (int) $this->configModel->get('tweakdrag_drag_scroll', 1);
|
||||||
|
$handleSize = (int) $this->configModel->get('tweakdrag_handle_size', self::DEFAULT_HANDLE_SIZE);
|
||||||
|
|
||||||
$this->response->html($this->helper->layout->config('tweakDrag:config/show', array(
|
$this->response->html($this->helper->layout->config('tweakDrag:config/show', array(
|
||||||
'title' => t('Settings').' > '.t('Tweak Drag'),
|
'title' => t('Settings').' > '.t('Tweak Drag'),
|
||||||
@@ -25,6 +30,7 @@ class ConfigController extends BaseController
|
|||||||
'tweakdrag_column_gap_size' => $gapSize,
|
'tweakdrag_column_gap_size' => $gapSize,
|
||||||
'tweakdrag_column_gap' => $columnGap,
|
'tweakdrag_column_gap' => $columnGap,
|
||||||
'tweakdrag_drag_scroll' => $dragScroll,
|
'tweakdrag_drag_scroll' => $dragScroll,
|
||||||
|
'tweakdrag_handle_size' => $handleSize,
|
||||||
),
|
),
|
||||||
'errors' => array(),
|
'errors' => array(),
|
||||||
)));
|
)));
|
||||||
@@ -40,10 +46,14 @@ class ConfigController extends BaseController
|
|||||||
$columnGap = isset($values['tweakdrag_column_gap']) ? 1 : 0;
|
$columnGap = isset($values['tweakdrag_column_gap']) ? 1 : 0;
|
||||||
$dragScroll = isset($values['tweakdrag_drag_scroll']) ? 1 : 0;
|
$dragScroll = isset($values['tweakdrag_drag_scroll']) ? 1 : 0;
|
||||||
|
|
||||||
|
$handleSize = isset($values['tweakdrag_handle_size']) ? (int) $values['tweakdrag_handle_size'] : self::DEFAULT_HANDLE_SIZE;
|
||||||
|
$handleSize = max(self::MIN_HANDLE_SIZE, min(self::MAX_HANDLE_SIZE, $handleSize));
|
||||||
|
|
||||||
if ($this->configModel->save(array(
|
if ($this->configModel->save(array(
|
||||||
'tweakdrag_column_gap_size' => $gapSize,
|
'tweakdrag_column_gap_size' => $gapSize,
|
||||||
'tweakdrag_column_gap' => $columnGap,
|
'tweakdrag_column_gap' => $columnGap,
|
||||||
'tweakdrag_drag_scroll' => $dragScroll,
|
'tweakdrag_drag_scroll' => $dragScroll,
|
||||||
|
'tweakdrag_handle_size' => $handleSize,
|
||||||
))) {
|
))) {
|
||||||
$this->flash->success(t('Settings saved successfully.'));
|
$this->flash->success(t('Settings saved successfully.'));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ class Plugin extends Base
|
|||||||
'template' => 'plugins/TweakDrag/Asset/js/drag-scroll.js',
|
'template' => 'plugins/TweakDrag/Asset/js/drag-scroll.js',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-scroll the board while dragging a card near a screen edge. Always loaded;
|
||||||
|
// it only acts during an active card drag.
|
||||||
|
$this->hook->on('template:layout:js', array(
|
||||||
|
'template' => 'plugins/TweakDrag/Asset/js/drag-autoscroll.js',
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPluginName()
|
public function getPluginName()
|
||||||
@@ -44,7 +50,7 @@ class Plugin extends Base
|
|||||||
|
|
||||||
public function getPluginVersion()
|
public function getPluginVersion()
|
||||||
{
|
{
|
||||||
return '0.2.0';
|
return '1.2.2';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPluginHomepage()
|
public function getPluginHomepage()
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -1,12 +1,18 @@
|
|||||||
# TweakDrag -- board drag, touch and column-gap tweaks
|
# TweakDrag -- board drag, touch and column-gap tweaks
|
||||||
|
|
||||||
A Kanboard plugin that groups board dragging and spacing tweaks. This first version
|
A Kanboard plugin that groups board dragging and spacing tweaks, configurable under
|
||||||
provides two options, both configurable under "Settings -> Tweak Drag":
|
"Settings -> Tweak Drag":
|
||||||
|
|
||||||
- **Drag the board background to scroll** (desktop) -- click and drag on the empty board
|
- **Drag the board background to scroll** (desktop) -- click and drag on the empty board
|
||||||
background to scroll the columns sideways, like the Trello board canvas.
|
background to scroll the columns sideways, like the Trello board canvas.
|
||||||
- **Widen the gap between columns** -- spread the columns further apart, easier to tell
|
- **Widen the gap between columns** -- spread the columns further apart, easier to tell
|
||||||
apart and to touch.
|
apart and to touch.
|
||||||
|
- **Mobile drag handle size** -- enlarge the card drag handle Kanboard shows on mobile so
|
||||||
|
it is easy to grab with a finger.
|
||||||
|
- **Drag auto-scroll** -- while dragging a card, the board scrolls when the pointer nears a
|
||||||
|
screen edge, so you can reach a column or row that is off screen. Horizontal scrolls the
|
||||||
|
board; vertical scrolls the page (when the vertical-collapse view is off). Works on
|
||||||
|
desktop and mobile.
|
||||||
|
|
||||||
These options were originally part of the ShrinkVertically plugin and moved here so that
|
These options were originally part of the ShrinkVertically plugin and moved here so that
|
||||||
drag/touch behaviour can grow on its own (planned: mobile drag-to-move cards, and a drag
|
drag/touch behaviour can grow on its own (planned: mobile drag-to-move cards, and a drag
|
||||||
@@ -23,6 +29,20 @@ movement threshold so ending a drag does not open the task) without bloating tha
|
|||||||
by the `--td-column-gap` CSS variable. This spreads the columns without shrinking the
|
by the `--td-column-gap` CSS variable. This spreads the columns without shrinking the
|
||||||
cards and keeps each column header aligned with its list. It also adds some spacing at
|
cards and keeps each column header aligned with its list. It also adds some spacing at
|
||||||
the left and right edges of the board.
|
the left and right edges of the board.
|
||||||
|
- **Mobile drag handle size**: Kanboard already supports dragging cards on mobile, but only
|
||||||
|
via a small handle (a FontAwesome `fa-arrows-alt` glyph it reveals on touch devices) that
|
||||||
|
is hard to grab. FontAwesome icons are sized by `font-size`, so a single rule scales the
|
||||||
|
handle from the `--td-handle-size` CSS variable. It is inert on desktop, where Kanboard
|
||||||
|
keeps the handle hidden.
|
||||||
|
- **Drag auto-scroll**: a script (`Asset/js/drag-autoscroll.js`) listens for jQuery UI
|
||||||
|
sortable's `sortstart`/`sortstop` (which also fire on touch via Kanboard's bundled Touch
|
||||||
|
Punch) and, while a card drag is active, scrolls toward whichever edge the pointer nears:
|
||||||
|
the board (`#board-container`) horizontally, and the window/page vertically (for the
|
||||||
|
collapse-off view; a no-op when the page does not scroll). Speed ramps up smoothly toward
|
||||||
|
the edge. The edge zone is 15% of each axis, clamped to 30-160px, so it adapts to screen
|
||||||
|
size and orientation. After each scroll step it dispatches a synthetic `mousemove` so
|
||||||
|
jQuery UI moves the drop placeholder onto the newly revealed area. It only acts during an
|
||||||
|
active drag, so it is idle otherwise.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -43,7 +63,25 @@ These settings are global (per Kanboard instance) and admin-only. Go to
|
|||||||
- **Widen the gap between columns** -- toggles the wider gap. Default: on.
|
- **Widen the gap between columns** -- toggles the wider gap. Default: on.
|
||||||
- **Drag the board background to scroll horizontally** -- toggles the Trello-style
|
- **Drag the board background to scroll horizontally** -- toggles the Trello-style
|
||||||
click-and-drag panning. Desktop (mouse) for now. Default: on.
|
click-and-drag panning. Desktop (mouse) for now. Default: on.
|
||||||
|
- **Mobile drag handle size in pixels** -- size of the mobile card drag handle
|
||||||
|
(`--td-handle-size`). No effect on desktop. Default: 20.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
AGPL-3.0. See LICENSE.
|
AGPL-3.0. See LICENSE.
|
||||||
|
|
||||||
|
## More Kanboard plugins by Ruben (drbeco)
|
||||||
|
|
||||||
|
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.
|
||||||
|
- **[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.
|
||||||
|
- **[ShrinkVertically](https://code.beco.cc/beco/ShrinkVertically)** -- shrink vertically-collapsed
|
||||||
|
board columns so the horizontal scrollbar stays within reach.
|
||||||
|
|||||||
@@ -27,6 +27,14 @@
|
|||||||
</p>
|
</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<?= $this->form->label(t('Mobile drag handle size in pixels'), 'tweakdrag_handle_size') ?>
|
||||||
|
<?= $this->form->number('tweakdrag_handle_size', $values, $errors) ?>
|
||||||
|
<p class="form-help">
|
||||||
|
<?= t('Size of the card drag handle that Kanboard shows on mobile, so it is easy to grab with a finger. No effect on desktop. Default: 20.') ?>
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
:root {
|
:root {
|
||||||
--td-column-gap: <?= (int) $this->app->config('tweakdrag_column_gap_size', 25) ?>px;
|
--td-column-gap: <?= (int) $this->app->config('tweakdrag_column_gap_size', 25) ?>px;
|
||||||
|
--td-handle-size: <?= (int) $this->app->config('tweakdrag_handle_size', 20) ?>px;
|
||||||
}
|
}
|
||||||
<?php if ((int) $this->app->config('tweakdrag_column_gap', 1) === 1): ?>
|
<?php if ((int) $this->app->config('tweakdrag_column_gap', 1) === 1): ?>
|
||||||
#board { border-collapse: separate !important; border-spacing: var(--td-column-gap, 25px) 0 !important; }
|
#board { border-collapse: separate !important; border-spacing: var(--td-column-gap, 25px) 0 !important; }
|
||||||
|
|||||||
Reference in New Issue
Block a user