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

Feature - Remote Notes! #222

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion src/fullcalendar_interop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function toEventInput(
daysOfWeek: frontmatter.daysOfWeek.map((c) => DAYS.indexOf(c)),
startRecur: frontmatter.startRecur,
endRecur: frontmatter.endRecur,
extendedProps: { isTask: false },
extendedProps: { isTask: false, remoteReplace: frontmatter.replaceRemote },
};
if (!frontmatter.allDay) {
event = {
Expand Down Expand Up @@ -146,6 +146,7 @@ export function toEventInput(
frontmatter.completed !== undefined &&
frontmatter.completed !== null,
taskCompleted: frontmatter.completed,
remoteReplace: frontmatter.replaceRemote
},
};
} else {
Expand All @@ -158,6 +159,7 @@ export function toEventInput(
frontmatter.completed !== undefined &&
frontmatter.completed !== null,
taskCompleted: frontmatter.completed,
remoteReplace: frontmatter.replaceRemote
},
};
}
Expand Down
2 changes: 0 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { CalendarView, FULL_CALENDAR_VIEW_TYPE } from "./ui/view";
import { renderCalendar } from "./ui/calendar";

import { EventModal } from "./ui/modal";
import { toEventInput } from "./fullcalendar_interop";
import {
DEFAULT_SETTINGS,
FullCalendarSettings,
Expand All @@ -15,7 +14,6 @@ export default class FullCalendarPlugin extends Plugin {
settings: FullCalendarSettings = DEFAULT_SETTINGS;

renderCalendar = renderCalendar;
processFrontmatter = toEventInput;

async activateView() {
this.app.workspace.detachLeavesOfType(FULL_CALENDAR_VIEW_TYPE);
Expand Down
5 changes: 5 additions & 0 deletions src/models/Event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Calendar, EventInput } from "@fullcalendar/core";
import { MetadataCache, Vault, WorkspaceLeaf } from "obsidian";
import { parseFrontmatter } from "src/frontmatter";
import { CalendarSource, EventFrontmatter } from "src/types";
import { getColors } from "./util";

import { dir } from "console";
import { MetadataCache, TFile, Vault, WorkspaceLeaf } from "obsidian";
import { toEventInput } from "src/fullcalendar_interop";
Expand Down
15 changes: 12 additions & 3 deletions src/models/IcsSource.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { EventSourceInput } from "@fullcalendar/core";
import { request } from "obsidian";
import { request, Vault } from "obsidian";
import FullCalendarPlugin from "src/main";
import { FCError, ICalSource } from "src/types";
import { IcalExpander } from "vendor/fullcalendar-ical/ical-expander/IcalExpander";
import {
expandICalEvents,
makeICalExpander,
} from "vendor/fullcalendar-ical/icalendar";
import { EventSource } from "./EventSource";
import { RemoteReplaceEvent } from "./RemoteReplaceEvent";
import { getColors } from "./util";

export class IcsSource extends EventSource {
info: ICalSource;
constructor(info: ICalSource) {
plugin: FullCalendarPlugin;
vault: Vault;
constructor(info: ICalSource, plugin: FullCalendarPlugin, vault: Vault) {
super();
this.info = info;
this.plugin = plugin;
this.vault = vault;
}

async toApi(): Promise<EventSourceInput | FCError> {
let $ = this;
let url = this.info.url;
if (url.startsWith("webcal")) {
url = "https" + url.slice("webcal".length);
Expand Down Expand Up @@ -51,7 +58,9 @@ export class IcsSource extends EventSource {
start,
end,
});
return events;
return events.filter((ev)=> {
return !RemoteReplaceEvent.checkNoteCreateP(ev, $.plugin, $.vault);
});
},
editable: false,
...getColors(this.info.color),
Expand Down
14 changes: 14 additions & 0 deletions src/models/NoteEvent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { MetadataCache, TFile, Vault, WorkspaceLeaf } from "obsidian";
import { modifyFrontmatter } from "src/frontmatter";
import { EventFrontmatter, FCError, validateFrontmatter } from "src/types";
import { LocalEvent } from "./Event";
import { modifyFrontmatter } from "src/serialization/frontmatter";
import { OFCEvent, FCError, validateEvent } from "src/types";
import { basenameFromEvent, LocalEvent } from "./Event";

function basenameFromEvent(event: EventFrontmatter): string {
switch (event.type) {
case "single":
case undefined:
return `${event.date} ${event.title}` + (event.startTime && event.endTime ? ` ${event.startTime}-${event.endTime}` : '');
case "recurring":
return `(Every ${event.daysOfWeek.join(",")}) ${event.title})`;
}
}


export class NoteEvent extends LocalEvent {
get path(): string {
return `${this.directory}/${this.filename}`;
Expand Down
6 changes: 5 additions & 1 deletion src/models/NoteSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FCError, LocalCalendarSource } from "src/types";
import { NoteEvent } from "./NoteEvent";
import { EventSource } from "./EventSource";
import { getColors } from "./util";
import { RemoteReplaceEvent } from "./RemoteReplaceEvent";

export class NoteSource extends EventSource {
info: LocalCalendarSource;
Expand Down Expand Up @@ -31,7 +32,10 @@ export class NoteSource extends EventSource {
let events: EventInput[] = [];
for (let file of eventFolder.children) {
if (file instanceof TFile) {
let event = NoteEvent.fromFile(this.cache, this.vault, file);
let event = RemoteReplaceEvent.fromFile(this.cache, this.vault, file);
if (!event){
event = NoteEvent.fromFile(this.cache, this.vault, file);
}
if (event) {
let calEvent = event.toCalendarEvent();
if (calEvent) {
Expand Down
143 changes: 143 additions & 0 deletions src/models/RemoteReplaceEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { EventInput } from "@fullcalendar/core";
import { MetadataCache, TFile, Vault } from "obsidian";
import { modifyFrontmatter } from "../serialization/frontmatter";
import FullCalendarPlugin from "src/main";
import { EventFrontmatter, FCError, LocalCalendarSource, OFCEvent, validateEvent } from "src/types";
import { NoteEvent } from "./NoteEvent";

function basenameFromEvent(event: EventFrontmatter): string {
switch (event.type) {
case "single":
case undefined:
return `${event.date} ${event.title}` + (event.startTime && event.endTime ? ` ${event.startTime}-${event.endTime}` : '');
case "recurring":
return `(Every ${event.daysOfWeek.join(",")}) ${event.title})`;
}
}

export class RemoteReplaceEvent extends NoteEvent {

constructor(
cache: MetadataCache,
vault: Vault,
data: OFCEvent,
{ directory, filename }: { directory: string; filename: string }
) {
super(cache, vault, data, {directory, filename});
}

static fromFile(
cache: MetadataCache,
vault: Vault,
file: TFile
): NoteEvent | null {
let data = validateEvent(cache.getFileCache(file)?.frontmatter);

if (!data) return null;
if (!data.replaceRemote) return null;
if (!data.title) {
data.title = file.basename;
}

return new NoteEvent(cache, vault, data, {
directory: file.parent.path,
filename: file.name,
});
}

static async create(
cache: MetadataCache,
vault: Vault,
directory: string,
data: EventFrontmatter
): Promise<NoteEvent> {
const filename = basenameFromEvent(data).replace(/\*|"|\\|\/|<|>|:|\||\?/g, '_');
const path = `${directory}/${filename}.md`;
if (vault.getAbstractFileByPath(path)) {
throw new FCError(`File with name '${filename}' already exists`);
}
const file = await vault.create(path, "");
await modifyFrontmatter(vault, file, data);

return new RemoteReplaceEvent(cache, vault, data, {
directory: file.parent.path,
filename: file.name,
});
}

static fromPath(
cache: MetadataCache,
vault: Vault,
path: string
): NoteEvent {
const file = vault.getAbstractFileByPath(path);
if (!(file instanceof TFile)) {
throw new FCError(`File not found at path: ${path}`);
}
const event = this.fromFile(cache, vault, file);
if (!event) {
throw new FCError(
`Could not construct event from file at path: ${path}`
);
}
return event;
}

async setData(data: EventFrontmatter): Promise<void> {
let file = this.file;
let filename = basenameFromEvent(data).replace(/\*|"|\\|\/|<|>|:|\||\?/g, '_');
let newFilename = `${filename}.md`;
if (
this.filename !== newFilename &&
this.vault.getAbstractFileByPath(
`${file.parent.path}/${newFilename}`
)
) {
throw new FCError(
"Multiple events with the same name on the same date are not yet supported. Please rename your event before moving it."
);
}

await modifyFrontmatter(this.vault, file, data);
if (this.filename !== newFilename) {
this.filename = newFilename;
await this.vault.rename(file, this.path);
}
this._data = data;
}

static noteCreatePath(ev: EventFrontmatter | EventInput, directory: string): string{
if (!ev.date || !ev.startTime || !ev.endTime){
let ds = new Date(Date.parse(ev.start));
let de = new Date(Date.parse(ev.end));
ev.date = `${ds.getFullYear()}-${("0" + (ds.getMonth() + 1)).slice(-2)}-${("0" + ds.getDate()).slice(-2)}`;
ev = {
...ev,
startTime: ("0" + ds.getHours()).slice(-2) + ":" + ("0" + ds.getMinutes()).slice(-2),
endTime: ("0" + de.getHours()).slice(-2) + ":" + ("0" + de.getMinutes()).slice(-2),
};
if (ev.endTime == "00:00"){
ev.endTime = "23:59";
}
}
let filename = `${ev.date} ${ev.title}` + (ev.startTime && ev.endTime ? ` ${ev.startTime}-${ev.endTime}` : '');
filename = filename.replace(/\*|"|\\|\/|<|>|:|\||\?/g, '_');
return `${directory}/${filename}.md`;
}

static checkNoteCreateP(ev: EventFrontmatter | EventInput, plugin: FullCalendarPlugin, vault: Vault): string|null{
return RemoteReplaceEvent.checkNoteCreate(ev, plugin.settings.calendarSources.filter(
(s) => s.type === "local"
).map(s => (s as LocalCalendarSource).directory), vault);
}

static checkNoteCreate(ev: EventFrontmatter | EventInput, sources: string[], vault: Vault): string|null{
for (const dir of sources){
const path = RemoteReplaceEvent.noteCreatePath(ev, dir);
if(vault.getAbstractFileByPath(path)){
return path;
}
}
return null;
}
}
19 changes: 14 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { MetadataCache, Vault } from "obsidian";

export const PLUGIN_SLUG = "full-calendar-plugin";

// Frontmatter
Expand Down Expand Up @@ -31,6 +29,14 @@ export type RecurringEventData = {
endRecur?: string;
} & CommonEventData;

export type ReplaceRemoteData = {
replaceRemote?: boolean;
} & CommonEventData;

export type EventFrontmatter =
| SingleEventData
| RecurringEventData
| ReplaceRemoteData;
export type OFCEvent = SingleEventData | RecurringEventData;

/*
Expand All @@ -41,11 +47,11 @@ export function validateEvent(obj?: Record<string, any>): OFCEvent | null {
if (obj === undefined) {
return null;
}

if (!obj.title) {
return null;
}

if (!obj.allDay && !obj.startTime) {
return null;
}
Expand All @@ -67,18 +73,21 @@ export function validateEvent(obj?: Record<string, any>): OFCEvent | null {
date: obj.date,
endDate: obj.endDate,
completed: obj.completed,
replaceRemote: obj.replaceRemote,
...timeInfo,
};
} else if (obj.type === "recurring") {
if (obj.daysOfWeek === undefined) {
return null;
}
return {
...obj,
title: obj.title,
type: "recurring",
daysOfWeek: obj.daysOfWeek,
startRecur: obj.startRecur,
endRecur: obj.endRecur,
replaceRemote: obj.replaceRemote,
...timeInfo,
};
}
Expand All @@ -90,6 +99,7 @@ export function validateEvent(obj?: Record<string, any>): OFCEvent | null {

type CalendarSourceCommon = {
color: string;
name: string;
};

/**
Expand Down Expand Up @@ -139,7 +149,6 @@ type AuthType = BasicAuth;
*/
export type CalDAVSource = {
type: "caldav";
name: string;
url: string;
homeUrl: string;
} & CalendarSourceCommon &
Expand Down
13 changes: 13 additions & 0 deletions src/ui/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,19 @@ export function renderCalendar(
container?.prepend(checkbox);
}
}
if (event.extendedProps.remoteReplace){
const strong = document.createElement("strong");
strong.type = "strong";
strong.innerText = "[N]";
strong.style = "margin-right: 2px;"

const container =
el.querySelector(".fc-event-time") ||
el.querySelector(".fc-event-title") ||
el.querySelector(".fc-list-event-title");

container?.prepend(strong);
}
},

longPressDelay: 250,
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/CalendarSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,14 @@ export const CalendarSettingRow = ({
>
</button>
{setting.type !== "local" && <NameSetting source={setting} />}
{setting.type === "local" ? (
<DirectorySetting source={setting} />
) : setting.type === "dailynote" ? (
<HeadingSetting source={setting} />
) : (
<UrlSetting source={setting} />
)}
{isCalDAV && <NameSetting source={setting} />}
{isCalDAV && <Username source={setting} />}
<input
style={{ maxWidth: "25%", minWidth: "3rem" }}
Expand Down
Loading