forked from GitHubMirrors/silverbullet-icalendar
Compare commits
14 Commits
7e848feeee
...
65e52802a2
| Author | SHA1 | Date | |
|---|---|---|---|
| 65e52802a2 | |||
| 698f4ee4c7 | |||
| 30dbbf333a | |||
| 07def7ed62 | |||
| 8d91c96fd5 | |||
| baa43ec1f6 | |||
| 95de1e3a1e | |||
| c40a0aff18 | |||
| 6960f9ef91 | |||
| 0ef8b9a77d | |||
| a9a0fbf267 | |||
| df0f786d94 | |||
| af75a00f7a | |||
| bc8e67fbdf |
2
PLUG.md
2
PLUG.md
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Library/sstent/icalendar
|
name: Library/sstent/icalendar
|
||||||
version: "0.3.32"
|
version: "0.3.33"
|
||||||
tags: meta/library
|
tags: meta/library
|
||||||
files:
|
files:
|
||||||
- icalendar.plug.js
|
- icalendar.plug.js
|
||||||
|
|||||||
@@ -21,3 +21,8 @@ This file tracks all major tracks for the project. Each track has its own detail
|
|||||||
|
|
||||||
- [x] **Track: Fix RRULE UNTIL object conversion error: Invalid UNTIL value: [object Object]**
|
- [x] **Track: Fix RRULE UNTIL object conversion error: Invalid UNTIL value: [object Object]**
|
||||||
*Link: [./tracks/fix_rrule_until_conversion_20260219/](./tracks/fix_rrule_until_conversion_20260219/)*
|
*Link: [./tracks/fix_rrule_until_conversion_20260219/](./tracks/fix_rrule_until_conversion_20260219/)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- [x] **Track: Fix SyntaxError: Invalid weekday string: [object Object] by implementing a generic recursive RRULE formatter.**
|
||||||
|
*Link: [./tracks/rrule_generic_formatter_20260219/](./tracks/rrule_generic_formatter_20260219/)*
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Track rrule_generic_formatter_20260219 Context
|
||||||
|
|
||||||
|
- [Specification](./spec.md)
|
||||||
|
- [Implementation Plan](./plan.md)
|
||||||
|
- [Metadata](./metadata.json)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"track_id": "rrule_generic_formatter_20260219",
|
||||||
|
"type": "bug",
|
||||||
|
"status": "new",
|
||||||
|
"created_at": "2026-02-19T00:00:00Z",
|
||||||
|
"updated_at": "2026-02-19T00:00:00Z",
|
||||||
|
"description": "Fix SyntaxError: Invalid weekday string: [object Object] by implementing a generic recursive RRULE formatter."
|
||||||
|
}
|
||||||
22
conductor/tracks/rrule_generic_formatter_20260219/plan.md
Normal file
22
conductor/tracks/rrule_generic_formatter_20260219/plan.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Implementation Plan - Generic RRULE Formatter
|
||||||
|
|
||||||
|
## Phase 1: Reproduction & Test Setup [checkpoint: b41f78c]
|
||||||
|
- [x] Task: Create reproduction test case for `BYDAY` object cfab0c3
|
||||||
|
- [x] Add a test in `icalendar_test.ts` mocking a `BYDAY` property as an array of objects (e.g. `[{ day: 'MO' }]`).
|
||||||
|
- [x] Run the test and confirm it fails with `SyntaxError: Invalid weekday string: [object Object]`.
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Reproduction & Test Setup' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 2: Implementation [checkpoint: 7d1fa9f]
|
||||||
|
- [x] Task: Implement Recursive Formatter 115a165
|
||||||
|
- [x] Update `formatRRuleValue` in `icalendar.ts` to be a recursive function.
|
||||||
|
- [x] Handle Arrays by joining elements with commas.
|
||||||
|
- [x] Handle `Date` objects using the existing iCal format.
|
||||||
|
- [x] Handle property objects (extract `date`, `day`, or `value` properties).
|
||||||
|
- [x] Run the reproduction test to confirm it passes.
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Implementation' (Protocol in workflow.md)
|
||||||
|
|
||||||
|
## Phase 3: Verification & Cleanup [checkpoint: 952ceb0]
|
||||||
|
- [x] Task: Composite and Regression Testing 1e613ea
|
||||||
|
- [x] Add a complex test case containing both nested `UNTIL` dates and `BYDAY` arrays.
|
||||||
|
- [x] Run the full regression test suite in `icalendar_test.ts`.
|
||||||
|
- [x] Task: Conductor - User Manual Verification 'Verification & Cleanup' (Protocol in workflow.md)
|
||||||
25
conductor/tracks/rrule_generic_formatter_20260219/spec.md
Normal file
25
conductor/tracks/rrule_generic_formatter_20260219/spec.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Specification: Generic RRULE Object Formatter
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The current implementation for converting RRULE objects (from `ts-ics`) back to strings is brittle and fails when encountering nested objects or arrays for properties like `BYDAY`. This results in `[object Object]` being injected into the RRULE string, causing syntax errors in the `rrule` library. This track replaces the specific property handling with a robust, recursive generic formatter.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
- **Recursive Value Formatting:** The `formatRRuleValue` function must handle nested structures:
|
||||||
|
- **Arrays:** Join elements with commas (e.g., `['MO', 'TU']` -> `MO,TU`).
|
||||||
|
- **Date Objects:** Format as `YYYYMMDDTHHMMSSZ`.
|
||||||
|
- **Nested Property Objects:** If an object has a `date`, `day`, or `value` property, extract and format that value (e.g., `{ day: 'MO' }` -> `MO`).
|
||||||
|
- **Recursion:** If an array contains objects, or an object contains an array, the logic must recurse until primitives are reached.
|
||||||
|
- iCalendar Compliance: Ensure the resulting string for every property matches the iCalendar spec format expected by `rrule.js`.
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
1. Reproduce: Create a test case in `icalendar_test.ts` where `BYDAY` is an object or an array of objects. Verify it fails with `Invalid weekday string: [object Object]`.
|
||||||
|
2. Fix: Re-implement `formatRRuleValue` in `icalendar.ts` as a recursive function that handles Arrays, Dates, and standard `ts-ics` nested value objects.
|
||||||
|
3. Verify:
|
||||||
|
- Confirm the `BYDAY` reproduction test passes.
|
||||||
|
- Add a composite test with `UNTIL` (Date) and `BYDAY` (Array) together.
|
||||||
|
- Run regression tests to ensure standard string RRULEs still work.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
- [ ] `expandRecurrences` correctly handles `BYDAY` when provided as an array of objects.
|
||||||
|
- [ ] `expandRecurrences` correctly handles `UNTIL` when provided as a nested date object.
|
||||||
|
- [ ] All 12+ existing tests pass, including regression checks for string-based RRULEs.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "icalendar-plug",
|
"name": "icalendar-plug",
|
||||||
"version": "0.3.32",
|
"version": "0.3.33",
|
||||||
"nodeModulesDir": "auto",
|
"nodeModulesDir": "auto",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"sync-version": "deno run -A scripts/sync-version.ts",
|
"sync-version": "deno run -A scripts/sync-version.ts",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: icalendar
|
name: icalendar
|
||||||
version: 0.3.32
|
version: 0.3.33
|
||||||
author: sstent
|
author: sstent
|
||||||
index: icalendar.ts
|
index: icalendar.ts
|
||||||
# Legacy SilverBullet permission name
|
# Legacy SilverBullet permission name
|
||||||
|
|||||||
20
icalendar.ts
20
icalendar.ts
@@ -3,7 +3,7 @@ import { convertIcsCalendar } from "https://esm.sh/ts-ics@2.4.0";
|
|||||||
import { RRule, RRuleSet } from "rrule";
|
import { RRule, RRuleSet } from "rrule";
|
||||||
import { getUtcOffsetMs, resolveIanaName } from "./timezones.ts";
|
import { getUtcOffsetMs, resolveIanaName } from "./timezones.ts";
|
||||||
|
|
||||||
const VERSION = "0.3.32";
|
const VERSION = "0.3.33";
|
||||||
const CACHE_KEY = "icalendar:lastSync";
|
const CACHE_KEY = "icalendar:lastSync";
|
||||||
|
|
||||||
console.log(`[iCalendar] Plug script executing at top level (Version ${VERSION})`);
|
console.log(`[iCalendar] Plug script executing at top level (Version ${VERSION})`);
|
||||||
@@ -30,15 +30,25 @@ const RRULE_KEY_MAP: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats an RRULE value for the string representation.
|
* Robustly formats an RRULE value for its string representation.
|
||||||
* Specifically handles Date objects and nested date objects from ts-ics.
|
* Handles:
|
||||||
|
* - Arrays: joins elements with commas (recursive)
|
||||||
|
* - Date objects: formats as YYYYMMDDTHHMMSSZ
|
||||||
|
* - Objects: extracts .date, .day, or .value properties (recursive)
|
||||||
|
* - Primitives: stringifies directly
|
||||||
*/
|
*/
|
||||||
function formatRRuleValue(v: any): string {
|
function formatRRuleValue(v: any): string {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v.map((item) => formatRRuleValue(item)).join(",");
|
||||||
|
}
|
||||||
if (v instanceof Date) {
|
if (v instanceof Date) {
|
||||||
return v.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
|
return v.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
|
||||||
}
|
}
|
||||||
if (typeof v === "object" && v !== null && v.date instanceof Date) {
|
if (typeof v === "object" && v !== null) {
|
||||||
return v.date.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
|
const val = v.date || v.day || v.value;
|
||||||
|
if (val !== undefined) {
|
||||||
|
return formatRRuleValue(val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return String(v);
|
return String(v);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,3 +225,44 @@ Deno.test("expandRecurrences - object rrule with until", () => {
|
|||||||
assertEquals(results[0].recurrent, true);
|
assertEquals(results[0].recurrent, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Deno.test("expandRecurrences - object rrule with byday", () => {
|
||||||
|
const now = new Date();
|
||||||
|
const start = new Date(now.getTime() - 10 * 86400000);
|
||||||
|
const startStr = localDateString(start);
|
||||||
|
|
||||||
|
const icsEvent = {
|
||||||
|
summary: "Object RRULE BYDAY Event",
|
||||||
|
start: startStr,
|
||||||
|
rrule: { frequency: "WEEKLY", byday: [{ day: "MO" }, { day: "WE" }] }
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = expandRecurrences(icsEvent, 30);
|
||||||
|
// Should now return multiple occurrences
|
||||||
|
assert(results.length > 1, `Expected > 1 occurrences, got ${results.length}`);
|
||||||
|
assertEquals(results[0].recurrent, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Deno.test("expandRecurrences - composite object rrule", () => {
|
||||||
|
const now = new Date();
|
||||||
|
const start = new Date(now.getTime() - 10 * 86400000);
|
||||||
|
const startStr = localDateString(start);
|
||||||
|
const untilDate = new Date(now.getTime() + 10 * 86400000);
|
||||||
|
|
||||||
|
const icsEvent = {
|
||||||
|
summary: "Composite RRULE Event",
|
||||||
|
start: startStr,
|
||||||
|
rrule: {
|
||||||
|
frequency: "WEEKLY",
|
||||||
|
until: { date: untilDate },
|
||||||
|
byday: [{ day: "MO" }, { day: "WE" }, { day: "FR" }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = expandRecurrences(icsEvent, 30);
|
||||||
|
// Should successfully expand multiple days per week until the date
|
||||||
|
assert(results.length > 1);
|
||||||
|
assertEquals(results[0].recurrent, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user