Merge branch 'develop' into bryanzzhu-weather

This commit is contained in:
Michael Teeuw
2020-09-18 12:15:44 +02:00
committed by GitHub
33 changed files with 767 additions and 467 deletions

View File

@@ -12,7 +12,11 @@
*/
(function (window) {
/**
* extend obj function
* Extend one object with another one
*
* @param {object} a The object to extend
* @param {object} b The object which extends the other, overwrites existing keys
* @returns {object} The merged object
*/
function extend(a, b) {
for (let key in b) {
@@ -24,7 +28,10 @@
}
/**
* NotificationFx function
* NotificationFx constructor
*
* @param {object} options The configuration options
* @class
*/
function NotificationFx(options) {
this.options = extend({}, this.options);
@@ -66,8 +73,7 @@
};
/**
* init function
* initialize and cache some vars
* Initialize and cache some vars
*/
NotificationFx.prototype._init = function () {
// create HTML structure
@@ -95,7 +101,7 @@
};
/**
* init events
* Init events
*/
NotificationFx.prototype._initEvents = function () {
// dismiss notification by tapping on it if someone has a touchscreen
@@ -105,7 +111,7 @@
};
/**
* show the notification
* Show the notification
*/
NotificationFx.prototype.show = function () {
this.active = true;
@@ -115,7 +121,7 @@
};
/**
* dismiss the notification
* Dismiss the notification
*/
NotificationFx.prototype.dismiss = function () {
this.active = false;
@@ -144,7 +150,7 @@
};
/**
* add to global namespace
* Add to global namespace
*/
window.NotificationFx = NotificationFx;
})(window);

View File

@@ -416,7 +416,7 @@ Module.register("calendar", {
* it will a localeSpecification object with the system locale time format.
*
* @param {number} timeFormat Specifies either 12 or 24 hour time format
* @returns {moment.LocaleSpecification}
* @returns {moment.LocaleSpecification} formatted time
*/
getLocaleSpecification: function (timeFormat) {
switch (timeFormat) {
@@ -432,12 +432,11 @@ Module.register("calendar", {
}
},
/* hasCalendarURL(url)
* Check if this config contains the calendar url.
/**
* Checks if this config contains the calendar url.
*
* argument url string - Url to look for.
*
* return bool - Has calendar url
* @param {string} url The calendar url
* @returns {boolean} True if the calendar config contains the url, False otherwise
*/
hasCalendarURL: function (url) {
for (var c in this.config.calendars) {
@@ -450,10 +449,10 @@ Module.register("calendar", {
return false;
},
/* createEventList()
/**
* Creates the sorted list of all events.
*
* return array - Array with events.
* @returns {object[]} Array with events.
*/
createEventList: function () {
var events = [];
@@ -534,10 +533,12 @@ Module.register("calendar", {
return false;
},
/* createEventList(url)
/**
* Requests node helper to add calendar url.
*
* argument url string - Url to add.
* @param {string} url The calendar url to add
* @param {object} auth The authentication method and credentials
* @param {object} calendarConfig The config of the specific calendar
*/
addCalendar: function (url, auth, calendarConfig) {
this.sendSocketNotification("ADD_CALENDAR", {
@@ -556,12 +557,10 @@ Module.register("calendar", {
},
/**
* symbolsForEvent(event)
* Retrieves the symbols for a specific event.
*
* argument event object - Event to look for.
*
* return array - The Symbols
* @param {object} event Event to look for.
* @returns {string[]} The symbols
*/
symbolsForEvent: function (event) {
let symbols = this.getCalendarPropertyAsArray(event.url, "symbol", this.config.defaultSymbol);
@@ -586,82 +585,72 @@ Module.register("calendar", {
},
/**
* symbolClassForUrl(url)
* Retrieves the symbolClass for a specific url.
* Retrieves the symbolClass for a specific calendar url.
*
* @param url string - Url to look for.
*
* @returns string
* @param {string} url The calendar url
* @returns {string} The class to be used for the symbols of the calendar
*/
symbolClassForUrl: function (url) {
return this.getCalendarProperty(url, "symbolClass", "");
},
/**
* titleClassForUrl(url)
* Retrieves the titleClass for a specific url.
* Retrieves the titleClass for a specific calendar url.
*
* @param url string - Url to look for.
*
* @returns string
* @param {string} url The calendar url
* @returns {string} The class to be used for the title of the calendar
*/
titleClassForUrl: function (url) {
return this.getCalendarProperty(url, "titleClass", "");
},
/**
* timeClassForUrl(url)
* Retrieves the timeClass for a specific url.
* Retrieves the timeClass for a specific calendar url.
*
* @param url string - Url to look for.
*
* @returns string
* @param {string} url The calendar url
* @returns {string} The class to be used for the time of the calendar
*/
timeClassForUrl: function (url) {
return this.getCalendarProperty(url, "timeClass", "");
},
/* calendarNameForUrl(url)
* Retrieves the calendar name for a specific url.
/**
* Retrieves the calendar name for a specific calendar url.
*
* argument url string - Url to look for.
*
* return string - The name of the calendar
* @param {string} url The calendar url
* @returns {string} The name of the calendar
*/
calendarNameForUrl: function (url) {
return this.getCalendarProperty(url, "name", "");
},
/* colorForUrl(url)
* Retrieves the color for a specific url.
/**
* Retrieves the color for a specific calendar url.
*
* argument url string - Url to look for.
*
* return string - The Color
* @param {string} url The calendar url
* @returns {string} The color
*/
colorForUrl: function (url) {
return this.getCalendarProperty(url, "color", "#fff");
},
/* countTitleForUrl(url)
* Retrieves the name for a specific url.
/**
* Retrieves the count title for a specific calendar url.
*
* argument url string - Url to look for.
*
* return string - The Symbol
* @param {string} url The calendar url
* @returns {string} The title
*/
countTitleForUrl: function (url) {
return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
},
/* getCalendarProperty(url, property, defaultValue)
* Helper method to retrieve the property for a specific url.
/**
* Helper method to retrieve the property for a specific calendar url.
*
* argument url string - Url to look for.
* argument property string - Property to look for.
* argument defaultValue string - Value if property is not found.
*
* return string - The Property
* @param {string} url The calendar url
* @param {string} property The property to look for
* @param {string} defaultValue The value if the property is not found
* @returns {*} The property
*/
getCalendarProperty: function (url, property, defaultValue) {
for (var c in this.config.calendars) {
@@ -737,22 +726,27 @@ Module.register("calendar", {
}
},
/* capFirst(string)
/**
* Capitalize the first letter of a string
* Return capitalized string
*
* @param {string} string The string to capitalize
* @returns {string} The capitalized string
*/
capFirst: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
/* titleTransform(title)
/**
* Transforms the title of an event for usage.
* Replaces parts of the text as defined in config.titleReplace.
* Shortens title based on config.maxTitleLength and config.wrapEvents
*
* argument title string - The title to transform.
*
* return string - The transformed title.
* @param {string} title The title to transform.
* @param {object} titleReplace Pairs of strings to be replaced in the title
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
* @param {number} maxTitleLength The max length of the string
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
* @returns {string} The transformed title.
*/
titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
for (var needle in titleReplace) {
@@ -771,7 +765,7 @@ Module.register("calendar", {
return title;
},
/* broadcastEvents()
/**
* Broadcasts the events to all other modules for reuse.
* The all events available in one array, sorted on startdate.
*/

View File

@@ -6,9 +6,27 @@
*/
const Log = require("../../../js/logger.js");
const ical = require("ical");
const moment = require("moment");
const request = require("request");
/**
* Moment date
*
* @external Moment
* @see {@link http://momentjs.com}
*/
const moment = require("moment");
/**
*
* @param {string} url The url of the calendar to fetch
* @param {number} reloadInterval Time in ms the calendar is fetched again
* @param {string[]} excludedEvents An array of words / phrases from event titles that will be excluded from being shown.
* @param {number} maximumEntries The maximum number of events fetched.
* @param {number} maximumNumberOfDays The maximum number of days an event should be in the future.
* @param {object} auth The object containing options for authentication against the calendar.
* @param {boolean} includePastEvents If true events from the past maximumNumberOfDays will be fetched too
* @class
*/
const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
const self = this;
@@ -18,7 +36,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
let fetchFailedCallback = function () {};
let eventsReceivedCallback = function () {};
/* fetchCalendar()
/**
* Initiates calendar fetch.
*/
const fetchCalendar = function () {
@@ -184,8 +202,16 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
// 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
const pastLocal = pastMoment.subtract(past.getTimezoneOffset(), "minutes").toDate();
const futureLocal = futureMoment.subtract(future.getTimezoneOffset(), "minutes").toDate();
let pastLocal = 0;
let futureLocal = 0;
if (isFullDayEvent(event)) {
// if full day event, only use the date part of the ranges
pastLocal = pastMoment.toDate();
futureLocal = futureMoment.toDate();
} else {
pastLocal = pastMoment.subtract(past.getTimezoneOffset(), "minutes").toDate();
futureLocal = futureMoment.subtract(future.getTimezoneOffset(), "minutes").toDate();
}
const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
// The "dates" array contains the set of dates within our desired date range range that are valid
@@ -214,6 +240,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
const dateKey = date.toISOString().substring(0, 10);
let curEvent = event;
let showRecurrence = true;
let duration = 0;
startDate = moment(date);
@@ -325,7 +352,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
});
};
/* scheduleTimer()
/**
* Schedule the timer for the next update.
*/
const scheduleTimer = function () {
@@ -335,12 +362,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
}, reloadInterval);
};
/* isFullDayEvent(event)
/**
* Checks if an event is a fullday event.
*
* argument event object - The event object to check.
*
* return bool - The event is a fullday event.
* @param {object} event The event object to check.
* @returns {boolean} True if the event is a fullday event, false otherwise
*/
const isFullDayEvent = function (event) {
if (event.start.length === 8 || event.start.dateOnly) {
@@ -358,14 +384,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
return false;
};
/* timeFilterApplies()
/**
* Determines if the user defined time filter should apply
*
* argument now Date - Date object using previously created object for consistency
* argument endDate Moment - Moment object representing the event end date
* argument filter string - The time to subtract from the end date to determine if an event should be shown
*
* return bool - The event should be filtered out
* @param {Date} now Date object using previously created object for consistency
* @param {Moment} endDate Moment object representing the event end date
* @param {string} filter The time to subtract from the end date to determine if an event should be shown
* @returns {boolean} True if the event should be filtered out, false otherwise
*/
const timeFilterApplies = function (now, endDate, filter) {
if (filter) {
@@ -380,12 +405,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
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.
* @param {object} event The event object to check.
* @returns {string} The title of the event, or "Event" if no title is found.
*/
const getTitleFromEvent = function (event) {
let title = "Event";
@@ -416,14 +440,14 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
/* public methods */
/* startFetch()
/**
* Initiate fetchCalendar();
*/
this.startFetch = function () {
fetchCalendar();
};
/* broadcastItems()
/**
* Broadcast the existing events.
*/
this.broadcastEvents = function () {
@@ -431,37 +455,37 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
eventsReceivedCallback(self);
};
/* onReceive(callback)
/**
* Sets the on success callback
*
* argument callback function - The on success callback.
* @param {Function} callback The on success callback.
*/
this.onReceive = function (callback) {
eventsReceivedCallback = callback;
};
/* onError(callback)
/**
* Sets the on error callback
*
* argument callback function - The on error callback.
* @param {Function} callback The on error callback.
*/
this.onError = function (callback) {
fetchFailedCallback = callback;
};
/* url()
/**
* Returns the url of this fetcher.
*
* return string - The url of this fetcher.
* @returns {string} The url of this fetcher.
*/
this.url = function () {
return url;
};
/* events()
/**
* Returns current available events for this fetcher.
*
* return array - The current available events for this fetcher.
* @returns {object[]} The current available events for this fetcher.
*/
this.events = function () {
return events;

View File

@@ -4,7 +4,6 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
const NodeHelper = require("node_helper");
const validUrl = require("valid-url");
const CalendarFetcher = require("./calendarfetcher.js");
@@ -24,12 +23,18 @@ module.exports = NodeHelper.create({
}
},
/* createFetcher(url, reloadInterval)
/**
* Creates a fetcher for a new url if it doesn't exist yet.
* Otherwise it reuses the existing one.
*
* attribute url string - URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds.
* @param {string} url The url of the calendar
* @param {number} fetchInterval How often does the calendar needs to be fetched in ms
* @param {string[]} excludedEvents An array of words / phrases from event titles that will be excluded from being shown.
* @param {number} maximumEntries The maximum number of events fetched.
* @param {number} maximumNumberOfDays The maximum number of days an event should be in the future.
* @param {object} auth The object containing options for authentication against the calendar.
* @param {boolean} broadcastPastEvents If true events from the past maximumNumberOfDays will be included in event broadcasts
* @param {string} identifier ID of the module
*/
createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, identifier) {
var self = this;

View File

@@ -152,6 +152,13 @@ Module.register("clock", {
timeWrapper.appendChild(periodWrapper);
}
/**
* Format the time according to the config
*
* @param {object} config The config of the module
* @param {object} time time to format
* @returns {string} The formatted time string
*/
function formatTime(config, time) {
var formatString = hourSymbol + ":mm";
if (config.showPeriod && config.timeFormat !== 24) {
@@ -159,6 +166,7 @@ Module.register("clock", {
}
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);

View File

@@ -37,6 +37,8 @@ Module.register("currentweather", {
weatherEndpoint: "weather",
appendLocationNameToHeader: true,
useLocationAsHeader: false,
calendarClass: "calendar",
tableClass: "large",
@@ -267,15 +269,16 @@ Module.register("currentweather", {
// Override getHeader method.
getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined) {
return this.data.header + " " + this.fetchedLocationName;
}
if (this.config.useLocationAsHeader && this.config.location !== false) {
return this.config.location;
}
return this.data.header;
if (this.config.appendLocationNameToHeader) {
if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
else return this.fetchedLocationName;
}
return this.data.header ? this.data.header : "";
},
// Override notification handler.

View File

@@ -205,8 +205,8 @@ Module.register("newsfeed", {
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
},
/* registerFeeds()
* registers the feeds to be used by the backend.
/**
* Registers the feeds to be used by the backend.
*/
registerFeeds: function () {
for (var f in this.config.feeds) {
@@ -218,10 +218,10 @@ Module.register("newsfeed", {
}
},
/* generateFeed()
/**
* Generate an ordered list of items for this configured module.
*
* attribute feeds object - An object with feeds returned by the node helper.
* @param {object} feeds An object with feeds returned by the node helper.
*/
generateFeed: function (feeds) {
var newsItems = [];
@@ -274,12 +274,11 @@ Module.register("newsfeed", {
this.newsItems = newsItems;
},
/* subscribedToFeed(feedUrl)
/**
* Check if this module is configured to show this feed.
*
* attribute feedUrl string - Url of the feed to check.
*
* returns bool
* @param {string} feedUrl Url of the feed to check.
* @returns {boolean} True if it is subscribed, false otherwise
*/
subscribedToFeed: function (feedUrl) {
for (var f in this.config.feeds) {
@@ -291,12 +290,11 @@ Module.register("newsfeed", {
return false;
},
/* titleForFeed(feedUrl)
* Returns title for a specific feed Url.
/**
* Returns title for the specific feed url.
*
* attribute feedUrl string - Url of the feed to check.
*
* returns string
* @param {string} feedUrl Url of the feed
* @returns {string} The title of the feed
*/
titleForFeed: function (feedUrl) {
for (var f in this.config.feeds) {
@@ -308,7 +306,7 @@ Module.register("newsfeed", {
return "";
},
/* scheduleUpdateInterval()
/**
* Schedule visual update.
*/
scheduleUpdateInterval: function () {

View File

@@ -9,12 +9,14 @@ const FeedMe = require("feedme");
const request = require("request");
const iconv = require("iconv-lite");
/* Fetcher
/**
* Responsible for requesting an update on the set interval and broadcasting the data.
*
* attribute url string - URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds.
* attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article.
* @param {string} url URL of the news feed.
* @param {number} reloadInterval Reload interval in milliseconds.
* @param {string} encoding Encoding of the feed.
* @param {boolean} logFeedWarnings If true log warnings when there is an error parsing a news article.
* @class
*/
const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
const self = this;
@@ -31,7 +33,7 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
/* private methods */
/* fetchNews()
/**
* Request the new items.
*/
const fetchNews = function () {
@@ -95,7 +97,7 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
.pipe(parser);
};
/* scheduleTimer()
/**
* Schedule the timer for the next update.
*/
const scheduleTimer = function () {
@@ -107,10 +109,10 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
/* public methods */
/* setReloadInterval()
/**
* Update the reload interval, but only if we need to increase the speed.
*
* attribute interval number - Interval for the update in milliseconds.
* @param {number} interval Interval for the update in milliseconds.
*/
this.setReloadInterval = function (interval) {
if (interval > 1000 && interval < reloadInterval) {
@@ -118,14 +120,14 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
}
};
/* startFetch()
/**
* Initiate fetchNews();
*/
this.startFetch = function () {
fetchNews();
};
/* broadcastItems()
/**
* Broadcast the existing items.
*/
this.broadcastItems = function () {

View File

@@ -24,12 +24,12 @@ module.exports = NodeHelper.create({
}
},
/* createFetcher(feed, config)
/**
* Creates a fetcher for a new feed if it doesn't exist yet.
* Otherwise it reuses the existing one.
*
* attribute feed object - A feed object.
* attribute config object - A configuration object containing reload interval in milliseconds.
* @param {object} feed The feed object.
* @param {object} config The configuration object.
*/
createFetcher: function (feed, config) {
const url = feed.url || "";
@@ -68,7 +68,7 @@ module.exports = NodeHelper.create({
fetcher.startFetch();
},
/* broadcastFeeds()
/**
* Creates an object with all feed items of the different registered feeds,
* and broadcasts these using sendSocketNotification.
*/

View File

@@ -72,7 +72,7 @@ module.exports = NodeHelper.create({
performFetch: function () {
var self = this;
simpleGits.forEach((sg) => {
sg.git.fetch().status((err, data) => {
sg.git.fetch(["--dry-run"]).status((err, data) => {
data.module = sg.module;
if (!err) {
sg.git.log({ "-1": null }, (err, data2) => {

View File

@@ -1,5 +1,5 @@
# Weather Module
This module aims 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 aims 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 fulfill both purposes.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html).

View File

@@ -76,11 +76,12 @@ Module.register("weather", {
// Override getHeader method.
getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined && this.weatherProvider) {
return this.data.header + " " + this.weatherProvider.fetchedLocation();
if (this.config.appendLocationNameToHeader && this.weatherProvider) {
if (this.data.header) return this.data.header + " " + this.weatherProvider.fetchedLocation();
else return this.weatherProvider.fetchedLocation();
}
return this.data.header;
return this.data.header ? this.data.header : "";
},
// Start the weather module.

View File

@@ -12,7 +12,7 @@ var WeatherProvider = Class.extend({
// Weather Provider Properties
providerName: null,
// The following properties have accestor methods.
// The following properties have accessor methods.
// Try to not access them directly.
currentWeatherObject: null,
weatherForecastArray: null,
@@ -136,6 +136,9 @@ WeatherProvider.providers = [];
/**
* Static method to register a new weather provider.
*
* @param {string} providerIdentifier The name of the weather provider
* @param {object} providerDetails The details of the weather provider
*/
WeatherProvider.register = function (providerIdentifier, providerDetails) {
WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails);
@@ -143,6 +146,10 @@ WeatherProvider.register = function (providerIdentifier, providerDetails) {
/**
* Static method to initialize a new weather provider.
*
* @param {string} providerIdentifier The name of the weather provider
* @param {object} delegate The weather module
* @returns {object} The new weather provider
*/
WeatherProvider.initialize = function (providerIdentifier, delegate) {
providerIdentifier = providerIdentifier.toLowerCase();

View File

@@ -103,7 +103,7 @@ Module.register("weatherforecast", {
getDom: function () {
var wrapper = document.createElement("div");
if (this.config.appid === "") {
if (this.config.appid === "" || this.config.appid === "YOUR_OPENWEATHER_API_KEY") {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
wrapper.className = "dimmed light small";
return wrapper;
@@ -206,10 +206,11 @@ Module.register("weatherforecast", {
// Override getHeader method.
getHeader: function () {
if (this.config.appendLocationNameToHeader) {
return this.data.header + " " + this.fetchedLocationName;
if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
else return this.fetchedLocationName;
}
return this.data.header;
return this.data.header ? this.data.header : "";
},
// Override notification handler.