Compare commits

...

495 Commits

Author SHA1 Message Date
Michael Teeuw
447c0bffdc Merge pull request #1968 from MichMich/develop
Release 2.11.0
2020-04-01 12:21:33 +02:00
Michael Teeuw
46df59d77a Prepare release 2.11.0 2020-04-01 11:52:39 +02:00
Michael Teeuw
8c85e240b7 Merge pull request #1967 from MichMich/rejas-date
Rejas date
2020-04-01 11:15:31 +02:00
Michael Teeuw
2464d01891 Fix date test. 2020-04-01 10:57:50 +02:00
Michael Teeuw
66642a19c5 Merge branch 'date' of https://github.com/rejas/MagicMirror into rejas-date 2020-04-01 10:08:33 +02:00
Michael Teeuw
f7f4e92e0a Merge branch 'develop' of https://github.com/MichMich/MagicMirror into develop 2020-04-01 09:58:55 +02:00
Michael Teeuw
2674bf22d8 Let's use sensible defaults. :) 2020-04-01 09:44:22 +02:00
rejas
ff7dc95e93 Implement suggestions from Michael 2020-03-28 09:24:30 +01:00
rejas
4e4d3418b3 LInt mockdate helper 2020-03-25 06:53:09 +01:00
rejas
3659b5b5c9 Update mockdate info 2020-03-25 06:40:23 +01:00
rejas
65e1b60fb7 Fix error when no compliments are set 2020-03-25 06:40:23 +01:00
rejas
e2427fe299 Try out a mockdate class 2020-03-25 06:40:23 +01:00
rejas
b08f882324 Add (failing) test for new date field 2020-03-25 06:40:23 +01:00
rejas
2a31ece0c6 Refactor code 2020-03-25 06:40:23 +01:00
rejas
cafa4211a6 Update changelog and add another birtday 2020-03-25 06:40:23 +01:00
rejas
82b50d3059 Add basic compliment on a specific day of the year 2020-03-25 06:39:24 +01:00
Michael Teeuw
39cb582e07 Merge pull request #1960 from effelle/develop
Update pt-br.json
2020-03-24 21:16:41 +01:00
Michael Teeuw
fe6645a420 Merge pull request #1955 from rejas/jshint
Remove jshint dependency
2020-03-24 21:16:12 +01:00
Michael Teeuw
a49816f4eb Merge pull request #1948 from rejas/lint
Run linter tests on travis (was: Run linter before commit)
2020-03-24 21:15:41 +01:00
Michael Teeuw
7ac97f854c Merge pull request #1937 from Legion2/develop
use relative path for socket.io
2020-03-24 21:13:36 +01:00
Federico Leoni
8f203852ca Update CHANGELOG.md 2020-03-24 10:06:12 -03:00
Federico Leoni
c342f7bce6 Update pt-br.json 2020-03-23 14:55:36 -03:00
Michael Teeuw
fe1c5df9d5 Merge pull request #1958 from bugsounet/master
issue #1956
2020-03-19 19:17:03 +01:00
bugsounet
8aa7a55559 issue #1956 2020-03-19 19:03:25 +01:00
rejas
e9461586cb Update changelog 2020-03-15 21:25:11 +01:00
rejas
a91f2de26a Remove jshint dependency, use eslint for config verification 2020-03-15 20:38:52 +01:00
rejas
5a4ae99283 Add no-multi-spaces rule to eslint and run it 2020-03-15 15:49:34 +01:00
rejas
437d030502 Remove some dead files, fix stylelint error 2020-03-11 12:44:44 +01:00
rejas
f3d45eff69 Fix js error but add a stylelint error 2020-03-11 11:43:45 +01:00
rejas
f22e39e22b Add switch to auto fix eslint and stylelint issues 2020-03-11 11:29:59 +01:00
rejas
7470a3b813 Dont fix eslint errors automatically 2020-03-11 10:48:43 +01:00
rejas
0b2e836e3d Commit a faulty js to see if travis complains 2020-03-11 09:01:21 +01:00
rejas
c3d57eef4f Remove husky again, move lint as test to travis 2020-03-11 08:52:25 +01:00
rejas
ca2571b438 Update changelog 2020-03-08 16:31:34 +01:00
rejas
6c926b8876 Add husky for pre-commit hook 2020-03-08 16:25:26 +01:00
rejas
5517a913d4 Run linter manually 2020-03-08 16:20:54 +01:00
Michael Teeuw
ef7720ff06 Merge pull request #1939 from dtreskunov/dtreskunov/fix-moon-illumination-percent-calc
fix floating point bug in moon illumination calc
2020-02-27 16:35:38 +01:00
Michael Teeuw
daa13b4b30 Merge pull request #1941 from bibaldo/bibaldo-currentweather
Add new default config to 'current weather' module
2020-02-27 16:34:26 +01:00
Michael Teeuw
9d4e237c09 Merge pull request #1938 from tosbaha/develop
Turkish translation update
2020-02-27 16:32:40 +01:00
Michael Teeuw
883c169ef7 Merge branch 'develop' into develop 2020-02-27 16:31:10 +01:00
Michael Teeuw
2b2b07f6b4 Merge pull request #1936 from buxxi/develop
Using promises to resolve which modules has a git remote
2020-02-27 16:30:23 +01:00
Jose Forte
f338b68f36 Update CHANGELOG.md 2020-02-21 10:31:40 -03:00
Jose Forte
c877b5fc70 Add new default config to 'current weather' module 2020-02-21 10:17:05 -03:00
Leon Kiefer
6aeec81072 removed additional slash from the socket.io path 2020-02-19 20:58:58 +01:00
Denis Treskunov
3f78c664eb fix floating point bug in moon illumination calc 2020-02-19 09:31:02 -08:00
mustafa
3b7df1d5c2 Changelog updated 2020-02-19 15:25:46 +03:00
mustafa
b6fc063c75 Turkish translation updated 2020-02-19 15:21:25 +03:00
Leon Kiefer
7b56817ae6 use relative path for socket.io fix #1934 2020-02-18 17:09:25 +01:00
buxxi
75c8c3f50b Using promises to resolve which modules has a git remote so the modules wont be skipped on the first check 2020-02-17 22:56:06 +01:00
Michael Teeuw
be07ff2129 Merge pull request #1923 from wast/patch-1
Fix Croatian translation for Calendar module word FEELS.
2020-02-12 16:34:45 +01:00
Michael Teeuw
56846b258c Merge branch 'develop' into patch-1 2020-02-12 16:34:33 +01:00
Michael Teeuw
f67310c8fa Merge pull request #1927 from fewieden/fix/weather-tests
1840 fix weather tests
2020-02-12 16:33:31 +01:00
Michael Teeuw
3dff3a1d4a Merge branch 'develop' into fix/weather-tests 2020-02-12 16:33:19 +01:00
Michael Teeuw
142d30b1c6 Merge pull request #1929 from buxxi/develop
Adding support for ignoring update check for certain modules
2020-02-11 19:21:46 +01:00
buxxi
c925e88475 Adding support for ignoring update check for certain modules 2020-02-11 18:13:39 +01:00
fewieden
56392e41d0 Add 1840 to changelog 2020-02-10 22:05:43 +01:00
fewieden
cd7b6450c6 Run tests also on lts and last stable version of nodejs 2020-02-10 21:51:29 +01:00
Felix Wiedenbach
de6a9f5811 fix timing issue in weather tests => returning response by url instead of index 2020-02-10 18:56:55 +01:00
Michael Teeuw
d7295948fd Merge pull request #1925 from dtreskunov/dtreskunov/fix-clock-module-moon-widget
fix bugs in showMoonTimes in clock module
2020-02-10 07:40:10 +01:00
Denis Treskunov
f4c3e412a2 fix bugs in showMoonTimes in clock module
1. as reported on https://github.com/MichMich/MagicMirror/pull/1885,
toLocaleString is not supported on old iPad Safari

2. SunCalc.getMoonTimes returns moonRise/moonSet times for the given date,
so moonRise can be after moonSet. We want to display the most relevant
times, which are today's moonRise and the next moonSet.
2020-02-09 22:03:35 -08:00
Stjepan
0d461e14d9 Merge pull request #1 from wast/patch-2
Update CHANGELOG.md
2020-02-09 10:26:54 +01:00
Stjepan
845c2571fe Update CHANGELOG.md
Fix FEELS translation in Croatian
2020-02-09 10:24:20 +01:00
Stjepan
d5c02d1031 Update hr.json
Fix FEELS translation in Croatian.
2020-02-09 10:22:41 +01:00
Michael Teeuw
30e93a9372 Merge pull request #1915 from normankong/develop
Support HTTPS Magicmirror (resubmit)
2020-02-06 10:10:06 +01:00
Michael Teeuw
e5cdcd190c Merge pull request #1918 from sergge1/develop
to make logic of parameter name to its actual behavior
2020-02-06 10:09:22 +01:00
sergge1
271f1cd71b to make logic of name to actual behaviour
hideTemp = false -> temp shold appear at the MM
hideTemp === true -> should not appear.
This is the logic. Worked vise versa, just corrected in the code
2020-02-06 00:04:48 +02:00
Michael Teeuw
4abb790e12 Merge pull request #1916 from d-Rickyy-b/log-date-timestamp
Log date timestamp
2020-02-05 12:43:26 +01:00
Michael Teeuw
89c9d4dfc4 Merge branch 'develop' into develop 2020-02-05 12:42:43 +01:00
karenorman
5511c15921 Update description in config.js.sample 2020-02-05 10:23:49 +08:00
Rico
e160f9a0f6 docs: add info about changed timestamp in changelog 2020-02-03 21:43:45 +01:00
Rico
65ea341ed0 feat: use date in log timestamp
I find it quite hard to look something up in the logs, when there is no date in the log file. The time itself is not sufficient for debugging, hence I suggest also logging the date.
2020-02-03 21:39:46 +01:00
Michael Teeuw
561827e896 Remove logging. 2020-02-01 19:12:05 +01:00
Michael Teeuw
1315aceb44 Remove duplicate 2.11 entry. 2020-02-01 17:41:49 +01:00
Michael Teeuw
19afd89df5 Fix Typo 2020-02-01 16:48:35 +01:00
Michael Teeuw
cbcc893f64 Restore MM.sh to keep PM2 working. 2020-02-01 15:16:23 +01:00
Michael Teeuw
a41bae3132 Cleanup 2020-02-01 15:05:26 +01:00
Michael Teeuw
8ef8388c32 Set default DISPLAY in package.json. 2020-02-01 15:02:47 +01:00
Michael Teeuw
e2d4a0fde8 Improve Feedback. 2020-02-01 14:52:52 +01:00
Michael Teeuw
65b1d61295 Improve feedback. 2020-02-01 14:43:43 +01:00
Michael Teeuw
1b38e73eb2 Fix install scripts. 2020-02-01 14:41:46 +01:00
Michael Teeuw
e2afee3275 Add feedback. 2020-02-01 14:39:50 +01:00
Michael Teeuw
7f2ecbd04f Cleanup. 2020-02-01 14:32:38 +01:00
Michael Teeuw
21dbaa1a03 Move DISPLAY default to electron script. 2020-02-01 13:59:13 +01:00
Michael Teeuw
09c1ea992c Update start methods. 2020-02-01 13:56:15 +01:00
karenorman
c8c327b6ab Add HTTPS support 2020-02-01 20:46:26 +08:00
Michael Teeuw
3bb801f579 Merge pull request #1914 from kblankenship1989/fix-iso-string
fix: add missing +1 for month
2020-02-01 04:57:12 +01:00
Kurtis Blankenship
ca49d8adb3 fix: correcting syntax 2020-01-31 20:46:43 -06:00
Kurtis Blankenship
88b15797a0 fix: add missing +1 for month 2020-01-31 20:35:46 -06:00
Michael Teeuw
28ebbd229c Merge pull request #1910 from sergge1/develop
added Ukrainian (uk) translation
2020-01-31 19:51:48 +01:00
sergge1
d7f6f46805 2.11.0 - added Ukrainian translation 2020-01-31 11:28:00 +02:00
sergge1
c41d6965ab added Ukrainian translation 2020-01-31 11:18:43 +02:00
sergge1
602119fa41 Ukrainian translation added 2020-01-31 11:17:11 +02:00
Michael Teeuw
4c2a9b47f5 Merge pull request #1906 from mmphego/develop
Grammar fixes on config.js.sample file
2020-01-26 16:46:48 +01:00
Michael Teeuw
5a26fefd6b Merge pull request #1903 from roramirez/not-log-error-vendor-test
Revert error logging introduced in 1e97b5c
2020-01-26 16:46:06 +01:00
Mpho Mphego
85d26f7320 Grammar fixes to config.
This PR does the following:

- [x] Corrects grammar on `config.js.sample`
2020-01-26 14:10:24 +02:00
Michael Teeuw
9487af1852 Merge pull request #1901 from kolbyjack/fix-97e5d92-regression
Fix regression causing 'undefined' to show up when config.hideTemp is…
2020-01-24 17:46:45 +01:00
Jon Kolb
a5b09d7846 Update CHANGELOG.md 2020-01-24 10:51:24 -05:00
Jon Kolb
6ba5056c96 Fix regression causing 'undefined' to show up when config.hideTemp is false 2020-01-24 10:42:45 -05:00
Michael Teeuw
50b4d05ef5 Merge pull request #1899 from MichMich/Skip-weather
Skip weather tests for now
2020-01-23 19:56:23 +01:00
Michael Teeuw
13ebe6d0e1 Check method 2020-01-23 19:52:07 +01:00
Michael Teeuw
95dedf0d80 Skip weather tests for now 2020-01-23 19:38:15 +01:00
Michael Teeuw
89a9368166 Merge pull request #1896 from sejka/patch-1
adds PRECIP polish translation
2020-01-23 19:29:43 +01:00
Michael Teeuw
c10e6159da Merge pull request #1895 from adi-miller/heb
Add Hebrew translation
2020-01-23 19:29:26 +01:00
Michael Teeuw
c30693b8a8 Merge pull request #1894 from roramirez/mode-permission-changelog
Revert permission mode for CHANGELOG
2020-01-23 19:28:59 +01:00
Michael Teeuw
7fc4f61182 Merge pull request #1892 from roramirez/upgrade-electron-6
Upgrade to Electron 6:
2020-01-23 19:28:37 +01:00
Karol Sejka
b44ab88927 adds PRECIP polish translation 2020-01-23 02:24:47 +01:00
adimi
2ca81f965a Add hebrew translation 2020-01-22 22:03:34 +02:00
Michael Teeuw
9f4dc1e382 Merge pull request #1887 from dtreskunov/dtreskunov/sun-and-moon
fix calculation of duration until next sunrise
2020-01-22 15:23:01 +01:00
Michael Teeuw
5ec9704e7f Merge pull request #1893 from roramirez/vendor-fix-test
Fix the vendor_spec test:
2020-01-22 15:22:29 +01:00
Denis Treskunov
7ad0cfd5f6 update CHANGELOG.md 2020-01-21 19:16:42 -08:00
Denis Treskunov
ac8f679dfd Merge remote-tracking branch 'upstream/develop' into dtreskunov/sun-and-moon 2020-01-21 19:13:51 -08:00
Rodrigo Ramírez Norambuena
a7ee2ef3a6 Upgrade to Electron 6:
New version of Electron has enable by default sandbox
http://www.atom.pe/docs/api/sandbox-option/

There was some issues to migrate a new version of Electron for
MagicMirror. Using the new version in Travis CI was failing at this
time. The problem is because the testing runner is a Docker enviroment

The issue experimented is the same topic mentioned here:
 - https://github.com/electron/electron/issues/17972
 - https://github.com/electron-userland/spectron/issues/443

The fix for to all of this is to set the `--no-sandbox` mode in CI
testing https://electronjs.org/docs/all#--no-sandbox

This change use the feature to set and disable Sandbox using
by enviroment variable `ELECTRON_DISABLE_SANDBOX=1`
https://github.com/electron/electron/pull/16576

This change has reference #1800
2020-01-20 22:19:10 +00:00
Rodrigo Ramírez Norambuena
6e7edd9824 Revert error logging introduced in 1e97b5c 2020-01-19 08:50:37 -03:00
Rodrigo Ramírez Norambuena
81b310086f Revert permission mode for CHANGELOG
This change revert change of permission mode for file CHANGELOG.md
introduced in commit a619fc4fef
2020-01-19 04:40:05 -03:00
Rodrigo Ramírez Norambuena
cce57c7229 Fix the vendor_spec test:
This change set after startApplication the configuration to run this
test.

