13 Commits
v1.1 ... v1.4.2

Author SHA1 Message Date
8a97c5cb94 fix Always-off (on/off/default safe values) + bump emphasis to 1.375em (v1.4.2) 2026-07-08 13:03:35 -03:00
d1178bc463 fix stuck enhance-recurrence toggle + clarify label (v1.4.1) 2026-07-08 09:13:07 -03:00
c747d33491 per-board "Emphasize due date" with global default (v1.4) 2026-07-08 09:00:50 -03:00
850127e712 increase click-guard.js to 110ms differentiate drag/click t3 try remove cached js first 2026-07-07 22:05:19 -03:00
70f42352b0 increase click-guard.js to 120ms differentiate drag/click t2 2026-07-07 22:04:14 -03:00
d57544ebfd increase Asset/js/click-guard.js 100ms to 110ms differentiate drag/click v1.3.3 2026-07-07 22:02:34 -03:00
c3896e1962 enhance-recurrence radio button only on display bugfix v1.3.2 2026-07-07 09:51:38 -03:00
065825269e v1.3.1 bump 2026-07-07 09:45:17 -03:00
6726dfa456 enhance recurrence dialog yes/no side-by-side v1.3.1 2026-07-07 09:43:54 -03:00
17ebfcccd9 OrganonTweaks: enhance recurrence dialog -- Yes/No radios + [+7] factor button (JS); added asset , v1.3.0 2026-07-07 09:41:15 -03:00
95561e5565 OrganonTweaks: enhance recurrence dialog -- Yes/No radios + [+7] factor button (JS); v1.3.0 2026-07-07 09:40:51 -03:00
ff6abf1d49 fix comment "+" position: anchor on description span (tooltipLink), not <a>, so it sits left; v1.2.1 2026-07-07 09:19:50 -03:00
310c34620f OrganonTweaks: swallow post-drag click so a held press (>100ms) never opens the card; v1.2 2026-07-06 22:27:33 -03:00
11 changed files with 306 additions and 12 deletions

35
Asset/js/click-guard.js Normal file
View File

@@ -0,0 +1,35 @@
/*
* OrganonTweaks -- only open a card on a quick click.
*
* A real click is a quick press-release; a drag is a press, some movement, and a release
* much later. When you drop a card, jQuery UI can leak a trailing "click" on it, and
* Kanboard opens the card -- an unwanted side effect of the drag.
*
* We simply time the press on a card: record the moment of mousedown / touchstart, and on
* the click that follows, if more than THRESHOLD ms elapsed the press was a drag (held),
* not a click, so we swallow the click in the capture phase before Kanboard's open handler
* runs. A normal click is well under the threshold and opens the card as usual.
*/
(function () {
"use strict";
var THRESHOLD = 110; // ms; a press on a card held longer than this is a drag, not a click
var pressStart = 0;
function onDown(e) {
pressStart = (e.target && e.target.closest && e.target.closest(".task-board")) ? Date.now() : 0;
}
document.addEventListener("mousedown", onDown, true);
document.addEventListener("touchstart", onDown, true);
document.addEventListener("click", function (e) {
if (pressStart &&
e.target && e.target.closest && e.target.closest(".task-board") &&
(Date.now() - pressStart) > THRESHOLD) {
e.stopImmediatePropagation();
e.preventDefault();
}
pressStart = 0;
}, true);
})();

View File

