mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 08:50:59 -04:00
Release 2.30.0 (#3673)
## [2.30.0] - 2025-01-01 Thanks to: @xsorifc28, @HeikoGr, @bugsounet, @khassel, @KristjanESPERANTO, @rejas, @sdetweil. > ⚠️ This release needs nodejs version `v20` or `v22 or higher`, minimum version is `v20.18.1` ### Added - [core] Add wayland and windows start options to `package.json` (#3594) - [docs] Add step for npm publishing in release process (#3595) - [core] Add GitHub workflow to run spellcheck a few days before each release (#3623) - [core] Add test flag to `index.html` to pass to module js for test mode detection (needed by #3630) - [core] Add export on animation names (#3644) - [compliments] Add support for refreshing remote compliments file, and test cases (#3630) - [linter] Re-add `eslint-plugin-import`now that it supports ESLint v9 (#3586) - [linter] Re-activate `eslint-plugin-package-json` to lint `package.json` (#3643) - [linter] Add linting for markdown files (#3646) - [linter] Add some handy ESLint rules. - [calendar] Add ability to display end date for full date events, where end is not same day (showEnd=true) (#3650) - [core] Add text to the config.js.sample file about the locale variable (#3654, #3655) - [core] Add fetch timeout for all node_helpers (thru undici, forces node 20.18.1 minimum) to help on slower systems. (#3660) (3661) ### Changed - [core] Run code style checks in workflow only once (#3648) - [core] Fix animations export #3644 only on server side (#3649) - [core] Use project URL in fallback config (#3656) - [core] Fix Access Denied crash writing js/positions.js (on synology nas) #3651. new message, MM starts, but no modules showing (#3652) - [linter] Switch to 'npx' for lint-staged in pre-commit hook (#3658) ### Removed - [tests] Remove `node-pty` and `drivelist` from rebuilded test (#3575) - [deps] Remove `@eslint/js` dependency. Already installed with `eslint` in deep (#3636) ### Updated - [repo] Reactivate `stale.yaml` as GitHub action to mark issues as stale after 60 days and close them 7 days later (if no activity) (#3577, #3580, #3581) - [core] Update electron dependency to v32 (test electron rebuild) and all other dependencies too (#3657) - [tests] All test configs have been updated to allow full external access, allowing for easier debugging (especially when running as a container) - [core] Run and test with node 23 (#3588) - [workflow] delete exception `allow-ghsas: GHSA-8hc4-vh64-cxmj` in `dep-review.yaml` (#3659) ### Fixed - [updatenotification] Fix pm2 using detection when pm2 script is inside or outside MagicMirror root folder (#3576) (#3605) (#3626) (#3628) - [core] Fix loading node_helper of modules: avoid black screen, display errors and continue loading with next module (#3578) - [weather] Change default value for weatherEndpoint of provider openweathermap to "/onecall" (#3574) - [tests] Fix electron tests with mock dates, the mock on server side was missing (#3597) - [tests] Fix testcases with hard coded Date.now (#3597) - [core] Fix missing `basePath` where `location.host` is used (#3613) - [compliments] croner library changed filenames used in latest version (#3624) - [linter] Fix ESLint ignore pattern which caused that default modules not to be linted (#3632) - [core] Fix module path in case of sub/sub folder is used and use path.resolve for resolve `moduleFolder` and `defaultModuleFolder` in app.js (#3653) - [calendar] Update to resolve issues #3098 #3144 #3351 #3422 #3443 #3467 #3537 related to timezone changes - [calendar] Fix #3267 (styles array), also fixes event with both exdate AND recurrence(and testcase) - [calendar] Fix showEndsOnlyWithDuration not working, #3598, applies ONLY to full day events - [calendar] Fix showEnd for Full Day events (#3602) - [tests] Suppress "module is not defined" in e2e tests (#3647) - [calendar] Fix #3267 (styles array, really this time!) - [core] Fix #3662 js/positions.js created incorrectly --------- 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: Karsten Hassel <hassel@gmx.de> 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: 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> Co-authored-by: jargordon <50050429+jargordon@users.noreply.github.com> Co-authored-by: Daniel <32464403+dkallen78@users.noreply.github.com> Co-authored-by: Ryan Williams <65094007+ryan-d-williams@users.noreply.github.com> Co-authored-by: Panagiotis Skias <panagiotis.skias@gmail.com> Co-authored-by: Marc Landis <dirk.rettschlag@gmail.com> Co-authored-by: HeikoGr <20295490+HeikoGr@users.noreply.github.com> Co-authored-by: Pedro Lamas <pedrolamas@gmail.com> Co-authored-by: veeck <gitkraken@veeck.de>
This commit is contained in:
parent
94c3c699e8
commit
c24de64d77
145 changed files with 4895 additions and 1915 deletions
18
.github/CONTRIBUTING.md
vendored
18
.github/CONTRIBUTING.md
vendored
|
@ -6,22 +6,28 @@ We hold our code to standard, and these standards are documented below.
|
|||
|
||||
## Linters
|
||||
|
||||
We use prettier for automatic linting of all our files: `npm run lint:prettier`.
|
||||
We use [prettier](https://prettier.io/) for automatic formatting a lot all our files. The configuration is in our `prettier.config.mjs` file.
|
||||
|
||||
To run prettier, use `npm run lint:prettier`.
|
||||
|
||||
### JavaScript: Run ESLint
|
||||
|
||||
We use [ESLint](https://eslint.org) on our JavaScript files.
|
||||
|
||||
The ESLint configuration is in our `eslint.config.mjs` file.
|
||||
We use [ESLint](https://eslint.org) to lint our JavaScript files. The configuration is in our `eslint.config.mjs` file.
|
||||
|
||||
To run ESLint, use `npm run lint:js`.
|
||||
|
||||
### CSS: Run StyleLint
|
||||
|
||||
We use [StyleLint](https://stylelint.io) to lint our CSS. Our configuration is in our `.stylelintrc` file.
|
||||
We use [StyleLint](https://stylelint.io) to lint our CSS. The configuration is in our `.stylelintrc.json` file.
|
||||
|
||||
To run StyleLint, use `npm run lint:css`.
|
||||
|
||||
### Markdown: Run markdownlint
|
||||
|
||||
We use [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) to lint our markdown files. The configuration is in our `.markdownlint.json` file.
|
||||
|
||||
To run markdownlint, use `npm run markdownlint:css`.
|
||||
|
||||
## Testing
|
||||
|
||||
We use [Jest](https://jestjs.io) for JavaScript testing.
|
||||
|
@ -43,7 +49,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 18 or later (recommended is 20).
|
||||
**Node Version**: Make sure it's version 20 or later (recommended is 22).
|
||||
|
||||
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -35,7 +35,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 18 or later (recommended is 20).
|
||||
**Node Version**: Make sure it's version 20 or later (recommended is 22).
|
||||
|
||||
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
|
||||
|
||||
|
|
19
.github/stale.yaml
vendored
19
.github/stale.yaml
vendored
|
@ -1,19 +0,0 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- under investigation
|
||||
- pr welcome
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
25
.github/workflows/automated-tests.yaml
vendored
25
.github/workflows/automated-tests.yaml
vendored
|
@ -13,12 +13,32 @@ permissions:
|
|||
contents: read
|
||||
|
||||
jobs:
|
||||
code-style-check:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 23
|
||||
cache: "npm"
|
||||
- name: "Install dependencies"
|
||||
run: |
|
||||
npm run install-mm:dev
|
||||
- name: "Run linter tests"
|
||||
run: |
|
||||
npm run test:prettier
|
||||
npm run test:js
|
||||
npm run test:css
|
||||
npm run test:markdown
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.9.0, 20.x, 22.x]
|
||||
node-version: [20.18.1, 20.x, 22.x, 23.x]
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
@ -36,7 +56,4 @@ jobs:
|
|||
Xvfb :99 -screen 0 1024x768x16 &
|
||||
export DISPLAY=:99
|
||||
touch css/custom.css
|
||||
npm run test:prettier
|
||||
npm run test:js
|
||||
npm run test:css
|
||||
npm run test
|
||||
|
|
2
.github/workflows/dep-review.yaml
vendored
2
.github/workflows/dep-review.yaml
vendored
|
@ -16,5 +16,3 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
allow-ghsas: GHSA-8hc4-vh64-cxmj
|
||||
|
|
6
.github/workflows/electron-rebuild.yaml
vendored
6
.github/workflows/electron-rebuild.yaml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.9.0, 20.x, 22.x]
|
||||
node-version: [20.18.1, 20.x, 22.x, 23.x]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
@ -23,8 +23,8 @@ jobs:
|
|||
run: npm install @electron/rebuild
|
||||
- name: Install node-libgpiod deps
|
||||
run: sudo apt-get install gpiod libgpiod2 libgpiod-dev
|
||||
- name: Install some test library to be rebuilded
|
||||
run: npm install node-libgpiod node-pty drivelist
|
||||
- name: Install test library (node-libgpiod) to be rebuilded
|
||||
run: npm install node-libgpiod
|
||||
- name: Run electron-rebuild
|
||||
run: npx electron-rebuild
|
||||
continue-on-error: false
|
||||
|
|
31
.github/workflows/spellcheck.yaml
vendored
Normal file
31
.github/workflows/spellcheck.yaml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
# This workflow will run a spellcheck on the codebase.
|
||||
# It runs a few days before each release. At 00:00 on day-of-month 27 in March, June, September, and December.
|
||||
|
||||
name: Run Spellcheck
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 27 3,6,9,12 *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
spellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: develop
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
check-latest: true
|
||||
cache: "npm"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm run install-mm:dev
|
||||
- name: Run Spellcheck
|
||||
run: npm run test:spellcheck
|
22
.github/workflows/stale.yaml
vendored
Normal file
22
.github/workflows/stale.yaml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: "Close stale issues and PRs"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # needed for manually running this workflow
|
||||
schedule:
|
||||
- cron: "30 1 * * 6" # every Saturday at 1:30
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions."
|
||||
days-before-issue-stale: 60
|
||||
days-before-issue-close: 7
|
||||
operations-per-run: 100
|
||||
stale-issue-label: "wontfix"
|
||||
exempt-issue-labels: "pinned,security,under investigation,pr welcome"
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
if command -v npm &> /dev/null; then
|
||||
npm run lint:staged
|
||||
if command -v npx &> /dev/null; then
|
||||
npx lint-staged
|
||||
fi
|
||||
|
|
6
.markdownlint.json
Normal file
6
.markdownlint.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"line_length": false,
|
||||
"no-duplicate-heading": false,
|
||||
"no-inline-html": false,
|
||||
"no-trailing-punctuation": false
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"trailingComma": "none"
|
||||
}
|
127
CHANGELOG.md
127
CHANGELOG.md
|
@ -1,10 +1,74 @@
|
|||
# MagicMirror² Change Log
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/#donate) With your help we can continue to improve the MagicMirror².
|
||||
|
||||
## [2.30.0] - 2025-01-01
|
||||
|
||||
Thanks to: @xsorifc28, @HeikoGr, @bugsounet, @khassel, @KristjanESPERANTO, @rejas, @sdetweil.
|
||||
|
||||
> ⚠️ This release needs nodejs version `v20` or `v22 or higher`, minimum version is `v20.18.1`
|
||||
|
||||
### Added
|
||||
|
||||
- [core] Add wayland and windows start options to `package.json` (#3594)
|
||||
- [docs] Add step for npm publishing in release process (#3595)
|
||||
- [core] Add GitHub workflow to run spellcheck a few days before each release (#3623)
|
||||
- [core] Add test flag to `index.html` to pass to module js for test mode detection (needed by #3630)
|
||||
- [core] Add export on animation names (#3644)
|
||||
- [compliments] Add support for refreshing remote compliments file, and test cases (#3630)
|
||||
- [linter] Re-add `eslint-plugin-import`now that it supports ESLint v9 (#3586)
|
||||
- [linter] Re-activate `eslint-plugin-package-json` to lint `package.json` (#3643)
|
||||
- [linter] Add linting for markdown files (#3646)
|
||||
- [linter] Add some handy ESLint rules.
|
||||
- [calendar] Add ability to display end date for full date events, where end is not same day (showEnd=true) (#3650)
|
||||
- [core] Add text to the config.js.sample file about the locale variable (#3654, #3655)
|
||||
- [core] Add fetch timeout for all node_helpers (thru undici, forces node 20.18.1 minimum) to help on slower systems. (#3660) (3661)
|
||||
|
||||
### Changed
|
||||
|
||||
- [core] Run code style checks in workflow only once (#3648)
|
||||
- [core] Fix animations export #3644 only on server side (#3649)
|
||||
- [core] Use project URL in fallback config (#3656)
|
||||
- [core] Fix Access Denied crash writing js/positions.js (on synology nas) #3651. new message, MM starts, but no modules showing (#3652)
|
||||
- [linter] Switch to 'npx' for lint-staged in pre-commit hook (#3658)
|
||||
|
||||
### Removed
|
||||
|
||||
- [tests] Remove `node-pty` and `drivelist` from rebuilded test (#3575)
|
||||
- [deps] Remove `@eslint/js` dependency. Already installed with `eslint` in deep (#3636)
|
||||
|
||||
### Updated
|
||||
|
||||
- [repo] Reactivate `stale.yaml` as GitHub action to mark issues as stale after 60 days and close them 7 days later (if no activity) (#3577, #3580, #3581)
|
||||
- [core] Update electron dependency to v32 (test electron rebuild) and all other dependencies too (#3657)
|
||||
- [tests] All test configs have been updated to allow full external access, allowing for easier debugging (especially when running as a container)
|
||||
- [core] Run and test with node 23 (#3588)
|
||||
- [workflow] delete exception `allow-ghsas: GHSA-8hc4-vh64-cxmj` in `dep-review.yaml` (#3659)
|
||||
|
||||
### Fixed
|
||||
|
||||
- [updatenotification] Fix pm2 using detection when pm2 script is inside or outside MagicMirror root folder (#3576) (#3605) (#3626) (#3628)
|
||||
- [core] Fix loading node_helper of modules: avoid black screen, display errors and continue loading with next module (#3578)
|
||||
- [weather] Change default value for weatherEndpoint of provider openweathermap to "/onecall" (#3574)
|
||||
- [tests] Fix electron tests with mock dates, the mock on server side was missing (#3597)
|
||||
- [tests] Fix testcases with hard coded Date.now (#3597)
|
||||
- [core] Fix missing `basePath` where `location.host` is used (#3613)
|
||||
- [compliments] croner library changed filenames used in latest version (#3624)
|
||||
- [linter] Fix ESLint ignore pattern which caused that default modules not to be linted (#3632)
|
||||
- [core] Fix module path in case of sub/sub folder is used and use path.resolve for resolve `moduleFolder` and `defaultModuleFolder` in app.js (#3653)
|
||||
- [calendar] Update to resolve issues #3098 #3144 #3351 #3422 #3443 #3467 #3537 related to timezone changes
|
||||
- [calendar] Fix #3267 (styles array), also fixes event with both exdate AND recurrence(and testcase)
|
||||
- [calendar] Fix showEndsOnlyWithDuration not working, #3598, applies ONLY to full day events
|
||||
- [calendar] Fix showEnd for Full Day events (#3602)
|
||||
- [tests] Suppress "module is not defined" in e2e tests (#3647)
|
||||
- [calendar] Fix #3267 (styles array, really this time!)
|
||||
- [core] Fix #3662 js/positions.js created incorrectly
|
||||
|
||||
## [2.29.0] - 2024-10-01
|
||||
|
||||
Thanks to: @bugsounet, @dkallen78, @jargordon, @khassel, @KristjanESPERANTO, @MarcLandis, @rejas, @ryan-d-williams, @sdetweil, @skpanagiotis.
|
||||
|
@ -13,7 +77,7 @@ Thanks to: @bugsounet, @dkallen78, @jargordon, @khassel, @KristjanESPERANTO, @Ma
|
|||
|
||||
### Added
|
||||
|
||||
- [compliments] Added support for cron type date/time format entries mm hh DD MM dow (minutes/hours/days/months and day of week) see https://crontab.cronhub.io for construction (#3481)
|
||||
- [compliments] Added support for cron type date/time format entries mm hh DD MM dow (minutes/hours/days/months and day of week) see <https://crontab.cronhub.io> for construction (#3481)
|
||||
- [core] Check config at every start of MagicMirror² (#3450)
|
||||
- [core] Add spelling check (cspell): `npm run test:spelling` and handle spelling issues (#3544)
|
||||
- [core] removed `config.paths.vendor` (could not work because `vendor` is hardcoded in `index.html`), renamed `config.paths.modules` to `config.foreignModulesDir`, added variable `MM_CUSTOMCSS_FILE` which - if set - overrides `config.customCss`, added variable `MM_MODULES_DIR` which - if set - overrides `config.foreignModulesDir`, added test for `MM_MODULES_DIR` (#3530)
|
||||
|
@ -43,9 +107,9 @@ Thanks to: @bugsounet, @dkallen78, @jargordon, @khassel, @KristjanESPERANTO, @Ma
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fixed `checks` badge in README.md
|
||||
- [docs] Fixed `checks` badge in README.md
|
||||
- [weather] Fixed issue with the UK Met Office provider following a change in their API paths and header info.
|
||||
- [core] add check for node_helper loading for multiple instances of same module (#3502)
|
||||
- [core] Add check for node_helper loading for multiple instances of same module (#3502)
|
||||
- [weather] Fixed issue for respecting unit config on broadcasted notifications
|
||||
- [tests] Fixes calendar test by moving it from e2e to electron with fixed date (#3532)
|
||||
- [calendar] fixed sliceMultiDayEvents getting wrong count and displaying incorrect entries, Europe/Berlin (#3542)
|
||||
|
@ -117,7 +181,7 @@ For more info, please read the following post: [A New Chapter for MagicMirror: T
|
|||
|
||||
### Fixed
|
||||
|
||||
- Correct apiBase of weathergov weatherProvider to match documentation (#2926)
|
||||
- [weather] Correct apiBase of weathergov weatherProvider to match documentation (#2926)
|
||||
- Worked around several issues in the RRULE library that were causing deleted calender events to still show, some
|
||||
initial and recurring events to not show, and some event times to be off an hour. (#3291)
|
||||
- Skip changelog requirement when running tests for dependency updates (#3320)
|
||||
|
@ -269,7 +333,7 @@ Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not al
|
|||
|
||||
### Updated
|
||||
|
||||
- Added support for precipitation probability with openmeteo weather-provider
|
||||
- [weather] Added support for precipitation probability with openmeteo weather-provider
|
||||
- Update electron to v25.2 and other dependencies
|
||||
- Use node v20 in github workflow (replacing v14)
|
||||
- Refactor formatTime into common util function for default modules
|
||||
|
@ -442,7 +506,7 @@ Special thanks to the following contributors: @eouia, @khassel, @kolbyjack, @Kri
|
|||
|
||||
### Added
|
||||
|
||||
- Added a new config option `httpHeaders` used by helmet (see https://helmetjs.github.io/). You can now set own httpHeaders which will override the defaults in `js/defaults.js` which is useful e.g. if you want to embed MagicMirror into another website (solves #2847).
|
||||
- Added a new config option `httpHeaders` used by helmet (see <https://helmetjs.github.io/>). You can now set own httpHeaders which will override the defaults in `js/defaults.js` which is useful e.g. if you want to embed MagicMirror into another website (solves #2847).
|
||||
- Show endDate for calendar events when dateHeader is enabled and showEnd is set to true (#2192).
|
||||
- Added the notification emitting from the weather module on information updated.
|
||||
- Use recommended file extension for YAML files (#2864).
|
||||
|
@ -1606,3 +1670,50 @@ It includes (but is not limited to) the following features:
|
|||
### Initial release of MagicMirror
|
||||
|
||||
This was part of the blogpost: [https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the](https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the)
|
||||
|
||||
[2.30.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.29.0...v2.30.0
|
||||
[2.29.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.28.0...v2.29.0
|
||||
[2.28.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.27.0...v2.28.0
|
||||
[2.27.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.26.0...v2.27.0
|
||||
[2.26.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.25.0...v2.26.0
|
||||
[2.25.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.24.0...v2.25.0
|
||||
[2.24.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.23.0...v2.24.0
|
||||
[2.23.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.22.0...v2.23.0
|
||||
[2.22.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.21.0...v2.22.0
|
||||
[2.21.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.20.0...v2.21.0
|
||||
[2.20.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.19.0...v2.20.0
|
||||
[2.19.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.18.0...v2.19.0
|
||||
[2.18.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.17.1...v2.18.0
|
||||
[2.17.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.17.0...v2.17.1
|
||||
[2.17.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.16.0...v2.17.0
|
||||
[2.16.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.15.0...v2.16.0
|
||||
[2.15.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.14.0...v2.15.0
|
||||
[2.14.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.13.0...v2.14.0
|
||||
[2.13.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.12.0...v2.13.0
|
||||
[2.12.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.11.0...v2.12.0
|
||||
[2.11.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.10.1...v2.11.0
|
||||
[2.10.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.10.0...v2.10.1
|
||||
[2.10.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.9.0...v2.10.0
|
||||
[2.9.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.8.0...v2.9.0
|
||||
[2.8.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.7.1...v2.8.0
|
||||
[2.7.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.7.0...v2.7.1
|
||||
[2.7.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.6.0...v2.7.0
|
||||
[2.6.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.5.0...v2.6.0
|
||||
[2.5.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.4.1...v2.5.0
|
||||
[2.4.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.4.0...v2.4.1
|
||||
[2.4.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.3.1...v2.4.0
|
||||
[2.3.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.3.0...v2.3.1
|
||||
[2.3.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.2.2...v2.3.0
|
||||
[2.2.2]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.2.1...v2.2.2
|
||||
[2.2.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.2.0...v2.2.1
|
||||
[2.2.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.1.3...v2.2.0
|
||||
[2.1.3]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.1.2...v2.1.3
|
||||
[2.1.2]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.1.1...v2.1.2
|
||||
[2.1.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.1.0...v2.1.1
|
||||
[2.1.0]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.0.5...v2.1.0
|
||||
[2.0.5]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.0.4...v2.0.5
|
||||
[2.0.4]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.0.3...v2.0.4
|
||||
[2.0.3]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.0.2...v2.0.3
|
||||
[2.0.2]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.0.1...v2.0.2
|
||||
[2.0.1]: https://github.com/MagicMirrorOrg/MagicMirror/compare/v2.0.0...v2.0.1
|
||||
[2.0.0]: https://github.com/MagicMirrorOrg/MagicMirror/releases/tag/v2.0.0
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# Collaboration
|
||||
|
||||
This document describes how collaborators of this repository should work together.
|
||||
|
||||
## Pull Requests
|
||||
|
@ -33,6 +35,7 @@ Are done by
|
|||
- [ ] update `CHANGELOG.md`
|
||||
- [ ] add all contributor names: `...`
|
||||
- [ ] add min. node version: > ⚠️ This release needs nodejs version `v20` or `v22`, minimum version is `v20.9.0`
|
||||
- [ ] check release link at the bottom of the file
|
||||
- [ ] commit and push all changes
|
||||
- [ ] after successful test run via github actions: create pull request from `develop` to `master` branch
|
||||
- [ ] add label `mastermerge`
|
||||
|
@ -42,13 +45,14 @@ Are done by
|
|||
- [ ] create new release with
|
||||
- [ ] corresponding version tag `v2.xx.0`
|
||||
- [ ] a release name: `...`
|
||||
- [ ] description of the PR is the section of the `CHANGELOG.md`
|
||||
- [ ] description of the release is the section of the `CHANGELOG.md`
|
||||
|
||||
### Draft new development release
|
||||
|
||||
- [ ] checkout `develop` branch
|
||||
- [ ] update `package.json` and `package-lock.json` to reflect correct version number `2.xx.0-develop`
|
||||
- [ ] draft new section in `CHANGELOG.md`
|
||||
- [ ] create new release link at the bottom of the file
|
||||
- [ ] commit and publish `develop` branch
|
||||
|
||||
### After release
|
||||
|
@ -56,3 +60,4 @@ Are done by
|
|||
- [ ] publish release notes with link to github release on forum in new locked topic
|
||||
- [ ] close all issues with label `ready (coming with next release)`
|
||||
- [ ] release new documentation by merging `develop` on `master` in documentation repository
|
||||
- [ ] publish new version on [npm](https://www.npmjs.com/package/magicmirror)
|
||||
|
|
20
README.md
20
README.md
|
@ -1,14 +1,14 @@
|
|||

|
||||
# 
|
||||
|
||||
<p style="text-align: center">
|
||||
<a href="https://choosealicense.com/licenses/mit">
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License">
|
||||
</a>
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/magicmirrororg/magicmirror/automated-tests.yaml" alt="GitHub Actions">
|
||||
<img src="https://img.shields.io/github/check-runs/magicmirrororg/magicmirror/master" alt="Build Status">
|
||||
<a href="https://github.com/MagicMirrorOrg/MagicMirror">
|
||||
<img src="https://img.shields.io/github/stars/magicmirrororg/magicmirror?style=social">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License">
|
||||
</a>
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/magicmirrororg/magicmirror/automated-tests.yaml" alt="GitHub Actions">
|
||||
<img src="https://img.shields.io/github/check-runs/magicmirrororg/magicmirror/master" alt="Build Status">
|
||||
<a href="https://github.com/MagicMirrorOrg/MagicMirror">
|
||||
<img src="https://img.shields.io/github/stars/magicmirrororg/magicmirror?style=social" alt="GitHub Stars">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
**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/MagicMirrorOrg/MagicMirror/graphs/contributors).
|
||||
|
@ -24,7 +24,7 @@ For the full documentation including **[installation instructions](https://docs.
|
|||
- Website: [https://magicmirror.builders](https://magicmirror.builders)
|
||||
- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders)
|
||||
- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders)
|
||||
- Technical discussions: https://forum.magicmirror.builders/category/11/core-system
|
||||
- Technical discussions: <https://forum.magicmirror.builders/category/11/core-system>
|
||||
- Discord: [https://discord.gg/J5BAtvx](https://discord.gg/J5BAtvx)
|
||||
- Blog: [https://michaelteeuw.nl/tagged/magicmirror](https://michaelteeuw.nl/tagged/magicmirror)
|
||||
- Donations: [https://magicmirror.builders/#donate](https://magicmirror.builders/#donate)
|
||||
|
@ -49,5 +49,5 @@ If we receive enough donations we might even be able to free up some working hou
|
|||
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
|
||||
|
||||
<p style="text-align: center">
|
||||
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
|
||||
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
|
||||
</p>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
});
|
||||
|
||||
// determine if "--use-tls"-flag was provided
|
||||
config["tls"] = process.argv.indexOf("--use-tls") > 0;
|
||||
config.tls = process.argv.indexOf("--use-tls") > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,11 @@ let config = {
|
|||
httpsCertificate: "", // HTTPS Certificate path, only require when useHttps is true
|
||||
|
||||
language: "en",
|
||||
locale: "en-US",
|
||||
locale: "en-US", // this variable is provided as a consistent location
|
||||
// it is currently only used by 3rd party modules. no MagicMirror code uses this value
|
||||
// as we have no usage, we have no constraints on what this field holds
|
||||
// see https://en.wikipedia.org/wiki/Locale_(computer_software) for the possibilities
|
||||
|
||||
logLevel: ["INFO", "LOG", "WARN", "ERROR"], // Add "DEBUG" for even more logging
|
||||
timeFormat: 24,
|
||||
units: "metric",
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
"currentweather",
|
||||
"CUSTOMCSS",
|
||||
"customregions",
|
||||
"cxmj",
|
||||
"Cymraeg",
|
||||
"dariom",
|
||||
"darksky",
|
||||
|
@ -48,6 +49,8 @@
|
|||
"DAYBEFOREYESTERDAY",
|
||||
"defaultmodules",
|
||||
"dgoth",
|
||||
"dkallen",
|
||||
"drivelist",
|
||||
"DTEND",
|
||||
"Duffman",
|
||||
"earlman",
|
||||
|
@ -79,6 +82,7 @@
|
|||
"fullday",
|
||||
"fullscreen",
|
||||
"Gevoelstemperatuur",
|
||||
"GHSA",
|
||||
"ghsas",
|
||||
"grenagit",
|
||||
"Hirschberger",
|
||||
|
@ -92,6 +96,7 @@
|
|||
"jakemulley",
|
||||
"jakobsarwary",
|
||||
"jalibu",
|
||||
"jargordon",
|
||||
"jetson",
|
||||
"jkriegshauser",
|
||||
"jsdocs",
|
||||
|
@ -112,8 +117,10 @@
|
|||
"krekos",
|
||||
"Kristjan",
|
||||
"krukle",
|
||||
"Landis",
|
||||
"larryare",
|
||||
"letsencrypt",
|
||||
"libgpiod",
|
||||
"Lightspeed",
|
||||
"locationforecast",
|
||||
"lockstring",
|
||||
|
@ -145,6 +152,7 @@
|
|||
"newsitems",
|
||||
"nfogal",
|
||||
"njwilliams",
|
||||
"nonrepeating",
|
||||
"Norsk",
|
||||
"nunjuck",
|
||||
"odroid",
|
||||
|
@ -162,6 +170,7 @@
|
|||
"psieg",
|
||||
"radokristof",
|
||||
"rajniszp",
|
||||
"rebuilded",
|
||||
"Reis",
|
||||
"rejas",
|
||||
"Resig",
|
||||
|
@ -172,6 +181,7 @@
|
|||
"sdetweil",
|
||||
"sendheaders",
|
||||
"serveronly",
|
||||
"skpanagiotis",
|
||||
"SMHI",
|
||||
"Snille",
|
||||
"socketclient",
|
||||
|
@ -189,6 +199,7 @@
|
|||
"tada",
|
||||
"taglist",
|
||||
"Teeuw",
|
||||
"TESTMODE",
|
||||
"thomasrockhu",
|
||||
"tomzt",
|
||||
"ukmetoffice",
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import eslintPluginImport from "eslint-plugin-import";
|
||||
import eslintPluginJest from "eslint-plugin-jest";
|
||||
import eslintPluginJs from "@eslint/js";
|
||||
import eslintPluginPackageJson from "eslint-plugin-package-json/configs/recommended";
|
||||
import eslintPluginStylistic from "@stylistic/eslint-plugin";
|
||||
import globals from "globals";
|
||||
|
||||
const config = [
|
||||
eslintPluginJs.configs.recommended,
|
||||
eslintPluginImport.flatConfigs.recommended,
|
||||
eslintPluginPackageJson,
|
||||
{
|
||||
files: ["**/*.js"],
|
||||
languageOptions: {
|
||||
|
@ -51,8 +55,12 @@ const config = [
|
|||
"@stylistic/semi": ["error", "always"],
|
||||
"@stylistic/space-before-function-paren": ["error", "always"],
|
||||
"@stylistic/spaced-comment": "off",
|
||||
"dot-notation": "error",
|
||||
eqeqeq: "error",
|
||||
"id-length": "off",
|
||||
"import/extensions": "error",
|
||||
"import/newline-after-import": "error",
|
||||
"import/order": "error",
|
||||
"init-declarations": "off",
|
||||
"jest/consistent-test-it": "warn",
|
||||
"jest/no-done-callback": "warn",
|
||||
|
@ -60,7 +68,7 @@ const config = [
|
|||
"jest/prefer-mock-promise-shorthand": "warn",
|
||||
"jest/prefer-to-be": "warn",
|
||||
"jest/prefer-to-have-length": "warn",
|
||||
"max-lines-per-function": ["warn", 350],
|
||||
"max-lines-per-function": ["warn", 400],
|
||||
"max-statements": "off",
|
||||
"no-global-assign": "off",
|
||||
"no-inline-comments": "off",
|
||||
|
@ -71,6 +79,7 @@ const config = [
|
|||
"no-ternary": "off",
|
||||
"no-throw-literal": "error",
|
||||
"no-undefined": "off",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unused-vars": "off",
|
||||
"no-useless-return": "error",
|
||||
"no-warning-comments": "off",
|
||||
|
@ -101,10 +110,12 @@ const config = [
|
|||
"@stylistic/quote-props": ["error", "as-needed"],
|
||||
"func-style": "off",
|
||||
"import/namespace": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"max-lines-per-function": ["error", 100],
|
||||
"no-magic-numbers": "off",
|
||||
"one-var": "off",
|
||||
"prefer-destructuring": "off"
|
||||
"prefer-destructuring": "off",
|
||||
"sort-keys": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -114,7 +125,7 @@ const config = [
|
|||
}
|
||||
},
|
||||
{
|
||||
ignores: ["config/**", "modules/**", "!modules/default/**", "js/positions.js"]
|
||||
ignores: ["config/**", "modules/**/*", "!modules/default/**", "js/positions.js"]
|
||||
}
|
||||
];
|
||||
|
||||
|
|
18
fonts/package-lock.json
generated
18
fonts/package-lock.json
generated
|
@ -9,21 +9,19 @@
|
|||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.1.0",
|
||||
"@fontsource/roboto-condensed": "^5.1.0"
|
||||
"@fontsource/roboto": "^5.1.1",
|
||||
"@fontsource/roboto-condensed": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/roboto": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.0.tgz",
|
||||
"integrity": "sha512-cFRRC1s6RqPygeZ8Uw/acwVHqih8Czjt6Q0MwoUoDe9U3m4dH1HmNDRBZyqlMSFwgNAUKgFImncKdmDHyKpwdg==",
|
||||
"license": "Apache-2.0"
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.1.tgz",
|
||||
"integrity": "sha512-XwVVXtERDQIM7HPUIbyDe0FP4SRovpjF7zMI8M7pbqFp3ahLJsJTd18h+E6pkar6UbV3btbwkKjYARr5M+SQow=="
|
||||
},
|
||||
"node_modules/@fontsource/roboto-condensed": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.1.0.tgz",
|
||||
"integrity": "sha512-cTS62X9bgR6H+3qRtaDwt0I+3ocitMPalyr2OrzJtilIcuEo4my8UA4VVhOgr0OI2Sk9JNrNYcSxkv0k4XuKtQ==",
|
||||
"license": "OFL-1.1"
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-5.1.1.tgz",
|
||||
"integrity": "sha512-0SYkGnWPsvyCI3TAqBYAglfVUqVu/fsdgsyl5u396oK8ZgyamWHdQMFHDqCWrb4H4hNiewJT1l2ShDCA/cu6Ug=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.1.0",
|
||||
"@fontsource/roboto-condensed": "^5.1.0"
|
||||
"@fontsource/roboto": "^5.1.1",
|
||||
"@fontsource/roboto-condensed": "^5.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
window.mmVersion = "#VERSION#";
|
||||
window.mmTestMode = "#TESTMODE#";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -155,3 +155,4 @@ function removeAnimateCSS (element, animation) {
|
|||
node.classList.remove("animate__animated", animationName);
|
||||
node.style.removeProperty("--animate-duration");
|
||||
}
|
||||
if (typeof window === "undefined") module.exports = { AnimateCSSIn, AnimateCSSOut };
|
||||
|
|
22
js/app.js
22
js/app.js
|
@ -11,8 +11,14 @@ const Utils = require(`${__dirname}/utils`);
|
|||
const defaultModules = require(`${__dirname}/../modules/default/defaultmodules`);
|
||||
const { getEnvVarsAsObj } = require(`${__dirname}/server_functions`);
|
||||
|
||||
// used to control fetch timeout for node_helpers
|
||||
const { setGlobalDispatcher, Agent } = require("undici");
|
||||
// common timeout value, provide environment override in case
|
||||
const fetch_timeout = process.env.mmFetchTimeout !== undefined ? process.env.mmFetchTimeout : 30000;
|
||||
|
||||
// Get version number.
|
||||
global.version = require(`${__dirname}/../package.json`).version;
|
||||
global.mmTestMode = process.env.mmTestMode === "true";
|
||||
Log.log(`Starting MagicMirror: v${global.version}`);
|
||||
|
||||
// Log system information.
|
||||
|
@ -163,10 +169,10 @@ function App () {
|
|||
const elements = module.split("/");
|
||||
const moduleName = elements[elements.length - 1];
|
||||
const env = getEnvVarsAsObj();
|
||||
let moduleFolder = `${__dirname}/../${env.modulesDir}/${module}`;
|
||||
let moduleFolder = path.resolve(`${__dirname}/../${env.modulesDir}`, module);
|
||||
|
||||
if (defaultModules.includes(moduleName)) {
|
||||
const defaultModuleFolder = `${__dirname}/../modules/default/${module}`;
|
||||
const defaultModuleFolder = path.resolve(`${__dirname}/../modules/default/`, module);
|
||||
if (process.env.JEST_WORKER_ID === undefined) {
|
||||
moduleFolder = defaultModuleFolder;
|
||||
} else {
|
||||
|
@ -177,7 +183,7 @@ function App () {
|
|||
}
|
||||
}
|
||||
|
||||
const moduleFile = `${moduleFolder}/${module}.js`;
|
||||
const moduleFile = `${moduleFolder}/${moduleName}.js`;
|
||||
|
||||
try {
|
||||
fs.accessSync(moduleFile, fs.R_OK);
|
||||
|
@ -197,7 +203,13 @@ function App () {
|
|||
|
||||
// if the helper was found
|
||||
if (loadHelper) {
|
||||
const Module = require(helperPath);
|
||||
let Module;
|
||||
try {
|
||||
Module = require(helperPath);
|
||||
} catch (e) {
|
||||
Log.error(`Error when loading ${moduleName}:`, e.message);
|
||||
return;
|
||||
}
|
||||
let m = new Module();
|
||||
|
||||
if (m.requiresVersion) {
|
||||
|
@ -288,6 +300,8 @@ function App () {
|
|||
}
|
||||
}
|
||||
|
||||
setGlobalDispatcher(new Agent({ connect: { timeout: fetch_timeout } }));
|
||||
|
||||
await loadModules(modules);
|
||||
|
||||
httpServer = new Server(config);
|
||||
|
|
|
@ -70,7 +70,7 @@ const defaults = {
|
|||
position: "bottom_bar",
|
||||
classes: "xsmall dimmed",
|
||||
config: {
|
||||
text: "www.michaelteeuw.nl"
|
||||
text: "https://magicmirror.builders/"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -76,6 +76,23 @@ function createWindow () {
|
|||
|
||||
const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
|
||||
|
||||
if (process.env.JEST_WORKER_ID !== undefined && process.env.MOCK_DATE !== undefined) {
|
||||
// if we are running with jest and we want to mock the current date
|
||||
const fakeNow = new Date(process.env.MOCK_DATE).valueOf();
|
||||
Date = class extends Date {
|
||||
constructor (...args) {
|
||||
if (args.length === 0) {
|
||||
super(fakeNow);
|
||||
} else {
|
||||
super(...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
const __DateNowOffset = fakeNow - Date.now();
|
||||
const __DateNow = Date.now;
|
||||
Date.now = () => __DateNow() + __DateNowOffset;
|
||||
}
|
||||
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow(electronOptions);
|
||||
|
||||
|
@ -85,7 +102,7 @@ function createWindow () {
|
|||
*/
|
||||
|
||||
let prefix;
|
||||
if ((config["tls"] !== null && config["tls"]) || config.useHttps) {
|
||||
if ((config.tls !== null && config.tls) || config.useHttps) {
|
||||
prefix = "https://";
|
||||
} else {
|
||||
prefix = "http://";
|
||||
|
@ -134,11 +151,11 @@ function createWindow () {
|
|||
//remove response headers that prevent sites of being embedded into iframes if configured
|
||||
mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
let curHeaders = details.responseHeaders;
|
||||
if (config["ignoreXOriginHeader"] || false) {
|
||||
if (config.ignoreXOriginHeader || false) {
|
||||
curHeaders = Object.fromEntries(Object.entries(curHeaders).filter((header) => !(/x-frame-options/i).test(header[0])));
|
||||
}
|
||||
|
||||
if (config["ignoreContentSecurityPolicy"] || false) {
|
||||
if (config.ignoreContentSecurityPolicy || false) {
|
||||
curHeaders = Object.fromEntries(Object.entries(curHeaders).filter((header) => !(/content-security-policy/i).test(header[0])));
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ const Loader = (function () {
|
|||
* @returns {object} with key: values as assembled in js/server_functions.js
|
||||
*/
|
||||
const getEnvVars = async function () {
|
||||
const res = await fetch(`${location.protocol}//${location.host}/env`);
|
||||
const res = await fetch(`${location.protocol}//${location.host}${config.basePath}env`);
|
||||
return JSON.parse(await res.text());
|
||||
};
|
||||
|
||||
|
|
|
@ -608,7 +608,7 @@ const MM = (function () {
|
|||
// if server startup time has changed (which means server was restarted)
|
||||
// the client reloads the mm page
|
||||
try {
|
||||
const res = await fetch(`${location.protocol}//${location.host}/startup`);
|
||||
const res = await fetch(`${location.protocol}//${location.host}${config.basePath}startup`);
|
||||
const curr = await res.text();
|
||||
if (startUp === "") startUp = curr;
|
||||
if (startUp !== curr) {
|
||||
|
|
|
@ -72,11 +72,7 @@ function Server (config) {
|
|||
app.use(helmet(config.httpHeaders));
|
||||
app.use("/js", express.static(__dirname));
|
||||
|
||||
let directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations"];
|
||||
if (process.env.JEST_WORKER_ID !== undefined) {
|
||||
// add tests directories only when running tests
|
||||
directories.push("/tests/configs", "/tests/mocks");
|
||||
}
|
||||
let directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs", "/tests/mocks"];
|
||||
for (const directory of directories) {
|
||||
app.use(directory, express.static(path.resolve(global.root_path + directory)));
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ function geExpectedReceivedHeaders (url) {
|
|||
function getHtml (req, res) {
|
||||
let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
|
||||
html = html.replace("#VERSION#", global.version);
|
||||
html = html.replace("#TESTMODE#", global.mmTestMode);
|
||||
|
||||
let configFile = "config/config.js";
|
||||
if (typeof global.configuration_file !== "undefined") {
|
||||
|
|
15
js/utils.js
15
js/utils.js
|
@ -24,9 +24,9 @@ module.exports = {
|
|||
versions: "kernel, node, npm, pm2"
|
||||
});
|
||||
let systemDataString = "System information:";
|
||||
systemDataString += `\n### SYSTEM: manufacturer: ${staticData["system"]["manufacturer"]}; model: ${staticData["system"]["model"]}; virtual: ${staticData["system"]["virtual"]}`;
|
||||
systemDataString += `\n### OS: platform: ${staticData["osInfo"]["platform"]}; distro: ${staticData["osInfo"]["distro"]}; release: ${staticData["osInfo"]["release"]}; arch: ${staticData["osInfo"]["arch"]}; kernel: ${staticData["versions"]["kernel"]}`;
|
||||
systemDataString += `\n### VERSIONS: electron: ${process.versions.electron}; used node: ${staticData["versions"]["node"]}; installed node: ${installedNodeVersion}; npm: ${staticData["versions"]["npm"]}; pm2: ${staticData["versions"]["pm2"]}`;
|
||||
systemDataString += `\n### SYSTEM: manufacturer: ${staticData.system.manufacturer}; model: ${staticData.system.model}; virtual: ${staticData.system.virtual}`;
|
||||
systemDataString += `\n### OS: platform: ${staticData.osInfo.platform}; distro: ${staticData.osInfo.distro}; release: ${staticData.osInfo.release}; arch: ${staticData.osInfo.arch}; kernel: ${staticData.versions.kernel}`;
|
||||
systemDataString += `\n### VERSIONS: electron: ${process.versions.electron}; used node: ${staticData.versions.node}; installed node: ${installedNodeVersion}; npm: ${staticData.versions.npm}; pm2: ${staticData.versions.pm2}`;
|
||||
systemDataString += `\n### OTHER: timeZone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}; ELECTRON_ENABLE_GPU: ${process.env.ELECTRON_ENABLE_GPU}`;
|
||||
Log.info(systemDataString);
|
||||
|
||||
|
@ -52,7 +52,7 @@ module.exports = {
|
|||
// if not already discovered
|
||||
if (modulePositions.length === 0) {
|
||||
// get the lines of the index.html
|
||||
const lines = fs.readFileSync(indexFileName).toString().split(os.EOL);
|
||||
const lines = fs.readFileSync(indexFileName).toString().split("\n");
|
||||
// loop thru the lines
|
||||
lines.forEach((line) => {
|
||||
// run the regex on each line
|
||||
|
@ -65,7 +65,12 @@ module.exports = {
|
|||
modulePositions.push(positionName);
|
||||
}
|
||||
});
|
||||
fs.writeFileSync(discoveredPositionsJSFilename, `const modulePositions=${JSON.stringify(modulePositions)}`);
|
||||
try {
|
||||
fs.writeFileSync(discoveredPositionsJSFilename, `const modulePositions=${JSON.stringify(modulePositions)}`);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("unable to write js/positions.js with the discovered module positions\nmake the MagicMirror/js folder writeable by the user starting MagicMirror");
|
||||
}
|
||||
}
|
||||
// return the list to the caller
|
||||
return modulePositions;
|
||||
|
|
|
@ -168,12 +168,17 @@ Module.register("calendar", {
|
|||
|
||||
this.selfUpdate();
|
||||
},
|
||||
notificationReceived (notification, payload, sender) {
|
||||
|
||||
if (notification === "FETCH_CALENDAR") {
|
||||
if (this.hasCalendarURL(payload.url)) {
|
||||
this.sendSocketNotification(notification, { url: payload.url, id: this.identifier });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Override socket notification handler.
|
||||
socketNotificationReceived (notification, payload) {
|
||||
if (notification === "FETCH_CALENDAR") {
|
||||
this.sendSocketNotification(notification, { url: payload.url, id: this.identifier });
|
||||
}
|
||||
|
||||
if (this.identifier !== payload.id) {
|
||||
return;
|
||||
|
@ -417,18 +422,26 @@ Module.register("calendar", {
|
|||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
|
||||
// Add end time if showEnd
|
||||
if (this.config.showEnd) {
|
||||
if (this.config.showEndsOnlyWithDuration && event.startDate === event.endDate) {
|
||||
// no duration here, don't display end
|
||||
} else {
|
||||
// and has a duation
|
||||
if (event.startDate !== event.endDate) {
|
||||
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) {
|
||||
//subtract one second so that fullDayEvents end at 23:59:59, and not at 0:00:00 one the next day
|
||||
event.endDate -= ONE_SECOND;
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
|
||||
// only show end if requested and allowed and the dates are different
|
||||
if (this.config.showEnd && !this.config.showEndsOnlyWithDuration && moment(event.startDate, "x").format("YYYYMMDD") !== moment(event.endDate, "x").format("YYYYMMDD")) {
|
||||
timeWrapper.innerHTML += "-";
|
||||
timeWrapper.innerHTML += CalendarUtils.capFirst(moment(event.endDate, "x").format(this.config.fullDayEventDateFormat));
|
||||
} else
|
||||
if ((moment(event.startDate, "x").format("YYYYMMDD") !== moment(event.endDate, "x").format("YYYYMMDD")) && (moment(event.startDate, "x") < moment(now, "x"))) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(now, "x").format(this.config.fullDayEventDateFormat));
|
||||
}
|
||||
} else if (this.config.getRelative > 0 && event.startDate < now) {
|
||||
// Ongoing and getRelative is set
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(
|
||||
|
@ -460,16 +473,18 @@ Module.register("calendar", {
|
|||
if (event.startDate >= now || (event.fullDayEvent && this.eventEndingWithinNextFullTimeUnit(event, ONE_DAY))) {
|
||||
// Use relative time
|
||||
if (!this.config.hideTime && !event.fullDayEvent) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
|
||||
Log.debug("event not hidden and not fullday");
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }))}`;
|
||||
} else {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(
|
||||
Log.debug("event full day or hidden");
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(
|
||||
moment(event.startDate, "x").calendar(null, {
|
||||
sameDay: this.config.showTimeToday ? "LT" : `[${this.translate("TODAY")}]`,
|
||||
nextDay: `[${this.translate("TOMORROW")}]`,
|
||||
nextWeek: "dddd",
|
||||
sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat
|
||||
})
|
||||
);
|
||||
)}`;
|
||||
}
|
||||
if (event.fullDayEvent) {
|
||||
// Full days events within the next two days
|
||||
|
@ -488,9 +503,11 @@ Module.register("calendar", {
|
|||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("DAYAFTERTOMORROW"));
|
||||
}
|
||||
}
|
||||
Log.info("event fullday");
|
||||
} else if (event.startDate - now < this.config.getRelative * ONE_HOUR) {
|
||||
Log.info("not full day but within getrelative size");
|
||||
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").fromNow());
|
||||
timeWrapper.innerHTML = `${CalendarUtils.capFirst(moment(event.startDate, "x").fromNow())}`;
|
||||
}
|
||||
} else {
|
||||
// Ongoing event
|
||||
|
@ -603,6 +620,7 @@ Module.register("calendar", {
|
|||
const calendar = this.calendarData[calendarUrl];
|
||||
let remainingEntries = this.maximumEntriesForUrl(calendarUrl);
|
||||
let maxPastDaysCompare = now - this.maximumPastDaysForUrl(calendarUrl) * ONE_DAY;
|
||||
let by_url_calevents = [];
|
||||
for (const e in calendar) {
|
||||
const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
|
||||
|
||||
|
@ -620,9 +638,6 @@ Module.register("calendar", {
|
|||
if (this.config.hideDuplicates && this.listContainsEvent(events, event)) {
|
||||
continue;
|
||||
}
|
||||
if (--remainingEntries < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event.url = calendarUrl;
|
||||
|
@ -667,15 +682,21 @@ Module.register("calendar", {
|
|||
|
||||
for (let splitEvent of splitEvents) {
|
||||
if (splitEvent.endDate > now && splitEvent.endDate <= future) {
|
||||
events.push(splitEvent);
|
||||
by_url_calevents.push(splitEvent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
events.push(event);
|
||||
by_url_calevents.push(event);
|
||||
}
|
||||
}
|
||||
by_url_calevents.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
Log.debug(`pushing ${by_url_calevents.length} events to total with room for ${remainingEntries}`);
|
||||
events = events.concat(by_url_calevents.slice(0, remainingEntries));
|
||||
Log.debug(`events for calendar=${events.length}`);
|
||||
}
|
||||
|
||||
Log.info(`sorting events count=${events.length}`);
|
||||
events.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
|
@ -715,7 +736,7 @@ Module.register("calendar", {
|
|||
}
|
||||
events = newEvents;
|
||||
}
|
||||
|
||||
Log.info(`slicing events total maxcount=${this.config.maximumEntries}`);
|
||||
return events.slice(0, this.config.maximumEntries);
|
||||
},
|
||||
|
||||
|
@ -886,9 +907,9 @@ Module.register("calendar", {
|
|||
let p = this.getCalendarProperty(url, property, defaultValue);
|
||||
if (property === "symbol" || property === "recurringSymbol" || property === "fullDaySymbol") {
|
||||
const className = this.getCalendarProperty(url, "symbolClassName", this.config.defaultSymbolClassName);
|
||||
p = className + p;
|
||||
if (p instanceof Array) p.push(className);
|
||||
else p = className + p;
|
||||
}
|
||||
|
||||
if (!(p instanceof Array)) p = [p];
|
||||
return p;
|
||||
},
|
||||
|
|
|
@ -56,7 +56,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
|
|||
|
||||
try {
|
||||
data = ical.parseICS(responseData);
|
||||
Log.debug(`parsed data=${JSON.stringify(data)}`);
|
||||
Log.debug(`parsed data=${JSON.stringify(data, null, 2)}`);
|
||||
events = CalendarFetcherUtils.filterEvents(data, {
|
||||
excludedEvents,
|
||||
includePastEvents,
|
||||
|
|
|
@ -160,7 +160,7 @@ const CalendarFetcherUtils = {
|
|||
}
|
||||
|
||||
if (event.type === "VEVENT") {
|
||||
Log.debug(`Event:\n${JSON.stringify(event)}`);
|
||||
Log.debug(`Event:\n${JSON.stringify(event, null, 2)}`);
|
||||
let startMoment = eventDate(event, "start");
|
||||
let endMoment;
|
||||
|
||||
|
@ -246,6 +246,8 @@ const CalendarFetcherUtils = {
|
|||
const location = event.location || false;
|
||||
const geo = event.geo || false;
|
||||
const description = event.description || false;
|
||||
let d1;
|
||||
let d2;
|
||||
|
||||
if (event.rrule && typeof event.rrule !== "undefined" && !isFacebookBirthday) {
|
||||
const rule = event.rrule;
|
||||
|
@ -261,9 +263,10 @@ const CalendarFetcherUtils = {
|
|||
|
||||
// For recurring events, get the set of start dates that fall within the range
|
||||
// of dates we're looking for.
|
||||
// kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time
|
||||
|
||||
let pastLocal;
|
||||
let futureLocal;
|
||||
|
||||
if (CalendarFetcherUtils.isFullDayEvent(event)) {
|
||||
Log.debug("fullday");
|
||||
// if full day event, only use the date part of the ranges
|
||||
|
@ -283,52 +286,52 @@ const CalendarFetcherUtils = {
|
|||
}
|
||||
futureLocal = futureMoment.toDate(); // future
|
||||
}
|
||||
Log.debug(`Search for recurring events between: ${pastLocal} and ${futureLocal}`);
|
||||
const hasByWeekdayRule = rule.options.byweekday !== undefined && rule.options.byweekday !== null;
|
||||
const oneDayInMs = 24 * 60 * 60 * 1000;
|
||||
d1 = new Date(new Date(pastLocal.valueOf() - oneDayInMs).getTime());
|
||||
d2 = new Date(new Date(futureLocal.valueOf() + oneDayInMs).getTime());
|
||||
Log.debug(`Search for recurring events between: ${d1} and ${d2}`);
|
||||
|
||||
event.start = rule.options.dtstart;
|
||||
|
||||
Log.debug("fix rrule start=", rule.options.dtstart);
|
||||
Log.debug("event before rrule.between=", JSON.stringify(event, null, 2), "exdates=", event.exdate);
|
||||
// fixup the exdate and recurrence date to local time too for post between() handling
|
||||
CalendarFetcherUtils.fixEventtoLocal(event);
|
||||
|
||||
Log.debug(`RRule: ${rule.toString()}`);
|
||||
rule.options.tzid = null; // RRule gets *very* confused with timezones
|
||||
let dates = rule.between(new Date(pastLocal.valueOf() - oneDayInMs), new Date(futureLocal.valueOf() + oneDayInMs), true, () => { return true; });
|
||||
Log.debug(`Title: ${event.summary}, with dates: ${JSON.stringify(dates)}`);
|
||||
|
||||
let dates = rule.between(d1, d2, true, () => { return true; });
|
||||
|
||||
Log.debug(`Title: ${event.summary}, with dates: \n\n${JSON.stringify(dates)}\n`);
|
||||
|
||||
// shouldn't need this anymore, as RRULE not passed junk
|
||||
dates = dates.filter((d) => {
|
||||
if (JSON.stringify(d) === "null") return false;
|
||||
else return true;
|
||||
});
|
||||
|
||||
// RRule can generate dates with an incorrect recurrence date. Process the array here and apply date correction.
|
||||
if (hasByWeekdayRule) {
|
||||
Log.debug("Rule has byweekday, checking for correction");
|
||||
dates.forEach((date, index, arr) => {
|
||||
// NOTE: getTimezoneOffset() is negative of the expected value. For America/Los_Angeles under DST (GMT-7),
|
||||
// this value is +420. For Australia/Sydney under DST (GMT+11), this value is -660.
|
||||
const tzOffset = date.getTimezoneOffset() / 60;
|
||||
const hour = date.getHours();
|
||||
if ((tzOffset < 0) && (hour < -tzOffset)) { // east of GMT
|
||||
Log.debug(`East of GMT (tzOffset: ${tzOffset}) and hour=${hour} < ${-tzOffset}, Subtracting 1 day from ${date}`);
|
||||
arr[index] = new Date(date.valueOf() - oneDayInMs);
|
||||
} else if ((tzOffset > 0) && (hour >= (24 - tzOffset))) { // west of GMT
|
||||
Log.debug(`West of GMT (tzOffset: ${tzOffset}) and hour=${hour} >= 24-${tzOffset}, Adding 1 day to ${date}`);
|
||||
arr[index] = new Date(date.valueOf() + oneDayInMs);
|
||||
}
|
||||
});
|
||||
// Adjusting the dates could push it beyond the 'until' date, so filter those out here.
|
||||
if (rule.options.until !== null) {
|
||||
dates = dates.filter((date) => {
|
||||
return date.valueOf() <= rule.options.until.valueOf();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The dates array from rrule can be confused by DST. If the event was created during DST and we
|
||||
// are querying a date range during non-DST, rrule can have the incorrect time for the date range.
|
||||
// Reprocess the array here computing and applying the time offset.
|
||||
dates.forEach((date, index, arr) => {
|
||||
let adjustHours = CalendarFetcherUtils.calculateTimezoneAdjustment(event, date);
|
||||
if (adjustHours !== 0) {
|
||||
Log.debug(`Applying timezone adjustment hours=${adjustHours} to ${date}`);
|
||||
arr[index] = new Date(date.valueOf() + (adjustHours * 60 * 60 * 1000));
|
||||
}
|
||||
// go thru all the rrule.between() dates and put back the tz offset removed so rrule.between would work
|
||||
let datesLocal = [];
|
||||
let offset = d1.getTimezoneOffset();
|
||||
Log.debug("offset =", offset);
|
||||
dates.forEach((d) => {
|
||||
let dtext = d.toISOString().slice(0, -5);
|
||||
Log.debug(" date text form without tz=", dtext);
|
||||
let dLocal = new Date(d.valueOf() + (offset * 60000));
|
||||
let offset2 = dLocal.getTimezoneOffset();
|
||||
Log.debug("date after offset applied=", dLocal);
|
||||
if (offset !== offset2) {
|
||||
// woops, dst/std switch
|
||||
let delta = offset - offset2;
|
||||
Log.debug("offset delta=", delta);
|
||||
dLocal = new Date(d.valueOf() + ((offset - delta) * 60000));
|
||||
Log.debug("corrected normalized date=", dLocal);
|
||||
} else Log.debug(" neutralized date=", dLocal);
|
||||
datesLocal.push(dLocal);
|
||||
});
|
||||
dates = datesLocal;
|
||||
|
||||
|
||||
// The "dates" array contains the set of dates within our desired date range range that are valid
|
||||
// for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
|
||||
|
@ -337,29 +340,22 @@ const CalendarFetcherUtils = {
|
|||
// because the logic below will filter out any recurrences that don't actually belong within
|
||||
// our display range.
|
||||
// Would be great if there was a better way to handle this.
|
||||
Log.debug(`event.recurrences: ${event.recurrences}`);
|
||||
//
|
||||
// i don't think we will ever see this anymore (oct 2024) due to code fixes for rrule.between()
|
||||
//
|
||||
Log.debug("event.recurrences:", event.recurrences);
|
||||
if (event.recurrences !== undefined) {
|
||||
for (let dateKey in event.recurrences) {
|
||||
// Only add dates that weren't already in the range we added from the rrule so that
|
||||
// we don't double-add those events.
|
||||
let d = new Date(dateKey);
|
||||
if (!moment(d).isBetween(pastMoment, futureMoment)) {
|
||||
if (!moment(d).isBetween(d1, d2)) {
|
||||
Log.debug("adding recurring event not found in between list =", d, " should not happen now using local dates oct 17,24");
|
||||
dates.push(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, sometimes rrule doesn't include the event.start even if it is in the requested range. Ensure
|
||||
// inclusion here. Unfortunately dates.includes() doesn't find it so we have to do forEach().
|
||||
{
|
||||
let found = false;
|
||||
dates.forEach((d) => { if (d.valueOf() === event.start.valueOf()) found = true; });
|
||||
if (!found) {
|
||||
Log.debug(`event.start=${event.start} was not included in results from rrule; adding`);
|
||||
dates.splice(0, 0, event.start);
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through the set of date entries to see which recurrences should be added to our event list.
|
||||
for (let d in dates) {
|
||||
let date = dates[d];
|
||||
|
@ -367,30 +363,42 @@ const CalendarFetcherUtils = {
|
|||
let curDurationMs = durationMs;
|
||||
let showRecurrence = true;
|
||||
|
||||
startMoment = moment(date);
|
||||
let startMoment = moment(date);
|
||||
|
||||
// Remove the time information of each date by using its substring, using the following method:
|
||||
// .toISOString().substring(0,10).
|
||||
// since the date is given as ISOString with YYYY-MM-DDTHH:MM:SS.SSSZ
|
||||
// (see https://momentjs.com/docs/#/displaying/as-iso-string/).
|
||||
// This must be done after `date` is adjusted
|
||||
const dateKey = date.toISOString().substring(0, 10);
|
||||
let dateKey = CalendarFetcherUtils.getDateKeyFromDate(date);
|
||||
|
||||
Log.debug("event date dateKey=", dateKey);
|
||||
// For each date that we're checking, it's possible that there is a recurrence override for that one day.
|
||||
if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) {
|
||||
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
|
||||
curEvent = curEvent.recurrences[dateKey];
|
||||
startMoment = moment(curEvent.start);
|
||||
curDurationMs = curEvent.end.valueOf() - startMoment.valueOf();
|
||||
if (curEvent.recurrences !== undefined) {
|
||||
Log.debug("have recurrences=", curEvent.recurrences);
|
||||
if (curEvent.recurrences[dateKey] !== undefined) {
|
||||
Log.debug("have a recurrence match for dateKey=", dateKey);
|
||||
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
|
||||
curEvent = curEvent.recurrences[dateKey];
|
||||
curEvent.start = new Date(new Date(curEvent.start.valueOf()).getTime());
|
||||
curEvent.end = new Date(new Date(curEvent.end.valueOf()).getTime());
|
||||
startMoment = CalendarFetcherUtils.getAdjustedStartMoment(curEvent.start, event);
|
||||
endMoment = CalendarFetcherUtils.getAdjustedStartMoment(curEvent.end, event);
|
||||
date = curEvent.start;
|
||||
curDurationMs = new Date(endMoment).valueOf() - startMoment.valueOf();
|
||||
} else {
|
||||
Log.debug("recurrence key ", dateKey, " doesn't match");
|
||||
}
|
||||
}
|
||||
// If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
|
||||
else if (curEvent.exdate !== undefined && curEvent.exdate[dateKey] !== undefined) {
|
||||
// This date is an exception date, which means we should skip it in the recurrence pattern.
|
||||
showRecurrence = false;
|
||||
if (curEvent.exdate !== undefined) {
|
||||
Log.debug("have datekey=", dateKey, " exdates=", curEvent.exdate);
|
||||
if (curEvent.exdate[dateKey] !== undefined) {
|
||||
// This date is an exception date, which means we should skip it in the recurrence pattern.
|
||||
showRecurrence = false;
|
||||
}
|
||||
}
|
||||
Log.debug(`duration: ${curDurationMs}`);
|
||||
|
||||
startMoment = CalendarFetcherUtils.getAdjustedStartMoment(date, event);
|
||||
|
||||
endMoment = moment(startMoment.valueOf() + curDurationMs);
|
||||
|
||||
if (startMoment.valueOf() === endMoment.valueOf()) {
|
||||
endMoment = endMoment.endOf("day");
|
||||
}
|
||||
|
@ -408,7 +416,7 @@ const CalendarFetcherUtils = {
|
|||
}
|
||||
|
||||
if (showRecurrence === true) {
|
||||
Log.debug(`saving event: ${description}`);
|
||||
Log.debug(`saving event: ${recurrenceTitle}`);
|
||||
newEvents.push({
|
||||
title: recurrenceTitle,
|
||||
startDate: startMoment.format("x"),
|
||||
|
@ -421,7 +429,10 @@ const CalendarFetcherUtils = {
|
|||
geo: geo,
|
||||
description: description
|
||||
});
|
||||
} else {
|
||||
Log.debug("not saving event ", recurrenceTitle, new Date(startMoment));
|
||||
}
|
||||
Log.debug(" ");
|
||||
}
|
||||
// End recurring event parsing.
|
||||
} else {
|
||||
|
@ -472,7 +483,9 @@ const CalendarFetcherUtils = {
|
|||
startDate: startMoment.add(adjustHours, "hours").format("x"),
|
||||
endDate: endMoment.add(adjustHours, "hours").format("x"),
|
||||
fullDayEvent: fullDayEvent,
|
||||
recurringEvent: false,
|
||||
class: event.class,
|
||||
firstYear: event.start.getFullYear(),
|
||||
location: location,
|
||||
geo: geo,
|
||||
description: description
|
||||
|
@ -488,6 +501,200 @@ const CalendarFetcherUtils = {
|
|||
return newEvents;
|
||||
},
|
||||
|
||||
/**
|
||||
* fixup thew event fields that have dates to use local time
|
||||
* BEFORE calling rrule.between
|
||||
* @param the event being processed
|
||||
* @returns nothing
|
||||
*/
|
||||
fixEventtoLocal (event) {
|
||||
// if there are excluded dates, their date is incorrect and possibly key as well.
|
||||
if (event.exdate !== undefined) {
|
||||
Object.keys(event.exdate).forEach((dateKey) => {
|
||||
// get the date
|
||||
let exdate = event.exdate[dateKey];
|
||||
Log.debug("exdate w key=", exdate);
|
||||
//exdate=CalendarFetcherUtils.convertDateToLocalTime(exdate, event.end.tz)
|
||||
exdate = new Date(new Date(exdate.valueOf() - ((120 * 60 * 1000))).getTime());
|
||||
Log.debug("new exDate item=", exdate, " with old key=", dateKey);
|
||||
let newkey = exdate.toISOString().slice(0, 10);
|
||||
if (newkey !== dateKey) {
|
||||
Log.debug("new exDate item=", exdate, ` key=${newkey}`);
|
||||
event.exdate[newkey] = exdate;
|
||||
//delete event.exdate[dateKey]
|
||||
}
|
||||
});
|
||||
Log.debug("updated exdate list=", event.exdate);
|
||||
}
|
||||
if (event.recurrences) {
|
||||
Object.keys(event.recurrences).forEach((dateKey) => {
|
||||
let exdate = event.recurrences[dateKey];
|
||||
//exdate=new Date(new Date(exdate.valueOf()-(60*60*1000)).getTime())
|
||||
Log.debug("new recurrence item=", exdate, " with old key=", dateKey);
|
||||
exdate.start = CalendarFetcherUtils.convertDateToLocalTime(exdate.start, exdate.start.tz);
|
||||
exdate.end = CalendarFetcherUtils.convertDateToLocalTime(exdate.end, exdate.end.tz);
|
||||
Log.debug("adjusted recurringEvent start=", exdate.start, " end=", exdate.end);
|
||||
});
|
||||
}
|
||||
Log.debug("modified recurrences before rrule.between", event.recurrences);
|
||||
},
|
||||
|
||||
/**
|
||||
* convert a UTC date to local time
|
||||
* BEFORE calling rrule.between
|
||||
* @param date ti conert
|
||||
* tz event is currently in
|
||||
* @returns updated date object
|
||||
*/
|
||||
convertDateToLocalTime (date, tz) {
|
||||
let delta_tz_offset = 0;
|
||||
let now_offset = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(moment.tz.guess());
|
||||
let event_offset = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(tz);
|
||||
Log.debug("date to convert=", date);
|
||||
if (Math.sign(now_offset) !== Math.sign(event_offset)) {
|
||||
delta_tz_offset = Math.abs(now_offset) + Math.abs(event_offset);
|
||||
} else {
|
||||
// signs are the same
|
||||
// if negative
|
||||
if (Math.sign(now_offset) === -1) {
|
||||
// la looking at chicago
|
||||
if (now_offset < event_offset) { // 5 -7
|
||||
delta_tz_offset = now_offset - event_offset;
|
||||
}
|
||||
else { //7 -5 , chicago looking at LA
|
||||
delta_tz_offset = event_offset - now_offset;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// berlin looking at sydney
|
||||
if (now_offset < event_offset) { // 5 -7
|
||||
delta_tz_offset = event_offset - now_offset;
|
||||
Log.debug("less delta=", delta_tz_offset);
|
||||
}
|
||||
else { // 11 - 2, sydney looking at berlin
|
||||
delta_tz_offset = -(now_offset - event_offset);
|
||||
Log.debug("more delta=", delta_tz_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
const newdate = new Date(new Date(date.valueOf() + (delta_tz_offset * 60 * 1000)).getTime());
|
||||
Log.debug("modified date =", newdate);
|
||||
return newdate;
|
||||
},
|
||||
|
||||
/**
|
||||
* get the exdate/recurrence hash key from the date object
|
||||
* BEFORE calling rrule.between
|
||||
* @param the date of the event
|
||||
* @returns string date key YYYY-MM-DD
|
||||
*/
|
||||
getDateKeyFromDate (date) {
|
||||
// get our runtime timezone offset
|
||||
const nowDiff = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(moment.tz.guess());
|
||||
let startday = date.getDate();
|
||||
let adjustment = 0;
|
||||
Log.debug(" day of month=", (`0${startday}`).slice(-2), " nowDiff=", nowDiff, ` start time=${date.toString().split(" ")[4].slice(0, 2)}`);
|
||||
Log.debug("date string= ", date.toString());
|
||||
Log.debug("date iso string ", date.toISOString());
|
||||
// if the dates are different
|
||||
if (date.toString().slice(8, 10) < date.toISOString().slice(8, 10)) {
|
||||
startday = date.toString().slice(8, 10);
|
||||
Log.debug("< ", startday);
|
||||
} else { // tostring is more
|
||||
if (date.toString().slice(8, 10) > date.toISOString().slice(8, 10)) {
|
||||
startday = date.toISOString().slice(8, 10);
|
||||
Log.debug("> ", startday);
|
||||
}
|
||||
}
|
||||
return date.toISOString().substring(0, 8) + (`0${startday}`).slice(-2);
|
||||
},
|
||||
|
||||
/**
|
||||
* get the timezone offset from the timezone string
|
||||
*
|
||||
* @param the timezone string
|
||||
* @returns the numerical offset
|
||||
*/
|
||||
getTimezoneOffsetFromTimezone (timeZone) {
|
||||
const str = new Date().toLocaleString("en", { timeZone, timeZoneName: "longOffset" });
|
||||
Log.debug("tz offset=", str);
|
||||
const [_, h, m] = str.match(/([+-]\d+):(\d+)$/) || ["", "+00", "00"];
|
||||
return h * 60 + (h > 0 ? +m : -m);
|
||||
},
|
||||
|
||||
/**
|
||||
* fixup the date start moment after rrule.between returns date array
|
||||
*
|
||||
* @param date object from rrule.between results
|
||||
* the event object it came from
|
||||
* @returns moment object
|
||||
*/
|
||||
getAdjustedStartMoment (date, event) {
|
||||
|
||||
let startMoment = moment(date);
|
||||
|
||||
Log.debug("startMoment pre=", startMoment);
|
||||
// get our runtime timezone offset
|
||||
const nowDiff = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(moment.tz.guess()); // 10/18 16:49, 300
|
||||
let eventDiff = CalendarFetcherUtils.getTimezoneOffsetFromTimezone(event.end.tz); // watch out, start tz is cleared to handle rrule 120 23:49
|
||||
|
||||
Log.debug("tz diff event=", eventDiff, " local=", nowDiff, " end event timezone=", event.end.tz);
|
||||
|
||||
// if the diffs are different (not same tz for processing as event)
|
||||
if (nowDiff !== eventDiff) {
|
||||
// if signs are different
|
||||
if (Math.sign(nowDiff) !== Math.sign(eventDiff)) {
|
||||
// its the accumulated total
|
||||
Log.debug("diff signs, accumulate");
|
||||
eventDiff = Math.abs(eventDiff) + Math.abs(nowDiff);
|
||||
// sign of diff depends on where you are looking at which event.
|
||||
// australia looking at US, add to get same time
|
||||
Log.debug("new different event diff=", eventDiff);
|
||||
if (Math.sign(nowDiff) === -1) {
|
||||
eventDiff *= -1;
|
||||
// US looking at australia event have to subtract
|
||||
Log.debug("new diff, same sign, total event diff=", eventDiff);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// signs are the same, all east of UTC or all west of UTC
|
||||
// if the signs are negative (west of UTC)
|
||||
Log.debug("signs are the same");
|
||||
if (Math.sign(eventDiff) === -1) {
|
||||
//if west, looking at more west
|
||||
if (nowDiff < eventDiff) {
|
||||
//-600 -420
|
||||
eventDiff = -(eventDiff - (nowDiff - eventDiff)); //-180
|
||||
Log.debug("now looking back east delta diff=", eventDiff);
|
||||
}
|
||||
else {
|
||||
Log.debug("now looking more west");
|
||||
eventDiff = Math.abs(eventDiff - nowDiff);
|
||||
}
|
||||
} else {
|
||||
Log.debug("signs are both positive");
|
||||
// signs are positive (east of UTC)
|
||||
// berlin < sydney
|
||||
if (nowDiff < eventDiff) {
|
||||
// germany vs australia
|
||||
eventDiff = -(eventDiff - nowDiff);
|
||||
}
|
||||
else {
|
||||
// australia vs germany
|
||||
//eventDiff = eventDiff; //- nowDiff
|
||||
}
|
||||
}
|
||||
}
|
||||
startMoment = moment.tz(new Date(date.valueOf() + (eventDiff * (60 * 1000))), event.end.tz);
|
||||
} else {
|
||||
Log.debug("same tz event and display");
|
||||
eventDiff = 0;
|
||||
startMoment = moment.tz(new Date(date.valueOf() - (eventDiff * (60 * 1000))), event.end.tz);
|
||||
}
|
||||
Log.debug("startMoment post=", startMoment);
|
||||
return startMoment;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup iana tz from windows
|
||||
* @param {string} msTZName the timezone name to lookup
|
||||
|
|
|
@ -12,6 +12,7 @@ Module.register("compliments", {
|
|||
},
|
||||
updateInterval: 30000,
|
||||
remoteFile: null,
|
||||
remoteFileRefreshInterval: 0,
|
||||
fadeSpeed: 4000,
|
||||
morningStartTime: 3,
|
||||
morningEndTime: 12,
|
||||
|
@ -20,6 +21,9 @@ Module.register("compliments", {
|
|||
random: true,
|
||||
specialDayUnique: false
|
||||
},
|
||||
urlSuffix: "",
|
||||
compliments_new: null,
|
||||
refreshMinimumDelay: 15 * 60 * 60 * 1000, // 15 minutes
|
||||
lastIndexUsed: -1,
|
||||
// Set currentweather from module
|
||||
currentWeatherType: "",
|
||||
|
@ -41,6 +45,22 @@ Module.register("compliments", {
|
|||
const response = await this.loadComplimentFile();
|
||||
this.config.compliments = JSON.parse(response);
|
||||
this.updateDom();
|
||||
if (this.config.remoteFileRefreshInterval !== 0) {
|
||||
if ((this.config.remoteFileRefreshInterval >= this.refreshMinimumDelay) || window.mmTestMode === "true") {
|
||||
setInterval(async () => {
|
||||
const response = await this.loadComplimentFile();
|
||||
if (response) {
|
||||
this.compliments_new = JSON.parse(response);
|
||||
}
|
||||
else {
|
||||
Log.error(`${this.name} remoteFile refresh failed`);
|
||||
}
|
||||
},
|
||||
this.config.remoteFileRefreshInterval);
|
||||
} else {
|
||||
Log.error(`${this.name} remoteFileRefreshInterval less than minimum`);
|
||||
}
|
||||
}
|
||||
}
|
||||
let minute_sync_delay = 1;
|
||||
// loop thru all the configured when events
|
||||
|
@ -185,8 +205,18 @@ Module.register("compliments", {
|
|||
async loadComplimentFile () {
|
||||
const isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0,
|
||||
url = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile);
|
||||
const response = await fetch(url);
|
||||
return await response.text();
|
||||
// because we may be fetching the same url,
|
||||
// we need to force the server to not give us the cached result
|
||||
// create an extra property (ignored by the server handler) just so the url string is different
|
||||
// that will never be the same, using the ms value of date
|
||||
if (isRemote && this.config.remoteFileRefreshInterval !== 0) this.urlSuffix = `?dummy=${Date.now()}`;
|
||||
//
|
||||
try {
|
||||
const response = await fetch(url + this.urlSuffix);
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
Log.info(`${this.name} fetch failed error=`, error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -236,6 +266,27 @@ Module.register("compliments", {
|
|||
compliment.lastElementChild.remove();
|
||||
wrapper.appendChild(compliment);
|
||||
}
|
||||
// if a new set of compliments was loaded from the refresh task
|
||||
// we do this here to make sure no other function is using the compliments list
|
||||
if (this.compliments_new) {
|
||||
// use them
|
||||
if (JSON.stringify(this.config.compliments) !== JSON.stringify(this.compliments_new)) {
|
||||
// only reset if the contents changes
|
||||
this.config.compliments = this.compliments_new;
|
||||
// reset the index
|
||||
this.lastIndexUsed = -1;
|
||||
}
|
||||
// clear new file list so we don't waste cycles comparing between refreshes
|
||||
this.compliments_new = null;
|
||||
}
|
||||
// only in test mode
|
||||
if (window.mmTestMode === "true") {
|
||||
// check for (undocumented) remoteFile2 to test new file load
|
||||
if (this.config.remoteFile2 !== null && this.config.remoteFileRefreshInterval !== 0) {
|
||||
// switch the file so that next time it will be loaded from a changed file
|
||||
this.config.remoteFile = this.config.remoteFile2;
|
||||
}
|
||||
}
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ Module.register("newsfeed", {
|
|||
|
||||
getUrlPrefix (item) {
|
||||
if (item.useCorsProxy) {
|
||||
return `${location.protocol}//${location.host}/cors?url=`;
|
||||
return `${location.protocol}//${location.host}${config.basePath}cors?url=`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@ class Updater {
|
|||
this.autoRestart = config.updateAutorestart;
|
||||
this.moduleList = {};
|
||||
this.updating = false;
|
||||
this.usePM2 = false;
|
||||
this.PM2 = null;
|
||||
this.usePM2 = false; // don't use pm2 by default
|
||||
this.PM2Id = null; // pm2 process number
|
||||
this.version = global.version;
|
||||
this.root_path = global.root_path;
|
||||
Log.info("updatenotification: Updater Class Loaded!");
|
||||
|
@ -139,11 +139,11 @@ class Updater {
|
|||
else this.npmRestart();
|
||||
}
|
||||
|
||||
// restart MagicMiror with "pm2"
|
||||
// restart MagicMiror with "pm2": use PM2Id for restart it
|
||||
pm2Restart () {
|
||||
Log.info("updatenotification: PM2 will restarting MagicMirror...");
|
||||
const pm2 = require("pm2");
|
||||
pm2.restart(this.PM2, (err, proc) => {
|
||||
pm2.restart(this.PM2Id, (err, proc) => {
|
||||
if (err) {
|
||||
Log.error("updatenotification:[PM2] restart Error", err);
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ class Updater {
|
|||
const out = process.stdout;
|
||||
const err = process.stderr;
|
||||
const subprocess = Spawn("npm start", { cwd: this.root_path, shell: true, detached: true, stdio: ["ignore", out, err] });
|
||||
subprocess.unref();
|
||||
subprocess.unref(); // detach the newly launched process from the master process
|
||||
process.exit();
|
||||
}
|
||||
|
||||
|
@ -166,38 +166,45 @@ class Updater {
|
|||
return new Promise((resolve) => {
|
||||
if (fs.existsSync("/.dockerenv")) {
|
||||
Log.info("updatenotification: Running in docker container, not using PM2 ...");
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.unique_id === undefined) {
|
||||
Log.info("updatenotification: [PM2] You are not using pm2");
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.debug(`updatenotification: [PM2] Search for pm2 id: ${process.env.pm_id} -- name: ${process.env.name} -- unique_id: ${process.env.unique_id}`);
|
||||
|
||||
const pm2 = require("pm2");
|
||||
pm2.connect((err) => {
|
||||
if (err) {
|
||||
Log.error("updatenotification: [PM2]", err);
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
pm2.list((err, list) => {
|
||||
if (err) {
|
||||
Log.error("updatenotification: [PM2] Can't get process List!");
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
list.forEach((pm) => {
|
||||
if (pm.pm2_env.version === this.version && pm.pm2_env.status === "online" && pm.pm2_env.pm_cwd.includes(`${this.root_path}/`)) {
|
||||
this.PM2 = pm.name;
|
||||
Log.debug(`updatenotification: [PM2] found pm2 process id: ${pm.pm_id} -- name: ${pm.name} -- unique_id: ${pm.pm2_env.unique_id}`);
|
||||
if (pm.pm2_env.status === "online" && process.env.name === pm.name && +process.env.pm_id === +pm.pm_id && process.env.unique_id === pm.pm2_env.unique_id) {
|
||||
this.PM2Id = pm.pm_id;
|
||||
this.usePM2 = true;
|
||||
Log.info("updatenotification: [PM2] You are using pm2 with", this.PM2);
|
||||
Log.info(`updatenotification: [PM2] You are using pm2 with id: ${this.PM2Id} (${pm.name})`);
|
||||
resolve(true);
|
||||
} else {
|
||||
Log.debug(`updatenotification: [PM2] pm2 process id: ${pm.pm_id} don't match...`);
|
||||
}
|
||||
});
|
||||
pm2.disconnect();
|
||||
if (!this.PM2) {
|
||||
if (!this.usePM2) {
|
||||
Log.info("updatenotification: [PM2] You are not using pm2");
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
* @param {boolean} useCorsProxy A flag to indicate
|
||||
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
|
||||
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
|
||||
* @param {string} basePath, default /
|
||||
* @returns {Promise} resolved when the fetch is done. The response headers is placed in a headers-property (provided the response does not already contain a headers-property).
|
||||
*/
|
||||
async function performWebRequest (url, type = "json", useCorsProxy = false, requestHeaders = undefined, expectedResponseHeaders = undefined) {
|
||||
async function performWebRequest (url, type = "json", useCorsProxy = false, requestHeaders = undefined, expectedResponseHeaders = undefined, basePath = "/") {
|
||||
const request = {};
|
||||
let requestUrl;
|
||||
if (useCorsProxy) {
|
||||
requestUrl = getCorsUrl(url, requestHeaders, expectedResponseHeaders);
|
||||
requestUrl = getCorsUrl(url, requestHeaders, expectedResponseHeaders, basePath);
|
||||
} else {
|
||||
requestUrl = url;
|
||||
request.headers = getHeadersToSend(requestHeaders);
|
||||
|
@ -37,13 +38,14 @@ async function performWebRequest (url, type = "json", useCorsProxy = false, requ
|
|||
* @param {string} url the url to fetch from
|
||||
* @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send
|
||||
* @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive
|
||||
* @param {string} basePath, default /
|
||||
* @returns {string} to be used as URL when calling CORS-method on server.
|
||||
*/
|
||||
const getCorsUrl = function (url, requestHeaders, expectedResponseHeaders) {
|
||||
const getCorsUrl = function (url, requestHeaders, expectedResponseHeaders, basePath = "/") {
|
||||
if (!url || url.length < 1) {
|
||||
throw new Error(`Invalid URL: ${url}`);
|
||||
} else {
|
||||
let corsUrl = `${location.protocol}//${location.host}/cors?`;
|
||||
let corsUrl = `${location.protocol}//${location.host}${basePath}cors?`;
|
||||
|
||||
const requestHeaderString = getRequestHeaderString(requestHeaders);
|
||||
if (requestHeaderString) corsUrl = `${corsUrl}sendheaders=${requestHeaderString}`;
|
||||
|
|
|
@ -244,17 +244,17 @@ WeatherProvider.register("openmeteo", {
|
|||
.add(Math.max(0, Math.min(7, this.config.maxNumberOfDays)), "days")
|
||||
.endOf("day");
|
||||
|
||||
params["start_date"] = startDate.format("YYYY-MM-DD");
|
||||
params.start_date = startDate.format("YYYY-MM-DD");
|
||||
|
||||
switch (this.config.type) {
|
||||
case "hourly":
|
||||
case "daily":
|
||||
case "forecast":
|
||||
params["end_date"] = endDate.format("YYYY-MM-DD");
|
||||
params.end_date = endDate.format("YYYY-MM-DD");
|
||||
break;
|
||||
case "current":
|
||||
params["current_weather"] = true;
|
||||
params["end_date"] = params["start_date"];
|
||||
params.current_weather = true;
|
||||
params.end_date = params.start_date;
|
||||
break;
|
||||
default:
|
||||
// Failsafe
|
||||
|
@ -262,7 +262,7 @@ WeatherProvider.register("openmeteo", {
|
|||
}
|
||||
|
||||
return Object.keys(params)
|
||||
.filter((key) => (params[key] ? true : false))
|
||||
.filter((key) => (!!params[key]))
|
||||
.map((key) => {
|
||||
switch (key) {
|
||||
case "hourly":
|
||||
|
|
|
@ -17,10 +17,13 @@ WeatherProvider.register("openweathermap", {
|
|||
defaults: {
|
||||
apiVersion: "3.0",
|
||||
apiBase: "https://api.openweathermap.org/data/",
|
||||
weatherEndpoint: "", // can be "onecall", "forecast" or "weather" (for current)
|
||||
// weatherEndpoint is "/onecall" since API 3.0
|
||||
// "/onecall", "/forecast" or "/weather" only for pro customers
|
||||
weatherEndpoint: "/onecall",
|
||||
locationID: false,
|
||||
location: false,
|
||||
lat: 0, // the onecall endpoint needs lat / lon values, it doesn't support the locationId
|
||||
// the /onecall endpoint needs lat / lon values, it doesn't support the locationId
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
apiKey: ""
|
||||
},
|
||||
|
@ -90,30 +93,6 @@ WeatherProvider.register("openweathermap", {
|
|||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
/**
|
||||
* Overrides method for setting config to check if endpoint is correct for hourly
|
||||
* @param {object} config The configuration object
|
||||
*/
|
||||
setConfig (config) {
|
||||
this.config = config;
|
||||
if (!this.config.weatherEndpoint) {
|
||||
switch (this.config.type) {
|
||||
case "hourly":
|
||||
this.config.weatherEndpoint = "/onecall";
|
||||
break;
|
||||
case "daily":
|
||||
case "forecast":
|
||||
this.config.weatherEndpoint = "/forecast";
|
||||
break;
|
||||
case "current":
|
||||
this.config.weatherEndpoint = "/weather";
|
||||
break;
|
||||
default:
|
||||
Log.error("weatherEndpoint not configured and could not resolve it based on type");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
|
||||
/*
|
||||
* Gets the complete url for the request
|
||||
|
@ -306,12 +285,12 @@ WeatherProvider.register("openweathermap", {
|
|||
current.weatherType = this.convertWeatherType(data.current.weather[0].icon);
|
||||
current.humidity = data.current.humidity;
|
||||
current.uv_index = data.current.uvi;
|
||||
if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) {
|
||||
current.rain = data.current["rain"]["1h"];
|
||||
if (data.current.hasOwnProperty("rain") && !isNaN(data.current.rain["1h"])) {
|
||||
current.rain = data.current.rain["1h"];
|
||||
precip = true;
|
||||
}
|
||||
if (data.current.hasOwnProperty("snow") && !isNaN(data.current["snow"]["1h"])) {
|
||||
current.snow = data.current["snow"]["1h"];
|
||||
if (data.current.hasOwnProperty("snow") && !isNaN(data.current.snow["1h"])) {
|
||||
current.snow = data.current.snow["1h"];
|
||||
precip = true;
|
||||
}
|
||||
if (precip) {
|
||||
|
|
|
@ -119,7 +119,7 @@ const WeatherProvider = Class.extend({
|
|||
return JSON.parse(data);
|
||||
}
|
||||
const useCorsProxy = typeof this.config.useCorsProxy !== "undefined" && this.config.useCorsProxy;
|
||||
return performWebRequest(url, type, useCorsProxy, requestHeaders, expectedResponseHeaders);
|
||||
return performWebRequest(url, type, useCorsProxy, requestHeaders, expectedResponseHeaders, config.basePath);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
4598
package-lock.json
generated
4598
package-lock.json
generated
File diff suppressed because it is too large
Load diff
93
package.json
93
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "magicmirror",
|
||||
"version": "2.29.0",
|
||||
"version": "2.30.0",
|
||||
"description": "The open source modular smart mirror platform.",
|
||||
"keywords": [
|
||||
"magic mirror",
|
||||
|
@ -24,30 +24,37 @@
|
|||
],
|
||||
"main": "js/electron.js",
|
||||
"scripts": {
|
||||
"start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
|
||||
"start:dev": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js dev",
|
||||
"server": "node ./serveronly",
|
||||
"config:check": "node js/check_config.js",
|
||||
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
|
||||
"install-mm": "npm install --no-audit --no-fund --no-update-notifier --only=prod --omit=dev",
|
||||
"install-mm:dev": "npm install --no-audit --no-fund --no-update-notifier",
|
||||
"install-vendor": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
|
||||
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
|
||||
"postinstall": "npm run install-vendor && npm run install-fonts && echo \"MagicMirror² installation finished successfully! \n\"",
|
||||
"test": "NODE_ENV=test jest -i --forceExit",
|
||||
"test:coverage": "NODE_ENV=test jest --coverage -i --verbose false --forceExit",
|
||||
"test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
|
||||
"test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit",
|
||||
"test:unit": "NODE_ENV=test jest --selectProjects unit",
|
||||
"test:prettier": "prettier . --check",
|
||||
"test:js": "eslint .",
|
||||
"test:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json",
|
||||
"test:calendar": "node ./modules/default/calendar/debug.js",
|
||||
"test:spelling": "cspell . --gitignore",
|
||||
"config:check": "node js/check_config.js",
|
||||
"lint:prettier": "prettier . --write",
|
||||
"lint:js": "eslint . --fix",
|
||||
"lint:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix",
|
||||
"lint:staged": "lint-staged",
|
||||
"prepare": "[ -f node_modules/.bin/husky ] && husky || echo no husky installed."
|
||||
"lint:js": "eslint . --fix",
|
||||
"lint:markdown": "markdownlint-cli2 . --fix",
|
||||
"lint:prettier": "prettier . --write",
|
||||
"postinstall": "npm run install-vendor && npm run install-fonts && echo \"MagicMirror² installation finished successfully! \n\"",
|
||||
"prepare": "[ -f node_modules/.bin/husky ] && husky || echo no husky installed.",
|
||||
"server": "node ./serveronly",
|
||||
"start": "npm run start:x11",
|
||||
"start:dev": "npm run start -- dev",
|
||||
"start:wayland": "WAYLAND_DISPLAY=\"${WAYLAND_DISPLAY:=wayland-1}\" ./node_modules/.bin/electron js/electron.js --enable-features=UseOzonePlatform --ozone-platform=wayland",
|
||||
"start:wayland:dev": "npm run start:wayland -- dev",
|
||||
"start:windows": ".\\node_modules\\.bin\\electron js\\electron.js",
|
||||
"start:windows:dev": "npm run start:windows -- dev",
|
||||
"start:x11": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
|
||||
"start:x11:dev": "npm run start -- dev",
|
||||
"test": "NODE_ENV=test jest -i --forceExit",
|
||||
"test:calendar": "node ./modules/default/calendar/debug.js",
|
||||
"test:coverage": "NODE_ENV=test jest --coverage -i --verbose false --forceExit",
|
||||
"test:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json",
|
||||
"test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit",
|
||||
"test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
|
||||
"test:js": "eslint .",
|
||||
"test:markdown": "markdownlint-cli2 .",
|
||||
"test:prettier": "prettier . --check",
|
||||
"test:spelling": "cspell . --gitignore",
|
||||
"test:unit": "NODE_ENV=test jest --selectProjects unit"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "prettier --write",
|
||||
|
@ -56,48 +63,50 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ajv": "^8.17.1",
|
||||
"ansis": "^3.3.2",
|
||||
"ansis": "^3.5.2",
|
||||
"console-stamp": "^3.1.2",
|
||||
"envsub": "^4.1.0",
|
||||
"eslint": "^9.11.1",
|
||||
"express": "^4.21.0",
|
||||
"eslint": "^9.17.0",
|
||||
"express": "^4.21.2",
|
||||
"express-ipfilter": "^1.3.2",
|
||||
"feedme": "^2.0.2",
|
||||
"helmet": "^7.1.0",
|
||||
"helmet": "^8.0.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"module-alias": "^2.2.3",
|
||||
"moment": "^2.30.1",
|
||||
"node-ical": "0.18.0",
|
||||
"pm2": "^5.4.2",
|
||||
"socket.io": "^4.8.0",
|
||||
"node-ical": "^0.20.1",
|
||||
"pm2": "^5.4.3",
|
||||
"socket.io": "^4.8.1",
|
||||
"suncalc": "^1.9.0",
|
||||
"systeminformation": "^5.23.5"
|
||||
"systeminformation": "^5.24.3",
|
||||
"undici": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@stylistic/eslint-plugin": "^2.8.0",
|
||||
"cspell": "^8.14.4",
|
||||
"eslint-plugin-jest": "^28.8.3",
|
||||
"eslint-plugin-jsdoc": "^50.3.0",
|
||||
"eslint-plugin-package-json": "^0.15.3",
|
||||
"@stylistic/eslint-plugin": "^2.12.1",
|
||||
"cspell": "^8.17.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.10.0",
|
||||
"eslint-plugin-jsdoc": "^50.6.1",
|
||||
"eslint-plugin-package-json": "^0.19.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"husky": "^9.1.6",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.7.0",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "^15.2.10",
|
||||
"playwright": "^1.47.2",
|
||||
"prettier": "^3.3.3",
|
||||
"lint-staged": "^15.3.0",
|
||||
"markdownlint-cli2": "^0.17.1",
|
||||
"playwright": "^1.49.1",
|
||||
"prettier": "^3.4.2",
|
||||
"sinon": "^19.0.2",
|
||||
"stylelint": "^16.9.0",
|
||||
"stylelint": "^16.12.0",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"stylelint-prettier": "^5.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron": "^31.6.0"
|
||||
"electron": "^32.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0 <21 || 22"
|
||||
"node": ">=20.18.1 <21 || >=22"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"node_helper": "js/node_helper.js",
|
||||
|
|
13
prettier.config.mjs
Normal file
13
prettier.config.mjs
Normal file
|
@ -0,0 +1,13 @@
|
|||
const config = {
|
||||
overrides: [
|
||||
{
|
||||
files: "*.md",
|
||||
options: {
|
||||
parser: "markdown"
|
||||
}
|
||||
}
|
||||
],
|
||||
trailingComma: "none"
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules:
|
||||
// Using exotic content. This is why don't accept go to JSON configuration file
|
||||
(() => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "alert",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
units: "metric",
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
hideDuplicates: false,
|
||||
maximumEntries: 100,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/3_move_first_allday_repeating_event.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
logLevel: ["INFO", "LOG", "WARN", "ERROR", "DEBUG"],
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/end_of_day_berlin_moved.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
33
tests/configs/modules/calendar/berlin_multi.js
Normal file
33
tests/configs/modules/calendar/berlin_multi.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/RepeatingEvent.Oct21.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/whole_day_moved_over_dst_change_berlin.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
33
tests/configs/modules/calendar/chicago_late_in_timezone.js
Normal file
33
tests/configs/modules/calendar/chicago_late_in_timezone.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 20,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
//url: "http://localhost:8080/tests/mocks/chicago_late_in_timezone.ics"
|
||||
url: "http://localhost:8080/tests/mocks/chicago_late_in_timezone.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -11,6 +13,8 @@ let config = {
|
|||
calendars: [
|
||||
{
|
||||
maximumEntries: 5,
|
||||
pastDaysCount: 5,
|
||||
broadcastPastEvents: true,
|
||||
maximumNumberOfDays: 10000,
|
||||
symbol: "birthday-cake",
|
||||
fullDaySymbol: "calendar-day",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
34
tests/configs/modules/calendar/diff_tz_start_end.js
Normal file
34
tests/configs/modules/calendar/diff_tz_start_end.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
dateEndFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/diff_tz_start_end.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
33
tests/configs/modules/calendar/end_of_day_berlin_moved.js
Normal file
33
tests/configs/modules/calendar/end_of_day_berlin_moved.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/end_of_day_berlin_moved.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
dateEndFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/event_with_time_over_multiple_days_non_repeating.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
dateEndFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
showEnd: true,
|
||||
showEndsOnlyWithDuration: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/event_with_time_over_multiple_days_non_repeating.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
maximumNumberOfDays: 28,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/exdate_and_recurrence_together.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -8,6 +8,8 @@
|
|||
* See tests/electron/modules/calendar_spec.js
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -28,10 +30,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("19 Oct 2023 12:30:00 GMT-07:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* See tests/electron/modules/calendar_spec.js
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -28,10 +30,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("19 Oct 2023 12:30:00 GMT-07:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* See tests/electron/modules/calendar_spec.js
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -28,10 +30,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("19 Oct 2023 12:30:00 GMT-07:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* See tests/electron/modules/calendar_spec.js
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -28,10 +30,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("14 Sep 2023 12:30:00 GMT+10:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* See tests/electron/modules/calendar_spec.js
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -28,10 +30,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("14 Sep 2023 12:30:00 GMT+10:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* See tests/electron/modules/calendar_spec.js
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -28,10 +30,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("14 Sep 2023 12:30:00 GMT+10:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 24,
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
fade: false,
|
||||
urgency: 0,
|
||||
dateFormat: "Do.MMM, HH:mm",
|
||||
fullDayEventDateFormat: "Do.MMM",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
showEnd: true,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/fullday_event_over_multiple_days_nonrepeating.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: "calendar",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
hideDuplicates: false,
|
||||
maximumEntries: 100,
|
||||
sliceMultiDayEvents: true,
|
||||
dateFormat: "MMM Do, HH:mm",
|
||||
timeFormat: "absolute",
|
||||
getRelative: 0,
|
||||
urgency: 0,
|
||||
calendars: [
|
||||
{
|
||||
maximumEntries: 100,
|
||||
url: "http://localhost:8080/tests/mocks/germany_at_end_of_day_repeating.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
* MIT Licensed.
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -20,10 +22,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("07 Mar 2024 10:38:00 GMT-07:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* MIT Licensed.
|
||||
*/
|
||||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
@ -20,10 +22,6 @@ let config = {
|
|||
]
|
||||
};
|
||||
|
||||
Date.now = () => {
|
||||
return new Date("01 Sept 2024 10:38:00 GMT+2:00").valueOf();
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = config;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "clock",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "clock",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
language: "es",
|
||||
timeFormat: 12,
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
language: "es",
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
language: "es",
|
||||
timeFormat: 12,
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
language: "es",
|
||||
timeFormat: 12,
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
|
|
17
tests/configs/modules/compliments/compliments_file.js
Normal file
17
tests/configs/modules/compliments/compliments_file.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
updateInterval: 3000,
|
||||
remoteFile: "http://localhost:8080/tests/mocks/compliments_test.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") { module.exports = config; }
|
19
tests/configs/modules/compliments/compliments_file_change.js
Normal file
19
tests/configs/modules/compliments/compliments_file_change.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
modules: [
|
||||
{
|
||||
module: "compliments",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
updateInterval: 3000,
|
||||
remoteFileRefreshInterval: 1500,
|
||||
remoteFile: "http://localhost:8080/tests/mocks/compliments_test.json",
|
||||
remoteFile2: "http://localhost:8080/tests/mocks/compliments_file.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") { module.exports = config; }
|
|
@ -1,4 +1,6 @@
|
|||
let config = {
|
||||
address: "0.0.0.0",
|
||||
ipWhitelist: [],
|
||||
timeFormat: 12,
|
||||
|
||||
modules: [
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue