Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Advanced Recurring Rules to the Event Create/Edit Event modal #500

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
511 changes: 335 additions & 176 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json",
"lint": "prettier --check 'src/**/*.ts*'",
"fix-lint": "prettier --write 'src/**/*.ts*'",
"lint": "prettier --check ./src/**/*.ts*",
"fix-lint": "prettier --write ./src/**/*.ts*",
"compile": "tsc -noEmit --skipLibCheck",
"prepare": "husky install",
"test": "jest --ci --silent=true",
Expand Down Expand Up @@ -43,7 +43,7 @@
"ts-jest": "^29.0.3",
"tslib": "2.3.1",
"typescript": "4.4.4",
"zod-fast-check": "^0.9.0"
"zod-fast-check": "^0.10.0"
},
"dependencies": {
"@fullcalendar/core": "^5.10.1",
Expand Down
112 changes: 112 additions & 0 deletions src/calendars/FullNoteCalendar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,116 @@ describe("Note Calendar Tests", () => {
// .calls[0];
// expect(file.path).toBe(join("events", filename));
// });

it("creates an rrule event", async () => {
const obsidian = makeApp(MockAppBuilder.make().done());
const calendar = new FullNoteCalendar(obsidian, color, dirName);
const event = {
type: "rrule",
title: "Test Event",
startDate: "2023-09-12",
rrule: "DTSTART:20230912T110000Z\nRRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU",
skipDates: ["2023-09-19"],
allDay: false,
startTime: "11:00",
endTime: "12:30",
};

(obsidian.create as jest.Mock).mockReturnValue({
path: join(dirName, "2022-01-01 Test Event.md"),
});
const { lineNumber } = await calendar.createEvent(parseEvent(event));
expect(lineNumber).toBeUndefined();
expect(obsidian.create).toHaveBeenCalledTimes(1);
const returns = (obsidian.create as jest.Mock).mock.calls[0];
console.warn(returns);
expect(returns).toMatchInlineSnapshot(`
[
"events/(every week on Tuesday for 30 times) Test Event.md",
"---
title: Test Event
allDay: false
startTime: 11:00
endTime: 12:30
type: rrule
startDate: 2023-09-12
rrule: |-
DTSTART:20230912T110000Z
RRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU
skipDates: [2023-09-19]
---
",
]
`);
});
it("modifies an rrule event", async () => {
const rawEvent = {
type: "rrule",
title: "Test Event",
startDate: "2023-09-12",
rrule: "DTSTART:20230912T110000Z\nRRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU",
skipDates: ["2023-09-19"],
allDay: false,
startTime: "11:00",
endTime: "12:30",
};
const event = parseEvent(rawEvent);
const filename = "(every week on Tuesday for 30 times) Test Event.md";
const obsidian = makeApp(
MockAppBuilder.make()
.folder(
new MockAppBuilder("events").file(
filename,
new FileBuilder().frontmatter(event)
)
)
.done()
);
const calendar = new FullNoteCalendar(obsidian, color, dirName);

const firstFile = obsidian.getAbstractFileByPath(
join("events", filename)
) as TFile;

const contents = await obsidian.read(firstFile);

const mockFn = jest.fn();
await calendar.modifyEvent(
{ path: join("events", filename), lineNumber: undefined },
// @ts-ignore
parseEvent({
...rawEvent,
rrule: "DTSTART:20230912T110000Z\nRRULE:FREQ=MONTHLY;COUNT=5;INTERVAL=2;BYDAY=TU;BYSETPOS=1",
}),
mockFn
);
const newFilename =
"events/(every 2 months on Tuesday for 5 times) Test Event.md";
// TODO: make the third param a mock that we can inspect
const newLoc = mockFn.mock.calls[0][0];
expect(newLoc.file.path).toBe(newFilename);
expect(newLoc.lineNumber).toBeUndefined();

expect(obsidian.rewrite).toHaveReturnedTimes(1);
const [file, rewriteCallback] = (obsidian.rewrite as jest.Mock).mock
.calls[0];
expect(file.path).toBe(join("events", filename));

expect(rewriteCallback(contents)).toMatchInlineSnapshot(`
"---
title: Test Event
allDay: false
startTime: 11:00
endTime: 12:30
type: rrule
startDate: 2023-09-12
rrule: |-
DTSTART:20230912T110000Z
RRULE:FREQ=MONTHLY;COUNT=5;INTERVAL=2;BYDAY=TU;BYSETPOS=1
RRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU
skipDates: [2023-09-19]
---
"
`);
});
});
25 changes: 23 additions & 2 deletions src/calendars/FullNoteCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,17 @@ function stringifyYamlLine(
k: string | number | symbol,
v: PrintableAtom
): string {
return `${String(k)}: ${stringifyYamlAtom(v)}`;
let stringifiedAtom: string;
if (k === "rrule" && typeof v === "string") {
stringifiedAtom = "|-";

const indentation = "\n ";
const replacedValue = v.replace("\n", indentation);
stringifiedAtom += `${indentation}${replacedValue}`;
} else {
stringifiedAtom = stringifyYamlAtom(v);
}
return `${String(k)}: ${stringifiedAtom}`;
}

function newFrontmatter(fields: Partial<OFCEvent>): string {
Expand Down Expand Up @@ -109,7 +119,18 @@ function modifyFrontmatterString(
const linesAdded: Set<string | number | symbol> = new Set();
// Modify rows in-place.
for (let i = 0; i < frontmatter.length; i++) {
const line: string = frontmatter[i];
let line: string = frontmatter[i];
if (line.endsWith("|-")) {
let j = i + 1;
while (
j < frontmatter.length &&
frontmatter[j].startsWith(" ")
) {
line += `\n${frontmatter[j]}`;
i++;
j++;
}
}
const obj: Record<any, any> | null = parseYaml(line);
if (!obj) {
continue;
Expand Down
77 changes: 42 additions & 35 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,47 @@ import CalDAVCalendar from "./calendars/CalDAVCalendar";

export default class FullCalendarPlugin extends Plugin {
settings: FullCalendarSettings = DEFAULT_SETTINGS;
cache: EventCache = new EventCache({
local: (info) =>
info.type === "local"
? new FullNoteCalendar(
new ObsidianIO(this.app),
info.color,
info.directory
)
: null,
dailynote: (info) =>
info.type === "dailynote"
? new DailyNoteCalendar(
new ObsidianIO(this.app),
info.color,
info.heading
)
: null,
ical: (info) =>
info.type === "ical" ? new ICSCalendar(info.color, info.url) : null,
caldav: (info) =>
info.type === "caldav"
? new CalDAVCalendar(
info.color,
info.name,
{
type: "basic",
username: info.username,
password: info.password,
},
info.url,
info.homeUrl
)
: null,
FOR_TEST_ONLY: () => null,
});
cache: EventCache = (() => {
console.debug("Event Cache Creation");

return new EventCache({
local: (info) =>
info.type === "local"
? new FullNoteCalendar(
new ObsidianIO(this.app),
info.color,
info.directory
)
: null,
dailynote: (info) =>
info.type === "dailynote"
? new DailyNoteCalendar(
new ObsidianIO(this.app),
info.color,
info.heading
)
: null,
ical: (info) =>
info.type === "ical"
? new ICSCalendar(info.color, info.url)
: null,
caldav: (info) =>
info.type === "caldav"
? new CalDAVCalendar(
info.color,
info.name,
{
type: "basic",
username: info.username,
password: info.password,
},
info.url,
info.homeUrl
)
: null,
FOR_TEST_ONLY: () => null,
});
})();

renderCalendar = renderCalendar;
processFrontmatter = toEventInput;
Expand All @@ -78,6 +84,7 @@ export default class FullCalendarPlugin extends Plugin {
}
}
async onload() {
console.debug("On Load called...");
await this.loadSettings();

this.cache.reset(this.settings.calendarSources);
Expand Down
Loading