2023-04-09 12:49:50 +02:00
/* global CalendarUtils, cloneObject */
2016-03-31 11:05:32 +02:00
2022-01-26 23:09:26 +01:00
/ * M a g i c M i r r o r ²
2018-06-11 23:09:00 +02:00
* Module : Calendar
*
2020-04-28 23:05:28 +02:00
* By Michael Teeuw https : //michaelteeuw.nl
2018-06-11 23:09:00 +02:00
* MIT Licensed .
* /
2016-10-14 15:23:03 +02:00
Module . register ( "calendar" , {
2018-06-11 23:09:00 +02:00
// Define module defaults
2016-03-31 11:05:32 +02:00
defaults : {
maximumEntries : 10 , // Total Maximum Entries
2016-04-03 19:52:13 +02:00
maximumNumberOfDays : 365 ,
2020-11-21 18:03:34 +01:00
limitDays : 0 , // Limit the number of days shown, 0 = no limit
2023-02-21 22:39:21 +01:00
pastDaysCount : 0 ,
2016-03-31 11:05:32 +02:00
displaySymbol : true ,
2022-01-09 11:25:03 +01:00
defaultSymbol : "calendar-alt" , // Fontawesome Symbol see https://fontawesome.com/cheatsheet?from=io
2022-10-16 21:48:57 +02:00
defaultSymbolClassName : "fas fa-fw fa-" ,
2019-04-08 21:49:19 +02:00
showLocation : false ,
2016-04-26 22:34:12 +02:00
displayRepeatingCountTitle : false ,
2016-10-14 15:23:03 +02:00
defaultRepeatingCountTitle : "" ,
2016-04-03 19:52:13 +02:00
maxTitleLength : 25 ,
2020-06-01 22:25:07 +02:00
maxLocationTitleLength : 25 ,
2023-06-30 20:13:07 +02:00
wrapEvents : false , // Wrap events to multiple lines breaking at maxTitleLength
2020-06-01 22:25:07 +02:00
wrapLocationEvents : false ,
2019-01-16 22:51:44 -08:00
maxTitleLines : 3 ,
2020-06-01 22:25:07 +02:00
maxEventTitleLines : 3 ,
2023-06-30 20:13:07 +02:00
fetchInterval : 60 * 60 * 1000 , // Update every hour
2016-03-31 11:05:32 +02:00
animationSpeed : 2000 ,
2016-04-03 19:52:13 +02:00
fade : true ,
2016-05-10 01:01:00 -06:00
urgency : 7 ,
timeFormat : "relative" ,
2016-11-10 17:26:29 +01:00
dateFormat : "MMM Do" ,
2019-02-19 14:07:01 +01:00
dateEndFormat : "LT" ,
2020-10-12 09:01:50 -05:00
fullDayEventDateFormat : "MMM Do" ,
2018-10-26 15:22:05 +02:00
showEnd : false ,
2016-09-04 00:05:02 +02:00
getRelative : 6 ,
2016-03-31 11:05:32 +02:00
fadePoint : 0.25 , // Start on 1/4th of the list.
2016-11-30 21:24:04 +01:00
hidePrivate : false ,
2018-04-08 14:57:28 +03:00
hideOngoing : false ,
2021-02-09 16:05:38 +01:00
hideTime : false ,
2023-08-26 13:53:41 +02:00
hideDuplicates : true ,
2022-05-10 14:05:38 -04:00
showTimeToday : false ,
2017-01-29 00:59:38 +01:00
colored : false ,
2023-09-20 22:04:41 +02:00
customEvents : [ ] , // Array of {keyword: "", symbol: "", color: "", eventClass: ""} where Keyword is a regexp and symbol/color/eventClass are to be applied for matched
2018-06-11 12:54:27 +02:00
tableClass : "small" ,
2016-04-05 14:35:11 -04:00
calendars : [
2016-03-31 11:05:32 +02:00
{
2022-01-09 08:38:48 +01:00
symbol : "calendar-alt" ,
2020-10-12 09:01:50 -05:00
url : "https://www.calendarlabs.com/templates/ical/US-Holidays.ics"
2020-05-11 22:22:32 +02:00
}
2016-03-31 11:05:32 +02:00
] ,
titleReplace : {
2016-06-06 12:05:41 +02:00
"De verjaardag van " : "" ,
"'s birthday" : ""
2016-04-05 10:01:54 +02:00
} ,
2020-06-01 22:25:07 +02:00
locationTitleReplace : {
"street " : ""
} ,
2017-03-12 11:36:40 -05:00
broadcastEvents : true ,
2019-02-18 21:11:24 +01:00
excludedEvents : [ ] ,
2019-04-01 15:39:25 -04:00
sliceMultiDayEvents : false ,
2019-04-10 16:50:18 -04:00
broadcastPastEvents : false ,
2021-02-21 11:29:19 +01:00
nextDaysRelative : false ,
2023-02-15 15:53:24 -05:00
selfSignedCert : false ,
coloredText : false ,
coloredBorder : false ,
coloredSymbol : false ,
coloredBackground : false ,
limitDaysNeverSkip : false ,
2023-10-21 19:41:17 +02:00
flipDateHeaderTitle : false ,
updateOnFetch : true
2016-03-31 11:05:32 +02:00
} ,
2020-10-11 22:39:42 -05:00
requiresVersion : "2.1.0" ,
2016-03-31 11:05:32 +02:00
// Define required scripts.
2016-10-14 15:23:03 +02:00
getStyles : function ( ) {
2019-01-09 21:38:07 +00:00
return [ "calendar.css" , "font-awesome.css" ] ;
2016-03-31 11:05:32 +02:00
} ,
// Define required scripts.
2016-10-14 15:23:03 +02:00
getScripts : function ( ) {
2023-04-09 12:49:50 +02:00
return [ "calendarutils.js" , "moment.js" ] ;
2016-03-31 11:05:32 +02:00
} ,
2016-04-21 01:04:00 +02:00
// Define required translations.
2016-10-14 15:23:03 +02:00
getTranslations : function ( ) {
2017-03-30 22:14:11 +02:00
// The translations for the default modules are defined in the core translation files.
2021-01-02 21:08:53 +01:00
// Therefore we can just return false. Otherwise we should have returned a dictionary.
2016-06-04 20:32:55 -06:00
// If you're trying to build your own module including translations, check out the documentation.
2016-05-11 12:38:41 +02:00
return false ;
2016-04-21 01:04:00 +02:00
} ,
2016-03-31 11:05:32 +02:00
// Override start method.
2016-10-14 15:23:03 +02:00
start : function ( ) {
2023-03-19 14:32:23 +01:00
Log . info ( ` Starting module: ${ this . name } ` ) ;
2016-03-31 11:05:32 +02:00
2023-02-15 15:53:24 -05:00
if ( this . config . colored ) {
Log . warn ( "Your are using the deprecated config values 'colored'. Please switch to 'coloredSymbol' & 'coloredText'!" ) ;
this . config . coloredText = true ;
this . config . coloredSymbol = true ;
}
if ( this . config . coloredSymbolOnly ) {
Log . warn ( "Your are using the deprecated config values 'coloredSymbolOnly'. Please switch to 'coloredSymbol' & 'coloredText'!" ) ;
this . config . coloredText = false ;
this . config . coloredSymbol = true ;
}
2016-03-31 11:05:32 +02:00
// Set locale.
2023-04-09 12:49:50 +02:00
moment . updateLocale ( config . language , CalendarUtils . getLocaleSpecification ( config . timeFormat ) ) ;
2017-06-26 13:03:03 +02:00
2020-10-12 09:01:50 -05:00
// clear data holder before start
this . calendarData = { } ;
// indicate no data available yet
this . loaded = false ;
2023-10-21 19:41:17 +02:00
// data holder of calendar url. Avoid fade out/in on updateDom (one for each calendar update)
this . calendarDisplayer = { } ;
2021-01-04 21:59:41 +01:00
this . config . calendars . forEach ( ( calendar ) => {
2016-04-05 14:35:11 -04:00
calendar . url = calendar . url . replace ( "webcal://" , "http://" ) ;
2017-01-30 16:26:42 -06:00
2021-01-04 21:59:41 +01:00
const calendarConfig = {
2017-01-30 16:26:42 -06:00
maximumEntries : calendar . maximumEntries ,
2019-04-01 15:39:25 -04:00
maximumNumberOfDays : calendar . maximumNumberOfDays ,
2023-02-21 22:39:21 +01:00
pastDaysCount : calendar . pastDaysCount ,
2021-02-21 11:29:19 +01:00
broadcastPastEvents : calendar . broadcastPastEvents ,
2023-12-21 21:44:17 +01:00
selfSignedCert : calendar . selfSignedCert ,
excludedEvents : calendar . excludedEvents ,
fetchInterval : calendar . fetchInterval
2017-01-30 16:26:42 -06:00
} ;
2021-01-04 21:59:41 +01:00
2023-12-07 08:11:56 +01:00
if ( typeof calendar . symbolClass === "undefined" || calendar . symbolClass === null ) {
2018-10-04 02:07:08 +02:00
calendarConfig . symbolClass = "" ;
}
2023-12-07 08:11:56 +01:00
if ( typeof calendar . titleClass === "undefined" || calendar . titleClass === null ) {
2018-10-04 02:07:08 +02:00
calendarConfig . titleClass = "" ;
}
2023-12-07 08:11:56 +01:00
if ( typeof calendar . timeClass === "undefined" || calendar . timeClass === null ) {
2018-10-04 02:07:08 +02:00
calendarConfig . timeClass = "" ;
}
2017-01-30 16:26:42 -06:00
2017-03-07 00:12:43 +01:00
// we check user and password here for backwards compatibility with old configs
2020-05-11 22:22:32 +02:00
if ( calendar . user && calendar . pass ) {
2017-07-30 22:32:28 -04:00
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" ) ;
2017-03-07 00:12:43 +01:00
calendar . auth = {
user : calendar . user ,
pass : calendar . pass
2019-06-05 10:23:58 +02:00
} ;
2017-03-07 00:12:43 +01:00
}
2020-10-12 09:01:50 -05:00
// tell helper to start a fetcher for this calendar
// fetcher till cycle
2017-03-07 00:12:43 +01:00
this . addCalendar ( calendar . url , calendar . auth , calendarConfig ) ;
2021-01-04 21:59:41 +01:00
} ) ;
2023-01-26 12:55:34 +01:00
Calendar translate (#3249)
Hello and thank you for wanting to contribute to the MagicMirror²
project
**Please make sure that you have followed these 4 rules before
submitting your Pull Request:**
> 1. Base your pull requests against the `develop` branch.
DONE ;D
> 2. Include these infos in the description:
> - Does the pull request solve a **related** issue?
NO
> - What does the pull request accomplish? Use a list if needed.
For calendar entries containing a year (e.g. DOB) in the title, the age
can be calculated.
Example before:

after:

Achieved by adding a new keyword `transform` to customEvents
```
customEvents: [
{keyword: 'Geburtstag', symbol: 'birthday-cake', color: 'Gold', transform: { search: '^([^\']*) \'(\\d{4})$' , replace: '$1 ($2.)', yearmatchgroup: 2}},
{keyword: 'in Hamburg', transform: { search: ' in Hamburg$' , replace: ''}}
],
```
and therewith obsoleting `titleReplace`; a backward compatibility part
is already included.
If `yearmatchgroup` is unset, behaviour is as in previous code (some
additions to which RegExes are accepted, though)
If `yearmatchgroup` is set, it is considered the RegEx match group id,
which will be used for calculating the age.
> - If it includes major visual changes please add screenshots.
NO
> 3. Please run `npm run lint:prettier` before submitting so that style
issues are fixed.
DONE
> 4. Don't forget to add an entry about your changes to the CHANGELOG.md
file.
DONE
> Thanks again and have a nice day!
You too and if any questions, feel free to let me know.
---------
Co-authored-by: veeck <michael.veeck@nebenan.de>
2023-11-01 00:07:56 +01:00
// for backward compatibility titleReplace
if ( typeof this . config . titleReplace !== "undefined" ) {
Log . warn ( "Deprecation warning: Please consider upgrading your calendar titleReplace configuration to customEvents." ) ;
for ( const [ titlesearchstr , titlereplacestr ] of Object . entries ( this . config . titleReplace ) ) {
this . config . customEvents . push ( { keyword : ".*" , transform : { search : titlesearchstr , replace : titlereplacestr } } ) ;
}
}
2023-10-21 19:41:17 +02:00
this . selfUpdate ( ) ;
2016-03-31 11:05:32 +02:00
} ,
// Override socket notification handler.
2016-10-14 15:23:03 +02:00
socketNotificationReceived : function ( notification , payload ) {
2022-07-12 14:35:04 +02:00
if ( notification === "FETCH_CALENDAR" ) {
2022-07-29 10:25:23 +02:00
this . sendSocketNotification ( notification , { url : payload . url , id : this . identifier } ) ;
2022-07-12 14:35:04 +02:00
}
2022-07-29 10:25:23 +02:00
2020-06-01 17:19:41 +02:00
if ( this . identifier !== payload . id ) {
return ;
}
2020-06-01 13:12:54 +02:00
2016-04-05 14:35:11 -04:00
if ( notification === "CALENDAR_EVENTS" ) {
2016-03-31 11:05:32 +02:00
if ( this . hasCalendarURL ( payload . url ) ) {
this . calendarData [ payload . url ] = payload . events ;
2021-05-29 13:22:31 +02:00
this . error = null ;
2016-04-05 10:01:54 +02:00
this . loaded = true ;
2016-10-14 15:23:03 +02:00
if ( this . config . broadcastEvents ) {
this . broadcastEvents ( ) ;
}
2023-10-21 19:41:17 +02:00
if ( ! this . config . updateOnFetch ) {
if ( this . calendarDisplayer [ payload . url ] === undefined ) {
// calendar will never displayed, so display it
this . updateDom ( this . config . animationSpeed ) ;
// set this calendar as displayed
this . calendarDisplayer [ payload . url ] = true ;
} else {
Log . debug ( "[Calendar] DOM not updated waiting self update()" ) ;
}
return ;
}
2016-03-31 11:05:32 +02:00
}
2021-05-02 10:28:11 +02:00
} else if ( notification === "CALENDAR_ERROR" ) {
2021-05-02 14:43:12 +02:00
let error _message = this . translate ( payload . error _type ) ;
this . error = this . translate ( "MODULE_CONFIG_ERROR" , { MODULE _NAME : this . name , ERROR : error _message } ) ;
2019-06-14 14:03:07 +02:00
this . loaded = true ;
2016-03-31 11:05:32 +02:00
}
this . updateDom ( this . config . animationSpeed ) ;
} ,
// Override dom generator.
2016-10-14 15:23:03 +02:00
getDom : function ( ) {
2022-10-11 21:05:11 +02:00
const ONE _SECOND = 1000 ; // 1,000 milliseconds
const ONE _MINUTE = ONE _SECOND * 60 ;
const ONE _HOUR = ONE _MINUTE * 60 ;
const ONE _DAY = ONE _HOUR * 24 ;
2020-11-23 21:48:34 +01:00
2022-01-13 11:01:06 -05:00
const events = this . createEventList ( true ) ;
2021-01-04 21:59:41 +01:00
const wrapper = document . createElement ( "table" ) ;
2018-06-11 12:54:27 +02:00
wrapper . className = this . config . tableClass ;
2016-03-31 11:05:32 +02:00
2021-03-27 14:30:06 +01:00
if ( this . error ) {
wrapper . innerHTML = this . error ;
2023-03-19 14:32:23 +01:00
wrapper . className = ` ${ this . config . tableClass } dimmed ` ;
2021-03-27 14:30:06 +01:00
return wrapper ;
}
2016-03-31 11:05:32 +02:00
if ( events . length === 0 ) {
2020-05-11 22:22:32 +02:00
wrapper . innerHTML = this . loaded ? this . translate ( "EMPTY" ) : this . translate ( "LOADING" ) ;
2023-03-19 14:32:23 +01:00
wrapper . className = ` ${ this . config . tableClass } dimmed ` ;
2016-03-31 11:05:32 +02:00
return wrapper ;
}
2021-02-09 17:37:43 +01:00
let currentFadeStep = 0 ;
let startFade ;
let fadeSteps ;
2018-11-21 09:32:56 +01:00
if ( this . config . fade && this . config . fadePoint < 1 ) {
if ( this . config . fadePoint < 0 ) {
this . config . fadePoint = 0 ;
}
2021-02-09 17:37:43 +01:00
startFade = events . length * this . config . fadePoint ;
fadeSteps = events . length - startFade ;
2018-11-21 09:32:56 +01:00
}
2018-11-21 12:10:39 +01:00
2021-02-09 17:37:43 +01:00
let lastSeenDate = "" ;
2018-05-09 22:32:15 -04:00
2021-02-09 17:37:43 +01:00
events . forEach ( ( event , index ) => {
const dateAsString = moment ( event . startDate , "x" ) . format ( this . config . dateFormat ) ;
2020-05-11 22:22:32 +02:00
if ( this . config . timeFormat === "dateheaders" ) {
if ( lastSeenDate !== dateAsString ) {
2021-01-04 21:59:41 +01:00
const dateRow = document . createElement ( "tr" ) ;
2023-02-15 15:53:24 -05:00
dateRow . className = "dateheader normal" ;
2022-10-11 21:05:11 +02:00
if ( event . today ) dateRow . className += " today" ;
2023-02-21 22:39:21 +01:00
else if ( event . dayBeforeYesterday ) dateRow . className += " dayBeforeYesterday" ;
else if ( event . yesterday ) dateRow . className += " yesterday" ;
2022-10-11 21:05:11 +02:00
else if ( event . tomorrow ) dateRow . className += " tomorrow" ;
2023-02-21 22:39:21 +01:00
else if ( event . dayAfterTomorrow ) dateRow . className += " dayAfterTomorrow" ;
2018-05-09 22:32:15 -04:00
2021-01-04 21:59:41 +01:00
const dateCell = document . createElement ( "td" ) ;
2018-05-09 22:32:15 -04:00
dateCell . colSpan = "3" ;
dateCell . innerHTML = dateAsString ;
2019-12-20 15:20:12 +01:00
dateCell . style . paddingTop = "10px" ;
2018-05-09 22:32:15 -04:00
dateRow . appendChild ( dateCell ) ;
wrapper . appendChild ( dateRow ) ;
2021-02-09 17:37:43 +01:00
if ( this . config . fade && index >= startFade ) {
2020-05-11 22:22:32 +02:00
//fading
2021-02-09 17:37:43 +01:00
currentFadeStep = index - startFade ;
2020-05-11 22:22:32 +02:00
dateRow . style . opacity = 1 - ( 1 / fadeSteps ) * currentFadeStep ;
2018-11-21 09:32:56 +01:00
}
2018-05-09 22:32:15 -04:00
lastSeenDate = dateAsString ;
}
}
2021-01-04 21:59:41 +01:00
const eventWrapper = document . createElement ( "tr" ) ;
2017-01-28 18:21:02 +01:00
2023-02-15 15:53:24 -05:00
if ( this . config . coloredText ) {
2023-03-19 14:32:23 +01:00
eventWrapper . style . cssText = ` color: ${ this . colorForUrl ( event . url , false ) } ` ;
2023-02-15 15:53:24 -05:00
}
if ( this . config . coloredBackground ) {
eventWrapper . style . backgroundColor = this . colorForUrl ( event . url , true ) ;
2017-01-29 00:59:38 +01:00
}
2017-01-28 18:21:02 +01:00
2023-02-15 15:53:24 -05:00
if ( this . config . coloredBorder ) {
eventWrapper . style . borderColor = this . colorForUrl ( event . url , false ) ;
}
eventWrapper . className = "event-wrapper normal event" ;
2022-10-11 21:05:11 +02:00
if ( event . today ) eventWrapper . className += " today" ;
2023-02-21 22:39:21 +01:00
else if ( event . dayBeforeYesterday ) eventWrapper . className += " dayBeforeYesterday" ;
else if ( event . yesterday ) eventWrapper . className += " yesterday" ;
2022-10-11 21:05:11 +02:00
else if ( event . tomorrow ) eventWrapper . className += " tomorrow" ;
2023-02-21 22:39:21 +01:00
else if ( event . dayAfterTomorrow ) eventWrapper . className += " dayAfterTomorrow" ;
2016-03-31 11:05:32 +02:00
2021-01-04 21:59:41 +01:00
const symbolWrapper = document . createElement ( "td" ) ;
2018-04-27 11:06:45 -05:00
2021-01-04 21:59:41 +01:00
if ( this . config . displaySymbol ) {
2023-02-15 15:53:24 -05:00
if ( this . config . coloredSymbol ) {
2023-03-19 14:32:23 +01:00
symbolWrapper . style . cssText = ` color: ${ this . colorForUrl ( event . url , false ) } ` ;
2018-04-27 11:06:45 -05:00
}
2021-01-04 21:59:41 +01:00
const symbolClass = this . symbolClassForUrl ( event . url ) ;
2023-03-19 14:32:23 +01:00
symbolWrapper . className = ` symbol align-right ${ symbolClass } ` ;
2018-10-04 02:07:08 +02:00
2021-01-04 21:59:41 +01:00
const symbols = this . symbolsForEvent ( event ) ;
symbols . forEach ( ( s , index ) => {
const symbol = document . createElement ( "span" ) ;
2022-10-16 21:48:57 +02:00
symbol . className = s ;
2021-01-04 21:59:41 +01:00
if ( index > 0 ) {
2017-03-16 16:57:55 +01:00
symbol . style . paddingLeft = "5px" ;
}
symbolWrapper . appendChild ( symbol ) ;
2021-01-04 21:59:41 +01:00
} ) ;
2016-03-31 11:05:32 +02:00
eventWrapper . appendChild ( symbolWrapper ) ;
2020-05-11 22:22:32 +02:00
} else if ( this . config . timeFormat === "dateheaders" ) {
2021-01-04 21:59:41 +01:00
const blankCell = document . createElement ( "td" ) ;
2018-10-04 02:07:08 +02:00
blankCell . innerHTML = " " ;
2018-05-09 22:32:15 -04:00
eventWrapper . appendChild ( blankCell ) ;
2016-03-31 11:05:32 +02:00
}
2021-01-04 21:59:41 +01:00
const titleWrapper = document . createElement ( "td" ) ;
let repeatingCountTitle = "" ;
2016-05-11 12:38:41 +02:00
2019-01-16 22:53:28 -08:00
if ( this . config . displayRepeatingCountTitle && event . firstYear !== undefined ) {
2016-05-03 11:56:24 +02:00
repeatingCountTitle = this . countTitleForUrl ( event . url ) ;
2016-05-11 12:38:41 +02:00
2016-10-14 15:23:03 +02:00
if ( repeatingCountTitle !== "" ) {
2021-01-04 21:59:41 +01:00
const thisYear = new Date ( parseInt ( event . startDate ) ) . getFullYear ( ) ,
2016-04-26 22:34:12 +02:00
yearDiff = thisYear - event . firstYear ;
2016-05-11 12:38:41 +02:00
2023-04-07 14:54:19 +02:00
repeatingCountTitle = ` , ${ yearDiff } ${ repeatingCountTitle } ` ;
2016-04-26 22:34:12 +02:00
}
2016-05-11 12:38:41 +02:00
}
Calendar translate (#3249)
Hello and thank you for wanting to contribute to the MagicMirror²
project
**Please make sure that you have followed these 4 rules before
submitting your Pull Request:**
> 1. Base your pull requests against the `develop` branch.
DONE ;D
> 2. Include these infos in the description:
> - Does the pull request solve a **related** issue?
NO
> - What does the pull request accomplish? Use a list if needed.
For calendar entries containing a year (e.g. DOB) in the title, the age
can be calculated.
Example before:

after:

Achieved by adding a new keyword `transform` to customEvents
```
customEvents: [
{keyword: 'Geburtstag', symbol: 'birthday-cake', color: 'Gold', transform: { search: '^([^\']*) \'(\\d{4})$' , replace: '$1 ($2.)', yearmatchgroup: 2}},
{keyword: 'in Hamburg', transform: { search: ' in Hamburg$' , replace: ''}}
],
```
and therewith obsoleting `titleReplace`; a backward compatibility part
is already included.
If `yearmatchgroup` is unset, behaviour is as in previous code (some
additions to which RegExes are accepted, though)
If `yearmatchgroup` is set, it is considered the RegEx match group id,
which will be used for calculating the age.
> - If it includes major visual changes please add screenshots.
NO
> 3. Please run `npm run lint:prettier` before submitting so that style
issues are fixed.
DONE
> 4. Don't forget to add an entry about your changes to the CHANGELOG.md
file.
DONE
> Thanks again and have a nice day!
You too and if any questions, feel free to let me know.
---------
Co-authored-by: veeck <michael.veeck@nebenan.de>
2023-11-01 00:07:56 +01:00
var transformedTitle = event . title ;
// Color events if custom color or eventClass are specified, transform title if required
2020-11-28 13:17:14 +01:00
if ( this . config . customEvents . length > 0 ) {
2021-02-09 17:37:43 +01:00
for ( let ev in this . config . customEvents ) {
2023-09-20 22:04:41 +02:00
let needle = new RegExp ( this . config . customEvents [ ev ] . keyword , "gi" ) ;
if ( needle . test ( event . title ) ) {
Calendar translate (#3249)
Hello and thank you for wanting to contribute to the MagicMirror²
project
**Please make sure that you have followed these 4 rules before
submitting your Pull Request:**
> 1. Base your pull requests against the `develop` branch.
DONE ;D
> 2. Include these infos in the description:
> - Does the pull request solve a **related** issue?
NO
> - What does the pull request accomplish? Use a list if needed.
For calendar entries containing a year (e.g. DOB) in the title, the age
can be calculated.
Example before:

after:

Achieved by adding a new keyword `transform` to customEvents
```
customEvents: [
{keyword: 'Geburtstag', symbol: 'birthday-cake', color: 'Gold', transform: { search: '^([^\']*) \'(\\d{4})$' , replace: '$1 ($2.)', yearmatchgroup: 2}},
{keyword: 'in Hamburg', transform: { search: ' in Hamburg$' , replace: ''}}
],
```
and therewith obsoleting `titleReplace`; a backward compatibility part
is already included.
If `yearmatchgroup` is unset, behaviour is as in previous code (some
additions to which RegExes are accepted, though)
If `yearmatchgroup` is set, it is considered the RegEx match group id,
which will be used for calculating the age.
> - If it includes major visual changes please add screenshots.
NO
> 3. Please run `npm run lint:prettier` before submitting so that style
issues are fixed.
DONE
> 4. Don't forget to add an entry about your changes to the CHANGELOG.md
file.
DONE
> Thanks again and have a nice day!
You too and if any questions, feel free to let me know.
---------
Co-authored-by: veeck <michael.veeck@nebenan.de>
2023-11-01 00:07:56 +01:00
if ( typeof this . config . customEvents [ ev ] . transform === "object" ) {
transformedTitle = CalendarUtils . titleTransform ( transformedTitle , [ this . config . customEvents [ ev ] . transform ] ) ;
}
2023-09-20 22:04:41 +02:00
if ( typeof this . config . customEvents [ ev ] . color !== "undefined" && this . config . customEvents [ ev ] . color !== "" ) {
2021-02-09 13:01:57 +01:00
// Respect parameter ColoredSymbolOnly also for custom events
2023-02-15 15:53:24 -05:00
if ( this . config . coloredText ) {
2023-03-19 14:32:23 +01:00
eventWrapper . style . cssText = ` color: ${ this . config . customEvents [ ev ] . color } ` ;
titleWrapper . style . cssText = ` color: ${ this . config . customEvents [ ev ] . color } ` ;
2021-02-09 15:21:14 +01:00
}
2023-02-15 15:53:24 -05:00
if ( this . config . displaySymbol && this . config . coloredSymbol ) {
2023-03-19 14:32:23 +01:00
symbolWrapper . style . cssText = ` color: ${ this . config . customEvents [ ev ] . color } ` ;
2020-11-28 13:17:14 +01:00
}
}
2023-09-20 22:04:41 +02:00
if ( typeof this . config . customEvents [ ev ] . eventClass !== "undefined" && this . config . customEvents [ ev ] . eventClass !== "" ) {
eventWrapper . className += ` ${ this . config . customEvents [ ev ] . eventClass } ` ;
}
2020-11-28 13:17:14 +01:00
}
}
}
2023-04-09 12:49:50 +02:00
titleWrapper . innerHTML = CalendarUtils . shorten ( transformedTitle , this . config . maxTitleLength , this . config . wrapEvents , this . config . maxTitleLines ) + repeatingCountTitle ;
2017-01-28 18:21:02 +01:00
2021-01-04 21:59:41 +01:00
const titleClass = this . titleClassForUrl ( event . url ) ;
2018-10-04 02:07:08 +02:00
2023-02-15 15:53:24 -05:00
if ( ! this . config . coloredText ) {
2023-03-19 14:32:23 +01:00
titleWrapper . className = ` title bright ${ titleClass } ` ;
2017-01-29 00:59:38 +01:00
} else {
2023-03-19 14:32:23 +01:00
titleWrapper . className = ` title ${ titleClass } ` ;
2017-01-29 00:59:38 +01:00
}
2017-01-28 18:21:02 +01:00
2020-05-11 22:22:32 +02:00
if ( this . config . timeFormat === "dateheaders" ) {
2023-02-15 15:53:24 -05:00
if ( this . config . flipDateHeaderTitle ) eventWrapper . appendChild ( titleWrapper ) ;
2018-05-09 22:32:15 -04:00
if ( event . fullDayEvent ) {
titleWrapper . colSpan = "2" ;
2021-04-25 22:34:17 +02:00
titleWrapper . classList . add ( "align-left" ) ;
2019-06-05 09:32:10 +02:00
} else {
2021-01-04 21:59:41 +01:00
const timeWrapper = document . createElement ( "td" ) ;
2023-03-19 14:32:23 +01:00
timeWrapper . className = ` time light ${ this . config . flipDateHeaderTitle ? "align-right " : "align-left " } ${ this . timeClassForUrl ( event . url ) } ` ;
2018-05-09 22:32:15 -04:00
timeWrapper . style . paddingLeft = "2px" ;
2023-02-15 15:53:24 -05:00
timeWrapper . style . textAlign = this . config . flipDateHeaderTitle ? "right" : "left" ;
2018-11-21 12:10:39 +01:00
timeWrapper . innerHTML = moment ( event . startDate , "x" ) . format ( "LT" ) ;
2022-05-10 21:44:15 +02:00
// Add endDate to dataheaders if showEnd is enabled
if ( this . config . showEnd ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML += ` - ${ CalendarUtils . capFirst ( moment ( event . endDate , "x" ) . format ( "LT" ) ) } ` ;
2022-05-10 21:44:15 +02:00
}
2018-05-09 22:32:15 -04:00
eventWrapper . appendChild ( timeWrapper ) ;
2018-05-10 19:54:01 -04:00
2023-02-15 15:53:24 -05:00
if ( ! this . config . flipDateHeaderTitle ) titleWrapper . classList . add ( "align-right" ) ;
}
if ( ! this . config . flipDateHeaderTitle ) eventWrapper . appendChild ( titleWrapper ) ;
2019-06-05 09:32:10 +02:00
} else {
2021-01-04 21:59:41 +01:00
const timeWrapper = document . createElement ( "td" ) ;
2018-05-09 22:32:15 -04:00
eventWrapper . appendChild ( titleWrapper ) ;
2021-01-04 21:59:41 +01:00
const now = new Date ( ) ;
2020-11-23 21:48:34 +01:00
if ( this . config . timeFormat === "absolute" ) {
// Use dateFormat
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( moment ( event . startDate , "x" ) . format ( this . config . dateFormat ) ) ;
2020-11-23 21:48:34 +01:00
// Add end time if showEnd
2020-05-11 22:22:32 +02:00
if ( this . config . showEnd ) {
timeWrapper . innerHTML += "-" ;
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML += CalendarUtils . capFirst ( moment ( event . endDate , "x" ) . format ( this . config . dateEndFormat ) ) ;
2018-08-28 17:35:53 +02:00
}
2020-11-23 21:48:34 +01:00
// 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
2022-10-11 21:05:11 +02:00
event . endDate -= ONE _SECOND ;
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( moment ( event . startDate , "x" ) . format ( this . config . fullDayEventDateFormat ) ) ;
2022-03-27 09:16:25 -05:00
} else if ( this . config . getRelative > 0 && event . startDate < now ) {
2020-11-23 21:48:34 +01:00
// Ongoing and getRelative is set
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst (
2020-11-23 21:48:34 +01:00
this . translate ( "RUNNING" , {
2023-03-19 14:32:23 +01:00
fallback : ` ${ this . translate ( "RUNNING" ) } {timeUntilEnd} ` ,
2020-11-23 21:48:34 +01:00
timeUntilEnd : moment ( event . endDate , "x" ) . fromNow ( true )
} )
) ;
2022-10-11 21:05:11 +02:00
} else if ( this . config . urgency > 0 && event . startDate - now < this . config . urgency * ONE _DAY ) {
2020-11-23 21:48:34 +01:00
// Within urgency days
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( moment ( event . startDate , "x" ) . fromNow ( ) ) ;
2020-11-23 21:48:34 +01:00
}
if ( event . fullDayEvent && this . config . nextDaysRelative ) {
// Full days events within the next two days
if ( event . today ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "TODAY" ) ) ;
2023-02-21 22:39:21 +01:00
} else if ( event . yesterday ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "YESTERDAY" ) ) ;
2022-10-11 21:05:11 +02:00
} else if ( event . startDate - now < ONE _DAY && event . startDate - now > 0 ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "TOMORROW" ) ) ;
2022-10-11 21:05:11 +02:00
} else if ( event . startDate - now < 2 * ONE _DAY && event . startDate - now > 0 ) {
2020-11-23 21:48:34 +01:00
if ( this . translate ( "DAYAFTERTOMORROW" ) !== "DAYAFTERTOMORROW" ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "DAYAFTERTOMORROW" ) ) ;
2018-05-09 22:32:15 -04:00
}
}
2020-11-23 21:48:34 +01:00
}
} else {
// Show relative times
2021-12-21 15:22:52 +01:00
if ( event . startDate >= now || ( event . fullDayEvent && event . today ) ) {
2022-10-11 21:05:11 +02:00
// Use relative time
2021-12-21 15:22:52 +01:00
if ( ! this . config . hideTime && ! event . fullDayEvent ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( moment ( event . startDate , "x" ) . calendar ( null , { sameElse : this . config . dateFormat } ) ) ;
2021-02-09 16:05:38 +01:00
} else {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst (
2021-02-09 16:05:38 +01:00
moment ( event . startDate , "x" ) . calendar ( null , {
2023-03-19 14:32:23 +01:00
sameDay : this . config . showTimeToday ? "LT" : ` [ ${ this . translate ( "TODAY" ) } ] ` ,
nextDay : ` [ ${ this . translate ( "TOMORROW" ) } ] ` ,
2021-04-13 07:29:11 +01:00
nextWeek : "dddd" ,
2021-12-21 15:22:52 +01:00
sameElse : event . fullDayEvent ? this . config . fullDayEventDateFormat : this . config . dateFormat
2021-02-09 16:05:38 +01:00
} )
) ;
}
2021-11-06 15:03:08 +01:00
if ( event . fullDayEvent ) {
2021-10-24 18:02:19 +02:00
// Full days events within the next two days
if ( event . today ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "TODAY" ) ) ;
2023-02-21 22:39:21 +01:00
} else if ( event . dayBeforeYesterday ) {
if ( this . translate ( "DAYBEFOREYESTERDAY" ) !== "DAYBEFOREYESTERDAY" ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "DAYBEFOREYESTERDAY" ) ) ;
2023-02-21 22:39:21 +01:00
}
} else if ( event . yesterday ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "YESTERDAY" ) ) ;
2022-10-11 21:05:11 +02:00
} else if ( event . startDate - now < ONE _DAY && event . startDate - now > 0 ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "TOMORROW" ) ) ;
2022-10-11 21:05:11 +02:00
} else if ( event . startDate - now < 2 * ONE _DAY && event . startDate - now > 0 ) {
2021-10-24 18:02:19 +02:00
if ( this . translate ( "DAYAFTERTOMORROW" ) !== "DAYAFTERTOMORROW" ) {
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( this . translate ( "DAYAFTERTOMORROW" ) ) ;
2021-10-24 18:02:19 +02:00
}
}
2022-10-11 21:05:11 +02:00
} else if ( event . startDate - now < this . config . getRelative * ONE _HOUR ) {
2022-05-10 21:44:15 +02:00
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst ( moment ( event . startDate , "x" ) . fromNow ( ) ) ;
2021-12-21 15:22:52 +01:00
}
} else {
2020-11-23 21:48:34 +01:00
// Ongoing event
2023-04-09 12:49:50 +02:00
timeWrapper . innerHTML = CalendarUtils . capFirst (
2018-05-09 22:32:15 -04:00
this . translate ( "RUNNING" , {
2023-03-19 14:32:23 +01:00
fallback : ` ${ this . translate ( "RUNNING" ) } {timeUntilEnd} ` ,
2018-05-09 22:32:15 -04:00
timeUntilEnd : moment ( event . endDate , "x" ) . fromNow ( true )
} )
) ;
}
2016-04-15 13:13:06 +02:00
}
2023-03-19 14:32:23 +01:00
timeWrapper . className = ` time light ${ this . timeClassForUrl ( event . url ) } ` ;
2018-05-09 22:32:15 -04:00
eventWrapper . appendChild ( timeWrapper ) ;
2016-04-15 12:50:34 +02:00
}
2016-03-31 11:05:32 +02:00
// Create fade effect.
2021-02-09 17:37:43 +01:00
if ( index >= startFade ) {
currentFadeStep = index - startFade ;
2020-05-11 22:22:32 +02:00
eventWrapper . style . opacity = 1 - ( 1 / fadeSteps ) * currentFadeStep ;
2016-03-31 11:05:32 +02:00
}
2023-02-15 15:53:24 -05:00
wrapper . appendChild ( eventWrapper ) ;
2019-04-08 21:49:19 +02:00
if ( this . config . showLocation ) {
if ( event . location !== false ) {
2021-01-04 21:59:41 +01:00
const locationRow = document . createElement ( "tr" ) ;
2023-02-15 15:53:24 -05:00
locationRow . className = "event-wrapper-location normal xsmall light" ;
2022-10-11 21:05:11 +02:00
if ( event . today ) locationRow . className += " today" ;
2023-02-21 22:39:21 +01:00
else if ( event . dayBeforeYesterday ) locationRow . className += " dayBeforeYesterday" ;
else if ( event . yesterday ) locationRow . className += " yesterday" ;
2022-10-11 21:05:11 +02:00
else if ( event . tomorrow ) locationRow . className += " tomorrow" ;
2023-02-21 22:39:21 +01:00
else if ( event . dayAfterTomorrow ) locationRow . className += " dayAfterTomorrow" ;
2019-04-08 21:49:19 +02:00
if ( this . config . displaySymbol ) {
2021-01-04 21:59:41 +01:00
const symbolCell = document . createElement ( "td" ) ;
2019-04-08 21:49:19 +02:00
locationRow . appendChild ( symbolCell ) ;
}
2023-02-15 15:53:24 -05:00
if ( this . config . coloredText ) {
2023-03-19 14:32:23 +01:00
locationRow . style . cssText = ` color: ${ this . colorForUrl ( event . url , false ) } ` ;
2023-02-15 15:53:24 -05:00
}
if ( this . config . coloredBackground ) {
locationRow . style . backgroundColor = this . colorForUrl ( event . url , true ) ;
}
if ( this . config . coloredBorder ) {
locationRow . style . borderColor = this . colorForUrl ( event . url , false ) ;
}
2021-01-04 21:59:41 +01:00
const descCell = document . createElement ( "td" ) ;
2019-04-08 21:49:19 +02:00
descCell . className = "location" ;
descCell . colSpan = "2" ;
2023-04-09 12:49:50 +02:00
const transformedTitle = CalendarUtils . titleTransform ( event . location , this . config . locationTitleReplace ) ;
descCell . innerHTML = CalendarUtils . shorten ( transformedTitle , this . config . maxLocationTitleLength , this . config . wrapLocationEvents , this . config . maxEventTitleLines ) ;
2019-04-08 21:49:19 +02:00
locationRow . appendChild ( descCell ) ;
wrapper . appendChild ( locationRow ) ;
2021-02-09 17:37:43 +01:00
if ( index >= startFade ) {
currentFadeStep = index - startFade ;
2020-05-11 22:22:32 +02:00
locationRow . style . opacity = 1 - ( 1 / fadeSteps ) * currentFadeStep ;
2019-04-08 21:49:19 +02:00
}
}
}
2021-02-09 17:37:43 +01:00
} ) ;
2018-11-21 12:10:39 +01:00
2016-03-31 11:05:32 +02:00
return wrapper ;
} ,
2020-07-30 12:54:39 +02:00
/ * *
* 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
2018-06-11 23:09:00 +02:00
* /
2016-10-14 15:23:03 +02:00
hasCalendarURL : function ( url ) {
2021-01-04 21:59:41 +01:00
for ( const calendar of this . config . calendars ) {
2016-03-31 11:05:32 +02:00
if ( calendar . url === url ) {
return true ;
}
}
return false ;
} ,
2020-07-30 12:54:39 +02:00
/ * *
2018-06-11 23:09:00 +02:00
* Creates the sorted list of all events .
2022-01-17 10:48:16 -05:00
* @ param { boolean } limitNumberOfEntries Whether to filter returned events for display .
2020-08-03 11:19:54 +02:00
* @ returns { object [ ] } Array with events .
2018-06-11 23:09:00 +02:00
* /
2022-01-17 10:48:16 -05:00
createEventList : function ( limitNumberOfEntries ) {
2022-10-11 21:05:11 +02:00
const ONE _SECOND = 1000 ; // 1,000 milliseconds
const ONE _MINUTE = ONE _SECOND * 60 ;
const ONE _HOUR = ONE _MINUTE * 60 ;
const ONE _DAY = ONE _HOUR * 24 ;
2021-01-04 21:59:41 +01:00
const now = new Date ( ) ;
const today = moment ( ) . startOf ( "day" ) ;
const future = moment ( ) . startOf ( "day" ) . add ( this . config . maximumNumberOfDays , "days" ) . toDate ( ) ;
let events = [ ] ;
2021-02-09 17:37:43 +01:00
for ( const calendarUrl in this . calendarData ) {
const calendar = this . calendarData [ calendarUrl ] ;
2022-07-02 21:08:43 -04:00
let remainingEntries = this . maximumEntriesForUrl ( calendarUrl ) ;
2023-02-21 22:39:21 +01:00
let maxPastDaysCompare = now - this . maximumPastDaysForUrl ( calendarUrl ) * ONE _DAY ;
2021-02-09 17:37:43 +01:00
for ( const e in calendar ) {
const event = JSON . parse ( JSON . stringify ( calendar [ e ] ) ) ; // clone object
2020-06-27 19:43:09 +12:00
2022-06-09 09:21:00 -04:00
if ( this . config . hidePrivate && event . class === "PRIVATE" ) {
// do not add the current event, skip it
2019-04-01 15:39:25 -04:00
continue ;
}
2022-06-09 09:21:00 -04:00
if ( limitNumberOfEntries ) {
2023-02-21 22:39:21 +01:00
if ( event . endDate < maxPastDaysCompare ) {
2020-04-20 22:16:23 +02:00
continue ;
2018-04-08 14:57:28 +03:00
}
2022-06-09 09:21:00 -04:00
if ( this . config . hideOngoing && event . startDate < now ) {
continue ;
}
2023-08-26 13:53:41 +02:00
if ( this . config . hideDuplicates && this . listContainsEvent ( events , event ) ) {
2018-04-08 14:57:28 +03:00
continue ;
2016-11-30 21:09:57 +01:00
}
2022-07-02 21:08:43 -04:00
if ( -- remainingEntries < 0 ) {
break ;
}
2018-05-09 22:32:15 -04:00
}
2023-08-26 13:53:41 +02:00
2021-02-09 17:37:43 +01:00
event . url = calendarUrl ;
2022-10-11 21:05:11 +02:00
event . today = event . startDate >= today && event . startDate < today + ONE _DAY ;
2023-02-21 22:39:21 +01:00
event . dayBeforeYesterday = event . startDate >= today - ONE _DAY * 2 && event . startDate < today - ONE _DAY ;
event . yesterday = event . startDate >= today - ONE _DAY && event . startDate < today ;
2022-10-11 21:05:11 +02:00
event . tomorrow = ! event . today && event . startDate >= today + ONE _DAY && event . startDate < today + 2 * ONE _DAY ;
2023-02-21 22:39:21 +01:00
event . dayAfterTomorrow = ! event . tomorrow && event . startDate >= today + ONE _DAY * 2 && event . startDate < today + 3 * ONE _DAY ;
2019-02-18 21:11:24 +01:00
/ * i f s l i c e M u l t i D a y E v e n t s i s s e t t o t r u e , m u l t i d a y e v e n t s ( e v e n t s e x c e e d i n g a t l e a s t o n e m i d n i g h t ) a r e s l i c e d i n t o d a y s ,
2020-05-11 22:22:32 +02:00
* otherwise , esp . in dateheaders mode it is not clear how long these events are .
* /
2022-10-11 21:05:11 +02:00
const maxCount = Math . ceil ( ( event . endDate - 1 - moment ( event . startDate , "x" ) . endOf ( "day" ) . format ( "x" ) ) / ONE _DAY ) + 1 ;
2019-05-14 09:53:34 -05:00
if ( this . config . sliceMultiDayEvents && maxCount > 1 ) {
2021-01-04 21:59:41 +01:00
const splitEvents = [ ] ;
let midnight = moment ( event . startDate , "x" ) . clone ( ) . startOf ( "day" ) . add ( 1 , "day" ) . format ( "x" ) ;
let count = 1 ;
2019-04-10 10:31:55 +02:00
while ( event . endDate > midnight ) {
2021-02-09 17:37:43 +01:00
const thisEvent = JSON . parse ( JSON . stringify ( event ) ) ; // clone object
2022-10-11 21:05:11 +02:00
thisEvent . today = thisEvent . startDate >= today && thisEvent . startDate < today + ONE _DAY ;
thisEvent . tomorrow = ! thisEvent . today && thisEvent . startDate >= today + ONE _DAY && thisEvent . startDate < today + 2 * ONE _DAY ;
2019-04-10 10:31:55 +02:00
thisEvent . endDate = midnight ;
2023-03-19 14:32:23 +01:00
thisEvent . title += ` ( ${ count } / ${ maxCount } ) ` ;
2019-04-10 10:31:55 +02:00
splitEvents . push ( thisEvent ) ;
event . startDate = midnight ;
count += 1 ;
midnight = moment ( midnight , "x" ) . add ( 1 , "day" ) . format ( "x" ) ; // next day
}
// Last day
2023-03-19 14:32:23 +01:00
event . title += ` ( ${ count } / ${ maxCount } ) ` ;
2022-10-11 21:05:11 +02:00
event . today += event . startDate >= today && event . startDate < today + ONE _DAY ;
event . tomorrow = ! event . today && event . startDate >= today + ONE _DAY && event . startDate < today + 2 * ONE _DAY ;
2019-04-10 10:31:55 +02:00
splitEvents . push ( event ) ;
2021-01-04 21:59:41 +01:00
for ( let splitEvent of splitEvents ) {
if ( splitEvent . endDate > now && splitEvent . endDate <= future ) {
events . push ( splitEvent ) ;
2019-04-10 10:31:55 +02:00
}
}
2019-02-18 21:11:24 +01:00
} else {
events . push ( event ) ;
}
2016-03-31 11:05:32 +02:00
}
}
2016-10-14 15:23:03 +02:00
events . sort ( function ( a , b ) {
2016-03-31 11:05:32 +02:00
return a . startDate - b . startDate ;
} ) ;
2020-11-21 18:03:34 +01:00
2022-01-17 10:48:16 -05:00
if ( ! limitNumberOfEntries ) {
2022-01-13 11:01:06 -05:00
return events ;
}
2020-11-23 21:48:34 +01:00
// Limit the number of days displayed
2020-11-21 18:03:34 +01:00
// If limitDays is set > 0, limit display to that number of days
if ( this . config . limitDays > 0 ) {
2021-01-04 21:59:41 +01:00
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" ) ;
2020-11-23 21:48:34 +01:00
// 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
2023-02-15 15:53:24 -05:00
if ( ! this . config . limitDaysNeverSkip && newEvents . length === 1 && days === 1 && newEvents [ 0 ] . fullDayEvent ) {
2020-11-23 21:48:34 +01:00
days -- ;
}
days ++ ;
if ( days > this . config . limitDays ) {
continue ;
} else {
lastDate = eventDate ;
}
}
2020-11-24 01:20:19 +01:00
newEvents . push ( ev ) ;
2020-11-21 18:03:34 +01:00
}
2020-11-23 21:48:34 +01:00
events = newEvents ;
2020-11-21 18:03:34 +01:00
}
2017-10-01 21:36:43 +02:00
return events . slice ( 0 , this . config . maximumEntries ) ;
2016-03-31 11:05:32 +02:00
} ,
2020-05-11 22:22:32 +02:00
listContainsEvent : function ( eventList , event ) {
2021-01-04 21:59:41 +01:00
for ( const evt of eventList ) {
2023-08-26 13:53:41 +02:00
if ( evt . title === event . title && parseInt ( evt . startDate ) === parseInt ( event . startDate ) && parseInt ( evt . endDate ) === parseInt ( event . endDate ) ) {
2018-05-09 22:32:15 -04:00
return true ;
}
}
return false ;
} ,
2020-07-30 12:54:39 +02:00
/ * *
* 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
* /
2017-03-07 00:12:43 +01:00
addCalendar : function ( url , auth , calendarConfig ) {
2016-04-05 14:35:11 -04:00
this . sendSocketNotification ( "ADD_CALENDAR" , {
2020-06-01 13:12:54 +02:00
id : this . identifier ,
2016-03-31 11:05:32 +02:00
url : url ,
2017-07-27 17:59:23 +02:00
excludedEvents : calendarConfig . excludedEvents || this . config . excludedEvents ,
2017-01-30 16:26:42 -06:00
maximumEntries : calendarConfig . maximumEntries || this . config . maximumEntries ,
maximumNumberOfDays : calendarConfig . maximumNumberOfDays || this . config . maximumNumberOfDays ,
2023-02-21 22:39:21 +01:00
pastDaysCount : calendarConfig . pastDaysCount || this . config . pastDaysCount ,
2023-06-30 20:13:07 +02:00
fetchInterval : calendarConfig . fetchInterval || this . config . fetchInterval ,
2018-10-04 02:07:08 +02:00
symbolClass : calendarConfig . symbolClass ,
titleClass : calendarConfig . titleClass ,
timeClass : calendarConfig . timeClass ,
2019-04-01 15:39:25 -04:00
auth : auth ,
2021-02-21 11:29:19 +01:00
broadcastPastEvents : calendarConfig . broadcastPastEvents || this . config . broadcastPastEvents ,
selfSignedCert : calendarConfig . selfSignedCert || this . config . selfSignedCert
2016-03-31 11:05:32 +02:00
} ) ;
} ,
2018-10-04 02:07:08 +02:00
/ * *
2020-07-08 21:53:34 +02:00
* Retrieves the symbols for a specific event .
2020-07-27 14:24:30 +02:00
* @ param { object } event Event to look for .
2020-07-30 12:54:39 +02:00
* @ returns { string [ ] } The symbols
2018-06-11 23:09:00 +02:00
* /
2020-07-08 21:53:34 +02:00
symbolsForEvent : function ( event ) {
2020-07-14 21:05:44 +02:00
let symbols = this . getCalendarPropertyAsArray ( event . url , "symbol" , this . config . defaultSymbol ) ;
2020-07-18 21:10:36 +02:00
if ( event . recurringEvent === true && this . hasCalendarProperty ( event . url , "recurringSymbol" ) ) {
symbols = this . mergeUnique ( this . getCalendarPropertyAsArray ( event . url , "recurringSymbol" , this . config . defaultSymbol ) , symbols ) ;
2020-07-08 21:53:34 +02:00
}
2020-07-14 21:05:44 +02:00
2020-07-18 21:10:36 +02:00
if ( event . fullDayEvent === true && this . hasCalendarProperty ( event . url , "fullDaySymbol" ) ) {
symbols = this . mergeUnique ( this . getCalendarPropertyAsArray ( event . url , "fullDaySymbol" , this . config . defaultSymbol ) , symbols ) ;
2020-07-08 21:53:34 +02:00
}
2021-12-26 09:25:28 -05:00
// If custom symbol is set, replace event symbol
for ( let ev of this . config . customEvents ) {
if ( typeof ev . symbol !== "undefined" && ev . symbol !== "" ) {
let needle = new RegExp ( ev . keyword , "gi" ) ;
if ( needle . test ( event . title ) ) {
2023-01-16 21:33:05 +00:00
// Get the default prefix for this class name and add to the custom symbol provided
const className = this . getCalendarProperty ( event . url , "symbolClassName" , this . config . defaultSymbolClassName ) ;
symbols [ 0 ] = className + ev . symbol ;
2021-12-26 09:25:28 -05:00
break ;
}
}
}
2020-07-14 21:05:44 +02:00
return symbols ;
} ,
mergeUnique : function ( arr1 , arr2 ) {
return arr1 . concat (
arr2 . filter ( function ( item ) {
return arr1 . indexOf ( item ) === - 1 ;
} )
) ;
2016-03-31 11:05:32 +02:00
} ,
2017-01-28 18:21:02 +01:00
2018-10-04 02:07:08 +02:00
/ * *
2020-07-30 12:54:39 +02:00
* 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
2018-10-04 02:07:08 +02:00
* /
symbolClassForUrl : function ( url ) {
return this . getCalendarProperty ( url , "symbolClass" , "" ) ;
} ,
/ * *
2020-07-30 12:54:39 +02:00
* 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
2018-10-04 02:07:08 +02:00
* /
titleClassForUrl : function ( url ) {
return this . getCalendarProperty ( url , "titleClass" , "" ) ;
} ,
/ * *
2020-07-30 12:54:39 +02:00
* 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
2018-10-04 02:07:08 +02:00
* /
timeClassForUrl : function ( url ) {
return this . getCalendarProperty ( url , "timeClass" , "" ) ;
} ,
2020-07-30 12:54:39 +02:00
/ * *
* Retrieves the calendar name for a specific calendar url .
* @ param { string } url The calendar url
* @ returns { string } The name of the calendar
2019-03-26 09:02:19 -04:00
* /
calendarNameForUrl : function ( url ) {
return this . getCalendarProperty ( url , "name" , "" ) ;
} ,
2020-07-30 12:54:39 +02:00
/ * *
* Retrieves the color for a specific calendar url .
* @ param { string } url The calendar url
2023-02-15 15:53:24 -05:00
* @ param { boolean } isBg Determines if we fetch the bgColor or not
2020-07-30 12:54:39 +02:00
* @ returns { string } The color
2018-06-11 23:09:00 +02:00
* /
2023-02-15 15:53:24 -05:00
colorForUrl : function ( url , isBg ) {
return this . getCalendarProperty ( url , isBg ? "bgColor" : "color" , "#fff" ) ;
2017-01-28 18:21:02 +01:00
} ,
2017-02-07 23:51:13 +01:00
2020-07-30 12:54:39 +02:00
/ * *
* Retrieves the count title for a specific calendar url .
* @ param { string } url The calendar url
* @ returns { string } The title
2018-06-11 23:09:00 +02:00
* /
2016-10-14 15:23:03 +02:00
countTitleForUrl : function ( url ) {
2017-02-07 23:51:13 +01:00
return this . getCalendarProperty ( url , "repeatingCountTitle" , this . config . defaultRepeatingCountTitle ) ;
} ,
2016-04-26 22:34:12 +02:00
2022-07-02 21:08:43 -04:00
/ * *
* Retrieves the maximum entry count for a specific calendar url .
* @ param { string } url The calendar url
2022-08-08 10:30:37 +02:00
* @ returns { number } The maximum entry count
2022-07-02 21:08:43 -04:00
* /
maximumEntriesForUrl : function ( url ) {
return this . getCalendarProperty ( url , "maximumEntries" , this . config . maximumEntries ) ;
} ,
2023-02-21 22:39:21 +01:00
/ * *
* Retrieves the maximum count of past days which events of should be displayed for a specific calendar url .
* @ param { string } url The calendar url
* @ returns { number } The maximum past days count
* /
maximumPastDaysForUrl : function ( url ) {
return this . getCalendarProperty ( url , "pastDaysCount" , this . config . pastDaysCount ) ;
} ,
2020-07-30 12:54:39 +02:00
/ * *
* 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
2018-06-11 23:09:00 +02:00
* /
2017-02-07 23:51:13 +01:00
getCalendarProperty : function ( url , property , defaultValue ) {
2021-01-04 21:59:41 +01:00
for ( const calendar of this . config . calendars ) {
2017-03-16 16:57:55 +01:00
if ( calendar . url === url && calendar . hasOwnProperty ( property ) ) {
2017-02-08 00:05:28 +01:00
return calendar [ property ] ;
}
}
return defaultValue ;
2016-04-26 22:34:12 +02:00
} ,
2016-03-31 11:05:32 +02:00
2020-07-14 21:05:44 +02:00
getCalendarPropertyAsArray : function ( url , property , defaultValue ) {
let p = this . getCalendarProperty ( url , property , defaultValue ) ;
2022-10-16 21:48:57 +02:00
if ( property === "symbol" || property === "recurringSymbol" || property === "fullDaySymbol" ) {
const className = this . getCalendarProperty ( url , "symbolClassName" , this . config . defaultSymbolClassName ) ;
p = className + p ;
}
2020-07-14 21:05:44 +02:00
if ( ! ( p instanceof Array ) ) p = [ p ] ;
return p ;
} ,
2020-07-18 21:10:36 +02:00
hasCalendarProperty : function ( url , property ) {
return ! ! this . getCalendarProperty ( url , property , undefined ) ;
} ,
2020-07-30 12:54:39 +02:00
/ * *
2018-06-11 23:09:00 +02:00
* Broadcasts the events to all other modules for reuse .
* The all events available in one array , sorted on startdate .
* /
2016-10-14 15:23:03 +02:00
broadcastEvents : function ( ) {
2022-01-13 11:01:06 -05:00
const eventList = this . createEventList ( false ) ;
for ( const event of eventList ) {
event . symbol = this . symbolsForEvent ( event ) ;
event . calendarName = this . calendarNameForUrl ( event . url ) ;
2023-02-15 15:53:24 -05:00
event . color = this . colorForUrl ( event . url , false ) ;
2022-01-13 11:01:06 -05:00
delete event . url ;
2016-10-14 15:23:03 +02:00
}
this . sendNotification ( "CALENDAR_EVENTS" , eventList ) ;
2023-10-21 19:41:17 +02:00
} ,
/ * *
* Refresh the DOM every minute if needed : When using relative date format for events that start
* or end in less than an hour , the date shows minute granularity and we want to keep that accurate .
* --
* When updateOnFetch is not set , it will Avoid fade out / in on updateDom when many calendars are used
* and it ' s allow to refresh The DOM every minute with animation speed too
* ( because updateDom is not set in CALENDAR _EVENTS for this case )
* /
selfUpdate : function ( ) {
const ONE _MINUTE = 60 * 1000 ;
setTimeout (
( ) => {
setInterval ( ( ) => {
Log . debug ( "[Calendar] self update" ) ;
if ( this . config . updateOnFetch ) {
this . updateDom ( 1 ) ;
} else {
this . updateDom ( this . config . animationSpeed ) ;
}
} , ONE _MINUTE ) ;
} ,
ONE _MINUTE - ( new Date ( ) % ONE _MINUTE )
) ;
2016-03-31 11:05:32 +02:00
}
2016-04-03 19:52:13 +02:00
} ) ;