Release 2.28.0 (#3490)

## [2.28.0] - 2024-07-01

Thanks to: @btoconnor, @bugsounet, @JasonStieber, @khassel,
@kleinmantara and @WallysWellies.

> ⚠️ This release needs nodejs version >= v20

### Added

- [calendar] Added config option "showEndsOnlyWithDuration" for default
calendar
- [compliments] Added `specialDayUnique` config option, defaults to
`false` (#3465)
- [weather] Provider weathergov: Use `precipitationLast3Hours` if
`precipitationLastHour` is `null` (#3124)

### Removed

- [tests] delete node v18 support (#3462)

### Updated

- [core] Update dependencies including electron to v31
- [core] use node >= v20 (#3462)
- [core] Update `config.js.sample` to use openmeteo as weather provider
which needs no api key
- [tests] Use latest@version of node for `automated-tests.yaml` (#3483)
- [updatenotification] Avoid using pm2 when running in docker container

### Fixed

- [core] Fixed crash possibility if `module: <name>` is not defined and
on `postion: <positon>` mistake (#3445)
- [weather] Fixed precipitationProbability in forecast for provider
openmeteo (#3446)
- [weather] Fixed type=daily for provider openmeteo having no data when
running after 23:00 (#3449)
- [weather] Fixed type=daily for provider openmeteo showing nightly
icons in forecast when current time is "nightly" (#3458)
- [weather] Fixed forecast and hourly weather for provider openmeteo to
use real temperatures, not apparent temperatures (#3466)
- [tests] Fixed e2e tests running in docker container which needs
`address: "0.0.0.0"` (#3479)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Michael Teeuw <michael@xonaymedia.nl>
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ross Younger <crazyscot@gmail.com>
Co-authored-by: Veeck <github@veeck.de>
Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr>
Co-authored-by: jkriegshauser <joshuakr@nvidia.com>
Co-authored-by: illimarkangur <116028111+illimarkangur@users.noreply.github.com>
Co-authored-by: sam detweiler <sdetweil@gmail.com>
Co-authored-by: vppencilsharpener <tim.pray@gmail.com>
Co-authored-by: veeck <michael.veeck@nebenan.de>
Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com>
Co-authored-by: Brian O'Connor <btoconnor@users.noreply.github.com>
Co-authored-by: WallysWellies <59727507+WallysWellies@users.noreply.github.com>
Co-authored-by: Jason Stieber <jrstieber@gmail.com>
This commit is contained in:
Karsten Hassel 2024-06-30 23:44:21 +02:00 committed by GitHub
parent 5ea8a3469a
commit 53fc814ff8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 2330 additions and 1254 deletions

View file

@ -18,7 +18,7 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
node-version: [18.x, 20.x, 21.x]
node-version: [20.x, 22.x]
steps:
- name: "Checkout code"
uses: actions/checkout@v4
@ -26,6 +26,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
check-latest: true
cache: "npm"
- name: "Install dependencies"
run: |

View file

@ -6,9 +6,17 @@ jobs:
rebuild:
name: Run electron-rebuild
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: "Use Node.js ${{ matrix.node-version }}"
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
check-latest: true
- name: Install MagicMirror
run: npm run install-mm
- name: Install @electron/rebuild

View file

@ -5,6 +5,39 @@ 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².
## [2.28.0] - 2024-07-01
Thanks to: @btoconnor, @bugsounet, @JasonStieber, @khassel, @kleinmantara and @WallysWellies.
> ⚠️ This release needs nodejs version >= v20
### Added
- [calendar] Added config option "showEndsOnlyWithDuration" for default calendar
- [compliments] Added `specialDayUnique` config option, defaults to `false` (#3465)
- [weather] Provider weathergov: Use `precipitationLast3Hours` if `precipitationLastHour` is `null` (#3124)
### Removed
- [tests] delete node v18 support (#3462)
### Updated
- [core] Update dependencies including electron to v31
- [core] use node >= v20 (#3462)
- [core] Update `config.js.sample` to use openmeteo as weather provider which needs no api key
- [tests] Use latest@version of node for `automated-tests.yaml` (#3483)
- [updatenotification] Avoid using pm2 when running in docker container
### Fixed
- [core] Fixed crash possibility if `module: <name>` is not defined and on `postion: <positon>` mistake (#3445)
- [weather] Fixed precipitationProbability in forecast for provider openmeteo (#3446)
- [weather] Fixed type=daily for provider openmeteo having no data when running after 23:00 (#3449)
- [weather] Fixed type=daily for provider openmeteo showing nightly icons in forecast when current time is "nightly" (#3458)
- [weather] Fixed forecast and hourly weather for provider openmeteo to use real temperatures, not apparent temperatures (#3466)
- [tests] Fixed e2e tests running in docker container which needs `address: "0.0.0.0"` (#3479)
## [2.27.0] - 2024-04-01
Thanks to: @bugsounet, @crazyscot, @illimarkangur, @jkriegshauser, @khassel, @KristjanESPERANTO, @Paranoid93, @rejas, @sdetweil and @vppencilsharpener.
@ -16,7 +49,7 @@ For more info, please read the following post: [A New Chapter for MagicMirror: T
### Added
- Output of system information to the console for troubleshooting (#3328 and #3337), ignore errors under aarch64 (#3349)
- [chore] Add `eslint-plugin-package-json` to lint the `package.json` files (#3368)
- [core] Add `eslint-plugin-package-json` to lint the `package.json` files (#3368)
- [weather] `showHumidity` config is now a string describing where to show this element. Supported values: "wind", "temp", "feelslike", "below", "none". (#3330)
- electron-rebuild test suite for electron and 3rd party modules compatibility (#3392)
- Create MM² icon and attach it to electron process (#3407)
@ -28,8 +61,8 @@ For more info, please read the following post: [A New Chapter for MagicMirror: T
- Use node prefix for build-in modules (#3340)
- Rework logging colors (#3350)
- Update pm2 to v5.3.1 with no allow-ghsas (#3364)
- [chore] Update husky and let lint-staged fix ESLint issues
- [chore] Update dependencies including electron to v29 (#3357) and node-ical
- [core] Update husky and let lint-staged fix ESLint issues
- [core] Update dependencies including electron to v29 (#3357) and node-ical
- Update translations for estonian (#3371)
- Update electron to v29 and update other dependencies
- [calendar] fullDay events over several days now show the left days from the first day on and 'today' on the last day
@ -51,9 +84,9 @@ For more info, please read the following post: [A New Chapter for MagicMirror: T
- added message in case where config.js is missing the module.export line PR #3383
- Fixed an issue where recurring events could extend past their recurrence end date (#3393)
- Don't display any `npm WARN <....>` on install (#3399)
- Fixed move suncalc dependency to production from dev, as it is used by clock module
- [core] Moved suncalc dependency to production from dev, as it is used by clock module
- [compliments] Fix mirror not responding anymore when no compliments are to be shown (#3385)
- [chore] Fixed mastermerge workflow (#3415)
- [core] Fixed mastermerge workflow (#3415)
### Deleted
@ -87,7 +120,7 @@ This release also marks the latest release by Michael Teeuw. For more info, plea
- Update electron to v27 and update other dependencies as well as github actions
- Update newsfeed: Use `html-to-text` instead of regex for transform description
- Review ESLint config (#3269)
- Updated dependencies
- Update dependencies
- Clock module: optionally display current moon phase in addition to rise/set times
- electron is now per default started without gpu, if needed it must be enabled with new env var `ELECTRON_ENABLE_GPU=1` on startup (#3226)
- Replace prettier by stylistic in ESLint config to lint JavaScript (and disable some rules for `config/config.js*` files)

View file

@ -67,11 +67,10 @@ let config = {
module: "weather",
position: "top_right",
config: {
weatherProvider: "openweathermap",
weatherProvider: "openmeteo",
type: "current",
location: "New York",
locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
apiKey: "YOUR_OPENWEATHER_API_KEY"
lat: 40.776676,
lon: -73.971321
}
},
{
@ -79,11 +78,10 @@ let config = {
position: "top_right",
header: "Weather Forecast",
config: {
weatherProvider: "openweathermap",
weatherProvider: "openmeteo",
type: "forecast",
location: "New York",
locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
apiKey: "YOUR_OPENWEATHER_API_KEY"
lat: 40.776676,
lon: -73.971321
}
},
{

View file

@ -9,19 +9,19 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@fontsource/roboto": "^5.0.12",
"@fontsource/roboto-condensed": "^5.0.15"
"@fontsource/roboto": "^5.0.13",
"@fontsource/roboto-condensed": "^5.0.16"
}
},
"node_modules/@fontsource/roboto": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.12.tgz",
"integrity": "sha512-x0o17jvgoSSbS9OZnUX2+xJmVRvVCfeaYJjkS7w62iN7CuJWtMf5vJj8LqgC7ibqIkitOHVW+XssRjgrcHn62g=="
"version": "5.0.13",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.13.tgz",
"integrity": "sha512-j61DHjsdUCKMXSdNLTOxcG701FWnF0jcqNNQi2iPCDxU8seN/sMxeh62dC++UiagCWq9ghTypX+Pcy7kX+QOeQ=="
},
"node_modules/@fontsource/roboto-condensed": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.0.15.tgz",
"integrity": "sha512-0AFvcI/8JQ5+FZE12hdaE1W/1ooJUzd5XP7jD74+SP+PArsswJ+6OV/oKTasgeQeZlvGrcD3chO6yr5K4JGwzA=="
"version": "5.0.16",
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.0.16.tgz",
"integrity": "sha512-pjO80g5x/hkqzWCIafvkS3JrkBDxSiTjEy4LdqQKJYrmoGx8x2AlhSUMgzIzG/ge4kT98bA7+gmm7yquzrrZ/w=="
}
}
}

View file

@ -2,16 +2,16 @@
"name": "magicmirror-fonts",
"version": "1.0.0",
"description": "Package for fonts use by MagicMirror² core.",
"bugs": {
"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/MagicMirrorOrg/MagicMirror"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
},
"dependencies": {
"@fontsource/roboto": "^5.0.12",
"@fontsource/roboto-condensed": "^5.0.15"
"@fontsource/roboto": "^5.0.13",
"@fontsource/roboto-condensed": "^5.0.16"
}
}

View file

@ -58,6 +58,10 @@ function App () {
async function loadConfig () {
Log.log("Loading config ...");
const defaults = require(`${__dirname}/defaults`);
if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
defaults.address = "0.0.0.0";
}
// For this check proposed to TestSuite
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
@ -253,8 +257,15 @@ function App () {
let modules = [];
for (const module of config.modules) {
if (!modules.includes(module.module) && !module.disabled) {
modules.push(module.module);
if (module.disabled) continue;
if (module.module) {
if (Utils.moduleHasValidPosition(module.position) || typeof (module.position) === "undefined") {
modules.push(module.module);
} else {
Log.warn("Invalid module position found for this configuration:", module);
}
} else {
Log.warn("No module name found for this configuration:", module);
}
}

View file

@ -5,6 +5,10 @@ const { Linter } = require("eslint");
const linter = new Linter();
const Ajv = require("ajv");
const ajv = new Ajv();
const rootPath = path.resolve(`${__dirname}/../`);
const Log = require(`${rootPath}/js/logger.js`);
@ -59,6 +63,68 @@ function checkConfigFile () {
for (const error of errors) {
Log.error(`Line ${error.line} column ${error.column}: ${error.message}`);
}
return;
}
Log.info("Checking modules structure configuration... ");
// Make Ajv schema confguration of modules config
// only scan "module" and "position"
const schema = {
type: "object",
properties: {
modules: {
type: "array",
items: {
type: "object",
properties: {
module: {
type: "string"
},
position: {
type: "string",
enum: [
"top_bar",
"top_left",
"top_center",
"top_right",
"upper_third",
"middle_center",
"lower_third",
"bottom_left",
"bottom_center",
"bottom_right",
"bottom_bar",
"fullscreen_above",
"fullscreen_below"
]
}
},
required: ["module"]
}
}
}
};
// scan all modules
const validate = ajv.compile(schema);
const data = require(configFileName);
const valid = validate(data);
if (!valid) {
let module = validate.errors[0].instancePath.split("/")[2];
let position = validate.errors[0].instancePath.split("/")[3];
Log.error(colors.red("This module configuration contains errors:"));
Log.error(data.modules[module]);
if (position) {
Log.error(colors.red(`${position}: ${validate.errors[0].message}`));
Log.error(validate.errors[0].params.allowedValues);
} else {
Log.error(colors.red(validate.errors[0].message));
}
} else {
Log.info(colors.green("Your modules structure configuration doesn't contain errors :)"));
}
}

View file

@ -50,7 +50,8 @@ const Loader = (function () {
* @returns {object[]} module data as configured in config
*/
const getAllModules = function () {
return config.modules;
const AllModules = config.modules.filter((module) => (module.module !== undefined) && (MM.getAvailableModulePositions.indexOf(module.position) > -1 || typeof (module.position) === "undefined"));
return AllModules;
};
/**

View file

@ -450,10 +450,10 @@ const MM = (function () {
* an ugly top margin. By using this function, the top bar will be hidden if the
* update notification is not visible.
*/
const updateWrapperStates = function () {
const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
const modulePositions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
positions.forEach(function (position) {
const updateWrapperStates = function () {
modulePositions.forEach(function (position) {
const wrapper = selectWrapper(position);
const moduleWrappers = wrapper.getElementsByClassName("module");
@ -701,7 +701,10 @@ const MM = (function () {
showModule (module, speed, callback, options) {
// do not change module.hidden yet, only if we really show it later
showModule(module, speed, callback, options);
}
},
// return all available module postions.
getAvailableModulePositions: modulePositions
};
}());

View file

@ -25,5 +25,16 @@ module.exports = {
} catch (e) {
Log.error(e);
}
},
// return all available module positions
getAvailableModulePositions () {
return ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
},
// return if postion is on modulePositions Array (true/false)
moduleHasValidPosition (position) {
if (this.getAvailableModulePositions().indexOf(position) === -1) return false;
return true;
}
};

View file

@ -29,6 +29,7 @@ Module.register("calendar", {
dateEndFormat: "LT",
fullDayEventDateFormat: "MMM Do",
showEnd: false,
showEndsOnlyWithDuration: false,
getRelative: 6,
hidePrivate: false,
hideOngoing: false,
@ -388,7 +389,11 @@ Module.register("calendar", {
// Add endDate to dataheaders if showEnd is enabled
if (this.config.showEnd) {
timeWrapper.innerHTML += ` - ${CalendarUtils.capFirst(moment(event.endDate, "x").format("LT"))}`;
if (this.config.showEndsOnlyWithDuration && event.startDate === event.endDate) {
// no duration here, don't display end
} else {
timeWrapper.innerHTML += ` - ${CalendarUtils.capFirst(moment(event.endDate, "x").format("LT"))}`;
}
}
eventWrapper.appendChild(timeWrapper);
@ -407,8 +412,12 @@ Module.register("calendar", {
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
// Add end time if showEnd
if (this.config.showEnd) {
timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += CalendarUtils.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
if (this.config.showEndsOnlyWithDuration && event.startDate === event.endDate) {
// no duration here, don't display end
} else {
timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += CalendarUtils.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
}
}
// For full day events we use the fullDayEventDateFormat
if (event.fullDayEvent) {

View file

@ -15,7 +15,8 @@ Module.register("compliments", {
morningEndTime: 12,
afternoonStartTime: 12,
afternoonEndTime: 17,
random: true
random: true,
specialDayUnique: false
},
lastIndexUsed: -1,
// Set currentweather from module
@ -98,6 +99,10 @@ Module.register("compliments", {
// Add compliments for special days
for (let entry in this.config.compliments) {
if (new RegExp(entry).test(date)) {
// Only display compliments configured for the day if specialDayUnique is set to true
if (this.config.specialDayUnique) {
compliments.length = 0;
}
Array.prototype.push.apply(compliments, this.config.compliments[entry]);
}
}

View file

@ -1,6 +1,6 @@
const Exec = require("node:child_process").exec;
const Spawn = require("node:child_process").spawn;
const pm2 = require("pm2");
const fs = require("node:fs");
const Log = require("logger");
@ -139,6 +139,7 @@ class Updater {
// restart MagicMiror with "pm2"
pm2Restart () {
Log.info("updatenotification: PM2 will restarting MagicMirror...");
const pm2 = require("pm2");
pm2.restart(this.PM2, (err, proc) => {
if (err) {
Log.error("updatenotification:[PM2] restart Error", err);
@ -160,6 +161,14 @@ class Updater {
check_PM2_Process () {
Log.info("updatenotification: Checking PM2 using...");
return new Promise((resolve) => {
if (fs.existsSync("/.dockerenv")) {
Log.info("updatenotification: Running in docker container, not using PM2 ...");
this.usePM2 = false;
resolve(false);
return;
}
const pm2 = require("pm2");
pm2.connect((err) => {
if (err) {
Log.error("updatenotification: [PM2]", err);

View file

@ -398,14 +398,14 @@ WeatherProvider.register("openmeteo", {
currentWeather.windFromDirection = weather.winddirection_10m_dominant;
currentWeather.sunrise = weather.sunrise;
currentWeather.sunset = weather.sunset;
currentWeather.temperature = parseFloat((weather.apparent_temperature_max + weather.apparent_temperature_min) / 2);
currentWeather.minTemperature = parseFloat(weather.apparent_temperature_min);
currentWeather.maxTemperature = parseFloat(weather.apparent_temperature_max);
currentWeather.weatherType = this.convertWeatherType(weather.weathercode, currentWeather.isDayTime());
currentWeather.temperature = parseFloat((weather.temperature_2m_max + weather.temperature_2m_min) / 2);
currentWeather.minTemperature = parseFloat(weather.temperature_2m_min);
currentWeather.maxTemperature = parseFloat(weather.temperature_2m_max);
currentWeather.weatherType = this.convertWeatherType(weather.weathercode, true);
currentWeather.rain = parseFloat(weather.rain_sum);
currentWeather.snow = parseFloat(weather.snowfall_sum * 10);
currentWeather.precipitationAmount = parseFloat(weather.precipitation_sum);
currentWeather.precipitationProbability = parseFloat(weather.precipitation_probability);
currentWeather.precipitationProbability = parseFloat(weather.precipitation_hours * 100 / 24);
currentWeather.uv_index = parseFloat(weather.uv_index_max);
days.push(currentWeather);
@ -420,7 +420,7 @@ WeatherProvider.register("openmeteo", {
const now = moment();
weathers.hourly.forEach((weather, i) => {
if ((hours.length === 0 && weather.time.hour() <= now.hour()) || hours.length >= this.config.maxEntries) {
if ((hours.length === 0 && weather.time <= now) || hours.length >= this.config.maxEntries) {
return;
}
@ -432,9 +432,9 @@ WeatherProvider.register("openmeteo", {
currentWeather.windFromDirection = weather.winddirection_10m;
currentWeather.sunrise = weathers.daily[h].sunrise;
currentWeather.sunset = weathers.daily[h].sunset;
currentWeather.temperature = parseFloat(weather.apparent_temperature);
currentWeather.minTemperature = parseFloat(weathers.daily[h].apparent_temperature_min);
currentWeather.maxTemperature = parseFloat(weathers.daily[h].apparent_temperature_max);
currentWeather.temperature = parseFloat(weather.temperature_2m);
currentWeather.minTemperature = parseFloat(weathers.daily[h].temperature_2m_min);
currentWeather.maxTemperature = parseFloat(weathers.daily[h].temperature_2m_max);
currentWeather.weatherType = this.convertWeatherType(weather.weathercode, currentWeather.isDayTime());
currentWeather.humidity = parseFloat(weather.relativehumidity_2m);
currentWeather.rain = parseFloat(weather.rain);

View file

@ -211,7 +211,7 @@ WeatherProvider.register("weathergov", {
currentWeather.minTemperature = currentWeatherData.minTemperatureLast24Hours.value;
currentWeather.maxTemperature = currentWeatherData.maxTemperatureLast24Hours.value;
currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value);
currentWeather.precipitationAmount = currentWeatherData.precipitationLastHour.value;
currentWeather.precipitationAmount = currentWeatherData.precipitationLastHour.value ? currentWeatherData.precipitationLastHour.value : currentWeatherData.precipitationLast3Hours.value;
if (currentWeatherData.heatIndex.value !== null) {
currentWeather.feelsLikeTemp = currentWeatherData.heatIndex.value;
} else if (currentWeatherData.windChill.value !== null) {

3140
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,27 @@
{
"name": "magicmirror",
"version": "2.27.0",
"version": "2.28.0",
"description": "The open source modular smart mirror platform.",
"keywords": [
"magic mirror",
"magicmirror",
"smart mirror",
"mirror UI",
"modular"
],
"homepage": "https://magicmirror.builders",
"bugs": {
"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/MagicMirrorOrg/MagicMirror"
},
"license": "MIT",
"author": "Michael Teeuw",
"contributors": [
"https://github.com/MagicMirrorOrg/MagicMirror/graphs/contributors"
],
"main": "js/electron.js",
"scripts": {
"start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
@ -28,50 +48,14 @@
"lint:staged": "lint-staged",
"prepare": "[ -f node_modules/.bin/husky ] && husky || echo no husky installed."
},
"repository": {
"type": "git",
"url": "https://github.com/MagicMirrorOrg/MagicMirror"
},
"keywords": [
"magic mirror",
"magicmirror",
"smart mirror",
"mirror UI",
"modular"
],
"author": "Michael Teeuw",
"contributors": [
"https://github.com/MagicMirrorOrg/MagicMirror/graphs/contributors"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
},
"homepage": "https://magicmirror.builders",
"devDependencies": {
"@stylistic/eslint-plugin": "^1.7.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-jsdoc": "^48.2.2",
"eslint-plugin-package-json": "^0.12.1",
"eslint-plugin-unicorn": "^51.0.1",
"express-basic-auth": "^1.2.1",
"husky": "^9.0.11",
"jest": "^29.7.0",
"jsdom": "^24.0.0",
"lint-staged": "^15.2.2",
"playwright": "^1.42.1",
"prettier": "^3.2.5",
"sinon": "^17.0.1",
"stylelint": "^16.3.1",
"stylelint-config-standard": "^36.0.0",
"stylelint-prettier": "^5.0.0"
},
"optionalDependencies": {
"electron": "^29.1.6"
"lint-staged": {
"*": "prettier --write",
"*.js": "eslint --fix",
"*.css": "stylelint --fix"
},
"dependencies": {
"ansis": "^2.3.0",
"ajv": "^8.16.0",
"ansis": "^3.2.0",
"console-stamp": "^3.1.2",
"envsub": "^4.1.0",
"eslint": "^8.57.0",
@ -84,21 +68,38 @@
"module-alias": "^2.2.3",
"moment": "^2.30.1",
"node-ical": "^0.18.0",
"pm2": "^5.3.1",
"pm2": "^5.4.1",
"socket.io": "^4.7.5",
"suncalc": "^1.9.0",
"systeminformation": "^5.22.6"
"systeminformation": "^5.22.11"
},
"lint-staged": {
"*": "prettier --write",
"*.js": "eslint --fix",
"*.css": "stylelint --fix"
"devDependencies": {
"@stylistic/eslint-plugin": "^1.8.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^28.6.0",
"eslint-plugin-jsdoc": "^48.5.0",
"eslint-plugin-package-json": "^0.15.0",
"eslint-plugin-unicorn": "^54.0.0",
"express-basic-auth": "^1.2.1",
"husky": "^9.0.11",
"jest": "^29.7.0",
"jsdom": "^24.1.0",
"lint-staged": "^15.2.7",
"playwright": "^1.45.0",
"prettier": "^3.3.2",
"sinon": "^18.0.0",
"stylelint": "^16.6.1",
"stylelint-config-standard": "^36.0.1",
"stylelint-prettier": "^5.0.0"
},
"optionalDependencies": {
"electron": "^31.1.0"
},
"engines": {
"node": ">=20"
},
"_moduleAliases": {
"node_helper": "js/node_helper.js",
"logger": "js/logger.js"
},
"engines": {
"node": ">=18"
}
}

View file

@ -0,0 +1,22 @@
let config = {
modules: [
{
module: "compliments",
position: "middle_center",
config: {
specialDayUnique: false,
compliments: {
anytime: [
"Typical message 1",
"Typical message 2",
"Typical message 3"
],
"....-..-..": ["Special day message"]
}
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { module.exports = config; }

View file

@ -0,0 +1,22 @@
let config = {
modules: [
{
module: "compliments",
position: "middle_center",
config: {
specialDayUnique: true,
compliments: {
anytime: [
"Typical message 1",
"Typical message 2",
"Typical message 3"
],
"....-..-..": ["Special day message"]
}
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { module.exports = config; }

View file

@ -54,4 +54,28 @@ describe("Compliments module", () => {
await expect(doTest(["Remote compliment file works!"])).resolves.toBe(true);
});
});
describe("Feature specialDayUnique in compliments module", () => {
describe("specialDayUnique is false", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_specialDayUnique_false.js");
await helpers.getDocument();
});
it("compliments array can contain all values", async () => {
await expect(doTest(["Special day message", "Typical message 1", "Typical message 2", "Typical message 3"])).resolves.toBe(true);
});
});
describe("specialDayUnique is true", () => {
beforeAll(async () => {
await helpers.startApplication("tests/configs/modules/compliments/compliments_specialDayUnique_true.js");
await helpers.getDocument();
});
it("compliments array contains only special value", async () => {
await expect(doTest(["Special day message"])).resolves.toBe(true);
});
});
});
});

8
vendor/package-lock.json generated vendored
View file

@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1",
"@fortawesome/fontawesome-free": "^6.5.2",
"animate.css": "^4.1.1",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
@ -19,9 +19,9 @@
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz",
"integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz",
"integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==",
"hasInstallScript": true,
"engines": {
"node": ">=6"

8
vendor/package.json vendored
View file

@ -2,16 +2,16 @@
"name": "magicmirror-vendors",
"version": "1.0.0",
"description": "Package for vendors use by MagicMirror² core.",
"bugs": {
"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/MagicMirrorOrg/MagicMirror"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/MagicMirrorOrg/MagicMirror/issues"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1",
"@fortawesome/fontawesome-free": "^6.5.2",
"animate.css": "^4.1.1",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",