Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b63d366296 | |||
| 89888235d1 | |||
| 4ebebd5ffb | |||
| 9b637160b2 | |||
| e85d5d71f9 | |||
| ce3205646f | |||
| 4ce18d9e5f |
@@ -13,7 +13,7 @@
|
||||
(function () {
|
||||
"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;
|
||||
|
||||
function onDown(e) {
|
||||
|
||||
@@ -21,6 +21,7 @@ class ConfigController extends BaseController
|
||||
'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_month_filters' => (int) $this->configModel->get('organon_tweaks_month_filters', 1),
|
||||
'organon_tweaks_recurring_filters' => (int) $this->configModel->get('organon_tweaks_recurring_filters', 1),
|
||||
),
|
||||
'errors' => array(),
|
||||
)));
|
||||
@@ -37,6 +38,7 @@ class ConfigController extends BaseController
|
||||
$emphasizeDueDateDefault = isset($values['organon_tweaks_emphasize_duedate_default']) ? 1 : 0;
|
||||
$showAllFilter = isset($values['organon_tweaks_show_all_filter']) ? 1 : 0;
|
||||
$monthFilters = isset($values['organon_tweaks_month_filters']) ? 1 : 0;
|
||||
$recurringFilters = isset($values['organon_tweaks_recurring_filters']) ? 1 : 0;
|
||||
|
||||
if ($this->configModel->save(array(
|
||||
'organon_tweaks_always_comment_icon' => $alwaysCommentIcon,
|
||||
@@ -46,6 +48,7 @@ class ConfigController extends BaseController
|
||||
'organon_tweaks_emphasize_duedate_default' => $emphasizeDueDateDefault,
|
||||
'organon_tweaks_show_all_filter' => $showAllFilter,
|
||||
'organon_tweaks_month_filters' => $monthFilters,
|
||||
'organon_tweaks_recurring_filters' => $recurringFilters,
|
||||
))) {
|
||||
$this->flash->success(t('Settings saved successfully.'));
|
||||
} else {
|
||||
@@ -68,6 +71,12 @@ class ConfigController extends BaseController
|
||||
$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')));
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
const SHOW_ALL_FILTER_KEY = 'organon_tweaks_show_all_filter';
|
||||
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
|
||||
// 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
|
||||
// (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(
|
||||
array('Show all tasks', '', 0),
|
||||
array('Board: show all tasks', '', 0),
|
||||
);
|
||||
const GROUP_MONTH = array(
|
||||
array('Due this month', 'duemonth:this', 1),
|
||||
array('Around this month', 'aroundmonth:this', 1),
|
||||
array('Month: due this month', 'duemonth: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) {
|
||||
$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)
|
||||
|
||||
23
Plugin.php
23
Plugin.php
@@ -58,26 +58,29 @@ class Plugin extends Base
|
||||
));
|
||||
}
|
||||
|
||||
// Auto-managed shared custom filters (v1.5 "Show all tasks", v1.6 month 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 (ConfigController).
|
||||
// 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;
|
||||
$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');
|
||||
}
|
||||
|
||||
// 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).
|
||||
// `duemonth:VALUE` = a whole calendar month; `aroundmonth:VALUE` = that month padded by 10
|
||||
// days on each side. Always available to type, independent of the bookmark toggles above.
|
||||
// Guarded so a future Kanboard service rename no-ops (keyword absent) instead of breaking.
|
||||
// `duemonth:VALUE` / `aroundmonth:VALUE` = a calendar month (padded by 10 days for around);
|
||||
// `recurring:XY` = classify recurrence (X system evt/cal/any, Y role ori/dup/all). Always
|
||||
// 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'])) {
|
||||
$this->container->extend('taskLexer', function ($builder, $c) {
|
||||
return $builder
|
||||
->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 +109,7 @@ class Plugin extends Base
|
||||
|
||||
public function getPluginVersion()
|
||||
{
|
||||
return '1.6.0';
|
||||
return '1.7.2';
|
||||
}
|
||||
|
||||
public function getPluginHomepage()
|
||||
|
||||
15
README.md
15
README.md
@@ -93,3 +93,18 @@ name). No build step and no database migration.
|
||||
## 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.
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
<?= $this->form->csrf() ?>
|
||||
|
||||
<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) ?>
|
||||
<p class="form-help">
|
||||
<?= 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>
|
||||
<p class="form-help"><?= t('A "+" bubble on cards with no comments yet, to add the first one from the board.') ?></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>
|
||||
@@ -22,36 +25,28 @@
|
||||
<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.') ?>
|
||||
<?= 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>
|
||||
</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).') ?>
|
||||
<?= 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>
|
||||
</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>
|
||||
<legend><?= t('Board filters') ?></legend>
|
||||
|
||||
<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) ?>
|
||||
<p class="form-help">
|
||||
<?= 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>
|
||||
</fieldset>
|
||||
<p class="form-help"><?= t('One-click clear that shows open and closed tasks.') ?></p>
|
||||
|
||||
<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) ?>
|
||||
<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.') ?>
|
||||
</p>
|
||||
<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>
|
||||
|
||||
<div class="form-actions">
|
||||
|
||||
Reference in New Issue
Block a user