Compare commits

...

549 Commits

Author SHA1 Message Date
Michael Teeuw
de57daa3cd Merge pull request #1498 from MichMich/develop
Release 2.6.0.
2019-01-01 17:09:33 +01:00
Michael Teeuw
e70e011a9c Add dependency. 2019-01-01 16:57:16 +01:00
Michael Teeuw
99febb99f1 Additional update info. 2019-01-01 16:48:12 +01:00
Michael Teeuw
874d79be36 Upgrade Electron to 2.0.16 2019-01-01 16:33:59 +01:00
Michael Teeuw
b33663c9f8 Prepare for release 2.6.0. 2019-01-01 15:52:29 +01:00
Michael Teeuw
7e69fa39eb Merge pull request #1497 from fewieden/feature/weather-module-improvements
fix rain amount information for different units and providers, docume…
2019-01-01 15:19:27 +01:00
Michael Teeuw
de06490539 Merge pull request #1496 from janfrode/patch-1
Update README.md
2019-01-01 15:18:45 +01:00
fewieden
40a30c24a0 link provider readme in module readme 2018-12-30 20:52:27 +01:00
fewieden
de04c12d3c fix rain amount information for different units and providers, documentation 2018-12-30 20:46:25 +01:00
Jan-Frode Myklebust
38fb53b058 Update README.md
Wrong delimiter used for electronOptions. Use : instead of =.
2018-12-30 20:33:16 +01:00
Michael Teeuw
ed617c5943 Merge pull request #1485 from djgalloway/wip-endtime
Document endTime variables
2018-12-30 15:42:04 +01:00
Michael Teeuw
986337da0c Merge pull request #1495 from fewieden/feature/weather-module-improvements
weather module feels like temperature
2018-12-30 15:40:22 +01:00
fewieden
8dd7621f29 add original feels like temperature and fixed it for imperial units 2018-12-30 14:17:13 +01:00
fewieden
88d862303d fixed beaufortwindspeed for imperial units 2018-12-30 14:14:17 +01:00
fewieden
cc274ffebe fixed darksky metric units 2018-12-30 14:11:16 +01:00
Michael Teeuw
28a108f79b Merge pull request #1491 from devpwnz/patch-1
Update ro.json
2018-12-29 10:51:28 +01:00
Michael Teeuw
b9f75bf7d2 Merge pull request #1489 from fewieden/feature/weather-module-improvements
WIP: weather module improvements
2018-12-29 10:49:18 +01:00
devpwnz
39994d5797 Update CHANGELOG.md 2018-12-29 10:08:57 +02:00
devpwnz
8e28be6558 Update ro.json 2018-12-29 10:00:29 +02:00
fewieden
8a65bef004 add unit and language handling for weather provider darksky 2018-12-28 19:39:00 +01:00
Michael Teeuw
b94dc5044b Merge pull request #1488 from fewieden/weather
Weather refactoring
2018-12-28 08:56:58 +01:00
fewieden
b853c00dd4 Add changelog entry 2018-12-27 23:12:28 +01:00
fewieden
7a0bc81f48 Merge branch 'develop' of https://github.com/MichMich/MagicMirror into weather
# Conflicts:
#	css/main.css
2018-12-27 23:03:41 +01:00
fewieden
10bc326490 cleanup 2018-12-27 19:37:02 +01:00
fewieden
1920f8158e config options and documentation 2018-12-27 18:52:35 +01:00
fewieden
0ed2ba0183 darksky forecast and darksky current weather fixes 2018-12-27 17:56:34 +01:00
fewieden
95adc0aec1 forecast 2018-12-27 17:14:03 +01:00
fewieden
ebee80d10e small improvements 2018-12-27 17:13:49 +01:00
fewieden
63836185d9 weatherprovider 2018-12-27 17:13:06 +01:00
David Galloway
d0195e0509 Document endTime variables
Missed docs in 188aa14d82

