Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f78aa2087 | |||
| 8057272b9c | |||
| dc22739647 | |||
| 2d3c716852 | |||
| 269964d4cc | |||
| ac8e3b9544 | |||
| 717095848b | |||
| a905079abc | |||
| e032d5d2b4 | |||
| b63d366296 | |||
| 89888235d1 | |||
| 4ebebd5ffb | |||
| 9b637160b2 | |||
| e85d5d71f9 | |||
| ce3205646f | |||
| 4ce18d9e5f |
@@ -13,7 +13,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var THRESHOLD = 110; // ms; a press on a card held longer than this is a drag, not a click
|
var THRESHOLD = 120; // ms; a press on a card held longer than this is a drag, not a click
|
||||||
var pressStart = 0;
|
var pressStart = 0;
|
||||||
|
|
||||||
function onDown(e) {
|
function onDown(e) {
|
||||||
|
|||||||
42
Asset/js/shortcut-labels.js
Normal file
42
Asset/js/shortcut-labels.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* OrganonTweaks -- show the keyboard shortcuts Kanboard already binds, next to the task Actions
|
||||||
|
* menu items. Display only: this adds NO key bindings, it just surfaces the existing ones (listed
|
||||||
|
* in Kanboard's own "Keyboard shortcuts" help). Items are matched by their FontAwesome icon class,
|
||||||
|
* so it is independent of the URL format (pretty vs query-string) and the interface language.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var HINTS = [
|
||||||
|
{ icon: "fa-edit", key: "e" }, // Edit the task
|
||||||
|
{ icon: "fa-plus", key: "s" }, // Add a sub-task
|
||||||
|
{ icon: "fa-comment-o", key: "c" }, // Add a comment
|
||||||
|
{ icon: "fa-code-fork", key: "l" } // Add internal link
|
||||||
|
];
|
||||||
|
|
||||||
|
function annotate() {
|
||||||
|
var sidebar = document.querySelector(".sidebar");
|
||||||
|
if (!sidebar) { return; }
|
||||||
|
|
||||||
|
HINTS.forEach(function (hint) {
|
||||||
|
var icon = sidebar.querySelector("li a i." + hint.icon);
|
||||||
|
if (!icon) { return; }
|
||||||
|
|
||||||
|
var link = icon.closest("a");
|
||||||
|
if (!link || link.querySelector(".organon-kbd")) { return; }
|
||||||
|
|
||||||
|
var kbd = document.createElement("span");
|
||||||
|
kbd.className = "organon-kbd";
|
||||||
|
kbd.textContent = hint.key;
|
||||||
|
kbd.style.cssText = "margin-left:8px;padding:0 5px;border:1px solid #ccc;" +
|
||||||
|
"border-radius:3px;font-family:monospace;font-size:0.85em;color:#777;background:#f7f7f7;";
|
||||||
|
link.appendChild(kbd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState !== "loading") {
|
||||||
|
annotate();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", annotate);
|
||||||
|
}
|
||||||
|
})();
|
||||||
67
Asset/js/title-click-edit.js
Normal file
67
Asset/js/title-click-edit.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* OrganonTweaks -- clicking a task title opens the edit modal instead of the read-only card view,
|
||||||
|
* reusing Kanboard's own modal opener (KB.modal.open).
|
||||||
|
* - Board: the title is a link, and a hidden marker rendered next to it (only when the user may
|
||||||
|
* edit) carries the server-generated edit URL.
|
||||||
|
* - Card view: the title (#task-summary > h2) opens the same modal via #task-view's data-edit-url,
|
||||||
|
* but only when the user may edit -- detected by the presence of the sidebar "Edit" action.
|
||||||
|
*
|
||||||
|
* A capture-phase delegated click runs before Kanboard's own handlers, survives the board's AJAX
|
||||||
|
* re-renders, and stops both the link navigation and the card's own click.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function openEdit(url) {
|
||||||
|
if (url && window.KB && KB.modal) {
|
||||||
|
KB.modal.open(url, "large", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sidebar "Edit the task" action (fa-edit) only renders when the current user may edit.
|
||||||
|
function canEdit() {
|
||||||
|
return !!document.querySelector(".sidebar a i.fa-edit");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("click", function (e) {
|
||||||
|
if (!e.target || !e.target.closest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Board card title -> open the edit modal via the per-card marker URL.
|
||||||
|
var link = e.target.closest(".task-board-title a");
|
||||||
|
if (link) {
|
||||||
|
var card = link.closest(".task-board");
|
||||||
|
var marker = card ? card.querySelector(".organon-edit-url") : null;
|
||||||
|
if (marker) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
openEdit(marker.getAttribute("data-url"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card-view title -> open the same edit modal (#task-view carries the edit URL).
|
||||||
|
var title = e.target.closest("#task-summary > h2");
|
||||||
|
if (title && canEdit()) {
|
||||||
|
var taskView = document.querySelector("#task-view");
|
||||||
|
if (taskView && taskView.getAttribute("data-edit-url")) {
|
||||||
|
openEdit(taskView.getAttribute("data-edit-url"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// Make the card-view title look clickable (the board title is already a link).
|
||||||
|
function markTitle() {
|
||||||
|
var title = document.querySelector("#task-summary > h2");
|
||||||
|
if (title && canEdit()) {
|
||||||
|
title.style.cursor = "pointer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState !== "loading") {
|
||||||
|
markTitle();
|
||||||
|
} else {
|
||||||
|
document.addEventListener("DOMContentLoaded", markTitle);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -21,6 +21,9 @@ class ConfigController extends BaseController
|
|||||||
'organon_tweaks_emphasize_duedate_default' => (int) $this->configModel->get('organon_tweaks_emphasize_duedate_default', 0),
|
'organon_tweaks_emphasize_duedate_default' => (int) $this->configModel->get('organon_tweaks_emphasize_duedate_default', 0),
|
||||||
'organon_tweaks_show_all_filter' => (int) $this->configModel->get('organon_tweaks_show_all_filter', 1),
|
'organon_tweaks_show_all_filter' => (int) $this->configModel->get('organon_tweaks_show_all_filter', 1),
|
||||||
'organon_tweaks_month_filters' => (int) $this->configModel->get('organon_tweaks_month_filters', 1),
|
'organon_tweaks_month_filters' => (int) $this->configModel->get('organon_tweaks_month_filters', 1),
|
||||||
|
'organon_tweaks_recurring_filters' => (int) $this->configModel->get('organon_tweaks_recurring_filters', 1),
|
||||||
|
'organon_tweaks_shortcut_labels' => (int) $this->configModel->get('organon_tweaks_shortcut_labels', 1),
|
||||||
|
'organon_tweaks_title_click_edit' => (int) $this->configModel->get('organon_tweaks_title_click_edit', 1),
|
||||||
),
|
),
|
||||||
'errors' => array(),
|
'errors' => array(),
|
||||||
)));
|
)));
|
||||||
@@ -37,6 +40,9 @@ class ConfigController extends BaseController
|
|||||||
$emphasizeDueDateDefault = isset($values['organon_tweaks_emphasize_duedate_default']) ? 1 : 0;
|
$emphasizeDueDateDefault = isset($values['organon_tweaks_emphasize_duedate_default']) ? 1 : 0;
|
||||||
$showAllFilter = isset($values['organon_tweaks_show_all_filter']) ? 1 : 0;
|
$showAllFilter = isset($values['organon_tweaks_show_all_filter']) ? 1 : 0;
|
||||||
$monthFilters = isset($values['organon_tweaks_month_filters']) ? 1 : 0;
|
$monthFilters = isset($values['organon_tweaks_month_filters']) ? 1 : 0;
|
||||||
|
$recurringFilters = isset($values['organon_tweaks_recurring_filters']) ? 1 : 0;
|
||||||
|
$shortcutLabels = isset($values['organon_tweaks_shortcut_labels']) ? 1 : 0;
|
||||||
|
$titleClickEdit = isset($values['organon_tweaks_title_click_edit']) ? 1 : 0;
|
||||||
|
|
||||||
if ($this->configModel->save(array(
|
if ($this->configModel->save(array(
|
||||||
'organon_tweaks_always_comment_icon' => $alwaysCommentIcon,
|
'organon_tweaks_always_comment_icon' => $alwaysCommentIcon,
|
||||||
@@ -46,6 +52,9 @@ class ConfigController extends BaseController
|
|||||||
'organon_tweaks_emphasize_duedate_default' => $emphasizeDueDateDefault,
|
'organon_tweaks_emphasize_duedate_default' => $emphasizeDueDateDefault,
|
||||||
'organon_tweaks_show_all_filter' => $showAllFilter,
|
'organon_tweaks_show_all_filter' => $showAllFilter,
|
||||||
'organon_tweaks_month_filters' => $monthFilters,
|
'organon_tweaks_month_filters' => $monthFilters,
|
||||||
|
'organon_tweaks_recurring_filters' => $recurringFilters,
|
||||||
|
'organon_tweaks_shortcut_labels' => $shortcutLabels,
|
||||||
|
'organon_tweaks_title_click_edit' => $titleClickEdit,
|
||||||
))) {
|
))) {
|
||||||
$this->flash->success(t('Settings saved successfully.'));
|
$this->flash->success(t('Settings saved successfully.'));
|
||||||
} else {
|
} else {
|
||||||
@@ -68,6 +77,12 @@ class ConfigController extends BaseController
|
|||||||
$projectHelper->removeGroup($projectHelper::GROUP_MONTH);
|
$projectHelper->removeGroup($projectHelper::GROUP_MONTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($recurringFilters === 1) {
|
||||||
|
$projectHelper->addGroupToAllProjects($projectHelper::GROUP_RECURRING);
|
||||||
|
} else {
|
||||||
|
$projectHelper->removeGroup($projectHelper::GROUP_RECURRING);
|
||||||
|
}
|
||||||
|
|
||||||
$this->response->redirect($this->helper->url->to('ConfigController', 'show', array('plugin' => 'OrganonTweaks')));
|
$this->response->redirect($this->helper->url->to('ConfigController', 'show', array('plugin' => 'OrganonTweaks')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
Filter/TaskRecurringFilter.php
Normal file
100
Filter/TaskRecurringFilter.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Kanboard\Plugin\OrganonTweaks\Filter;
|
||||||
|
|
||||||
|
use Kanboard\Core\Filter\FilterInterface;
|
||||||
|
use Kanboard\Filter\BaseFilter;
|
||||||
|
use Kanboard\Model\TaskModel;
|
||||||
|
use PicoDb\Database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter tasks by recurrence: `recurring:XY`, one attribute with one composite value so it can
|
||||||
|
* INTERSECT the two axes (stacking two values of the same attribute would only OR them).
|
||||||
|
*
|
||||||
|
* X = system : evt (event-triggered = Kanboard native) | cal (calendar = RecoReco) | any
|
||||||
|
* Y = role : ori (original/template) | dup (duplicate/copy) | all
|
||||||
|
*
|
||||||
|
* Conditions are authoritative -- taken straight from how core classifies these cards:
|
||||||
|
* - evtori = recurrence_status PENDING, evtdup = recurrence_status PROCESSED. This is exactly how
|
||||||
|
* core colors the board refresh icon (app/Template/board/task_footer.php: normal icon = PENDING,
|
||||||
|
* fa-inverse = PROCESSED), so the filter matches what the board shows.
|
||||||
|
* - calori = RecoReco metadata recoreco_enabled=1, caldup = recoreco_clone=1.
|
||||||
|
* - *all / any* = the OR-unions of the above.
|
||||||
|
*
|
||||||
|
* Cross-plugin: the calendar side reads RecoReco metadata keys. If RecoReco is not installed the
|
||||||
|
* metadata sets are empty, so `cal*` matches nothing and `evt*` still works (event-only degrade).
|
||||||
|
*
|
||||||
|
* One attribute + one value -> single-value path, so the predicates below are grouped in a scoped
|
||||||
|
* beginOr/closeOr and the whole group still ANDs with any other search attribute.
|
||||||
|
*/
|
||||||
|
class TaskRecurringFilter extends BaseFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function setDatabase(Database $db)
|
||||||
|
{
|
||||||
|
$this->db = $db;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttributes()
|
||||||
|
{
|
||||||
|
return array('recurring');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply()
|
||||||
|
{
|
||||||
|
$value = strtolower(trim((string) $this->value));
|
||||||
|
$system = substr($value, 0, 3); // evt | cal | any
|
||||||
|
$role = substr($value, 3, 3); // ori | dup | all
|
||||||
|
|
||||||
|
if (! in_array($system, array('evt', 'cal', 'any'), true) ||
|
||||||
|
! in_array($role, array('ori', 'dup', 'all'), true)) {
|
||||||
|
return $this; // unknown/typo value -> no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
$wantEvt = $system === 'evt' || $system === 'any';
|
||||||
|
$wantCal = $system === 'cal' || $system === 'any';
|
||||||
|
$wantOri = $role === 'ori' || $role === 'all';
|
||||||
|
$wantDup = $role === 'dup' || $role === 'all';
|
||||||
|
|
||||||
|
$this->query->beginOr();
|
||||||
|
|
||||||
|
if ($wantEvt && $wantOri) {
|
||||||
|
$this->query->eq(TaskModel::TABLE.'.recurrence_status', TaskModel::RECURRING_STATUS_PENDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wantEvt && $wantDup) {
|
||||||
|
$this->query->eq(TaskModel::TABLE.'.recurrence_status', TaskModel::RECURRING_STATUS_PROCESSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wantCal && $wantOri) {
|
||||||
|
$this->query->in(TaskModel::TABLE.'.id', $this->metaIds('recoreco_enabled'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($wantCal && $wantDup) {
|
||||||
|
$this->query->in(TaskModel::TABLE.'.id', $this->metaIds('recoreco_clone'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->query->closeOr();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task ids carrying a RecoReco metadata flag set to 1. Returns a non-matching sentinel when the
|
||||||
|
* set is empty so the IN clause matches nothing (RecoReco absent -> event-only degrade).
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function metaIds($name)
|
||||||
|
{
|
||||||
|
$ids = $this->db->table('task_has_metadata')
|
||||||
|
->eq('name', $name)
|
||||||
|
->eq('value', '1')
|
||||||
|
->findAllByColumn('task_id');
|
||||||
|
|
||||||
|
return ! empty($ids) ? $ids : array(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,17 +27,27 @@ class OrganonProjectHelper extends Base
|
|||||||
// Config toggles for the auto-managed custom-filter groups.
|
// Config toggles for the auto-managed custom-filter groups.
|
||||||
const SHOW_ALL_FILTER_KEY = 'organon_tweaks_show_all_filter';
|
const SHOW_ALL_FILTER_KEY = 'organon_tweaks_show_all_filter';
|
||||||
const MONTH_FILTERS_KEY = 'organon_tweaks_month_filters';
|
const MONTH_FILTERS_KEY = 'organon_tweaks_month_filters';
|
||||||
|
const RECURRING_FILTERS_KEY = 'organon_tweaks_recurring_filters';
|
||||||
|
|
||||||
// Reserved custom-filter groups: each row is [name, query, append]. The NAME is reserved by the
|
// Reserved custom-filter groups: each row is [name, query, append]. The NAME is reserved by the
|
||||||
// plugin (fixed, non-translated strings so dedup is stable across locales); a user's own filter
|
// plugin (fixed, non-translated strings so dedup is stable across locales); a user's own filter
|
||||||
// with a reserved name is absorbed into the canonical form. "Show all tasks" is a REPLACE filter
|
// with a reserved name is absorbed into the canonical form. "Show all tasks" is a REPLACE filter
|
||||||
// (append 0) so it clears; the rest are APPEND (1) so clicks stack (distinct attributes AND).
|
// (append 0) so it clears; the rest are APPEND (1) so clicks stack (distinct attributes AND).
|
||||||
|
// Names are prefixed by group so the dropdown -- which core sorts alphabetically by name
|
||||||
|
// (CustomFilterModel::getAll -> asc(name)) -- clusters them: Board < Month < Recurring.
|
||||||
const GROUP_SHOW_ALL = array(
|
const GROUP_SHOW_ALL = array(
|
||||||
array('Show all tasks', '', 0),
|
array('Board: show all tasks', '', 0),
|
||||||
);
|
);
|
||||||
const GROUP_MONTH = array(
|
const GROUP_MONTH = array(
|
||||||
array('Due this month', 'duemonth:this', 1),
|
array('Month: due this month', 'duemonth:this', 1),
|
||||||
array('Around this month', 'aroundmonth:this', 1),
|
array('Month: around this month', 'aroundmonth:this', 1),
|
||||||
|
);
|
||||||
|
const GROUP_RECURRING = array(
|
||||||
|
array('Recurring: all', 'recurring:anyall', 1),
|
||||||
|
array('Recurring: event', 'recurring:evtall', 1),
|
||||||
|
array('Recurring: calendar', 'recurring:calall', 1),
|
||||||
|
array('Recurring: templates', 'recurring:anyori', 1),
|
||||||
|
array('Recurring: duplicates', 'recurring:anydup', 1),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,6 +145,10 @@ class OrganonProjectHelper extends Base
|
|||||||
if ((int) $this->configModel->get(self::MONTH_FILTERS_KEY, 1) === 1) {
|
if ((int) $this->configModel->get(self::MONTH_FILTERS_KEY, 1) === 1) {
|
||||||
$this->ensureGroup($project_id, self::GROUP_MONTH);
|
$this->ensureGroup($project_id, self::GROUP_MONTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((int) $this->configModel->get(self::RECURRING_FILTERS_KEY, 1) === 1) {
|
||||||
|
$this->ensureGroup($project_id, self::GROUP_RECURRING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ensureGroup($project_id, array $rows)
|
private function ensureGroup($project_id, array $rows)
|
||||||
|
|||||||
42
Plugin.php
42
Plugin.php
@@ -58,26 +58,48 @@ class Plugin extends Base
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-managed shared custom filters (v1.5 "Show all tasks", v1.6 month filters). Ensured
|
// Tweak: show the keyboard shortcuts Kanboard already binds next to the task Actions menu
|
||||||
// lazily whenever a project header renders (covers old + new boards); each group is gated by
|
// items (display only -- no new bindings). Done in JS since those menu <li> have no hook.
|
||||||
// its own config toggle inside OrganonProjectHelper::ensureBoardFilters(). The toggles also
|
if ((int) $this->configModel->get('organon_tweaks_shortcut_labels', 1) === 1) {
|
||||||
// bulk add/remove the filters (ConfigController).
|
$this->hook->on('template:layout:js', array(
|
||||||
|
'template' => 'plugins/OrganonTweaks/Asset/js/shortcut-labels.js',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tweak: clicking a task title opens the edit modal (instead of the read-only card view).
|
||||||
|
// JS reuses Kanboard's own modal opener. On the board the title is a link, so a hidden marker
|
||||||
|
// (rendered by the hook below, only when the user may edit) carries the server-generated edit
|
||||||
|
// URL for the JS to open; on the card view the JS reads #task-view's data-edit-url.
|
||||||
|
if ((int) $this->configModel->get('organon_tweaks_title_click_edit', 1) === 1) {
|
||||||
|
$this->hook->on('template:layout:js', array(
|
||||||
|
'template' => 'plugins/OrganonTweaks/Asset/js/title-click-edit.js',
|
||||||
|
));
|
||||||
|
$this->template->hook->attach('template:board:private:task:before-title', 'organonTweaks:board/title_edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-managed shared custom filters (v1.5 "Show all tasks", v1.6 month filters, v1.7
|
||||||
|
// recurring filters). Ensured lazily whenever a project header renders (covers old + new
|
||||||
|
// boards); each group is gated by its own config toggle inside
|
||||||
|
// OrganonProjectHelper::ensureBoardFilters(). The toggles also bulk add/remove the filters.
|
||||||
$showAllFilters = (int) $this->configModel->get('organon_tweaks_show_all_filter', 1) === 1;
|
$showAllFilters = (int) $this->configModel->get('organon_tweaks_show_all_filter', 1) === 1;
|
||||||
$monthFilters = (int) $this->configModel->get('organon_tweaks_month_filters', 1) === 1;
|
$monthFilters = (int) $this->configModel->get('organon_tweaks_month_filters', 1) === 1;
|
||||||
if ($showAllFilters || $monthFilters) {
|
$recurringFilters = (int) $this->configModel->get('organon_tweaks_recurring_filters', 1) === 1;
|
||||||
|
if ($showAllFilters || $monthFilters || $recurringFilters) {
|
||||||
$this->template->hook->attach('template:project:header:before', 'organonTweaks:project_header/board_filters');
|
$this->template->hook->attach('template:project:header:before', 'organonTweaks:project_header/board_filters');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom board-search keywords, registered into the task lexer by extending its factory
|
// Custom board-search keywords, registered into the task lexer by extending its factory
|
||||||
// service (Pimple preserves factory status on extend, so the lexer stays per-request fresh).
|
// service (Pimple preserves factory status on extend, so the lexer stays per-request fresh).
|
||||||
// `duemonth:VALUE` = a whole calendar month; `aroundmonth:VALUE` = that month padded by 10
|
// `duemonth:VALUE` / `aroundmonth:VALUE` = a calendar month (padded by 10 days for around);
|
||||||
// days on each side. Always available to type, independent of the bookmark toggles above.
|
// `recurring:XY` = classify recurrence (X system evt/cal/any, Y role ori/dup/all). Always
|
||||||
// Guarded so a future Kanboard service rename no-ops (keyword absent) instead of breaking.
|
// available to type, independent of the bookmark toggles above. Guarded so a future Kanboard
|
||||||
|
// service rename no-ops (keyword absent) instead of breaking.
|
||||||
if (isset($this->container['taskLexer'])) {
|
if (isset($this->container['taskLexer'])) {
|
||||||
$this->container->extend('taskLexer', function ($builder, $c) {
|
$this->container->extend('taskLexer', function ($builder, $c) {
|
||||||
return $builder
|
return $builder
|
||||||
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('duemonth')->setPadDays(0))
|
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('duemonth')->setPadDays(0))
|
||||||
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('aroundmonth')->setPadDays(10));
|
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskMonthRangeFilter())->setAttribute('aroundmonth')->setPadDays(10))
|
||||||
|
->withFilter((new \Kanboard\Plugin\OrganonTweaks\Filter\TaskRecurringFilter())->setDatabase($c['db']));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +128,7 @@ class Plugin extends Base
|
|||||||
|
|
||||||
public function getPluginVersion()
|
public function getPluginVersion()
|
||||||
{
|
{
|
||||||
return '1.6.0';
|
return '1.8.4';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPluginHomepage()
|
public function getPluginHomepage()
|
||||||
|
|||||||
66
README.md
66
README.md
@@ -52,12 +52,37 @@ board stays where you were.
|
|||||||
Dropping a card sometimes leaks a trailing "click" that opens the card -- an unwanted side
|
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
|
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
|
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
|
the following `click`) and, if it was held longer than **120 ms**, swallows the click in the
|
||||||
capture phase before Kanboard's open handler -- so a drag never opens the card. Normal
|
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.
|
clicks (well under 120 ms) still open cards as usual. The 120 ms threshold is fixed.
|
||||||
|
|
||||||
- **On by default.** Toggle it under "Settings -> Organon Tweaks".
|
- **On by default.** Toggle it under "Settings -> Organon Tweaks".
|
||||||
|
|
||||||
|
### Show keyboard shortcuts on the task menu
|
||||||
|
|
||||||
|
Kanboard already binds handy keys on the task view (`e` = edit, `s` = new sub-task, `c` = new
|
||||||
|
comment, `l` = new internal link) but never shows them, so they go undiscovered. This tweak adds a
|
||||||
|
small key hint next to each of those items in the task **Actions** sidebar. It is **display only**:
|
||||||
|
it adds no new bindings, it just surfaces the ones Kanboard already has.
|
||||||
|
|
||||||
|
- Implemented in `Asset/js/shortcut-labels.js`, matching the menu items by their FontAwesome icon
|
||||||
|
class, so it is independent of the URL format and the interface language.
|
||||||
|
- **On by default.** Toggle it under "Settings -> Organon Tweaks".
|
||||||
|
|
||||||
|
### Click a title to edit
|
||||||
|
|
||||||
|
By default, clicking a card title (on the board) or the title on the card view lands you on the
|
||||||
|
read-only card view. With this tweak on, clicking the title opens the **edit** form directly,
|
||||||
|
reusing Kanboard's own modal -- so from the board you edit and, on Save, come straight back to the
|
||||||
|
board. It applies only where you may edit (a read-only viewer keeps the normal title), and
|
||||||
|
shared/public boards are untouched.
|
||||||
|
|
||||||
|
- Implemented in `Asset/js/title-click-edit.js` -- a capture-phase click that reuses `KB.modal.open`.
|
||||||
|
On the board, a hidden marker (`Template/board/title_edit.php`, rendered by
|
||||||
|
`template:board:private:task:before-title` only when the user may edit) carries the
|
||||||
|
server-generated edit URL; on the card view the title reuses `#task-view`'s `data-edit-url`.
|
||||||
|
- **On by default.** Toggle it under "Settings -> Organon Tweaks".
|
||||||
|
|
||||||
### Enhance the recurrence dialog
|
### Enhance the recurrence dialog
|
||||||
|
|
||||||
Kanboard's native "Edit recurrence" dialog is a plain form. This makes two small usability
|
Kanboard's native "Edit recurrence" dialog is a plain form. This makes two small usability
|
||||||
@@ -73,11 +98,29 @@ edits when it opens (there is no template hook, so it is done in JS,
|
|||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
Global (per Kanboard instance) and admin-only, under "Settings -> Organon Tweaks":
|
Global (per Kanboard instance) and admin-only, under "Settings -> Organon Tweaks", grouped as on
|
||||||
|
the page:
|
||||||
|
|
||||||
|
**Board card readability**
|
||||||
|
|
||||||
- **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.
|
- **Emphasize due date by default** -- default off (overridable per board under Settings -> Integrations).
|
||||||
|
|
||||||
|
**Board filters** (shared custom filters added to every board)
|
||||||
|
|
||||||
|
- **Add a "Show all tasks" filter to every board** -- default on.
|
||||||
|
- **Add month filters to every board** -- default on.
|
||||||
|
- **Add recurring filters to every board** -- default on.
|
||||||
|
|
||||||
|
**Card interaction**
|
||||||
|
|
||||||
- **Open a card only on a quick click** -- default on.
|
- **Open a card only on a quick click** -- default on.
|
||||||
|
- **Show keyboard shortcuts on the task menu** -- default on.
|
||||||
|
- **Clicking a task title opens the edit form** -- default on.
|
||||||
|
|
||||||
|
Standalone:
|
||||||
|
|
||||||
|
- **Keep the board scroll position across refreshes** -- default on.
|
||||||
- **Enhance the recurrence dialog** -- default on.
|
- **Enhance the recurrence dialog** -- default on.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -93,3 +136,18 @@ name). No build step and no database migration.
|
|||||||
## 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.
|
||||||
|
- **[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.
|
||||||
|
- **[TweakDrag](https://code.beco.cc/beco/TweakDrag)** -- board drag and touch niceties:
|
||||||
|
drag-to-scroll, a wider column gap, and smoother card dragging.
|
||||||
|
|||||||
3
Template/board/title_edit.php
Normal file
3
Template/board/title_edit.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?>
|
||||||
|
<span class="organon-edit-url" style="display:none" data-url="<?= $this->url->href('TaskModificationController', 'edit', array('task_id' => $task['id'])) ?>"></span>
|
||||||
|
<?php endif ?>
|
||||||
@@ -6,10 +6,13 @@
|
|||||||
<?= $this->form->csrf() ?>
|
<?= $this->form->csrf() ?>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
<legend><?= t('Board card readability') ?></legend>
|
||||||
|
|
||||||
<?= $this->form->checkbox('organon_tweaks_always_comment_icon', t('Always show the comment icon on board cards'), 1, isset($values['organon_tweaks_always_comment_icon']) && $values['organon_tweaks_always_comment_icon'] == 1) ?>
|
<?= $this->form->checkbox('organon_tweaks_always_comment_icon', t('Always show the comment icon on board cards'), 1, isset($values['organon_tweaks_always_comment_icon']) && $values['organon_tweaks_always_comment_icon'] == 1) ?>
|
||||||
<p class="form-help">
|
<p class="form-help"><?= t('A "+" bubble on cards with no comments yet, to add the first one from the board.') ?></p>
|
||||||
<?= t('Show a comment bubble with a "+" on cards that have no comments yet, so you can add the first comment straight from the board. Cards that already have comments keep showing the count as usual.') ?>
|
|
||||||
</p>
|
<?= $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('Enlarge and bold the due date; the default for boards set to "Use default" (overridable per board under Settings -> Integrations).') ?></p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -19,39 +22,37 @@
|
|||||||
</p>
|
</p>
|
||||||
</fieldset>
|
</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>
|
<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) ?>
|
<?= $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">
|
<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).') ?>
|
<?= t('In the native "Edit recurrence" dialog, replace the Generate recurrent task dropdown with Yes/No radios, and add a [+7] button on the factor to add a week at once.') ?>
|
||||||
</p>
|
</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<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) ?>
|
<legend><?= t('Board filters') ?></legend>
|
||||||
<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>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<?= $this->form->checkbox('organon_tweaks_show_all_filter', t('Add a "Show all tasks" filter to every board'), 1, isset($values['organon_tweaks_show_all_filter']) && $values['organon_tweaks_show_all_filter'] == 1) ?>
|
<?= $this->form->checkbox('organon_tweaks_show_all_filter', t('Add a "Show all tasks" filter to every board'), 1, isset($values['organon_tweaks_show_all_filter']) && $values['organon_tweaks_show_all_filter'] == 1) ?>
|
||||||
<p class="form-help">
|
<p class="form-help"><?= t('One-click clear that shows open and closed tasks.') ?></p>
|
||||||
<?= t('Keep a shared "Show all tasks" custom filter (an empty query, so open and closed tasks are shown) on every board, old and new. It appears in the board filter bookmark menu as a one-click way to clear the filter. Turning this off removes it from all boards; turning it on recreates it everywhere.') ?>
|
|
||||||
</p>
|
<?= $this->form->checkbox('organon_tweaks_month_filters', t('Add month filters to every board'), 1, isset($values['organon_tweaks_month_filters']) && $values['organon_tweaks_month_filters'] == 1) ?>
|
||||||
|
<p class="form-help"><?= t('"Due this month" and "Around this month" (duemonth: / aroundmonth: keywords).') ?></p>
|
||||||
|
|
||||||
|
<?= $this->form->checkbox('organon_tweaks_recurring_filters', t('Add recurring filters to every board'), 1, isset($values['organon_tweaks_recurring_filters']) && $values['organon_tweaks_recurring_filters'] == 1) ?>
|
||||||
|
<p class="form-help"><?= t('Adds recurrence bookmarks. Also a recurring:XY search keyword -- X = evt/cal/any (native/calendar/both), Y = ori/dup/all (template/duplicate/both). Example: recurring:evtori.') ?></p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<?= $this->form->checkbox('organon_tweaks_month_filters', t('Add month filters to every board'), 1, isset($values['organon_tweaks_month_filters']) && $values['organon_tweaks_month_filters'] == 1) ?>
|
<legend><?= t('Card interaction') ?></legend>
|
||||||
<p class="form-help">
|
|
||||||
<?= t('Keep shared "Due this month" and "Around this month" custom filters on every board (using the duemonth: and aroundmonth: search keywords this plugin adds). They are append filters, so clicking them stacks onto the current filter. Turning this off removes them from all boards; turning it on recreates them everywhere.') ?>
|
<?= $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>
|
<p class="form-help"><?= t('A press held longer than 120 ms is treated as a drag, so releasing it does not open the card. Fixes a drag sometimes opening the card on release.') ?></p>
|
||||||
|
|
||||||
|
<?= $this->form->checkbox('organon_tweaks_shortcut_labels', t('Show keyboard shortcuts on the task menu'), 1, isset($values['organon_tweaks_shortcut_labels']) && $values['organon_tweaks_shortcut_labels'] == 1) ?>
|
||||||
|
<p class="form-help"><?= t('Adds the existing key hints (e, s, c, l) next to the task Actions items.') ?></p>
|
||||||
|
|
||||||
|
<?= $this->form->checkbox('organon_tweaks_title_click_edit', t('Clicking a task title opens the edit form'), 1, isset($values['organon_tweaks_title_click_edit']) && $values['organon_tweaks_title_click_edit'] == 1) ?>
|
||||||
|
<p class="form-help"><?= t('On the board and the card view, click the title to open the edit modal (only where you may edit).') ?></p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
|
|||||||
@@ -22,3 +22,4 @@
|
|||||||
<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>
|
||||||
|
<hr>
|
||||||
|
|||||||
Reference in New Issue
Block a user