8 Commits

Author SHA1 Message Date
924e287aee drag for mobile - handle sizeable 2026-07-01 10:30:07 -03:00
4e48e33b26 VERSION v0.2 2026-07-01 09:53:26 -03:00
57bab4650b Templates php 2026-07-01 09:51:41 -03:00
99fa73433a Controller/ConfigController.php added 2026-07-01 09:51:25 -03:00
c6f7ee297b assets added css js 2026-07-01 09:51:15 -03:00
29ed19b4cb plugin main php file 2026-07-01 09:50:40 -03:00
2f739c8b75 README explains 2026-07-01 09:50:16 -03:00
99ff483e44 cleaning skeleton 2026-07-01 09:50:06 -03:00
17 changed files with 294 additions and 67 deletions

View File

@@ -1 +0,0 @@
/* Skeleton plugin styles -- add CSS here. Loaded via template:layout:css. */

40
Asset/css/tweak-drag.css Normal file
View File

@@ -0,0 +1,40 @@
/*
* TweakDrag -- board drag/touch and column-gap tweaks.
*
* The wider column gap is applied conditionally from Template/layout/variable.php (only
* when the "Widen the gap between columns" setting is on), using border-spacing with the
* --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
* "Drag the board background to scroll" setting is enabled). The .td-drag-scroll class is
* set on #board-container by the script, so these rules are inert when disabled. A grab
* cursor advertises the empty background; cards keep the normal cursor since pressing them
* does a card drag, not a pan.
*/
#board-container.td-drag-scroll {
cursor: grab;
}
#board-container.td-drag-scroll .task-board {
cursor: default;
}
.td-grabbing,
.td-grabbing * {
cursor: grabbing !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);
}

75
Asset/js/drag-scroll.js Normal file
View File

@@ -0,0 +1,75 @@
/*
* TweakDrag -- optional click-and-drag horizontal scrolling of the board.
*
* Like the Trello board canvas: press the empty board background and drag left/right
* to scroll #board-container horizontally. The pan only starts on empty background --
* 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.
*
* Loaded only when the "Drag the board background to scroll" setting is enabled.
*/
(function () {
"use strict";
// Pressing any of these (or their descendants) must NOT start a background pan.
var IGNORE = "a, button, input, textarea, select, label, " +
".task-board, [data-task-id], .task-board-sort-handle, " +
".draggable-row-handle, .dropdown, .ui-sortable-handle";
function isIgnored(el) {
return !!(el && el.closest && el.closest(IGNORE));
}
function init() {
var container = document.getElementById("board-container");
if (!container || container.getAttribute("data-td-drag") === "1") {
return;
}
container.setAttribute("data-td-drag", "1");
container.classList.add("td-drag-scroll");
var dragging = false;
var startX = 0;
var startScrollLeft = 0;
container.addEventListener("mousedown", function (e) {
// Left button only, and only on empty board background.
if (e.button !== 0 || isIgnored(e.target)) {
return;
}
dragging = true;
startX = e.clientX;
startScrollLeft = container.scrollLeft;
container.classList.add("td-grabbing");
e.preventDefault();
});
document.addEventListener("mousemove", function (e) {
if (!dragging) {
return;
}
container.scrollLeft = startScrollLeft - (e.clientX - startX);
e.preventDefault();
});
function stop() {
if (!dragging) {
return;
}
dragging = false;
container.classList.remove("td-grabbing");
}
document.addEventListener("mouseup", stop);
document.addEventListener("mouseleave", stop);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

View File

@@ -1 +0,0 @@
// Skeleton plugin script -- add JS here. Loaded via template:layout:js.

View File

View File

@@ -0,0 +1,65 @@
<?php
namespace Kanboard\Plugin\TweakDrag\Controller;
use Kanboard\Controller\BaseController;
/**
* Settings page for the TweakDrag plugin: board drag/touch and column-gap options.
*/
class ConfigController extends BaseController
{
const DEFAULT_GAP_SIZE = 25;
const MIN_GAP_SIZE = 0;
const MAX_GAP_SIZE = 200;
const DEFAULT_HANDLE_SIZE = 20;
const MIN_HANDLE_SIZE = 8;
const MAX_HANDLE_SIZE = 200;
public function show()
{
$gapSize = (int) $this->configModel->get('tweakdrag_column_gap_size', self::DEFAULT_GAP_SIZE);
$columnGap = (int) $this->configModel->get('tweakdrag_column_gap', 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(
'title' => t('Settings').' &gt; '.t('Tweak Drag'),
'values' => array(
'tweakdrag_column_gap_size' => $gapSize,
'tweakdrag_column_gap' => $columnGap,
'tweakdrag_drag_scroll' => $dragScroll,
'tweakdrag_handle_size' => $handleSize,
),
'errors' => array(),
)));
}
public function save()
{
$values = $this->request->getValues();
$gapSize = isset($values['tweakdrag_column_gap_size']) ? (int) $values['tweakdrag_column_gap_size'] : self::DEFAULT_GAP_SIZE;
$gapSize = max(self::MIN_GAP_SIZE, min(self::MAX_GAP_SIZE, $gapSize));
$columnGap = isset($values['tweakdrag_column_gap']) ? 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(
'tweakdrag_column_gap_size' => $gapSize,
'tweakdrag_column_gap' => $columnGap,
'tweakdrag_drag_scroll' => $dragScroll,
'tweakdrag_handle_size' => $handleSize,
))) {
$this->flash->success(t('Settings saved successfully.'));
} else {
$this->flash->failure(t('Unable to save your settings.'));
}
$this->response->redirect($this->helper->url->to('ConfigController', 'show', array('plugin' => 'TweakDrag')));
}
}