The previous statement when e2e are running using --recursive the
MM_CONFIG_FILE was setting by the test running before this one.
2020-01-19 04:06:21 -03:00
Denis Treskunov
9cffa3dd67 fix calculation of duration until next sunrise 2020-01-18 17:02:40 -08:00
Michael Teeuw
7b3a59455d Merge pull request #1885 from dtreskunov/dtreskunov/sun-and-moon
add sun/moon rise/set times
2020-01-18 21:29:41 +01:00
Michael Teeuw
fefe5659d3 Merge branch 'develop' into dtreskunov/sun-and-moon 2020-01-18 21:20:35 +01:00
Michael Teeuw
db72e22e61 Fuck it. 2020-01-18 21:17:54 +01:00
Michael Teeuw
a85ed709af Remove newlines. 2020-01-18 21:09:18 +01:00
Michael Teeuw
e24d1a1261 Fix newlines? 2020-01-18 21:02:49 +01:00
Michael Teeuw
d6f1bf18de Merge pull request #1884 from kblankenship1989/fix-timeshift-calendar
fix: Issue #1798 - fixing recurrent calendar events crosstime DST
2020-01-18 19:59:24 +01:00
Michael Teeuw
97e5d923fa Merge pull request #1882 from adi-miller/hide_weather
Added the ability to hide the temp label and weather icon in the `cur…
2020-01-18 19:54:30 +01:00
Denis Treskunov
36762a6e46 add duration until next sunset/sunrise 2020-01-18 09:50:43 -08:00
Denis Treskunov
40a9f9dd85 fix stylelint:simple 2020-01-18 08:45:52 -08:00
Denis Treskunov
85a52523cf show sun/moon as bright rather than bold if obj is visible 2020-01-18 08:43:33 -08:00
Kurtis Blankenship
5ed7974099 Merge branch 'fix-timeshift-calendar' of github.com:kblankenship1989/MagicMirror into fix-timeshift-calendar 2020-01-18 06:38:13 -06:00
Kurtis Blankenship
70ca9ce2e0 fix: fixing lint error 2020-01-18 06:38:01 -06:00
kblankenship1989
af9f555e8f fix: fixing copy / paste error on futureLocal 2020-01-18 06:28:21 -06:00
Denis Treskunov
08b9e7b5b5 add sun/moon rise/set times
Icons become bold when the object is above the horizon. Also shows
percent of moon illumination.
2020-01-17 22:28:24 -08:00
Kurtis Blankenship
84f74c53b5 perf: fix wording on changelog 2020-01-17 23:08:34 -06:00
Kurtis Blankenship
762bae907c perf: updating changelog 2020-01-17 23:04:51 -06:00
Kurtis Blankenship
2b738fa14b Merge branch 'develop' of github.com:MichMich/MagicMirror into fix-timeshift-calendar 2020-01-17 22:54:59 -06:00
Kurtis Blankenship
8aa745471b fix: Issue #1798 - fixing recurrent calendar events crosstime DST 2020-01-17 22:53:14 -06:00
Adi Miller
63950f572a Adding trailing enter to avoid build warning. 2020-01-16 16:47:49 +02:00
Adi Miller
81aab7e86f Added the ability to hide the temp label and weather icon in the currentweather module to allow showing only information such as wind and sunset/rise. 2020-01-16 16:26:20 +02:00
Michael Teeuw
1fd5fd4832 Update ISSUE_TEMPLATE.md 2020-01-14 09:20:50 +01:00
Michael Teeuw
ceaf478a84 Merge pull request #1875 from bastilimbach/master
Add different GitHub repos to issue template
2020-01-13 12:29:52 +01:00
Sebastian Limbach
61256f98ca Fix typo 2020-01-13 09:40:38 +01:00
Sebastian Limbach
818ed5d189 Add different GitHub repos
This commit adds the official docker image repo as well as the installation scripts repo to the issue template to avoid the creation of issue which don't belong to the MagicMirror core.
2020-01-12 20:15:04 +01:00
Michael Teeuw
0ca00e5059 Merge pull request #1874 from sdetweil/develop
cleanup installers folder
2020-01-12 17:44:50 +01:00
Sam Detweiler
a2c743167d cleanup installers foldr 2020-01-12 10:19:37 -06:00
Sam Detweiler
786641662c cleanup installers folder 2020-01-12 10:16:59 -06:00
sam detweiler
8380896deb Merge pull request #42 from MichMich/develop
synch with fork source
2020-01-12 10:14:44 -06:00
Michael Teeuw
1152689f34 Remove docs and link to docs.magicmirror.builders. 2020-01-12 13:01:56 +01:00
Michael Teeuw
d2afdc82f1 Merge branch 'master' into develop. 2020-01-10 22:27:44 +01:00
Michael Teeuw
5bf90ae31d Merge pull request #1872 from MichMich/2.10.1-docs
2.10.1 docs
2020-01-10 22:23:18 +01:00
Michael Teeuw
b533e3d3e1 Add install link. 2020-01-10 22:16:19 +01:00
Michael Teeuw
4d9df309c8 Use https. 2020-01-10 22:14:13 +01:00
Michael Teeuw
65126365a6 Add links to documentation site. 2020-01-10 22:11:10 +01:00
Michael Teeuw
0afe403057 Merge pull request #1861 from khassel/develop
fix run-start.sh: if running in docker-container, just start electron…
2020-01-08 16:25:57 +01:00
Michael Teeuw
d500353722 Merge pull request #1871 from shahrukh-alizai/patch-1
Remove the extra two "of" words from Manifesto content.
2020-01-08 16:24:29 +01:00
Shahrukh Khan
eea8b4dfbf Update README.md
Remove extra two "of" words
2020-01-08 18:10:36 +05:00
Karsten Hassel
20a9150ea3 fix run-start.sh: if running in docker-container, just start electron, see #1859 2020-01-03 19:17:50 +01:00
sam detweiler
c51e613568 Merge pull request #34 from MichMich/develop
sync Develop
2020-01-02 09:31:24 -06:00
Michael Teeuw
69a887fb05 Merge pull request #1851 from ZakarFin/fi-translations
Add missing translations for fi
2020-01-02 16:16:00 +01:00
Michael Teeuw
01c1d150c6 Merge branch 'develop' into fi-translations 2020-01-02 16:15:47 +01:00
Michael Teeuw
c51c09348e Merge pull request #1857 from HeikoGr/master
force declaration of public ip adress in config file (ISSUE #1852)
2020-01-02 16:14:14 +01:00
Michael Teeuw
0a4df191a7 Merge branch 'develop' into master 2020-01-02 16:13:30 +01:00
HeikoGr
760995773e Update CHANGELOG.md 2020-01-02 16:04:38 +01:00
HeikoGr
94ff8a9b04 force declaration of public ip adress in config file (ISSUE #1852) 2020-01-02 14:15:24 +00:00
Sami Mäkinen
10d5529f79 Add missing translations for fi 2020-01-02 00:04:59 +02:00
Michael Teeuw
2b511b67c3 Prepare for 2.11.0 2020-01-01 22:21:55 +01:00
Michael Teeuw
b595cdd31e Merge pull request #1850 from MichMich/develop
Version 2.10.0
2020-01-01 22:06:34 +01:00
Michael Teeuw
e64870ca58 Prepare 2.10.0 release. 2020-01-01 21:50:02 +01:00
Michael Teeuw
a40f8d5b3a Merge pull request #1848 from oemel09/fix-calendar-full-multiple-day-events
Display still ongoing multiple day events as happening today
2020-01-01 21:47:54 +01:00
Michael Teeuw
61b8871ead Update package lock. 2020-01-01 21:23:25 +01:00
Michael Teeuw
4b5a3ed44d Downgrade Electron to check Travis. 2020-01-01 21:12:31 +01:00
Michael Teeuw
7d6b7b2691 Reverse tests for debugging. 2020-01-01 21:01:17 +01:00
Michael Teeuw
3dba46b74f Downgrade test utils. 2020-01-01 20:26:16 +01:00
Michael Teeuw
2767e31a28 Downgrade Spectron 2020-01-01 20:18:48 +01:00
Michael Teeuw
40886bcf08 Remove Snyk 2020-01-01 19:48:10 +01:00
Michael Teeuw
04dde74572 Move and alias node_module. 2020-01-01 19:17:23 +01:00
Michael Teeuw
29f3ac065f Re-enable weather tests. 2020-01-01 19:03:32 +01:00
Michael Teeuw
d2b30b4f9c Merge pull request #1849 from roramirez/fix-ci
Fix CI
2020-01-01 18:59:34 +01:00
Rodrigo Ramírez Norambuena
3a42663dea Update changelog for revert commit 2019-12-31 19:40:59 +00:00
Rodrigo Ramírez Norambuena
905d0ca409 Update package-lock* 2019-12-31 19:33:21 +00:00
Rodrigo Ramírez Norambuena
7e2c78666e Revert "stop helper class being erased"
This reverts commit 2fbedca746.
2019-12-31 19:31:13 +00:00
oemel09
6a5f0225fe Display still ongoing multiple day events as happening today 2019-12-31 13:59:58 +01:00
Michael Teeuw
ddb01fca31 Merge pull request #1847 from oemel09/fix-full-day-events
Fixes some events not being recognized as full day events
2019-12-31 13:20:18 +01:00
oemel09
684a3cd49e Fixes some events not being recognized as full day events 2019-12-31 12:37:29 +01:00
Michael Teeuw
ad5e42115b Merge pull request #1846 from oemel09/fix-calendar-debug
Fixes authentication in calendar debug script
2019-12-31 11:31:09 +01:00
oemel09
2d6b8d0c47 Fixes authentication in calendar debug script 2019-12-31 11:27:15 +01:00
Michael Teeuw
61471e5449 Merge pull request #1844 from RGMTAMU/minorREADMEFix
Very minor README spelling error fix
2019-12-30 14:34:15 +01:00
Shiva
b6f7ab7a9c Very minor README spelling error fix 2019-12-29 16:38:47 -06:00
Michael Teeuw
2d061be98e Fix corrupted file. 2019-12-29 15:12:50 +01:00
Michael Teeuw
d6e97b8c76 Cleanup. 2019-12-29 14:52:26 +01:00
Michael Teeuw
9e44660746 Merge pull request #1841 from MichMich/skip-weather-tests
Skip weather tests.
2019-12-29 14:37:31 +01:00
Michael Teeuw
03e6ca58ab Link to issue. 2019-12-29 14:34:07 +01:00
sam detweiler
e884cc1f75 Merge pull request #32 from MichMich/develop
sync Develop
2019-12-29 07:33:57 -06:00
Michael Teeuw
94c63b0554 Skipping weather tests for now. 2019-12-29 14:30:12 +01:00
Michael Teeuw
c7c8c40a70 disable test 2019-12-29 13:56:53 +01:00
Michael Teeuw
91c726c706 Merge branch 'pr/SpencerCornish/1834' into develop 2019-12-29 13:51:18 +01:00
Michael Teeuw
c9805e7ac9 Updated package.lock files. 2019-12-29 13:47:05 +01:00
Michael Teeuw
a4db0e40e3 Merge pull request #1815 from sdetweil/newservermode
fix regression and add support for commented lines in config.js
2019-12-29 00:01:23 +01:00
Michael Teeuw
305b55cb2a Merge pull request #1814 from sdetweil/newscripts
updates for libc6++ on pi 0, node 10
2019-12-29 00:00:03 +01:00
Sam Detweiler
272ca1ac4f synch package-lock.json for testing 2019-12-28 15:46:10 -06:00
Sam Detweiler
bbc6a1b38f synch package-lock for testing 2019-12-28 15:39:57 -06:00
sam detweiler
525c235d3b Merge pull request #31 from MichMich/develop
synch to Develop
2019-12-28 15:01:54 -06:00
Michael Teeuw
0e93713464 Merge pull request #1826 from sdetweil/fixcompliments
Fix compliments to support \n
2019-12-28 22:01:16 +01:00
sam detweiler
8f60e103f9 Merge branch 'develop' into fixcompliments 2019-12-28 14:58:01 -06:00
Michael Teeuw
9cc702241d Merge pull request #1827 from sdetweil/fix_helper
Fix helper being erased accidentally
2019-12-28 21:44:10 +01:00
sam detweiler
0d61c44232 Merge branch 'develop' into newscripts 2019-12-28 14:42:06 -06:00
sam detweiler
9a8faac316 Merge branch 'develop' into fix_helper 2019-12-28 14:36:49 -06:00
Michael Teeuw
05f710cb5c Merge pull request #1838 from tbouron/fix/module-header
Module header updates correctly, if a module need to dynamically show/hide its header based on a condition
2019-12-28 19:41:42 +01:00
Michael Teeuw
c6184769e7 Merge pull request #1836 from akraus53/patch-2
Added a padding between days in dateheader mode
2019-12-28 19:41:15 +01:00
Michael Teeuw
515d1bd920 Merge branch 'develop' into fixcompliments 2019-12-28 19:38:18 +01:00
Michael Teeuw
b19cb17bba Merge branch 'develop' into newservermode 2019-12-28 19:37:41 +01:00
Michael Teeuw
c1e808bce6 Merge pull request #1806 from sdetweil/newElectron
New electron version
2019-12-28 19:35:57 +01:00
Thomas Bouron
d8b7292d4b Fix module header update if using customising from module.getHeaders() 2019-12-26 14:07:13 +00:00
Sam Detweiler
edd5e2f5bc move lxsession screensaver checking to last, some systems still have the folder but don't use it 2019-12-24 06:55:12 -06:00
Alexander Kraus
efc6cb73b2 Added my contribution to the changelog 2019-12-20 15:22:14 +01:00
Alexander Kraus
ecd79dc34b Added a padding between days in dateheader mode 2019-12-20 15:20:12 +01:00
Spencer Cornish
cdfc8b825d Changelog 2019-12-19 21:11:57 -07:00
Spencer Cornish
301344c96d Update unsecure dependencies 2019-12-19 21:02:39 -07:00
sam detweiler
5279995c3b Merge branch 'develop' into newservermode 2019-12-13 08:03:06 -06:00
Sam Detweiler
5176b06b59 add check for root user and abort 2019-12-13 08:01:32 -06:00
Sam Detweiler
ed61ac624d http default if no address is 0.0.0.0, not localhost, hard code localhost as per readme 2019-12-10 12:15:30 -06:00
Sam Detweiler
4da6e3ecee add test for error on 200 rc test path 2019-12-10 11:29:26 -06:00
Sam Detweiler
9e5561936a Merge branch 'newElectron' of https://github.com/sdetweil/MagicMirror into newElectron 2019-12-10 10:54:18 -06:00
Sam Detweiler
843cd0eff6 change spectron version to match doc, update locak file 2019-12-10 10:53:41 -06:00
sam detweiler
6b6ee934a1 Merge branch 'develop' into newElectron 2019-12-10 08:31:50 -06:00
Sam Detweiler
aea57ffaf4 add new run_start.sh script 2019-12-10 08:29:18 -06:00
Sam Detweiler
87b48661fa remove forced npm install 2019-12-09 16:41:56 -06:00
Sam Detweiler
556fa44858 move travis to node 10 , latest npm 2019-12-09 16:26:49 -06:00
Sam Detweiler
1c3e196508 move travis to node 10 and latest npm 2019-12-09 16:24:16 -06:00
Sam Detweiler
9b6812ad0c add package-lock file 2019-12-09 11:15:50 -06:00
Sam Detweiler
b7944c7fa4 add changelog 2019-12-09 10:36:21 -06:00
Sam Detweiler
2fbedca746 stop helper class being erased 2019-12-09 10:33:35 -06:00
Sam Detweiler
340d04a48c add changelog entry 2019-12-09 09:52:15 -06:00
Sam Detweiler
460a383ffc add changelog 2019-12-09 09:46:45 -06:00
Sam Detweiler
52c4e3a256 add changelog update 2019-12-09 09:43:32 -06:00
sam detweiler
4338f11eb1 Merge branch 'develop' into newscripts 2019-12-09 09:40:06 -06:00
Sam Detweiler
42bab052e0 fix comment 2019-12-08 10:07:55 -06:00
Sam Detweiler
0cb377618e add support for sequential compliments usage 2019-12-08 09:28:50 -06:00
Sam Detweiler
5e4d25b957 add support for newline (\n) in compliment text 2019-12-08 08:50:00 -06:00
Sam Detweiler
153d1853fa add apt get upgrade to actually upgrade files, as OS repos are lagging 2019-12-08 08:46:41 -06:00
Sam Detweiler
22a3448461 latest fixes for install and pm2 2019-12-08 08:40:01 -06:00
Sam Detweiler
2cad869680 latest fixes for install and pm2 2019-12-08 08:35:50 -06:00
Your Name
bc912a8ea4 Merge branch 'newscripts' of https://github.com/sdetweil/MagicMirror into newscripts 2019-11-27 08:38:12 -06:00
Your Name
db9176c284 fix pm2 install needing sudo 2019-11-27 08:37:51 -06:00
Sam Detweiler
06308210c0 add support for secured apt-get repositories 2019-11-23 06:57:10 -06:00
Sam Detweiler
63d9904370 regression checking for macOS 2019-11-22 06:30:46 -06:00
Sam Detweiler
caaeff5cb7 add support for commented lines in config.js 2019-11-22 06:22:21 -06:00
Sam Detweiler
d8c93d3455 Merge branch 'newscripts' of https://github.com/sdetweil/MagicMirror into newscripts 2019-11-22 06:18:31 -06:00
Sam Detweiler
cb28e5fddc watch out for commented lines in config 2019-11-22 06:18:04 -06:00
Sam Detweiler
7d7ec1a00b updates for libc6++ on pi 0, node 10 2019-11-21 22:32:48 -06:00
Sam Detweiler
a5bc8dfa3f add package.json to update 2019-11-19 19:50:46 -06:00
sam detweiler
d74f055180 Merge pull request #28 from MichMich/develop
sync changes
2019-11-19 17:15:00 -08:00
Michael Teeuw
73be6c35a6 Merge pull request #1808 from FritzJay/develop
Use config.decimalSymbol when displaying the predicted amount of rain in weatherforecast
2019-11-13 08:01:42 +01:00
Fritz Jay
24f74e1400 Fixed spelling error. 2019-11-12 19:30:42 -08:00
Fritz Jay
8900e069f3 Updated changelog 2019-11-12 19:26:38 -08:00
Fritz Jay
1dfec119bb Fixed bug that was causing predicted amount of rain value to use DOT as a decimal separator regardless of the decimalSymbol config value. 2019-11-12 18:16:55 -08:00
Sam Detweiler
9e23d35f01 update elecgtron version 2019-11-08 13:20:39 -08:00
Sam Detweiler
cd2b240308 upgrade electron 2019-11-08 11:43:17 -08:00
Michael Teeuw
749f366a4a Merge pull request #1796 from sdetweil/fix-untrack
Fix untrack-css on MacOS
2019-10-27 15:27:29 +01:00
Sam Detweiler
420aaa92fa fix for MacOS 2019-10-27 09:14:55 -05:00
Sam Detweiler
985698bbc3 fix for no 'Xorg' process on MacOS 2019-10-27 09:13:44 -05:00
Michael Teeuw
f9c9139f20 Merge pull request #1790 from sdetweil/patch-1
left electron left in dependencies section vs pr 1788
2019-10-25 16:38:35 +02:00
Michael Teeuw
f308e7541f Merge pull request #1791 from sdetweil/newscripts
New scripts cleanuo
2019-10-25 16:09:14 +02:00
Sam Detweiler
cb7ccd7854 update readme and upgrade script to work from website, fix tabs in config sample 2019-10-25 08:46:41 -05:00
Sam Detweiler
a4953028d0 remove electron from dependencies section 2019-10-25 08:13:25 -05:00
sam detweiler
3d6485588d pr merge order left electron in dependencies section 2019-10-25 07:37:21 -05:00
Michael Teeuw
51bb9fede7 Merge pull request #1789 from sdetweil/newscripts
New scripts for install, update and pm2 config
2019-10-25 14:26:13 +02:00
Michael Teeuw
269c429959 Merge branch 'develop' into newscripts 2019-10-25 14:25:57 +02:00
Michael Teeuw
fcdc84a12a Merge pull request #1788 from sdetweil/newservermode
New servermode
2019-10-25 14:24:05 +02:00
Michael Teeuw
ecdd9734eb Merge branch 'develop' into newservermode 2019-10-25 14:23:50 +02:00
Michael Teeuw
84fc2c65af Merge pull request #1784 from MichMich/dependabot/npm_and_yarn/nwmatcher-1.4.4
Bump nwmatcher from 1.4.3 to 1.4.4
2019-10-25 14:21:31 +02:00
Sam Detweiler
c8849a17b6 add detection of xwindows running 2019-10-25 06:22:10 -05:00
Sam Detweiler
d8b49218e9 Merge branch 'newscripts' of https://github.com/sdetweil/MagicMirror into newscripts 2019-10-24 22:14:19 -05:00
Sam Detweiler
e95023e8cc remove forced arch parm for armv6 to armv7, doesn't work 2019-10-24 22:13:46 -05:00
sam detweiler
878710e2cf Add text for new upgrade script 2019-10-24 14:21:38 -05:00
Sam Detweiler
0677d0a810 add changelog info 2019-10-24 09:36:15 -05:00
Sam Detweiler
9c98fea8f4 add new upgrade/pm2 scripts, update installer script 2019-10-24 09:33:34 -05:00
Sam Detweiler
e958f33450 add support for armv6l using serveronlymode, make serveronly config option, electron install optional 2019-10-24 09:20:33 -05:00
sam detweiler
937080b011 Merge pull request #19 from MichMich/master
catch up to 2.9
2019-10-24 07:06:53 -05:00
Michael Teeuw
aee5803dd2 Merge pull request #1773 from qistoph/timestamp_log
Timestamp log
2019-10-23 13:07:06 +02:00
Chris van Marle
f1f394b871 Add merge to package-lock to fix Travis fail 2019-10-23 10:44:30 +00:00
Chris van Marle
81a32b56f0 Fix TravisCI error 2019-10-23 10:16:01 +00:00
Chris van Marle
a6aae70a55 Add UTC timestamp to console log 2019-10-23 10:16:01 +00:00
dependabot[bot]
823eb23773 Bump nwmatcher from 1.4.3 to 1.4.4
Bumps [nwmatcher](https://github.com/dperini/nwmatcher) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/dperini/nwmatcher/releases)
- [Commits](https://github.com/dperini/nwmatcher/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2019-10-22 17:12:07 +00:00
Michael Teeuw
1ff51822df Prepare 2.10.0-develop 2019-10-01 19:47:40 +02:00
Michael Teeuw
500147e130 Merge pull request #1776 from MichMich/develop
v2.9.0
2019-10-01 19:45:34 +02:00
Michael Teeuw
d90de18d99 Prepare 2.9.0 2019-10-01 19:36:51 +02:00
Michael Teeuw
ba4f48662f Merge pull request #1770 from buxxi/develop
Changes to the clock module regarding the notifications sent.
2019-10-01 18:14:41 +02:00
buxxi
d208437c05 Changes to the clock module regarding the notifications sent. Disable sending of CLOCK_SECOND when displaySeconds not set. Avoid drifting by calculating the interval each time and use setTimeout instead of setInterval. Make sure the values sent with CLOCK_SECOND and CLOCK_MINUTE has the correct values instead of starting at 00:00 on startup 2019-09-22 17:33:01 +02:00
Michael Teeuw
01faa2e1d7 Merge pull request #1768 from fewieden/feature/automated-forecast-tests
Automated weather forecast tests
2019-09-13 18:57:26 +02:00
Felix Wiedenbach
c630c387d6 it should render colored rows 2019-09-13 17:06:58 +02:00
Felix Wiedenbach
a774718607 it should render custom table class 2019-09-13 16:21:12 +02:00
Felix Wiedenbach
9430c70d0d it should render fading rows in weather forecast 2019-09-13 15:44:38 +02:00
Felix Wiedenbach
f3e893fddb it should render min and max temperatures 2019-09-13 15:08:44 +02:00
Felix Wiedenbach
11e144ca64 it should render days and icons in weather forecast 2019-09-13 14:58:04 +02:00
Felix Wiedenbach
fbceab707e draft for default weather forecast test cases 2019-09-13 13:54:55 +02:00
Felix Wiedenbach
5b2efc43b9 default weather forecast test config 2019-09-13 13:54:17 +02:00
Felix Wiedenbach
8d85d1aa2d move mocks to separate directory for better overview 2019-09-13 13:53:15 +02:00
Michael Teeuw
b469fc7577 Merge pull request #1755 from buxxi/develop
Fixing weatherforecast module not displaying rain amount if using fal…
2019-09-11 16:51:04 +02:00
Michael Teeuw
9db54831c8 Merge pull request #1766 from fewieden/feature/automated-weather-tests
Automated tests for new weather module
2019-09-11 16:45:28 +02:00
Michael Teeuw
9b88bde09a Merge pull request #1767 from roramirez/prevent-error-404-test-vendor
Prevent error CI build for 404 Test vendors
2019-09-11 16:44:24 +02:00
Felix Wiedenbach
aad03a74c5 fix test path and eslint 2019-09-11 15:58:51 +02:00
fewieden
55eb6e2e5c Add changelog entry 2019-09-11 13:51:19 +02:00
fewieden
ca07355873 adjust tests to new translations 2019-09-11 13:30:54 +02:00
fewieden
11a59e26b2 update package locks 2019-09-11 13:13:08 +02:00
fewieden
ec6d9e3521 Merge branch 'develop' into feature/automated-weather-tests
# Conflicts:
#	modules/default/weather/weather.js
#	package-lock.json
#	package.json
2019-09-11 13:07:31 +02:00
fewieden
230accd31e fix current weather tests 2019-09-11 12:59:55 +02:00
fewieden
a24a4a747e current weather tests 2019-09-11 12:27:06 +02:00
Rodrigo Ramírez Norambuena
1e97b5c27a Prevent error CI build for 404 Test vendors 2019-09-10 18:24:45 -03:00
buxxi
a314ea1aa3 Fixing weatherforecast module not displaying rain amount if using fallback endpoint 2019-08-18 12:44:28 +02:00
Michael Teeuw
a77128d5f7 Merge pull request #1752 from roramirez/mode-en.json-translation
Revert change mode file en.json for translations
2019-08-16 13:18:01 +02:00
Michael Teeuw
1b2673367e Merge pull request #1753 from roramirez/tests-404-vendors
Tests 404 vendors
2019-08-16 13:17:46 +02:00
Rodrigo Ramírez Norambuena
ce10e91a60 Merge remote-tracking branch 'origin/develop' into tests-404-vendors 2019-08-09 01:47:35 -04:00
Rodrigo Ramírez Norambuena
5244b37d2c Revert change mode file en.json for translations
This change it done in commit c80e04fe8d
and this patch revert to old status for this file.
2019-08-09 01:29:01 -04:00
Michael Teeuw
1239c6716b Merge pull request #1749 from putera/develop
Adding a Malay translations
2019-08-06 07:48:03 +02:00
Zulkifli Mohamed
67f6258ab0 Adding a Malay translations 2019-08-06 08:39:57 +08:00
Zulkifli Mohamed
9f63172b43 Adding a Malay translations 2019-08-06 08:36:39 +08:00
Michael Teeuw
bd8bfeb525 Merge pull request #1745 from roramirez/update-year-license
Update year Copyright License
2019-08-03 18:47:28 +02:00
Michael Teeuw
4918c4ef4b Merge pull request #1744 from roramirez/custom-css-step-1
Skip from worktree the css/custom.css:
2019-08-03 18:47:04 +02:00
Michael Teeuw
00d9ea9344 Merge pull request #1743 from roramirez/lint
linter fixes
2019-08-03 18:45:58 +02:00
Michael Teeuw
33537cde76 Merge pull request #1742 from roramirez/es-update
Add PRECIP for the Spanish translation
2019-08-03 18:45:27 +02:00
Michael Teeuw
3d5db5c9ca Merge pull request #1741 from roramirez/remove-comment-bash-lines
installer: Remove comment lines added in PR #1715
2019-08-03 18:44:58 +02:00
Michael Teeuw
fdf339514d Merge pull request #1748 from roramirez/fix-build
Fix Build Failed in Travis CI:
2019-08-03 18:32:01 +02:00
Rodrigo Ramírez Norambuena
937c4e485a Merge remote-tracking branch 'origin/develop' into remove-comment-bash-lines 2019-08-02 00:48:44 -04:00
Rodrigo Ramírez Norambuena
837c060e1f Merge remote-tracking branch 'origin/develop' into custom-css-step-1 2019-08-02 00:47:24 -04:00
Rodrigo Ramírez Norambuena
00bacd7dde Merge remote-tracking branch 'origin/develop' into es-update 2019-08-02 00:46:54 -04:00
Rodrigo Ramírez Norambuena
781031775e Merge remote-tracking branch 'origin/develop' into lint 2019-08-02 00:46:32 -04:00
Rodrigo Ramírez Norambuena
97aee3d375 Merge remote-tracking branch 'origin/develop' into update-year-license 2019-08-02 00:46:06 -04:00
Rodrigo Ramírez Norambuena
34698751f2 Fix Build Failed in Travis CI:
This commit set trusty as image to use and revert this commit changes

Add xvfb as service: 3c31460f2f
Remove failing script: d5cb60b19c
2019-08-02 00:31:43 -04:00
Michael Teeuw
3c31460f2f Add xvfb as service. 2019-07-31 09:53:46 +02:00
Michael Teeuw
d5cb60b19c Remove failing script. 2019-07-31 09:49:22 +02:00
Rodrigo Ramírez Norambuena
3a7cfe3208 Enable Test e2e/vendor_spec.js 2019-07-26 02:21:56 -04:00
Rodrigo Ramírez Norambuena
f079cdad64 Merge remote-tracking branch 'origin/develop' into tests-404-vendors 2019-07-26 02:19:10 -04:00
Rodrigo Ramírez Norambuena
2822303138 Skip from worktree the css/custom.css:
On the next release the css/custom.css will rename to
css/custom.css.sample

This change run git instructions to detach the file from own local
repository. This instructions are called in untrack-css.sh file from
run-start.sh and npm postinstall step

Reference #1540
2019-07-26 02:12:10 -04:00
Rodrigo Ramírez Norambuena
e38de75520 Update year Copyright License 2019-07-26 02:04:38 -04:00
Rodrigo Ramírez Norambuena
2723604d3e installer: Remove comment lines added in PR #1715 2019-07-26 01:25:54 -04:00
Rodrigo Ramírez Norambuena
82ee051c1a linter fixes 2019-07-26 01:25:26 -04:00
Rodrigo Ramírez Norambuena
7bdf49b7e0 Add PRECIP for the Spanish translation 2019-07-26 01:24:59 -04:00
Michael Teeuw
32521aba6b Merge pull request #1724 from vincep5/develop
Fix for weather module not refreshing data after exception
2019-07-11 20:04:30 +02:00
vincep5
819c4cde1c Fix for weather module not refreshing data after exception 2019-07-11 12:49:24 -05:00
Michael Teeuw
776c486b1a Merge pull request #1720 from nischi/master
Fixing send notification in newsfeed module
2019-07-09 10:43:09 +02:00
Michael Teeuw
bcd97120a4 Merge branch 'develop' into master 2019-07-09 10:33:29 +02:00
Thierry Nischelwitzer
312bfb8509 add changes to changelog 2019-07-09 10:24:43 +02:00
Thierry Nischelwitzer
11c9a50931 send NEWS_FEED notification also for the first newsmessage which are shown 2019-07-09 10:20:02 +02:00
Thierry Nischelwitzer
94c0656bcd update to 2.8 2019-07-02 19:38:17 +02:00
Michael Teeuw
13313d0b25 Fix changelog. 2019-07-01 20:27:37 +02:00
Michael Teeuw
3a20db1d76 Merge pull request #1716 from sdetweil/newupdater
fix updatenotification module to not crash
2019-07-01 20:25:59 +02:00
Michael Teeuw
00148b4cc8 Prepare 2.9.0-develop 2019-07-01 20:22:52 +02:00
Michael Teeuw
a31546b1ff Merge pull request #1717 from MichMich/develop
Release 2.8.0
2019-07-01 20:11:40 +02:00
Michael Teeuw
361b62b8e2 Add update info. 2019-07-01 20:05:28 +02:00
sam detweiler
37327b77a7 Merge branch 'develop' into newupdater 2019-07-01 12:56:01 -05:00
Sam Detweiler
7ef8a5bb11 fix conflict with develop branch 2019-07-01 12:45:40 -05:00
Sam Detweiler
11cfb8af32 Merge branch 'newupdater' of https://github.com/sdetweil/MagicMirror into newupdater
pull update
2019-07-01 12:36:52 -05:00
Sam Detweiler
7315f7d283 fix conflict with master repo develop branch 2019-07-01 12:35:47 -05:00
Michael Teeuw
7d58eb718e Prepare release 2.8.0 2019-07-01 19:06:26 +02:00
Michael Teeuw
651be76776 Merge pull request #1715 from sdetweil/newinstaller
update installer to handle non-pi and other issues
2019-07-01 18:36:08 +02:00
sam detweiler
4a5c6f1d39 Update CHANGELOG.md
add info on updatenotification module improvements
2019-06-30 07:18:45 -05:00
sam detweiler
5745d71d6a Update CHANGELOG.md
add info on installer changes
2019-06-30 06:57:41 -05:00
Sam Detweiler
db62b7421a update installer for npm missing, non-PI platforms, mac option on pm2 install 2019-06-29 16:02:44 -05:00
Sam Detweiler
4084c57789 fix updatenotification to not crash 2019-06-29 15:59:03 -05:00
Michael Teeuw
f90bec985a Fix merge issue. 2019-06-27 09:29:01 +02:00
Michael Teeuw
90f911c529 Fix Merge Issue 2019-06-27 09:27:24 +02:00
Michael Teeuw
6c88b106db Merge pull request #1713 from alltopafi/weather-module-location-header
allow location property in config,js to be used as header
2019-06-27 09:24:28 +02:00
Michael Teeuw
cef69d1b97 Merge pull request #1711 from NiekertDev/patch-1
Fix missing '#' (pound) in link
2019-06-27 09:23:10 +02:00
Jesse Alltop
f76a7fb331 allow location property in config,js to be used as header 2019-06-26 19:31:16 -05:00
Niekert
3e7b8b0663 Fix missing '#' (pound) in link 2019-06-24 17:41:51 +02:00
Josef Spitzlberger
a6eb3ad037 added three more class names to 'newsfeed.js'
added to 'newsfeed.js' in order to design the news article better with css, three more class-names were introduced: newsfeed-desc, newsfeed-desc, newsfeed-desc
2019-06-21 13:08:36 +02:00
Josef Spitzlberger
9468749384 added three class names
in order to design the news article better with css, three more class-names were introduced: newsfeed-desc, newsfeed-desc, newsfeed-desc
2019-06-21 13:05:40 +02:00
Michael Teeuw
37417fa1bb Merge pull request #1706 from vincep5/develop
Adding sunrise/sunset to weathergov
2019-06-18 14:06:52 +02:00
vincep5
217146351e changelog notes 2019-06-17 10:25:33 -05:00
vincep5
818ec33cef Adding sunrise/sunset to weathergov 2019-06-17 10:20:49 -05:00
Michael Teeuw
cd1671830a Merge pull request #1704 from Hg347/develop
Develop
2019-06-15 13:27:21 +02:00
Christoph Hagedorn
a5fca87dd0 updated info: fixed race condition on module update 2019-06-15 11:49:57 +02:00
Christoph Hagedorn
f06ce55626 Merge remote-tracking branch 'upstream/develop' into develop 2019-06-15 11:33:44 +02:00
Christoph Hagedorn
853085e755 added null check for moduleWrapper
(fixes race condition if element is updated)
2019-06-15 11:32:03 +02:00
Michael Teeuw
2b7accaf68 Merge pull request #1703 from rejas/fix_eslint_errors_automatically
Fix tests
2019-06-14 14:39:12 +02:00
Veeck
5533d93172 Update changelog 2019-06-14 14:06:36 +02:00
Veeck
f0e8c865fe Fix markdown error 2019-06-14 14:03:58 +02:00
Veeck
36400c0a83 Fix some eslint errors from previous merges 2019-06-14 14:03:07 +02:00
Veeck
c5383557b5 Pass fix options to eslint 2019-06-14 14:02:48 +02:00
Michael Teeuw
b645007884 Updated the fetchedLocationName (See PR: #1702) 2019-06-14 13:42:59 +02:00
Michael Teeuw
63ac137206 Merge branch 'mattdb-calendar-rrule' into develop 2019-06-14 13:32:20 +02:00
Michael Teeuw
808cbf8e0b Fix Typecheck. 2019-06-14 13:32:07 +02:00
Michael Teeuw
8ed77ba0c7 Merge 2019-06-14 13:31:59 +02:00
Michael Teeuw
66c74c51e4 Merge branch 'rejas-cleanups' into develop 2019-06-14 13:19:43 +02:00
Michael Teeuw
7a272ef0ab Merge conflict. 2019-06-14 13:19:29 +02:00
Michael Teeuw
60b817ec8e Merge branch 'cybex-dev-develop' into develop 2019-06-14 13:05:36 +02:00
Michael Teeuw
a41ecaf7cc Merge 2019-06-14 13:05:22 +02:00
Matt Bauer
d41afa0e53 Calendar fetch error is still a loading result
Also log error on node side as well.
2019-06-13 15:52:49 -05:00
Matt Bauer
a6284e05e5 Update rrule and request dependencies for calendar’s vendored ical.js
rrule-alt has not been updated in some time, while the main rrule has. ical.js is using updated rrule. Getting rid of rrule-alt would affect some third-party modules, however, so we should keep it around for now at least.
2019-06-13 14:45:32 -05:00
Matt Bauer
e56f61441d Repeating event handling “robustified” 2019-06-13 13:44:54 -05:00
Matt Bauer
7b4b7dffa2 Update calendar’s vendored ical.js 2019-06-13 13:44:54 -05:00
Michael Teeuw
77a214ef9c Merge pull request #1695 from Jason-Cooke/patch-2
docs: fix typo
2019-06-13 20:16:15 +02:00
Michael Teeuw
cf2723aafb Merge pull request #1699 from Hg347/calendar-1696
fixed calendar, issue 1696, ics file start date is not date type
2019-06-13 20:15:53 +02:00
Christoph Hagedorn
499e99cfc5 fixed url 2019-06-13 18:11:41 +02:00
Michael Teeuw
a7b83e9fe3 Merge pull request #1637 from maloakes/master
Add Weather provider UK Met Office
2019-06-13 16:51:09 +02:00
Christoph Hagedorn
964504b9c3 curr.exdates[i] might not be a date, so toISOString() may fail too 2019-06-11 23:24:03 +02:00
Christoph Hagedorn
ec65e66c58 added fixed issue in changelog 2019-06-11 22:43:48 +02:00
Christoph Hagedorn
e694b080be curr.start is not always a date type (depends on ics file). A type check
has been added.
2019-06-09 17:46:48 +02:00
Malcolm Oakes
70894b3938 Corrected merge problem 2019-06-08 15:34:37 +01:00
Malcolm Oakes
fb7115fc13 Merge remote-tracking branch 'upstream/develop' 2019-06-07 16:44:42 +01:00
Malcolm Oakes
a619fc4fef Allow temp and wind units to be specified separately if required. 2019-06-07 15:27:08 +01:00
Jason Cooke
7c6c5fd06f docs: fix typo 2019-06-07 10:34:55 +12:00
Malcolm Oakes
2970568eab Merge branch 'master' of https://github.com/maloakes/MagicMirror
Merge latest from github
2019-06-06 10:59:47 +01:00
rejas
62cb3a610e Actually only test json files in jsonlint, rename stylelintrc for clarity 2019-06-05 20:33:53 +02:00
rejas
f600c163ca Fix intendation 2019-06-05 18:30:04 +02:00
rejas
77cb68e5ac Remove unnecessary entry 2019-06-05 18:29:51 +02:00
rejas
c6314576aa Fix duplicate json entry 2019-06-05 17:04:51 +02:00
rejas
515c183070 Fix a comparison that was an assignement 2019-06-05 17:01:54 +02:00
rejas
63b9c0e6b8 More dependency updates and cleanup of old ones 2019-06-05 16:54:32 +02:00
rejas
84893b1664 Upgrade markdown lint to latest 2019-06-05 10:26:20 +02:00
rejas
835668d96d Add eslint semi rule 2019-06-05 10:23:58 +02:00
rejas
2bce15dc6e Remove multiple-empty-lines 2019-06-05 10:03:28 +02:00
rejas
8f1a212b52 More typo fixes 2019-06-05 09:46:59 +02:00
rejas
5c08bde0fa More == -> === and != -> !== fixes 2019-06-05 09:32:10 +02:00
rejas
98a84c031e Better fix for translator comparison 2019-06-05 08:55:17 +02:00
rejas
ea1715384e Revert "Revert "Fix some == with ===""
This reverts commit d9a4ee4f65.
2019-06-05 08:48:22 +02:00
rejas
d9a4ee4f65 Revert "Fix some == with ==="
This reverts commit 5d39d85215.
2019-06-04 11:04:47 +02:00
rejas
62017c4661 Update changelog 2019-06-04 10:43:16 +02:00
rejas
702b98f510 Cleanup imports 2019-06-04 10:43:06 +02:00
rejas
69aafd7d6a Fix missing ; and == and some missing vars 2019-06-04 10:19:25 +02:00
rejas
c1559dd8c8 More spelling fixes 2019-06-04 10:15:50 +02:00
rejas
4df1895560 Fix badge for travis which moved to .com from .org 2019-06-04 10:14:37 +02:00
rejas
cac92da6e4 Remove unused dependency, move mocha-logger to dev 2019-06-04 10:13:58 +02:00
rejas
5d39d85215 Fix some == with === 2019-06-04 09:51:51 +02:00
rejas
99b4c43fd5 Fix typos and some whitespaces 2019-06-04 09:33:53 +02:00
Charles Dyason
b2f59d6813 Added clock notifications for elapsed time.
Added notifications to default `clock` module to broadcast:
 - `CLOCK_SECOND` for a clock second, and 
 - `CLOCK_MINUTE` for a clock minute having elapsed.

Each notification is broadcasted with the corresponding value i.e. `CLOCK_SECOND` -> `30` and `CLOCK_MINUTE` -> `5` .
2019-06-03 14:01:27 +02:00
Charles Dyason
c7d79bb893 Updated config.js.sample with new configuration entries
Added `broadcastNewsFeeds: true` and `broadcastNewsUpdates: true` to the `config.js.sample` file
2019-05-31 19:45:58 +02:00
Charles Dyason
aa80c468c4 Added broadcasting of news feeds with incremental updates
Added ability to enable broadcasting of news feed items with `NEWS_FEED` notification and broadcasting updated news feed items with `NEWS_FEED_UPDATE` to other modules. This is merged into the default `newsfeed` module.

One can set ability to broadcast the whole news feed or broadcast only updated news feed items.
2019-05-31 16:37:47 +02:00
Charles Dyason
6008cba2db Minor typo corrections 2019-05-31 16:17:09 +02:00
Michael Teeuw
caf56671dc Merge pull request #1682 from kolbyjack/update/optimize-clock-update
Only call updateDom in clock.js when the content has changed
2019-05-19 07:34:05 +02:00
Jon Kolb
44eccf5ee4 Only call updateDom in clock.js when the content has changed 2019-05-18 20:02:22 -04:00
Michael Teeuw
8f96e4847c Merge pull request #1674 from eouia/develop
allow html5 autoplay
2019-05-17 16:29:48 +02:00
Michael Teeuw
ae3e307f33 Merge pull request #1677 from mattdb/develop
Calendar: only slice multi-day events when they actually span midnight
2019-05-17 16:28:30 +02:00
Michael Teeuw
3fe0c758ed Merge pull request #1678 from vincep5/develop
Fix City List
2019-05-17 16:27:18 +02:00
vincep5
7240fb32d2 update city list url 2019-05-14 15:00:30 -05:00
vin p
e23a3461ba Merge pull request #6 from MichMich/develop
Develop
2019-05-14 14:45:48 -05:00
Matt Bauer
727eb0cfd7 Calendar: only slice multi-day events when they actually span midnight 2019-05-14 10:08:53 -05:00
Seongnoh Sean Yi
3796076360 change Squotes to Dquotes 2019-05-13 23:24:59 +02:00
Seongnoh Sean Yi
ef9576f8c4 Fixed:allowance HTML5 autoplay
From commit: 94fc4cb8a2
2019-05-13 21:26:28 +02:00
Seongnoh Sean Yi
94fc4cb8a2 allow html5 autoplay policy
From Chrome 66, autoplay policy is changed. This 1 line can help to play html5 audio and video without user-gesture-allowance in MagicMirror
2019-05-13 21:19:50 +02:00
Michael Teeuw
ccb248db91 Create stale.yml 2019-05-07 21:09:11 +02:00
Michael Teeuw
e7de447725 Merge pull request #1655 from spitzlbergerj/master
default module calendar - added config option nextDaysRelative
2019-05-07 20:49:43 +02:00
Michael Teeuw
7ff5429cb7 Remove newline. 2019-05-07 20:41:02 +02:00
Michael Teeuw
17c581b4aa Update changelog. 2019-05-07 20:35:29 +02:00
Josef Spitzlberger
41e5c2939f added config option nextDaysRelative
added configuration option nextDaysRelative to always display today's and tomorrow's appointments in relative mode, even if timeformat is set to absolute
2019-05-07 20:35:28 +02:00
Josef Spitzlberger
7e2ab51298 added Config Option nextDaysRelative
added configuration option nextDaysRelative to always display today's and tomorrow's appointments in relative mode, even if timeformat is set to absolute
2019-05-07 20:34:35 +02:00
Michael Teeuw
03f917fd9c Update changelog. 2019-05-07 20:31:41 +02:00
Josef Spitzlberger
d24e10a728 added config option nextDaysRelative
added configuration option nextDaysRelative to always display today's and tomorrow's appointments in relative mode, even if timeformat is set to absolute
2019-05-07 20:18:32 +02:00
Josef Spitzlberger
a17ac1c16e added Config Option nextDaysRelative
added configuration option nextDaysRelative to always display today's and tomorrow's appointments in relative mode, even if timeformat is set to absolute
2019-05-07 20:18:08 +02:00
Michael Teeuw
dd6b972be4 Merge pull request #1628 from kolbyjack/feature/calendar-past-events
Add includePastEvents global and calendar-specific settings
2019-05-07 20:12:37 +02:00
Michael Teeuw
7d0c9ba0d9 Merge pull request #1666 from kevbodavidson/patch-1
Update config.js.sample
2019-05-07 20:07:32 +02:00
Michael Teeuw
6fa211634f Merge branch 'develop' into patch-1 2019-05-07 20:07:17 +02:00
Michael Teeuw
1ca24c7f38 Merge branch 'patch-1' of https://github.com/kevbodavidson/magicmirror into pr/kevbodavidson/1666 2019-05-07 20:06:06 +02:00
Michael Teeuw
bcfbccae59 Update changelog. 2019-05-07 20:06:03 +02:00
kevbodavidson
20b75ce6ed Update config.js.sample 2019-05-07 20:04:55 +02:00
Michael Teeuw
2ea15d7bf5 Merge pull request #1660 from magic21nrw/master
Update ical.js #1631
2019-05-07 19:53:04 +02:00
Michael Teeuw
18e14c597f Merge branch 'master' of https://github.com/magic21nrw/magicmirror-1 into pr/magic21nrw/1660 2019-05-07 19:46:26 +02:00
Michael Teeuw
06d75999d7 Update changelog. 2019-05-07 19:46:23 +02:00
magic21nrw
7047a7cae6 Update ical.js
Fixed Bug with more than one calendar.
2019-05-07 19:45:28 +02:00
Michael Teeuw
20d2124867 Merge pull request #1661 from vincep5/develop
Update Feels to Feels like
2019-05-07 19:41:01 +02:00
kevbodavidson
3c7a85361e Update config.js.sample 2019-04-24 17:19:47 -04:00
vincep5
1ffbbdac99 Update English Feels to Feels like 2019-04-17 21:16:36 -05:00
vin p
eeccca8842 Merge pull request #5 from MichMich/develop
Develop
2019-04-17 21:08:04 -05:00
magic21nrw
e2d2dbd2ba Update ical.js
Fixed Bug with more than one calendar.
2019-04-17 20:05:33 +02:00
Jon Kolb
c61f0409fb Rename includePastEvents calendar config option to broadcastPastEvents 2019-04-17 08:16:04 -04:00
Jon Kolb
806be39a6d Add includePastEvents global and calendar-specific settings 2019-04-17 08:13:51 -04:00
Michael Teeuw
6170b0d059 Merge pull request #1653 from darkniki/patch-2
Update CHANGELOG.md
2019-04-17 13:51:49 +02:00
Michael Teeuw
bca838495e Merge pull request #1652 from michaelarnauts/1651-calendar-fix
Fix sliceMultiDayEvents so it respects maximumNumberOfDays
2019-04-17 13:51:07 +02:00
Michael Teeuw
e31a747250 Merge pull request #1636 from darkniki/patch-1
Update ru.json
2019-04-17 13:49:46 +02:00
Michael Teeuw
4cf430e146 Merge pull request #1646 from michaelarnauts/patch-3
sliceMultiDayEvents is false by default
2019-04-17 12:17:44 +02:00
darkniki
ef554cf6ec Update CHANGELOG.md 2019-04-11 14:46:32 +03:00
Thierry Nischelwitzer
fcd91daee6 Merge remote-tracking branch 'upstream/master'
Update MagicMirror
2019-04-10 18:07:55 +02:00
Michaël Arnauts
396c78b46a Changelog 2019-04-10 10:35:11 +02:00
Michaël Arnauts
4677a3fd89 Fix many issues with sliceMultiDayEvents 2019-04-10 10:31:55 +02:00
Michael Teeuw
834ab5c6b9 Merge pull request #1642 from michaelarnauts/patch-2
Handle SIGTERM messages
2019-04-09 09:53:16 +02:00
Michael Teeuw
1599e8f7ff Merge pull request #1647 from retroflex/develop
Added option to show location of calendar events
2019-04-09 09:52:39 +02:00
Michael Teeuw
7430704002 Merge pull request #1635 from ZakarFin/translations
Add translations for FI week/feels
2019-04-09 09:50:31 +02:00
retroflex
4d7b19c8cb Added calendar event location 2019-04-08 21:57:32 +02:00
retroflex
40f535cf3c Added possibility to show event location. 2019-04-08 21:49:19 +02:00
Michaël Arnauts
17425dcaf7 Add CHANGELOG entry. Fix test. 2019-04-07 16:41:05 +02:00
Michaël Arnauts
6b87fc64af sliceMultiDayEvents is false by default 2019-04-07 15:26:25 +02:00
Thierry Nischelwitzer
35174b0348 bugfixing calendar module 2019-04-07 11:15:16 +02:00
Michaël Arnauts
fd53541719 Handle SIGTERM messages 2019-04-06 20:50:54 +02:00
Malcolm Oakes
7c68bff9f5 Update from develop branch 2019-04-03 16:24:42 +01:00
Malcolm Oakes
6c64991951 Update from develop branch 2019-04-03 16:23:55 +01:00
Malcolm Oakes
ca04ff0f37 Update from develop branch 2019-04-03 16:22:26 +01:00
Malcolm Oakes
7742575cab Merge latest from develop branch 2019-04-03 16:16:59 +01:00
Malcolm Oakes
cdcdce702d Changes for UK Met Office weather data provider 2019-04-03 15:46:45 +01:00
Malcolm Oakes
c80e04fe8d Add new weather data provider UK Met Office (Datapoint) 2019-04-03 15:19:32 +01:00
darkniki
c3b3ea107a Update ru.json
Added "FEELS" translation
2019-04-03 08:31:40 +03:00
Sami Mäkinen
1dc530c549 Add translations for FI week/feels 2019-04-02 22:59:34 +03:00
Michael Teeuw
8b0b70e757 Prepare 2.8.0-develop. 2019-04-02 09:55:20 +02:00
fewieden
a8bd196234 current weather tests 2019-01-13 23:59:05 +01:00
fewieden
239d425940 webdriver ajax stub 2019-01-13 23:58:35 +01:00
fewieden
baa3c1461c test setup 2019-01-13 23:57:19 +01:00
fewieden
fa8e398e90 dependencies 2019-01-13 23:50:29 +01:00
Rodrigo Ramírez Norambuena
f34407fc43 Add 404 test HTTP code for vendors 2017-04-18 23:44:50 -03:00
150 changed files with 7516 additions and 6214 deletions

View File

@@ -3,4 +3,3 @@ vendor/*
!/modules/default/**
!/modules/node_helper
!/modules/node_helper/**
!/modules/default/defaultmodules.js

View File

@@ -2,9 +2,12 @@
"rules": {
"indent": ["error", "tab"],
"quotes": ["error", "double"],
"semi": ["error"],
"max-len": ["error", 250],
"curly": "error",
"camelcase": ["error", {"properties": "never"}],
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
"no-multi-spaces": "error",
"no-trailing-spaces": ["error", {"ignoreComments": false }],
"no-irregular-whitespace": ["error"]
},
@@ -15,6 +18,7 @@
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2017,
"ecmaFeatures": {
"globalReturn": true
}

View File

@@ -1,15 +1,28 @@
Please only submit reproducible issues.
## I'm not sure if this is a bug
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
## I'm having troubles installing or configuring MagicMirror
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
## I found a bug in the MagicMirror installer
If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository:
[https://github.com/sdetweil/MagicMirror_scripts](https://github.com/sdetweil/MagicMirror_scripts)
## I found a bug in the MagicMirror Docker image
If you are facing an issue or found a bug while running MagicMirror inside a Docker container please create an issue in the GitHub repository of the MagicMirror Docker image:
[https://github.com/bastilimbach/docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror)
---
## I found a bug in MagicMirror
Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line.
When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version *and* your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 0.12.13 or later.
**Node Version**: Make sure it's version 8 or later.
**MagicMirror Version**: Now that the versions have split, tell us if you are using the PHP version (v1) or the newer JavaScript version (v2).
**MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file.
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.

19
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- under investigation
- pr welcome
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

12
.gitignore vendored
View File

@@ -11,7 +11,9 @@ coverage
.grunt
.lock-wscript
build/Release
node_modules
/node_modules/**/*
fonts/node_modules/**/*
vendor/node_modules/**/*
jspm_modules
.npm
.node_repl_history
@@ -57,12 +59,6 @@ Temporary Items
.directory
.Trash-*
# Various Magic Mirror ignoramuses and anti-ignoramuses.
# Don't ignore the node_helper core module.
!/modules/node_helper
!/modules/node_helper/**
# Ignore all modules except the default modules.
/modules/**
!/modules/default
@@ -81,3 +77,5 @@ Temporary Items
*.orig
*.rej
*.bak
!/tests/node_modules/**/*

14
.snyk
View File

@@ -1,14 +0,0 @@
version: v1.5.2
ignore: {}
patch:
'npm:minimatch:20160620':
- snyk > recursive-readdir > minimatch:
patched: '2016-07-30T14:02:31.280Z'
'npm:negotiator:20160616':
- socket.io > engine.io > accepts > negotiator:
patched: '2016-07-30T14:02:31.280Z'
'npm:ws:20160624':
- socket.io > engine.io > ws:
patched: '2016-07-30T14:02:31.280Z'
- socket.io > socket.io-client > engine.io-client > ws:
patched: '2016-07-30T14:02:31.280Z'

View File

@@ -1,18 +1,24 @@
dist: trusty
language: node_js
node_js:
- "8"
- 10
- lts/*
- node
before_install:
- npm i -g npm
before_script:
- yarn danger ci
- npm install grunt-cli -g
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 5
- yarn danger ci
- npm install grunt-cli -g
- "export DISPLAY=:99.0"
- "export ELECTRON_DISABLE_SANDBOX=1"
- "sh -e /etc/init.d/xvfb start"
- sleep 5
script:
- grunt
- npm run test:unit
- npm run test:e2e
- npm run test:lint
- npm run test:e2e
- npm run test:unit
after_script:
- npm list
- npm list
cache:
directories:
- node_modules
directories:
- node_modules

View File

@@ -3,9 +3,159 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
---
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror² core.
## [2.11.0] - 2020-04-01
🚨 READ THIS BEFORE UPDATING 🚨
In the past years the project has grown a lot. This came with a huge downside: poor maintainability. If I let the project continue the way it was, it would eventually crash and burn. More important: I would completely lose the drive and interest to continue the project. Because of this the decision was made to simplify the core by removing all side features like automatic installers and support for exotic platforms. This release (2.11.0) is the first real release that will reflect (parts) of these changes. As a result of this, some things might break. So before you continue make sure to backup your installation. Your config, your modules or better yet: your full MagicMirror folder. In other words: update at your own risk.
For more information regarding this major change, please check issue [#1860](https://github.com/MichMich/MagicMirror/issues/1860).
### Deleted
- Remove installers.
- Remove externalized scripts.
- Remove jshint dependency, instead eslint checks your config file now
### Added
- Brazilian translation for "FEELS".
- Ukrainian translation.
- Finnish translation for "PRECIP", "UPDATE_INFO_MULTIPLE" and "UPDATE_INFO_SINGLE".
- Added the ability to hide the temp label and weather icon in the `currentweather` module to allow showing only information such as wind and sunset/rise.
- The `clock` module now optionally displays sun and moon data, including rise/set times, remaining daylight, and percent of moon illumination.
- Added Hebrew translation.
- Add HTTPS support and update config.js.sample
- Run tests on long term support and latest stable version of nodejs
- Added the ability to configure a list of modules that shouldn't be update checked.
- Run linters on git commits
- Added date functionality to compliments: display birthday wishes or celebrate an anniversary
### Fixed
- Force declaration of public ip address in config file (ISSUE #1852)
- Fixes `run-start.sh`: If running in docker-container, don't check the environment, just start electron (ISSUE #1859)
- Fix calendar time offset for recurring events crossing Daylight Savings Time (ISSUE #1798)
- Fix regression in currentweather module causing 'undefined' to show up when config.hideTemp is false
- Fix FEELS translation for Croatian
- Fixed weather tests [#1840](https://github.com/MichMich/MagicMirror/issues/1840)
- Fixed Socket.io can't be used with Reverse Proxy in serveronly mode [#1934](https://github.com/MichMich/MagicMirror/issues/1934)
- Fix update checking skipping 3rd party modules the first time
### Changed
- Remove documentation from core repository and link to new dedicated docs site: [docs.magicmirror.builders](https://docs.magicmirror.builders).
- Updated config.js.sample: Corrected some grammar on `config.js.sample` comment section.
- Removed `run-start.sh` script and update start commands:
- To start using electron, use `npm run start`.
- To start in server only mode, use `npm run server`.
- Remove redundant logging from modules.
- Timestamp in log output now also contains the date
- Turkish translation.
- Option to configure the size of the currentweather module.
## [2.10.1] - 2020-01-10
### Changed
- Updated README.md: Added links to the official documentation website and remove links to broken installer.
## [2.10.0] - 2020-01-01
Special thanks to @sdetweil for all his great contributions!
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`.
### Added
- Timestamps in log output.
- Padding in dateheader mode of the calendar module.
- New upgrade script to help users consume regular updates installers/upgrade-script.sh.
- New script to help setup pm2, without install installers/fixuppm2.sh.
### Updated
- Updated lower bound of `lodash` and `helmet` dependencies for security patches.
- Updated compliments.js to handle newline in text, as textfields to not interpolate contents.
- Updated raspberry.sh installer script to handle new platform issues, split node/npm, pm2, and screen saver changes.
- Improve handling for armv6l devices, where electron support has gone away, add optional serveronly config option.
- Improved run-start.sh to handle for serveronly mode, by choice, or when electron not available.
- Only check for xwindows running if not on macOS.
### Fixed
- Fixed issue in weatherforecast module where predicted amount of rain was not using the decimal symbol specified in config.js.
- Module header now updates correctly, if a module need to dynamically show/hide its header based on a condition.
- Fix handling of config.js for serverOnly mode commented out.
- Fixed issue in calendar module where the debug script didn't work correctly with authentication.
- Fixed issue that some full day events were not correctly recognized as such.
- Display full day events lasting multiple days as happening today instead of some days ago if they are still ongoing.
## [2.9.0] - 2019-10-01
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
### Added
- Spanish translation for "PRECIP".
- Adding a Malay (Malaysian) translation for MagicMirror².
- Add test check URLs of vendors 200 and 404 HTTP CODE.
- Add tests for new weather module and helper to stub ajax requests.
### Updated
- Updatenotification module: Display update notification for a limited (configurable) time.
- Enabled e2e/vendor_spec.js tests.
- The css/custom.css will be rename after the next release. We've add into `run-start.sh` a instruction by GIT to ignore with `--skip-worktree` and `rm --cached`. [#1540](https://github.com/MichMich/MagicMirror/issues/1540)
- Disable sending of notification CLOCK_SECOND when displaySeconds is false.
### Fixed
- Updatenotification module: Properly handle race conditions, prevent crash.
- Send `NEWS_FEED` notification also for the first news messages which are shown.
- Fixed issue where weather module would not refresh data after a network or API outage. [#1722](https://github.com/MichMich/MagicMirror/issues/1722)
- Fixed weatherforecast module not displaying rain amount on fallback endpoint.
- Notifications CLOCK_SECOND & CLOCK_MINUTE being from startup instead of matched against the clock and avoid drifting.
## [2.8.0] - 2019-07-01
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
### Added
- Option to show event location in calendar
- Finnish translation for "Feels" and "Weeks"
- Russian translation for “Feels”
- Calendar module: added `nextDaysRelative` config option
- Add `broadcastPastEvents` config option for calendars to include events from the past `maximumNumberOfDays` in event broadcasts
- Added feature to broadcast news feed items `NEWS_FEED` and updated news items `NEWS_FEED_UPDATED` in default [newsfeed](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/newsfeed) module (when news is updated) with documented default and `config.js` options in [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md)
- Added notifications to default `clock` module broadcasting `CLOCK_SECOND` and `CLOCK_MINUTE` for the respective time elapsed.
- Added UK Met Office Datapoint feed as a provider in the default weather module.
- Added new provider class
- Added suncalc.js dependency to calculate sun times (not provided in UK Met Office feed)
- Added "tempUnits" and "windUnits" to allow, for example, temp in metric (i.e. celsius) and wind in imperial (i.e. mph). These will override "units" if specified, otherwise the "units" value will be used.
- Use Feels Like temp from feed if present
- Optionally display probability of precipitation (PoP) in current weather (UK Met Office data)
- Automatically try to fix eslint errors by passing `--fix` option to it
- Added sunrise and sunset times to weathergov weather provider [#1705](https://github.com/MichMich/MagicMirror/issues/1705)
- Added "useLocationAsHeader" to display "location" in `config.js` as header when location name is not returned
- Added to `newsfeed.js`: in order to design the news article better with css, three more class-names were introduced: newsfeed-desc, newsfeed-desc, newsfeed-desc
### Updated
- English translation for "Feels" to "Feels like"
- Fixed the example calender url in `config.js.sample`
- Update `ical.js` to solve various calendar issues.
- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
- Only update clock once per minute when seconds aren't shown
### Fixed
- Fixed uncaught exception, race condition on module update
- Fixed issue [#1696](https://github.com/MichMich/MagicMirror/issues/1696), some ical files start date to not parse to date type
- Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates)
- Handle SIGTERM messages
- Fixes sliceMultiDayEvents so it respects maximumNumberOfDays
- Minor types in default NewsFeed [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md)
- Fix typos and small syntax errors, cleanup dependencies, remove multiple-empty-lines, add semi-rule
- Fixed issues with calendar not displaying one-time changes to repeating events
- Updated the fetchedLocationName variable in currentweather.js so that city shows up in the header
### Updated installer
- give non-pi2+ users (pi0, odroid, jetson nano, mac, windows, ...) option to continue install
- use current username vs hardcoded 'pi' to support non-pi install
- check for npm installed. node install doesn't do npm anymore
- check for mac as part of PM2 install, add install option string
- update pm2 config with current username instead of hard coded 'pi'
- check for screen saver config, "/etc/xdg/lxsession", bypass if not setup
## [2.7.1] - 2019-04-02
@@ -440,7 +590,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Added option `remoteFile` to compliments module to load compliment array from filesystem.
- Added option `zoom` to scale the whole mirror display with a given factor.
- Added option `roundTemp` for currentweather and weatherforecast modules to display temperatures rounded to nearest integer.
- Added abilty set the classes option to compliments module for style and text size of compliments.
- Added ability set the classes option to compliments module for style and text size of compliments.
- Added ability to configure electronOptions
- Calendar module: option to hide private events
- Add root_path for global vars

View File

@@ -1,9 +1,10 @@
module.exports = function(grunt) {
require("time-grunt")(grunt);
var fix = (grunt.option("env") || "lint") === "lint";
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
eslint: {
options: {
fix: fix,
configFile: ".eslintrc.json"
},
target: [
@@ -19,14 +20,14 @@ module.exports = function(grunt) {
"!modules/default/alert/classie.js",
"config/*",
"translations/translations.js",
"vendor/vendor.js",
"modules/node_modules/node_helper/index.js"
"vendor/vendor.js"
]
},
stylelint: {
simple: {
options: {
configFile: ".stylelintrc"
fix: fix,
configFile: ".stylelintrc.json"
},
src: [
"css/main.css",
@@ -42,11 +43,10 @@ module.exports = function(grunt) {
src: [
"package.json",
".eslintrc.json",
".stylelintrc",
".stylelintrc.json",
"translations/*.json",
"modules/default/*/translations/*.json",
"installers/pm2_MagicMirror.json",
"vendor/package.js"
"vendor/package.json"
],
options: {
reporter: "jshint"
@@ -76,7 +76,8 @@ module.exports = function(grunt) {
"MD018": false,
"MD012": false,
"MD026": false,
"MD038": false
"MD038": false,
"MD047": false
}
},
src: [
@@ -100,5 +101,6 @@ module.exports = function(grunt) {
grunt.loadNpmTasks("grunt-jsonlint");
grunt.loadNpmTasks("grunt-yamllint");
grunt.loadNpmTasks("grunt-markdownlint");
grunt.registerTask("default", ["eslint", "stylelint", "jsonlint", "markdownlint", "yamllint"]);
};

View File

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

214
README.md
View File

@@ -5,7 +5,7 @@
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a>
<a href="http://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://travis-ci.org/MichMich/MagicMirror"><img src="https://travis-ci.org/MichMich/MagicMirror.svg" alt="Travis"></a>
<a href="https://travis-ci.com/MichMich/MagicMirror"><img src="https://travis-ci.com/MichMich/MagicMirror.svg" alt="Travis"></a>
<a href="https://snyk.io/test/github/MichMich/MagicMirror"><img src="https://snyk.io/test/github/MichMich/MagicMirror/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/MichMich/MagicMirror" style="max-width:100%;"></a>
</p>
@@ -13,194 +13,20 @@
MagicMirror² focuses on a modular plugin system and uses [Electron](http://electron.atom.io/) as an application wrapper. So no more web server or browser installs necessary!
## Table Of Contents
## Documentation
For the full documentation including **[installation instructions](https://docs.magicmirror.builders/getting-started/installation.html)**, please visit our dedicated documentation website: [https://docs.magicmirror.builders](https://docs.magicmirror.builders).
- [Table Of Contents](#table-of-contents)
- [Installation](#installation)
- [Raspberry Pi](#raspberry-pi)
- [Automatic Installation (Raspberry Pi only!)](#automatic-installation-raspberry-pi-only)
- [Manual Installation](#manual-installation)
- [Server Only](#server-only)
- [Client Only](#client-only)
- [Docker](#docker)
- [Configuration](#configuration)
- [Raspberry Specific](#raspberry-specific)
- [General](#general)
- [Modules](#modules)
- [Updating](#updating)
- [Community](#community)
- [Contributing Guidelines](#contributing-guidelines)
- [Enjoying MagicMirror? Consider a donation!](#enjoying-magicmirror-consider-a-donation)
- [Manifesto](#manifesto)
## Installation
### Raspberry Pi
#### 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
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 with: `npm install && npm start` \
For **Server Only** use: `npm install && node serveronly` .
**: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
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
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
```
To get more information about the available Dockerfile versions and configurations head over to the respective [GitHub repository](https://github.com/bastilimbach/docker-MagicMirror).
## Configuration
### 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 can check your configuration running `npm run config:check` in `/home/pi/MagicMirror`.
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 *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). |
| `customCss` | The path of the `custom.css` stylesheet. The default is `css/custom.css`. |
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. |
| `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. |
| `config` | An object with the module configuration properties. Check the documentation of the module for more information. This field is optional, unless the module requires extra configuration. |
## Modules
The following modules are installed by default.
- [**Clock**](modules/default/clock)
- [**Calendar**](modules/default/calendar)
- [**Current Weather**](modules/default/currentweather)
- [**Weather Forecast**](modules/default/weatherforecast)
- [**News Feed**](modules/default/newsfeed)
- [**Compliments**](modules/default/compliments)
- [**Hello World**](modules/default/helloworld)
- [**Alert**](modules/default/alert)
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)!
## 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
The community around the MagicMirror² is constantly growing. We even have a [forum](https://forum.magicmirror.builders) now where you can share your ideas, ask questions, help others and get inspired by other builders. We would love to see you there!
## Links
- Website: [https://magicmirror.builders](https://magicmirror.builders)
- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders)
- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders)
- Discord: [https://discord.gg/J5BAtvx](https://discord.gg/J5BAtvx)
- Blog: [https://michaelteeuw.nl/tagged/magicmirror](https://michaelteeuw.nl/tagged/magicmirror)
- Donations: [https://magicmirror.builders/#donate](https://magicmirror.builders/#donate)
## Contributing Guidelines
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation.
Please keep the following in mind:
- **Bug Reports**: Make sure you're running the latest version. If the issue(s) still persist: please open a clearly documented issue with a clear title.
- **Minor Bug Fixes**: Please send a pull request with a clear explanation of the issue or a link to the issue it solves.
- **Major Bug Fixes**: please discuss your approach in an GitHub issue before you start to alter a big part of the code.
- **New Features**: please please discuss in a GitHub issue before you start to alter a big part of the code. Without discussion upfront, the pull request will not be accepted / merged.
Thanks for your help in making MagicMirror² better!
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation. For the full contribution guidelines, check out: [https://docs.magicmirror.builders/getting-started/contributing.html](https://docs.magicmirror.builders/getting-started/contributing.html)
## Enjoying MagicMirror? Consider a donation!
@@ -208,26 +34,10 @@ Thanks for your help in making MagicMirror² better!
MagicMirror² is opensource and free. That doesn't mean we don't need any money.
Please consider a donation to help us cover the ongoing costs like webservers and email services.
If we recieve enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core.
If we receive enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core.
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
## 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

@@ -1,8 +1,6 @@
/* jshint esversion: 6 */
"use strict";
// Use seperate scope to prevent global scope pollution
// Use separate scope to prevent global scope pollution
(function () {
var config = {};
@@ -19,7 +17,7 @@
// Prefer command line arguments over environment variables
["address", "port"].forEach((key) => {
config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]);
})
});
}
function getServerConfig(url) {
@@ -30,7 +28,7 @@
const request = lib.get(url, (response) => {
var configData = "";
// Gather incomming data
// Gather incoming data
response.on("data", function(chunk) {
configData += chunk;
});
@@ -43,8 +41,8 @@
request.on("error", function(error) {
reject(new Error(`Unable to read config from server (${url} (${error.message}`));
});
})
};
});
}
function fail(message, code = 1) {
if (message !== undefined && typeof message === "string") {
@@ -89,7 +87,7 @@
});
child.on("close", (code) => {
if (code != 0) {
if (code !== 0) {
console.log(`There something wrong. The clientonly is not running code ${code}`);
}
});

View File

@@ -3,7 +3,7 @@
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* For more information how you can configurate this file
* For more information on how you can configure this file
* See https://github.com/MichMich/MagicMirror#configuration
*
*/
@@ -12,8 +12,8 @@ var config = {
address: "localhost", // Address to listen on, can be:
// - "localhost", "127.0.0.1", "::1" to listen on loopback interface
// - another specific IPv4/6 to listen on a specific interface
// - "", "0.0.0.0", "::" to listen on any interface
// Default, when address config is left out, is "localhost"
// - "0.0.0.0", "::" to listen on any interface
// Default, when address config is left out or empty, is "localhost"
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses
// or add a specific IPv4 of 192.168.1.5 :
@@ -21,9 +21,18 @@ var config = {
// or IPv4 range of 192.168.3.0 --> 192.168.3.15 use CIDR format :
// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.3.0/28"],
useHttps: false, // Support HTTPS or not, default "false" will use HTTP
httpsPrivateKey: "", // HTTPS private key path, only require when useHttps is true
httpsCertificate: "", // HTTPS Certificate path, only require when useHttps is true
language: "en",
timeFormat: 24,
units: "metric",
// serverOnly: true/false/"local" ,
// local for armv6l processors, default
// starts serveronly and then starts chrome browser
// false, default for all NON-armv6l devices
// true, force serveronly mode, because you want to.. no UI on this device
modules: [
{
@@ -45,8 +54,7 @@ var config = {
calendars: [
{
symbol: "calendar-check",
url: "webcal://www.calendarlabs.com/templates/ical/US-Holidays.ics"
}
url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" }
]
}
},
@@ -59,7 +67,7 @@ var config = {
position: "top_right",
config: {
location: "New York",
locationID: "", //ID from http://bulk.openweathermap.org/sample/; unzip the gz file and find your city
locationID: "", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
appid: "YOUR_OPENWEATHER_API_KEY"
}
},
@@ -69,7 +77,7 @@ var config = {
header: "Weather Forecast",
config: {
location: "New York",
locationID: "5128581", //ID from https://openweathermap.org/city
locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
appid: "YOUR_OPENWEATHER_API_KEY"
}
},
@@ -84,7 +92,9 @@ var config = {
}
],
showSourceTitle: true,
showPublishDate: true
showPublishDate: true,
broadcastNewsFeeds: true,
broadcastNewsUpdates: true
}
},
]

View File

@@ -1,14 +0,0 @@
/*****************************************************
* Magic Mirror *
* Custom CSS *
* *
* By Michael Teeuw http://michaelteeuw.nl *
* MIT Licensed. *
* *
* Add any custom CSS below. *
* Changes to this files will be ignored by GIT. *
*****************************************************/
body {
}

View File

@@ -1,9 +1,9 @@
import { danger, fail, warn } from "danger"
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.")
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.
@@ -12,6 +12,6 @@ if (danger.github.pr.base.ref === "master" && danger.github.pr.user.login !== "M
// 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.")
fail("Please send all your pull requests to the `develop` branch.<br>Pull requests on the `master` branch will not be accepted.");
}
}
}

View File

@@ -37,7 +37,7 @@
<div class="region bottom right"><div class="container"></div></div>
</div>
<div class="region fullscreen above"><div class="container"></div></div>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="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>

View File

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

View File

@@ -1,7 +0,0 @@
{
"apps" : [{
"name" : "MagicMirror",
"script" : "/home/pi/MagicMirror/installers/mm.sh",
"watch" : ["/home/pi/MagicMirror/config/config.js"]
}]
}

View File

@@ -1,2 +0,0 @@
echo "\033[32mMagicMirror installation successful!"
exit 0

View File

@@ -1,173 +0,0 @@
#!/usr/bin/env bash
# This is an installer script for MagicMirror2. It works well enough
# that it can detect if you have Node installed, run a binary script
# and then download and run MagicMirror2.
echo -e "\e[0m"
echo '$$\ $$\ $$\ $$\ $$\ $$\ $$$$$$\'
echo '$$$\ $$$ | \__| $$$\ $$$ |\__| $$ __$$\'
echo '$$$$\ $$$$ | $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$$$\ $$$$ |$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ \__/ $$ |'
echo '$$\$$\$$ $$ | \____$$\ $$ __$$\ $$ |$$ _____|$$\$$\$$ $$ |$$ |$$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$$$$$ |'
echo '$$ \$$$ $$ | $$$$$$$ |$$ / $$ |$$ |$$ / $$ \$$$ $$ |$$ |$$ | \__|$$ | \__|$$ / $$ |$$ | \__|$$ ____/'
echo '$$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ |$$ | $$ |\$ /$$ |$$ |$$ | $$ | $$ | $$ |$$ | $$ |'
echo '$$ | \_/ $$ |\$$$$$$$ |\$$$$$$$ |$$ |\$$$$$$$\ $$ | \_/ $$ |$$ |$$ | $$ | \$$$$$$ |$$ | $$$$$$$$\'
echo '\__| \__| \_______| \____$$ |\__| \_______|\__| \__|\__|\__| \__| \______/ \__| \________|'
echo ' $$\ $$ |'
echo ' \$$$$$$ |'
echo ' \______/'
echo -e "\e[0m"
# Define the tested version of Node.js.
NODE_TESTED="v5.1.0"
# Determine which Pi is running.
ARM=$(uname -m)
# Check the Raspberry Pi version.
if [ "$ARM" != "armv7l" ]; then
echo -e "\e[91mSorry, your Raspberry Pi is not supported."
echo -e "\e[91mPlease run MagicMirror on a Raspberry Pi 2 or 3."
echo -e "\e[91mIf this is a Pi Zero, you are in the same boat as the original Raspberry Pi. You must run in server only mode."
exit;
fi
# Define helper methods.
function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }
function command_exists () { type "$1" &> /dev/null ;}
# Update before first apt-get
echo -e "\e[96mUpdating packages ...\e[90m"
sudo apt-get update || echo -e "\e[91mUpdate failed, carrying on installation ...\e[90m"
# Installing helper tools
echo -e "\e[96mInstalling helper tools ...\e[90m"
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"
NODE_INSTALL=false
if command_exists node && command_exists npm; then
echo -e "\e[0mNode currently installed. Checking version number.";
NODE_CURRENT=$(node -v)
echo -e "\e[0mMinimum Node version: \e[1m$NODE_TESTED\e[0m"
echo -e "\e[0mInstalled Node version: \e[1m$NODE_CURRENT\e[0m"
if version_gt $NODE_TESTED $NODE_CURRENT; then
echo -e "\e[96mNode should be upgraded.\e[0m"
NODE_INSTALL=true
# Check if a node process is currenlty running.
# If so abort installation.
if pgrep "node" > /dev/null; then
echo -e "\e[91mA Node process is currently running. Can't upgrade."
echo "Please quit all Node processes and restart the installer."
exit;
fi
else
echo -e "\e[92mNo Node.js upgrade necessary.\e[0m"
fi
else
echo -e "\e[93mNode.js is not installed.\e[0m";
NODE_INSTALL=true
fi
# Install or upgrade node if necessary.
if $NODE_INSTALL; then
echo -e "\e[96mInstalling Node.js ...\e[90m"
# Fetch the latest version of Node.js from the selected branch
# 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="10.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"
fi
# Install MagicMirror
cd ~
if [ -d "$HOME/MagicMirror" ] ; then
echo -e "\e[93mIt seems like MagicMirror is already installed."
echo -e "To prevent overwriting, the installer will be aborted."
echo -e "Please rename the \e[1m~/MagicMirror\e[0m\e[93m folder and try again.\e[0m"
echo ""
echo -e "If you want to upgrade your installation run \e[1m\e[97mgit pull\e[0m from the ~/MagicMirror directory."
echo ""
exit;
fi
echo -e "\e[96mCloning MagicMirror ...\e[90m"
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."
exit;
fi
cd ~/MagicMirror || exit
echo -e "\e[96mInstalling dependencies ...\e[90m"
if npm install; then
echo -e "\e[92mDependencies installation Done!\e[0m"
else
echo -e "\e[91mUnable to install dependencies!"
exit;
fi
# Use sample config for start MagicMirror
cp config/config.js.sample config/config.js
# Check if plymouth is installed (default with PIXEL desktop environment), then install custom splashscreen.
echo -e "\e[96mCheck plymouth installation ...\e[0m"
if command_exists plymouth; then
THEME_DIR="/usr/share/plymouth/themes"
echo -e "\e[90mSplashscreen: Checking themes directory.\e[0m"
if [ -d $THEME_DIR ]; then
echo -e "\e[90mSplashscreen: Create theme directory if not exists.\e[0m"
if [ ! -d $THEME_DIR/MagicMirror ]; then
sudo mkdir $THEME_DIR/MagicMirror
fi
if sudo cp ~/MagicMirror/splashscreen/splash.png $THEME_DIR/MagicMirror/splash.png && sudo cp ~/MagicMirror/splashscreen/MagicMirror.plymouth $THEME_DIR/MagicMirror/MagicMirror.plymouth && sudo cp ~/MagicMirror/splashscreen/MagicMirror.script $THEME_DIR/MagicMirror/MagicMirror.script; then
echo -e "\e[90mSplashscreen: Theme copied successfully.\e[0m"
if sudo plymouth-set-default-theme -R MagicMirror; then
echo -e "\e[92mSplashscreen: Changed theme to MagicMirror successfully.\e[0m"
else
echo -e "\e[91mSplashscreen: Couldn't change theme to MagicMirror!\e[0m"
fi
else
echo -e "\e[91mSplashscreen: Copying theme failed!\e[0m"
fi
else
echo -e "\e[91mSplashscreen: Themes folder doesn't exist!\e[0m"
fi
else
echo -e "\e[93mplymouth is not installed.\e[0m";
fi
# Use pm2 control like a service MagicMirror
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
if [[ "$(ps --no-headers -o comm 1)" =~ systemd ]]; then #Checking for systemd
sudo pm2 startup systemd -u pi --hp /home/pi
else
sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u pi --hp /home/pi"
fi
pm2 start ~/MagicMirror/installers/pm2_MagicMirror.json
pm2 save
fi
# Disable Screensaver
read -p "Do you want to disable the screen saver? (y/N)?" choice
if [[ $choice =~ ^[Yy]$ ]]; then
sudo su -c "echo -e '@xset s noblank\n@xset s off\n@xset -dpms' >> /etc/xdg/lxsession/LXDE-pi/autostart"
export DISPLAY=:0; xset s noblank;xset s off;xset -dpms
fi
echo " "
echo -e "\e[92mWe're ready! Run \e[1m\e[97mDISPLAY=:0 npm start\e[0m\e[92m from the ~/MagicMirror directory to start your MagicMirror.\e[0m"
echo " "
echo " "

View File

@@ -4,13 +4,18 @@
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
var fs = require("fs");
var Server = require(__dirname + "/server.js");
var Utils = require(__dirname + "/utils.js");
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js");
var path = require("path");
// Alias modules mentioned in package.js under _moduleAliases.
require("module-alias/register");
// add timestamps in front of log messages
require("console-stamp")(console, "yyyy-mm-dd HH:MM:ss.l");
// Get version number.
global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
console.log("Starting MagicMirror: v" + global.version);
@@ -48,7 +53,6 @@ var App = function() {
*
* argument callback function - The callback function.
*/
var loadConfig = function(callback) {
console.log("Loading config ...");
var defaults = require(__dirname + "/defaults.js");
@@ -67,7 +71,7 @@ var App = function() {
var config = Object.assign(defaults, c);
callback(config);
} catch (e) {
if (e.code == "ENOENT") {
if (e.code === "ENOENT") {
console.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration."));
} else if (e instanceof ReferenceError || e instanceof SyntaxError) {
console.error(Utils.colors.error("WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e.stack));
@@ -96,7 +100,7 @@ var App = function() {
". Check README and CHANGELOG for more up-to-date ways of getting the same functionality.")
);
}
}
};
/* loadModule(module)
* Loads a specific module.
@@ -107,10 +111,10 @@ var App = function() {
var elements = module.split("/");
var moduleName = elements[elements.length - 1];
var moduleFolder = __dirname + "/../modules/" + module;
var moduleFolder = __dirname + "/../modules/" + module;
if (defaultModules.indexOf(moduleName) !== -1) {
moduleFolder = __dirname + "/../modules/default/" + module;
moduleFolder = __dirname + "/../modules/default/" + module;
}
var helperPath = moduleFolder + "/node_helper.js";
@@ -173,7 +177,7 @@ var App = function() {
};
/* cmpVersions(a,b)
* Compare two symantic version numbers and return the difference.
* Compare two semantic version numbers and return the difference.
*
* argument a string - Version number a.
* argument a string - Version number b.
@@ -197,7 +201,7 @@ var App = function() {
/* start(callback)
* This methods starts the core app.
* It loads the config, then it loads all modules.
* When it"s done it executs the callback with the config as argument.
* When it's done it executes the callback with the config as argument.
*
* argument callback function - The callback function.
*/
@@ -231,7 +235,6 @@ var App = function() {
if (typeof callback === "function") {
callback(config);
}
});
});
});
@@ -259,7 +262,16 @@ var App = function() {
*/
process.on("SIGINT", () => {
console.log("[SIGINT] Received. Shutting down server...");
setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds
setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds
this.stop();
process.exit(0);
});
/* We also need to listen to SIGTERM signals so we stop everything when we are asked to stop by the OS.
*/
process.on("SIGTERM", () => {
console.log("[SIGTERM] Received. Shutting down server...");
setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds
this.stop();
process.exit(0);
});

View File

@@ -21,7 +21,7 @@
var prototype = new this();
initializing = false;
// Make a copy of all prototype properies, to prevent reference issues.
// Make a copy of all prototype properties, to prevent reference issues.
for (var name in prototype) {
prototype[name] = cloneObject(prototype[name]);
}
@@ -29,8 +29,8 @@
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) {
prototype[name] = typeof prop[name] === "function" &&
typeof _super[name] === "function" && fnTest.test(prop[name]) ? (function (name, fn) {
return function () {
var tmp = this._super;
@@ -43,7 +43,6 @@
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) : prop[name];

View File

@@ -1,5 +1,3 @@
/* jshint esversion: 6 */
"use strict";
const electron = require("electron");
@@ -17,6 +15,7 @@ const BrowserWindow = electron.BrowserWindow;
let mainWindow;
function createWindow() {
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
var electronOptionsDefaults = {
width: 800,
height: 600,

View File

@@ -8,7 +8,7 @@
var Loader = (function() {
/* Create helper valiables */
/* Create helper variables */
var loadedModuleFiles = [];
var loadedFiles = [];
@@ -55,7 +55,7 @@ var Loader = (function() {
module.start();
}
// Notifiy core of loded modules.
// Notify core of loaded modules.
MM.modulesStarted(moduleObjects);
};
@@ -83,10 +83,10 @@ var Loader = (function() {
var elements = module.split("/");
var moduleName = elements[elements.length - 1];
var moduleFolder = config.paths.modules + "/" + module;
var moduleFolder = config.paths.modules + "/" + module;
if (defaultModules.indexOf(moduleName) !== -1) {
moduleFolder = config.paths.modules + "/default/" + module;
moduleFolder = config.paths.modules + "/default/" + module;
}
if (moduleData.disabled === true) {
@@ -104,7 +104,6 @@ var Loader = (function() {
config: moduleData.config,
classes: (typeof moduleData.classes !== "undefined") ? moduleData.classes + " " + module : module
});
}
return moduleFiles;
@@ -138,7 +137,6 @@ var Loader = (function() {
afterLoad();
});
}
};
/* bootstrapModule(module, mObj)
@@ -164,7 +162,6 @@ var Loader = (function() {
});
});
});
};
/* loadFile(fileName)
@@ -175,7 +172,7 @@ var Loader = (function() {
*/
var loadFile = function(fileName, callback) {
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
switch (extension.toLowerCase()) {
case "js":
@@ -210,7 +207,6 @@ var Loader = (function() {
document.getElementsByTagName("head")[0].appendChild(stylesheet);
break;
}
};
/* Public Methods */
@@ -261,5 +257,4 @@ var Loader = (function() {
loadFile(module.file(fileName), callback);
}
};
})();

View File

@@ -1,5 +1,4 @@
/* global Log, Loader, Module, config, defaults */
/* jshint -W020, esversion: 6 */
/* Magic Mirror
* Main System
@@ -39,11 +38,13 @@ var MM = (function() {
dom.opacity = 0;
wrapper.appendChild(dom);
if (typeof module.getHeader() !== "undefined" && module.getHeader() !== "") {
var moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.getHeader();
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
var moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.getHeader();
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") {
moduleHeader.style = "display: none;";
}
var moduleContent = document.createElement("div");
@@ -203,15 +204,15 @@ var MM = (function() {
*/
var updateModuleContent = function(module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {return;}
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
contentWrapper[0].innerHTML = "";
contentWrapper[0].appendChild(newContent);
if( headerWrapper.length > 0 && newHeader) {
headerWrapper[0].innerHTML = newHeader;
}
headerWrapper[0].innerHTML = newHeader;
headerWrapper[0].style = headerWrapper.length > 0 && newHeader ? undefined : "display: none;";
};
/* hideModule(module, speed, callback)
@@ -291,7 +292,7 @@ var MM = (function() {
var moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
// Restore the postition. See hideModule() for more info.
// Restore the position. See hideModule() for more info.
moduleWrapper.style.position = "static";
updateWrapperStates();
@@ -304,14 +305,16 @@ var MM = (function() {
module.showHideTimer = setTimeout(function() {
if (typeof callback === "function") { callback(); }
}, speed);
} else {
// invoke callback
if (typeof callback === "function") { callback(); }
}
};
/* updateWrapperStates()
* Checks for all positions if it has visible content.
* If not, if will hide the position to prevent unwanted margins.
* This method schould be called by the show and hide methods.
* This method should be called by the show and hide methods.
*
* Example:
* If the top_bar only contains the update notification. And no update is available,
@@ -319,7 +322,6 @@ var MM = (function() {
* an ugly top margin. By using this function, the top bar will be hidden if the
* update notification is not visible.
*/
var updateWrapperStates = function() {
var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
@@ -329,7 +331,7 @@ var MM = (function() {
var showWrapper = false;
Array.prototype.forEach.call(moduleWrappers, function(moduleWrapper) {
if (moduleWrapper.style.position == "" || moduleWrapper.style.position == "static") {
if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
showWrapper = true;
}
});
@@ -376,7 +378,7 @@ var MM = (function() {
*
* return array - Filtered collection of modules.
*/
var exceptWithClass = function(className) {
var exceptWithClass = function(className) {
return modulesByClass(className, false);
};
@@ -438,10 +440,10 @@ var MM = (function() {
});
};
if (typeof modules.withClass === "undefined") { Object.defineProperty(modules, "withClass", {value: withClass, enumerable: false}); }
if (typeof modules.exceptWithClass === "undefined") { Object.defineProperty(modules, "exceptWithClass", {value: exceptWithClass, enumerable: false}); }
if (typeof modules.exceptModule === "undefined") { Object.defineProperty(modules, "exceptModule", {value: exceptModule, enumerable: false}); }
if (typeof modules.enumerate === "undefined") { Object.defineProperty(modules, "enumerate", {value: enumerate, enumerable: false}); }
if (typeof modules.withClass === "undefined") { Object.defineProperty(modules, "withClass", {value: withClass, enumerable: false}); }
if (typeof modules.exceptWithClass === "undefined") { Object.defineProperty(modules, "exceptWithClass", {value: exceptWithClass, enumerable: false}); }
if (typeof modules.exceptModule === "undefined") { Object.defineProperty(modules, "exceptModule", {value: exceptModule, enumerable: false}); }
if (typeof modules.enumerate === "undefined") { Object.defineProperty(modules, "enumerate", {value: enumerate, enumerable: false}); }
};
return {
@@ -478,7 +480,7 @@ var MM = (function() {
/* sendNotification(notification, payload, sender)
* Send a notification to all modules.
*
* argument notification string - The identifier of the noitication.
* 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.
*/
@@ -558,7 +560,7 @@ var MM = (function() {
})();
// Add polyfill for Object.assign.
if (typeof Object.assign != "function") {
if (typeof Object.assign !== "function") {
(function() {
Object.assign = function(target) {
"use strict";

View File

@@ -76,7 +76,7 @@ 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 can to be subclassed if the module wants to display info on the mirror.
* Alternatively, the getTemplete method could be subclassed.
* Alternatively, the getTemplate method could be subclassed.
*
* return DomObject | Promise - The dom or a promise with the dom to display.
*/
@@ -92,7 +92,7 @@ var Module = Class.extend({
// the template is a filename
self.nunjucksEnvironment().render(template, templateData, function (err, res) {
if (err) {
Log.error(err)
Log.error(err);
}
div.innerHTML = res;
@@ -121,7 +121,7 @@ var Module = Class.extend({
/* 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.
* This method needs to be subclassed if the module wants to use a template.
* 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.
*
@@ -138,7 +138,7 @@ var Module = Class.extend({
* return Object
*/
getTemplateData: function () {
return {}
return {};
},
/* notificationReceived(notification, payload, sender)
@@ -151,9 +151,9 @@ var Module = Class.extend({
*/
notificationReceived: function (notification, payload, sender) {
if (sender) {
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
// Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
} else {
Log.log(this.name + " received a system notification: " + notification);
// Log.log(this.name + " received a system notification: " + notification);
}
},
@@ -164,7 +164,7 @@ var Module = Class.extend({
* @returns Nunjucks Environment
*/
nunjucksEnvironment: function() {
if (this._nunjucksEnvironment != null) {
if (this._nunjucksEnvironment !== null) {
return this._nunjucksEnvironment;
}
@@ -175,7 +175,7 @@ var Module = Class.extend({
lstripBlocks: true
});
this._nunjucksEnvironment.addFilter("translate", function(str) {
return self.translate(str)
return self.translate(str);
});
return this._nunjucksEnvironment;
@@ -233,7 +233,7 @@ var Module = Class.extend({
},
/* socket()
* Returns a socket object. If it doesn"t exist, it"s created.
* Returns a socket object. If it doesn't exist, it"s created.
* It also registers the notification callback.
*/
socket: function () {
@@ -417,8 +417,11 @@ var Module = Class.extend({
callback = callback || function () { };
options = options || {};
this.resume();
MM.showModule(this, speed, callback, options);
var self = this;
MM.showModule(this, speed, function () {
self.resume();
callback;
}, options);
}
});
@@ -438,11 +441,10 @@ Module.create = function (name) {
var ModuleClass = Module.extend(clonedDefinition);
return new ModuleClass();
};
/* cmpVersions(a,b)
* Compare two symantic version numbers and return the difference.
* Compare two semantic version numbers and return the difference.
*
* argument a string - Version number a.
* argument a string - Version number b.

View File

@@ -5,7 +5,7 @@
* MIT Licensed.
*/
var Class = require("../../../js/class.js");
var Class = require("./class.js");
var express = require("express");
var path = require("path");
@@ -101,9 +101,9 @@ NodeHelper = Class.extend({
var onevent = socket.onevent;
socket.onevent = function(packet) {
var args = packet.data || [];
onevent.call(this, packet); // original call
onevent.call(this, packet); // original call
packet.data = ["*"].concat(args);
onevent.call(this, packet); // additional call to catch-all
onevent.call(this, packet); // additional call to catch-all
};
// register catch all.

View File

@@ -7,8 +7,6 @@
var express = require("express");
var app = require("express")();
var server = require("http").Server(app);
var io = require("socket.io")(server);
var path = require("path");
var ipfilter = require("express-ipfilter").IpFilter;
var fs = require("fs");
@@ -22,12 +20,24 @@ var Server = function(config, callback) {
port = process.env.MM_PORT;
}
var server = null;
if(config.useHttps){
var options = {
key: fs.readFileSync(config.httpsPrivateKey),
cert: fs.readFileSync(config.httpsCertificate)
};
server = require("https").Server(options, app);
}else{
server = require("http").Server(app);
}
var io = require("socket.io")(server);
console.log("Starting server on port " + port + " ... ");
server.listen(port, config.address ? config.address : null);
server.listen(port, config.address ? config.address : "localhost");
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length == 0) {
console.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"))
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
console.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
}
app.use(function(req, res, next) {

View File

@@ -8,15 +8,17 @@ var MMSocket = function(moduleName) {
self.moduleName = moduleName;
// Private Methods
self.socket = io("/" + self.moduleName);
self.socket = io("/" + self.moduleName, {
path: window.location.pathname + "socket.io"
});
var notificationCallback = function() {};
var onevent = self.socket.onevent;
self.socket.onevent = function(packet) {
var args = packet.data || [];
onevent.call(this, packet); // original call
onevent.call(this, packet); // original call
packet.data = ["*"].concat(args);
onevent.call(this, packet); // additional call to catch-all
onevent.call(this, packet); // additional call to catch-all
};
// register catch all.

View File

@@ -18,7 +18,7 @@ var Translator = (function() {
xhr.overrideMimeType("application/json");
xhr.open("GET", file, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == "200") {
if (xhr.readyState === 4 && xhr.status === 200) {
callback(JSON.parse(stripComments(xhr.responseText)));
}
};
@@ -159,6 +159,7 @@ var Translator = (function() {
return key;
},
/* load(module, file, isFallback, callback)
* Load a translation file (json) and remember the data.
*

View File

@@ -1,721 +0,0 @@
# MagicMirror² Module Development Documentation
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 depends upon, including web links to those
- Whether 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.
A module can be placed in one single folder. Or multiple modules can be grouped in a subfolder. Note that name of the module must be unique. Even when a module with a similar name is placed in a different folder, they can't be loaded at the same time.
### Files
- **modulename/modulename.js** - This is your core module script.
- **modulename/node_helper.js** - This is an optional helper that will be loaded by the node script. The node helper and module script can communicate with each other using an integrated socket system.
- **modulename/public** - Any files in this folder can be accessed 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.
## 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",{});
````
Of course, the above module would not do anything fancy, so it's good to look at one of the simplest modules: **helloworld**:
````javascript
//helloworld.js:
Module.register("helloworld",{
// Default module config.
defaults: {
text: "Hello World!"
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
wrapper.innerHTML = this.config.text;
return wrapper;
}
});
````
As you can see, the `Module.register()` method takes two arguments: the name of the module and an object with the module properties.
### Available module instance properties
After the module is initialized, the module instance has a few available module properties:
| 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 `this.data` data object contain the following 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.
- `data.header` - The header added to the module.
- `data.position` - The position in which the instance will be shown.
#### `defaults: {}`
Any properties defined in the defaults object, will be merged with the module config as defined in the user's config.js file. This is the best place to set your modules' configuration defaults. Any of the module configuration properties can be accessed using `this.config.propertyName`, but more about that later.
#### `requiresVersion:`
*Introduced in version: 2.1.0.*
A string that defines the minimum version of the MagicMirror framework. If it is set, the system compares the required version with the users version. If the version of the user is out of date, it won't run the module. Make sure to also set this value in the Node helper.
**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
Example:
````javascript
requiresVersion: "2.1.0",
````
### Subclassable module methods
#### `init()`
This method is called when a module gets instantiated. In most cases you do not need to subclass this method.
#### `loaded(callback)`
*Introduced in version: 2.1.1.*
This method is called when a module is loaded. Subsequent modules in the config are not yet loaded. The `callback` function MUST be called when the module is done loading. In most cases you do not need to subclass this method.
**Example:**
````javascript
loaded: function(callback) {
this.finishLoading();
Log.log(this.name + ' is loaded!');
callback();
}
````
#### `start()`
This method is called when all modules are loaded and the system is ready to boot up. Keep in mind that the dom object for the module is not yet created. The start method is a perfect place to define any additional module properties:
**Example:**
````javascript
start: function() {
this.mySpecialProperty = "So much wow!";
Log.log(this.name + ' is started!');
}
````
#### `getScripts()`
**Should return: Array**
The getScripts method is called to request any additional scripts that need to be loaded. This method should therefore return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.js')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder.
**Example:**
````javascript
getScripts: function() {
return [
'script.js', // will try to load it from the vendor folder, otherwise it will load is from the module folder.
'moment.js', // this file is available in the vendor folder, so it doesn't need to be available in the module folder.
this.file('anotherfile.js'), // this file will be loaded straight from the module folder.
'https://code.jquery.com/jquery-2.2.3.min.js', // this file will be loaded from the jquery servers.
]
}
````
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore, it's advised not to use any external urls.
#### `getStyles()`
**Should return: Array**
The getStyles method is called to request any additional stylesheets that need to be loaded. This method should therefore return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.css')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder.
**Example:**
````javascript
getStyles: function() {
return [
'script.css', // will try to load it from the vendor folder, otherwise it will load is from the module folder.
'font-awesome.css', // this file is available in the vendor folder, so it doesn't need to be available in the module folder.
this.file('anotherfile.css'), // this file will be loaded straight from the module folder.
'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', // this file will be loaded from the bootstrapcdn servers.
]
}
````
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore, it's advised not to use any external URLs.
#### `getTranslations()`
**Should return: Dictionary**
The getTranslations method is called to request translation files that need to be loaded. This method should therefore return a dictionary with the files to load, identified by the country's short name.
If the module does not have any module specific translations, the function can just be omitted or return `false`.
**Example:**
````javascript
getTranslations: function() {
return {
en: "translations/en.json",
de: "translations/de.json"
}
}
````
#### `getDom()`
**Should return:** Dom Object
Whenever the MagicMirror needs to update the information on screen (because it starts, or because your module asked a refresh using `this.updateDom()`), the system calls the getDom method. This method should therefore return a dom object.
**Example:**
````javascript
getDom: function() {
var wrapper = document.createElement("div");
wrapper.innerHTML = 'Hello world!';
return wrapper;
}
````
#### `getHeader()`
**Should return:** String
Whenever the MagicMirror needs to update the information on screen (because it starts, or because your module asked a refresh using `this.updateDom()`), the system calls the getHeader method to retrieve the module's header. This method should therefor return a string. If this method is not subclassed, this function will return the user's configured header.
If you want to use the original user's configured header, reference `this.data.header`.
**NOTE:** If the user did not configure a default header, no header will be displayed and thus this method will not be called.
**Example:**
````javascript
getHeader: function() {
return this.data.header + ' Foo Bar';
}
````
#### `notificationReceived(notification, payload, sender)`
That MagicMirror core has the ability to send notifications to modules. Or even better: the modules have the possibility to send notifications to other modules. When this module is called, it has 3 arguments:
- `notification` - String - The notification identifier.
- `payload` - AnyType - The payload of a notification.
- `sender` - Module - The sender of the notification. If this argument is `undefined`, the sender of the notification is the core system.
**Example:**
````javascript
notificationReceived: function(notification, payload, sender) {
if (sender) {
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
} else {
Log.log(this.name + " received a system notification: " + notification);
}
}
````
**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)`
When using a node_helper, the node helper can send your module notifications. When this module is called, it has 2 arguments:
- `notification` - String - The notification identifier.
- `payload` - AnyType - The payload of a notification.
**Note 1:** When a node helper sends a notification, all modules of that module type receive the same notifications. <br>
**Note 2:** The socket connection is established as soon as the module sends its first message using [sendSocketNotification](thissendsocketnotificationnotification-payload).
**Example:**
````javascript
socketNotificationReceived: function(notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
````
#### `suspend()`
When a module is hidden (using the `module.hide()` method), the `suspend()` method will be called. By subclassing this method you can perform tasks like halting the update timers.
#### `resume()`
When a module is requested to be shown (using the `module.show()` method), the `resume()` method will be called. By subclassing this method you can perform tasks restarting the update timers.
### Module instance methods
Each module instance has some handy methods which can be helpful building your module.
#### `this.file(filename)`
***filename* String** - The name of the file you want to create the path for.<br>
**Returns String**
If you want to create a path to a file in your module folder, use the `file()` method. It returns the path to the filename given as the attribute. Is method comes in handy when configuring the [getScripts](#getscripts) and [getStyles](#getstyles) methods.
#### `this.updateDom(speed)`
***speed* Number** - Optional. Animation speed in milliseconds.<br>
Whenever your module need to be updated, call the `updateDom(speed)` method. It requests the MagicMirror core to update its dom object. If you define the speed, the content update will be animated, but only if the content will really change.
As an example: the clock modules calls this method every second:
````javascript
...
start: function() {
var self = this;
setInterval(function() {
self.updateDom(); // no speed defined, so it updates instantly.
}, 1000); //perform every 1000 milliseconds.
},
...
````
#### `this.sendNotification(notification, payload)`
***notification* String** - The notification identifier.<br>
***payload* AnyType** - Optional. A notification payload.<br>
If you want to send a notification to all other modules, use the `sendNotification(notification, payload)`. All other modules will receive the message via the [notificationReceived](#notificationreceivednotification-payload-sender) method. In that case, the sender is automatically set to the instance calling the sendNotification method.
**Example:**
````javascript
this.sendNotification('MYMODULE_READY_FOR_ACTION', {foo:bar});
````
#### `this.sendSocketNotification(notification, payload)`
***notification* String** - The notification identifier.<br>
***payload* AnyType** - Optional. A notification payload.<br>
If you want to send a notification to the node_helper, use the `sendSocketNotification(notification, payload)`. Only the node_helper of this module will receive the socket notification.
**Example:**
````javascript
this.sendSocketNotification('SET_CONFIG', this.config);
````
#### `this.hide(speed, callback, options)`
***speed* Number** - Optional (Required when setting callback or options), The speed of the hide animation in milliseconds.
***callback* Function** - Optional, The callback after the hide animation is finished.
***options* Function** - Optional, Object with additional options for the hide action (see below). (*Introduced in version: 2.1.0.*)
To hide a module, you can call the `hide(speed, callback)` method. You can call the hide method on the module instance itself using `this.hide()`, but of course you can also hide another module using `anOtherModule.hide()`.
Possible configurable options:
- `lockString` - String - When setting lock string, the module can not be shown without passing the correct lockstring. This way (multiple) modules can prevent a module from showing. It's considered best practice to use your modules identifier as the locksString: `this.identifier`. See *visibility locking* below.
**Note 1:** If the hide animation is cancelled, for instance because the show method is called before the hide animation was finished, the callback will not be called.<br>
**Note 2:** If the hide animation is hijacked (an other method calls hide on the same module), the callback will not be called.<br>
**Note 3:** If the dom is not yet created, the hide method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender).
#### `this.show(speed, callback, options)`
***speed* Number** - Optional (Required when setting callback or options), The speed of the show animation in milliseconds.
***callback* Function** - Optional, The callback after the show animation is finished.
***options* Function** - Optional, Object with additional options for the show action (see below). (*Introduced in version: 2.1.0.*)
To show a module, you can call the `show(speed, callback)` method. You can call the show method on the module instance itself using `this.show()`, but of course you can also show another module using `anOtherModule.show()`.
Possible configurable options:
- `lockString` - String - When setting lock string, the module can not be shown without passing the correct lockstring. This way (multiple) modules can prevent a module from showing. See *visibility locking* below.
- `force` - Boolean - When setting the force tag to `true`, the locking mechanism will be overwritten. Use this option with caution. It's considered best practice to let the usage of the force option be use- configurable. See *visibility locking* below.
**Note 1:** If the show animation is canceled, for instance because the hide method is called before the show animation was finished, the callback will not be called.<br>
**Note 2:** If the show animation is hijacked (an other method calls show on the same module), the callback will not be called.<br>
**Note 3:** If the dom is not yet created, the show method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender).
#### Visibility locking
(*Introduced in version: 2.1.0.*)
Visibility locking helps the module system to prevent unwanted hide/show actions. The following scenario explains the concept:
**Module B asks module A to hide:**
````javascript
moduleA.hide(0, {lockString: "module_b_identifier"});
````
Module A is now hidden, and has an lock array with the following strings:
````javascript
moduleA.lockStrings == ["module_b_identifier"]
moduleA.hidden == true
````
**Module C asks module A to hide:**
````javascript
moduleA.hide(0, {lockString: "module_c_identifier"});
````
Module A is now hidden, and has an lock array with the following strings:
````javascript
moduleA.lockStrings == ["module_b_identifier", "module_c_identifier"]
moduleA.hidden == true
````
**Module B asks module A to show:**
````javascript
moduleA.show(0, {lockString: "module_b_identifier"});
````
The lockString will be removed from moduleAs locks array, but since there still is an other lock string available, the module remains hidden:
````javascript
moduleA.lockStrings == ["module_c_identifier"]
moduleA.hidden == true
````
**Module C asks module A to show:**
````javascript
moduleA.show(0, {lockString: "module_c_identifier"});
````
The lockString will be removed from moduleAs locks array, and since this will result in an empty lock array, the module will be visible:
````javascript
moduleA.lockStrings == []
moduleA.hidden == false
````
**Note:** The locking mechanism can be overwritten by using the force tag:
````javascript
moduleA.show(0, {force: true});
````
This will reset the lockstring array, and will show the module.
````javascript
moduleA.lockStrings == []
moduleA.hidden == false
````
Use this `force` method with caution. See `show()` method for more information.
#### `this.translate(identifier)`
***identifier* String** - Identifier of the string that should be translated.
The Magic Mirror contains a convenience wrapper for `l18n`. You can use this to automatically serve different translations for your modules based on the user's `language` configuration.
If no translation is found, a fallback will be used. The fallback sequence is as follows:
- 1. Translation as defined in module translation file of the user's preferred language.
- 2. Translation as defined in core translation file of the user's preferred language.
- 3. Translation as defined in module translation file of the fallback language (the first defined module translation file).
- 4. Translation as defined in core translation file of the fallback language (the first defined core translation file).
- 5. The key (identifier) of the translation.
When adding translations to your module, it's a good idea to see if an appropriate translation is already available in the [core translation files](https://github.com/MichMich/MagicMirror/tree/master/translations). This way, your module can benefit from the existing translations.
**Example:**
````javascript
this.translate("INFO") //Will return a translated string for the identifier INFO
````
**Example json file:**
````javascript
{
"INFO": "Really important information!"
}
````
**Note:** although comments are officially not supported in JSON files, MagicMirror allows it by stripping the comments before parsing the JSON file. Comments in translation files could help other translators.
##### `this.translate(identifier, variables)`
***identifier* String** - Identifier of the string that should be translated.
***variables* Object** - Object of variables to be used in translation.
This improved and backwards compatible way to handle translations behaves like the normal translation function and follows the rules described above. It's recommended to use this new format for translating everywhere. It allows translator to change the word order in the sentence to be translated.
**Example:**
````javascript
var timeUntilEnd = moment(event.endDate, "x").fromNow(true);
this.translate("RUNNING", { "timeUntilEnd": timeUntilEnd) }); // Will return a translated string for the identifier RUNNING, replacing `{timeUntilEnd}` with the contents of the variable `timeUntilEnd` in the order that translator intended.
````
**Example English .json file:**
````javascript
{
"RUNNING": "Ends in {timeUntilEnd}",
}
````
**Example Finnish .json file:**
````javascript
{
"RUNNING": "Päättyy {timeUntilEnd} päästä",
}
````
**Note:** The *variables* Object has an special case called `fallback`. It's used to support old translations in translation files that do not have the variables in them. If you are upgrading an old module that had translations that did not support the word order, it is recommended to have the fallback layout.
**Example:**
````javascript
var timeUntilEnd = moment(event.endDate, "x").fromNow(true);
this.translate("RUNNING", {
"fallback": this.translate("RUNNING") + " {timeUntilEnd}"
"timeUntilEnd": timeUntilEnd
)}); // Will return a translated string for the identifier RUNNING, replacing `{timeUntilEnd}` with the contents of the variable `timeUntilEnd` in the order that translator intended. (has a fallback)
````
**Example Swedish .json file that does not have the variable in it:**
````javascript
{
"RUNNING": "Slutar",
}
````
In this case the `translate`-function will not find any variables in the translation, will look for `fallback` variable and use that if possible to create the translation.
## The Node Helper: node_helper.js
The node helper is a Node.js script that is able to do some backend task to support your module. For every module type, only one node helper instance will be created. For example: if your MagicMirror uses two calendar modules, there will be only one calendar node helper instantiated.
**Note:** Because there is only one node helper per module type, there is no default config available within your module. It's your task to send the desired config from your module to your node helper.
In it's most simple form, the node_helper.js file must contain:
````javascript
var NodeHelper = require("node_helper");
module.exports = NodeHelper.create({});
````
Of course, the above helper would not do anything useful. So with the information above, you should be able to make it a bit more sophisticated.
### Available module instance properties
#### `this.name`
**String**
The name of the module
#### `this.path`
**String**
The path of the module
#### `this.expressApp`
**Express App Instance**
This is a link to the express instance. It will allow you to define extra routes.
**Example:**
````javascript
start: function() {
this.expressApp.get('/foobar', function (req, res) {
res.send('GET request to /foobar');
});
}
````
**Note:** By default, a public path to your module's public folder will be created:
````javascript
this.expressApp.use("/" + this.name, express.static(this.path + "/public"));
````
#### `this.io`
**Socket IO Instance**
This is a link to the IO instance. It will allow you to do some Socket.IO magic. In most cases you won't need this, since the Node Helper has a few convenience methods to make this simple.
#### `requiresVersion:`
*Introduced in version: 2.1.0.*
A string that defines the minimum version of the MagicMirror framework. If it is set, the system compares the required version with the users version. If the version of the user is out of date, it won't run the module.
**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
Example:
````javascript
requiresVersion: "2.1.0",
````
### Subclassable module methods
#### `init()`
This method is called when a node helper gets instantiated. In most cases you do not need to subclass this method.
#### `start()`
This method is called when all node helpers are loaded and the system is ready to boot up. The start method is a perfect place to define any additional module properties:
**Example:**
````javascript
start: function() {
this.mySpecialProperty = "So much wow!";
Log.log(this.name + ' is started!');
}
````
#### `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:
- `notification` - String - The notification identifier.
- `payload` - AnyType - The payload of a notification.
**Note:** The socket connection is established as soon as the module sends its first message using [sendSocketNotification](thissendsocketnotificationnotification-payload).
**Example:**
````javascript
socketNotificationReceived: function(notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
````
### Module instance methods
Each node helper has some handy methods which can be helpful building your module.
#### `this.sendSocketNotification(notification, payload)`
***notification* String** - The notification identifier.<br>
***payload* AnyType** - Optional. A notification payload.<br>
If you want to send a notification to all your modules, use the `sendSocketNotification(notification, payload)`. Only the module of your module type will receive the socket notification.
**Note:** Since all instances of your module will receive the notifications, it's your task to make sure the right module responds to your messages.
**Example:**
````javascript
this.sendSocketNotification('SET_CONFIG', this.config);
````
## MagicMirror Helper Methods
The core Magic Mirror object: `MM` has some handy method that will help you in controlling your and other modules. Most of the `MM` methods are available via convenience methods on the Module instance.
### Module selection
The only additional method available for your module, is the feature to retrieve references to other modules. This can be used to hide and show other modules.
#### `MM.getModules()`
**Returns Array** - An array with module instances.<br>
To make a selection of all currently loaded module instances, run the `MM.getModules()` method. It will return an array with all currently loaded module instances. The returned array has a lot of filtering methods. See below for more info.
**Note:** This method returns an empty array if not all modules are started yet. Wait for the `ALL_MODULES_STARTED` [notification](#notificationreceivednotification-payload-sender).
##### `.withClass(classnames)`
***classnames* String or Array** - The class names on which you want to filter.
**Returns Array** - An array with module instances.<br>
If you want to make a selection based on one or more class names, use the withClass method on a result of the `MM.getModules()` method. The argument of the `withClass(classname)` method can be an array, or space separated string.
**Examples:**
````javascript
var modules = MM.getModules().withClass('classname');
var modules = MM.getModules().withClass('classname1 classname2');
var modules = MM.getModules().withClass(['classname1','classname2']);
````
##### `.exceptWithClass(classnames)`
***classnames* String or Array** - The class names of the modules you want to remove from the results.
**Returns Array** - An array with module instances.<br>
If you to remove some modules from a selection based on a classname, use the exceptWithClass method on a result of the `MM.getModules()` method. The argument of the `exceptWithClass(classname)` method can be an array, or space separated string.
**Examples:**
````javascript
var modules = MM.getModules().exceptWithClass('classname');
var modules = MM.getModules().exceptWithClass('classname1 classname2');
var modules = MM.getModules().exceptWithClass(['classname1','classname2']);
````
##### `.exceptModule(module)`
***module* Module Object** - The reference to a module you want to remove from the results.
**Returns Array** - An array with module instances.<br>
If you to remove a specific module instance from a selection based on a classname, use the exceptWithClass method on a result of the `MM.getModules()` method. This can be helpful if you want to select all module instances except the instance of your module.
**Examples:**
````javascript
var modules = MM.getModules().exceptModule(this);
````
Of course, you can combine all of the above filters:
**Example:**
````javascript
var modules = MM.getModules().withClass('classname1').exceptwithClass('classname2').exceptModule(aModule);
````
##### `.enumerate(callback)`
***callback* Function(module)** - The callback run on every instance.
If you want to perform an action on all selected modules, you can use the `enumerate` function:
````javascript
MM.getModules().enumerate(function(module) {
Log.log(module.name);
});
````
**Example:**
To hide all modules except the your module instance, you could write something like:
````javascript
Module.register("modulename",{
//...
notificationReceived: function(notification, payload, sender) {
if (notification === 'DOM_OBJECTS_CREATED') {
MM.getModules().exceptModule(this).enumerate(function(module) {
module.hide(1000, function() {
//Module hidden.
});
});
}
},
//...
});
````
## MagicMirror Logger
The Magic Mirror contains a convenience wrapper for logging. Currently, this logger is a simple proxy to the original `console.log` methods. But it might get additional features in the future. The Loggers is currently only available in the core module file (not in the node_helper).
**Examples:**
````javascript
Log.info('error');
Log.log('log');
Log.error('info');
````

View File

@@ -1,65 +1,4 @@
# Module: Alert
The alert module is one of the default modules of the MagicMirror. This module displays notifications from other modules.
## Usage
To use this module, add it to the modules array in the config/config.js file:
```
modules: [
{
module: "alert",
config: {
// The config property is optional.
// See 'Configuration options' for more information.
}
}
]
```
## Configuration options
The following properties can be configured:
| Option | Description
| ----------------- | -----------
| `effect` | The animation effect to use for notifications. <br><br> **Possible values:** `scale` `slide` `genie` `jelly` `flip` `exploader` `bouncyflip` <br> **Default value:** `slide`
| `alert_effect` | The animation effect to use for alerts. <br><br> **Possible values:** `scale` `slide` `genie` `jelly` `flip` `exploader` `bouncyflip` <br> **Default value:** `jelly`
| `display_time` | Time a notification is displayed in milliseconds. <br><br> **Possible values:** `int` <br> **Default value:** `3500`
| `position` | Position where the notifications should be displayed. <br><br> **Possible values:** `left` `center` `right` <br> **Default value:** `center`
| `welcome_message` | Message shown at startup. <br><br> **Possible values:** `string` `false` <br> **Default value:** `false` (no message at startup)
## Developer notes
For notifications use:
```
self.sendNotification("SHOW_ALERT", {type: "notification"});
```
For alerts use:
```
self.sendNotification("SHOW_ALERT", {});
```
### Notification params
| Option | Description
| ------------------ | -----------
| `title` | The title of the notification. <br><br> **Possible values:** `text` or `html`
| `message` | The message of the notification. <br><br> **Possible values:** `text` or `html`
| `timer` (optional) | How long the notification should stay visible in ms. <br> If absent, the default `display_time` is used. <br> **Possible values:** `int` `float`
### Alert params
| Option | Description
| ----------------------------------------------- | -----------
| `title` | The title of the alert. <br><br> **Possible values:** `text` or `html`
| `message` | The message of the alert. <br><br> **Possible values:** `text` or `html`
| `imageUrl` (optional) | Image to show in the alert <br><br> **Possible values:** `url` `path` <br> **Default value:** `none`
| `imageFA` (optional) | Font Awesome icon to show in the alert <br><br> **Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website. <br> **Default value:** `none`
| `imageHeight` (optional even with imageUrl set) | Height of the image <br><br> **Possible values:** `intpx` <br> **Default value:** `80px`
| `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)
See [tympanus.net](http://tympanus.net/codrops/licensing/) for license.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/alert.html).

View File

@@ -35,13 +35,13 @@ Module.register("alert",{
};
},
show_notification: function(message) {
if (this.config.effect == "slide") {this.config.effect = this.config.effect + "-" + this.config.position;}
if (this.config.effect === "slide") {this.config.effect = this.config.effect + "-" + this.config.position;}
msg = "";
if (message.title) {
msg += "<span class='thin dimmed medium'>" + message.title + "</span>";
}
if (message.message){
if (msg != ""){
if (msg !== ""){
msg+= "<br />";
}
msg += "<span class='light bright small'>" + message.message + "</span>";
@@ -132,7 +132,7 @@ Module.register("alert",{
if (typeof payload.type === "undefined") { payload.type = "alert"; }
if (payload.type === "alert") {
this.show_alert(payload, sender);
} else if (payload.type = "notification") {
} else if (payload.type === "notification") {
this.show_notification(payload);
}
} else if (notification === "HIDE_ALERT") {
@@ -152,5 +152,4 @@ Module.register("alert",{
}
Log.info("Starting module: " + this.name);
}
});

View File

@@ -2,104 +2,4 @@
The `calendar` module is one of the default modules of the MagicMirror.
This module displays events from a public .ical calendar. It can combine multiple calendars.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: "calendar",
position: "top_left", // This can be any of the regions. Best results in left or right regions.
config: {
// The config property is optional.
// If no config is set, an example calendar is shown.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
| Option | Description
| ---------------------------- | -----------
| `maximumEntries` | The maximum number of events shown. / **Possible values:** `0` - `100` <br> **Default value:** `10`
| `maximumNumberOfDays` | The maximum number of days in the future. <br><br> **Default value:** `365`
| `displaySymbol` | Display a symbol in front of an entry. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `defaultSymbol` | The default symbol. <br><br> **Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website. <br> **Default value:** `calendar`
| `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`
| `maxTitleLines` | The maximum number of lines a title will wrap vertically before being cut (Only enabled if `wrapEvents` is also enabled). <br><br> **Possible values:** `0` - `10` <br> **Default value:** `3`
| `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)
| `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, 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`
| `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:** `[]`
| `sliceMultiDayEvents` | If this is set to true, events exceeding at least one midnight will be sliced into separate events including a counter like (1/2). This is especially helpful in "dateheaders" mode. Events will be sliced at midnight, end time for all events but the last will be 23:59 **Default value:** `true`
### 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',
symbol: 'calendar',
auth: {
user: 'username',
pass: 'superstrongpassword',
method: 'basic'
}
},
],
}
````
#### Calendar configuration options:
| Option | Description
| --------------------- | -----------
| `url` | The url of the calendar .ical. This property is required. <br><br> **Possible values:** Any public accessble .ical calendar.
| `symbol` | The symbol to show in front of an event. This property is optional. <br><br> **Possible values:** See [Font Awesome](http://fontawesome.io/icons/) website. To have multiple symbols you can define them in an array e.g. `["calendar", "plane"]`
| `color` | The font color of an event from this calendar. This property should be set if the config is set to colored: true. <br><br> **Possible values:** HEX, RGB or RGBA values (#efefef, rgb(242,242,242), rgba(242,242,242,0.5)).
| `repeatingCountTitle` | The count title for yearly repating events in this calendar. <br><br> **Example:** `'Birthday'`
| `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
| `name` | The name of the calendar. Included in event broadcasts as `calendarName`.
| `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:
| Option | Description
| --------------------- | -----------
| `user` | The username for HTTP authentication.
| `pass` | The password for HTTP authentication. (If you use Bearer authentication, this should be your BearerToken.)
| `method` | Which authentication method should be used. HTTP Basic, Digest and Bearer authentication methods are supported. Basic authentication is used by default if this option is omitted. **Possible values:** `digest`, `basic`, `bearer` **Default value:** `basic`
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/calendar.html).

View File

@@ -15,6 +15,7 @@ Module.register("calendar", {
maximumNumberOfDays: 365,
displaySymbol: true,
defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/
showLocation: false,
displayRepeatingCountTitle: false,
defaultRepeatingCountTitle: "",
maxTitleLength: 25,
@@ -48,7 +49,9 @@ Module.register("calendar", {
},
broadcastEvents: true,
excludedEvents: [],
sliceMultiDayEvents: false
sliceMultiDayEvents: false,
broadcastPastEvents: false,
nextDaysRelative: false
},
// Define required scripts.
@@ -82,7 +85,8 @@ Module.register("calendar", {
var calendarConfig = {
maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays
maximumNumberOfDays: calendar.maximumNumberOfDays,
broadcastPastEvents: calendar.broadcastPastEvents,
};
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = "";
@@ -101,7 +105,7 @@ Module.register("calendar", {
calendar.auth = {
user: calendar.user,
pass: calendar.pass
}
};
}
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
@@ -131,10 +135,9 @@ Module.register("calendar", {
}
} else if (notification === "FETCH_ERROR") {
Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
this.loaded = true;
} else if (notification === "INCORRECT_URL") {
Log.error("Calendar Error. Incorrect url: " + payload.url);
} else {
Log.log("Calendar received an unknown socket notification: " + notification);
}
this.updateDom(this.config.animationSpeed);
@@ -175,6 +178,7 @@ Module.register("calendar", {
dateCell.colSpan = "3";
dateCell.innerHTML = dateAsString;
dateCell.style.paddingTop = "10px";
dateRow.appendChild(dateCell);
wrapper.appendChild(dateRow);
@@ -187,7 +191,6 @@ Module.register("calendar", {
}
}
var eventWrapper = document.createElement("tr");
if (this.config.colored && !this.config.coloredSymbolOnly) {
@@ -220,7 +223,7 @@ Module.register("calendar", {
symbolWrapper.appendChild(symbol);
}
eventWrapper.appendChild(symbolWrapper);
}else if(this.config.timeFormat === "dateheaders"){
} else if(this.config.timeFormat === "dateheaders"){
var blankCell = document.createElement("td");
blankCell.innerHTML = "&nbsp;&nbsp;&nbsp;";
eventWrapper.appendChild(blankCell);
@@ -257,7 +260,7 @@ Module.register("calendar", {
titleWrapper.colSpan = "2";
titleWrapper.align = "left";
}else{
} else {
var timeClass = this.timeClassForUrl(event.url);
var timeWrapper = document.createElement("td");
@@ -270,7 +273,7 @@ Module.register("calendar", {
}
eventWrapper.appendChild(titleWrapper);
}else{
} else {
var timeWrapper = document.createElement("td");
eventWrapper.appendChild(titleWrapper);
@@ -315,7 +318,7 @@ Module.register("calendar", {
}
if(this.config.showEnd){
timeWrapper.innerHTML += "-" ;
timeWrapper.innerHTML += this.capFirst(moment(event.endDate , "x").format(this.config.fullDayEventDateFormat));
timeWrapper.innerHTML += this.capFirst(moment(event.endDate , "x").format(this.config.fullDayEventDateFormat));
}
} else {
if (event.startDate >= new Date()) {
@@ -325,7 +328,7 @@ Module.register("calendar", {
// 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") {
if(this.config.timeFormat === "absolute" && !this.config.nextDaysRelative) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
} else {
// Otherwise just say 'Today/Tomorrow at such-n-such time'
@@ -379,6 +382,31 @@ Module.register("calendar", {
currentFadeStep = e - startFade;
eventWrapper.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
}
if (this.config.showLocation) {
if (event.location !== false) {
var locationRow = document.createElement("tr");
locationRow.className = "normal xsmall light";
if (this.config.displaySymbol) {
var symbolCell = document.createElement("td");
locationRow.appendChild(symbolCell);
}
var descCell = document.createElement("td");
descCell.className = "location";
descCell.colSpan = "2";
descCell.innerHTML = event.location;
locationRow.appendChild(descCell);
wrapper.appendChild(locationRow);
if (e >= startFade) {
currentFadeStep = e - startFade;
locationRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
}
}
}
}
return wrapper;
@@ -436,10 +464,14 @@ Module.register("calendar", {
var events = [];
var today = moment().startOf("day");
var now = new Date();
var future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
for (var c in this.calendarData) {
var calendar = this.calendarData[c];
for (var e in calendar) {
var event = calendar[e];
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
if(event.endDate < now) {
continue;
}
if(this.config.hidePrivate) {
if(event.class === "PRIVATE") {
// do not add the current event, skip it
@@ -460,24 +492,31 @@ Module.register("calendar", {
/* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
*/
if (this.config.sliceMultiDayEvents) {
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x"); //next midnight
var maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1;
if (this.config.sliceMultiDayEvents && maxCount > 1) {
var splitEvents = [];
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
var count = 1;
var maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1
if (event.endDate > midnight) {
while (event.endDate > midnight) {
var nextEvent = JSON.parse(JSON.stringify(event)); //make a copy without reference to the original event
nextEvent.startDate = midnight;
event.endDate = midnight;
event.title += " (" + count + "/" + maxCount + ")";
events.push(event);
event = nextEvent;
count += 1;
midnight = moment(midnight, "x").add(1, "day").format("x"); //move further one day for next split
}
event.title += " ("+count+"/"+maxCount+")";
}
events.push(event);
while (event.endDate > midnight) {
var thisEvent = JSON.parse(JSON.stringify(event)); // clone object
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < (today + 24 * 60 * 60 * 1000);
thisEvent.endDate = midnight;
thisEvent.title += " (" + count + "/" + maxCount + ")";
splitEvents.push(thisEvent);
event.startDate = midnight;
count += 1;
midnight = moment(midnight, "x").add(1, "day").format("x"); // next day
}
// Last day
event.title += " ("+count+"/"+maxCount+")";
splitEvents.push(event);
for (event of splitEvents) {
if ((event.endDate > now) && (event.endDate <= future)) {
events.push(event);
}
}
} else {
events.push(event);
}
@@ -487,11 +526,9 @@ Module.register("calendar", {
events.sort(function (a, b) {
return a.startDate - b.startDate;
});
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)){
@@ -499,7 +536,6 @@ Module.register("calendar", {
}
}
return false;
},
/* createEventList(url)
@@ -517,7 +553,8 @@ Module.register("calendar", {
symbolClass: calendarConfig.symbolClass,
titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass,
auth: auth
auth: auth,
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
});
},
@@ -678,7 +715,6 @@ Module.register("calendar", {
* Capitalize the first letter of a string
* Return capitalized string
*/
capFirst: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},

View File

@@ -8,7 +8,7 @@
var ical = require("./vendor/ical.js");
var moment = require("moment");
var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) {
var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
var self = this;
var reloadTimer = null;
@@ -28,7 +28,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
var opts = {
headers: {
"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
},
gzip: true
};
@@ -37,9 +37,9 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
if(auth.method === "bearer"){
opts.auth = {
bearer: auth.pass
}
};
}else{
} else {
opts.auth = {
user: auth.user,
pass: auth.pass
@@ -47,7 +47,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
if(auth.method === "digest"){
opts.auth.sendImmediately = false;
}else{
} else {
opts.auth.sendImmediately = true;
}
}
@@ -63,7 +63,8 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
// console.log(data);
newEvents = [];
var limitFunction = function(date, i) {return i < maximumEntries;};
// limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves
var limitFunction = function(date, i) {return true;};
var eventDate = function(event, time) {
return (event[time].length === 8) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
@@ -74,6 +75,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var now = new Date();
var today = moment().startOf("day").toDate();
var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1,"seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
var past = today;
if (includePastEvents) {
past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
}
// FIXME:
// Ugly fix to solve the facebook birthday issue.
@@ -102,7 +108,6 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
}
}
// calculate the duration f the event for use with recurring events.
var duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
@@ -110,12 +115,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
startDate = startDate.startOf("day");
}
var title = "Event";
if (event.summary) {
title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
} else if(event.description) {
title = event.description;
}
var title = getTitleFromEvent(event);
var excluded = false,
dateFilter = null;
@@ -171,29 +171,105 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var geo = event.geo || false;
var description = event.description || false;
if (typeof event.rrule != "undefined" && event.rrule != null && !isFacebookBirthday) {
if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
var rule = event.rrule;
var addedEvents = 0;
// can cause problems with e.g. birthdays before 1900
if(rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900 ||
if(rule.options && 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 recurring events, get the set of start dates that fall within the range
// of dates we"re looking for.
// kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time
var pastLocal = moment(past).subtract(past.getTimezoneOffset(), "minutes").toDate();
var futureLocal = moment(future).subtract(future.getTimezoneOffset(), "minutes").toDate();
var datesLocal = rule.between(pastLocal, futureLocal, true, limitFunction);
var dates = datesLocal.map(function(dateLocal) {
var date = moment(dateLocal).add(dateLocal.getTimezoneOffset(), "minutes").toDate();
return date;
});
// The "dates" array contains the set of dates within our desired date range range that are valid
// for the recurrence rule. *However*, it"s possible for us to have a specific recurrence that
// had its date changed from outside the range to inside the range. For the time being,
// we"ll handle this by adding *all* recurrence entries into the set of dates that we check,
// because the logic below will filter out any recurrences that don"t actually belong within
// our display range.
// Would be great if there was a better way to handle this.
if (event.recurrences != undefined)
{
var pastMoment = moment(past);
var futureMoment = moment(future);
for (var r in event.recurrences)
{
// Only add dates that weren't already in the range we added from the rrule so that
// we don"t double-add those events.
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) != true)
{
dates.push(new Date(r));
}
}
}
// Loop through the set of date entries to see which recurrences should be added to our event list.
for (var d in dates) {
startDate = moment(new Date(dates[d]));
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
var date = dates[d];
// ical.js started returning recurrences and exdates as ISOStrings without time information.
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same
// (see https://github.com/peterbraden/ical.js/pull/84 )
var dateKey = date.toISOString().substring(0,10);
var curEvent = event;
var showRecurrence = true;
if (timeFilterApplies(now, endDate, dateFilter)) {
continue;
// Stop parsing this event's recurrences if we've already found maximumEntries worth of recurrences.
// (The logic below would still filter the extras, but the check is simple since we're already tracking the count)
if (addedEvents >= maximumEntries) {
break;
}
if (endDate.format("x") > now) {
startDate = moment(date);
// For each date that we"re checking, it"s possible that there is a recurrence override for that one day.
if ((curEvent.recurrences != undefined) && (curEvent.recurrences[dateKey] != undefined))
{
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
curEvent = curEvent.recurrences[dateKey];
startDate = moment(curEvent.start);
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
}
// If there"s no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
else if ((curEvent.exdate != undefined) && (curEvent.exdate[dateKey] != undefined))
{
// This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false;
}
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
if (startDate.format("x") == endDate.format("x")) {
endDate = endDate.endOf("day");
}
var recurrenceTitle = getTitleFromEvent(curEvent);
// If this recurrence ends before the start of the date range, or starts after the end of the date range, don"t add
// it to the event list.
if (endDate.isBefore(past) || startDate.isAfter(future)) {
showRecurrence = false;
}
if (timeFilterApplies(now, endDate, dateFilter)) {
showRecurrence = false;
}
if ((showRecurrence === true) && (addedEvents < maximumEntries)) {
addedEvents++;
newEvents.push({
title: title,
title: recurrenceTitle,
startDate: startDate.format("x"),
endDate: endDate.format("x"),
fullDayEvent: isFullDayEvent(event),
@@ -205,19 +281,27 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
});
}
}
// end recurring event parsing
} else {
// console.log("Single event ...");
// Single event.
var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
if (!fullDayEvent && endDate < new Date()) {
//console.log("It's not a fullday event, and it is in the past. So skip: " + title);
continue;
}
if (includePastEvents) {
if (endDate < past) {
//console.log("Past event is too far in the past. So skip: " + title);
continue;
}
} else {
if (!fullDayEvent && endDate < new Date()) {
//console.log("It's not a fullday event, and it is in the past. So skip: " + title);
continue;
}
if (fullDayEvent && endDate <= today) {
//console.log("It's a fullday event, and it is before today. So skip: " + title);
continue;
if (fullDayEvent && endDate <= today) {
//console.log("It's a fullday event, and it is before today. So skip: " + title);
continue;
}
}
if (startDate > future) {
@@ -229,6 +313,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
continue;
}
// adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
if (fullDayEvent && startDate <= today) {
startDate = moment(today);
}
// Every thing is good. Add it to the list.
newEvents.push({
@@ -278,7 +367,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
* return bool - The event is a fullday event.
*/
var isFullDayEvent = function(event) {
if (event.start.length === 8) {
if (event.start.length === 8 || event.start.dateOnly) {
return true;
}
@@ -315,6 +404,24 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
return false;
};
/* getTitleFromEvent(event)
* Gets the title from the event.
*
* argument event object - The event object to check.
*
* return string - The title of the event, or "Event" if no title is found.
*/
var getTitleFromEvent = function (event) {
var title = "Event";
if (event.summary) {
title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
} else if (event.description) {
title = event.description;
}
return title;
};
var testTitleByFilter = function (title, filter, useRegex, regexFlags) {
if (useRegex) {
// Assume if leading slash, there is also trailing slash
@@ -329,7 +436,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
} else {
return title.includes(filter);
}
}
};
/* public methods */
@@ -383,8 +490,6 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
this.events = function() {
return events;
};
};
module.exports = CalendarFetcher;

View File

@@ -8,13 +8,14 @@
var CalendarFetcher = require("./calendarfetcher.js");
var url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
var url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
// var url = "https://www.googleapis.com/calendar/v3/calendars/primary/events/"; // URL for Bearer auth (must be configured in Google OAuth2 first)
var fetchInterval = 60 * 60 * 1000;
var maximumEntries = 10;
var maximumNumberOfDays = 365;
var user = "magicmirror";
var pass = "MyStrongPass";
var broadcastPastEvents = false;
var auth = {
user: user,
@@ -23,7 +24,7 @@ var auth = {
console.log("Create fetcher ...");
fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries, maximumNumberOfDays, auth);
fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maximumNumberOfDays, auth);
fetcher.onReceive(function(fetcher) {
console.log(fetcher.events());
@@ -37,4 +38,4 @@ fetcher.onError(function(fetcher, error) {
fetcher.startFetch();
console.log("Create fetcher done! ");
console.log("Create fetcher done! ");

View File

@@ -24,7 +24,7 @@ module.exports = NodeHelper.create({
socketNotificationReceived: function(notification, payload) {
if (notification === "ADD_CALENDAR") {
//console.log('ADD_CALENDAR: ');
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth);
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents);
}
},
@@ -36,7 +36,7 @@ module.exports = NodeHelper.create({
* attribute reloadInterval number - Reload interval in milliseconds.
*/
createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) {
createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents) {
var self = this;
if (!validUrl.isUri(url)) {
@@ -47,7 +47,7 @@ module.exports = NodeHelper.create({
var fetcher;
if (typeof self.fetchers[url] === "undefined") {
console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents);
fetcher.onReceive(function(fetcher) {
//console.log('Broadcast events.');
@@ -60,6 +60,7 @@ module.exports = NodeHelper.create({
});
fetcher.onError(function(fetcher, error) {
console.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
self.sendSocketNotification("FETCH_ERROR", {
url: fetcher.url(),
error: error

View File

@@ -1,6 +1,4 @@
language: node_js
node_js:
- "0.10"
- "0.12"
- "4.2"
- "8.9"
install: npm install

View File

@@ -1,13 +1,16 @@
var ical = require('ical')
, months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
'use strict';
const ical = require('ical');
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function(err, data){
for (var k in data){
if (data.hasOwnProperty(k)){
var ev = data[k]
console.log("Conference", ev.summary, 'is in', ev.location, 'on the', ev.start.getDate(), 'of', months[ev.start.getMonth()] );
}
}
})
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function (err, data) {
for (let k in data) {
if (data.hasOwnProperty(k)) {
var ev = data[k];
if (data[k].type == 'VEVENT') {
console.log(`${ev.summary} is in ${ev.location} on the ${ev.start.getDate()} of ${months[ev.start.getMonth()]} at ${ev.start.toLocaleTimeString('en-GB')}`);
}
}
}
});

View File

@@ -0,0 +1,118 @@
var ical = require('./node-ical')
var moment = require('moment')
var data = ical.parseFile('./examples/example_rrule.ics');
// Complicated example demonstrating how to handle recurrence rules and exceptions.
for (var k in data) {
// When dealing with calendar recurrences, you need a range of dates to query against,
// because otherwise you can get an infinite number of calendar events.
var rangeStart = moment("2017-01-01");
var rangeEnd = moment("2017-12-31");
var event = data[k]
if (event.type === 'VEVENT') {
var title = event.summary;
var startDate = moment(event.start);
var endDate = moment(event.end);
// Calculate the duration of the event for use with recurring events.
var duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
// Simple case - no recurrences, just print out the calendar event.
if (typeof event.rrule === 'undefined')
{
console.log('title:' + title);
console.log('startDate:' + startDate.format('MMMM Do YYYY, h:mm:ss a'));
console.log('endDate:' + endDate.format('MMMM Do YYYY, h:mm:ss a'));
console.log('duration:' + moment.duration(duration).humanize());
console.log();
}
// Complicated case - if an RRULE exists, handle multiple recurrences of the event.
else if (typeof event.rrule !== 'undefined')
{
// For recurring events, get the set of event start dates that fall within the range
// of dates we're looking for.
var dates = event.rrule.between(
rangeStart.toDate(),
rangeEnd.toDate(),
true,
function(date, i) {return true;}
)
// The "dates" array contains the set of dates within our desired date range range that are valid
// for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
// had its date changed from outside the range to inside the range. One way to handle this is
// to add *all* recurrence override entries into the set of dates that we check, and then later
// filter out any recurrences that don't actually belong within our range.
if (event.recurrences != undefined)
{
for (var r in event.recurrences)
{
// Only add dates that weren't already in the range we added from the rrule so that
// we don't double-add those events.
if (moment(new Date(r)).isBetween(rangeStart, rangeEnd) != true)
{
dates.push(new Date(r));
}
}
}
// Loop through the set of date entries to see which recurrences should be printed.
for(var i in dates) {
var date = dates[i];
var curEvent = event;
var showRecurrence = true;
var curDuration = duration;
startDate = moment(date);
// Use just the date of the recurrence to look up overrides and exceptions (i.e. chop off time information)
var dateLookupKey = date.toISOString().substring(0, 10);
// For each date that we're checking, it's possible that there is a recurrence override for that one day.
if ((curEvent.recurrences != undefined) && (curEvent.recurrences[dateLookupKey] != undefined))
{
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
curEvent = curEvent.recurrences[dateLookupKey];
startDate = moment(curEvent.start);
curDuration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
}
// If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
else if ((curEvent.exdate != undefined) && (curEvent.exdate[dateLookupKey] != undefined))
{
// This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false;
}
// Set the the title and the end date from either the regular event or the recurrence override.
var recurrenceTitle = curEvent.summary;
endDate = moment(parseInt(startDate.format("x")) + curDuration, 'x');
// If this recurrence ends before the start of the date range, or starts after the end of the date range,
// don't process it.
if (endDate.isBefore(rangeStart) || startDate.isAfter(rangeEnd)) {
showRecurrence = false;
}
if (showRecurrence === true) {
console.log('title:' + recurrenceTitle);
console.log('startDate:' + startDate.format('MMMM Do YYYY, h:mm:ss a'));
console.log('endDate:' + endDate.format('MMMM Do YYYY, h:mm:ss a'));
console.log('duration:' + moment.duration(curDuration).humanize());
console.log();
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:ical
X-WR-TIMEZONE:US/Central
X-WR-CALDESC:
BEGIN:VEVENT
UID:98765432-ABCD-DCBB-999A-987765432123
DTSTART;TZID=US/Central:20170601T090000
DTEND;TZID=US/Central:20170601T170000
DTSTAMP:20170727T044436Z
EXDATE;TZID=US/Central:20170706T090000,20170713T090000,20170720T090000,20
170803T090000
LAST-MODIFIED:20170727T044435Z
RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20170814T045959Z;BYDAY=TH
SEQUENCE:0
SUMMARY:Recurring weekly meeting from June 1 - Aug 14 (except July 6, July 13, July 20, Aug 3)
END:VEVENT
BEGIN:VEVENT
UID:98765432-ABCD-DCBB-999A-987765432123
RECURRENCE-ID;TZID=US/Central:20170629T090000
DTSTART;TZID=US/Central:20170703T090000
DTEND;TZID=US/Central:20170703T120000
DTSTAMP:20170727T044436Z
LAST-MODIFIED:20170216T143445Z
SEQUENCE:0
SUMMARY:Last meeting in June moved to Monday July 3 and shortened to half day
END:VEVENT
BEGIN:VEVENT
UID:12354454-ABCD-DCBB-999A-2349872354897
DTSTART;TZID=US/Central:20171201T130000
DTEND;TZID=US/Central:20171201T150000
DTSTAMP:20170727T044436Z
LAST-MODIFIED:20170727T044435Z
SEQUENCE:0
SUMMARY:Single event on Dec 1
END:VEVENT
END:VCALENDAR

View File

@@ -33,9 +33,9 @@
for (var i = 0; i<p.length; i++){
if (p[i].indexOf('=') > -1){
var segs = p[i].split('=');
out[segs[0]] = parseValue(segs.slice(1).join('='));
}
}
return out || sp
@@ -44,7 +44,7 @@
var parseValue = function(val){
if ('TRUE' === val)
return true;
if ('FALSE' === val)
return false;
@@ -55,75 +55,52 @@
return val;
}
var storeParam = function(name){
return function(val, params, curr){
var data;
if (params && params.length && !(params.length==1 && params[0]==='CHARSET=utf-8')){
data = {params:parseParams(params), val:text(val)}
}
else
data = text(val)
var storeValParam = function (name) {
return function (val, curr) {
var current = curr[name];
if (Array.isArray(current)) {
current.push(val);
return curr;
}
var current = curr[name];
if (Array.isArray(current)){
current.push(data);
return curr;
}
if (current != null) {
curr[name] = [current, val];
return curr;
}
if (current != null){
curr[name] = [current, data];
return curr;
curr[name] = val;
return curr
}
curr[name] = data;
return curr
}
}
var addTZ = function(dt, params){
var storeParam = function (name) {
return function (val, params, curr) {
var data;
if (params && params.length && !(params.length == 1 && params[0] === 'CHARSET=utf-8')) {
data = { params: parseParams(params), val: text(val) }
}
else
data = text(val)
return storeValParam(name)(data, curr);
}
}
var addTZ = function (dt, params) {
var p = parseParams(params);
if (params && p && dt){
if (params && p){
dt.tz = p.TZID
}
return dt
}
var parseTimestamp = function(val){
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
return new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
return new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
}
return undefined;
}
var dateParam = function(name){
return function(val, params, curr){
return function (val, params, curr) {
var newDate = text(val);
// Store as string - worst case scenario
storeParam(name)(val, undefined, curr)
if (params && params[0] === "VALUE=DATE") {
// Just Date
@@ -131,47 +108,54 @@
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
if (comps !== null) {
// No TZ info - assume same timezone as this computer
curr[name] = new Date(
newDate = new Date(
comps[1],
parseInt(comps[2], 10)-1,
comps[3]
);
curr[name] = addTZ(curr[name], params);
return curr;
newDate = addTZ(newDate, params);
newDate.dateOnly = true;
// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
}
}
curr[name] = []
val.split(',').forEach(function(val){
var newDate = parseTimestamp(val);
curr[name].push(addTZ(newDate, params));
});
if (curr[name].length === 0){
delete curr[name];
} else if (curr[name].length === 1){
curr[name] = curr[name][0];
}
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
newDate = new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
newDate = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
return curr;
newDate = addTZ(newDate, params);
}
// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
}
}
var exdateParam = function(name){
return function(val, params, curr){
var date = dateParam(name)(val, params, curr);
if (date.exdates === undefined) {
date.exdates = [];
}
if (Array.isArray(date.exdate)){
date.exdates = date.exdates.concat(date.exdate);
} else {
date.exdates.push(date.exdate);
}
return date;
}
}
var geoParam = function(name){
return function(val, params, curr){
@@ -195,7 +179,52 @@
}
}
var addFBType = function(fb, params){
// EXDATE is an entry that represents exceptions to a recurrence rule (ex: "repeat every day except on 7/4").
// The EXDATE entry itself can also contain a comma-separated list, so we make sure to parse each date out separately.
// There can also be more than one EXDATE entries in a calendar record.
// Since there can be multiple dates, we create an array of them. The index into the array is the ISO string of the date itself, for ease of use.
// i.e. You can check if ((curr.exdate != undefined) && (curr.exdate[date iso string] != undefined)) to see if a date is an exception.
// NOTE: This specifically uses date only, and not time. This is to avoid a few problems:
// 1. The ISO string with time wouldn't work for "floating dates" (dates without timezones).
// ex: "20171225T060000" - this is supposed to mean 6 AM in whatever timezone you're currently in
// 2. Daylight savings time potentially affects the time you would need to look up
// 3. Some EXDATE entries in the wild seem to have times different from the recurrence rule, but are still excluded by calendar programs. Not sure how or why.
// These would fail any sort of sane time lookup, because the time literally doesn't match the event. So we'll ignore time and just use date.
// ex: DTSTART:20170814T140000Z
// RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=2;BYDAY=MO,TU
// EXDATE:20171219T060000
// Even though "T060000" doesn't match or overlap "T1400000Z", it's still supposed to be excluded? Odd. :(
// TODO: See if this causes any problems with events that recur multiple times a day.
var exdateParam = function (name) {
return function (val, params, curr) {
var separatorPattern = /\s*,\s*/g;
curr[name] = curr[name] || [];
var dates = val ? val.split(separatorPattern) : [];
dates.forEach(function (entry) {
var exdate = new Array();
dateParam(name)(entry, params, exdate);
if (exdate[name])
{
if (typeof exdate[name].toISOString === 'function') {
curr[name][exdate[name].toISOString().substring(0, 10)] = exdate[name];
} else {
console.error("No toISOString function in exdate[name]", exdate[name]);
}
}
}
)
return curr;
}
}
// RECURRENCE-ID is the ID of a specific recurrence within a recurrence rule.
// TODO: It's also possible for it to have a range, like "THISANDPRIOR", "THISANDFUTURE". This isn't currently handled.
var recurrenceParam = function (name) {
return dateParam(name);
}
var addFBType = function (fb, params) {
var p = parseParams(params);
if (params && p){
@@ -239,7 +268,7 @@
//scan all high level object in curr and drop all strings
var key,
obj;
for (key in curr) {
if(curr.hasOwnProperty(key)) {
obj = curr[key];
@@ -248,14 +277,93 @@
}
}
}
return curr
}
var par = stack.pop()
if (curr.uid)
par[curr.uid] = curr
{
// If this is the first time we run into this UID, just save it.
if (par[curr.uid] === undefined)
{
par[curr.uid] = curr;
}
else
{
// If we have multiple ical entries with the same UID, it's either going to be a
// modification to a recurrence (RECURRENCE-ID), and/or a significant modification
// to the entry (SEQUENCE).
// TODO: Look into proper sequence logic.
if (curr.recurrenceid === undefined)
{
// If we have the same UID as an existing record, and it *isn't* a specific recurrence ID,
// not quite sure what the correct behaviour should be. For now, just take the new information
// and merge it with the old record by overwriting only the fields that appear in the new record.
var key;
for (key in curr) {
par[curr.uid][key] = curr[key];
}
}
}
// If we have recurrence-id entries, list them as an array of recurrences keyed off of recurrence-id.
// To use - as you're running through the dates of an rrule, you can try looking it up in the recurrences
// array. If it exists, then use the data from the calendar object in the recurrence instead of the parent
// for that day.
// NOTE: Sometimes the RECURRENCE-ID record will show up *before* the record with the RRULE entry. In that
// case, what happens is that the RECURRENCE-ID record ends up becoming both the parent record and an entry
// in the recurrences array, and then when we process the RRULE entry later it overwrites the appropriate
// fields in the parent record.
if (curr.recurrenceid != null)
{
// TODO: Is there ever a case where we have to worry about overwriting an existing entry here?
// Create a copy of the current object to save in our recurrences array. (We *could* just do par = curr,
// except for the case that we get the RECURRENCE-ID record before the RRULE record. In that case, we
// would end up with a shared reference that would cause us to overwrite *both* records at the point
// that we try and fix up the parent record.)
var recurrenceObj = new Object();
var key;
for (key in curr) {
recurrenceObj[key] = curr[key];
}
if (recurrenceObj.recurrences != undefined) {
delete recurrenceObj.recurrences;
}
// If we don't have an array to store recurrences in yet, create it.
if (par[curr.uid].recurrences === undefined) {
par[curr.uid].recurrences = new Array();
}
// Save off our cloned recurrence object into the array, keyed by date but not time.
// We key by date only to avoid timezone and "floating time" problems (where the time isn't associated with a timezone).
// TODO: See if this causes a problem with events that have multiple recurrences per day.
if (typeof curr.recurrenceid.toISOString === 'function') {
par[curr.uid].recurrences[curr.recurrenceid.toISOString().substring(0,10)] = recurrenceObj;
} else {
console.error("No toISOString function in curr.recurrenceid", curr.recurrenceid);
}
}
// One more specific fix - in the case that an RRULE entry shows up after a RECURRENCE-ID entry,
// let's make sure to clear the recurrenceid off the parent field.
if ((par[curr.uid].rrule != undefined) && (par[curr.uid].recurrenceid != undefined))
{
delete par[curr.uid].recurrenceid;
}
}
else
par[Math.random()*100000] = curr // Randomly assign ID : TODO - use true GUID
@@ -277,6 +385,11 @@
, 'COMPLETED': dateParam('completed')
, 'CATEGORIES': categoriesParam('categories')
, 'FREEBUSY': freebusyParam('freebusy')
, 'DTSTAMP': dateParam('dtstamp')
, 'CREATED': dateParam('created')
, 'LAST-MODIFIED': dateParam('lastmodified')
, 'RECURRENCE-ID': recurrenceParam('recurrenceid')
},
@@ -292,7 +405,7 @@
name = name.substring(2);
return (storeParam(name))(val, params, ctx, stack, line);
}
return storeParam(name.toLowerCase())(val, params, ctx);
},

View File

@@ -6,9 +6,16 @@ exports.fromURL = function(url, opts, cb){
if (!cb)
return;
request(url, opts, function(err, r, data){
if (err)
return cb(err, null);
cb(undefined, ical.parseICS(data));
if (err)
{
return cb(err, null);
}
else if (r.statusCode != 200)
{
return cb(r.statusCode + ": " + r.statusMessage, null);
}
cb(undefined, ical.parseICS(data));
})
}
@@ -17,40 +24,54 @@ exports.parseFile = function(filename){
}
var rrule = require('rrule-alt').RRule
var rrulestr = rrule.rrulestr
var rrule = require('rrule').RRule
function getLocaleISOString(date) {
var year = date.getFullYear().toString(10).padStart(4,'0');
var month = (date.getMonth() + 1).toString(10).padStart(2,'0');
var day = date.getDate().toString(10).padStart(2,'0');
var hour = date.getHours().toString(10).padStart(2,'0');
var minute = date.getMinutes().toString(10).padStart(2,'0');
var second = date.getSeconds().toString(10).padStart(2,'0');
return `${year}${month}${day}T${hour}${minute}${second}Z`;
}
ical.objectHandlers['RRULE'] = function(val, params, curr, stack, line){
curr.rrule = line;
return curr
}
var originalEnd = ical.objectHandlers['END'];
ical.objectHandlers['END'] = function(val, params, curr, stack){
if (curr.rrule) {
var rule = curr.rrule;
if (rule.indexOf('DTSTART') === -1) {
ical.objectHandlers['END'] = function (val, params, curr, stack) {
// Recurrence rules are only valid for VEVENT, VTODO, and VJOURNAL.
// More specifically, we need to filter the VCALENDAR type because we might end up with a defined rrule
// due to the subtypes.
if ((val === "VEVENT") || (val === "VTODO") || (val === "VJOURNAL")) {
if (curr.rrule) {
var rule = curr.rrule.replace('RRULE:', '');
if (rule.indexOf('DTSTART') === -1) {
if (curr.start.length === 8) {
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start);
if (comps) {
curr.start = new Date (comps[1], comps[2] - 1, comps[3]);
}
}
if (curr.start.length === 8) {
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start);
if (comps) {
curr.start = new Date(comps[1], comps[2] - 1, comps[3]);
}
}
rule += ' DTSTART:' + curr.start.toISOString().replace(/[-:]/g, '');
rule = rule.replace(/\.[0-9]{3}/, '');
}
for (var i in curr.exdates) {
rule += ' EXDATE:' + curr.exdates[i].toISOString().replace(/[-:]/g, '');
rule = rule.replace(/\.[0-9]{3}/, '');
}
try {
curr.rrule = rrulestr(rule);
}
catch(err) {
console.log("Unrecognised element in calendar feed, ignoring: " + rule);
curr.rrule = null;
}
}
if (typeof curr.start.toISOString === 'function') {
try {
// kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time
rule += ';DTSTART=' + getLocaleISOString(curr.start);
} catch (error) {
console.error("ERROR when trying to convert to ISOString", error);
}
} else {
console.error("No toISOString function in curr.start", curr.start);
}
}
curr.rrule = rrule.fromString(rule);
}
}
return originalEnd.call(this, val, params, curr, stack);
}

View File

@@ -10,17 +10,18 @@
],
"homepage": "https://github.com/peterbraden/ical.js",
"author": "Peter Braden <peterbraden@peterbraden.co.uk> (peterbraden.co.uk)",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git://github.com/peterbraden/ical.js.git"
},
"dependencies": {
"request": "2.68.0",
"rrule": "2.0.0"
"request": "^2.88.0",
"rrule": "2.4.1"
},
"devDependencies": {
"vows": "0.7.0",
"underscore": "1.3.0"
"vows": "0.8.2",
"underscore": "1.9.1"
},
"scripts": {
"test": "./node_modules/vows/bin/vows ./test/test.js"

View File

@@ -7,6 +7,7 @@ A tolerant, minimal icalendar parser for javascript/node
(http://tools.ietf.org/html/rfc5545)
## Install - Node.js ##
ical.js is availble on npm:
@@ -33,19 +34,29 @@ Use the request library to fetch the specified URL (```opts``` gets passed on to
## Example 1 - Print list of upcoming node conferences (see example.js)
```javascript
var ical = require('ical')
, months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
'use strict';
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function(err, data) {
for (var k in data){
if (data.hasOwnProperty(k)) {
var ev = data[k]
console.log("Conference",
ev.summary,
'is in',
ev.location,
'on the', ev.start.getDate(), 'of', months[ev.start.getMonth()]);
}
}
});
const ical = require('ical');
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function (err, data) {
for (let k in data) {
if (data.hasOwnProperty(k)) {
var ev = data[k];
if (data[k].type == 'VEVENT') {
console.log(`${ev.summary} is in ${ev.location} on the ${ev.start.getDate()} of ${months[ev.start.getMonth()]} at ${ev.start.toLocaleTimeString('en-GB')}`);
}
}
}
});
```
## Recurrences and Exceptions ##
Calendar events with recurrence rules can be significantly more complicated to handle correctly. There are three parts to handling them:
1. rrule - the recurrence rule specifying the pattern of recurring dates and times for the event.
2. recurrences - an optional array of event data that can override specific occurrences of the event.
3. exdate - an optional array of dates that should be excluded from the recurrence pattern.
See example_rrule.js for an example of handling recurring calendar events.

View File

@@ -43,6 +43,12 @@ vows.describe('node-ical').addBatch({
, 'has a summary (invalid colon handling tolerance)' : function(topic){
assert.equal(topic.summary, '[Async]: Everything Express')
}
, 'has a date only start datetime' : function(topic){
assert.equal(topic.start.dateOnly, true)
}
, 'has a date only end datetime' : function(topic){
assert.equal(topic.end.dateOnly, true)
}
}
, 'event d4c8' :{
topic : function(events){
@@ -108,7 +114,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(), 0);
assert.equal(topic.end.getUTCHours(), 00);
assert.equal(topic.end.getUTCMinutes(), 30);
}
}
@@ -146,7 +152,7 @@ vows.describe('node-ical').addBatch({
}
, 'has a start datetime' : function(topic) {
assert.equal(topic.start.getFullYear(), 2011);
assert.equal(topic.start.getMonth(), 9);
assert.equal(topic.start.getMonth(), 09);
assert.equal(topic.start.getDate(), 11);
}
@@ -192,12 +198,12 @@ 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, 9, 19, 0,0).toISOString())
assert.equal(topic.start.toISOString(), new Date(2011, 10, 09, 19, 0,0).toISOString())
}
}
}
, 'with test6.ics (testing assembly.org)' : {
, 'with test6.ics (testing assembly.org)': {
topic: function () {
return ical.parseFile('./test/test6.ics')
}
@@ -208,13 +214,13 @@ vows.describe('node-ical').addBatch({
})[0];
}
, 'has a start' : function(topic){
assert.equal(topic.start.toISOString(), new Date(2011, 7, 4, 12, 0,0).toISOString())
assert.equal(topic.start.toISOString(), new Date(2011, 07, 04, 12, 0,0).toISOString())
}
}
, 'event with rrule' :{
topic: function(events){
return _.select(_.values(events), function(x){
return x.summary == "foobarTV broadcast starts"
return x.summary === "foobarTV broadcast starts"
})[0];
}
, "Has an RRULE": function(topic){
@@ -249,7 +255,7 @@ vows.describe('node-ical').addBatch({
},
'task completed': function(task){
assert.equal(task.completion, 100);
assert.equal(task.completed.toISOString(), new Date(2013, 6, 16, 10, 57, 45).toISOString());
assert.equal(task.completed.toISOString(), new Date(2013, 06, 16, 10, 57, 45).toISOString());
}
}
}
@@ -272,7 +278,7 @@ vows.describe('node-ical').addBatch({
},
'grabbing custom properties': {
topic: function(topic) {
}
}
},
@@ -367,14 +373,115 @@ 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(), 0);
assert.equal(topic.end.getUTCMinutes(), 00);
}
}
},
}
'url request errors' : {
, 'with test12.ics (testing recurrences and exdates)': {
topic: function () {
return ical.parseFile('./test/test12.ics')
}
, 'event with rrule': {
topic: function (events) {
return _.select(_.values(events), function (x) {
return x.uid === '0000001';
})[0];
}
, "Has an RRULE": function (topic) {
assert.notEqual(topic.rrule, undefined);
}
, "Has summary Treasure Hunting": function (topic) {
assert.equal(topic.summary, 'Treasure Hunting');
}
, "Has two EXDATES": function (topic) {
assert.notEqual(topic.exdate, undefined);
assert.notEqual(topic.exdate[new Date(2015, 06, 08, 12, 0, 0).toISOString().substring(0, 10)], undefined);
assert.notEqual(topic.exdate[new Date(2015, 06, 10, 12, 0, 0).toISOString().substring(0, 10)], undefined);
}
, "Has a RECURRENCE-ID override": function (topic) {
assert.notEqual(topic.recurrences, undefined);
assert.notEqual(topic.recurrences[new Date(2015, 06, 07, 12, 0, 0).toISOString().substring(0, 10)], undefined);
assert.equal(topic.recurrences[new Date(2015, 06, 07, 12, 0, 0).toISOString().substring(0, 10)].summary, 'More Treasure Hunting');
}
}
}
, 'with test13.ics (testing recurrence-id before rrule)': {
topic: function () {
return ical.parseFile('./test/test13.ics')
}
, 'event with rrule': {
topic: function (events) {
return _.select(_.values(events), function (x) {
return x.uid === '6m2q7kb2l02798oagemrcgm6pk@google.com';
})[0];
}
, "Has an RRULE": function (topic) {
assert.notEqual(topic.rrule, undefined);
}
, "Has summary 'repeated'": function (topic) {
assert.equal(topic.summary, 'repeated');
}
, "Has a RECURRENCE-ID override": function (topic) {
assert.notEqual(topic.recurrences, undefined);
assert.notEqual(topic.recurrences[new Date(2016, 7, 26, 14, 0, 0).toISOString().substring(0, 10)], undefined);
assert.equal(topic.recurrences[new Date(2016, 7, 26, 14, 0, 0).toISOString().substring(0, 10)].summary, 'bla bla');
}
}
}
, 'with test14.ics (testing comma-separated exdates)': {
topic: function () {
return ical.parseFile('./test/test14.ics')
}
, 'event with comma-separated exdate': {
topic: function (events) {
return _.select(_.values(events), function (x) {
return x.uid === '98765432-ABCD-DCBB-999A-987765432123';
})[0];
}
, "Has summary 'Example of comma-separated exdates'": function (topic) {
assert.equal(topic.summary, 'Example of comma-separated exdates');
}
, "Has four comma-separated EXDATES": function (topic) {
assert.notEqual(topic.exdate, undefined);
// Verify the four comma-separated EXDATES are there
assert.notEqual(topic.exdate[new Date(2017, 6, 6, 12, 0, 0).toISOString().substring(0, 10)], undefined);
assert.notEqual(topic.exdate[new Date(2017, 6, 17, 12, 0, 0).toISOString().substring(0, 10)], undefined);
assert.notEqual(topic.exdate[new Date(2017, 6, 20, 12, 0, 0).toISOString().substring(0, 10)], undefined);
assert.notEqual(topic.exdate[new Date(2017, 7, 3, 12, 0, 0).toISOString().substring(0, 10)], undefined);
// Verify an arbitrary date isn't there
assert.equal(topic.exdate[new Date(2017, 4, 5, 12, 0, 0).toISOString().substring(0, 10)], undefined);
}
}
}
, 'with test14.ics (testing exdates with bad times)': {
topic: function () {
return ical.parseFile('./test/test14.ics')
}
, 'event with exdates with bad times': {
topic: function (events) {
return _.select(_.values(events), function (x) {
return x.uid === '1234567-ABCD-ABCD-ABCD-123456789012';
})[0];
}
, "Has summary 'Example of exdate with bad times'": function (topic) {
assert.equal(topic.summary, 'Example of exdate with bad times');
}
, "Has two EXDATES even though they have bad times": function (topic) {
assert.notEqual(topic.exdate, undefined);
// Verify the two EXDATES are there, even though they have bad times
assert.notEqual(topic.exdate[new Date(2017, 11, 18, 12, 0, 0).toISOString().substring(0, 10)], undefined);
assert.notEqual(topic.exdate[new Date(2017, 11, 19, 12, 0, 0).toISOString().substring(0, 10)], undefined);
}
}
}
, 'url request errors': {
topic : function () {
ical.fromURL('http://not.exist/', {}, this.callback);
ical.fromURL('http://255.255.255.255/', {}, this.callback);
}
, 'are passed back to the callback' : function (err, result) {
assert.instanceOf(err, Error);

View File

@@ -0,0 +1,19 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:0000001
SUMMARY:Treasure Hunting
DTSTART;TZID=America/Los_Angeles:20150706T120000
DTEND;TZID=America/Los_Angeles:20150706T130000
RRULE:FREQ=DAILY;COUNT=10
EXDATE;TZID=America/Los_Angeles:20150708T120000
EXDATE;TZID=America/Los_Angeles:20150710T120000
END:VEVENT
BEGIN:VEVENT
UID:0000001
SUMMARY:More Treasure Hunting
LOCATION:The other island
DTSTART;TZID=America/Los_Angeles:20150709T150000
DTEND;TZID=America/Los_Angeles:20150707T160000
RECURRENCE-ID;TZID=America/Los_Angeles:20150707T120000
END:VEVENT
END:VCALENDAR

View File

@@ -0,0 +1,57 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:ical
X-WR-TIMEZONE:Europe/Kiev
X-WR-CALDESC:
BEGIN:VTIMEZONE
TZID:Europe/Kiev
X-LIC-LOCATION:Europe/Kiev
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
TZNAME:EEST
DTSTART:19700329T030000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
TZNAME:EET
DTSTART:19701025T040000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=Europe/Kiev:20160826T140000
DTEND;TZID=Europe/Kiev:20160826T150000
DTSTAMP:20160825T061505Z
UID:6m2q7kb2l02798oagemrcgm6pk@google.com
RECURRENCE-ID;TZID=Europe/Kiev:20160826T140000
CREATED:20160823T125221Z
DESCRIPTION:
LAST-MODIFIED:20160823T130320Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:bla bla
TRANSP:OPAQUE
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=Europe/Kiev:20160825T140000
DTEND;TZID=Europe/Kiev:20160825T150000
RRULE:FREQ=DAILY;UNTIL=20160828T110000Z
DTSTAMP:20160825T061505Z
UID:6m2q7kb2l02798oagemrcgm6pk@google.com
CREATED:20160823T125221Z
DESCRIPTION:
LAST-MODIFIED:20160823T125221Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:repeated
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR

View File

@@ -0,0 +1,33 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:ical
X-WR-TIMEZONE:Europe/Kiev
X-WR-CALDESC:
BEGIN:VEVENT
UID:98765432-ABCD-DCBB-999A-987765432123
DTSTART;TZID=US/Central:20170216T090000
DTEND;TZID=US/Central:20170216T190000
DTSTAMP:20170727T044436Z
EXDATE;TZID=US/Central:20170706T090000,20170717T090000,20170720T090000,20
170803T090000
LAST-MODIFIED:20170727T044435Z
RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20170814T045959Z;INTERVAL=2;BYDAY=MO,TH
SEQUENCE:0
SUMMARY:Example of comma-separated exdates
END:VEVENT
BEGIN:VEVENT
UID:1234567-ABCD-ABCD-ABCD-123456789012
DTSTART:20170814T140000Z
DTEND:20170815T000000Z
DTSTAMP:20171204T134925Z
EXDATE:20171219T060000
EXDATE:20171218T060000
LAST-MODIFIED:20171024T140004Z
RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=2;BYDAY=MO,TU
SEQUENCE:0
SUMMARY:Example of exdate with bad times
END:VEVENT
END:VCALENDAR

View File

@@ -2,45 +2,4 @@
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:
````javascript
modules: [
{
module: "clock",
position: "top_left", // This can be any of the regions.
config: {
// The config property is optional.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
| Option | Description
| ----------------- | -----------
| `timeFormat` | Use 12 or 24 hour format. <br><br> **Possible values:** `12` or `24` <br> **Default value:** uses value of _config.timeFormat_
| `displaySeconds` | Display seconds. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `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`
| `clockBold` | Remove the colon and bold the minutes to make a more modern look. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showDate` | Turn off or on the Date section. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showWeek` | Turn off or on the Week section. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `dateFormat` | Configure the date format as you like. <br><br> **Possible values:** [Docs](http://momentjs.com/docs/#/displaying/format/) <br> **Default value:** `"dddd, LL"`
| `displayType` | Display a digital clock, analog clock, or both together. <br><br> **Possible values:** `digital`, `analog`, or `both` <br> **Default value:** `digital`
| `analogSize` | **Specific to the analog clock.** Defines how large the analog display is. <br><br> **Possible values:** A positive number of pixels` <br> **Default value:** `200px`
| `analogFace` | **Specific to the analog clock.** Specifies which clock face to use. <br><br> **Possible values:** `simple` for a simple border, `none` for no face or border, or `face-###` (where ### is currently a value between 001 and 012, inclusive) <br> **Default value:** `simple`
| `secondsColor` | **Specific to the analog clock.** Specifies what color to make the 'seconds' hand. <br><br> **Possible values:** `any HTML RGB Color` <br> **Default value:** `#888888`
| `analogPlacement` | **Specific to the analog clock. _(requires displayType set to `'both'`)_** Specifies where the analog clock is in relation to the digital clock <br><br> **Possible values:** `top`, `right`, `bottom`, or `left` <br> **Default value:** `bottom`
| `analogShowDate` | **Specific to the analog clock.** If the clock is used as a separate module and set to analog only, this configures whether a date is also displayed with the clock. <br><br> **Possible values:** `false`, `top`, or `bottom` <br> **Default value:** `top`
| `timezone` | Specific a timezone to show clock. <br><br> **Possible examples values:** `America/New_York`, `America/Santiago`, `Etc/GMT+10` <br> **Default value:** `none`. See more informations about configuration value [here](https://momentjs.com/timezone/docs/#/data-formats/packed-format/)
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/clock.html).

View File

@@ -26,10 +26,15 @@ Module.register("clock",{
analogShowDate: "top", // options: false, 'top', or 'bottom'
secondsColor: "#888888",
timezone: null,
showSunTimes: false,
showMoonTimes: false,
lat: 47.630539,
lon: -122.344147,
},
// Define required scripts.
getScripts: function() {
return ["moment.js", "moment-timezone.js"];
return ["moment.js", "moment-timezone.js", "suncalc.js"];
},
// Define styles.
getStyles: function() {
@@ -41,9 +46,40 @@ Module.register("clock",{
// Schedule update interval.
var self = this;
setInterval(function() {
self.second = moment().second();
self.minute = moment().minute();
//Calculate how many ms should pass until next update depending on if seconds is displayed or not
var delayCalculator = function(reducedSeconds) {
if (self.config.displaySeconds) {
return 1000 - moment().milliseconds();
} else {
return ((60 - reducedSeconds) * 1000) - moment().milliseconds();
}
};
//A recursive timeout function instead of interval to avoid drifting
var notificationTimer = function() {
self.updateDom();
}, 1000);
//If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
if (self.config.displaySeconds) {
self.second = (self.second + 1) % 60;
if (self.second !== 0) {
self.sendNotification("CLOCK_SECOND", self.second);
setTimeout(notificationTimer, delayCalculator(0));
return;
}
}
//If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
self.minute = (self.minute + 1) % 60;
self.sendNotification("CLOCK_MINUTE", self.minute);
setTimeout(notificationTimer, delayCalculator(0));
};
//Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
setTimeout(notificationTimer, delayCalculator(self.second));
// Set locale.
moment.locale(config.language);
@@ -62,12 +98,16 @@ Module.register("clock",{
var timeWrapper = document.createElement("div");
var secondsWrapper = document.createElement("sup");
var periodWrapper = document.createElement("span");
var weekWrapper = document.createElement("div")
var sunWrapper = document.createElement("div");
var moonWrapper = document.createElement("div");
var weekWrapper = document.createElement("div");
// Style Wrappers
dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light";
secondsWrapper.className = "dimmed";
weekWrapper.className = "week dimmed medium"
sunWrapper.className = "sun dimmed small";
moonWrapper.className = "moon dimmed small";
weekWrapper.className = "week dimmed medium";
// Set content of wrappers.
// The moment().format("h") method has a bug on the Raspberry Pi.
@@ -75,6 +115,7 @@ Module.register("clock",{
// See issue: https://github.com/MichMich/MagicMirror/issues/181
var timeString;
var now = moment();
this.lastDisplayedMinute = now.minute();
if (this.config.timezone) {
now.tz(this.config.timezone);
}
@@ -110,6 +151,49 @@ Module.register("clock",{
timeWrapper.appendChild(periodWrapper);
}
function formatTime(config, time) {
var formatString = hourSymbol + ":mm";
if (config.showPeriod && config.timeFormat !== 24) {
formatString += config.showPeriodUpper ? "A" : "a";
}
return moment(time).format(formatString);
}
if (this.config.showSunTimes) {
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
var nextEvent;
if (now.isBefore(sunTimes.sunrise)) {
nextEvent = sunTimes.sunrise;
} else if (now.isBefore(sunTimes.sunset)) {
nextEvent = sunTimes.sunset;
} else {
const tomorrowSunTimes = SunCalc.getTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon);
nextEvent = tomorrowSunTimes.sunrise;
}
const untilNextEvent = moment.duration(moment(nextEvent).diff(now));
const untilNextEventString = untilNextEvent.hours() + "h " + untilNextEvent.minutes() + "m";
sunWrapper.innerHTML = "<span class=\"" + (isVisible ? "bright" : "") + "\"><i class=\"fa fa-sun-o\" aria-hidden=\"true\"></i> " + untilNextEventString + "</span>" +
"<span><i class=\"fa fa-arrow-up\" aria-hidden=\"true\"></i>" + formatTime(this.config, sunTimes.sunrise) + "</span>" +
"<span><i class=\"fa fa-arrow-down\" aria-hidden=\"true\"></i>" + formatTime(this.config, sunTimes.sunset) + "</span>";
}
if (this.config.showMoonTimes) {
const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon);
const moonRise = moonTimes.rise;
var moonSet;
if (moment(moonTimes.set).isAfter(moonTimes.rise)) {
moonSet = moonTimes.set;
} else {
const nextMoonTimes = SunCalc.getMoonTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon);
moonSet = nextMoonTimes.set;
}
const isVisible = now.isBetween(moonRise, moonSet) || moonTimes.alwaysUp === true;
const illuminatedFractionString = Math.round(moonIllumination.fraction * 100) + "%";
moonWrapper.innerHTML = "<span class=\"" + (isVisible ? "bright" : "") + "\"><i class=\"fa fa-moon-o\" aria-hidden=\"true\"></i> " + illuminatedFractionString + "</span>" +
"<span><i class=\"fa fa-arrow-up\" aria-hidden=\"true\"></i> " + (moonRise ? formatTime(this.config, moonRise) : "...") + "</span>"+
"<span><i class=\"fa fa-arrow-down\" aria-hidden=\"true\"></i> " + (moonSet ? formatTime(this.config, moonSet) : "...") + "</span>";
}
/****************************************************************
* Create wrappers for ANALOG clock, only if specified in config
*/
@@ -132,7 +216,7 @@ Module.register("clock",{
clockCircle.style.width = this.config.analogSize;
clockCircle.style.height = this.config.analogSize;
if (this.config.analogFace != "" && this.config.analogFace != "simple" && this.config.analogFace != "none") {
if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
clockCircle.style.background = "url("+ this.data.path + "faces/" + this.config.analogFace + ".svg)";
clockCircle.style.backgroundSize = "100%";
@@ -140,7 +224,7 @@ Module.register("clock",{
// clockCircle.style.border = "1px solid black";
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
} else if (this.config.analogFace != "none") {
} else if (this.config.analogFace !== "none") {
clockCircle.style.border = "2px solid white";
}
var clockFace = document.createElement("div");
@@ -178,6 +262,8 @@ Module.register("clock",{
// Display only a digital clock
wrapper.appendChild(dateWrapper);
wrapper.appendChild(timeWrapper);
wrapper.appendChild(sunWrapper);
wrapper.appendChild(moonWrapper);
wrapper.appendChild(weekWrapper);
} else if (this.config.displayType === "analog") {
// Display only an analog clock
@@ -212,6 +298,8 @@ Module.register("clock",{
digitalWrapper.style.cssFloat = "none";
digitalWrapper.appendChild(dateWrapper);
digitalWrapper.appendChild(timeWrapper);
digitalWrapper.appendChild(sunWrapper);
digitalWrapper.appendChild(moonWrapper);
digitalWrapper.appendChild(weekWrapper);
var appendClocks = function(condition, pos1, pos2) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -66,3 +66,13 @@
-ms-transform-origin: 50% 100%;
transform-origin: 50% 100%;
}
.module.clock .sun,
.module.clock .moon {
display: flex;
}
.module.clock .sun > *,
.module.clock .moon > * {
flex: 1;
}

View File

@@ -2,150 +2,4 @@
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:
````javascript
modules: [
{
module: "compliments",
position: "lower_third", // This can be any of the regions.
// Best results in one of the middle regions like: lower_third
config: {
// The config property is optional.
// If no config is set, the default compliments are shown.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
| Option | Description
| ---------------- | -----------
| `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 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
The `compliments` property contains an object with four arrays: <code>morning</code>, <code>afternoon</code>, <code>evening</code> and <code>anytime</code>. Based on the time of the day, the compliments will be picked out of one of these arrays. The arrays contain one or multiple compliments.
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`
#### Example use with currentweather module
````javascript
config: {
compliments: {
day_sunny: [
"Today is a sunny day",
"It's a beautiful day"
],
snow: [
"Snowball battle!"
],
rain: [
"Don't forget your umbrella"
]
}
}
````
#### Default value:
````javascript
config: {
compliments: {
anytime: [
"Hey there sexy!"
],
morning: [
"Good morning, handsome!",
"Enjoy your day!",
"How was your sleep?"
],
afternoon: [
"Hello, beauty!",
"You look sexy!",
"Looking good today!"
],
evening: [
"Wow, you look hot!",
"You look nice!",
"Hi, sexy!"
]
}
}
````
#### 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.
Adding the `remoteFile` variable will override an array you specify in the configuration file.
This file must be straight JSON. Note that the array names need quotes
around them ("morning", "afternoon", "evening", "snow", "rain", etc.).
#### Example compliments.json file:
````json
{
"anytime" : [
"Hey there sexy!"
],
"morning" : [
"Good morning, sunshine!",
"Who needs coffee when you have your smile?",
"Go get 'em, Tiger!"
],
"afternoon" : [
"Hitting your stride!",
"You are making a difference!",
"You're more fun than bubble wrap!"
],
"evening" : [
"You made someone smile today, I know it.",
"You are making a difference.",
"The day was better for your efforts."
]
}
````
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/compliments.html).

View File

@@ -28,6 +28,9 @@ Module.register("compliments", {
"Wow, you look hot!",
"You look nice!",
"Hi, sexy!"
],
"....-01-01": [
"Happy new year!"
]
},
updateInterval: 30000,
@@ -36,9 +39,11 @@ Module.register("compliments", {
morningStartTime: 3,
morningEndTime: 12,
afternoonStartTime: 12,
afternoonEndTime: 17
afternoonEndTime: 17,
random: true,
mockDate: null
},
lastIndexUsed:-1,
// Set currentweather from module
currentWeatherType: "",
@@ -54,7 +59,7 @@ Module.register("compliments", {
this.lastComplimentIndex = -1;
var self = this;
if (this.config.remoteFile != null) {
if (this.config.remoteFile !== null) {
this.complimentFile(function(response) {
self.config.compliments = JSON.parse(response);
self.updateDom();
@@ -101,6 +106,7 @@ Module.register("compliments", {
*/
complimentArray: function() {
var hour = moment().hour();
var date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
var compliments;
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) {
@@ -121,6 +127,12 @@ Module.register("compliments", {
compliments.push.apply(compliments, this.config.compliments.anytime);
for (entry in this.config.compliments) {
if (new RegExp(entry).test(date)) {
compliments.push.apply(compliments, this.config.compliments[entry]);
}
}
return compliments;
},
@@ -134,7 +146,7 @@ Module.register("compliments", {
xobj.overrideMimeType("application/json");
xobj.open("GET", path, true);
xobj.onreadystatechange = function() {
if (xobj.readyState == 4 && xobj.status == "200") {
if (xobj.readyState === 4 && xobj.status === 200) {
callback(xobj.responseText);
}
};
@@ -147,25 +159,48 @@ Module.register("compliments", {
* return compliment string - A compliment.
*/
randomCompliment: function() {
// get the current time of day compliments list
var compliments = this.complimentArray();
var index = this.randomIndex(compliments);
// variable for index to next message to display
let index = 0;
// are we randomizing
if(this.config.random){
// yes
index = this.randomIndex(compliments);
}
else{
// no, sequential
// if doing sequential, don't fall off the end
index = (this.lastIndexUsed >= (compliments.length-1))?0: ++this.lastIndexUsed;
}
return compliments[index];
return compliments[index] || "";
},
// Override dom generator.
getDom: function() {
var complimentText = this.randomCompliment();
var compliment = document.createTextNode(complimentText);
var wrapper = document.createElement("div");
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line";
// get the compliment text
var complimentText = this.randomCompliment();
// split it into parts on newline text
var parts = complimentText.split("\n");
// create a span to hold it all
var compliment = document.createElement("span");
// process all the parts of the compliment text
for (part of parts){
// create a text element for each part
compliment.appendChild(document.createTextNode(part));
// add a break `
compliment.appendChild(document.createElement("BR"));
}
// remove the last break
compliment.lastElementChild.remove();
wrapper.appendChild(compliment);
return wrapper;
},
// From data currentweather set weather type
setCurrentWeatherType: function(data) {
var weatherIconTable = {
@@ -191,10 +226,9 @@ Module.register("compliments", {
this.currentWeatherType = weatherIconTable[data.weather[0].icon];
},
// Override notification handler.
notificationReceived: function(notification, payload, sender) {
if (notification == "CURRENTWEATHER_DATA") {
if (notification === "CURRENTWEATHER_DATA") {
this.setCurrentWeatherType(payload.data);
}
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -2,87 +2,4 @@
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:
````javascript
modules: [
{
module: "currentweather",
position: "top_right", // This can be any of the regions.
// Best results in left or right regions.
config: {
// See 'Configuration options' for more information.
location: "Amsterdam,Netherlands",
locationID: "", //Location ID from http://openweathermap.org/help/city_list.txt
appid: "abcde12345abcde12345abcde12345ab" //openweathermap.org API key.
}
}
]
````
## Configuration options
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](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`
| `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`
| `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`
| `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`
| `apiBase` | The OpenWeatherMap base URL. <br><br> **Default value:** `'http://api.openweathermap.org/data/'`
| `weatherEndpoint` | The OpenWeatherMap API endPoint. <br><br> **Default value:** `'weather'`
| `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'`
| `iconTable` | The conversion table to convert the weather conditions to weather-icons. <br><br> **Default value:** view tabel below.
#### Default Icon Table
````javascript
iconTable: {
'01d': 'wi-day-sunny',
'02d': 'wi-day-cloudy',
'03d': 'wi-cloudy',
'04d': 'wi-cloudy-windy',
'09d': 'wi-showers',
'10d': 'wi-rain',
'11d': 'wi-thunderstorm',
'13d': 'wi-snow',
'50d': 'wi-fog',
'01n': 'wi-night-clear',
'02n': 'wi-night-cloudy',
'03n': 'wi-night-cloudy',
'04n': 'wi-night-cloudy',
'09n': 'wi-night-showers',
'10n': 'wi-night-rain',
'11n': 'wi-night-thunderstorm',
'13n': 'wi-night-snow',
'50n': 'wi-night-alt-cloudy-windy'
}
````
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/currentweather.html).

View File

@@ -23,6 +23,7 @@ Module.register("currentweather",{
showWindDirection: true,
showWindDirectionAsArrow: false,
useBeaufort: true,
appendLocationNameToHeader: false,
useKMPHwind: false,
lang: config.language,
decimalSymbol: ".",
@@ -41,8 +42,10 @@ Module.register("currentweather",{
appendLocationNameToHeader: true,
calendarClass: "calendar",
tableClass: "large",
onlyTemp: false,
hideTemp: false,
roundTemp: false,
iconTable: {
@@ -173,6 +176,7 @@ Module.register("currentweather",{
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
wrapper.className = this.config.tableClass;
if (this.config.appid === "") {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
@@ -191,11 +195,7 @@ Module.register("currentweather",{
}
var large = document.createElement("div");
large.className = "large light";
var weatherIcon = document.createElement("span");
weatherIcon.className = "wi weathericon " + this.weatherType;
large.appendChild(weatherIcon);
large.className = "light";
var degreeLabel = "";
if (this.config.units === "metric" || this.config.units === "imperial") {
@@ -219,10 +219,16 @@ Module.register("currentweather",{
this.config.decimalSymbol = ".";
}
var temperature = document.createElement("span");
temperature.className = "bright";
temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(temperature);
if (this.config.hideTemp === false) {
var weatherIcon = document.createElement("span");
weatherIcon.className = "wi weathericon " + this.weatherType;
large.appendChild(weatherIcon);
var temperature = document.createElement("span");
temperature.className = "bright";
temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(temperature);
}
if (this.config.showIndoorTemperature && this.indoorTemperature) {
var indoorIcon = document.createElement("span");
@@ -269,6 +275,10 @@ Module.register("currentweather",{
return this.data.header + " " + this.fetchedLocationName;
}
if (this.config.useLocationAsHeader && this.config.location !== false) {
return this.config.location;
}
return this.data.header;
},
@@ -353,7 +363,7 @@ Module.register("currentweather",{
} 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
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location;
} else {
@@ -383,6 +393,7 @@ Module.register("currentweather",{
this.humidity = parseFloat(data.main.humidity);
this.temperature = this.roundValue(data.main.temp);
this.fetchedLocationName = data.name;
this.feelsLike = 0;
if (this.config.useBeaufort){

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,25 +1,4 @@
# Module: Hello World
The `helloworld` module is one of the default modules of the MagicMirror. It is a simple way to display a static text on the mirror.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: "helloworld",
position: "bottom_bar", // This can be any of the regions.
config: {
// See 'Configuration options' for more information.
text: "Hello world!"
}
}
]
````
## Configuration options
The following properties can be configured:
| Option | Description
| ------ | -----------
| `text` | The text to display. <br><br> **Example:** `'Hello world!'` <br> **Default value:** `'Hello world!'`
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/helloworld.html).

View File

@@ -15,10 +15,10 @@ Module.register("helloworld",{
},
getTemplate: function () {
return "helloworld.njk"
return "helloworld.njk";
},
getTemplateData: function () {
return this.config
return this.config;
}
});

View File

@@ -2,96 +2,4 @@
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
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: "newsfeed",
position: "bottom_bar", // This can be any of the regions. Best results in center regions.
config: {
// The config property is optional.
// If no config is set, an example calendar is shown.
// See 'Configuration options' for more information.
feeds: [
{
title: "New York Times",
url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml",
},
{
title: "BBC",
url: "http://feeds.bbci.co.uk/news/video_and_audio/news_front_page/rss.xml?edition=uk",
},
]
}
}
]
````
### Notifications
#### Interacting with the module
MagicMirror's [notification mechanism](https://github.com/MichMich/MagicMirror/tree/master/modules#thissendnotificationnotification-payload) allows to send notifications to the `newsfeed` module. The following notifications are supported:
| Notification Identifier | Description
| ----------------------- | -----------
| `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`.<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.
| `ARTICLE_INFO_REQUEST` | Causes `newsfeed` to respond with the notification `ARTICLE_INFO_RESPONSE`, the payload of which provides the `title`, `source`, `date`, `desc` and `url` of the current news title.
Note the payload of the sent notification event is ignored.
#### Example
The following example shows how the next news article title can be displayed on the MagicMirror.
````javascript
this.sendNotification('ARTICLE_NEXT');
````
#### `newsfeed` specific notification emitting modules
The third party [MMM-Gestures](https://github.com/thobach/MMM-Gestures) module supports above notifications when moving your hand up, down, left or right in front of a gesture sensor attached to the MagicMirror. See module's readme for more details.
## Configuration options
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" }]`<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:
| Option | Description
| ---------- | -----------
| `title` | The name of the feed source to be displayed above the news items. <br><br> This property is optional.
| `url` | The url of the feed used for the headlines. <br><br> **Example:** `'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'`
| `encoding` | The encoding of the news feed. <br><br> This property is optional. <br> **Possible values:**`'UTF-8'`, `'ISO-8859-1'`, etc ... <br> **Default value:** `'UTF-8'`
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/newsfeed.html).

View File

@@ -81,11 +81,10 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
scheduleTimer();
});
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/)",
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
"Pragma": "no-cache"}
"Pragma": "no-cache"};
request({uri: url, encoding: null, headers: headers})
.on("error", function(error) {

View File

@@ -20,6 +20,8 @@ Module.register("newsfeed",{
],
showSourceTitle: true,
showPublishDate: true,
broadcastNewsFeeds: true,
broadcastNewsUpdates: true,
showDescription: false,
wrapTitle: true,
wrapDescription: true,
@@ -103,7 +105,7 @@ Module.register("newsfeed",{
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) {
var sourceAndTimestamp = document.createElement("div");
sourceAndTimestamp.className = "light small dimmed";
sourceAndTimestamp.className = "newsfeed-source light small dimmed";
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") {
sourceAndTimestamp.innerHTML = this.newsItems[this.activeItem].sourceTitle;
@@ -166,14 +168,14 @@ Module.register("newsfeed",{
if(!this.config.showFullArticle){
var title = document.createElement("div");
title.className = "bright medium light" + (!this.config.wrapTitle ? " no-wrap" : "");
title.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : "");
title.innerHTML = this.newsItems[this.activeItem].title;
wrapper.appendChild(title);
}
if (this.isShowingDescription) {
var description = document.createElement("div");
description.className = "small light" + (!this.config.wrapDescription ? " no-wrap" : "");
description.className = "newsfeed-desc small light" + (!this.config.wrapDescription ? " no-wrap" : "");
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);
@@ -189,7 +191,7 @@ Module.register("newsfeed",{
fullArticle.style.top = "0";
fullArticle.style.left = "0";
fullArticle.style.border = "none";
fullArticle.src = this.getActiveItemURL()
fullArticle.src = this.getActiveItemURL();
fullArticle.style.zIndex = 1;
wrapper.appendChild(fullArticle);
}
@@ -211,7 +213,7 @@ Module.register("newsfeed",{
},
getActiveItemURL: function() {
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
},
/* registerFeeds()
@@ -266,6 +268,20 @@ Module.register("newsfeed",{
}, this);
}
// get updated news items and broadcast them
var updatedItems = [];
newsItems.forEach(value => {
if (this.newsItems.findIndex(value1 => value1 === value) === -1) {
// Add item to updated items list
updatedItems.push(value);
}
});
// check if updated items exist, if so and if we should broadcast these updates, then lets do so
if (this.config.broadcastNewsUpdates && updatedItems.length > 0) {
this.sendNotification("NEWS_FEED_UPDATE", {items: updatedItems});
}
this.newsItems = newsItems;
},
@@ -311,9 +327,19 @@ Module.register("newsfeed",{
self.updateDom(self.config.animationSpeed);
// Broadcast NewsFeed if needed
if (self.config.broadcastNewsFeeds) {
self.sendNotification("NEWS_FEED", {items: self.newsItems});
}
timer = setInterval(function() {
self.activeItem++;
self.updateDom(self.config.animationSpeed);
// Broadcast NewsFeed if needed
if (self.config.broadcastNewsFeeds) {
self.sendNotification("NEWS_FEED", {items: self.newsItems});
}
}, this.config.updateInterval);
},
@@ -341,7 +367,6 @@ Module.register("newsfeed",{
},
notificationReceived: function(notification, payload, sender) {
Log.info(this.name + " - received notification: " + notification);
if(notification === "ARTICLE_NEXT"){
var before = this.activeItem;
this.activeItem++;
@@ -398,9 +423,7 @@ Module.register("newsfeed",{
date: this.newsItems[this.activeItem].pubdate,
desc: this.newsItems[this.activeItem].description,
url: this.getActiveItemURL()
})
} else {
Log.info(this.name + " - unknown notification, ignoring: " + notification);
});
}
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -2,26 +2,4 @@
The `updatenotification` module is one of the default modules of the MagicMirror.
This will display a message whenever a new version of the MagicMirror application is available.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: "updatenotification",
position: "top_center", // This can be any of the regions.
config: {
// The config property is optional.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
| Option | Description
| ---------------- | -----------
| `updateInterval` | How often do you want to check for a new version? This value represents the interval in milliseconds. <br><br> **Possible values:** Any value above `60000` (1 minute) <br> **Default value:** `600000` (10 minutes);
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/updatenotification.html).

View File

@@ -10,61 +10,74 @@ module.exports = NodeHelper.create({
config: {},
updateTimer: null,
updateProcessStarted: false,
start: function () {
},
configureModules: function(modules) {
for (moduleName in modules) {
if (defaultModules.indexOf(moduleName) < 0) {
// Default modules are included in the main MagicMirror repo
var moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
var stat;
// Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten
// others will be added in front
// this method returns promises so we can't wait for every one to resolve before continuing
simpleGits.push({"module": "default", "git": SimpleGit(path.normalize(__dirname + "/../../../"))});
var promises = [];
for (moduleName in modules) {
if (!this.ignoreUpdateChecking(moduleName)) {
// Default modules are included in the main MagicMirror repo
var moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
try {
stat = fs.statSync(path.join(moduleFolder, ".git"));
//console.log("checking git for module="+moduleName)
let stat = fs.statSync(path.join(moduleFolder, ".git"));
promises.push(this.resolveRemote(moduleName, moduleFolder));
} catch(err) {
// Error when directory .git doesn't exist
// This module is not managed with git, skip
continue;
}
var res = function(mn, mf) {
var git = SimpleGit(mf);
git.getRemotes(true, function(err, remotes) {
if (remotes.length < 1 || remotes[0].name.length < 1) {
// No valid remote for folder, skip
return;
}
// Folder has .git and has at least one git remote, watch this folder
simpleGits.push({"module": mn, "git": git});
});
}(moduleName, moduleFolder);
}
}
// Push MagicMirror itself last, biggest chance it'll show up last in UI and isn't overwritten
simpleGits.push({"module": "default", "git": SimpleGit(path.normalize(__dirname + "/../../../"))});
return Promise.all(promises);
},
socketNotificationReceived: function (notification, payload) {
if (notification === "CONFIG") {
this.config = payload;
} else if(notification === "MODULES") {
this.configureModules(payload);
this.preformFetch();
// if this is the 1st time thru the update check process
if (!this.updateProcessStarted) {
this.updateProcessStarted = true;
this.configureModules(payload).then(() => this.performFetch());
}
}
},
preformFetch() {
var self = this;
resolveRemote: function(moduleName, moduleFolder) {
return new Promise((resolve, reject) => {
var git = SimpleGit(moduleFolder);
git.getRemotes(true, (err, remotes) => {
if (remotes.length < 1 || remotes[0].name.length < 1) {
// No valid remote for folder, skip
return resolve();
}
// Folder has .git and has at least one git remote, watch this folder
simpleGits.unshift({"module": moduleName, "git": git});
resolve();
});
});
},
simpleGits.forEach(function(sg) {
sg.git.fetch().status(function(err, data) {
performFetch: function() {
var self = this;
simpleGits.forEach((sg) => {
sg.git.fetch().status((err, data) => {
data.module = sg.module;
if (!err) {
sg.git.log({"-1": null}, function(err, data2) {
sg.git.log({"-1": null}, (err, data2) => {
if (!err && data2.latest && "hash" in data2.latest) {
data.hash = data2.latest.hash;
self.sendSocketNotification("STATUS", data);
@@ -79,14 +92,29 @@ module.exports = NodeHelper.create({
scheduleNextFetch: function(delay) {
if (delay < 60 * 1000) {
delay = 60 * 1000
delay = 60 * 1000;
}
var self = this;
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function() {
self.preformFetch();
self.performFetch();
}, delay);
},
ignoreUpdateChecking: function(moduleName) {
// Should not check for updates for default modules
if (defaultModules.indexOf(moduleName) >= 0) {
return true;
}
// Should not check for updates for ignored modules
if (this.config.ignoreModules.indexOf(moduleName) >= 0) {
return true;
}
// The rest of the modules that passes should check for updates
return false;
}
});

View File

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

109
modules/default/weather/README.md Normal file → Executable file
View File

@@ -1,110 +1,5 @@
# 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.
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 change 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` , `darksky` , or `weathergov` <br> **Default value:** `openweathermap`
| `type` | Which type of weather data should be displayed. <br><br> **Possible values:** `current` or `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, Kelvin = 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 interesting 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`
| `showPrecipitationAmount` | Show the amount of rain/snow in the forecast <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `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`
| `maxNumberOfDays` | How many days of forecast to return. Specified by config.js <br><br> **Possible values:** `1` - `16` <br> **Default value:** `5` (5 days) <br> This value is optional. By default the weatherforecast module will return 5 days.
### 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`, `/forecast` (free users) or `/forecast/daily` (paying users or old apiKey only) <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**
### Weather.gov options
| Option | Description
| ---------------------------- | -----------
| `apiBase` | The weather.gov base URL. <br><br> **Possible value:** `'https://api.weather.gov/points/'` <br> This value is **REQUIRED**
| `weatherEndpoint` | The weather.gov API endPoint. <br><br> **Possible values:** `/forecast` for forecast and `/forecast/hourly` for current. <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).
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html).

19
modules/default/weather/current.njk Normal file → Executable file
View File

@@ -9,7 +9,7 @@
{{ current.windSpeed | round }}
{% endif %}
{% if config.showWindDirection %}
<sup>
<sup>
{% if config.showWindDirectionAsArrow %}
<i class="fa fa-long-arrow-up" style="transform:rotate({{ current.windDirection }}deg);"></i>
{% else %}
@@ -24,7 +24,7 @@
{% endif %}
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
<span>
{% if current.nextSunAction() == "sunset" %}
{% if current.nextSunAction() === "sunset" %}
{{ current.sunset | formatTime }}
{% else %}
{{ current.sunrise | formatTime }}
@@ -56,11 +56,18 @@
</div>
{% endif %}
</div>
{% if config.showFeelsLike and not config.onlyTemp %}
{% if (config.showFeelsLike or config.showPrecipitationAmount) and not config.onlyTemp %}
<div class="normal medium">
<span class="dimmed">
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
</span>
{% if config.showFeelsLike %}
<span class="dimmed">
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
</span>
{% endif %}
{% if config.showPrecipitationAmount %}
<span class="dimmed">
{{ "PRECIP" | translate }} {{ current.precipitation | unit("precip") }}
</span>
{% endif %}
</div>
{% endif %}
{% else %}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -28,5 +28,5 @@
</div>
{% endif %}
<!-- Uncomment the line below to see the contents of the `current` object. -->
<!-- Uncomment the line below to see the contents of the `forecast` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{forecast | dump}}</div> -->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

10
modules/default/weather/providers/README.md Normal file → Executable file
View File

@@ -18,9 +18,9 @@ This is the script in which the weather provider will be defined. In it's most s
````javascript
WeatherProvider.register("yourprovider", {
providerName: "YourProvider",
fetchCurrentWeather() {},
fetchWeatherForecast() {}
});
````
@@ -91,7 +91,9 @@ A convenience function to make requests. It returns a promise.
| Property | Type | Value/Unit |
| --- | --- | --- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric` and `imperial` |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `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. |
@@ -104,7 +106,7 @@ A convenience function to make requests. It returns a promise.
| humidity | `number` | Percentage of humidity |
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
#### Current weather

10
modules/default/weather/providers/darksky.js Normal file → Executable file
View File

@@ -32,7 +32,8 @@ WeatherProvider.register("darksky", {
this.setCurrentWeather(currentWeather);
}).catch(function(request) {
Log.error("Could not load data ... ", request);
});
})
.finally(() => this.updateAvailable())
},
fetchWeatherForecast() {
@@ -47,7 +48,8 @@ WeatherProvider.register("darksky", {
this.setWeatherForecast(forecast);
}).catch(function(request) {
Log.error("Could not load data ... ", request);
});
})
.finally(() => this.updateAvailable())
},
// Create a URL from the config and base URL.
@@ -58,7 +60,7 @@ WeatherProvider.register("darksky", {
// Implement WeatherDay generator.
generateWeatherDayFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units);
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
currentWeather.date = moment();
currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity);
@@ -76,7 +78,7 @@ WeatherProvider.register("darksky", {
const days = [];
for (const forecast of forecasts) {
const weather = new WeatherObject(this.config.units);
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
weather.date = moment(forecast.time, "X");
weather.minTemperature = forecast.temperatureMin;

18
modules/default/weather/providers/openweathermap.js Normal file → Executable file
View File

@@ -34,6 +34,7 @@ WeatherProvider.register("openweathermap", {
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
},
// Overwrite the fetchCurrentWeather method.
@@ -54,6 +55,7 @@ WeatherProvider.register("openweathermap", {
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
},
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
@@ -68,7 +70,7 @@ WeatherProvider.register("openweathermap", {
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units);
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
currentWeather.humidity = currentWeatherData.main.humidity;
currentWeather.temperature = currentWeatherData.main.temp;
@@ -86,13 +88,13 @@ WeatherProvider.register("openweathermap", {
*/
generateWeatherObjectsFromForecast(forecasts) {
if (this.config.weatherEndpoint == "/forecast") {
if (this.config.weatherEndpoint === "/forecast") {
return this.fetchForecastHourly(forecasts);
} else if (this.config.weatherEndpoint == "/forecast/daily") {
} else if (this.config.weatherEndpoint === "/forecast/daily") {
return this.fetchForecastDaily(forecasts);
}
// if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
const days = [new WeatherObject(this.config.units)];
const days = [new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits)];
return days;
},
@@ -109,7 +111,7 @@ WeatherProvider.register("openweathermap", {
let snow = 0;
// variable for date
let date = "";
let weather = new WeatherObject(this.config.units);
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
for (const forecast of forecasts) {
@@ -123,7 +125,7 @@ WeatherProvider.register("openweathermap", {
// push weather information to days array
days.push(weather);
// create new weather-object
weather = new WeatherObject(this.config.units);
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
minTemp = [];
maxTemp = [];
@@ -140,7 +142,7 @@ WeatherProvider.register("openweathermap", {
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
}
if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) {
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
}
@@ -187,7 +189,7 @@ WeatherProvider.register("openweathermap", {
const days = [];
for (const forecast of forecasts) {
const weather = new WeatherObject(this.config.units);
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
weather.date = moment(forecast.dt, "X");
weather.minTemperature = forecast.temp.min;

View File

@@ -0,0 +1,264 @@
/* global WeatherProvider, WeatherObject */
/* Magic Mirror
* Module: Weather
*
* By Malcolm Oakes https://github.com/maloakes
* MIT Licensed.
*
* This class is a provider for UK Met Office Datapoint.
*/
WeatherProvider.register("ukmetoffice", {
// 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: "UK Met Office",
units: {
imperial: "us",
metric: "si"
},
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl("3hourly"))
.then(data => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location ||
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`);
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl("daily"))
.then(data => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location ||
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`);
const forecast = this.generateWeatherObjectsFromForecast(data);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
},
/** UK Met Office Specific Methods - These are not part of the default provider methods */
/*
* Gets the complete url for the request
*/
getUrl(forecastType) {
return this.config.apiBase + this.config.locationID + this.getParams(forecastType);
},
/*
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// data times are always UTC
let nowUtc = moment.utc()
let midnightUtc = nowUtc.clone().startOf("day")
let timeInMins = nowUtc.diff(midnightUtc, "minutes");
// loop round each of the (5) periods, look for today (the first period may be yesterday)
for (i in currentWeatherData.SiteRep.DV.Location.Period) {
let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0,10), "YYYY-MM-DD")
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// check this is the period we want, after today the diff will be -ve
if (moment().diff(periodDate, "minutes") > 0) {
// loop round the reports looking for the one we are in
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
for (j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep){
let p = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].$;
if (timeInMins >= p && timeInMins-180 < p) {
// finally got the one we want, so populate weather object
currentWeather.humidity = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].H;
currentWeather.temperature = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].T);
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].F);
currentWeather.precipitation = parseInt(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].Pp);
currentWeather.windSpeed = this.convertWindSpeed(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].S);
currentWeather.windDirection = this.convertWindDirection(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].D);
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].W);
}
}
}
}
}
// determine the sunrise/sunset times - not supplied in UK Met Office data
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location)
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1];
return currentWeather;
},
/*
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
const days = [];
// loop round the (5) periods getting the data
// for each period array, Day is [0], Night is [1]
for (j in forecasts.SiteRep.DV.Location.Period) {
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// data times are always UTC
dateStr = forecasts.SiteRep.DV.Location.Period[j].value
let periodDate = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD")
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// populate the weather object
weather.date = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD");
weather.minTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[1].Nm);
weather.maxTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[0].Dm);
weather.weatherType = this.convertWeatherType(forecasts.SiteRep.DV.Location.Period[j].Rep[0].W);
weather.precipitation = parseInt(forecasts.SiteRep.DV.Location.Period[j].Rep[0].PPd);
days.push(weather);
}
}
return days;
},
/*
* calculate the astronomical data
*/
calcAstroData(location) {
const sunTimes = [];
// determine the sunrise/sunset times
let times = SunCalc.getTimes(new Date(), location.lat, location.lon);
sunTimes.push(moment(times.sunrise, "X"));
sunTimes.push(moment(times.sunset, "X"));
return sunTimes;
},
/*
* Convert the Met Office icons to a more usable name.
*/
convertWeatherType(weatherType) {
const weatherTypes = {
0: "night-clear",
1: "day-sunny",
2: "night-alt-cloudy",
3: "day-cloudy",
5: "fog",
6: "fog",
7: "cloudy",
8: "cloud",
9: "night-sprinkle",
10: "day-sprinkle",
11: "raindrops",
12: "sprinkle",
13: "night-alt-showers",
14: "day-showers",
15: "rain",
16: "night-alt-sleet",
17: "day-sleet",
18: "sleet",
19: "night-alt-hail",
20: "day-hail",
21: "hail",
22: "night-alt-snow",
23: "day-snow",
24: "snow",
25: "night-alt-snow",
26: "day-snow",
27: "snow",
28: "night-alt-thunderstorm",
29: "day-thunderstorm",
30: "thunderstorm"
};
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
},
/*
* Convert temp (from degrees C) if required
*/
convertTemp(tempInC) {
return this.tempUnits === "imperial" ? tempInC * 9 / 5 + 32 : tempInC;
},
/*
* Convert wind speed (from mph) if required
*/
convertWindSpeed(windInMph) {
return this.windUnits === "metric" ? windInMph * 2.23694 : windInMph;
},
/*
* Convert the wind direction cardinal to value
*/
convertWindDirection(windDirection) {
const windCardinals = {
"N": 0,
"NNE": 22,
"NE": 45,
"ENE": 67,
"E": 90,
"ESE": 112,
"SE": 135,
"SSE": 157,
"S": 180,
"SSW": 202,
"SW": 225,
"WSW": 247,
"W": 270,
"WNW": 292,
"NW": 315,
"NNW": 337
};
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
},
/*
* Generates an url with api parameters based on the config.
*
* return String - URL params.
*/
getParams(forecastType) {
let params = "?";
params += "res=" + forecastType;
params += "&key=" + this.config.apiKey;
return params;
}
});

90
modules/default/weather/providers/weathergov.js Normal file → Executable file
View File

@@ -35,6 +35,7 @@ WeatherProvider.register("weathergov", {
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
},
// Overwrite the fetchCurrentWeather method.
@@ -53,6 +54,7 @@ WeatherProvider.register("weathergov", {
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
},
/** Weather.gov Specific Methods - These are not part of the default provider methods */
@@ -67,13 +69,18 @@ WeatherProvider.register("weathergov", {
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units);
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
currentWeather.temperature = currentWeatherData.temperature;
currentWeather.windSpeed = currentWeatherData.windSpeed.split(" ", 1);
currentWeather.windDirection = this.convertDirectiontoDegrees(currentWeatherData.windDirection);
currentWeather.windDirection = this.convertWindDirection(currentWeatherData.windDirection);
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.shortForecast, currentWeatherData.isDaytime);
// determine the sunrise/sunset times - not supplied in weather.gov data
let times = this.calcAstroData(this.config.lat, this.config.lon)
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1];
return currentWeather;
},
@@ -95,7 +102,7 @@ WeatherProvider.register("weathergov", {
let maxTemp = [];
// variable for date
let date = "";
let weather = new WeatherObject(this.config.units);
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
weather.precipitation = 0;
for (const forecast of forecasts) {
@@ -109,7 +116,7 @@ WeatherProvider.register("weathergov", {
// push weather information to days array
days.push(weather);
// create new weather-object
weather = new WeatherObject(this.config.units);
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
minTemp = [];
maxTemp = [];
@@ -134,9 +141,9 @@ WeatherProvider.register("weathergov", {
minTemp.push(forecast.temperature);
maxTemp.push(forecast.temperature);
}
// last day
// calculate minimum/maximum temperature, specify rain amount
// calculate minimum/maximum temperature
weather.minTemperature = Math.min.apply(null, minTemp);
weather.maxTemperature = Math.max.apply(null, maxTemp);
@@ -145,6 +152,20 @@ WeatherProvider.register("weathergov", {
return days.slice(1);
},
/*
* Calculate the astronomical data
*/
calcAstroData(lat, lon) {
const sunTimes = [];
// determine the sunrise/sunset times
let times = SunCalc.getTimes(new Date(), lat, lon);
sunTimes.push(moment(times.sunrise, "X"));
sunTimes.push(moment(times.sunset, "X"));
return sunTimes;
},
/*
* Convert the icons to a more usable name.
*/
@@ -202,7 +223,7 @@ WeatherProvider.register("weathergov", {
}
return "night-clear";
} else if (weatherType.includes("Dust") || weatherType.includes("Sand")) {
} else if (weatherType.includes("Dust") || weatherType.includes("Sand")) {
return "dust";
} else if (weatherType.includes("Fog")) {
return "fog";
@@ -218,39 +239,26 @@ WeatherProvider.register("weathergov", {
/*
Convert the direction into Degrees
*/
convertDirectiontoDegrees(direction) {
if (direction === "NNE"){
return 33.75;
} else if (direction === "NE") {
return 56.25;
} else if (direction === "ENE") {
return 78.75;
} else if (direction === "E") {
return 101.25;
} else if (direction === "ESE") {
return 123.75;
} else if (direction === "SE") {
return 146.25;
} else if (direction === "SSE") {
return 168.75;
} else if (direction === "S") {
return 191.25;
} else if (direction === "SSW") {
return 213.75;
} else if (direction === "SW") {
return 236.25;
} else if (direction === "WSW") {
return 258.75;
} else if (direction === "W") {
return 281.25;
} else if (direction === "WNW") {
return 303.75;
} else if (direction === "NW") {
return 326.25;
} else if (direction === "NNW") {
return 348.75;
} else {
return 0;
}
convertWindDirection(windDirection) {
const windCardinals = {
"N": 0,
"NNE": 22,
"NE": 45,
"ENE": 67,
"E": 90,
"ESE": 112,
"SE": 135,
"SSE": 157,
"S": 180,
"SSW": 202,
"SW": 225,
"WSW": 247,
"W": 270,
"WNW": 292,
"NW": 315,
"NNW": 337
};
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
}
});

View File

@@ -19,6 +19,10 @@ Module.register("weather",{
locationID: false,
appid: "",
units: config.units,
tempUnits: config.units,
windUnits: config.units,
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
timeFormat: config.timeFormat,
@@ -68,13 +72,14 @@ Module.register("weather",{
"moment.js",
"weatherprovider.js",
"weatherobject.js",
"suncalc.js",
this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")
];
},
// Override getHeader method.
getHeader: function() {
if (this.config.appendLocationNameToHeader && this.weatherProvider) {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined && this.weatherProvider) {
return this.data.header + " " + this.weatherProvider.fetchedLocation();
}
@@ -84,6 +89,7 @@ Module.register("weather",{
// Start the weather module.
start: function () {
moment.locale(this.config.lang);
// Initialize the weather provider.
this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
@@ -137,7 +143,7 @@ Module.register("weather",{
humidity: this.indoorHumidity,
temperature: this.indoorTemperature
}
}
};
},
// What to do when the weather provider has new information available?
@@ -188,13 +194,13 @@ Module.register("weather",{
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
if (type === "temperature") {
if (this.config.units === "metric" || this.config.units === "imperial") {
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
value += "°";
}
if (this.config.degreeLabel) {
if (this.config.units === "metric") {
if (this.config.tempUnits === "metric") {
value += "C";
} else if (this.config.units === "imperial") {
} else if (this.config.tempUnits === "imperial") {
value += "F";
} else {
value += "K";
@@ -204,10 +210,14 @@ Module.register("weather",{
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
value = "";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
if (this.config.weatherProvider === "ukmetoffice") {
value += "%";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
}
}
} else if (type === "humidity") {
value += "%"
value += "%";
}
return value;

18
modules/default/weather/weatherobject.js Normal file → Executable file
View File

@@ -13,8 +13,11 @@
// As soon as we start implementing the forecast, mode properties will be added.
class WeatherObject {
constructor(units) {
constructor(units, tempUnits, windUnits) {
this.units = units;
this.tempUnits = tempUnits;
this.windUnits = windUnits;
this.date = null;
this.windSpeed = null;
this.windDirection = null;
@@ -28,6 +31,8 @@ class WeatherObject {
this.rain = null;
this.snow = null;
this.precipitation = null;
this.feelsLikeTemp = null;
}
cardinalWindDirection() {
@@ -67,7 +72,7 @@ class WeatherObject {
}
beaufortWindSpeed() {
const windInKmh = this.units === "imperial" ? this.windSpeed * 1.609344 : this.windSpeed * 60 * 60 / 1000;
const windInKmh = (this.windUnits === "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) {
@@ -82,8 +87,11 @@ class WeatherObject {
}
feelsLike() {
const windInMph = this.units === "imperial" ? this.windSpeed : this.windSpeed * 2.23694;
const tempInF = this.units === "imperial" ? this.temperature : this.temperature * 9 / 5 + 32;
if (this.feelsLikeTemp) {
return this.feelsLikeTemp;
}
const windInMph = (this.windUnits === "imperial") ? this.windSpeed : this.windSpeed * 2.23694;
const tempInF = this.tempUnits === "imperial" ? this.temperature : this.temperature * 9 / 5 + 32;
let feelsLike = tempInF;
if (windInMph > 3 && tempInF < 50) {
@@ -97,6 +105,6 @@ class WeatherObject {
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
}
return this.units === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
return this.tempUnits === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
}
}

View File

@@ -9,7 +9,6 @@
* This class is the blueprint for a weather provider.
*/
/**
* Base BluePrint for the WeatherProvider
*/
@@ -23,15 +22,14 @@ var WeatherProvider = Class.extend({
weatherForecastArray: null,
fetchedLocationName: null,
// The following properties will be set automaticly.
// The following properties will be set automatically.
// 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.
// All the following methods can be overwritten, although most are good as they are.
// Called when a weather provider is initialized.
init: function(config) {
@@ -51,13 +49,13 @@ var WeatherProvider = Class.extend({
},
// This method should start the API request to fetch the current weather.
// This method should definetly be overwritten in the provider.
// This method should definitely 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.
// This method should definitely be overwritten in the provider.
fetchWeatherForecast: function() {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
},
@@ -81,16 +79,12 @@ var WeatherProvider = Class.extend({
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.
@@ -103,7 +97,7 @@ var WeatherProvider = Class.extend({
this.delegate.updateAvailable(this);
},
// A convinience function to make requests. It returns a promise.
// A convenience 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();
@@ -113,12 +107,12 @@ var WeatherProvider = Class.extend({
if (this.status === 200) {
resolve(JSON.parse(this.response));
} else {
reject(request)
reject(request);
}
}
};
request.send();
})
});
}
});

View File

@@ -2,81 +2,4 @@
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:
````javascript
modules: [
{
module: "weatherforecast",
position: "top_right", // This can be any of the regions.
// Best results in left or right regions.
config: {
// See 'Configuration options' for more information.
location: "Amsterdam,Netherlands",
locationID: "", //Location ID from http://openweathermap.org/help/city_list.txt
appid: "abcde12345abcde12345abcde12345ab" //openweathermap.org API key.
}
}
]
````
## Configuration options
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](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)
| `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.)
| `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`
| `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 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 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
iconTable: {
'01d': 'wi-day-sunny',
'02d': 'wi-day-cloudy',
'03d': 'wi-cloudy',
'04d': 'wi-cloudy-windy',
'09d': 'wi-showers',
'10d': 'wi-rain',
'11d': 'wi-thunderstorm',
'13d': 'wi-snow',
'50d': 'wi-fog',
'01n': 'wi-night-clear',
'02n': 'wi-night-cloudy',
'03n': 'wi-night-cloudy',
'04n': 'wi-night-cloudy',
'09n': 'wi-night-showers',
'10n': 'wi-night-rain',
'11n': 'wi-night-thunderstorm',
'13n': 'wi-night-snow',
'50n': 'wi-night-alt-cloudy-windy'
}
````
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weatherforecast.html).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -19,9 +19,9 @@
}
.weatherforecast tr.colored .min-temp {
color: #BCDDFF;
color: #BCDDFF;
}
.weatherforecast tr.colored .max-temp {
color: #FF8E99;
color: #FF8E99;
}

View File

@@ -82,7 +82,7 @@ Module.register("weatherforecast",{
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;
},
@@ -180,9 +180,9 @@ Module.register("weatherforecast",{
rainCell.innerHTML = "";
} else {
if(config.units !== "imperial") {
rainCell.innerHTML = parseFloat(forecast.rain).toFixed(1) + " mm";
rainCell.innerHTML = parseFloat(forecast.rain).toFixed(1).replace(".", this.config.decimalSymbol) + " mm";
} else {
rainCell.innerHTML = (parseFloat(forecast.rain) / 25.4).toFixed(2) + " in";
rainCell.innerHTML = (parseFloat(forecast.rain) / 25.4).toFixed(2).replace(".", this.config.decimalSymbol) + " in";
}
}
rainCell.className = "align-right bright rain";
@@ -240,7 +240,7 @@ Module.register("weatherforecast",{
/* updateWeather(compliments)
* Requests new data from openweather.org.
* Calls processWeather on succesfull response.
* Calls processWeather on successful response.
*/
updateWeather: function() {
if (this.config.appid === "") {
@@ -261,7 +261,7 @@ Module.register("weatherforecast",{
} else if (this.status === 401) {
self.updateDom(self.config.animationSpeed);
if (self.config.forecastEndpoint == "forecast/daily") {
if (self.config.forecastEndpoint === "forecast/daily") {
self.config.forecastEndpoint = "forecast";
Log.warn(self.name + ": Your AppID does not support long term forecasts. Switching to fallback endpoint.");
}
@@ -291,7 +291,7 @@ Module.register("weatherforecast",{
} 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
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location;
} else {
@@ -315,7 +315,7 @@ Module.register("weatherforecast",{
*/
parserDataWeather: function(data) {
if (data.hasOwnProperty("main")) {
data["temp"] = {"min": data.main.temp_min, "max": data.main.temp_max}
data["temp"] = {"min": data.main.temp_min, "max": data.main.temp_max};
}
return data;
},
@@ -330,7 +330,7 @@ Module.register("weatherforecast",{
this.forecast = [];
var lastDay = null;
var forecastData = {}
var forecastData = {};
for (var i = 0, count = data.list.length; i < count; i++) {
@@ -353,7 +353,7 @@ Module.register("weatherforecast",{
icon: this.config.iconTable[forecast.weather[0].icon],
maxTemp: this.roundValue(forecast.temp.max),
minTemp: this.roundValue(forecast.temp.min),
rain: forecast.rain
rain: this.processRain(forecast, data.list)
};
this.forecast.push(forecastData);
@@ -434,5 +434,38 @@ Module.register("weatherforecast",{
roundValue: function(temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
},
/* processRain(forecast, allForecasts)
* Calculates the amount of rain for a whole day even if long term forecasts isn't available for the appid.
*
* When using the the fallback endpoint forecasts are provided in 3h intervals and the rain-property is an object instead of number.
* That object has a property "3h" which contains the amount of rain since the previous forecast in the list.
* This code finds all forecasts that is for the same day and sums the amount of rain and returns that.
*/
processRain: function(forecast, allForecasts) {
//If the amount of rain actually is a number, return it
if (!isNaN(forecast.rain)) {
return forecast.rain;
}
//Find all forecasts that is for the same day
var checkDateTime = (!!forecast.dt_txt) ? moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(forecast.dt, "X");
var daysForecasts = allForecasts.filter(function(item) {
var itemDateTime = (!!item.dt_txt) ? moment(item.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(item.dt, "X");
return itemDateTime.isSame(checkDateTime, "day") && item.rain instanceof Object;
});
//If no rain this day return undefined so it wont be displayed for this day
if (daysForecasts.length == 0) {
return undefined;
}
//Summarize all the rain from the matching days
return daysForecasts.map(function(item) {
return Object.values(item.rain)[0];
}).reduce(function(a, b) {
return a + b;
}, 0);
}
});

8093
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,20 @@
{
"name": "magicmirror",
"version": "2.7.1",
"version": "2.11.0",
"description": "The open source modular smart mirror platform.",
"main": "js/electron.js",
"scripts": {
"start": "sh run-start.sh",
"install": "cd vendor && npm install",
"install-fonts": "cd fonts && npm install",
"postinstall": "sh installers/postinstall/postinstall.sh && npm run install-fonts",
"start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
"server": "node ./serveronly",
"install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error",
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error",
"postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"",
"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",
"test:lint": "grunt --env=test",
"config:check": "node tests/configs/check_config.js",
"lint": "grunt"
"lint": "grunt --env=lint"
},
"repository": {
"type": "git",
@@ -41,37 +43,41 @@
"grunt": "latest",
"grunt-eslint": "latest",
"grunt-jsonlint": "latest",
"grunt-markdownlint": "^1.0.43",
"grunt-markdownlint": "latest",
"grunt-stylelint": "latest",
"grunt-yamllint": "latest",
"http-auth": "^3.2.3",
"jsdom": "^11.6.2",
"jshint": "^2.9.5",
"mocha": "^4.1.0",
"mocha": "^7.0.0",
"mocha-each": "^1.1.0",
"spectron": "^3.8.0",
"stylelint": "^8.4.0",
"mocha-logger": "^1.0.6",
"spectron": "^8.0.0",
"stylelint": "latest",
"stylelint-config-standard": "latest",
"time-grunt": "latest"
},
"optionalDependencies": {
"electron": "^6.1.7"
},
"dependencies": {
"ajv": "6.5.5",
"body-parser": "^1.18.2",
"colors": "^1.1.2",
"electron": "^3.0.13",
"console-stamp": "^0.2.9",
"express": "^4.16.2",
"express-ipfilter": "0.3.1",
"express-ipfilter": "^1.0.1",
"feedme": "latest",
"helmet": "^3.9.0",
"home-path": "^1.0.6",
"helmet": "^3.21.2",
"iconv-lite": "latest",
"mocha-logger": "^1.0.6",
"lodash": "^4.17.15",
"module-alias": "^2.2.2",
"moment": "latest",
"request": "^2.87.0",
"request": "^2.88.0",
"rrule": "^2.6.2",
"rrule-alt": "^2.2.8",
"simple-git": "^1.85.0",
"socket.io": "^2.1.1",
"valid-url": "latest",
"walk": "latest"
"valid-url": "latest"
},
"_moduleAliases": {
"node_helper": "js/node_helper.js"
}
}

View File

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

View File

@@ -1,5 +1,6 @@
var app = require("../js/app.js");
app.start(function(config) {
var bindAddress = config.address ? config.address : "localhost";
console.log("\nReady to go! Please point your browser to: http://" + bindAddress + ":" + config.port);
var httpType = config.useHttps ? "https" : "http";
console.log("\nReady to go! Please point your browser to: " + httpType + "://" + bindAddress + ":" + config.port);
});

View File

@@ -9,14 +9,17 @@
*
*/
var v = require("jshint");
const Linter = require("eslint").Linter;
const linter = new Linter();
const config = require(__dirname + "/../../.eslintrc.json");
var path = require("path");
var fs = require("fs");
var Utils = require(__dirname + "/../../js/utils.js");
/* getConfigFile()
* Return string with path of configuration file
* Check if set by enviroment variable MM_CONFIG_FILE
* Check if set by environment variable MM_CONFIG_FILE
*/
function getConfigFile() {
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
@@ -35,7 +38,7 @@ function checkConfigFile() {
console.error(Utils.colors.error("File not found: "), configFileName);
return;
}
// check permision
// check permission
try {
fs.accessSync(configFileName, fs.F_OK);
} catch (e) {
@@ -50,16 +53,15 @@ function checkConfigFile() {
// 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) {
const messages = linter.verify(data, config);
if (messages.length === 0) {
console.log("Your configuration file doesn't contain syntax errors :)");
return true;
} else {
errors = v.JSHINT.data().errors;
for (idx in errors) {
errors = messages;
for (var idx in errors) {
error = errors[idx];
console.log("Line", error.line, "col", error.character, error.reason);
console.log("Line", error.line, "col", error.column, error.message);
}
}
});
@@ -67,4 +69,4 @@ function checkConfigFile() {
if (process.env.NODE_ENV !== "test") {
checkConfigFile();
};
}

View File

@@ -1,4 +1,4 @@
/* Magic Mirror Test config sample enviroment
/* Magic Mirror Test config sample environment
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.

View File

@@ -0,0 +1,41 @@
/* Magic Mirror Test config compliments with date type
*
* By Rejas
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
},
},
modules: [
{
module: "compliments",
position: "middle_center",
config: {
mockDate: "2020-01-01",
compliments: {
morning: [],
afternoon: [],
evening: [],
"....-01-01": [
"Happy new year!"
]
}
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}

View File

@@ -6,7 +6,6 @@
* MIT Licensed.
*/
var config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -0,0 +1,35 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
},
},
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}

View File

@@ -0,0 +1,40 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
},
},
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000,
useBeaufort: false,
showWindDirectionAsArrow: true,
showHumidity: true,
roundTemp: true,
degreeLabel: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}

View File

@@ -0,0 +1,37 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "imperial",
electronOptions: {
webPreferences: {
nodeIntegration: true,
},
},
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000,
decimalSymbol: ",",
showHumidity: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}

View File

@@ -0,0 +1,37 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
},
},
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
type: "forecast",
location: "Munich",
apiKey: "fake key",
weatherEndpoint: "/forecast/daily",
initialLoadDelay: 3000
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}

View File

@@ -0,0 +1,40 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
},
},
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
type: "forecast",
location: "Munich",
apiKey: "fake key",
weatherEndpoint: "/forecast/daily",
initialLoadDelay: 3000,
showPrecipitationAmount: true,
colored: true,
tableClass: "myTableClass"
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = config;}

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