Calendar fixes
This commit is contained in:
@@ -387,9 +387,18 @@ Embed a list of open maintenance tasks anywhere on a wiki page:
|
|||||||
{{maintenance_tasks>}}
|
{{maintenance_tasks>}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To limit how far back overdue tasks are shown, add the `past` setting:
|
||||||
|
|
||||||
|
```
|
||||||
|
{{maintenance_tasks>&past=14}}
|
||||||
|
```
|
||||||
|
|
||||||
This renders all non-completed maintenance tasks due today or earlier, sorted
|
This renders all non-completed maintenance tasks due today or earlier, sorted
|
||||||
with overdue tasks first (then by date, time, and title).
|
with overdue tasks first (then by date, time, and title).
|
||||||
|
|
||||||
|
The `past` value is an integer number of days. Overdue tasks older than that
|
||||||
|
window are hidden. The default is `30`.
|
||||||
|
|
||||||
Each task shows its date, optional time, summary, and a "Complete" button.
|
Each task shows its date, optional time, summary, and a "Complete" button.
|
||||||
|
|
||||||
### 0.10) CalDAV sync
|
### 0.10) CalDAV sync
|
||||||
|
|||||||
@@ -150,6 +150,22 @@
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
var MaintenanceTasks = (function () {
|
var MaintenanceTasks = (function () {
|
||||||
|
|
||||||
|
function getSecurityToken(container) {
|
||||||
|
var sectok = container ? container.getAttribute('data-luxtools-sectok') : '';
|
||||||
|
if (sectok) return String(sectok);
|
||||||
|
|
||||||
|
if (window.JSINFO && window.JSINFO.sectok) {
|
||||||
|
return String(window.JSINFO.sectok);
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = document.querySelector('input[name="sectok"], input[name="securitytoken"]');
|
||||||
|
if (input && input.value) {
|
||||||
|
return String(input.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function handleAction(button) {
|
function handleAction(button) {
|
||||||
var action = button.getAttribute('data-action');
|
var action = button.getAttribute('data-action');
|
||||||
if (!action) return;
|
if (!action) return;
|
||||||
@@ -171,15 +187,12 @@
|
|||||||
// Find AJAX URL and security token from parent container or global
|
// Find AJAX URL and security token from parent container or global
|
||||||
var container = item.closest('[data-luxtools-ajax-url]');
|
var container = item.closest('[data-luxtools-ajax-url]');
|
||||||
var ajaxUrl = container ? container.getAttribute('data-luxtools-ajax-url') : '';
|
var ajaxUrl = container ? container.getAttribute('data-luxtools-ajax-url') : '';
|
||||||
var sectok = container ? container.getAttribute('data-luxtools-sectok') : '';
|
var sectok = getSecurityToken(container);
|
||||||
|
|
||||||
if (!ajaxUrl) {
|
if (!ajaxUrl) {
|
||||||
// Fallback: use DokuWiki's standard AJAX endpoint
|
// Fallback: use DokuWiki's standard AJAX endpoint
|
||||||
ajaxUrl = (window.DOKU_BASE || '/') + 'lib/exe/ajax.php';
|
ajaxUrl = (window.DOKU_BASE || '/') + 'lib/exe/ajax.php';
|
||||||
}
|
}
|
||||||
if (!sectok && window.JSINFO && window.JSINFO.sectok) {
|
|
||||||
sectok = window.JSINFO.sectok;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
button.textContent = '...';
|
button.textContent = '...';
|
||||||
|
|||||||
@@ -116,29 +116,33 @@ class CalendarService
|
|||||||
*
|
*
|
||||||
* @param CalendarSlot $maintenanceSlot
|
* @param CalendarSlot $maintenanceSlot
|
||||||
* @param string $todayIso YYYY-MM-DD
|
* @param string $todayIso YYYY-MM-DD
|
||||||
|
* @param int $pastDays Maximum number of overdue days to include
|
||||||
* @return CalendarEvent[] Sorted: overdue first, then today, then by title
|
* @return CalendarEvent[] Sorted: overdue first, then today, then by title
|
||||||
*/
|
*/
|
||||||
public static function openMaintenanceTasks(CalendarSlot $maintenanceSlot, string $todayIso): array
|
public static function openMaintenanceTasks(CalendarSlot $maintenanceSlot, string $todayIso, int $pastDays = 30): array
|
||||||
{
|
{
|
||||||
if (!$maintenanceSlot->isEnabled()) return [];
|
if (!$maintenanceSlot->isEnabled()) return [];
|
||||||
if (!ChronoID::isIsoDate($todayIso)) return [];
|
if (!ChronoID::isIsoDate($todayIso)) return [];
|
||||||
|
|
||||||
|
$pastDays = max(0, $pastDays);
|
||||||
|
|
||||||
$file = $maintenanceSlot->getFile();
|
$file = $maintenanceSlot->getFile();
|
||||||
if ($file === '' || !is_file($file) || !is_readable($file)) return [];
|
if ($file === '' || !is_file($file) || !is_readable($file)) return [];
|
||||||
|
|
||||||
$cacheKey = $maintenanceSlot->getKey() . '|tasks|' . $todayIso;
|
$cacheKey = $maintenanceSlot->getKey() . '|tasks|' . $todayIso . '|' . $pastDays;
|
||||||
if (isset(self::$taskCache[$cacheKey])) {
|
if (isset(self::$taskCache[$cacheKey])) {
|
||||||
return self::$taskCache[$cacheKey];
|
return self::$taskCache[$cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
$tasks = self::parseAllTasksFromFile($file, $maintenanceSlot->getKey(), $todayIso);
|
$tasks = self::parseAllTasksFromFile($file, $maintenanceSlot->getKey(), $todayIso);
|
||||||
|
$oldestOpenDateIso = self::oldestOpenTaskDate($todayIso, $pastDays);
|
||||||
|
|
||||||
// Filter: only non-completed, due today or earlier
|
// Filter: only non-completed, due today or earlier, and not older than the configured overdue window.
|
||||||
$open = [];
|
$open = [];
|
||||||
foreach ($tasks as $task) {
|
foreach ($tasks as $task) {
|
||||||
if ($task->isCompleted()) continue;
|
if ($task->isCompleted()) continue;
|
||||||
// dateIso is the date the task falls on
|
|
||||||
if ($task->dateIso > $todayIso) continue;
|
if ($task->dateIso > $todayIso) continue;
|
||||||
|
if ($task->dateIso < $oldestOpenDateIso) continue;
|
||||||
$open[] = $task;
|
$open[] = $task;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +166,26 @@ class CalendarService
|
|||||||
return $open;
|
return $open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $todayIso
|
||||||
|
* @param int $pastDays
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function oldestOpenTaskDate(string $todayIso, int $pastDays): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$today = new DateTimeImmutable($todayIso . ' 00:00:00', new DateTimeZone('UTC'));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return $todayIso;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pastDays === 0) {
|
||||||
|
return $today->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $today->sub(new DateInterval('P' . $pastDays . 'D'))->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get slot-level day indicator data for a whole month.
|
* Get slot-level day indicator data for a whole month.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
use dokuwiki\Extension\SyntaxPlugin;
|
use dokuwiki\Extension\SyntaxPlugin;
|
||||||
use dokuwiki\plugin\luxtools\CalendarService;
|
use dokuwiki\plugin\luxtools\CalendarService;
|
||||||
use dokuwiki\plugin\luxtools\CalendarSlot;
|
use dokuwiki\plugin\luxtools\CalendarSlot;
|
||||||
use dokuwiki\plugin\luxtools\ChronoID;
|
|
||||||
|
|
||||||
require_once(__DIR__ . '/../autoload.php');
|
require_once(__DIR__ . '/../autoload.php');
|
||||||
|
|
||||||
@@ -14,9 +13,12 @@ require_once(__DIR__ . '/../autoload.php');
|
|||||||
*
|
*
|
||||||
* Syntax:
|
* Syntax:
|
||||||
* {{maintenance_tasks>}}
|
* {{maintenance_tasks>}}
|
||||||
|
* {{maintenance_tasks>&past=14}}
|
||||||
*/
|
*/
|
||||||
class syntax_plugin_luxtools_maintenance extends SyntaxPlugin
|
class syntax_plugin_luxtools_maintenance extends SyntaxPlugin
|
||||||
{
|
{
|
||||||
|
private const DEFAULT_PAST_DAYS = 30;
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function getType()
|
public function getType()
|
||||||
{
|
{
|
||||||
@@ -39,7 +41,7 @@ class syntax_plugin_luxtools_maintenance extends SyntaxPlugin
|
|||||||
public function connectTo($mode)
|
public function connectTo($mode)
|
||||||
{
|
{
|
||||||
$this->Lexer->addSpecialPattern(
|
$this->Lexer->addSpecialPattern(
|
||||||
'\{\{maintenance_tasks>\}\}',
|
'\{\{maintenance_tasks>.*?\}\}',
|
||||||
$mode,
|
$mode,
|
||||||
'plugin_luxtools_maintenance'
|
'plugin_luxtools_maintenance'
|
||||||
);
|
);
|
||||||
@@ -48,7 +50,13 @@ class syntax_plugin_luxtools_maintenance extends SyntaxPlugin
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
public function handle($match, $state, $pos, Doku_Handler $handler)
|
public function handle($match, $state, $pos, Doku_Handler $handler)
|
||||||
{
|
{
|
||||||
return ['ok' => true];
|
$match = substr($match, strlen('{{maintenance_tasks>'), -2);
|
||||||
|
$params = $this->parseFlags($match);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ok' => true,
|
||||||
|
'past' => $this->normalizePastDays($params['past'] ?? null),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
@@ -72,12 +80,17 @@ class syntax_plugin_luxtools_maintenance extends SyntaxPlugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
$todayIso = date('Y-m-d');
|
$todayIso = date('Y-m-d');
|
||||||
$tasks = CalendarService::openMaintenanceTasks($maintenanceSlot, $todayIso);
|
$pastDays = $this->normalizePastDays($data['past'] ?? null);
|
||||||
|
$tasks = CalendarService::openMaintenanceTasks($maintenanceSlot, $todayIso, $pastDays);
|
||||||
|
$ajaxUrl = defined('DOKU_BASE') ? (string)DOKU_BASE . 'lib/exe/ajax.php' : 'lib/exe/ajax.php';
|
||||||
|
$secToken = function_exists('getSecurityToken') ? (string)getSecurityToken() : '';
|
||||||
|
|
||||||
$title = (string)$this->getLang('chronological_maintenance_title');
|
$title = (string)$this->getLang('chronological_maintenance_title');
|
||||||
if ($title === '') $title = 'Tasks';
|
if ($title === '') $title = 'Tasks';
|
||||||
|
|
||||||
$renderer->doc .= '<div class="luxtools-plugin luxtools-maintenance-tasks">';
|
$renderer->doc .= '<div class="luxtools-plugin luxtools-maintenance-tasks"'
|
||||||
|
. ' data-luxtools-ajax-url="' . hsc($ajaxUrl) . '"'
|
||||||
|
. ' data-luxtools-sectok="' . hsc($secToken) . '">';
|
||||||
$renderer->doc .= '<h3>' . hsc($title) . '</h3>';
|
$renderer->doc .= '<h3>' . hsc($title) . '</h3>';
|
||||||
|
|
||||||
if ($tasks === []) {
|
if ($tasks === []) {
|
||||||
@@ -126,4 +139,56 @@ class syntax_plugin_luxtools_maintenance extends SyntaxPlugin
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $rawFlags
|
||||||
|
* @return array<string,string>
|
||||||
|
*/
|
||||||
|
protected function parseFlags(string $rawFlags): array
|
||||||
|
{
|
||||||
|
$rawFlags = trim($rawFlags);
|
||||||
|
if ($rawFlags === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rawFlags[0] === '&') {
|
||||||
|
$rawFlags = substr($rawFlags, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
foreach (explode('&', $rawFlags) as $flag) {
|
||||||
|
if (trim($flag) === '') continue;
|
||||||
|
|
||||||
|
[$name, $value] = array_pad(explode('=', $flag, 2), 2, '');
|
||||||
|
$name = strtolower(trim($name));
|
||||||
|
$value = trim($value);
|
||||||
|
|
||||||
|
if ($name === '') continue;
|
||||||
|
$params[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $value
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function normalizePastDays($value): int
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return self::DEFAULT_PAST_DAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_int($value)) {
|
||||||
|
return max(0, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = trim((string)$value);
|
||||||
|
if ($value === '' || !preg_match('/^-?\d+$/', $value)) {
|
||||||
|
return self::DEFAULT_PAST_DAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max(0, (int)$value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user