Signed-off-by: David Galloway <dgallowa@redhat.com>
2018-12-20 11:40:50 -05:00
Michael Teeuw
5d9bcd9918 Merge pull request #1476 from wast/patch-1
Create hr.json
2018-12-19 16:18:32 +01:00
Michael Teeuw
db04c26d24 Merge pull request #1480 from michael5r/newsfeed-removestarttags-bug
[FIX] Bug in newsfeed module using removeStartTags on a description
2018-12-19 16:17:52 +01:00
Michael Teeuw
56b399655e Merge pull request #1483 from balassy/feature/weatherforecast-degree-labels
Always display the degree symbol in the Weather Forecast module
2018-12-19 16:15:18 +01:00
Michael Teeuw
24e15c0568 Add ajv dependency to fix linting error. 2018-12-19 16:04:52 +01:00
György Balássy
f0c516e82d CHANGED: The Weather Forecast module by default displays the &deg; symbol after every numeric value to be consistent with the Current Weather module. 2018-12-14 11:32:58 +01:00
György Balássy
f38203ef62 Merge pull request #5 from MichMich/develop
Update the develop branch from the original repo
2018-12-14 09:13:37 +01:00
mschmidt
a77c026803 Add issue number to changelog 2018-12-10 14:06:55 -06:00
mschmidt
5b6306671c Initial 2018-12-10 14:02:50 -06:00
Stjepan
25610222bc Update translations.js
Added Croatian.
2018-12-06 11:41:01 +01:00
Stjepan
c17f941fb9 Update CHANGELOG.md
Added Croatian translation to the changelog.
2018-12-06 11:38:34 +01:00
Stjepan
ae6ab1d203 Create hr.json
Croatian translation.
2018-12-06 10:00:34 +01:00
Michael Teeuw
92accf99b4 Merge pull request #1468 from ax42/patch-1
Update README.md
2018-11-29 10:18:39 +01:00
Michael Teeuw
b02702fe80 Merge pull request #1467 from lavolp3/calendar_issues
Fading for dateheaders, fixed bug for fulldayevents
2018-11-29 10:18:09 +01:00
Alexis Iglauer
5c549ec6e5 Update README.md
Typo
2018-11-22 23:28:04 +01:00
Dirk
af459a5a28 formatting corrected
Corrected formatting due to Travis CI errors
2018-11-21 12:10:39 +01:00
Dirk
07770601f6 Update CHANGELOG.md
- Fading for dateheaders timeFormat in Calendar [#1464](https://github.com/MichMich/MagicMirror/issues/1464)
- Bug showing FullDayEvents one day too long in calendar fixe
2018-11-21 10:24:53 +01:00
Dirk
cc96b86b3a Fading for dateheaders
Included fading for dateheaders option
Removed unnecessary switch statement in dateheaders option
2018-11-21 09:32:56 +01:00
Michael Teeuw
1547f4d8b2 Merge pull request #1456 from EdgardosReis/develop
Portuguese translation for "Feels"
2018-11-08 08:57:28 +01:00
Michael Teeuw
75054fcc70 Merge pull request #1458 from tomwardill/ignore-rrule-errors
Ignore RRULE errors for unparseable elements.
2018-11-08 08:56:50 +01:00
Tom Wardill
390b3b173b Update CHANGELOG.md 2018-11-07 18:53:54 +00:00
Tom Wardill
78daa65d28 Ignore rrule errors 2018-11-07 18:53:04 +00:00
EdgardosReis
3bbdd08d24 Portuguese translation for "Feels" 2018-11-07 00:34:02 +00:00
Michael Teeuw
cec1f12918 Merge pull request #1424 from thobach/develop
Allow to parse recurring calendar events where the start date is before 1900
2018-10-30 16:20:33 +01:00
Thomas Bachmann
d923ae2107 Merge branch 'develop' into develop 2018-10-29 20:30:37 +01:00
Thomas Bachmann
85931155e6 Fixed eslint issues
.. as requested in Pull Request #1424
2018-10-29 20:26:54 +01:00
Michael Teeuw
4fd87aca09 Change showEnd default to false. 2018-10-26 15:22:05 +02:00
Michael Teeuw
600e0ec7e3 Merge pull request #1425 from Ybbet/addClassCellCalendar
Add class cell calendar
2018-10-26 15:14:31 +02:00
Michael Teeuw
51fbff1a4a Merge branch 'develop' into addClassCellCalendar 2018-10-26 15:14:17 +02:00
Michael Teeuw
03b1389ee5 Merge pull request #1426 from gberg927/develop
Changed OpenWeatherMap URL in ReadME
2018-10-26 15:12:34 +02:00
Michael Teeuw
8f014e9d82 Merge pull request #1427 from P-Storm/develop
Added Font-awesome 5
2018-10-26 15:11:55 +02:00
Michael Teeuw
cecc6f7561 Merge branch 'develop' into develop 2018-10-26 15:11:46 +02:00
Michael Teeuw
62ba81c6a6 Merge pull request #1430 from shade34321/weather_forecast_screenshot
Added in 5 day forecast screenshot
2018-10-26 14:42:02 +02:00
Michael Teeuw
c5e3422fcd Merge branch 'develop' into weather_forecast_screenshot 2018-10-26 14:40:57 +02:00
Michael Teeuw
bd5a46b4ab Merge pull request #1431 from shade34321/weather_screenshot
Added in screenshot for current weather module.
2018-10-26 14:38:07 +02:00
Michael Teeuw
3a972bbbab Merge pull request #1432 from shade34321/clock_screenshot
Added in screenshot
2018-10-26 14:37:21 +02:00
Michael Teeuw
7768ea28bd Merge pull request #1433 from shade34321/compliments_screenshot
Added in compliments screenshot
2018-10-26 14:36:22 +02:00
Michael Teeuw
75add44e86 Merge pull request #1434 from shade34321/news_feed_screenshot
Added in screenshot for the newfeed module
2018-10-26 14:35:53 +02:00
Michael Teeuw
7d94365cbf Merge pull request #1437 from Duske/patch-1
(doc) showEnd config
2018-10-26 14:34:23 +02:00
Michael Teeuw
2d830fb8e7 Merge pull request #1441 from cphamlet/patch-1
Fix Broken Link
2018-10-26 14:33:36 +02:00
Michael Teeuw
633bf36fe7 Merge pull request #1447 from Santanachia/master
Fix polish translation
2018-10-26 14:31:44 +02:00
Marcin
c0a5e23d95 Merge branch 'develop' into master 2018-10-23 09:00:20 +02:00
Marcin Bielecki
d3798344dd fix polish translation 2018-10-23 08:57:53 +02:00
Teddy
ed37460402 Merge branch 'master' into addClassCellCalendar 2018-10-21 22:38:50 +02:00
cphamlet
9b6ba65cdb Fix Broken Link
http://www.openweathermap.org/help/city_list.txt is a dead link, suggest replacing with https://openweathermap.org/city
2018-10-14 15:08:55 -05:00
Dustin
42a9631926 Merge branch 'develop' into patch-1 2018-10-09 10:51:09 +02:00
Shade Alabsa
676a8a6421 Fixed README formatting errors. 2018-10-08 19:40:54 -04:00
Dustin
cdbf022ce0 Update CHANGELOG.md 2018-10-08 21:19:55 +02:00
Dustin
db79e1271e (doc) showEnd config
Add documentation for showEnd configuration
2018-10-08 21:11:28 +02:00
Shade Alabsa
d2b3efacf9 Added in screenshot for the newfeed module 2018-10-07 15:20:11 -04:00
Shade Alabsa
a2ab94f971 Added in compliments screenshot 2018-10-07 15:16:10 -04:00
Shade Alabsa
a0d92d764b Added in screenshot 2018-10-07 15:12:10 -04:00
Shade Alabsa
649b78e3f2 Added in screenshot for current weather module. 2018-10-07 15:01:51 -04:00
Shade Alabsa
e7df1c3e56 Added in 5 day forecast screenshot 2018-10-07 14:55:07 -04:00
P-Storm
53833ae0c3 Spaces to tab 2018-10-05 01:42:28 +02:00
P-DESK\P-Storm
66b914774a Updated changelog 2018-10-05 01:24:47 +02:00
P-DESK\P-Storm
fc89feec4e * Added font awesome 5, keeping shims in place for the calendar app (https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4)
* Updated example sample config
2018-10-05 01:16:25 +02:00
Dennis Glasberg
d311dbd9d5 Update README.md 2018-10-03 21:28:30 -04:00
Dennis Glasberg
f97aa67100 Merge pull request #2 from gberg927/Weather-Module-Readme-URL
Weather module readme url
2018-10-03 21:23:56 -04:00
Dennis Glasberg
9a8add780c Update README.md 2018-10-03 21:20:38 -04:00
Teddy Payet
3b48f1d042 - Possibility to add classes to the cell of symbol, title and time of the events of calendar. 2018-10-04 02:07:08 +02:00
Teddy Payet
332b54e7a5 Merge branch 'develop' of https://github.com/MichMich/MagicMirror into addClassCellCalendar 2018-10-04 02:02:20 +02:00
Thomas Bachmann
007b2f0c88 Allow to parse recurring calendar events where the start date is before 1900
Some birthday calendar events have a start date before 1900.
2018-10-03 22:43:29 +02:00
Thomas Bachmann
3f083862e7 Allow to parse recurring calendar events where the start date is before 1970
Some birthday calendar events have a start date before 1970.
2018-10-03 22:05:51 +02:00
Thomas Bachmann
39619d5277 Allow to parse recurring calendar events where the start date is before 1970
Some birthday calendar events have a start date before 1970.
2018-10-03 22:03:50 +02:00
Michael Teeuw
d4fe01f9b9 Prepare for 2.6.0-dev. 2018-10-01 08:20:15 +02:00
Michael Teeuw
6db61b4357 Merge pull request #1418 from MichMich/develop
Develop
2018-10-01 08:16:04 +02:00
Michael Teeuw
f245cbf7f2 Merge pull request #1419 from rudibarani/master
Details to install the latest version of Node.js
2018-10-01 08:05:22 +02:00
rudibarani
6f2b04669f Details to install the latest version of Node.js
Added the direct code to always install the latest version of Node.js for the manual installation.
Maybe you could also include this in the automatic setup script, which does not seem to install the latest version of Node.js.
2018-09-30 23:39:22 +02:00
Michael Teeuw
9a46081d0b Prepare for release 2.5.0 2018-09-30 21:36:04 +02:00
Teddy Payet
3c7e507ca1 Merge branch 'develop' of https://github.com/MichMich/MagicMirror into develop 2018-09-26 10:03:26 +02:00
Michael Teeuw
7117725e69 Merge pull request #1405 from ubertao/multi-line-compliments
Multi-line compliments
2018-09-25 11:36:27 +02:00
ubertao
ba428c6cfe Use 'white-space: pre-line' for multi-line compliment. 2018-09-24 23:01:17 +08:00
ubertao
d76c924ad1 Update compliments README.md for multi-line support. 2018-09-20 09:09:06 +08:00
ubertao
cad7debc5b Replace innerHTML() with createElement() and appendChild() for security. 2018-09-20 08:49:17 +08:00
ubertao
40725aa2a2 Update CHANGELOG.md with multi-line compliments support. 2018-09-17 00:54:01 +08:00
ubertao
6034891fed Support multi-line compliments. 2018-09-17 00:51:37 +08:00
Michael Teeuw
9dd9862d33 Merge pull request #1402 from ubertao/pr-zh-cn-2.5.0
Update zh-cn translation for 2.5.0
2018-09-13 08:59:57 +02:00
Michael Teeuw
48c72e319b Merge pull request #1401 from ubertao/pr-calender-gzip
Pr calender gzip
2018-09-13 08:58:13 +02:00
ubertao
4b6208fd9c Update CHANGELOG.md for zh-cn translation updates. 2018-09-12 09:35:58 +08:00
ubertao
168904a159 Update zh-cn translation to 2.5.0 2018-09-12 09:31:46 +08:00
ubertao
28f1498ec3 update CHANGELOG.md adding gzip fix for calendar module 2018-09-12 08:17:10 +08:00
ubertao
d3028e10d3 Merge branch 'develop' of https://github.com/MichMich/MagicMirror into pr-calender-gzip 2018-09-12 08:15:52 +08:00
Teddy Payet
5eb0b77a8a Merge upstream/develop 2018-09-08 23:40:39 +02:00
ubertao
4aace5b95a update changelog with gzip calendar fix 2018-09-08 23:44:12 +08:00
ubertao
044dbd4a65 Add gzip support to calendar fetcher. 2018-09-08 23:05:19 +08:00
Michael Teeuw
5dbae7c9d7 Merge pull request #1383 from balassy/bugfix/updatenotification-localization
Making the word "commit" localizable in the UpdateNotification module message
2018-09-05 09:12:04 +02:00
György Balássy
ec44cb2761 CHANGED: The wording in CHANGELOG is modified to be more descriptive. 2018-09-05 04:41:09 +02:00
György Balássy
b06cf55c0b FIXED: Character encoding issue in pl.json occurred during rebase. 2018-09-05 04:33:42 +02:00
György Balássy
b601f6a138 CHANGED: The UPDATE_INFO key in the localization file is changed to UPDATE_INFO_SINGLE and UPDATE_INFO_MULTIPLE to allow different localization for single and multiple commits. 2018-09-05 04:30:57 +02:00
György Balássy
ddebc63488 ADDED: CHANGELOG entry. 2018-09-05 04:30:23 +02:00
György Balássy
35440822be CHANGED: The COMMIT_COUNT placeholder in the UPDATE_INFO message of the UpdateNotification module does not contain the word "commit" or "commits" any more, so language files can independently localize them. 2018-09-05 04:30:22 +02:00
György Balássy
f4c6bcfb8e Merge pull request #4 from MichMich/develop
Update Develop from the original repo
2018-09-05 04:26:13 +02:00
Michael Teeuw
6365c5c9ef Merge pull request #1396 from matt08/patch-4
Updated Polish translation
2018-09-04 11:42:16 +02:00
matt08
dd0334d30d Update pl.json 2018-08-31 09:24:18 +02:00
matt08
c462a44973 Polish translate for "Feels" 2018-08-31 09:17:04 +02:00
Michael Teeuw
6f88f5db83 Merge pull request #1385 from el97/patch-4
Update sv.json
2018-08-31 09:14:33 +02:00
matt08
93617f62a2 Updating "feels" translation 2018-08-31 09:14:16 +02:00
György Balássy
61d5f39408 Merge pull request #3 from MichMich/develop
Update the develop branch from the original repository
2018-08-30 00:00:56 +02:00
Michael Teeuw
c1fddaa7dd Merge pull request #1392 from vlebourl/develop
support for showing the end time of non full day events and the end date of several day long
2018-08-29 09:12:02 +02:00
vlb
188aa14d82 added support for showing end of events in calendar 2018-08-28 18:11:38 +02:00
Vincent Le Bourlot
5c25dd5b6d Update CHANGELOG.md 2018-08-28 18:00:13 +02:00
vlb
7c579cf7b7 added support for showing end of events through config parameters showEnd and dateEndFormat 2018-08-28 17:35:53 +02:00
vlb
c755c823fa added support for events having a duration instead of an end 2018-08-28 17:29:42 +02:00
Michael Teeuw
116588c237 Merge pull request #1390 from ccrlawrence/patch-1
ClientOnly: Global variable name used in callback function
2018-08-28 15:37:55 +02:00
Michael Teeuw
f02c1e4dc7 Merge branch 'develop' into patch-1 2018-08-28 15:36:58 +02:00
Michael Teeuw
c4e8cc1641 Correct changelog entry. 2018-08-28 15:33:53 +02:00
Michael Teeuw
93e68ad147 Merge branch 'develop' into patch-1 2018-08-28 15:32:47 +02:00
vlb
7ba88a83f0 consider events lasting several full days as full day events 2018-08-28 13:05:06 +02:00
ccrlawrence
c9293327ce Update CHANGELOG.md 2018-08-26 16:01:41 +01:00
ccrlawrence
fa1f35a89e ClientOnly: Global variable name used in callback function.
The global 'config' variable is used in the callback function, changed to local one. Unwanted behaviour when accessing server on docker or if using 0.0.0.0 or blank address in config file as it just passes this to electron to display.
2018-08-26 15:53:01 +01:00
Michael Teeuw
845ce7a711 Merge pull request #1384 from YangVincent/develop
Update OpenWeather city list instructions
2018-08-21 16:31:08 +02:00
el97
2b40007563 Update sv.json
Small changes. Added "FEELS": "Känns som".
2018-08-21 15:29:20 +02:00
Vincent Yang
217034c4a7 Update Changelog for weather city link 2018-08-21 01:06:12 -07:00
Vincent Yang
0b9d4f17ab Fix link for finding cities in OpenWeather 2018-08-21 01:04:58 -07:00
Michael Teeuw
7fb0ec12dd Merge pull request #1381 from Elaniobro/rp-zero-w-es6-fix
RaspBerry Pi Zero W default calendar module fix
2018-08-17 12:18:31 +02:00
György Balássy
3581158a7b Merge pull request #2 from MichMich/develop
Update Develop from original repo
2018-08-17 06:40:54 +02:00
Elan Trybuch
facfa73214 Merge branch 'develop' into rp-zero-w-es6-fix 2018-08-16 12:07:38 -04:00
Elan Trybuch
0ef4a86d42 Add patch note to CHANGELOG.md 2018-08-16 11:51:10 -04:00
Elan Trybuch
d4ec4795c3 Fix ES6 syntax bug on RaspberryPi Zero W
Following this issue https://github.com/MichMich/MagicMirror/issues/694 it seems that the Midori Browser does not recoginize ES6 syntax. Further, the use of 'var' is seen throughout the calendar module excpet on line 439, where the error is reported
2018-08-16 11:45:34 -04:00
Michael Teeuw
b13d0aa283 Merge pull request #1373 from jannekalliola/develop
Calendar: Absolute dates do not show absolute even if getRelative and urgency are set to zero
2018-08-16 12:05:22 +02:00
Michael Teeuw
a6965342e7 Update CHANGELOG.md 2018-08-16 12:00:06 +02:00
Michael Teeuw
752dfa5b7f Merge pull request #1369 from heskja/patch-2
Patch 2
2018-08-16 11:56:03 +02:00
Michael Teeuw
87aa283f22 Merge pull request #1376 from vincep5/develop
weatherforecast rainfall rounding
2018-08-16 11:55:25 +02:00
vincep5
6598ae080f weatherforecast rainfall rounding 2018-08-07 11:48:10 -05:00
Janne Kalliola
7c5e8a66e4 Added also description of the change to changelog 2018-08-05 19:49:26 +03:00
Janne Kalliola
c9577bcdc5 Added an if to use absolute dates with all events 2018-08-05 19:47:27 +03:00
heskja
8254c2e83c Merge pull request #1 from heskja/patch-1
Update nb.json
2018-08-03 20:24:46 +02:00
heskja
20a9ac841d Update nn.json
Added translation for "FEELS"
2018-08-03 20:23:19 +02:00
heskja
ae86b75d89 Update nb.json
Added translation for "FEELS"
2018-08-03 20:22:44 +02:00
György Balássy
93a0afe612 Merge pull request #1 from MichMich/develop
Sync Develop from original repo
2018-08-02 06:26:23 +02:00
Michael Teeuw
439027220b Merge pull request #1366 from Ybbet/alert_css
Alert css #1353
2018-08-01 10:19:52 +02:00
Teddy Payet
4a07272d7a Changelog.md updated 2018-08-01 09:42:23 +02:00
Teddy Payet
81432b54a3 Classes for alert module
Use of classes instead of inline style. With those modifications, it will be easier to personnalize the alert with custom.css
2018-08-01 09:37:27 +02:00
Michael Teeuw
b84a6e0c02 Merge pull request #1358 from jagobagascon/develop
Added missing spanish text
2018-07-15 19:47:04 +02:00
jagoba
37dc5a00e8 Merge branch 'bugfix/spanish-missing-localization' into develop 2018-07-15 19:21:59 +02:00
jagoba
e6edf85fbe Added Spanish translation for "FEELS" 2018-07-15 19:13:04 +02:00
Michael Teeuw
cb533a26f2 Merge pull request #1356 from balassy/bugfix/hungarian-localization
Updating the Hungarian localization
2018-07-13 22:01:56 +02:00
György Balássy
a7278f76a8 UPDATED: The CHANGELOG.md file with description of the changes in the Hungarian localization. 2018-07-13 16:42:47 +02:00
György Balássy
80bd32382f CHANGED: The Hungarian localization of the updatenotification module is changed to be more natural, because the existing messages felt like they were created with machine translation, and they were not only unnatural, but also misleading. 2018-07-13 16:32:32 +02:00
György Balássy
db21ced104 ADDED: Missing Hungarian localization for the "FEELS" resource key. 2018-07-13 16:26:15 +02:00
György Balássy
717c6555cb ADDED: Missing Hungarian localization for the "WEEK" resource key. 2018-07-13 16:23:34 +02:00
Michael Teeuw
a412e4af5c Merge pull request #1354 from Ybbet/develop
Wrong mixup… (cf german and spanish)
2018-07-11 02:21:14 +02:00
Teddy Payet
3350bf1ac6 CHANGELOG 2018-07-11 01:52:29 +02:00
Teddy Payet
4aa3353a1d Wrong mixup… (cf german and spanish)
Thanks fewieden.
2018-07-11 01:45:16 +02:00
Michael Teeuw
ff48a58537 Merge pull request #1352 from Ybbet/develop
Add some translations (mostly french).
2018-07-10 16:15:13 +02:00
Teddy Payet
08fa511d17 Add some translations (mostly french). 2018-07-10 15:03:54 +02:00
Michael Teeuw
c295115ffc Merge pull request #1347 from cederstrom/toggle-news-article-fullscreen
Abillity to toggle news article in fullscreen
2018-07-10 12:41:20 +02:00
Michael Teeuw
5fb14610ec Merge pull request #1348 from cederstrom/swedish-translations
Swedish translations
2018-07-10 12:40:04 +02:00
Andreas Cederström
e87c2350b7 Update CHANGELOG.md 2018-07-07 17:36:51 +02:00
Andreas Cederström
44e691e840 Update CHANGELOG.md 2018-07-07 17:35:04 +02:00
Andreas Cederström
b5a7234cf3 Swedish translation for "FEELS" 2018-07-07 17:26:17 +02:00
Andreas Cederström
d12509957f Abillity to toggle article in fullscreen 2018-07-07 16:50:10 +02:00
Michael Teeuw
f01e7b7e20 Prepare for 2.5.0 2018-07-04 11:32:04 +02:00
Michael Teeuw
6aa156d956 Merge pull request #1343 from MichMich/develop
Release 2.4.1
2018-07-04 11:27:05 +02:00
Michael Teeuw
ef5ea93de1 Prepare for release 2.4.1 2018-07-04 11:20:37 +02:00
Michael Teeuw
b4913f51f2 Merge pull request #1341 from jannekalliola/master
Fixed parsing date
2018-07-03 17:08:07 +02:00
Janne Kalliola
dc3e960e79 Fixed parsing date, as dt_txt is missing from certain weather API results 2018-07-02 23:22:09 +03:00
fewieden
0fe79b5288 indoor data, new filter, small cleanup 2018-07-02 15:43:24 +02:00
Michael Teeuw
1f76bd1942 Setup the next release (2.5.0). 2018-07-01 21:01:41 +02:00
Michael Teeuw
3545f80920 Merge pull request #1338 from MichMich/develop
Release 2.4.0
2018-07-01 20:50:46 +02:00
Michael Teeuw
0b2d1564ef Prepare to release 2.4.0 2018-07-01 20:43:04 +02:00
Michael Teeuw
fdacf824b3 Merge pull request #1337 from ShameerAshraf/develop
Fixed Wind Chill and Heat Index for Kelvin
2018-06-30 21:03:19 +02:00
Shameer Ashraf
5c01a44644 Updated changelog 2018-06-29 13:27:55 -04:00
Shameer Ashraf
4eb49d872b Updated changelog 2018-06-29 13:24:28 -04:00
Shameer Ashraf
34e5f29419 Fixed Wind Chill in Kelvin 2018-06-29 01:00:20 -04:00
Shameer Ashraf
f4910f0a8e Fixed Heat Index for Kelvin 2018-06-29 00:23:04 -04:00
Michael Teeuw
c8c14611dc Merge pull request #1334 from mdrayer/quick-fix-readme-raspberry-pi
Correct the "Raspberry Pi" link in the ToC.
2018-06-27 20:22:30 +02:00
Michael Drayer
491201991e Correct the "Raspberry Pi" link in the ToC. 2018-06-27 14:18:25 -04:00
Michael Teeuw
401f3574fd Update CHANGELOG.md 2018-06-27 10:29:49 +02:00
Michael Teeuw
173a86172c Add update translations. 2018-06-27 10:29:08 +02:00
Michael Teeuw
9ecbff024a Update CHANGELOG.md 2018-06-27 10:26:30 +02:00
Michael Teeuw
1b5be34be4 Merge pull request #1333 from ubertao/fixlocale
Fix locale id zh_cn -> zh-cn, zh_tw -> zh-tw, pt_br -> pt-br
2018-06-27 10:25:34 +02:00
Michael Teeuw
ceb3a997b6 Merge pull request #1330 from pintman/patch-1
minor typo in position fixed.
2018-06-27 10:23:15 +02:00
Ubertao
b1ead7fec8 Fix locale id zh_cn -> zh-cn, zh_tw -> zh-tw, pt_br -> pt-br 2018-06-27 14:09:02 +08:00
Michael Teeuw
d534dbb006 Merge pull request #1331 from flyingchipmunk/dev_newsfeed_logging
Add option to newsfeed for logging errors
2018-06-27 02:39:14 +02:00
Matthew Veno
e56377117b Add option to newsfeed for logging errors
- 'logFeedWarnings' added to newsfeed config, defaulted to false
- Only log parse feed errors when logFeedWarnings is true
- Updated README and CHANGELOG
- Fixes #1329
2018-06-26 20:01:28 -04:00
Marco Bakera
63483dc6c3 minor typo in position fixed. 2018-06-24 14:29:55 +02:00
fewieden
66ceafd010 show indoor data, add loading message 2018-06-16 10:53:17 +02:00
Michael Teeuw
dd793650c3 Merge pull request #1314 from Ybbet/develop
Customize classes for table.
2018-06-12 09:19:16 +02:00
Teddy Payet
afd829307d Tabs and spaces from the original files
With a diff, here the orginal tabulations.
2018-06-11 23:09:00 +02:00
Teddy Payet
09abdc0f12 ESLint format
Resolve format ith eslint
2018-06-11 19:59:21 +02:00
Teddy Payet
ed4d17f578 README updated
Update of README for the new option.
2018-06-11 16:41:08 +02:00
Teddy Payet
aeeeb5a37b Add changelog 2018-06-11 14:04:06 +02:00
Teddy
dcb2e51587 Update .gitignore 2018-06-11 14:00:16 +02:00
Teddy Payet
cbc2eaf908 Customize classes for table
MagicMirror offers helper classes in the main.css. Therefore, we give
the possibility to indicate the class that we want.
2018-06-11 12:54:27 +02:00
Michael Teeuw
8808031e7c Merge pull request #1309 from sdetweil/fix_suspend
invoke callback for suspend notification, even if no dom content
2018-06-08 13:21:40 +02:00
Sam Detweiler
23ac7213d3 remove trailing spaces from reformatted else 2018-06-08 06:11:36 -05:00
Michael Teeuw
add7b44d0b Style change. 2018-06-07 16:31:49 +02:00
Michael Teeuw
1e4b7599a7 Merge branch 'develop' into fix_suspend 2018-06-07 16:29:16 +02:00
Sam Detweiler
54443b038a Revert "fix changelog"
This reverts commit c3f03e3f95.
2018-06-07 08:09:39 -05:00
Sam Detweiler
c3f03e3f95 fix changelog 2018-06-07 08:04:49 -05:00
Sam Detweiler
18135624f6 update changelog 2018-06-07 08:02:16 -05:00
Sam Detweiler
11238d6b71 fix tabs 2018-06-07 07:59:07 -05:00
Sam Detweiler
848f94b1e0 invoke callback for suspend notification, even if no dom content 2018-06-07 07:50:42 -05:00
Michael Teeuw
d47cfe9504 Merge pull request #1304 from kjb085/kb/calendar-regex
Add regex filtering to calendar module
2018-06-05 21:02:56 +02:00
Kenn Breece
70dccff293 Add regex filtering to calendar module 2018-06-03 21:12:31 -04:00
Michael Teeuw
e40873710b Merge pull request #1302 from idoodler/develop
Ability to fetch compliments from a remote server
2018-06-03 19:46:01 +02:00
idoodler
b140ef3b7a Ability to fetch compliments from a remote server 2018-06-03 15:47:56 +02:00
Michael Teeuw
3b7b74aa67 Merge pull request #1297 from OiYouYeahYou/linting-fix
Add and lint clientonly/index.js
2018-05-29 11:56:18 +02:00
Jason
de8e5b2d69 Merge branch 'develop' into linting-fix 2018-05-26 19:44:06 +01:00
Jason
afea33b0e3 Changelog 2018-05-26 19:42:58 +01:00
Jason
b44fbc1e4f add and lint clientonly 2018-05-26 19:36:46 +01:00
Michael Teeuw
c4dee3dd8d Merge pull request #1293 from derRAV3N/patch-1
Add note to README.md
2018-05-22 15:27:03 +02:00
derRAV3N
091e024032 Add note to README.md
Add note to README.md to not add calendars that have entries before 1st January 1970.
2018-05-22 15:14:05 +02:00
Michael Teeuw
0e030f7f48 Add information about the Electron update. 2018-05-21 14:06:50 +02:00
fewieden
3341c9e3bf start with forecast template 2018-05-21 10:57:22 +02:00
fewieden
91ddc00f7e fix moment, add unit filter 2018-05-21 10:56:46 +02:00
Michael Teeuw
3049ba0b24 Merge pull request #1290 from edward-shen/develop
newsfeed now remembers user configuration settings for descriptions after fullscreen view. Fixes #1282.
2018-05-16 07:27:38 +02:00
Edward Shen
55a161fafe Fixes #1282.
Added a runtime var isShowingDescription that gets reset to user config.
this.config.showDescription no longer mutates during runtime.
Changelog has been updated to include this fix.
2018-05-15 20:37:45 -04:00
Michael Teeuw
349af24c81 Merge pull request #1287 from ringzer/patch-1
Update README.md
2018-05-12 12:33:25 +02:00
ringzer
788f1c4b3e Update README.md
Included /home/pi/MagicMirror/ path when copying config.js.sample and running npm run config:check
2018-05-11 16:47:03 +01:00
Michael Teeuw
889af461c6 Upgrade to Electron 2.0.0. 2018-05-11 16:23:43 +02:00
Michael Teeuw
df86e59089 Merge pull request #1284 from jrlambs/develop
New calendar display format
2018-05-11 15:36:36 +02:00
=
c6bf69cce4 fix linting errors. add line to changelog 2018-05-10 19:54:01 -04:00
=
e492012004 fix missing s on timeFormat 2018-05-09 22:36:53 -04:00
=
94c46f9881 New calendar display format with date headers for days and times listed next to events for that date
IE:

Sunday, May 1st
  2:00 pm       Soccer
  4:00 pm       Basketball
2018-05-09 22:32:15 -04:00
Michael Teeuw
1eaa9d32ea Merge pull request #1283 from jannekalliola/develop
Changed weatherforecast to use dt_txt field
2018-05-09 09:35:49 +02:00
Janne Kalliola
0e2e8d2e2a Changed weatherforecast to use dt_txt field 2018-05-08 18:45:38 +03:00
Michael Teeuw
cfb39c6364 Merge pull request #1279 from parnic/develop
Fixed coloredSymbolOnly
2018-05-05 18:38:54 +02:00
Parnic
173499f496 Fixed coloredSymbolOnly 2018-05-05 08:31:58 -05:00
Michael Teeuw
1081049074 Merge pull request #1264 from parnic/develop
Fixed heat index
2018-05-01 09:55:42 +02:00
Michael Teeuw
961dc85514 Merge pull request #1275 from john3300/colored-symbol-only
Added option to calendar module that colors only the symbol
2018-05-01 09:55:13 +02:00
Michael Teeuw
6434acd492 Merge branch 'develop' into colored-symbol-only 2018-05-01 09:55:05 +02:00
Michael Teeuw
ccb27c89d8 Merge pull request #1262 from ndom91/patch-1
Updated newsfeed.js - improved fullscreen iframe
2018-05-01 09:53:56 +02:00
Brian Johnson
8053256203 Added option to calendar module that colors only the symbol instead of the whole line 2018-04-27 11:06:45 -05:00
Parnic
4abd7301fd Updated changelog 2018-04-17 19:45:12 -05:00
Parnic
d6fe5ab417 Fixed heat index
Celsius and Fahrenheit were flipped. The index was computed in Fahrenheit but used as if it were Celsius.
2018-04-17 19:43:09 -05:00
Nico Domino
a739fbdf1d Update CHANGELOG.md 2018-04-17 00:39:05 +02:00
Nico Domino
c90a1ab6dc Updated newsfeed.js - improved fullscreen iframe
Had much better performance using 100vw (viewport width) than 100% (why - idk), but with 100% about 5% of my screen (1080x1920) to the right of the scroll bar was left black/blank, with 100vw I legitimately takes up the whole screen/viewport width. 

With the 10000 height the articles would always load about half way scrolled down. So I reduced the height and style.height to 3000. At my resolution at least, which I assume is fairly common, I had much better results. Unfortunately 3000 also isn't perfect - this still requires some tweaking. The article loads perfectly at the top of the iframe at 2500, but 2500 is much too small for most articles. 3000 seemed a good compromise, I could scroll far enoguh to read most articles on Reuters, and also load far enoguh up to read the beginning of the article.

And finally I added a "scroll back up" button notification. This seems to work flawlessly.
2018-04-17 00:25:58 +02:00
Michael Teeuw
05ef68e079 Merge pull request #1258 from jannekalliola/develop
Support for hiding on-going events
2018-04-08 18:14:39 +02:00
Michael Teeuw
dee4a7f3c7 Merge pull request #1257 from parnic/develop
Fixed to work in Midori browser
2018-04-08 18:11:00 +02:00
Janne Kalliola
75753df0d8 Added description of on-going event hiding changes to the changelog 2018-04-08 15:07:20 +03:00
Janne Kalliola
30c5d78647 Support for hiding on-going events 2018-04-08 14:57:28 +03:00
Chris Pickett
6bb4db3842 Midori 0.4.3 support 2018-04-07 20:01:53 -05:00
Chris Pickett
cc0907fcd7 Updated changelog 2018-04-07 20:00:51 -05:00
Michael Teeuw
20b018bcc7 Merge pull request #1250 from bastilimbach/master
Remove yarn-or-npm as it breaks production builds
2018-04-06 14:40:26 +02:00
Michael Teeuw
aafe2fa8d0 Update CHANGELOG.md 2018-04-06 14:39:41 +02:00
Michael Teeuw
ee7bd73b3f Merge branch 'develop' into master 2018-04-06 14:39:04 +02:00
Michael Teeuw
b67f3bd629 Move change to 2.4.0 changelog. 2018-04-06 14:37:26 +02:00
Michael Teeuw
38b81e79f8 Merge pull request #1255 from wonjerry/develop
Error in default/currentWeather
2018-04-06 14:35:19 +02:00
wonjerry
b73c549131 Error in MagicMirror/modules/default/currentWeather/currentWeather.js line 296, 300
Notice that self.config.animationSpeed can not be found because the notificationReceived function does not have "self" variable.
2018-04-06 21:25:10 +09:00
Michael Teeuw
d21d9f0141 Use Electron 2 Beta. 2018-04-06 13:21:53 +02:00
Michael Teeuw
7bb85032a1 Merge pull request #1252 from BerndKohl/feelslike-localisation
enabling translation for "feelsLike" in current weather
2018-04-06 13:11:51 +02:00
Michael Teeuw
d90446ad28 Update CHANGELOG.md 2018-04-06 13:10:41 +02:00
Michael Teeuw
8b5e2f5528 Add dutch 'Feels' temperature. 2018-04-06 13:04:55 +02:00
Michael Teeuw
1bcc3ab7f1 Add default translation. 2018-04-06 13:04:04 +02:00
Michael Teeuw
3359c3cd45 Update currentweather.js 2018-04-06 13:03:06 +02:00
Michael Teeuw
af812f3c90 Fix translation file. 2018-04-06 13:02:27 +02:00
Michael Teeuw
1e6201093b restore windChillInF variable. 2018-04-06 13:01:23 +02:00
BerndKohl
959ea69427 enabling translation for "feelsLike" in current weather
enabled translation
fixed typos in comments
added German translation
2018-04-06 12:28:47 +02:00
Sebastian Limbach
cea744a914 Remove yarn-or-npm 2018-04-03 19:38:00 +02:00
Michael Teeuw
e8baf48764 Update CHANGELOG.md 2018-04-02 14:18:28 +02:00
Michael Teeuw
41242a2ae1 Merge pull request #1248 from E3V3A/patch-5
null check for notification removal
2018-04-02 14:17:25 +02:00
Michael Teeuw
f1dee488a7 Fix indent. 2018-04-02 14:11:21 +02:00
Michael Teeuw
3b4ff1818e Update CHANGELOG.md 2018-04-02 14:03:16 +02:00
E:V:A
497145b1b5 null check for notification removal
Make sure there is something to remove, before we attempt to remove the notifications. 
- This fixes #1240
2018-04-02 14:07:25 +03:00
Michael Teeuw
10eb41d319 FIx wind chill in Fahrenheit. 2018-04-02 12:58:19 +02:00
Michael Teeuw
4daf2e4a3d Merge pull request #1246 from secuflag/develop
Update italian translation
2018-04-01 19:44:24 +02:00
secuflag
f3266a5111 Update italian translation 2018-04-01 19:38:03 +02:00
Michael Teeuw
27cac4e8b8 Merge v2.3.1 2018-04-01 19:17:08 +02:00
Michael Teeuw
60b9a5b9da Merge pull request #1245 from MichMich/electron-downgrade
v2.3.1
2018-04-01 19:12:25 +02:00
Michael Teeuw
eaaa62a7f3 Downgrade Electron. 2018-04-01 19:05:38 +02:00
Michael Teeuw
6ce732ec3d Preparation for v2.4.0. 2018-04-01 14:23:28 +02:00
Michael Teeuw
fb0cc61e09 Merge pull request #1241 from MichMich/develop
Release 2.3.0
2018-04-01 14:16:36 +02:00
Michael Teeuw
be29b5daf8 Prepare for release 2.3.0 2018-04-01 14:08:38 +02:00
Michael Teeuw
f010adabd0 Merge pull request #1232 from Kiina/develop
Update electron to 1.7.13
2018-03-28 12:27:05 +02:00
Dominic Dey-Marckmann
c93b263b1f Update electron to 1.7.13 2018-03-27 21:09:08 +02:00
Michael Teeuw
15f34d6b54 Update CHANGELOG.md 2018-03-25 14:56:20 +02:00
Michael Teeuw
d0eeb55999 Merge pull request #1186 from BonySimon/patch-1
Fix exception on translation of objects
2018-03-25 14:55:52 +02:00
Michael Teeuw
08c0d39b23 Update translator.js 2018-03-25 14:55:42 +02:00
Michael Teeuw
d0ecde3277 Merge pull request #1176 from relm923/forecast_max_days
Forecast - Max Days
2018-03-25 14:53:30 +02:00
Michael Teeuw
45ec57afd7 Merge branch 'develop' into forecast_max_days 2018-03-25 14:52:42 +02:00
Michael Teeuw
b0cd053083 Merge pull request #1202 from iampranavsethi/currentweather-module-updates
Currentweather module updates
2018-03-25 14:52:05 +02:00
Michael Teeuw
698a11be58 Merge branch 'develop' into currentweather-module-updates 2018-03-25 14:51:47 +02:00
Michael Teeuw
efb08fb1e1 Update CHANGELOG.md 2018-03-25 14:51:18 +02:00
Michael Teeuw
f89bc8422e Merge pull request #1224 from bacongobbler/clearer-install-question
capitalize "y/n" for clearer intent
2018-03-25 14:47:35 +02:00
Michael Teeuw
4bf4889a08 Merge pull request #1225 from moham96/patch-1
use shallow clone
2018-03-25 14:47:09 +02:00
Michael Teeuw
01ab8ba38e Merge pull request #1227 from E3V3A/patch-4
removed known issues as they are closed
2018-03-25 14:46:44 +02:00
E:V:A
ae6d15e812 removed known issues as they are closed 2018-03-25 12:07:29 +03:00
MOHAMMAD RASIM
1abfbe1d34 use shallow clone
not need to download the whole repo history
2018-03-24 16:11:57 +03:00
Matthew Fisher
d3095297c2 capitalize "y/n" for clearer intent
If you press enter, `choice` is an empty string and will default to "no". The convention is to capitalize the default answer so users know what happens when they auto-accept prompts.
2018-03-23 16:29:37 -07:00
Michael Teeuw
79d40d5644 Merge pull request #1199 from ThomasMirlacher/develop
Add dc:date to parsing in newsfeed module, which allows parsing of mo…
2018-03-21 12:17:23 +01:00
Thomas Mirlacher
008e305a84 use doublequotes. 2018-03-21 11:58:09 +01:00
Michael Teeuw
0379611edd Merge pull request #1218 from kjb085/kb/calendar-adv-filter
Add advanced filtering to excludedEvents
2018-03-21 11:34:41 +01:00
Kenn Breece
96d883c1c7 Merge branch 'develop' into kb/calendar-adv-filter 2018-03-20 15:38:16 -04:00
Kenn Breece
be0f262e37 Add advanced filtering to excludedEvents 2018-03-18 23:54:23 -04:00
Michael Teeuw
1676adf071 Merge pull request #1209 from E3V3A/patch-3
update node stable to 9.x
2018-03-14 10:26:25 +01:00
E:V:A
a5d5630067 update node stable to 9.x 2018-03-14 11:24:41 +02:00
Pranav Sethi
275956caba Fixed typos in README.md for currentweather. 2018-03-12 22:22:45 -04:00
Michael Teeuw
bf8ed87fc9 Merge pull request #1197 from ptz0n/patch-1
Update README.md
2018-03-12 18:34:21 +01:00
Michael Teeuw
fb3afac097 Merge pull request #1200 from djgalloway/wip-apt-y
Assume yes when installing deps on Raspberry Pi via apt-get
2018-03-12 18:33:36 +01:00
Michael Teeuw
eda8b037a9 Merge pull request #1205 from Tajnymag/develop
Added yarn support
2018-03-12 18:32:31 +01:00
Marek Lukáš
8ef14f7a54 Added changes from [9974e35] to CHANGELOG 2018-03-12 15:33:30 +01:00
Marek Lukáš
9974e35656 Added yarn support 2018-03-12 15:28:43 +01:00
Pranav Sethi
15bc5431b6 Fixed Trailling Spaces 2018-03-12 05:25:33 -04:00
Pranav Sethi
f767531d89 Fixed Trailling Spaces 2018-03-12 05:25:01 -04:00
Pranav Sethi
7285ada9dd Added feels like and kmph wind for currentweather module 2018-03-12 05:06:16 -04:00
Pranav Sethi
b9b9773df9 Merge branch 'develop' into currentweather-module-updates 2018-03-12 03:56:44 -04:00
David Galloway
c29a83b259 Assume yes when installing deps on Raspberry Pi via apt-get
Signed-off-by: David Galloway <dgallowa@redhat.com>
2018-03-11 13:17:33 -04:00
Thomas Mirlacher
fa45e66da6 Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds. 2018-03-10 00:30:45 +01:00
Erik Eng
7cbcdddac9 Merge branch 'develop' into patch-1 2018-03-07 09:18:07 +01:00
Erik Eng
a2f17900da Update README.md
Correct manual installation step.
2018-03-07 09:13:33 +01:00
Michael Teeuw
fb7e97b8ad Merge pull request #1190 from E3V3A/patch-1
made the module "this" instances into a table
2018-03-06 09:36:04 +01:00
Michael Teeuw
0c92a8a8e9 Merge pull request #1191 from E3V3A/patch-2
fix md header
2018-03-06 09:35:34 +01:00
Michael Teeuw
dcc59380e5 Merge pull request #1194 from bastilimbach/develop
Remove old docker config and link to docker repository
2018-03-06 09:33:50 +01:00
Sebastian Limbach
f9bf25f96d Add changes 2018-03-04 14:07:44 +01:00
Sebastian Limbach
cbcbea8b08 Remove old docker config 2018-03-04 13:59:45 +01:00
Pranav Sethi
62eb4f20da Fixed Trailling Spaces to Pass checks 2018-03-03 05:08:12 -05:00
Pranav Sethi
43d5311e5e Fixed Trailling Spaces 2018-03-03 05:00:26 -05:00
Pranav Sethi
a6f08a09d5 Added Feels Like and Windspeed in KMPH 2018-03-03 04:52:22 -05:00
Pranav Sethi
d93f5d7785 Added Feels Like and Windspeed in KMPH 2018-03-03 04:21:24 -05:00
E:V:A
c7170e6dc2 fix md header 2018-03-01 11:53:10 +02:00
E:V:A
bcf3ca7339 made the module "this" instances into a table 2018-03-01 09:44:57 +02:00
Steelskin3
0388f5787a Fix exception on translation of objects
Sometimes, the content of translations[module.name][key] is not a string but an entire object. It cause a crash on template.replace(...) of createStringFromTemplate(...). It is currently the case of MMM-Voice-Control and the previous commit have broked this module (10 month ago... I am surprised to be the first founded it)
2018-02-23 11:51:59 +01:00
Michael Teeuw
6514df9d96 Merge pull request #1184 from patoberli/patch-1
Update README.md
2018-02-20 11:29:27 +01:00
Michael Teeuw
8f5b9869dc Merge branch 'develop' into patch-1 2018-02-20 11:28:58 +01:00
patoberli
580c5fe23f Update README.md
Added a hint to use the full and not lite version of Raspbian, as the GUI is missing and thus ndm (electron) can't start after the installation.
2018-02-17 21:54:31 +01:00
Michael Teeuw
b0af5b26ba Merge pull request #1183 from fewieden/feature/translations-update
translations update
2018-02-17 10:30:30 +01:00
Michael Teeuw
9e898932f6 Merge pull request #1182 from fewieden/feature/automated-tests
Automated tests
2018-02-17 10:29:23 +01:00
fewieden
1f873b93f6 changelog, linting 2018-02-17 10:17:59 +01:00
fewieden
f414707f11 update translations for updatenotifications 2018-02-17 10:14:37 +01:00
fewieden
505825056c use translation template for updatenotifications 2018-02-17 10:10:57 +01:00
fewieden
38f7716738 linting 2018-02-17 09:20:34 +01:00
fewieden
a69d08b554 changelog 2018-02-17 09:18:12 +01:00
fewieden
3ccdb64833 deprecated unit tests 2018-02-16 22:09:15 +01:00
fewieden
78d8bff599 clone array, clone nested object, add safe checks for objects (memory address) 2018-02-16 19:58:28 +01:00
fewieden
a756fed70b fixed and reenabled lockstring test 2018-02-16 08:43:27 +01:00
fewieden
3e2a1e3548 linting 2018-02-16 00:08:11 +01:00
fewieden
96b2f2b3a4 clone object unit test 2018-02-16 00:01:02 +01:00
fewieden
d81d7d4f68 compare version unit test 2018-02-15 23:53:57 +01:00
fewieden
20244c4fb5 translations integration test 2018-02-15 23:22:52 +01:00
Michael Teeuw
b50d31ffe2 Merge pull request #1178 from E3V3A/develop
Added ToC
2018-02-13 09:24:36 +01:00
fewieden
d709a44960 strip comments unit test 2018-02-13 07:17:46 +01:00
E:V:A
4e4d07ced6 Added ToC
ToDo: Fix ToC links to headers
2018-02-11 18:24:31 +02:00
fewieden
d775bc9d7e loadCoreTranslationsFallback unit test 2018-02-11 09:08:09 +01:00
fewieden
85528761eb only load core callback if there is one available 2018-02-11 09:07:41 +01:00
fewieden
ad3eac9ddb loadcoretransations unit test 2018-02-11 08:58:02 +01:00
Reagan Elm
613f9fccd2 Update changelog 2018-02-10 20:56:10 -05:00
Reagan Elm
3d1741c904 Respect maxNumberOfDays regardless of endpoint 2018-02-10 20:53:38 -05:00
fewieden
26be14ba67 load unit test 2018-02-10 20:33:22 +01:00
fewieden
daa0755920 add missing parameter in documentation 2018-02-10 12:38:55 +01:00
fewieden
305d60e09b translator unit tests 2018-02-10 12:32:43 +01:00
fewieden
d0029efd02 utils unit tests 2018-02-10 12:30:33 +01:00
fewieden
fb4d42bf5b moved test 2018-02-10 12:28:30 +01:00
Michael Teeuw
20eec53b14 Add Manifesto. 2018-02-07 13:04:10 +01:00
Michael Teeuw
8343db44db Merge pull request #1173 from vvzvlad/vvzvlad_local
add variable morning afternoon times
2018-02-07 12:32:42 +01:00
vvzvlad
649652e373 Merge branch 'develop' into vvzvlad_local 2018-02-07 14:10:43 +03:00
vvzvlad
e37ed7c32d add variable morning afternoon times 2018-02-07 14:06:26 +03:00
vvzvlad
f9a525068b add variable morning afternoon times 2018-02-05 18:15:02 +03:00
Michael Teeuw
aa11e6d62e Merge pull request #1166 from henrysun18/develop
Show remote compliments on boot
2018-02-01 08:20:22 +01:00
henrysun18
6802d152da Show remote compliments at boot instead of after one updateInterval 2018-01-31 23:19:47 -05:00
Michael Teeuw
3b40f393d8 Merge pull request #1163 from pinsdorf/patch-1
corrected link to 3rd party modules wiki page
2018-01-30 13:49:37 +01:00
pinsdorf
020443ae8a corrected link to 3rd party modules wiki page
The link to the 3rd Party Modules still directs to the old wiki page, where use has to follow another link to the right page. Updated the link in README.md to navigate to the right wiki page instantly.
2018-01-30 10:59:18 +01:00
Michael Teeuw
1d0baccffc Merge pull request #1161 from thobach/master
Allow to scroll in full page article view of default newsfeed module
2018-01-30 09:47:33 +01:00
Thomas Bachmann
5426f0f329 Updated documentation for scroll mode in newsfeed module 2018-01-29 21:41:43 +01:00
Thomas Bachmann
790249dd1a Merge branch 'MichMich/develop' 2018-01-29 21:37:25 +01:00
Thomas Bachmann
446a201d25 Allow to scroll articles with gesture events 2018-01-29 21:26:34 +01:00
Thomas Bachmann
b6538d5e18 Merge remote-tracking branch 'MichMich/master' 2018-01-29 18:54:32 +01:00
Michael Teeuw
edd6043059 Fail PRs that are sent to the master branch. 2018-01-26 12:12:44 +01:00
Michael Teeuw
fe4ffeb7f1 Text cleanup. 2018-01-25 20:45:25 +01:00
Michael Teeuw
27b3875bfb Changed missing Changlog text. 2018-01-25 20:38:42 +01:00
Michael Teeuw
e2dbe8a0a2 Minor fixes. 2018-01-25 20:07:51 +01:00
Michael Teeuw
29fc7910b7 Remove Jest. Update dangerfile.js. 2018-01-25 19:51:23 +01:00
Michael Teeuw
22e2fdc707 Jest implementation to get danger.js to work. 2018-01-25 19:43:09 +01:00
Michael Teeuw
584786eb9f Temp disable danger. 2018-01-25 17:00:51 +01:00
Michael Teeuw
d803d9eaf9 Import 'includes' from lodash. 2018-01-25 16:39:49 +01:00
Michael Teeuw
bad6575d83 Add Danger.js 2018-01-25 16:24:05 +01:00
Michael Teeuw
fbcb7ae836 Update README.md 2018-01-23 19:10:37 +01:00
Michael Teeuw
2c1a1b10c8 Merge pull request #1147 from E3V3A/patch-2
Update README.md
2018-01-23 09:51:15 +01:00
E:V:A
155fb16a8a Update README.md
Updated README by reformatting and restructuring.
- Added, Clarified and corrected grammatics and wrong info 
- Moved/Removed some redundant parts
2018-01-23 10:44:55 +02:00
Michael Teeuw
b1ab2ce96a Merge pull request #1146 from cederstrom/put-article-in-front-of-modules
Put article iframe in front of modules
2018-01-21 11:33:22 +01:00
Andreas Cederström
f299ba6218 Put article ifram in front of modules
Before this change the article was brought up in its ifram in fullscreen and you could still see the other modules in front of it
2018-01-20 23:46:58 +01:00
Michael Teeuw
d167ad1923 Merge pull request #1136 from E3V3A/patch-2
Update README
2018-01-19 09:15:54 +01:00
E:V:A
93626e8154 Fixed bullet points markup for lint 2018-01-19 09:13:59 +02:00
Michael Teeuw
d5040c091a Merge pull request #1143 from shbatm/bug-fix
Fix for #1140 - sendNotification module errors after #1116
2018-01-18 13:35:47 +01:00
shbatm
868daef0f0 Fix for #1140 - sendNotification module errors after #1116 2018-01-17 09:49:17 -06:00
Michael Teeuw
dc8e85e7f2 Merge pull request #1139 from amcolash/patch-1
Change English translation to "In 2 days"
2018-01-16 09:20:57 +01:00
Michael Teeuw
22d32d7ca3 Merge pull request #1141 from ConnorChristie/old-dom-event-fix
Fix to emit DOM_OBJECTS_CREATED event after module DOMs have actually loaded
2018-01-16 09:20:39 +01:00
Connor Christie
2d500f8074 Fix to emit DOM_OBJECTS_CREATED event after module DOMs have actually loaded 2018-01-14 22:03:09 -06:00
Andrew McOlash
452cdc17c6 Change English translation to "In 2 days" 2018-01-14 15:52:15 -06:00
E:V:A
bcbfee0321 Update README
Updated README to warn about long installation time as discussed in #1124.
2018-01-12 10:00:35 +02:00
Michael Teeuw
dab2e7ede3 Merge pull request #1127 from roramirez/set-version-test-7-node
Set only 7 version of node to run tests in Travis CI
2018-01-08 11:26:25 +01:00
Michael Teeuw
d91acb8352 Merge pull request #1126 from d-Rickyy-b/patch-1
Fix typo in newsfeed documentation
2018-01-08 11:23:49 +01:00
Rodrigo Ramírez Norambuena
373dd8058e Set only 7 version of node to run tests in Travis CI 2018-01-07 01:03:00 -03:00
Rico
de6310e52a Fix typo
A little typo which lead to poor formatting
2018-01-07 03:30:01 +01:00
Michael Teeuw
8c297a4a4c Merge pull request #1123 from E3V3A/patch-1
Added general advice
2018-01-06 19:49:44 +01:00
E:V:A
4eb5c817bc fixed typos 2018-01-06 20:19:43 +02:00
E:V:A
07e4b26b9e Added general advice 2018-01-06 19:57:46 +02:00
Michael Teeuw
b63aa62985 Merge pull request #1121 from henrikra/master
Add basic typescript types for module
2018-01-06 14:29:52 +01:00
Henrik Raitasola
ca701c0580 Merge branch 'master' of github.com:henrikra/MagicMirror 2018-01-06 15:26:51 +02:00
Henrik Raitasola
e37043a6a8 Solve conflict 2018-01-06 15:26:38 +02:00
Michael Teeuw
7c26975d14 Merge branch 'develop' into master 2018-01-06 14:16:21 +01:00
Henrik Raitasola
47f8a43637 Add changelog 2018-01-06 15:10:10 +02:00
Henrik Raitasola
1238c0cefe Rename file to be more explicit 2018-01-06 15:06:58 +02:00
Henrik Raitasola
4c35fda045 Add most used module properties 2018-01-06 15:04:54 +02:00
Henrik Raitasola
780124c2f5 Add basic types to get started 2018-01-06 14:51:32 +02:00
Michael Teeuw
38e0af41ce Merge pull request #1116 from ConnorChristie/async-dom
DOM creation notifications in cases of async template rendering
2018-01-04 21:51:13 +01:00
Connor Christie
601e99eec0 Merge branch 'develop' into async-dom 2018-01-02 18:10:07 -06:00
Connor Christie
745a2adee7 Merge branch 'develop' into async-dom 2018-01-02 18:08:55 -06:00
Connor Christie
9e83234df1 Update changelog 2018-01-02 18:06:21 -06:00
Connor Christie
e45eeadf7d Merge remote-tracking branch 'origin/master' into async-dom 2018-01-02 18:04:03 -06:00
Michael Teeuw
9a778cea6b Merge v2.2.2 changes. 2018-01-02 18:46:41 +01:00
Michael Teeuw
20823bfc87 Add missing package-lock.json. 2018-01-02 18:41:33 +01:00
Connor Christie
be3d703692 Fix linter errors 2018-01-01 10:55:39 -06:00
Connor Christie
e2df5739f0 Update module docs 2018-01-01 10:40:52 -06:00
Connor Christie
7bb11d6436 Add documentation regarding updates 2018-01-01 10:38:00 -06:00
Connor Christie
80b84212cc Add notification for module dom creation with async support 2018-01-01 10:23:15 -06:00
Connor Christie
4a1bee769b Add true module dom creation events 2018-01-01 09:42:34 -06:00
Michael Teeuw
de99a7aeaf Merge 2.2.1 changes. 2018-01-01 14:23:38 +01:00
Michael Teeuw
a0a02701b0 Update version number. 2018-01-01 13:42:39 +01:00
Michael Teeuw
1314ae1555 Add info about v2.2.1 2018-01-01 13:40:39 +01:00
Michael Teeuw
67cf0e745c Fix linting errors. 2018-01-01 13:39:05 +01:00
Michael Teeuw
538a2acbf5 Fix linting errors. 2018-01-01 13:38:07 +01:00
Michael Teeuw
8d0e453666 Preparation for v2.3.0 release. 2018-01-01 12:59:28 +01:00
Michael Teeuw
ace04f0b30 Merge pull request #1115 from MichMich/develop
Cleanup.
2018-01-01 12:48:22 +01:00
Michael Teeuw
f8e25d6c4a Cleanup. 2018-01-01 12:47:54 +01:00
Michael Teeuw
b2bc43da4f Merge pull request #1113 from MichMich/develop
Release v2.2.0.
2018-01-01 12:44:24 +01:00
Michael Teeuw
cb12e540d2 Add package lock. 2018-01-01 12:40:37 +01:00
Michael Teeuw
39955af2fa Upgrade packages. 2018-01-01 12:36:53 +01:00
Michael Teeuw
86e1f0615d Prepare for release v2.2.0 2018-01-01 12:33:00 +01:00
Michael Teeuw
dc81ab6dee Merge pull request #1111 from TTigges/weather_decimal_marks
added option of decimal comma for temp values for default weather modules
2017-12-31 12:25:24 +01:00
Michael Teeuw
f8cf6a65ae Merge branch 'develop' into weather_decimal_marks 2017-12-31 12:25:06 +01:00
Michael Teeuw
ba909c696d Merge branch 'develop' into weather_decimal_marks 2017-12-31 12:24:25 +01:00
Torben Tigges
df0515cebf currentweather, weatherforecast, changed option of decimal comma to any decimal symbol 2017-12-31 01:15:59 +01:00
Torben Tigges
46c0e14d67 currentweather, weatherforecast, added option of decimal comma for temperature values to config 2017-12-30 22:03:26 +01:00
Michael Teeuw
97d7733464 Remove trailing spaces. 2017-12-30 21:46:34 +01:00
Michael Teeuw
afda84ef09 Electron reverted. 2017-12-30 21:39:56 +01:00
Michael Teeuw
61d6e74102 Use an old electron version ...
Hopefully Electron will be fixed soon.
2017-12-30 21:33:56 +01:00
Michael Teeuw
8d74258ce2 Merge pull request #1102 from EricWarnke/patch-1
Spelling/grammar fix
2017-12-13 09:22:38 +01:00
Eric Warnke
5dfba0b834 Spelling/gramar fix 2017-12-12 15:54:33 -07:00
Michael Teeuw
056370ec08 Merge pull request #1092 from sebcaps/develop
Fix #1091 : handle empty description
2017-11-26 20:54:13 +01:00
unknown
4e2c254558 Fix #1091 : handle empty description 2017-11-26 11:52:01 +01:00
Michael Teeuw
38c2bdb447 Merge pull request #1090 from rejas/patch-1
Fix typo
2017-11-26 10:42:20 +01:00
Veeck
0cee4717e2 Fix typo 2017-11-26 10:02:31 +01:00
Michael Teeuw
221b04c466 Merge pull request #1089 from MichMich/patch-1
Update CHANGELOG.md
2017-11-25 12:15:54 +01:00
Michael Teeuw
237e9b7191 Merge pull request #1088 from reeno/patch-2
fixed width Font Awesome symbols
2017-11-25 12:15:27 +01:00
Michael Teeuw
9457e44a88 Update CHANGELOG.md 2017-11-25 12:14:54 +01:00
reeno
80a9d40a44 fixed width Font Awesome symbols
See http://fontawesome.io/examples/#fixed-width
2017-11-25 12:08:40 +01:00
Michael Teeuw
0715325a63 Merge pull request #1081 from ChytilTomas/develop
Develop
2017-11-17 09:06:06 +01:00
Michael Teeuw
0a026fef0f Update CHANGELOG.md 2017-11-17 09:05:33 +01:00
Tomáš Chytil
5fbf650d2d Czech translation 2017-11-16 23:06:52 +01:00
Tomáš Chytil
dabdde0c3f Czech translation 2017-11-16 23:01:00 +01:00
Michael Teeuw
b6ca92a7ef Merge pull request #1080 from slametps/develop
some new features (resubmission)
2017-11-15 08:26:25 +01:00
slametps
62f7339170 add some new options
add some new options
2017-11-15 12:21:45 +07:00
slametps
eaec682ea7 per feed reloadInterval support
per feed reloadInterval support
2017-11-15 12:21:22 +07:00
slametps
12110a4442 new options
* truncated description support
* specific reloadInterval for particular feed
2017-11-15 12:21:02 +07:00
slametps
df597f53b2 add no-cache entries in HTTP header
add no-cache entries in HTTP header
2017-11-15 12:19:53 +07:00
slametps
d7d40254d4 updated
updated
2017-11-15 12:19:19 +07:00
Michael Teeuw
e7c4a2cce6 Merge pull request #1072 from morozgrafix/newsfeed_filter
Added ability to set a list of prohibited words that will be filtered…
2017-11-01 11:53:00 +01:00
Sergey Morozov
0eb1c0cea6 Added ability to set a list of prohibited words that will be filtered out of newsfeed
Resolves #1071

`prohibitedWords` config parameter is an array of words. If set and case insensitive greedy match is found anywhere in the title then that newsfeed item will not be displayed. Readme updated with instructions.

Users should be careful on the words selection as careless setting may remove many or all items from the newsfeed. Some obvious mistakes like `space, comma, dot` etc. can be prevented programatically, but I left it out of this PR

Example:

with `prohibitedWords: ['dodgers']`

Original `newsItems`:
```
0:{title: "New York City, Russia, Los Angeles Dodgers: Your Wednesday Briefing", description: "Here’s what you need to know to start your day.", pubdate: "Wed, 01 Nov 2017 09:37:36 GMT", url: "https://www.nytimes.com/2017/11/01/briefing/new-yo…ssia-los-angeles-dodgers.html?partner=rss&emc=rss", sourceTitle: "New York Times"}
1:{title: "A Mangled School Bus, Bodies Everywhere; ‘It Was Surreal’", description: "A truck ramming bicyclists. The driver emerging wi…e attack, it was as confusing as it was gruesome.", pubdate: "Wed, 01 Nov 2017 09:27:41 GMT", url: "https://www.nytimes.com/2017/10/31/nyregion/nyc-sc…r-attack-truck-witnesses.html?partner=rss&emc=rss", sourceTitle: "New York Times"}
2:{title: "Dodgers 3, Astros 1 | Series tied, 3-3: With a Rally and a Romp, Dodgers Top Astros and Force Game 7", description: "Down by a run with just four innings left in Game …nst a dominating Justin Verlander. Game 7 awaits.", pubdate: "Wed, 01 Nov 2017 07:21:07 GMT", url: "https://www.nytimes.com/2017/11/01/sports/dodgers-win-game-6.html?partner=rss&emc=rss", sourceTitle: "New York Times"}
3:{title: "José Andrés Fed Puerto Rico, and May Change How Aid Is Given", description: "The chef’s huge effort is just the latest led by c…ocally based way to feed people after a disaster.", pubdate: "Wed, 01 Nov 2017 06:40:09 GMT", url: "https://www.nytimes.com/2017/10/30/dining/jose-andres-puerto-rico.html?partner=rss&emc=rss", sourceTitle: "New York Times"}
```

Filtered `newsItems`:
```
0:{title: "A Mangled School Bus, Bodies Everywhere; ‘It Was Surreal’", description: "A truck ramming bicyclists. The driver emerging wi…e attack, it was as confusing as it was gruesome.", pubdate: "Wed, 01 Nov 2017 09:27:41 GMT", url: "https://www.nytimes.com/2017/10/31/nyregion/nyc-sc…r-attack-truck-witnesses.html?partner=rss&emc=rss", sourceTitle: "New York Times"}
1:{title: "José Andrés Fed Puerto Rico, and May Change How Aid Is Given", description: "The chef’s huge effort is just the latest led by c…ocally based way to feed people after a disaster.", pubdate: "Wed, 01 Nov 2017 06:40:09 GMT", url: "https://www.nytimes.com/2017/10/30/dining/jose
```
2017-11-01 03:50:34 -07:00
Michael Teeuw
07d35a8513 Remove todo item. 2017-10-19 16:52:57 +02:00
Michael Teeuw
16c887814e Show humidity. 2017-10-19 16:51:51 +02:00
Michael Teeuw
22a50b72fd Show unit. 2017-10-19 16:43:12 +02:00
Michael Teeuw
a79e1b6ca1 Rename templates to .njk files to allow syntax highlighting. 2017-10-18 13:52:11 +02:00
Michael Teeuw
995296ef53 Merge branch 'develop' into weather-refactor 2017-10-18 13:49:53 +02:00
Michael Teeuw
f4d5996a88 Allow use of .njk extension for template files. 2017-10-18 13:49:03 +02:00
Michael Teeuw
ab732b5435 Make all visiable values dynamic. 2017-10-18 13:38:56 +02:00
Michael Teeuw
241ff5cb6e Set temperature rounding. 2017-10-18 12:19:02 +02:00
Michael Teeuw
ec2169e079 Merge branch 'develop' into weather-refactor 2017-10-18 12:01:44 +02:00
Michael Teeuw
c75662e720 Remove trailing spaces. 2017-10-18 12:01:06 +02:00
Michael Teeuw
d567fd4842 Merge branch 'develop' into weather-refactor 2017-10-18 11:59:29 +02:00
Michael Teeuw
0776dfc80e Minor changes. 2017-10-18 11:58:45 +02:00
Michael Teeuw
79a662fb93 Log nunjucks parsing errors for debugging. 2017-10-18 11:55:02 +02:00
Michael Teeuw
8b009b7ee9 Add Catalan translation. 2017-10-18 10:15:47 +02:00
Michael Teeuw
c4face30cc Update CHANGELOG.md 2017-10-18 10:14:54 +02:00
sergibarca
d4dbb5cb51 Translation to Catalan. 2017-10-17 22:24:13 +02:00
Michael Teeuw
1e27187652 Merge pull request #1060 from nhubbard/revert-1053-remove-package-lock.json
Revert "Delete large package-lock.json files"
2017-10-16 14:46:39 +02:00
Michael Teeuw
3ff278291f Merge pull request #1061 from qistoph/difflink
Add Github diff link to update info
2017-10-16 14:43:55 +02:00
Chris van Marle
4e8bf216df Update CHANGELOG 2017-10-16 14:26:15 +02:00
Chris van Marle
e7b9100f1c Add Github link to update info 2017-10-16 14:24:29 +02:00
Michael Teeuw
08aa9790f3 Fix lint issues. 2017-10-16 14:17:12 +02:00
Michael Teeuw
5f13fd2dca Merge pull request #1058 from shbatm/develop_1056
Gracefully Shutdown Module node_helpers on exit/SIGINT (Fixes #1056)
2017-10-16 13:59:40 +02:00
shbatm
f4c6f42c38 Added default implementation of stop() function. 2017-10-15 18:44:11 -05:00
Nicholas Hubbard
6a10d08189 Revert "Delete large package-lock.json files" 2017-10-13 19:59:39 -04:00
shbatm
f646360af6 Gracefully shutdown node_helpers (Fixes #1056)
Updated documentation


Corrected Typo in Documentation


Style correction
2017-10-13 16:43:11 -05:00
Michael Teeuw
95f265ebbf Merge pull request #1057 from Stromwerk/develop
Add Bulgarian translations for MagicMirror² and Alert module
2017-10-13 21:05:16 +02:00
Kalin Koychev
39d0142993 - Add Bulgarian translations for MagicMirror² and Alert module 2017-10-13 21:51:21 +03:00
Michael Teeuw
da16172244 Merge pull request #1055 from qistoph/commentscheck
Check trailing white space in comments too
2017-10-13 20:39:44 +02:00
Michael Teeuw
71aded0fae Merge pull request #1054 from qistoph/gruntfix
Fix some white space grunt errors
2017-10-13 20:39:04 +02:00
Michael Teeuw
e173b3ea41 Merge pull request #1053 from roramirez/remove-package-lock.json
Delete large package-lock.json files
2017-10-13 20:38:18 +02:00
Chris van Marle
6c4f9466b9 Don't ignore comments in trailing whitespace check 2017-10-12 11:06:57 +02:00
Chris van Marle
664196c5ef Fix some trailing whitespace grunt errors 2017-10-12 10:23:40 +02:00
Michael Teeuw
c8d5044e7a Merge pull request #1051 from egin10/patch-1
correcting translation for indonesian language
2017-10-10 17:20:28 +02:00
Michael Teeuw
d8c31fe560 Merge pull request #1049 from egin10/patch-2
Updating and Correcting Translation
2017-10-10 17:19:50 +02:00
Rodrigo Ramírez Norambuena
516db855f5 Delete large package-lock.json files 2017-10-08 23:41:51 -03:00
Ginanjar S.B
9cdcf08ab1 correcting translation for indonesian language 2017-10-09 08:05:11 +07:00
Ginanjar
326fa73b22 Updating and Correcting Translation 2017-10-08 03:03:29 +07:00
Michael Teeuw
79cacefd07 Merge pull request #1047 from jannickfahlbusch/useHTTPSOpenWeatherMap
Weather (Current/Forecast): Use HTTPS instead of HTTP
2017-10-06 21:32:49 +02:00
Jannick Fahlbusch
07e28bfee6 Use HTTPS instead of HTTP
Chrome blocks insecure requests (HTTP) when MagicMirror is loaded
via HTTPS. This commit changes the protocol used for OpenWeatherMap.
2017-10-06 21:31:06 +02:00
Michael Teeuw
980b017fbe Merge pull request #1046 from wbleek/master
moved weekNumber into field and adjusted language handling accordingly [issue #910]
2017-10-03 22:13:21 +02:00
Wolf-Gideon Bleek
4567fd1eb0 Merge branch 'develop' into master 2017-10-03 21:29:10 +02:00
Wolf-Gideon Bleek
8c150c23f3 moved weekNumber into field and adjusted language handling accordingly [issue #910] 2017-10-03 21:25:30 +02:00
Wolf-Gideon Bleek
b4fd570269 moved weekNumber into field and adjusted language handling accordingly [issue #910] 2017-10-03 21:18:25 +02:00
Michael Teeuw
681a845ef3 Add Darksky provider. 2017-10-03 14:38:54 +02:00
Michael Teeuw
6e051d73c1 Merge pull request #1044 from cederstrom/honor-calendar-maximum-entries
Slice the list of all calendar events to honor config.maximumEntries
2017-10-01 21:51:33 +02:00
Michael Teeuw
e381e1a313 Merge pull request #1042 from roramirez/readme-localhost-bind
Updated readme about default value for `address` configuration
2017-10-01 21:49:47 +02:00
Andreas Cederström
2d03ff63cf Slice the list of all events to honor config.maximumEntries
Scenario:
* Specify more than one calendar in `config.calendars`
* Specify a maximum number of entries in `config.maximumEntries`

Result:
The module will show as many as maximumEntries for EACH calendar

Expected:
The module shall not show more than a total of maximumEntries, regardless of how many calendars I specify

Fixes #1043
2017-10-01 21:47:10 +02:00
Rodrigo Ramírez Norambuena
309de631ed Updated readme about default value for address configuration changed it
in https://github.com/MichMich/MagicMirror/commit/2f05228d
2017-10-01 15:01:34 -03:00
Michael Teeuw
ad240cf52f Fix lint errors. 2017-10-01 16:19:14 +02:00
Michael Teeuw
99e3a47dde Use templates to render weather. 2017-10-01 13:50:15 +02:00
Michael Teeuw
3fa810b7b8 Merge branch 'develop' into weather-refactor 2017-10-01 13:25:52 +02:00
Michael Teeuw
bad7316b80 Allow html tags in text string. 2017-10-01 13:22:29 +02:00
Michael Teeuw
4757c36233 Updated changelog and minor fix example 2017-10-01 13:16:07 +02:00
Michael Teeuw
9ca6180207 Merge branch 'templates' into develop 2017-10-01 13:10:25 +02:00
Michael Teeuw
30179ad977 Setup of new dev release. 2017-10-01 12:39:48 +02:00
Nicholas Hubbard
cd129fb055 Revert "Fix Indentation?"
This reverts commit 2bf18d8bda.
2017-09-30 19:44:54 -04:00
Michael Teeuw
880a977dd6 Merge pull request #1038 from roramirez/templates-fixes
This fix the follow things:
2017-09-30 13:39:00 +02:00
Rodrigo Ramírez Norambuena
795e33881c This fix the follow things:
* The default screen on missing configuration file and syntax error.
    * Tests
2017-09-29 19:10:03 -03:00
Nicholas Hubbard
837e275bfd Update fork 2017-09-29 10:11:46 -04:00
Nicholas Hubbard
7be6031e19 Merge branch 'weather-refactor' of https://github.com/MichMich/MagicMirror into weather-refactor 2017-09-29 10:11:22 -04:00
Michael Teeuw
3f9181905a Fix typo's. 2017-09-29 11:05:59 +02:00
Michael Teeuw
5efc43260e Switch to async rendering. 2017-09-28 16:43:38 +02:00
Michael Teeuw
e01794a07f First Nunchucks Implementation. 2017-09-28 16:11:25 +02:00
Michael Teeuw
ff9c6bac0a Add a small forecast example. 2017-09-22 13:26:44 +02:00
Michael Teeuw
713111254b First implementation of the currentWeatherView 2017-09-22 12:26:47 +02:00
Michael Teeuw
5b1462a3e8 Add readme. 2017-09-22 10:37:39 +02:00
Nicholas Hubbard
2bf18d8bda Fix Indentation? 2017-09-21 20:12:25 -04:00
Nicholas Hubbard
7f2e643e62 Add Dark Sky Module 2017-09-21 20:06:42 -04:00
Michael Teeuw
ef172592b8 A first setup of the new Weather Module 2017-09-21 16:38:18 +02:00
122 changed files with 13612 additions and 804 deletions

View File

@@ -5,12 +5,18 @@
"max-len": ["error", 250],
"curly": "error",
"camelcase": ["error", {"properties": "never"}],
"no-trailing-spaces": ["error"],
"no-trailing-spaces": ["error", {"ignoreComments": false }],
"no-irregular-whitespace": ["error"]
},
"env": {
"browser": true,
"node": true,
"es6": true
}
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"globalReturn": true
}
}
}

3
.gitignore vendored
View File

@@ -19,6 +19,9 @@ jspm_modules
# Visual Studio Code ignoramuses.
.vscode/
# IDE Code ignoramuses.
.idea/
# Various Windows ignoramuses.
Thumbs.db
ehthumbs.db

View File

@@ -1,10 +1,8 @@
language: node_js
node_js:
- "8"
- "7"
- "6"
- "5.1"
before_script:
- yarn danger ci
- npm install grunt-cli -g
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"

View File

@@ -1,7 +1,204 @@
# MagicMirror² Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
---
## [2.6.0] - 2019-01-01
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues updating, make sure you are running the latest version of Node.
### ✨ Experimental ✨
- New default [module weather](modules/default/weather). This module will eventually replace the current `currentweather` and `weatherforecast` modules. The new module is still pretty experimental, but it's included so you can give it a try and help us improve this module. Please give us you feedback using [this forum post](https://forum.magicmirror.builders/topic/9335/default-weather-module-refactoring).
### Added
- Possibility to add classes to the cell of symbol, title and time of the events of calendar.
- Font-awesome 5, still has 4 for backwards compatibility.
- Missing `showEnd` in calendar documentation
- Screenshot for the new feed module
- Screenshot for the compliments module
- Screenshot for the clock module
- Screenshot for the current weather
- Screenshot for the weather forecast module
- Portuguese translation for "Feels"
- Croatian translation
- Fading for dateheaders timeFormat in Calendar [#1464](https://github.com/MichMich/MagicMirror/issues/1464)
- Documentation for the existing `scale` option in the Weather Forecast module.
### Fixed
- Allow to parse recurring calendar events where the start date is before 1900
- Fixed Polish translation for Single Update Info
- Ignore entries with unparseable details in the calendar module
- Bug showing FullDayEvents one day too long in calendar fixed
- Bug in newsfeed when `removeStartTags` is used on the description [#1478](https://github.com/MichMich/MagicMirror/issues/1478)
### Updated
- The default calendar setting `showEnd` is changed to `false`.
### Changed
- The Weather Forecast module by default displays the &deg; symbol after every numeric value to be consistent with the Current Weather module.
## [2.5.0] - 2018-10-01
### Added
- Romanian translation for "Feels"
- Support multi-line compliments
- Simplified Chinese translation for "Feels"
- Polish translate for "Feels"
- French translate for "Feels"
- Translations for newsfeed module
- Support for toggling news article in fullscreen
- Hungarian translation for "Feels" and "Week"
- Spanish translation for "Feels"
- Add classes instead of inline style to the message from the module Alert
- Support for events having a duration instead of an end
- Support for showing end of events through config parameters showEnd and dateEndFormat
### Fixed
- Fixed gzip encoded calendar loading issue #1400.
- Mixup between german and spanish translation for newsfeed.
- Fixed close dates to be absolute, if no configured in the config.js - module Calendar
- Fixed the UpdateNotification module message about new commits in the repository, so they can be correctly localized in singular and plural form.
- Fix for weatherforecast rainfall rounding [#1374](https://github.com/MichMich/MagicMirror/issues/1374)
- Fix calendar parsing issue for Midori on RasperryPi Zero w, related to issue #694.
- Fix weather city ID link in sample config
- Fixed issue with clientonly not updating with IP address and port provided on command line.
### Updated
- Updated Simplified Chinese translation
- Swedish translations
- Hungarian translations for the updatenotification module
- Updated Norsk bokmål translation
- Updated Norsk nynorsk translation
- Consider multi days event as full day events
## [2.4.1] - 2018-07-04
### Fixed
- Fix weather parsing issue #1332.
## [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).
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
### Added
- Enabled translation of feelsLike for module currentweather
- Added support for on-going calendar events
- Added scroll up in fullscreen newsfeed article view
- Changed fullscreen newsfeed width from 100% to 100vw (better results)
- Added option to calendar module that colors only the symbol instead of the whole line
- Added option for new display format in the calendar module with date headers with times/events below.
- Ability to fetch compliments from a remote server
- Add regex filtering to calendar module
- Customize classes for table
- Added option to newsfeed module to only log error parsing a news article if enabled
- Add update translations for Português Brasileiro
### Changed
- Upgrade to Electron 2.0.0.
- Remove yarn-or-npm which breaks production builds.
- Invoke module suspend even if no dom content. [#1308](https://github.com/MichMich/MagicMirror/issues/1308)
### Fixed
- Fixed issue where wind chill could not be displayed in Fahrenheit. [#1247](https://github.com/MichMich/MagicMirror/issues/1247)
- Fixed issues where a module crashes when it tries to dismiss a non existing alert. [#1240](https://github.com/MichMich/MagicMirror/issues/1240)
- In default module currentWeather/currentWeather.js line 296, 300, self.config.animationSpeed can not be found because the notificationReceived function does not have "self" variable.
- Fixed browser-side code to work on the Midori browser.
- Fixed issue where heat index was reporting incorrect values in Celsius and Fahrenheit. [#1263](https://github.com/MichMich/MagicMirror/issues/1263)
- Fixed weatherforecast to use dt_txt field instead of dt to handle timezones better
- Newsfeed now remembers to show the description when `"ARTICLE_LESS_DETAILS"` is called if the user wants to always show the description. [#1282](https://github.com/MichMich/MagicMirror/issues/1282)
- `clientonly/*.js` is now linted, and one linting error is fixed
- Fix issue #1196 by changing underscore to hyphen in locale id, in align with momentjs.
- Fixed issue where heat index and wind chill were reporting incorrect values in Kelvin. [#1263](https://github.com/MichMich/MagicMirror/issues/1263)
### Updated
- Updated Italian translation
- Updated German translation
- Updated Dutch translation
## [2.3.1] - 2018-04-01
### Fixed
- Downgrade electron to 1.4.15 to solve the black screen issue.[#1243](https://github.com/MichMich/MagicMirror/issues/1243)
## [2.3.0] - 2018-04-01
### Added
- Add new settings in compliments module: setting time intervals for morning and afternoon
- Add system notification `MODULE_DOM_CREATED` for notifying each module when their Dom has been fully loaded.
- Add types for module.
- Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR.
- Allow to scroll 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
- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings)
- Automated integration tests translations
- Add advanced filtering to the excludedEvents configuration of the default calendar module
- New currentweather module config option: `showFeelsLike`: Shows how it actually feels like. (wind chill or heat index)
- New currentweather module config option: `useKMPHwind`: adds an option to see wind speed in Kmph instead of just m/s or Beaufort.
- Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds.
### Changed
- 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.
- Yarn can be used now as an installation tool
- Changed Electron dependency to v1.7.13.
### Fixed
- News article in fullscreen (iframe) is now shown in front of modules.
- Forecast respects maxNumberOfDays regardless of endpoint.
- Fix exception on translation of objects.
## [2.2.2] - 2018-01-02
### Added
- Add missing `package-lock.json`.
### Changed
- Changed Electron dependency to v1.7.10.
## [2.2.1] - 2018-01-01
### Fixed
- Fixed linting errors.
## [2.2.0] - 2018-01-01
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
### Changed
- Calender week is now handled with a variable translation in order to move number language specific.
- Reverted the Electron dependency back to 1.4.15 since newer version don't seem to work on the Raspberry Pi very well.
### Added
- Add option to use [Nunjucks](https://mozilla.github.io/nunjucks/) templates in modules. (See `helloworld` module as an example.)
- Add Bulgarian translations for MagicMirror² and Alert module.
- Add graceful shutdown of modules by calling `stop` function of each `node_helper` on SIGINT before exiting.
- Link update subtext to Github diff of current version versus tracking branch.
- Add Catalan translation.
- Add ability to filter out newsfeed items based on prohibited words found in title (resolves #1071)
- Add options to truncate description support of a feed in newsfeed module
- Add reloadInterval option for particular feed in newsfeed module
- Add no-cache entries of HTTP headers in newsfeed module (fetcher)
- Add Czech translation.
- Add option for decimal symbols other than the decimal point for temperature values in both default weather modules: WeatherForecast and CurrentWeather.
### Fixed
- Fixed issue with calendar module showing more than `maximumEntries` allows
- WeatherForecast and CurrentWeather are now using HTTPS instead of HTTP
- Correcting translation for Indonesian language
- Fix issue where calendar icons wouldn't align correctly
## [2.1.3] - 2017-10-01
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
@@ -29,7 +226,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Changed 'default.js' - listen on all attached interfaces by default.
- Add execution of `npm list` after the test are ran in Travis CI.
- Change hooks for the vendors e2e tests.
- Add log when clientonly failed on starting.
- Add log when clientonly failed on starting.
- Add warning color when are using full ip whitelist.
- Set version of the `express-ipfilter` on 0.3.1.

View File

@@ -11,6 +11,7 @@ module.exports = function(grunt) {
"modules/default/*.js",
"modules/default/*/*.js",
"serveronly/*.js",
"clientonly/*.js",
"*.js",
"tests/**/*.js",
"!modules/default/alert/notificationFx.js",

183
README.md
View File

@@ -16,120 +16,131 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec
## Table Of Contents
- [Usage](#usage)
- [Installation](#installation)
- [Raspberry Pi](#raspberry-pi)
- [General](#general)
- [Server Only](#server-only)
- [Client Only](#client-only)
- [Docker](#docker)
- [Configuration](#configuration)
- [Modules](#modules)
- [Updating](#updating)
- [Known Issues](#known-issues)
- [Community](#community)
- [Contributing Guidelines](#contributing-guidelines)
- [Manifesto](#manifesto)
## Usage
## Installation
### Raspberry Pi Support
Electron, the app wrapper around MagicMirror², only supports the Raspberry Pi 2 & 3. The Raspberry Pi 1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself.
### Raspberry Pi
### Automatic Installer (Raspberry Pi Only!)
#### Automatic Installation (Raspberry Pi only!)
*Electron*, the app wrapper around MagicMirror², only supports the Raspberry Pi 2/3. The Raspberry Pi 0/1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself. (Yes, people have managed to run MM² also on a Pi0, so if you insist, search in the forums.)
Note that you will need to install the latest full version of Raspbian, **don't use the Lite version**.
Execute the following command on your Raspberry Pi to install MagicMirror²:
````
```bash
bash -c "$(curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/master/installers/raspberry.sh)"
````
```
### Manual Installation
#### Manual Installation
1. Download and install the latest Node.js version.
1. Download and install the latest *Node.js* version:
- `curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -`
- `sudo apt install -y nodejs`
2. Clone the repository and check out the master branch: `git clone https://github.com/MichMich/MagicMirror`
3. Enter the repository: `cd ~/MagicMirror`
4. Install and run the app: `npm install && npm start`
3. Enter the repository: `cd MagicMirror/`
4. Install and run the app with: `npm install && npm start` \
For **Server Only** use: `npm install && node serveronly` .
**Important:** `npm start` does **not** work via SSH, use `DISPLAY=:0 nohup npm start &` instead. This starts the mirror on the remote display.
**Note:** if you want to debug on Raspberry Pi you can use `npm start dev` which will start the MagicMirror app with Dev Tools enabled.
**:warning: Important!**
- **The installation step for `npm install` will take a very long time**, often with little or no terminal response! \
For the RPi3 this is **~10** minutes and for the Rpi2 **~25** minutes. \
Do not interrupt or you risk getting a :broken_heart: by Raspberry Jam.
Also note that:
- `npm start` does **not** work via SSH. But you can use `DISPLAY=:0 nohup npm start &` instead. \
This starts the mirror on the remote display.
- If you want to debug on Raspberry Pi you can use `npm start dev` which will start MM with *Dev Tools* enabled.
- To access toolbar menu when in mirror mode, hit `ALT` key.
- To toggle the (web) `Developer Tools` from mirror mode, use `CTRL-SHIFT-I` or `ALT` and select `View`.
### Server Only
In some cases, you want to start the application without an actual app window. In this case, you can start MagicMirror² in server only mode by manually running `node serveronly` or using Docker. This will start the server, after which you can open the application in your browser of choice. Detailed description below.
**Important:** Make sure that you whitelist the interface/ip (`ipWhitelist`) in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server. You also need to set the local host `address` field to `0.0.0.0` in order for the RPi to listen on all interfaces and not only `localhost` (default).
```javascript
var config = {
address: "0.0.0.0", // default is "localhost"
port: 8080, // default
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:172.17.0.1"], // default -- need to add your IP here
...
};
```
### Client Only
When you have a server running remotely and want to connect a standalone client to this instance, you can manually run `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server)
**Important:** Make sure that you whitelist the interface/ip in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server
This is when you already have a server running remotely and want your RPi to connect as a standalone client to this instance, to show the MM from the server. Then from your RPi, you run it with: `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server)
#### Docker
### Docker
MagicMirror² in server only mode can be deployed using [Docker](https://docker.com). After a successful [Docker installation](https://docs.docker.com/engine/installation/) you just need to execute the following command in the shell:
```bash
docker run -d \
--publish 80:8080 \
--restart always \
--volume ~/magic_mirror/config:/opt/magic_mirror/config \
--volume ~/magic_mirror/modules:/opt/magic_mirror/modules \
--name magic_mirror \
bastilimbach/docker-magicmirror
--publish 80:8080 \
--restart always \
--volume ~/magic_mirror/config:/opt/magic_mirror/config \
--volume ~/magic_mirror/modules:/opt/magic_mirror/modules \
--name magic_mirror \
bastilimbach/docker-magicmirror
```
To get more information about the available Dockerfile versions and configurations head over to the respective [GitHub repository](https://github.com/bastilimbach/docker-MagicMirror).
| **Volumes** | **Description** |
| --- | --- |
| `/opt/magic_mirror/config` | Mount this volume to insert your own config into the docker container. |
| `/opt/magic_mirror/modules` | Mount this volume to add your own custom modules into the docker container. |
You may need to add your Docker Host IP to your `ipWhitelist` option. If you have some issues setting up this configuration, check [this forum post](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto).
```javascript
var config = {
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:172.17.0.1"]
};
```
If you want to run the server on a raspberry pi, use the `raspberry` tag. (bastilimbach/docker-magicmirror:raspberry)
#### Manual
1. Download and install the latest Node.js version.
2. Clone the repository and check out the master branch: `git clone https://github.com/MichMich/MagicMirror`
3. Enter the repository: `cd ~/MagicMirror`
4. Install and run the app: `npm install && node serveronly`
### Raspberry Configuration & Auto Start.
The following wiki links are helpful in the configuration of your MagicMirror² operating system:
- [Configuring the Raspberry Pi](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi)
- [Auto Starting MagicMirror](https://github.com/MichMich/MagicMirror/wiki/Auto-Starting-MagicMirror)
### Updating your MagicMirror²
If you want to update your MagicMirror² to the latest version, use your terminal to go to your Magic Mirror folder and type the following command:
```bash
git pull && npm install
```
If you changed nothing more than the config or the modules, this should work without any problems.
Type `git status` to see your changes, if there are any, you can reset them with `git reset --hard`. After that, git pull should be possible.
## Configuration
1. Duplicate `config/config.js.sample` to `config/config.js`. **Note:** If you used the installer script. This step is already done for you.
2. Modify your required settings.
### Raspberry Specific
The following wiki links are helpful for the initial configuration of your MagicMirror² operating system:
- [Configuring the Raspberry Pi](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi)
- [Auto Starting MagicMirror](https://github.com/MichMich/MagicMirror/wiki/Auto-Starting-MagicMirror)
### General
1. Copy `/home/pi/MagicMirror/config/config.js.sample` to `/home/pi/MagicMirror/config/config.js`. \
**Note:** If you used the installer script. This step is already done for you.
2. Modify your required settings. \
Note: You'll can check your configuration running `npm run config:check` in `/home/pi/MagicMirror`.
Note: You'll can check your configuration running the follow command:
```bash
npm run config:check
```
The following properties can be configured:
| **Option** | **Description** |
| --- | --- |
| `port` | The port on which the MagicMirror² server will run on. The default value is `8080`. |
| `address` | The ip address the accept connections. The default open bind `::` is IPv6 is available or `0.0.0.0` IPv4 run on. Example config: `192.168.10.100`. |
| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`. It is possible to specify IPs with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or define ip ranges (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`). Set `[]` to allow all IP addresses. For more information about how configure this directive see the [follow post ipWhitelist HowTo](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto) |
| `address` | The *interface* ip address on which to accept connections. The default is `localhost`, which would prevent exposing the built-in webserver to machines on the local network. To expose it to other machines, use: `0.0.0.0`. |
| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`, which is from `localhost` only. Add your IP when needed. You can also specify IP ranges with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or directly with (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`). Set `[]` to allow all IP addresses. For more information see: [follow post ipWhitelist HowTo](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto) |
| `zoom` | This allows to scale the mirror contents with a given zoom factor. The default value is `1.0`|
| `language` | The language of the interface. (Note: Not all elements will be localized.) Possible values are `en`, `nl`, `ru`, `fr`, etc., but the default value is `en`. |
| `timeFormat` | The form of time notation that will be used. Possible values are `12` or `24`. The default is `24`. |
| `units` | The units that will be used in the default weather modules. Possible values are `metric` or `imperial`. The default is `metric`. |
| `modules` | An array of active modules. **The array must contain objects. See the next table below for more information.** |
| `electronOptions` | An optional array of Electron (browser) options. This allows configuration of e.g. the browser screen size and position (example: `electronOptions: { fullscreen: false, width: 800, height: 600 }`). Kiosk mode can be enabled by setting `kiosk = true`, `autoHideMenuBar = false` and `fullscreen = false`. More options can be found [here](https://github.com/electron/electron/blob/master/docs/api/browser-window.md). |
| `electronOptions` | An optional array of Electron (browser) options. This allows configuration of e.g. the browser screen size and position (example: `electronOptions: { fullscreen: false, width: 800, height: 600 }`). Kiosk mode can be enabled by setting `kiosk: true`, `autoHideMenuBar: false` and `fullscreen: false`. More options can be found [here](https://github.com/electron/electron/blob/master/docs/api/browser-window.md). |
| `customCss` | The path of the `custom.css` stylesheet. The default is `css/custom.css`. |
Module configuration:
@@ -137,7 +148,7 @@ Module configuration:
| **Option** | **Description** |
| --- | --- |
| `module` | The name of the module. This can also contain the subfolder. Valid examples include `clock`, `default/calendar` and `custommodules/mymodule`. |
| `position` | The location of the module in which the module will be loaded. Possible values are `top_ bar`, `top_left`, `top_center`, `top_right`, `upper_third`, `middle_center`, `lower_third`, `bottom_left`, `bottom_center`, `bottom_right`, `bottom_bar`, `fullscreen_above`, and `fullscreen_below`. This field is optional but most modules require this field to set. Check the documentation of the module for more information. Multiple modules with the same position will be ordered based on the order in the configuration file. |
| `position` | The location of the module in which the module will be loaded. Possible values are `top_bar`, `top_left`, `top_center`, `top_right`, `upper_third`, `middle_center`, `lower_third`, `bottom_left`, `bottom_center`, `bottom_right`, `bottom_bar`, `fullscreen_above`, and `fullscreen_below`. This field is optional but most modules require this field to set. Check the documentation of the module for more information. Multiple modules with the same position will be ordered based on the order in the configuration file. |
| `classes` | Additional classes which are passed to the module. The field is optional. |
| `header` | To display a header text above the module, add the header property. This field is optional. |
| `disabled` | Set disabled to `true` to skip creating the module. This field is optional. |
@@ -156,12 +167,20 @@ The following modules are installed by default.
- [**Hello World**](modules/default/helloworld)
- [**Alert**](modules/default/alert)
For more available modules, check out out the wiki page: [MagicMirror² Modules](https://github.com/MichMich/MagicMirror/wiki/MagicMirror²-Modules). If you want to build your own modules, check out the [MagicMirror² Module Development Documentation](modules) and don't forget to add it to the wiki and the [forum](https://forum.magicmirror.builders/category/7/showcase)!
For more available modules, check out out the wiki page [MagicMirror² 3rd Party Modules](https://github.com/MichMich/MagicMirror/wiki/3rd-party-modules). If you want to build your own modules, check out the [MagicMirror² Module Development Documentation](modules) and don't forget to add it to the wiki and the [forum](https://forum.magicmirror.builders/category/7/showcase)!
## Known issues
- Electron seems to have some issues on certain Raspberry Pi 2's. See [#145](https://github.com/MichMich/MagicMirror/issues/145).
- MagicMirror² (Electron) sometimes quits without an error after an extended period of use. See [#150](https://github.com/MichMich/MagicMirror/issues/150).
## Updating
If you want to update your MagicMirror² to the latest version, use your terminal to go to your Magic Mirror folder and type the following command:
```bash
git pull && npm install
```
If you changed nothing more than the config or the modules, this should work without any problems.
Type `git status` to see your changes, if there are any, you can reset them with `git reset --hard`. After that, git pull should be possible.
## Community
@@ -180,6 +199,22 @@ Please keep the following in mind:
Thanks for your help in making MagicMirror² better!
## Manifesto
A real Manifesto is still to be written. Till then, Michael's response on [one of the repository issues](https://github.com/MichMich/MagicMirror/issues/1174) gives a great summary:
> "... I started this project as an ultimate starter project for Raspberry Pi enthusiasts. As a matter of fact, for most of the contributors, the MagicMirror project is the first open source project they ever contributed to. This is one of the reasons why the MagicMirror project is featured in several RasPi magazines.
>
>The project has a lot of opportunities for improvement. We could use a powerful framework like Vue to ramp up the development speed. We could use SASS for better/easier css implementations. We could make it an NPM installable package. And as you say, we could bundle it up. The big downside of of of these changes is that it over complicates things: a user no longer will be able to open just one file and make a small modification and see how it works out.
>
>Of course, a bundled version can be complimentary to the regular un-bundled version. And I'm sure a lot of (new) users will opt for the bundled version. But this means those users won't be motivated to take a peek under the hood. They will just remain 'users'. They won't become contributors, and worse: they won't be motivated to take their first steps in software development.
>
>And to be honest: motivating curious users to step out of their comfort zone and take those first steps is what drives me in this project. Therefor my ultimate goal is this project is to keep it as accessible as possible."
>
> ~ Michael Teeuw
<p align="center">
<br>
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>

View File

@@ -62,13 +62,13 @@
// 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(`http://${config.address}:${config.port}/config/`)
.then(function (config) {
.then(function (configReturn) {
// Pass along the server config via an environment variable
var env = Object.create(process.env);
var options = { env: env };
config.address = config.address;
config.port = config.port;
env.config = JSON.stringify(config);
configReturn.address = config.address;
configReturn.port = config.port;
env.config = JSON.stringify(configReturn);
// Spawn electron application
const electron = require("electron");
@@ -88,7 +88,7 @@
process.stdout.write(`Client: ${err}`);
});
child.on('close', (code) => {
child.on("close", (code) => {
if (code != 0) {
console.log(`There something wrong. The clientonly is not running code ${code}`);
}

View File

@@ -44,7 +44,7 @@ var config = {
config: {
calendars: [
{
symbol: "calendar-check-o ",
symbol: "calendar-check",
url: "webcal://www.calendarlabs.com/templates/ical/US-Holidays.ics"
}
]
@@ -59,7 +59,7 @@ var config = {
position: "top_right",
config: {
location: "New York",
locationID: "", //ID from http://www.openweathermap.org/help/city_list.txt
locationID: "", //ID from http://bulk.openweathermap.org/sample/; unzip the gz file and find your city
appid: "YOUR_OPENWEATHER_API_KEY"
}
},
@@ -69,7 +69,7 @@ var config = {
header: "Weather Forecast",
config: {
location: "New York",
locationID: "5128581", //ID from http://www.openweathermap.org/help/city_list.txt
locationID: "5128581", //ID from https://openweathermap.org/city
appid: "YOUR_OPENWEATHER_API_KEY"
}
},

View File

@@ -95,7 +95,7 @@ body {
header {
text-transform: uppercase;
font-size: 15px;
font-family: "Roboto Condensed";
font-family: "Roboto Condensed", Arial, Helvetica, sans-serif;
font-weight: 400;
border-bottom: 1px solid #666;
line-height: 15px;
@@ -128,6 +128,10 @@ sup {
text-overflow: ellipsis;
}
.pre-line {
white-space: pre-line;
}
/**
* Region Definitions.
*/
@@ -151,6 +155,7 @@ sup {
.region.right {
right: 0;
text-align: right;
}
.region.top {
@@ -161,6 +166,10 @@ sup {
margin-bottom: 25px;
}
.region.bottom .container {
margin-top: 25px;
}
.region.top .container:empty {
margin-bottom: 0;
}
@@ -185,10 +194,6 @@ sup {
bottom: 0;
}
.region.bottom .container {
margin-top: 25px;
}
.region.bottom .container:empty {
margin-top: 0;
}
@@ -231,10 +236,6 @@ sup {
text-align: left;
}
.region.right {
text-align: right;
}
.region table {
width: 100%;
border-spacing: 0;

17
dangerfile.js Normal file
View File

@@ -0,0 +1,17 @@
import { danger, fail, warn } from "danger"
// Check if the CHANGELOG.md file has been edited
// Fail the build and post a comment reminding submitters to do so if it wasn't changed
if (!danger.git.modified_files.includes("CHANGELOG.md")) {
warn("Please include an updated `CHANGELOG.md` file.<br>This way we can keep track of all the contributions.")
}
// Check if the PR request is send to the master branch.
// This should only be done by MichMich.
if (danger.github.pr.base.ref === "master" && danger.github.pr.user.login !== "MichMich") {
// Check if the PR body or title includes the text: #accepted.
// If not, the PR will fail.
if ((danger.github.pr.body + danger.github.pr.title).includes("#accepted")) {
fail("Please send all your pull requests to the `develop` branch.<br>Pull requests on the `master` branch will not be accepted.")
}
}

12
fonts/package-lock.json generated Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "magicmirror-fonts",
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"roboto-fontface": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.8.0.tgz",
"integrity": "sha512-ZYzRkETgBrdEGzL5JSKimvjI2CX7ioyZCkX2BpcfyjqI+079W0wHAyj5W4rIZMcDSOHgLZtgz1IdDi/vU77KEQ=="
}
}
}

View File

@@ -10,6 +10,6 @@
"url": "https://github.com/MichMich/MagicMirror/issues"
},
"dependencies": {
"roboto-fontface": "^0.8.0"
"roboto-fontface": "^0.8.0"
}
}

7
fonts/yarn.lock Normal file
View File

@@ -0,0 +1,7 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
roboto-fontface@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/roboto-fontface/-/roboto-fontface-0.8.0.tgz#031a83c8f79932801a57d83bf743f37250163499"

View File

@@ -38,6 +38,7 @@
</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>

View File

@@ -42,7 +42,7 @@ sudo apt-get update || echo -e "\e[91mUpdate failed, carrying on installation ..
# Installing helper tools
echo -e "\e[96mInstalling helper tools ...\e[90m"
sudo apt-get install curl wget git build-essential unzip || exit
sudo apt-get --assume-yes install curl wget git build-essential unzip || exit
# Check if we need to install or upgrade Node.js.
echo -e "\e[96mCheck current Node installation ...\e[0m"
@@ -82,7 +82,7 @@ if $NODE_INSTALL; then
# The NODE_STABLE_BRANCH variable will need to be manually adjusted when a new branch is released. (e.g. 7.x)
# Only tested (stable) versions are recommended as newer versions could break MagicMirror.
NODE_STABLE_BRANCH="6.x"
NODE_STABLE_BRANCH="9.x"
curl -sL https://deb.nodesource.com/setup_$NODE_STABLE_BRANCH | sudo -E bash -
sudo apt-get install -y nodejs
echo -e "\e[92mNode.js installation Done!\e[0m"
@@ -101,7 +101,7 @@ if [ -d "$HOME/MagicMirror" ] ; then
fi
echo -e "\e[96mCloning MagicMirror ...\e[90m"
if git clone https://github.com/MichMich/MagicMirror.git; then
if git clone --depth=1 https://github.com/MichMich/MagicMirror.git; then
echo -e "\e[92mCloning MagicMirror Done!\e[0m"
else
echo -e "\e[91mUnable to clone MagicMirror."
@@ -149,7 +149,7 @@ else
fi
# Use pm2 control like a service MagicMirror
read -p "Do you want use pm2 for auto starting of your MagicMirror (y/n)?" choice
read -p "Do you want use pm2 for auto starting of your MagicMirror (y/N)?" choice
if [[ $choice =~ ^[Yy]$ ]]; then
sudo npm install -g pm2
sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u pi --hp /home/pi"

View File

@@ -236,6 +236,33 @@ var App = function() {
});
});
};
/* stop()
* This methods stops the core app.
* This calls each node_helper's STOP() function, if it exists.
* Added to fix #1056
*/
this.stop = function() {
for (var h in nodeHelpers) {
var nodeHelper = nodeHelpers[h];
if (typeof nodeHelper.stop === "function") {
nodeHelper.stop();
}
}
};
/* Listen for SIGINT signal and call stop() function.
*
* Added to fix #1056
* Note: this is only used if running `server-only`. Otherwise
* this.stop() is called by app.on("before-quit"... in `electron.js`
*/
process.on("SIGINT", () => {
console.log("[SIGINT] Received. Shutting down server...");
setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds
this.stop();
process.exit(0);
});
};
module.exports = new App();

View File

@@ -92,7 +92,4 @@ function cloneObject(obj) {
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = Class;
module.exports._test = {
cloneObject: cloneObject
}
}

View File

@@ -96,6 +96,20 @@ app.on("activate", function() {
}
});
/* This method will be called when SIGINT is received and will call
* each node_helper's stop function if it exists. Added to fix #1056
*
* Note: this is only used if running Electron. Otherwise
* core.stop() is called by process.on("SIGINT"... in `app.js`
*/
app.on("before-quit", (event) => {
console.log("Shutting down server...");
event.preventDefault();
setTimeout(() => { process.exit(0); }, 3000); // Force-quit after 3 seconds.
core.stop();
process.exit(0);
});
// 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].indexOf(config.address) > -1) {

View File

@@ -1,5 +1,5 @@
/* global Log, Loader, Module, config, defaults */
/* jshint -W020 */
/* jshint -W020, esversion: 6 */
/* Magic Mirror
* Main System
@@ -19,42 +19,49 @@ var MM = (function() {
* are configured for a specific position.
*/
var createDomObjects = function() {
for (var m in modules) {
var module = modules[m];
var domCreationPromises = [];
if (typeof module.data.position === "string") {
var wrapper = selectWrapper(module.data.position);
var dom = document.createElement("div");
dom.id = module.identifier;
dom.className = module.name;
if (typeof module.data.classes === "string") {
dom.className = "module " + dom.className + " " + module.data.classes;
}
dom.opacity = 0;
wrapper.appendChild(dom);
if (typeof module.data.header !== "undefined" && module.data.header !== "") {
var moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.data.header;
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
}
var moduleContent = document.createElement("div");
moduleContent.className = "module-content";
dom.appendChild(moduleContent);
updateDom(module, 0);
modules.forEach(function(module) {
if (typeof module.data.position !== "string") {
return;
}
}
var wrapper = selectWrapper(module.data.position);
var dom = document.createElement("div");
dom.id = module.identifier;
dom.className = module.name;
if (typeof module.data.classes === "string") {
dom.className = "module " + dom.className + " " + module.data.classes;
}
dom.opacity = 0;
wrapper.appendChild(dom);
if (typeof module.data.header !== "undefined" && module.data.header !== "") {
var moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.data.header;
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
}
var moduleContent = document.createElement("div");
moduleContent.className = "module-content";
dom.appendChild(moduleContent);
var domCreationPromise = updateDom(module, 0);
domCreationPromises.push(domCreationPromise);
domCreationPromise.then(function() {
sendNotification("MODULE_DOM_CREATED", null, null, module);
}).catch(Log.error);
});
updateWrapperStates();
sendNotification("DOM_OBJECTS_CREATED");
Promise.all(domCreationPromises).then(function() {
sendNotification("DOM_OBJECTS_CREATED");
});
};
/* selectWrapper(position)
@@ -79,11 +86,12 @@ var MM = (function() {
* argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification.
* argument sender Module - The module that sent the notification.
* argument sendTo Module - The module to send the notification to. (optional)
*/
var sendNotification = function(notification, payload, sender) {
var sendNotification = function(notification, payload, sender, sendTo) {
for (var m in modules) {
var module = modules[m];
if (module !== sender) {
if (module !== sender && (!sendTo || module === sendTo)) {
module.notificationReceived(notification, payload, sender);
}
}
@@ -94,19 +102,53 @@ var MM = (function() {
*
* argument module Module - The module that needs an update.
* argument speed Number - The number of microseconds for the animation. (optional)
*
* return Promise - Resolved when the dom is fully updated.
*/
var updateDom = function(module, speed) {
var newContent = module.getDom();
var newHeader = module.getHeader();
return new Promise(function(resolve) {
var newContentPromise = module.getDom();
var newHeader = module.getHeader();
if (!module.hidden) {
if (!(newContentPromise instanceof Promise)) {
// convert to a promise if not already one to avoid if/else's everywhere
newContentPromise = Promise.resolve(newContentPromise);
}
newContentPromise.then(function(newContent) {
var updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
updatePromise.then(resolve).catch(Log.error);
}).catch(Log.error);
});
};
/* updateDomWithContent(module, speed, newHeader, newContent)
* Update the dom with the specified content
*
* argument module Module - The module that needs an update.
* argument speed Number - The number of microseconds for the animation. (optional)
* argument newHeader String - The new header that is generated.
* argument newContent Domobject - The new content that is generated.
*
* return Promise - Resolved when the module dom has been updated.
*/
var updateDomWithContent = function(module, speed, newHeader, newContent) {
return new Promise(function(resolve) {
if (module.hidden || !speed) {
updateModuleContent(module, newHeader, newContent);
resolve();
return;
}
if (!moduleNeedsUpdate(module, newHeader, newContent)) {
resolve();
return;
}
if (!speed) {
updateModuleContent(module, newHeader, newContent);
resolve();
return;
}
@@ -115,16 +157,16 @@ var MM = (function() {
if (!module.hidden) {
showModule(module, speed / 2);
}
resolve();
});
} else {
updateModuleContent(module, newHeader, newContent);
}
});
};
/* moduleNeedsUpdate(module, newContent)
* Check if the content has changed.
*
* argument module Module - The module to check.
* argument newHeader String - The new header that is generated.
* argument newContent Domobject - The new content that is generated.
*
* return bool - Does the module need an update?
@@ -152,6 +194,7 @@ var MM = (function() {
* Update the content of a module on screen.
*
* argument module Module - The module to check.
* argument newHeader String - The new header that is generated.
* argument newContent Domobject - The new content that is generated.
*/
var updateModuleContent = function(module, newHeader, newContent) {
@@ -202,6 +245,9 @@ var MM = (function() {
if (typeof callback === "function") { callback(); }
}, speed);
} else {
// invoke callback even if no content, issue 1308
if (typeof callback === "function") { callback(); }
}
};

View File

@@ -27,6 +27,11 @@ var Module = Class.extend({
// visibility when hiding and showing module.
lockStrings: [],
// Storage of the nunjuck Environment,
// This should not be referenced directly.
// Use the nunjucksEnvironment() to get it.
_nunjucksEnvironment: null,
/* init()
* Is called when the module is instantiated.
*/
@@ -70,25 +75,37 @@ var Module = Class.extend({
/* getDom()
* This method generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
* This method needs to be subclassed if the module wants to display info on the mirror.
* This method can to be subclassed if the module wants to display info on the mirror.
* Alternatively, the getTemplete method could be subclassed.
*
* return domobject - The dom to display.
* return DomObject | Promise - The dom or a promise with the dom to display.
*/
getDom: function () {
var nameWrapper = document.createElement("div");
var name = document.createTextNode(this.name);
nameWrapper.appendChild(name);
var self = this;
return new Promise(function(resolve) {
var div = document.createElement("div");
var template = self.getTemplate();
var templateData = self.getTemplateData();
var identifierWrapper = document.createElement("div");
var identifier = document.createTextNode(this.identifier);
identifierWrapper.appendChild(identifier);
identifierWrapper.className = "small dimmed";
// 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) {
if (err) {
Log.error(err)
}
var div = document.createElement("div");
div.appendChild(nameWrapper);
div.appendChild(identifierWrapper);
div.innerHTML = res;
return div;
resolve(div);
});
} else {
// the template is a template string.
div.innerHTML = self.nunjucksEnvironment().renderString(template, templateData);
resolve(div);
}
});
},
/* getHeader()
@@ -102,6 +119,28 @@ var Module = Class.extend({
return this.data.header;
},
/* getTemplate()
* This method returns the template for the module which is used by the default getDom implementation.
* This method needs to be subclassed if the module wants to use a tempate.
* It can either return a template sting, or a template filename.
* If the string ends with '.html' it's considered a file from within the module's folder.
*
* return string - The template string of filename.
*/
getTemplate: function () {
return "<div class=\"normal\">" + this.name + "</div><div class=\"small dimmed\">" + this.identifier + "</div>";
},
/* getTemplateData()
* This method returns the data to be used in the template.
* This method needs to be subclassed if the module wants to use a custom data.
*
* return Object
*/
getTemplateData: function () {
return {}
},
/* notificationReceived(notification, payload, sender)
* This method is called when a notification arrives.
* This method is called by the Magic Mirror core.
@@ -118,6 +157,30 @@ var Module = Class.extend({
}
},
/** nunjucksEnvironment()
* Returns the nunjucks environment for the current module.
* The environment is checked in the _nunjucksEnvironment instance variable.
* @returns Nunjucks Environment
*/
nunjucksEnvironment: function() {
if (this._nunjucksEnvironment != null) {
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) {
return self.translate(str)
});
return this._nunjucksEnvironment;
},
/* socketNotificationReceived(notification, payload)
* This method is called when a socket notification arrives.
*
@@ -276,8 +339,8 @@ var Module = Class.extend({
* Request the translation for a given key with optional variables and default value.
*
* argument key string - The key of the string to translate
* argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional)
* argument defaultValue string - The default value with variables. (Optional)
* argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional)
* argument defaultValue string - The default value with variables. (Optional)
*/
translate: function (key, defaultValueOrVariables, defaultValue) {
if(typeof defaultValueOrVariables === "object") {
@@ -414,11 +477,3 @@ Module.register = function (name, moduleDefinition) {
Log.log("Module registered: " + name);
Module.definitions[name] = moduleDefinition;
};
if (typeof exports != "undefined") { // For testing purpose only
// A good a idea move the function cmpversions a helper file.
// It's used into other side.
exports._test = {
cmpVersions: cmpVersions
}
}

View File

@@ -126,6 +126,9 @@ var Translator = (function() {
// variables: {timeToWait: "2 hours", work: "painting"}
// to: "Please wait for 2 hours before continuing with painting."
function createStringFromTemplate(template, variables) {
if(Object.prototype.toString.call(template) !== "[object String]") {
return template;
}
if(variables.fallback && !template.match(new RegExp("\{.+\}"))) {
template = variables.fallback;
}
@@ -156,11 +159,12 @@ var Translator = (function() {
return key;
},
/* load(module, file, callback)
/* load(module, file, isFallback, callback)
* Load a translation file (json) and remember the data.
*
* argument module Module - The module to load the translation file for.
* argument file string - Path of the file we want to load.
* argument isFallback boolean - Flag to indicate fallback translations.
* argument callback function - Function called when done.
*/
load: function(module, file, isFallback, callback) {
@@ -216,10 +220,12 @@ var Translator = (function() {
// defined translation after the following line.
for (var first in translations) {break;}
Log.log("Loading core translation fallback file: " + translations[first]);
loadJSON(translations[first], function(translations) {
self.coreTranslationsFallback = translations;
});
if (first) {
Log.log("Loading core translation fallback file: " + translations[first]);
loadJSON(translations[first], function(translations) {
self.coreTranslationsFallback = translations;
});
}
},
};
})();

31
module-types.ts Normal file
View File

@@ -0,0 +1,31 @@
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,
};
export declare const Module: {
register(moduleName: string, moduleProperties: ModuleProperties): void;
};
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,
};

View File

@@ -2,6 +2,42 @@
This document describes the way to develop your own MagicMirror² modules.
Table of Contents:
- Module structure
- Files
- The Core module file: modulename.js
- Available module instance properties
- Subclassable module methods
- Module instance methods
- Visibility locking
- The Node Helper: node_helper.js
- Available module instance properties
- Subclassable module methods
- Module instance methods
- MagicMirror Helper Methods
- Module Selection
- MagicMirror Logger
---
## General Advice
As MagicMirror has gained huge popularity, so has the number of available modules. For new users and developers alike, it is very time consuming to navigate around the various repositories in order to find out what exactly a certain modules does, how it looks and what it depends on. Unfortunately, this information is rarely available, nor easily obtained without having to install it first.
Therefore **we highly recommend you to include the following information in your README file.**
- A high quality screenshot of your working module
- A short, one sentence, clear description what it does (duh!)
- What external API's it depend on, including web links to those
- Wheteher the API/request require a key and the user limitations of those. (Is it free?)
Surely this also help you get better recognition and feedback for your work.
## Module structure
All modules are loaded in the `modules` folder. The default modules are grouped together in the `modules/default` folder. Your module should be placed in a subfolder of `modules`. Note that any file or folder your create in the `modules` folder will be ignored by git, allowing you to upgrade the MagicMirror² without the loss of your files.
@@ -14,7 +50,7 @@ A module can be placed in one single folder. Or multiple modules can be grouped
- **modulename/public** - Any files in this folder can be accesed via the browser on `/modulename/filename.ext`.
- **modulename/anyfileorfolder** Any other file or folder in the module folder can be used by the core module script. For example: *modulename/css/modulename.css* would be a good path for your additional module styles.
## Core module file: modulename.js
## The Core module file: modulename.js
This is the script in which the module will be defined. This script is required in order for the module to be used. In it's most simple form, the core module file must contain:
````javascript
Module.register("modulename",{});
@@ -44,30 +80,16 @@ As you can see, the `Module.register()` method takes two arguments: the name of
### Available module instance properties
After the module is initialized, the module instance has a few available module properties:
#### `this.name`
**String**
| Instance Property | Type | Description |
|:----------------- |:---- |:----------- |
| `this.name` | String | The name of the module. |
| `this.identifier` | String | This is a unique identifier for the module instance. |
| `this.hidden` | Boolean | This represents if the module is currently hidden (faded away). |
| `this.config` | Boolean | The configuration of the module instance as set in the user's `config.js` file. This config will also contain the module's defaults if these properties are not over-written by the user config. |
| `this.data` | Object | The data object contain additional metadata about the module instance. (See below) |
The name of the module.
#### `this.identifier`
**String**
This is a unique identifier for the module instance.
#### `this.hidden`
**Boolean**
This represents if the module is currently hidden (faded away).
#### `this.config`
**Boolean**
The configuration of the module instance as set in the user's config.js file. This config will also contain the module's defaults if these properties are not over written by the user config.
#### `this.data`
**Object**
The data object contains additional metadata about the module instance:
The `this.data` data object contain the follwoing metadata:
- `data.classes` - The classes which are added to the module dom wrapper.
- `data.file` - The filename of the core module file.
- `data.path` - The path of the module folder.
@@ -230,11 +252,12 @@ notificationReceived: function(notification, payload, sender) {
}
````
**Note:** the system sends two notifications when starting up. These notifications could come in handy!
**Note:** the system sends three notifications when starting up. These notifications could come in handy!
- `ALL_MODULES_STARTED` - All modules are started. You can now send notifications to other modules.
- `DOM_OBJECTS_CREATED` - All dom objects are created. The system is now ready to perform visual changes.
- `MODULE_DOM_CREATED` - This module's dom has been fully loaded. You can now access your module's dom objects.
#### `socketNotificationReceived: function(notification, payload)`
@@ -555,6 +578,17 @@ start: function() {
}
````
#### `stop()`
This method is called when the MagicMirror server receives a `SIGINT` command and is shutting down. This method should include any commands needed to close any open connections, stop any sub-processes and gracefully exit the module.
**Example:**
````javascript
stop: function() {
console.log("Shutting down MyModule");
this.connection.close();
}
````
#### `socketNotificationReceived: function(notification, payload)`
With this method, your node helper can receive notifications from your modules. When this method is called, it has 2 arguments:

View File

@@ -60,5 +60,5 @@ self.sendNotification("SHOW_ALERT", {});
| `timer` (optional) | How long the alert should stay visible in ms. <br> **Important:** If you do not use the `timer`, it is your duty to hide the alert by using `self.sendNotification("HIDE_ALERT");`! <br><br>**Possible values:** `int` `float` <br> **Default value:** `none`
## Open Source Licenses
###[NotificationStyles](https://github.com/codrops/NotificationStyles)
### [NotificationStyles](https://github.com/codrops/NotificationStyles)
See [ympanus.net](http://tympanus.net/codrops/licensing/) for license.

View File

@@ -38,13 +38,13 @@ Module.register("alert",{
if (this.config.effect == "slide") {this.config.effect = this.config.effect + "-" + this.config.position;}
msg = "";
if (message.title) {
msg += "<span class='thin' style='line-height: 35px; font-size:24px' color='#4A4A4A'>" + message.title + "</span>";
msg += "<span class='thin dimmed medium'>" + message.title + "</span>";
}
if (message.message){
if (msg != ""){
msg+= "<br />";
}
msg += "<span class='light' style='font-size:28px;line-height: 30px;'>" + message.message + "</span>";
msg += "<span class='light bright small'>" + message.message + "</span>";
}
new NotificationFx({
@@ -63,9 +63,9 @@ Module.register("alert",{
params.imageUrl = null;
image = "";
} else if (typeof params.imageFA === "undefined"){
image = "<img src='" + (params.imageUrl).toString() + "' height=" + (params.imageHeight).toString() + " style='margin-bottom: 10px;'/><br />";
image = "<img src='" + (params.imageUrl).toString() + "' height='" + (params.imageHeight).toString() + "' style='margin-bottom: 10px;'/><br />";
} else if (typeof params.imageUrl === "undefined"){
image = "<span class='" + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;color: #fff;font-size:" + (params.imageHeight).toString() + ";'/></span><br />";
image = "<span class='bright " + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;font-size:" + (params.imageHeight).toString() + ";'/></span><br />";
}
//Create overlay
var overlay = document.createElement("div");
@@ -79,16 +79,16 @@ Module.register("alert",{
}
//Display title and message only if they are provided in notification parameters
message ="";
var message = "";
if (params.title) {
message += "<span class='light' style='line-height: 35px; font-size:30px' color='#4A4A4A'>" + params.title + "</span>"
message += "<span class='light dimmed medium'>" + params.title + "</span>";
}
if (params.message) {
if (message != ""){
if (message !== ""){
message += "<br />";
}
message += "<span class='thin' style='font-size:22px;line-height: 30px;'>" + params.message + "</span>";
message += "<span class='thin bright small'>" + params.message + "</span>";
}
//Store alert in this.alerts
@@ -110,25 +110,27 @@ Module.register("alert",{
},
hide_alert: function(sender) {
//Dismiss alert and remove from this.alerts
this.alerts[sender.name].dismiss();
this.alerts[sender.name] = null;
//Remove overlay
var overlay = document.getElementById("overlay");
overlay.parentNode.removeChild(overlay);
if (this.alerts[sender.name]) {
this.alerts[sender.name].dismiss();
this.alerts[sender.name] = null;
//Remove overlay
var overlay = document.getElementById("overlay");
overlay.parentNode.removeChild(overlay);
}
},
setPosition: function(pos) {
//Add css to body depending on the set position for notifications
var 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;}";}
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") {
if (payload.type === "alert") {
this.show_alert(payload, sender);
} else if (payload.type = "notification") {
this.show_notification(payload);
@@ -141,7 +143,7 @@ Module.register("alert",{
this.alerts = {};
this.setPosition(this.config.position);
if (this.config.welcome_message) {
if (this.config.welcome_message == true){
if (this.config.welcome_message === true){
this.show_notification({title: this.translate("sysTitle"), message: this.translate("welcome")});
}
else{

View File

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

View File

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

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

@@ -33,30 +33,37 @@ The following properties can be configured:
| `maxTitleLength` | The maximum title length. <br><br> **Possible values:** `10` - `50` <br> **Default value:** `25`
| `wrapEvents` | Wrap event titles to multiple lines. Breaks lines at the length defined by `maxTitleLength`. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `fetchInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `300000` (5 minutes)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `2000` (2 seconds)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:** `0` - `5000` <br> **Default value:** `2000` (2 seconds)
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `fadePoint` | Where to start fade? <br><br> **Possible values:** `0` (top of the list) - `1` (bottom of list) <br> **Default value:** `0.25`
| `tableClass` | Name of the classes issued from `main.css`. <br><br> **Possible values:** xsmall, small, medium, large, xlarge. <br> **Default value:** _small._
| `calendars` | The list of calendars. <br><br> **Possible values:** An array, see _calendar configuration_ below. <br> **Default value:** _An example calendar._
| `titleReplace` | An object of textual replacements applied to the tile of the event. This allow to remove or replace certains words in the title. <br><br> **Example:** `{'Birthday of ' : '', 'foo':'bar'}` <br> **Default value:** `{ "De verjaardag van ": "", "'s birthday": "" }`
| `displayRepeatingCountTitle` | Show count title for yearly repeating events (e.g. "X. Birthday", "X. Anniversary") <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `dateFormat` | Format to use for the date of events (when using absolute dates) <br><br> **Possible values:** See [Moment.js formats](http://momentjs.com/docs/#/parsing/string-format/) <br> **Default value:** `MMM Do` (e.g. Jan 18th)
| `dateEndFormat` | Format to use for the end time of events <br><br> **Possible values:** See [Moment.js formats](http://momentjs.com/docs/#/parsing/string-format/) <br> **Default value:** `HH:mm` (e.g. 16:30)
| `showEnd` | Show end time of events <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `fullDayEventDateFormat` | Format to use for the date of full day events (when using absolute dates) <br><br> **Possible values:** See [Moment.js formats](http://momentjs.com/docs/#/parsing/string-format/) <br> **Default value:** `MMM Do` (e.g. Jan 18th)
| `timeFormat` | Display event times as absolute dates, or relative time <br><br> **Possible values:** `absolute` or `relative` <br> **Default value:** `relative`
| `timeFormat` | Display event times as absolute dates, or relative time, or using absolute date headers with times for each event next to it <br><br> **Possible values:** `absolute` or `relative` or `dateheaders` <br> **Default value:** `relative`
| `showEnd` | Display the end of a date as well <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `getRelative` | How much time (in hours) should be left until calendar events start getting relative? <br><br> **Possible values:** `0` (events stay absolute) - `48` (48 hours before the event starts) <br> **Default value:** `6`
| `urgency` | When using a timeFormat of `absolute`, the `urgency` setting allows you to display events within a specific time frame as `relative`. This allows events within a certain time frame to be displayed as relative (in xx days) while others are displayed as absolute dates <br><br> **Possible values:** a positive integer representing the number of days for which you want a relative date, for example `7` (for 7 days) <br><br> **Default value:** `7`
| `broadcastEvents` | If this property is set to true, the calendar will broadcast all the events to all other modules with the notification message: `CALENDAR_EVENTS`. The event objects are stored in an array and contain the following fields: `title`, `startDate`, `endDate`, `fullDayEvent`, `location` and `geo`. <br><br> **Possible values:** `true`, `false` <br><br> **Default value:** `true`
| `hidePrivate` | Hides private calendar events. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown. <br><br> **Example:** `['Birthday', 'Hide This Event']` <br> **Default value:** `[]`
| `hideOngoing` | Hides calendar events that have already started. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown. <br><br>Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.<br>**Required**<br>`filterBy` - string used to determine if filter is applied.<br>**Optional**<br>`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]<br>`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity<br>`regex` - set to `true` if filterBy is a regex. For those not familiar with regex it is used for pattern matching, please see [here](https://regexr.com/) for more info.<br><br> **Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}, {filterBy: '^[0-9]{1,}.*', regex: true}]` <br> **Default value:** `[]`
### Calendar configuration
The `calendars` property contains an array of the configured calendars.
The `colored` property gives the option for an individual color for each calendar.
The `coloredSymbolOnly` property will apply color to the symbol only, not the whole line. This is only applicable when `colored` is also enabled.
#### Default value:
````javascript
config: {
colored: false,
coloredSymbolOnly: false,
calendars: [
{
url: 'http://www.calendarlabs.com/templates/ical/US-Holidays.ics',
@@ -81,6 +88,9 @@ config: {
| `maximumEntries` | The maximum number of events shown. Overrides global setting. **Possible values:** `0` - `100`
| `maximumNumberOfDays` | The maximum number of days in the future. Overrides global setting
| `auth` | The object containing options for authentication against the calendar.
| `symbolClass` | Add a class to the cell of symbol.
| `titleClass` | Add a class to the title's cell.
| `timeClass` | Add a class to the time's cell.
#### Calendar authentication options:

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

@@ -25,11 +25,16 @@ Module.register("calendar", {
urgency: 7,
timeFormat: "relative",
dateFormat: "MMM Do",
dateEndFormat: "HH:mm",
fullDayEventDateFormat: "MMM Do",
showEnd: false,
getRelative: 6,
fadePoint: 0.25, // Start on 1/4th of the list.
hidePrivate: false,
hideOngoing: false,
colored: false,
coloredSymbolOnly: false,
tableClass: "small",
calendars: [
{
symbol: "calendar",
@@ -46,7 +51,7 @@ Module.register("calendar", {
// Define required scripts.
getStyles: function () {
return ["calendar.css", "font-awesome.css"];
return ["calendar.css", "font-awesome5.css", "font-awesome5.v4shims.css"];
},
// Define required scripts.
@@ -77,6 +82,15 @@ Module.register("calendar", {
maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays
};
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = "";
}
if (calendar.titleClass === "undefined" || calendar.titleClass === null) {
calendarConfig.titleClass = "";
}
if (calendar.timeClass === "undefined" || calendar.timeClass === null) {
calendarConfig.timeClass = "";
}
// we check user and password here for backwards compatibility with old configs
if(calendar.user && calendar.pass) {
@@ -122,19 +136,52 @@ Module.register("calendar", {
var events = this.createEventList();
var wrapper = document.createElement("table");
wrapper.className = "small";
wrapper.className = this.config.tableClass;
if (events.length === 0) {
wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING");
wrapper.className = "small dimmed";
wrapper.className = this.config.tableClass + " dimmed";
return wrapper;
}
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startFade = events.length * this.config.fadePoint;
var fadeSteps = events.length - startFade;
}
var currentFadeStep = 0;
var lastSeenDate = "";
for (var e in events) {
var event = events[e];
var dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
if(this.config.timeFormat === "dateheaders"){
if(lastSeenDate !== dateAsString){
var dateRow = document.createElement("tr");
dateRow.className = "normal";
var dateCell = document.createElement("td");
dateCell.colSpan = "3";
dateCell.innerHTML = dateAsString;
dateRow.appendChild(dateCell);
wrapper.appendChild(dateRow);
if (e >= startFade) { //fading
currentFadeStep = e - startFade;
dateRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
}
lastSeenDate = dateAsString;
}
}
var eventWrapper = document.createElement("tr");
if (this.config.colored) {
if (this.config.colored && !this.config.coloredSymbolOnly) {
eventWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
}
@@ -142,7 +189,14 @@ Module.register("calendar", {
if (this.config.displaySymbol) {
var symbolWrapper = document.createElement("td");
symbolWrapper.className = "symbol align-right";
if (this.config.colored && this.config.coloredSymbolOnly) {
symbolWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
}
var symbolClass = this.symbolClassForUrl(event.url);
symbolWrapper.className = "symbol align-right " + symbolClass;
var symbols = this.symbolsForUrl(event.url);
if(typeof symbols === "string") {
symbols = [symbols];
@@ -150,13 +204,17 @@ Module.register("calendar", {
for(var i = 0; i < symbols.length; i++) {
var symbol = document.createElement("span");
symbol.className = "fa fa-" + symbols[i];
symbol.className = "fa fa-fw fa-" + symbols[i];
if(i > 0){
symbol.style.paddingLeft = "5px";
}
symbolWrapper.appendChild(symbol);
}
eventWrapper.appendChild(symbolWrapper);
}else if(this.config.timeFormat === "dateheaders"){
var blankCell = document.createElement("td");
blankCell.innerHTML = "&nbsp;&nbsp;&nbsp;";
eventWrapper.appendChild(blankCell);
}
var titleWrapper = document.createElement("td"),
@@ -176,109 +234,141 @@ Module.register("calendar", {
titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
var titleClass = this.titleClassForUrl(event.url);
if (!this.config.colored) {
titleWrapper.className = "title bright";
titleWrapper.className = "title bright " + titleClass;
} else {
titleWrapper.className = "title";
titleWrapper.className = "title " + titleClass;
}
eventWrapper.appendChild(titleWrapper);
if(this.config.timeFormat === "dateheaders"){
var timeWrapper = document.createElement("td");
//console.log(event.today);
var now = new Date();
// Define second, minute, hour, and day variables
var oneSecond = 1000; // 1,000 milliseconds
var oneMinute = oneSecond * 60;
var oneHour = oneMinute * 60;
var oneDay = oneHour * 24;
if (event.fullDayEvent) {
if (event.today) {
timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
} else if (event.startDate - now < oneDay && event.startDate - now > 0) {
timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
} else if (event.startDate - now < 2 * oneDay && event.startDate - now > 0) {
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
}
} else {
/* Check to see if the user displays absolute or relative dates with their events
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
// This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
}
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
}
if (event.fullDayEvent) {
titleWrapper.colSpan = "2";
titleWrapper.align = "left";
}else{
var timeClass = this.timeClassForUrl(event.url);
var timeWrapper = document.createElement("td");
timeWrapper.className = "time light " + timeClass;
timeWrapper.align = "left";
timeWrapper.style.paddingLeft = "2px";
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
eventWrapper.appendChild(timeWrapper);
titleWrapper.align = "right";
}
} else {
if (event.startDate >= new Date()) {
if (event.startDate - now < 2 * oneDay) {
// This event is within the next 48 hours (2 days)
if (event.startDate - now < this.config.getRelative * oneHour) {
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
eventWrapper.appendChild(titleWrapper);
}else{
var timeWrapper = document.createElement("td");
eventWrapper.appendChild(titleWrapper);
//console.log(event.today);
var now = new Date();
// Define second, minute, hour, and day variables
var oneSecond = 1000; // 1,000 milliseconds
var oneMinute = oneSecond * 60;
var oneHour = oneMinute * 60;
var oneDay = oneHour * 24;
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;
if (event.today) {
timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
} else if (event.startDate - now < oneDay && event.startDate - now > 0) {
timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
} else if (event.startDate - now < 2 * oneDay && event.startDate - now > 0) {
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
} else {
// Otherwise just say 'Today/Tomorrow at such-n-such time'
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
}
} else {
/* Check to see if the user displays absolute or relative dates with their events
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
// This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
}
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
}
}
if(this.config.showEnd){
timeWrapper.innerHTML += "-" ;
timeWrapper.innerHTML += this.capFirst(moment(event.endDate , "x").format(this.config.fullDayEventDateFormat));
}
} else {
timeWrapper.innerHTML = this.capFirst(
this.translate("RUNNING", {
fallback: this.translate("RUNNING") + " {timeUntilEnd}",
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
})
);
if (event.startDate >= new Date()) {
if (event.startDate - now < 2 * oneDay) {
// This event is within the next 48 hours (2 days)
if (event.startDate - now < this.config.getRelative * oneHour) {
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else {
if(this.config.timeFormat === "absolute") {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
} else {
// Otherwise just say 'Today/Tomorrow at such-n-such time'
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
}
}
} else {
/* Check to see if the user displays absolute or relative dates with their events
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
// This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
}
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
}
}
} else {
timeWrapper.innerHTML = this.capFirst(
this.translate("RUNNING", {
fallback: this.translate("RUNNING") + " {timeUntilEnd}",
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
})
);
}
if (this.config.showEnd) {
timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
}
}
//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
//console.log(event);
var timeClass = this.timeClassForUrl(event.url);
timeWrapper.className = "time light " + timeClass;
eventWrapper.appendChild(timeWrapper);
}
//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
//console.log(event);
timeWrapper.className = "time light";
eventWrapper.appendChild(timeWrapper);
wrapper.appendChild(eventWrapper);
// Create fade effect.
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = events.length * this.config.fadePoint;
var steps = events.length - startingPoint;
if (e >= startingPoint) {
var currentStep = e - startingPoint;
eventWrapper.style.opacity = 1 - (1 / steps * currentStep);
}
if (e >= startFade) {
currentFadeStep = e - startFade;
eventWrapper.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
}
}
@@ -289,9 +379,9 @@ Module.register("calendar", {
* This function accepts a number (either 12 or 24) and returns a moment.js LocaleSpecification with the
* corresponding timeformat to be used in the calendar display. If no number is given (or otherwise invalid input)
* it will a localeSpecification object with the system locale time format.
*
*
* @param {number} timeFormat Specifies either 12 or 24 hour time format
* @returns {moment.LocaleSpecification}
* @returns {moment.LocaleSpecification}
*/
getLocaleSpecification: function(timeFormat) {
switch (timeFormat) {
@@ -336,6 +426,7 @@ Module.register("calendar", {
createEventList: function () {
var events = [];
var today = moment().startOf("day");
var now = new Date();
for (var c in this.calendarData) {
var calendar = this.calendarData[c];
for (var e in calendar) {
@@ -346,6 +437,14 @@ Module.register("calendar", {
continue;
}
}
if(this.config.hideOngoing) {
if(event.startDate < now) {
continue;
}
}
if(this.listContainsEvent(events,event)){
continue;
}
event.url = c;
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
events.push(event);
@@ -356,7 +455,18 @@ Module.register("calendar", {
return a.startDate - b.startDate;
});
return events;
return events.slice(0, this.config.maximumEntries);
},
listContainsEvent: function(eventList, event){
for(var evt of eventList){
if(evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)){
return true;
}
}
return false;
},
/* createEventList(url)
@@ -371,11 +481,15 @@ Module.register("calendar", {
maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries,
maximumNumberOfDays: calendarConfig.maximumNumberOfDays || this.config.maximumNumberOfDays,
fetchInterval: this.config.fetchInterval,
symbolClass: calendarConfig.symbolClass,
titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass,
auth: auth
});
},
/* symbolsForUrl(url)
/**
* symbolsForUrl(url)
* Retrieves the symbols for a specific url.
*
* argument url string - Url to look for.
@@ -386,6 +500,42 @@ Module.register("calendar", {
return this.getCalendarProperty(url, "symbol", this.config.defaultSymbol);
},
/**
* symbolClassForUrl(url)
* Retrieves the symbolClass for a specific url.
*
* @param url string - Url to look for.
*
* @returns string
*/
symbolClassForUrl: function (url) {
return this.getCalendarProperty(url, "symbolClass", "");
},
/**
* titleClassForUrl(url)
* Retrieves the titleClass for a specific url.
*
* @param url string - Url to look for.
*
* @returns string
*/
titleClassForUrl: function (url) {
return this.getCalendarProperty(url, "titleClass", "");
},
/**
* timeClassForUrl(url)
* Retrieves the timeClass for a specific url.
*
* @param url string - Url to look for.
*
* @returns string
*/
timeClassForUrl: function (url) {
return this.getCalendarProperty(url, "timeClass", "");
},
/* colorForUrl(url)
* Retrieves the color for a specific url.
*
@@ -430,7 +580,7 @@ Module.register("calendar", {
/**
* Shortens a string if it's longer than maxLength and add a ellipsis to the end
*
*
* @param {string} string Text string to shorten
* @param {number} maxLength The max length of the string
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength

View File

@@ -29,7 +29,8 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var opts = {
headers: {
"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
}
},
gzip: true
};
if (auth) {
@@ -90,6 +91,9 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var endDate;
if (typeof event.end !== "undefined") {
endDate = eventDate(event, "end");
} else if(typeof event.duration !== "undefined") {
dur=moment.duration(event.duration);
endDate = startDate.clone().add(dur);
} else {
if (!isFacebookBirthday) {
endDate = startDate;
@@ -113,11 +117,48 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
title = event.description;
}
var excluded = false;
var excluded = false,
dateFilter = null;
for (var f in excludedEvents) {
var filter = excludedEvents[f];
if (title.toLowerCase().includes(filter.toLowerCase())) {
excluded = true;
var filter = excludedEvents[f],
testTitle = title.toLowerCase(),
until = null,
useRegex = false,
regexFlags = "g";
if (filter instanceof Object) {
if (typeof filter.until !== "undefined") {
until = filter.until;
}
if (typeof filter.regex !== "undefined") {
useRegex = filter.regex;
}
// If additional advanced filtering is added in, this section
// must remain last as we overwrite the filter object with the
// filterBy string
if (filter.caseSensitive) {
filter = filter.filterBy;
testTitle = title;
} else if (useRegex) {
filter = filter.filterBy;
testTitle = title;
regexFlags += "i";
} else {
filter = filter.filterBy.toLowerCase();
}
} else {
filter = filter.toLowerCase();
}
if (testTitleByFilter(testTitle, filter, useRegex, regexFlags)) {
if (until) {
dateFilter = until;
} else {
excluded = true;
}
break;
}
}
@@ -130,13 +171,26 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var geo = event.geo || false;
var description = event.description || false;
if (typeof event.rrule != "undefined" && !isFacebookBirthday) {
if (typeof event.rrule != "undefined" && event.rrule != null && !isFacebookBirthday) {
var rule = event.rrule;
// can cause problems with e.g. birthdays before 1900
if(rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900 ||
rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900){
rule.origOptions.dtstart.setYear(1900);
rule.options.dtstart.setYear(1900);
}
var dates = rule.between(today, future, true, limitFunction);
for (var d in dates) {
startDate = moment(new Date(dates[d]));
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
if (timeFilterApplies(now, endDate, dateFilter)) {
continue;
}
if (endDate.format("x") > now) {
newEvents.push({
title: title,
@@ -171,6 +225,10 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
continue;
}
if (timeFilterApplies(now, endDate, dateFilter)) {
continue;
}
// Every thing is good. Add it to the list.
newEvents.push({
@@ -227,8 +285,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var start = event.start || 0;
var startDate = new Date(start);
var end = event.end || 0;
if (end - start === 24 * 60 * 60 * 1000 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
if (((end - start) % (24 * 60 * 60 * 1000)) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
// Is 24 hours, and starts on the middle of the night.
return true;
}
@@ -236,6 +293,44 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
return false;
};
/* timeFilterApplies()
* Determines if the user defined time filter should apply
*
* argument now Date - Date object using previously created object for consistency
* argument endDate Moment - Moment object representing the event end date
* argument filter string - The time to subtract from the end date to determine if an event should be shown
*
* return bool - The event should be filtered out
*/
var timeFilterApplies = function(now, endDate, filter) {
if (filter) {
var until = filter.split(" "),
value = parseInt(until[0]),
increment = until[1].slice("-1") === "s" ? until[1] : until[1] + "s", // Massage the data for moment js
filterUntil = moment(endDate.format()).subtract(value, increment);
return now < filterUntil.format("x");
}
return false;
};
var testTitleByFilter = function (title, filter, useRegex, regexFlags) {
if (useRegex) {
// Assume if leading slash, there is also trailing slash
if (filter[0] === "/") {
// Strip leading and trailing slashes
filter = filter.substr(1).slice(0, -1);
}
filter = new RegExp(filter, regexFlags);
return filter.test(title);
} else {
return title.includes(filter);
}
}
/* public methods */
/* startFetch()

View File

@@ -44,7 +44,13 @@ ical.objectHandlers['END'] = function(val, params, curr, stack){
rule += ' EXDATE:' + curr.exdates[i].toISOString().replace(/[-:]/g, '');
rule = rule.replace(/\.[0-9]{3}/, '');
}
curr.rrule = rrulestr(rule);
try {
curr.rrule = rrulestr(rule);
}
catch(err) {
console.log("Unrecognised element in calendar feed, ignoring: " + rule);
curr.rrule = null;
}
}
return originalEnd.call(this, val, params, curr, stack);
}

View File

@@ -108,7 +108,7 @@ vows.describe('node-ical').addBatch({
assert.equal(topic.end.getFullYear(), 1998);
assert.equal(topic.end.getUTCMonth(), 2);
assert.equal(topic.end.getUTCDate(), 15);
assert.equal(topic.end.getUTCHours(), 00);
assert.equal(topic.end.getUTCHours(), 0);
assert.equal(topic.end.getUTCMinutes(), 30);
}
}
@@ -146,7 +146,7 @@ vows.describe('node-ical').addBatch({
}
, 'has a start datetime' : function(topic) {
assert.equal(topic.start.getFullYear(), 2011);
assert.equal(topic.start.getMonth(), 09);
assert.equal(topic.start.getMonth(), 9);
assert.equal(topic.start.getDate(), 11);
}
@@ -192,7 +192,7 @@ vows.describe('node-ical').addBatch({
}
, 'has a start' : function(topic){
assert.equal(topic.start.tz, 'America/Phoenix')
assert.equal(topic.start.toISOString(), new Date(2011, 10, 09, 19, 0,0).toISOString())
assert.equal(topic.start.toISOString(), new Date(2011, 10, 9, 19, 0,0).toISOString())
}
}
}
@@ -208,7 +208,7 @@ vows.describe('node-ical').addBatch({
})[0];
}
, 'has a start' : function(topic){
assert.equal(topic.start.toISOString(), new Date(2011, 07, 04, 12, 0,0).toISOString())
assert.equal(topic.start.toISOString(), new Date(2011, 7, 4, 12, 0,0).toISOString())
}
}
, 'event with rrule' :{
@@ -249,7 +249,7 @@ vows.describe('node-ical').addBatch({
},
'task completed': function(task){
assert.equal(task.completion, 100);
assert.equal(task.completed.toISOString(), new Date(2013, 06, 16, 10, 57, 45).toISOString());
assert.equal(task.completed.toISOString(), new Date(2013, 6, 16, 10, 57, 45).toISOString());
}
}
}
@@ -367,7 +367,7 @@ vows.describe('node-ical').addBatch({
assert.equal(topic.end.getFullYear(), 2014);
assert.equal(topic.end.getMonth(), 3);
assert.equal(topic.end.getUTCHours(), 19);
assert.equal(topic.end.getUTCMinutes(), 00);
assert.equal(topic.end.getUTCMinutes(), 0);
}
}
},

View File

@@ -2,6 +2,11 @@
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.
## Screenshot
- Current time
![Current time](clock_screenshot.png)
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:

View File

@@ -94,7 +94,7 @@ Module.register("clock",{
dateWrapper.innerHTML = now.format(this.config.dateFormat);
}
if (this.config.showWeek) {
weekWrapper.innerHTML = this.translate("WEEK") + " " + now.week();
weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() });
}
timeWrapper.innerHTML = timeString;
secondsWrapper.innerHTML = now.format("ss");

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -2,6 +2,10 @@
The `compliments` module is one of the default modules of the MagicMirror.
This module displays a random compliment.
## Screenshots
- Compliments Screenshot
![Compliments Screenshot](compliments_screenshot.png)
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
@@ -30,8 +34,14 @@ The following properties can be configured:
| `updateInterval` | How often does the compliment have to change? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `30000` (30 seconds)
| `fadeSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `4000` (4 seconds)
| `compliments` | The list of compliments. <br><br> **Possible values:** An object with four arrays: `morning`, `afternoon`, `evening` and `anytime`. See _compliment configuration_ below. <br> **Default value:** See _compliment configuration_ below.
| `remoteFile` | External file from which to load the compliments <br><br> **Possible values:** Path to a JSON file containing compliments, configured as per the value of the _compliments configuration_ (see below). An object with four arrays: `morning`, `afternoon`, `evening` and `anytime`. - `compliments.json` <br> **Default value:** `null` (Do not load from file)
| `remoteFile` | External file from which to load the compliments <br><br> **Possible values:** Path or URL (starting with `http://` or `https://`) to a JSON file containing compliments, configured as per the value of the _compliments configuration_ (see below). An object with four arrays: `morning`, `afternoon`, `evening` and `anytime`. - `compliments.json` <br> **Default value:** `null` (Do not load from file)
| `classes` | Override the CSS classes of the div showing the compliments <br><br> **Default value:** `thin xlarge bright`
| `morningStartTime` | Time in hours (in 24 format), after which the mode of "morning" will begin <br> **Possible values:** `0` - `24` <br><br> **Default value:** `3`
| `morningEndTime` | Time in hours (in 24 format), after which the mode of "morning" will end <br> **Possible values:** `0` - `24` <br><br> **Default value:** `12`
| `afternoonStartTime` | Time in hours (in 24 format), after which the mode "afternoon" will begin <br> **Possible values:** `0` - `24` <br><br> **Default value:** `12`
| `afternoonEndTime` | Time in hours (in 24 format), after which the mode "afternoon" will end <br> **Possible values:** `0` - `24` <br><br> **Default value:** `17`
All the rest of the time that does not fall into the morningStartTime-morningEndTime and afternoonStartTime-afternoonEndTime ranges is considered "evening".
### Compliment configuration
@@ -39,22 +49,22 @@ The `compliments` property contains an object with four arrays: <code>morning</c
If use the currentweather is possible use a actual weather for set compliments. The availables properties are:
* `day_sunny`
* `day_cloudy`
* `cloudy`
* `cloudy_windy`
* `showers`
* `rain`
* `thunderstorm`
* `snow`
* `fog`
* `night_clear`
* `night_cloudy`
* `night_showers`
* `night_rain`
* `night_thunderstorm`
* `night_snow`
* `night_alt_cloudy_windy`
- `day_sunny`
- `day_cloudy`
- `cloudy`
- `cloudy_windy`
- `showers`
- `rain`
- `thunderstorm`
- `snow`
- `fog`
- `night_clear`
- `night_cloudy`
- `night_showers`
- `night_rain`
- `night_thunderstorm`
- `night_snow`
- `night_alt_cloudy_windy`
#### Example use with currentweather module
````javascript
@@ -101,6 +111,13 @@ config: {
}
````
#### Multi-line compliments:
Use `\n` to split compliment text into multiple lines, e.g. `First line.\nSecond line.` will be shown as:
```
First line.
Second line.
```
### External Compliment File
You may specify an external file that contains the three compliment arrays. This is particularly useful if you have a
large number of compliments and do not wish to crowd your `config.js` file with a large array of compliments.

View File

@@ -32,7 +32,11 @@ Module.register("compliments", {
},
updateInterval: 30000,
remoteFile: null,
fadeSpeed: 4000
fadeSpeed: 4000,
morningStartTime: 3,
morningEndTime: 12,
afternoonStartTime: 12,
afternoonEndTime: 17
},
// Set currentweather from module
@@ -49,14 +53,15 @@ Module.register("compliments", {
this.lastComplimentIndex = -1;
var self = this;
if (this.config.remoteFile != null) {
this.complimentFile((response) => {
this.config.compliments = JSON.parse(response);
this.complimentFile(function(response) {
self.config.compliments = JSON.parse(response);
self.updateDom();
});
}
// Schedule update timer.
var self = this;
setInterval(function() {
self.updateDom(self.config.fadeSpeed);
}, this.config.updateInterval);
@@ -98,9 +103,9 @@ Module.register("compliments", {
var hour = moment().hour();
var compliments;
if (hour >= 3 && hour < 12 && this.config.compliments.hasOwnProperty("morning")) {
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) {
compliments = this.config.compliments.morning.slice(0);
} else if (hour >= 12 && hour < 17 && this.config.compliments.hasOwnProperty("afternoon")) {
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) {
compliments = this.config.compliments.afternoon.slice(0);
} else if(this.config.compliments.hasOwnProperty("evening")) {
compliments = this.config.compliments.evening.slice(0);
@@ -123,9 +128,11 @@ Module.register("compliments", {
* Retrieve a file from the local filesystem
*/
complimentFile: function(callback) {
var xobj = new XMLHttpRequest();
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", this.file(this.config.remoteFile), true);
xobj.open("GET", path, true);
xobj.onreadystatechange = function() {
if (xobj.readyState == 4 && xobj.status == "200") {
callback(xobj.responseText);
@@ -152,7 +159,7 @@ Module.register("compliments", {
var compliment = document.createTextNode(complimentText);
var wrapper = document.createElement("div");
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright";
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line";
wrapper.appendChild(compliment);
return wrapper;
@@ -192,4 +199,4 @@ Module.register("compliments", {
}
},
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -2,6 +2,11 @@
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.
## Screenshot
- Current weather screenshot
![Current Weather Screenshot](weather_screenshot.png)
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
@@ -29,7 +34,7 @@ The following properties can be configured:
| Option | Description
| ---------------------------- | -----------
| `location` | The location used for weather information. <br><br> **Example:** `'Amsterdam,Netherlands'` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `locationID` | Location ID from [OpenWeatherMap](http://openweathermap.org/help/city_list.txt) **This will override anything you put in location.** <br> Leave blank if you want to use location. <br> **Example:** `1234567` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `locationID` | Location ID from [OpenWeatherMap](https://openweathermap.org/find) **This will override anything you put in location.** <br> Leave blank if you want to use location. <br> **Example:** `1234567` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `appid` | The [OpenWeatherMap](https://home.openweathermap.org) API key, which can be obtained by creating an OpenWeatherMap account. <br><br> This value is **REQUIRED**
| `units` | What units to use. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` =Fahrenheit <br> **Default value:** `config.units`
| `roundTemp` | Round temperature value to nearest integer. <br><br> **Possible values:** `true` (round to integer) or `false` (display exact value with decimal point) <br> **Default value:** `false`
@@ -43,9 +48,12 @@ The following properties can be configured:
| `showWindDirectionAsArrow` | Show the wind direction as an arrow instead of abbreviation <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showHumidity` | Show the current humidity <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showIndoorTemperature` | If you have another module that emits the INDOOR_TEMPERATURE notification, the indoor temperature will be displayed <br> **Default value:** `false`
| `onlyTemp` | Show only current Temperature and weather icon. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `onlyTemp` | Show only current Temperature and weather icon without windspeed, sunset, sunrise time and feels like. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showFeelsLike` | Shows the Feels like temperature weather. <br><br> **Possible values:**`true` or `false`<br>**Default value:** `true`
| `useKMPHWind` | Uses KMPH as units for windspeed. <br><br> **Possible values:**`true` or `false`<br>**Default value:** `false`
| `useBeaufort` | Pick between using the Beaufort scale for wind speed or using the default units. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `lang` | The language of the days. <br><br> **Possible values:** `en`, `nl`, `ru`, etc ... <br> **Default value:** uses value of _config.language_
| `decimalSymbol` | The decimal symbol to use.<br><br> **Possible values:** `.`, `,` or any other symbol.<br> **Default value:** `.`
| `initialLoadDelay` | The initial delay before loading. If you have multiple modules that use the same API key, you might want to delay one of the requests. (Milliseconds) <br><br> **Possible values:** `1000` - `5000` <br> **Default value:** `0`
| `retryDelay` | The delay before retrying after a request failure. (Milliseconds) <br><br> **Possible values:** `1000` - `60000` <br> **Default value:** `2500`
| `apiVersion` | The OpenWeatherMap API version to use. <br><br> **Default value:** `2.5`

View File

@@ -23,17 +23,20 @@ Module.register("currentweather",{
showWindDirection: true,
showWindDirectionAsArrow: false,
useBeaufort: true,
useKMPHwind: false,
lang: config.language,
decimalSymbol: ".",
showHumidity: false,
degreeLabel: false,
showIndoorTemperature: false,
showIndoorHumidity: false,
showFeelsLike: true,
initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
apiVersion: "2.5",
apiBase: "http://api.openweathermap.org/data/",
apiBase: "https://api.openweathermap.org/data/",
weatherEndpoint: "weather",
appendLocationNameToHeader: true,
@@ -64,7 +67,7 @@ Module.register("currentweather",{
},
},
// create a variable for the first upcoming calendaar event. Used if no location is specified.
// 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.
@@ -84,7 +87,7 @@ Module.register("currentweather",{
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 yiur own module including translations, check out the documentation.
// If you're trying to build your own module including translations, check out the documentation.
return false;
},
@@ -104,7 +107,7 @@ Module.register("currentweather",{
this.indoorTemperature = null;
this.indoorHumidity = null;
this.weatherType = null;
this.feelsLike = null;
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
@@ -209,9 +212,13 @@ Module.register("currentweather",{
}
}
if (this.config.decimalSymbol === "") {
this.config.decimalSymbol = ".";
}
var temperature = document.createElement("span");
temperature.className = "bright";
temperature.innerHTML = " " + this.temperature + "&deg;" + degreeLabel;
temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + "&deg;" + degreeLabel;
large.appendChild(temperature);
if (this.config.showIndoorTemperature && this.indoorTemperature) {
@@ -221,7 +228,7 @@ Module.register("currentweather",{
var indoorTemperatureElem = document.createElement("span");
indoorTemperatureElem.className = "bright";
indoorTemperatureElem.innerHTML = " " + this.indoorTemperature + "&deg;" + degreeLabel;
indoorTemperatureElem.innerHTML = " " + this.indoorTemperature.replace(".", this.config.decimalSymbol) + "&deg;" + degreeLabel;
large.appendChild(indoorTemperatureElem);
}
@@ -237,6 +244,19 @@ Module.register("currentweather",{
}
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") + " " + this.feelsLike + "&deg;" + degreeLabel;
small.appendChild(feelsLike);
wrapper.appendChild(small);
}
return wrapper;
},
@@ -273,11 +293,11 @@ Module.register("currentweather",{
}
if (notification === "INDOOR_TEMPERATURE") {
this.indoorTemperature = this.roundValue(payload);
this.updateDom(self.config.animationSpeed);
this.updateDom(this.config.animationSpeed);
}
if (notification === "INDOOR_HUMIDITY") {
this.indoorHumidity = this.roundValue(payload);
this.updateDom(self.config.animationSpeed);
this.updateDom(this.config.animationSpeed);
}
},
@@ -360,13 +380,71 @@ Module.register("currentweather",{
this.humidity = parseFloat(data.main.humidity);
this.temperature = this.roundValue(data.main.temp);
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":
var tc = this.temperature - 273.15;
tempInF = 1.8 * tc + 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":
var tc = windChillInC + 273.15;
this.feelsLike = tc.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];
@@ -492,4 +570,5 @@ Module.register("currentweather",{
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -16,7 +16,8 @@ var defaultModules = [
"helloworld",
"newsfeed",
"weatherforecast",
"updatenotification"
"updatenotification",
"weather"
];
/*************** DO NOT EDIT THE LINE BELOW ***************/

View File

@@ -14,10 +14,11 @@ Module.register("helloworld",{
text: "Hello World!"
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
wrapper.innerHTML = this.config.text;
return wrapper;
getTemplate: function () {
return "helloworld.njk"
},
getTemplateData: function () {
return this.config
}
});

View File

@@ -0,0 +1,5 @@
<!--
Use ` | safe` to allow html tages within the text string.
https://mozilla.github.io/nunjucks/templating.html#autoescaping
-->
<div>{{text | safe}}</div>

View File

@@ -2,6 +2,10 @@
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.
## Screenshot
- News Feed Screenshot using the NYT
![NYT News Feed Screenshot](newsfeed_screenshot.png)
## Using the module
### Configuration
@@ -39,8 +43,9 @@ MagicMirror's [notification mechanism](https://github.com/MichMich/MagicMirror/t
| ----------------------- | -----------
| `ARTICLE_NEXT` | Shows the next news title (hiding the summary or previously fully displayed article)
| `ARTICLE_PREVIOUS` | Shows the previous news title (hiding the summary or previously fully displayed article)
| `ARTICLE_MORE_DETAILS` | When received the _first time_, shows the corresponding description of the currently displayed news title. <br> The module expects that the module's configuration option `showDescription` is set to `false` (default value). <br><br> When received a _second consecutive time_, shows the full news article in an IFRAME. <br> This requires that the news page can be embedded in an IFRAME, e.g. doesn't have the HTTP response header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) set to e.g. `DENY`.
| `ARTICLE_MORE_DETAILS` | When received the _first time_, shows the corresponding description of the currently displayed news title. <br> The module expects that the module's configuration option `showDescription` is set to `false` (default value). <br><br> When received a _second consecutive time_, shows the full news article in an IFRAME. <br> This requires that the news page can be embedded in an IFRAME, e.g. doesn't have the HTTP response header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) set to e.g. `DENY`.<br><br>When received the _next consecutive times_, reloads the page and scrolls down by `scrollLength` pixels to paginate through the article.
| `ARTICLE_LESS_DETAILS` | Hides the summary or full news article and only displays the news title of the currently viewed news item.
| `ARTICLE_TOGGLE_FULL` | Toogles article in fullscreen.
Note the payload of the sent notification event is ignored.
@@ -57,25 +62,30 @@ The third party [MMM-Gestures](https://github.com/thobach/MMM-Gestures) module s
The following properties can be configured:
| Option | Description
| ----------------- | -----------
| `feeds` | An array of feed urls that will be used as source. <br> More info about this object can be found below. <br> **Default value:** `[{ title: "New York Times", url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", encoding: "UTF-8" }]`
| `showSourceTitle` | Display the title of the source. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showPublishDate` | Display the publish date of an headline. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showDescription` | Display the description of an item. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `wrapTitle` | Wrap the title of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `wrapDescription` | Wrap the description of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `hideLoading` | Hide module instead of showing LOADING status. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `reloadInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `300000` (5 minutes)
| `updateInterval` | How often do you want to display a new headline? (Milliseconds) <br><br> **Possible values:**`1000` - `60000` <br> **Default value:** `10000` (10 seconds)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `2500` (2.5 seconds)
| `maxNewsItems` | Total amount of news items to cycle through. (0 for unlimited) <br><br> **Possible values:**`0` - `...` <br> **Default value:** `0`
| `ignoreOldItems` | Ignore news items that are outdated. <br><br> **Possible values:**`true` or `false <br> **Default value:** `false`
| `ignoreOlderThan` | How old should news items be before they are considered outdated? (Milliseconds) <br><br> **Possible values:**`1` - `...` <br> **Default value:** `86400000` (1 day)
| `removeStartTags` | Some newsfeeds feature tags at the **beginning** of their titles or descriptions, such as _[VIDEO]_. This setting allows for the removal of specified tags from the beginning of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
| `startTags` | List the tags you would like to have removed at the beginning of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
| `removeEndTags` | Remove specified tags from the **end** of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
| `endTags` | List the tags you would like to have removed at the end of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
| Option | Description
| ------------------ | -----------
| `feeds` | An array of feed urls that will be used as source. <br> More info about this object can be found below. <br> **Default value:** `[{ title: "New York Times", url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", encoding: "UTF-8" }]`<br>You can add `reloadInterval` option to set particular reloadInterval to a feed.
| `showSourceTitle` | Display the title of the source. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showPublishDate` | Display the publish date of an headline. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showDescription` | Display the description of an item. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `wrapTitle` | Wrap the title of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `wrapDescription` | Wrap the description of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `truncDescription` | Truncate description? <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `lengthDescription`| How many characters to be displayed for a truncated description? <br><br> **Possible values:** `1` - `500` <br> **Default value:** `400`
| `hideLoading` | Hide module instead of showing LOADING status. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `reloadInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `300000` (5 minutes)
| `updateInterval` | How often do you want to display a new headline? (Milliseconds) <br><br> **Possible values:**`1000` - `60000` <br> **Default value:** `10000` (10 seconds)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `2500` (2.5 seconds)
| `maxNewsItems` | Total amount of news items to cycle through. (0 for unlimited) <br><br> **Possible values:**`0` - `...` <br> **Default value:** `0`
| `ignoreOldItems` | Ignore news items that are outdated. <br><br> **Possible values:**`true` or `false` <br> **Default value:** `false`
| `ignoreOlderThan` | How old should news items be before they are considered outdated? (Milliseconds) <br><br> **Possible values:**`1` - `...` <br> **Default value:** `86400000` (1 day)
| `removeStartTags` | Some newsfeeds feature tags at the **beginning** of their titles or descriptions, such as _[VIDEO]_. This setting allows for the removal of specified tags from the beginning of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
| `startTags` | List the tags you would like to have removed at the beginning of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
| `removeEndTags` | Remove specified tags from the **end** of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
| `endTags` | List the tags you would like to have removed at the end of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
| `prohibitedWords` | Remove news feed item if one of these words is found anywhere in the title (case insensitive and greedy matching) <br><br> **Possible values:** `['word']` or `['word1','word2',...]`
| `scrollLength` | Scrolls the full news article page by a given number of pixels when a `ARTICLE_MORE_DETAILS` notification is received and the full news article is already displayed.<br><br> **Possible values:** `1` or `10000` <br> **Default value:** `500`
| `logFeedWarnings` | Log warnings when there is an error parsing a news article. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
The `feeds` property contains an array with multiple objects. These objects have the following properties:

View File

@@ -14,9 +14,10 @@ var iconv = require("iconv-lite");
*
* attribute url string - URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds.
* attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article.
*/
var Fetcher = function(url, reloadInterval, encoding) {
var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
var self = this;
if (reloadInterval < 1000) {
reloadInterval = 1000;
@@ -45,13 +46,13 @@ var Fetcher = function(url, reloadInterval, encoding) {
var title = item.title;
var description = item.description || item.summary || item.content || "";
var pubdate = item.pubdate || item.published || item.updated;
var pubdate = item.pubdate || item.published || item.updated || item["dc:date"];
var url = item.url || item.link || "";
if (title && pubdate) {
var regex = /(<([^>]+)>)/ig;
description = description.replace(regex, "");
description = description.toString().replace(regex, "");
items.push({
title: title,
@@ -60,18 +61,17 @@ var Fetcher = function(url, reloadInterval, encoding) {
url: url,
});
} else {
// console.log("Can't parse feed item:");
// console.log(item);
// console.log('Title: ' + title);
// console.log('Description: ' + description);
// console.log('Pubdate: ' + pubdate);
} else if (logFeedWarnings) {
console.log("Can't parse feed item:");
console.log(item);
console.log("Title: " + title);
console.log("Description: " + description);
console.log("Pubdate: " + pubdate);
}
});
parser.on("end", function() {
parser.on("end", function() {
//console.log("end parsing - " + url);
self.broadcastItems();
scheduleTimer();
});
@@ -83,7 +83,9 @@ var Fetcher = function(url, reloadInterval, encoding) {
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
headers = {"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"}
headers = {"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)",
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
"Pragma": "no-cache"}
request({uri: url, encoding: null, headers: headers})
.on("error", function(error) {

View File

@@ -23,8 +23,10 @@ Module.register("newsfeed",{
showDescription: false,
wrapTitle: true,
wrapDescription: true,
truncDescription: true,
lengthDescription: 400,
hideLoading: false,
reloadInterval: 5 * 60 * 1000, // every 5 minutes
reloadInterval: 5 * 60 * 1000, // every 5 minutes
updateInterval: 10 * 1000,
animationSpeed: 2.5 * 1000,
maxNewsItems: 0, // 0 for unlimited
@@ -33,8 +35,10 @@ Module.register("newsfeed",{
removeStartTags: "",
removeEndTags: "",
startTags: [],
endTags: []
endTags: [],
prohibitedWords: [],
scrollLength: 500,
logFeedWarnings: false
},
// Define required scripts.
@@ -60,9 +64,11 @@ Module.register("newsfeed",{
this.newsItems = [];
this.loaded = false;
this.activeItem = 0;
this.scrollPosition = 0;
this.registerFeeds();
this.isShowingDescription = this.config.showDescription;
},
// Override socket notification handler.
@@ -84,7 +90,7 @@ Module.register("newsfeed",{
if (this.config.feedUrl) {
wrapper.className = "small bright";
wrapper.innerHTML = "The configuration options for the newsfeed module have changed.<br>Please check the documentation.";
wrapper.innerHTML = this.translate("configuration_changed");
return wrapper;
}
@@ -117,22 +123,22 @@ Module.register("newsfeed",{
//Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags == "title" || this.config.removeStartTags == "both") {
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
for (f=0; f<this.config.startTags.length;f++) {
if (this.newsItems[this.activeItem].title.slice(0,this.config.startTags[f].length) == this.config.startTags[f]) {
if (this.newsItems[this.activeItem].title.slice(0,this.config.startTags[f].length) === this.config.startTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(this.config.startTags[f].length,this.newsItems[this.activeItem].title.length);
}
}
}
if (this.config.removeStartTags == "description" || this.config.removeStartTags == "both") {
if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
if (this.config.showDescription) {
if (this.isShowingDescription) {
for (f=0; f<this.config.startTags.length;f++) {
if (this.newsItems[this.activeItem].description.slice(0,this.config.startTags[f].length) == this.config.startTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length,this.newsItems[this.activeItem].description.length);
if (this.newsItems[this.activeItem].description.slice(0,this.config.startTags[f].length) === this.config.startTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length,this.newsItems[this.activeItem].description.length);
}
}
}
@@ -143,14 +149,14 @@ Module.register("newsfeed",{
if (this.config.removeEndTags) {
for (f=0; f<this.config.endTags.length;f++) {
if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length)==this.config.endTags[f]) {
if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length)===this.config.endTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(0,-this.config.endTags[f].length);
}
}
if (this.config.showDescription) {
if (this.isShowingDescription) {
for (f=0; f<this.config.endTags.length;f++) {
if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length)==this.config.endTags[f]) {
if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length)===this.config.endTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(0,-this.config.endTags[f].length);
}
}
@@ -165,23 +171,26 @@ Module.register("newsfeed",{
wrapper.appendChild(title);
}
if (this.config.showDescription) {
if (this.isShowingDescription) {
var description = document.createElement("div");
description.className = "small light" + (!this.config.wrapDescription ? " no-wrap" : "");
description.innerHTML = this.newsItems[this.activeItem].description;
var txtDesc = this.newsItems[this.activeItem].description;
description.innerHTML = (this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc);
wrapper.appendChild(description);
}
if (this.config.showFullArticle) {
var fullArticle = document.createElement("iframe");
fullArticle.className = "";
fullArticle.style.width = "100%";
fullArticle.style.width = "100vw";
// very large height value to allow scrolling
fullArticle.height = "3000";
fullArticle.style.height = "3000";
fullArticle.style.top = "0";
fullArticle.style.left = "0";
fullArticle.style.position = "fixed";
fullArticle.height = window.innerHeight;
fullArticle.style.border = "none";
fullArticle.src = this.newsItems[this.activeItem].url;
fullArticle.src = typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
fullArticle.style.zIndex = 1;
wrapper.appendChild(fullArticle);
}
@@ -241,6 +250,18 @@ Module.register("newsfeed",{
if(this.config.maxNewsItems > 0) {
newsItems = newsItems.slice(0, this.config.maxNewsItems);
}
if(this.config.prohibitedWords.length > 0) {
newsItems = newsItems.filter(function(value){
for (var i=0; i < this.config.prohibitedWords.length; i++) {
if (value["title"].toLowerCase().indexOf(this.config.prohibitedWords[i].toLowerCase()) > -1) {
return false;
}
}
return true;
}, this);
}
this.newsItems = newsItems;
},
@@ -304,8 +325,12 @@ Module.register("newsfeed",{
},
resetDescrOrFullArticleAndTimer: function() {
this.config.showDescription = false;
this.isShowingDescription = this.config.showDescription;
this.config.showFullArticle = false;
this.scrollPosition = 0;
// reset bottom bar alignment
document.getElementsByClassName("region bottom bar")[0].style.bottom = "0";
document.getElementsByClassName("region bottom bar")[0].style.top = "inherit";
if(!timer){
this.scheduleUpdateInterval();
}
@@ -313,7 +338,7 @@ Module.register("newsfeed",{
notificationReceived: function(notification, payload, sender) {
Log.info(this.name + " - received notification: " + notification);
if(notification == "ARTICLE_NEXT"){
if(notification === "ARTICLE_NEXT"){
var before = this.activeItem;
this.activeItem++;
if (this.activeItem >= this.newsItems.length) {
@@ -322,7 +347,7 @@ Module.register("newsfeed",{
this.resetDescrOrFullArticleAndTimer();
Log.info(this.name + " - going from article #" + before + " to #" + this.activeItem + " (of " + this.newsItems.length + ")");
this.updateDom(100);
} else if(notification == "ARTICLE_PREVIOUS"){
} else if(notification === "ARTICLE_PREVIOUS"){
var before = this.activeItem;
this.activeItem--;
if (this.activeItem < 0) {
@@ -333,20 +358,52 @@ Module.register("newsfeed",{
this.updateDom(100);
}
// if "more details" is received the first time: show article summary, on second time show full article
else if(notification == "ARTICLE_MORE_DETAILS"){
this.config.showDescription = !this.config.showDescription;
this.config.showFullArticle = !this.config.showDescription;
clearInterval(timer);
timer = null;
Log.info(this.name + " - showing " + this.config.showDescription ? "article description" : "full article");
this.updateDom(100);
} else if(notification == "ARTICLE_LESS_DETAILS"){
else if(notification === "ARTICLE_MORE_DETAILS"){
// full article is already showing, so scrolling down
if(this.config.showFullArticle === true){
this.scrollPosition += this.config.scrollLength;
window.scrollTo(0, this.scrollPosition);
Log.info(this.name + " - scrolling down");
Log.info(this.name + " - ARTICLE_MORE_DETAILS, scroll position: " + this.config.scrollLength);
}
else {
this.showFullArticle();
}
} else if(notification === "ARTICLE_SCROLL_UP"){
if(this.config.showFullArticle === true){
this.scrollPosition -= this.config.scrollLength;
window.scrollTo(0, this.scrollPosition);
Log.info(this.name + " - scrolling up");
Log.info(this.name + " - ARTICLE_SCROLL_UP, scroll position: " + this.config.scrollLength);
}
} else if(notification === "ARTICLE_LESS_DETAILS"){
this.resetDescrOrFullArticleAndTimer();
Log.info(this.name + " - showing only article titles again");
this.updateDom(100);
} else if (notification === "ARTICLE_TOGGLE_FULL"){
if (this.config.showFullArticle){
this.activeItem++;
this.resetDescrOrFullArticleAndTimer();
} else {
this.showFullArticle();
}
} else {
Log.info(this.name + " - unknown notification, ignoring: " + notification);
}
},
showFullArticle: function() {
this.isShowingDescription = !this.isShowingDescription;
this.config.showFullArticle = !this.isShowingDescription;
// make bottom bar align to top to allow scrolling
if(this.config.showFullArticle === true){
document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit";
document.getElementsByClassName("region bottom bar")[0].style.top = "-90px";
}
clearInterval(timer);
timer = null;
Log.info(this.name + " - showing " + this.isShowingDescription ? "article description" : "full article");
this.updateDom(100);
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -36,7 +36,7 @@ module.exports = NodeHelper.create({
var url = feed.url || "";
var encoding = feed.encoding || "UTF-8";
var reloadInterval = config.reloadInterval || 5 * 60 * 1000;
var reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000;
if (!validUrl.isUri(url)) {
self.sendSocketNotification("INCORRECT_URL", url);
@@ -46,7 +46,7 @@ module.exports = NodeHelper.create({
var fetcher;
if (typeof self.fetchers[url] === "undefined") {
console.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval);
fetcher = new Fetcher(url, reloadInterval, encoding);
fetcher = new Fetcher(url, reloadInterval, encoding, config.logFeedWarnings);
fetcher.onReceive(function(fetcher) {
self.broadcastFeeds();

View File

@@ -0,0 +1,3 @@
{
"configuration_changed": "Die Konfigurationsoptionen für das Newsfeed-Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation."
}

View File

@@ -0,0 +1,3 @@
{
"configuration_changed": "The configuration options for the newsfeed module have changed.\nPlease check the documentation."
}

View File

@@ -0,0 +1,3 @@
{
"configuration_changed": "Las opciones de configuración para el módulo de suministro de noticias han cambiado. \nVerifique la documentación."
}

View File

@@ -0,0 +1,3 @@
{
"configuration_changed": "Les options de configuration du module newsfeed ont changé. \nVeuillez consulter la documentation."
}

View File

@@ -64,7 +64,10 @@ module.exports = NodeHelper.create({
sg.git.fetch().status(function(err, data) {
data.module = sg.module;
if (!err) {
self.sendSocketNotification("STATUS", data);
sg.git.log({"-1": null}, function(err, data2) {
data.hash = data2.latest.hash;
self.sendSocketNotification("STATUS", data);
});
}
});
});

View File

@@ -34,6 +34,17 @@ Module.register("updatenotification", {
}
},
diffLink: function(text) {
var localRef = this.status.hash;
var remoteRef = this.status.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");
@@ -47,20 +58,27 @@ Module.register("updatenotification", {
icon.innerHTML = "&nbsp;";
message.appendChild(icon);
var updateInfoKeyName = this.status.behind == 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
var subtextHtml = this.translate(updateInfoKeyName, {
COMMIT_COUNT: this.status.behind,
BRANCH_NAME: this.status.current
});
var text = document.createElement("span");
if (this.status.module == "default") {
text.innerHTML = this.translate("UPDATE_NOTIFICATION");
subtextHtml = this.diffLink(subtextHtml);
} else {
text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE").replace("MODULE_NAME", this.status.module);
text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", {
MODULE_NAME: this.status.module
});
}
message.appendChild(text);
wrapper.appendChild(message);
var subtext = document.createElement("div");
subtext.innerHTML = this.translate("UPDATE_INFO")
.replace("COMMIT_COUNT", this.status.behind + " " + ((this.status.behind == 1) ? "commit" : "commits"))
.replace("BRANCH_NAME", this.status.current);
subtext.innerHTML = subtextHtml;
subtext.className = "xsmall dimmed";
wrapper.appendChild(subtext);
}

View File

@@ -0,0 +1,98 @@
# Weather Module
This module is aimed to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fullfil both purposes.
The biggest cange is the use of weather providers. This way we are not bound to one API source. And users can choose which API they want to use as their source.
The module is in a very early stage, and needs a lot of work. It's API isn't set in stone, so keep that in mind when you want to contribute.
## Example
![Current Weather Screenshot](current.png) ![Weather Forecast Screenshot](forecast.png)
## Usage
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: "weather",
position: "top_right",
config: {
// See 'Configuration options' for more information.
type: 'current'
}
}
]
````
## Configuration options
The following properties can be configured:
### General options
| Option | Description
| ---------------------------- | -----------
| `weatherProvider` | Which weather provider should be used. <br><br> **Possible values:** `openweathermap` and `darksky` <br> **Default value:** `openweathermap`
| `type` | Which type of weather data should be displayed. <br><br> **Possible values:** `current` and `forecast` <br> **Default value:** `current`
| `units` | What units to use. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` =Fahrenheit <br> **Default value:** `config.units`
| `roundTemp` | Round temperature value to nearest integer. <br><br> **Possible values:** `true` (round to integer) or `false` (display exact value with decimal point) <br> **Default value:** `false`
| `degreeLabel` | Show the degree label for your chosen units (Metric = C, Imperial = F, Kelvins = K). <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `updateInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `600000` (10 minutes)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `1000` (1 second)
| `timeFormat` | Use 12 or 24 hour format. <br><br> **Possible values:** `12` or `24` <br> **Default value:** uses value of _config.timeFormat_
| `showPeriod` | Show the period (am/pm) with 12 hour format <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showPeriodUpper` | Show the period (AM/PM) with 12 hour format as uppercase <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `lang` | The language of the days. <br><br> **Possible values:** `en`, `nl`, `ru`, etc ... <br> **Default value:** uses value of _config.language_
| `decimalSymbol` | The decimal symbol to use.<br><br> **Possible values:** `.`, `,` or any other symbol.<br> **Default value:** `.`
| `initialLoadDelay` | The initial delay before loading. If you have multiple modules that use the same API key, you might want to delay one of the requests. (Milliseconds) <br><br> **Possible values:** `1000` - `5000` <br> **Default value:** `0`
| `appendLocationNameToHeader` | If set to `true`, the returned location name will be appended to the header of the module, if the header is enabled. This is mainly intresting when using calender based weather. <br><br> **Default value:** `true`
| `calendarClass` | The class for the calender module to base the event based weather information on. <br><br> **Default value:** `'calendar'`
#### Current weather options
| Option | Description
| ---------------------------- | -----------
| `onlyTemp` | Show only current Temperature and weather icon without windspeed, sunset, sunrise time and feels like. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `useBeaufort` | Pick between using the Beaufort scale for wind speed or using the default units. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showWindDirection` | Show the wind direction next to the wind speed. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showWindDirectionAsArrow` | Show the wind direction as an arrow instead of abbreviation <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showHumidity` | Show the current humidity <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showIndoorTemperature` | If you have another module that emits the `INDOOR_TEMPERATURE` notification, the indoor temperature will be displayed <br> **Default value:** `false`
| `showIndoorHumidity` | If you have another module that emits the `INDOOR_HUMIDITY` notification, the indoor humidity will be displayed <br> **Default value:** `false`
| `showFeelsLike` | Shows the Feels like temperature weather. <br><br> **Possible values:**`true` or `false`<br>**Default value:** `true`
#### Weather forecast options
| Option | Description
| ---------------------------- | -----------
| `tableClass` | The class for the forecast table. <br><br> **Default value:** `'small'`
| `colored` | If set to `true`, the min and max temperature are color coded. <br><br> **Default value:** `false`
| `showRainAmount` | Show the amount of rain in the forecast <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
### Openweathermap options
| Option | Description
| ---------------------------- | -----------
| `apiVersion` | The OpenWeatherMap API version to use. <br><br> **Default value:** `2.5`
| `apiBase` | The OpenWeatherMap base URL. <br><br> **Default value:** `'http://api.openweathermap.org/data/'`
| `weatherEndpoint` | The OpenWeatherMap API endPoint. <br><br> **Possible values:** `/weather` or `/forecast/daily` <br> **Default value:** `'/weather'`
| `locationID` | Location ID from [OpenWeatherMap](https://openweathermap.org/find) **This will override anything you put in location.** <br> Leave blank if you want to use location. <br> **Example:** `1234567` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `location` | The location used for weather information. <br><br> **Example:** `'Amsterdam,Netherlands'` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `apiKey` | The [OpenWeatherMap](https://home.openweathermap.org) API key, which can be obtained by creating an OpenWeatherMap account. <br><br> This value is **REQUIRED**
### Darksky options
| Option | Description
| ---------------------------- | -----------
| `apiBase` | The DarkSky base URL. The darksky api has disabled [cors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), therefore a proxy is required. <br><br> **Possible value:** `'https://cors-anywhere.herokuapp.com/https://api.darksky.net'` <br> This value is **REQUIRED**
| `weatherEndpoint` | The DarkSky API endPoint. <br><br> **Possible values:** `/forecast` <br> This value is **REQUIRED**
| `apiKey` | The [DarkSky](https://darksky.net/dev/register) API key, which can be obtained by creating an DarkSky account. <br><br> This value is **REQUIRED**
| `lat` | The geo coordinate latitude. <br><br> This value is **REQUIRED**
| `lon` | The geo coordinate longitude. <br><br> This value is **REQUIRED**
## API Provider Development
If you want to add another API provider checkout the [Guide](providers).

View File

@@ -0,0 +1,68 @@
{% if current %}
{% if not config.onlyTemp %}
<div class="normal medium">
<span class="wi wi-strong-wind dimmed"></span>
<span>
{% if config.useBeaufort %}
{{current.beaufortWindSpeed() | round}}
{% else %}
{{current.windSpeed | round}}
{% endif %}
{% if config.showWindDirection %}
<sup>
{% if config.showWindDirectionAsArrow %}
<i class="fa fa-long-arrow-up" style="transform:rotate({{current.windDirection}}deg);"></i>
{% else %}
{{current.cardinalWindDirection() | translate}}
{% endif %}
&nbsp;
</sup>
{% endif %}
</span>
{% if config.showHumidity and current.humidity %}
<span>{{ current.humidity }}</span><sup>&nbsp;<i class="wi wi-humidity humidityIcon"></i></sup>
{% endif %}
<span class="wi dimmed wi-{{current.nextSunAction()}}"></span>
<span>
{% if current.nextSunAction() == "sunset" %}
{{current.sunset | formatTime}}
{% else %}
{{current.sunrise | formatTime}}
{% endif %}
</span>
</div>
{% endif %}
<div class="large light">
<span class="wi weathericon wi-{{current.weatherType}}"></span>
<span class="bright">
{{current.temperature | roundValue | unit("temperature")}}
</span>
{% if config.showIndoorTemperature and indoor.temperature %}
<span class="fa fa-home"></span>
<span class="bright">
{{indoor.temperature | roundValue | unit("temperature")}}
</span>
{% endif %}
{% if config.showIndoorHumidity and indoor.humidity %}
<span class="fa fa-tint"></span>
<span class="bright">
{{indoor.humidity | roundValue}}%
</span>
{% endif %}
</div>
{% if config.showFeelsLike and not config.onlyTemp %}
<div class="normal medium">
<span class="dimmed">
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") }}
</span>
</div>
{% endif %}
{% else %}
<div class="dimmed light small">
{{"LOADING" | translate}}
</div>
{% endif %}
<!-- Unclomment the line below to see the contents of the `current` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{current | dump}}</div> -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -0,0 +1,26 @@
{% if forecast %}
<table class="{{config.tableClass}}">
{% for f in forecast %}
<tr {% if config.colored %}class="colored"{% endif %}>
<td class="day">{{f.date.format('ddd')}}</td>
<td class="bright weather-icon"><span class="wi weathericon wi-{{f.weatherType}}"></span></td>
<td class="align-right bright max-temp">
{{f.maxTemperature | roundValue | unit("temperature")}}
</td>
<td class="align-right min-temp">
{{f.minTemperature | roundValue | unit("temperature")}}
</td>
{% if config.showRainAmount %}
<td class="align-right bright rain">
{{f.rain | unit("rain")}}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% else %}
{{"LOADING" | translate}}
{% endif %}
<!-- Unclomment the line below to see the contents of the `current` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{forecast | dump}}</div> -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,129 @@
# MagicMirror² Weather Module Weather Provider Development Documentation
This document describes the way to develop your own MagicMirror² weather module weather provider.
Table of Contents:
- The weather provider file: yourprovider.js
- [Weather provider methods to implement](#weather-provider-methods-to-implement)
- [Weather Provider instance methods](#weather-provider-instance-methods)
- [WeatherObject](#weatherobject)
---
## The weather provider file: yourprovider.js
This is the script in which the weather provider will be defined. In it's most simple form, the weather provider must implement the following:
````javascript
WeatherProvider.register("yourprovider", {
providerName: "YourProvider",
fetchCurrentWeather() {},
fetchWeatherForecast() {}
});
````
### Weather provider methods to implement
#### `fetchCurrentWeather()`
This method is called when the weather module tries to fetch the current weather of your provider. The implementation of this method is required.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the current weather information (as a [WeatherObject](#weatherobject)) needs to be set with `this.setCurrentWeather(currentWeather);`.
It will then automatically refresh the module DOM with the new data.
#### `fetchWeatherForecast()`
This method is called when the weather module tries to fetch the weather weather of your provider. The implementation of this method is required.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setCurrentWeather(forecast);`.
It will then automatically refresh the module DOM with the new data.
### Weather Provider instance methods
#### `init()`
Called when a weather provider is initialized.
#### `setConfig(config)`
Called to set the config, this config is the same as the weather module's config.
#### `start()`
Called when the weather provider is about to start.
#### `currentWeather()`
This returns a WeatherDay object for the current weather.
#### `weatherForecast()`
This returns an array of WeatherDay objects for the weather forecast.
#### `fetchedLocation()`
This returns the name of the fetched location or an empty string.
#### `setCurrentWeather(currentWeatherObject)`
Set the currentWeather and notify the delegate that new information is available.
#### `setWeatherForecast(weatherForecastArray)`
Set the weatherForecastArray and notify the delegate that new information is available.
#### `setFetchedLocation(name)`
Set the fetched location name.
#### `updateAvailable()`
Notify the delegate that new weather is available.
#### `fetchData(url, method, data)`
A convinience function to make requests. It returns a promise.
### WeatherObject
| Property | Type | Value/Unit |
| --- | --- | --- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric` and `imperial` |
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
| windSpeed |`number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
| windDirection |`number` | Direction of the wind in degrees. |
| sunrise |`object` | [Moment.js](https://momentjs.com/) object of sunrise. |
| sunset |`object` | [Moment.js](https://momentjs.com/) object of sunset. |
| temperature | `number` | Current temperature |
| minTemperature | `number` | Lowest temperature of the day. |
| maxTemperature | `number` | Highest temperature of the day. |
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| humidity | `number` | Percentage of humidity |
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
#### Current weather
For the current weather object the following properties are required:
- humidity
- sunrise
- sunset
- temperature
- units
- weatherType
- windDirection
- windSpeed
#### Weather forecast
For the forecast weather object the following properties are required:
- date
- maxTemperature
- minTemperature
- rain
- units
- weatherType

View File

@@ -0,0 +1,113 @@
/* global WeatherProvider, WeatherDay */
/* Magic Mirror
* Module: Weather
* Provider: Dark Sky
*
* By Nicholas Hubbard https://github.com/nhubbard
* MIT Licensed
*
* This class is a provider for Dark Sky.
*/
WeatherProvider.register("darksky", {
// Set the name of the provider.
// Not strictly required, but helps for debugging.
providerName: "Dark Sky",
units: {
imperial: 'us',
metric: 'si'
},
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
if(!data || !data.currently || typeof data.currently.temperature === "undefined") {
// No usable data?
return;
}
const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
}).catch(function(request) {
Log.error("Could not load data ... ", request);
});
},
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
if(!data || !data.daily || !data.daily.data.length) {
// No usable data?
return;
}
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
this.setWeatherForecast(forecast);
}).catch(function(request) {
Log.error("Could not load data ... ", request);
});
},
// Create a URL from the config and base URL.
getUrl() {
const units = this.units[this.config.units] || "auto";
return `${this.config.apiBase}${this.config.weatherEndpoint}/${this.config.apiKey}/${this.config.lat},${this.config.lon}?units=${units}&lang=${this.config.lang}`;
},
// Implement WeatherDay generator.
generateWeatherDayFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units);
currentWeather.date = moment();
currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity);
currentWeather.temperature = parseFloat(currentWeatherData.currently.temperature);
currentWeather.windSpeed = parseFloat(currentWeatherData.currently.windSpeed);
currentWeather.windDirection = currentWeatherData.currently.windBearing;
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.currently.icon);
currentWeather.sunrise = moment(currentWeatherData.daily.data[0].sunriseTime, "X");
currentWeather.sunset = moment(currentWeatherData.daily.data[0].sunsetTime, "X");
return currentWeather;
},
generateWeatherObjectsFromForecast(forecasts) {
const days = [];
for (const forecast of forecasts) {
const weather = new WeatherObject(this.config.units);
weather.date = moment(forecast.time, "X");
weather.minTemperature = forecast.temperatureMin;
weather.maxTemperature = forecast.temperatureMax;
weather.weatherType = this.convertWeatherType(forecast.icon);
if (this.config.units === "metric" && !isNaN(forecast.precipAccumulation)) {
weather.rain = forecast.precipAccumulation * 10;
} else {
weather.rain = forecast.precipAccumulation;
}
days.push(weather);
}
return days;
},
// Map icons from Dark Sky to our icons.
convertWeatherType(weatherType) {
const weatherTypes = {
"clear-day": "day-sunny",
"clear-night": "night-clear",
"rain": "rain",
"snow": "snow",
"sleet": "snow",
"wind": "wind",
"fog": "fog",
"cloudy": "cloudy",
"partly-cloudy-day": "day-cloudy",
"partly-cloudy-night": "night-cloudy"
};
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
}
});

View File

@@ -0,0 +1,165 @@
/* global WeatherProvider, WeatherObject */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* This class is the blueprint for a weather provider.
*/
WeatherProvider.register("openweathermap", {
// Set the name of the provider.
// This isn't strictly necessary, since it will fallback to the provider identifier
// But for debugging (and future alerts) it would be nice to have the real name.
providerName: "OpenWeatherMap",
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.setFetchedLocation(`${data.name}, ${data.sys.country}`);
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
if (!data || !data.list || !data.list.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.setFetchedLocation(`${data.city.name}, ${data.city.country}`);
const forecast = this.generateWeatherObjectsFromForecast(data.list);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
},
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
/*
* Gets the complete url for the request
*/
getUrl() {
return this.config.apiBase + this.config.apiVersion + this.config.weatherEndpoint + this.getParams();
},
/*
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units);
currentWeather.humidity = currentWeatherData.main.humidity;
currentWeather.temperature = currentWeatherData.main.temp;
currentWeather.windSpeed = currentWeatherData.wind.speed;
currentWeather.windDirection = currentWeatherData.wind.deg;
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.weather[0].icon);
currentWeather.sunrise = moment(currentWeatherData.sys.sunrise, "X");
currentWeather.sunset = moment(currentWeatherData.sys.sunset, "X");
return currentWeather;
},
/*
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
const days = [];
for (const forecast of forecasts) {
const weather = new WeatherObject(this.config.units);
weather.date = moment(forecast.dt, "X");
weather.minTemperature = forecast.temp.min;
weather.maxTemperature = forecast.temp.max;
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
if (this.config.units === "imperial" && !isNaN(forecast.rain)) {
weather.rain = forecast.rain / 25.4
} else {
weather.rain = forecast.rain;
}
days.push(weather);
}
return days;
},
/*
* Convert the OpenWeatherMap icons to a more usable name.
*/
convertWeatherType(weatherType) {
const weatherTypes = {
"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"
};
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
},
/* getParams(compliments)
* Generates an url with api parameters based on the config.
*
* return String - URL params.
*/
getParams() {
let 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.apiKey;
return params;
}
});

View File

@@ -0,0 +1,45 @@
.weather .weathericon,
.weather .fa-home {
font-size: 75%;
line-height: 65px;
display: inline-block;
-ms-transform: translate(0, -3px); /* IE 9 */
-webkit-transform: translate(0, -3px); /* Safari */
transform: translate(0, -3px);
}
.weather .humidityIcon {
padding-right: 4px;
}
.weather .humidity-padding {
padding-bottom: 6px;
}
.weather .day {
padding-left: 0;
padding-right: 25px;
}
.weather .weather-icon {
padding-right: 30px;
text-align: center;
}
.weather .min-temp {
padding-left: 20px;
padding-right: 0;
}
.weather .rain {
padding-left: 20px;
padding-right: 0;
}
.weather tr.colored .min-temp {
color: #bcddff;
}
.weather tr.colored .max-temp {
color: #ff8e99;
}

View File

@@ -0,0 +1,212 @@
/* global Module, WeatherProvider */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("weather",{
// Default module config.
defaults: {
updateInterval: 10 * 60 * 1000,
weatherProvider: "openweathermap",
roundTemp: false,
type: "current", //current, forecast
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,
lang: config.language,
showHumidity: false,
degreeLabel: false,
showIndoorTemperature: false,
showIndoorHumidity: false,
initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
apiVersion: "2.5",
apiBase: "http://api.openweathermap.org/data/",
weatherEndpoint: "/weather",
appendLocationNameToHeader: true,
calendarClass: "calendar",
tableClass: "small",
onlyTemp: false,
showRainAmount: true,
colored: false,
showFeelsLike: true
},
// Module properties.
weatherProvider: null,
// Define required scripts.
getStyles: function() {
return ["font-awesome.css", "weather-icons.css", "weather.css"];
},
// Return the scripts that are nessecery for the weather module.
getScripts: function () {
return [
"moment.js",
"weatherprovider.js",
"weatherobject.js",
this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")
];
},
// Override getHeader method.
getHeader: function() {
if (this.config.appendLocationNameToHeader && this.weatherProvider) {
return this.data.header + " " + this.weatherProvider.fetchedLocation();
}
return this.data.header;
},
// Start the weather module.
start: function () {
moment.locale(this.config.lang);
// Initialize the weather provider.
this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
// Let the weather provider know we are starting.
this.weatherProvider.start();
// Add custom filters
this.addFilters();
// Schedule the first update.
this.scheduleUpdate(this.config.initialLoadDelay);
},
// Override notification handler.
notificationReceived: function(notification, payload, sender) {
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;
}
}
}
} else if (notification === "INDOOR_TEMPERATURE") {
this.indoorTemperature = this.roundValue(payload);
this.updateDom(300);
} else if (notification === "INDOOR_HUMIDITY") {
this.indoorHumidity = this.roundValue(payload);
this.updateDom(300);
}
},
// Select the template depending on the display type.
getTemplate: function () {
return `${this.config.type.toLowerCase()}.njk`;
},
// Add all the data to the template.
getTemplateData: function () {
return {
config: this.config,
current: this.weatherProvider.currentWeather(),
forecast: this.weatherProvider.weatherForecast(),
indoor: {
humidity: this.indoorHumidity,
temperature: this.indoorTemperature
}
}
},
// What to do when the weather provider has new information available?
updateAvailable: function() {
Log.log("New weather information available.");
this.updateDom(0);
this.scheduleUpdate();
},
scheduleUpdate: function(delay = null) {
var nextLoad = this.config.updateInterval;
if (delay !== null && delay >= 0) {
nextLoad = delay;
}
setTimeout(() => {
if (this.config.type === "forecast") {
this.weatherProvider.fetchWeatherForecast();
} else {
this.weatherProvider.fetchCurrentWeather();
}
}, nextLoad);
},
roundValue: function(temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
},
addFilters() {
this.nunjucksEnvironment().addFilter("formatTime", function(date) {
date = moment(date);
if (this.config.timeFormat !== 24) {
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
return date.format("h:mm A");
} else {
return date.format("h:mm a");
}
} else {
return date.format("h:mm");
}
}
return date.format("HH:mm");
}.bind(this));
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
if (type === "temperature") {
value += "°";
if (this.config.degreeLabel) {
if (this.config.units === "metric") {
value += "C";
} else if (this.config.units === "imperial") {
value += "F";
} else {
value += "K";
}
}
} else if (type === "rain") {
if (isNaN(value)) {
value = "";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
}
}
return value;
}.bind(this));
this.nunjucksEnvironment().addFilter("roundValue", function(value) {
return this.roundValue(value);
}.bind(this));
}
});

View File

@@ -0,0 +1,100 @@
/* global Class */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* This class is the blueprint for a day which includes weather information.
*/
// Currently this is focused on the information which is necessary for the current weather.
// As soon as we start implementing the forecast, mode properties will be added.
class WeatherObject {
constructor(units) {
this.units = units;
this.date = null;
this.windSpeed = null;
this.windDirection = null;
this.sunrise = null;
this.sunset = null;
this.temperature = null;
this.minTemperature = null;
this.maxTemperature = null;
this.weatherType = null;
this.humidity = null;
this.rain = null;
}
cardinalWindDirection() {
if (this.windDirection > 11.25 && this.windDirection <= 33.75){
return "NNE";
} else if (this.windDirection > 33.75 && this.windDirection <= 56.25) {
return "NE";
} else if (this.windDirection > 56.25 && this.windDirection <= 78.75) {
return "ENE";
} else if (this.windDirection > 78.75 && this.windDirection <= 101.25) {
return "E";
} else if (this.windDirection > 101.25 && this.windDirection <= 123.75) {
return "ESE";
} else if (this.windDirection > 123.75 && this.windDirection <= 146.25) {
return "SE";
} else if (this.windDirection > 146.25 && this.windDirection <= 168.75) {
return "SSE";
} else if (this.windDirection > 168.75 && this.windDirection <= 191.25) {
return "S";
} else if (this.windDirection > 191.25 && this.windDirection <= 213.75) {
return "SSW";
} else if (this.windDirection > 213.75 && this.windDirection <= 236.25) {
return "SW";
} else if (this.windDirection > 236.25 && this.windDirection <= 258.75) {
return "WSW";
} else if (this.windDirection > 258.75 && this.windDirection <= 281.25) {
return "W";
} else if (this.windDirection > 281.25 && this.windDirection <= 303.75) {
return "WNW";
} else if (this.windDirection > 303.75 && this.windDirection <= 326.25) {
return "NW";
} else if (this.windDirection > 326.25 && this.windDirection <= 348.75) {
return "NNW";
} else {
return "N";
}
}
beaufortWindSpeed() {
const windInKmh = this.units === "imperial" ? this.windSpeed * 1.609344 : this.windSpeed * 60 * 60 / 1000;
const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (const [index, speed] of speeds.entries()) {
if (speed > windInKmh) {
return index;
}
}
return 12;
}
nextSunAction() {
return moment().isBetween(this.sunrise, this.sunset) ? "sunset" : "sunrise";
}
feelsLike() {
const windInMph = this.units === "imperial" ? this.windSpeed : this.windSpeed * 2.23694;
const tempInF = this.units === "imperial" ? this.temperature : this.temperature * 9 / 5 + 32;
let feelsLike = tempInF;
if (windInMph > 3 && tempInF < 50) {
feelsLike = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
} else if (tempInF > 80 && this.humidity > 40) {
feelsLike = -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;
}
return this.units === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
}
}

View File

@@ -0,0 +1,154 @@
/* global Class */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* This class is the blueprint for a weather provider.
*/
/**
* Base BluePrint for the WeatherProvider
*/
var WeatherProvider = Class.extend({
// Weather Provider Properties
providerName: null,
// The following properties have accestor methods.
// Try to not access them directly.
currentWeatherObject: null,
weatherForecastArray: null,
fetchedLocationName: null,
// The following properties will be set automaticly.
// You do not need to overwrite these properties.
config: null,
delegate: null,
providerIdentifier: null,
// Weather Provider Methods
// All the following methods can be overwrited, although most are good as they are.
// Called when a weather provider is initialized.
init: function(config) {
this.config = config;
Log.info(`Weather provider: ${this.providerName} initialized.`);
},
// Called to set the config, this config is the same as the weather module's config.
setConfig: function(config) {
this.config = config;
Log.info(`Weather provider: ${this.providerName} config set.`, this.config);
},
// Called when the weather provider is about to start.
start: function() {
Log.info(`Weather provider: ${this.providerName} started.`);
},
// This method should start the API request to fetch the current weather.
// This method should definetly be overwritten in the provider.
fetchCurrentWeather: function() {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchCurrentWeather method.`);
},
// This method should start the API request to fetch the weather forecast.
// This method should definetly be overwritten in the provider.
fetchWeatherForecast: function() {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
},
// This returns a WeatherDay object for the current weather.
currentWeather: function() {
return this.currentWeatherObject;
},
// This returns an array of WeatherDay objects for the weather forecast.
weatherForecast: function() {
return this.weatherForecastArray;
},
// This returns the name of the fetched location or an empty string.
fetchedLocation: function() {
return this.fetchedLocationName || "";
},
// Set the currentWeather and notify the delegate that new information is available.
setCurrentWeather: function(currentWeatherObject) {
// We should check here if we are passing a WeatherDay
this.currentWeatherObject = currentWeatherObject;
this.updateAvailable();
},
// Set the weatherForecastArray and notify the delegate that new information is available.
setWeatherForecast: function(weatherForecastArray) {
// We should check here if we are passing a WeatherDay
this.weatherForecastArray = weatherForecastArray;
this.updateAvailable();
},
// Set the fetched location name.
setFetchedLocation: function(name) {
this.fetchedLocationName = name;
},
// Notify the delegate that new weather is available.
updateAvailable: function() {
this.delegate.updateAvailable(this);
},
// A convinience function to make requests. It returns a promise.
fetchData: function(url, method = "GET", data = null) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open(method, url, true);
request.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(JSON.parse(this.response));
} else {
reject(request)
}
}
};
request.send();
})
}
});
/**
* Collection of registered weather providers.
*/
WeatherProvider.providers = [];
/**
* Static method to register a new weather provider.
*/
WeatherProvider.register = function(providerIdentifier, providerDetails) {
WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails);
};
/**
* Static method to initialize a new weather provider.
*/
WeatherProvider.initialize = function(providerIdentifier, delegate) {
providerIdentifier = providerIdentifier.toLowerCase();
var provider = new WeatherProvider.providers[providerIdentifier]();
provider.delegate = delegate;
provider.setConfig(delegate.config);
provider.providerIdentifier = providerIdentifier;
if (!provider.providerName) {
provider.providerName = providerIdentifier;
}
return provider;
};

View File

@@ -2,6 +2,11 @@
The `weatherforecast` module is one of the default modules of the MagicMirror.
This module displays the weather forecast for the coming week, including an an icon to display the current conditions, the minimum temperature and the maximum temperature.
## Screenshots
- 5 day forecast
![Screenshot of 5 day forecast](forecast_screenshot.png)
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
@@ -28,15 +33,16 @@ The following properties can be configured:
| Option | Description
| ---------------------------- | -----------
| `location` | The location used for weather information. <br><br> **Example:** `'Amsterdam,Netherlands'` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `locationID` | Location ID from [OpenWeatherMap](http://openweathermap.org/help/city_list.txt) **This will override anything you put in location.** <br> Leave blank if you want to use location. <br> **Example:** `1234567` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `locationID` | Location ID from [OpenWeatherMap](https://openweathermap.org/find) **This will override anything you put in location.** <br> Leave blank if you want to use location. <br> **Example:** `1234567` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `appid` | The [OpenWeatherMap](https://home.openweathermap.org) API key, which can be obtained by creating an OpenWeatherMap account. <br><br> This value is **REQUIRED**
| `units` | What units to use. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` =Fahrenheit <br> **Default value:** `config.units`
| `roundTemp` | Round temperature values to nearest integer. <br><br> **Possible values:** `true` (round to integer) or `false` (display exact value with decimal point) <br> **Default value:** `false`
| `maxNumberOfDays` | How many days of forecast to return. Specified by config.js <br><br> **Possible values:** `1` - `16` <br> **Default value:** `7` (7 days) <br> This value is optional. By default the weatherforecast module will return 7 days.
| `showRainAmount` | Should the predicted rain amount be displayed? <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false` <br> This value is optional. By default the weatherforecast module will not display the predicted amount of rain.
| `updateInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `600000` (10 minutes)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `1000` (1 second)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:** `0` - `5000` <br> **Default value:** `1000` (1 second)
| `lang` | The language of the days. <br><br> **Possible values:** `en`, `nl`, `ru`, etc ... <br> **Default value:** uses value of _config.language_
| `decimalSymbol` | The decimal symbol to use.<br><br> **Possible values:** `.`, `,` or any other symbol.<br> **Default value:** `.`
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `fadePoint` | Where to start fade? <br><br> **Possible values:** `0` (top of the list) - `1` (bottom of list) <br> **Default value:** `0.25`
| `initialLoadDelay` | The initial delay before loading. If you have multiple modules that use the same API key, you might want to delay one of the requests. (Milliseconds) <br><br> **Possible values:** `1000` - `5000` <br> **Default value:** `2500` (2.5 seconds delay. This delay is used to keep the OpenWeather API happy.)
@@ -45,9 +51,11 @@ The following properties can be configured:
| `apiBase` | The OpenWeatherMap base URL. <br><br> **Default value:** `'http://api.openweathermap.org/data/'`
| `forecastEndpoint` | The OpenWeatherMap API endPoint. <br><br> **Default value:** `'forecast/daily'`
| `appendLocationNameToHeader` | If set to `true`, the returned location name will be appended to the header of the module, if the header is enabled. This is mainly intresting when using calender based weather. <br><br> **Default value:** `true`
| `calendarClass` | The class for the calender module to base the event based weather information on. <br><br> **Default value:** `'calendar'`
| `calendarClass` | The class for the calendar module to base the event based weather information on. <br><br> **Default value:** `'calendar'`
| `tableClass` | Name of the classes issued from `main.css`. <br><br> **Possible values:** xsmall, small, medium, large, xlarge. <br> **Default value:** _small._
| `iconTable` | The conversion table to convert the weather conditions to weather-icons. <br><br> **Default value:** view table below
`colored` | If set 'colored' to true the min-temp get a blue tone and the max-temp get a red tone. <br><br> **Default value:** `'false'`
| `colored` | If set `colored` to `true` the min-temp gets a blue tone and the max-temp gets a red tone. <br><br> **Default value:** `'false'`
| `scale ` | If set to `true` the module will display `C` for Celsius degrees and `F` for Fahrenheit degrees after the number, based on the value of the `units` option, otherwise only the &deg; character is displayed. <br><br> **Default value:** `false`
#### Default Icon Table
````javascript

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -21,6 +21,7 @@ Module.register("weatherforecast",{
animationSpeed: 1000,
timeFormat: config.timeFormat,
lang: config.language,
decimalSymbol: ".",
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
colored: false,
@@ -30,11 +31,12 @@ Module.register("weatherforecast",{
retryDelay: 2500,
apiVersion: "2.5",
apiBase: "http://api.openweathermap.org/data/",
apiBase: "https://api.openweathermap.org/data/",
forecastEndpoint: "forecast/daily",
appendLocationNameToHeader: true,
calendarClass: "calendar",
tableClass: "small",
roundTemp: false,
@@ -116,7 +118,7 @@ Module.register("weatherforecast",{
}
var table = document.createElement("table");
table.className = "small";
table.className = this.config.tableClass;
for (var f in this.forecast) {
var forecast = this.forecast[f];
@@ -140,14 +142,14 @@ Module.register("weatherforecast",{
icon.className = "wi weathericon " + forecast.icon;
iconCell.appendChild(icon);
var degreeLabel = "";
var degreeLabel = "&deg;";
if(this.config.scale) {
switch(this.config.units) {
case "metric":
degreeLabel = " &deg;C";
degreeLabel += " C";
break;
case "imperial":
degreeLabel = " &deg;F";
degreeLabel += " F";
break;
case "default":
degreeLabel = "K";
@@ -155,13 +157,17 @@ Module.register("weatherforecast",{
}
}
if (this.config.decimalSymbol === "" || this.config.decimalSymbol === " ") {
this.config.decimalSymbol = ".";
}
var maxTempCell = document.createElement("td");
maxTempCell.innerHTML = forecast.maxTemp + degreeLabel;
maxTempCell.innerHTML = forecast.maxTemp.replace(".", this.config.decimalSymbol) + degreeLabel;
maxTempCell.className = "align-right bright max-temp";
row.appendChild(maxTempCell);
var minTempCell = document.createElement("td");
minTempCell.innerHTML = forecast.minTemp + degreeLabel;
minTempCell.innerHTML = forecast.minTemp.replace(".", this.config.decimalSymbol) + degreeLabel;
minTempCell.className = "align-right min-temp";
row.appendChild(minTempCell);
@@ -171,7 +177,7 @@ Module.register("weatherforecast",{
rainCell.innerHTML = "";
} else {
if(config.units !== "imperial") {
rainCell.innerHTML = forecast.rain + " mm";
rainCell.innerHTML = parseFloat(forecast.rain).toFixed(1) + " mm";
} else {
rainCell.innerHTML = (parseFloat(forecast.rain) / 25.4).toFixed(2) + " in";
}
@@ -254,7 +260,6 @@ Module.register("weatherforecast",{
if (self.config.forecastEndpoint == "forecast/daily") {
self.config.forecastEndpoint = "forecast";
self.config.maxNumberOfDays = self.config.maxNumberOfDays * 8;
Log.warn(self.name + ": Your AppID does not support long term forecasts. Switching to fallback endpoint.");
}
@@ -293,12 +298,6 @@ Module.register("weatherforecast",{
params += "&units=" + this.config.units;
params += "&lang=" + this.config.lang;
/*
* Submit a specific number of days to forecast, between 1 to 16 days.
* The OpenWeatherMap API properly handles values outside of the 1 - 16 range and returns 7 days by default.
* This is simply being pedantic and doing it ourselves.
*/
params += "&cnt=" + (((this.config.maxNumberOfDays < 1) || (this.config.maxNumberOfDays > 16)) ? 7 * 8 : this.config.maxNumberOfDays);
params += "&APPID=" + this.config.appid;
return params;
@@ -335,8 +334,15 @@ Module.register("weatherforecast",{
var forecast = data.list[i];
this.parserDataWeather(forecast); // hack issue #1017
var day = moment(forecast.dt, "X").format("ddd");
var hour = moment(forecast.dt, "X").format("H");
var day;
var hour;
if(!!forecast.dt_txt) {
day = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd");
hour = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("H");
} else {
day = moment(forecast.dt, "X").format("ddd");
hour = moment(forecast.dt, "X").format("H");
}
if (day !== lastDay) {
var forecastData = {
@@ -344,11 +350,16 @@ Module.register("weatherforecast",{
icon: this.config.iconTable[forecast.weather[0].icon],
maxTemp: this.roundValue(forecast.temp.max),
minTemp: this.roundValue(forecast.temp.min),
rain: this.roundValue(forecast.rain)
rain: forecast.rain
};
this.forecast.push(forecastData);
lastDay = day;
// Stop processing when maxNumberOfDays is reached
if (this.forecast.length === this.config.maxNumberOfDays) {
break;
}
} else {
//Log.log("Compare max: ", forecast.temp.max, parseFloat(forecastData.maxTemp));
forecastData.maxTemp = forecast.temp.max > parseFloat(forecastData.maxTemp) ? this.roundValue(forecast.temp.max) : forecastData.maxTemp;

View File

@@ -23,6 +23,16 @@ NodeHelper = Class.extend({
console.log("Starting module helper: " + this.name);
},
/* stop()
* Called when the MagicMirror server receives a `SIGINT`
* Close any open connections, stop any sub-processes and
* gracefully exit the module.
*
*/
stop: function() {
console.log("Stopping module helper: " + this.name);
},
/* socketNotificationReceived(notification, payload)
* This method is called when a socket notification arrives.
*

7649
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "magicmirror",
"version": "2.1.3",
"version": "2.6.0",
"description": "The open source modular smart mirror platform.",
"main": "js/electron.js",
"scripts": {
@@ -11,7 +11,8 @@
"test": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests --recursive",
"test:unit": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/unit --recursive",
"test:e2e": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/e2e --recursive",
"config:check": "node tests/configs/check_config.js"
"config:check": "node tests/configs/check_config.js",
"lint": "grunt"
},
"repository": {
"type": "git",
@@ -33,39 +34,43 @@
},
"homepage": "https://magicmirror.builders",
"devDependencies": {
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"current-week-number": "^1.0.7",
"danger": "^3.1.3",
"grunt": "latest",
"grunt-eslint": "latest",
"grunt-jsonlint": "latest",
"grunt-markdownlint": "^1.0.39",
"grunt-markdownlint": "^1.0.43",
"grunt-stylelint": "latest",
"grunt-yamllint": "latest",
"http-auth": "^3.1.3",
"jshint": "^2.9.4",
"mocha": "^3.4.2",
"http-auth": "^3.2.3",
"jsdom": "^11.6.2",
"jshint": "^2.9.5",
"mocha": "^4.1.0",
"mocha-each": "^1.1.0",
"spectron": "3.6.x",
"stylelint": "^8.0.0",
"spectron": "^3.8.0",
"stylelint": "^8.4.0",
"stylelint-config-standard": "latest",
"time-grunt": "latest"
},
"dependencies": {
"body-parser": "^1.17.2",
"ajv": "6.5.5",
"body-parser": "^1.18.2",
"colors": "^1.1.2",
"electron": "^1.6.10",
"express": "^4.15.3",
"electron": "^2.0.16",
"express": "^4.16.2",
"express-ipfilter": "0.3.1",
"feedme": "latest",
"helmet": "^3.6.1",
"helmet": "^3.9.0",
"home-path": "^1.0.6",
"iconv-lite": "latest",
"mocha-logger": "^1.0.5",
"mocha-logger": "^1.0.6",
"moment": "latest",
"request": "^2.81.0",
"rrule-alt": "^2.2.5",
"simple-git": "^1.73.0",
"socket.io": "^2.0.2",
"request": "^2.87.0",
"rrule-alt": "^2.2.8",
"simple-git": "^1.85.0",
"socket.io": "^2.1.1",
"valid-url": "latest",
"walk": "latest"
}

View File

@@ -1,4 +1,4 @@
if [ -z "$DISPLAY" ]; then #If not set DISPLAY is SSH remote or tty
export DISPLAY=:0 # Set by defaul display
export DISPLAY=:0 # Set by default display
fi
electron js/electron.js $1

View File

@@ -14,8 +14,6 @@ var path = require("path");
var fs = require("fs");
var Utils = require(__dirname + "/../../js/utils.js");
if (process.env.NODE_ENV == "test") { return 0 };
/* getConfigFile()
* Return string with path of configuration file
* Check if set by enviroment variable MM_CONFIG_FILE
@@ -30,37 +28,43 @@ function getConfigFile() {
return configFileName;
}
var configFileName = getConfigFile();
// Check if file is present
if (fs.existsSync(configFileName) === false) {
console.error(Utils.colors.error("File not found: "), configFileName);
return;
}
// check permision
try {
fs.accessSync(configFileName, fs.F_OK);
} catch (e) {
console.log(Utils.colors.error(e));
return;
}
// Validate syntax of the configuration file.
// In case the there errors show messages and
// return
console.info(Utils.colors.info("Checking file... ", configFileName));
// I'm not sure if all ever is utf-8
fs.readFile(configFileName, "utf-8", function (err, data) {
if (err) { throw err; }
v.JSHINT(data); // Parser by jshint
if (v.JSHINT.errors.length == 0) {
console.log("Your configuration file don't containt syntax error :)");
return true;
} else {
errors = v.JSHINT.data().errors;
for (idx in errors) {
error = errors[idx];
console.log("Line", error.line, "col", error.character, error.reason);
}
function checkConfigFile() {
var configFileName = getConfigFile();
// Check if file is present
if (fs.existsSync(configFileName) === false) {
console.error(Utils.colors.error("File not found: "), configFileName);
return;
}
});
// check permision
try {
fs.accessSync(configFileName, fs.F_OK);
} catch (e) {
console.log(Utils.colors.error(e));
return;
}
// Validate syntax of the configuration file.
// In case the there errors show messages and
// return
console.info(Utils.colors.info("Checking file... ", configFileName));
// I'm not sure if all ever is utf-8
fs.readFile(configFileName, "utf-8", function (err, data) {
if (err) { throw err; }
v.JSHINT(data); // Parser by jshint
if (v.JSHINT.errors.length == 0) {
console.log("Your configuration file doesn't contain syntax errors :)");
return true;
} else {
errors = v.JSHINT.data().errors;
for (idx in errors) {
error = errors[idx];
console.log("Line", error.line, "col", error.character, error.reason);
}
}
});
}
if (process.env.NODE_ENV !== "test") {
checkConfigFile();
};

View File

@@ -0,0 +1,13 @@
{
// Escaped
"FOO\"BAR": "Today",
/*
* The following lines
* represent cardinal directions
*/
"N": "N",
"E": "E",
"S": "S",
"W": "W"
}

View File

@@ -0,0 +1,33 @@
{
"LOADING": "Loading &hellip;",
"TODAY": "Today",
"TOMORROW": "Tomorrow",
"DAYAFTERTOMORROW": "In 2 days",
"RUNNING": "Ends in",
"EMPTY": "No upcoming events.",
"WEEK": "Week {weekNumber}",
"N": "N",
"NNE": "NNE",
"NE": "NE",
"ENE": "ENE",
"E": "E",
"ESE": "ESE",
"SE": "SE",
"SSE": "SSE",
"S": "S",
"SSW": "SSW",
"SW": "SW",
"WSW": "WSW",
"W": "W",
"WNW": "WNW",
"NW": "NW",
"NNW": "NNW",
"UPDATE_NOTIFICATION": "MagicMirror² update available.",
"UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.",
"UPDATE_INFO_SINGLE": "The current installation is COMMIT_COUNT commit behind on the BRANCH_NAME branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is COMMIT_COUNT commits behind on the BRANCH_NAME branch."
}

View File

@@ -0,0 +1,33 @@
{
"LOADING": "Loading &hellip;",
"TODAY": "Today",
"TOMORROW": "Tomorrow",
"DAYAFTERTOMORROW": "In 2 days",
"RUNNING": "Ends in",
"EMPTY": "No upcoming events.",
"WEEK": "Week {weekNumber}",
"N": "N",
"NNE": "NNE",
"NE": "NE",
"ENE": "ENE",
"E": "E",
"ESE": "ESE",
"SE": "SE",
"SSE": "SSE",
"S": "S",
"SSW": "SSW",
"SW": "SW",
"WSW": "WSW",
"W": "W",
"WNW": "WNW",
"NW": "NW",
"NNW": "NNW",
"UPDATE_NOTIFICATION": "MagicMirror² update available.",
"UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.",
"UPDATE_INFO_SINGLE": "The current installation is COMMIT_COUNT commit behind on the BRANCH_NAME branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is COMMIT_COUNT commits behind on the BRANCH_NAME branch."
}

View File

@@ -0,0 +1,129 @@
const fs = require("fs");
const path = require("path");
const chai = require("chai");
const expect = chai.expect;
const mlog = require("mocha-logger");
const translations = require("../../translations/translations.js");
const helmet = require("helmet");
const {JSDOM} = require("jsdom");
const express = require("express");
describe("Translations", function() {
let server;
before(function() {
const app = express();
app.use(helmet());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
next();
});
app.use("/translations", express.static(path.join(__dirname, "..", "..", "translations")));
server = app.listen(3000);
});
after(function() {
server.close();
});
it("should have a translation file in the specified path", function() {
for(let language in translations) {
const file = fs.statSync(translations[language]);
expect(file.isFile()).to.be.equal(true);
}
});
const mmm = {
name: "TranslationTest",
file(file) {
return `http://localhost:3000/${file}`;
}
};
describe("Parsing language files through the Translator class", function() {
for(let language in translations) {
it(`should parse ${language}`, function(done) {
const dom = new JSDOM(`<script>var translations = ${JSON.stringify(translations)}; var Log = {log: function(){}};</script>\
<script src="${path.join(__dirname, "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
Translator.load(mmm, translations[language], false, function() {
expect(Translator.translations[mmm.name]).to.be.an("object");
expect(Object.keys(Translator.translations[mmm.name]).length).to.be.at.least(1);
done();
});
};
});
}
});
describe("Same keys", function() {
let base;
before(function(done) {
const dom = new JSDOM(`<script>var translations = ${JSON.stringify(translations)}; var Log = {log: function(){}};</script>\
<script src="${path.join(__dirname, "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
Translator.load(mmm, translations.en, false, function() {
base = Object.keys(Translator.translations[mmm.name]).sort();
done();
});
};
});
for (let language in translations) {
if (language === "en") {
continue;
}
describe(`Translation keys of ${language}`, function() {
let keys;
before(function(done){
const dom = new JSDOM(`<script>var translations = ${JSON.stringify(translations)}; var Log = {log: function(){}};</script>\
<script src="${path.join(__dirname, "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
Translator.load(mmm, translations[language], false, function() {
keys = Object.keys(Translator.translations[mmm.name]).sort();
done();
});
};
});
it(`${language} keys should be in base`, function() {
keys.forEach(function(key) {
expect(base.indexOf(key)).to.be.at.least(0);
});
});
it(`${language} should contain all base keys`, function() {
// TODO: when all translations are fixed, use
// expect(keys).to.deep.equal(base);
// instead of the try-catch-block
try {
expect(keys).to.deep.equal(base);
} catch(e) {
if (e instanceof chai.AssertionError) {
const diff = base.filter(key => !keys.includes(key));
mlog.pending(`Missing Translations for language ${language}: ${diff}`);
this.skip();
} else {
throw e;
}
}
})
});
}
});
});

View File

@@ -25,7 +25,7 @@ describe("Check configuration without modules", function () {
});
before(function () {
// Set config sample for use in test
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/without_modules.js";
});

View File

@@ -0,0 +1,109 @@
const chai = require("chai");
const expect = chai.expect;
const path = require("path");
const {JSDOM} = require("jsdom");
describe("File js/class", function() {
describe("Test function cloneObject", function() {
let clone;
let dom;
before(function(done) {
dom = new JSDOM(`<script>var Log = {log: function() {}};</script>\
<script src="${path.join(__dirname, "..", "..", "..", "js", "class.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {cloneObject} = dom.window;
clone = cloneObject;
done();
};
});
it("should clone object", function() {
const expected = {name: "Rodrigo", web: "https://rodrigoramirez.com", project: "MagicMirror"};
const obj = clone(expected);
expect(obj).to.deep.equal(expected);
expect(expected === obj).to.equal(false);
});
it("should clone array", function() {
const expected = [1, null, undefined, "TEST"];
const obj = clone(expected);
expect(obj).to.deep.equal(expected);
expect(expected === obj).to.equal(false);
});
it("should clone number", function() {
let expected = 1;
let obj = clone(expected);
expect(obj).to.equal(expected);
expected = 1.23;
obj = clone(expected);
expect(obj).to.equal(expected);
});
it("should clone string", function() {
const expected = "Perfect stranger";
const obj = clone(expected);
expect(obj).to.equal(expected);
});
it("should clone undefined", function() {
const expected = undefined;
const obj = clone(expected);
expect(obj).to.equal(expected);
});
it("should clone null", function() {
const expected = null;
const obj = clone(expected);
expect(obj).to.equal(expected);
});
it("should clone nested object", function() {
const expected = {
name: "fewieden",
link: "https://github.com/fewieden",
versions: ["2.0", "2.1", "2.2"],
answerForAllQuestions: 42,
properties: {
items: [{foo: "bar"}, {lorem: "ipsum"}],
invalid: undefined,
nothing: null
}
};
const obj = clone(expected);
expect(obj).to.deep.equal(expected);
expect(expected === obj).to.equal(false);
expect(expected.versions === obj.versions).to.equal(false);
expect(expected.properties === obj.properties).to.equal(false);
expect(expected.properties.items === obj.properties.items).to.equal(false);
expect(expected.properties.items[0] === obj.properties.items[0]).to.equal(false);
expect(expected.properties.items[1] === obj.properties.items[1]).to.equal(false);
});
describe("Test lockstring code", function() {
let log;
before(function() {
log = dom.window.Log.log;
dom.window.Log.log = function cmp(str) {
expect(str).to.equal("lockStrings");
};
});
after(function() {
dom.window.Log.log = log;
});
it("should clone object and log lockStrings", function() {
const expected = {name: "Module", lockStrings: "stringLock"};
const obj = clone(expected);
expect(obj).to.deep.equal(expected);
expect(expected === obj).to.equal(false);
});
});
});
});

View File

@@ -0,0 +1,17 @@
const chai = require("chai");
const expect = chai.expect;
const deprecated = require("../../../js/deprecated");
describe("Deprecated", function() {
it("should be an object", function() {
expect(deprecated).to.be.an("object");
});
it("should contain configs array with deprecated options as strings", function() {
expect(deprecated.configs).to.be.an("array");
for (let option of deprecated.configs) {
expect(option).to.be.an("string");
}
expect(deprecated.configs).to.include("kioskmode");
});
});

View File

@@ -0,0 +1,298 @@
const chai = require("chai");
const expect = chai.expect;
const path = require("path");
const fs = require("fs");
const helmet = require("helmet");
const {JSDOM} = require("jsdom");
const express = require("express");
describe("Translator", function() {
let server;
before(function() {
const app = express();
app.use(helmet());
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
next();
});
app.use("/translations", express.static(path.join(__dirname, "..", "..", "..", "tests", "configs", "data")));
server = app.listen(3000);
});
after(function() {
server.close();
});
describe("translate", function() {
const translations = {
"MMM-Module": {
"Hello": "Hallo",
"Hello {username}": "Hallo {username}"
}
};
const coreTranslations = {
"Hello": "XXX",
"Hello {username}": "XXX",
"FOO": "Foo",
"BAR {something}": "Bar {something}"
};
const translationsFallback = {
"MMM-Module": {
"Hello": "XXX",
"Hello {username}": "XXX",
"FOO": "XXX",
"BAR {something}": "XXX",
"A key": "A translation"
}
};
const coreTranslationsFallback = {
"FOO": "XXX",
"BAR {something}": "XXX",
"Hello": "XXX",
"Hello {username}": "XXX",
"A key": "XXX",
"Fallback": "core fallback"
};
function setTranslations(Translator) {
Translator.translations = translations;
Translator.coreTranslations = coreTranslations;
Translator.translationsFallback = translationsFallback;
Translator.coreTranslationsFallback = coreTranslationsFallback;
}
it("should return custom module translation", function(done) {
const dom = new JSDOM(`<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
setTranslations(Translator);
let translation = Translator.translate({name: "MMM-Module"}, "Hello");
expect(translation).to.be.equal("Hallo");
translation = Translator.translate({name: "MMM-Module"}, "Hello {username}", {username: "fewieden"});
expect(translation).to.be.equal("Hallo fewieden");
done();
};
});
it("should return core translation", function(done) {
const dom = new JSDOM(`<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
setTranslations(Translator);
let translation = Translator.translate({name: "MMM-Module"}, "FOO");
expect(translation).to.be.equal("Foo");
translation = Translator.translate({name: "MMM-Module"}, "BAR {something}", {something: "Lorem Ipsum"});
expect(translation).to.be.equal("Bar Lorem Ipsum");
done();
};
});
it("should return custom module translation fallback", function(done) {
const dom = new JSDOM(`<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
setTranslations(Translator);
const translation = Translator.translate({name: "MMM-Module"}, "A key");
expect(translation).to.be.equal("A translation");
done();
};
});
it("should return core translation fallback", function(done) {
const dom = new JSDOM(`<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
setTranslations(Translator);
const translation = Translator.translate({name: "MMM-Module"}, "Fallback");
expect(translation).to.be.equal("core fallback");
done();
};
});
it("should return translation with placeholder for missing variables", function(done) {
const dom = new JSDOM(`<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
setTranslations(Translator);
const translation = Translator.translate({name: "MMM-Module"}, "Hello {username}");
expect(translation).to.be.equal("Hallo {username}");
done();
};
});
it("should return key if no translation was found", function(done) {
const dom = new JSDOM(`<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
setTranslations(Translator);
const translation = Translator.translate({name: "MMM-Module"}, "MISSING");
expect(translation).to.be.equal("MISSING");
done();
};
});
});
describe("load", function() {
const mmm = {
name: "TranslationTest",
file(file) {
return `http://localhost:3000/translations/${file}`;
}
};
it("should load translations", function(done) {
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
const file = "TranslationTest.json";
Translator.load(mmm, file, false, function() {
const json = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", file));
expect(Translator.translations[mmm.name]).to.be.deep.equal(json);
done();
});
};
});
it("should load translation fallbacks", function(done) {
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
const file = "TranslationTest.json";
Translator.load(mmm, file, true, function() {
const json = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", file));
expect(Translator.translationsFallback[mmm.name]).to.be.deep.equal(json);
done();
});
};
});
it("should strip comments", function(done) {
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
const file = "StripComments.json";
Translator.load(mmm, file, false, function() {
expect(Translator.translations[mmm.name]).to.be.deep.equal({
"FOO\"BAR": "Today",
"N": "N",
"E": "E",
"S": "S",
"W": "W"
});
done();
});
};
});
it("should not load translations, if module fallback exists", function(done) {
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator, XMLHttpRequest} = dom.window;
const file = "TranslationTest.json";
XMLHttpRequest.prototype.send = function() {
throw "Shouldn't load files";
};
Translator.translationsFallback[mmm.name] = {
Hello: "Hallo"
};
Translator.load(mmm, file, false, function() {
expect(Translator.translations[mmm.name]).to.be.undefined;
expect(Translator.translationsFallback[mmm.name]).to.be.deep.equal({
Hello: "Hallo"
});
done();
});
};
});
});
describe("loadCoreTranslations", function() {
it("should load core translations and fallback", function(done) {
const dom = new JSDOM(`<script>var translations = {en: "http://localhost:3000/translations/en.json"}; var Log = {log: function(){}};</script>\
<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
Translator.loadCoreTranslations("en");
const en = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", "en.json"));
setTimeout(function() {
expect(Translator.coreTranslations).to.be.deep.equal(en);
expect(Translator.coreTranslationsFallback).to.be.deep.equal(en);
done();
}, 500);
};
});
it("should load core fallback if language cannot be found", function(done) {
const dom = new JSDOM(`<script>var translations = {en: "http://localhost:3000/translations/en.json"}; var Log = {log: function(){}};</script>\
<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
Translator.loadCoreTranslations("MISSINGLANG");
const en = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", "en.json"));
setTimeout(function() {
expect(Translator.coreTranslations).to.be.deep.equal({});
expect(Translator.coreTranslationsFallback).to.be.deep.equal(en);
done();
}, 500);
};
});
});
describe("loadCoreTranslationsFallback", function() {
it("should load core translations fallback", function(done) {
const dom = new JSDOM(`<script>var translations = {en: "http://localhost:3000/translations/en.json"}; var Log = {log: function(){}};</script>\
<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
Translator.loadCoreTranslationsFallback();
const en = require(path.join(__dirname, "..", "..", "..", "tests", "configs", "data", "en.json"));
setTimeout(function() {
expect(Translator.coreTranslationsFallback).to.be.deep.equal(en);
done();
}, 500);
};
});
it("should load core fallback if language cannot be found", function(done) {
const dom = new JSDOM(`<script>var translations = {}; var Log = {log: function(){}};</script>\
<script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {Translator} = dom.window;
Translator.loadCoreTranslations();
setTimeout(function() {
expect(Translator.coreTranslationsFallback).to.be.deep.equal({});
done();
}, 500);
};
});
});
});

View File

@@ -0,0 +1,41 @@
var chai = require("chai");
var expect = chai.expect;
var Utils = require("../../../js/utils.js");
var colors = require("colors/safe");
describe("Utils", function() {
describe("colors", function() {
var colorsEnabled = colors.enabled;
afterEach(function() {
colors.enabled = colorsEnabled;
});
it("should have info, warn and error properties", function() {
expect(Utils.colors).to.have.property("info");
expect(Utils.colors).to.have.property("warn");
expect(Utils.colors).to.have.property("error");
});
it("properties should be functions", function() {
expect(Utils.colors.info).to.be.a("function");
expect(Utils.colors.warn).to.be.a("function");
expect(Utils.colors.error).to.be.a("function");
});
it("should print colored message in supported consoles", function() {
colors.enabled = true;
expect(Utils.colors.info("some informations")).to.be.equal("\u001b[34msome informations\u001b[39m");
expect(Utils.colors.warn("a warning")).to.be.equal("\u001b[33ma warning\u001b[39m");
expect(Utils.colors.error("ERROR!")).to.be.equal("\u001b[31mERROR!\u001b[39m");
});
it("should print message in unsupported consoles", function() {
colors.enabled = false;
expect(Utils.colors.info("some informations")).to.be.equal("some informations");
expect(Utils.colors.warn("a warning")).to.be.equal("a warning");
expect(Utils.colors.error("ERROR!")).to.be.equal("ERROR!");
});
});
});

View File

@@ -1,51 +0,0 @@
var chai = require("chai");
var expect = chai.expect;
var jsClass = require("../../../js/class.js");
describe("File js/class", function() {
describe("Test function cloneObject", function() {
var cloneObject = jsClass._test.cloneObject;
it("should be return equals object", function() {
var expected = {name: "Rodrigo", web: "https://rodrigoramirez.com", project: "MagicMirror"};
var obj = {};
obj = cloneObject(expected);
expect(expected).to.deep.equal(obj);
});
it("should be return equals int", function() {
var expected = 1;
var obj = {};
obj = cloneObject(expected);
expect(expected).to.equal(obj);
});
it("should be return equals string", function() {
var expected = "Perfect stranger";
var obj = {};
obj = cloneObject(expected);
expect(expected).to.equal(obj);
});
it("should be return equals undefined", function() {
var expected = undefined;
var obj = {};
obj = cloneObject(expected);
expect(undefined).to.equal(obj);
});
// CoverageME
/*
context("Test lockstring code", function() {
it("should be return equals object", function() {
var expected = {name: "Module", lockStrings: "stringLock"};
var obj = {};
obj = cloneObject(expected);
expect(expected).to.deep.equal(obj);
});
});
*/
});
});

View File

@@ -1,20 +1,32 @@
var chai = require("chai");
var expect = chai.expect;
var classMM = require("../../../js/class.js"); // require for load module.js
var moduleMM = require("../../../js/module.js")
const chai = require("chai");
const expect = chai.expect;
const path = require("path");
const {JSDOM} = require("jsdom");
describe("Test function cmpVersions in js/module.js", function() {
let cmp;
before(function(done) {
const dom = new JSDOM(`<script>var Class = {extend: function() { return {}; }};</script>\
<script src="${path.join(__dirname, "..", "..", "..", "js", "module.js")}">`, { runScripts: "dangerously",
resources: "usable" });
dom.window.onload = function() {
const {cmpVersions} = dom.window;
cmp = cmpVersions;
done();
};
});
it("should return -1 when comparing 2.1 to 2.2", function() {
expect(moduleMM._test.cmpVersions("2.1", "2.2")).to.equal(-1);
expect(cmp("2.1", "2.2")).to.equal(-1);
});
it("should be return 0 when comparing 2.2 to 2.2", function() {
expect(moduleMM._test.cmpVersions("2.2", "2.2")).to.equal(0);
expect(cmp("2.2", "2.2")).to.equal(0);
});
it("should be return 1 when comparing 1.1 to 1.0", function() {
expect(moduleMM._test.cmpVersions("1.1", "1.0")).to.equal(1);
expect(cmp("1.1", "1.0")).to.equal(1);
});
});

View File

@@ -1,118 +0,0 @@
var fs = require("fs");
var path = require("path");
var chai = require("chai");
var expect = chai.expect;
var mlog = require("mocha-logger");
describe("Translations have the same keys as en.js", function() {
var translations = require("../../../translations/translations.js");
var base = JSON.parse(stripComments(fs.readFileSync("translations/en.json", "utf8")));
var baseKeys = Object.keys(base).sort();
Object.keys(translations).forEach(function(tr) {
var fileName = translations[tr];
var fileContent = stripComments(fs.readFileSync(fileName, "utf8"));
var fileTranslations = JSON.parse(fileContent);
var fileKeys = Object.keys(fileTranslations).sort();
it(fileName + " keys should be in base", function() {
fileKeys.forEach(function(key) {
expect( baseKeys.indexOf(key) ).to.be.at.least(0);
});
});
it(fileName + " should contain all base keys", function() {
var test = this;
baseKeys.forEach(function(key) {
// TODO: when all translations are fixed, use
// expect(fileKeys).to.deep.equal(baseKeys);
// instead of the try-catch-block
try {
expect(fileKeys).to.deep.equal(baseKeys);
} catch(e) {
if (e instanceof chai.AssertionError) {
diff = baseKeys.filter(function(x) { return fileKeys.indexOf(x) < 0 });
mlog.pending("Missing Translations for language " + tr + ": ", diff);
test.skip();
} else {
throw e;
}
}
});
});
});
});
// Copied from js/translator.js
function stripComments(str, opts) {
// strip comments copied from: https://github.com/sindresorhus/strip-json-comments
var singleComment = 1;
var multiComment = 2;
function stripWithoutWhitespace() {
return "";
}
function stripWithWhitespace(str, start, end) {
return str.slice(start, end).replace(/\S/g, " ");
}
opts = opts || {};
var currentChar;
var nextChar;
var insideString = false;
var insideComment = false;
var offset = 0;
var ret = "";
var strip = opts.whitespace === false ? stripWithoutWhitespace : stripWithWhitespace;
for (var i = 0; i < str.length; i++) {
currentChar = str[i];
nextChar = str[i + 1];
if (!insideComment && currentChar === "\"") {
var escaped = str[i - 1] === "\\" && str[i - 2] !== "\\";
if (!escaped) {
insideString = !insideString;
}
}
if (insideString) {
continue;
}
if (!insideComment && currentChar + nextChar === "//") {
ret += str.slice(offset, i);
offset = i;
insideComment = singleComment;
i++;
} else if (insideComment === singleComment && currentChar + nextChar === "\r\n") {
i++;
insideComment = false;
ret += strip(str, offset, i);
offset = i;
continue;
} else if (insideComment === singleComment && currentChar === "\n") {
insideComment = false;
ret += strip(str, offset, i);
offset = i;
} else if (!insideComment && currentChar + nextChar === "/*") {
ret += str.slice(offset, i);
offset = i;
insideComment = multiComment;
i++;
continue;
} else if (insideComment === multiComment && currentChar + nextChar === "*/") {
i++;
insideComment = false;
ret += strip(str, offset, i + 1);
offset = i + 1;
continue;
}
}
return ret + (insideComment ? strip(str.substr(offset)) : str.substr(offset));
}

View File

@@ -25,6 +25,7 @@
"NNW": "NNW",
"UPDATE_NOTIFICATION": "MagicMirror² update beskikbaar.",
"UPDATE_NOTIFICATION_MODULE": "Update beskikbaar vir MODULE_NAME module.",
"UPDATE_INFO": "Die huidige installasie is COMMIT_COUNT agter op die BRANCH_NAME branch."
"UPDATE_NOTIFICATION_MODULE": "Update beskikbaar vir {MODULE_NAME} module.",
"UPDATE_INFO_SINGLE": "Die huidige installasie is {COMMIT_COUNT} commit agter op die {BRANCH_NAME} branch.",
"UPDATE_INFO_MULTIPLE": "Die huidige installasie is {COMMIT_COUNT} commits agter op die {BRANCH_NAME} branch."
}

33
translations/bg.json Normal file
View File

@@ -0,0 +1,33 @@
{
"LOADING": "Зареждане &hellip;",
"TODAY": "Днес",
"TOMORROW": "Утре",
"DAYAFTERTOMORROW": "Вдругиден",
"RUNNING": "Свършва на",
"EMPTY": "Няма предстоящи събития.",
"WEEK": "Седмица {weekNumber}",
"N": "С",
"NNE": "ССИ",
"NE": "СИ",
"ENE": "ИСИ",
"E": "И",
"ESE": "ИЮИ",
"SE": "ЮИ",
"SSE": "ЮЮИ",
"S": "Ю",
"SSW": "ЮЮЗ",
"SW": "ЮЗ",
"WSW": "ЗЮЗ",
"W": "З",
"WNW": "ЗСЗ",
"NW": "СЗ",
"NNW": "ССЗ",
"UPDATE_NOTIFICATION": "Налична актуализация за MagicMirror².",
"UPDATE_NOTIFICATION_MODULE": "Налична актуализация за {MODULE_NAME} модул.",
"UPDATE_INFO_SINGLE": "Текущата инсталация е изостанала с {COMMIT_COUNT} commit къмита на клон {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Текущата инсталация е изостанала с {COMMIT_COUNT} commits къмита на клон {BRANCH_NAME}."
}

33
translations/ca.json Normal file
View File

@@ -0,0 +1,33 @@
{
"LOADING": "Carregant &hellip;",
"TODAY": "Avui",
"TOMORROW": "Demà",
"DAYAFTERTOMORROW": "Demà passat",
"RUNNING": "Acaba en",
"EMPTY": "No hi ha esdeveniments programats.",
"WEEK": "Setmana",
"N": "N",
"NNE": "NNE",
"NE": "NE",
"ENE": "ENE",
"E": "E",
"ESE": "ESE",
"SE": "SE",
"SSE": "SSE",
"S": "S",
"SSW": "SSO",
"SW": "SO",
"WSW": "OSO",
"W": "O",
"WNW": "ONO",
"NW": "NO",
"NNW": "NNO",
"UPDATE_NOTIFICATION": "MagicMirror² actualizació disponible.",
"UPDATE_NOTIFICATION_MODULE": "Disponible una actualizació per al mòdul {MODULE_NAME}.",
"UPDATE_INFO_SINGLE": "La teva instal·lació actual està {COMMIT_COUNT} commit canvis darrere de la branca {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "La teva instal·lació actual està {COMMIT_COUNT} commits canvis darrere de la branca {BRANCH_NAME}."
}

33
translations/cs.json Normal file
View File

@@ -0,0 +1,33 @@
{
"LOADING": "Načítání &hellip;",
"TODAY": "Dnes",
"TOMORROW": "Zítra",
"DAYAFTERTOMORROW": "Pozítří",
"RUNNING": "Končí za",
"EMPTY": "Žádné nadcházející události.",
"WEEK": "{weekNumber}. týden",
"N": "S",
"NNE": "SSV",
"NE": "SV",
"ENE": "VSV",
"E": "V",
"ESE": "VJV",
"SE": "JV",
"SSE": "JJV",
"S": "J",
"SSW": "JJZ",
"SW": "JZ",
"WSW": "ZJZ",
"W": "Z",
"WNW": "ZSZ",
"NW": "SZ",
"NNW": "SSZ",
"UPDATE_NOTIFICATION": "Dostupná aktualizace pro MagicMirror².",
"UPDATE_NOTIFICATION_MODULE": "Dostupná aktualizace pro modul {MODULE_NAME}.",
"UPDATE_INFO_SINGLE": "Současná instalace je na větvi {BRANCH_NAME} pozadu o {COMMIT_COUNT} commit.",
"UPDATE_INFO_MULTIPLE": "Současná instalace je na větvi {BRANCH_NAME} pozadu o {COMMIT_COUNT} commits."
}

View File

@@ -7,7 +7,7 @@
"RUNNING": "Gorffen mewn",
"EMPTY": "Dim digwyddiadau.",
"WEEK": "Wythnos",
"WEEK": "Wythnos {weekNumber}",
"N": "Go",
"NNE": "GoGoDw",
@@ -27,6 +27,7 @@
"NNW": "GoGoGe",
"UPDATE_NOTIFICATION": "MagicMirror² mwy diweddar yn barod.",
"UPDATE_NOTIFICATION_MODULE": "Mae diweddaraiad ar gyfer y modiwl MODULE_NAME.",
"UPDATE_INFO": "Mae'r fersiwn bresenol COMMIT_COUNT commit tu ôl i'r gangen BRANCH_NAME."
"UPDATE_NOTIFICATION_MODULE": "Mae diweddaraiad ar gyfer y modiwl {MODULE_NAME}.",
"UPDATE_INFO_SINGLE": "Mae'r fersiwn bresenol {COMMIT_COUNT} commit tu ôl i'r gangen {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Mae'r fersiwn bresenol {COMMIT_COUNT} commit tu ôl i'r gangen {BRANCH_NAME}."
}

View File

@@ -26,6 +26,7 @@
"UPDATE_NOTIFICATION": "MagicMirror² opdatering tilgængelig.",
"UPDATE_NOTIFICATION_MODULE": "Opdatering tilgængelig for MODULE_NAME modulet.",
"UPDATE_INFO": "Den nuværende installation er COMMIT_COUNT bagud på BRANCH_NAME branch'en."
"UPDATE_NOTIFICATION_MODULE": "Opdatering tilgængelig for {MODULE_NAME} modulet.",
"UPDATE_INFO_SINGLE": "Den nuværende installation er {COMMIT_COUNT} commit bagud på {BRANCH_NAME} branch'en.",
"UPDATE_INFO_MULTIPLE": "Den nuværende installation er {COMMIT_COUNT} commits bagud på {BRANCH_NAME} branch'en."
}

View File

@@ -7,7 +7,7 @@
"RUNNING": "noch",
"EMPTY": "Keine Termine.",
"WEEK": "Woche",
"WEEK": "{weekNumber}. Kalenderwoche",
"N": "N",
"NNE": "NNO",
@@ -27,6 +27,9 @@
"NNW": "NNW",
"UPDATE_NOTIFICATION": "Aktualisierung für MagicMirror² verfügbar.",
"UPDATE_NOTIFICATION_MODULE": "Aktualisierung für das MODULE_NAME Modul verfügbar.",
"UPDATE_INFO": "Die aktuelle Installation ist COMMIT_COUNT hinter dem BRANCH_NAME branch."
"UPDATE_NOTIFICATION_MODULE": "Aktualisierung für das {MODULE_NAME} Modul verfügbar.",
"UPDATE_INFO_SINGLE": "Die aktuelle Installation ist {COMMIT_COUNT} Commit hinter dem {BRANCH_NAME} Branch.",
"UPDATE_INFO_MULTIPLE": "Die aktuelle Installation ist {COMMIT_COUNT} Commits hinter dem {BRANCH_NAME} Branch.",
"FEELS": "Gefühlt"
}

View File

@@ -3,11 +3,11 @@
"TODAY": "Today",
"TOMORROW": "Tomorrow",
"DAYAFTERTOMORROW": "The day after tomorrow",
"DAYAFTERTOMORROW": "In 2 days",
"RUNNING": "Ends in",
"EMPTY": "No upcoming events.",
"WEEK": "Week",
"WEEK": "Week {weekNumber}",
"N": "N",
"NNE": "NNE",
@@ -27,6 +27,9 @@
"NNW": "NNW",
"UPDATE_NOTIFICATION": "MagicMirror² update available.",
"UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.",
"UPDATE_INFO": "The current installation is COMMIT_COUNT behind on the BRANCH_NAME branch."
"UPDATE_NOTIFICATION_MODULE": "Update available for {MODULE_NAME} module.",
"UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.",
"FEELS": "Feels"
}

View File

@@ -7,7 +7,7 @@
"RUNNING": "Termina en",
"EMPTY": "No hay eventos programados.",
"WEEK": "Semana",
"WEEK": "Semana {weekNumber}",
"N": "N",
"NNE": "NNE",
@@ -27,6 +27,9 @@
"NNW": "NNO",
"UPDATE_NOTIFICATION": "MagicMirror² actualización disponible.",
"UPDATE_NOTIFICATION_MODULE": "Disponible una actualización para el módulo MODULE_NAME.",
"UPDATE_INFO": "Tu actual instalación está COMMIT_COUNT cambios detrás de la rama BRANCH_NAME."
"UPDATE_NOTIFICATION_MODULE": "Disponible una actualización para el módulo {MODULE_NAME}.",
"UPDATE_INFO_SINGLE": "Tu actual instalación está {COMMIT_COUNT} commit cambios detrás de la rama {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Tu actual instalación está {COMMIT_COUNT} commits cambios detrás de la rama {BRANCH_NAME}.",
"FEELS": "Sensación térmica de"
}

View File

@@ -25,6 +25,7 @@
"NNW": "Põhjaloe",
"UPDATE_NOTIFICATION": "MagicMirror²´le uuendus saadaval.",
"UPDATE_NOTIFICATION_MODULE": "Uuendus saadaval MODULE_NAME moodulile.",
"UPDATE_INFO": "Praegune paigaldus on COMMIT_COUNT tagapool BRANCH_NAME harul."
"UPDATE_NOTIFICATION_MODULE": "Uuendus saadaval {MODULE_NAME} moodulile.",
"UPDATE_INFO_SINGLE": "Praegune paigaldus on {COMMIT_COUNT} commit tagapool {BRANCH_NAME} harul.",
"UPDATE_INFO_MULTIPLE": "Praegune paigaldus on {COMMIT_COUNT} commits tagapool {BRANCH_NAME} harul."
}

View File

@@ -25,5 +25,5 @@
"NNW": "PPL",
"UPDATE_NOTIFICATION": "MagicMirror² päivitys saatavilla.",
"UPDATE_NOTIFICATION_MODULE": "Päivitys saatavilla moduulille MODULE_NAME."
"UPDATE_NOTIFICATION_MODULE": "Päivitys saatavilla moduulille {MODULE_NAME}."
}

View File

@@ -6,8 +6,8 @@
"DAYAFTERTOMORROW": "Après-demain",
"RUNNING": "Se termine dans",
"EMPTY": "Aucun RDV à venir.",
"WEEK": "Semaine",
"WEEK": "Semaine {weekNumber}",
"N": "N",
"NNE": "NNE",
@@ -25,8 +25,11 @@
"WNW": "ONO",
"NW": "NO",
"NNW": "NNO",
"UPDATE_NOTIFICATION": "Une mise à jour de MagicMirror² est disponible",
"UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module MODULE_NAME .",
"UPDATE_INFO": "L'installation actuelle est COMMIT_COUNT en retard sur la branche BRANCH_NAME ."
"UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module {MODULE_NAME} .",
"UPDATE_INFO_SINGLE": "L'installation actuelle est {COMMIT_COUNT} commit en retard sur la branche {BRANCH_NAME} .",
"UPDATE_INFO_MULTIPLE": "L'installation actuelle est {COMMIT_COUNT} commits en retard sur la branche {BRANCH_NAME} .",
"FEELS": "Ressenti"
}

35
translations/hr.json Normal file
View File

@@ -0,0 +1,35 @@
{
"LOADING": "Učitavanje &hellip;",
"TODAY": "Danas",
"TOMORROW": "Sutra",
"DAYAFTERTOMORROW": "Prekosutra",
"RUNNING": "Završava za",
"EMPTY": "Nema nadolazećih događaja.",
"WEEK": "Tjedan {weekNumber}",
"N": "N",
"NNE": "NNE",
"NE": "NE",
"ENE": "ENE",
"E": "E",
"ESE": "ESE",
"SE": "SE",
"SSE": "SSE",
"S": "S",
"SSW": "SSW",
"SW": "SW",
"WSW": "WSW",
"W": "W",
"WNW": "WNW",
"NW": "NW",
"NNW": "NNW",
"UPDATE_NOTIFICATION": "Dostupna je aktualizacija MagicMirror².",
"UPDATE_NOTIFICATION_MODULE": "Dostupna je aktualizacija modula {MODULE_NAME}.",
"UPDATE_INFO_SINGLE": "Instalirana verzija {COMMIT_COUNT} commit kasni za branch-om {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Instalirana verzija {COMMIT_COUNT} commit-ova kasni za branch-om {BRANCH_NAME}.",
"FEELS": "Osjeća"
}

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