123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846 |
- /* global cloneObject */
-
- /* Magic Mirror
- * Module: Calendar
- *
- * By Michael Teeuw https://michaelteeuw.nl
- * MIT Licensed.
- */
- Module.register("calendar", {
- // Define module defaults
- defaults: {
- maximumEntries: 10, // Total Maximum Entries
- maximumNumberOfDays: 365,
- limitDays: 0, // Limit the number of days shown, 0 = no limit
- displaySymbol: true,
- defaultSymbol: "calendar", // Fontawesome Symbol see https://fontawesome.com/cheatsheet?from=io
- showLocation: false,
- displayRepeatingCountTitle: false,
- defaultRepeatingCountTitle: "",
- maxTitleLength: 25,
- maxLocationTitleLength: 25,
- wrapEvents: false, // wrap events to multiple lines breaking at maxTitleLength
- wrapLocationEvents: false,
- maxTitleLines: 3,
- maxEventTitleLines: 3,
- fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
- animationSpeed: 2000,
- fade: true,
- urgency: 7,
- timeFormat: "relative",
- dateFormat: "MMM Do",
- dateEndFormat: "LT",
- fullDayEventDateFormat: "MMM Do",
- showEnd: false,
- getRelative: 6,
- fadePoint: 0.25, // Start on 1/4th of the list.
- hidePrivate: false,
- hideOngoing: false,
- hideTime: false,
- colored: false,
- coloredSymbolOnly: false,
- customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched
- tableClass: "small",
- calendars: [
- {
- symbol: "calendar",
- url: "https://www.calendarlabs.com/templates/ical/US-Holidays.ics"
- }
- ],
- titleReplace: {
- "De verjaardag van ": "",
- "'s birthday": ""
- },
- locationTitleReplace: {
- "street ": ""
- },
- broadcastEvents: true,
- excludedEvents: [],
- sliceMultiDayEvents: false,
- broadcastPastEvents: false,
- nextDaysRelative: false,
- selfSignedCert: false
- },
-
- requiresVersion: "2.1.0",
-
- // Define required scripts.
- getStyles: function () {
- return ["calendar.css", "font-awesome.css"];
- },
-
- // Define required scripts.
- getScripts: function () {
- return ["moment.js"];
- },
-
- // Define required translations.
- getTranslations: function () {
- // The translations for the default modules are defined in the core translation files.
- // Therefore we can just return false. Otherwise we should have returned a dictionary.
- // If you're trying to build your own module including translations, check out the documentation.
- return false;
- },
-
- // Override start method.
- start: function () {
- Log.info("Starting module: " + this.name);
-
- // Set locale.
- moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat));
-
- // clear data holder before start
- this.calendarData = {};
-
- // indicate no data available yet
- this.loaded = false;
-
- this.config.calendars.forEach((calendar) => {
- calendar.url = calendar.url.replace("webcal://", "http://");
-
- const calendarConfig = {
- maximumEntries: calendar.maximumEntries,
- maximumNumberOfDays: calendar.maximumNumberOfDays,
- broadcastPastEvents: calendar.broadcastPastEvents,
- selfSignedCert: calendar.selfSignedCert
- };
-
- if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
- calendarConfig.symbolClass = "";
- }
- if (calendar.titleClass === "undefined" || calendar.titleClass === null) {
- calendarConfig.titleClass = "";
- }
- if (calendar.timeClass === "undefined" || calendar.timeClass === null) {
- calendarConfig.timeClass = "";
- }
-
- // we check user and password here for backwards compatibility with old configs
- if (calendar.user && calendar.pass) {
- Log.warn("Deprecation warning: Please update your calendar authentication configuration.");
- Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options");
- calendar.auth = {
- user: calendar.user,
- pass: calendar.pass
- };
- }
-
- // tell helper to start a fetcher for this calendar
- // fetcher till cycle
- this.addCalendar(calendar.url, calendar.auth, calendarConfig);
- });
- },
-
- // Override socket notification handler.
- socketNotificationReceived: function (notification, payload) {
- if (this.identifier !== payload.id) {
- return;
- }
-
- if (notification === "CALENDAR_EVENTS") {
- if (this.hasCalendarURL(payload.url)) {
- this.calendarData[payload.url] = payload.events;
- this.error = null;
- this.loaded = true;
-
- if (this.config.broadcastEvents) {
- this.broadcastEvents();
- }
- }
- } else if (notification === "CALENDAR_ERROR") {
- let error_message = this.translate(payload.error_type);
- this.error = this.translate("MODULE_CONFIG_ERROR", { MODULE_NAME: this.name, ERROR: error_message });
- this.loaded = true;
- }
-
- this.updateDom(this.config.animationSpeed);
- },
-
- // Override dom generator.
- getDom: function () {
- // Define second, minute, hour, and day constants
- const oneSecond = 1000; // 1,000 milliseconds
- const oneMinute = oneSecond * 60;
- const oneHour = oneMinute * 60;
- const oneDay = oneHour * 24;
-
- const events = this.createEventList();
- const wrapper = document.createElement("table");
- wrapper.className = this.config.tableClass;
-
- if (this.error) {
- wrapper.innerHTML = this.error;
- wrapper.className = this.config.tableClass + " dimmed";
- return wrapper;
- }
-
- if (events.length === 0) {
- wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING");
- wrapper.className = this.config.tableClass + " dimmed";
- return wrapper;
- }
-
- let currentFadeStep = 0;
- let startFade;
- let fadeSteps;
-
- if (this.config.fade && this.config.fadePoint < 1) {
- if (this.config.fadePoint < 0) {
- this.config.fadePoint = 0;
- }
- startFade = events.length * this.config.fadePoint;
- fadeSteps = events.length - startFade;
- }
-
- let lastSeenDate = "";
-
- events.forEach((event, index) => {
- const dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
- if (this.config.timeFormat === "dateheaders") {
- if (lastSeenDate !== dateAsString) {
- const dateRow = document.createElement("tr");
- dateRow.className = "normal";
-
- const dateCell = document.createElement("td");
- dateCell.colSpan = "3";
- dateCell.innerHTML = dateAsString;
- dateCell.style.paddingTop = "10px";
- dateRow.appendChild(dateCell);
- wrapper.appendChild(dateRow);
-
- if (this.config.fade && index >= startFade) {
- //fading
- currentFadeStep = index - startFade;
- dateRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
- }
-
- lastSeenDate = dateAsString;
- }
- }
-
- const eventWrapper = document.createElement("tr");
-
- if (this.config.colored && !this.config.coloredSymbolOnly) {
- eventWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
- }
-
- eventWrapper.className = "normal event";
-
- const symbolWrapper = document.createElement("td");
-
- if (this.config.displaySymbol) {
- if (this.config.colored && this.config.coloredSymbolOnly) {
- symbolWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
- }
-
- const symbolClass = this.symbolClassForUrl(event.url);
- symbolWrapper.className = "symbol align-right " + symbolClass;
-
- const symbols = this.symbolsForEvent(event);
- // If symbols are displayed and custom symbol is set, replace event symbol
- if (this.config.displaySymbol && this.config.customEvents.length > 0) {
- for (let ev in this.config.customEvents) {
- if (typeof this.config.customEvents[ev].symbol !== "undefined" && this.config.customEvents[ev].symbol !== "") {
- let needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
- if (needle.test(event.title)) {
- symbols[0] = this.config.customEvents[ev].symbol;
- break;
- }
- }
- }
- }
- symbols.forEach((s, index) => {
- const symbol = document.createElement("span");
- symbol.className = "fa fa-fw fa-" + s;
- if (index > 0) {
- symbol.style.paddingLeft = "5px";
- }
- symbolWrapper.appendChild(symbol);
- });
- eventWrapper.appendChild(symbolWrapper);
- } else if (this.config.timeFormat === "dateheaders") {
- const blankCell = document.createElement("td");
- blankCell.innerHTML = " ";
- eventWrapper.appendChild(blankCell);
- }
-
- const titleWrapper = document.createElement("td");
- let repeatingCountTitle = "";
-
- if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
- repeatingCountTitle = this.countTitleForUrl(event.url);
-
- if (repeatingCountTitle !== "") {
- const thisYear = new Date(parseInt(event.startDate)).getFullYear(),
- yearDiff = thisYear - event.firstYear;
-
- repeatingCountTitle = ", " + yearDiff + ". " + repeatingCountTitle;
- }
- }
-
- // Color events if custom color is specified
- if (this.config.customEvents.length > 0) {
- for (let ev in this.config.customEvents) {
- if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") {
- let needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
- if (needle.test(event.title)) {
- // Respect parameter ColoredSymbolOnly also for custom events
- if (!this.config.coloredSymbolOnly) {
- eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
- titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
- }
- if (this.config.displaySymbol) {
- symbolWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
- }
- break;
- }
- }
- }
- }
-
- titleWrapper.innerHTML = this.titleTransform(event.title, this.config.titleReplace, this.config.wrapEvents, this.config.maxTitleLength, this.config.maxTitleLines) + repeatingCountTitle;
-
- const titleClass = this.titleClassForUrl(event.url);
-
- if (!this.config.colored) {
- titleWrapper.className = "title bright " + titleClass;
- } else {
- titleWrapper.className = "title " + titleClass;
- }
-
- if (this.config.timeFormat === "dateheaders") {
- if (event.fullDayEvent) {
- titleWrapper.colSpan = "2";
- titleWrapper.classList.add("align-left");
- } else {
- const timeWrapper = document.createElement("td");
- timeWrapper.className = "time light align-left " + this.timeClassForUrl(event.url);
- timeWrapper.style.paddingLeft = "2px";
- timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
- eventWrapper.appendChild(timeWrapper);
- titleWrapper.classList.add("align-right");
- }
-
- eventWrapper.appendChild(titleWrapper);
- } else {
- const timeWrapper = document.createElement("td");
-
- eventWrapper.appendChild(titleWrapper);
- const now = new Date();
-
- if (this.config.timeFormat === "absolute") {
- // Use dateFormat
- timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
- // Add end time if showEnd
- if (this.config.showEnd) {
- timeWrapper.innerHTML += "-";
- timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
- }
- // For full day events we use the fullDayEventDateFormat
- if (event.fullDayEvent) {
- //subtract one second so that fullDayEvents end at 23:59:59, and not at 0:00:00 one the next day
- event.endDate -= oneSecond;
- timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
- }
- if (this.config.getRelative > 0 && event.startDate < now) {
- // Ongoing and getRelative is set
- timeWrapper.innerHTML = this.capFirst(
- this.translate("RUNNING", {
- fallback: this.translate("RUNNING") + " {timeUntilEnd}",
- timeUntilEnd: moment(event.endDate, "x").fromNow(true)
- })
- );
- } else if (this.config.urgency > 0 && event.startDate - now < this.config.urgency * oneDay) {
- // Within urgency days
- timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
- }
- if (event.fullDayEvent && this.config.nextDaysRelative) {
- // Full days events within the next two days
- if (event.today) {
- timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
- } else if (event.startDate - now < oneDay && event.startDate - now > 0) {
- timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
- } else if (event.startDate - now < 2 * oneDay && event.startDate - now > 0) {
- if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
- timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
- }
- }
- }
- } else {
- // Show relative times
- if (event.startDate >= now) {
- // Use relative time
- if (!this.config.hideTime) {
- timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
- } else {
- timeWrapper.innerHTML = this.capFirst(
- moment(event.startDate, "x").calendar(null, {
- sameDay: "[" + this.translate("TODAY") + "]",
- nextDay: "[" + this.translate("TOMORROW") + "]",
- nextWeek: "dddd",
- sameElse: this.config.dateFormat
- })
- );
- }
- if (event.startDate - now < this.config.getRelative * oneHour) {
- // If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
- timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
- }
- } else {
- // Ongoing event
- timeWrapper.innerHTML = this.capFirst(
- this.translate("RUNNING", {
- fallback: this.translate("RUNNING") + " {timeUntilEnd}",
- timeUntilEnd: moment(event.endDate, "x").fromNow(true)
- })
- );
- }
- }
- timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
- eventWrapper.appendChild(timeWrapper);
- }
-
- wrapper.appendChild(eventWrapper);
-
- // Create fade effect.
- if (index >= startFade) {
- currentFadeStep = index - startFade;
- eventWrapper.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
- }
-
- if (this.config.showLocation) {
- if (event.location !== false) {
- const locationRow = document.createElement("tr");
- locationRow.className = "normal xsmall light";
-
- if (this.config.displaySymbol) {
- const symbolCell = document.createElement("td");
- locationRow.appendChild(symbolCell);
- }
-
- const descCell = document.createElement("td");
- descCell.className = "location";
- descCell.colSpan = "2";
- descCell.innerHTML = this.titleTransform(event.location, this.config.locationTitleReplace, this.config.wrapLocationEvents, this.config.maxLocationTitleLength, this.config.maxEventTitleLines);
- locationRow.appendChild(descCell);
-
- wrapper.appendChild(locationRow);
-
- if (index >= startFade) {
- currentFadeStep = index - startFade;
- locationRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
- }
- }
- }
- });
-
- return wrapper;
- },
-
- /**
- * This function accepts a number (either 12 or 24) and returns a moment.js LocaleSpecification with the
- * corresponding timeformat to be used in the calendar display. If no number is given (or otherwise invalid input)
- * it will a localeSpecification object with the system locale time format.
- *
- * @param {number} timeFormat Specifies either 12 or 24 hour time format
- * @returns {moment.LocaleSpecification} formatted time
- */
- getLocaleSpecification: function (timeFormat) {
- switch (timeFormat) {
- case 12: {
- return { longDateFormat: { LT: "h:mm A" } };
- }
- case 24: {
- return { longDateFormat: { LT: "HH:mm" } };
- }
- default: {
- return { longDateFormat: { LT: moment.localeData().longDateFormat("LT") } };
- }
- }
- },
-
- /**
- * Checks if this config contains the calendar url.
- *
- * @param {string} url The calendar url
- * @returns {boolean} True if the calendar config contains the url, False otherwise
- */
- hasCalendarURL: function (url) {
- for (const calendar of this.config.calendars) {
- if (calendar.url === url) {
- return true;
- }
- }
-
- return false;
- },
-
- /**
- * Creates the sorted list of all events.
- *
- * @returns {object[]} Array with events.
- */
- createEventList: function () {
- const now = new Date();
- const today = moment().startOf("day");
- const future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
- let events = [];
-
- for (const calendarUrl in this.calendarData) {
- const calendar = this.calendarData[calendarUrl];
- for (const e in calendar) {
- const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
-
- if (event.endDate < now) {
- continue;
- }
- if (this.config.hidePrivate) {
- if (event.class === "PRIVATE") {
- // do not add the current event, skip it
- continue;
- }
- }
- if (this.config.hideOngoing) {
- if (event.startDate < now) {
- continue;
- }
- }
- if (this.listContainsEvent(events, event)) {
- continue;
- }
- event.url = calendarUrl;
- event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
-
- /* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
- * otherwise, esp. in dateheaders mode it is not clear how long these events are.
- */
- const maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / (1000 * 60 * 60 * 24)) + 1;
- if (this.config.sliceMultiDayEvents && maxCount > 1) {
- const splitEvents = [];
- let midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
- let count = 1;
- while (event.endDate > midnight) {
- const thisEvent = JSON.parse(JSON.stringify(event)); // clone object
- thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + 24 * 60 * 60 * 1000;
- thisEvent.endDate = midnight;
- thisEvent.title += " (" + count + "/" + maxCount + ")";
- splitEvents.push(thisEvent);
-
- event.startDate = midnight;
- count += 1;
- midnight = moment(midnight, "x").add(1, "day").format("x"); // next day
- }
- // Last day
- event.title += " (" + count + "/" + maxCount + ")";
- splitEvents.push(event);
-
- for (let splitEvent of splitEvents) {
- if (splitEvent.endDate > now && splitEvent.endDate <= future) {
- events.push(splitEvent);
- }
- }
- } else {
- events.push(event);
- }
- }
- }
-
- events.sort(function (a, b) {
- return a.startDate - b.startDate;
- });
-
- // Limit the number of days displayed
- // If limitDays is set > 0, limit display to that number of days
- if (this.config.limitDays > 0) {
- let newEvents = [];
- let lastDate = today.clone().subtract(1, "days").format("YYYYMMDD");
- let days = 0;
- for (const ev of events) {
- let eventDate = moment(ev.startDate, "x").format("YYYYMMDD");
- // if date of event is later than lastdate
- // check if we already are showing max unique days
- if (eventDate > lastDate) {
- // if the only entry in the first day is a full day event that day is not counted as unique
- if (newEvents.length === 1 && days === 1 && newEvents[0].fullDayEvent) {
- days--;
- }
- days++;
- if (days > this.config.limitDays) {
- continue;
- } else {
- lastDate = eventDate;
- }
- }
- newEvents.push(ev);
- }
- events = newEvents;
- }
-
- return events.slice(0, this.config.maximumEntries);
- },
-
- listContainsEvent: function (eventList, event) {
- for (const evt of eventList) {
- if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)) {
- return true;
- }
- }
- return false;
- },
-
- /**
- * Requests node helper to add calendar url.
- *
- * @param {string} url The calendar url to add
- * @param {object} auth The authentication method and credentials
- * @param {object} calendarConfig The config of the specific calendar
- */
- addCalendar: function (url, auth, calendarConfig) {
- this.sendSocketNotification("ADD_CALENDAR", {
- id: this.identifier,
- url: url,
- excludedEvents: calendarConfig.excludedEvents || this.config.excludedEvents,
- maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries,
- maximumNumberOfDays: calendarConfig.maximumNumberOfDays || this.config.maximumNumberOfDays,
- fetchInterval: this.config.fetchInterval,
- symbolClass: calendarConfig.symbolClass,
- titleClass: calendarConfig.titleClass,
- timeClass: calendarConfig.timeClass,
- auth: auth,
- broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
- selfSignedCert: calendarConfig.selfSignedCert || this.config.selfSignedCert
- });
- },
-
- /**
- * Retrieves the symbols for a specific event.
- *
- * @param {object} event Event to look for.
- * @returns {string[]} The symbols
- */
- symbolsForEvent: function (event) {
- let symbols = this.getCalendarPropertyAsArray(event.url, "symbol", this.config.defaultSymbol);
-
- if (event.recurringEvent === true && this.hasCalendarProperty(event.url, "recurringSymbol")) {
- symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "recurringSymbol", this.config.defaultSymbol), symbols);
- }
-
- if (event.fullDayEvent === true && this.hasCalendarProperty(event.url, "fullDaySymbol")) {
- symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "fullDaySymbol", this.config.defaultSymbol), symbols);
- }
-
- return symbols;
- },
-
- mergeUnique: function (arr1, arr2) {
- return arr1.concat(
- arr2.filter(function (item) {
- return arr1.indexOf(item) === -1;
- })
- );
- },
-
- /**
- * Retrieves the symbolClass for a specific calendar url.
- *
- * @param {string} url The calendar url
- * @returns {string} The class to be used for the symbols of the calendar
- */
- symbolClassForUrl: function (url) {
- return this.getCalendarProperty(url, "symbolClass", "");
- },
-
- /**
- * Retrieves the titleClass for a specific calendar url.
- *
- * @param {string} url The calendar url
- * @returns {string} The class to be used for the title of the calendar
- */
- titleClassForUrl: function (url) {
- return this.getCalendarProperty(url, "titleClass", "");
- },
-
- /**
- * Retrieves the timeClass for a specific calendar url.
- *
- * @param {string} url The calendar url
- * @returns {string} The class to be used for the time of the calendar
- */
- timeClassForUrl: function (url) {
- return this.getCalendarProperty(url, "timeClass", "");
- },
-
- /**
- * Retrieves the calendar name for a specific calendar url.
- *
- * @param {string} url The calendar url
- * @returns {string} The name of the calendar
- */
- calendarNameForUrl: function (url) {
- return this.getCalendarProperty(url, "name", "");
- },
-
- /**
- * Retrieves the color for a specific calendar url.
- *
- * @param {string} url The calendar url
- * @returns {string} The color
- */
- colorForUrl: function (url) {
- return this.getCalendarProperty(url, "color", "#fff");
- },
-
- /**
- * Retrieves the count title for a specific calendar url.
- *
- * @param {string} url The calendar url
- * @returns {string} The title
- */
- countTitleForUrl: function (url) {
- return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
- },
-
- /**
- * Helper method to retrieve the property for a specific calendar url.
- *
- * @param {string} url The calendar url
- * @param {string} property The property to look for
- * @param {string} defaultValue The value if the property is not found
- * @returns {*} The property
- */
- getCalendarProperty: function (url, property, defaultValue) {
- for (const calendar of this.config.calendars) {
- if (calendar.url === url && calendar.hasOwnProperty(property)) {
- return calendar[property];
- }
- }
-
- return defaultValue;
- },
-
- getCalendarPropertyAsArray: function (url, property, defaultValue) {
- let p = this.getCalendarProperty(url, property, defaultValue);
- if (!(p instanceof Array)) p = [p];
- return p;
- },
-
- hasCalendarProperty: function (url, property) {
- return !!this.getCalendarProperty(url, property, undefined);
- },
-
- /**
- * Shortens a string if it's longer than maxLength and add a ellipsis to the end
- *
- * @param {string} string Text string to shorten
- * @param {number} maxLength The max length of the string
- * @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
- * @param {number} maxTitleLines The max number of vertical lines before cutting event title
- * @returns {string} The shortened string
- */
- shorten: function (string, maxLength, wrapEvents, maxTitleLines) {
- if (typeof string !== "string") {
- return "";
- }
-
- if (wrapEvents === true) {
- const words = string.split(" ");
- let temp = "";
- let currentLine = "";
- let line = 0;
-
- for (let i = 0; i < words.length; i++) {
- const word = words[i];
- if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) {
- // max - 1 to account for a space
- currentLine += word + " ";
- } else {
- line++;
- if (line > maxTitleLines - 1) {
- if (i < words.length) {
- currentLine += "…";
- }
- break;
- }
-
- if (currentLine.length > 0) {
- temp += currentLine + "<br>" + word + " ";
- } else {
- temp += word + "<br>";
- }
- currentLine = "";
- }
- }
-
- return (temp + currentLine).trim();
- } else {
- if (maxLength && typeof maxLength === "number" && string.length > maxLength) {
- return string.trim().slice(0, maxLength) + "…";
- } else {
- return string.trim();
- }
- }
- },
-
- /**
- * Capitalize the first letter of a string
- *
- * @param {string} string The string to capitalize
- * @returns {string} The capitalized string
- */
- capFirst: function (string) {
- return string.charAt(0).toUpperCase() + string.slice(1);
- },
-
- /**
- * Transforms the title of an event for usage.
- * Replaces parts of the text as defined in config.titleReplace.
- * Shortens title based on config.maxTitleLength and config.wrapEvents
- *
- * @param {string} title The title to transform.
- * @param {object} titleReplace Pairs of strings to be replaced in the title
- * @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
- * @param {number} maxTitleLength The max length of the string
- * @param {number} maxTitleLines The max number of vertical lines before cutting event title
- * @returns {string} The transformed title.
- */
- titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
- for (let needle in titleReplace) {
- const replacement = titleReplace[needle];
-
- const regParts = needle.match(/^\/(.+)\/([gim]*)$/);
- if (regParts) {
- // the parsed pattern is a regexp.
- needle = new RegExp(regParts[1], regParts[2]);
- }
-
- title = title.replace(needle, replacement);
- }
-
- title = this.shorten(title, maxTitleLength, wrapEvents, maxTitleLines);
- return title;
- },
-
- /**
- * Broadcasts the events to all other modules for reuse.
- * The all events available in one array, sorted on startdate.
- */
- broadcastEvents: function () {
- const eventList = [];
- for (const url in this.calendarData) {
- for (const ev of this.calendarData[url]) {
- const event = cloneObject(ev);
- event.symbol = this.symbolsForEvent(event);
- event.calendarName = this.calendarNameForUrl(url);
- event.color = this.colorForUrl(url);
- delete event.url;
- eventList.push(event);
- }
- }
-
- eventList.sort(function (a, b) {
- return a.startDate - b.startDate;
- });
-
- this.sendNotification("CALENDAR_EVENTS", eventList);
- }
- });
|