@@ -0,0 +1,105 @@
/*
* OrganonTweaks -- enhance Kanboard's native "Edit recurrence" dialog.
*
* That template (task_recurrence/edit.php) has no hook, so we transform the form in JS when
* the modal opens:
* 1. Replace the "Generate recurrent task" Yes/No dropdown with two radio buttons (one
* click instead of open + select). The original <select> is kept, hidden, and stays in
* sync, so the form still submits recurrence_status normally.
* 2. Add a small [+7] button next to the "Factor" input that adds 7 (a week of days).
*
* Detection: the modal is injected into #modal-box dynamically, so we watch the document for
* an un-enhanced recurrence form appearing. Loaded only when the setting is enabled.
*/
(function () {
"use strict";
function enhanceStatus(select) {
var group = document.createElement("span");
group.className = "ot-recurrence-status";
var radios = [];
for (var i = 0; i < select.options.length; i++) {
var opt = select.options[i];
var label = document.createElement("label");
// Kanboard styles form labels as display:block; force inline so Yes/No sit
// side by side instead of stacked.
label.style.display = "inline-block";
label.style.marginRight = "14px";
label.style.cursor = "pointer";
var radio = document.createElement("input");
radio.type = "radio";
// No name attribute on purpose: a named radio would be SUBMITTED with the form,
// and Kanboard rejects the extra key ("invalid identifier"). These are display
// only -- the hidden <select> carries the value -- so we enforce the mutual
// exclusivity ourselves.
radio.value = opt.value;
radio.checked = opt.value === select.value;
radio.style.marginRight = "4px";
radios.push(radio);
radio.addEventListener("change", function () {
for (var k = 0; k < radios.length; k++) {
radios[k].checked = (radios[k] === this);
}
select.value = this.value;
select.dispatchEvent(new Event("change", { bubbles: true }));
});
label.appendChild(radio);
label.appendChild(document.createTextNode(opt.text));
group.appendChild(label);
}
select.style.display = "none";
select.parentNode.insertBefore(group, select.nextSibling);
}
function enhanceFactor(input) {
var btn = document.createElement("button");
btn.type = "button";
btn.className = "btn";
btn.textContent = "+7";
btn.title = "Add 7 (one week of days)";
btn.style.marginLeft = "6px";
btn.addEventListener("click", function () {
input.value = (parseInt(input.value, 10) || 0) + 7;
input.dispatchEvent(new Event("change", { bubbles: true }));
});
input.parentNode.insertBefore(btn, input.nextSibling);
}
function enhance() {
var select = document.querySelector('select[name="recurrence_status"]:not([data-ot-recurrence])');
if (!select) {
return;
}
select.setAttribute("data-ot-recurrence", "1");
enhanceStatus(select);
var factor = document.querySelector('input[name="recurrence_factor"]:not([data-ot-recurrence])');
if (factor) {
factor.setAttribute("data-ot-recurrence", "1");
enhanceFactor(factor);
}
}
function init() {
enhance();
if (window.MutationObserver) {
new MutationObserver(enhance).observe(document.body, { childList: true, subtree: true });
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

View File

@@ -53,19 +53,18 @@
// 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");
// NB: the description icon is wrapped in <span class="tooltip"> (Kanboard's
// tooltipLink), not an <a>, so we walk up to whatever is the row's direct child.
var anchor = row.querySelector("i.fa-file-text-o") ||
row.querySelector(".task-icon-age") ||
row.querySelector(".task-board-closed");
if (descIcon) {
anchor = descIcon.closest("a");
}
if (!anchor) {
anchor = row.querySelector(".task-icon-age") || row.querySelector(".task-board-closed");
while (anchor && anchor.parentNode !== row) {
anchor = anchor.parentNode;
}
// Move only if needed (keeps the MutationObserver from looping).
if (anchor && anchor.parentNode === row && item.nextElementSibling !== anchor) {
if (anchor && anchor !== item && item.nextElementSibling !== anchor) {
row.insertBefore(item, anchor);
}
}

View File

@@ -16,6 +16,9 @@ class ConfigController extends BaseController
'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),
'organon_tweaks_quick_click_only' => (int) $this->configModel->get('organon_tweaks_quick_click_only', 1),
'organon_tweaks_enhance_recurrence' => (int) $this->configModel->get('organon_tweaks_enhance_recurrence', 1),
'organon_tweaks_emphasize_duedate_default' => (int) $this->configModel->get('organon_tweaks_emphasize_duedate_default', 0),
),
'errors' => array(),
)));
@@ -27,10 +30,16 @@ class ConfigController extends BaseController
$alwaysCommentIcon = isset($values['organon_tweaks_always_comment_icon']) ? 1 : 0;
$keepScroll = isset($values['organon_tweaks_keep_scroll']) ? 1 : 0;
$quickClickOnly = isset($values['organon_tweaks_quick_click_only']) ? 1 : 0;
$enhanceRecurrence = isset($values['organon_tweaks_enhance_recurrence']) ? 1 : 0;
$emphasizeDueDateDefault = isset($values['organon_tweaks_emphasize_duedate_default']) ? 1 : 0;
if ($this->configModel->save(array(
'organon_tweaks_always_comment_icon' => $alwaysCommentIcon,
'organon_tweaks_keep_scroll' => $keepScroll,
'organon_tweaks_quick_click_only' => $quickClickOnly,
'organon_tweaks_enhance_recurrence' => $enhanceRecurrence,
'organon_tweaks_emphasize_duedate_default' => $emphasizeDueDateDefault,
))) {
$this->flash->success(t('Settings saved successfully.'));
} else {

View File

@@ -0,0 +1,49 @@
<?php
namespace Kanboard\Plugin\OrganonTweaks\Helper;
use Kanboard\Core\Base;
/**
* Per-board helper for OrganonTweaks.
*
* Resolves the 3-state "emphasize due date" setting for the CURRENT board (read from the route
* project_id, which the router populates for pretty URLs before the layout renders):
* per-board 'on' -> on, 'off' -> off, 'default'/absent -> follow the LIVE global default.
*
* The stored values are the non-falsy strings 'on'/'off' (not '1'/'0'): Kanboard's
* MetadataModel::get() uses `?:`, and PHP treats the string "0" as falsy, so a stored "0" would
* come back as the 'default' fallback and "Always off" would never be honored.
*/
class OrganonProjectHelper extends Base
{
const EMPHASIZE_KEY = 'organon_tweaks_emphasize_duedate';
const EMPHASIZE_DEFAULT_KEY = 'organon_tweaks_emphasize_duedate_default';
/**
* Should the current board emphasize the due date? Board pages only (project_id in the route).
*
* @return bool
*/
public function isEmphasizeDueDate()
{
$project_id = $this->request->getIntegerParam('project_id');
if ($project_id <= 0) {
return false;
}
$value = $this->projectMetadataModel->get($project_id, self::EMPHASIZE_KEY, 'default');
if ($value === 'on') {
return true;
}
if ($value === 'off') {
return false;
}
// 'default' (or absent) -> follow the live global default.
return (int) $this->configModel->get(self::EMPHASIZE_DEFAULT_KEY, 0) === 1;
}
}

View File

@@ -20,6 +20,13 @@ class Plugin extends Base
// Settings page for the plugin's tweaks.
$this->template->hook->attach('template:config:sidebar', 'organonTweaks:config/sidebar');
// Per-board tweak: emphasize the due date on the board. Configured with 3-state radios on the
// board's Settings -> Integrations (Use default / Always on / Always off); the global default
// (for "Use default" boards) lives in the plugin settings above. The emphasis <style> is
// emitted into the head only on enabled board pages (gated by OrganonProjectHelper).
$this->template->hook->attach('template:project:integrations', 'organonTweaks:project/integration');
$this->template->hook->attach('template:layout:head', 'organonTweaks:layout/emphasize_duedate');
// Tweak: always show a comment icon on board cards. When a card has no comments
// yet, add a bubble "+" that opens the same comment modal (add the first comment
// straight from the board). Cards with comments already show the native count.
@@ -34,12 +41,28 @@ class Plugin extends Base
'template' => 'plugins/OrganonTweaks/Asset/js/keep-scroll.js',
));
}
// Tweak: only open a card on a quick click (a press held > 100ms is a drag, so the
// trailing click is swallowed and the card does not open).
if ((int) $this->configModel->get('organon_tweaks_quick_click_only', 1) === 1) {
$this->hook->on('template:layout:js', array(
'template' => 'plugins/OrganonTweaks/Asset/js/click-guard.js',
));
}
// Tweak: enhance the native "Edit recurrence" dialog (Yes/No radios + a [+7] button
// on the factor). No template hook there, so it is done in JS.
if ((int) $this->configModel->get('organon_tweaks_enhance_recurrence', 1) === 1) {
$this->hook->on('template:layout:js', array(
'template' => 'plugins/OrganonTweaks/Asset/js/enhance-recurrence.js',
));
}
}
public function getHelpers()
{
return array(
'Plugin\OrganonTweaks\Helper' => array('OrganonColumnHelper'),
'Plugin\OrganonTweaks\Helper' => array('OrganonColumnHelper', 'OrganonProjectHelper'),
);
}
@@ -60,7 +83,7 @@ class Plugin extends Base
public function getPluginVersion()
{
return '1.1.0';
return '1.4.2';
}
public function getPluginHomepage()