View File

View File

View File

@@ -1,6 +1,6 @@
<?php
namespace Kanboard\Plugin\Skeleton;
namespace Kanboard\Plugin\TweakDrag;
use Kanboard\Core\Plugin\Base;
@@ -8,28 +8,33 @@ class Plugin extends Base
{
public function initialize()
{
// 1. Render a visible word at the top of every page (the demo output).
$this->template->hook->attach('template:layout:top', 'skeleton:layout/header');
// 2. Load the plugin stylesheet (currently empty -- proves the CSS hook fires).
// Load the plugin stylesheet (drag cursor styling; inert until enabled).
$this->hook->on('template:layout:css', array(
'template' => 'plugins/Skeleton/Asset/css/skeleton.css',
'template' => 'plugins/TweakDrag/Asset/css/tweak-drag.css',
));
// 3. Load the plugin script (currently empty -- proves the JS hook fires).
$this->hook->on('template:layout:js', array(
'template' => 'plugins/Skeleton/Asset/js/skeleton.js',
));
// Inject the column-gap CSS variable and, when enabled, the gap rule.
$this->template->hook->attach('template:layout:head', 'tweakDrag:layout/variable');
// Add a "Tweak Drag" entry to the Settings sidebar.
$this->template->hook->attach('template:config:sidebar', 'tweakDrag:config/sidebar');
// Optional: click-and-drag the board background to scroll horizontally.
if ((int) $this->configModel->get('tweakdrag_drag_scroll', 1) === 1) {
$this->hook->on('template:layout:js', array(
'template' => 'plugins/TweakDrag/Asset/js/drag-scroll.js',
));
}
}
public function getPluginName()
{
return 'Skeleton';
return 'TweakDrag';
}
public function getPluginDescription()
{
return t('Reusable skeleton/template for building Kanboard plugins.');
return t('Board drag, touch and column-gap tweaks (drag-to-scroll, wider column gap).');
}
public function getPluginAuthor()
@@ -39,12 +44,12 @@ class Plugin extends Base
public function getPluginVersion()
{
return '0.1.0';
return '0.3.0';
}
public function getPluginHomepage()
{
return 'https://code.beco.cc/beco/kanboard-plugin-skeleton';
return 'https://code.beco.cc/beco/TweakDrag';
}
public function getCompatibleVersion()

View File

@@ -1,19 +1,35 @@
# Skeleton -- a Kanboard plugin template
# TweakDrag -- board drag, touch and column-gap tweaks
A minimal, working Kanboard plugin that you copy and rename as the starting point for a
real plugin. By itself it does only one trivial thing: it renders the word "Skeleton" at
the top of every page. It changes no data and runs no database migration.
A Kanboard plugin that groups board dragging and spacing tweaks, configurable under
"Settings -> Tweak Drag":
## What it demonstrates
- **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.
- **Widen the gap between columns** -- spread the columns further apart, easier to tell
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.
- A complete `Plugin.php` registration class with all the metadata Kanboard shows in
Settings -> Plugins (name, description, author, version, homepage, compatible version).
- A template hook (`template:layout:top`) that injects a template into the page.
- Asset hooks (`template:layout:css` and `template:layout:js`) that load a stylesheet and
a script. They are empty for now but prove the injection path works -- handy when a real
plugin needs custom CSS or JS.
- The standard plugin directory layout, with stub folders (`Controller/`, `Model/`,
`Schema/`, `Locale/`, `Test/`) ready to grow into.
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
movement threshold so ending a drag does not open the task) without bloating that plugin.
## How it works
- **Drag to scroll**: a small script (`Asset/js/drag-scroll.js`, loaded only when the
option is on) starts a horizontal pan on `#board-container` only when the press lands on
empty background -- pressing a task card, a drag handle or any control is ignored, so
Kanboard's own card and column drag-and-drop keep working unchanged.
- **Column gap**: the board is a table, so the gap is set with `border-spacing` (injected
in the page head from `Template/layout/variable.php` only when the option is on), sized
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
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.
## Requirements
@@ -21,45 +37,21 @@ the top of every page. It changes no data and runs no database migration.
## Installation
No build step and no dependencies.
Copy this folder into your Kanboard installation as `plugins/TweakDrag/`. The directory
name must be exactly `TweakDrag` (Kanboard derives the plugin namespace from the folder
name). No build step and no database migration.
1. Copy this folder into your Kanboard installation as `plugins/Skeleton/`.
2. Reload any page. The word "Skeleton" appears at the top.
3. Confirm it under Settings -> Plugins.
## Settings
To uninstall, delete `plugins/Skeleton/`. Nothing else is left behind.
These settings are global (per Kanboard instance) and admin-only. Go to
"Settings -> Tweak Drag":
## Directory layout
```
Skeleton/
Plugin.php Registration and hook wiring (the only required file).
README.md
LICENSE AGPL-3.0.
Template/
layout/header.php The visible "Skeleton" word.
Asset/
css/skeleton.css Loaded via template:layout:css.
js/skeleton.js Loaded via template:layout:js.
Controller/ Stub for future request handlers.
Model/ Stub for future business logic / DB access.
Schema/ Stub for future database migrations.
Locale/ Stub for future translations (e.g. pt_BR/, fr_FR/).
Test/ Stub for future unit tests.
```
## How to fork this into a new plugin
1. Copy the folder and rename it, e.g. `plugins/MyPlugin/`. The folder name must match the
namespace and start with a capital letter.
2. In `Plugin.php`, change the namespace from `Kanboard\Plugin\Skeleton` to
`Kanboard\Plugin\MyPlugin`.
3. Update the metadata methods (`getPluginName`, `getPluginDescription`, `getPluginAuthor`,
`getPluginVersion`, `getPluginHomepage`, `getCompatibleVersion`).
4. Update the hook target paths: the lowercase prefix in `'skeleton:layout/header'` and the
`plugins/Skeleton/Asset/...` asset paths must match the new plugin name.
5. Replace `Template/layout/header.php` with your real template, or attach to a different
hook. See the Kanboard plugin hooks documentation for the full list of hook points.
- **Column gap in pixels** -- width of the column gap (`--td-column-gap`). Default: 25.
- **Widen the gap between columns** -- toggles the wider gap. Default: on.
- **Drag the board background to scroll horizontally** -- toggles the Trello-style
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

