Compare commits

...

832 Commits

Author SHA1 Message Date
Michael Teeuw
0300ce05d5 Release 2.22.0 (#2983)
## [2.22.0] - 2023-01-01

Thanks to: @angeldeejay, @buxxi, @dariom, @dWoolridge,
@KristjanESPERANTO, @MagMar94, @naveensrinivasan, @retroflex, @SkySails
and @Tom.

Special thanks to @khassel, @rejas and @sdetweil for taking over most
(if not all) of the work on this release as project collaborators. This
version would not be there without their effort. Thank you!

### Added

- Added test for remoteFile option in compliments module
- Added hourlyWeather functionality to Weather.gov weather provider
- Removed weatherEndpoint definition from weathergov.js (not used)
- Added css class names "today" and "tomorrow" for default calendar
- Added Collaboration.md
- Added new github action for dependency review (#2862)
- Added a WeatherProvider for Open-Meteo
- Added Yr as a weather provider
- Added config options "ignoreXOriginHeader" and
"ignoreContentSecurityPolicy"

### Removed

- Removed usage of internal fetch function of node until it is more
stable

### Updated

- Cleaned up test directory (#2937) and jest config (#2959)
- Wait for all modules to start before declaring the system ready
(#2487)
- Updated e2e tests (moved `done()` in helper functions) and use es6
syntax in all tests
- Updated da translation
- Rework weather module
- Make sure smhi provider api only gets a maximum of 6 digits
coordinates (#2955)
  - Use fetch instead of XMLHttpRequest in weatherprovider (#2935)
  - Reworked how weatherproviders handle units (#2849)
  - Use unix() method for parsing times, fix suntimes on the way (#2950)
  - Refactor conversion functions into utils class (#2958)
- The `cors`-method in `server.js` now supports sending and recieving
HTTP headers
- Replace `…` by `…`
- Cleanup compliments module
- Updated dependencies including electron to v22 (#2903)

### Fixed

- Correctly show apparent temperature in SMHI weather provider
- Ensure updatenotification module isn't shown when local is _ahead_ of
remote
- Handle node_helper errors during startup (#2944)
- Possibility to change FontAwesome class in calendar, so icons like
`fab fa-facebook-square` works.
- Fix cors problems with newsfeed articles (as far as possible), allow
disabling cors per feed with option `useCorsProxy: false` (#2840)
- Tests not waiting for the application to start and stop before
starting the next test
- Fix electron tests failing sometimes in github workflow
- Fixed gap in clock module when displayed on the left side with
displayType=digital
- Fixed playwright issue by upgrading to v1.29.1 (#2969)

Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com>
Co-authored-by: Karsten Hassel <hassel@gmx.de>
Co-authored-by: Malte Hallström <46646495+SkySails@users.noreply.github.com>
Co-authored-by: Veeck <github@veeck.de>
Co-authored-by: veeck <michael@veeck.de>
Co-authored-by: dWoolridge <dwoolridge@charter.net>
Co-authored-by: Johan <jojjepersson@yahoo.se>
Co-authored-by: Dario Mratovich <dario_mratovich@hotmail.com>
Co-authored-by: Dario Mratovich <dario.mratovich@outlook.com>
Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com>
Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com>
Co-authored-by: buxxi <buxxi@omfilm.net>
Co-authored-by: Thomas Hirschberger <47733292+Tom-Hirschberger@users.noreply.github.com>
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com>
2023-01-01 18:09:08 +01:00
Michael Teeuw
9e0293047f Merge pull request #2921 from MichMich/develop
Release 2.21.0
2022-10-01 20:06:53 +02:00
Michael Teeuw
298e585361 Prepare 2.21.0 2022-10-01 19:53:44 +02:00
Michael Teeuw
21a6d1bcea Merge pull request #2920 from khassel/issue-template
update issue template
2022-10-01 19:41:10 +02:00
Karsten Hassel
bbe17b9b01 removed deprecated Docker Repository from issue template 2022-10-01 19:13:14 +02:00
Michael Teeuw
4381fc6695 Merge pull request #2912 from khassel/e2e
fix e2e tests not failing on errors
2022-09-28 09:59:10 +02:00
Michael Teeuw
818fd7b490 Merge pull request #2914 from khassel/remove-old-weather
Remove old weather modules
2022-09-28 09:58:47 +02:00
Michael Teeuw
4c6c6f9ed3 Merge pull request #2917 from khassel/update_deps
update dependencies
2022-09-28 09:58:25 +02:00
Karsten Hassel
2338a90191 update dependencies 2022-09-27 22:25:20 +02:00
Karsten Hassel
6cad0e191b fix weather README 2022-09-21 23:40:15 +02:00
Karsten Hassel
f23e604ed4 remove old weather modules 2022-09-21 22:51:48 +02:00
Karsten Hassel
0c1abad9df fix calendar_test_recurring.ics and uncomment recurring tests 2022-09-21 21:01:43 +02:00
Karsten Hassel
fb96cc3c72 fix e2e tests not failing on errors, disable recurring-event-tests of calendar 2022-09-21 00:28:43 +02:00
Michael Teeuw
e917f40542 Merge pull request #2908 from BKeyport/develop 2022-09-18 07:41:08 +02:00
Brendan Keyport
29d467715f ran prettier 2022-09-17 14:47:34 -07:00
B. Keyport
b791a3761f uncaught breaking change - now caught 2022-09-16 23:56:01 -07:00
B. Keyport
02201d9f15 CSS error in old coding, will break. 2022-09-13 14:03:06 -07:00
B. Keyport
b8dbf95497 Minor tweak to uncaught error in sizing. 2022-09-13 13:58:56 -07:00
B. Keyport
65022f3ce1 reset font sizes not to break 2022-09-13 12:44:10 -07:00
B. Keyport
44854d6a4f Fully implement font size adjustments to variables
Partially implementing variables is silly, noticed font sizes weren't implemented fully. Corrected this in my custom, decided it should be in main. Feel free to adjust REM factors to match defaults you like.
2022-09-08 12:19:59 -07:00
B. Keyport
203014c654 Fully implement font size adjustments to variables
Partially implementing variables is silly, noticed font sizes weren't implemented fully. Corrected this in my custom, decided it should be in main. Feel free to adjust REM factors to match defaults you like.
2022-09-08 12:11:56 -07:00
Michael Teeuw
d3e53586fd Merge pull request #2906 from khassel/update-deps
update dependencies, revert electron to v19
2022-09-06 21:59:57 +02:00
Michael Teeuw
9dd343054e Merge pull request #2905 from sdetweil/fixcal
Fixcal
2022-09-06 21:59:40 +02:00
Sam Detweiler
11d17dd2c0 fix typo, duplcate line 2022-09-06 13:29:08 -05:00
Karsten Hassel
4cc3e481cc update dependencies, revert electron to v19 2022-09-05 23:26:04 +02:00
Sam Detweiler
174da38cc8 run prettier 2022-09-05 15:02:16 -05:00
Sam Detweiler
0a35505e8d fix repeating calendar events without byday adjusted incorrectly 2022-09-05 14:52:53 -05:00
Sam Detweiler
032f7ac299 fix events with rrule byweekday 2022-09-05 14:13:46 -05:00
Michael Teeuw
ca906c4b36 Merge pull request #2902 from SkySails/feat/smhi-weather-provider-improvements
feat(weather/smhi): Add hourly forecasts, apparent temperature & custom location name
2022-08-30 10:38:38 +02:00
Malte Hallström
50f72f09ac refactor: fix linting & code style issues 2022-08-30 10:06:34 +02:00
Malte Hallström
02cf9b37e2 refactor: use const instead of let 2022-08-29 20:08:27 +02:00
Malte Hallström
6f273d76b3 chore: update changelog 2022-08-28 20:36:52 +02:00
Malte Hallström
0a1067ec7d feat(weather/smhi): support custom location names 2022-08-28 20:31:54 +02:00
Malte Hallström
48756e8774 feat(weather/smhi): calculate apparent temperature 2022-08-28 20:31:54 +02:00
Malte Hallström
4915ad8fc7 feat(weather/smhi): support hourly forecasts 2022-08-28 20:31:54 +02:00
Michael Teeuw
ba128cbae9 Merge pull request #2881 from SmartMirrorUmbrella/develop
Add possibility to fetch calendars through socket notifications
2022-08-19 12:53:42 +02:00
Michael Teeuw
2a6e51493e Merge pull request #2897 from khassel/update-deps
update dependencies
2022-08-19 12:53:26 +02:00
Michael Teeuw
ef678f9f8a Merge branch 'develop' into update-deps 2022-08-19 12:53:19 +02:00
Michael Teeuw
e997ee7071 Merge pull request #2898 from rejas/docs
Update jsdocs
2022-08-19 12:52:53 +02:00
Michael Teeuw
1273390af3 Merge pull request #2900 from rejas/issue_2899
Use type value in weatherbit provider
2022-08-19 12:52:32 +02:00
veeck
0f6eb4a244 Update CHANGELOG 2022-08-17 09:23:32 +02:00
veeck
2c0c62c89b Remove default weatherEndpoint 2022-08-17 09:18:35 +02:00
veeck
ec13c952d9 Use type value from config instead just only weatherEndpoint 2022-08-16 22:39:30 +02:00
veeck
6fc71cf6f8 Update license year 2022-08-16 17:32:13 +02:00
rejas
bd3137a3dc Update CHANGELOG 2022-08-08 10:45:42 +02:00
rejas
b26da4f97f Fix jsdoc in calendar 2022-08-08 10:30:37 +02:00
veeck
cde0adc28e Update nodehelper docs 2022-08-08 10:30:23 +02:00
Karsten Hassel
2813f101b8 update dependencies 2022-08-07 23:21:59 +02:00
Michael Teeuw
4d8fb8c176 Merge pull request #2895 from buxxi/develop 2022-08-06 18:38:23 +02:00
krukle
9ae62d60f7 fix automated test: error 'key' is not defined 2022-08-06 10:47:37 +02:00
buxxi
17c14e137e Make the calendar show the whole multi day event with how far it has progressed 2022-08-02 16:45:37 +02:00
krukle
bc239f6608 fix checkstyle errors 2022-07-29 10:25:23 +02:00
Christoffer Eid
6493fad8a4 Merge branch 'develop' into develop 2022-07-28 16:32:19 +02:00
Michael Teeuw
d539f459ca Merge pull request #2874 from MichMich/dependabot/github_actions/codecov/codecov-action-3
Bump codecov/codecov-action from 2 to 3
2022-07-28 15:30:32 +02:00
Michael Teeuw
2cca6a2f39 Merge pull request #2875 from MichMich/dependabot/github_actions/actions/setup-node-3
Bump actions/setup-node from 2 to 3
2022-07-28 15:30:17 +02:00
Michael Teeuw
80e07bae0d Merge pull request #2891 from khassel/mm-install
new script `install-mm` for simplifying mm installation
2022-07-28 15:29:43 +02:00
Michael Teeuw
d4c4f6e1a5 Merge branch 'develop' into mm-install 2022-07-28 15:29:37 +02:00
Michael Teeuw
d24fe4e983 Merge pull request #2890 from MikeBishop/showTimeToday
Show time today
2022-07-28 15:25:50 +02:00
Michael Teeuw
aaa9042810 Merge pull request #2889 from MikeBishop/no_day_after
English doesn't have a "day after tomorrow" word
2022-07-28 15:24:49 +02:00
Michael Teeuw
a4bb1cefb9 Merge pull request #2884 from khassel/newsfeed-ttl
respect rss ttl provided by newsfeed
2022-07-28 15:23:32 +02:00
Mike Bishop
c3339b47bb Remove newline to re-run tests 2022-07-25 18:34:24 -04:00
Mike Bishop
0c1e5ea881 Use German as the base for tests 2022-07-25 18:23:27 -04:00
Karsten Hassel
3fbd9006ad new script install-mm for simplifying mm installation and new params --no-audit --no-fund --no-update-notifier for less noise 2022-07-25 22:35:16 +02:00
Mike Bishop
be9761146c CHANGELOG 2022-07-25 14:09:56 -04:00
Mike Bishop
5aa9e7e0f6 Add showTimeToday option 2022-07-25 13:52:43 -04:00
Mike Bishop
65e87aea52 CHANGELOG 2022-07-25 13:49:19 -04:00
Mike Bishop
66fffc932c English doesn't have a "day after tomorrow" word 2022-07-25 13:33:47 -04:00
Karsten Hassel
1e934e16af respect rss ttl provided by newsfeed 2022-07-21 22:07:36 +02:00
krukle
82fbb7e32d add changes to changelog 2022-07-14 08:49:37 +02:00
krukle
8bf9b9bef9 Add possibility to fetch calendars through socket notifications 2022-07-12 14:35:04 +02:00
Michael Teeuw
2d15e4f976 Merge pull request #2877 from davide125/permissions
Drop executable bit where not needed
2022-07-06 16:30:08 +02:00
Michael Teeuw
055ce56a57 Merge pull request #2878 from kolbyjack/fix-per-calendar-max-entries
Broadcast all calendar events while still honoring global and per-calendar maximumEntries
2022-07-05 16:12:52 +02:00
Jonathan Kolb
f1f2a61dc8 Broadcast all calendar events while still honoring global and per-calendar maximumEntries 2022-07-03 13:17:18 -04:00
Davide Cavalca
39c1b37726 Drop executable bit where not needed 2022-07-02 14:38:10 -07:00
Michael Teeuw
5b1b25fa86 Prepare v2.21.0-develop 2022-07-02 13:09:41 +02:00
dependabot[bot]
54ab0b1bf0 Bump actions/setup-node from 2 to 3
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-02 11:07:27 +00:00
dependabot[bot]
5507e9ffe9 Bump codecov/codecov-action from 2 to 3
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2 to 3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-02 11:07:24 +00:00
Michael Teeuw
30d5bfe59e Merge pull request #2873 from MichMich/develop
v2.20.0
2022-07-02 13:07:02 +02:00
Michael Teeuw
b716ec33d9 Release v2.20.0 2022-07-02 13:00:01 +02:00
Michael Teeuw
e25209d9f9 Merge pull request #2871 from sdetweil/fix_maxEntries
Fix maximumEntries regression
2022-06-28 15:26:31 +02:00
Sam Detweiler
1f4ac82495 add changelog for #2868 2022-06-28 08:05:13 -05:00
Sam Detweiler
0baf58f3fd fix regression calendar.maximumEntries not used 2022-06-28 08:01:28 -05:00
Michael Teeuw
9a769203e3 Merge pull request #2870 from khassel/update_deps 2022-06-25 10:33:18 +02:00
Karsten Hassel
1435efaea7 update depencies (before upcoming new release) 2022-06-24 23:44:17 +02:00
Michael Teeuw
c411ac821e Merge pull request #2864 from KristjanESPERANTO/develop
Use .yaml instead of .yml
2022-06-17 15:34:21 +02:00
Michael Teeuw
da0489fc0c Merge pull request #2866 from kolbyjack/broadcast-duplicate-events
Include duplicate events in broadcasts
2022-06-17 15:32:17 +02:00
Jonathan Kolb
c82de1e314 Group filters that only apply when limiting for display 2022-06-09 09:21:00 -04:00
Jonathan Kolb
604a555e14 Include duplicate events in broadcasts 2022-06-09 07:52:29 -04:00
Kristjan SCHMIDT
87e011ff96 Add entry for YAML file extention 2022-06-07 00:07:56 +02:00
Kristjan SCHMIDT
9b2051827c Use .yaml instead of .yml
This is the recommended file extension: https://yaml.org/faq.html
2022-06-06 23:57:52 +02:00
Michael Teeuw
47aefb0c82 Merge pull request #2858 from turrisxyz/Pinned-Dependencies-GitHub
chore: Set permissions for GitHub actions
2022-06-04 14:59:28 +02:00
Michael Teeuw
70da1d6f5c Merge pull request #2861 from turrisxyz/Dependabot-GitHub-Actions
chore: Included githubactions in the dependabot config
2022-06-04 14:59:14 +02:00
Michael Teeuw
c0743ce9de Merge pull request #2857 from khassel/fetch 2022-06-04 14:14:52 +02:00
naveen
fe7b4044e0 chore: Included githubactions in the dependabot config
This should help with keeping the GitHub actions updated on new releases. This will also help with keeping it secure.

Dependabot helps in keeping the supply chain secure https://docs.github.com/en/code-security/dependabot

GitHub actions up to date https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot

https://github.com/ossf/scorecard/blob/main/docs/checks.md#dependency-update-tool
Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com>
2022-06-02 01:29:42 +00:00
nathannaveen
701ce8ad47 chore: Set permissions for GitHub actions
Restrict the GitHub token permissions only to the required ones; this way, even if the attackers will succeed in compromising your workflow, they won’t be able to do much.

- Included permissions for the action. https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions

https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs

[Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)

Signed-off-by: nathannaveen <42319948+nathannaveen@users.noreply.github.com>
2022-05-30 00:58:20 +00:00
Karsten Hassel
591f907134 fix newsfeedfetcher using correct stream version (changed with node-18-fetch) 2022-05-28 22:28:37 +02:00
Michael Teeuw
4f1db749c0 Merge pull request #2855 from khassel/fetch 2022-05-28 06:38:52 +02:00
Karsten Hassel
ae72ed8c67 improve startApplication 2022-05-27 21:49:29 +02:00
Karsten Hassel
ab7934fa98 Merge branch 'develop' into fetch
# Conflicts:
#	CHANGELOG.md
2022-05-27 21:04:07 +02:00
Michael Teeuw
a4c77f0c9e Merge pull request #2853 from khassel/update_deps 2022-05-27 20:32:45 +02:00
Karsten Hassel
0023c64d59 use internal fetch function of node instead external node-fetch library if node version >= v18 2022-05-27 19:46:28 +02:00
Karsten Hassel
b55b3bd63b update dependencies including electron to v19 2022-05-24 23:50:33 +02:00
Michael Teeuw
cbda20f67e Merge pull request #2850 from khassel/iframe
added a new config option `httpHeaders` used by helmet
2022-05-18 09:15:32 +02:00
Michael Teeuw
e598dbb206 Merge branch 'develop' into iframe 2022-05-18 09:15:16 +02:00
Michael Teeuw
094881fc5c Merge pull request #2848 from rejas/issue_2192
Show endDate for calendar events when using dateHeaders
2022-05-18 09:14:36 +02:00
Michael Teeuw
b13414cab8 Merge branch 'develop' into issue_2192 2022-05-18 09:14:29 +02:00
Michael Teeuw
3e65ac653b Merge pull request #2844 from rejas/actions
Update github actions
2022-05-18 09:13:40 +02:00
Michael Teeuw
05621c9876 Merge branch 'develop' into actions 2022-05-18 09:13:31 +02:00
Michael Teeuw
938619ce0c Merge pull request #2843 from khassel/update-deps
update dependencies
2022-05-18 09:12:59 +02:00
Michael Teeuw
8e5267d44f Merge pull request #2842 from MMRIZE/weather
Added outgoing notification of the weather module
2022-05-18 09:12:48 +02:00
Karsten Hassel
3b55886c45 added a new config option httpHeaders used by helmet 2022-05-13 22:29:43 +02:00
rejas
ed3aceb427 Update CHANGELOG 2022-05-10 21:44:46 +02:00
rejas
c3b2aaec69 Show endDate for calendar events
when dateHeader is enabled and showEnd is set to true
2022-05-10 21:44:39 +02:00
veeck
bb1d3431cc Update node test matrix for real 2022-05-02 22:35:03 +02:00
veeck
fe83fe338a Update CHANGELOG 2022-05-02 22:02:20 +02:00
veeck
6504b5e818 Update github actions and tested node versions 2022-05-02 22:01:26 +02:00
Karsten Hassel
8578900bfb update deps 2022-05-02 21:50:16 +02:00
Michael Teeuw
d730dd04bf Merge pull request #2839 from rohitdharavath/patch-1 2022-04-26 20:46:38 +02:00
eouia
1d90c5e1fe Add JSDoc description 2022-04-26 17:37:23 +02:00
eouia
0f39b7733c fix typo jsdom tag returns 2022-04-26 14:57:47 +02:00
eouia
038b6765e7 Change property name clearly 2022-04-26 14:52:05 +02:00
eouia
58569a648c Added ECMA Version of lint to 2020 2022-04-26 14:51:12 +02:00
eouia
df0f048ecc Added outgoing notification 2022-04-26 13:55:39 +02:00
Rohith Dharavath
288a008e72 Update electron.js
corrected spelling mistake
2022-04-21 01:53:22 +05:30
Michael Teeuw
439690b981 Merge pull request #2836 from khassel/fix-non-latin 2022-04-09 19:18:17 +02:00
Karsten Hassel
b4e75f6844 fix problems with non latin fonds caused by updating to fontsource (fixes #2835). 2022-04-05 20:58:48 +02:00
Michael Teeuw
3f6a5a7772 Prepare 2.20.0-develop 2022-04-01 20:49:47 +02:00
Michael Teeuw
a2d7cdcfb4 Merge pull request #2834 from MichMich/develop
Release v2.19.0
2022-04-01 20:43:39 +02:00
Michael Teeuw
d995ce38ea Add missing contributor to list. 2022-04-01 20:39:22 +02:00
Michael Teeuw
0b21a22027 Prepare 2.19.0 2022-04-01 20:36:55 +02:00
Michael Teeuw
111d75b351 Merge pull request #2832 from khassel/disable-gpu
Add env var to disable gpu under electron when set
2022-04-01 20:34:15 +02:00
Michael Teeuw
b54cac72ec Merge branch 'develop' into disable-gpu 2022-04-01 20:30:01 +02:00
Michael Teeuw
69b2dd698c Merge pull request #2819 from krekos/patch-1
Add missing CS translations
2022-04-01 20:29:13 +02:00
Michael Teeuw
16bf19d115 Merge branch 'patch-1' of https://github.com/krekos/magicmirror into pr/krekos/2819 2022-04-01 20:24:04 +02:00
Michael Teeuw
c57c9c2a8f Run Prettier. 2022-04-01 20:21:46 +02:00
Michael Teeuw
9f750e5980 Add Changelog. 2022-04-01 20:20:43 +02:00
Krekos
8d296e563d Add missing CS translations 2022-04-01 20:18:54 +02:00
Karsten Hassel
88f7570caf add comments in electron.js 2022-03-29 21:24:17 +02:00
Karsten Hassel
00a7c6b5be added new env var ELECTRON_DISABLE_GPU which disable gpu under electron if set (fixes #2831). 2022-03-29 20:41:36 +02:00
Michael Teeuw
612b06346d Merge pull request #2829 from sdetweil/calfix 2022-03-29 19:55:20 +02:00
Michael Teeuw
9620566fbb Merge pull request #2830 from khassel/node-ical 2022-03-29 19:54:48 +02:00
Karsten Hassel
ed65c70a36 update node-ical to v0.15 and added luxon as dependency for not breaking the "no-optional" install (see #2718 and #2824). 2022-03-28 19:41:41 +02:00
Sam Detweiler
cdd87436be fix fullday/showend conflict #2629 2022-03-27 09:16:25 -05:00
Michael Teeuw
a143ebc8e0 Merge pull request #2827 from shin10/develop 2022-03-26 15:21:40 +01:00
Michael Trenkler
93581ca4d9 toggle pointer-events with suspension 2022-03-26 15:15:21 +01:00
Michael Teeuw
c5e9c9a683 Merge pull request #2825 from rejas/issue_2812 2022-03-22 21:41:55 +01:00
veeck
1bdab10872 Update dependencies 2022-03-22 21:29:49 +01:00
veeck
eca339ad60 Fix weird code in weathergov, still returns no data though 2022-03-22 21:11:44 +01:00
veeck
af5eb3447f Update CHANGELOG 2022-03-22 20:49:07 +01:00
rejas
b72cb52a71 Remove windspeed conversion from openweathermap 2022-03-22 20:46:15 +01:00
Michael Teeuw
d0565a15d3 Merge pull request #2820 from KristjanESPERANTO/german 2022-03-12 19:49:45 +01:00
Kristjan SCHMIDT
468bf1d6a0 Update German translation
- Remove unnecessary HTML Entities
- Completion of missing translations
- Rewordings
- Corrections of typography mistakes
2022-03-12 13:03:14 +01:00
Krekos
1d2637c980 Add missing CS translations 2022-03-12 12:21:20 +01:00
Michael Teeuw
a52c68d852 Merge pull request #2809 from Nerfzooka/patch-1
Revive contribution link
2022-03-11 15:46:16 +01:00
Michael Teeuw
e12f57d872 Merge pull request #2815 from 10bias/weather-provider-weatherflow
Weather provider weatherflow
2022-03-11 15:43:22 +01:00
Michael Teeuw
5def02ff7f Merge pull request #2810 from philnagel/weatherModule-weathergov-feelsLikeFix
fix parsing of feels like for weathergov
2022-03-11 15:39:11 +01:00
Michael Teeuw
7cab6eb680 Merge branch 'develop' into patch-1 2022-03-11 15:38:08 +01:00
Michael Teeuw
dcaa3526a4 Merge pull request #2817 from khassel/update_deps
Update dependencies
2022-03-11 15:36:43 +01:00
Michael Teeuw
2a989bc235 Merge pull request #2818 from kolbyjack/fix/changing-start-date
Don't adjust startDate for full day events if endDate is in the past
2022-03-11 15:36:25 +01:00
Jon Kolb
d588fab981 Don't adjust startDate for full day events if endDate is in the past 2022-03-02 22:09:15 -05:00
Karsten Hassel
9cb006b871 update dependencies 2022-02-28 23:08:56 +01:00
tobias
9056abaf4a add function to switch units 2022-02-26 11:09:41 +01:00
tobias
3c27fd10b6 fix a problem with sunrise 2022-02-25 18:30:01 +01:00
tobias
4048d79fc5 run lint:prettier 2022-02-22 11:14:28 +01:00
tobias
212e60c12d fix copy and paste mistake in comment 2022-02-22 11:12:17 +01:00
tobias
45142bc1bc updated the changelog 2022-02-21 14:58:48 +01:00
tobias
e791a663c8 add weatherprovider for weatherflow 2022-02-21 14:54:35 +01:00
Michael Teeuw
81ae95eba7 Merge pull request #2814 from KristjanESPERANTO/patch-2 2022-02-20 12:18:21 +01:00
Kristjan ESPERANTO
7ca5d81123 Update CHANGELOG.md 2022-02-20 02:30:47 +01:00
Kristjan ESPERANTO
e234d2379b Fix log output
Before:
```
MMM-OlympicGames - Load translationfalse: translations/de.json
translator.js:107 MMM-OlympicGames - Load translation  fallback: translations/en.json
```

After:
```
MMM-OlympicGames - Load translation: translations/de.json
translator.js:107 MMM-OlympicGames - Load translation fallback: translations/en.json
```
2022-02-20 02:24:22 +01:00
philna
d0838d53c2 fix parsing of feels like for weathergov 2022-02-08 21:07:08 -06:00
Michael Teeuw
880e2160a3 Merge pull request #2808 from KristjanESPERANTO/min-node-version-14 2022-02-07 09:41:36 +01:00
Nerfzooka
c214d776df Revive contribution link
Not sure if this is the contribution page that was intended, but the old link was pretty dead (https://docs.magicmirror.builders/getting-started/contributing.html).
2022-02-04 13:33:20 -05:00
Michael Teeuw
0a8220ca52 Merge pull request #2806 from khassel/update_deps
update to electron v17 and other deps
2022-02-02 09:08:59 +01:00
Michael Teeuw
b0c57272c2 Merge branch 'develop' into update_deps 2022-02-02 09:08:47 +01:00
Michael Teeuw
fbdebcae8a Merge pull request #2807 from KristjanESPERANTO/develop
Update Danish translation
2022-02-02 09:03:16 +01:00
Michael Teeuw
8e376deaaa Merge branch 'develop' into develop 2022-02-02 09:03:01 +01:00
Kristjan SCHMIDT
2d353ffa35 Minimal node version is 14
This was probably overseen in #2746
2022-02-02 01:47:04 +01:00
Kristjan SCHMIDT
b70a9c36fa Begin with capital letters 2022-02-02 00:48:50 +01:00
Kristjan SCHMIDT
c98967cb1e Update Danish translation 2022-02-02 00:46:05 +01:00
Kristjan SCHMIDT
05f0d1855c Update Danish translation
Translation from @The-Exterminator #2805
2022-02-01 23:51:01 +01:00
Karsten Hassel
cee5043625 update to electron v17 and other deps 2022-02-01 20:07:18 +01:00
Michael Teeuw
528358f3c3 Merge pull request #2795 from oscarb/patch-1 2022-01-28 11:23:15 +01:00
Michael Teeuw
da90412cea Merge pull request #2798 from khassel/cors 2022-01-28 11:22:43 +01:00
Michael Teeuw
e794aab012 Merge pull request #2799 from KristjanESPERANTO/develop 2022-01-28 11:21:50 +01:00
Michael Teeuw
69daf14ffd Merge pull request #2800 from khassel/update_deps 2022-01-28 11:21:14 +01:00
Karsten Hassel
afa6c4270d update dependencies including electron v17 beta 2022-01-27 22:05:33 +01:00
Kristjan SCHMIDT
be22309e45 Undo minor change: Version 1.0.0 wasn't MagicMirror² 2022-01-26 23:54:20 +01:00
Kristjan SCHMIDT
6f27e5ae07 MagicMirror -> MagicMirror² 2022-01-26 23:47:51 +01:00
Kristjan SCHMIDT
7e79547973 Add "Using a consistent spelling of MagicMirror²." 2022-01-26 23:16:22 +01:00
Kristjan SCHMIDT
a5668b1b99 Magic Mirror -> MagicMirror²
Consistent spelling
2022-01-26 23:09:26 +01:00
Karsten Hassel
f04dd6b6cd introduce useCorsProxy, per default only enabled for darksky and envcanada 2022-01-26 22:44:20 +01:00
Karsten Hassel
9f9c17ea8a increase test timeout to 20 sec. 2022-01-26 00:38:34 +01:00
Karsten Hassel
48845ab60e Merge branch 'develop' into cors
# Conflicts:
#	CHANGELOG.md
2022-01-26 00:31:02 +01:00
Karsten Hassel
59bc2318f8 fix weather tests and add CHANGELOG 2022-01-26 00:29:01 +01:00
Karsten Hassel
c622db918b working version, use corsUrl in weather providers 2022-01-25 23:42:42 +01:00
Michael Teeuw
4b381f33b5 Merge pull request #2794 from khassel/newsfeed-title-url
Newsfeed module: Add option to display title as url
2022-01-25 14:24:22 +01:00
Karsten Hassel
7cfc7b9d74 first cors approach 2022-01-25 00:43:57 +01:00
Oscar Björkman
7f52f9bcf2 Fix broken link to contribution guidelines 2022-01-23 01:55:56 +01:00
Karsten Hassel
c4e7e42cdd added new config option showTitleAsUrl to newsfeed module 2022-01-22 23:34:57 +01:00
Michael Teeuw
d181365620 Merge pull request #2788 from khassel/husky 2022-01-19 20:04:41 +01:00
Michael Teeuw
97b474665a Merge pull request #2787 from kolbyjack/broadcast_all_events 2022-01-19 20:03:49 +01:00
Karsten Hassel
284b01c524 husky changes 2022-01-17 21:39:21 +01:00
Jon Kolb
e3857ca0f4 Address review feedback 2022-01-17 10:48:16 -05:00
Jonathan Kolb
0aee42255f Return full list of events from createEventList for broadcasting 2022-01-17 10:07:12 -05:00
Michael Teeuw
fcb0d33e29 Merge pull request #2774 from khassel/remove-old-weather-modules 2022-01-14 23:11:12 +01:00
Karsten Hassel
3a761f2082 change link color to white 2022-01-14 22:54:42 +01:00
Karsten Hassel
ed0ad7b988 use fake modules instead 2022-01-14 22:16:07 +01:00
Karsten Hassel
c392b5a661 Merge branch 'develop' into remove-old-weather-modules
# Conflicts:
#	modules/default/currentweather/currentweather.js
2022-01-14 22:07:21 +01:00
Michael Teeuw
766848eea3 Merge pull request #2776 from SiderealArt/patch-1
Update zh-tw translation
2022-01-14 10:22:20 +01:00
Michael Teeuw
7ede537d1b Fix prettier issues. 2022-01-14 10:00:14 +01:00
Michael Teeuw
ec38f90662 Merge remote-tracking branch 'origin/develop' into pr/SiderealArt/2776 2022-01-14 09:58:05 +01:00
Michael Teeuw
d793b82d51 Merge pull request #2784 from khassel/e2e-tests 2022-01-14 07:10:26 +01:00
Michael Teeuw
aafeb1e875 Merge pull request #2785 from khassel/helmet-fix 2022-01-14 07:09:30 +01:00
Karsten Hassel
480c10b239 fix helmet, use defaults from v4 2022-01-14 00:05:08 +01:00
Karsten Hassel
82bb2544de add CHANGELOG and fix weather_spec 2022-01-13 22:46:10 +01:00
Karsten Hassel
b714abd9a0 Merge branch 'develop' into e2e-tests 2022-01-13 22:35:26 +01:00
Karsten Hassel
c1be0180f9 change weather_spec 2022-01-13 22:33:57 +01:00
Karsten Hassel
2cfafe7bfe improve waitForElement 2022-01-13 21:52:47 +01:00
Michael Teeuw
da8edbfdc1 Merge pull request #2783 from JHWelch/update-urls
Update urls
2022-01-13 09:44:07 +01:00
Jordan Welch
54bcbbc1de Update contribution link to active URL 2022-01-12 20:36:35 -06:00
Jordan Welch
5463183e01 Update config introduction to active URL. 2022-01-12 20:35:25 -06:00
Karsten Hassel
42b80b18f8 testing wait alternatives 2022-01-13 00:50:15 +01:00
Michael Teeuw
65fada7ef1 Merge pull request #2771 from CFenner/patch-1 2022-01-12 20:50:09 +01:00
Christopher Fenner
9e4997aa81 Merge branch 'develop' into patch-1 2022-01-12 20:39:09 +01:00
Michael Teeuw
e8c6ef945a Merge pull request #2782 from khassel/helmet 2022-01-12 20:18:01 +01:00
Karsten Hassel
14a22efae1 Merge branch 'develop' into helmet 2022-01-12 20:07:20 +01:00
Michael Teeuw
f7f0bc8681 Merge pull request #2780 from Tom-Hirschberger/develop 2022-01-12 19:52:07 +01:00
Michael Teeuw
9cb8a135e6 Merge pull request #2778 from khassel/roboto 2022-01-12 19:47:34 +01:00
Michael Teeuw
a02bce6517 Merge pull request #2766 from k1rd3rf/patch-1 2022-01-12 19:46:22 +01:00
Karsten Hassel
9604c3a187 helmet upgrade to v5 2022-01-11 21:51:09 +01:00
Tom Hirschberger
1ba4213910 set the hidden class by classList features instead of string manipulation now 2022-01-11 15:54:45 +01:00
Tom Hirschberger
6c63fac240 updated changelog 2022-01-11 15:52:56 +01:00
Tom Hirschberger
b3dd531abb removed adding of shown class 2022-01-11 15:52:00 +01:00
Tom Hirschberger
4ce42d4f70 added shown/hidden class feature to CHANGELOG 2022-01-11 15:25:37 +01:00
Tom Hirschberger
4325fcdca2 automatically add/remove a hidden/shown class to the module wrappers if they get hidden or shown 2022-01-11 15:20:33 +01:00
SiderealArt
8cf9d5f3ed Update CHANGELOG.md 2022-01-11 09:01:24 +08:00
Karsten Hassel
8cb6015930 refactor tests 2022-01-10 23:27:53 +01:00
Karsten Hassel
f3cefde7cb update deps and add CHANGELOG 2022-01-10 22:42:07 +01:00
SiderealArt
92fcdde60d Update zh-tw translation 2022-01-10 17:33:12 +08:00
Karsten Hassel
2dcf55ea89 change from deprecated roboto-fontface to @fontsource 2022-01-09 16:17:38 +01:00
Christopher Fenner
8537f40f1d Update calendar.js 2022-01-09 11:25:03 +01:00
Christopher Fenner
8417590893 update test case 2022-01-09 11:10:00 +01:00
Christopher Fenner
4c43f5db15 use alternative calendar icon 2022-01-09 08:38:48 +01:00
Karsten Hassel
eb32ec89b4 remove old weather modules 2022-01-08 23:08:41 +01:00
Michael Teeuw
ad66c02735 Merge pull request #2773 from khassel/weather-test-absolute 2022-01-08 07:12:35 +01:00
Karsten Hassel
e8c0611db4 increase test timeout 2022-01-07 22:00:31 +01:00
Karsten Hassel
7fa54642e1 Merge branch 'develop' into weather-test-absolute
# Conflicts:
#	CHANGELOG.md
2022-01-07 21:52:37 +01:00
Michael Teeuw
0e6d40f96c Merge pull request #2764 from oraclesean/patch-1 2022-01-07 21:44:46 +01:00
Karsten Hassel
cd1fe4e182 weather test for new absolute property 2022-01-07 21:38:56 +01:00
CFenner
c56b601ab8 adjust test case 2022-01-06 21:49:21 +01:00
Christopher Fenner
e78fa11fd4 Update CHANGELOG.md 2022-01-06 21:40:19 +01:00
Christopher Fenner
1619dd29e9 Apply suggestions from code review 2022-01-06 21:28:25 +01:00
CFenner
793f3dd75c Merge remote-tracking branch 'upstream/develop' into patch-1 2022-01-06 21:24:24 +01:00
Christopher Fenner
ce05f8e958 use solid type for font awesome icons 2022-01-05 13:28:07 +01:00
Christopher Fenner
c8c4585946 use solid type for font awesome icons 2022-01-05 13:27:20 +01:00
Christopher Fenner
990d1adfb2 use solid type for font awesome icons 2022-01-05 13:26:50 +01:00
Christopher Fenner
404343de1d use solid type for font awesome icons 2022-01-05 13:19:55 +01:00
Christopher Fenner
24bfaaca7e use solid type for font awesome icons 2022-01-05 13:18:08 +01:00
Christopher Fenner
060ca43fc8 use solid type for font awesome icons
related to #2768
2022-01-05 13:13:40 +01:00
Fredrik Oterholt
b87e802c8d Broken link for contribution guidelines
Replace with correct URL for the contribution guidelines
2022-01-04 09:18:29 +01:00
Sean Scott
0f596d5620 Update weather.js
Set absoluteDates default to false
2022-01-02 14:45:24 -07:00
Sean Scott
3ebdb67bc0 Update CHANGELOG.md
Update CHANCEGLOG
2022-01-02 11:44:01 -07:00
Sean Scott
a5668ef729 Update forecast.njk
Add an absolute date option to the weather module's forecast.
2022-01-02 11:35:29 -07:00
Michael Teeuw
24fccf6c44 Merge pull request #2762 from MichMich/prepare-2.19.0-develop
Prepare v2.19.0-develop.
2022-01-01 19:47:26 +01:00
Michael Teeuw
e12692252e Prepare v2.19.0-develop. 2022-01-01 19:37:55 +01:00
Michael Teeuw
a6cbc9f0ef Merge pull request #2761 from MichMich/develop
release v2.18.0
2022-01-01 19:31:41 +01:00
Michael Teeuw
be073daaf2 Merge pull request #2760 from MichMich/prepare-release-2.18.0
Prepare-release-2.18.0
2022-01-01 19:24:05 +01:00
Michael Teeuw
4c919a7489 Prepare 2.18.0. 2022-01-01 19:17:35 +01:00
Michael Teeuw
7a7ed48063 Merge pull request #2754 from kolbyjack/broadcast-custom-symbols 2021-12-29 09:14:00 +01:00
Michael Teeuw
01010fe795 Merge pull request #2753 from khassel/calendar-test 2021-12-29 09:13:11 +01:00
Karsten Hassel
32382dc461 Merge branch 'develop' into calendar-test 2021-12-28 13:45:58 +01:00
Jon Kolb
bf83341fb9 Merge branch 'develop' into broadcast-custom-symbols 2021-12-28 07:33:22 -05:00
Michael Teeuw
c4d2a7b409 Merge pull request #2751 from KristjanESPERANTO/patch-1
Add End-to-End Testing + Format
2021-12-28 13:23:54 +01:00
Michael Teeuw
4ad832dcdd Merge pull request #2755 from sdetweil/newcal 2021-12-27 17:59:50 +01:00
sam detweiler
b35d25eda4 Merge branch 'develop' into newcal 2021-12-27 08:29:09 -06:00
Michael Teeuw
da51a5512f Merge pull request #2756 from sdetweil/fixhttps 2021-12-27 10:19:34 +01:00
Sam Detweiler
710d51bf32 Merge branch 'fixhttps' of https://github.com/sdetweil/MagicMirror into fixhttps 2021-12-26 17:54:32 +01:00
Sam Detweiler
446bb229bc fix changelog 2021-12-26 17:53:28 +01:00
sam detweiler
c605023bad Merge branch 'develop' into fixhttps 2021-12-26 10:46:12 -06:00
Sam Detweiler
28d866c001 enable useHTTP for full electron version 2021-12-26 17:43:27 +01:00
Sam Detweiler
95b587381b add changelog entry 2021-12-26 16:43:14 +01:00
Sam Detweiler
fc14431147 fix date correction comparison for fulldate, just less than, not less or equal 2021-12-26 16:40:17 +01:00
Karsten Hassel
5d8f2f5da1 iterate over timezones 2021-12-26 16:27:40 +01:00
Jon Kolb
cf428dc1f7 Add CHANGELOG entry 2021-12-26 09:44:04 -05:00
Jon Kolb
c579989ded Perform customEvent symbols replacement in symbolsForEvent 2021-12-26 09:25:28 -05:00
Karsten Hassel
a954a62762 added test for calendar recurring event with checks the correct date displayed (related to #2752) 2021-12-26 14:30:38 +01:00
Kristjan Esperanto
855860c00c Update CONTRIBUTING.md 2021-12-26 06:54:46 +01:00
Kristjan Esperanto
58c48b1b21 End-to-End Testing + Format 2021-12-25 21:58:30 +01:00
Michael Teeuw
512c392386 Merge pull request #2748 from sdetweil/calfix 2021-12-24 18:00:06 +01:00
Sam Detweiler
c5ed70dbfc past prettier 2021-12-25 02:26:11 +11:00
Sam Detweiler
f75ca0c399 after prettier 2021-12-25 02:22:42 +11:00
sam detweiler
75c2977daf Merge branch 'develop' into calfix 2021-12-24 09:17:25 -06:00
Sam Detweiler
b3ef4b40c5 fix calendar across timezone, showEnd/FullDay 2021-12-25 02:10:48 +11:00
Michael Teeuw
bd5664cf8e Merge pull request #2746 from khassel/node-version-update 2021-12-23 18:56:50 +01:00
Karsten Hassel
272219eb61 Merge branch 'develop' into node-version-update 2021-12-23 18:48:40 +01:00
Michael Teeuw
acbcb47739 Merge pull request #2739 from rico24/develop
Added missing translations (NL)
2021-12-23 10:27:53 +01:00
Michael Teeuw
672575e427 Merge pull request #2747 from khassel/electron-tests
fix electron tests
2021-12-23 10:27:42 +01:00
Michael Teeuw
be0b3b1d63 Merge pull request #2745 from KristjanESPERANTO/shebang
Add missing shebang
2021-12-23 10:27:30 +01:00
Karsten Hassel
c88d25b4ee update node versions in templates and github workflows 2021-12-22 22:35:55 +01:00
Karsten Hassel
9b83862a96 fixed electron tests with retry, update dependencies 2021-12-22 22:31:39 +01:00
Kristjan SCHMIDT
21c7f0da6e Added missing shebang 2021-12-22 21:00:13 +01:00
Michael Teeuw
dca5759569 Merge pull request #2744 from MichMich/patch-use-https-for-shields
Use https for shield links.
2021-12-22 09:13:27 +01:00
Michael Teeuw
0abf87bfa2 Use https for links and images. 2021-12-22 09:08:43 +01:00
rico24
6b8b37843b Added missing translations 2021-12-21 20:50:11 +01:00
Michael Teeuw
fa0b83f056 Merge pull request #2738 from MichMich/patch-replace-broken-shields
Replace broken shields.
2021-12-21 20:36:21 +01:00
Michael Teeuw
a706fa3590 Add changelog. 2021-12-21 20:31:13 +01:00
Michael Teeuw
852aa3d260 Remove flacky badge. 2021-12-21 20:30:09 +01:00
Michael Teeuw
c4edcaad87 Replace broken shields. 2021-12-21 20:12:05 +01:00
Michael Teeuw
9d0cab01d5 Merge pull request #2736 from rejas/issue_2712
Allow dangerouslyDisablingAutoEscaping in newsfeed items
2021-12-21 19:44:03 +01:00
Michael Teeuw
c3e507234d Merge pull request #2737 from rejas/RelativeFullDayEvents
Resubmit of "Relative full day events"
2021-12-21 19:42:47 +01:00
rejas
405ec82dd0 Run prettier 2021-12-21 15:22:52 +01:00
rejas
9c4c77fe84 Merge branch 'develop' into RelativeFullDayEvents 2021-12-21 15:19:36 +01:00
rejas
831afdf9e7 Update CHANGELOG 2021-12-21 13:35:38 +01:00
rejas
1996efb183 Add new variable dangerouslyDisableAutoEscaping 2021-12-21 13:31:39 +01:00
rejas
19bb2a0238 Add safe option to newsfeed templates 2021-12-21 08:13:00 +01:00
Michael Teeuw
af6cf70558 Merge pull request #2734 from rejas/issue_2731 2021-12-20 19:41:31 +01:00
rejas
9b57e6049e Update CHANGELOG 2021-12-20 13:37:07 +01:00
rejas
f54c06fb94 Add test for old items 2021-12-20 13:35:50 +01:00
rejas
2107e7c427 Show empty message when no newsfeed items are available 2021-12-20 13:29:56 +01:00
Michael Teeuw
eae277f165 Merge pull request #2730 from khassel/newsfeed-user-agent 2021-12-14 05:59:57 +01:00
Karsten Hassel
a9c4ad2895 update User-Agent-Header in calendarfetcher 2021-12-13 23:00:07 +01:00
Karsten Hassel
e88ba1ab1c update dependencies 2021-12-13 21:34:32 +01:00
Karsten Hassel
677dcb87ef fixed User-Agent-Header for newsfeed module (#2729) 2021-12-13 21:24:37 +01:00
Michael Teeuw
7bb217636e Merge pull request #2723 from MariusVaice/patch-1
Added lithuanian language to translations.js
2021-12-13 20:42:31 +01:00
MariusVaice
abc16e98eb Update CHANGELOG.md 2021-12-03 17:29:48 +02:00
MariusVaice
3b1405609e Added lithuanian language 2021-12-03 17:28:59 +02:00
Michael Teeuw
b0d3518c1d Merge pull request #2716 from khassel/revert-node-ical
Revert node ical update
2021-11-29 19:49:26 +01:00
Karsten Hassel
aed005618e revert node-ical update 2021-11-29 00:21:00 +01:00
Michael Teeuw
559970c95d Merge pull request #2715 from khassel/playwright 2021-11-28 21:25:30 +01:00
Karsten Hassel
f6f3aa11ea add CHANGELOG.md 2021-11-27 23:50:49 +01:00
Karsten Hassel
472c91f022 update dependencies 2021-11-27 23:10:04 +01:00
Karsten Hassel
4a7f498683 replace spectron with playwright, rewrite electron tests 2021-11-27 23:00:02 +01:00
Michael Teeuw
2b87d6ec01 Merge pull request #2699 from khassel/test-updates 2021-11-06 15:12:00 +01:00
AmpioRosso
1af282a7a1 Don't display 'in xxx' for all day events when using relative times 2021-11-06 15:03:08 +01:00
AmpioRosso
46b63f52fe Updated changelog.md, fix for issue #2424 2021-10-24 18:12:05 +02:00
AmpioRosso
01977005fb Issue #2424 - Don't display start time of full date events when using relative dates. Also use the fullDateEventDateFormat when showing full day events more than a week out 2021-10-24 18:02:19 +02:00
Karsten Hassel
b1a46b365b increase load delay for modules tests 2021-10-22 22:49:10 +02:00
Karsten Hassel
3a0a02d3ba Merge branch 'develop' into test-updates
# Conflicts:
#	CHANGELOG.md
2021-10-20 21:22:12 +02:00
Karsten Hassel
1acbef5bfa increase testTimeout, minor changes tests e2e 2021-10-20 21:05:19 +02:00
Michael Teeuw
66b2ba07bb Merge pull request #2697 from jupadin/fixTimeOffset 2021-10-20 15:20:22 +02:00
Julian Dinter
4777538103 Updated CHANGELOG. 2021-10-20 15:11:41 +02:00
Julian Dinter
31d31fc3d3 [Fix] Fixed time zone calculation error on starting date of events with different time zones. 2021-10-20 15:05:40 +02:00
Michael Teeuw
21f606c6ba Merge pull request #2694 from rejas/codecov2 2021-10-17 20:04:55 +02:00
veeck
6508ec2d17 Update CHANGELOG 2021-10-17 19:09:43 +02:00
veeck
c623ca9fe0 Update other actions and dependencies 2021-10-17 19:02:22 +02:00
veeck
90deaf564f Update codecov action before the old one gets deprecated 2021-10-17 18:56:18 +02:00
Michael Teeuw
e2d2cf67fa Merge pull request #2693 from rejas/patch-1 2021-10-17 18:51:20 +02:00
Veeck
8ee73a11bb Update CHANGELOG
add credits where credits due :-)
2021-10-17 18:39:06 +02:00
Michael Teeuw
b5b01be373 Merge pull request #2691 from rejas/issue_2638 2021-10-17 17:07:12 +02:00
veeck
730f2eb0b8 Add fix for chaotic newsfeed display after network connection loss 2021-10-17 16:01:01 +02:00
Michael Teeuw
4576754de2 Merge pull request #2687 from khassel/weather-e2e 2021-10-16 03:15:16 +02:00
Karsten Hassel
0fb9e0bc89 increase test delay for alert module test 2021-10-16 00:25:31 +02:00
Karsten Hassel
000c34ef73 update dependencies 2021-10-16 00:18:52 +02:00
Karsten Hassel
0ec80a7791 move weather-test to e2e 2021-10-16 00:05:12 +02:00
Michael Teeuw
e9650285bd Merge pull request #2686 from fewieden/clean-up-alert-module
Clean up alert module
2021-10-15 09:08:23 +02:00
Felix Wiedenbach
d0cc0a4034 move notificationReceived 2021-10-15 06:50:54 +02:00
Felix Wiedenbach
f9c4a3a9c0 updated CHANGELOG.md 2021-10-15 06:44:58 +02:00
Felix Wiedenbach
15fd2021bb blur modules instead of black overlay 2021-10-15 06:43:53 +02:00
Felix Wiedenbach
75cf1d610e migrate manual DOM creation for alerts to nunjuck template 2021-10-15 06:31:07 +02:00
Felix Wiedenbach
8f5ee9466a move notificationFx styles into styles directory 2021-10-15 06:26:23 +02:00
Felix Wiedenbach
467720f1c4 migrate manual DOM creation for notifications to nunjuck template 2021-10-15 06:23:50 +02:00
Felix Wiedenbach
026e624e23 use alert as default in notificationReceived 2021-10-15 06:17:03 +02:00
Felix Wiedenbach
460a9fc5f7 move notification effect override into start function 2021-10-15 06:14:45 +02:00
Felix Wiedenbach
3695e64ce9 migrate set position DOM manipulation to css 2021-10-15 06:11:24 +02:00
Felix Wiedenbach
b3bb829e4d fix missing translation files 2021-10-15 06:05:06 +02:00
Felix Wiedenbach
1b9f8c23d2 shorthand camelcase functions 2021-10-15 06:03:51 +02:00
Michael Teeuw
5ee5fd7332 Cleanup Changelog. 2021-10-07 09:09:16 +02:00
Michael Teeuw
18d94b9a26 Merge pull request #2682 from MMRIZE/ko
fix language Code kr->ko and add missed translation
2021-10-07 09:07:33 +02:00
eouia
3ae9686b2b modify CHANGELOG.md 2021-10-06 21:32:31 +02:00
eouia
bbe4ef8497 fix Language ko 2021-10-06 15:58:16 +02:00
eouia
a2fd354dc9 fix ko language 2021-10-06 15:56:33 +02:00
Seongnoh Sean Yi
a7202078ce Merge branch 'develop' into ko 2021-10-06 15:44:31 +02:00
eouia
d8940a9cea Merge branch 'ko' of https://github.com/MMRIZE/MagicMirror into ko 2021-10-06 15:42:13 +02:00
eouia
96611333bf fix translations.js 2021-10-06 15:42:01 +02:00
Michael Teeuw
5074123f57 Merge pull request #2679 from fewieden/clean-up-updatenotifications-and-nunjuck-templating
Clean up updatenotifications and nunjuck templating
2021-10-06 15:24:02 +02:00
Michael Teeuw
5bfbd3eaa0 Merge branch 'develop' into clean-up-updatenotifications-and-nunjuck-templating 2021-10-06 15:21:54 +02:00
Michael Teeuw
7fdeed14f5 Merge pull request #2680 from rejas/issue_2678 2021-10-05 10:31:18 +02:00
veeck
04a9ca92b5 Update dependencies 2021-10-05 10:24:42 +02:00
Michael Teeuw
016a1e9adb Merge pull request #2683 from khassel/calendar-test 2021-10-05 08:30:23 +02:00
Karsten Hassel
1d12e57606 move calendar tests from category electron to e2e 2021-10-04 23:04:28 +02:00
Seongnoh Sean Yi
cb753f5371 Merge branch 'develop' into ko 2021-10-04 11:16:33 +02:00
eouia
7a9f9b2705 edit CHANGELOG 2021-10-04 11:15:06 +02:00
eouia
6d4b4dd9fc fix language Code kr->ko and add missed translation 2021-10-04 11:05:33 +02:00
rejas
3d9c92fb63 Update CHANGELOG 2021-10-02 23:05:10 +02:00
rejas
d4168f6b5d Use feels_like data from openweathermap instead of calculating it 2021-10-02 22:15:21 +02:00
Felix Wiedenbach
1751cabb9d remove obsolete snapshot 2021-10-02 14:56:13 +02:00
Felix Wiedenbach
b0e3b6414a bump ecmascript to 2018 for eslint 2021-10-02 14:42:49 +02:00
Felix Wiedenbach
51967ed9f5 shorthand function 2021-10-02 14:18:58 +02:00
Felix Wiedenbach
fc06f13c30 Merge branch 'develop' into clean-up-updatenotifications-and-nunjuck-templating
# Conflicts:
#	CHANGELOG.md
2021-10-02 14:15:54 +02:00
Felix Wiedenbach
b094707324 remove mocking from implementation and use jest to mock git cli instead 2021-10-02 14:08:16 +02:00
Michael Teeuw
e4a0a517d5 Merge 2.17.1 2021-10-01 18:58:26 +02:00
Michael Teeuw
222a5f3779 Merge pull request #2674 from rejas/issue_2671_master
Update electron to fix certificate errors
2021-10-01 18:48:07 +02:00
veeck
72f7106086 Update version 2021-10-01 18:31:29 +02:00
veeck
33387b60cc Update CHANGELOG 2021-10-01 18:30:06 +02:00
veeck
83cc18f648 Update electron to fix certificate errors 2021-10-01 18:27:29 +02:00
Michael Teeuw
1735ca57d5 Fix Formatting 2021-10-01 16:03:29 +02:00
Michael Teeuw
a49459b253 Prepare 2.18.0 Dev Branch 2021-10-01 15:25:54 +02:00
Michael Teeuw
5a4fbbf48a Merge pull request #2672 from MichMich/develop
Release 2.17.0
2021-10-01 15:17:49 +02:00
Michael Teeuw
f7465679c0 Prepare 2.17.0 Release 2021-10-01 15:06:26 +02:00
Michael Teeuw
2a5299ebcb Merge pull request #2670 from khassel/new-e2e 2021-10-01 14:41:44 +02:00
Karsten Hassel
b3bddb2c99 fix without_modules.js 2021-09-29 22:01:35 +02:00
Karsten Hassel
997aec8cc2 remove default values from test configs 2021-09-29 21:12:03 +02:00
Karsten Hassel
c67320f185 fix logger.js 2021-09-28 22:08:21 +02:00
Karsten Hassel
8224a6ac35 add custom.css before testing 2021-09-28 21:38:30 +02:00
Karsten Hassel
abcee8aa56 fix global-setup.js 2021-09-28 21:23:59 +02:00
Karsten Hassel
23c6b44921 Merge branch 'develop' into new-e2e
# Conflicts:
#	CHANGELOG.md
2021-09-28 21:12:04 +02:00
Karsten Hassel
1034171e91 add CHANGELOG 2021-09-28 21:10:24 +02:00
Michael Teeuw
bf49f79e6e Merge pull request #2669 from rejas/cleanup 2021-09-28 13:34:08 +02:00
veeck
f7f24dbdfe Update CHANGELOGE 2021-09-28 12:15:07 +02:00
veeck
31ec848aec Update dependencies 2021-09-28 12:14:58 +02:00
veeck
9eb08420b6 Add proper names to github action steps 2021-09-28 12:14:39 +02:00
veeck
eb6d8d4f83 Remove unused file 2021-09-28 12:14:16 +02:00
Michael Teeuw
e10f620cf9 Merge pull request #2664 from khassel/test-setup 2021-09-28 11:51:19 +02:00
Karsten Hassel
f750436b64 update module clock_es 2021-09-26 22:09:41 +02:00
Karsten Hassel
a4a8504558 update module clock 2021-09-26 22:02:29 +02:00
Karsten Hassel
385e5aabaa prettier 2021-09-26 00:29:03 +02:00
Karsten Hassel
d831315e20 update module newsfeed 2021-09-26 00:18:22 +02:00
Karsten Hassel
e0906f3462 update module helloworld 2021-09-25 23:52:38 +02:00
Karsten Hassel
6595b6a44f refactor global-setup.js 2021-09-25 23:45:34 +02:00
Karsten Hassel
0183d7a080 update modules alert and compliments 2021-09-25 23:37:37 +02:00
Karsten Hassel
89e803ee42 update without_modules.js and env_spec.js 2021-09-25 22:12:53 +02:00
Karsten Hassel
c0ce52abe3 change getDocument, delay needed, now 2 tests moved 2021-09-25 00:01:41 +02:00
Karsten Hassel
a1c7f20990 fix logger.js, move jsdom in startApplication 2021-09-24 21:30:51 +02:00
Karsten Hassel
0ef6f89d44 fix server.close() issue 2021-09-24 00:30:00 +02:00
Karsten Hassel
54b04962a8 snapshot 2021-09-23 22:52:32 +02:00
Karsten Hassel
60f8de282d snapshot 2021-09-23 00:05:30 +02:00
karsten13
b4350278a0 first tests 2021-09-21 23:48:29 +02:00
Felix Wiedenbach
332e429a41 updated CHANGELOG.md 2021-09-18 04:02:07 +02:00
Felix Wiedenbach
7be25c21ed use nunjuck template, es6, removed unused config option 2021-09-18 03:57:45 +02:00
Felix Wiedenbach
1dfa5d383c nunjuck template for updatenotification 2021-09-18 03:55:32 +02:00
Felix Wiedenbach
c2d2c278e0 fixed updatenotification async/await issues and more es6 2021-09-18 03:53:40 +02:00
Felix Wiedenbach
75a57829c2 fixed git helper async/await issues, template strings, clean up 2021-09-18 03:50:53 +02:00
Karsten Hassel
c3c5307624 fix basic-auth.js 2021-09-17 20:03:57 +02:00
Karsten Hassel
879d585f2e update dependencies, add CHANGELOG 2021-09-16 23:16:38 +02:00
Karsten Hassel
9969fede35 refactor e2e 2021-09-16 23:02:17 +02:00
Karsten Hassel
c15b31b374 close server 2021-09-15 21:23:43 +02:00
Karsten Hassel
a3a6c33b32 move translations_spec to e2e 2021-09-13 23:57:18 +02:00
Karsten Hassel
236bf6e0fc silence logger for tests, use modulePaths for e2e 2021-09-13 23:55:41 +02:00
Karsten Hassel
974de179e0 refactor tests in 3 categories unit, e2e and electron 2021-09-13 23:07:34 +02:00
Michael Teeuw
60e03777f3 Merge pull request #2662 from rejas/prettier 2021-09-11 16:10:40 +02:00
Michael Teeuw
05f6b2510f Merge pull request #2661 from khassel/mock_git 2021-09-11 16:09:04 +02:00
rejas
f3274977f5 Ignore config dir 2021-09-11 11:55:51 +02:00
rejas
cf7fb1a3b9 Update CHANGELOG 2021-09-11 11:20:00 +02:00
rejas
12457a87d4 Test on latest version of node 16 again 2021-09-11 11:17:12 +02:00
rejas
9a8de7db80 Run prettier again 2021-09-11 11:16:09 +02:00
rejas
68e02a528e Dont ignore everything when running prettier 2021-09-11 11:15:33 +02:00
Karsten Hassel
277055f44e added tests for updatenotification 2021-09-10 21:12:27 +02:00
Michael Teeuw
c3fc745c7e Merge pull request #2655 from khassel/logger
change use of logger.js in jest tests
2021-09-10 09:01:10 +02:00
Karsten Hassel
8901ed219d update dependencies 2021-09-09 23:56:30 +02:00
Karsten Hassel
d7c70dc021 fix logger.js 2021-09-09 23:30:36 +02:00
Karsten Hassel
53c789bff9 again logger.js due to problem with e2e test 2021-09-09 21:54:20 +02:00
Karsten Hassel
eb63745664 update tests in updatenotification_spec.js due to problems after merging PR's 2021-09-09 21:29:28 +02:00
Karsten Hassel
91d72e48ad Merge branch 'develop' into logger 2021-09-09 21:18:28 +02:00
Karsten Hassel
1dcda63192 add logger.js to jests moduleNameMapper 2021-09-09 21:17:16 +02:00
Karsten Hassel
3ea6544f77 remove logger special 2021-09-09 21:12:55 +02:00
Karsten Hassel
d12a587f11 Merge branch 'updatenotification' into logger 2021-09-09 20:51:58 +02:00
Karsten Hassel
2b147bb98b do logger mocking in logger.js, remove sandbox stuff from unit tests 2021-09-09 20:50:35 +02:00
Michael Teeuw
6529eaaf9a Merge pull request #2650 from rejas/weather 2021-09-09 20:30:37 +02:00
Michael Teeuw
a68aa148b8 Merge pull request #2651 from khassel/updatenotification 2021-09-09 20:30:15 +02:00
Karsten Hassel
98942d6f9c rename to GitHelper 2021-09-09 19:51:07 +02:00
Karsten Hassel
690efc0aff fix test 2021-09-09 00:48:39 +02:00
Karsten Hassel
627cfa1dff hold node v16 at v16.8 due to errors in e2e tests with v16.9 2021-09-09 00:31:24 +02:00
Karsten Hassel
99aca932db update dependencies 2021-09-09 00:24:13 +02:00
Karsten Hassel
dd43f35bbe add unit tests 2021-09-09 00:03:28 +02:00
Karsten Hassel
093988e136 extract git stuff in own class 2021-09-06 23:55:32 +02:00
rejas
087a472765 Fix tomezone test error as reported by @khassel 2021-09-06 21:15:23 +02:00
Karsten Hassel
ce13d7f98b added comments, get hash only for mm 2021-09-05 23:39:23 +02:00
Karsten Hassel
b1fc766908 update dependencies 2021-09-04 23:17:54 +02:00
Karsten Hassel
22384342db fix update notification, remove simple-git 2021-09-04 22:55:03 +02:00
rejas
badce5146a Update CHANGELOG 2021-09-04 22:54:58 +02:00
rejas
0bf3ff9c17 Refer to new docs page for development documentation 2021-09-04 22:49:40 +02:00
Karsten Hassel
860840c367 dirty working snapshot 2021-09-04 19:08:18 +02:00
rejas
221b6325f6 Cleanup some docs in the weather modules 2021-09-04 13:50:21 +02:00
Michael Teeuw
06389e35f9 Merge pull request #2648 from rejas/weather_cleanup
Add common methods to weatherobject
2021-09-03 11:08:43 +02:00
veeck
a7756cec13 Inline some functions 2021-09-02 20:37:41 +02:00
veeck
9ee11654a6 Update jsdocs 2021-09-02 20:35:43 +02:00
veeck
a273266e5e Remove useless returns and now unused jsdoc variables 2021-09-02 20:35:25 +02:00
rejas
e2158716d6 Revert "Update dependencies"
This reverts commit f49312ed13.
2021-09-01 20:50:43 +02:00
rejas
c132206543 Use new method in ukmetofficedatahub provider 2021-09-01 20:02:45 +02:00
rejas
f49312ed13 Update dependencies 2021-09-01 11:02:06 +02:00
rejas
a9f69f07e6 Update CHANGELOG 2021-08-31 23:43:33 +02:00
rejas
d7429a4812 Add eslint rule for === vs == and fix its occurence 2021-08-31 23:39:40 +02:00
rejas
be76d5ce9a Use new method in smhi provider 2021-08-31 23:34:22 +02:00
rejas
f2bc10c5c0 Add tests for new methods 2021-08-31 23:32:05 +02:00
rejas
43eb760bce Use isDayTime method 2021-08-31 22:01:30 +02:00
rejas
a7684e3e9f Add common method for determining if it is daytime 2021-08-31 21:55:43 +02:00
rejas
8949aa3bec Add common method for updating suntimes 2021-08-31 21:41:27 +02:00
Michael Teeuw
e40ddd4b69 Merge pull request #2643 from MMRIZE/develop
Add custom switches for electron mainWindow
2021-08-31 13:12:52 +02:00
Seongnoh Sean Yi
17637fb1f6 Merge branch 'develop' into develop 2021-08-31 10:50:05 +02:00
Michael Teeuw
f71defe958 Merge pull request #2641 from rejas/issue_2462
Actually use showTime parameter in clock module
2021-08-31 09:22:32 +02:00
Michael Teeuw
b8d6a6da1f Merge pull request #2645 from khassel/black_cursor
electron: disable black cursor on start
2021-08-31 09:21:39 +02:00
Karsten Hassel
fbc886b21c run prettier ... 2021-08-30 19:55:26 +02:00
Karsten Hassel
8879fb55de disable black cursor on start 2021-08-30 19:47:26 +02:00
rejas
ed316e8bf3 Try fixing test 2021-08-30 16:38:59 +02:00
eouia
45529f7de9 Add custom switches for electron mainWindow 2021-08-30 11:38:40 +02:00
eouia
dbdff38d2e Add custom switches for electron mainWindow 2021-08-30 11:32:24 +02:00
rejas
21c3179e03 Update dependencies 2021-08-29 20:07:29 +02:00
rejas
c05d93aed8 Update CHANGELOG 2021-08-29 20:05:42 +02:00
rejas
6225abb010 Fix showTime parameter 2021-08-29 20:00:55 +02:00
rejas
c41fff8f5c Add test for showTime parameter 2021-08-29 20:00:42 +02:00
Michael Teeuw
8589d9c482 Merge pull request #2634 from jupadin/fixTimeOffset 2021-08-29 14:03:07 +02:00
Michael Teeuw
7f264953af Merge pull request #2635 from rejas/clock_layout_fix 2021-08-29 14:01:02 +02:00
Julian Dinter
cfff2ad72b Changed comment regarding "ical.js" and "node-ical". 2021-08-25 17:28:37 +02:00
rejas
c0258b352e Update dependencies 2021-08-22 22:45:08 +02:00
rejas
3e1b051ec3 Fix layout of digital clock
ALignment was always set to center. Instead it now takes the positions alignment (left/center/right)
2021-08-22 22:34:11 +02:00
Julian Dinter
b34bb87d7a Added fix to CHANGELOG. 2021-08-22 14:27:18 +02:00
Julian Dinter
83b8cc6729 Ran npm run lint:prettier.. 2021-08-22 14:23:10 +02:00
Julian Dinter
878c0be727 [Fix] start time of calendar event gets corrected by time zone offset. 2021-08-22 14:16:02 +02:00
Julian Dinter
e7f06f5c0c Removed duplicated and thus superfluous debug messages. 2021-08-22 14:06:24 +02:00
Julian Dinter
a1fc38c5fe Prettified and added debug messages. 2021-08-22 13:57:24 +02:00
Michael Teeuw
ff0ab24000 Merge pull request #2631 from apiontek/develop 2021-08-15 23:26:35 +02:00
Michael Teeuw
56a10d192d Merge pull request #2627 from rejas/clock 2021-08-15 23:26:07 +02:00
Adam Piontek
1a8413d8f0 update weathergov provider to try fetching not just current but also forecast when API URLs available 2021-08-10 18:54:34 -04:00
rejas
934b156ebb Update CHANGELOG 2021-08-07 13:33:11 +02:00
rejas
f9639d9705 Refactor clock to allow finer placement of analog clock 2021-08-07 13:30:55 +02:00
Michael Teeuw
4c345c4f33 Merge pull request #2625 from rejas/jsdoc 2021-08-05 19:18:30 +02:00
rejas
490151267a Print warnings about jsdoc during testing too 2021-08-05 16:41:10 +02:00
rejas
3d19a08cc7 Update CHANGELOG 2021-08-05 16:40:49 +02:00
rejas
385c4c32f9 Update dependencies 2021-08-05 16:39:14 +02:00
rejas
3a5052c871 Final jsdoc comments 2021-08-05 16:38:57 +02:00
rejas
f84f590f1d Start filling last gaps of jsdoc 2021-08-03 08:25:57 +02:00
Michael Teeuw
5b9eba7819 Merge pull request #2621 from rejas/issue_2620 2021-08-02 18:55:35 +02:00
rejas
cd18794fca Update jsdocs 2021-08-01 09:53:28 +02:00
rejas
ae3d552ad7 Update dependencies 2021-08-01 09:52:04 +02:00
rejas
be5f71f4a7 Update CHANGELOG 2021-08-01 09:40:44 +02:00
rejas
745a5f0376 Move ignoreToday logic into template to fix undefined forecast 2021-08-01 09:39:07 +02:00
Michael Teeuw
99114b2a61 Merge pull request #2614 from khassel/update_templates 2021-07-21 08:30:40 +02:00
Karsten Hassel
df9bd2b0f9 Merge branch 'develop' into update_templates 2021-07-20 22:33:02 +02:00
Michael Teeuw
e194b559ac Merge pull request #2613 from rejas/cleanup 2021-07-20 21:06:05 +02:00
Karsten Hassel
af5344dccd update github templates 2021-07-14 21:25:12 +02:00
rejas
2d7b8121d7 Update CHANGELOG 2021-07-14 16:03:19 +02:00
rejas
0297450702 Remove eslint rules that now pass 2021-07-14 15:21:03 +02:00
rejas
6b17f6aa28 Final var conversions 2021-07-14 15:06:23 +02:00
rejas
8a7abfe42d Update dependencies 2021-07-14 15:05:43 +02:00
veeck
dd5041395c Run stylelint over all css files 2021-07-14 10:41:29 +02:00
veeck
36d6a5bc15 Start cleaning up some jsdoc 2021-07-14 10:41:29 +02:00
rejas
2619f92d09 More var -> let/const conversions 2021-07-14 10:41:29 +02:00
rejas
53720ae8ae Fix some eslint issues in the tests 2021-07-14 10:41:29 +02:00
veeck
bcff953fbb Fix warning in weather provider 2021-07-14 10:41:29 +02:00
veeck
bcc0cc599d Fix == usages 2021-07-14 10:41:29 +02:00
veeck
a1e3fed312 Disable eslint checks in deprecated weather modules 2021-07-14 10:41:29 +02:00
veeck
399dca2ef9 Make eslint complain about var usage 2021-07-14 10:41:29 +02:00
veeck
2e44e1626d Remove unused variables 2021-07-14 10:41:29 +02:00
veeck
39aa2dfe01 Run linter over all files again 2021-07-14 10:41:29 +02:00
veeck
099929c677 Actually test all js and css files when lint script is run 2021-07-14 10:41:29 +02:00
rejas
af5d132410 Rename global version variable 2021-07-14 10:41:29 +02:00
Michael Teeuw
79acbc3a98 Merge pull request #2611 from khassel/test_config
refactor test configs
2021-07-14 10:40:27 +02:00
Karsten Hassel
e75e4e2284 use process.cwd() for correct path 2021-07-14 00:12:59 +02:00
Karsten Hassel
9aa0af4f9c factory again 2021-07-05 22:07:33 +02:00
Michael Teeuw
50e272efba Fix release date. 2021-07-05 19:54:31 +02:00
karsten13
209e049893 run prettier 2021-07-05 19:51:18 +02:00
Karsten Hassel
bbb3accf0c use factory 2021-07-05 19:45:58 +02:00
Karsten Hassel
179989aa42 add missing comments 2021-07-05 19:21:39 +02:00
Karsten Hassel
2881d19d43 remove env.js 2021-07-05 18:27:14 +02:00
Karsten Hassel
7cfc3458ec fix tests 2021-07-05 18:08:56 +02:00
karsten13
659e1da79d run prettier 2021-07-05 00:40:01 +02:00
Karsten Hassel
a7ae79493d refactor test config 2021-07-05 00:35:28 +02:00
Michael Teeuw
8b484ee707 Merge pull request #2609 from khassel/electron13 2021-07-04 21:39:31 +02:00
Karsten Hassel
d617d4aa09 update package-lock.json 2021-07-04 20:54:54 +02:00
Karsten Hassel
99c04648b4 update CHANGELOG.md 2021-07-04 19:35:36 +02:00
karsten13
f945d50c0d run prettier 2021-07-04 19:21:17 +02:00
Karsten Hassel
e9fabd59ed contextIsolation: false 2021-07-04 18:30:43 +02:00
Karsten Hassel
ad13de3588 update electron to v13 2021-07-04 17:46:22 +02:00
Michael Teeuw
aad8141e27 Prepare v.2.17.0-develop 2021-07-01 14:34:02 +02:00
Michael Teeuw
26a76f80d6 Merge pull request #2607 from MichMich/develop
Release v2.16.0
2021-07-01 14:30:00 +02:00
Michael Teeuw
53ead2087f Prepare v2.16.0 2021-07-01 14:08:10 +02:00
Michael Teeuw
faee811d67 Merge pull request #2605 from rico24/patch-1
Update nl.json
2021-06-29 09:56:35 +02:00
Michael Teeuw
b9c739df1f Fix prettier issues. 2021-06-29 09:49:13 +02:00
rico24
6e124842e8 Merge branch 'develop' into patch-1 2021-06-28 22:38:10 +02:00
rico24
7a5928ea24 Update CHANGELOG.md 2021-06-28 21:03:05 +02:00
Michael Teeuw
7fdf7de11c Merge pull request #2600 from njwilliams/njw/openweathermap-onecall-fix 2021-06-25 11:32:49 +02:00
Michael Teeuw
b75eedb84e Merge pull request #2604 from khassel/update_deps 2021-06-25 11:01:08 +02:00
rico24
3b92ae49a9 Update nl.json
Added the missing variables based on the en.json file.
2021-06-25 10:58:05 +02:00
Nick Williams
775d1091db typo when not using /onecall - was getting forecast from wrong attr 2021-06-24 21:39:32 -04:00
Karsten Hassel
1f77b491fc update dependencies 2021-06-24 23:01:29 +02:00
Michael Teeuw
eff2fd7cc0 Merge pull request #2603 from khassel/dev_console_test 2021-06-24 08:38:49 +02:00
Karsten Hassel
58d2a0d874 Merge branch 'develop' into dev_console_test
# Conflicts:
#	CHANGELOG.md
2021-06-23 23:14:19 +02:00
Michael Teeuw
ea9def997a Merge pull request #2602 from khassel/jest 2021-06-22 08:31:05 +02:00
Karsten Hassel
a222c58047 use --forceExit running jest, define timeouts for github workflows 2021-06-21 22:27:36 +02:00
Michael Teeuw
39a838c2ab Merge pull request #2601 from khassel/jest 2021-06-20 14:45:41 +02:00
Karsten Hassel
cfc0bcd5ad workaround for dev_console test using getWindowCount 2021-06-18 21:39:55 +02:00
Karsten Hassel
3418c9b50f revert changes to dev_console test (has impact on other tests), add CHANGELOG 2021-06-18 12:42:13 +02:00
Nick Williams
e686611890 update One Call API results for openweathermap
The results from the /onecall endpoint were not
being parsed correctly in current and forecast mode - it was
assuming the current/forecast endpoint API, and the return
datasets are different. The effect was that the module
would simply display "Loading..." when in /onecall mode, since
it has no way of displaying error status (ideally, it should,
but leave that for another day)
2021-06-17 19:30:56 -04:00
karsten13
ebb5dee1fc run prettier 2021-06-17 23:19:57 +02:00
Karsten Hassel
d9edaffd9c reset changes on js/logger.js, mock logger.js in global_vars tests, workaround for failing dev_console test 2021-06-17 22:50:26 +02:00
Michael Teeuw
cbe7b1a5b9 Merge pull request #2599 from khassel/jest 2021-06-17 07:06:36 +02:00
Karsten Hassel
e758fd4093 add CHANGELOG 2021-06-16 20:54:03 +02:00
Karsten Hassel
14a99a3b25 Merge branch 'develop' into jest 2021-06-16 20:53:02 +02:00
Karsten Hassel
1ba67506a0 fix logger.js after jest changes 2021-06-16 20:51:32 +02:00
Michael Teeuw
2a6ca5d5ac Merge pull request #2581 from Crazylegstoo/envCanadaFix
Fix to Environment Canada weather provider
2021-06-16 10:07:48 +02:00
Michael Teeuw
aa12e6495a Merge pull request #2593 from khassel/jest
replace mocha with jest
2021-06-16 10:06:32 +02:00
Karsten Hassel
9269848f66 Merge branch 'develop' into jest
# Conflicts:
#	CHANGELOG.md
2021-06-15 23:32:22 +02:00
Karsten Hassel
a71e61cd30 use short if in weatherforecast.js, add comment to logger.js 2021-06-15 23:29:07 +02:00
Michael Teeuw
8be4604c97 Merge pull request #2580 from r3wald/feature/newsfeed-show-as-list
feature: show newsfeed as list
2021-06-15 11:14:15 +02:00
Michael Teeuw
a7bba903f5 Merge branch 'develop' into envCanadaFix 2021-06-15 11:13:28 +02:00
Karsten Hassel
1d8af5835d Merge branch 'develop' into jest
# Conflicts:
#	package-lock.json
#	package.json
2021-06-14 23:21:08 +02:00
Karsten Hassel
189c01fc74 update moduleNameMapper 2021-06-14 22:45:21 +02:00
Karsten Hassel
e3a5bbf661 use expect where missing 2021-06-14 21:07:38 +02:00
Karsten Hassel
ee23c5f72c update clock tests, remove testSequencer 2021-06-14 19:21:21 +02:00
Karsten Hassel
a2083be76b test order 2021-06-14 00:45:40 +02:00
Michael Teeuw
a1c4be83d6 Merge pull request #2578 from rejas/docs 2021-06-13 19:27:24 +02:00
Karsten Hassel
d2fde2bfc8 fix segment faults in node16 on ubuntu 2021-06-12 23:53:04 +02:00
Karsten Hassel
e8956b0b55 update package-lock.json 2021-06-12 22:52:05 +02:00
Karsten Hassel
2af4009a93 change missing translations log, disable console.log in unit tests 2021-06-12 22:26:02 +02:00
Karsten Hassel
a5f7c946cc jest setup changes, increase setMaxListeners, disable console.log in tests/unit/global_vars 2021-06-12 21:26:08 +02:00
Karsten Hassel
298542b531 enable missing translation messages 2021-06-11 23:42:24 +02:00
Karsten Hassel
fca6707a29 add eslint-plugin-jest 2021-06-11 23:11:30 +02:00
Karsten Hassel
10d3a284e9 fix js lint warnings 2021-06-11 23:10:41 +02:00
karsten13
1a244726aa run prettier 2021-06-11 22:24:21 +02:00
Karsten Hassel
044935a164 add jest options 2021-06-11 22:18:05 +02:00
Karsten Hassel
99e5edf2c5 add CHANGELOG.md 2021-06-11 01:00:05 +02:00
Karsten Hassel
b26270bd13 fix helloworld_spec tests 2021-06-11 00:36:53 +02:00
Karsten Hassel
65a8cb9ddb remove unused references 2021-06-11 00:32:54 +02:00
Karsten Hassel
ba4b976e80 test config 2021-06-10 00:24:08 +02:00
veeck
297ae1dbaf Try updating dependencies 2021-06-09 13:09:28 +02:00
veeck
214614f740 Cleanup jsdoc via eslint 2021-06-09 12:49:57 +02:00
Karsten Hassel
0e14d3d6e8 snapshot e2e 2021-06-09 00:19:43 +02:00
Karsten Hassel
67011c0c32 snapshot e2e 2021-06-08 00:47:40 +02:00
Karsten Hassel
16bbb42b8d remove chai from unit tests 2021-06-08 00:47:15 +02:00
Kevin G
b85ac91e6c Corrected bullet for Updated list
Wording/location corrected for update to WEATHER module
2021-06-07 09:28:33 -04:00
Karsten Hassel
66759a33fa unit tests 2021-06-07 00:16:53 +02:00
Kevin G
bf5e83861c Highlight new custom precipitation unit-of-measure for WEATHER
With the new Environment Canada provider in WEATHER, it is now possible for provider to set a customized precipitation unit-of-measure
2021-06-05 11:39:21 -04:00
Robert Ewald
d56a6fb06f disabled by default. 2021-06-03 12:45:08 +02:00
Robert Ewald
5e7aa8e16d changelog updated. 2021-06-03 12:42:39 +02:00
Robert Ewald
bace0ad339 indentation in template fixed. 2021-06-03 12:38:54 +02:00
Robert Ewald
6014eaf8eb Merge remote-tracking branch 'upstream/develop' into feature/newsfeed-show-as-list 2021-06-03 12:27:26 +02:00
Robert Ewald
3e96e8b3f5 newsfeed: showAsList implemented. 2021-06-03 11:40:18 +02:00
Kevin G
95d1b8a6d0 Corrected formating 2021-06-02 16:25:53 -04:00
Kevin G
0ecb66c99e Fixed precipitation unit conversion logic
Found an error that precipitation amount was not being calculated correctly when config.js is asking for Imperial units. This has been fixed.
2021-06-02 16:15:44 -04:00
Kevin G
af52b91799 Fix to precipitation logic
Found a really dumb error I made that broke compatibility with OpenWeatherMap hourly forecast under certain conditions. This is now fixed.
2021-06-02 11:43:56 -04:00
veeck
3c50d6c30a Update CHANGELOG 2021-05-29 16:23:53 +02:00
veeck
2722c72c43 Update issue template 2021-05-29 16:23:41 +02:00
rejas
d5ab3101c6 Update links to config documentation 2021-05-29 16:23:41 +02:00
Michael Teeuw
32df76bdff Merge pull request #2577 from rejas/prettier 2021-05-29 16:22:52 +02:00
rejas
68a06c3d1d Update CHANGELOG 2021-05-29 16:13:24 +02:00
rejas
1b42dc779b Run prettier again 2021-05-29 16:11:39 +02:00
rejas
cedffd40f2 Lint all files, exclude others 2021-05-29 16:11:25 +02:00
Michael Teeuw
cdc8db4837 Merge pull request #2576 from rejas/calendar_fix 2021-05-29 14:02:13 +02:00
Michael Teeuw
a0ee23d84e Merge pull request #2568 from Crazylegstoo/envcanada 2021-05-29 14:01:04 +02:00
Michael Teeuw
49d2d8c9d0 Merge pull request #2574 from daniel-windsor/forecase-ignore-today 2021-05-29 13:59:55 +02:00
rejas
63620aa811 Update CHANGELOG 2021-05-29 13:33:41 +02:00
rejas
d5b11a1dba Update dependencies 2021-05-29 13:32:24 +02:00
rejas
4a63af0490 Set error to null once the events are coming again 2021-05-29 13:22:31 +02:00
Michael Teeuw
a68019293f Merge pull request #2567 from khassel/fix_husky 2021-05-27 07:14:56 +02:00
Karsten Hassel
6b1c91f0dd Merge branch 'develop' into fix_husky
# Conflicts:
#	CHANGELOG.md
2021-05-26 20:20:55 +02:00
Michael Teeuw
ea93785581 Merge pull request #2552 from rejas/error_handling 2021-05-26 20:12:17 +02:00
Karsten Hassel
32819c4fd5 Merge pull request #1 from rejas/fix_pretty_quick_too
Fix call to pretty quick, make husky script executable
2021-05-26 19:49:36 +02:00
Daniel Windsor
fc5a438cdc Add flag that removes today entry in forecast 2021-05-26 21:45:24 +12:00
veeck
57fe94f945 Fix call to pretty quick, make husky script executable 2021-05-26 11:39:29 +02:00
Robert Ewald
aa3a3bdf16 server data for newsfeed list. 2021-05-25 09:29:48 +02:00
Kevin G
db89da3daa Ran prettier 2021-05-21 12:11:58 -04:00
Kevin G
d6ba5796ce ran prettier... 2021-05-21 11:48:56 -04:00
Kevin G
3968743b28 ran prettier... 2021-05-21 11:45:54 -04:00
Kevin G
20c6226b84 Update for new weather provider 2021-05-21 10:20:16 -04:00
Kevin G
463ce394fe ran prettier... 2021-05-21 10:13:39 -04:00
Kevin G
1faefebe42 Formatting correction for min/max display 2021-05-21 10:06:54 -04:00
Karsten Hassel
e2c9339ec4 fix husky setup in prod environment 2021-05-19 20:58:29 +02:00
rejas
4b1c7da171 Update jsdoc 2021-05-19 11:12:56 +02:00
rejas
bdfd6e5e9f Fix calendar test 2021-05-19 11:12:56 +02:00
rejas
4c8508b0a9 Fix newsfeed test 2021-05-19 11:12:56 +02:00
veeck
06b3f92963 Refaktor calendar error once again for better messaging to the user 2021-05-19 11:12:56 +02:00
veeck
d43a57af36 Refaktor newsfeed error once again for better messaging to the user 2021-05-19 11:12:56 +02:00
rejas
aeefe28710 Update jsdoc 2021-05-19 11:12:56 +02:00
rejas
e9de961a23 Cleanup prohibited words filter code and test data 2021-05-19 11:12:56 +02:00
rejas
dcec778e02 Update CHANGELOG 2021-05-19 11:12:56 +02:00
rejas
b212641069 Move checkFetchStatus into NodelHelper 2021-05-19 11:12:42 +02:00
rejas
90aa50bb11 Add fetcher_helper for calendar and newsfeed 2021-05-19 11:12:42 +02:00
rejas
a6879e853b Fix tests 2021-05-19 11:12:42 +02:00
rejas
37fab7ac63 Update error handling for newsfeed and calendar 2021-05-19 11:12:42 +02:00
Michael Teeuw
8b01ae08c5 Merge pull request #2566 from khassel/update_deps 2021-05-19 11:07:43 +02:00
Michael Teeuw
b1cdf42790 Merge pull request #2565 from rejas/css_gap 2021-05-19 11:07:10 +02:00
Karsten Hassel
974968d238 update CHANGELOG.md 2021-05-18 20:44:43 +02:00
karsten13
bf467cbba5 add package-lock.json 2021-05-18 20:42:06 +02:00
Karsten Hassel
536aa2e96e update dependencies and migrate husky to v6 2021-05-18 20:36:56 +02:00
rejas
3d84344b75 Revert "Update dependencies, run linter"
This reverts commit 1054ba3b1e.
2021-05-14 22:06:55 +02:00
rejas
1054ba3b1e Update dependencies, run linter 2021-05-14 21:46:18 +02:00
rejas
aa8ddb9a92 Update CHANGELOG 2021-05-14 21:20:33 +02:00
rejas
fcfe57e5e2 Add more properties for finer body margins 2021-05-14 17:42:23 +02:00
Kevin G
c4fd4e0317 New provider - Environment Canada
Added a new provider that pulls weather data - current, forecast, and hourly - from Environment Canada (Canadian gov weather service). This provider supports Canadian locations only. Documentation will be provided that outlines specific behaviours of this provider and required 'weather' modules parms for Env Canada.
2021-05-13 11:12:30 -04:00
Kevin G
3c76933824 Edit Update
Formatting
2021-05-13 11:00:43 -04:00
Kevin G
fa83819bee Edit update
Formatting
2021-05-13 10:59:45 -04:00
Kevin G
b65ae88879 Edit update
Formatting
2021-05-13 10:58:08 -04:00
Kevin G
96db21f9bf Updates to support Environment Canada provider
Code updates to support a new weatherobject element called precipitationUnits. For the 'forecast' and 'hourly' UI, the weather module will use this new element if it is not null (i.e. a provider has pushed some value into this object element). If the element is null, then default 'units' processing will still occur. This allows a provider to have a customn unit of measure for precipitation - e.g. Env Canada will use mm for rain and cm for snow.
2021-05-13 10:56:30 -04:00
veeck
6d356ff770 Introduce font-size-small property and use it in calendar too (clean it up too) 2021-05-08 18:29:53 +02:00
veeck
21790b32bf Use hex notation instead of verbose name like everywhere else 2021-05-08 18:07:38 +02:00
rejas
159f3d0aa2 Replace deprecated symbol with modern solution 2021-05-08 18:05:30 +02:00
veeck
012a7b0678 Convert one px to rem 2021-05-08 18:04:14 +02:00
veeck
6595c85671 Remove useless(?) margins 2021-05-08 18:04:14 +02:00
veeck
cf5c0464fe Use custom property for gaps between modules 2021-05-08 18:04:14 +02:00
Michael Teeuw
e31450f731 Merge pull request #2560 from foundations-design/contribute 2021-05-08 08:50:00 +02:00
Michael Teeuw
3653984a95 Merge pull request #2562 from rejas/config_check 2021-05-08 08:49:10 +02:00
Michael Teeuw
0c0b856c37 Merge pull request #2561 from khassel/develop 2021-05-08 08:48:43 +02:00
Michael Teeuw
69f1b153ea Merge pull request #2559 from rejas/issue_2547 2021-05-08 08:46:44 +02:00
veeck
43ba4bd00e Fix calendar debug 2021-05-07 12:28:55 +02:00
veeck
bf5edcaac6 Fix failing config check when es6 notation is used 2021-05-07 12:23:29 +02:00
Karsten Hassel
ac51709211 update dependencies in package.json, require node >= v12, remove rrule and rrule-alt 2021-05-04 21:49:16 +02:00
Earl Man
e1a578e819 add myself to CHANGELOG.md 2021-05-04 09:25:36 -05:00
rejas
591c9e53b0 Update CHANGELOG 2021-05-02 15:07:49 +02:00
rejas
87d543eb3a Finish test case 2021-05-02 15:07:02 +02:00
rejas
40c1521591 Add test data 2021-05-02 14:55:45 +02:00
Michael Teeuw
b31c2a6264 Add contributors list.
The full list will be checked during release.
2021-04-29 10:32:20 +02:00
Michael Teeuw
66a42f13f1 Merge pull request #2546 from rejas/move 2021-04-27 16:27:55 +02:00
veeck
8de6ebbbd1 Update dependencies 2021-04-27 13:14:27 +02:00
rejas
649de694ed Update CHANGELOG 2021-04-27 13:14:27 +02:00
veeck
8a52fde8fc Move codecov.yml into github dir to other configs 2021-04-27 13:14:07 +02:00
rejas
fb8bd657de Move weatherforecast mock data in better suited directory 2021-04-27 13:14:07 +02:00
Michael Teeuw
b04a0a6b61 Merge pull request #2556 from khassel/updatenotification 2021-04-27 07:21:06 +02:00
Michael Teeuw
f29c911a0f Merge pull request #2557 from khassel/node16 2021-04-27 07:20:31 +02:00
Karsten Hassel
bd908123c2 replace node v10 with v16 in github workflow 2021-04-26 22:29:23 +02:00
Karsten Hassel
cbdb0b67ab fix updatenotification.js increasing timeout 2021-04-26 22:24:07 +02:00
Michael Teeuw
aa3848f420 Merge pull request #2544 from rejas/es6_conversion 2021-04-18 18:14:25 +02:00
rejas
6cf0748172 Update CHANGELOG 2021-04-18 18:07:26 +02:00
rejas
11122d3f81 Use es6 notation in clientonly 2021-04-18 15:36:00 +02:00
rejas
2dea9398f2 Use es6 notation in updatenotification module 2021-04-18 15:35:41 +02:00
rejas
de93b3294f Use es6 notation in main.js 2021-04-18 15:29:10 +02:00
rejas
7accb84eb9 Use es6 notation in config sample 2021-04-18 15:06:28 +02:00
rejas
ea90ed04d6 Use es6 notation in vendor 2021-04-18 15:06:21 +02:00
rejas
838eed2630 Use es6 notation in weatherprovider 2021-04-18 15:06:06 +02:00
rejas
376b65c749 Use es6 notation in serveronly 2021-04-18 14:52:04 +02:00
rejas
3b4432cb00 Use es6 notation in defaults 2021-04-18 14:51:57 +02:00
rejas
7bc71029de Use es6 notation in socketclient 2021-04-18 14:51:50 +02:00
rejas
d736dd92be Use es6 notation in tests 2021-04-18 14:51:28 +02:00
rejas
6eba8d681c Use es6 notation in module 2021-04-17 16:29:38 +02:00
rejas
5fe654c19d Use es6 notation in loader 2021-04-17 16:29:38 +02:00
rejas
dd366f35a8 Use es6 notation in test configs 2021-04-17 16:29:38 +02:00
rejas
2ababa521d Use es6 notation in weather module and ukmet provider 2021-04-17 16:29:38 +02:00
rejas
bda8f26511 Use es6 notation in compliments module and cleanup jsdoc 2021-04-17 16:29:38 +02:00
rejas
7f1a3df25b Use es6 notation in font tests 2021-04-17 16:29:38 +02:00
rejas
ef2ff50089 Use es6 notation in clock module 2021-04-17 16:29:38 +02:00
rejas
0b3964c827 Use es6 notation in helloworld tests 2021-04-17 16:29:38 +02:00
Michael Teeuw
ccf5bb9342 Merge pull request #2540 from rejas/patch-1 2021-04-17 15:52:38 +02:00
Michael Teeuw
4303882c6a Merge pull request #2542 from rejas/codecov 2021-04-17 15:50:49 +02:00
Michael Teeuw
c34028d549 Merge pull request #2541 from rejas/css 2021-04-17 15:50:18 +02:00
rejas
8e76cdcb57 Revert "Remove some tests to see if it still errors"
This reverts commit 0abebc1e32.
2021-04-17 13:26:16 +02:00
rejas
5eb66106b9 Rename yaml file 2021-04-17 13:17:24 +02:00
rejas
0abebc1e32 Remove some tests to see if it still errors 2021-04-17 13:05:06 +02:00
rejas
552e82f44d Update CHANGELOG 2021-04-17 12:35:46 +02:00
rejas
ada40e36db Add codecov yaml to set informational mode 2021-04-17 12:35:10 +02:00
rejas
480f734a06 Update CHANGELOG, undo accidental commit in clock.js 2021-04-17 12:20:25 +02:00
rejas
20bee9c334 Update dependencies 2021-04-17 11:19:39 +02:00
rejas
de8267f41e Fix css comment style 2021-04-17 10:27:36 +02:00
rejas
7bfaf07980 Use custom properties colors more often 2021-04-17 09:50:12 +02:00
rejas
a42fa8e9f9 Adjust default css to match styles from befor custom properties merge 2021-04-17 09:38:33 +02:00
Veeck
5facad683a Add link to subforum to README
As propose din #2539
2021-04-17 06:46:30 +02:00
Michael Teeuw
fd1913a72e Merge pull request #2538 from codac/master 2021-04-15 16:16:51 +02:00
Michael Teeuw
54c98b4250 Merge pull request #2537 from rejas/custom-properties 2021-04-15 16:15:27 +02:00
config
5d99baac21 Fixing fetch option httpsAgent to agent in calendar module (#466) 2021-04-15 14:51:33 +02:00
veeck
cb95bdf6d7 Undo script changes, Update CHANGELOG 2021-04-14 17:03:09 +02:00
veeck
25e803abfc Update custom.css.sample 2021-04-14 16:56:29 +02:00
veeck
7c6073e4ef Undo README changes 2021-04-14 16:45:38 +02:00
veeck
09bcbe8dfc Update dependencies and lock files 2021-04-14 16:45:07 +02:00
veeck
e0a9c7d0bb Merge branch 'develop' into contribute 2021-04-14 16:42:54 +02:00
Michael Teeuw
ac27d05fd5 Merge pull request #2532 from ezeholz/develop
Alert Module not recognizing multiple alerts
2021-04-14 16:17:45 +02:00
Ezequiel Holzweissig
d6ab56252f Merge branch 'develop' into develop 2021-04-13 20:56:32 -03:00
Michael Teeuw
dab178ed50 Merge pull request #2536 from B1gG/develop
Develop
2021-04-13 14:38:17 +02:00
B1gG
6ed50b6a75 updating changelog 2021-04-13 07:39:14 +01:00
B1gG
ee559ec650 to use dateFormat when timeFormat is relative 2021-04-13 07:29:11 +01:00
Michael Teeuw
4310238418 Merge pull request #2527 from rejas/patch-1 2021-04-12 21:03:27 +02:00
veeck
10c47a6c38 Really run prettier 2021-04-12 19:33:23 +02:00
veeck
4b8043086e Merge branch 'develop' into patch-1 2021-04-12 19:28:51 +02:00
Michael Teeuw
fd952b88bf Merge pull request #2535 from rejas/issue_2530 2021-04-12 19:23:07 +02:00
Ezequiel Holzweissig
e262d463c5 Merge branch 'develop' into develop 2021-04-12 14:20:33 -03:00
veeck
b02bce2510 Merge branch 'develop' into issue_2530 2021-04-12 19:11:24 +02:00
Michael Teeuw
58094531c0 Merge pull request #2534 from KristjanESPERANTO/develop 2021-04-12 19:08:36 +02:00
Michael Teeuw
d937e3ca7c Merge pull request #2533 from rejas/alert_test 2021-04-12 19:07:55 +02:00
Kristjan SCHMIDT
9c58413209 Put decimalSymbol at the end of the row like in the Current part 2021-04-12 18:34:33 +02:00
rejas
db65ff12d7 Update CHANGELOG 2021-04-11 22:49:26 +02:00
rejas
f93b819ea6 Fix missing await calls in tests 2021-04-11 22:42:21 +02:00
rejas
e6fea297e5 Add test for decimalSymbol in weatherforecast 2021-04-11 22:25:17 +02:00
Kristjan SCHMIDT
5c2a0e5634 Fix decimalSymbol for forcast 2021-04-11 22:25:15 +02:00
rejas
71aa21a82d Update dependencies 2021-04-11 21:07:26 +02:00
rejas
23821360c7 Update CHANGELOG 2021-04-11 21:03:35 +02:00
rejas
79f5b938f5 Add first test for alert module 2021-04-11 20:59:53 +02:00
Michael Teeuw
f8769fcc2a Merge pull request #2531 from KristjanESPERANTO/patch-1 2021-04-11 12:45:23 +02:00
Ezequiel Holzweissig
a3ed24c766 Prettifier 2021-04-10 20:38:33 -03:00
Ezequiel Holzweissig
82727b825c Fix alerts
Fixes #2522
2021-04-10 20:15:32 -03:00
Kristjan Esperanto
16e98496af Remove duplicate codecov badge
The second badge doesn't make sense, does it?
2021-04-10 23:55:11 +02:00
rejas
62896ce1a3 Merge branch 'develop' into patch-1 2021-04-10 20:37:47 +02:00
Michael Teeuw
7ea5b1ecbf Merge pull request #2526 from FrancoisRmn/add-some-translations 2021-04-10 20:36:11 +02:00
rejas
eecc95f8fb Run linter 2021-04-10 20:34:27 +02:00
FrancoisRmn
85808d85c4 add some translations 2021-04-10 17:56:47 +02:00
Gerardo Gonzalez
12cc670642 Merge branch 'develop' into patch-1 2021-04-10 14:04:53 +01:00
Michael Teeuw
13fcb55df6 Merge pull request #2518 from oemel09/hide-newsfeed-description 2021-04-10 14:56:49 +02:00
oemel09
a4dfd15888 Fixes tests for newsfeed description 2021-04-10 09:13:14 +02:00
Gerardo Gonzalez
7fbd326298 Update CHANGELOG.md
updating the changelog for the fix Fix wrong treatment of `appendLocationNameToHeader` when using `ukmetofficedatahub`
2021-04-10 01:30:12 +01:00
Gerardo Gonzalez
331d147d50 Update ukmetofficedatahub.js
There is a wrong treatment to appendLocationNameToHeader.
The location should be always returned and leave the weater.js to decide if is included or not in the header.
2021-04-10 01:20:52 +01:00
oemel09
acdcdc55bc Adjusts parenthesis in template to be consistent 2021-04-09 08:07:37 +02:00
oemel09
fdb0c0acb3 Adds test to check that newsfeed description does not show up 2021-04-09 08:05:19 +02:00
Earl Man
564bf47fb2 update sample css 2021-04-08 14:56:01 -05:00
Earl Man
eca35b2371 change --base to --font-size 2021-04-08 14:51:07 -05:00
Earl Man
f97d2e2644 revert 'dimming' method 2021-04-08 14:48:23 -05:00
Earl Man
fdd6659139 delete useless file 2021-04-08 14:39:21 -05:00
Earl Man
0d698fb659 Merge remote-tracking branch 'upstream/develop' into contribute 2021-04-08 14:35:39 -05:00
Earl Man
c68b39dda8 Merge branch 'css-properties' 2021-04-08 14:24:39 -05:00
oemel09
cb67286bc3 Adds test to validate that description is shown 2021-04-08 17:00:15 +02:00
oemel09
a49962b8de Moves validation into template 2021-04-08 17:00:15 +02:00
oemel09
256d5ae14f Rebases onto develop 2021-04-08 16:59:46 +02:00
oemel09
799ee8bcfa Check showDescription setting for newsfeed module 2021-04-08 16:59:03 +02:00
Michael Teeuw
fe8a317ef9 Merge pull request #2514 from jupadin/log-consistency
Log consistency
2021-04-08 11:04:11 +02:00
Michael Teeuw
b6d6ee45e0 Fix linter issue. 2021-04-08 10:40:31 +02:00
Michael Teeuw
0151466a28 Merge branch 'develop' into log-consistency 2021-04-08 10:37:18 +02:00
earlman
3588875e28 Update .gitignore 2021-04-04 13:52:18 -05:00
earlman
e8031aec39 add original main.css bak 2021-04-04 13:34:52 -05:00
earlman
1cabe107e6 run prettier 2021-04-04 13:28:56 -05:00
earlman
ba0c59744b update CHANGELOG.md 2021-04-04 13:28:34 -05:00
earlman
50c702c00a adjust new opacities 2021-04-04 13:24:59 -05:00
earlman
ff8c2fe227 add custom.css.sample 2021-04-04 13:22:33 -05:00
earlman
f9c78a5263 use variables for fonts 2021-04-04 13:18:33 -05:00
earlman
d46784a4d5 base font sizes on rem and var(--base) 2021-04-04 13:05:41 -05:00
earlman
ff43d082f2 base line-height on scale 2021-04-04 12:53:35 -05:00
earlman
83801736d7 add color variables 2021-04-04 12:38:17 -05:00
earlman
e16986cf71 Merge branch 'develop' of https://github.com/MichMich/MagicMirror into contribute 2021-04-04 11:55:03 -05:00
earlman
7ad12d954c add original main.css bak 2021-04-04 11:51:04 -05:00
Julian Dinter
fb74fadec2 Run 'npm run lint:prettier'. 2021-04-04 15:37:47 +02:00
Julian Dinter
c00bdf910e Clean up code by adding comments. 2021-04-04 15:22:12 +02:00
Julian Dinter
50cc1c56e5 Consistent log text. 2021-04-04 15:17:42 +02:00
Julian Dinter
0006099758 Changed log type of starting function of updatenotification module. 2021-04-04 14:52:24 +02:00
earlman
162dcec331 Merge branch 'master' of https://github.com/foundations-design/WhiteLight into develop 2021-04-03 11:15:33 -05:00
earlman
3e8bd022e2 add custom variables 2021-04-02 11:28:22 -05:00
earlman
01e5936671 install valid-url 2021-04-02 10:11:38 -05:00
Michael Teeuw
d85e1c70d6 Merge pull request #2512 from jupadin/log-consistency 2021-04-02 12:56:28 +02:00
Julian Dinter
f329770194 Added fix in CHANGELOG. 2021-04-02 12:42:31 +02:00
Julian Dinter
08194925cb Changed log type of starting function of calendar module. 2021-04-02 12:26:34 +02:00
earlman
2b7aa3e810 use yarn 2021-04-01 15:59:48 -05:00
earlman
549417f106 yarn 2021-04-01 15:00:12 -05:00
earlman
f0f370cc7d add *.env to .gitignore 2021-04-01 15:00:06 -05:00
earlman
b842241f8c fix broken start scripts & yarn 2021-04-01 14:08:48 -05:00
earlman
93aa4b7440 update README.md 2021-04-01 14:03:38 -05:00
Michael Teeuw
9e2eef7818 Merge pull request #2511 from khassel/update-node-ical 2021-04-01 20:12:12 +02:00
Karsten Hassel
b29b65851e Bump node-ical to v0.13.0 (now last runtime dependency using deprecated request package is removed). 2021-04-01 20:02:08 +02:00
Michael Teeuw
a6829bff4f Prepare develop: v2.16.0-develop 2021-04-01 14:36:50 +02:00
288 changed files with 19391 additions and 12180 deletions

View File

@@ -1,10 +1,10 @@
{
"extends": ["eslint:recommended", "plugin:prettier/recommended", "plugin:jsdoc/recommended"],
"plugins": ["prettier", "jsdoc"],
"plugins": ["prettier", "jsdoc", "jest"],
"env": {
"browser": true,
"es6": true,
"mocha": true,
"jest/globals": true,
"node": true
},
"globals": {
@@ -16,7 +16,7 @@
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2017,
"ecmaVersion": 2020,
"ecmaFeatures": {
"globalReturn": true
}
@@ -25,6 +25,7 @@
"prettier/prettier": "error",
"eqeqeq": "error",
"no-prototype-builtins": "off",
"no-unused-vars": "off"
"no-unused-vars": "off",
"no-useless-return": "error"
}
}

View File

@@ -4,13 +4,15 @@ Thanks for contributing to MagicMirror²!
We hold our code to standard, and these standards are documented below.
## Linters
If you wish to run our linters, use `npm run lint` without any arguments.
### JavaScript: Run ESLint
We use [ESLint](https://eslint.org) on our JavaScript files.
Our ESLint configuration is in our .eslintrc.json and .eslintignore files.
Our ESLint configuration is in our `.eslintrc.json` and `.eslintignore` files.
To run ESLint, use `npm run lint:js`.
@@ -18,9 +20,17 @@ To run ESLint, use `npm run lint:js`.
We use [StyleLint](https://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file.
To run StyleLint, use `npm run lint:style`.
To run StyleLint, use `npm run lint:css`.
### Submitting Issues
## Testing
We use [Jest](https://jestjs.io) for JavaScript testing.
To run all tests, use `npm run test`.
The specific test commands are defined in `package.json`. So you can also run the specific tests with other commands, e.g. `npm run test:unit` or `npx jest tests/e2e/env_spec.js`.
## Submitting Issues
Please only submit reproducible issues.
@@ -32,9 +42,9 @@ 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 10 or later.
**Node Version**: Make sure it's version 14 or later (recommended is 16).
**MagicMirror Version**: Now that the versions have split, tell us if you are using the PHP version (v1) or the newer JavaScript version (v2).
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.

2
.github/FUNDING.yaml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: MichMich
custom: ["https://magicmirror.builders/#donate"]

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +0,0 @@
github: MichMich
custom: ['https://magicmirror.builders/#donate']

View File

@@ -1,3 +1,7 @@
Hello and thank you for opening an issue.
**Please make sure that you have read the following lines before submitting your Issue:**
## I'm not sure if this is a bug
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
@@ -6,17 +10,17 @@ If you're not sure if it's a real bug or if it's just you, please open a topic o
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
A common problem is that your config file could be invalid. Please run in your MagicMirror directory: `npm run config:check` and see if it reports an error.
A common problem is that your config file could be invalid. Please run in your MagicMirror² directory: `npm run config:check` and see if it reports an error.
## I found a bug in the MagicMirror installer
## I found a bug in the MagicMirror² installer
If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository:
If you are facing an issue or found a bug while trying to install MagicMirror² via the installer please report it in the respective GitHub repository:
[https://github.com/sdetweil/MagicMirror_scripts](https://github.com/sdetweil/MagicMirror_scripts)
## I found a bug in the MagicMirror Docker image
## I found a bug in the MagicMirror² Docker image
If you are facing an issue or found a bug while running MagicMirror inside a Docker container please create an issue in the GitHub repository of the MagicMirror Docker image:
[https://github.com/bastilimbach/docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror)
If you are facing an issue or found a bug while running MagicMirror² inside a Docker container please create an issue in the corresponding repository:
[https://gitlab.com/khassel/magicmirror](https://gitlab.com/khassel/magicmirror)
---
@@ -27,9 +31,9 @@ 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 10 or later.
**Node Version**: Make sure it's version 14 or later (recommended is 16).
**MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file.
**MagicMirror² Version**: Please let us know which version of MagicMirror² you are running. It can be found in the `package.json` file.
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.

View File

@@ -1,24 +1,21 @@
Hello and thank you for wanting to contribute to the MagicMirror project
Hello and thank you for wanting to contribute to the MagicMirror² project
**Please make sure that you have followed these 4 rules before submitting your Pull Request:**
> 1) Base your pull requests against the `develop` branch.
> 1. Base your pull requests against the `develop` branch.
>
> 2. Include these infos in the description:
>
> 2) Include these infos in the description:
> * Does the pull request solve a **related** issue?
> * If so, can you reference the issue like this `Fixes #<issue_number>`?
> * What does the pull request accomplish? Use a list if needed.
> * If it includes major visual changes please add screenshots.
> - Does the pull request solve a **related** issue?
> - If so, can you reference the issue like this `Fixes #<issue_number>`?
> - What does the pull request accomplish? Use a list if needed.
> - If it includes major visual changes please add screenshots.
>
> 3. Please run `npm run lint:prettier` before submitting so that
> style issues are fixed.
>
> 3) Please run `npm run lint:prettier` before submitting so that
> style issues are fixed.
>
>
> 4) Don't forget to add an entry about your changes to
> the CHANGELOG.md file.
> 4. Don't forget to add an entry about your changes to
> the CHANGELOG.md file.
**Note**: Sometimes the development moves very fast. It is highly
recommended that you update your branch of `develop` before creating a

6
.github/codecov.yaml vendored Normal file
View File

@@ -0,0 +1,6 @@
coverage:
status:
project:
default:
# advanced settings
informational: true

6
.github/dependabot.yaml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

BIN
.github/header.psd vendored

Binary file not shown.

39
.github/workflows/automated-tests.yaml vendored Normal file
View File

@@ -0,0 +1,39 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: "Run Automated Tests"
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: Install dependencies and run tests
run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm run install-mm:dev
touch css/custom.css
npm run test:prettier
npm run test:js
npm run test:css
npm run test

View File

@@ -0,0 +1,33 @@
# This workflow runs the automated test and uploads the coverage results to codecov.io
# For more information see: https://github.com/codecov/codecov-action
name: "Run Codecov Tests"
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
permissions:
contents: read
jobs:
run-and-upload-coverage-report:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies and run coverage
run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm ci
touch css/custom.css
npm run test:coverage
- name: Upload coverage results to codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true

View File

@@ -1,24 +0,0 @@
# This workflow runs the automated test and uploads the coverage results to codecov.io
name: "Run Codecov Tests"
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]
jobs:
run-and-upload-coverage-report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm ci
npm run test:coverage
- uses: codecov/codecov-action@v1
with:
file: ./coverage/lcov.info
fail_ci_if_error: true

14
.github/workflows/depsreview.yaml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: "Dependency Review"
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: "Checkout Repository"
uses: actions/checkout@v3
- name: "Dependency Review"
uses: actions/dependency-review-action@v2

View File

@@ -0,0 +1,19 @@
# This workflow enforces the update of a changelog file on every pull request
# For more information see: https://github.com/dangoslen/changelog-enforcer
name: "Enforce Changelog"
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Enforce changelog
uses: dangoslen/changelog-enforcer@v3
with:
changeLogPath: "CHANGELOG.md"
skipLabels: "Skip Changelog"

View File

@@ -1,17 +0,0 @@
# This workflow enforces the update of a changelog file on every pull request
name: "Enforce Changelog"
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dangoslen/changelog-enforcer@v1.6.1
with:
changeLogPath: 'CHANGELOG.md'
skipLabels: 'Skip Changelog'

View File

@@ -1,32 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: "Run Automated Tests"
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm install
npm run test:prettier
npm run test:js
npm run test:css
npm run test:e2e
npm run test:unit

1
.gitignore vendored
View File

@@ -16,7 +16,6 @@ vendor/node_modules/**/*
jspm_modules
.npm
.node_repl_history
.nyc_output/
# Visual Studio Code ignoramuses.
.vscode/

7
.husky/pre-commit Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
[ -f "$(dirname "$0")/_/husky.sh" ] && . "$(dirname "$0")/_/husky.sh"
if command -v npm &> /dev/null; then
npm run lint:staged
fi

View File

@@ -1,5 +1,3 @@
/config
/coverage
package-lock.json
/config/**/*
/vendor/**/*
!/vendor/vendor.js
.github/**/*

View File

@@ -3,7 +3,285 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/).
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror².
## [2.22.0] - 2023-01-01
Thanks to: @angeldeejay, @buxxi, @dariom, @dWoolridge, @KristjanESPERANTO, @MagMar94, @naveensrinivasan, @retroflex, @SkySails and @Tom.
Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you!
### Added
- Added test for remoteFile option in compliments module
- Added hourlyWeather functionality to Weather.gov weather provider
- Removed weatherEndpoint definition from weathergov.js (not used)
- Added css class names "today" and "tomorrow" for default calendar
- Added Collaboration.md
- Added new github action for dependency review (#2862)
- Added a WeatherProvider for Open-Meteo
- Added Yr as a weather provider
- Added config options "ignoreXOriginHeader" and "ignoreContentSecurityPolicy"
### Removed
- Removed usage of internal fetch function of node until it is more stable
### Updated
- Cleaned up test directory (#2937) and jest config (#2959)
- Wait for all modules to start before declaring the system ready (#2487)
- Updated e2e tests (moved `done()` in helper functions) and use es6 syntax in all tests
- Updated da translation
- Rework weather module
- Make sure smhi provider api only gets a maximum of 6 digits coordinates (#2955)
- Use fetch instead of XMLHttpRequest in weatherprovider (#2935)
- Reworked how weatherproviders handle units (#2849)
- Use unix() method for parsing times, fix suntimes on the way (#2950)
- Refactor conversion functions into utils class (#2958)
- The `cors`-method in `server.js` now supports sending and recieving HTTP headers
- Replace `&hellip;` by `…`
- Cleanup compliments module
- Updated dependencies including electron to v22 (#2903)
### Fixed
- Correctly show apparent temperature in SMHI weather provider
- Ensure updatenotification module isn't shown when local is _ahead_ of remote
- Handle node_helper errors during startup (#2944)
- Possibility to change FontAwesome class in calendar, so icons like `fab fa-facebook-square` works.
- Fix cors problems with newsfeed articles (as far as possible), allow disabling cors per feed with option `useCorsProxy: false` (#2840)
- Tests not waiting for the application to start and stop before starting the next test
- Fix electron tests failing sometimes in github workflow
- Fixed gap in clock module when displayed on the left side with displayType=digital
- Fixed playwright issue by upgrading to v1.29.1 (#2969)
## [2.21.0] - 2022-10-01
Special thanks to: @BKeyport, @buxxi, @davide125, @khassel, @kolbyjack, @krukle, @MikeBishop, @rejas, @sdetweil, @SkySails and @veeck
### Added
- Added possibility to fetch calendars through socket notifications.
- New scripts `install-mm` (and `install-mm:dev`) for simplifying mm installation (now: `npm run install-mm`) and adding params `--no-audit --no-fund --no-update-notifier` for less noise.
- New `showTimeToday` option in calendar module shows time for current-day events even if `timeFormat` is `"relative"`.
- Added hourly forecasts, apparent temperature & custom location name to SMHI weather provider.
- Added new electron tests for calendar and moved some compliments tests from `e2e` to `electron` because of date mocking, removed mock stuff from compliments module.
### Removed
- Removed old and deprecated weather modules `currentweather` and `weatherforecast`.
- Removed `DAYAFTERTOMORROW` from English.
### Updated
- Updated dependencies.
- Updated jsdoc.
- Updated font tree to use variables consistently.
- Removed deprecated Docker Repository from issue template.
### Fixed
- Broadcast all calendar events while still honoring global and per-calendar maximumEntries.
- Respect rss ttl provided by newsfeed (#2883).
- Fix multi day calendar events always presented as "(1/X)" instead of the amount of days the event has progressed.
- Fix weatherbit provider to use type config value instead of endpoint.
- Fix calendar events which DO NOT specify rrule byday adjusted incorrectly (#2885).
- Fix e2e tests not failing on errors (#2911).
## [2.20.0] - 2022-07-02
Special thanks to the following contributors: @eouia, @khassel, @kolbyjack, @KristjanESPERANTO, @nathannaveen, @naveensrinivasan, @rejas, @rohitdharavath and @sdetweil.
### 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/defauls.js` which is useful e.g. if you want to embed MagicMirror into annother 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).
### Updated
- Use latest node 18 when running tests on github actions.
- Updated `electron` to v19 and other dependencies.
- Use internal fetch function of node instead external `node-fetch` library if used node version >= `v18`.
- Include duplicate events in broadcasts.
### Fixed
- Fix problems with non latin fonds caused by updating to fontsource (fixes #2835).
## [2.19.0] - 2022-04-01
Special thanks to the following contributors: @10bias, @CFenner, @JHWelch, @k1rd3rf, @khassel, @kolbyjack, @krekos, @KristjanESPERANTO, @Nerfzooka, @oraclesean, @oscarb, @philnagel, @rejas, @sdetweil, @shin10, @SiderealArt and @Tom-Hirschberger.
### Added
- Added a config option under the weather module, `absoluteDates`, providing an option to format weather forecast date output with either absolute or relative dates.
- Added test for new weather forecast `absoluteDates` property.
- The modules get a class hidden added/removed if they get hidden/shown which will also toggle pointer-events.
- Added new config option `showTitleAsUrl` to newsfeed module. If set, the displayed title is a link to the article which is useful when running in a browser and you want to read this article.
- Added internal cors proxy to get weather providers working without public proxies (fixes #2714). The new url `http(s)://address:port/cors?url=https://whatever-to-proxy` can be used in other modules too.
- Added a WeatherProvider for Weatherflow.
- Added new env var `ELECTRON_DISABLE_GPU` which disable gpu under electron if set (fixes #2831).
- Added missing Czech translations.
### Updated
- Deprecated roboto fonts package `roboto-fontface-bower` replaced with `fontsource`.
- Updated `electron` to v17, `helmet` to v5 (use defaults of v4) and other dependencies
- Updated Font Awesome css class to new default style (fixes #2768)
- Replaced deprecated modules `currentweather` and `weatherforecast` with dummy modules only displaying that they have to be replaced.
- Include all calendar events from the configured date range when broadcasting.
- Updated Danish and German translation.
- Updated `node-ical` to v0.15 and added `luxon` as dependency for not breaking the "no-optional" install (see #2718 and #2824).
### Fixed
- Improved and speedup e2e tests, artificial wait after mm start removed.
- Improved husky setup not blocking `git commit` if `husky` or `npm` is not installed.
- Using a consistent spelling of MagicMirror².
- Fix minor console output issue for loading translations (#2814).
- Don't adjust startDate for full day events if endDate is in the past.
- Fix windspeed conversion error in openweathermap provider. (#2812)
- Fix conflicting parms turning off showEnd for full day events. (#2629)
- Fix regression, calendar.maximumEntries not used to filter calendar level entries (#2868)
## [2.18.0] - 2022-01-01
Special thanks to the following contributors: @AmpioRosso, @eouia, @fewieden, @jupadin, @khassel, @kolbyjack, @KristjanESPERANTO, @MariusVaice, @rejas, @rico24 and @sdetweil.
### Added
- Added test for calendar recurring event with checks the correct date displayed (related to #2752).
### Updated
- ESLint version supports now ECMAScript 2018.
- Cleaned up `updatenotification` module and switched to nunjuck template.
- Moved calendar tests from category `electron` to `e2e`.
- Updated missed translations for Korean language (ko.json).
- Updated missed translations for Dutch language (nl.json).
- Cleaned up `alert` module and switched to nunjuck template.
- Moved weather tests from category `electron` to `e2e`.
- Updated github actions.
- Replace spectron with playwright, update dependencies including electron update to v16.
- Added lithuanian language to translations.js.
- Show info message if newsfeed is empty (fixes #2731).
- Added dangerouslyDisableAutoEscaping config option for newsfeed templates (fixes #2712).
- Added missing shebang to `installers/mm.sh`.
- Node versions in templates and github workflows.
- Updated translations for Traditional Chinese (Taiwan) (zh-tw.json).
### Fixed
- Fixed wrong file `kr.json` to `ko.json`. Use language code 'ko' instead of 'kr' for Korean language.
- Fixed `feels_like` data from openweathermaps current weather being ignored (#2678).
- Fixed chaotic newsfeed display after network connection loss thanks to @jalibu (#2638).
- Fixed incorrect time zone correction of recurring full day events (#2632 and #2634).
- Fixed e2e tests by increasing testTimeout.
- Revert node-ical update due to missing luxon package.
- Fixed User-Agent-Header for newsfeed and calendar module (#2729).
- Replace broken shields in Readme and use https for links.
- Fixed electron tests with retry.
- Fixed Calendar recurring cross timezone error (add/subtract a day, not just offset hours) (#2632).
- Fixed Calendar showEnd and Full Date overlay (#2629).
- Fixed regression on #2632, #2752.
- Broadcast custom symbols in CALENDAR_EVENTS.
## [2.17.1] - 2021-10-01
### Fixed
- Fixed error when accessing letsencrypt certificates
- Fixed Calendar module enhancement: displaying full events without time (#2424)
## [2.17.0] - 2021-10-01
Special thanks to the following contributors: @apiontek, @eouia, @jupadin, @khassel and @rejas.
### Added
- Added showTime parameter to clock module for enabling/disabling time display in analog clock.
- Added custom electron switches from user config (`config.electronSwitches`).
- Added unit tests for updatenotification module.
### Updated
- Bump electron to v13 (and spectron to v15) and update other dependencies in package.json.
- Refactor test configs, use default test config for all tests.
- Updated github templates.
- Actually test all js and css files when lint script is run.
- Updated jsdocs and print warnings during testing too.
- Updated weathergov provider to try fetching not just current, but also foreacst, when API URLs available.
- Refactored clock layout.
- Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime).
- Use of `logger.js` in jest tests.
- Run prettier over all relevant files.
- Move tests needing electron in new category `electron`, use `server only` mode in `e2e` tests.
- Updated dependencies in package.json.
### Fixed
- Fix undefined error with ignoreToday option in weather module (#2620).
- Fix time zone correction in calendar module when the date hour is equal to the time zone correction value (#2632).
- Fix black cursor on startup when using electron.
- Fix update notification not working for own repository (#2644).
## [2.16.0] - 2021-07-01
Special thanks to the following contributors: @210954, @B1gG, @codac, @Crazylegstoo, @daniel, @earlman, @ezeholz, @FrancoisRmn, @jupadin, @khassel, @KristjanESPERANTO, @njwilliams, @oemel09, @r3wald, @rejas, @rico24, Faizan Ahmed.
### Added
- Added French translations for "MODULE_CONFIG_ERROR" and "PRECIP".
- Added German translation for "PRECIP".
- Added Dutch translation for "WEEK", "PRECIP", "MODULE_CONFIG_CHANGED" and "MODULE_CONFIG_ERROR".
- Added first test for Alert module.
- Added support for `dateFormat` when not using `timeFormat: "absolute"`.
- Added custom-properties for colors and fonts for improved styling experience, see `custom.css.sample` file.
- Added custom-properties for gaps around body and between modules.
- Added test case for recurring calendar events.
- Added new Environment Canada provider for default WEATHER module (weather data for Canadian locations only).
- Added list view for newsfeed module.
- Added dev dependency jest, switching from mocha to jest.
### Updated
- Bump node-ical to v0.13.0 (now last runtime dependency using deprecated `request` package is removed).
- Use codecov in informational mode.
- Refactor code into es6 where possible (e.g. var -> let/const).
- Use node v16 in github workflow (replacing node v10).
- Moved some files into better suited directories.
- Updated dependencies in package.json, require node >= v12, remove `rrule-alt` and `rrule`.
- Updated dependencies in package.json and migrate husky to v6, fix husky setup in prod environment.
- Cleaned up error handling in newsfeed and calendar modules for real.
- Updated default WEATHER module such that a provider can optionally set a custom unit-of-measure for precipitation (`weatherObject.precipitationUnits`).
- Updated documentation.
- Updated jest tests: Reset changes on js/logger.js, mock logger.js in global_vars tests.
- Updated dependencies in package.json.
### Removed
- Switching from mocha to jest so removed following dev dependencies: chai, chai-as-promised, mocha, mocha-each, mocha-logger.
### Fixed
- Fix calendar start function logging inconsistency.
- Fix updatenotification start function logging inconsistency.
- Checks and applies the showDescription setting for the newsfeed module again.
- Fix issue with openweathermap not showing current or forecast info when using onecall API.
- Fix tests in weather module and add one for decimalPoint in forecast.
- Fix decimalSymbol in the forecast part of the new weather module (#2530).
- Fix wrong treatment of `appendLocationNameToHeader` when using `ukmetofficedatahub`.
- Fix alert not recognizing multiple alerts (#2522).
- Fix fetch option httpsAgent to agent in calendar module (#466).
- Fix module updatenotification which did not work for repos with many refs (#1907).
- Fix config check failing when encountering let syntax ("Parsing error: Unexpected token config").
- Fix calendar debug check.
- Really run prettier over all files.
- Fix logger.js after jest changes, use --forceExit running jest.
- Workaround for dev_console test using getWindowCount.
## [2.15.0] - 2021-04-01
@@ -93,10 +371,10 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Merging .gitignore in the config-folder with the .gitignore in the root-folder.
- Weather module - forecast now show TODAY and TOMORROW instead of weekday, to make it easier to understand.
- Update dependencies to latest versions.
- Update dependencies eslint, feedme, simple-git and socket.io to latest versions.
- Update lithuanian translation.
- Update config sample.
- Updated dependencies to latest versions.
- Updated dependencies eslint, feedme, simple-git and socket.io to latest versions.
- Updated lithuanian translation.
- Updated config sample.
- Highlight required version mismatch.
- No select Text for TouchScreen use.
- Corrected logic for timeFormat "relative" and "absolute".
@@ -117,19 +395,19 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Rename Greek translation to correct ISO 639-1 alpha-2 code (gr > el). (#2155)
- Add a space after icons of sunrise and sunset. (#2169)
- Fix calendar when no DTEND record found in event, startDate overlay when endDate set. (#2177)
- Fix windspeed convertion error in ukmetoffice weather provider. (#2189)
- Fix windspeed conversion error in ukmetoffice weather provider. (#2189)
- Fix console.debug not having timestamps. (#2199)
- Fix calendar full day event east of UTC start time. (#2200)
- Fix non-fullday recurring rule processing. (#2216)
- Catch errors when parsing calendar data with ical. (#2022)
- Fix Default Alert Module does not hide black overlay when alert is dismissed manually. (#2228)
- Weather module - Always displays night icons when local is other than English. (#2221)
- Update node-ical 0.12.4, fix invalid RRULE format in cal entries
- Updated node-ical 0.12.4, fix invalid RRULE format in cal entries
- Fix package.json for optional electron dependency (2378)
- Update node-ical version again, 0.12.5, change RRULE fix (#2371, #2379)
- Updated node-ical version again, 0.12.5, change RRULE fix (#2371, #2379)
- Remove undefined objects from modules array (#2382)
- Update node-ical version again, 0.12.7, change RRULE fix (#2371, #2379), node-ical now throws error (which we catch)
- Update simple-git version to 2.31 unhandled promise rejection (#2383)
- Updated node-ical version again, 0.12.7, change RRULE fix (#2371, #2379), node-ical now throws error (which we catch)
- Updated simple-git version to 2.31 unhandled promise rejection (#2383)
## [2.13.0] - 2020-10-01
@@ -140,8 +418,8 @@ Special thanks to the following contributors: @bryanzzhu, @bugsounet, @chamakura
### Added
- `--dry-run` Added option in fetch call within updatenotification node_helper. This is to prevent
MagicMirror from consuming any fetch result. Causes conflict with MMPM when attempting to check
for updates to MagicMirror and/or MagicMirror modules.
MagicMirror² from consuming any fetch result. Causes conflict with MMPM when attempting to check
for updates to MagicMirror² and/or MagicMirror² modules.
- Test coverage with Istanbul, run it with `npm run test:coverage`.
- Added lithuanian language.
- Added support in weatherforecast for OpenWeather onecall API.
@@ -214,7 +492,7 @@ Special thanks to the following contributors: @AndreKoepke, @andrezibaia, @bryan
🚨 READ THIS BEFORE UPDATING 🚨
In the past years the project has grown a lot. This came with a huge downside: poor maintainability. If I let the project continue the way it was, it would eventually crash and burn. More important: I would completely lose the drive and interest to continue the project. Because of this the decision was made to simplify the core by removing all side features like automatic installers and support for exotic platforms. This release (2.11.0) is the first real release that will reflect (parts) of these changes. As a result of this, some things might break. So before you continue make sure to backup your installation. Your config, your modules or better yet: your full MagicMirror folder. In other words: update at your own risk.
In the past years the project has grown a lot. This came with a huge downside: poor maintainability. If I let the project continue the way it was, it would eventually crash and burn. More important: I would completely lose the drive and interest to continue the project. Because of this the decision was made to simplify the core by removing all side features like automatic installers and support for exotic platforms. This release (2.11.0) is the first real release that will reflect (parts) of these changes. As a result of this, some things might break. So before you continue make sure to backup your installation. Your config, your modules or better yet: your full MagicMirror² folder. In other words: update at your own risk.
For more information regarding this major change, please check issue [#1860](https://github.com/MichMich/MagicMirror/issues/1860).
@@ -354,9 +632,10 @@ Special thanks to @sdetweil for all his great contributions!
- English translation for "Feels" to "Feels like"
- Fixed the example calendar url in `config.js.sample`
- Update `ical.js` to solve various calendar issues.
- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
- Updated `ical.js` to solve various calendar issues.
- Updated weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
- Only update clock once per minute when seconds aren't shown
- Updated weatherprovider documentation.
### Fixed
@@ -376,7 +655,7 @@ Special thanks to @sdetweil for all his great contributions!
- use current username vs hardcoded 'pi' to support non-pi install
- check for npm installed. node install doesn't do npm anymore
- check for mac as part of PM2 install, add install option string
- update pm2 config with current username instead of hard coded 'pi'
- Updated pm2 config with current username instead of hard coded 'pi'
- check for screen saver config, "/etc/xdg/lxsession", bypass if not setup
## [2.7.1] - 2019-04-02
@@ -414,7 +693,7 @@ Fixed `package.json` version number.
- Fixed unhandled error on bad git data in updatenotification module [#1285](https://github.com/MichMich/MagicMirror/issues/1285).
- Weather forecast now works with openweathermap in new weather module. Daily data are displayed, see issue [#1504](https://github.com/MichMich/MagicMirror/issues/1504).
- Fixed analogue clock border display issue where non-black backgrounds used (previous fix for issue 611)
- Fixed compatibility issues caused when modules request different versions of Font Awesome, see issue [#1522](https://github.com/MichMich/MagicMirror/issues/1522). MagicMirror now uses [Font Awesome 5 with v4 shims included for backwards compatibility](https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4#shims).
- Fixed compatibility issues caused when modules request different versions of Font Awesome, see issue [#1522](https://github.com/MichMich/MagicMirror/issues/1522). MagicMirror² now uses [Font Awesome 5 with v4 shims included for backwards compatibility](https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4#shims).
- Installation script problems with raspbian
- Calendar: only show repeating count if the event is actually repeating [#1534](https://github.com/MichMich/MagicMirror/pull/1534)
- Calendar: Fix exdate handling when multiple values are specified (comma separated)
@@ -526,7 +805,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
## [2.4.0] - 2018-07-01
**Warning:** This release includes an updated version of Electron. This requires a Raspberry Pi configuration change to allow the best performance and prevent the CPU from overheating. Please read the information on the [MagicMirror Wiki](https://github.com/michmich/magicmirror/wiki/configuring-the-raspberry-pi#enable-the-open-gl-driver-to-decrease-electrons-cpu-usage).
**Warning:** This release includes an updated version of Electron. This requires a Raspberry Pi configuration change to allow the best performance and prevent the CPU from overheating. Please read the information on the [MagicMirror² Wiki](https://github.com/michmich/magicmirror/wiki/configuring-the-raspberry-pi#enable-the-open-gl-driver-to-decrease-electrons-cpu-usage).
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
@@ -584,7 +863,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add types for module.
- Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR.
- Allow scrolling in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures)
- Changed 'compliments.js' - update DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments
- Changed 'compliments.js' - Updated DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments
- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings)
- Automated integration tests translations
- Add advanced filtering to the excludedEvents configuration of the default calendar module
@@ -596,7 +875,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add link to GitHub repository which contains the respective Dockerfile.
- Optimized automated unit tests cloneObject, cmpVersions
- Update notifications use now translation templates instead of normal strings.
- Updated notifications use now translation templates instead of normal strings.
- Yarn can be used now as an installation tool
- Changed Electron dependency to v1.7.13.
@@ -767,7 +1046,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add use pm2 for manager process into Installer RaspberryPi script.
- Russian Translation.
- Afrikaans Translation.
- Add postinstall script to notify user that MagicMirror installed successfully despite warnings from NPM.
- Add postinstall script to notify user that MagicMirror² installed successfully despite warnings from NPM.
- Init tests using mocha.
- Option to use RegExp in Calendar's titleReplace.
- Hungarian Translation.
@@ -803,7 +1082,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
### Fixed
- Update .gitignore to not ignore default modules folder.
- Updated .gitignore to not ignore default modules folder.
- Remove white flash on boot up.
- Added `update` in Raspberry Pi installation script.
- Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611))
@@ -830,7 +1109,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add VSCode IntelliSense support.
- Module API: Add Visibility locking to module system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information.
- Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information.
- Module API: Option to define the minimum MagicMirror version to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresversion) for more information.
- Module API: Option to define the minimum MagicMirror² version to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresversion) for more information.
- Calendar module now broadcasts the event list to all other modules using the notification system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/calendar) for more information.
- Possibility to use the calendar feed as the source for the weather (currentweather & weatherforecast) location data. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/weatherforecast) for more information.
- Added option to show rain amount in the weatherforecast default module
@@ -888,8 +1167,8 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
### Updated
- Force fullscreen when kioskmode is active.
- Update the .github templates and information with more modern information.
- Update the Gruntfile with a more functional StyleLint implementation.
- Updated the .github templates and information with more modern information.
- Updated the Gruntfile with a more functional StyleLint implementation.
## [2.0.4] - 2016-08-07
@@ -977,6 +1256,6 @@ It includes (but is not limited to) the following features:
## [1.0.0] - 2014-02-16
### Initial release of MagicMirror.
### 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)

12
Collaboration.md Normal file
View File

@@ -0,0 +1,12 @@
This document describes how collaborators of this repository should work together.
## Pull Requests
- never merge your own PR's
- never merge without someone having approved (approving and merging from same person is allowed)
- wait for all approvals requested (or the author decides something different in the comments)
## Issues
- "real" Issues are closed if the problem is solved and the fix is released
- unrelated Issues (e.g. related to a foreign module) are closed immediately with a comment to open an issue in the module repository or to discuss this further in the forum or discord

View File

@@ -1,6 +1,6 @@
# The MIT License (MIT)
Copyright © 2016-2021 Michael Teeuw
Copyright © 2016-2022 Michael Teeuw
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

View File

@@ -1,13 +1,17 @@
![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png)
<p style="text-align: center">
<a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a>
<a href="https://david-dm.org/MichMich/MagicMirror?type=dev"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge" alt="CLI Best Practices"></a>
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg?token=LEG1KitZR6" alt="CodeCov Status"/></a>
<a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a>
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg" /></a>
<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/workflow/status/michmich/magicmirror/Run%20Automated%20Tests" alt="GitHub Actions">
<img src="https://img.shields.io/github/checks-status/michmich/magicmirror/master" alt="Build Status">
<a href="https://codecov.io/gh/MichMich/MagicMirror">
<img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg?token=LEG1KitZR6" alt="CodeCov Status"/>
</a>
<a href="https://github.com/MichMich/MagicMirror">
<img src="https://img.shields.io/github/stars/michmich/magicmirror?style=social">
</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/MichMich/MagicMirror/graphs/contributors).
@@ -23,6 +27,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
- 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)
@@ -35,7 +40,7 @@ Contributions of all kinds are welcome, not only in the form of code but also wi
- documentation
- translations
For the full contribution guidelines, check out: [https://docs.magicmirror.builders/getting-started/contributing.html](https://docs.magicmirror.builders/getting-started/contributing.html)
For the full contribution guidelines, check out: [https://docs.magicmirror.builders/about/contributing.html](https://docs.magicmirror.builders/about/contributing.html)
## Enjoying MagicMirror? Consider a donation!

View File

@@ -2,7 +2,7 @@
// Use separate scope to prevent global scope pollution
(function () {
var config = {};
const config = {};
/**
* Helper function to get server address/hostname from either the commandline or env
@@ -17,8 +17,8 @@
* @returns {string} the value of the parameter
*/
function getCommandLineParameter(key, defaultValue = undefined) {
var index = process.argv.indexOf(`--${key}`);
var value = index > -1 ? process.argv[index + 1] : undefined;
const index = process.argv.indexOf(`--${key}`);
const value = index > -1 ? process.argv[index + 1] : undefined;
return value !== undefined ? String(value) : defaultValue;
}
@@ -43,7 +43,7 @@
// Select http or https module, depending on requested url
const lib = url.startsWith("https") ? require("https") : require("http");
const request = lib.get(url, (response) => {
var configData = "";
let configData = "";
// Gather incoming data
response.on("data", function (chunk) {
@@ -79,15 +79,15 @@
getServerAddress();
(config.address && config.port) || fail();
var prefix = config.tls ? "https://" : "http://";
const prefix = config.tls ? "https://" : "http://";
// Only start the client if a non-local server was provided
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) {
getServerConfig(`${prefix}${config.address}:${config.port}/config/`)
.then(function (configReturn) {
// Pass along the server config via an environment variable
var env = Object.create(process.env);
var options = { env: env };
const env = Object.create(process.env);
const options = { env: env };
configReturn.address = config.address;
configReturn.port = config.port;
configReturn.tls = config.tls;

View File

@@ -1,21 +1,20 @@
/* Magic Mirror Config Sample
/* MagicMirror² Config Sample
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*
* For more information on how you can configure this file
* See https://github.com/MichMich/MagicMirror#configuration
*
* see https://docs.magicmirror.builders/configuration/introduction.html
* and https://docs.magicmirror.builders/modules/configuration.html
*/
var config = {
let config = {
address: "localhost", // Address to listen on, can be:
// - "localhost", "127.0.0.1", "::1" to listen on loopback interface
// - another specific IPv4/6 to listen on a specific interface
// - "0.0.0.0", "::" to listen on any interface
// Default, when address config is left out or empty, is "localhost"
port: 8080,
basePath: "/", // The URL path where MagicMirror is hosted. If you are using a Reverse proxy
basePath: "/", // The URL path where MagicMirror² is hosted. If you are using a Reverse proxy
// you must set the sub path here. basePath must end with a /
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses
// or add a specific IPv4 of 192.168.1.5 :
@@ -58,7 +57,8 @@ var config = {
calendars: [
{
symbol: "calendar-check",
url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" }
url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics"
}
]
}
},

31
css/custom.css.sample Normal file
View File

@@ -0,0 +1,31 @@
/* MagicMirror² Custom CSS Sample
*
* Change color and fonts here.
*
* Beware that properties cannot be unitless, so for example write '--gap-body: 0px;' instead of just '--gap-body: 0;'
*
* MIT Licensed.
*/
/* Uncomment and adjust accordingly if you want to import another font from the google-fonts-api: */
/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;700&display=swap'); */
:root {
--color-text: #999;
--color-text-dimmed: #666;
--color-text-bright: #fff;
--color-background: black;
--font-primary: "Roboto Condensed";
--font-secondary: "Roboto";
--font-size: 20px;
--font-size-small: 0.75rem;
--gap-body-top: 60px;
--gap-body-right: 60px;
--gap-body-bottom: 60px;
--gap-body-left: 60px;
--gap-modules: 30px;
}

View File

@@ -1,8 +1,33 @@
:root {
--color-text: #999;
--color-text-dimmed: #666;
--color-text-bright: #fff;
--color-background: #000;
--font-primary: "Roboto Condensed";
--font-secondary: "Roboto";
--font-size: 20px;
--font-size-xsmall: 0.75rem;
--font-size-small: 1rem;
--font-size-medium: 1.5rem;
--font-size-large: 3.25rem;
--font-size-xlarge: 3.75rem;
--gap-body-top: 60px;
--gap-body-right: 60px;
--gap-body-bottom: 60px;
--gap-body-left: 60px;
--gap-modules: 30px;
}
html {
cursor: none;
overflow: hidden;
background: #000;
background: var(--color-background);
user-select: none;
font-size: var(--font-size);
}
::-webkit-scrollbar {
@@ -10,16 +35,15 @@ html {
}
body {
margin: 60px;
margin: var(--gap-body-top) var(--gap-body-right) var(--gap-body-bottom) var(--gap-body-left);
position: absolute;
height: calc(100% - 120px);
width: calc(100% - 120px);
background: #000;
color: #aaa;
font-family: "Roboto Condensed", sans-serif;
height: calc(100% - var(--gap-body-top) - var(--gap-body-bottom));
width: calc(100% - var(--gap-body-right) - var(--gap-body-left));
background: var(--color-background);
color: var(--color-text);
font-family: var(--font-primary), sans-serif;
font-weight: 400;
font-size: 2em;
line-height: 1.5em;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
@@ -28,60 +52,60 @@ body {
*/
.dimmed {
color: #666;
color: var(--color-text-dimmed);
}
.normal {
color: #999;
color: var(--color-text);
}
.bright {
color: #fff;
color: var(--color-text-bright);
}
.xsmall {
font-size: 15px;
line-height: 20px;
font-size: var(--font-size-xsmall);
line-height: 1.275;
}
.small {
font-size: 20px;
line-height: 25px;
font-size: var(--font-size-small);
line-height: 1.25;
}
.medium {
font-size: 30px;
line-height: 35px;
font-size: var(--font-size-medium);
line-height: 1.225;
}
.large {
font-size: 65px;
line-height: 65px;
font-size: var(--font-size-large);
line-height: 1;
}
.xlarge {
font-size: 75px;
line-height: 75px;
font-size: var(--font-size-xlarge);
line-height: 1;
letter-spacing: -3px;
}
.thin {
font-family: Roboto, sans-serif;
font-family: var(--font-secondary), sans-serif;
font-weight: 100;
}
.light {
font-family: "Roboto Condensed", sans-serif;
font-family: var(--font-primary), sans-serif;
font-weight: 300;
}
.regular {
font-family: "Roboto Condensed", sans-serif;
font-family: var(--font-primary), sans-serif;
font-weight: 400;
}
.bold {
font-family: "Roboto Condensed", sans-serif;
font-family: var(--font-primary), sans-serif;
font-weight: 700;
}
@@ -95,14 +119,14 @@ body {
header {
text-transform: uppercase;
font-size: 15px;
font-family: "Roboto Condensed", Arial, Helvetica, sans-serif;
font-size: var(--font-size-xsmall);
font-family: var(--font-primary), Arial, Helvetica, sans-serif;
font-weight: 400;
border-bottom: 1px solid #666;
border-bottom: 1px solid var(--color-text-dimmed);
line-height: 15px;
padding-bottom: 5px;
margin-bottom: 10px;
color: #999;
color: var(--color-text);
}
sup {
@@ -115,11 +139,19 @@ sup {
*/
.module {
margin-bottom: 30px;
margin-bottom: var(--gap-modules);
}
.module.hidden {
pointer-events: none;
}
.module:not(.hidden) {
pointer-events: auto;
}
.region.bottom .module {
margin-top: 30px;
margin-top: var(--gap-modules);
margin-bottom: 0;
}
@@ -143,17 +175,13 @@ sup {
.region.fullscreen {
position: absolute;
top: -60px;
left: -60px;
right: -60px;
bottom: -60px;
top: calc(-1 * var(--gap-body-top));
left: calc(-1 * var(--gap-body-left));
right: calc(-1 * var(--gap-body-right));
bottom: calc(-1 * var(--gap-body-bottom));
pointer-events: none;
}
.region.fullscreen * {
pointer-events: auto;
}
.region.right {
right: 0;
text-align: right;
@@ -163,18 +191,6 @@ sup {
top: 0;
}
.region.top .container {
margin-bottom: 25px;
}
.region.bottom .container {
margin-top: 25px;
}
.region.top .container:empty {
margin-bottom: 0;
}
.region.top.center,
.region.bottom.center {
left: 50%;
@@ -191,10 +207,6 @@ sup {
bottom: 0;
}
.region.bottom .container:empty {
margin-top: 0;
}
.region.bottom.right,
.region.bottom.center,
.region.bottom.left {

View File

@@ -1,12 +1,37 @@
{
"name": "magicmirror-fonts",
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"roboto-fontface": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
}
}
"name": "magicmirror-fonts",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "magicmirror-fonts",
"license": "MIT",
"dependencies": {
"@fontsource/roboto": "^4.5.8",
"@fontsource/roboto-condensed": "^4.5.9"
}
},
"node_modules/@fontsource/roboto": {
"version": "4.5.8",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
"integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
},
"node_modules/@fontsource/roboto-condensed": {
"version": "4.5.9",
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
"integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
}
},
"dependencies": {
"@fontsource/roboto": {
"version": "4.5.8",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
"integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
},
"@fontsource/roboto-condensed": {
"version": "4.5.9",
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
"integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "magicmirror-fonts",
"description": "Package for fonts use by MagicMirror Core.",
"description": "Package for fonts use by MagicMirror² Core.",
"repository": {
"type": "git",
"url": "git+https://github.com/MichMich/MagicMirror.git"
@@ -10,6 +10,7 @@
"url": "https://github.com/MichMich/MagicMirror/issues"
},
"dependencies": {
"roboto-fontface": "^0.10.0"
"@fontsource/roboto": "^4.5.8",
"@fontsource/roboto-condensed": "^4.5.9"
}
}

View File

@@ -2,57 +2,54 @@
font-family: Roboto;
font-style: normal;
font-weight: 100;
src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff");
src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/@fontsource/roboto/files/roboto-all-100-normal.woff") format("woff");
}
@font-face {
font-family: "Roboto Condensed";
font-style: normal;
font-weight: 300;
src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff") format("woff");
src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-300-normal.woff") format("woff");
}
@font-face {
font-family: "Roboto Condensed";
font-style: normal;
font-weight: 400;
src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff") format("woff");
src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-400-normal.woff") format("woff");
}
@font-face {
font-family: "Roboto Condensed";
font-style: normal;
font-weight: 700;
src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff") format("woff");
src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-700-normal.woff") format("woff");
}
@font-face {
font-family: Roboto;
font-style: normal;
font-weight: 400;
src: local("Roboto"), local("Roboto-Regular"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff");
src: local("Roboto"), local("Roboto-Regular"), url("node_modules/@fontsource/roboto/files/roboto-all-400-normal.woff") format("woff");
}
@font-face {
font-family: Roboto;
font-style: normal;
font-weight: 500;
src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff");
src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/@fontsource/roboto/files/roboto-all-500-normal.woff") format("woff");
}
@font-face {
font-family: Roboto;
font-style: normal;
font-weight: 700;
src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff");
src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/@fontsource/roboto/files/roboto-all-700-normal.woff") format("woff");
}
@font-face {
font-family: Roboto;
font-style: normal;
font-weight: 300;
src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff");
src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/@fontsource/roboto/files/roboto-all-300-normal.woff") format("woff");
}

View File

@@ -1,55 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>MagicMirror²</title>
<meta name="google" content="notranslate" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<head>
<title>MagicMirror²</title>
<meta name="google" content="notranslate" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="format-detection" content="telephone=no" />
<meta name="mobile-web-app-capable" content="yes" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<link rel="stylesheet" type="text/css" href="css/main.css">
<link rel="stylesheet" type="text/css" href="fonts/roboto.css">
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<link rel="stylesheet" type="text/css" href="css/main.css" />
<link rel="stylesheet" type="text/css" href="fonts/roboto.css" />
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
<script type="text/javascript">
var version = "#VERSION#";
</script>
</head>
<body>
<div class="region fullscreen below"><div class="container"></div></div>
<div class="region top bar">
<div class="container"></div>
<div class="region top left"><div class="container"></div></div>
<div class="region top center"><div class="container"></div></div>
<div class="region top right"><div class="container"></div></div>
</div>
<div class="region upper third"><div class="container"></div></div>
<div class="region middle center"><div class="container"></div></div>
<div class="region lower third"><div class="container"><br/></div></div>
<div class="region bottom bar">
<div class="container"></div>
<div class="region bottom left"><div class="container"></div></div>
<div class="region bottom center"><div class="container"></div></div>
<div class="region bottom right"><div class="container"></div></div>
</div>
<div class="region fullscreen above"><div class="container"></div></div>
<script type="text/javascript" src="socket.io/socket.io.js"></script>
<script type="text/javascript" src="vendor/node_modules/nunjucks/browser/nunjucks.min.js"></script>
<script type="text/javascript" src="js/defaults.js"></script>
<script type="text/javascript" src="#CONFIG_FILE#"></script>
<script type="text/javascript" src="vendor/vendor.js"></script>
<script type="text/javascript" src="modules/default/defaultmodules.js"></script>
<script type="text/javascript" src="js/logger.js"></script>
<script type="text/javascript" src="translations/translations.js"></script>
<script type="text/javascript" src="js/translator.js"></script>
<script type="text/javascript" src="js/class.js"></script>
<script type="text/javascript" src="js/module.js"></script>
<script type="text/javascript" src="js/loader.js"></script>
<script type="text/javascript" src="js/socketclient.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</body>
<script type="text/javascript">
window.mmVersion = "#VERSION#";
</script>
</head>
<body>
<div class="region fullscreen below"><div class="container"></div></div>
<div class="region top bar">
<div class="container"></div>
<div class="region top left"><div class="container"></div></div>
<div class="region top center"><div class="container"></div></div>
<div class="region top right"><div class="container"></div></div>
</div>
<div class="region upper third"><div class="container"></div></div>
<div class="region middle center"><div class="container"></div></div>
<div class="region lower third">
<div class="container"><br /></div>
</div>
<div class="region bottom bar">
<div class="container"></div>
<div class="region bottom left"><div class="container"></div></div>
<div class="region bottom center"><div class="container"></div></div>
<div class="region bottom right"><div class="container"></div></div>
</div>
<div class="region fullscreen above"><div class="container"></div></div>
<script type="text/javascript" src="socket.io/socket.io.js"></script>
<script type="text/javascript" src="vendor/node_modules/nunjucks/browser/nunjucks.min.js"></script>
<script type="text/javascript" src="js/defaults.js"></script>
<script type="text/javascript" src="#CONFIG_FILE#"></script>
<script type="text/javascript" src="vendor/vendor.js"></script>
<script type="text/javascript" src="modules/default/defaultmodules.js"></script>
<script type="text/javascript" src="js/logger.js"></script>
<script type="text/javascript" src="translations/translations.js"></script>
<script type="text/javascript" src="js/translator.js"></script>
<script type="text/javascript" src="js/class.js"></script>
<script type="text/javascript" src="js/module.js"></script>
<script type="text/javascript" src="js/loader.js"></script>
<script type="text/javascript" src="js/socketclient.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>

View File

@@ -1,3 +1,4 @@
#!/bin/bash
# This file is still here to keep PM2 working on older installations.
cd ~/MagicMirror
DISPLAY=:0 npm start

32
jest.config.js Normal file
View File

@@ -0,0 +1,32 @@
module.exports = async () => {
return {
verbose: true,
testTimeout: 20000,
testSequencer: "<rootDir>/tests/configs/test_sequencer.js",
projects: [
{
displayName: "unit",
moduleNameMapper: {
logger: "<rootDir>/js/logger.js"
},
testMatch: ["**/tests/unit/**/*.[jt]s?(x)"],
testPathIgnorePatterns: ["<rootDir>/tests/unit/mocks"]
},
{
displayName: "electron",
testMatch: ["**/tests/electron/**/*.[jt]s?(x)"],
testPathIgnorePatterns: ["<rootDir>/tests/electron/helpers/"]
},
{
displayName: "e2e",
setupFilesAfterEnv: ["<rootDir>/tests/e2e/helpers/mock-console.js"],
testMatch: ["**/tests/e2e/**/*.[jt]s?(x)"],
modulePaths: ["<rootDir>/js/"],
testPathIgnorePatterns: ["<rootDir>/tests/e2e/helpers/", "<rootDir>/tests/e2e/mocks"]
}
],
collectCoverageFrom: ["./clientonly/**/*.js", "./js/**/*.js", "./modules/**/*.js", "./serveronly/**/*.js"],
coverageReporters: ["lcov", "text"],
coverageProvider: "v8"
};
};

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* The Core App (Server)
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -37,7 +37,7 @@ if (process.env.MM_PORT) {
process.on("uncaughtException", function (err) {
Log.error("Whoops! There was an uncaught exception...");
Log.error(err);
Log.error("MagicMirror will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
Log.error("MagicMirror² will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
Log.error("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
});
@@ -48,6 +48,7 @@ process.on("uncaughtException", function (err) {
*/
function App() {
let nodeHelpers = [];
let httpServer;
/**
* Loads the config file. Combines it with the defaults, and runs the
@@ -127,7 +128,7 @@ function App() {
let m = new Module();
if (m.requiresVersion) {
Log.log(`Check MagicMirror version for node helper '${moduleName}' - Minimum version: ${m.requiresVersion} - Current version: ${global.version}`);
Log.log(`Check MagicMirror² version for node helper '${moduleName}' - Minimum version: ${m.requiresVersion} - Current version: ${global.version}`);
if (cmpVersions(global.version, m.requiresVersion) >= 0) {
Log.log("Version is ok!");
} else {
@@ -180,7 +181,6 @@ function App() {
*
* @param {string} a Version number a.
* @param {string} b Version number b.
*
* @returns {number} A positive number if a is larger than b, a negative
* number if a is smaller and 0 if they are the same
*/
@@ -222,18 +222,33 @@ function App() {
}
}
loadModules(modules, function () {
const server = new Server(config, function (app, io) {
Log.log("Server started ...");
loadModules(modules, async function () {
httpServer = new Server(config);
const { app, io } = await httpServer.open();
Log.log("Server started ...");
for (let nodeHelper of nodeHelpers) {
nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io);
nodeHelper.start();
const nodePromises = [];
for (let nodeHelper of nodeHelpers) {
nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io);
try {
nodePromises.push(nodeHelper.start());
} catch (error) {
Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
Log.error(error);
}
}
Promise.allSettled(nodePromises).then((results) => {
// Log errors that happened during async node_helper startup
results.forEach((result) => {
if (result.status === "rejected") {
Log.error(result.reason);
}
});
Log.log("Sockets connected & modules started ...");
if (typeof callback === "function") {
callback(config);
}
@@ -247,13 +262,16 @@ function App() {
* exists.
*
* Added to fix #1056
*
* @param {Function} callback Function to be called after the app has stopped
*/
this.stop = function () {
this.stop = function (callback) {
for (const nodeHelper of nodeHelpers) {
if (typeof nodeHelper.stop === "function") {
nodeHelper.stop();
}
}
httpServer.close().then(callback);
};
/**

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
*
* Check the configuration file for errors
*
@@ -52,7 +52,13 @@ function checkConfigFile() {
// I'm not sure if all ever is utf-8
const configFile = fs.readFileSync(configFileName, "utf-8");
const errors = linter.verify(configFile);
// Explicitly tell linter that he might encounter es6 syntax ("let config = {...}")
const errors = linter.verify(configFile, {
env: {
es6: true
}
});
if (errors.length === 0) {
Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)"));
} else {

View File

@@ -8,8 +8,8 @@
* MIT Licensed.
*/
(function () {
var initializing = false;
var fnTest = /xyz/.test(function () {
let initializing = false;
const fnTest = /xyz/.test(function () {
xyz;
})
? /\b_super\b/
@@ -20,27 +20,27 @@
// Create a new Class that inherits from this class
Class.extend = function (prop) {
var _super = this.prototype;
let _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
const prototype = new this();
initializing = false;
// Make a copy of all prototype properties, to prevent reference issues.
for (var p in prototype) {
for (const p in prototype) {
prototype[p] = cloneObject(prototype[p]);
}
// Copy the properties over onto the new prototype
for (var name in prop) {
for (const name in prop) {
// Check if we're overwriting an existing function
prototype[name] =
typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name])
? (function (name, fn) {
return function () {
var tmp = this._super;
const tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
@@ -48,7 +48,7 @@
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
const ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
@@ -84,7 +84,6 @@
* Define the clone method for later use. Helper Method.
*
* @param {object} obj Object to be cloned
*
* @returns {object} the cloned object
*/
function cloneObject(obj) {
@@ -92,8 +91,8 @@ function cloneObject(obj) {
return obj;
}
var temp = obj.constructor(); // give temp the original obj's constructor
for (var key in obj) {
const temp = obj.constructor(); // give temp the original obj's constructor
for (const key in obj) {
temp[key] = cloneObject(obj[key]);
if (key === "lockStrings") {

View File

@@ -1,17 +1,17 @@
/* global mmPort */
/* Magic Mirror
/* MagicMirror²
* Config Defaults
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var address = "localhost";
var port = 8080;
const address = "localhost";
let port = 8080;
if (typeof mmPort !== "undefined") {
port = mmPort;
}
var defaults = {
const defaults = {
address: address,
port: port,
basePath: "/",
@@ -25,6 +25,9 @@ var defaults = {
units: "metric",
zoom: 1,
customCss: "css/custom.css",
// httpHeaders used by helmet, see https://helmetjs.github.io/. You can add other/more object values by overriding this in config.js,
// e.g. you need to add `frameguard: false` for embedding MagicMirror in another website, see https://github.com/MichMich/MagicMirror/issues/2847
httpHeaders: { contentSecurityPolicy: false, crossOriginOpenerPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false },
modules: [
{
@@ -36,7 +39,7 @@ var defaults = {
position: "upper_third",
classes: "large thin",
config: {
text: "Magic Mirror<sup>2</sup>"
text: "MagicMirror²"
}
},
{
@@ -59,7 +62,7 @@ var defaults = {
position: "middle_center",
classes: "xsmall",
config: {
text: "If you get this message while your config file is already created,<br>" + "it probably contains an error. To validate your config file run in your MagicMirror directory<br>" + "<pre>npm run config:check</pre>"
text: "If you get this message while your config file is already created,<br>" + "it probably contains an error. To validate your config file run in your MagicMirror² directory<br>" + "<pre>npm run config:check</pre>"
}
},
{

View File

@@ -1,4 +1,4 @@
/* Magic Mirror Deprecated Config Options List
/* MagicMirror² Deprecated Config Options List
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.

View File

@@ -8,6 +8,12 @@ const Log = require("logger");
let config = process.env.config ? JSON.parse(process.env.config) : {};
// Module to control application life.
const app = electron.app;
// If ELECTRON_DISABLE_GPU is set electron is started with --disable-gpu flag.
// See https://www.electronjs.org/docs/latest/tutorial/offscreen-rendering for more info.
if (process.env.ELECTRON_DISABLE_GPU !== undefined) {
app.disableHardwareAcceleration();
}
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;
@@ -19,7 +25,8 @@ let mainWindow;
*
*/
function createWindow() {
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
let electronSwitchesDefaults = ["autoplay-policy", "no-user-gesture-required"];
app.commandLine.appendSwitch(...new Set(electronSwitchesDefaults, config.electronSwitches));
let electronOptionsDefaults = {
width: 800,
height: 600,
@@ -52,7 +59,7 @@ function createWindow() {
// If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost
let prefix;
if (config["tls"] !== null && config["tls"]) {
if ((config["tls"] !== null && config["tls"]) || config.useHttps) {
prefix = "https://";
} else {
prefix = "http://";
@@ -63,9 +70,19 @@ function createWindow() {
// Open the DevTools if run with "npm start dev"
if (process.argv.includes("dev")) {
if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
const devtools = new BrowserWindow(electronOptions);
mainWindow.webContents.setDevToolsWebContents(devtools.webContents);
}
mainWindow.webContents.openDevTools();
}
// simulate mouse move to hide black cursor on start
mainWindow.webContents.on("dom-ready", (event) => {
mainWindow.webContents.sendInputEvent({ type: "mouseMove", x: 0, y: 0 });
});
// Set responders for window events.
mainWindow.on("closed", function () {
mainWindow = null;
@@ -86,6 +103,20 @@ function createWindow() {
}, 1000);
});
}
//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) {
curHeaders = Object.fromEntries(Object.entries(curHeaders).filter((header) => !/x-frame-options/i.test(header[0])));
}
if (config["ignoreContentSecurityPolicy"] || false) {
curHeaders = Object.fromEntries(Object.entries(curHeaders).filter((header) => !/content-security-policy/i.test(header[0])));
}
callback({ responseHeaders: curHeaders });
});
}
// This method will be called when Electron has finished
@@ -97,7 +128,12 @@ app.on("ready", function () {
// Quit when all windows are closed.
app.on("window-all-closed", function () {
createWindow();
if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
app.quit();
} else {
createWindow();
}
});
app.on("activate", function () {
@@ -124,6 +160,13 @@ app.on("before-quit", (event) => {
process.exit(0);
});
/* handle errors from self signed certificates */
app.on("certificate-error", (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
callback(true);
});
// Start the core application if server is run on localhost
// This starts all node helpers and starts the webserver.
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) {

28
js/fetch.js Normal file
View File

@@ -0,0 +1,28 @@
/**
* Helper class to provide either third party fetch library or (if node >= 18)
* return internal node fetch implementation.
*
* Attention: After some discussion we always return the third party
* implementation until the node implementation is stable and more tested
*
* @see https://github.com/MichMich/MagicMirror/pull/2952
* @see https://github.com/MichMich/MagicMirror/issues/2649
* @param {string} url to be fetched
* @param {object} options object e.g. for headers
* @class
*/
async function fetch(url, options = {}) {
// const nodeVersion = process.version.match(/^v(\d+)\.*/)[1];
// if (nodeVersion >= 18) {
// // node version >= 18
// return global.fetch(url, options);
// } else {
// // node version < 18
// const nodefetch = require("node-fetch");
// return nodefetch(url, options);
// }
const nodefetch = require("node-fetch");
return nodefetch(url, options);
}
module.exports = fetch;

View File

@@ -1,29 +1,29 @@
/* global defaultModules, vendor */
/* Magic Mirror
/* MagicMirror²
* Module and File loaders.
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var Loader = (function () {
const Loader = (function () {
/* Create helper variables */
var loadedModuleFiles = [];
var loadedFiles = [];
var moduleObjects = [];
const loadedModuleFiles = [];
const loadedFiles = [];
const moduleObjects = [];
/* Private Methods */
/**
* Loops thru all modules and requests load for every module.
*/
var loadModules = function () {
var moduleData = getModuleData();
const loadModules = function () {
let moduleData = getModuleData();
var loadNextModule = function () {
const loadNextModule = function () {
if (moduleData.length > 0) {
var nextModule = moduleData[0];
const nextModule = moduleData[0];
loadModule(nextModule, function () {
moduleData = moduleData.slice(1);
loadNextModule();
@@ -46,9 +46,8 @@ var Loader = (function () {
/**
* Loops thru all modules and requests start for every module.
*/
var startModules = function () {
for (var m in moduleObjects) {
var module = moduleObjects[m];
const startModules = function () {
for (const module of moduleObjects) {
module.start();
}
@@ -56,7 +55,7 @@ var Loader = (function () {
MM.modulesStarted(moduleObjects);
// Starting modules also hides any modules that have requested to be initially hidden
for (let thisModule of moduleObjects) {
for (const thisModule of moduleObjects) {
if (thisModule.data.hiddenOnStartup) {
Log.info("Initially hiding " + thisModule.name);
thisModule.hide();
@@ -69,7 +68,7 @@ var Loader = (function () {
*
* @returns {object[]} module data as configured in config
*/
var getAllModules = function () {
const getAllModules = function () {
return config.modules;
};
@@ -78,29 +77,28 @@ var Loader = (function () {
*
* @returns {object[]} Module information.
*/
var getModuleData = function () {
var modules = getAllModules();
var moduleFiles = [];
const getModuleData = function () {
const modules = getAllModules();
const moduleFiles = [];
for (var m in modules) {
var moduleData = modules[m];
var module = moduleData.module;
modules.forEach(function (moduleData, index) {
const module = moduleData.module;
var elements = module.split("/");
var moduleName = elements[elements.length - 1];
var moduleFolder = config.paths.modules + "/" + module;
const elements = module.split("/");
const moduleName = elements[elements.length - 1];
let moduleFolder = config.paths.modules + "/" + module;
if (defaultModules.indexOf(moduleName) !== -1) {
moduleFolder = config.paths.modules + "/default/" + module;
}
if (moduleData.disabled === true) {
continue;
return;
}
moduleFiles.push({
index: m,
identifier: "module_" + m + "_" + module,
index: index,
identifier: "module_" + index + "_" + module,
name: moduleName,
path: moduleFolder + "/",
file: moduleName + ".js",
@@ -111,7 +109,7 @@ var Loader = (function () {
config: moduleData.config,
classes: typeof moduleData.classes !== "undefined" ? moduleData.classes + " " + module : module
});
}
});
return moduleFiles;
};
@@ -122,11 +120,11 @@ var Loader = (function () {
* @param {object} module Information about the module we want to load.
* @param {Function} callback Function called when done.
*/
var loadModule = function (module, callback) {
var url = module.path + module.file;
const loadModule = function (module, callback) {
const url = module.path + module.file;
var afterLoad = function () {
var moduleObject = Module.create(module.name);
const afterLoad = function () {
const moduleObject = Module.create(module.name);
if (moduleObject) {
bootstrapModule(module, moduleObject, function () {
callback();
@@ -153,7 +151,7 @@ var Loader = (function () {
* @param {Module} mObj Modules instance.
* @param {Function} callback Function called when done.
*/
var bootstrapModule = function (module, mObj, callback) {
const bootstrapModule = function (module, mObj, callback) {
Log.info("Bootstrapping module: " + module.name);
mObj.setData(module);
@@ -177,13 +175,14 @@ var Loader = (function () {
* @param {string} fileName Path of the file we want to load.
* @param {Function} callback Function called when done.
*/
var loadFile = function (fileName, callback) {
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
const loadFile = function (fileName, callback) {
const extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
let script, stylesheet;
switch (extension.toLowerCase()) {
case "js":
Log.log("Load script: " + fileName);
var script = document.createElement("script");
script = document.createElement("script");
script.type = "text/javascript";
script.src = fileName;
script.onload = function () {
@@ -202,7 +201,7 @@ var Loader = (function () {
break;
case "css":
Log.log("Load stylesheet: " + fileName);
var stylesheet = document.createElement("link");
stylesheet = document.createElement("link");
stylesheet.rel = "stylesheet";
stylesheet.type = "text/css";
stylesheet.href = fileName;

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Log
*
* This logger is very simple, but needs to be extended.
@@ -9,12 +9,13 @@
*/
(function (root, factory) {
if (typeof exports === "object") {
// add timestamps in front of log messages
require("console-stamp")(console, {
pattern: "yyyy-mm-dd HH:MM:ss.l",
include: ["debug", "log", "info", "warn", "error"]
});
if (process.env.JEST_WORKER_ID === undefined) {
// add timestamps in front of log messages
require("console-stamp")(console, {
pattern: "yyyy-mm-dd HH:MM:ss.l",
include: ["debug", "log", "info", "warn", "error"]
});
}
// Node, CommonJS-like
module.exports = factory(root.config);
} else {
@@ -22,29 +23,57 @@
root.Log = factory(root.config);
}
})(this, function (config) {
const logLevel = {
debug: Function.prototype.bind.call(console.debug, console),
log: Function.prototype.bind.call(console.log, console),
info: Function.prototype.bind.call(console.info, console),
warn: Function.prototype.bind.call(console.warn, console),
error: Function.prototype.bind.call(console.error, console),
group: Function.prototype.bind.call(console.group, console),
groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console),
groupEnd: Function.prototype.bind.call(console.groupEnd, console),
time: Function.prototype.bind.call(console.time, console),
timeEnd: Function.prototype.bind.call(console.timeEnd, console),
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
};
let logLevel;
let enableLog;
if (typeof exports === "object") {
// in nodejs and not running with jest
enableLog = process.env.JEST_WORKER_ID === undefined;
} else {
// in browser and not running with jsdom
enableLog = typeof window === "object" && window.name !== "jsdom";
}
logLevel.setLogLevel = function (newLevel) {
if (newLevel) {
Object.keys(logLevel).forEach(function (key, index) {
if (!newLevel.includes(key.toLocaleUpperCase())) {
logLevel[key] = function () {};
}
});
}
};
if (enableLog) {
logLevel = {
debug: Function.prototype.bind.call(console.debug, console),
log: Function.prototype.bind.call(console.log, console),
info: Function.prototype.bind.call(console.info, console),
warn: Function.prototype.bind.call(console.warn, console),
error: Function.prototype.bind.call(console.error, console),
group: Function.prototype.bind.call(console.group, console),
groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console),
groupEnd: Function.prototype.bind.call(console.groupEnd, console),
time: Function.prototype.bind.call(console.time, console),
timeEnd: Function.prototype.bind.call(console.timeEnd, console),
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
};
logLevel.setLogLevel = function (newLevel) {
if (newLevel) {
Object.keys(logLevel).forEach(function (key, index) {
if (!newLevel.includes(key.toLocaleUpperCase())) {
logLevel[key] = function () {};
}
});
}
};
} else {
logLevel = {
debug: function () {},
log: function () {},
info: function () {},
warn: function () {},
error: function () {},
group: function () {},
groupCollapsed: function () {},
groupEnd: function () {},
time: function () {},
timeEnd: function () {},
timeStamp: function () {}
};
logLevel.setLogLevel = function () {};
}
return logLevel;
});

View File

@@ -1,30 +1,30 @@
/* global Loader, defaults, Translator */
/* Magic Mirror
/* MagicMirror²
* Main System
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var MM = (function () {
var modules = [];
const MM = (function () {
let modules = [];
/* Private Methods */
/**
* Create dom objects for all modules that are configured for a specific position.
*/
var createDomObjects = function () {
var domCreationPromises = [];
const createDomObjects = function () {
const domCreationPromises = [];
modules.forEach(function (module) {
if (typeof module.data.position !== "string") {
return;
}
var wrapper = selectWrapper(module.data.position);
const wrapper = selectWrapper(module.data.position);
var dom = document.createElement("div");
const dom = document.createElement("div");
dom.id = module.identifier;
dom.className = module.name;
@@ -35,7 +35,7 @@ var MM = (function () {
dom.opacity = 0;
wrapper.appendChild(dom);
var moduleHeader = document.createElement("header");
const moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.getHeader();
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
@@ -46,11 +46,11 @@ var MM = (function () {
moduleHeader.style.display = "block;";
}
var moduleContent = document.createElement("div");
const moduleContent = document.createElement("div");
moduleContent.className = "module-content";
dom.appendChild(moduleContent);
var domCreationPromise = updateDom(module, 0);
const domCreationPromise = updateDom(module, 0);
domCreationPromises.push(domCreationPromise);
domCreationPromise
.then(function () {
@@ -70,14 +70,13 @@ var MM = (function () {
* Select the wrapper dom object for a specific position.
*
* @param {string} position The name of the position.
*
* @returns {HTMLElement} the wrapper element
* @returns {HTMLElement | void} the wrapper element
*/
var selectWrapper = function (position) {
var classes = position.replace("_", " ");
var parentWrapper = document.getElementsByClassName(classes);
const selectWrapper = function (position) {
const classes = position.replace("_", " ");
const parentWrapper = document.getElementsByClassName(classes);
if (parentWrapper.length > 0) {
var wrapper = parentWrapper[0].getElementsByClassName("container");
const wrapper = parentWrapper[0].getElementsByClassName("container");
if (wrapper.length > 0) {
return wrapper[0];
}
@@ -92,9 +91,9 @@ var MM = (function () {
* @param {Module} sender The module that sent the notification.
* @param {Module} [sendTo] The (optional) module to send the notification to.
*/
var sendNotification = function (notification, payload, sender, sendTo) {
for (var m in modules) {
var module = modules[m];
const sendNotification = function (notification, payload, sender, sendTo) {
for (const m in modules) {
const module = modules[m];
if (module !== sender && (!sendTo || module === sendTo)) {
module.notificationReceived(notification, payload, sender);
}
@@ -106,13 +105,12 @@ var MM = (function () {
*
* @param {Module} module The module that needs an update.
* @param {number} [speed] The (optional) number of microseconds for the animation.
*
* @returns {Promise} Resolved when the dom is fully updated.
*/
var updateDom = function (module, speed) {
const updateDom = function (module, speed) {
return new Promise(function (resolve) {
var newContentPromise = module.getDom();
var newHeader = module.getHeader();
const newHeader = module.getHeader();
let newContentPromise = module.getDom();
if (!(newContentPromise instanceof Promise)) {
// convert to a promise if not already one to avoid if/else's everywhere
@@ -121,7 +119,7 @@ var MM = (function () {
newContentPromise
.then(function (newContent) {
var updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
const updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
updatePromise.then(resolve).catch(Log.error);
})
@@ -136,10 +134,9 @@ var MM = (function () {
* @param {number} [speed] The (optional) number of microseconds for the animation.
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
*
* @returns {Promise} Resolved when the module dom has been updated.
*/
var updateDomWithContent = function (module, speed, newHeader, newContent) {
const updateDomWithContent = function (module, speed, newHeader, newContent) {
return new Promise(function (resolve) {
if (module.hidden || !speed) {
updateModuleContent(module, newHeader, newContent);
@@ -174,26 +171,25 @@ var MM = (function () {
* @param {Module} module The module to check.
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
*
* @returns {boolean} True if the module need an update, false otherwise
*/
var moduleNeedsUpdate = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier);
const moduleNeedsUpdate = function (module, newHeader, newContent) {
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {
return false;
}
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
var headerNeedsUpdate = false;
var contentNeedsUpdate = false;
let headerNeedsUpdate = false;
let contentNeedsUpdate;
if (headerWrapper.length > 0) {
headerNeedsUpdate = newHeader !== headerWrapper[0].innerHTML;
}
var tempContentWrapper = document.createElement("div");
const tempContentWrapper = document.createElement("div");
tempContentWrapper.appendChild(newContent);
contentNeedsUpdate = tempContentWrapper.innerHTML !== contentWrapper[0].innerHTML;
@@ -207,13 +203,13 @@ var MM = (function () {
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
*/
var updateModuleContent = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier);
const updateModuleContent = function (module, newHeader, newContent) {
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {
return;
}
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
contentWrapper[0].innerHTML = "";
contentWrapper[0].appendChild(newContent);
@@ -234,7 +230,7 @@ var MM = (function () {
* @param {Function} callback Called when the animation is done.
* @param {object} [options] Optional settings for the hide method.
*/
var hideModule = function (module, speed, callback, options) {
const hideModule = function (module, speed, callback, options) {
options = options || {};
// set lockString if set in options.
@@ -245,10 +241,11 @@ var MM = (function () {
}
}
var moduleWrapper = document.getElementById(module.identifier);
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
moduleWrapper.style.opacity = 0;
moduleWrapper.classList.add("hidden");
clearTimeout(module.showHideTimer);
module.showHideTimer = setTimeout(function () {
@@ -280,12 +277,12 @@ var MM = (function () {
* @param {Function} callback Called when the animation is done.
* @param {object} [options] Optional settings for the show method.
*/
var showModule = function (module, speed, callback, options) {
const showModule = function (module, speed, callback, options) {
options = options || {};
// remove lockString if set in options.
if (options.lockString) {
var index = module.lockStrings.indexOf(options.lockString);
const index = module.lockStrings.indexOf(options.lockString);
if (index !== -1) {
module.lockStrings.splice(index, 1);
}
@@ -309,16 +306,17 @@ var MM = (function () {
module.lockStrings = [];
}
var moduleWrapper = document.getElementById(module.identifier);
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
// Restore the position. See hideModule() for more info.
moduleWrapper.style.position = "static";
moduleWrapper.classList.remove("hidden");
updateWrapperStates();
// Waiting for DOM-changes done in updateWrapperStates before we can start the animation.
var dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
const dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
moduleWrapper.style.opacity = 1;
clearTimeout(module.showHideTimer);
@@ -346,14 +344,14 @@ var MM = (function () {
* an ugly top margin. By using this function, the top bar will be hidden if the
* update notification is not visible.
*/
var updateWrapperStates = function () {
var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
const updateWrapperStates = function () {
const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
positions.forEach(function (position) {
var wrapper = selectWrapper(position);
var moduleWrappers = wrapper.getElementsByClassName("module");
const wrapper = selectWrapper(position);
const moduleWrappers = wrapper.getElementsByClassName("module");
var showWrapper = false;
let showWrapper = false;
Array.prototype.forEach.call(moduleWrappers, function (moduleWrapper) {
if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
showWrapper = true;
@@ -367,7 +365,7 @@ var MM = (function () {
/**
* Loads the core config and combines it with the system defaults.
*/
var loadConfig = function () {
const loadConfig = function () {
// FIXME: Think about how to pass config around without breaking tests
/* eslint-disable */
if (typeof config === "undefined") {
@@ -385,15 +383,14 @@ var MM = (function () {
*
* @param {Module[]} modules Array of modules.
*/
var setSelectionMethodsForModules = function (modules) {
const setSelectionMethodsForModules = function (modules) {
/**
* Filter modules with the specified classes.
*
* @param {string|string[]} className one or multiple classnames (array or space divided).
*
* @returns {Module[]} Filtered collection of modules.
*/
var withClass = function (className) {
const withClass = function (className) {
return modulesByClass(className, true);
};
@@ -401,10 +398,9 @@ var MM = (function () {
* Filter modules without the specified classes.
*
* @param {string|string[]} className one or multiple classnames (array or space divided).
*
* @returns {Module[]} Filtered collection of modules.
*/
var exceptWithClass = function (className) {
const exceptWithClass = function (className) {
return modulesByClass(className, false);
};
@@ -413,20 +409,18 @@ var MM = (function () {
*
* @param {string|string[]} className one or multiple classnames (array or space divided).
* @param {boolean} include if the filter should include or exclude the modules with the specific classes.
*
* @returns {Module[]} Filtered collection of modules.
*/
var modulesByClass = function (className, include) {
var searchClasses = className;
const modulesByClass = function (className, include) {
let searchClasses = className;
if (typeof className === "string") {
searchClasses = className.split(" ");
}
var newModules = modules.filter(function (module) {
var classes = module.data.classes.toLowerCase().split(" ");
const newModules = modules.filter(function (module) {
const classes = module.data.classes.toLowerCase().split(" ");
for (var c in searchClasses) {
var searchClass = searchClasses[c];
for (const searchClass of searchClasses) {
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
return include;
}
@@ -445,8 +439,8 @@ var MM = (function () {
* @param {object} module The module instance to remove from the collection.
* @returns {Module[]} Filtered collection of modules.
*/
var exceptModule = function (module) {
var newModules = modules.filter(function (mod) {
const exceptModule = function (module) {
const newModules = modules.filter(function (mod) {
return mod.identifier !== module.identifier;
});
@@ -459,7 +453,7 @@ var MM = (function () {
*
* @param {Function} callback The function to execute with the module as an argument.
*/
var enumerate = function (callback) {
const enumerate = function (callback) {
modules.map(function (module) {
callback(module);
});
@@ -486,7 +480,7 @@ var MM = (function () {
* Main init method.
*/
init: function () {
Log.info("Initializing MagicMirror.");
Log.info("Initializing MagicMirror².");
loadConfig();
Log.setLogLevel(config.logLevel);
@@ -604,11 +598,11 @@ if (typeof Object.assign !== "function") {
if (target === undefined || target === null) {
throw new TypeError("Cannot convert undefined or null to object");
}
var output = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
const output = Object(target);
for (let index = 1; index < arguments.length; index++) {
const source = arguments[index];
if (source !== undefined && source !== null) {
for (var nextKey in source) {
for (const nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}

View File

@@ -1,19 +1,18 @@
/* global Class, cloneObject, Loader, MMSocket, nunjucks, Translator */
/* Magic Mirror
/* MagicMirror²
* Module Blueprint.
* @typedef {Object} Module
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*
*/
var Module = Class.extend({
const Module = Class.extend({
/*********************************************************
* All methods (and properties) below can be subclassed. *
*********************************************************/
// Set the minimum MagicMirror module version for this module.
// Set the minimum MagicMirror² module version for this module.
requiresVersion: "2.0.0",
// Module config defaults.
@@ -75,23 +74,22 @@ var Module = Class.extend({
},
/**
* Generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
* Generates the dom which needs to be displayed. This method is called by the MagicMirror² core.
* This method can to be subclassed if the module wants to display info on the mirror.
* Alternatively, the getTemplate method could be subclassed.
*
* @returns {HTMLElement|Promise} The dom or a promise with the dom to display.
*/
getDom: function () {
var self = this;
return new Promise(function (resolve) {
var div = document.createElement("div");
var template = self.getTemplate();
var templateData = self.getTemplateData();
return new Promise((resolve) => {
const div = document.createElement("div");
const template = this.getTemplate();
const templateData = this.getTemplateData();
// Check to see if we need to render a template string or a file.
if (/^.*((\.html)|(\.njk))$/.test(template)) {
// the template is a filename
self.nunjucksEnvironment().render(template, templateData, function (err, res) {
this.nunjucksEnvironment().render(template, templateData, function (err, res) {
if (err) {
Log.error(err);
}
@@ -102,7 +100,7 @@ var Module = Class.extend({
});
} else {
// the template is a template string.
div.innerHTML = self.nunjucksEnvironment().renderString(template, templateData);
div.innerHTML = this.nunjucksEnvironment().renderString(template, templateData);
resolve(div);
}
@@ -111,7 +109,7 @@ var Module = Class.extend({
/**
* Generates the header string which needs to be displayed if a user has a header configured for this module.
* This method is called by the Magic Mirror core, but only if the user has configured a default header for the module.
* This method is called by the MagicMirror² core, but only if the user has configured a default header for the module.
* This method needs to be subclassed if the module wants to display modified headers on the mirror.
*
* @returns {string} The header to display above the header.
@@ -143,7 +141,7 @@ var Module = Class.extend({
},
/**
* Called by the Magic Mirror core when a notification arrives.
* Called by the MagicMirror² core when a notification arrives.
*
* @param {string} notification The identifier of the notification.
* @param {*} payload The payload of the notification.
@@ -168,15 +166,13 @@ var Module = Class.extend({
return this._nunjucksEnvironment;
}
var self = this;
this._nunjucksEnvironment = new nunjucks.Environment(new nunjucks.WebLoader(this.file(""), { async: true }), {
trimBlocks: true,
lstripBlocks: true
});
this._nunjucksEnvironment.addFilter("translate", function (str, variables) {
return nunjucks.runtime.markSafe(self.translate(str, variables));
this._nunjucksEnvironment.addFilter("translate", (str, variables) => {
return nunjucks.runtime.markSafe(this.translate(str, variables));
});
return this._nunjucksEnvironment;
@@ -192,14 +188,14 @@ var Module = Class.extend({
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
/*
/**
* Called when the module is hidden.
*/
suspend: function () {
Log.log(this.name + " is suspended.");
},
/*
/**
* Called when the module is shown.
*/
resume: function () {
@@ -213,7 +209,7 @@ var Module = Class.extend({
/**
* Set the module data.
*
* @param {Module} data The module data
* @param {object} data The module data
*/
setData: function (data) {
this.data = data;
@@ -245,9 +241,8 @@ var Module = Class.extend({
this._socket = new MMSocket(this.name);
}
var self = this;
this._socket.setNotificationCallback(function (notification, payload) {
self.socketNotificationReceived(notification, payload);
this._socket.setNotificationCallback((notification, payload) => {
this.socketNotificationReceived(notification, payload);
});
return this._socket;
@@ -288,13 +283,12 @@ var Module = Class.extend({
* @param {Function} callback Function called when done.
*/
loadDependencies: function (funcName, callback) {
var self = this;
var dependencies = this[funcName]();
let dependencies = this[funcName]();
var loadNextDependency = function () {
const loadNextDependency = () => {
if (dependencies.length > 0) {
var nextDependency = dependencies[0];
Loader.loadFile(nextDependency, self, function () {
const nextDependency = dependencies[0];
Loader.loadFile(nextDependency, this, () => {
dependencies = dependencies.slice(1);
loadNextDependency();
});
@@ -400,12 +394,11 @@ var Module = Class.extend({
callback = callback || function () {};
options = options || {};
var self = this;
MM.hideModule(
self,
this,
speed,
function () {
self.suspend();
() => {
this.suspend();
callback();
},
options
@@ -441,7 +434,7 @@ var Module = Class.extend({
});
/**
* Merging MagicMirror (or other) default/config script by @bugsounet
* Merging MagicMirror² (or other) default/config script by @bugsounet
* Merge 2 objects or/with array
*
* Usage:
@@ -464,9 +457,9 @@ var Module = Class.extend({
* @returns {object} the merged config
*/
function configMerge(result) {
var stack = Array.prototype.slice.call(arguments, 1);
var item;
var key;
const stack = Array.prototype.slice.call(arguments, 1);
let item, key;
while (stack.length) {
item = stack.shift();
for (key in item) {
@@ -494,19 +487,19 @@ Module.create = function (name) {
return;
}
var moduleDefinition = Module.definitions[name];
var clonedDefinition = cloneObject(moduleDefinition);
const moduleDefinition = Module.definitions[name];
const clonedDefinition = cloneObject(moduleDefinition);
// Note that we clone the definition. Otherwise the objects are shared, which gives problems.
var ModuleClass = Module.extend(clonedDefinition);
const ModuleClass = Module.extend(clonedDefinition);
return new ModuleClass();
};
Module.register = function (name, moduleDefinition) {
if (moduleDefinition.requiresVersion) {
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version);
if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) {
Log.log("Check MagicMirror² version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.mmVersion);
if (cmpVersions(window.mmVersion, moduleDefinition.requiresVersion) >= 0) {
Log.log("Version is ok!");
} else {
Log.warn("Version is incorrect. Skip module: '" + name + "'");
@@ -517,6 +510,8 @@ Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition;
};
window.Module = Module;
/**
* Compare two semantic version numbers and return the difference.
*
@@ -526,14 +521,13 @@ Module.register = function (name, moduleDefinition) {
* number if a is smaller and 0 if they are the same
*/
function cmpVersions(a, b) {
var i, diff;
var regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, "").split(".");
var segmentsB = b.replace(regExStrip0, "").split(".");
var l = Math.min(segmentsA.length, segmentsB.length);
const regExStrip0 = /(\.0+)+$/;
const segmentsA = a.replace(regExStrip0, "").split(".");
const segmentsB = b.replace(regExStrip0, "").split(".");
const l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
for (let i = 0; i < l; i++) {
let diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
if (diff) {
return diff;
}

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Node Helper Superclass
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -22,39 +22,38 @@ const NodeHelper = Class.extend({
Log.log(`Starting module helper: ${this.name}`);
},
/* stop()
* Called when the MagicMirror server receives a `SIGINT`
/**
* Called when the MagicMirror² server receives a `SIGINT`
* Close any open connections, stop any sub-processes and
* gracefully exit the module.
*
*/
stop() {
Log.log(`Stopping module helper: ${this.name}`);
},
/* socketNotificationReceived(notification, payload)
/**
* This method is called when a socket notification arrives.
*
* argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification.
* @param {string} notification The identifier of the notification.
* @param {*} payload The payload of the notification.
*/
socketNotificationReceived(notification, payload) {
Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`);
},
/* setName(name)
/**
* Set the module name.
*
* argument name string - Module name.
* @param {string} name Module name.
*/
setName(name) {
this.name = name;
},
/* setPath(path)
/**
* Set the module path.
*
* argument path string - Module path.
* @param {string} path Module path.
*/
setPath(path) {
this.path = path;
@@ -113,6 +112,32 @@ const NodeHelper = Class.extend({
}
});
NodeHelper.checkFetchStatus = function (response) {
// response.status >= 200 && response.status < 300
if (response.ok) {
return response;
} else {
throw Error(response.statusText);
}
};
/**
* Look at the specified error and return an appropriate error type, that
* can be translated to a detailed error message
*
* @param {Error} error the error from fetching something
* @returns {string} the string of the detailed error message in the translations
*/
NodeHelper.checkFetchError = function (error) {
let error_type = "MODULE_ERROR_UNSPECIFIED";
if (error.code === "EAI_AGAIN") {
error_type = "MODULE_ERROR_NO_CONNECTION";
} else if (error.message === "Unauthorized") {
error_type = "MODULE_ERROR_UNAUTHORIZED";
}
return error_type;
};
NodeHelper.create = function (moduleDefinition) {
return NodeHelper.extend(moduleDefinition);
};

View File

@@ -1,11 +1,10 @@
/* Magic Mirror
/* MagicMirror²
* Server
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
const express = require("express");
const app = require("express")();
const path = require("path");
const ipfilter = require("express-ipfilter").IpFilter;
const fs = require("fs");
@@ -13,85 +12,108 @@ const helmet = require("helmet");
const Log = require("logger");
const Utils = require("./utils.js");
const { cors, getConfig, getHtml, getVersion } = require("./server_functions.js");
/**
* Server
*
* @param {object} config The MM config
* @param {Function} callback Function called when done.
* @class
*/
function Server(config, callback) {
function Server(config) {
const app = express();
const port = process.env.MM_PORT || config.port;
const serverSockets = new Set();
let server = null;
if (config.useHttps) {
const options = {
key: fs.readFileSync(config.httpsPrivateKey),
cert: fs.readFileSync(config.httpsCertificate)
};
server = require("https").Server(options, app);
} else {
server = require("http").Server(app);
}
const io = require("socket.io")(server, {
cors: {
origin: /.*$/,
credentials: true
},
allowEIO3: true
});
Log.log(`Starting server on port ${port} ... `);
server.listen(port, config.address || "localhost");
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
}
app.use(function (req, res, next) {
ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
if (err === undefined) {
return next();
/**
* Opens the server for incoming connections
*
* @returns {Promise} A promise that is resolved when the server listens to connections
*/
this.open = function () {
return new Promise((resolve) => {
if (config.useHttps) {
const options = {
key: fs.readFileSync(config.httpsPrivateKey),
cert: fs.readFileSync(config.httpsCertificate)
};
server = require("https").Server(options, app);
} else {
server = require("http").Server(app);
}
Log.log(err.message);
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
const io = require("socket.io")(server, {
cors: {
origin: /.*$/,
credentials: true
},
allowEIO3: true
});
server.on("connection", (socket) => {
serverSockets.add(socket);
socket.on("close", () => {
serverSockets.delete(socket);
});
});
Log.log(`Starting server on port ${port} ... `);
server.listen(port, config.address || "localhost");
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
}
app.use(function (req, res, next) {
ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
if (err === undefined) {
res.header("Access-Control-Allow-Origin", "*");
return next();
}
Log.log(err.message);
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
});
});
app.use(helmet(config.httpHeaders));
app.use("/js", express.static(__dirname));
// TODO add tests directory only when running tests?
const 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)));
}
app.get("/cors", async (req, res) => await cors(req, res));
app.get("/version", (req, res) => getVersion(req, res));
app.get("/config", (req, res) => getConfig(req, res));
app.get("/", (req, res) => getHtml(req, res));
server.on("listening", () => {
resolve({
app,
io
});
});
});
});
app.use(helmet({ contentSecurityPolicy: false }));
};
app.use("/js", express.static(__dirname));
const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
for (const directory of directories) {
app.use(directory, express.static(path.resolve(global.root_path + directory)));
}
app.get("/version", function (req, res) {
res.send(global.version);
});
app.get("/config", function (req, res) {
res.send(config);
});
app.get("/", function (req, res) {
let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
html = html.replace("#VERSION#", global.version);
let configFile = "config/config.js";
if (typeof global.configuration_file !== "undefined") {
configFile = global.configuration_file;
}
html = html.replace("#CONFIG_FILE#", configFile);
res.send(html);
});
if (typeof callback === "function") {
callback(app, io);
}
/**
* Closes the server and destroys all lingering connections to it.
*
* @returns {Promise} A promise that resolves when server has successfully shut down
*/
this.close = function () {
return new Promise((resolve) => {
for (const socket of serverSockets.values()) {
socket.destroy();
}
server.close(resolve);
});
};
}
module.exports = Server;

127
js/server_functions.js Normal file
View File

@@ -0,0 +1,127 @@
const fetch = require("./fetch");
const fs = require("fs");
const path = require("path");
const Log = require("logger");
/**
* Gets the config.
*
* @param {Request} req - the request
* @param {Response} res - the result
*/
function getConfig(req, res) {
res.send(config);
}
/**
* A method that forewards HTTP Get-methods to the internet to avoid CORS-errors.
*
* Example input request url: /cors?sendheaders=header1:value1,header2:value2&expectedheaders=header1,header2&url=http://www.test.com/path?param1=value1
*
* Only the url-param of the input request url is required. It must be the last parameter.
*
* @param {Request} req - the request
* @param {Response} res - the result
*/
async function cors(req, res) {
try {
const urlRegEx = "url=(.+?)$";
let url = "";
const match = new RegExp(urlRegEx, "g").exec(req.url);
if (!match) {
url = "invalid url: " + req.url;
Log.error(url);
res.send(url);
} else {
url = match[1];
const headersToSend = getHeadersToSend(req.url);
const expectedRecievedHeaders = geExpectedRecievedHeaders(req.url);
Log.log("cors url: " + url);
const response = await fetch(url, { headers: headersToSend });
for (const header of expectedRecievedHeaders) {
const headerValue = response.headers.get(header);
if (header) res.set(header, headerValue);
}
const data = await response.text();
res.send(data);
}
} catch (error) {
Log.error(error);
res.send(error);
}
}
/**
* Gets headers and values to attatch to the web request.
*
* @param {string} url - The url containing the headers and values to send.
* @returns {object} An object specifying name and value of the headers.
*/
function getHeadersToSend(url) {
const headersToSend = { "User-Agent": "Mozilla/5.0 MagicMirror/" + global.version };
const headersToSendMatch = new RegExp("sendheaders=(.+?)(&|$)", "g").exec(url);
if (headersToSendMatch) {
const headers = headersToSendMatch[1].split(",");
for (const header of headers) {
const keyValue = header.split(":");
if (keyValue.length !== 2) {
throw new Error(`Invalid format for header ${header}`);
}
headersToSend[keyValue[0]] = decodeURIComponent(keyValue[1]);
}
}
return headersToSend;
}
/**
* Gets the headers expected from the response.
*
* @param {string} url - The url containing the expected headers from the response.
* @returns {string[]} headers - The name of the expected headers.
*/
function geExpectedRecievedHeaders(url) {
const expectedRecievedHeaders = ["Content-Type"];
const expectedRecievedHeadersMatch = new RegExp("expectedheaders=(.+?)(&|$)", "g").exec(url);
if (expectedRecievedHeadersMatch) {
const headers = expectedRecievedHeadersMatch[1].split(",");
for (const header of headers) {
expectedRecievedHeaders.push(header);
}
}
return expectedRecievedHeaders;
}
/**
* Gets the HTML to display the magic mirror.
*
* @param {Request} req - the request
* @param {Response} res - the result
*/
function getHtml(req, res) {
let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
html = html.replace("#VERSION#", global.version);
let configFile = "config/config.js";
if (typeof global.configuration_file !== "undefined") {
configFile = global.configuration_file;
}
html = html.replace("#CONFIG_FILE#", configFile);
res.send(html);
}
/**
* Gets the MagicMirror version.
*
* @param {Request} req - the request
* @param {Response} res - the result
*/
function getVersion(req, res) {
res.send(global.version);
}
module.exports = { cors, getConfig, getHtml, getVersion };

View File

@@ -1,54 +1,53 @@
/* global io */
/* Magic Mirror
/* MagicMirror²
* TODO add description
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var MMSocket = function (moduleName) {
var self = this;
const MMSocket = function (moduleName) {
if (typeof moduleName !== "string") {
throw new Error("Please set the module name for the MMSocket.");
}
self.moduleName = moduleName;
this.moduleName = moduleName;
// Private Methods
var base = "/";
let base = "/";
if (typeof config !== "undefined" && typeof config.basePath !== "undefined") {
base = config.basePath;
}
self.socket = io("/" + self.moduleName, {
this.socket = io("/" + this.moduleName, {
path: base + "socket.io"
});
var notificationCallback = function () {};
var onevent = self.socket.onevent;
self.socket.onevent = function (packet) {
var args = packet.data || [];
onevent.call(this, packet); // original call
let notificationCallback = function () {};
const onevent = this.socket.onevent;
this.socket.onevent = (packet) => {
const args = packet.data || [];
onevent.call(this.socket, packet); // original call
packet.data = ["*"].concat(args);
onevent.call(this, packet); // additional call to catch-all
onevent.call(this.socket, packet); // additional call to catch-all
};
// register catch all.
self.socket.on("*", function (notification, payload) {
this.socket.on("*", (notification, payload) => {
if (notification !== "*") {
notificationCallback(notification, payload);
}
});
// Public Methods
this.setNotificationCallback = function (callback) {
this.setNotificationCallback = (callback) => {
notificationCallback = callback;
};
this.sendNotification = function (notification, payload) {
this.sendNotification = (notification, payload) => {
if (typeof payload === "undefined") {
payload = {};
}
self.socket.emit(notification, payload);
this.socket.emit(notification, payload);
};
};

View File

@@ -1,12 +1,12 @@
/* global translations */
/* Magic Mirror
/* MagicMirror²
* Translator (l10n)
*
* By Christopher Fenner https://github.com/CFenner
* MIT Licensed.
*/
var Translator = (function () {
const Translator = (function () {
/**
* Load a JSON file via XHR.
*
@@ -104,7 +104,7 @@ var Translator = (function () {
* @param {Function} callback Function called when done.
*/
load(module, file, isFallback, callback) {
Log.log(`${module.name} - Load translation${isFallback && " fallback"}: ${file}`);
Log.log(`${module.name} - Load translation${isFallback ? " fallback" : ""}: ${file}`);
if (this.translationsFallback[module.name]) {
callback();
@@ -141,12 +141,7 @@ var Translator = (function () {
* The first language defined in translations.js will be used.
*/
loadCoreTranslationsFallback: function () {
// The variable `first` will contain the first
// defined translation after the following line.
for (var first in translations) {
break;
}
let first = Object.keys(translations)[0];
if (first) {
Log.log("Loading core translation fallback file: " + translations[first]);
loadJSON(translations[first], (translations) => {
@@ -156,3 +151,5 @@ var Translator = (function () {
}
};
})();
window.Translator = Translator;

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Utils
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com

View File

@@ -1,16 +1,16 @@
type ModuleProperties = {
defaults?: object,
start?(): void,
getHeader?(): string,
getTemplate?(): string,
getTemplateData?(): object,
notificationReceived?(notification: string, payload: any, sender: object): void,
socketNotificationReceived?(notification: string, payload: any): void,
suspend?(): void,
resume?(): void,
getDom?(): HTMLElement,
getStyles?(): string[],
[key: string]: any,
defaults?: object;
start?(): void;
getHeader?(): string;
getTemplate?(): string;
getTemplateData?(): object;
notificationReceived?(notification: string, payload: any, sender: object): void;
socketNotificationReceived?(notification: string, payload: any): void;
suspend?(): void;
resume?(): void;
getDom?(): HTMLElement;
getStyles?(): string[];
[key: string]: any;
};
export declare const Module: {
@@ -18,14 +18,14 @@ export declare const Module: {
};
export declare const Log: {
info(message?: any, ...optionalParams: any[]): void,
log(message?: any, ...optionalParams: any[]): void,
error(message?: any, ...optionalParams: any[]): void,
warn(message?: any, ...optionalParams: any[]): void,
group(groupTitle?: string, ...optionalParams: any[]): void,
groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void,
groupEnd(): void,
time(timerName?: string): void,
timeEnd(timerName?: string): void,
timeStamp(timerName?: string): void,
};
info(message?: any, ...optionalParams: any[]): void;
log(message?: any, ...optionalParams: any[]): void;
error(message?: any, ...optionalParams: any[]): void;
warn(message?: any, ...optionalParams: any[]): void;
group(groupTitle?: string, ...optionalParams: any[]): void;
groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void;
groupEnd(): void;
time(timerName?: string): void;
timeEnd(timerName?: string): void;
timeStamp(timerName?: string): void;
};

View File

@@ -1,5 +1,5 @@
# Module: Alert
The alert module is one of the default modules of the MagicMirror. This module displays notifications from other modules.
The alert module is one of the default modules of the MagicMirror². This module displays notifications from other modules.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/alert.html).

View File

@@ -1,167 +1,146 @@
/* global NotificationFx */
/* Magic Mirror
/* MagicMirror²
* Module: alert
*
* By Paul-Vincent Roll https://paulvincentroll.com/
* MIT Licensed.
*/
Module.register("alert", {
alerts: {},
defaults: {
// scale|slide|genie|jelly|flip|bouncyflip|exploader
effect: "slide",
// scale|slide|genie|jelly|flip|bouncyflip|exploader
alert_effect: "jelly",
//time a notification is displayed in seconds
display_time: 3500,
//Position
effect: "slide", // scale|slide|genie|jelly|flip|bouncyflip|exploader
alert_effect: "jelly", // scale|slide|genie|jelly|flip|bouncyflip|exploader
display_time: 3500, // time a notification is displayed in seconds
position: "center",
//shown at startup
welcome_message: false
welcome_message: false // shown at startup
},
getScripts: function () {
getScripts() {
return ["notificationFx.js"];
},
getStyles: function () {
return ["notificationFx.css", "font-awesome.css"];
getStyles() {
return ["font-awesome.css", this.file(`./styles/notificationFx.css`), this.file(`./styles/${this.config.position}.css`)];
},
// Define required translations.
getTranslations: function () {
getTranslations() {
return {
en: "translations/en.json",
bg: "translations/bg.json",
da: "translations/da.json",
de: "translations/de.json",
nl: "translations/nl.json"
en: "translations/en.json",
es: "translations/es.json",
fr: "translations/fr.json",
hu: "translations/hu.json",
nl: "translations/nl.json",
ru: "translations/ru.json"
};
},
show_notification: function (message) {
getTemplate(type) {
return `templates/${type}.njk`;
},
start() {
Log.info(`Starting module: ${this.name}`);
if (this.config.effect === "slide") {
this.config.effect = this.config.effect + "-" + this.config.position;
this.config.effect = `${this.config.effect}-${this.config.position}`;
}
let msg = "";
if (message.title) {
msg += "<span class='thin dimmed medium'>" + message.title + "</span>";
if (this.config.welcome_message) {
const message = this.config.welcome_message === true ? this.translate("welcome") : this.config.welcome_message;
this.showNotification({ title: this.translate("sysTitle"), message });
}
if (message.message) {
if (msg !== "") {
msg += "<br />";
},
notificationReceived(notification, payload, sender) {
if (notification === "SHOW_ALERT") {
if (payload.type === "notification") {
this.showNotification(payload);
} else {
this.showAlert(payload, sender);
}
msg += "<span class='light bright small'>" + message.message + "</span>";
} else if (notification === "HIDE_ALERT") {
this.hideAlert(sender);
}
},
async showNotification(notification) {
const message = await this.renderMessage("notification", notification);
new NotificationFx({
message: msg,
message,
layout: "growl",
effect: this.config.effect,
ttl: message.timer !== undefined ? message.timer : this.config.display_time
ttl: notification.timer || this.config.display_time
}).show();
},
show_alert: function (params, sender) {
let image = "";
//Set standard params if not provided by module
if (typeof params.timer === "undefined") {
params.timer = null;
}
if (typeof params.imageHeight === "undefined") {
params.imageHeight = "80px";
}
if (typeof params.imageUrl === "undefined" && typeof params.imageFA === "undefined") {
params.imageUrl = null;
} else if (typeof params.imageFA === "undefined") {
image = "<img src='" + params.imageUrl.toString() + "' height='" + params.imageHeight.toString() + "' style='margin-bottom: 10px;'/><br />";
} else if (typeof params.imageUrl === "undefined") {
image = "<span class='bright " + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;font-size:" + params.imageHeight.toString() + ";'/></span><br />";
}
//Create overlay
const overlay = document.createElement("div");
overlay.id = "overlay";
overlay.innerHTML += '<div class="black_overlay"></div>';
document.body.insertBefore(overlay, document.body.firstChild);
//If module already has an open alert close it
async showAlert(alert, sender) {
// If module already has an open alert close it
if (this.alerts[sender.name]) {
this.hide_alert(sender);
this.hideAlert(sender, false);
}
//Display title and message only if they are provided in notification parameters
let message = "";
if (params.title) {
message += "<span class='light dimmed medium'>" + params.title + "</span>";
}
if (params.message) {
if (message !== "") {
message += "<br />";
}
message += "<span class='thin bright small'>" + params.message + "</span>";
// Add overlay
if (!Object.keys(this.alerts).length) {
this.toggleBlur(true);
}
//Store alert in this.alerts
const message = await this.renderMessage("alert", alert);
// Store alert in this.alerts
this.alerts[sender.name] = new NotificationFx({
message: image + message,
message,
effect: this.config.alert_effect,
ttl: params.timer,
onClose: () => this.hide_alert(sender),
ttl: alert.timer,
onClose: () => this.hideAlert(sender),
al_no: "ns-alert"
});
//Show alert
// Show alert
this.alerts[sender.name].show();
//Add timer to dismiss alert and overlay
if (params.timer) {
// Add timer to dismiss alert and overlay
if (alert.timer) {
setTimeout(() => {
this.hide_alert(sender);
}, params.timer);
this.hideAlert(sender);
}, alert.timer);
}
},
hide_alert: function (sender) {
//Dismiss alert and remove from this.alerts
hideAlert(sender, close = true) {
// Dismiss alert and remove from this.alerts
if (this.alerts[sender.name]) {
this.alerts[sender.name].dismiss();
this.alerts[sender.name] = null;
//Remove overlay
const overlay = document.getElementById("overlay");
overlay.parentNode.removeChild(overlay);
}
},
setPosition: function (pos) {
//Add css to body depending on the set position for notifications
const sheet = document.createElement("style");
if (pos === "center") {
sheet.innerHTML = ".ns-box {margin-left: auto; margin-right: auto;text-align: center;}";
}
if (pos === "right") {
sheet.innerHTML = ".ns-box {margin-left: auto;text-align: right;}";
}
if (pos === "left") {
sheet.innerHTML = ".ns-box {margin-right: auto;text-align: left;}";
}
document.body.appendChild(sheet);
},
notificationReceived: function (notification, payload, sender) {
if (notification === "SHOW_ALERT") {
if (typeof payload.type === "undefined") {
payload.type = "alert";
}
if (payload.type === "alert") {
this.show_alert(payload, sender);
} else if (payload.type === "notification") {
this.show_notification(payload);
}
} else if (notification === "HIDE_ALERT") {
this.hide_alert(sender);
}
},
start: function () {
this.alerts = {};
this.setPosition(this.config.position);
if (this.config.welcome_message) {
if (this.config.welcome_message === true) {
this.show_notification({ title: this.translate("sysTitle"), message: this.translate("welcome") });
} else {
this.show_notification({ title: this.translate("sysTitle"), message: this.config.welcome_message });
this.alerts[sender.name].dismiss(close);
delete this.alerts[sender.name];
// Remove overlay
if (!Object.keys(this.alerts).length) {
this.toggleBlur(false);
}
}
Log.info("Starting module: " + this.name);
},
renderMessage(type, data) {
return new Promise((resolve) => {
this.nunjucksEnvironment().render(this.getTemplate(type), data, function (err, res) {
if (err) {
Log.error("Failed to render alert", err);
}
resolve(res);
});
});
},
toggleBlur(add = false) {
const method = add ? "add" : "remove";
const modules = document.querySelectorAll(".module");
for (const module of modules) {
module.classList[method]("alert-blur");
}
}
});

View File

@@ -9,6 +9,8 @@
*
* Copyright 2014, Codrops
* https://tympanus.net/codrops/
*
* @param {object} window The window object
*/
(function (window) {
/**
@@ -122,8 +124,10 @@
/**
* Dismiss the notification
*
* @param {boolean} [close] call the onClose callback at the end
*/
NotificationFx.prototype.dismiss = function () {
NotificationFx.prototype.dismiss = function (close = true) {
this.active = false;
clearTimeout(this.dismissttl);
this.ntf.classList.remove("ns-show");
@@ -131,7 +135,7 @@
this.ntf.classList.add("ns-hide");
// callback
this.options.onClose();
if (close) this.options.onClose();
}, 25);
// after animation ends remove ntf from the DOM

View File

@@ -0,0 +1,5 @@
.ns-box {
margin-left: auto;
margin-right: auto;
text-align: center;
}

View File

@@ -0,0 +1,4 @@
.ns-box {
margin-right: auto;
text-align: left;
}

View File

@@ -6,7 +6,6 @@
line-height: 1.4;
margin-bottom: 10px;
z-index: 1;
color: black;
font-size: 70%;
position: relative;
display: table;
@@ -15,17 +14,17 @@
border-width: 1px;
border-radius: 5px;
border-style: solid;
border-color: #666;
border-color: var(--color-text-dimmed);
}
.ns-alert {
border-style: solid;
border-color: #fff;
border-color: var(--color-text-bright);
padding: 17px;
line-height: 1.4;
margin-bottom: 10px;
z-index: 3;
color: white;
color: var(--color-text-bright);
font-size: 70%;
position: fixed;
text-align: center;
@@ -40,12 +39,8 @@
border-radius: 20px;
}
.black_overlay {
position: fixed;
z-index: 2;
background-color: rgba(0, 0, 0, 0.93);
width: 100%;
height: 100%;
.alert-blur {
filter: blur(2px) brightness(50%);
}
[class^="ns-effect-"].ns-growl.ns-hide,

View File

@@ -0,0 +1,4 @@
.ns-box {
margin-left: auto;
text-align: right;
}

View File

@@ -0,0 +1,18 @@
{% if imageUrl or imageFA %}
{% set imageHeight = imageHeight if imageHeight else "80px" %}
{% if imageUrl %}
<img src="{{ imageUrl }}" height="{{ imageHeight }}" style="margin-bottom: 10px;"/>
{% else %}
<span class="bright fas fa-{{ imageFA }}" style='margin-bottom: 10px; font-size: {{ imageHeight }};'/></span>
{% endif %}
<br/>
{% endif %}
{% if title %}
<span class="thin dimmed medium">{{ title }}</span>
{% endif %}
{% if message %}
{% if title %}
<br/>
{% endif %}
<span class="light bright small">{{ message }}</span>
{% endif %}

View File

@@ -0,0 +1,9 @@
{% if title %}
<span class="thin dimmed medium">{{ title }}</span>
{% endif %}
{% if message %}
{% if title %}
<br/>
{% endif %}
<span class="light bright small">{{ message }}</span>
{% endif %}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror нотификация",
"sysTitle": "MagicMirror² нотификация",
"welcome": "Добре дошли, стартирането беше успешно"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror Notifikation",
"sysTitle": "MagicMirror² Notifikation",
"welcome": "Velkommen, modulet er succesfuldt startet!"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror Benachrichtigung",
"sysTitle": "MagicMirror² Benachrichtigung",
"welcome": "Willkommen, Start war erfolgreich!"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror Notification",
"sysTitle": "MagicMirror² Notification",
"welcome": "Welcome, start was successful!"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror Notificaciones",
"sysTitle": "MagicMirror² Notificaciones",
"welcome": "Bienvenido, ¡se iniciado correctamente!"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror Notification",
"sysTitle": "MagicMirror² Notification",
"welcome": "Bienvenue, le démarrage a été un succès!"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror értesítés",
"sysTitle": "MagicMirror² értesítés",
"welcome": "Üdvözöljük, indulás sikeres!"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror Notificatie",
"sysTitle": "MagicMirror² Notificatie",
"welcome": "Welkom, Succesvol gestart!"
}

View File

@@ -1,4 +1,4 @@
{
"sysTitle": "MagicMirror Уведомление",
"sysTitle": "MagicMirror² Уведомление",
"welcome": "Добро пожаловать, старт был успешным!"
}

2
modules/default/calendar/README.md Executable file → Normal file
View File

@@ -1,6 +1,6 @@
# Module: Calendar
The `calendar` module is one of the default modules of the MagicMirror.
The `calendar` module is one of the default modules of the MagicMirror².
This module displays events from a public .ical calendar. It can combine multiple calendars.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/calendar.html).

View File

@@ -1,13 +1,14 @@
.calendar .symbol {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding-left: 0;
padding-right: 10px;
font-size: 80%;
vertical-align: top;
font-size: var(--font-size-small);
}
.calendar .symbol span {
display: inline-block;
transform: translate(0, 2px);
padding-top: 4px;
}
.calendar .title {

205
modules/default/calendar/calendar.js Executable file → Normal file
View File

@@ -1,6 +1,6 @@
/* global cloneObject */
/* Magic Mirror
/* MagicMirror²
* Module: Calendar
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -13,7 +13,8 @@ Module.register("calendar", {
maximumNumberOfDays: 365,
limitDays: 0, // Limit the number of days shown, 0 = no limit
displaySymbol: true,
defaultSymbol: "calendar", // Fontawesome Symbol see https://fontawesome.com/cheatsheet?from=io
defaultSymbol: "calendar-alt", // Fontawesome Symbol see https://fontawesome.com/cheatsheet?from=io
defaultSymbolClassName: "fas fa-fw fa-",
showLocation: false,
displayRepeatingCountTitle: false,
defaultRepeatingCountTitle: "",
@@ -37,13 +38,14 @@ Module.register("calendar", {
hidePrivate: false,
hideOngoing: false,
hideTime: false,
showTimeToday: false,
colored: false,
coloredSymbolOnly: false,
customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched
tableClass: "small",
calendars: [
{
symbol: "calendar",
symbol: "calendar-alt",
url: "https://www.calendarlabs.com/templates/ical/US-Holidays.ics"
}
],
@@ -84,7 +86,7 @@ Module.register("calendar", {
// Override start method.
start: function () {
Log.log("Starting module: " + this.name);
Log.info("Starting module: " + this.name);
// Set locale.
moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat));
@@ -133,6 +135,10 @@ Module.register("calendar", {
// Override socket notification handler.
socketNotificationReceived: function (notification, payload) {
if (notification === "FETCH_CALENDAR") {
this.sendSocketNotification(notification, { url: payload.url, id: this.identifier });
}
if (this.identifier !== payload.id) {
return;
}
@@ -140,17 +146,17 @@ Module.register("calendar", {
if (notification === "CALENDAR_EVENTS") {
if (this.hasCalendarURL(payload.url)) {
this.calendarData[payload.url] = payload.events;
this.error = null;
this.loaded = true;
if (this.config.broadcastEvents) {
this.broadcastEvents();
}
}
} else if (notification === "FETCH_ERROR") {
Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
} else if (notification === "CALENDAR_ERROR") {
let error_message = this.translate(payload.error_type);
this.error = this.translate("MODULE_CONFIG_ERROR", { MODULE_NAME: this.name, ERROR: error_message });
this.loaded = true;
} else if (notification === "INCORRECT_URL") {
Log.error("Calendar Error. Incorrect url: " + payload.url);
}
this.updateDom(this.config.animationSpeed);
@@ -158,16 +164,21 @@ Module.register("calendar", {
// Override dom generator.
getDom: function () {
// Define second, minute, hour, and day constants
const oneSecond = 1000; // 1,000 milliseconds
const oneMinute = oneSecond * 60;
const oneHour = oneMinute * 60;
const oneDay = oneHour * 24;
const ONE_SECOND = 1000; // 1,000 milliseconds
const ONE_MINUTE = ONE_SECOND * 60;
const ONE_HOUR = ONE_MINUTE * 60;
const ONE_DAY = ONE_HOUR * 24;
const events = this.createEventList();
const events = this.createEventList(true);
const wrapper = document.createElement("table");
wrapper.className = this.config.tableClass;
if (this.error) {
wrapper.innerHTML = this.error;
wrapper.className = this.config.tableClass + " dimmed";
return wrapper;
}
if (events.length === 0) {
wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING");
wrapper.className = this.config.tableClass + " dimmed";
@@ -194,6 +205,8 @@ Module.register("calendar", {
if (lastSeenDate !== dateAsString) {
const dateRow = document.createElement("tr");
dateRow.className = "normal";
if (event.today) dateRow.className += " today";
else if (event.tomorrow) dateRow.className += " tomorrow";
const dateCell = document.createElement("td");
dateCell.colSpan = "3";
@@ -219,6 +232,8 @@ Module.register("calendar", {
}
eventWrapper.className = "normal event";
if (event.today) eventWrapper.className += " today";
else if (event.tomorrow) eventWrapper.className += " tomorrow";
const symbolWrapper = document.createElement("td");
@@ -231,21 +246,9 @@ Module.register("calendar", {
symbolWrapper.className = "symbol align-right " + symbolClass;
const symbols = this.symbolsForEvent(event);
// If symbols are displayed and custom symbol is set, replace event symbol
if (this.config.displaySymbol && this.config.customEvents.length > 0) {
for (let ev in this.config.customEvents) {
if (typeof this.config.customEvents[ev].symbol !== "undefined" && this.config.customEvents[ev].symbol !== "") {
let needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
if (needle.test(event.title)) {
symbols[0] = this.config.customEvents[ev].symbol;
break;
}
}
}
}
symbols.forEach((s, index) => {
const symbol = document.createElement("span");
symbol.className = "fa fa-fw fa-" + s;
symbol.className = s;
if (index > 0) {
symbol.style.paddingLeft = "5px";
}
@@ -305,15 +308,20 @@ Module.register("calendar", {
if (this.config.timeFormat === "dateheaders") {
if (event.fullDayEvent) {
titleWrapper.colSpan = "2";
titleWrapper.align = "left";
titleWrapper.classList.add("align-left");
} else {
const timeWrapper = document.createElement("td");
timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
timeWrapper.align = "left";
timeWrapper.className = "time light align-left " + this.timeClassForUrl(event.url);
timeWrapper.style.paddingLeft = "2px";
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
// Add endDate to dataheaders if showEnd is enabled
if (this.config.showEnd) {
timeWrapper.innerHTML += " - " + moment(event.endDate, "x").format("LT");
}
eventWrapper.appendChild(timeWrapper);
titleWrapper.align = "right";
titleWrapper.classList.add("align-right");
}
eventWrapper.appendChild(titleWrapper);
@@ -334,10 +342,9 @@ Module.register("calendar", {
// 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 -= oneSecond;
event.endDate -= ONE_SECOND;
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
}
if (this.config.getRelative > 0 && event.startDate < now) {
} else if (this.config.getRelative > 0 && event.startDate < now) {
// Ongoing and getRelative is set
timeWrapper.innerHTML = this.capFirst(
this.translate("RUNNING", {
@@ -345,7 +352,7 @@ Module.register("calendar", {
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
})
);
} else if (this.config.urgency > 0 && event.startDate - now < this.config.urgency * oneDay) {
} else if (this.config.urgency > 0 && event.startDate - now < this.config.urgency * ONE_DAY) {
// Within urgency days
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
}
@@ -353,9 +360,9 @@ Module.register("calendar", {
// Full days events within the next two days
if (event.today) {
timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
} else if (event.startDate - now < oneDay && event.startDate - now > 0) {
} else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) {
timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
} else if (event.startDate - now < 2 * oneDay && event.startDate - now > 0) {
} else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) {
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
}
@@ -363,21 +370,33 @@ Module.register("calendar", {
}
} else {
// Show relative times
if (event.startDate >= now) {
// Use relative time
if (!this.config.hideTime) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
if (event.startDate >= now || (event.fullDayEvent && event.today)) {
// Use relative time
if (!this.config.hideTime && !event.fullDayEvent) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
} else {
timeWrapper.innerHTML = this.capFirst(
moment(event.startDate, "x").calendar(null, {
sameDay: "[" + this.translate("TODAY") + "]",
sameDay: this.config.showTimeToday ? "LT" : "[" + this.translate("TODAY") + "]",
nextDay: "[" + this.translate("TOMORROW") + "]",
nextWeek: "dddd"
nextWeek: "dddd",
sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat
})
);
}
if (event.startDate - now < this.config.getRelative * oneHour) {
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
if (event.fullDayEvent) {
// Full days events within the next two days
if (event.today) {
timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
} else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) {
timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
} else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) {
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
}
}
} else if (event.startDate - now < this.config.getRelative * ONE_HOUR) {
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
}
} else {
@@ -406,6 +425,8 @@ Module.register("calendar", {
if (event.location !== false) {
const locationRow = document.createElement("tr");
locationRow.className = "normal xsmall light";
if (event.today) locationRow.className += " today";
else if (event.tomorrow) locationRow.className += " tomorrow";
if (this.config.displaySymbol) {
const symbolCell = document.createElement("td");
@@ -472,9 +493,15 @@ Module.register("calendar", {
/**
* Creates the sorted list of all events.
*
* @param {boolean} limitNumberOfEntries Whether to filter returned events for display.
* @returns {object[]} Array with events.
*/
createEventList: function () {
createEventList: function (limitNumberOfEntries) {
const ONE_SECOND = 1000; // 1,000 milliseconds
const ONE_MINUTE = ONE_SECOND * 60;
const ONE_HOUR = ONE_MINUTE * 60;
const ONE_DAY = ONE_HOUR * 24;
const now = new Date();
const today = moment().startOf("day");
const future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
@@ -482,40 +509,44 @@ Module.register("calendar", {
for (const calendarUrl in this.calendarData) {
const calendar = this.calendarData[calendarUrl];
let remainingEntries = this.maximumEntriesForUrl(calendarUrl);
for (const e in calendar) {
const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
if (event.endDate < now) {
if (this.config.hidePrivate && event.class === "PRIVATE") {
// do not add the current event, skip it
continue;
}
if (this.config.hidePrivate) {
if (event.class === "PRIVATE") {
// do not add the current event, skip it
if (limitNumberOfEntries) {
if (event.endDate < now) {
continue;
}
}
if (this.config.hideOngoing) {
if (event.startDate < now) {
if (this.config.hideOngoing && event.startDate < now) {
continue;
}
}
if (this.listContainsEvent(events, event)) {
continue;
if (this.listContainsEvent(events, event)) {
continue;
}
if (--remainingEntries < 0) {
break;
}
}
event.url = calendarUrl;
event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
event.today = event.startDate >= today && event.startDate < today + ONE_DAY;
event.tomorrow = !event.today && event.startDate >= today + ONE_DAY && event.startDate < today + 2 * ONE_DAY;
/* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
*/
const maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / (1000 * 60 * 60 * 24)) + 1;
const maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / ONE_DAY) + 1;
if (this.config.sliceMultiDayEvents && maxCount > 1) {
const splitEvents = [];
let midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
let count = 1;
while (event.endDate > midnight) {
const thisEvent = JSON.parse(JSON.stringify(event)); // clone object
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + 24 * 60 * 60 * 1000;
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + ONE_DAY;
thisEvent.tomorrow = !thisEvent.today && thisEvent.startDate >= today + ONE_DAY && thisEvent.startDate < today + 2 * ONE_DAY;
thisEvent.endDate = midnight;
thisEvent.title += " (" + count + "/" + maxCount + ")";
splitEvents.push(thisEvent);
@@ -526,6 +557,8 @@ Module.register("calendar", {
}
// Last day
event.title += " (" + count + "/" + maxCount + ")";
event.today += event.startDate >= today && event.startDate < today + ONE_DAY;
event.tomorrow = !event.today && event.startDate >= today + ONE_DAY && event.startDate < today + 2 * ONE_DAY;
splitEvents.push(event);
for (let splitEvent of splitEvents) {
@@ -543,6 +576,10 @@ Module.register("calendar", {
return a.startDate - b.startDate;
});
if (!limitNumberOfEntries) {
return events;
}
// Limit the number of days displayed
// If limitDays is set > 0, limit display to that number of days
if (this.config.limitDays > 0) {
@@ -623,6 +660,17 @@ Module.register("calendar", {
symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "fullDaySymbol", this.config.defaultSymbol), symbols);
}
// If custom symbol is set, replace event symbol
for (let ev of this.config.customEvents) {
if (typeof ev.symbol !== "undefined" && ev.symbol !== "") {
let needle = new RegExp(ev.keyword, "gi");
if (needle.test(event.title)) {
symbols[0] = ev.symbol;
break;
}
}
}
return symbols;
},
@@ -694,6 +742,16 @@ Module.register("calendar", {
return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
},
/**
* Retrieves the maximum entry count for a specific calendar url.
*
* @param {string} url The calendar url
* @returns {number} The maximum entry count
*/
maximumEntriesForUrl: function (url) {
return this.getCalendarProperty(url, "maximumEntries", this.config.maximumEntries);
},
/**
* Helper method to retrieve the property for a specific calendar url.
*
@@ -714,6 +772,11 @@ Module.register("calendar", {
getCalendarPropertyAsArray: function (url, property, defaultValue) {
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 = [p];
return p;
},
@@ -751,7 +814,7 @@ Module.register("calendar", {
line++;
if (line > maxTitleLines - 1) {
if (i < words.length) {
currentLine += "&hellip;";
currentLine += "";
}
break;
}
@@ -768,7 +831,7 @@ Module.register("calendar", {
return (temp + currentLine).trim();
} else {
if (maxLength && typeof maxLength === "number" && string.length > maxLength) {
return string.trim().slice(0, maxLength) + "&hellip;";
return string.trim().slice(0, maxLength) + "";
} else {
return string.trim();
}
@@ -819,22 +882,14 @@ Module.register("calendar", {
* The all events available in one array, sorted on startdate.
*/
broadcastEvents: function () {
const eventList = [];
for (const url in this.calendarData) {
for (const ev of this.calendarData[url]) {
const event = cloneObject(ev);
event.symbol = this.symbolsForEvent(event);
event.calendarName = this.calendarNameForUrl(url);
event.color = this.colorForUrl(url);
delete event.url;
eventList.push(event);
}
const eventList = this.createEventList(false);
for (const event of eventList) {
event.symbol = this.symbolsForEvent(event);
event.calendarName = this.calendarNameForUrl(event.url);
event.color = this.colorForUrl(event.url);
delete event.url;
}
eventList.sort(function (a, b) {
return a.startDate - b.startDate;
});
this.sendNotification("CALENDAR_EVENTS", eventList);
}
});

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Node Helper: Calendar - CalendarFetcher
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -6,8 +6,9 @@
*/
const CalendarUtils = require("./calendarutils");
const Log = require("logger");
const NodeHelper = require("node_helper");
const ical = require("node-ical");
const fetch = require("node-fetch");
const fetch = require("fetch");
const digest = require("digest-fetch");
const https = require("https");
@@ -40,7 +41,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
let fetcher = null;
let httpsAgent = null;
let headers = {
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version
};
if (selfSignedCert) {
@@ -52,27 +53,17 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
if (auth.method === "bearer") {
headers.Authorization = "Bearer " + auth.pass;
} else if (auth.method === "digest") {
fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, httpsAgent: httpsAgent });
fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, agent: httpsAgent });
} else {
headers.Authorization = "Basic " + Buffer.from(auth.user + ":" + auth.pass).toString("base64");
}
}
if (fetcher === null) {
fetcher = fetch(url, { headers: headers, httpsAgent: httpsAgent });
fetcher = fetch(url, { headers: headers, agent: httpsAgent });
}
fetcher
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
})
.then((response) => {
if (response.status !== 200) {
fetchFailedCallback(this, response.statusText);
scheduleTimer();
}
return response;
})
.then(NodeHelper.checkFetchStatus)
.then((response) => response.text())
.then((responseData) => {
let data = [];
@@ -87,12 +78,16 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
maximumNumberOfDays
});
} catch (error) {
fetchFailedCallback(this, error.message);
fetchFailedCallback(this, error);
scheduleTimer();
return;
}
this.broadcastEvents();
scheduleTimer();
})
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
});
};

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Calendar Util Methods
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -18,8 +18,8 @@ const CalendarUtils = {
* Calculate the time correction, either dst/std or full day in cases where
* utc time is day before plus offset
*
* @param {object} event
* @param {Date} date
* @param {object} event the event which needs adjustement
* @param {Date} date the date on which this event happens
* @returns {number} the necessary adjustment in hours
*/
calculateTimezoneAdjustment: function (event, date) {
@@ -98,7 +98,7 @@ const CalendarUtils = {
if (h > 0 && h < Math.abs(current_offset) / 60) {
// if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time)
// we need to fix that
adjustHours = 24;
//adjustHours = 24;
// Log.debug("adjusting date")
}
//-300 > -240
@@ -117,6 +117,13 @@ const CalendarUtils = {
return adjustHours;
},
/**
* Filter the events from ical according to the given config
*
* @param {object} data the calendar data from ical
* @param {object} config The configuration object
* @returns {string[]} the filtered events
*/
filterEvents: function (data, config) {
const newEvents = [];
@@ -131,13 +138,14 @@ const CalendarUtils = {
return CalendarUtils.isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
};
Log.debug("there are " + Object.entries(data).length + " calendar entries");
Log.debug("There are " + Object.entries(data).length + " calendar entries.");
Object.entries(data).forEach(([key, event]) => {
Log.debug("Processing entry...");
const now = new Date();
const today = moment().startOf("day").toDate();
const future = moment().startOf("day").add(config.maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
let past = today;
Log.debug("have entries ");
if (config.includePastEvents) {
past = moment().startOf("day").subtract(config.maximumNumberOfDays, "days").toDate();
}
@@ -152,10 +160,10 @@ const CalendarUtils = {
}
if (event.type === "VEVENT") {
Log.debug("Event:\n" + JSON.stringify(event));
let startDate = eventDate(event, "start");
let endDate;
Log.debug("\nevent=" + JSON.stringify(event));
if (typeof event.end !== "undefined") {
endDate = eventDate(event, "end");
} else if (typeof event.duration !== "undefined") {
@@ -169,16 +177,21 @@ const CalendarUtils = {
}
}
Log.debug(" start=" + startDate.toDate() + " end=" + endDate.toDate());
Log.debug("start: " + startDate.toDate());
Log.debug("end:: " + endDate.toDate());
// calculate the duration of the event for use with recurring events.
// Calculate the duration of the event for use with recurring events.
let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
Log.debug("duration: " + duration);
// FIXME: Since the parsed json object from node-ical comes with time information
// this check could be removed (?)
if (event.start.length === 8) {
startDate = startDate.startOf("day");
}
const title = CalendarUtils.getTitleFromEvent(event);
Log.debug("title: " + title);
let excluded = false,
dateFilter = null;
@@ -253,9 +266,13 @@ const CalendarUtils = {
let pastLocal = 0;
let futureLocal = 0;
if (CalendarUtils.isFullDayEvent(event)) {
Log.debug("fullday");
// if full day event, only use the date part of the ranges
pastLocal = pastMoment.toDate();
futureLocal = futureMoment.toDate();
Log.debug("pastLocal: " + pastLocal);
Log.debug("futureLocal: " + futureLocal);
} else {
// if we want past events
if (config.includePastEvents) {
@@ -267,9 +284,9 @@ const CalendarUtils = {
}
futureLocal = futureMoment.toDate(); // future
}
Log.debug(" between=" + pastLocal + " to " + futureLocal);
Log.debug("Search for recurring events between: " + pastLocal + " and " + futureLocal);
const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
Log.debug("title=" + event.summary + " dates=" + JSON.stringify(dates));
Log.debug("Title: " + event.summary + ", with dates: " + JSON.stringify(dates));
// 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
// had its date changed from outside the range to inside the range. For the time being,
@@ -277,6 +294,7 @@ const CalendarUtils = {
// 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);
if (event.recurrences !== undefined) {
for (let r in event.recurrences) {
// Only add dates that weren't already in the range we added from the rrule so that
@@ -289,62 +307,72 @@ const CalendarUtils = {
// 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];
// ical.js started returning recurrences and exdates as ISOStrings without time information.
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same
// (see https://github.com/peterbraden/ical.js/pull/84 )
// 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/).
const dateKey = date.toISOString().substring(0, 10);
let curEvent = event;
let showRecurrence = true;
// get the offset of today where we are processing
// this will be the correction we need to apply
// Get the offset of today where we are processing
// This will be the correction, we need to apply.
let nowOffset = new Date().getTimezoneOffset();
// for full day events, the time might be off from RRULE/Luxon problem
// get time zone offset of the rule calculated event
// For full day events, the time might be off from RRULE/Luxon problem
// Get time zone offset of the rule calculated event
let dateoffset = date.getTimezoneOffset();
// reduce the time by the offset
// Reduce the time by the following offset.
Log.debug(" recurring date is " + date + " offset is " + dateoffset);
let dh = moment(date).format("HH");
Log.debug(" recurring date is " + date + " offset is " + dateoffset / 60 + " Hour is " + dh);
if (CalendarUtils.isFullDayEvent(event)) {
Log.debug("fullday");
// if the offset is negative, east of GMT where the problem is
Log.debug("Fullday");
// If the offset is negative (east of GMT), where the problem is
if (dateoffset < 0) {
// if the date hour is less than the offset
if (dh < Math.abs(dateoffset / 60)) {
// if the rrule byweekday WAS explicitly set , correct it
// reduce the time by the offset
Log.debug(" recurring date is " + date + " offset is " + dateoffset);
// apply the correction to the date/time to get it UTC relative
date = new Date(date.getTime() - Math.abs(nowOffset) * 60000);
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// Apply the correction to the date/time to get it UTC relative
date = new Date(date.getTime() - Math.abs(24 * 60) * 60000);
}
// the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry
//duration = 24 * 60 * 60 * 1000;
Log.debug("new recurring date1 is " + date);
Log.debug("new recurring date1 fulldate is " + date);
}
} else {
// if the timezones are the same, correct date if needed
if (event.start.tz === moment.tz.guess()) {
// if the date hour is less than the offset
if (24 - dh < Math.abs(dateoffset / 60)) {
//if (event.start.tz === moment.tz.guess()) {
// if the date hour is less than the offset
if (24 - dh <= Math.abs(dateoffset / 60)) {
// if the rrule byweekday WAS explicitly set , correct it
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// apply the correction to the date/time back to right day
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
// the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry
//duration = 24 * 60 * 60 * 1000;
Log.debug("new recurring date2 is " + date);
}
// the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry
//duration = 24 * 60 * 60 * 1000;
Log.debug("new recurring date2 fulldate is " + date);
}
//}
}
} else {
// not full day, but luxon can still screw up the date on the rule processing
// we need to correct the date to get back to the right event for
if (dateoffset < 0) {
// if the date hour is less than the offset
if (dh < Math.abs(dateoffset / 60)) {
// reduce the time by the offset
Log.debug(" recurring date is " + date + " offset is " + dateoffset);
// apply the correction to the date/time to get it UTC relative
date = new Date(date.getTime() - Math.abs(nowOffset) * 60000);
if (dh <= Math.abs(dateoffset / 60)) {
// if the rrule byweekday WAS explicitly set , correct it
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// Reduce the time by t:
// Apply the correction to the date/time to get it UTC relative
date = new Date(date.getTime() - Math.abs(24 * 60) * 60000);
}
// the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry
//duration = 24 * 60 * 60 * 1000;
@@ -352,20 +380,24 @@ const CalendarUtils = {
}
} else {
// if the timezones are the same, correct date if needed
if (event.start.tz === moment.tz.guess()) {
// if the date hour is less than the offset
if (24 - dh < Math.abs(dateoffset / 60)) {
//if (event.start.tz === moment.tz.guess()) {
// if the date hour is less than the offset
if (24 - dh <= Math.abs(dateoffset / 60)) {
// if the rrule byweekday WAS explicitly set , correct it
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// apply the correction to the date/time back to right day
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
// the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry
//duration = 24 * 60 * 60 * 1000;
Log.debug("new recurring date2 is " + date);
}
// the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry
//duration = 24 * 60 * 60 * 1000;
Log.debug("new recurring date2 is " + date);
}
//}
}
}
startDate = moment(date);
Log.debug("Corrected startDate: " + startDate.toDate());
let adjustDays = CalendarUtils.calculateTimezoneAdjustment(event, date);
@@ -381,7 +413,7 @@ const CalendarUtils = {
// This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false;
}
Log.debug("duration=" + duration);
Log.debug("duration: " + duration);
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
if (startDate.format("x") === endDate.format("x")) {
@@ -401,7 +433,7 @@ const CalendarUtils = {
}
if (showRecurrence === true) {
Log.debug("saving event =" + description);
Log.debug("saving event: " + description);
addedEvents++;
newEvents.push({
title: recurrenceTitle,
@@ -417,7 +449,7 @@ const CalendarUtils = {
});
}
}
// end recurring event parsing
// End recurring event parsing.
} else {
// Single event.
const fullDayEvent = isFacebookBirthday ? true : CalendarUtils.isFullDayEvent(event);
@@ -449,10 +481,6 @@ const CalendarUtils = {
return;
}
// Adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
if (fullDayEvent && startDate <= today) {
startDate = moment(today);
}
// if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00)
if (fullDayEvent && startDate.format("x") === endDate.format("x")) {
endDate = endDate.endOf("day");
@@ -478,30 +506,14 @@ const CalendarUtils = {
return a.startDate - b.startDate;
});
// include up to maximumEntries current or upcoming events
// If past events should be included, include all past events
const now = moment();
let entries = 0;
let events = [];
for (let ne of newEvents) {
if (moment(ne.endDate, "x").isBefore(now)) {
if (config.includePastEvents) events.push(ne);
continue;
}
entries++;
// If max events has been saved, skip the rest
if (entries > config.maximumEntries) break;
events.push(ne);
}
return events;
return newEvents;
},
/**
* Lookup iana tz from windows
*
* @param msTZName
* @returns {*|null}
* @param {string} msTZName the timezone name to lookup
* @returns {string|null} the iana name or null of none is found
*/
getIanaTZFromMS: function (msTZName) {
// Get hash entry
@@ -571,12 +583,13 @@ const CalendarUtils = {
},
/**
* Determines if the user defined title filter should apply
*
* @param title
* @param filter
* @param useRegex
* @param regexFlags
* @returns {boolean|*}
* @param {string} title the title of the event
* @param {string} filter the string to look for, can be a regex also
* @param {boolean} useRegex true if a regex should be used, otherwise it just looks for the filter as a string
* @param {string} regexFlags flags that should be applied to the regex
* @returns {boolean} True if the title should be filtered out, false otherwise
*/
titleFilterApplies: function (title, filter, useRegex, regexFlags) {
if (useRegex) {

View File

@@ -1,10 +1,13 @@
/* CalendarFetcher Tester
* use this script with `node debug.js` to test the fetcher without the need
* of starting the MagicMirror core. Adjust the values below to your desire.
* of starting the MagicMirror² core. Adjust the values below to your desire.
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
// Alias modules mentioned in package.js under _moduleAliases.
require("module-alias/register");
const CalendarFetcher = require("./calendarfetcher.js");
const url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
@@ -26,11 +29,13 @@ const fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maxi
fetcher.onReceive(function (fetcher) {
console.log(fetcher.events());
console.log("------------------------------------------------------------");
process.exit(0);
});
fetcher.onError(function (fetcher, error) {
console.log("Fetcher error:");
console.log(error);
process.exit(1);
});
fetcher.startFetch();

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Node Helper: Calendar
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -19,6 +19,14 @@ module.exports = NodeHelper.create({
socketNotificationReceived: function (notification, payload) {
if (notification === "ADD_CALENDAR") {
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.selfSignedCert, payload.id);
} else if (notification === "FETCH_CALENDAR") {
const key = payload.id + payload.url;
if (typeof this.fetchers[key] === "undefined") {
Log.error("Calendar Error. No fetcher exists with key: ", key);
this.sendSocketNotification("CALENDAR_ERROR", { error_type: "MODULE_ERROR_UNSPECIFIED" });
return;
}
this.fetchers[key].startFetch();
}
},
@@ -40,13 +48,14 @@ module.exports = NodeHelper.create({
try {
new URL(url);
} catch (error) {
this.sendSocketNotification("INCORRECT_URL", { id: identifier, url: url });
Log.error("Calendar Error. Malformed calendar url: ", url, error);
this.sendSocketNotification("CALENDAR_ERROR", { error_type: "MODULE_ERROR_MALFORMED_URL" });
return;
}
let fetcher;
if (typeof this.fetchers[identifier + url] === "undefined") {
Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
Log.log("Create new calendarfetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert);
fetcher.onReceive((fetcher) => {
@@ -55,16 +64,16 @@ module.exports = NodeHelper.create({
fetcher.onError((fetcher, error) => {
Log.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
this.sendSocketNotification("FETCH_ERROR", {
let error_type = NodeHelper.checkFetchError(error);
this.sendSocketNotification("CALENDAR_ERROR", {
id: identifier,
url: fetcher.url(),
error: error
error_type
});
});
this.fetchers[identifier + url] = fetcher;
} else {
Log.log("Use existing calendar fetcher for url: " + url);
Log.log("Use existing calendarfetcher for url: " + url);
fetcher = this.fetchers[identifier + url];
fetcher.broadcastEvents();
}

View File

@@ -1,6 +1,6 @@
# Module: Clock
The `clock` module is one of the default modules of the MagicMirror.
The `clock` module is one of the default modules of the MagicMirror².
This module displays the current date and time. The information will be updated realtime.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/clock.html).

View File

@@ -1,6 +1,6 @@
/* global SunCalc */
/* Magic Mirror
/* MagicMirror²
* Module: Clock
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -12,11 +12,14 @@ Module.register("clock", {
displayType: "digital", // options: digital, analog, both
timeFormat: config.timeFormat,
timezone: null,
displaySeconds: true,
showPeriod: true,
showPeriodUpper: false,
clockBold: false,
showDate: true,
showTime: true,
showWeek: false,
dateFormat: "dddd, LL",
@@ -24,9 +27,8 @@ Module.register("clock", {
analogSize: "200px",
analogFace: "simple", // options: 'none', 'simple', 'face-###' (where ### is 001 to 012 inclusive)
analogPlacement: "bottom", // options: 'top', 'bottom', 'left', 'right'
analogShowDate: "top", // options: false, 'top', or 'bottom'
analogShowDate: "top", // OBSOLETE, can be replaced with analogPlacement and showTime, options: false, 'top', or 'bottom'
secondsColor: "#888888",
timezone: null,
showSunTimes: false,
showMoonTimes: false,
@@ -46,66 +48,75 @@ Module.register("clock", {
Log.info("Starting module: " + this.name);
// Schedule update interval.
var self = this;
self.second = moment().second();
self.minute = moment().minute();
this.second = moment().second();
this.minute = moment().minute();
//Calculate how many ms should pass until next update depending on if seconds is displayed or not
var delayCalculator = function (reducedSeconds) {
var EXTRA_DELAY = 50; //Deliberate imperceptable delay to prevent off-by-one timekeeping errors
// Calculate how many ms should pass until next update depending on if seconds is displayed or not
const delayCalculator = (reducedSeconds) => {
const EXTRA_DELAY = 50; // Deliberate imperceptible delay to prevent off-by-one timekeeping errors
if (self.config.displaySeconds) {
if (this.config.displaySeconds) {
return 1000 - moment().milliseconds() + EXTRA_DELAY;
} else {
return (60 - reducedSeconds) * 1000 - moment().milliseconds() + EXTRA_DELAY;
}
};
//A recursive timeout function instead of interval to avoid drifting
var notificationTimer = function () {
self.updateDom();
// A recursive timeout function instead of interval to avoid drifting
const notificationTimer = () => {
this.updateDom();
//If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
if (self.config.displaySeconds) {
self.second = moment().second();
if (self.second !== 0) {
self.sendNotification("CLOCK_SECOND", self.second);
// If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
if (this.config.displaySeconds) {
this.second = moment().second();
if (this.second !== 0) {
this.sendNotification("CLOCK_SECOND", this.second);
setTimeout(notificationTimer, delayCalculator(0));
return;
}
}
//If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
self.minute = moment().minute();
self.sendNotification("CLOCK_MINUTE", self.minute);
// If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
this.minute = moment().minute();
this.sendNotification("CLOCK_MINUTE", this.minute);
setTimeout(notificationTimer, delayCalculator(0));
};
//Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
setTimeout(notificationTimer, delayCalculator(self.second));
// Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
setTimeout(notificationTimer, delayCalculator(this.second));
// Set locale.
moment.locale(config.language);
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
const wrapper = document.createElement("div");
wrapper.classList.add("clockGrid");
/************************************
* Create wrappers for analog and digital clock
*/
const analogWrapper = document.createElement("div");
analogWrapper.className = "clockCircle";
const digitalWrapper = document.createElement("div");
digitalWrapper.className = "digital";
digitalWrapper.style.gridArea = "center";
/************************************
* Create wrappers for DIGITAL clock
*/
const dateWrapper = document.createElement("div");
const timeWrapper = document.createElement("div");
const secondsWrapper = document.createElement("sup");
const periodWrapper = document.createElement("span");
const sunWrapper = document.createElement("div");
const moonWrapper = document.createElement("div");
const weekWrapper = document.createElement("div");
var dateWrapper = document.createElement("div");
var timeWrapper = document.createElement("div");
var secondsWrapper = document.createElement("sup");
var periodWrapper = document.createElement("span");
var sunWrapper = document.createElement("div");
var moonWrapper = document.createElement("div");
var weekWrapper = document.createElement("div");
// Style Wrappers
dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light";
secondsWrapper.className = "dimmed";
secondsWrapper.className = "seconds dimmed";
sunWrapper.className = "sun dimmed small";
moonWrapper.className = "moon dimmed small";
weekWrapper.className = "week dimmed medium";
@@ -114,19 +125,18 @@ Module.register("clock", {
// The moment().format("h") method has a bug on the Raspberry Pi.
// So we need to generate the timestring manually.
// See issue: https://github.com/MichMich/MagicMirror/issues/181
var timeString;
var now = moment();
this.lastDisplayedMinute = now.minute();
let timeString;
const now = moment();
if (this.config.timezone) {
now.tz(this.config.timezone);
}
var hourSymbol = "HH";
let hourSymbol = "HH";
if (this.config.timeFormat !== 24) {
hourSymbol = "h";
}
if (this.config.clockBold === true) {
if (this.config.clockBold) {
timeString = now.format(hourSymbol + '[<span class="bold">]mm[</span>]');
} else {
timeString = now.format(hourSymbol + ":mm");
@@ -134,22 +144,24 @@ Module.register("clock", {
if (this.config.showDate) {
dateWrapper.innerHTML = now.format(this.config.dateFormat);
digitalWrapper.appendChild(dateWrapper);
}
if (this.config.showWeek) {
weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() });
}
timeWrapper.innerHTML = timeString;
secondsWrapper.innerHTML = now.format("ss");
if (this.config.showPeriodUpper) {
periodWrapper.innerHTML = now.format("A");
} else {
periodWrapper.innerHTML = now.format("a");
}
if (this.config.displaySeconds) {
timeWrapper.appendChild(secondsWrapper);
}
if (this.config.showPeriod && this.config.timeFormat !== 24) {
timeWrapper.appendChild(periodWrapper);
if (this.config.displayType !== "analog" && this.config.showTime) {
timeWrapper.innerHTML = timeString;
secondsWrapper.innerHTML = now.format("ss");
if (this.config.showPeriodUpper) {
periodWrapper.innerHTML = now.format("A");
} else {
periodWrapper.innerHTML = now.format("a");
}
if (this.config.displaySeconds) {
timeWrapper.appendChild(secondsWrapper);
}
if (this.config.showPeriod && this.config.timeFormat !== 24) {
timeWrapper.appendChild(periodWrapper);
}
digitalWrapper.appendChild(timeWrapper);
}
/**
@@ -160,17 +172,20 @@ Module.register("clock", {
* @returns {string} The formatted time string
*/
function formatTime(config, time) {
var formatString = hourSymbol + ":mm";
let formatString = hourSymbol + ":mm";
if (config.showPeriod && config.timeFormat !== 24) {
formatString += config.showPeriodUpper ? "A" : "a";
}
return moment(time).format(formatString);
}
/****************************************************************
* Create wrappers for Sun Times, only if specified in config
*/
if (this.config.showSunTimes) {
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
var nextEvent;
let nextEvent;
if (now.isBefore(sunTimes.sunrise)) {
nextEvent = sunTimes.sunrise;
} else if (now.isBefore(sunTimes.sunset)) {
@@ -184,21 +199,26 @@ Module.register("clock", {
sunWrapper.innerHTML =
'<span class="' +
(isVisible ? "bright" : "") +
'"><i class="fa fa-sun-o" aria-hidden="true"></i> ' +
'"><i class="fas fa-sun" aria-hidden="true"></i> ' +
untilNextEventString +
"</span>" +
'<span><i class="fa fa-arrow-up" aria-hidden="true"></i> ' +
'<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ' +
formatTime(this.config, sunTimes.sunrise) +
"</span>" +
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
'<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ' +
formatTime(this.config, sunTimes.sunset) +
"</span>";
digitalWrapper.appendChild(sunWrapper);
}
/****************************************************************
* Create wrappers for Moon Times, only if specified in config
*/
if (this.config.showMoonTimes) {
const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon);
const moonRise = moonTimes.rise;
var moonSet;
let moonSet;
if (moment(moonTimes.set).isAfter(moonTimes.rise)) {
moonSet = moonTimes.set;
} else {
@@ -210,21 +230,26 @@ Module.register("clock", {
moonWrapper.innerHTML =
'<span class="' +
(isVisible ? "bright" : "") +
'"><i class="fa fa-moon-o" aria-hidden="true"></i> ' +
'"><i class="fas fa-moon" aria-hidden="true"></i> ' +
illuminatedFractionString +
"</span>" +
'<span><i class="fa fa-arrow-up" aria-hidden="true"></i> ' +
'<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ' +
(moonRise ? formatTime(this.config, moonRise) : "...") +
"</span>" +
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
'<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ' +
(moonSet ? formatTime(this.config, moonSet) : "...") +
"</span>";
digitalWrapper.appendChild(moonWrapper);
}
if (this.config.showWeek) {
weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() });
digitalWrapper.appendChild(weekWrapper);
}
/****************************************************************
* Create wrappers for ANALOG clock, only if specified in config
*/
if (this.config.displayType !== "digital") {
// If it isn't 'digital', then an 'analog' clock was also requested
@@ -232,34 +257,32 @@ Module.register("clock", {
if (this.config.timezone) {
now.tz(this.config.timezone);
}
var second = now.seconds() * 6,
const second = now.seconds() * 6,
minute = now.minute() * 6 + second / 60,
hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
// Create wrappers
var clockCircle = document.createElement("div");
clockCircle.className = "clockCircle";
clockCircle.style.width = this.config.analogSize;
clockCircle.style.height = this.config.analogSize;
analogWrapper.style.width = this.config.analogSize;
analogWrapper.style.height = this.config.analogSize;
if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
clockCircle.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)";
clockCircle.style.backgroundSize = "100%";
analogWrapper.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)";
analogWrapper.style.backgroundSize = "100%";
// The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
// clockCircle.style.border = "1px solid black";
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
// analogWrapper.style.border = "1px solid black";
analogWrapper.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
} else if (this.config.analogFace !== "none") {
clockCircle.style.border = "2px solid white";
analogWrapper.style.border = "2px solid white";
}
var clockFace = document.createElement("div");
const clockFace = document.createElement("div");
clockFace.className = "clockFace";
var clockHour = document.createElement("div");
const clockHour = document.createElement("div");
clockHour.id = "clockHour";
clockHour.style.transform = "rotate(" + hour + "deg)";
clockHour.className = "clockHour";
var clockMinute = document.createElement("div");
const clockMinute = document.createElement("div");
clockMinute.id = "clockMinute";
clockMinute.style.transform = "rotate(" + minute + "deg)";
clockMinute.className = "clockMinute";
@@ -269,89 +292,33 @@ Module.register("clock", {
clockFace.appendChild(clockMinute);
if (this.config.displaySeconds) {
var clockSecond = document.createElement("div");
const clockSecond = document.createElement("div");
clockSecond.id = "clockSecond";
clockSecond.style.transform = "rotate(" + second + "deg)";
clockSecond.className = "clockSecond";
clockSecond.style.backgroundColor = this.config.secondsColor;
clockFace.appendChild(clockSecond);
}
clockCircle.appendChild(clockFace);
analogWrapper.appendChild(clockFace);
}
/*******************************************
* Combine wrappers, check for .displayType
* Update placement, respect old analogShowDate even if it's not needed anymore
*/
if (this.config.displayType === "digital") {
// Display only a digital clock
wrapper.appendChild(dateWrapper);
wrapper.appendChild(timeWrapper);
wrapper.appendChild(sunWrapper);
wrapper.appendChild(moonWrapper);
wrapper.appendChild(weekWrapper);
} else if (this.config.displayType === "analog") {
if (this.config.displayType === "analog") {
// Display only an analog clock
if (this.config.showWeek) {
weekWrapper.style.paddingBottom = "15px";
} else {
dateWrapper.style.paddingBottom = "15px";
}
if (this.config.analogShowDate === "top") {
wrapper.appendChild(dateWrapper);
wrapper.appendChild(weekWrapper);
wrapper.appendChild(clockCircle);
wrapper.classList.add("clockGrid--bottom");
} else if (this.config.analogShowDate === "bottom") {
wrapper.appendChild(clockCircle);
wrapper.appendChild(dateWrapper);
wrapper.appendChild(weekWrapper);
} else {
wrapper.appendChild(clockCircle);
}
} else {
// Both clocks have been configured, check position
var placement = this.config.analogPlacement;
var analogWrapper = document.createElement("div");
analogWrapper.id = "analog";
analogWrapper.style.cssFloat = "none";
analogWrapper.appendChild(clockCircle);
var digitalWrapper = document.createElement("div");
digitalWrapper.id = "digital";
digitalWrapper.style.cssFloat = "none";
digitalWrapper.appendChild(dateWrapper);
digitalWrapper.appendChild(timeWrapper);
digitalWrapper.appendChild(sunWrapper);
digitalWrapper.appendChild(moonWrapper);
digitalWrapper.appendChild(weekWrapper);
var appendClocks = function (condition, pos1, pos2) {
var padding = [0, 0, 0, 0];
padding[placement === condition ? pos1 : pos2] = "20px";
analogWrapper.style.padding = padding.join(" ");
if (placement === condition) {
wrapper.appendChild(analogWrapper);
wrapper.appendChild(digitalWrapper);
} else {
wrapper.appendChild(digitalWrapper);
wrapper.appendChild(analogWrapper);
}
};
if (placement === "left" || placement === "right") {
digitalWrapper.style.display = "inline-block";
digitalWrapper.style.verticalAlign = "top";
analogWrapper.style.display = "inline-block";
appendClocks("left", 1, 3);
} else {
digitalWrapper.style.textAlign = "center";
appendClocks("top", 2, 0);
wrapper.classList.add("clockGrid--top");
}
wrapper.appendChild(analogWrapper);
} else if (this.config.displayType === "digital") {
wrapper.appendChild(digitalWrapper);
} else if (this.config.displayType === "both") {
wrapper.classList.add("clockGrid--" + this.config.analogPlacement);
wrapper.appendChild(analogWrapper);
wrapper.appendChild(digitalWrapper);
}
// Return the wrapper to the dom.

View File

@@ -1,5 +1,26 @@
.clockGrid {
display: inline-flex;
gap: 15px;
}
.clockGrid--left {
flex-direction: row;
}
.clockGrid--right {
flex-direction: row-reverse;
}
.clockGrid--top {
flex-direction: column;
}
.clockGrid--bottom {
flex-direction: column-reverse;
}
.clockCircle {
margin: 0 auto;
place-self: center;
position: relative;
border-radius: 50%;
background-size: 100%;
@@ -17,7 +38,7 @@
width: 6px;
height: 6px;
margin: -3px 0 0 -3px;
background: white;
background: var(--color-text-bright);
border-radius: 3px;
content: "";
display: block;
@@ -29,9 +50,9 @@
position: absolute;
top: 50%;
left: 50%;
margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
margin: -2px 0 -2px -25%; /* numbers must match negative length & thickness */
padding: 2px 0 2px 25%; /* indicator length & thickness */
background: white;
background: var(--color-text-bright);
transform-origin: 100% 50%;
border-radius: 3px 0 0 3px;
}
@@ -44,7 +65,7 @@
left: 50%;
margin: -35% -2px 0; /* numbers must match negative length & thickness */
padding: 35% 2px 0; /* indicator length & thickness */
background: white;
background: var(--color-text-bright);
transform-origin: 50% 100%;
border-radius: 3px 0 0 3px;
}
@@ -57,7 +78,7 @@
left: 50%;
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
padding: 38% 1px 0 0; /* indicator length & thickness */
background: #888;
background: var(--color-text);
transform-origin: 50% 100%;
}

View File

@@ -1,6 +1,6 @@
# Module: Compliments
The `compliments` module is one of the default modules of the MagicMirror.
The `compliments` module is one of the default modules of the MagicMirror².
This module displays a random compliment.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/compliments.html).

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Module: Compliments
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -21,8 +21,7 @@ Module.register("compliments", {
morningEndTime: 12,
afternoonStartTime: 12,
afternoonEndTime: 17,
random: true,
mockDate: null
random: true
},
lastIndexUsed: -1,
// Set currentweather from module
@@ -39,37 +38,35 @@ Module.register("compliments", {
this.lastComplimentIndex = -1;
var self = this;
if (this.config.remoteFile !== null) {
this.complimentFile(function (response) {
self.config.compliments = JSON.parse(response);
self.updateDom();
this.loadComplimentFile().then((response) => {
this.config.compliments = JSON.parse(response);
this.updateDom();
});
}
// Schedule update timer.
setInterval(function () {
self.updateDom(self.config.fadeSpeed);
setInterval(() => {
this.updateDom(this.config.fadeSpeed);
}, this.config.updateInterval);
},
/* randomIndex(compliments)
/**
* Generate a random index for a list of compliments.
*
* argument compliments Array<String> - Array with compliments.
*
* return Number - Random index.
* @param {string[]} compliments Array with compliments.
* @returns {number} a random index of given array
*/
randomIndex: function (compliments) {
if (compliments.length === 1) {
return 0;
}
var generate = function () {
const generate = function () {
return Math.floor(Math.random() * compliments.length);
};
var complimentIndex = generate();
let complimentIndex = generate();
while (complimentIndex === this.lastComplimentIndex) {
complimentIndex = generate();
@@ -80,70 +77,65 @@ Module.register("compliments", {
return complimentIndex;
},
/* complimentArray()
/**
* Retrieve an array of compliments for the time of the day.
*
* return compliments Array<String> - Array with compliments for the time of the day.
* @returns {string[]} array with compliments for the time of the day.
*/
complimentArray: function () {
var hour = moment().hour();
var date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
var compliments;
const hour = moment().hour();
const date = moment().format("YYYY-MM-DD");
let compliments = [];
// Add time of day compliments
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) {
compliments = this.config.compliments.morning.slice(0);
compliments = [...this.config.compliments.morning];
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) {
compliments = this.config.compliments.afternoon.slice(0);
compliments = [...this.config.compliments.afternoon];
} else if (this.config.compliments.hasOwnProperty("evening")) {
compliments = this.config.compliments.evening.slice(0);
}
if (typeof compliments === "undefined") {
compliments = new Array();
compliments = [...this.config.compliments.evening];
}
// Add compliments based on weather
if (this.currentWeatherType in this.config.compliments) {
compliments.push.apply(compliments, this.config.compliments[this.currentWeatherType]);
Array.prototype.push.apply(compliments, this.config.compliments[this.currentWeatherType]);
}
compliments.push.apply(compliments, this.config.compliments.anytime);
// Add compliments for anytime
Array.prototype.push.apply(compliments, this.config.compliments.anytime);
for (var entry in this.config.compliments) {
// Add compliments for special days
for (let entry in this.config.compliments) {
if (new RegExp(entry).test(date)) {
compliments.push.apply(compliments, this.config.compliments[entry]);
Array.prototype.push.apply(compliments, this.config.compliments[entry]);
}
}
return compliments;
},
/* complimentFile(callback)
/**
* Retrieve a file from the local filesystem
*
* @returns {Promise} Resolved when the file is loaded
*/
complimentFile: function (callback) {
var xobj = new XMLHttpRequest(),
isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0,
path = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile);
xobj.overrideMimeType("application/json");
xobj.open("GET", path, true);
xobj.onreadystatechange = function () {
if (xobj.readyState === 4 && xobj.status === 200) {
callback(xobj.responseText);
}
};
xobj.send(null);
loadComplimentFile: async function () {
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();
},
/* complimentArray()
/**
* Retrieve a random compliment.
*
* return compliment string - A compliment.
* @returns {string} a compliment
*/
randomCompliment: function () {
getRandomCompliment: function () {
// get the current time of day compliments list
var compliments = this.complimentArray();
const compliments = this.complimentArray();
// variable for index to next message to display
let index = 0;
let index;
// are we randomizing
if (this.config.random) {
// yes
@@ -159,37 +151,36 @@ Module.register("compliments", {
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
const wrapper = document.createElement("div");
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line";
// get the compliment text
var complimentText = this.randomCompliment();
const complimentText = this.getRandomCompliment();
// split it into parts on newline text
var parts = complimentText.split("\n");
// create a span to hold it all
var compliment = document.createElement("span");
const parts = complimentText.split("\n");
// create a span to hold the compliment
const compliment = document.createElement("span");
// process all the parts of the compliment text
for (var part of parts) {
// create a text element for each part
compliment.appendChild(document.createTextNode(part));
// add a break `
compliment.appendChild(document.createElement("BR"));
for (const part of parts) {
if (part !== "") {
// create a text element for each part
compliment.appendChild(document.createTextNode(part));
// add a break
compliment.appendChild(document.createElement("BR"));
}
}
// only add compliment to wrapper if there is actual text in there
if (compliment.children.length > 0) {
// remove the last break
compliment.lastElementChild.remove();
wrapper.appendChild(compliment);
}
// remove the last break
compliment.lastElementChild.remove();
wrapper.appendChild(compliment);
return wrapper;
},
// From data currentweather set weather type
setCurrentWeatherType: function (type) {
this.currentWeatherType = type;
},
// Override notification handler.
notificationReceived: function (notification, payload, sender) {
if (notification === "CURRENTWEATHER_TYPE") {
this.setCurrentWeatherType(payload.type);
this.currentWeatherType = payload.type;
}
}
});

View File

@@ -1,8 +0,0 @@
# Module: Current Weather
> :warning: **This module is deprecated in favor of the [weather](https://docs.magicmirror.builders/modules/weather.html) module.**
The `currentweather` module is one of the default modules of the MagicMirror.
This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/currentweather.html).

View File

@@ -1,15 +0,0 @@
.currentweather .weathericon,
.currentweather .fa-home {
font-size: 75%;
line-height: 65px;
display: inline-block;
transform: translate(0, -3px);
}
.currentweather .humidityIcon {
padding-right: 4px;
}
.currentweather .humidity-padding {
padding-bottom: 6px;
}

View File

@@ -1,598 +0,0 @@
/* Magic Mirror
* Module: CurrentWeather
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*
* This module is deprecated. Any additional feature will no longer be merged.
*/
Module.register("currentweather", {
// Default module config.
defaults: {
location: false,
locationID: false,
appid: "",
units: config.units,
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
timeFormat: config.timeFormat,
showPeriod: true,
showPeriodUpper: false,
showWindDirection: true,
showWindDirectionAsArrow: false,
useBeaufort: true,
useKMPHwind: false,
lang: config.language,
decimalSymbol: ".",
showHumidity: false,
showSun: true,
degreeLabel: false,
showIndoorTemperature: false,
showIndoorHumidity: false,
showFeelsLike: true,
initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
apiVersion: "2.5",
apiBase: "https://api.openweathermap.org/data/",
weatherEndpoint: "weather",
appendLocationNameToHeader: true,
useLocationAsHeader: false,
calendarClass: "calendar",
tableClass: "large",
onlyTemp: false,
hideTemp: false,
roundTemp: false,
iconTable: {
"01d": "day-sunny",
"02d": "day-cloudy",
"03d": "cloudy",
"04d": "cloudy-windy",
"09d": "showers",
"10d": "rain",
"11d": "thunderstorm",
"13d": "snow",
"50d": "fog",
"01n": "night-clear",
"02n": "night-cloudy",
"03n": "night-cloudy",
"04n": "night-cloudy",
"09n": "night-showers",
"10n": "night-rain",
"11n": "night-thunderstorm",
"13n": "night-snow",
"50n": "night-alt-cloudy-windy"
}
},
// create a variable for the first upcoming calendar event. Used if no location is specified.
firstEvent: false,
// create a variable to hold the location name based on the API result.
fetchedLocationName: "",
// Define required scripts.
getScripts: function () {
return ["moment.js"];
},
// Define required scripts.
getStyles: function () {
return ["weather-icons.css", "currentweather.css"];
},
// Define required translations.
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation.
return false;
},
// Define start sequence.
start: function () {
Log.info("Starting module: " + this.name);
// Set locale.
moment.locale(config.language);
this.windSpeed = null;
this.windDirection = null;
this.windDeg = null;
this.sunriseSunsetTime = null;
this.sunriseSunsetIcon = null;
this.temperature = null;
this.indoorTemperature = null;
this.indoorHumidity = null;
this.weatherType = null;
this.feelsLike = null;
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
},
// add extra information of current weather
// windDirection, humidity, sunrise and sunset
addExtraInfoWeather: function (wrapper) {
var small = document.createElement("div");
small.className = "normal medium";
var windIcon = document.createElement("span");
windIcon.className = "wi wi-strong-wind dimmed";
small.appendChild(windIcon);
var windSpeed = document.createElement("span");
windSpeed.innerHTML = " " + this.windSpeed;
small.appendChild(windSpeed);
if (this.config.showWindDirection) {
var windDirection = document.createElement("sup");
if (this.config.showWindDirectionAsArrow) {
if (this.windDeg !== null) {
windDirection.innerHTML = ' &nbsp;<i class="fa fa-long-arrow-down" style="transform:rotate(' + this.windDeg + 'deg);"></i>&nbsp;';
}
} else {
windDirection.innerHTML = " " + this.translate(this.windDirection);
}
small.appendChild(windDirection);
}
var spacer = document.createElement("span");
spacer.innerHTML = "&nbsp;";
small.appendChild(spacer);
if (this.config.showHumidity) {
var humidity = document.createElement("span");
humidity.innerHTML = this.humidity;
var supspacer = document.createElement("sup");
supspacer.innerHTML = "&nbsp;";
var humidityIcon = document.createElement("sup");
humidityIcon.className = "wi wi-humidity humidityIcon";
humidityIcon.innerHTML = "&nbsp;";
small.appendChild(humidity);
small.appendChild(supspacer);
small.appendChild(humidityIcon);
}
if (this.config.showSun) {
var sunriseSunsetIcon = document.createElement("span");
sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
small.appendChild(sunriseSunsetIcon);
var sunriseSunsetTime = document.createElement("span");
sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
small.appendChild(sunriseSunsetTime);
}
wrapper.appendChild(small);
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
wrapper.className = this.config.tableClass;
if (this.config.appid === "") {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
wrapper.className = "dimmed light small";
return wrapper;
}
if (!this.loaded) {
wrapper.innerHTML = this.translate("LOADING");
wrapper.className = "dimmed light small";
return wrapper;
}
if (this.config.onlyTemp === false) {
this.addExtraInfoWeather(wrapper);
}
var large = document.createElement("div");
large.className = "light";
var degreeLabel = "";
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if (this.config.degreeLabel) {
switch (this.config.units) {
case "metric":
degreeLabel += "C";
break;
case "imperial":
degreeLabel += "F";
break;
case "default":
degreeLabel += "K";
break;
}
}
if (this.config.decimalSymbol === "") {
this.config.decimalSymbol = ".";
}
if (this.config.hideTemp === false) {
var weatherIcon = document.createElement("span");
weatherIcon.className = "wi weathericon wi-" + this.weatherType;
large.appendChild(weatherIcon);
var temperature = document.createElement("span");
temperature.className = "bright";
temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(temperature);
}
if (this.config.showIndoorTemperature && this.indoorTemperature) {
var indoorIcon = document.createElement("span");
indoorIcon.className = "fa fa-home";
large.appendChild(indoorIcon);
var indoorTemperatureElem = document.createElement("span");
indoorTemperatureElem.className = "bright";
indoorTemperatureElem.innerHTML = " " + this.indoorTemperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(indoorTemperatureElem);
}
if (this.config.showIndoorHumidity && this.indoorHumidity) {
var indoorHumidityIcon = document.createElement("span");
indoorHumidityIcon.className = "fa fa-tint";
large.appendChild(indoorHumidityIcon);
var indoorHumidityElem = document.createElement("span");
indoorHumidityElem.className = "bright";
indoorHumidityElem.innerHTML = " " + this.indoorHumidity + "%";
large.appendChild(indoorHumidityElem);
}
wrapper.appendChild(large);
if (this.config.showFeelsLike && this.config.onlyTemp === false) {
var small = document.createElement("div");
small.className = "normal medium";
var feelsLike = document.createElement("span");
feelsLike.className = "dimmed";
feelsLike.innerHTML = this.translate("FEELS", {
DEGREE: this.feelsLike + degreeLabel
});
small.appendChild(feelsLike);
wrapper.appendChild(small);
}
return wrapper;
},
// Override getHeader method.
getHeader: function () {
if (this.config.useLocationAsHeader && this.config.location !== false) {
return this.config.location;
}
if (this.config.appendLocationNameToHeader) {
if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
else return this.fetchedLocationName;
}
return this.data.header ? this.data.header : "";
},
// Override notification handler.
notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
if (this.config.appendLocationNameToHeader) {
this.hide(0, { lockString: this.identifier });
}
}
if (notification === "CALENDAR_EVENTS") {
var senderClasses = sender.data.classes.toLowerCase().split(" ");
if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
this.firstEvent = false;
for (var e in payload) {
var event = payload[e];
if (event.location || event.geo) {
this.firstEvent = event;
//Log.log("First upcoming event with location: ", event);
break;
}
}
}
}
if (notification === "INDOOR_TEMPERATURE") {
this.indoorTemperature = this.roundValue(payload);
this.updateDom(this.config.animationSpeed);
}
if (notification === "INDOOR_HUMIDITY") {
this.indoorHumidity = this.roundValue(payload);
this.updateDom(this.config.animationSpeed);
}
},
/* updateWeather(compliments)
* Requests new data from openweather.org.
* Calls processWeather on succesfull response.
*/
updateWeather: function () {
if (this.config.appid === "") {
Log.error("CurrentWeather: APPID not set!");
return;
}
var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.weatherEndpoint + this.getParams();
var self = this;
var retry = true;
var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
self.processWeather(JSON.parse(this.response));
} else if (this.status === 401) {
self.updateDom(self.config.animationSpeed);
Log.error(self.name + ": Incorrect APPID.");
retry = true;
} else {
Log.error(self.name + ": Could not load weather.");
}
if (retry) {
self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
}
}
};
weatherRequest.send();
},
/* getParams(compliments)
* Generates an url with api parameters based on the config.
*
* return String - URL params.
*/
getParams: function () {
var params = "?";
if (this.config.locationID) {
params += "id=" + this.config.locationID;
} else if (this.config.location) {
params += "q=" + this.config.location;
} else if (this.firstEvent && this.firstEvent.geo) {
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location;
} else {
this.hide(this.config.animationSpeed, { lockString: this.identifier });
return;
}
params += "&units=" + this.config.units;
params += "&lang=" + this.config.lang;
params += "&APPID=" + this.config.appid;
return params;
},
/* processWeather(data)
* Uses the received data to set the various values.
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function (data) {
if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.humidity = parseFloat(data.main.humidity);
this.temperature = this.roundValue(data.main.temp);
this.fetchedLocationName = data.name;
this.feelsLike = 0;
if (this.config.useBeaufort) {
this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
} else if (this.config.useKMPHwind) {
this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0);
} else {
this.windSpeed = parseFloat(data.wind.speed).toFixed(0);
}
// ONLY WORKS IF TEMP IN C //
var windInMph = parseFloat(data.wind.speed * 2.23694);
var tempInF = 0;
switch (this.config.units) {
case "metric":
tempInF = 1.8 * this.temperature + 32;
break;
case "imperial":
tempInF = this.temperature;
break;
case "default":
tempInF = 1.8 * (this.temperature - 273.15) + 32;
break;
}
if (windInMph > 3 && tempInF < 50) {
// windchill
var windChillInF = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
var windChillInC = (windChillInF - 32) * (5 / 9);
// this.feelsLike = windChillInC.toFixed(0);
switch (this.config.units) {
case "metric":
this.feelsLike = windChillInC.toFixed(0);
break;
case "imperial":
this.feelsLike = windChillInF.toFixed(0);
break;
case "default":
this.feelsLike = (windChillInC + 273.15).toFixed(0);
break;
}
} else if (tempInF > 80 && this.humidity > 40) {
// heat index
var Hindex =
-42.379 +
2.04901523 * tempInF +
10.14333127 * this.humidity -
0.22475541 * tempInF * this.humidity -
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
switch (this.config.units) {
case "metric":
this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
break;
case "imperial":
this.feelsLike = Hindex.toFixed(0);
break;
case "default":
var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
this.feelsLike = tc.toFixed(0);
break;
}
} else {
this.feelsLike = parseFloat(this.temperature).toFixed(0);
}
this.windDirection = this.deg2Cardinal(data.wind.deg);
this.windDeg = data.wind.deg;
this.weatherType = this.config.iconTable[data.weather[0].icon];
var now = new Date();
var sunrise = new Date(data.sys.sunrise * 1000);
var sunset = new Date(data.sys.sunset * 1000);
// The moment().format('h') method has a bug on the Raspberry Pi.
// So we need to generate the timestring manually.
// See issue: https://github.com/MichMich/MagicMirror/issues/181
var sunriseSunsetDateObject = sunrise < now && sunset > now ? sunset : sunrise;
var timeString = moment(sunriseSunsetDateObject).format("HH:mm");
if (this.config.timeFormat !== 24) {
//var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
//timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
timeString = moment(sunriseSunsetDateObject).format("h:mm A");
} else {
//timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
timeString = moment(sunriseSunsetDateObject).format("h:mm a");
}
} else {
//timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
timeString = moment(sunriseSunsetDateObject).format("h:mm");
}
}
this.sunriseSunsetTime = timeString;
this.sunriseSunsetIcon = sunrise < now && sunset > now ? "wi-sunset" : "wi-sunrise";
this.show(this.config.animationSpeed, { lockString: this.identifier });
this.loaded = true;
this.updateDom(this.config.animationSpeed);
this.sendNotification("CURRENTWEATHER_DATA", { data: data });
this.sendNotification("CURRENTWEATHER_TYPE", { type: this.config.iconTable[data.weather[0].icon].replace("-", "_") });
},
/* scheduleUpdate()
* Schedule next update.
*
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/
scheduleUpdate: function (delay) {
var nextLoad = this.config.updateInterval;
if (typeof delay !== "undefined" && delay >= 0) {
nextLoad = delay;
}
var self = this;
setTimeout(function () {
self.updateWeather();
}, nextLoad);
},
/* ms2Beaufort(ms)
* Converts m2 to beaufort (windspeed).
*
* see:
* https://www.spc.noaa.gov/faq/tornado/beaufort.html
* https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
*
* argument ms number - Windspeed in m/s.
*
* return number - Windspeed in beaufort.
*/
ms2Beaufort: function (ms) {
var kmh = (ms * 60 * 60) / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
if (speed > kmh) {
return beaufort;
}
}
return 12;
},
deg2Cardinal: function (deg) {
if (deg > 11.25 && deg <= 33.75) {
return "NNE";
} else if (deg > 33.75 && deg <= 56.25) {
return "NE";
} else if (deg > 56.25 && deg <= 78.75) {
return "ENE";
} else if (deg > 78.75 && deg <= 101.25) {
return "E";
} else if (deg > 101.25 && deg <= 123.75) {
return "ESE";
} else if (deg > 123.75 && deg <= 146.25) {
return "SE";
} else if (deg > 146.25 && deg <= 168.75) {
return "SSE";
} else if (deg > 168.75 && deg <= 191.25) {
return "S";
} else if (deg > 191.25 && deg <= 213.75) {
return "SSW";
} else if (deg > 213.75 && deg <= 236.25) {
return "SW";
} else if (deg > 236.25 && deg <= 258.75) {
return "WSW";
} else if (deg > 258.75 && deg <= 281.25) {
return "W";
} else if (deg > 281.25 && deg <= 303.75) {
return "WNW";
} else if (deg > 303.75 && deg <= 326.25) {
return "NW";
} else if (deg > 326.25 && deg <= 348.75) {
return "NNW";
} else {
return "N";
}
},
/* function(temperature)
* Rounds a temperature to 1 decimal or integer (depending on config.roundTemp).
*
* argument temperature number - Temperature.
*
* return string - Rounded Temperature.
*/
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
}
});

View File

@@ -1,9 +0,0 @@
const NodeHelper = require("node_helper");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
start: function () {
Log.warn(`The module '${this.name}' is deprecated in favor of the 'weather'-module, please refer to the documentation for a migration path`);
}
});

View File

@@ -1,13 +1,10 @@
/* Magic Mirror
* Default Modules List
/* MagicMirror² Default Modules List
* Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
// Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
var defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"];
const defaultModules = ["alert", "calendar", "clock", "compliments", "helloworld", "newsfeed", "updatenotification", "weather"];
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {

View File

@@ -1,5 +1,5 @@
# Module: Hello World
The `helloworld` module is one of the default modules of the MagicMirror. It is a simple way to display a static text on the mirror.
The `helloworld` module is one of the default modules of the MagicMirror². It is a simple way to display a static text on the mirror.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/helloworld.html).

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Module: HelloWorld
*
* By Michael Teeuw https://michaelteeuw.nl

View File

@@ -1,6 +1,6 @@
# Module: News Feed
The `newsfeed` module is one of the default modules of the MagicMirror.
The `newsfeed` module is one of the default modules of the MagicMirror².
This module displays news headlines based on an RSS feed. Scrolling through news headlines happens time-based (`updateInterval`), but can also be controlled by sending news feed specific notifications to the module.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/newsfeed.html).

View File

@@ -12,3 +12,12 @@ iframe.newsfeed-fullarticle {
bottom: inherit;
top: -90px;
}
.newsfeed-list {
list-style: none;
}
.newsfeed-list li {
text-align: justify;
margin-bottom: 0.5em;
}

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Module: NewsFeed
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -14,11 +14,13 @@ Module.register("newsfeed", {
encoding: "UTF-8" //ISO-8859-1
}
],
showAsList: false,
showSourceTitle: true,
showPublishDate: true,
broadcastNewsFeeds: true,
broadcastNewsUpdates: true,
showDescription: false,
showTitleAsUrl: false,
wrapTitle: true,
wrapDescription: true,
truncDescription: true,
@@ -36,7 +38,16 @@ Module.register("newsfeed", {
endTags: [],
prohibitedWords: [],
scrollLength: 500,
logFeedWarnings: false
logFeedWarnings: false,
dangerouslyDisableAutoEscaping: false
},
getUrlPrefix: function (item) {
if (item.useCorsProxy) {
return location.protocol + "//" + location.host + "/cors?url=";
} else {
return "";
}
},
// Define required scripts.
@@ -89,8 +100,8 @@ Module.register("newsfeed", {
this.loaded = true;
this.error = null;
} else if (notification === "INCORRECT_URL") {
this.error = `Incorrect url: ${payload.url}`;
} else if (notification === "NEWSFEED_ERROR") {
this.error = this.translate(payload.error_type);
this.scheduleUpdateInterval();
}
},
@@ -120,7 +131,7 @@ Module.register("newsfeed", {
}
if (this.newsItems.length === 0) {
return {
loaded: false
empty: true
};
}
if (this.activeItem >= this.newsItems.length) {
@@ -128,6 +139,10 @@ Module.register("newsfeed", {
}
const item = this.newsItems[this.activeItem];
const items = this.newsItems.map(function (item) {
item.publishDate = moment(new Date(item.pubdate)).fromNow();
return item;
});
return {
loaded: true,
@@ -135,12 +150,19 @@ Module.register("newsfeed", {
sourceTitle: item.sourceTitle,
publishDate: moment(new Date(item.pubdate)).fromNow(),
title: item.title,
description: item.description
url: this.getUrlPrefix(item) + item.url,
description: item.description,
items: items
};
},
getActiveItemURL: function () {
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
const item = this.newsItems[this.activeItem];
if (item) {
return typeof item.url === "string" ? this.getUrlPrefix(item) + item.url : this.getUrlPrefix(item) + item.url.href;
} else {
return "";
}
},
/**
@@ -178,21 +200,21 @@ Module.register("newsfeed", {
const dateB = new Date(b.pubdate);
return dateB - dateA;
});
if (this.config.maxNewsItems > 0) {
newsItems = newsItems.slice(0, this.config.maxNewsItems);
}
if (this.config.prohibitedWords.length > 0) {
newsItems = newsItems.filter(function (value) {
newsItems = newsItems.filter(function (item) {
for (let word of this.config.prohibitedWords) {
if (value["title"].toLowerCase().indexOf(word.toLowerCase()) > -1) {
if (item.title.toLowerCase().indexOf(word.toLowerCase()) > -1) {
return false;
}
}
return true;
}, this);
}
newsItems.forEach((item) => {
//Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
@@ -214,7 +236,6 @@ Module.register("newsfeed", {
}
//Remove selected tags from the end of rss feed items (title or description)
if (this.config.removeEndTags) {
for (let endTag of this.config.endTags) {
if (item.title.slice(-endTag.length) === endTag) {
@@ -290,6 +311,9 @@ Module.register("newsfeed", {
this.sendNotification("NEWS_FEED", { items: this.newsItems });
}
// #2638 Clear timer if it already exists
if (this.timer) clearInterval(this.timer);
this.timer = setInterval(() => {
this.activeItem++;
this.updateDom(this.config.animationSpeed);

View File

@@ -1,25 +1,86 @@
{% macro escapeText(text, dangerouslyDisableAutoEscaping=false) %}
{% if dangerouslyDisableAutoEscaping %}
{{ text | safe}}
{% else %}
{{ text }}
{% endif %}
{% endmacro %}
{% macro escapeTitle(title, url, dangerouslyDisableAutoEscaping=false, showTitleAsUrl=false) %}
{% if dangerouslyDisableAutoEscaping %}
{% if showTitleAsUrl %}
<a href="{{ url }}" style="text-decoration:none;color:#ffffff" target="_blank">{{ title | safe }}</a>
{% else %}
{{ title | safe}}
{% endif %}
{% else %}
{% if showTitleAsUrl %}
<a href="{{ url }}" style="text-decoration:none;color:#ffffff" target="_blank">{{ title }}</a>
{% else %}
{{ title }}
{% endif %}
{% endif %}
{% endmacro %}
{% if loaded %}
<div>
{% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %}
<div class="newsfeed-source light small dimmed">
{% if sourceTitle and config.showSourceTitle %}
{{ sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
{% endif %}
{% if config.showPublishDate %}
{{ publishDate }}:
{% endif %}
{% if config.showAsList %}
<ul class="newsfeed-list">
{% for item in items %}
<li>
{% if (config.showSourceTitle and item.sourceTitle) or config.showPublishDate %}
<div class="newsfeed-source light small dimmed">
{% if item.sourceTitle and config.showSourceTitle %}
{{ item.sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
{% endif %}
{% if config.showPublishDate %}
{{ item.publishDate }}:
{% endif %}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
{{ escapeTitle(item.title, item.url, config.dangerouslyDisableAutoEscaping, config.showTitleAsUrl) }}
</div>
{% if config.showDescription %}
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ escapeText(item.description | truncate(config.lengthDescription), config.dangerouslyDisableAutoEscaping) }}
{% else %}
{{ escapeText(item.description, config.dangerouslyDisableAutoEscaping) }}
{% endif %}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<div>
{% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %}
<div class="newsfeed-source light small dimmed">
{% if sourceTitle and config.showSourceTitle %}
{{ escapeText(sourceTitle, config.dangerouslyDisableAutoEscaping) }}{% if config.showPublishDate %}, {% else %}: {% endif %}
{% endif %}
{% if config.showPublishDate %}
{{ publishDate }}:
{% endif %}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
{{ escapeTitle(title, url, config.dangerouslyDisableAutoEscaping, config.showTitleAsUrl) }}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
{{ title }}
</div>
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ description | truncate(config.lengthDescription) }}
{% else %}
{{ description }}
{% if config.showDescription %}
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ escapeText(description | truncate(config.lengthDescription), config.dangerouslyDisableAutoEscaping) }}
{% else %}
{{ escapeText(description, config.dangerouslyDisableAutoEscaping) }}
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
{% elseif empty %}
<div class="small dimmed">
{{ "NEWSFEED_NO_ITEMS" | translate | safe }}
</div>
{% elseif error %}
<div class="small dimmed">

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Node Helper: Newsfeed - NewsfeedFetcher
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -6,8 +6,10 @@
*/
const Log = require("logger");
const FeedMe = require("feedme");
const fetch = require("node-fetch");
const NodeHelper = require("node_helper");
const fetch = require("fetch");
const iconv = require("iconv-lite");
const stream = require("stream");
/**
* Responsible for requesting an update on the set interval and broadcasting the data.
@@ -16,9 +18,10 @@ const iconv = require("iconv-lite");
* @param {number} reloadInterval Reload interval in milliseconds.
* @param {string} encoding Encoding of the feed.
* @param {boolean} logFeedWarnings If true log warnings when there is an error parsing a news article.
* @param {boolean} useCorsProxy If true cors proxy is used for article url's.
* @class
*/
const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings, useCorsProxy) {
let reloadTimer = null;
let items = [];
@@ -55,7 +58,8 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
title: title,
description: description,
pubdate: pubdate,
url: url
url: url,
useCorsProxy: useCorsProxy
});
} else if (logFeedWarnings) {
Log.warn("Can't parse feed item:");
@@ -76,20 +80,40 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
scheduleTimer();
});
parser.on("ttl", (minutes) => {
try {
// 86400000 = 24 hours is mentioned in the docs as maximum value:
const ttlms = Math.min(minutes * 60 * 1000, 86400000);
if (ttlms > reloadInterval) {
reloadInterval = ttlms;
Log.info("Newsfeed-Fetcher: reloadInterval set to ttl=" + reloadInterval + " for url " + url);
}
} catch (error) {
Log.warn("Newsfeed-Fetcher: feed ttl is no valid integer=" + minutes + " for url " + url);
}
});
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
const headers = {
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)",
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version,
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
Pragma: "no-cache"
};
fetch(url, { headers: headers })
.then(NodeHelper.checkFetchStatus)
.then((response) => {
let nodeStream;
if (response.body instanceof stream.Readable) {
nodeStream = response.body;
} else {
nodeStream = stream.Readable.fromWeb(response.body);
}
nodeStream.pipe(iconv.decodeStream(encoding)).pipe(parser);
})
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
})
.then((res) => {
res.body.pipe(iconv.decodeStream(encoding)).pipe(parser);
});
};

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Node Helper: Newsfeed
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -27,40 +27,44 @@ module.exports = NodeHelper.create({
* Creates a fetcher for a new feed if it doesn't exist yet.
* Otherwise it reuses the existing one.
*
* @param {object} feed The feed object.
* @param {object} config The configuration object.
* @param {object} feed The feed object
* @param {object} config The configuration object
*/
createFetcher: function (feed, config) {
const url = feed.url || "";
const encoding = feed.encoding || "UTF-8";
const reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000;
let useCorsProxy = feed.useCorsProxy;
if (useCorsProxy === undefined) useCorsProxy = true;
try {
new URL(url);
} catch (error) {
this.sendSocketNotification("INCORRECT_URL", { url: url });
Log.error("Newsfeed Error. Malformed newsfeed url: ", url, error);
this.sendSocketNotification("NEWSFEED_ERROR", { error_type: "MODULE_ERROR_MALFORMED_URL" });
return;
}
let fetcher;
if (typeof this.fetchers[url] === "undefined") {
Log.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval);
fetcher = new NewsfeedFetcher(url, reloadInterval, encoding, config.logFeedWarnings);
Log.log("Create new newsfetcher for url: " + url + " - Interval: " + reloadInterval);
fetcher = new NewsfeedFetcher(url, reloadInterval, encoding, config.logFeedWarnings, useCorsProxy);
fetcher.onReceive(() => {
this.broadcastFeeds();
});
fetcher.onError((fetcher, error) => {
this.sendSocketNotification("FETCH_ERROR", {
url: fetcher.url(),
error: error
Log.error("Newsfeed Error. Could not fetch newsfeed: ", url, error);
let error_type = NodeHelper.checkFetchError(error);
this.sendSocketNotification("NEWSFEED_ERROR", {
error_type
});
});
this.fetchers[url] = fetcher;
} else {
Log.log("Use existing news fetcher for url: " + url);
Log.log("Use existing newsfetcher for url: " + url);
fetcher = this.fetchers[url];
fetcher.setReloadInterval(reloadInterval);
fetcher.broadcastItems();

View File

@@ -1,6 +1,6 @@
# Module: Update Notification
The `updatenotification` module is one of the default modules of the MagicMirror.
This will display a message whenever a new version of the MagicMirror application is available.
The `updatenotification` module is one of the default modules of the MagicMirror².
This will display a message whenever a new version of the MagicMirror² application is available.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/updatenotification.html).

View File

@@ -0,0 +1,167 @@
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const fs = require("fs");
const path = require("path");
const Log = require("logger");
const BASE_DIR = path.normalize(`${__dirname}/../../../`);
class GitHelper {
constructor() {
this.gitRepos = [];
}
getRefRegex(branch) {
return new RegExp(`s*([a-z,0-9]+[.][.][a-z,0-9]+) ${branch}`, "g");
}
async execShell(command) {
const { stdout = "", stderr = "" } = await exec(command);
return { stdout, stderr };
}
async isGitRepo(moduleFolder) {
const { stderr } = await this.execShell(`cd ${moduleFolder} && git remote -v`);
if (stderr) {
Log.error(`Failed to fetch git data for ${moduleFolder}: ${stderr}`);
return false;
}
return true;
}
async add(moduleName) {
let moduleFolder = BASE_DIR;
if (moduleName !== "default") {
moduleFolder = `${moduleFolder}modules/${moduleName}`;
}
try {
Log.info(`Checking git for module: ${moduleName}`);
// Throws error if file doesn't exist
fs.statSync(path.join(moduleFolder, ".git"));
// Fetch the git or throw error if no remotes
const isGitRepo = await this.isGitRepo(moduleFolder);
if (isGitRepo) {
// Folder has .git and has at least one git remote, watch this folder
this.gitRepos.push({ module: moduleName, folder: moduleFolder });
}
} catch (err) {
// Error when directory .git doesn't exist or doesn't have any remotes
// This module is not managed with git, skip
}
}
async getStatusInfo(repo) {
let gitInfo = {
module: repo.module,
behind: 0, // commits behind
current: "", // branch name
hash: "", // current hash
tracking: "", // remote branch
isBehindInStatus: false
};
if (repo.module === "default") {
// the hash is only needed for the mm repo
const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`);
if (stderr) {
Log.error(`Failed to get current commit hash for ${repo.module}: ${stderr}`);
}
gitInfo.hash = stdout;
}
const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git status -sb`);
if (stderr) {
Log.error(`Failed to get git status for ${repo.module}: ${stderr}`);
// exit without git status info
return;
}
// only the first line of stdout is evaluated
let status = stdout.split("\n")[0];
// examples for status:
// ## develop...origin/develop
// ## master...origin/master [behind 8]
// ## master...origin/master [ahead 8, behind 1]
status = status.match(/## (.*)\.\.\.([^ ]*)(?: .*behind (\d+))?/);
// examples for status:
// [ '## develop...origin/develop', 'develop', 'origin/develop' ]
// [ '## master...origin/master [behind 8]', 'master', 'origin/master', '8' ]
// [ '## master...origin/master [ahead 8, behind 1]', 'master', 'origin/master', '1' ]
gitInfo.current = status[1];
gitInfo.tracking = status[2];
if (status[3]) {
// git fetch was already called before so `git status -sb` delivers already the behind number
gitInfo.behind = parseInt(status[3]);
gitInfo.isBehindInStatus = true;
}
return gitInfo;
}
async getRepoInfo(repo) {
const gitInfo = await this.getStatusInfo(repo);
if (!gitInfo) {
return;
}
if (gitInfo.isBehindInStatus) {
return gitInfo;
}
const { stderr } = await this.execShell(`cd ${repo.folder} && git fetch --dry-run`);
// example output:
// From https://github.com/MichMich/MagicMirror
// e40ddd4..06389e3 develop -> origin/develop
// here the result is in stderr (this is a git default, don't ask why ...)
const matches = stderr.match(this.getRefRegex(gitInfo.current));
if (!matches || !matches[0]) {
// no refs found, nothing to do
return;
}
// get behind with refs
try {
const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${matches[0]}`);
gitInfo.behind = parseInt(stdout);
return gitInfo;
} catch (err) {
Log.error(`Failed to get git revisions for ${repo.module}: ${err}`);
}
}
async getRepos() {
const gitResultList = [];
for (const repo of this.gitRepos) {
try {
const gitInfo = await this.getRepoInfo(repo);
if (gitInfo) {
gitResultList.push(gitInfo);
}
} catch (e) {
Log.error(`Failed to retrieve repo info for ${repo.module}: ${e}`);
}
}
return gitResultList;
}
}
module.exports = GitHelper;

View File

@@ -1,117 +1,66 @@
const SimpleGit = require("simple-git");
const simpleGits = [];
const fs = require("fs");
const path = require("path");
const defaultModules = require(__dirname + "/../defaultmodules.js");
const Log = require("logger");
const GitHelper = require("./git_helper");
const defaultModules = require("../defaultmodules");
const NodeHelper = require("node_helper");
const ONE_MINUTE = 60 * 1000;
module.exports = NodeHelper.create({
config: {},
updateTimer: null,
updateProcessStarted: false,
start: function () {},
gitHelper: new GitHelper(),
configureModules: async function (modules) {
// Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten
// others will be added in front
// this method returns promises so we can't wait for every one to resolve before continuing
simpleGits.push({ module: "default", git: this.createGit(path.normalize(__dirname + "/../../../")) });
for (let moduleName in modules) {
async configureModules(modules) {
for (const moduleName of modules) {
if (!this.ignoreUpdateChecking(moduleName)) {
// Default modules are included in the main MagicMirror repo
let moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
try {
Log.info("Checking git for module: " + moduleName);
// Throws error if file doesn't exist
fs.statSync(path.join(moduleFolder, ".git"));
// Fetch the git or throw error if no remotes
let git = await this.resolveRemote(moduleFolder);
// Folder has .git and has at least one git remote, watch this folder
simpleGits.unshift({ module: moduleName, git: git });
} catch (err) {
// Error when directory .git doesn't exist or doesn't have any remotes
// This module is not managed with git, skip
continue;
}
await this.gitHelper.add(moduleName);
}
}
await this.gitHelper.add("default");
},
socketNotificationReceived: function (notification, payload) {
async socketNotificationReceived(notification, payload) {
if (notification === "CONFIG") {
this.config = payload;
} else if (notification === "MODULES") {
// if this is the 1st time thru the update check process
if (!this.updateProcessStarted) {
this.updateProcessStarted = true;
this.configureModules(payload).then(() => this.performFetch());
await this.configureModules(payload);
await this.performFetch();
}
}
},
resolveRemote: async function (moduleFolder) {
let git = this.createGit(moduleFolder);
let remotes = await git.getRemotes(true);
async performFetch() {
const repos = await this.gitHelper.getRepos();
if (remotes.length < 1 || remotes[0].name.length < 1) {
throw new Error("No valid remote for folder " + moduleFolder);
}
return git;
},
performFetch: async function () {
for (let sg of simpleGits) {
try {
let fetchData = await sg.git.fetch(["--dry-run"]).status();
let logData = await sg.git.log({ "-1": null });
if (logData.latest && "hash" in logData.latest) {
this.sendSocketNotification("STATUS", {
module: sg.module,
behind: fetchData.behind,
current: fetchData.current,
hash: logData.latest.hash,
tracking: fetchData.tracking
});
}
} catch (err) {
Log.error("Failed to fetch git data for " + sg.module + ": " + err);
}
for (const repo of repos) {
this.sendSocketNotification("STATUS", repo);
}
this.scheduleNextFetch(this.config.updateInterval);
},
scheduleNextFetch: function (delay) {
if (delay < 60 * 1000) {
delay = 60 * 1000;
}
let self = this;
scheduleNextFetch(delay) {
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function () {
self.performFetch();
}, delay);
this.updateTimer = setTimeout(() => {
this.performFetch();
}, Math.max(delay, ONE_MINUTE));
},
createGit: function (folder) {
return SimpleGit({ baseDir: folder, timeout: { block: this.config.timeout } });
},
ignoreUpdateChecking: function (moduleName) {
ignoreUpdateChecking(moduleName) {
// Should not check for updates for default modules
if (defaultModules.indexOf(moduleName) >= 0) {
if (defaultModules.includes(moduleName)) {
return true;
}
// Should not check for updates for ignored modules
if (this.config.ignoreModules.indexOf(moduleName) >= 0) {
if (this.config.ignoreModules.includes(moduleName)) {
return true;
}

View File

@@ -0,0 +1,3 @@
.module.updatenotification a.difflink {
text-decoration: none;
}

View File

@@ -1,4 +1,4 @@
/* Magic Mirror
/* MagicMirror²
* Module: UpdateNotification
*
* By Michael Teeuw https://michaelteeuw.nl
@@ -8,112 +8,82 @@ Module.register("updatenotification", {
defaults: {
updateInterval: 10 * 60 * 1000, // every 10 minutes
refreshInterval: 24 * 60 * 60 * 1000, // one day
ignoreModules: [],
timeout: 1000
ignoreModules: []
},
suspended: false,
moduleList: {},
start: function () {
var self = this;
Log.log("Start updatenotification");
start() {
Log.info(`Starting module: ${this.name}`);
this.addFilters();
setInterval(() => {
self.moduleList = {};
self.updateDom(2);
}, self.config.refreshInterval);
this.moduleList = {};
this.updateDom(2);
}, this.config.refreshInterval);
},
notificationReceived: function (notification, payload, sender) {
suspend() {
this.suspended = true;
},
resume() {
this.suspended = false;
this.updateDom(2);
},
notificationReceived(notification) {
if (notification === "DOM_OBJECTS_CREATED") {
this.sendSocketNotification("CONFIG", this.config);
this.sendSocketNotification("MODULES", Module.definitions);
//this.hide(0, { lockString: self.identifier });
this.sendSocketNotification("MODULES", Object.keys(Module.definitions));
}
},
socketNotificationReceived: function (notification, payload) {
socketNotificationReceived(notification, payload) {
if (notification === "STATUS") {
this.updateUI(payload);
}
},
updateUI: function (payload) {
var self = this;
getStyles() {
return [`${this.name}.css`];
},
getTemplate() {
return `${this.name}.njk`;
},
getTemplateData() {
return { moduleList: this.moduleList, suspended: this.suspended };
},
updateUI(payload) {
if (payload && payload.behind > 0) {
// if we haven't seen info for this module
if (this.moduleList[payload.module] === undefined) {
// save it
this.moduleList[payload.module] = payload;
self.updateDom(2);
this.updateDom(2);
}
//self.show(1000, { lockString: self.identifier });
} else if (payload && payload.behind === 0) {
// if the module WAS in the list, but shouldn't be
if (this.moduleList[payload.module] !== undefined) {
// remove it
delete this.moduleList[payload.module];
self.updateDom(2);
this.updateDom(2);
}
}
},
diffLink: function (module, text) {
var localRef = module.hash;
var remoteRef = module.tracking.replace(/.*\//, "");
return '<a href="https://github.com/MichMich/MagicMirror/compare/' + localRef + "..." + remoteRef + '" ' + 'class="xsmall dimmed" ' + 'style="text-decoration: none;" ' + 'target="_blank" >' + text + "</a>";
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
if (this.suspended === false) {
// process the hash of module info found
for (var key of Object.keys(this.moduleList)) {
let m = this.moduleList[key];
var message = document.createElement("div");
message.className = "small bright";
var icon = document.createElement("i");
icon.className = "fa fa-exclamation-circle";
icon.innerHTML = "&nbsp;";
message.appendChild(icon);
var updateInfoKeyName = m.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
var subtextHtml = this.translate(updateInfoKeyName, {
COMMIT_COUNT: m.behind,
BRANCH_NAME: m.current
});
var text = document.createElement("span");
if (m.module === "default") {
text.innerHTML = this.translate("UPDATE_NOTIFICATION");
subtextHtml = this.diffLink(m, subtextHtml);
} else {
text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", {
MODULE_NAME: m.module
});
}
message.appendChild(text);
wrapper.appendChild(message);
var subtext = document.createElement("div");
subtext.innerHTML = subtextHtml;
subtext.className = "xsmall dimmed";
wrapper.appendChild(subtext);
addFilters() {
this.nunjucksEnvironment().addFilter("diffLink", (text, status) => {
if (status.module !== "default") {
return text;
}
}
return wrapper;
},
suspend: function () {
this.suspended = true;
},
resume: function () {
this.suspended = false;
this.updateDom(2);
const localRef = status.hash;
const remoteRef = status.tracking.replace(/.*\//, "");
return `<a href="https://github.com/MichMich/MagicMirror/compare/${localRef}...${remoteRef}" class="xsmall dimmed difflink" target="_blank">${text}</a>`;
});
}
});

Some files were not shown because too many files have changed in this diff Show More