Merge pull request #6 from MichMich/develop

Develop
This commit is contained in:
vin p
2019-05-14 14:45:48 -05:00
committed by GitHub
9 changed files with 269 additions and 112 deletions

19
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- under investigation
- pr welcome
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -15,9 +15,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Option to show event location in calendar - Option to show event location in calendar
- Finnish translation for "Feels" and "Weeks" - Finnish translation for "Feels" and "Weeks"
- Russian translation for “Feels” - Russian translation for “Feels”
- Calendar module: added `nextDaysRelative` config option
- Add `broadcastPastEvents` config option for calendars to include events from the past `maximumNumberOfDays` in event broadcasts
### Updated ### Updated
- English translation for "Feels" to "Feels like" - English translation for "Feels" to "Feels like"
- Fixed the example calender url in `config.js.sample`
- Update `ical.js` to solve various calendar issues.
### Fixed ### Fixed
- Handle SIGTERM messages - Handle SIGTERM messages

View File

@@ -45,8 +45,7 @@ var config = {
calendars: [ calendars: [
{ {
symbol: "calendar-check", symbol: "calendar-check",
url: "webcal://www.calendarlabs.com/templates/ical/US-Holidays.ics" url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" }
}
] ]
} }
}, },

View File

@@ -54,8 +54,9 @@ The following properties can be configured:
| `hidePrivate` | Hides private calendar events. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false` | `hidePrivate` | Hides private calendar events. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `hideOngoing` | Hides calendar events that have already started. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false` | `hideOngoing` | Hides calendar events that have already started. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown. <br><br>Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.<br>**Required**<br>`filterBy` - string used to determine if filter is applied.<br>**Optional**<br>`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]<br>`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity<br>`regex` - set to `true` if filterBy is a regex. For those not familiar with regex it is used for pattern matching, please see [here](https://regexr.com/) for more info.<br><br> **Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}, {filterBy: '^[0-9]{1,}.*', regex: true}]` <br> **Default value:** `[]` | `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown. <br><br>Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.<br>**Required**<br>`filterBy` - string used to determine if filter is applied.<br>**Optional**<br>`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]<br>`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity<br>`regex` - set to `true` if filterBy is a regex. For those not familiar with regex it is used for pattern matching, please see [here](https://regexr.com/) for more info.<br><br> **Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}, {filterBy: '^[0-9]{1,}.*', regex: true}]` <br> **Default value:** `[]`
| `sliceMultiDayEvents` | If this is set to true, events exceeding at least one midnight will be sliced into separate events including a counter like (1/2). This is especially helpful in "dateheaders" mode. Events will be sliced at midnight, end time for all events but the last will be 23:59 **Default value:** `false` | `broadcastPastEvents` | If this is set to true, events from the past `maximumNumberOfDays` will be included in event broadcasts <br> **Default value:** `false`
| `sliceMultiDayEvents` | If this is set to true, events exceeding at least one midnight will be sliced into separate events including a counter like (1/2). This is especially helpful in "dateheaders" mode. Events will be sliced at midnight, end time for all events but the last will be 23:59 <br> **Default value:** `true`
| `nextDaysRelative ` | If this is set to true, the appointments of today and tomorrow are displayed relatively, even if the timeformat is set to absolute. <br> **Default value:** `false`
### Calendar configuration ### Calendar configuration
@@ -96,6 +97,7 @@ config: {
| `symbolClass` | Add a class to the cell of symbol. | `symbolClass` | Add a class to the cell of symbol.
| `titleClass` | Add a class to the title's cell. | `titleClass` | Add a class to the title's cell.
| `timeClass` | Add a class to the time's cell. | `timeClass` | Add a class to the time's cell.
| `broadcastPastEvents` | Whether to include past events from this calendar. Overrides global setting
#### Calendar authentication options: #### Calendar authentication options:

View File

@@ -49,7 +49,9 @@ Module.register("calendar", {
}, },
broadcastEvents: true, broadcastEvents: true,
excludedEvents: [], excludedEvents: [],
sliceMultiDayEvents: false sliceMultiDayEvents: false,
broadcastPastEvents: false,
nextDaysRelative: false
}, },
// Define required scripts. // Define required scripts.
@@ -83,7 +85,8 @@ Module.register("calendar", {
var calendarConfig = { var calendarConfig = {
maximumEntries: calendar.maximumEntries, maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays maximumNumberOfDays: calendar.maximumNumberOfDays,
broadcastPastEvents: calendar.broadcastPastEvents,
}; };
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) { if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = ""; calendarConfig.symbolClass = "";
@@ -326,7 +329,7 @@ Module.register("calendar", {
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow() // If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else { } else {
if(this.config.timeFormat === "absolute") { if(this.config.timeFormat === "absolute" && !this.config.nextDaysRelative) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat)); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
} else { } else {
// Otherwise just say 'Today/Tomorrow at such-n-such time' // Otherwise just say 'Today/Tomorrow at such-n-such time'
@@ -467,6 +470,9 @@ Module.register("calendar", {
var calendar = this.calendarData[c]; var calendar = this.calendarData[c];
for (var e in calendar) { for (var e in calendar) {
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
if(event.endDate < now) {
continue;
}
if(this.config.hidePrivate) { if(this.config.hidePrivate) {
if(event.class === "PRIVATE") { if(event.class === "PRIVATE") {
// do not add the current event, skip it // do not add the current event, skip it
@@ -550,7 +556,8 @@ Module.register("calendar", {
symbolClass: calendarConfig.symbolClass, symbolClass: calendarConfig.symbolClass,
titleClass: calendarConfig.titleClass, titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass, timeClass: calendarConfig.timeClass,
auth: auth auth: auth,
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
}); });
}, },

View File

@@ -8,7 +8,7 @@
var ical = require("./vendor/ical.js"); var ical = require("./vendor/ical.js");
var moment = require("moment"); var moment = require("moment");
var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) { var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
var self = this; var self = this;
var reloadTimer = null; var reloadTimer = null;
@@ -74,6 +74,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var now = new Date(); var now = new Date();
var today = moment().startOf("day").toDate(); var today = moment().startOf("day").toDate();
var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1,"seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat. var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1,"seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
var past = today;
if (includePastEvents) {
past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
}
// FIXME: // FIXME:
// Ugly fix to solve the facebook birthday issue. // Ugly fix to solve the facebook birthday issue.
@@ -181,7 +186,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
rule.options.dtstart.setYear(1900); rule.options.dtstart.setYear(1900);
} }
var dates = rule.between(today, future, true, limitFunction); var dates = rule.between(past, future, true, limitFunction);
for (var d in dates) { for (var d in dates) {
startDate = moment(new Date(dates[d])); startDate = moment(new Date(dates[d]));
@@ -191,7 +196,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
continue; continue;
} }
if (endDate.format("x") > now) { if (includePastEvents || endDate.format("x") > now) {
newEvents.push({ newEvents.push({
title: title, title: title,
startDate: startDate.format("x"), startDate: startDate.format("x"),
@@ -210,14 +215,21 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
// Single event. // Single event.
var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event); var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
if (!fullDayEvent && endDate < new Date()) { if (includePastEvents) {
//console.log("It's not a fullday event, and it is in the past. So skip: " + title); if (endDate < past) {
continue; //console.log("Past event is too far in the past. So skip: " + title);
} continue;
}
} else {
if (!fullDayEvent && endDate < new Date()) {
//console.log("It's not a fullday event, and it is in the past. So skip: " + title);
continue;
}
if (fullDayEvent && endDate <= today) { if (fullDayEvent && endDate <= today) {
//console.log("It's a fullday event, and it is before today. So skip: " + title); //console.log("It's a fullday event, and it is before today. So skip: " + title);
continue; continue;
}
} }
if (startDate > future) { if (startDate > future) {

View File

@@ -15,6 +15,7 @@ var maximumEntries = 10;
var maximumNumberOfDays = 365; var maximumNumberOfDays = 365;
var user = "magicmirror"; var user = "magicmirror";
var pass = "MyStrongPass"; var pass = "MyStrongPass";
var broadcastPastEvents = false;
var auth = { var auth = {
user: user, user: user,

View File

@@ -24,7 +24,7 @@ module.exports = NodeHelper.create({
socketNotificationReceived: function(notification, payload) { socketNotificationReceived: function(notification, payload) {
if (notification === "ADD_CALENDAR") { if (notification === "ADD_CALENDAR") {
//console.log('ADD_CALENDAR: '); //console.log('ADD_CALENDAR: ');
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth); this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents);
} }
}, },
@@ -36,7 +36,7 @@ module.exports = NodeHelper.create({
* attribute reloadInterval number - Reload interval in milliseconds. * attribute reloadInterval number - Reload interval in milliseconds.
*/ */
createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) { createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents) {
var self = this; var self = this;
if (!validUrl.isUri(url)) { if (!validUrl.isUri(url)) {
@@ -47,7 +47,7 @@ module.exports = NodeHelper.create({
var fetcher; var fetcher;
if (typeof self.fetchers[url] === "undefined") { if (typeof self.fetchers[url] === "undefined") {
console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval); console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth); fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents);
fetcher.onReceive(function(fetcher) { fetcher.onReceive(function(fetcher) {
//console.log('Broadcast events.'); //console.log('Broadcast events.');

View File

@@ -33,9 +33,9 @@
for (var i = 0; i<p.length; i++){ for (var i = 0; i<p.length; i++){
if (p[i].indexOf('=') > -1){ if (p[i].indexOf('=') > -1){
var segs = p[i].split('='); var segs = p[i].split('=');
out[segs[0]] = parseValue(segs.slice(1).join('=')); out[segs[0]] = parseValue(segs.slice(1).join('='));
} }
} }
return out || sp return out || sp
@@ -44,7 +44,7 @@
var parseValue = function(val){ var parseValue = function(val){
if ('TRUE' === val) if ('TRUE' === val)
return true; return true;
if ('FALSE' === val) if ('FALSE' === val)
return false; return false;
@@ -55,75 +55,52 @@
return val; return val;
} }
var storeParam = function(name){ var storeValParam = function (name) {
return function(val, params, curr){ return function (val, curr) {
var data; var current = curr[name];
if (params && params.length && !(params.length==1 && params[0]==='CHARSET=utf-8')){ if (Array.isArray(current)) {
data = {params:parseParams(params), val:text(val)} current.push(val);
} return curr;
else }
data = text(val)
var current = curr[name]; if (current != null) {
if (Array.isArray(current)){ curr[name] = [current, val];
current.push(data); return curr;
return curr; }
}
if (current != null){ curr[name] = val;
curr[name] = [current, data]; return curr
return curr;
} }
curr[name] = data;
return curr
}
} }
var addTZ = function(dt, params){ var storeParam = function (name) {
return function (val, params, curr) {
var data;
if (params && params.length && !(params.length == 1 && params[0] === 'CHARSET=utf-8')) {
data = { params: parseParams(params), val: text(val) }
}
else
data = text(val)
return storeValParam(name)(data, curr);
}
}
var addTZ = function (dt, params) {
var p = parseParams(params); var p = parseParams(params);
if (params && p && dt){ if (params && p){
dt.tz = p.TZID dt.tz = p.TZID
} }
return dt return dt
} }
var parseTimestamp = function(val){
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
return new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
return new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
}
return undefined;
}
var dateParam = function(name){ var dateParam = function(name){
return function(val, params, curr){ return function (val, params, curr) {
var newDate = text(val);
// Store as string - worst case scenario
storeParam(name)(val, undefined, curr)
if (params && params[0] === "VALUE=DATE") { if (params && params[0] === "VALUE=DATE") {
// Just Date // Just Date
@@ -131,47 +108,54 @@
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val); var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
if (comps !== null) { if (comps !== null) {
// No TZ info - assume same timezone as this computer // No TZ info - assume same timezone as this computer
curr[name] = new Date( newDate = new Date(
comps[1], comps[1],
parseInt(comps[2], 10)-1, parseInt(comps[2], 10)-1,
comps[3] comps[3]
); );
curr[name] = addTZ(curr[name], params); newDate = addTZ(newDate, params);
return curr; newDate.dateOnly = true;
// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
} }
} }
curr[name] = []
val.split(',').forEach(function(val){
var newDate = parseTimestamp(val);
curr[name].push(addTZ(newDate, params));
});
if (curr[name].length === 0){ //typical RFC date-time format
delete curr[name]; var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
} else if (curr[name].length === 1){ if (comps !== null) {
curr[name] = curr[name][0]; if (comps[7] == 'Z'){ // GMT
} newDate = new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
newDate = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
return curr; newDate = addTZ(newDate, params);
} }
// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
}
} }
var exdateParam = function(name){
return function(val, params, curr){
var date = dateParam(name)(val, params, curr);
if (date.exdates === undefined) {
date.exdates = [];
}
if (Array.isArray(date.exdate)){
date.exdates = date.exdates.concat(date.exdate);
} else {
date.exdates.push(date.exdate);
}
return date;
}
}
var geoParam = function(name){ var geoParam = function(name){
return function(val, params, curr){ return function(val, params, curr){
@@ -195,7 +179,52 @@
} }
} }
var addFBType = function(fb, params){ // EXDATE is an entry that represents exceptions to a recurrence rule (ex: "repeat every day except on 7/4").
// The EXDATE entry itself can also contain a comma-separated list, so we make sure to parse each date out separately.
// There can also be more than one EXDATE entries in a calendar record.
// Since there can be multiple dates, we create an array of them. The index into the array is the ISO string of the date itself, for ease of use.
// i.e. You can check if ((curr.exdate != undefined) && (curr.exdate[date iso string] != undefined)) to see if a date is an exception.
// NOTE: This specifically uses date only, and not time. This is to avoid a few problems:
// 1. The ISO string with time wouldn't work for "floating dates" (dates without timezones).
// ex: "20171225T060000" - this is supposed to mean 6 AM in whatever timezone you're currently in
// 2. Daylight savings time potentially affects the time you would need to look up
// 3. Some EXDATE entries in the wild seem to have times different from the recurrence rule, but are still excluded by calendar programs. Not sure how or why.
// These would fail any sort of sane time lookup, because the time literally doesn't match the event. So we'll ignore time and just use date.
// ex: DTSTART:20170814T140000Z
// RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=2;BYDAY=MO,TU
// EXDATE:20171219T060000
// Even though "T060000" doesn't match or overlap "T1400000Z", it's still supposed to be excluded? Odd. :(
// TODO: See if this causes any problems with events that recur multiple times a day.
var exdateParam = function (name) {
return function (val, params, curr) {
var separatorPattern = /\s*,\s*/g;
curr[name] = curr[name] || [];
var dates = val ? val.split(separatorPattern) : [];
dates.forEach(function (entry) {
var exdate = new Array();
dateParam(name)(entry, params, exdate);
if (exdate[name])
{
if (typeof exdate[name].toISOString === 'function') {
curr[name][exdate[name].toISOString().substring(0, 10)] = exdate[name];
} else {
console.error("No toISOString function in exdate[name]", exdate[name]);
}
}
}
)
return curr;
}
}
// RECURRENCE-ID is the ID of a specific recurrence within a recurrence rule.
// TODO: It's also possible for it to have a range, like "THISANDPRIOR", "THISANDFUTURE". This isn't currently handled.
var recurrenceParam = function (name) {
return dateParam(name);
}
var addFBType = function (fb, params) {
var p = parseParams(params); var p = parseParams(params);
if (params && p){ if (params && p){
@@ -239,7 +268,7 @@
//scan all high level object in curr and drop all strings //scan all high level object in curr and drop all strings
var key, var key,
obj; obj;
for (key in curr) { for (key in curr) {
if(curr.hasOwnProperty(key)) { if(curr.hasOwnProperty(key)) {
obj = curr[key]; obj = curr[key];
@@ -248,14 +277,93 @@
} }
} }
} }
return curr return curr
} }
var par = stack.pop() var par = stack.pop()
if (curr.uid) if (curr.uid)
par[curr.uid] = curr {
// If this is the first time we run into this UID, just save it.
if (par[curr.uid] === undefined)
{
par[curr.uid] = curr;
}
else
{
// If we have multiple ical entries with the same UID, it's either going to be a
// modification to a recurrence (RECURRENCE-ID), and/or a significant modification
// to the entry (SEQUENCE).
// TODO: Look into proper sequence logic.
if (curr.recurrenceid === undefined)
{
// If we have the same UID as an existing record, and it *isn't* a specific recurrence ID,
// not quite sure what the correct behaviour should be. For now, just take the new information
// and merge it with the old record by overwriting only the fields that appear in the new record.
var key;
for (key in curr) {
par[curr.uid][key] = curr[key];
}
}
}
// If we have recurrence-id entries, list them as an array of recurrences keyed off of recurrence-id.
// To use - as you're running through the dates of an rrule, you can try looking it up in the recurrences
// array. If it exists, then use the data from the calendar object in the recurrence instead of the parent
// for that day.
// NOTE: Sometimes the RECURRENCE-ID record will show up *before* the record with the RRULE entry. In that
// case, what happens is that the RECURRENCE-ID record ends up becoming both the parent record and an entry
// in the recurrences array, and then when we process the RRULE entry later it overwrites the appropriate
// fields in the parent record.
if (curr.recurrenceid != null)
{
// TODO: Is there ever a case where we have to worry about overwriting an existing entry here?
// Create a copy of the current object to save in our recurrences array. (We *could* just do par = curr,
// except for the case that we get the RECURRENCE-ID record before the RRULE record. In that case, we
// would end up with a shared reference that would cause us to overwrite *both* records at the point
// that we try and fix up the parent record.)
var recurrenceObj = new Object();
var key;
for (key in curr) {
recurrenceObj[key] = curr[key];
}
if (recurrenceObj.recurrences != undefined) {
delete recurrenceObj.recurrences;
}
// If we don't have an array to store recurrences in yet, create it.
if (par[curr.uid].recurrences === undefined) {
par[curr.uid].recurrences = new Array();
}
// Save off our cloned recurrence object into the array, keyed by date but not time.
// We key by date only to avoid timezone and "floating time" problems (where the time isn't associated with a timezone).
// TODO: See if this causes a problem with events that have multiple recurrences per day.
if (typeof curr.recurrenceid.toISOString === 'function') {
par[curr.uid].recurrences[curr.recurrenceid.toISOString().substring(0,10)] = recurrenceObj;
} else {
console.error("No toISOString function in curr.recurrenceid", curr.recurrenceid);
}
}
// One more specific fix - in the case that an RRULE entry shows up after a RECURRENCE-ID entry,
// let's make sure to clear the recurrenceid off the parent field.
if ((par[curr.uid].rrule != undefined) && (par[curr.uid].recurrenceid != undefined))
{
delete par[curr.uid].recurrenceid;
}
}
else else
par[Math.random()*100000] = curr // Randomly assign ID : TODO - use true GUID par[Math.random()*100000] = curr // Randomly assign ID : TODO - use true GUID
@@ -277,6 +385,11 @@
, 'COMPLETED': dateParam('completed') , 'COMPLETED': dateParam('completed')
, 'CATEGORIES': categoriesParam('categories') , 'CATEGORIES': categoriesParam('categories')
, 'FREEBUSY': freebusyParam('freebusy') , 'FREEBUSY': freebusyParam('freebusy')
, 'DTSTAMP': dateParam('dtstamp')
, 'CREATED': dateParam('created')
, 'LAST-MODIFIED': dateParam('lastmodified')
, 'RECURRENCE-ID': recurrenceParam('recurrenceid')
}, },
@@ -292,7 +405,7 @@
name = name.substring(2); name = name.substring(2);
return (storeParam(name))(val, params, ctx, stack, line); return (storeParam(name))(val, params, ctx, stack, line);
} }
return storeParam(name.toLowerCase())(val, params, ctx); return storeParam(name.toLowerCase())(val, params, ctx);
}, },