View File

41
Template/config/show.php Normal file
View File

@@ -0,0 +1,41 @@
<div class="page-header">
<h2><?= t('Tweak Drag') ?></h2>
</div>
<form method="post" action="<?= $this->url->href('ConfigController', 'save', array('plugin' => 'TweakDrag')) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<fieldset>
<?= $this->form->label(t('Column gap in pixels'), 'tweakdrag_column_gap_size') ?>
<?= $this->form->number('tweakdrag_column_gap_size', $values, $errors) ?>
<p class="form-help">
<?= t('Width of the gap between board columns when the option below is on. Default: 25.') ?>
</p>
</fieldset>
<fieldset>
<?= $this->form->checkbox('tweakdrag_column_gap', t('Widen the gap between columns'), 1, isset($values['tweakdrag_column_gap']) && $values['tweakdrag_column_gap'] == 1) ?>
<p class="form-help">
<?= t('Spreads the board columns further apart (by the pixel value above), easier to tell apart and to touch. Also adds some spacing at the left and right edges of the board.') ?>
</p>
</fieldset>
<fieldset>
<?= $this->form->checkbox('tweakdrag_drag_scroll', t('Drag the board background to scroll horizontally'), 1, isset($values['tweakdrag_drag_scroll']) && $values['tweakdrag_drag_scroll'] == 1) ?>
<p class="form-help">
<?= t('Like Trello: click and drag on the empty board background to scroll the columns sideways. Dragging a task card or a handle still works as usual.') ?>
</p>
</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">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button>
</div>
</form>

View File

@@ -0,0 +1,3 @@
<li <?= $this->app->checkMenuSelection('ConfigController', 'show', 'TweakDrag') ?>>
<?= $this->url->link(t('Tweak Drag'), 'ConfigController', 'show', array('plugin' => 'TweakDrag')) ?>
</li>

View File

@@ -1 +0,0 @@
<div class="skeleton-plugin-marker">Skeleton</div>

View File

@@ -0,0 +1,9 @@
<style type="text/css">
:root {
--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): ?>
#board { border-collapse: separate !important; border-spacing: var(--td-column-gap, 25px) 0 !important; }
<?php endif ?>
</style>

View File

View File

@@ -1 +1 @@
TweakDrag v0.1
TweakDrag v0.3