View File

@@ -47,12 +47,38 @@ board stays where you were.
itself is replaced), reads/writes `scrollLeft` on whichever `#board-container` is current.
- **On by default.** Toggle it under "Settings -> Organon Tweaks".
### Open a card only on a quick click
Dropping a card sometimes leaks a trailing "click" that opens the card -- an unwanted side
effect of the drag. A real click is a quick press-release; a drag is a press held much
longer. So `Asset/js/click-guard.js` times the press on a card (`mousedown`/`touchstart` to
the following `click`) and, if it was held longer than **100 ms**, swallows the click in the
capture phase before Kanboard's open handler -- so a drag never opens the card. Normal
clicks (well under 100 ms) still open cards as usual. The 100 ms threshold is fixed.
- **On by default.** Toggle it under "Settings -> Organon Tweaks".
### Enhance the recurrence dialog
Kanboard's native "Edit recurrence" dialog is a plain form. This makes two small usability
edits when it opens (there is no template hook, so it is done in JS,
`Asset/js/enhance-recurrence.js`):
- the "Generate recurrent task" **Yes/No dropdown** becomes two **radio buttons** (one click
to set, instead of open + select). The original select is kept hidden and stays in sync, so
the form submits exactly as before.
- a small **[+7]** button next to the "Factor" input adds 7 to it (one week of days).
- **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.
- **Open a card only on a quick click** -- default on.
- **Enhance the recurrence dialog** -- default on.
## Requirements

