MagicMirror Forum
    • Recent
    • Tags
    • Unsolved
    • Solved
    • MagicMirror² Repository
    • Documentation
    • 3rd-Party-Modules
    • Donate
    • Discord
    • Register
    • Login
    1. Home
    2. redux703
    A New Chapter for MagicMirror: The Community Takes the Lead
    Read the statement by Michael Teeuw here.
    R
    Offline
    • Profile
    • Following 0
    • Followers 0
    • Topics 3
    • Posts 11
    • Groups 0

    redux703

    @redux703

    1
    Reputation
    1
    Profile views
    11
    Posts
    0
    Followers
    0
    Following
    Joined
    Last Online

    redux703 Unfollow Follow

    Best posts made by redux703

    • RE: mmm-calendarEXT3 multiple questions

      @sdetweil Thanks! This worked like a charm. Also had to add to normal calendar module

      posted in Troubleshooting
      R
      redux703

    Latest posts made by redux703

    • RE: MMM-calendarext3 wrong month

      @sdetweil I changed it back now it works again thanks!

      posted in Troubleshooting
      R
      redux703
    • RE: MMM-calendarext3 wrong month

      @sdetweil

      {
                  module: "MMM-CalendarExt3", //Maand Agenda
                  classes: "page0",
                  position: "middle_center",
                  title: "",
                  config: {
                      broadcastPastEvents: true, // <= IMPORTANT to see past events
                      mode: "month",
                      instanceId: "basicCalendar",
                      locale: 'nl-NL',
                      maxEventLines: 6,
                      firstDayOfWeek: 1,
                      weeksInView: 5,
                      //monthIndex: -1,
                      calendarSet: ["Algemeen"],
                  }
              },
      
      posted in Troubleshooting
      R
      redux703
    • RE: MMM-calendarext3 wrong month

      @sdetweil

      /* global Log, Module, config */
      /* eslint-disable no-unused-vars */
      
      const popoverSupported = Object.prototype.hasOwnProperty.call(HTMLElement.prototype, "popover")
      /*
      Even though `.prototype.hasOwnProperty` is not recommended, it's the only way to check the existence of `popover` feature at this moment.
      console.log(Object.prototype.hasOwnProperty.call(HTMLElement, "popover")) // false
      console.log(Object.hasOwn(HTMLElement, "popover")) // false
      console.log(HTMLElement.prototype.showPopover === 'function') // false
      consoe.log(HTMLElement.prototype.hasOwnProperty("popover")) // true
      */
      
      if (!popoverSupported) console.info("This browser doesn't support popover yet. Update your system.")
      const animationSupported = (typeof window !== "undefined" && window?.mmVersion) ? +(window.mmVersion.split(".").join("")) >= 2250 : false
      
      Module.register("MMM-CalendarExt3", {
        defaults: {
          mode: "week", // or 'month', 'day'
          weekIndex: -1, // Which week from this week starts in a view. Ignored on mode 'month'
          dayIndex: -1,
          weeksInView: 3, //  How many weeks will be displayed. Ignored on mode 'month'
          instanceId: null,
          firstDayOfWeek: 1, // 0: Sunday, 1: Monday
          minimalDaysOfNewYear: null, // When the first week of new year starts in your country.
          weekends: [], // or [0, 6]. 0: Sunday, 6: Saturday
          locale: null, // 'de' or 'en-US' or prefer array like ['en-CA', 'en-US', 'en']
          cellDateOptions: {
            month: "short",
            day: "numeric"
          },
          eventTimeOptions: {
            timeStyle: "short"
          },
          headerWeekDayOptions: {
            weekday: "long"
          },
          headerTitleOptions: {
            month: "long"
          },
          calendarSet: [],
          maxEventLines: 5, // How many events will be shown in a day cell.
          // It could be possible to use {} like {"4": 6, "5": 5, "6": 4} to set different lines by the number of the week of the month.
          // Also, it could be possible to use [] like [8, 8, 7, 6, 5] to set different lines by the number of week of the month.
          fontSize: "20px",
          eventHeight: "22px",
          eventFilter: (ev) => { return true },
          eventSorter: null,
          eventTransformer: (ev) => { return ev },
          refreshInterval: 1000 * 60 * 2, // too frequent refresh. 10 minutes is enough.
          waitFetch: 1000 * 2,
          glanceTime: 1000 * 60, // deprecated, use refreshInterval instead.
          animationSpeed: 2000,
          useSymbol: true,
          displayLegend: false,
          useWeather: false,
          weatherLocationName: null,
          //notification: 'CALENDAR_EVENTS', /* reserved */
          manipulateDateCell: (cellDom, events) => { },
          weatherNotification: "WEATHER_UPDATED",
          weatherPayload: (payload) => { return payload },
          eventNotification: "CALENDAR_EVENTS",
          eventPayload: (payload) => { return payload },
          displayEndTime: false,
          displayWeatherTemp: false,
          popoverTemplate: "./popover.html",
          popoverTimeout: 1000 * 30,
          popoverPeriodOptions: {
            dateStyle: "short",
            timeStyle: "short"
          },
          popoverDateOptions: {
            dateStyle: "full"
          },
          displayCW: true,   // Weeknummer
          animateIn: "fadeIn",
          animateOut: "fadeOut",
          skipPassedEventToday: false,
          showMore: true,
          useIconify: true,
          useMarquee: false,
      
          skipDuplicated: true,
          monthIndex: 1,
          referenceDate: null,
      
          customHeader: false // true or function
        },
      
        defaulNotifications: {
          weatherNotification: "WEATHER_UPDATED",
          weatherPayload: (payload) => { return payload },
          eventNotification: "CALENDAR_EVENTS",
          eventPayload: (payload) => { return payload }
        },
      
        getStyles () {
          let css = ["MMM-CalendarExt3.css"]
          return css
        },
      
        getMoment (options) {
          let moment = (options.referenceDate) ? new Date(options.referenceDate) : new Date(Date.now())
          //let moment = (this.tempMoment) ? new Date(this.tempMoment.valueOf()) : new Date()
          switch (options.mode) {
            case "day":
              moment = new Date(moment.getFullYear(), moment.getMonth(), moment.getDate() + options.dayIndex)
              break
            case "month":
              moment = new Date(moment.getFullYear(), moment.getMonth() + options.monthIndex, 1)
              break
            case "week":
            default:
              moment = new Date(moment.getFullYear(), moment.getMonth(), moment.getDate() + (7 * options.weekIndex))
          }
          return moment
        },
      
        regularizeConfig (options) {
          const weekInfoFallback = {
            firstDay: 1,
            minimalDays: 4,
            weekend: [0, 6]
          }
      
          options.locale = Intl.getCanonicalLocales(options.locale ?? config?.locale ?? config?.language)?.[0] ?? ""
          const calInfo = new Intl.Locale(options.locale)
          if (calInfo?.weekInfo) {
            options.firstDayOfWeek = (options.firstDayOfWeek !== null) ? options.firstDayOfWeek : (calInfo.weekInfo?.firstDay ?? weekInfoFallback.firstDay)
            options.minimalDaysOfNewYear = (options.minimalDaysOfNewYear !== null) ? options.minimalDaysOfNewYear : (calInfo.weekInfo?.minimalDays ?? weekInfoFallback.minDays)
            options.weekends = ((Array.isArray(options.weekends) && options.weekends?.length) ? options.weekends : (calInfo.weekInfo?.weekend ?? [])).map((d) => d % 7)
          }
      
          options.instanceId = options.instanceId ?? this.identifier
          this.notifications = {
            weatherNotification: options.weatherNotification ?? this.defaulNotifications.weatherNotification,
            weatherPayload: (typeof options.weatherPayload === "function") ? options.weatherPayload : this.defaulNotifications.weatherPayload,
            eventNotification: options.eventNotification ?? this.defaulNotifications.eventNotification,
            eventPayload: (typeof options.eventPayload === "function") ? options.eventPayload : this.defaulNotifications.eventPayload
          }
      
          options.mode = (["day", "month", "week"].includes(options.mode)) ? options.mode : "week"
          options.weekIndex = (options.mode === "month") ? 0 : options.weekIndex
          options.weeksInView = (options.mode === "month") ? 6 : options.weeksInView
          options.dayIndex = (options.mode === "day") ? options.dayIndex : 0
      
          return options
        },
      
        start () {
          this.activeConfig = this.regularizeConfig({ ...this.config })
          this.originalConfig = { ...this.activeConfig }
      
          this.fetchTimer = null
          this.refreshTimer = null
          this.forecast = []
          this.eventPool = new Map()
          this.popoverTimer = null
      
          this._ready = false
      
          let _moduleLoaded = new Promise((resolve, reject) => {
            import(`/${this.file("CX3_Shared/CX3_shared.mjs")}`).then((m) => {
              this.library = m
              if (this.config.useIconify) this.library.prepareIconify()
              resolve()
            }).catch((err) => {
              console.error(err)
              reject(err)
            })
          })
      
          let _domCreated = new Promise((resolve) => {
            this._domReady = resolve
          })
      
          Promise.allSettled([_moduleLoaded, _domCreated]).then(() => {
            this._ready = true
            this.library.prepareMagic()
            setTimeout(() => {
              this.updateAnimate()
            }, this.activeConfig.waitFetch)
          })
          if (popoverSupported) {
            this.preparePopover()
          }
        },
      
        preparePopover () {
          if (!popoverSupported) return
          if (document.getElementById("CX3_POPOVER")) return
      
          fetch(this.file(this.config.popoverTemplate)).then((response) => {
            return response.text()
          }).then((text) => {
            const template = new DOMParser().parseFromString(text, "text/html").querySelector("#CX3_T_POPOVER").content.querySelector(".popover")
            const popover = document.importNode(template, true)
            popover.id = "CX3_POPOVER"
            document.body.append(popover)
            popover.ontoggle = (ev) => {
              if (this.popoverTimer) {
                clearTimeout(this.popoverTimer)
                this.popoverTimer = null
              }
              if (ev.newState === "open") {
                this.popoverTimer = setTimeout(() => {
                  try {
                    popover.hidePopover()
                    popover.querySelector(".container").innerHTML = ""
                  } catch {
                    // do nothing
                  }
                }, this.activeConfig.popoverTimeout)
              } else { // closed
                popover.querySelector(".container").innerHTML = ""
              }
            }
          }).catch((err) => {
            console.error("[CX3]", err)
          })
        },
      
        dayPopover(cDom, events, options) {
          const popover = document.getElementById("CX3_POPOVER")
          if (!popover) return
          const container = popover.querySelector(".container")
          container.innerHTML = ""
          const ht = popover.querySelector("template#CX3_T_EVENTLIST").content.cloneNode(true)
          container.append(document.importNode(ht, true))
          let header = container.querySelector(".header")
          header.innerHTML = new Intl.DateTimeFormat(options.locale, { dateStyle: "full" }).formatToParts(new Date(+cDom.dataset.date))
          .reduce((prev, cur, curIndex) => {
            const result = `${prev}<span class="eventTimeParts ${cur.type} seq_${curIndex} ${cur.source}">${cur.value}</span>`
            return result
          }, "")
      
          let list = container.querySelector(".list")
          list.innerHTML = ""
          const { renderSymbol } = this.library
          events.forEach((e) => {
            const pOption = (e.fullDayEvent) ? { dateStyle: "short" } : { dateStyle: "short", timeStyle: "short" }
      
            const item = popover.querySelector("template#CX3_T_EVENTITEM").content.firstElementChild.cloneNode(true)
            item.style.setProperty("--calendarColor", e.color)
            item.classList.add("event")
            const symbol = item.querySelector(".symbol")
            renderSymbol(symbol, e, config)
            const time = item.querySelector(".time")
            time.innerHTML = new Intl.DateTimeFormat(options.locale, pOption).formatRangeToParts(new Date(+e.startDate), new Date(+e.endDate))
            .reduce((prev, cur, curIndex) => {
              const result = `${prev}<span class="eventTimeParts ${cur.type} seq_${curIndex} ${cur.source}">${cur.value}</span>`
              return result
            }, "")
            const title = item.querySelector(".title")
            title.innerHTML = e.title
            list.append(item)
          })
      
          this.activatePopover(popover)
        },
      
        eventPopover(eDom, options) {
          const popover = document.getElementById("CX3_POPOVER")
          if (!popover) return
          const container = popover.querySelector(".container")
          container.innerHTML = ""
          const ht = popover.querySelector("template#CX3_T_EVENTDETAIL").content.cloneNode(true)
          container.append(document.importNode(ht, true))
          let eSymbol = eDom.querySelector(".symbol").cloneNode(true)
          container.querySelector(".symbol").append(eSymbol)
          let eTitle = eDom.querySelector(".title").cloneNode(true)
          container.querySelector(".title").append(eTitle)
          let header = container.querySelector(".header")
          header.style.setProperty("--calendarColor", eDom.style.getPropertyValue("--calendarColor"))
          header.style.setProperty("--oppositeColor", eDom.style.getPropertyValue("--oppositeColor"))
          header.dataset.isFullday = eDom.dataset.fullDayEvent
      
          let criteria = container.querySelector(".criteria")
          criteria.innerHTML = ""
          const ps = ["location", "description", "calendar"]
          ps.forEach((c) => {
            if (eDom.dataset[c]) {
              const ct = popover.querySelector("template#CX3_T_CRITERIA").content.firstElementChild.cloneNode(true)
              //ct.querySelector('.name').innerHTML = c
              //ct.querySelector('.name').classList.add(c)
              ct.classList.add(c)
              ct.querySelector(".value").innerHTML = eDom.dataset[c]
              criteria.append(document.importNode(ct, true))
            }
          })
      
          let start = new Date(+(eDom.dataset.startDate))
          let end = new Date(+(eDom.dataset.endDate))
          const ct = popover.querySelector("template#CX3_T_CRITERIA").content.firstElementChild.cloneNode(true)
          criteria.append(document.importNode(ct, true))
          const n = Array.from(criteria.childNodes).at(-1)
          n.classList.add("period")
          const pOption = (eDom.dataset.fullDayEvent === "true") ? { dateStyle: "short" } : { dateStyle: "short", timeStyle: "short" }
          n.querySelector(".value").innerHTML = new Intl.DateTimeFormat(options.locale, pOption).formatRangeToParts(start, end)
          .reduce((prev, cur, curIndex) => {
            const result = `${prev}<span class="eventTimeParts ${cur.type} seq_${curIndex} ${cur.source}">${cur.value}</span>`
            return result
          }, "")
          this.activatePopover(popover)
        },
      
        activatePopover (popover) {
          let opened = document.querySelectorAll("[popover-opened]")
          for (const o of Array.from(opened)) {
            o.hidePopover()
          }
          popover.showPopover()
        },
      
        notificationReceived (notification, payload, sender) {
          const replyCurrentConfig = ({ callback }) => {
            if (typeof callback === "function") {
              callback({ ...this.activeConfig })
            }
          }
      
          if (notification === this.notifications.eventNotification) {
            let convertedPayload = this.notifications.eventPayload(payload)
            this.eventPool.set(sender.identifier, JSON.parse(JSON.stringify(convertedPayload)))
          }
      
          if (notification === "MODULE_DOM_CREATED") {
            this._domReady()
            const moduleContainer = document.querySelector(`#${this.identifier} .module-content`)
            const callback = (mutationsList) => {
              for (let mutation of mutationsList) {
                const content = document.querySelector(`#${this.identifier} .module-content .CX3`)
                if (mutation.addedNodes.length > 0) this.updated(content, this.activeConfig)
              }
            }
            const MutationObserver = window.MutationObserver || window.WebKitMutationObserver
            const observer = new MutationObserver(callback)
            observer.observe(moduleContainer, { childList: true })
          }
      
          if (notification === this.notifications.weatherNotification) {
            let convertedPayload = this.notifications.weatherPayload(payload)
            if (
              (this.activeConfig.useWeather
              && ((this.activeConfig.weatherLocationName && convertedPayload.locationName.includes(this.activeConfig.weatherLocationName))
              || !this.activeConfig.weatherLocationName))
              && (Array.isArray(convertedPayload?.forecastArray) && convertedPayload?.forecastArray.length)
            ) {
              this.forecast = [...convertedPayload.forecastArray].map((o) => {
                let d = new Date(o.date)
                o.dateId = d.toLocaleDateString("en-CA")
                return o
              })
            } else {
              if (this.activeConfig.weatherLocationName && !convertedPayload.locationName.includes(this.activeConfig.weatherLocationName)) {
                Log.warn(`"weatherLocationName: '${this.activeConfig.weatherLocationName}'" doesn't match with location of weather module ('${convertedPayload.locationName}')`)
              }
            }
          }
      
          if (["CX3_GLANCE_CALENDAR", "CX3_MOVE_CALENDAR", "CX3_SET_DATE"].includes(notification)) {
            console.warn("[DEPRECATED]'CX3_GLANCE_CALENDAR' notification was deprecated. Use 'CX3_SET_CONFIG' instead. (README.md)")
          }
          if (payload?.instanceId && payload?.instanceId !== this.activeConfig?.instanceId) return
      
          if (notification === "CX3_GET_CONFIG") {
            replyCurrentConfig(payload)
          }
      
          if (notification === "CX3_SET_CONFIG") {
            this.activeConfig = this.regularizeConfig({ ...this.activeConfig, ...payload })
            this.updateAnimate()
            replyCurrentConfig(payload)
          }
      
          if (notification === "CX3_RESET") {
            this.activeConfig = this.regularizeConfig({ ...this.originalConfig })
            this.updateAnimate()
            replyCurrentConfig(payload)
          }
        },
      
        getDom () {
          let dom = document.createElement("div")
          dom.innerHTML = ""
          dom.classList.add("bodice", `CX3_${this.activeConfig.instanceId}`, "CX3")
          if (this.activeConfig.fontSize) dom.style.setProperty("--fontsize", this.activeConfig.fontSize)
          if (!this.library?.loaded) {
            Log.warn("[CX3] Module is not prepared yet, wait a while.")
            return dom
          }
          dom = this.draw(dom, this.activeConfig)
      
          if (this.refreshTimer) {
            clearTimeout(this.refreshTimer)
            this.refreshTimer = null
          }
          this.refreshTimer = setTimeout(() => {
            clearTimeout(this.refreshTimer)
            this.refreshTimer = null
            this.updateAnimate()
          }, this.activeConfig.refreshInterval)
          this.sendNotification("CX3_DOM_UPDATED", { instanceId: this.activeConfig.instanceId })
          return dom
        },
      
      
        updated (dom, options) {
          if (!dom) return
          dom.querySelectorAll(".title")?.forEach((e) => {
            const parent = e.closest(".event")
            const { offsetWidth, scrollWidth } = e
            if (options.useMarquee && parent?.dataset?.noMarquee !== "true" && offsetWidth < scrollWidth) {
              const m = document.createElement("span")
              m.innerHTML = e.innerHTML
              e.innerHTML = ""
              e.append(m)
              e.classList.add("marquee")
              m.classList.add("marqueeText")
              const length = m.offsetWidth
              m.style.setProperty("--marqueeOffset", `${offsetWidth}px`)
              m.style.setProperty("--marqueeScroll", `${scrollWidth}px`)
              m.style.setProperty("--marqueeLength", `${length}s`)
            }
          })
        },
      
        async draw (dom, options) {
          if (!this.library?.loaded) return dom
          const {
            isToday, isPastDay, isFutureDay, isThisMonth, isThisYear, getWeekNo, renderEventAgenda,
            prepareEvents, getBeginOfWeek, getEndOfWeek, displayLegend, regularizeEvents
          } = this.library
      
          const startDayOfWeek = getBeginOfWeek(new Date(Date.now()), options).getDay()
      
          dom.innerHTML = ""
          //dom.style.setProperty("--maxeventlines", options.maxEventLines)
          dom.style.setProperty("--eventheight", options.eventHeight)
          dom.style.setProperty("--displayEndTime", (options.displayEndTime) ? "inherit" : "none")
          dom.style.setProperty("--displayWeatherTemp", (options.displayWeatherTemp) ? "inline-block" : "none")
          dom.dataset.mode = options.mode
      
          const makeCellDom = (d) => {
            let tm = new Date(d.valueOf())
            let cell = document.createElement("div")
            cell.classList.add("cell")
            if (isPastDay(tm)) cell.classList.add("past")
            if (isToday(tm)) cell.classList.add("today")
            if (isFutureDay(tm)) cell.classList.add("future")
            if (isThisMonth(tm)) cell.classList.add("thisMonth")
            if (isThisYear(tm)) cell.classList.add("thisYear")
            cell.classList.add(
              `year_${tm.getFullYear()}`,
              `month_${tm.getMonth() + 1}`,
              `date_${tm.getDate()}`,
              `weekday_${tm.getDay()}`
            )
            cell.dataset.date = new Date(tm.getFullYear(), tm.getMonth(), tm.getDate()).valueOf()
            options.weekends.forEach((w, i) => {
              if (tm.getDay() === w) cell.classList.add("weekend", `weekend_${i + 1}`)
            })
            let h = document.createElement("div")
            h.classList.add("cellHeader")
      
            let cwDom = document.createElement("div")
            cwDom.innerHTML = getWeekNo(tm, options)
            cwDom.classList.add("cw")
            if (tm.getDay() === startDayOfWeek) {
              cwDom.classList.add("cwFirst")
            }
      
            h.append(cwDom)
      
            let forecasted = this.forecast.find((e) => {
              return (tm.toLocaleDateString("en-CA") === e.dateId)
            })
      
            if (forecasted && forecasted?.weatherType) {
              let weatherDom = document.createElement("div")
              weatherDom.classList.add("cellWeather")
              let icon = document.createElement("span")
              icon.classList.add("wi", `wi-${forecasted.weatherType}`)
              weatherDom.append(icon)
              let maxTemp = document.createElement("span")
              maxTemp.classList.add("maxTemp", "temperature")
              maxTemp.innerHTML = Math.round(forecasted.maxTemperature)
              weatherDom.append(maxTemp)
              let minTemp = document.createElement("span")
              minTemp.classList.add("minTemp", "temperature")
              minTemp.innerHTML = Math.round(forecasted.minTemperature)
              weatherDom.append(minTemp)
              h.append(weatherDom)
            }
            let dateDom = document.createElement("div")
            dateDom.classList.add("cellDate")
            let dParts = new Intl.DateTimeFormat(options.locale, options.cellDateOptions).formatToParts(tm)
            let dateHTML = dParts.reduce((prev, cur, curIndex) => {
              const result = `${prev}<span class="dateParts ${cur.type} seq_${curIndex}">${cur.value}</span>`
              return result
            }, "")
            dateDom.innerHTML = dateHTML
      
            h.append(dateDom)
      
            let b = document.createElement("div")
            b.classList.add("cellBody")
      
            let f = document.createElement("div")
            f.classList.add("cellFooter")
      
            cell.append(h)
            cell.append(b)
            cell.append(f)
            return cell
          }
      
          const rangeCalendar = (moment, options) => {
            let boc, eoc
            switch (options.mode) {
              case "day":
                boc = new Date(moment.getFullYear(), moment.getMonth(), moment.getDate())
                eoc = new Date(boc.valueOf())
                eoc.setDate(boc.getDate() + 7 * options.weeksInView)
                eoc.setMilliseconds(-1)
                break
              case "month":
                boc = getBeginOfWeek(new Date(moment.getFullYear(), moment.getMonth(), 1), options)
                eoc = getEndOfWeek(new Date(moment.getFullYear(), moment.getMonth() + 1, 0), options)
                break
              case "week":
              default:
                boc = getBeginOfWeek(new Date(moment.getFullYear(), moment.getMonth(), moment.getDate()), options)
                eoc = getEndOfWeek(new Date(boc.getFullYear(), boc.getMonth(), boc.getDate() + (7 * (options.weeksInView - 1))), options)
                break
            }
            return { boc, eoc }
          }
      
          const makeDayHeaderDom = (dom, options, range) => {
            let wm = new Date(range.boc.valueOf())
            let dayDom = document.createElement("div")
            dayDom.classList.add("headerContainer", "weekGrid")
            for (let i = 0; i < 7; i++) {
              let dm = new Date(wm.getFullYear(), wm.getMonth(), wm.getDate() + i)
              let day = (dm.getDay() + 7) % 7
              let dDom = document.createElement("div")
              dDom.classList.add("weekday", `weekday_${day}`)
              options.weekends.forEach((w, i) => {
                if (day === w) dDom.classList.add("weekend", `weekend_${i + 1}`)
              })
              dDom.innerHTML = new Intl.DateTimeFormat(options.locale, options.headerWeekDayOptions).format(dm)
              dayDom.append(dDom)
            }
      
            dom.append(dayDom)
          }
      
          const makeWeekGridDom = (dom, options, events, { boc, eoc }) => {
            const getMaxEventLines = ({ maxEventLines }, weekCount) => {
              if (Number.isInteger(+maxEventLines) && Number.isFinite(+maxEventLines)) return +maxEventLines
              let lines = []
              if (typeof maxEventLines === "object") {
                if (!Array.isArray(maxEventLines)) {
                  const maxKeys = Object.keys(maxEventLines)
                    .filter((k) => Number.isInteger(+k) && Number.isFinite(+k)).map((k) => +k)
                    .reduce((p, v) => { return (p > v ? p : v) }, 0)
                  lines.length = maxKeys + 1
                  const defaultLines = maxEventLines?.[0] ?? maxEventLines?.["0"] ?? this.defaults.maxEventLines
                  lines.fill(defaultLines)
                  for (let [key, value] of Object.entries(maxEventLines)) {
                    if (Number.isInteger(+key) && Number.isFinite(+key) && Number.isInteger(+value) && Number.isFinite(+value)) {
                      lines[+key] = +value
                    }
                  }
                } else {
                  lines = [...maxEventLines.filter((v) => Number.isInteger(+v) && Number.isFinite(+v))]
                }
              }
              return +(lines?.[weekCount] ?? lines?.[0] ?? this.defaults.maxEventLines)
            }
      
            // how many weeks between boc(begin of calendar) and eoc(end of calendar)
            let count = 1;
            let eocWeek = getWeekNo(eoc, options)
            let w = new Date(boc.valueOf())
            do {
              w.setDate(w.getDate() + 7)
              count++
            } while (getWeekNo(w, options) !== eocWeek)
      
            count = (options.mode === "month") ? count : options.weeksInView
            const maxEventLines = getMaxEventLines(options, count)
            dom.style.setProperty("--maxeventlines", maxEventLines)
            dom.dataset.maxEventLines = maxEventLines
            let wm = new Date(boc.valueOf())
            do {
              let wDom = document.createElement("div")
              wDom.classList.add("week")
              wDom.dataset.weekNo = getWeekNo(wm, options)
      
              let ccDom = document.createElement("div")
              ccDom.classList.add("cellContainer", "weekGrid")
      
              let ecDom = document.createElement("div")
              ecDom.classList.add("eventContainer", "weekGrid", "weekGridRow")
      
              let boundary = []
      
              let cm = new Date(wm.valueOf())
              for (let i = 0; i < 7; i++) {
                if (i) cm = new Date(cm.getFullYear(), cm.getMonth(), cm.getDate() + 1)
                ccDom.append(makeCellDom(cm, i))
                boundary.push(cm.getTime())
              }
              boundary.push(cm.setHours(23, 59, 59, 999))
      
              let sw = new Date(wm.valueOf())
              let ew = new Date(sw.getFullYear(), sw.getMonth(), sw.getDate() + 6, 23, 59, 59, 999)
              let eventsOfWeek = events.filter((ev) => {
                return !(ev.endDate <= sw.getTime() || ev.startDate >= ew.getTime())
              })
              for (let event of eventsOfWeek) {
                if (options.skipPassedEventToday) {
                  if (event.today && event.isPassed && !event.isFullday && !event.isMultiday && !event.isCurrent) event.skip = true
                }
                if (event?.skip) continue
      
                let eDom = renderEventAgenda(event, options, moment)
      
                let startLine = 0
                if (event.startDate >= boundary.at(0)) {
                  startLine = boundary.findIndex((b, idx, bounds) => {
                    return (event.startDate >= b && event.startDate < bounds[idx + 1])
                  })
                } else {
                  eDom.classList.add("continueFromPreviousWeek")
                }
      
                let endLine = boundary.length - 1
                if (event.endDate <= boundary.at(-1)) {
                  endLine = boundary.findIndex((b, idx, bounds) => {
                    return (event.endDate <= b && event.endDate > bounds[idx - 1])
                  })
                } else {
                  eDom.classList.add("continueToNextWeek")
                }
      
                eDom.style.gridColumnStart = startLine + 1
                eDom.style.gridColumnEnd = endLine + 1
      
                if (event?.noMarquee) {
                  eDom.dataset.noMarquee = true
                }
      
                if (event?.skip) {
                  eDom.dataset.skip = true
                }
      
                if (popoverSupported) {
                  if (!eDom.id) eDom.id = `${eDom.dataset.calendarSeq}_${eDom.dataset.startDate}_${eDom.dataset.endDate}_${new Date(Date.now()).getTime()}`
                  eDom.dataset.popoverble = true
                  eDom.onclick = () => {
                    this.eventPopover(eDom, options)
                  }
                }
      
                ecDom.append(eDom)
              }
      
              let dateCells = ccDom.querySelectorAll(".cell")
              for (let i = 0; i < dateCells.length; i++) {
                let dateCell = dateCells[i]
                let dateStart = new Date(+dateCell.dataset.date)
                let dateEnd = new Date(dateStart.getFullYear(), dateStart.getMonth(), dateStart.getDate(), 23, 59, 59, 999)
                let thatDayEvents = eventsOfWeek.filter((ev) => {
                  return !(ev.endDate <= dateStart.valueOf() || ev.startDate > dateEnd.valueOf())
                })
                dateCell.dataset.events = thatDayEvents.length
                dateCell.dataset.hasEvents = (thatDayEvents.length > 0) ? "true" : "false"
                if (typeof options.manipulateDateCell === "function") {
                  options.manipulateDateCell(dateCell, thatDayEvents)
                }
      
                if (options.showMore) {
                  const skipped = thatDayEvents.filter((ev) => ev.skip).length
                  const noskip = thatDayEvents.length - skipped
                  const noskipButOverflowed = (noskip > maxEventLines) ? noskip - maxEventLines : 0
                  const hidden = skipped + noskipButOverflowed
                  if (hidden) {
                    dateCell.classList.add("hasMore")
                    dateCell.style.setProperty("--more", hidden)
                  }
                }
      
                if (popoverSupported) {
                  if (!dateCell.id) dateCell.id = `${dateCell.dataset.date}_${new Date(Date.now()).getTime()}`
                  dateCell.dataset.popoverble = true
                  dateCell.onclick = () => {
                    this.dayPopover(dateCell, thatDayEvents, options)
                  }
                }
              }
      
              wDom.append(ccDom)
              wDom.append(ecDom)
      
              dom.append(wDom)
              wm = new Date(wm.getFullYear(), wm.getMonth(), wm.getDate() + 7)
            } while (wm.valueOf() <= eoc.valueOf())
          }
      
          const customHeaderDom = (dom, options, { boc, eoc }) => {
            const defaultCustomHeader = (options, boc, eoc) => {
              try {
                const locale = options.locale
                const titleOptions = options.headerTitleOptions
                if (options.mode === "month") {
                  const moment = this.getMoment(options)
                  return new Intl.DateTimeFormat(locale, titleOptions)
                    .formatToParts(new Date(moment.valueOf()))
                    .reduce((prev, cur, curIndex) => {
                      const result = `${prev}<span class="headerTimeParts ${cur.type} seq_${curIndex} ${cur.source}">${cur.value}</span>`
                      return result
                    }, "")
                } else {
                  const begin = new Date(boc.valueOf())
                  const end = new Date(eoc.valueOf())
                  return new Intl.DateTimeFormat(locale, titleOptions)
                    .formatRangeToParts(begin, end)
                    .reduce((prev, cur, curIndex) => {
                      const result = `${prev}<span class="headerTimeParts ${cur.type} seq_${curIndex} ${cur.source}">${cur.value}</span>`
                      return result
                    }, "")
                }
              } catch (e) {
                Log.error(e)
                return ""
              }
            }
            const header = document.createElement("h1")
            header.classList.add("headerTitle")
            header.innerHTML = (typeof options.customHeader === "function") ? options.customHeader(options, boc, eoc) : defaultCustomHeader(options, boc, eoc)
            dom.prepend(header)
          }
      
          let moment = this.getMoment(options)
          let { boc, eoc } = rangeCalendar(moment, options)
          dom.dataset.beginOfCalendar = boc.valueOf()
          dom.dataset.endOfCalendar = eoc.valueOf()
          const targetEvents = prepareEvents({
            targetEvents: regularizeEvents({
              eventPool: this.eventPool,
              config: options
            }),
            config: options,
            range: [boc, eoc]
          })
          makeDayHeaderDom(dom, options, { boc, eoc })
          makeWeekGridDom(dom, options, targetEvents, { boc, eoc })
          if (options.displayLegend) displayLegend(dom, targetEvents, options)
          if (options.customHeader) customHeaderDom(dom, options, { boc, eoc })
          return dom
        },
      
        getHeader () {
          if (this.data.header && this.data.header.trim() !== "") return this.data.header
          if (!this.activeConfig.customHeader && this.activeConfig.mode === "month") {
            const moment = this.getMoment(this.activeConfig)
            const locale = this.activeConfig.locale
            const titleOptions = this.activeConfig.headerTitleOptions
            return new Intl.DateTimeFormat(locale, titleOptions).format(new Date(moment.valueOf()))
          }
          return this.data.header
        },
      
        updateAnimate () {
          this.updateDom(
            (!animationSupported)
              ? this.config.animationSpeed
              : {
                options: {
                  speed: this.config.animationSpeed,
                  animate: {
                    in: this.config.animateIn,
                    out: this.config.animateOut
                  }
                }
              }
          )
        }
      })
      
      
      posted in Troubleshooting
      R
      redux703
    • MMM-calendarext3 wrong month

      Hello,

      I have the calendarExt3 module working. But now I have an strange error.
      It is now august, and when i start the calendar it shows july, and with monthIndex: -1, it shows september.

      What can i do to show the actual month?

      posted in Troubleshooting
      R
      redux703
    • RE: MMM-calendarExt3 filter agendas

      @sdetweil hey the error was probably comming from the agenda it self… it was put in the samsung agenda app, which would add it to the hotmail calemdar that would sync with google agenda.

      The only problem i now still have is the calendarSet.
      It still doesnt filter out the calendars i dont want…

      posted in Troubleshooting
      R
      redux703
    • RE: MMM-calendarExt3 filter agendas

      @sdetweil
      How can i do that?

      posted in Troubleshooting
      R
      redux703
    • RE: MMM-calendarExt3 filter agendas

      @sdetweil
      Yep it still shows there.
      While in the source agenda (google calendar on the phone etc) it is still correct. It also seems only to happen with “jarig” in the title. But some are also correct

      posted in Troubleshooting
      R
      redux703
    • RE: MMM-calendarExt3 filter agendas

      @BKeyport
      Yeah the calendarSet: (‘’) worked so far. But for some reason it gives two strange errors:
      1, it is now (2025-08-01), and the month calendar shows 2025-09-01, and the week calender is still right.
      2, for some reason, there are some dates that get mixed up. For example: someones birthday that is in December, now shows on July, while the same day. The month is different.

      posted in Troubleshooting
      R
      redux703
    • MMM-calendarExt3 filter agendas

      Hello,

      I have multiple pages with calendars, is it possible to filter it? So:
      Page 0, all agenda’s
      Page 1, person 1
      Page 2, person 2
      Page 3, person 3

      posted in Troubleshooting
      R
      redux703
    • RE: mmm-calendarEXT3 multiple questions

      @sdetweil Thanks! This worked like a charm. Also had to add to normal calendar module

      posted in Troubleshooting
      R
      redux703