diff --git a/.eslintrc.json b/.eslintrc.json index 637b2eb7..751bf56d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,7 @@ }, "parserOptions": { "sourceType": "module", - "ecmaVersion": 2017, + "ecmaVersion": 2018, "ecmaFeatures": { "globalReturn": true } diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d3912283..3563f85e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,13 +4,15 @@ Thanks for contributing to MagicMirror²! We hold our code to standard, and these standards are documented below. +## Linters + If you wish to run our linters, use `npm run lint` without any arguments. ### JavaScript: Run ESLint We use [ESLint](https://eslint.org) on our JavaScript files. -Our ESLint configuration is in our .eslintrc.json and .eslintignore files. +Our ESLint configuration is in our `.eslintrc.json` and `.eslintignore` files. To run ESLint, use `npm run lint:js`. @@ -20,7 +22,15 @@ We use [StyleLint](https://stylelint.io) to lint our CSS. Our configuration is i To run StyleLint, use `npm run lint:css`. -### Submitting Issues +## Testing + +We use [Jest](https://jestjs.io) for JavaScript testing. + +To run all tests, use `npm run test`. + +The specific test commands are defined in `package.json`. So you can also run the specific tests with other commands, e.g. `npm run test:unit` or `npx jest tests/e2e/env_spec.js`. + +## Submitting Issues Please only submit reproducible issues. @@ -32,7 +42,7 @@ When submitting a new issue, please supply the following information: **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX). -**Node Version**: Make sure it's version 12 or later (recommended is 14). +**Node Version**: Make sure it's version 14 or later (recommended is 16). **MagicMirror Version**: Please let us know which version of MagicMirror you are running. It can be found in the `package.json` file. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index fd922dad..b441c20a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -33,7 +33,7 @@ When submitting a new issue, please supply the following information: **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX). -**Node Version**: Make sure it's version 12 or later (recommended is 14). +**Node Version**: Make sure it's version 14 or later (recommended is 16). **MagicMirror Version**: Please let us know which version of MagicMirror you are running. It can be found in the `package.json` file. diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 9e1047b3..cdd86e6a 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -15,14 +15,15 @@ jobs: timeout-minutes: 30 strategy: matrix: - node-version: [12.x, 14.x, 16.x] + node-version: [14.x, 16.x, 17.x] steps: - name: Checkout code uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} + cache: "npm" - name: Install dependencies and run tests run: | Xvfb :99 -screen 0 1024x768x16 & diff --git a/.github/workflows/codecov-test-suites.yml b/.github/workflows/codecov-test-suites.yml index 8c2be8da..d080a18b 100644 --- a/.github/workflows/codecov-test-suites.yml +++ b/.github/workflows/codecov-test-suites.yml @@ -23,7 +23,7 @@ jobs: touch css/custom.css npm run test:coverage - name: Upload coverage results to codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: - file: ./coverage/lcov.info + files: ./coverage/lcov.info fail_ci_if_error: true diff --git a/.github/workflows/enforce-changelog.yml b/.github/workflows/enforce-changelog.yml index e1c2856d..07732cd7 100644 --- a/.github/workflows/enforce-changelog.yml +++ b/.github/workflows/enforce-changelog.yml @@ -14,7 +14,7 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - name: Enforce changelog️ - uses: dangoslen/changelog-enforcer@v1.6.1 + uses: dangoslen/changelog-enforcer@v2 with: changeLogPath: "CHANGELOG.md" skipLabels: "Skip Changelog" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a1639db..8202fa22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,55 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror² +❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror². + +## [2.18.0] - 2022-01-01 + +Special thanks to the following contributors: @AmpioRosso, @eouia, @fewieden, @jupadin, @khassel, @kolbyjack, @KristjanESPERANTO, @MariusVaice, @rejas, @rico24 and @sdetweil. + +### Added + +- Added test for calendar recurring event with checks the correct date displayed (related to #2752). + +### Updated + +- ESLint version supports now ECMAScript 2018. +- Cleaned up `updatenotification` module and switched to nunjuck template. +- Moved calendar tests from category `electron` to `e2e`. +- Update missed translations for Korean language (ko.json). +- Update missed translations for Dutch language (nl.json). +- Cleaned up `alert` module and switched to nunjuck template. +- Moved weather tests from category `electron` to `e2e`. +- Updated github actions. +- Replace spectron with playwright, update dependencies including electron update to v16. +- Added lithuanian language to translations.js. +- Show info message if newsfeed is empty (fixes #2731). +- Added dangerouslyDisableAutoEscaping config option for newsfeed templates (fixes #2712). +- Added missing shebang to `installers/mm.sh`. +- Node versions in templates and github workflows. + +### Fixed + +- Fixed wrong file `kr.json` to `ko.json`. Use language code 'ko' instead of 'kr' for Korean language. +- Fixed `feels_like` data from openweathermaps current weather being ignored (#2678). +- Fixed chaotic newsfeed display after network connection loss thanks to @jalibu (#2638). +- Fixed incorrect time zone correction of recurring full day events (#2632 and #2634). +- Fixed e2e tests by increasing testTimeout. +- Revert node-ical update due to missing luxon package. +- Fixed User-Agent-Header for newsfeed and calendar module (#2729). +- Replace broken shields in Readme and use https for links. +- Fixed electron tests with retry. +- Fixed Calendar recurring cross timezone error (add/subtract a day, not just offset hours) (#2632). +- Fixed Calendar showEnd and Full Date overlay (#2629). +- Fixed regression on #2632, #2752. +- Broadcast custom symbols in CALENDAR_EVENTS. ## [2.17.1] - 2021-10-01 ### Fixed - Fixed error when accessing letsencrypt certificates +- Fixed Calendar module enhancement: displaying full events without time (#2424) ## [2.17.0] - 2021-10-01 diff --git a/README.md b/README.md index 8a243f89..6fe93019 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ ![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png)

- Dependency Status - devDependency Status - CLI Best Practices - CodeCov Status - License - Tests + + License + + GitHub Actions + Build Status + + CodeCov Status + + + +

**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors). diff --git a/installers/mm.sh b/installers/mm.sh index cbbadd24..d56ffca3 100755 --- a/installers/mm.sh +++ b/installers/mm.sh @@ -1,3 +1,4 @@ +#!/bin/bash # This file is still here to keep PM2 working on older installations. cd ~/MagicMirror DISPLAY=:0 npm start diff --git a/js/electron.js b/js/electron.js index 2c44ab02..509b5474 100644 --- a/js/electron.js +++ b/js/electron.js @@ -53,7 +53,7 @@ function createWindow() { // If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost let prefix; - if (config["tls"] !== null && config["tls"]) { + if ((config["tls"] !== null && config["tls"]) || config.useHttps) { prefix = "https://"; } else { prefix = "http://"; @@ -140,6 +140,13 @@ app.on("before-quit", (event) => { process.exit(0); }); +/* handle errors from self signed certificates */ + +app.on("certificate-error", (event, webContents, url, error, certificate, callback) => { + event.preventDefault(); + callback(true); +}); + // Start the core application if server is run on localhost // This starts all node helpers and starts the webserver. if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) { diff --git a/modules/default/alert/alert.js b/modules/default/alert/alert.js index 2e471ac1..0ed34fb0 100644 --- a/modules/default/alert/alert.js +++ b/modules/default/alert/alert.js @@ -7,161 +7,140 @@ * MIT Licensed. */ Module.register("alert", { + alerts: {}, + defaults: { - // scale|slide|genie|jelly|flip|bouncyflip|exploader - effect: "slide", - // scale|slide|genie|jelly|flip|bouncyflip|exploader - alert_effect: "jelly", - //time a notification is displayed in seconds - display_time: 3500, - //Position + effect: "slide", // scale|slide|genie|jelly|flip|bouncyflip|exploader + alert_effect: "jelly", // scale|slide|genie|jelly|flip|bouncyflip|exploader + display_time: 3500, // time a notification is displayed in seconds position: "center", - //shown at startup - welcome_message: false + welcome_message: false // shown at startup }, - getScripts: function () { + + getScripts() { return ["notificationFx.js"]; }, - getStyles: function () { - return ["notificationFx.css", "font-awesome.css"]; + + getStyles() { + return ["font-awesome.css", this.file(`./styles/notificationFx.css`), this.file(`./styles/${this.config.position}.css`)]; }, - // Define required translations. - getTranslations: function () { + + getTranslations() { return { - en: "translations/en.json", + bg: "translations/bg.json", + da: "translations/da.json", de: "translations/de.json", - nl: "translations/nl.json" + en: "translations/en.json", + es: "translations/es.json", + fr: "translations/fr.json", + hu: "translations/hu.json", + nl: "translations/nl.json", + ru: "translations/ru.json" }; }, - show_notification: function (message) { + + getTemplate(type) { + return `templates/${type}.njk`; + }, + + start() { + Log.info(`Starting module: ${this.name}`); + if (this.config.effect === "slide") { - this.config.effect = this.config.effect + "-" + this.config.position; + this.config.effect = `${this.config.effect}-${this.config.position}`; } - let msg = ""; - if (message.title) { - msg += "" + message.title + ""; + + if (this.config.welcome_message) { + const message = this.config.welcome_message === true ? this.translate("welcome") : this.config.welcome_message; + this.showNotification({ title: this.translate("sysTitle"), message }); } - if (message.message) { - if (msg !== "") { - msg += "
"; + }, + + notificationReceived(notification, payload, sender) { + if (notification === "SHOW_ALERT") { + if (payload.type === "notification") { + this.showNotification(payload); + } else { + this.showAlert(payload, sender); } - msg += "" + message.message + ""; + } else if (notification === "HIDE_ALERT") { + this.hideAlert(sender); } + }, + + async showNotification(notification) { + const message = await this.renderMessage("notification", notification); new NotificationFx({ - message: msg, + message, layout: "growl", effect: this.config.effect, - ttl: message.timer !== undefined ? message.timer : this.config.display_time + ttl: notification.timer || this.config.display_time }).show(); }, - show_alert: function (params, sender) { - let image = ""; - //Set standard params if not provided by module - if (typeof params.timer === "undefined") { - params.timer = null; - } - if (typeof params.imageHeight === "undefined") { - params.imageHeight = "80px"; - } - if (typeof params.imageUrl === "undefined" && typeof params.imageFA === "undefined") { - params.imageUrl = null; - } else if (typeof params.imageFA === "undefined") { - image = "
"; - } else if (typeof params.imageUrl === "undefined") { - image = "
"; - } - //Create overlay - const overlay = document.createElement("div"); - overlay.id = "overlay"; - overlay.innerHTML += '
'; - document.body.insertBefore(overlay, document.body.firstChild); - //If module already has an open alert close it + async showAlert(alert, sender) { + // If module already has an open alert close it if (this.alerts[sender.name]) { - this.hide_alert(sender, false); + this.hideAlert(sender, false); } - //Display title and message only if they are provided in notification parameters - let message = ""; - if (params.title) { - message += "" + params.title + ""; - } - if (params.message) { - if (message !== "") { - message += "
"; - } - - message += "" + params.message + ""; + // Add overlay + if (!Object.keys(this.alerts).length) { + this.toggleBlur(true); } - //Store alert in this.alerts + const message = await this.renderMessage("alert", alert); + + // Store alert in this.alerts this.alerts[sender.name] = new NotificationFx({ - message: image + message, + message, effect: this.config.alert_effect, - ttl: params.timer, - onClose: () => this.hide_alert(sender), + ttl: alert.timer, + onClose: () => this.hideAlert(sender), al_no: "ns-alert" }); - //Show alert + // Show alert this.alerts[sender.name].show(); - //Add timer to dismiss alert and overlay - if (params.timer) { + // Add timer to dismiss alert and overlay + if (alert.timer) { setTimeout(() => { - this.hide_alert(sender); - }, params.timer); + this.hideAlert(sender); + }, alert.timer); } }, - hide_alert: function (sender, close = true) { - //Dismiss alert and remove from this.alerts + + hideAlert(sender, close = true) { + // Dismiss alert and remove from this.alerts if (this.alerts[sender.name]) { this.alerts[sender.name].dismiss(close); - this.alerts[sender.name] = null; - //Remove overlay - const overlay = document.getElementById("overlay"); - overlay.parentNode.removeChild(overlay); - } - }, - setPosition: function (pos) { - //Add css to body depending on the set position for notifications - const sheet = document.createElement("style"); - if (pos === "center") { - sheet.innerHTML = ".ns-box {margin-left: auto; margin-right: auto;text-align: center;}"; - } - if (pos === "right") { - sheet.innerHTML = ".ns-box {margin-left: auto;text-align: right;}"; - } - if (pos === "left") { - sheet.innerHTML = ".ns-box {margin-right: auto;text-align: left;}"; - } - document.body.appendChild(sheet); - }, - notificationReceived: function (notification, payload, sender) { - if (notification === "SHOW_ALERT") { - if (typeof payload.type === "undefined") { - payload.type = "alert"; - } - if (payload.type === "alert") { - this.show_alert(payload, sender); - } else if (payload.type === "notification") { - this.show_notification(payload); - } - } else if (notification === "HIDE_ALERT") { - this.hide_alert(sender); - } - }, - start: function () { - this.alerts = {}; - this.setPosition(this.config.position); - if (this.config.welcome_message) { - if (this.config.welcome_message === true) { - this.show_notification({ title: this.translate("sysTitle"), message: this.translate("welcome") }); - } else { - this.show_notification({ title: this.translate("sysTitle"), message: this.config.welcome_message }); + delete this.alerts[sender.name]; + // Remove overlay + if (!Object.keys(this.alerts).length) { + this.toggleBlur(false); } } - Log.info("Starting module: " + this.name); + }, + + renderMessage(type, data) { + return new Promise((resolve) => { + this.nunjucksEnvironment().render(this.getTemplate(type), data, function (err, res) { + if (err) { + Log.error("Failed to render alert", err); + } + + resolve(res); + }); + }); + }, + + toggleBlur(add = false) { + const method = add ? "add" : "remove"; + const modules = document.querySelectorAll(".module"); + for (const module of modules) { + module.classList[method]("alert-blur"); + } } }); diff --git a/modules/default/alert/styles/center.css b/modules/default/alert/styles/center.css new file mode 100644 index 00000000..4e8f5e1d --- /dev/null +++ b/modules/default/alert/styles/center.css @@ -0,0 +1,5 @@ +.ns-box { + margin-left: auto; + margin-right: auto; + text-align: center; +} diff --git a/modules/default/alert/styles/left.css b/modules/default/alert/styles/left.css new file mode 100644 index 00000000..86d2746c --- /dev/null +++ b/modules/default/alert/styles/left.css @@ -0,0 +1,4 @@ +.ns-box { + margin-right: auto; + text-align: left; +} diff --git a/modules/default/alert/notificationFx.css b/modules/default/alert/styles/notificationFx.css similarity index 99% rename from modules/default/alert/notificationFx.css rename to modules/default/alert/styles/notificationFx.css index 39faacf7..8e033e0d 100644 --- a/modules/default/alert/notificationFx.css +++ b/modules/default/alert/styles/notificationFx.css @@ -39,12 +39,8 @@ border-radius: 20px; } -.black_overlay { - position: fixed; - z-index: 2; - background-color: rgba(0, 0, 0, 0.93); - width: 100%; - height: 100%; +.alert-blur { + filter: blur(2px) brightness(50%); } [class^="ns-effect-"].ns-growl.ns-hide, diff --git a/modules/default/alert/styles/right.css b/modules/default/alert/styles/right.css new file mode 100644 index 00000000..add9b6f1 --- /dev/null +++ b/modules/default/alert/styles/right.css @@ -0,0 +1,4 @@ +.ns-box { + margin-left: auto; + text-align: right; +} diff --git a/modules/default/alert/templates/alert.njk b/modules/default/alert/templates/alert.njk new file mode 100644 index 00000000..5592ea35 --- /dev/null +++ b/modules/default/alert/templates/alert.njk @@ -0,0 +1,18 @@ +{% if imageUrl or imageFA %} + {% set imageHeight = imageHeight if imageHeight else "80px" %} + {% if imageUrl %} + + {% else %} + + {% endif %} +
+{% endif %} +{% if title %} + {{ title }} +{% endif %} +{% if message %} + {% if title %} +
+ {% endif %} + {{ message }} +{% endif %} diff --git a/modules/default/alert/templates/notification.njk b/modules/default/alert/templates/notification.njk new file mode 100644 index 00000000..1d67bcda --- /dev/null +++ b/modules/default/alert/templates/notification.njk @@ -0,0 +1,9 @@ +{% if title %} + {{ title }} +{% endif %} +{% if message %} + {% if title %} +
+ {% endif %} + {{ message }} +{% endif %} diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js index 7525e3a8..52bb87da 100755 --- a/modules/default/calendar/calendar.js +++ b/modules/default/calendar/calendar.js @@ -237,18 +237,6 @@ Module.register("calendar", { symbolWrapper.className = "symbol align-right " + symbolClass; const symbols = this.symbolsForEvent(event); - // If symbols are displayed and custom symbol is set, replace event symbol - if (this.config.displaySymbol && this.config.customEvents.length > 0) { - for (let ev in this.config.customEvents) { - if (typeof this.config.customEvents[ev].symbol !== "undefined" && this.config.customEvents[ev].symbol !== "") { - let needle = new RegExp(this.config.customEvents[ev].keyword, "gi"); - if (needle.test(event.title)) { - symbols[0] = this.config.customEvents[ev].symbol; - break; - } - } - } - } symbols.forEach((s, index) => { const symbol = document.createElement("span"); symbol.className = "fa fa-fw fa-" + s; @@ -368,9 +356,9 @@ Module.register("calendar", { } } else { // Show relative times - if (event.startDate >= now) { + if (event.startDate >= now || (event.fullDayEvent && event.today)) { // Use relative time - if (!this.config.hideTime) { + if (!this.config.hideTime && !event.fullDayEvent) { timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat })); } else { timeWrapper.innerHTML = this.capFirst( @@ -378,11 +366,22 @@ Module.register("calendar", { sameDay: "[" + this.translate("TODAY") + "]", nextDay: "[" + this.translate("TOMORROW") + "]", nextWeek: "dddd", - sameElse: this.config.dateFormat + sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat }) ); } - if (event.startDate - now < this.config.getRelative * oneHour) { + if (event.fullDayEvent) { + // Full days events within the next two days + if (event.today) { + timeWrapper.innerHTML = this.capFirst(this.translate("TODAY")); + } else if (event.startDate - now < oneDay && event.startDate - now > 0) { + timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW")); + } else if (event.startDate - now < 2 * oneDay && event.startDate - now > 0) { + if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") { + timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW")); + } + } + } else if (event.startDate - now < this.config.getRelative * oneHour) { // If event is within getRelative hours, display 'in xxx' time format or moment.fromNow() timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); } @@ -629,6 +628,17 @@ Module.register("calendar", { symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "fullDaySymbol", this.config.defaultSymbol), symbols); } + // 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)) { + symbols[0] = ev.symbol; + break; + } + } + } + return symbols; }, diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index 805e080b..16f1db90 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -41,7 +41,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn let fetcher = null; let httpsAgent = null; let headers = { - "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)" + "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version }; if (selfSignedCert) { diff --git a/modules/default/calendar/calendarutils.js b/modules/default/calendar/calendarutils.js index 91679f13..a3bfaeab 100644 --- a/modules/default/calendar/calendarutils.js +++ b/modules/default/calendar/calendarutils.js @@ -98,7 +98,7 @@ const CalendarUtils = { if (h > 0 && h < Math.abs(current_offset) / 60) { // if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time) // we need to fix that - adjustHours = 24; + //adjustHours = 24; // Log.debug("adjusting date") } //-300 > -240 @@ -160,7 +160,7 @@ const CalendarUtils = { } if (event.type === "VEVENT") { - Log.debug("\nEvent: " + JSON.stringify(event)); + Log.debug("Event:\n" + JSON.stringify(event)); let startDate = eventDate(event, "start"); let endDate; @@ -177,8 +177,8 @@ const CalendarUtils = { } } - Log.debug("startDate (local): " + startDate.toDate()); - Log.debug("endDate (local): " + endDate.toDate()); + Log.debug("start: " + startDate.toDate()); + Log.debug("end:: " + endDate.toDate()); // Calculate the duration of the event for use with recurring events. let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x")); @@ -332,40 +332,38 @@ const CalendarUtils = { Log.debug("Fullday"); // If the offset is negative (east of GMT), where the problem is if (dateoffset < 0) { - // Remove the offset, independently of the comparison between the date hour and the offset, - // since in the case that *date houre < offset*, the *new Date* command will handle this by - // representing the day before. - - // Reduce the time by the offset: - // Apply the correction to the date/time to get it UTC relative - date = new Date(date.getTime() - Math.abs(nowOffset) * 60000); - // the duration was calculated way back at the top before we could correct the start time.. - // fix it for this event entry - //duration = 24 * 60 * 60 * 1000; - Log.debug("new recurring date1 is " + date); + if (dh < Math.abs(dateoffset / 60)) { + // reduce the time by the offset + // Apply the correction to the date/time to get it UTC relative + date = new Date(date.getTime() - Math.abs(24 * 60) * 60000); + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + //duration = 24 * 60 * 60 * 1000; + Log.debug("new recurring date1 fulldate is " + date); + } } else { // if the timezones are the same, correct date if needed - if (event.start.tz === moment.tz.guess()) { - // if the date hour is less than the offset - if (24 - dh < Math.abs(dateoffset / 60)) { - // apply the correction to the date/time back to right day - date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); - // the duration was calculated way back at the top before we could correct the start time.. - // fix it for this event entry - //duration = 24 * 60 * 60 * 1000; - Log.debug("new recurring date2 is " + date); - } + //if (event.start.tz === moment.tz.guess()) { + // if the date hour is less than the offset + if (24 - dh <= Math.abs(dateoffset / 60)) { + // apply the correction to the date/time back to right day + date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + //duration = 24 * 60 * 60 * 1000; + Log.debug("new recurring date2 fulldate is " + date); } + //} } } else { // not full day, but luxon can still screw up the date on the rule processing // we need to correct the date to get back to the right event for if (dateoffset < 0) { // if the date hour is less than the offset - if (dh < Math.abs(dateoffset / 60)) { + if (dh <= Math.abs(dateoffset / 60)) { // Reduce the time by the offset: // Apply the correction to the date/time to get it UTC relative - date = new Date(date.getTime() - Math.abs(nowOffset) * 60000); + date = new Date(date.getTime() - Math.abs(24 * 60) * 60000); // the duration was calculated way back at the top before we could correct the start time.. // fix it for this event entry //duration = 24 * 60 * 60 * 1000; @@ -373,21 +371,21 @@ const CalendarUtils = { } } else { // if the timezones are the same, correct date if needed - if (event.start.tz === moment.tz.guess()) { - // if the date hour is less than the offset - if (24 - dh < Math.abs(dateoffset / 60)) { - // apply the correction to the date/time back to right day - date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); - // the duration was calculated way back at the top before we could correct the start time.. - // fix it for this event entry - //duration = 24 * 60 * 60 * 1000; - Log.debug("new recurring date2 is " + date); - } + //if (event.start.tz === moment.tz.guess()) { + // if the date hour is less than the offset + if (24 - dh <= Math.abs(dateoffset / 60)) { + // apply the correction to the date/time back to right day + date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + //duration = 24 * 60 * 60 * 1000; + Log.debug("new recurring date2 is " + date); } + //} } } startDate = moment(date); - Log.debug("Corrected startDate (local): " + startDate.toDate()); + Log.debug("Corrected startDate: " + startDate.toDate()); let adjustDays = CalendarUtils.calculateTimezoneAdjustment(event, date); diff --git a/modules/default/newsfeed/newsfeed.js b/modules/default/newsfeed/newsfeed.js index 7e21f8a8..0b781964 100644 --- a/modules/default/newsfeed/newsfeed.js +++ b/modules/default/newsfeed/newsfeed.js @@ -37,7 +37,8 @@ Module.register("newsfeed", { endTags: [], prohibitedWords: [], scrollLength: 500, - logFeedWarnings: false + logFeedWarnings: false, + dangerouslyDisableAutoEscaping: false }, // Define required scripts. @@ -121,7 +122,7 @@ Module.register("newsfeed", { } if (this.newsItems.length === 0) { return { - loaded: false + empty: true }; } if (this.activeItem >= this.newsItems.length) { @@ -184,6 +185,7 @@ Module.register("newsfeed", { const dateB = new Date(b.pubdate); return dateB - dateA; }); + if (this.config.maxNewsItems > 0) { newsItems = newsItems.slice(0, this.config.maxNewsItems); } @@ -219,7 +221,6 @@ Module.register("newsfeed", { } //Remove selected tags from the end of rss feed items (title or description) - if (this.config.removeEndTags) { for (let endTag of this.config.endTags) { if (item.title.slice(-endTag.length) === endTag) { @@ -295,6 +296,9 @@ Module.register("newsfeed", { this.sendNotification("NEWS_FEED", { items: this.newsItems }); } + // #2638 Clear timer if it already exists + if (this.timer) clearInterval(this.timer); + this.timer = setInterval(() => { this.activeItem++; this.updateDom(this.config.animationSpeed); diff --git a/modules/default/newsfeed/newsfeed.njk b/modules/default/newsfeed/newsfeed.njk index 2b037dcc..04f0ec79 100644 --- a/modules/default/newsfeed/newsfeed.njk +++ b/modules/default/newsfeed/newsfeed.njk @@ -1,3 +1,11 @@ +{% macro escapeText(text, dangerouslyDisableAutoEscaping=false) %} + {% if dangerouslyDisableAutoEscaping %} + {{ text | safe}} + {% else %} + {{ text }} + {% endif %} +{% endmacro %} + {% if loaded %} {% if config.showAsList %}