This commit is contained in:
Adorian Doran 2026-02-28 19:03:35 +02:00
commit 1d698106da
25 changed files with 312 additions and 29 deletions

View File

@ -22,6 +22,7 @@
"@fullcalendar/interaction": "6.1.20",
"@fullcalendar/list": "6.1.20",
"@fullcalendar/multimonth": "6.1.20",
"@fullcalendar/rrule": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.0",
@ -63,6 +64,7 @@
"react-i18next": "16.5.4",
"react-window": "2.2.7",
"reveal.js": "5.2.1",
"rrule": "2.8.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
"vanilla-js-wheel-zoom": "9.0.4"

View File

@ -3,6 +3,8 @@ import options from "../services/options.js";
import zoomService from "../components/zoom.js";
import contextMenu, { type MenuItem } from "./context_menu.js";
import { t } from "../services/i18n.js";
import server from "../services/server.js";
import * as clipboardExt from "../services/clipboard_ext.js";
import type { BrowserWindow } from "electron";
import type { CommandNames, AppContext } from "../components/app_context.js";
@ -60,6 +62,33 @@ function setupContextMenu() {
uiIcon: "bx bx-copy",
handler: () => webContents.copy()
});
items.push({
enabled: hasText,
title: t("electron_context_menu.copy-as-markdown"),
uiIcon: "bx bx-copy-alt",
handler: async () => {
const selection = window.getSelection();
if (!selection || !selection.rangeCount) return '';
const range = selection.getRangeAt(0);
const div = document.createElement('div');
div.appendChild(range.cloneContents());
const htmlContent = div.innerHTML;
if (htmlContent) {
try {
const { markdownContent } = await server.post<{ markdownContent: string }>(
"other/to-markdown",
{ htmlContent }
);
await clipboardExt.copyTextWithToast(markdownContent);
} catch (error) {
console.error("Failed to copy as markdown:", error);
}
}
}
});
}
if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === "none") {

View File

@ -882,6 +882,7 @@
"electron_context_menu": {
"cut": "قص",
"copy": "نسخ",
"copy-as-markdown": "نسخ كـ Markdown",
"paste": "لصق",
"copy-link": "نسخ الرابط",
"add-term-to-dictionary": "اضافة \"{{term}}\" الى القاموس",

View File

@ -1760,6 +1760,7 @@
"add-term-to-dictionary": "将 \"{{term}}\" 添加到字典",
"cut": "剪切",
"copy": "复制",
"copy-as-markdown": "复制为 Markdown",
"copy-link": "复制链接",
"paste": "粘贴",
"paste-as-plain-text": "以纯文本粘贴",

View File

@ -1729,6 +1729,7 @@
"add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen",
"cut": "Ausschneiden",
"copy": "Kopieren",
"copy-as-markdown": "Als Markdown kopieren",
"copy-link": "Link kopieren",
"paste": "Einfügen",
"paste-as-plain-text": "Als unformatierten Text einfügen",

View File

@ -1810,6 +1810,7 @@
"add-term-to-dictionary": "Add \"{{term}}\" to dictionary",
"cut": "Cut",
"copy": "Copy",
"copy-as-markdown": "Copy as Markdown",
"copy-link": "Copy link",
"paste": "Paste",
"paste-as-plain-text": "Paste as plain text",

View File

@ -1778,6 +1778,7 @@
"add-term-to-dictionary": "Agregar \"{{term}}\" al diccionario",
"cut": "Cortar",
"copy": "Copiar",
"copy-as-markdown": "Copiar como markdown",
"copy-link": "Copiar enlace",
"paste": "Pegar",
"paste-as-plain-text": "Pegar como texto plano",

View File

@ -1689,6 +1689,7 @@
"add-term-to-dictionary": "Ajouter «{{term}}» au dictionnaire",
"cut": "Couper",
"copy": "Copier",
"copy-as-markdown": "Copier en markdown",
"copy-link": "Copier le lien",
"paste": "Coller",
"paste-as-plain-text": "Coller comme texte brut",

View File

@ -1808,6 +1808,7 @@
"add-term-to-dictionary": "Cuir \"{{term}}\" leis an bhfoclóir",
"cut": "Gearr",
"copy": "Cóipeáil",
"copy-as-markdown": "Cóipeáil mar markdown",
"copy-link": "Cóipeáil nasc",
"paste": "Greamaigh",
"paste-as-plain-text": "Greamaigh mar théacs simplí",

View File

@ -1810,6 +1810,7 @@
"add-term-to-dictionary": "\"{{term}}\" को डिक्शनरी में जोड़ें",
"cut": "कट (Cut)",
"copy": "कॉपी (Copy)",
"copy-as-markdown": "Markdown के रूप में कॉपी करें",
"copy-link": "लिंक कॉपी करें",
"paste": "पेस्ट (Paste)",
"paste-as-plain-text": "प्लेन टेक्स्ट की तरह पेस्ट करें",

View File

@ -334,6 +334,7 @@
"electron_context_menu": {
"cut": "Taglia",
"copy": "Copia",
"copy-as-markdown": "Copia come markdown",
"paste": "Incolla",
"copy-link": "Copia collegamento",
"paste-as-plain-text": "Incolla come testo semplice",

View File

@ -1353,6 +1353,7 @@
"add-term-to-dictionary": "辞書に \"{{term}}\" を追加",
"cut": "切り取り",
"copy": "コピー",
"copy-as-markdown": "Markdownとしてコピー",
"copy-link": "リンクをコピー",
"paste": "貼り付け",
"paste-as-plain-text": "プレーンテキストで貼り付け",

View File

@ -614,6 +614,7 @@
"electron_context_menu": {
"cut": "Wytnij",
"copy": "Kopiuj",
"copy-as-markdown": "Kopiuj jako markdown",
"copy-link": "Kopiuj link",
"paste": "Wklej",
"paste-as-plain-text": "Wklej jako zwykły tekst",

View File

@ -1774,6 +1774,7 @@
"add-term-to-dictionary": "Adicionar \"{{term}}\" ao dicionário",
"cut": "Cortar",
"copy": "Copiar",
"copy-as-markdown": "Copiar como markdown",
"copy-link": "Copiar ligação",
"paste": "Colar",
"paste-as-plain-text": "Colar como texto sem formatação",

View File

@ -1637,6 +1637,7 @@
"add-term-to-dictionary": "Adicionar \"{{term}}\" ao dicionário",
"cut": "Cortar",
"copy": "Copiar",
"copy-as-markdown": "Copiar como markdown",
"copy-link": "Copiar link",
"paste": "Colar",
"paste-as-plain-text": "Colar como texto sem formatação",

View File

@ -1724,6 +1724,7 @@
"electron_context_menu": {
"add-term-to-dictionary": "Adaugă „{{term}}” în dicționar",
"copy": "Copiază",
"copy-as-markdown": "Copiază ca markdown",
"copy-link": "Copiază legătura",
"cut": "Decupează",
"paste": "Lipește",

View File

@ -708,6 +708,7 @@
"paste": "Вставить",
"copy-link": "Скопировать ссылку",
"copy": "Скопировать",
"copy-as-markdown": "Копировать как Markdown",
"cut": "Вырезать",
"search_online": "Поиск \"{{term}}\" в {{searchEngine}}",
"add-term-to-dictionary": "Добавить \"{{term}}\" в словарь",

View File

@ -1722,6 +1722,7 @@
"add-term-to-dictionary": "將 \"{{term}}\" 新增至字典",
"cut": "剪下",
"copy": "複製",
"copy-as-markdown": "複製為 Markdown",
"copy-link": "複製連結",
"paste": "貼上",
"paste-as-plain-text": "以純文字貼上",

View File

@ -1533,6 +1533,7 @@
"add-term-to-dictionary": "Додати \"{{term}}\" до словника",
"cut": "Вирізати",
"copy": "Копіювати",
"copy-as-markdown": "Копіювати як Markdown",
"copy-link": "Копіювати посилання",
"paste": "Вставити",
"paste-as-plain-text": "Вставити як звичайний текст",

View File

@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { buildNote, buildNotes } from "../../../test/easy-froca.js";
import { buildEvent, buildEvents } from "./event_builder.js";
import { LOCALE_MAPPINGS } from "./index.js";
@ -148,7 +148,7 @@ describe("Promoted attributes", () => {
expect(event).toHaveLength(1);
expect(event[0]?.promotedAttributes).toMatchObject([
[ "assignee", "Target note" ]
])
]);
});
it("supports start time and end time", async () => {
@ -177,6 +177,86 @@ describe("Promoted attributes", () => {
});
describe("Recurrence", () => {
it("supports valid recurrence without end date", async () => {
const noteIds = buildNotes([
{
title: "Recurring Event",
"#startDate": "2025-05-05",
"#recurrence": "FREQ=DAILY;COUNT=5"
}
]);
const events = await buildEvents(noteIds);
expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({
title: "Recurring Event",
start: "2025-05-05",
});
expect(events[0].rrule).toContain("DTSTART:20250505");
expect(events[0].rrule).toContain("FREQ=DAILY;COUNT=5");
expect(events[0].end).toBeUndefined();
});
it("supports recurrence with start and end time (duration calculated)", async () => {
const noteIds = buildNotes([
{
title: "Timed Recurring Event",
"#startDate": "2025-05-05",
"#startTime": "13:00",
"#endTime": "15:30",
"#recurrence": "FREQ=WEEKLY;COUNT=3"
}
]);
const events = await buildEvents(noteIds);
expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({
title: "Timed Recurring Event",
start: "2025-05-05T13:00:00",
duration: "02:30"
});
expect(events[0].rrule).toContain("DTSTART:20250505T130000");
expect(events[0].end).toBeUndefined();
});
it("removes end date when recurrence is valid", async () => {
const noteIds = buildNotes([
{
title: "Recurring With End",
"#startDate": "2025-05-05",
"#endDate": "2025-05-07",
"#recurrence": "FREQ=DAILY;COUNT=2"
}
]);
const events = await buildEvents(noteIds);
expect(events).toHaveLength(1);
expect(events[0].rrule).toBeDefined();
expect(events[0].end).toBeUndefined();
});
it("writes to console on invalid recurrence rule", async () => {
const noteIds = buildNotes([
{
title: "Invalid Recurrence",
"#startDate": "2025-05-05",
"#recurrence": "RRULE:FREQ=INVALID"
}
]);
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
await buildEvents(noteIds);
const calledWithInvalid = consoleSpy.mock.calls.some(call =>
call[0].includes("has an invalid #recurrence string")
);
expect(calledWithInvalid).toBe(true);
consoleSpy.mockRestore();
});
});
describe("Building locales", () => {
it("every language has a locale defined", async () => {
for (const { id, contentOnly } of LOCALES) {

View File

@ -1,17 +1,22 @@
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
import { dayjs } from "@triliumnext/commons";
import clsx from "clsx";
import { start } from "repl";
import * as rruleLib from 'rrule';
import FNote from "../../../entities/fnote";
import froca from "../../../services/froca";
import server from "../../../services/server";
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
import toastService from "../../../services/toast";
import { getCustomisableLabel, getMonthsInDateRange } from "./utils";
interface Event {
startDate: string,
endDate?: string | null,
startTime?: string | null,
endTime?: string | null,
isArchived?: boolean;
isArchived?: boolean,
recurrence?: string | null;
}
export async function buildEvents(noteIds: string[]) {
@ -28,8 +33,17 @@ export async function buildEvents(noteIds: string[]) {
const endDate = getCustomisableLabel(note, "endDate", "calendar:endDate");
const startTime = getCustomisableLabel(note, "startTime", "calendar:startTime");
const endTime = getCustomisableLabel(note, "endTime", "calendar:endTime");
const recurrence = getCustomisableLabel(note, "recurrence", "calendar:recurrence");
const isArchived = note.hasLabel("archived");
events.push(await buildEvent(note, { startDate, endDate, startTime, endTime, isArchived }));
try {
events.push(await buildEvent(note, { startDate, endDate, startTime, endTime, recurrence, isArchived }));
} catch (error) {
if (error instanceof Error) {
const errorMessage = error.message;
toastService.showError(errorMessage);
console.error(errorMessage);
}
}
}
return events.flat();
@ -59,6 +73,7 @@ export async function buildEventsForCalendar(note: FNote, e: EventSourceFuncArg)
events.push(await buildEvent(dateNote, { startDate }));
if (dateNote.hasChildren()) {
const childNoteIds = await dateNote.getSubtreeNoteIds();
for (const childNoteId of childNoteIds) {
@ -79,7 +94,7 @@ export async function buildEventsForCalendar(note: FNote, e: EventSourceFuncArg)
return events.flat();
}
export async function buildEvent(note: FNote, { startDate, endDate, startTime, endTime, isArchived }: Event) {
export async function buildEvent(note: FNote, { startDate, endDate, startTime, endTime, recurrence, isArchived }: Event) {
const customTitleAttributeName = note.getLabelValue("calendar:title");
const titles = await parseCustomTitle(customTitleAttributeName, note);
const colorClass = note.getColorClass();
@ -98,9 +113,10 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
startDate = (startTime ? `${startDate}T${startTime}:00` : startDate);
if (!startTime) {
const endDateOffset = offsetDate(endDate ?? startDate, 1);
if (endDateOffset) {
endDate = formatDateToLocalISO(endDateOffset);
if (endDate) {
endDate = dayjs(endDate).add(1, "day").format("YYYY-MM-DD");
} else if (startDate) {
endDate = dayjs(startDate).add(1, "day").format("YYYY-MM-DD");
}
}
@ -118,6 +134,30 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
if (endDate) {
eventData.end = endDate;
}
if (recurrence) {
// Generate rrule string
const rruleString = `DTSTART:${dayjs(startDate).format("YYYYMMDD[T]HHmmss")}\n${recurrence}`;
// Validate rrule string
let rruleValid = true;
try {
rruleLib.rrulestr(rruleString, { forceset: true }) as rruleLib.RRuleSet;
} catch {
rruleValid = false;
}
if (rruleValid) {
delete eventData.end;
eventData.rrule = rruleString;
if (endDate){
const duration = dayjs.duration(dayjs(endDate).diff(dayjs(startDate)));
eventData.duration = duration.format("HH:mm");
}
} else {
throw new Error(`Note "${note.noteId} ${note.title}" has an invalid #recurrence string ${recurrence}. Excluding...`);
}
}
events.push(eventData);
}
return events;

View File

@ -252,6 +252,7 @@ function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
plugins.push((await import("@fullcalendar/timegrid")).default);
plugins.push((await import("@fullcalendar/list")).default);
plugins.push((await import("@fullcalendar/multimonth")).default);
plugins.push((await import("@fullcalendar/rrule")).default);
if (isEditable || isCalendarRoot) {
plugins.push((await import("@fullcalendar/interaction")).default);
}

View File

@ -185,6 +185,18 @@
at which the event ends (in relation with <code spellcheck="false">endDate</code> if
present, or <code spellcheck="false">startDate</code>).</td>
</tr>
<tr>
<td><code spellcheck="false">#recurrence</code>
</td>
<td>This is an optional CalDAV <code spellcheck="false">RRULE</code> string
that if present, determines whether a task should repeat or not. Note that
it does not include the <code spellcheck="false">DTSTART</code> attribute,
which is derived from the <code spellcheck="false">#startDate</code> and
<code
spellcheck="false">#startTime</code>directly. For examples of valid <code spellcheck="false">RRULE</code> strings
see <a href="https://icalendar.org/rrule-tool.html">https://icalendar.org/rrule-tool.html</a>
</td>
</tr>
<tr>
<td><code spellcheck="false">#color</code>
</td>
@ -282,6 +294,65 @@
<p>When not used in a Journal, the calendar is recursive. That is, it will
look for events not just in its child notes but also in the children of
these child notes.</p>
<p>&nbsp;</p>
<h2>Recurrence</h2>
<p>The built in calendar view also supports repeating tasks. If a child note
of the calendar has a #recurrence label with a valid recurrence, that event
will repeat on the calendar according to the recurrence string.&nbsp;</p>
<p>For example, to make a note repeat on the calendar:</p>
<ul>
<li>
<p>Every Day - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=1"</code>
</p>
</li>
<li>
<p>Every 3 days - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=3"</code>
</p>
</li>
<li>
<p>Every week - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=1"</code>
</p>
</li>
<li>
<p>Every 2 weeks on Monday, Wednesday and Friday - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR"</code>
</p>
</li>
<li>
<p>Every 3 months - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=3"</code>
</p>
</li>
<li>
<p>Every 2 months on the First Sunday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU"</code>
</p>
</li>
<li>
<p>Every month on the Last Friday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=1;BYDAY=-1FR"</code>
</p>
</li>
<li>
<p>And so on.</p>
</li>
</ul>
<p>For other examples of valid <code spellcheck="false">RRULE</code> strings
see <a href="https://icalendar.org/rrule-tool.html">https://icalendar.org/rrule-tool.html</a>
</p>
<p>Note that the recurrence string does not include the <code spellcheck="false">DTSTART</code> attribute
as defined in the iCAL specifications. This is derived directly from the
<code
spellcheck="false">startDate</code>and <code spellcheck="false">startTime</code> attributes</p>
<p>If you want to override the label the calendar uses to fetch the recurrence
string, you can use the <code spellcheck="false">#calendar:recurrence</code> attribute.
For example, you can set <code spellcheck="false">#calendar:recurrence=taskRepeats</code>.
Then you can set your recurrence string like <code spellcheck="false">#taskRepeats="FREQ=DAILY;INTERVAL=1"</code>
</p>
<p>Also note that the recurrence label can be made promoted as with the start
and end dates.&nbsp;</p>
<aside class="admonition warning">
<p>If the recurrence string is not valid, a toast will be shown with the
note ID and title of the note with the erroneous recurrence message. This
note will not be added to the calendar</p>
</aside>
<p>&nbsp;</p>
<h2>Use-cases</h2>
<h3>Using with the Journal / calendar</h3>
<p>It is possible to integrate the calendar view into the Journal with day

View File

@ -72,11 +72,12 @@ For each note of the calendar, the following attributes can be used:
| `#endDate` | Similar to `startDate`, mentions the end date if the event spans across multiple days. The date is inclusive, so the end day is also considered. The attribute can be missing for single-day events. |
| `#startTime` | The time the event starts at. If this value is missing, then the event is considered a full-day event. The format is `HH:MM` (hours in 24-hour format and minutes). |
| `#endTime` | Similar to `startTime`, it mentions the time at which the event ends (in relation with `endDate` if present, or `startDate`). |
| `#recurrence` | This is an optional CalDAV `RRULE` string that if present, determines whether a task should repeat or not. Note that it does not include the `DTSTART` attribute, which is derived from the `#startDate` and `#startTime` directly. For examples of valid `RRULE` strings see [https://icalendar.org/rrule-tool.html](https://icalendar.org/rrule-tool.html) |
| `#color` | Displays the event with a specified color (named such as `red`, `gray` or hex such as `#FF0000`). This will also change the color of the note in other places such as the note tree. |
| `#calendar:color` | **❌️ Removed since v0.100.0. Use** `**#color**` **instead.**  <br> <br>Similar to `#color`, but applies the color only for the event in the calendar and not for other places such as the note tree. |
| `#calendar:color` | **❌️ Removed since v0.100.0. Use** `**#color**` **instead.**   <br> <br>Similar to `#color`, but applies the color only for the event in the calendar and not for other places such as the note tree. |
| `#iconClass` | If present, the icon of the note will be displayed to the left of the event title. |
| `#calendar:title` | Changes the title of an event to point to an attribute of the note other than the title, can either a label or a relation (without the `#` or `~` symbol). See _Use-cases_ for more information. |
| `#calendar:displayedAttributes` | Allows displaying the value of one or more attributes in the calendar like this:       <br> <br>![](7_Calendar_image.png)      <br> <br>`#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"`     <br> <br>It can also be used with relations, case in which it will display the title of the target note:      <br> <br>`~assignee=@My assignee #calendar:displayedAttributes="assignee"` |
| `#calendar:displayedAttributes` | Allows displaying the value of one or more attributes in the calendar like this:        <br> <br>![](7_Calendar_image.png)       <br> <br>`#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"`      <br> <br>It can also be used with relations, case in which it will display the title of the target note:       <br> <br>`~assignee=@My assignee #calendar:displayedAttributes="assignee"` |
| `#calendar:startDate` | Allows using a different label to represent the start date, other than `startDate` (e.g. `expiryDate`). The label name **must not be** prefixed with `#`. If the label is not defined for a note, the default will be used instead. |
| `#calendar:endDate` | Similar to `#calendar:startDate`, allows changing the attribute which is being used to read the end date. |
| `#calendar:startTime` | Similar to `#calendar:startDate`, allows changing the attribute which is being used to read the start time. |
@ -102,6 +103,32 @@ This will result in:
When not used in a Journal, the calendar is recursive. That is, it will look for events not just in its child notes but also in the children of these child notes.
## Recurrence
The built in calendar view also supports repeating tasks. If a child note of the calendar has a #recurrence label with a valid recurrence, that event will repeat on the calendar according to the recurrence string. 
For example, to make a note repeat on the calendar:
* Every Day - `#recurrence="FREQ=DAILY;INTERVAL=1"`
* Every 3 days - `#recurrence="FREQ=DAILY;INTERVAL=3"`
* Every week - `#recurrence="FREQ=WEEKLY;INTERVAL=1"`
* Every 2 weeks on Monday, Wednesday and Friday - `#recurrence="FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR"`
* Every 3 months - `#recurrence="FREQ=MONTHLY;INTERVAL=3"`
* Every 2 months on the First Sunday - `#recurrence="FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU"`
* Every month on the Last Friday - `#recurrence="FREQ=MONTHLY;INTERVAL=1;BYDAY=-1FR"`
* And so on.
For other examples of valid `RRULE` strings see [https://icalendar.org/rrule-tool.html](https://icalendar.org/rrule-tool.html)
Note that the recurrence string does not include the `DTSTART` attribute as defined in the iCAL specifications. This is derived directly from the `startDate` and `startTime` attributes
If you want to override the label the calendar uses to fetch the recurrence string, you can use the `#calendar:recurrence` attribute. For example, you can set `#calendar:recurrence=taskRepeats`. Then you can set your recurrence string like `#taskRepeats="FREQ=DAILY;INTERVAL=1"`
Also note that the recurrence label can be made promoted as with the start and end dates. 
> [!WARNING]
> If the recurrence string is not valid, a toast will be shown with the note ID and title of the note with the erroneous recurrence message. This note will not be added to the calendar
## Use-cases
### Using with the Journal / calendar

52
pnpm-lock.yaml generated
View File

@ -200,6 +200,9 @@ importers:
'@fullcalendar/multimonth':
specifier: 6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
'@fullcalendar/rrule':
specifier: 6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)(rrule@2.8.1)
'@fullcalendar/timegrid':
specifier: 6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
@ -323,6 +326,9 @@ importers:
reveal.js:
specifier: 5.2.1
version: 5.2.1
rrule:
specifier: 2.8.1
version: 2.8.1
svg-pan-zoom:
specifier: 3.6.2
version: 3.6.2
@ -3392,6 +3398,12 @@ packages:
peerDependencies:
'@fullcalendar/core': ~6.1.20
'@fullcalendar/rrule@6.1.20':
resolution: {integrity: sha512-5Awk7bmaA97hSZRpIBehenXkYreVIvx8nnaMFZ/LDGRuK1mgbR4vSUrDTvVU+oEqqKnj/rqMBByWqN5NeehQxw==}
peerDependencies:
'@fullcalendar/core': ~6.1.20
rrule: ^2.6.0
'@fullcalendar/timegrid@6.1.20':
resolution: {integrity: sha512-4H+/MWbz3ntA50lrPif+7TsvMeX3R1GSYjiLULz0+zEJ7/Yfd9pupZmAwUs/PBpA6aAcFmeRr0laWfcz1a9V1A==}
peerDependencies:
@ -13131,6 +13143,9 @@ packages:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
rrule@2.8.1:
resolution: {integrity: sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==}
rrweb-cssom@0.8.0:
resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
@ -16043,6 +16058,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.4.0
'@ckeditor/ckeditor5-upload': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-ai@47.4.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
@ -16183,6 +16200,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
'@ckeditor/ckeditor5-widget': 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-cloud-services@47.4.0':
dependencies:
@ -16397,6 +16416,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-inline@47.4.0':
dependencies:
@ -16512,6 +16533,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-font@47.4.0':
dependencies:
@ -16586,6 +16609,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
'@ckeditor/ckeditor5-widget': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-html-embed@47.4.0':
dependencies:
@ -16770,8 +16795,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-merge-fields@47.4.0':
dependencies:
@ -16794,8 +16817,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-operations-compressor@47.4.0':
dependencies:
@ -16850,8 +16871,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
'@ckeditor/ckeditor5-widget': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-pagination@47.4.0':
dependencies:
@ -16959,8 +16978,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-slash-command@47.4.0':
dependencies:
@ -16973,8 +16990,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-source-editing-enhanced@47.4.0':
dependencies:
@ -17022,8 +17037,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-table@47.4.0':
dependencies:
@ -17036,8 +17049,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-template@47.4.0':
dependencies:
@ -17150,8 +17161,6 @@ snapshots:
'@ckeditor/ckeditor5-engine': 47.4.0
'@ckeditor/ckeditor5-utils': 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-widget@47.4.0':
dependencies:
@ -17171,8 +17180,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.4.0
ckeditor5: 47.4.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@codemirror/autocomplete@6.18.6':
dependencies:
@ -18514,6 +18521,11 @@ snapshots:
'@fullcalendar/core': 6.1.20
'@fullcalendar/daygrid': 6.1.20(@fullcalendar/core@6.1.20)
'@fullcalendar/rrule@6.1.20(@fullcalendar/core@6.1.20)(rrule@2.8.1)':
dependencies:
'@fullcalendar/core': 6.1.20
rrule: 2.8.1
'@fullcalendar/timegrid@6.1.20(@fullcalendar/core@6.1.20)':
dependencies:
'@fullcalendar/core': 6.1.20
@ -30286,6 +30298,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
rrule@2.8.1:
dependencies:
tslib: 2.8.1
rrweb-cssom@0.8.0:
optional: true