Skip to content
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
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Dependencies
node_modules/

# Build outputs
dist/
.parcel-cache/

# Debug scripts
debug-*.js
check-*.js

# OS files
.DS_Store

# Editor files
.vscode/
.idea/
*.swp
*.swo
*~

# Logs
*.log
npm-debug.log*
3 changes: 3 additions & 0 deletions .parcelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@parcel/config-default"
}
256 changes: 182 additions & 74 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,52 @@ const settingsTemplate: SettingSchemaDesc[] = [
logseq.useSettingsSchema(settingsTemplate);

function sortDate(data) {
return data.sort(function (a, b) {
return (
Math.round(new Date(a.start).getTime() / 1000) -
Math.round(new Date(b.start).getTime() / 1000)
);
// Filter out events with invalid dates
const validEvents = data.filter(event => {
if (!event.start) {
return false;
}
const startDate = new Date(event.start);
if (isNaN(startDate.getTime())) {
return false;
}
return true;
});

const sorted = validEvents.sort(function (a, b) {
// Sort by the displayed date/time in each event's timezone
// This ensures events sort by what the user sees (14:30) not absolute time
let aMoment, bMoment;

if (a.timezone) {
aMoment = moment(a.start).tz(a.timezone);
} else {
aMoment = moment(a.start);
}

if (b.timezone) {
bMoment = moment(b.start).tz(b.timezone);
} else {
bMoment = moment(b.start);
}

// Compare by date/time components (year, month, day, hour, minute)
// This gives us the "displayed" sort order
const aSort = aMoment.year() * 100000000 +
(aMoment.month() + 1) * 1000000 +
aMoment.date() * 10000 +
aMoment.hour() * 100 +
aMoment.minute();
const bSort = bMoment.year() * 100000000 +
(bMoment.month() + 1) * 1000000 +
bMoment.date() * 10000 +
bMoment.hour() * 100 +
bMoment.minute();

return aSort - bSort;
});

return sorted;
}
async function findDate(preferredDateFormat) {
if ((await logseq.Editor.getCurrentPage()) != null) {
Expand Down Expand Up @@ -176,12 +216,13 @@ function rawParser(rawData) {
//@ts-expect-error
eventsArray.push(rawDataV2[dataValue]); //simplifying results, credits to https://github.com/muness/obsidian-ics for this implementations
} else {
const dates = event.rrule.between(
new Date(2021, 0, 1, 0, 0, 0, 0),
new Date(2023, 11, 31, 0, 0, 0, 0)
);
// Generate recurring events from 1 year ago to 2 years in the future
const today = new Date();
const startDate = new Date(today.getFullYear() - 1, 0, 1, 0, 0, 0, 0);
const endDate = new Date(today.getFullYear() + 2, 11, 31, 23, 59, 59, 999);
const dates = event.rrule.between(startDate, endDate);
console.log(dates);
if (dates.length === 0) continue;
if (!dates || !Array.isArray(dates) || dates.length === 0) continue;

console.log("Summary:", event.summary);
console.log("Original start:", event.start);
Expand All @@ -190,38 +231,62 @@ function rawParser(rawData) {
`${event.rrule.origOptions.dtstart} [${event.rrule.origOptions.tzid}]`
);

dates.forEach((date) => {
let newDate;
if (event.rrule.origOptions.tzid) {
// tzid present (calculate offset from recurrence start)
const dateTimezone = moment.tz.zone("UTC");
const localTimezone = moment.tz.guess();

const tz =
event.rrule.origOptions.tzid === localTimezone
? event.rrule.origOptions.tzid
: localTimezone;
const timezone = moment.tz.zone(tz);
const offset =
timezone.utcOffset(date) - dateTimezone.utcOffset(date);
// newDate = moment(date).add(offset, "minutes").toDate();
// console.log(offset)
newDate = date
//FIXME: this is a hack to get around the fact that the offset is not being calculated correctly
} else {
// tzid not present (calculate offset from original start)
newDate = new Date(
date.setHours(
date.getHours() -
(event.start.getTimezoneOffset() - date.getTimezoneOffset()) /
60
)
);
}
const start = moment(newDate);
const secondaryEvent = { ...event, start: start["_d"] };
eventsArray.push(secondaryEvent);
});
try {
dates.forEach((date) => {
let newDate;
let newEndDate;
if (event.rrule.origOptions.tzid) {
// tzid present - properly handle timezone conversion
const originalStartTime = moment(event.rrule.origOptions.dtstart);
const hours = originalStartTime.hours();
const minutes = originalStartTime.minutes();
const seconds = originalStartTime.seconds();

// Create moment in the event's timezone with the recurrence date and original time
// Use the date components from the recurrence date, time from original event
newDate = moment.tz({
year: date.getFullYear(),
month: date.getMonth(),
date: date.getDate(),
hour: hours,
minute: minutes,
second: seconds
}, event.rrule.origOptions.tzid).toDate();

// Calculate end time based on original duration
if (event.end) {
const duration = moment(event.end).diff(moment(event.start));
newEndDate = moment(newDate).add(duration, 'milliseconds').toDate();
}
} else {
// tzid not present (calculate offset from original start)
const hours = event.start.getHours();
const minutes = event.start.getMinutes();
const seconds = event.start.getSeconds();

newDate = new Date(date);
newDate.setHours(hours, minutes, seconds);

// Calculate end time based on original duration
if (event.end) {
const duration = event.end.getTime() - event.start.getTime();
newEndDate = new Date(newDate.getTime() + duration);
}
}
const start = moment(newDate);
const secondaryEvent = {
...event,
start: start["_d"],
end: newEndDate || event.end,
// Preserve timezone info for proper time formatting later
timezone: event.rrule.origOptions.tzid
};
eventsArray.push(secondaryEvent);
});
} catch (error) {
console.error("Error processing recurring event:", event.summary, error);
continue;
}

console.log(
"-----------------------------------------------------------------------------------------"
Expand All @@ -236,6 +301,12 @@ function parseLocation(rawLocation){
const matches = rawLocation.match(urlRegexSafe());
var parsed = rawLocation;
var linkDesc;

// If no matches found, return the raw location
if (!matches || matches.length === 0) {
return parsed;
}

for (const match of matches) {
try{
var url = new URL(match);
Expand Down Expand Up @@ -296,23 +367,37 @@ function templateFormatter(
return templatex1;
}

async function formatTime(rawTimeStamp) {
let formattedTimeStamp = new Date(rawTimeStamp);
let initialHours = formattedTimeStamp.getHours();
async function formatTime(rawTimeStamp, timezone = null) {
let formattedTimeStamp;
let initialHours;
let minutes;

// If timezone is provided, use moment-timezone to get the correct time in that timezone
if (timezone) {
const momentTime = moment(rawTimeStamp).tz(timezone);
initialHours = momentTime.hours();
minutes = momentTime.minutes();
} else {
// No timezone provided, use local time (original behavior)
formattedTimeStamp = new Date(rawTimeStamp);
initialHours = formattedTimeStamp.getHours();
minutes = formattedTimeStamp.getMinutes();
}

let hours;
if (initialHours == 0) {
hours = "00";
} else {
hours = initialHours;
if (formattedTimeStamp.getHours() < 10) {
hours = "0" + formattedTimeStamp.getHours();
if (initialHours < 10) {
hours = "0" + initialHours;
}
}
var formattedTime;
if (formattedTimeStamp.getMinutes() < 10) {
formattedTime = hours + ":" + "0" + formattedTimeStamp.getMinutes();
if (minutes < 10) {
formattedTime = hours + ":" + "0" + minutes;
} else {
formattedTime = hours + ":" + formattedTimeStamp.getMinutes();
formattedTime = hours + ":" + minutes;
}
if (
typeof logseq.settings?.timeFormat == "undefined" ||
Expand Down Expand Up @@ -346,6 +431,9 @@ async function insertJournalBlocks(
sibling: true,
isPageBlock: true,
})) as BlockEntity;

// Collect all events for today first, then insert them in order
const eventsToInsert = [];
for (const dataKey in data) {
try {
let description = data[dataKey]["description"]; //Parsing result from rawParser into usable data for templateFormatter
Expand All @@ -354,8 +442,9 @@ async function insertJournalBlocks(
formattedStart,
preferredDateFormat
);
let startTime = await formatTime(formattedStart) ;
let endTime = await formatTime(data[dataKey]["end"]);
let timezone = data[dataKey]["timezone"] || null;
let startTime = await formatTime(formattedStart, timezone);
let endTime = await formatTime(data[dataKey]["end"], timezone);
let location = data[dataKey]["location"];
let summary;
summary = data[dataKey]["summary"];
Expand All @@ -371,34 +460,48 @@ async function insertJournalBlocks(
location
);
if (startDate.toLowerCase() == emptyToday.toLowerCase()) {
var currentBlock = await logseq.Editor.insertBlock(
startBlock.uuid,
`${headerString.replaceAll("\\n", "\n")}`,
{ sibling: false }
);
if (logseq.settings?.templateLine2 != "") {
let SecondTemplateLine = templateFormatter(
logseq.settings?.templateLine2,
description,
startDate,
startTime,
endTime,
summary,
location
);
await logseq.Editor.insertBlock(
currentBlock!.uuid,
`${SecondTemplateLine.replaceAll("\\n", "\n")}`,
{ sibling: false }
);
}
eventsToInsert.push({
summary,
startTime,
headerString,
description,
startDate,
endTime,
location
});
}
} catch (error) {
console.log(data[dataKey]);
console.log("error");
console.log(error);
}
}

// Insert events in forward order (sibling:false appends as last child)
for (let i = 0; i < eventsToInsert.length; i++) {
const event = eventsToInsert[i];
var currentBlock = await logseq.Editor.insertBlock(
startBlock.uuid,
`${event.headerString.replaceAll("\\n", "\n")}`,
{ sibling: false }
);
if (logseq.settings?.templateLine2 != "") {
let SecondTemplateLine = templateFormatter(
logseq.settings?.templateLine2,
event.description,
event.startDate,
event.startTime,
event.endTime,
event.summary,
event.location
);
await logseq.Editor.insertBlock(
currentBlock!.uuid,
`${SecondTemplateLine.replaceAll("\\n", "\n")}`,
{ sibling: false }
);
}
}
let updatedBlock = await logseq.Editor.getBlock(startBlock.uuid, {
includeChildren: true,
})
Expand All @@ -412,7 +515,12 @@ async function openCalendar2(calendarName, url) {
const userConfigs = await logseq.App.getUserConfigs();
const preferredDateFormat = userConfigs.preferredDateFormat;
logseq.App.showMsg("Fetching Calendar Items");
let response2 = await axios.get(url);

// Add cache-busting parameter to force fresh calendar data
const cacheBuster = `?nocache=${new Date().getTime()}`;
const urlWithCacheBuster = url.includes('?') ? `${url}&nocache=${new Date().getTime()}` : url + cacheBuster;

let response2 = await axios.get(urlWithCacheBuster);
console.log(response2);
var hello = await rawParser(response2.data);
const date = await findDate(preferredDateFormat);
Expand Down
Loading