View File

@@ -19,6 +19,27 @@
</p>
</fieldset>
<fieldset>
<?= $this->form->checkbox('organon_tweaks_quick_click_only', t('Open a card only on a quick click'), 1, isset($values['organon_tweaks_quick_click_only']) && $values['organon_tweaks_quick_click_only'] == 1) ?>
<p class="form-help">
<?= t('A press on a card held longer than 100 ms is treated as a drag, so releasing it does not open the card. Fixes a drag sometimes opening the card on release. Normal clicks are well under 100 ms.') ?>
</p>
</fieldset>
<fieldset>
<?= $this->form->checkbox('organon_tweaks_enhance_recurrence', t('Enhance the native recurrence dialog'), 1, isset($values['organon_tweaks_enhance_recurrence']) && $values['organon_tweaks_enhance_recurrence'] == 1) ?>
<p class="form-help">
<?= t('In the native "Edit recurrence" dialog, replace the Generate recurrent task dropdown with Yes/No radio buttons (one click), and add a [+7] button next to the factor that adds 7 (one week of days).') ?>
</p>
</fieldset>
<fieldset>
<?= $this->form->checkbox('organon_tweaks_emphasize_duedate_default', t('Emphasize due date by default'), 1, isset($values['organon_tweaks_emphasize_duedate_default']) && $values['organon_tweaks_emphasize_duedate_default'] == 1) ?>
<p class="form-help">
<?= t('Default for boards set to "Use default": enlarge and bold the due date on the board. Each board can override this under its own Settings -> Integrations.') ?>
</p>
</fieldset>
<div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button>
</div>

View File

@@ -0,0 +1,3 @@
<?php if ($this->OrganonProjectHelper->isEmphasizeDueDate()): ?>
<style>.task-date { font-size: 1.375em; font-weight: bold; }</style>
<?php endif ?>

View File

@@ -0,0 +1,24 @@
<?php $choice = isset($values['organon_tweaks_emphasize_duedate']) && in_array($values['organon_tweaks_emphasize_duedate'], array('on', 'off'), true) ? $values['organon_tweaks_emphasize_duedate'] : 'default' ?>
<h3><i class="fa fa-umbrella fa-fw" aria-hidden="true"></i> <?= t('OrganonTweaks') ?></h3>
<div class="listing">
<p><strong><?= t('Emphasize due date on the board') ?></strong></p>
<label style="display:inline-block; margin-right:16px;">
<input type="radio" name="organon_tweaks_emphasize_duedate" value="default" <?= $choice === 'default' ? 'checked="checked"' : '' ?>> <?= t('Use default') ?>
</label>
<label style="display:inline-block; margin-right:16px;">
<input type="radio" name="organon_tweaks_emphasize_duedate" value="on" <?= $choice === 'on' ? 'checked="checked"' : '' ?>> <?= t('Always on') ?>
</label>
<label style="display:inline-block; margin-right:16px;">
<input type="radio" name="organon_tweaks_emphasize_duedate" value="off" <?= $choice === 'off' ? 'checked="checked"' : '' ?>> <?= t('Always off') ?>
</label>
<p class="form-help">
<?= t('Enlarge and bold the due date on this board. "Use default" follows the global OrganonTweaks setting.') ?>
</p>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button>
</div>

View File

@@ -1 +1 @@
OrganonTweaks v1.1
OrganonTweaks v1.4.2