Merge branch 'develop' into pr/ZoneMR/2061
@@ -1,4 +1,5 @@
|
||||
# Module: Alert
|
||||
|
||||
The alert module is one of the default modules of the MagicMirror. This module displays notifications from other modules.
|
||||
|
||||
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/alert.html).
|
||||
|
@@ -1,13 +1,12 @@
|
||||
/* global Module */
|
||||
/* global NotificationFx */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: alert
|
||||
*
|
||||
* By Paul-Vincent Roll http://paulvincentroll.com
|
||||
* By Paul-Vincent Roll https://paulvincentroll.com/
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("alert",{
|
||||
Module.register("alert", {
|
||||
defaults: {
|
||||
// scale|slide|genie|jelly|flip|bouncyflip|exploader
|
||||
effect: "slide",
|
||||
@@ -18,31 +17,33 @@ Module.register("alert",{
|
||||
//Position
|
||||
position: "center",
|
||||
//shown at startup
|
||||
welcome_message: false,
|
||||
welcome_message: false
|
||||
},
|
||||
getScripts: function() {
|
||||
return ["classie.js", "modernizr.custom.js", "notificationFx.js"];
|
||||
getScripts: function () {
|
||||
return ["notificationFx.js"];
|
||||
},
|
||||
getStyles: function() {
|
||||
return ["ns-default.css", "font-awesome.css"];
|
||||
getStyles: function () {
|
||||
return ["notificationFx.css", "font-awesome.css"];
|
||||
},
|
||||
// Define required translations.
|
||||
getTranslations: function() {
|
||||
getTranslations: function () {
|
||||
return {
|
||||
en: "translations/en.json",
|
||||
de: "translations/de.json",
|
||||
nl: "translations/nl.json",
|
||||
nl: "translations/nl.json"
|
||||
};
|
||||
},
|
||||
show_notification: function(message) {
|
||||
if (this.config.effect === "slide") {this.config.effect = this.config.effect + "-" + this.config.position;}
|
||||
msg = "";
|
||||
show_notification: function (message) {
|
||||
if (this.config.effect === "slide") {
|
||||
this.config.effect = this.config.effect + "-" + this.config.position;
|
||||
}
|
||||
let msg = "";
|
||||
if (message.title) {
|
||||
msg += "<span class='thin dimmed medium'>" + message.title + "</span>";
|
||||
}
|
||||
if (message.message){
|
||||
if (msg !== ""){
|
||||
msg+= "<br />";
|
||||
if (message.message) {
|
||||
if (msg !== "") {
|
||||
msg += "<br />";
|
||||
}
|
||||
msg += "<span class='light bright small'>" + message.message + "</span>";
|
||||
}
|
||||
@@ -54,23 +55,26 @@ Module.register("alert",{
|
||||
ttl: message.timer !== undefined ? message.timer : this.config.display_time
|
||||
}).show();
|
||||
},
|
||||
show_alert: function(params, sender) {
|
||||
var self = this;
|
||||
show_alert: function (params, sender) {
|
||||
let image = "";
|
||||
//Set standard params if not provided by module
|
||||
if (typeof params.timer === "undefined") { params.timer = null; }
|
||||
if (typeof params.imageHeight === "undefined") { params.imageHeight = "80px"; }
|
||||
if (typeof params.timer === "undefined") {
|
||||
params.timer = null;
|
||||
}
|
||||
if (typeof params.imageHeight === "undefined") {
|
||||
params.imageHeight = "80px";
|
||||
}
|
||||
if (typeof params.imageUrl === "undefined" && typeof params.imageFA === "undefined") {
|
||||
params.imageUrl = null;
|
||||
image = "";
|
||||
} else if (typeof params.imageFA === "undefined"){
|
||||
image = "<img src='" + (params.imageUrl).toString() + "' height='" + (params.imageHeight).toString() + "' style='margin-bottom: 10px;'/><br />";
|
||||
} else if (typeof params.imageUrl === "undefined"){
|
||||
image = "<span class='bright " + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;font-size:" + (params.imageHeight).toString() + ";'/></span><br />";
|
||||
} else if (typeof params.imageFA === "undefined") {
|
||||
image = "<img src='" + params.imageUrl.toString() + "' height='" + params.imageHeight.toString() + "' style='margin-bottom: 10px;'/><br />";
|
||||
} else if (typeof params.imageUrl === "undefined") {
|
||||
image = "<span class='bright " + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;font-size:" + params.imageHeight.toString() + ";'/></span><br />";
|
||||
}
|
||||
//Create overlay
|
||||
var overlay = document.createElement("div");
|
||||
const overlay = document.createElement("div");
|
||||
overlay.id = "overlay";
|
||||
overlay.innerHTML += "<div class=\"black_overlay\"></div>";
|
||||
overlay.innerHTML += '<div class="black_overlay"></div>';
|
||||
document.body.insertBefore(overlay, document.body.firstChild);
|
||||
|
||||
//If module already has an open alert close it
|
||||
@@ -79,12 +83,12 @@ Module.register("alert",{
|
||||
}
|
||||
|
||||
//Display title and message only if they are provided in notification parameters
|
||||
var message = "";
|
||||
let message = "";
|
||||
if (params.title) {
|
||||
message += "<span class='light dimmed medium'>" + params.title + "</span>";
|
||||
}
|
||||
if (params.message) {
|
||||
if (message !== ""){
|
||||
if (message !== "") {
|
||||
message += "<br />";
|
||||
}
|
||||
|
||||
@@ -102,34 +106,40 @@ Module.register("alert",{
|
||||
this.alerts[sender.name].show();
|
||||
//Add timer to dismiss alert and overlay
|
||||
if (params.timer) {
|
||||
setTimeout(function() {
|
||||
self.hide_alert(sender);
|
||||
setTimeout(() => {
|
||||
this.hide_alert(sender);
|
||||
}, params.timer);
|
||||
}
|
||||
|
||||
},
|
||||
hide_alert: function(sender) {
|
||||
hide_alert: function (sender) {
|
||||
//Dismiss alert and remove from this.alerts
|
||||
if (this.alerts[sender.name]) {
|
||||
this.alerts[sender.name].dismiss();
|
||||
this.alerts[sender.name] = null;
|
||||
//Remove overlay
|
||||
var overlay = document.getElementById("overlay");
|
||||
const overlay = document.getElementById("overlay");
|
||||
overlay.parentNode.removeChild(overlay);
|
||||
}
|
||||
},
|
||||
setPosition: function(pos) {
|
||||
setPosition: function (pos) {
|
||||
//Add css to body depending on the set position for notifications
|
||||
var sheet = document.createElement("style");
|
||||
if (pos === "center") {sheet.innerHTML = ".ns-box {margin-left: auto; margin-right: auto;text-align: center;}";}
|
||||
if (pos === "right") {sheet.innerHTML = ".ns-box {margin-left: auto;text-align: right;}";}
|
||||
if (pos === "left") {sheet.innerHTML = ".ns-box {margin-right: auto;text-align: left;}";}
|
||||
const sheet = document.createElement("style");
|
||||
if (pos === "center") {
|
||||
sheet.innerHTML = ".ns-box {margin-left: auto; margin-right: auto;text-align: center;}";
|
||||
}
|
||||
if (pos === "right") {
|
||||
sheet.innerHTML = ".ns-box {margin-left: auto;text-align: right;}";
|
||||
}
|
||||
if (pos === "left") {
|
||||
sheet.innerHTML = ".ns-box {margin-right: auto;text-align: left;}";
|
||||
}
|
||||
document.body.appendChild(sheet);
|
||||
|
||||
},
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
if (notification === "SHOW_ALERT") {
|
||||
if (typeof payload.type === "undefined") { payload.type = "alert"; }
|
||||
if (typeof payload.type === "undefined") {
|
||||
payload.type = "alert";
|
||||
}
|
||||
if (payload.type === "alert") {
|
||||
this.show_alert(payload, sender);
|
||||
} else if (payload.type === "notification") {
|
||||
@@ -139,15 +149,14 @@ Module.register("alert",{
|
||||
this.hide_alert(sender);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
start: function () {
|
||||
this.alerts = {};
|
||||
this.setPosition(this.config.position);
|
||||
if (this.config.welcome_message) {
|
||||
if (this.config.welcome_message === true){
|
||||
this.show_notification({title: this.translate("sysTitle"), message: this.translate("welcome")});
|
||||
}
|
||||
else{
|
||||
this.show_notification({title: this.translate("sysTitle"), message: this.config.welcome_message});
|
||||
if (this.config.welcome_message === true) {
|
||||
this.show_notification({ title: this.translate("sysTitle"), message: this.translate("welcome") });
|
||||
} else {
|
||||
this.show_notification({ title: this.translate("sysTitle"), message: this.config.welcome_message });
|
||||
}
|
||||
}
|
||||
Log.info("Starting module: " + this.name);
|
||||
|
@@ -1,79 +0,0 @@
|
||||
/*!
|
||||
* classie - class helper functions
|
||||
* from bonzo https://github.com/ded/bonzo
|
||||
*
|
||||
* classie.has( elem, 'my-class' ) -> true/false
|
||||
* classie.add( elem, 'my-new-class' )
|
||||
* classie.remove( elem, 'my-unwanted-class' )
|
||||
* classie.toggle( elem, 'my-class' )
|
||||
*/
|
||||
// jscs:disable
|
||||
/*jshint browser: true, strict: true, undef: true */
|
||||
/*global define: false */
|
||||
|
||||
(function(window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
// class helper functions from bonzo https://github.com/ded/bonzo
|
||||
|
||||
function classReg(className) {
|
||||
return new RegExp("(^|\\s+)" + className + "(\\s+|$)");
|
||||
}
|
||||
|
||||
// classList support for class management
|
||||
// altho to be fair, the api sucks because it won't accept multiple classes at once
|
||||
var hasClass, addClass, removeClass;
|
||||
|
||||
if ("classList" in document.documentElement) {
|
||||
hasClass = function(elem, c) {
|
||||
return elem.classList.contains(c);
|
||||
};
|
||||
addClass = function(elem, c) {
|
||||
elem.classList.add(c);
|
||||
};
|
||||
removeClass = function(elem, c) {
|
||||
elem.classList.remove(c);
|
||||
};
|
||||
} else {
|
||||
hasClass = function(elem, c) {
|
||||
return classReg(c).test(elem.className);
|
||||
};
|
||||
addClass = function(elem, c) {
|
||||
if (!hasClass(elem, c)) {
|
||||
elem.className = elem.className + " " + c;
|
||||
}
|
||||
};
|
||||
removeClass = function(elem, c) {
|
||||
elem.className = elem.className.replace(classReg(c), " ");
|
||||
};
|
||||
}
|
||||
|
||||
function toggleClass(elem, c) {
|
||||
var fn = hasClass(elem, c) ? removeClass : addClass;
|
||||
fn(elem, c);
|
||||
}
|
||||
|
||||
var classie = {
|
||||
// full names
|
||||
hasClass: hasClass,
|
||||
addClass: addClass,
|
||||
removeClass: removeClass,
|
||||
toggleClass: toggleClass,
|
||||
// short names
|
||||
has: hasClass,
|
||||
add: addClass,
|
||||
remove: removeClass,
|
||||
toggle: toggleClass
|
||||
};
|
||||
|
||||
// transport
|
||||
if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(classie);
|
||||
} else {
|
||||
// browser global
|
||||
window.classie = classie;
|
||||
}
|
||||
|
||||
})(window);
|
933
modules/default/alert/notificationFx.css
Normal file
@@ -0,0 +1,933 @@
|
||||
/* Based on work by https://tympanus.net/codrops/licensing/ */
|
||||
|
||||
.ns-box {
|
||||
background-color: rgba(0, 0, 0, 0.93);
|
||||
padding: 17px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 10px;
|
||||
z-index: 1;
|
||||
color: black;
|
||||
font-size: 70%;
|
||||
position: relative;
|
||||
display: table;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
border-width: 1px;
|
||||
border-radius: 5px;
|
||||
border-style: solid;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
.ns-alert {
|
||||
border-style: solid;
|
||||
border-color: #fff;
|
||||
padding: 17px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 10px;
|
||||
z-index: 3;
|
||||
color: white;
|
||||
font-size: 70%;
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
right: 0;
|
||||
left: 0;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
top: 40%;
|
||||
width: 40%;
|
||||
height: auto;
|
||||
word-wrap: break-word;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.black_overlay {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
background-color: rgba(0, 0, 0, 0.93);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[class^="ns-effect-"].ns-growl.ns-hide,
|
||||
[class*=" ns-effect-"].ns-growl.ns-hide {
|
||||
animation-direction: reverse;
|
||||
}
|
||||
|
||||
.ns-effect-flip {
|
||||
transform-origin: 50% 100%;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.ns-effect-flip.ns-show,
|
||||
.ns-effect-flip.ns-hide {
|
||||
animation-name: animFlipFront;
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
|
||||
.ns-effect-flip.ns-hide {
|
||||
animation-name: animFlipBack;
|
||||
}
|
||||
|
||||
@keyframes animFlipFront {
|
||||
0% {
|
||||
transform: perspective(1000px) rotate3d(1, 0, 0, -90deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(1000px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animFlipBack {
|
||||
0% {
|
||||
transform: perspective(1000px) rotate3d(1, 0, 0, 90deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(1000px);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-bouncyflip.ns-show,
|
||||
.ns-effect-bouncyflip.ns-hide {
|
||||
animation-name: flipInX;
|
||||
animation-duration: 0.8s;
|
||||
}
|
||||
|
||||
@keyframes flipInX {
|
||||
0% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, -10deg);
|
||||
transition-timing-function: ease-in;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, 5deg);
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(400px);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-bouncyflip.ns-hide {
|
||||
animation-name: flipInXSimple;
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
|
||||
@keyframes flipInXSimple {
|
||||
0% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(400px);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-exploader {
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.ns-effect-exploader p {
|
||||
padding: 0.25em 2em 0.25em 3em;
|
||||
}
|
||||
|
||||
.ns-effect-exploader.ns-show {
|
||||
animation-name: animLoad;
|
||||
animation-duration: 1s;
|
||||
}
|
||||
|
||||
@keyframes animLoad {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale3d(0, 0.3, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-exploader.ns-hide {
|
||||
animation-name: animFade;
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
|
||||
.ns-effect-exploader.ns-show .ns-box-inner,
|
||||
.ns-effect-exploader.ns-show .ns-close {
|
||||
animation-fill-mode: both;
|
||||
animation-duration: 0.3s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.ns-effect-exploader.ns-show .ns-close {
|
||||
animation-name: animFade;
|
||||
}
|
||||
|
||||
.ns-effect-exploader.ns-show .ns-box-inner {
|
||||
animation-name: animFadeMove;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
@keyframes animFadeMove {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 10px, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animFade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-scale.ns-show,
|
||||
.ns-effect-scale.ns-hide {
|
||||
animation-name: animScale;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
@keyframes animScale {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 40px, 0) scale3d(0.1, 0.6, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0) scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-jelly.ns-show {
|
||||
animation-name: animJelly;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
.ns-effect-jelly.ns-hide {
|
||||
animation-name: animFade;
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
|
||||
@keyframes animFade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animJelly {
|
||||
0% {
|
||||
transform: matrix3d(0.7, 0, 0, 0, 0, 0.7, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
2.083333% {
|
||||
transform: matrix3d(0.75266, 0, 0, 0, 0, 0.76342, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
4.166667% {
|
||||
transform: matrix3d(0.81071, 0, 0, 0, 0, 0.84545, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
6.25% {
|
||||
transform: matrix3d(0.86808, 0, 0, 0, 0, 0.9286, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
8.333333% {
|
||||
transform: matrix3d(0.92038, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
10.416667% {
|
||||
transform: matrix3d(0.96482, 0, 0, 0, 0, 1.05202, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
12.5% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1.08204, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
14.583333% {
|
||||
transform: matrix3d(1.02563, 0, 0, 0, 0, 1.09149, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
16.666667% {
|
||||
transform: matrix3d(1.04227, 0, 0, 0, 0, 1.08453, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
18.75% {
|
||||
transform: matrix3d(1.05102, 0, 0, 0, 0, 1.06666, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
20.833333% {
|
||||
transform: matrix3d(1.05334, 0, 0, 0, 0, 1.04355, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
22.916667% {
|
||||
transform: matrix3d(1.05078, 0, 0, 0, 0, 1.02012, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: matrix3d(1.04487, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
27.083333% {
|
||||
transform: matrix3d(1.03699, 0, 0, 0, 0, 0.98534, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
29.166667% {
|
||||
transform: matrix3d(1.02831, 0, 0, 0, 0, 0.97688, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
31.25% {
|
||||
transform: matrix3d(1.01973, 0, 0, 0, 0, 0.97422, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
33.333333% {
|
||||
transform: matrix3d(1.01191, 0, 0, 0, 0, 0.97618, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
35.416667% {
|
||||
transform: matrix3d(1.00526, 0, 0, 0, 0, 0.98122, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
37.5% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.98773, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
39.583333% {
|
||||
transform: matrix3d(0.99617, 0, 0, 0, 0, 0.99433, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
41.666667% {
|
||||
transform: matrix3d(0.99368, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
43.75% {
|
||||
transform: matrix3d(0.99237, 0, 0, 0, 0, 1.00413, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
45.833333% {
|
||||
transform: matrix3d(0.99202, 0, 0, 0, 0, 1.00651, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
47.916667% {
|
||||
transform: matrix3d(0.99241, 0, 0, 0, 0, 1.00726, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: matrix3d(0.99329, 0, 0, 0, 0, 1.00671, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
52.083333% {
|
||||
transform: matrix3d(0.99447, 0, 0, 0, 0, 1.00529, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
54.166667% {
|
||||
transform: matrix3d(0.99577, 0, 0, 0, 0, 1.00346, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
56.25% {
|
||||
transform: matrix3d(0.99705, 0, 0, 0, 0, 1.0016, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
58.333333% {
|
||||
transform: matrix3d(0.99822, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
60.416667% {
|
||||
transform: matrix3d(0.99921, 0, 0, 0, 0, 0.99884, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
62.5% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.99816, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
64.583333% {
|
||||
transform: matrix3d(1.00057, 0, 0, 0, 0, 0.99795, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
66.666667% {
|
||||
transform: matrix3d(1.00095, 0, 0, 0, 0, 0.99811, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
68.75% {
|
||||
transform: matrix3d(1.00114, 0, 0, 0, 0, 0.99851, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
70.833333% {
|
||||
transform: matrix3d(1.00119, 0, 0, 0, 0, 0.99903, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
72.916667% {
|
||||
transform: matrix3d(1.00114, 0, 0, 0, 0, 0.99955, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: matrix3d(1.001, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
77.083333% {
|
||||
transform: matrix3d(1.00083, 0, 0, 0, 0, 1.00033, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
79.166667% {
|
||||
transform: matrix3d(1.00063, 0, 0, 0, 0, 1.00052, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
81.25% {
|
||||
transform: matrix3d(1.00044, 0, 0, 0, 0, 1.00058, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
83.333333% {
|
||||
transform: matrix3d(1.00027, 0, 0, 0, 0, 1.00053, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
85.416667% {
|
||||
transform: matrix3d(1.00012, 0, 0, 0, 0, 1.00042, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
87.5% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1.00027, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
89.583333% {
|
||||
transform: matrix3d(0.99991, 0, 0, 0, 0, 1.00013, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
91.666667% {
|
||||
transform: matrix3d(0.99986, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
93.75% {
|
||||
transform: matrix3d(0.99983, 0, 0, 0, 0, 0.99991, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
95.833333% {
|
||||
transform: matrix3d(0.99982, 0, 0, 0, 0, 0.99985, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
97.916667% {
|
||||
transform: matrix3d(0.99983, 0, 0, 0, 0, 0.99984, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-slide-left.ns-show {
|
||||
animation-name: animSlideElasticLeft;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes animSlideElasticLeft {
|
||||
0% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1000, 0, 0, 1);
|
||||
}
|
||||
|
||||
1.666667% {
|
||||
transform: matrix3d(1.92933, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -739.26805, 0, 0, 1);
|
||||
}
|
||||
|
||||
3.333333% {
|
||||
transform: matrix3d(1.96989, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -521.82545, 0, 0, 1);
|
||||
}
|
||||
|
||||
5% {
|
||||
transform: matrix3d(1.70901, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -349.26115, 0, 0, 1);
|
||||
}
|
||||
|
||||
6.666667% {
|
||||
transform: matrix3d(1.4235, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -218.3238, 0, 0, 1);
|
||||
}
|
||||
|
||||
8.333333% {
|
||||
transform: matrix3d(1.21065, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -123.29848, 0, 0, 1);
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: matrix3d(1.08167, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -57.59273, 0, 0, 1);
|
||||
}
|
||||
|
||||
11.666667% {
|
||||
transform: matrix3d(1.0165, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -14.72371, 0, 0, 1);
|
||||
}
|
||||
|
||||
13.333333% {
|
||||
transform: matrix3d(0.99057, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11.12794, 0, 0, 1);
|
||||
}
|
||||
|
||||
15% {
|
||||
transform: matrix3d(0.98478, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 24.86339, 0, 0, 1);
|
||||
}
|
||||
|
||||
16.666667% {
|
||||
transform: matrix3d(0.98719, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 30.40503, 0, 0, 1);
|
||||
}
|
||||
|
||||
18.333333% {
|
||||
transform: matrix3d(0.9916, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 30.75275, 0, 0, 1);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: matrix3d(0.99541, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 28.10141, 0, 0, 1);
|
||||
}
|
||||
|
||||
21.666667% {
|
||||
transform: matrix3d(0.99795, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 23.98271, 0, 0, 1);
|
||||
}
|
||||
|
||||
23.333333% {
|
||||
transform: matrix3d(0.99936, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 19.40752, 0, 0, 1);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 14.99558, 0, 0, 1);
|
||||
}
|
||||
|
||||
26.666667% {
|
||||
transform: matrix3d(1.00021, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11.08575, 0, 0, 1);
|
||||
}
|
||||
|
||||
28.333333% {
|
||||
transform: matrix3d(1.00022, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7.82507, 0, 0, 1);
|
||||
}
|
||||
|
||||
30% {
|
||||
transform: matrix3d(1.00016, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.23737, 0, 0, 1);
|
||||
}
|
||||
|
||||
31.666667% {
|
||||
transform: matrix3d(1.0001, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.27389, 0, 0, 1);
|
||||
}
|
||||
|
||||
33.333333% {
|
||||
transform: matrix3d(1.00005, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.84893, 0, 0, 1);
|
||||
}
|
||||
|
||||
35% {
|
||||
transform: matrix3d(1.00002, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.86364, 0, 0, 1);
|
||||
}
|
||||
|
||||
36.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.22079, 0, 0, 1);
|
||||
}
|
||||
|
||||
38.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.16687, 0, 0, 1);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.37284, 0, 0, 1);
|
||||
}
|
||||
|
||||
41.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.45594, 0, 0, 1);
|
||||
}
|
||||
|
||||
43.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.46116, 0, 0, 1);
|
||||
}
|
||||
|
||||
45% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.4214, 0, 0, 1);
|
||||
}
|
||||
|
||||
46.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.35963, 0, 0, 1);
|
||||
}
|
||||
|
||||
48.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.29103, 0, 0, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.22487, 0, 0, 1);
|
||||
}
|
||||
|
||||
51.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.16624, 0, 0, 1);
|
||||
}
|
||||
|
||||
53.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.11734, 0, 0, 1);
|
||||
}
|
||||
|
||||
55% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.07854, 0, 0, 1);
|
||||
}
|
||||
|
||||
56.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.04909, 0, 0, 1);
|
||||
}
|
||||
|
||||
58.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.02773, 0, 0, 1);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.01295, 0, 0, 1);
|
||||
}
|
||||
|
||||
61.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00331, 0, 0, 1);
|
||||
}
|
||||
|
||||
63.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0025, 0, 0, 1);
|
||||
}
|
||||
|
||||
65% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00559, 0, 0, 1);
|
||||
}
|
||||
|
||||
66.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00684, 0, 0, 1);
|
||||
}
|
||||
|
||||
68.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00692, 0, 0, 1);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00632, 0, 0, 1);
|
||||
}
|
||||
|
||||
71.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00539, 0, 0, 1);
|
||||
}
|
||||
|
||||
73.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00436, 0, 0, 1);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00337, 0, 0, 1);
|
||||
}
|
||||
|
||||
76.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00249, 0, 0, 1);
|
||||
}
|
||||
|
||||
78.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00176, 0, 0, 1);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00118, 0, 0, 1);
|
||||
}
|
||||
|
||||
81.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00074, 0, 0, 1);
|
||||
}
|
||||
|
||||
83.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00042, 0, 0, 1);
|
||||
}
|
||||
|
||||
85% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00019, 0, 0, 1);
|
||||
}
|
||||
|
||||
86.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00005, 0, 0, 1);
|
||||
}
|
||||
|
||||
88.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00004, 0, 0, 1);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00008, 0, 0, 1);
|
||||
}
|
||||
|
||||
91.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1);
|
||||
}
|
||||
|
||||
93.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1);
|
||||
}
|
||||
|
||||
95% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00009, 0, 0, 1);
|
||||
}
|
||||
|
||||
96.666667% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00008, 0, 0, 1);
|
||||
}
|
||||
|
||||
98.333333% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00007, 0, 0, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-slide-left.ns-hide {
|
||||
animation-name: animSlideLeft;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
@keyframes animSlideLeft {
|
||||
0% {
|
||||
transform: translate3d(-30px, 0, 0) translate3d(-100%, 0, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-slide-right.ns-show {
|
||||
animation: animSlideElasticRight 2000ms linear both;
|
||||
}
|
||||
|
||||
@keyframes animSlideElasticRight {
|
||||
0% {
|
||||
transform: matrix3d(2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1000, 0, 0, 1);
|
||||
}
|
||||
|
||||
2.15% {
|
||||
transform: matrix3d(1.486, 0, 0, 0, 0, 0.514, 0, 0, 0, 0, 1, 0, 664.594, 0, 0, 1);
|
||||
}
|
||||
|
||||
4.1% {
|
||||
transform: matrix3d(1.147, 0, 0, 0, 0, 0.853, 0, 0, 0, 0, 1, 0, 419.708, 0, 0, 1);
|
||||
}
|
||||
|
||||
4.3% {
|
||||
transform: matrix3d(1.121, 0, 0, 0, 0, 0.879, 0, 0, 0, 0, 1, 0, 398.136, 0, 0, 1);
|
||||
}
|
||||
|
||||
6.46% {
|
||||
transform: matrix3d(0.948, 0, 0, 0, 0, 1.052, 0, 0, 0, 0, 1, 0, 206.714, 0, 0, 1);
|
||||
}
|
||||
|
||||
8.11% {
|
||||
transform: matrix3d(0.908, 0, 0, 0, 0, 1.092, 0, 0, 0, 0, 1, 0, 105.491, 0, 0, 1);
|
||||
}
|
||||
|
||||
8.61% {
|
||||
transform: matrix3d(0.907, 0, 0, 0, 0, 1.093, 0, 0, 0, 0, 1, 0, 81.572, 0, 0, 1);
|
||||
}
|
||||
|
||||
12.11% {
|
||||
transform: matrix3d(0.95, 0, 0, 0, 0, 1.05, 0, 0, 0, 0, 1, 0, -18.434, 0, 0, 1);
|
||||
}
|
||||
|
||||
14.16% {
|
||||
transform: matrix3d(0.979, 0, 0, 0, 0, 1.021, 0, 0, 0, 0, 1, 0, -38.734, 0, 0, 1);
|
||||
}
|
||||
|
||||
16.12% {
|
||||
transform: matrix3d(0.997, 0, 0, 0, 0, 1.003, 0, 0, 0, 0, 1, 0, -43.356, 0, 0, 1);
|
||||
}
|
||||
|
||||
19.72% {
|
||||
transform: matrix3d(1.006, 0, 0, 0, 0, 0.994, 0, 0, 0, 0, 1, 0, -34.155, 0, 0, 1);
|
||||
}
|
||||
|
||||
27.23% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -7.839, 0, 0, 1);
|
||||
}
|
||||
|
||||
30.83% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1.951, 0, 0, 1);
|
||||
}
|
||||
|
||||
38.34% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.037, 0, 0, 1);
|
||||
}
|
||||
|
||||
41.99% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.812, 0, 0, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.159, 0, 0, 1);
|
||||
}
|
||||
|
||||
60.56% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.025, 0, 0, 1);
|
||||
}
|
||||
|
||||
82.78% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.001, 0, 0, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-slide-right.ns-hide {
|
||||
animation-name: animSlideRight;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
@keyframes animSlideRight {
|
||||
0% {
|
||||
transform: translate3d(30px, 0, 0) translate3d(100%, 0, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-slide-center.ns-show {
|
||||
animation: animSlideElasticCenter 2000ms linear both;
|
||||
}
|
||||
|
||||
@keyframes animSlideElasticCenter {
|
||||
0% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, -300, 0, 1);
|
||||
}
|
||||
|
||||
2.15% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1.971, 0, 0, 0, 0, 1, 0, 0, -199.378, 0, 1);
|
||||
}
|
||||
|
||||
4.1% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1.294, 0, 0, 0, 0, 1, 0, 0, -125.912, 0, 1);
|
||||
}
|
||||
|
||||
4.3% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1.243, 0, 0, 0, 0, 1, 0, 0, -119.441, 0, 1);
|
||||
}
|
||||
|
||||
6.46% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.895, 0, 0, 0, 0, 1, 0, 0, -62.014, 0, 1);
|
||||
}
|
||||
|
||||
8.11% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.817, 0, 0, 0, 0, 1, 0, 0, -31.647, 0, 1);
|
||||
}
|
||||
|
||||
8.61% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.813, 0, 0, 0, 0, 1, 0, 0, -24.472, 0, 1);
|
||||
}
|
||||
|
||||
12.11% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 1, 0, 0, 5.53, 0, 1);
|
||||
}
|
||||
|
||||
14.16% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.959, 0, 0, 0, 0, 1, 0, 0, 11.62, 0, 1);
|
||||
}
|
||||
|
||||
16.12% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.994, 0, 0, 0, 0, 1, 0, 0, 13.007, 0, 1);
|
||||
}
|
||||
|
||||
19.72% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1.012, 0, 0, 0, 0, 1, 0, 0, 10.247, 0, 1);
|
||||
}
|
||||
|
||||
27.23% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 2.352, 0, 1);
|
||||
}
|
||||
|
||||
30.83% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0.585, 0, 1);
|
||||
}
|
||||
|
||||
38.34% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.311, 0, 1);
|
||||
}
|
||||
|
||||
41.99% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.244, 0, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.048, 0, 1);
|
||||
}
|
||||
|
||||
60.56% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0.007, 0, 1);
|
||||
}
|
||||
|
||||
82.78% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-slide-center.ns-hide {
|
||||
animation-name: animSlideCenter;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
@keyframes animSlideCenter {
|
||||
0% {
|
||||
transform: translate3d(0, -30px, 0) translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.ns-effect-genie.ns-show,
|
||||
.ns-effect-genie.ns-hide {
|
||||
animation-name: animGenie;
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes animGenie {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, calc(200% + 30px), 0) scale3d(0, 1, 1);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
40% {
|
||||
opacity: 0.5;
|
||||
transform: translate3d(0, 0, 0) scale3d(0.02, 1.1, 1);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
70% {
|
||||
opacity: 0.6;
|
||||
transform: translate3d(0, -40px, 0) scale3d(0.8, 1.1, 1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0) scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
@@ -1,34 +1,21 @@
|
||||
/**
|
||||
* Based on work by
|
||||
*
|
||||
* notificationFx.js v1.0.0
|
||||
* http://www.codrops.com
|
||||
* https://tympanus.net/codrops/
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* https://opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Copyright 2014, Codrops
|
||||
* http://www.codrops.com
|
||||
* https://tympanus.net/codrops/
|
||||
*/
|
||||
// jscs:disable
|
||||
;(function(window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var docElem = window.document.documentElement,
|
||||
support = {animations: Modernizr.cssanimations},
|
||||
animEndEventNames = {
|
||||
"WebkitAnimation": "webkitAnimationEnd",
|
||||
"OAnimation": "oAnimationEnd",
|
||||
"msAnimation": "MSAnimationEnd",
|
||||
"animation": "animationend"
|
||||
},
|
||||
// animation end event name
|
||||
animEndEventName = animEndEventNames[ Modernizr.prefixed("animation") ];
|
||||
|
||||
(function (window) {
|
||||
/**
|
||||
* extend obj function
|
||||
*/
|
||||
function extend(a, b) {
|
||||
for (var key in b) {
|
||||
for (let key in b) {
|
||||
if (b.hasOwnProperty(key)) {
|
||||
a[key] = b[key];
|
||||
}
|
||||
@@ -70,19 +57,23 @@
|
||||
ttl: 6000,
|
||||
al_no: "ns-box",
|
||||
// callbacks
|
||||
onClose: function() { return false; },
|
||||
onOpen: function() { return false; }
|
||||
onClose: function () {
|
||||
return false;
|
||||
},
|
||||
onOpen: function () {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* init function
|
||||
* initialize and cache some vars
|
||||
*/
|
||||
NotificationFx.prototype._init = function() {
|
||||
NotificationFx.prototype._init = function () {
|
||||
// create HTML structure
|
||||
this.ntf = document.createElement("div");
|
||||
this.ntf.className = this.options.al_no + " ns-" + this.options.layout + " ns-effect-" + this.options.effect + " ns-type-" + this.options.type;
|
||||
var strinner = "<div class=\"ns-box-inner\">";
|
||||
this.ntf.className = this.options.al_no + " ns-" + this.options.layout + " ns-effect-" + this.options.effect + " ns-type-" + this.options.type;
|
||||
let strinner = '<div class="ns-box-inner">';
|
||||
strinner += this.options.message;
|
||||
strinner += "</div>";
|
||||
this.ntf.innerHTML = strinner;
|
||||
@@ -91,13 +82,12 @@
|
||||
this.options.wrapper.insertBefore(this.ntf, this.options.wrapper.nextSibling);
|
||||
|
||||
// dismiss after [options.ttl]ms
|
||||
var self = this;
|
||||
if (this.options.ttl) {
|
||||
this.dismissttl = setTimeout(function() {
|
||||
if (self.active) {
|
||||
self.dismiss();
|
||||
}
|
||||
}, this.options.ttl);
|
||||
this.dismissttl = setTimeout(() => {
|
||||
if (this.active) {
|
||||
this.dismiss();
|
||||
}
|
||||
}, this.options.ttl);
|
||||
}
|
||||
|
||||
// init events
|
||||
@@ -107,59 +97,54 @@
|
||||
/**
|
||||
* init events
|
||||
*/
|
||||
NotificationFx.prototype._initEvents = function() {
|
||||
var self = this;
|
||||
NotificationFx.prototype._initEvents = function () {
|
||||
// dismiss notification by tapping on it if someone has a touchscreen
|
||||
this.ntf.querySelector(".ns-box-inner").addEventListener("click", function() { self.dismiss(); });
|
||||
this.ntf.querySelector(".ns-box-inner").addEventListener("click", () => {
|
||||
this.dismiss();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* show the notification
|
||||
*/
|
||||
NotificationFx.prototype.show = function() {
|
||||
NotificationFx.prototype.show = function () {
|
||||
this.active = true;
|
||||
classie.remove(this.ntf, "ns-hide");
|
||||
classie.add(this.ntf, "ns-show");
|
||||
this.ntf.classList.remove("ns-hide");
|
||||
this.ntf.classList.add("ns-show");
|
||||
this.options.onOpen();
|
||||
};
|
||||
|
||||
/**
|
||||
* dismiss the notification
|
||||
*/
|
||||
NotificationFx.prototype.dismiss = function() {
|
||||
var self = this;
|
||||
NotificationFx.prototype.dismiss = function () {
|
||||
this.active = false;
|
||||
clearTimeout(this.dismissttl);
|
||||
classie.remove(this.ntf, "ns-show");
|
||||
setTimeout(function() {
|
||||
classie.add(self.ntf, "ns-hide");
|
||||
this.ntf.classList.remove("ns-show");
|
||||
setTimeout(() => {
|
||||
this.ntf.classList.add("ns-hide");
|
||||
|
||||
// callback
|
||||
self.options.onClose();
|
||||
this.options.onClose();
|
||||
}, 25);
|
||||
|
||||
// after animation ends remove ntf from the DOM
|
||||
var onEndAnimationFn = function(ev) {
|
||||
if (support.animations) {
|
||||
if (ev.target !== self.ntf) return false;
|
||||
this.removeEventListener(animEndEventName, onEndAnimationFn);
|
||||
const onEndAnimationFn = (ev) => {
|
||||
if (ev.target !== this.ntf) {
|
||||
return false;
|
||||
}
|
||||
this.ntf.removeEventListener("animationend", onEndAnimationFn);
|
||||
|
||||
if (this.parentNode === self.options.wrapper) {
|
||||
self.options.wrapper.removeChild(this);
|
||||
if (ev.target.parentNode === this.options.wrapper) {
|
||||
this.options.wrapper.removeChild(this.ntf);
|
||||
}
|
||||
};
|
||||
|
||||
if (support.animations) {
|
||||
this.ntf.addEventListener(animEndEventName, onEndAnimationFn);
|
||||
} else {
|
||||
onEndAnimationFn();
|
||||
}
|
||||
this.ntf.addEventListener("animationend", onEndAnimationFn);
|
||||
};
|
||||
|
||||
/**
|
||||
* add to global namespace
|
||||
*/
|
||||
window.NotificationFx = NotificationFx;
|
||||
|
||||
})(window);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror нотификация",
|
||||
"welcome": "Добре дошли, стартирането беше успешно"
|
||||
"sysTitle": "MagicMirror нотификация",
|
||||
"welcome": "Добре дошли, стартирането беше успешно"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notifikation",
|
||||
"welcome": "Velkommen, modulet er succesfuldt startet!"
|
||||
"sysTitle": "MagicMirror Notifikation",
|
||||
"welcome": "Velkommen, modulet er succesfuldt startet!"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Benachrichtigung",
|
||||
"welcome": "Willkommen, Start war erfolgreich!"
|
||||
"sysTitle": "MagicMirror Benachrichtigung",
|
||||
"welcome": "Willkommen, Start war erfolgreich!"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notification",
|
||||
"welcome": "Welcome, start was successful!"
|
||||
"sysTitle": "MagicMirror Notification",
|
||||
"welcome": "Welcome, start was successful!"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notificaciones",
|
||||
"welcome": "Bienvenido, ¡se iniciado correctamente!"
|
||||
"sysTitle": "MagicMirror Notificaciones",
|
||||
"welcome": "Bienvenido, ¡se iniciado correctamente!"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notification",
|
||||
"welcome": "Bienvenue, le démarrage a été un succès!"
|
||||
"sysTitle": "MagicMirror Notification",
|
||||
"welcome": "Bienvenue, le démarrage a été un succès!"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror értesítés",
|
||||
"welcome": "Üdvözöljük, indulás sikeres!"
|
||||
"sysTitle": "MagicMirror értesítés",
|
||||
"welcome": "Üdvözöljük, indulás sikeres!"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notificatie",
|
||||
"welcome": "Welkom, Succesvol gestart!"
|
||||
"sysTitle": "MagicMirror Notificatie",
|
||||
"welcome": "Welkom, Succesvol gestart!"
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Уведомление",
|
||||
"welcome": "Добро пожаловать, старт был успешным!"
|
||||
"sysTitle": "MagicMirror Уведомление",
|
||||
"welcome": "Добро пожаловать, старт был успешным!"
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# Module: Calendar
|
||||
|
||||
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.
|
||||
|
||||
|
@@ -7,8 +7,6 @@
|
||||
|
||||
.calendar .symbol span {
|
||||
display: inline-block;
|
||||
-ms-transform: translate(0, 2px); /* IE 9 */
|
||||
-webkit-transform: translate(0, 2px); /* Safari */
|
||||
transform: translate(0, 2px);
|
||||
}
|
||||
|
||||
|
@@ -1,26 +1,27 @@
|
||||
/* global Module */
|
||||
/* global cloneObject */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Calendar
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("calendar", {
|
||||
|
||||
// Define module defaults
|
||||
defaults: {
|
||||
maximumEntries: 10, // Total Maximum Entries
|
||||
maximumNumberOfDays: 365,
|
||||
displaySymbol: true,
|
||||
defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/
|
||||
defaultSymbol: "calendar", // Fontawesome Symbol see https://fontawesome.com/cheatsheet?from=io
|
||||
showLocation: false,
|
||||
displayRepeatingCountTitle: false,
|
||||
defaultRepeatingCountTitle: "",
|
||||
maxTitleLength: 25,
|
||||
maxLocationTitleLength: 25,
|
||||
wrapEvents: false, // wrap events to multiple lines breaking at maxTitleLength
|
||||
wrapLocationEvents: false,
|
||||
maxTitleLines: 3,
|
||||
maxEventTitleLines: 3,
|
||||
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
|
||||
animationSpeed: 2000,
|
||||
fade: true,
|
||||
@@ -40,13 +41,16 @@ Module.register("calendar", {
|
||||
calendars: [
|
||||
{
|
||||
symbol: "calendar",
|
||||
url: "http://www.calendarlabs.com/templates/ical/US-Holidays.ics",
|
||||
},
|
||||
url: "https://www.calendarlabs.com/templates/ical/US-Holidays.ics"
|
||||
}
|
||||
],
|
||||
titleReplace: {
|
||||
"De verjaardag van ": "",
|
||||
"'s birthday": ""
|
||||
},
|
||||
locationTitleReplace: {
|
||||
"street ": ""
|
||||
},
|
||||
broadcastEvents: true,
|
||||
excludedEvents: [],
|
||||
sliceMultiDayEvents: false,
|
||||
@@ -86,7 +90,7 @@ Module.register("calendar", {
|
||||
var calendarConfig = {
|
||||
maximumEntries: calendar.maximumEntries,
|
||||
maximumNumberOfDays: calendar.maximumNumberOfDays,
|
||||
broadcastPastEvents: calendar.broadcastPastEvents,
|
||||
broadcastPastEvents: calendar.broadcastPastEvents
|
||||
};
|
||||
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
|
||||
calendarConfig.symbolClass = "";
|
||||
@@ -99,7 +103,7 @@ Module.register("calendar", {
|
||||
}
|
||||
|
||||
// we check user and password here for backwards compatibility with old configs
|
||||
if(calendar.user && calendar.pass) {
|
||||
if (calendar.user && calendar.pass) {
|
||||
Log.warn("Deprecation warning: Please update your calendar authentication configuration.");
|
||||
Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options");
|
||||
calendar.auth = {
|
||||
@@ -113,7 +117,7 @@ Module.register("calendar", {
|
||||
// Trigger ADD_CALENDAR every fetchInterval to make sure there is always a calendar
|
||||
// fetcher running on the server side.
|
||||
var self = this;
|
||||
setInterval(function() {
|
||||
setInterval(function () {
|
||||
self.addCalendar(calendar.url, calendar.auth, calendarConfig);
|
||||
}, self.config.fetchInterval);
|
||||
}
|
||||
@@ -124,6 +128,10 @@ Module.register("calendar", {
|
||||
|
||||
// Override socket notification handler.
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
if (this.identifier !== payload.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification === "CALENDAR_EVENTS") {
|
||||
if (this.hasCalendarURL(payload.url)) {
|
||||
this.calendarData[payload.url] = payload.events;
|
||||
@@ -145,13 +153,12 @@ Module.register("calendar", {
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function () {
|
||||
|
||||
var events = this.createEventList();
|
||||
var wrapper = document.createElement("table");
|
||||
wrapper.className = this.config.tableClass;
|
||||
|
||||
if (events.length === 0) {
|
||||
wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING");
|
||||
wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING");
|
||||
wrapper.className = this.config.tableClass + " dimmed";
|
||||
return wrapper;
|
||||
}
|
||||
@@ -170,8 +177,8 @@ Module.register("calendar", {
|
||||
for (var e in events) {
|
||||
var event = events[e];
|
||||
var dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
|
||||
if(this.config.timeFormat === "dateheaders"){
|
||||
if(lastSeenDate !== dateAsString){
|
||||
if (this.config.timeFormat === "dateheaders") {
|
||||
if (lastSeenDate !== dateAsString) {
|
||||
var dateRow = document.createElement("tr");
|
||||
dateRow.className = "normal";
|
||||
var dateCell = document.createElement("td");
|
||||
@@ -182,9 +189,10 @@ Module.register("calendar", {
|
||||
dateRow.appendChild(dateCell);
|
||||
wrapper.appendChild(dateRow);
|
||||
|
||||
if (e >= startFade) { //fading
|
||||
if (e >= startFade) {
|
||||
//fading
|
||||
currentFadeStep = e - startFade;
|
||||
dateRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
|
||||
dateRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
|
||||
}
|
||||
|
||||
lastSeenDate = dateAsString;
|
||||
@@ -210,20 +218,20 @@ Module.register("calendar", {
|
||||
symbolWrapper.className = "symbol align-right " + symbolClass;
|
||||
|
||||
var symbols = this.symbolsForUrl(event.url);
|
||||
if(typeof symbols === "string") {
|
||||
if (typeof symbols === "string") {
|
||||
symbols = [symbols];
|
||||
}
|
||||
|
||||
for(var i = 0; i < symbols.length; i++) {
|
||||
for (var i = 0; i < symbols.length; i++) {
|
||||
var symbol = document.createElement("span");
|
||||
symbol.className = "fa fa-fw fa-" + symbols[i];
|
||||
if(i > 0){
|
||||
if (i > 0) {
|
||||
symbol.style.paddingLeft = "5px";
|
||||
}
|
||||
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 = " ";
|
||||
eventWrapper.appendChild(blankCell);
|
||||
@@ -233,7 +241,6 @@ Module.register("calendar", {
|
||||
repeatingCountTitle = "";
|
||||
|
||||
if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
|
||||
|
||||
repeatingCountTitle = this.countTitleForUrl(event.url);
|
||||
|
||||
if (repeatingCountTitle !== "") {
|
||||
@@ -244,7 +251,7 @@ Module.register("calendar", {
|
||||
}
|
||||
}
|
||||
|
||||
titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
|
||||
titleWrapper.innerHTML = this.titleTransform(event.title, this.config.titleReplace, this.config.wrapEvents, this.config.maxTitleLength, this.config.maxTitleLines) + repeatingCountTitle;
|
||||
|
||||
var titleClass = this.titleClassForUrl(event.url);
|
||||
|
||||
@@ -254,17 +261,15 @@ Module.register("calendar", {
|
||||
titleWrapper.className = "title " + titleClass;
|
||||
}
|
||||
|
||||
if(this.config.timeFormat === "dateheaders"){
|
||||
var timeWrapper;
|
||||
|
||||
if (this.config.timeFormat === "dateheaders") {
|
||||
if (event.fullDayEvent) {
|
||||
titleWrapper.colSpan = "2";
|
||||
titleWrapper.align = "left";
|
||||
|
||||
} else {
|
||||
|
||||
var timeClass = this.timeClassForUrl(event.url);
|
||||
var timeWrapper = document.createElement("td");
|
||||
timeWrapper.className = "time light " + timeClass;
|
||||
timeWrapper = document.createElement("td");
|
||||
timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
|
||||
timeWrapper.align = "left";
|
||||
timeWrapper.style.paddingLeft = "2px";
|
||||
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
|
||||
@@ -274,10 +279,9 @@ Module.register("calendar", {
|
||||
|
||||
eventWrapper.appendChild(titleWrapper);
|
||||
} else {
|
||||
var timeWrapper = document.createElement("td");
|
||||
timeWrapper = document.createElement("td");
|
||||
|
||||
eventWrapper.appendChild(titleWrapper);
|
||||
//console.log(event.today);
|
||||
var now = new Date();
|
||||
// Define second, minute, hour, and day variables
|
||||
var oneSecond = 1000; // 1,000 milliseconds
|
||||
@@ -299,14 +303,14 @@ Module.register("calendar", {
|
||||
}
|
||||
} else {
|
||||
/* Check to see if the user displays absolute or relative dates with their events
|
||||
* Also check to see if an event is happening within an 'urgency' time frameElement
|
||||
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
||||
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
||||
*
|
||||
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
||||
*/
|
||||
* Also check to see if an event is happening within an 'urgency' time frameElement
|
||||
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
||||
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
||||
*
|
||||
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
||||
*/
|
||||
if (this.config.timeFormat === "absolute") {
|
||||
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
|
||||
if (this.config.urgency > 1 && event.startDate - now < this.config.urgency * oneDay) {
|
||||
// This event falls within the config.urgency period that the user has set
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
|
||||
} else {
|
||||
@@ -316,9 +320,9 @@ Module.register("calendar", {
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
|
||||
}
|
||||
}
|
||||
if(this.config.showEnd){
|
||||
timeWrapper.innerHTML += "-" ;
|
||||
timeWrapper.innerHTML += this.capFirst(moment(event.endDate , "x").format(this.config.fullDayEventDateFormat));
|
||||
if (this.config.showEnd) {
|
||||
timeWrapper.innerHTML += "-";
|
||||
timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.fullDayEventDateFormat));
|
||||
}
|
||||
} else {
|
||||
if (event.startDate >= new Date()) {
|
||||
@@ -328,7 +332,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" && !this.config.nextDaysRelative) {
|
||||
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'
|
||||
@@ -337,14 +341,14 @@ Module.register("calendar", {
|
||||
}
|
||||
} else {
|
||||
/* Check to see if the user displays absolute or relative dates with their events
|
||||
* Also check to see if an event is happening within an 'urgency' time frameElement
|
||||
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
||||
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
||||
*
|
||||
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
||||
*/
|
||||
* Also check to see if an event is happening within an 'urgency' time frameElement
|
||||
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
||||
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
||||
*
|
||||
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
||||
*/
|
||||
if (this.config.timeFormat === "absolute") {
|
||||
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
|
||||
if (this.config.urgency > 1 && event.startDate - now < this.config.urgency * oneDay) {
|
||||
// This event falls within the config.urgency period that the user has set
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
} else {
|
||||
@@ -365,13 +369,10 @@ Module.register("calendar", {
|
||||
if (this.config.showEnd) {
|
||||
timeWrapper.innerHTML += "-";
|
||||
timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
|
||||
|
||||
}
|
||||
}
|
||||
//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
|
||||
//console.log(event);
|
||||
var timeClass = this.timeClassForUrl(event.url);
|
||||
timeWrapper.className = "time light " + timeClass;
|
||||
timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
|
||||
eventWrapper.appendChild(timeWrapper);
|
||||
}
|
||||
|
||||
@@ -380,7 +381,7 @@ Module.register("calendar", {
|
||||
// Create fade effect.
|
||||
if (e >= startFade) {
|
||||
currentFadeStep = e - startFade;
|
||||
eventWrapper.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
|
||||
eventWrapper.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
|
||||
}
|
||||
|
||||
if (this.config.showLocation) {
|
||||
@@ -396,14 +397,14 @@ Module.register("calendar", {
|
||||
var descCell = document.createElement("td");
|
||||
descCell.className = "location";
|
||||
descCell.colSpan = "2";
|
||||
descCell.innerHTML = event.location;
|
||||
descCell.innerHTML = this.titleTransform(event.location, this.config.locationTitleReplace, this.config.wrapLocationEvents, this.config.maxLocationTitleLength, this.config.maxEventTitleLines);
|
||||
locationRow.appendChild(descCell);
|
||||
|
||||
wrapper.appendChild(locationRow);
|
||||
|
||||
if (e >= startFade) {
|
||||
currentFadeStep = e - startFade;
|
||||
locationRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
|
||||
locationRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,20 +421,17 @@ Module.register("calendar", {
|
||||
* @param {number} timeFormat Specifies either 12 or 24 hour time format
|
||||
* @returns {moment.LocaleSpecification}
|
||||
*/
|
||||
getLocaleSpecification: function(timeFormat) {
|
||||
getLocaleSpecification: function (timeFormat) {
|
||||
switch (timeFormat) {
|
||||
case 12: {
|
||||
return { longDateFormat: {LT: "h:mm A"} };
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
return { longDateFormat: {LT: "HH:mm"} };
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return { longDateFormat: {LT: moment.localeData().longDateFormat("LT")} };
|
||||
break;
|
||||
}
|
||||
case 12: {
|
||||
return { longDateFormat: { LT: "h:mm A" } };
|
||||
}
|
||||
case 24: {
|
||||
return { longDateFormat: { LT: "HH:mm" } };
|
||||
}
|
||||
default: {
|
||||
return { longDateFormat: { LT: moment.localeData().longDateFormat("LT") } };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -469,37 +467,37 @@ Module.register("calendar", {
|
||||
var calendar = this.calendarData[c];
|
||||
for (var e in calendar) {
|
||||
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
|
||||
if(event.endDate < now) {
|
||||
if (event.endDate < now) {
|
||||
continue;
|
||||
}
|
||||
if(this.config.hidePrivate) {
|
||||
if(event.class === "PRIVATE") {
|
||||
// do not add the current event, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(this.config.hideOngoing) {
|
||||
if(event.startDate < now) {
|
||||
if (this.config.hidePrivate) {
|
||||
if (event.class === "PRIVATE") {
|
||||
// do not add the current event, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(this.listContainsEvent(events,event)){
|
||||
if (this.config.hideOngoing) {
|
||||
if (event.startDate < now) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (this.listContainsEvent(events, event)) {
|
||||
continue;
|
||||
}
|
||||
event.url = c;
|
||||
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
|
||||
event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
var maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1;
|
||||
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
|
||||
*/
|
||||
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;
|
||||
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.today = thisEvent.startDate >= today && thisEvent.startDate < today + 24 * 60 * 60 * 1000;
|
||||
thisEvent.endDate = midnight;
|
||||
thisEvent.title += " (" + count + "/" + maxCount + ")";
|
||||
splitEvents.push(thisEvent);
|
||||
@@ -509,11 +507,11 @@ Module.register("calendar", {
|
||||
midnight = moment(midnight, "x").add(1, "day").format("x"); // next day
|
||||
}
|
||||
// Last day
|
||||
event.title += " ("+count+"/"+maxCount+")";
|
||||
event.title += " (" + count + "/" + maxCount + ")";
|
||||
splitEvents.push(event);
|
||||
|
||||
for (event of splitEvents) {
|
||||
if ((event.endDate > now) && (event.endDate <= future)) {
|
||||
if (event.endDate > now && event.endDate <= future) {
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
@@ -529,9 +527,9 @@ Module.register("calendar", {
|
||||
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)){
|
||||
listContainsEvent: function (eventList, event) {
|
||||
for (var evt of eventList) {
|
||||
if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -545,6 +543,7 @@ Module.register("calendar", {
|
||||
*/
|
||||
addCalendar: function (url, auth, calendarConfig) {
|
||||
this.sendSocketNotification("ADD_CALENDAR", {
|
||||
id: this.identifier,
|
||||
url: url,
|
||||
excludedEvents: calendarConfig.excludedEvents || this.config.excludedEvents,
|
||||
maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries,
|
||||
@@ -554,7 +553,7 @@ Module.register("calendar", {
|
||||
titleClass: calendarConfig.titleClass,
|
||||
timeClass: calendarConfig.timeClass,
|
||||
auth: auth,
|
||||
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
|
||||
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents
|
||||
});
|
||||
},
|
||||
|
||||
@@ -681,8 +680,9 @@ Module.register("calendar", {
|
||||
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
var word = words[i];
|
||||
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) { // max - 1 to account for a space
|
||||
currentLine += (word + " ");
|
||||
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) {
|
||||
// max - 1 to account for a space
|
||||
currentLine += word + " ";
|
||||
} else {
|
||||
line++;
|
||||
if (line > maxTitleLines - 1) {
|
||||
@@ -693,9 +693,9 @@ Module.register("calendar", {
|
||||
}
|
||||
|
||||
if (currentLine.length > 0) {
|
||||
temp += (currentLine + "<br>" + word + " ");
|
||||
temp += currentLine + "<br>" + word + " ";
|
||||
} else {
|
||||
temp += (word + "<br>");
|
||||
temp += word + "<br>";
|
||||
}
|
||||
currentLine = "";
|
||||
}
|
||||
@@ -728,20 +728,20 @@ Module.register("calendar", {
|
||||
*
|
||||
* return string - The transformed title.
|
||||
*/
|
||||
titleTransform: function (title) {
|
||||
for (var needle in this.config.titleReplace) {
|
||||
var replacement = this.config.titleReplace[needle];
|
||||
titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
|
||||
for (var needle in titleReplace) {
|
||||
var replacement = titleReplace[needle];
|
||||
|
||||
var regParts = needle.match(/^\/(.+)\/([gim]*)$/);
|
||||
if (regParts) {
|
||||
// the parsed pattern is a regexp.
|
||||
needle = new RegExp(regParts[1], regParts[2]);
|
||||
// the parsed pattern is a regexp.
|
||||
needle = new RegExp(regParts[1], regParts[2]);
|
||||
}
|
||||
|
||||
title = title.replace(needle, replacement);
|
||||
}
|
||||
|
||||
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents, this.config.maxTitleLines);
|
||||
title = this.shorten(title, maxTitleLength, wrapEvents, maxTitleLines);
|
||||
return title;
|
||||
},
|
||||
|
||||
@@ -763,11 +763,10 @@ Module.register("calendar", {
|
||||
}
|
||||
}
|
||||
|
||||
eventList.sort(function(a,b) {
|
||||
eventList.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
|
||||
this.sendNotification("CALENDAR_EVENTS", eventList);
|
||||
|
||||
}
|
||||
});
|
||||
|
@@ -1,90 +1,88 @@
|
||||
/* Magic Mirror
|
||||
* Node Helper: Calendar - CalendarFetcher
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const Log = require("../../../js/logger.js");
|
||||
const ical = require("ical");
|
||||
const moment = require("moment");
|
||||
const request = require("request");
|
||||
|
||||
var ical = require("./vendor/ical.js");
|
||||
var moment = require("moment");
|
||||
const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNumberOfDays, auth, includePastEvents) {
|
||||
const self = this;
|
||||
|
||||
var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
|
||||
var self = this;
|
||||
let reloadTimer = null;
|
||||
let events = [];
|
||||
|
||||
var reloadTimer = null;
|
||||
var events = [];
|
||||
|
||||
var fetchFailedCallback = function() {};
|
||||
var eventsReceivedCallback = function() {};
|
||||
let fetchFailedCallback = function () {};
|
||||
let eventsReceivedCallback = function () {};
|
||||
|
||||
/* fetchCalendar()
|
||||
* Initiates calendar fetch.
|
||||
*/
|
||||
var fetchCalendar = function() {
|
||||
|
||||
const fetchCalendar = function () {
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = null;
|
||||
|
||||
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||
var opts = {
|
||||
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||
const 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
|
||||
};
|
||||
|
||||
if (auth) {
|
||||
if(auth.method === "bearer"){
|
||||
if (auth.method === "bearer") {
|
||||
opts.auth = {
|
||||
bearer: auth.pass
|
||||
};
|
||||
|
||||
} else {
|
||||
opts.auth = {
|
||||
user: auth.user,
|
||||
pass: auth.pass
|
||||
pass: auth.pass,
|
||||
sendImmediately: auth.method !== "digest"
|
||||
};
|
||||
|
||||
if(auth.method === "digest"){
|
||||
opts.auth.sendImmediately = false;
|
||||
} else {
|
||||
opts.auth.sendImmediately = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ical.fromURL(url, opts, function(err, data) {
|
||||
request(url, opts, function (err, r, requestData) {
|
||||
if (err) {
|
||||
fetchFailedCallback(self, err);
|
||||
scheduleTimer();
|
||||
return;
|
||||
} else if (r.statusCode !== 200) {
|
||||
fetchFailedCallback(self, r.statusCode + ": " + r.statusMessage);
|
||||
scheduleTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log(data);
|
||||
newEvents = [];
|
||||
const data = ical.parseICS(requestData);
|
||||
const newEvents = [];
|
||||
|
||||
// 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]));
|
||||
const limitFunction = function (date, i) {
|
||||
return true;
|
||||
};
|
||||
|
||||
for (var e in data) {
|
||||
var event = data[e];
|
||||
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;
|
||||
const eventDate = function (event, time) {
|
||||
return event[time].length === 8 ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
|
||||
};
|
||||
|
||||
Object.entries(data).forEach(([key, event]) => {
|
||||
const now = new Date();
|
||||
const today = moment().startOf("day").toDate();
|
||||
const 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.
|
||||
let past = today;
|
||||
|
||||
if (includePastEvents) {
|
||||
past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
// Ugly fix to solve the facebook birthday issue.
|
||||
// FIXME: Ugly fix to solve the facebook birthday issue.
|
||||
// Otherwise, the recurring events only show the birthday for next year.
|
||||
var isFacebookBirthday = false;
|
||||
let isFacebookBirthday = false;
|
||||
if (typeof event.uid !== "undefined") {
|
||||
if (event.uid.indexOf("@facebook.com") !== -1) {
|
||||
isFacebookBirthday = true;
|
||||
@@ -92,14 +90,13 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
}
|
||||
|
||||
if (event.type === "VEVENT") {
|
||||
let startDate = eventDate(event, "start");
|
||||
let endDate;
|
||||
|
||||
var startDate = eventDate(event, "start");
|
||||
var endDate;
|
||||
if (typeof event.end !== "undefined") {
|
||||
endDate = eventDate(event, "end");
|
||||
} else if(typeof event.duration !== "undefined") {
|
||||
dur=moment.duration(event.duration);
|
||||
endDate = startDate.clone().add(dur);
|
||||
} else if (typeof event.duration !== "undefined") {
|
||||
endDate = startDate.clone().add(moment.duration(event.duration));
|
||||
} else {
|
||||
if (!isFacebookBirthday) {
|
||||
endDate = startDate;
|
||||
@@ -108,20 +105,20 @@ 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"));
|
||||
// calculate the duration of the event for use with recurring events.
|
||||
let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
|
||||
|
||||
if (event.start.length === 8) {
|
||||
startDate = startDate.startOf("day");
|
||||
}
|
||||
|
||||
var title = getTitleFromEvent(event);
|
||||
const title = getTitleFromEvent(event);
|
||||
|
||||
var excluded = false,
|
||||
let excluded = false,
|
||||
dateFilter = null;
|
||||
|
||||
for (var f in excludedEvents) {
|
||||
var filter = excludedEvents[f],
|
||||
for (let f in excludedEvents) {
|
||||
let filter = excludedEvents[f],
|
||||
testTitle = title.toLowerCase(),
|
||||
until = null,
|
||||
useRegex = false,
|
||||
@@ -164,97 +161,84 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
}
|
||||
|
||||
if (excluded) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
var location = event.location || false;
|
||||
var geo = event.geo || false;
|
||||
var description = event.description || false;
|
||||
const location = event.location || false;
|
||||
const geo = event.geo || false;
|
||||
const description = event.description || false;
|
||||
|
||||
if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
|
||||
var rule = event.rrule;
|
||||
var addedEvents = 0;
|
||||
const rule = event.rrule;
|
||||
let addedEvents = 0;
|
||||
|
||||
const pastMoment = moment(past);
|
||||
const futureMoment = moment(future);
|
||||
|
||||
// can cause problems with e.g. birthdays before 1900
|
||||
if(rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900 ||
|
||||
rule.options && rule.options.dtstart && rule.options.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);
|
||||
}
|
||||
|
||||
// For recurring events, get the set of start dates that fall within the range
|
||||
// of dates we"re looking for.
|
||||
// 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;
|
||||
const pastLocal = pastMoment.subtract(past.getTimezoneOffset(), "minutes").toDate();
|
||||
const futureLocal = futureMoment.subtract(future.getTimezoneOffset(), "minutes").toDate();
|
||||
const datesLocal = rule.between(pastLocal, futureLocal, true, limitFunction);
|
||||
const dates = datesLocal.map(function (dateLocal) {
|
||||
return moment(dateLocal).add(dateLocal.getTimezoneOffset(), "minutes").toDate();
|
||||
});
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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)
|
||||
{
|
||||
if (event.recurrences !== undefined) {
|
||||
for (let r in event.recurrences) {
|
||||
// Only add dates that weren't already in the range we added from the rrule so that
|
||||
// we don"t double-add those events.
|
||||
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) != true)
|
||||
{
|
||||
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) {
|
||||
var date = dates[d];
|
||||
for (let d in dates) {
|
||||
const 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
const dateKey = date.toISOString().substring(0, 10);
|
||||
let curEvent = event;
|
||||
let showRecurrence = true;
|
||||
|
||||
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))
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
// 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")) {
|
||||
if (startDate.format("x") === endDate.format("x")) {
|
||||
endDate = endDate.endOf("day");
|
||||
}
|
||||
|
||||
var recurrenceTitle = getTitleFromEvent(curEvent);
|
||||
const 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.
|
||||
@@ -266,7 +250,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
showRecurrence = false;
|
||||
}
|
||||
|
||||
if ((showRecurrence === true) && (addedEvents < maximumEntries)) {
|
||||
if (showRecurrence === true) {
|
||||
addedEvents++;
|
||||
newEvents.push({
|
||||
title: recurrenceTitle,
|
||||
@@ -283,43 +267,41 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
}
|
||||
// end recurring event parsing
|
||||
} else {
|
||||
// console.log("Single event ...");
|
||||
// Single event.
|
||||
var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
|
||||
const fullDayEvent = isFacebookBirthday ? true : isFullDayEvent(event);
|
||||
|
||||
if (includePastEvents) {
|
||||
// Past event is too far in the past, so skip.
|
||||
if (endDate < past) {
|
||||
//console.log("Past event is too far in the past. So skip: " + title);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// It's not a fullday event, and it is in the past, so skip.
|
||||
if (!fullDayEvent && endDate < new Date()) {
|
||||
//console.log("It's not a fullday event, and it is in the past. So skip: " + title);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// It's a fullday event, and it is before today, So skip.
|
||||
if (fullDayEvent && endDate <= today) {
|
||||
//console.log("It's a fullday event, and it is before today. So skip: " + title);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// It exceeds the maximumNumberOfDays limit, so skip.
|
||||
if (startDate > future) {
|
||||
//console.log("It exceeds the maximumNumberOfDays limit. So skip: " + title);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeFilterApplies(now, endDate, dateFilter)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
|
||||
// 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({
|
||||
title: title,
|
||||
startDate: startDate.format("x"),
|
||||
@@ -330,18 +312,15 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
geo: geo,
|
||||
description: description
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
newEvents.sort(function(a, b) {
|
||||
newEvents.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
|
||||
//console.log(newEvents);
|
||||
|
||||
events = newEvents.slice(0, maximumEntries);
|
||||
events = newEvents;
|
||||
|
||||
self.broadcastEvents();
|
||||
scheduleTimer();
|
||||
@@ -351,10 +330,9 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
/* scheduleTimer()
|
||||
* Schedule the timer for the next update.
|
||||
*/
|
||||
var scheduleTimer = function() {
|
||||
//console.log('Schedule update timer.');
|
||||
const scheduleTimer = function () {
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(function() {
|
||||
reloadTimer = setTimeout(function () {
|
||||
fetchCalendar();
|
||||
}, reloadInterval);
|
||||
};
|
||||
@@ -366,15 +344,15 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
*
|
||||
* return bool - The event is a fullday event.
|
||||
*/
|
||||
var isFullDayEvent = function(event) {
|
||||
const isFullDayEvent = function (event) {
|
||||
if (event.start.length === 8 || event.start.dateOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var start = event.start || 0;
|
||||
var startDate = new Date(start);
|
||||
var end = event.end || 0;
|
||||
if (((end - start) % (24 * 60 * 60 * 1000)) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
|
||||
const start = event.start || 0;
|
||||
const startDate = new Date(start);
|
||||
const end = event.end || 0;
|
||||
if ((end - start) % (24 * 60 * 60 * 1000) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
|
||||
// Is 24 hours, and starts on the middle of the night.
|
||||
return true;
|
||||
}
|
||||
@@ -391,11 +369,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
*
|
||||
* return bool - The event should be filtered out
|
||||
*/
|
||||
var timeFilterApplies = function(now, endDate, filter) {
|
||||
const timeFilterApplies = function (now, endDate, filter) {
|
||||
if (filter) {
|
||||
var until = filter.split(" "),
|
||||
const until = filter.split(" "),
|
||||
value = parseInt(until[0]),
|
||||
increment = until[1].slice("-1") === "s" ? until[1] : until[1] + "s", // Massage the data for moment js
|
||||
increment = until[1].slice(-1) === "s" ? until[1] : until[1] + "s", // Massage the data for moment js
|
||||
filterUntil = moment(endDate.format()).subtract(value, increment);
|
||||
|
||||
return now < filterUntil.format("x");
|
||||
@@ -405,16 +383,16 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
};
|
||||
|
||||
/* 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";
|
||||
* 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.
|
||||
*/
|
||||
const getTitleFromEvent = function (event) {
|
||||
let title = "Event";
|
||||
if (event.summary) {
|
||||
title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
|
||||
title = typeof event.summary.val !== "undefined" ? event.summary.val : event.summary;
|
||||
} else if (event.description) {
|
||||
title = event.description;
|
||||
}
|
||||
@@ -422,7 +400,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
return title;
|
||||
};
|
||||
|
||||
var testTitleByFilter = function (title, filter, useRegex, regexFlags) {
|
||||
const testTitleByFilter = function (title, filter, useRegex, regexFlags) {
|
||||
if (useRegex) {
|
||||
// Assume if leading slash, there is also trailing slash
|
||||
if (filter[0] === "/") {
|
||||
@@ -443,15 +421,15 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
/* startFetch()
|
||||
* Initiate fetchCalendar();
|
||||
*/
|
||||
this.startFetch = function() {
|
||||
this.startFetch = function () {
|
||||
fetchCalendar();
|
||||
};
|
||||
|
||||
/* broadcastItems()
|
||||
* Broadcast the existing events.
|
||||
*/
|
||||
this.broadcastEvents = function() {
|
||||
//console.log('Broadcasting ' + events.length + ' events.');
|
||||
this.broadcastEvents = function () {
|
||||
Log.info("Calendar-Fetcher: Broadcasting " + events.length + " events.");
|
||||
eventsReceivedCallback(self);
|
||||
};
|
||||
|
||||
@@ -460,7 +438,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
*
|
||||
* argument callback function - The on success callback.
|
||||
*/
|
||||
this.onReceive = function(callback) {
|
||||
this.onReceive = function (callback) {
|
||||
eventsReceivedCallback = callback;
|
||||
};
|
||||
|
||||
@@ -469,7 +447,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
*
|
||||
* argument callback function - The on error callback.
|
||||
*/
|
||||
this.onError = function(callback) {
|
||||
this.onError = function (callback) {
|
||||
fetchFailedCallback = callback;
|
||||
};
|
||||
|
||||
@@ -478,7 +456,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
*
|
||||
* return string - The url of this fetcher.
|
||||
*/
|
||||
this.url = function() {
|
||||
this.url = function () {
|
||||
return url;
|
||||
};
|
||||
|
||||
@@ -487,7 +465,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
||||
*
|
||||
* return array - The current available events for this fetcher.
|
||||
*/
|
||||
this.events = function() {
|
||||
this.events = function () {
|
||||
return events;
|
||||
};
|
||||
};
|
||||
|
@@ -2,36 +2,33 @@
|
||||
* use this script with `node debug.js` to test the fetcher without the need
|
||||
* of starting the MagicMirror core. Adjust the values below to your desire.
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const CalendarFetcher = require("./calendarfetcher.js");
|
||||
|
||||
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://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 = {
|
||||
const url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
|
||||
//const url = "https://www.googleapis.com/calendar/v3/calendars/primary/events/"; // URL for Bearer auth (must be configured in Google OAuth2 first)
|
||||
const fetchInterval = 60 * 60 * 1000;
|
||||
const maximumEntries = 10;
|
||||
const maximumNumberOfDays = 365;
|
||||
const user = "magicmirror";
|
||||
const pass = "MyStrongPass";
|
||||
const auth = {
|
||||
user: user,
|
||||
pass: pass
|
||||
};
|
||||
|
||||
console.log("Create fetcher ...");
|
||||
|
||||
fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maximumNumberOfDays, auth);
|
||||
const fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maximumNumberOfDays, auth);
|
||||
|
||||
fetcher.onReceive(function(fetcher) {
|
||||
fetcher.onReceive(function (fetcher) {
|
||||
console.log(fetcher.events());
|
||||
console.log("------------------------------------------------------------");
|
||||
});
|
||||
|
||||
fetcher.onError(function(fetcher, error) {
|
||||
fetcher.onError(function (fetcher, error) {
|
||||
console.log("Fetcher error:");
|
||||
console.log(error);
|
||||
});
|
||||
|
@@ -1,30 +1,26 @@
|
||||
/* Magic Mirror
|
||||
* Node Helper: Calendar
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var NodeHelper = require("node_helper");
|
||||
var validUrl = require("valid-url");
|
||||
var CalendarFetcher = require("./calendarfetcher.js");
|
||||
const NodeHelper = require("node_helper");
|
||||
const validUrl = require("valid-url");
|
||||
const CalendarFetcher = require("./calendarfetcher.js");
|
||||
const Log = require("../../../js/logger");
|
||||
|
||||
module.exports = NodeHelper.create({
|
||||
// Override start method.
|
||||
start: function() {
|
||||
var events = [];
|
||||
|
||||
start: function () {
|
||||
Log.log("Starting node helper for: " + this.name);
|
||||
this.fetchers = [];
|
||||
|
||||
console.log("Starting node helper for: " + this.name);
|
||||
|
||||
},
|
||||
|
||||
// Override socketNotificationReceived method.
|
||||
socketNotificationReceived: function(notification, payload) {
|
||||
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, payload.broadcastPastEvents);
|
||||
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.id);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -35,42 +31,40 @@ module.exports = NodeHelper.create({
|
||||
* attribute url string - URL of the news feed.
|
||||
* attribute reloadInterval number - Reload interval in milliseconds.
|
||||
*/
|
||||
|
||||
createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents) {
|
||||
createFetcher: function (url, fetchInterval, excludedEvents, maximumNumberOfDays, auth, broadcastPastEvents, identifier) {
|
||||
var self = this;
|
||||
|
||||
if (!validUrl.isUri(url)) {
|
||||
self.sendSocketNotification("INCORRECT_URL", {url: url});
|
||||
self.sendSocketNotification("INCORRECT_URL", { id: identifier, url: url });
|
||||
return;
|
||||
}
|
||||
|
||||
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, broadcastPastEvents);
|
||||
|
||||
fetcher.onReceive(function(fetcher) {
|
||||
//console.log('Broadcast events.');
|
||||
//console.log(fetcher.events());
|
||||
if (typeof self.fetchers[identifier + url] === "undefined") {
|
||||
Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
|
||||
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumNumberOfDays, auth, broadcastPastEvents);
|
||||
|
||||
fetcher.onReceive(function (fetcher) {
|
||||
self.sendSocketNotification("CALENDAR_EVENTS", {
|
||||
id: identifier,
|
||||
url: fetcher.url(),
|
||||
events: fetcher.events()
|
||||
});
|
||||
});
|
||||
|
||||
fetcher.onError(function(fetcher, error) {
|
||||
console.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
|
||||
fetcher.onError(function (fetcher, error) {
|
||||
Log.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
|
||||
self.sendSocketNotification("FETCH_ERROR", {
|
||||
id: identifier,
|
||||
url: fetcher.url(),
|
||||
error: error
|
||||
});
|
||||
});
|
||||
|
||||
self.fetchers[url] = fetcher;
|
||||
self.fetchers[identifier + url] = fetcher;
|
||||
} else {
|
||||
//console.log('Use existing news fetcher for url: ' + url);
|
||||
fetcher = self.fetchers[url];
|
||||
Log.log("Use existing calendar fetcher for url: " + url);
|
||||
fetcher = self.fetchers[identifier + url];
|
||||
fetcher.broadcastEvents();
|
||||
}
|
||||
|
||||
|
@@ -1,4 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8.9"
|
||||
install: npm install
|
178
modules/default/calendar/vendor/ical.js/LICENSE
vendored
@@ -1,178 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
13
modules/default/calendar/vendor/ical.js/NOTICE
vendored
@@ -1,13 +0,0 @@
|
||||
Copyright 2012 Peter Braden
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@@ -1,16 +0,0 @@
|
||||
'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 (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')}`);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@@ -1,118 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,40 +0,0 @@
|
||||
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
|
452
modules/default/calendar/vendor/ical.js/ical.js
vendored
@@ -1,452 +0,0 @@
|
||||
(function(name, definition) {
|
||||
|
||||
/****************
|
||||
* A tolerant, minimal icalendar parser
|
||||
* (http://tools.ietf.org/html/rfc5545)
|
||||
*
|
||||
* <peterbraden@peterbraden.co.uk>
|
||||
* **************/
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = definition();
|
||||
} else if (typeof define === 'function' && typeof define.amd === 'object'){
|
||||
define(definition);
|
||||
} else {
|
||||
this[name] = definition();
|
||||
}
|
||||
|
||||
}('ical', function(){
|
||||
|
||||
// Unescape Text re RFC 4.3.11
|
||||
var text = function(t){
|
||||
t = t || "";
|
||||
return (t
|
||||
.replace(/\\\,/g, ',')
|
||||
.replace(/\\\;/g, ';')
|
||||
.replace(/\\[nN]/g, '\n')
|
||||
.replace(/\\\\/g, '\\')
|
||||
)
|
||||
}
|
||||
|
||||
var parseParams = function(p){
|
||||
var out = {}
|
||||
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
|
||||
}
|
||||
|
||||
var parseValue = function(val){
|
||||
if ('TRUE' === val)
|
||||
return true;
|
||||
|
||||
if ('FALSE' === val)
|
||||
return false;
|
||||
|
||||
var number = Number(val);
|
||||
if (!isNaN(number))
|
||||
return number;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
var storeValParam = function (name) {
|
||||
return function (val, curr) {
|
||||
var current = curr[name];
|
||||
if (Array.isArray(current)) {
|
||||
current.push(val);
|
||||
return curr;
|
||||
}
|
||||
|
||||
if (current != null) {
|
||||
curr[name] = [current, val];
|
||||
return curr;
|
||||
}
|
||||
|
||||
curr[name] = val;
|
||||
return curr
|
||||
}
|
||||
}
|
||||
|
||||
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.tz = p.TZID
|
||||
}
|
||||
|
||||
return dt
|
||||
}
|
||||
|
||||
var dateParam = function(name){
|
||||
return function (val, params, curr) {
|
||||
|
||||
var newDate = text(val);
|
||||
|
||||
|
||||
if (params && params[0] === "VALUE=DATE") {
|
||||
// Just Date
|
||||
|
||||
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
|
||||
if (comps !== null) {
|
||||
// No TZ info - assume same timezone as this computer
|
||||
newDate = new Date(
|
||||
comps[1],
|
||||
parseInt(comps[2], 10)-1,
|
||||
comps[3]
|
||||
);
|
||||
|
||||
newDate = addTZ(newDate, params);
|
||||
newDate.dateOnly = true;
|
||||
|
||||
// Store as string - worst case scenario
|
||||
return storeValParam(name)(newDate, curr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//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)
|
||||
);
|
||||
}
|
||||
|
||||
newDate = addTZ(newDate, params);
|
||||
}
|
||||
|
||||
|
||||
// Store as string - worst case scenario
|
||||
return storeValParam(name)(newDate, curr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var geoParam = function(name){
|
||||
return function(val, params, curr){
|
||||
storeParam(val, params, curr)
|
||||
var parts = val.split(';');
|
||||
curr[name] = {lat:Number(parts[0]), lon:Number(parts[1])};
|
||||
return curr
|
||||
}
|
||||
}
|
||||
|
||||
var categoriesParam = function (name) {
|
||||
var separatorPattern = /\s*,\s*/g;
|
||||
return function (val, params, curr) {
|
||||
storeParam(val, params, curr)
|
||||
if (curr[name] === undefined)
|
||||
curr[name] = val ? val.split(separatorPattern) : []
|
||||
else
|
||||
if (val)
|
||||
curr[name] = curr[name].concat(val.split(separatorPattern))
|
||||
return curr
|
||||
}
|
||||
}
|
||||
|
||||
// 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){
|
||||
fb.type = p.FBTYPE || "BUSY"
|
||||
}
|
||||
|
||||
return fb;
|
||||
}
|
||||
|
||||
var freebusyParam = function (name) {
|
||||
return function(val, params, curr){
|
||||
var fb = addFBType({}, params);
|
||||
curr[name] = curr[name] || []
|
||||
curr[name].push(fb);
|
||||
|
||||
storeParam(val, params, fb);
|
||||
|
||||
var parts = val.split('/');
|
||||
|
||||
['start', 'end'].forEach(function (name, index) {
|
||||
dateParam(name)(parts[index], params, fb);
|
||||
});
|
||||
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
|
||||
objectHandlers : {
|
||||
'BEGIN' : function(component, params, curr, stack){
|
||||
stack.push(curr)
|
||||
|
||||
return {type:component, params:params}
|
||||
}
|
||||
|
||||
, 'END' : function(component, params, curr, stack){
|
||||
// prevents the need to search the root of the tree for the VCALENDAR object
|
||||
if (component === "VCALENDAR") {
|
||||
//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];
|
||||
if (typeof obj === 'string') {
|
||||
delete curr[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return curr
|
||||
}
|
||||
|
||||
var par = stack.pop()
|
||||
|
||||
if (curr.uid)
|
||||
{
|
||||
// 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
|
||||
|
||||
return par
|
||||
}
|
||||
|
||||
, 'SUMMARY' : storeParam('summary')
|
||||
, 'DESCRIPTION' : storeParam('description')
|
||||
, 'URL' : storeParam('url')
|
||||
, 'UID' : storeParam('uid')
|
||||
, 'LOCATION' : storeParam('location')
|
||||
, 'DTSTART' : dateParam('start')
|
||||
, 'DTEND' : dateParam('end')
|
||||
, 'EXDATE' : exdateParam('exdate')
|
||||
,' CLASS' : storeParam('class')
|
||||
, 'TRANSP' : storeParam('transparency')
|
||||
, 'GEO' : geoParam('geo')
|
||||
, 'PERCENT-COMPLETE': storeParam('completion')
|
||||
, 'COMPLETED': dateParam('completed')
|
||||
, 'CATEGORIES': categoriesParam('categories')
|
||||
, 'FREEBUSY': freebusyParam('freebusy')
|
||||
, 'DTSTAMP': dateParam('dtstamp')
|
||||
, 'CREATED': dateParam('created')
|
||||
, 'LAST-MODIFIED': dateParam('lastmodified')
|
||||
, 'RECURRENCE-ID': recurrenceParam('recurrenceid')
|
||||
|
||||
},
|
||||
|
||||
|
||||
handleObject : function(name, val, params, ctx, stack, line){
|
||||
var self = this
|
||||
|
||||
if(self.objectHandlers[name])
|
||||
return self.objectHandlers[name](val, params, ctx, stack, line)
|
||||
|
||||
//handling custom properties
|
||||
if(name.match(/X\-[\w\-]+/) && stack.length > 0) {
|
||||
//trimming the leading and perform storeParam
|
||||
name = name.substring(2);
|
||||
return (storeParam(name))(val, params, ctx, stack, line);
|
||||
}
|
||||
|
||||
return storeParam(name.toLowerCase())(val, params, ctx);
|
||||
},
|
||||
|
||||
|
||||
parseICS : function(str){
|
||||
var self = this
|
||||
var lines = str.split(/\r?\n/)
|
||||
var ctx = {}
|
||||
var stack = []
|
||||
|
||||
for (var i = 0, ii = lines.length, l = lines[0]; i<ii; i++, l=lines[i]){
|
||||
//Unfold : RFC#3.1
|
||||
while (lines[i+1] && /[ \t]/.test(lines[i+1][0])) {
|
||||
l += lines[i+1].slice(1)
|
||||
i += 1
|
||||
}
|
||||
|
||||
var kv = l.split(":")
|
||||
|
||||
if (kv.length < 2){
|
||||
// Invalid line - must have k&v
|
||||
continue;
|
||||
}
|
||||
|
||||
// Although the spec says that vals with colons should be quote wrapped
|
||||
// in practise nobody does, so we assume further colons are part of the
|
||||
// val
|
||||
var value = kv.slice(1).join(":")
|
||||
, kp = kv[0].split(";")
|
||||
, name = kp[0]
|
||||
, params = kp.slice(1)
|
||||
|
||||
ctx = self.handleObject(name, value, params, ctx, stack, l) || {}
|
||||
}
|
||||
|
||||
// type and params are added to the list of items, get rid of them.
|
||||
delete ctx.type
|
||||
delete ctx.params
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
}
|
||||
}))
|
@@ -1,8 +0,0 @@
|
||||
module.exports = require('./ical')
|
||||
|
||||
var node = require('./node-ical')
|
||||
|
||||
// Copy node functions across to exports
|
||||
for (var i in node){
|
||||
module.exports[i] = node[i]
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
var ical = require('./ical')
|
||||
, request = require('request')
|
||||
, fs = require('fs')
|
||||
|
||||
exports.fromURL = function(url, opts, cb){
|
||||
if (!cb)
|
||||
return;
|
||||
request(url, opts, function(err, r, data){
|
||||
if (err)
|
||||
{
|
||||
return cb(err, null);
|
||||
}
|
||||
else if (r.statusCode != 200)
|
||||
{
|
||||
return cb(r.statusCode + ": " + r.statusMessage, null);
|
||||
}
|
||||
|
||||
cb(undefined, ical.parseICS(data));
|
||||
})
|
||||
}
|
||||
|
||||
exports.parseFile = function(filename){
|
||||
return ical.parseICS(fs.readFileSync(filename, 'utf8'))
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
// 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 (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);
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "ical",
|
||||
"version": "0.5.0",
|
||||
"main": "index.js",
|
||||
"description": "A tolerant, minimal icalendar parser",
|
||||
"keywords": [
|
||||
"ical",
|
||||
"ics",
|
||||
"calendar"
|
||||
],
|
||||
"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.88.0",
|
||||
"rrule": "2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vows": "0.8.2",
|
||||
"underscore": "1.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/vows/bin/vows ./test/test.js"
|
||||
}
|
||||
}
|
@@ -1,62 +0,0 @@
|
||||
# ical.js #
|
||||
(Formerly node-ical)
|
||||
|
||||
[](https://travis-ci.org/peterbraden/ical.js)
|
||||
|
||||
A tolerant, minimal icalendar parser for javascript/node
|
||||
(http://tools.ietf.org/html/rfc5545)
|
||||
|
||||
|
||||
|
||||
## Install - Node.js ##
|
||||
|
||||
ical.js is availble on npm:
|
||||
|
||||
npm install ical
|
||||
|
||||
|
||||
|
||||
## API ##
|
||||
|
||||
ical.parseICS(str)
|
||||
|
||||
Parses a string with an ICS File
|
||||
|
||||
var data = ical.parseFile(filename)
|
||||
|
||||
Reads in the specified iCal file, parses it and returns the parsed data
|
||||
|
||||
ical.fromURL(url, options, function(err, data) {} )
|
||||
|
||||
Use the request library to fetch the specified URL (```opts``` gets passed on to the ```request()``` call), and call the function with the result (either an error or the data).
|
||||
|
||||
|
||||
|
||||
## Example 1 - Print list of upcoming node conferences (see example.js)
|
||||
```javascript
|
||||
'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 (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.
|
500
modules/default/calendar/vendor/ical.js/test/test.js
vendored
@@ -1,500 +0,0 @@
|
||||
/****
|
||||
* Tests
|
||||
*
|
||||
*
|
||||
***/
|
||||
process.env.TZ = 'America/San_Francisco';
|
||||
var ical = require('../index')
|
||||
|
||||
var vows = require('vows')
|
||||
, assert = require('assert')
|
||||
, _ = require('underscore')
|
||||
|
||||
vows.describe('node-ical').addBatch({
|
||||
'when parsing test1.ics (node conferences schedule from lanyrd.com, modified)': {
|
||||
topic: function () {
|
||||
return ical.parseFile('./test/test1.ics')
|
||||
}
|
||||
|
||||
,'we get 9 events': function (topic) {
|
||||
var events = _.select(_.values(topic), function(x){ return x.type==='VEVENT'})
|
||||
assert.equal (events.length, 9);
|
||||
}
|
||||
|
||||
,'event 47f6e' : {
|
||||
topic: function(events){
|
||||
return _.select(_.values(events),
|
||||
function(x){
|
||||
return x.uid ==='47f6ea3f28af2986a2192fa39a91fa7d60d26b76'})[0]
|
||||
}
|
||||
,'is in fort lauderdale' : function(topic){
|
||||
assert.equal(topic.location, "Fort Lauderdale, United States")
|
||||
}
|
||||
,'starts Tue, 29 Nov 2011' : function(topic){
|
||||
assert.equal(topic.start.toDateString(), new Date(2011,10,29).toDateString())
|
||||
}
|
||||
}
|
||||
, 'event 480a' : {
|
||||
topic: function(events){
|
||||
return _.select(_.values(events),
|
||||
function(x){
|
||||
return x.uid ==='480a3ad48af5ed8965241f14920f90524f533c18'})[0]
|
||||
}
|
||||
, '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){
|
||||
return _.select(_.values(events),
|
||||
function(x){
|
||||
return x.uid === 'd4c826dfb701f611416d69b4df81caf9ff80b03a'})[0]
|
||||
}
|
||||
, 'has a start datetime' : function(topic){
|
||||
assert.equal(topic.start.toDateString(), new Date(Date.UTC(2011, 2, 12, 20, 0, 0)).toDateString())
|
||||
}
|
||||
}
|
||||
|
||||
, 'event sdfkf09fsd0 (Invalid Date)' :{
|
||||
topic : function(events){
|
||||
return _.select(_.values(events),
|
||||
function(x){
|
||||
return x.uid === 'sdfkf09fsd0'})[0]
|
||||
}
|
||||
, 'has a start datetime' : function(topic){
|
||||
assert.equal(topic.start, "Next Year")
|
||||
}
|
||||
}
|
||||
}
|
||||
, 'with test2.ics (testing ical features)' : {
|
||||
topic: function () {
|
||||
return ical.parseFile('./test/test2.ics')
|
||||
}
|
||||
, 'todo item uid4@host1.com' : {
|
||||
topic : function(items){
|
||||
return items['uid4@host1.com']
|
||||
}
|
||||
, 'is a VTODO' : function(topic){
|
||||
assert.equal(topic.type, 'VTODO')
|
||||
}
|
||||
}
|
||||
, 'vfreebusy' : {
|
||||
topic: function(events) {
|
||||
return _.select(_.values(events), function(x) {
|
||||
return x.type === 'VFREEBUSY';
|
||||
})[0];
|
||||
}
|
||||
, 'has a URL' : function(topic) {
|
||||
assert.equal(topic.url, 'http://www.host.com/calendar/busytime/jsmith.ifb');
|
||||
}
|
||||
}
|
||||
, 'vfreebusy first freebusy' : {
|
||||
topic: function(events) {
|
||||
return _.select(_.values(events), function(x) {
|
||||
return x.type === 'VFREEBUSY';
|
||||
})[0].freebusy[0];
|
||||
}
|
||||
, 'has undefined type defaulting to busy' : function(topic) {
|
||||
assert.equal(topic.type, "BUSY");
|
||||
}
|
||||
, 'has an start datetime' : function(topic) {
|
||||
assert.equal(topic.start.getFullYear(), 1998);
|
||||
assert.equal(topic.start.getUTCMonth(), 2);
|
||||
assert.equal(topic.start.getUTCDate(), 14);
|
||||
assert.equal(topic.start.getUTCHours(), 23);
|
||||
assert.equal(topic.start.getUTCMinutes(), 30);
|
||||
}
|
||||
, 'has an end datetime' : function(topic) {
|
||||
assert.equal(topic.end.getFullYear(), 1998);
|
||||
assert.equal(topic.end.getUTCMonth(), 2);
|
||||
assert.equal(topic.end.getUTCDate(), 15);
|
||||
assert.equal(topic.end.getUTCHours(), 00);
|
||||
assert.equal(topic.end.getUTCMinutes(), 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
, 'with test3.ics (testing tvcountdown.com)' : {
|
||||
topic: function() {
|
||||
return ical.parseFile('./test/test3.ics');
|
||||
}
|
||||
, 'event -83' : {
|
||||
topic: function(events) {
|
||||
return _.select(_.values(events), function(x) {
|
||||
return x.uid === '20110505T220000Z-83@tvcountdown.com';
|
||||
})[0];
|
||||
}
|
||||
, 'has a start datetime' : function(topic) {
|
||||
assert.equal(topic.start.getFullYear(), 2011);
|
||||
assert.equal(topic.start.getMonth(), 4);
|
||||
}
|
||||
, 'has an end datetime' : function(topic) {
|
||||
assert.equal(topic.end.getFullYear(), 2011);
|
||||
assert.equal(topic.end.getMonth(), 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
, 'with test4.ics (testing tripit.com)' : {
|
||||
topic: function() {
|
||||
return ical.parseFile('./test/test4.ics');
|
||||
}
|
||||
, 'event c32a5...' : {
|
||||
topic: function(events) {
|
||||
return _.select(_.values(events), function(x) {
|
||||
return x.uid === 'c32a5eaba2354bb29e012ec18da827db90550a3b@tripit.com';
|
||||
})[0];
|
||||
}
|
||||
, 'has a start datetime' : function(topic) {
|
||||
assert.equal(topic.start.getFullYear(), 2011);
|
||||
assert.equal(topic.start.getMonth(), 09);
|
||||
assert.equal(topic.start.getDate(), 11);
|
||||
}
|
||||
|
||||
, 'has a summary' : function(topic){
|
||||
// escaped commas and semicolons should be replaced
|
||||
assert.equal(topic.summary, 'South San Francisco, CA, October 2011;')
|
||||
|
||||
}
|
||||
|
||||
, 'has a description' : function(topic){
|
||||
var desired = 'John Doe is in South San Francisco, CA from Oct 11 ' +
|
||||
'to Oct 13, 2011\nView and/or edit details in TripIt : http://www.tripit.c' +
|
||||
'om/trip/show/id/23710889\nTripIt - organize your travel at http://www.trip' +
|
||||
'it.com\n'
|
||||
assert.equal(topic.description, desired)
|
||||
|
||||
}
|
||||
|
||||
, 'has a geolocation' : function(topic){
|
||||
assert.ok(topic.geo, 'no geo param')
|
||||
assert.equal(topic.geo.lat, 37.654656)
|
||||
assert.equal(topic.geo.lon, -122.40775)
|
||||
}
|
||||
|
||||
, 'has transparency' : function(topic){
|
||||
assert.equal(topic.transparency, 'TRANSPARENT')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
, 'with test5.ics (testing meetup.com)' : {
|
||||
topic: function () {
|
||||
return ical.parseFile('./test/test5.ics')
|
||||
}
|
||||
, 'event nsmxnyppbfc@meetup.com' : {
|
||||
topic: function(events) {
|
||||
return _.select(_.values(events), function(x) {
|
||||
return x.uid === 'event_nsmxnyppbfc@meetup.com';
|
||||
})[0];
|
||||
}
|
||||
, 'has a start' : function(topic){
|
||||
assert.equal(topic.start.tz, 'America/Phoenix')
|
||||
assert.equal(topic.start.toISOString(), new Date(2011, 10, 09, 19, 0,0).toISOString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
, 'with test6.ics (testing assembly.org)': {
|
||||
topic: function () {
|
||||
return ical.parseFile('./test/test6.ics')
|
||||
}
|
||||
, 'event with no ID' : {
|
||||
topic: function(events) {
|
||||
return _.select(_.values(events), function(x) {
|
||||
return x.summary === 'foobar Summer 2011 starts!';
|
||||
})[0];
|
||||
}
|
||||
, 'has a start' : function(topic){
|
||||
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"
|
||||
})[0];
|
||||
}
|
||||
, "Has an RRULE": function(topic){
|
||||
assert.notEqual(topic.rrule, undefined);
|
||||
}
|
||||
, "RRule text": function(topic){
|
||||
assert.equal(topic.rrule.toText(), "every 5 weeks on Monday, Friday until January 30, 2013")
|
||||
}
|
||||
}
|
||||
}
|
||||
, 'with test7.ics (testing dtstart of rrule)' :{
|
||||
topic: function() {
|
||||
return ical.parseFile('./test/test7.ics');
|
||||
},
|
||||
'recurring yearly event (14 july)': {
|
||||
topic: function(events){
|
||||
var ev = _.values(events)[0];
|
||||
return ev.rrule.between(new Date(2013, 0, 1), new Date(2014, 0, 1));
|
||||
},
|
||||
'dt start well set': function(topic) {
|
||||
assert.equal(topic[0].toDateString(), new Date(2013, 6, 14).toDateString());
|
||||
}
|
||||
}
|
||||
}
|
||||
, "with test 8.ics (VTODO completion)": {
|
||||
topic: function() {
|
||||
return ical.parseFile('./test/test8.ics');
|
||||
},
|
||||
'grabbing VTODO task': {
|
||||
topic: function(topic) {
|
||||
return _.values(topic)[0];
|
||||
},
|
||||
'task completed': function(task){
|
||||
assert.equal(task.completion, 100);
|
||||
assert.equal(task.completed.toISOString(), new Date(2013, 06, 16, 10, 57, 45).toISOString());
|
||||
}
|
||||
}
|
||||
}
|
||||
, "with test 9.ics (VEVENT with VALARM)": {
|
||||
topic: function() {
|
||||
return ical.parseFile('./test/test9.ics');
|
||||
},
|
||||
'grabbing VEVENT task': {
|
||||
topic: function(topic) {
|
||||
return _.values(topic)[0];
|
||||
},
|
||||
'task completed': function(task){
|
||||
assert.equal(task.summary, "Event with an alarm");
|
||||
}
|
||||
}
|
||||
}
|
||||
, 'with test 11.ics (VEVENT with custom properties)': {
|
||||
topic: function() {
|
||||
return ical.parseFile('./test10.ics');
|
||||
},
|
||||
'grabbing custom properties': {
|
||||
topic: function(topic) {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'with test10.ics': {
|
||||
topic: function () {
|
||||
return ical.parseFile('./test/test10.ics');
|
||||
},
|
||||
|
||||
'when categories present': {
|
||||
topic: function (t) {return _.values(t)[0]},
|
||||
|
||||
'should be a list': function (e) {
|
||||
assert(e.categories instanceof [].constructor);
|
||||
},
|
||||
|
||||
'should contain individual category values': function (e) {
|
||||
assert.deepEqual(e.categories, ['cat1', 'cat2', 'cat3']);
|
||||
}
|
||||
},
|
||||
|
||||
'when categories present with trailing whitespace': {
|
||||
topic: function (t) {return _.values(t)[1]},
|
||||
|
||||
'should contain individual category values without whitespace': function (e) {
|
||||
assert.deepEqual(e.categories, ['cat1', 'cat2', 'cat3']);
|
||||
}
|
||||
},
|
||||
|
||||
'when categories present but empty': {
|
||||
topic: function (t) {return _.values(t)[2]},
|
||||
|
||||
'should be an empty list': function (e) {
|
||||
assert.deepEqual(e.categories, []);
|
||||
}
|
||||
},
|
||||
|
||||
'when categories present but singular': {
|
||||
topic: function (t) {return _.values(t)[3]},
|
||||
|
||||
'should be a list of single item': function (e) {
|
||||
assert.deepEqual(e.categories, ['lonely-cat']);
|
||||
}
|
||||
},
|
||||
|
||||
'when categories present on multiple lines': {
|
||||
topic: function (t) {return _.values(t)[4]},
|
||||
|
||||
'should contain the category values in an array': function (e) {
|
||||
assert.deepEqual(e.categories, ['cat1', 'cat2', 'cat3']);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'with test11.ics (testing zimbra freebusy)': {
|
||||
topic: function () {
|
||||
return ical.parseFile('./test/test11.ics');
|
||||
},
|
||||
|
||||
'freebusy params' : {
|
||||
topic: function(events) {
|
||||
return _.values(events)[0];
|
||||
}
|
||||
, 'has a URL' : function(topic) {
|
||||
assert.equal(topic.url, 'http://mail.example.com/yvr-2a@example.com/20140416');
|
||||
}
|
||||
, 'has an ORGANIZER' : function(topic) {
|
||||
assert.equal(topic.organizer, 'mailto:yvr-2a@example.com');
|
||||
}
|
||||
, 'has an start datetime' : function(topic) {
|
||||
assert.equal(topic.start.getFullYear(), 2014);
|
||||
assert.equal(topic.start.getMonth(), 3);
|
||||
}
|
||||
, 'has an end datetime' : function(topic) {
|
||||
assert.equal(topic.end.getFullYear(), 2014);
|
||||
assert.equal(topic.end.getMonth(), 6);
|
||||
}
|
||||
}
|
||||
, 'freebusy busy events' : {
|
||||
topic: function(events) {
|
||||
return _.select(_.values(events)[0].freebusy, function(x) {
|
||||
return x.type === 'BUSY';
|
||||
})[0];
|
||||
}
|
||||
, 'has an start datetime' : function(topic) {
|
||||
assert.equal(topic.start.getFullYear(), 2014);
|
||||
assert.equal(topic.start.getMonth(), 3);
|
||||
assert.equal(topic.start.getUTCHours(), 15);
|
||||
assert.equal(topic.start.getUTCMinutes(), 15);
|
||||
}
|
||||
, 'has an end datetime' : function(topic) {
|
||||
assert.equal(topic.end.getFullYear(), 2014);
|
||||
assert.equal(topic.end.getMonth(), 3);
|
||||
assert.equal(topic.end.getUTCHours(), 19);
|
||||
assert.equal(topic.end.getUTCMinutes(), 00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
, '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://255.255.255.255/', {}, this.callback);
|
||||
}
|
||||
, 'are passed back to the callback' : function (err, result) {
|
||||
assert.instanceOf(err, Error);
|
||||
if (!err){
|
||||
console.log(">E:", err, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}).export(module)
|
||||
|
||||
|
||||
//ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics',
|
||||
// {},
|
||||
// function(err, data){
|
||||
// console.log("OUT:", data)
|
||||
// })
|
@@ -1,78 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//lanyrd.com//Lanyrd//EN
|
||||
X-ORIGINAL-URL:http://lanyrd.com/topics/nodejs/nodejs.ics
|
||||
X-WR-CALNAME;CHARSET=utf-8:Node.js conferences
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:Dyncon 2011
|
||||
LOCATION;CHARSET=utf-8:Stockholm, Sweden
|
||||
URL:http://lanyrd.com/2011/dyncon/
|
||||
UID:d4c826dfb701f611416d69b4df81caf9ff80b03a
|
||||
DTSTART:20110312T200000Z
|
||||
DTEND;VALUE=DATE:20110314
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:[Async]: Everything Express
|
||||
LOCATION;CHARSET=utf-8:Brighton, United Kingdom
|
||||
URL:http://lanyrd.com/2011/asyncjs-express/
|
||||
UID:480a3ad48af5ed8965241f14920f90524f533c18
|
||||
DTSTART;VALUE=DATE:20110324
|
||||
DTEND;VALUE=DATE:20110325
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:JSConf US 2011
|
||||
LOCATION;CHARSET=utf-8:Portland, United States
|
||||
URL:http://lanyrd.com/2011/jsconf/
|
||||
UID:ed334cc85db5ebdff5ff5a630a7a48631a677dbe
|
||||
DTSTART;VALUE=DATE:20110502
|
||||
DTEND;VALUE=DATE:20110504
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:NodeConf 2011
|
||||
LOCATION;CHARSET=utf-8:Portland, United States
|
||||
URL:http://lanyrd.com/2011/nodeconf/
|
||||
UID:25169a7b1ba5c248278f47120a40878055dc8c15
|
||||
DTSTART;VALUE=DATE:20110505
|
||||
DTEND;VALUE=DATE:20110506
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:BrazilJS
|
||||
LOCATION;CHARSET=utf-8:Fortaleza, Brazil
|
||||
URL:http://lanyrd.com/2011/braziljs/
|
||||
UID:dafee3be83624f3388c5635662229ff11766bb9c
|
||||
DTSTART;VALUE=DATE:20110513
|
||||
DTEND;VALUE=DATE:20110515
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:Falsy Values
|
||||
LOCATION;CHARSET=utf-8:Warsaw, Poland
|
||||
URL:http://lanyrd.com/2011/falsy-values/
|
||||
UID:73cad6a09ac4e7310979c6130f871d17d990b5ad
|
||||
DTSTART;VALUE=DATE:20110518
|
||||
DTEND;VALUE=DATE:20110521
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:nodecamp.eu
|
||||
LOCATION;CHARSET=utf-8:Cologne, Germany
|
||||
URL:http://lanyrd.com/2011/nodecampde/
|
||||
UID:b728a5fdb5f292b6293e4a2fd97a1ccfc69e9d6f
|
||||
DTSTART;VALUE=DATE:20110611
|
||||
DTEND;VALUE=DATE:20110613
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:Rich Web Experience 2011
|
||||
LOCATION;CHARSET=utf-8:Fort Lauderdale, United States
|
||||
URL:http://lanyrd.com/2011/rich-web-experience/
|
||||
UID:47f6ea3f28af2986a2192fa39a91fa7d60d26b76
|
||||
DTSTART;VALUE=DATE:20111129
|
||||
DTEND;VALUE=DATE:20111203
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SUMMARY;CHARSET=utf-8:Foobar
|
||||
UID:sdfkf09fsd0
|
||||
DTSTART;VALUE=DATE:Next Year
|
||||
DTEND;VALUE=DATE:20111203
|
||||
END:VEVENT
|
||||
|
||||
END:VCALENDAR
|
@@ -1,34 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
BEGIN:VEVENT
|
||||
UID:1
|
||||
SUMMARY:Event with a category
|
||||
DESCRIPTION:Details for an event with a category
|
||||
CATEGORIES:cat1,cat2,cat3
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:2
|
||||
SUMMARY:Event with a category
|
||||
DESCRIPTION:Details for an event with a category
|
||||
CATEGORIES:cat1 , cat2, cat3
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:3
|
||||
SUMMARY:Event with a category
|
||||
DESCRIPTION:Details for an event with a category
|
||||
CATEGORIES:
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:4
|
||||
SUMMARY:Event with a category
|
||||
DESCRIPTION:Details for an event with a category
|
||||
CATEGORIES:lonely-cat
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:5
|
||||
SUMMARY:Event with a category
|
||||
DESCRIPTION:Details for an event with a category
|
||||
CATEGORIES:cat1
|
||||
CATEGORIES:cat2
|
||||
CATEGORIES:cat3
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@@ -1,41 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:Zimbra-Calendar-Provider
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VFREEBUSY
|
||||
ORGANIZER:mailto:yvr-2a@example.com
|
||||
DTSTAMP:20140516T235436Z
|
||||
DTSTART:20140415T235436Z
|
||||
DTEND:20140717T235436Z
|
||||
URL:http://mail.example.com/yvr-2a@example.com/20140416
|
||||
FREEBUSY;FBTYPE=BUSY:20140416T151500Z/20140416T190000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140416T195500Z/20140416T231500Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140417T193000Z/20140417T203000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140421T210000Z/20140421T213000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140423T180000Z/20140423T190000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140423T200000Z/20140423T210000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140423T223500Z/20140423T231500Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140424T155000Z/20140424T165500Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140424T170000Z/20140424T183000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140424T195000Z/20140424T230000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140425T144500Z/20140425T161500Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140425T180000Z/20140425T194500Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140425T223000Z/20140425T230000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140428T151500Z/20140428T163000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140428T170000Z/20140428T173000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140428T195500Z/20140428T213000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140428T231000Z/20140428T234000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140429T152500Z/20140429T170000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140429T180000Z/20140429T183000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140429T201500Z/20140429T230000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140430T162500Z/20140430T165500Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140430T180000Z/20140430T190000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140501T170000Z/20140501T173000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140501T175000Z/20140501T190000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140501T232000Z/20140501T235000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140502T163500Z/20140502T173000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140505T165500Z/20140505T173000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140505T201500Z/20140505T203000Z
|
||||
FREEBUSY;FBTYPE=BUSY:20140505T210000Z/20140505T213000Z
|
||||
END:VFREEBUSY
|
||||
END:VCALENDAR
|
@@ -1,19 +0,0 @@
|
||||
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
|
@@ -1,57 +0,0 @@
|
||||
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
|
@@ -1,33 +0,0 @@
|
||||
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
|
@@ -1,83 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
CALSCALE:GREGORIAN
|
||||
X-WR-TIMEZONE;VALUE=TEXT:US/Pacific
|
||||
METHOD:PUBLISH
|
||||
PRODID:-//Apple Computer\, Inc//iCal 1.0//EN
|
||||
X-WR-CALNAME;VALUE=TEXT:Example
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SEQUENCE:5
|
||||
DTSTART;TZID=US/Pacific:20021028T140000
|
||||
DTSTAMP:20021028T011706Z
|
||||
SUMMARY:Coffee with Jason
|
||||
UID:EC9439B1-FF65-11D6-9973-003065F99D04
|
||||
DTEND;TZID=US/Pacific:20021028T150000
|
||||
END:VEVENT
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DURATION:-P1D
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event reminder
|
||||
END:VALARM
|
||||
BEGIN:VEVENT
|
||||
SEQUENCE:1
|
||||
DTSTAMP:20021128T012034Z
|
||||
SUMMARY:Code Review
|
||||
UID:EC944331-FF65-11D6-9973-003065F99D04
|
||||
DTSTART;TZID=US/Pacific:20021127T120000
|
||||
DURATION:PT1H
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
SEQUENCE:1
|
||||
DTSTAMP:20021028T012034Z
|
||||
SUMMARY:Dinner with T
|
||||
UID:EC944CFA-FF65-11D6-9973-003065F99D04
|
||||
DTSTART;TZID=US/Pacific:20021216T200000
|
||||
DURATION:PT1H
|
||||
END:VEVENT
|
||||
BEGIN:VTODO
|
||||
DTSTAMP:19980130T134500Z
|
||||
SEQUENCE:2
|
||||
UID:uid4@host1.com
|
||||
ORGANIZER:MAILTO:unclesam@us.gov
|
||||
ATTENDEE;PARTSTAT=ACCEPTED:MAILTO:jqpublic@host.com
|
||||
DUE:19980415T235959
|
||||
STATUS:NEEDS-ACTION
|
||||
SUMMARY:Submit Income Taxes
|
||||
END:VTODO
|
||||
BEGIN:VALARM
|
||||
ACTION:AUDIO
|
||||
TRIGGER:19980403T120000
|
||||
ATTACH;FMTTYPE=audio/basic:http://host.com/pub/audio-
|
||||
files/ssbanner.aud
|
||||
REPEAT:4
|
||||
DURATION:PT1H
|
||||
END:VALARM
|
||||
BEGIN:VJOURNAL
|
||||
DTSTAMP:19970324T120000Z
|
||||
UID:uid5@host1.com
|
||||
ORGANIZER:MAILTO:jsmith@host.com
|
||||
STATUS:DRAFT
|
||||
CLASS:PUBLIC
|
||||
CATEGORY:Project Report, XYZ, Weekly Meeting
|
||||
DESCRIPTION:Project xyz Review Meeting Minutes\n
|
||||
Agenda\n1. Review of project version 1.0 requirements.\n2.
|
||||
Definition
|
||||
of project processes.\n3. Review of project schedule.\n
|
||||
Participants: John Smith, Jane Doe, Jim Dandy\n-It was
|
||||
decided that the requirements need to be signed off by
|
||||
product marketing.\n-Project processes were accepted.\n
|
||||
-Project schedule needs to account for scheduled holidays
|
||||
and employee vacation time. Check with HR for specific
|
||||
dates.\n-New schedule will be distributed by Friday.\n-
|
||||
Next weeks meeting is cancelled. No meeting until 3/23.
|
||||
END:VJOURNAL
|
||||
BEGIN:VFREEBUSY
|
||||
ORGANIZER:MAILTO:jsmith@host.com
|
||||
DTSTART:19980313T141711Z
|
||||
DTEND:19980410T141711Z
|
||||
FREEBUSY:19980314T233000Z/19980315T003000Z
|
||||
FREEBUSY:19980316T153000Z/19980316T163000Z
|
||||
FREEBUSY:19980318T030000Z/19980318T040000Z
|
||||
URL:http://www.host.com/calendar/busytime/jsmith.ifb
|
||||
END:VFREEBUSY
|
||||
END:VCALENDAR
|
@@ -1,226 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:tvcountdown.com
|
||||
X-WR-CALNAME:tvcountdown.com
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-WR-TIMEZONE:US/Eastern
|
||||
X-WR-CALNAME;VALUE=TEXT:tvcountdown.com
|
||||
X-WR-CALDESC:
|
||||
BEGIN:VEVENT
|
||||
UID:20110519T200000Z-79@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110519T200000
|
||||
DTEND;VALUE=DATE-TIME:20110519T203000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Big Bang Theory - S04E24 - The Roomate Transmogrfication
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110512T200000Z-79@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110512T200000
|
||||
DTEND;VALUE=DATE-TIME:20110512T203000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Big Bang Theory - S04E23 - The Engagement Reaction
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110505T220000Z-83@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110505T220000
|
||||
DTEND;VALUE=DATE-TIME:20110505T223000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:30 Rock - S05E23 - Respawn
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110505T200000Z-79@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110505T200000
|
||||
DTEND;VALUE=DATE-TIME:20110505T203000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Big Bang Theory - S04E22 - The Wildebeest Implementation
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110504T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110504T230000
|
||||
DTEND;VALUE=DATE-TIME:20110504T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E59 - David Barton
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110503T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110503T230000
|
||||
DTEND;VALUE=DATE-TIME:20110503T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E58 - Rachel Maddow
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110502T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110502T230000
|
||||
DTEND;VALUE=DATE-TIME:20110502T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E57 - Philip K. Howard
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110428T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110428T230000
|
||||
DTEND;VALUE=DATE-TIME:20110428T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E56 - William Cohan
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110428T220000Z-83@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110428T220000
|
||||
DTEND;VALUE=DATE-TIME:20110428T223000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:30 Rock - S05E22 - Everything Sunny All the Time Always
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110428T200000Z-79@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110428T200000
|
||||
DTEND;VALUE=DATE-TIME:20110428T203000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Big Bang Theory - S04E21 - The Agreement Dissection
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110427T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110427T230000
|
||||
DTEND;VALUE=DATE-TIME:20110427T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E55 - Sen. Bernie Sanders
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110426T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110426T230000
|
||||
DTEND;VALUE=DATE-TIME:20110426T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E54 - Elizabeth Warren
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110425T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110425T230000
|
||||
DTEND;VALUE=DATE-TIME:20110425T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E53 - Gigi Ibrahim
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110421T220000Z-83@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110421T220000
|
||||
DTEND;VALUE=DATE-TIME:20110421T223000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:30 Rock - S05E21 - 100th Episode Part 2 of 2
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110421T220000Z-83@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110421T220000
|
||||
DTEND;VALUE=DATE-TIME:20110421T223000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:30 Rock - S05E20 - 100th Episode Part 1 of 2
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110414T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110414T230000
|
||||
DTEND;VALUE=DATE-TIME:20110414T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E52 - Ricky Gervais
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110414T220000Z-83@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110414T220000
|
||||
DTEND;VALUE=DATE-TIME:20110414T223000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:30 Rock - S05E19 - I Heart Connecticut
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110413T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110413T230000
|
||||
DTEND;VALUE=DATE-TIME:20110413T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E51 - Tracy Morgan
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110412T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110412T230000
|
||||
DTEND;VALUE=DATE-TIME:20110412T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E50 - Gov. Deval Patrick
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110411T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110411T230000
|
||||
DTEND;VALUE=DATE-TIME:20110411T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E49 - Foo Fighters
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110407T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110407T230000
|
||||
DTEND;VALUE=DATE-TIME:20110407T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E48 - Jamie Oliver
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110407T200000Z-79@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110407T200000
|
||||
DTEND;VALUE=DATE-TIME:20110407T203000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Big Bang Theory - S04E20 - The Herb Garden Germination
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110406T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110406T230000
|
||||
DTEND;VALUE=DATE-TIME:20110406T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E47 - Mike Huckabee
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110405T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110405T230000
|
||||
DTEND;VALUE=DATE-TIME:20110405T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E46 - Colin Quinn
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110404T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110404T230000
|
||||
DTEND;VALUE=DATE-TIME:20110404T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E45 - Billy Crystal
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110331T230000Z-289@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110331T230000
|
||||
DTEND;VALUE=DATE-TIME:20110331T233000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Daily Show - S16E44 - Norm MacDonald
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20110331T200000Z-79@tvcountdown.com
|
||||
DTSTART;VALUE=DATE-TIME:20110331T200000
|
||||
DTEND;VALUE=DATE-TIME:20110331T203000
|
||||
DTSTAMP:20110430T192946Z
|
||||
URL;VALUE=URI:
|
||||
SUMMARY:The Big Bang Theory - S04E19 - The Zarnecki Incursion
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@@ -1,747 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
X-WR-CALNAME:John Doe (TripIt)
|
||||
X-WR-CALDESC:TripIt Calendar
|
||||
X-PUBLISHED-TTL:PT15M
|
||||
PRODID:-//John Doe/NONSGML Bennu 0.1//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
UID:c32a5eaba2354bb29e012ec18da827db90550a3b@tripit.com
|
||||
DTSTART;VALUE=DATE:20111011
|
||||
DTEND;VALUE=DATE:20111014
|
||||
SUMMARY:South San Francisco\, CA\, October 2011\;
|
||||
LOCATION:South San Francisco\, CA
|
||||
GEO:37.654656;-122.40775
|
||||
TRANSP:TRANSPARENT
|
||||
DESCRIPTION:John Doe is in South San Francisco\, CA from Oct 11
|
||||
to Oct 13\, 2011\nView and/or edit details in TripIt : http://www.tripit.c
|
||||
om/trip/show/id/23710889\nTripIt - organize your travel at http://www.trip
|
||||
it.com\n
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-ee275ccffa83f492d9eb63b01953b39f18d4f944@tripit.com
|
||||
DTSTART:20111011T100500
|
||||
DTEND:20111011T110500
|
||||
SUMMARY:Directions from SFO to Embassy Suites San Francisco Airport - Sout
|
||||
h San Francisco
|
||||
LOCATION:250 GATEWAY BLVD\, South San Francisco\, CA\, 94080
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/1234\n \n[Directions] 10/11/2011 10:05am - Directions from S
|
||||
FO to Embassy Suites San Francisco Airport - South San Francisco \nfrom: S
|
||||
FO \nto: 250 GATEWAY BLVD\, South San Francisco\, CA\, 94080 \nView direct
|
||||
ions here: http://maps.google.com/maps?output=mobile&saddr=SFO&daddr=250+G
|
||||
ATEWAY+BLVD%2C+South+San+Francisco%2C+CA%2C+94080 \n \n \n\nTripIt - organ
|
||||
ize your travel at http://www.tripit.com
|
||||
GEO:37.655634;-122.401273
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111011T165500Z
|
||||
SUMMARY:US403 PHX to SFO
|
||||
LOCATION:Phoenix (PHX)
|
||||
UID:item-c576afd397cf1f90578b4ba35e781b61ba8897db@tripit.com
|
||||
DTSTART:20111011T144500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/1234\n \n[Flight] 10/11/2011 US Airways(US) #403 dep PHX 7:4
|
||||
5am MST arr SFO 9:55am PDT\; John Doe\; seat(s) 8B\; conf #DXH9K
|
||||
Z\, BXQ9WH \nBooked on http://www.americanexpress-travel.com/\; Reference
|
||||
#: 4127 8626 9715\; http://www.americanexpress-travel.com/\; US:1-800-297-
|
||||
2977\, Outside:210-582-2716 \n \n \n\nTripIt - organize your travel at htt
|
||||
p://www.tripit.com
|
||||
GEO:37.618889;-122.375
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Pick-up Rental Car: Dollar Rent A Car
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-e99a90ee1c7e4f5b68a4e551009e5bb6c475940c@tripit.com
|
||||
DTSTART:20111011T172500Z
|
||||
DTEND:20111011T182500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/1234\n \n[Car Rental] Dollar Rent A Car\; San Francisco Inte
|
||||
rnational Airport\; primary driver John Doe\; conf #R9508361 \np
|
||||
ickup 10/11/2011 10:25am\; dropoff 10/13/2011 6:49pm \nEconomy \nBooked on
|
||||
http://www.americanexpress-travel.com/\; Reference #: 4127 8626 9715\; ht
|
||||
tp://www.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582
|
||||
-2716 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-in: Embassy Suites San Francisco Airport - South San Francis
|
||||
co
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-7f3288d418bed063cc82b4512e792fbb5d8ae761@tripit.com
|
||||
DTSTART:20111011T185500Z
|
||||
DTEND:20111011T195500Z
|
||||
LOCATION:250 GATEWAY BLVD\, South San Francisco\, CA\, 94080
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/23710889\n \n[Lodging] Embassy Suites San Francisco Airport - So
|
||||
uth San Francisco\; primary guest John Doe\; conf #R9508361 \n25
|
||||
0 GATEWAY BLVD\, South San Francisco\, CA\, 94080\; tel 1.650.589.3400 \na
|
||||
rrive 10/11/2011\; depart 10/13/2011\; rooms: 1 \nBooked on http://www.ame
|
||||
ricanexpress-travel.com/\; Reference #: 4127 8626 9715\; http://www.americ
|
||||
anexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-2716 \n \n \n\
|
||||
nTripIt - organize your travel at http://www.tripit.com
|
||||
GEO:37.655634;-122.401273
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-out: Embassy Suites San Francisco Airport - South San Franci
|
||||
sco
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-5eb4cb5fc25c55b0423921e18336e57f8c34598d@tripit.com
|
||||
DTSTART:20111014T011900Z
|
||||
DTEND:20111014T021900Z
|
||||
LOCATION:250 GATEWAY BLVD\, South San Francisco\, CA\, 94080
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/23710889\n \n[Lodging] Embassy Suites San Francisco Airport - So
|
||||
uth San Francisco\; primary guest John Doe\; conf #R9508361 \n25
|
||||
0 GATEWAY BLVD\, South San Francisco\, CA\, 94080\; tel 1.650.589.3400 \na
|
||||
rrive 10/11/2011\; depart 10/13/2011\; rooms: 1 \nBooked on http://www.ame
|
||||
ricanexpress-travel.com/\; Reference #: 4127 8626 9715\; http://www.americ
|
||||
anexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-2716 \n \n \n\
|
||||
nTripIt - organize your travel at http://www.tripit.com
|
||||
GEO:37.655634;-122.401273
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Drop-off Rental Car: Dollar Rent A Car
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-11fdbf5d02e84646025716d9f9c7a4158e1fb025@tripit.com
|
||||
DTSTART:20111014T014900Z
|
||||
DTEND:20111014T024900Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/23710889\n \n[Car Rental] Dollar Rent A Car\; San Francisco Inte
|
||||
rnational Airport\; primary driver John Doe\; conf #R9508361 \np
|
||||
ickup 10/11/2011 10:25am\; dropoff 10/13/2011 6:49pm \nEconomy \nBooked on
|
||||
http://www.americanexpress-travel.com/\; Reference #: 4127 8626 9715\; ht
|
||||
tp://www.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582
|
||||
-2716 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111014T051900Z
|
||||
SUMMARY:CO6256 SFO to PHX
|
||||
LOCATION:San Francisco (SFO)
|
||||
UID:item-cb485a571a01972d6bdc74c2b829905d6e3786bf@tripit.com
|
||||
DTSTART:20111014T031900Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/23710889\n \n[Flight] 10/13/2011 Continental Airlines(CO) #6256
|
||||
dep SFO 8:19pm PDT arr PHX 10:19pm MST\; John Doe\; conf #DXH9KZ
|
||||
\, BXQ9WH(Operated by United Airlines flight 6256) \nBooked on http://www.
|
||||
americanexpress-travel.com/\; Reference #: 4127 8626 9715\; http://www.ame
|
||||
ricanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-2716 \n \n
|
||||
\n\nTripIt - organize your travel at http://www.tripit.com
|
||||
GEO:33.436111;-112.009444
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
UID:c7b133db1e7be2713a4a63b75dcbad209690cab5@tripit.com
|
||||
DTSTART;VALUE=DATE:20111023
|
||||
DTEND;VALUE=DATE:20111028
|
||||
SUMMARY:Santa Barbara\, CA\, October 2011
|
||||
LOCATION:Santa Barbara\, CA
|
||||
GEO:34.420831;-119.69819
|
||||
TRANSP:TRANSPARENT
|
||||
DESCRIPTION:John Doe is in Santa Barbara\, CA from Oct 23 to Oct
|
||||
27\, 2011\nView and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\nTripIt - organize your travel at http://www.tripit.com
|
||||
\n
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111023T191200Z
|
||||
SUMMARY:US2719 PHX to SBA
|
||||
LOCATION:Phoenix (PHX)
|
||||
UID:item-c4375369e9070fcc04df39ed18c4d93087577591@tripit.com
|
||||
DTSTART:20111023T173500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Flight] 10/23/2011 US Airways(US) #2719 dep PHX 10
|
||||
:35am MST arr SBA 12:12pm PDT\; John Doe Ticket #0378717202638\;
|
||||
conf #A44XS5\, PRX98G\, FYYJZ4 \nBooked on http://www.americanexpress-tra
|
||||
vel.com/\; Reference #: 7128 8086 8504\; http://www.americanexpress-travel
|
||||
.com/\; US:1-800-297-2977\, Outside:210-582-2716\; Total Cost: $699.99 \n
|
||||
\n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
GEO:34.427778;-119.839444
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-962e4f045d12149319d1837ec096bf43770abd6e@tripit.com
|
||||
DTSTART:20111025T094000
|
||||
DTEND:20111025T104000
|
||||
SUMMARY:Directions from Hertz to Sofitel San Francisco Bay
|
||||
LOCATION:223 Twin Dolphin Drive\, Redwood City\, CA\, 94065
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Directions] 10/25/2011 9:40am - Directions from He
|
||||
rtz to Sofitel San Francisco Bay \nfrom: 780 McDonnell Road\, San Francisc
|
||||
o\, CA\, 94128 \nto: 223 Twin Dolphin Drive\, Redwood City\, CA\, 94065 \n
|
||||
View directions here: http://maps.google.com/maps?output=mobile&saddr=780+
|
||||
McDonnell+Road%2C+San+Francisco%2C+CA%2C+94128&daddr=223+Twin+Dolphin+Driv
|
||||
e%2C+Redwood+City%2C+CA%2C+94065 \n \n \n\nTripIt - organize your travel a
|
||||
t http://www.tripit.com
|
||||
GEO:37.5232475;-122.261296
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111025T162600Z
|
||||
SUMMARY:UA5304 SBA to SFO
|
||||
LOCATION:Santa Barbara (SBA)
|
||||
UID:item-ae300a6934c3820974dba2c9c5b8fae843c67693@tripit.com
|
||||
DTSTART:20111025T150900Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Flight] 10/25/2011 United Airlines(UA) #5304 dep S
|
||||
BA 8:09am PDT arr SFO 9:26am PDT\; John Doe Ticket #037871720263
|
||||
8\; seat(s) 11B\; conf #A44XS5\, PRX98G\, FYYJZ4 \nBooked on http://www.am
|
||||
ericanexpress-travel.com/\; Reference #: 7128 8086 8504\; http://www.ameri
|
||||
canexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-2716\; Total
|
||||
Cost: $699.99 \n \n \n\nTripIt - organize your travel at http://www.tripit
|
||||
.com
|
||||
GEO:37.618889;-122.375
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Pick-up Rental Car: Hertz
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-2a9fd5a57a4cdda4677fc6ce23738e1954fdbe2a@tripit.com
|
||||
DTSTART:20111025T163000Z
|
||||
DTEND:20111025T173000Z
|
||||
LOCATION:780 McDonnell Road\, San Francisco\, CA\, 94128
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Car Rental] Hertz\; San Francisco International Ai
|
||||
rport\; primary driver John Doe\; conf #F2633064194 \n780 McDonn
|
||||
ell Road\, San Francisco\, CA\, 94128 \npickup 10/25/2011 9:30am\; dropoff
|
||||
10/27/2011 7:00pm \nToyota Corolla or similar\; 84.57 USD \nBooked on htt
|
||||
p://www.hertz.com/\; Reference #: F2633064194\; http://www.hertz.com/\; 80
|
||||
0-654-3131\; Booking Rate: 84.57 USD\; Total Cost: 333.76 USD \n \n \n\nTr
|
||||
ipIt - organize your travel at http://www.tripit.com
|
||||
GEO:37.6297569;-122.4000351
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-98dfcb0bcfdcffcce9c58a84947212ed67cadda6@tripit.com
|
||||
DTSTART:20111025T163600Z
|
||||
DTEND:20111025T173600Z
|
||||
SUMMARY:Directions from SFO to Sofitel San Francisco Bay
|
||||
LOCATION:223 Twin Dolphin Drive\, Redwood City\, CA\, 94065
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Directions] 10/25/2011 9:36am - Directions from SF
|
||||
O to Sofitel San Francisco Bay \nfrom: SFO \nto: 223 Twin Dolphin Drive\,
|
||||
Redwood City\, CA\, 94065 \nView directions here: http://maps.google.com/m
|
||||
aps?output=mobile&saddr=SFO&daddr=223+Twin+Dolphin+Drive%2C+Redwood+City%2
|
||||
C+CA%2C+94065 \n \n \n\nTripIt - organize your travel at http://www.tripit
|
||||
.com
|
||||
GEO:37.5232475;-122.261296
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-in: Sofitel San Francisco Bay
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-8de3937b336c333faf2d55ad0a41c5ca6cc02393@tripit.com
|
||||
DTSTART:20111025T220000Z
|
||||
DTEND:20111025T230000Z
|
||||
LOCATION:223 Twin Dolphin Drive\, Redwood City\, CA\, 94065
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Lodging] Sofitel San Francisco Bay\; primary guest
|
||||
John Doe\; conf #F80-0GMW \n223 Twin Dolphin Drive\, Redwood Ci
|
||||
ty\, CA\, 94065\; tel (+1)650/598-9000 \narrive 10/25/2011\; depart 10/27/
|
||||
2011\; rooms: 1 \nBooked on http://www.sofitel.com/\; http://www.sofitel.c
|
||||
om/\; Total Cost: 564.00 USD \n \n \n\nTripIt - organize your travel at ht
|
||||
tp://www.tripit.com
|
||||
GEO:37.5232475;-122.261296
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-out: Sofitel San Francisco Bay
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-f3ade58646964bde101616a6d26ea7784a1a81e8@tripit.com
|
||||
DTSTART:20111027T190000Z
|
||||
DTEND:20111027T200000Z
|
||||
LOCATION:223 Twin Dolphin Drive\, Redwood City\, CA\, 94065
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Lodging] Sofitel San Francisco Bay\; primary guest
|
||||
John Doe\; conf #F80-0GMW \n223 Twin Dolphin Drive\, Redwood Ci
|
||||
ty\, CA\, 94065\; tel (+1)650/598-9000 \narrive 10/25/2011\; depart 10/27/
|
||||
2011\; rooms: 1 \nBooked on http://www.sofitel.com/\; http://www.sofitel.c
|
||||
om/\; Total Cost: 564.00 USD \n \n \n\nTripIt - organize your travel at ht
|
||||
tp://www.tripit.com
|
||||
GEO:37.5232475;-122.261296
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Drop-off Rental Car: Hertz
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-50620273fea0614d37775649034d5e1de92ae361@tripit.com
|
||||
DTSTART:20111028T020000Z
|
||||
DTEND:20111028T030000Z
|
||||
LOCATION:780 McDonnell Road\, San Francisco\, CA\, 94128
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Car Rental] Hertz\; San Francisco International Ai
|
||||
rport\; primary driver John Doe\; conf #F2633064194 \n780 McDonn
|
||||
ell Road\, San Francisco\, CA\, 94128 \npickup 10/25/2011 9:30am\; dropoff
|
||||
10/27/2011 7:00pm \nToyota Corolla or similar\; 84.57 USD \nBooked on htt
|
||||
p://www.hertz.com/\; Reference #: F2633064194\; http://www.hertz.com/\; 80
|
||||
0-654-3131\; Booking Rate: 84.57 USD\; Total Cost: 333.76 USD \n \n \n\nTr
|
||||
ipIt - organize your travel at http://www.tripit.com
|
||||
GEO:37.6297569;-122.4000351
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111028T051900Z
|
||||
SUMMARY:CO6256 SFO to PHX
|
||||
LOCATION:San Francisco (SFO)
|
||||
UID:item-71d327f30d8beeaf7bf50c8fa63ce16005b9b0df@tripit.com
|
||||
DTSTART:20111028T031900Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24259445\n \n[Flight] 10/27/2011 Continental Airlines(CO) #6256
|
||||
dep SFO 8:19pm PDT arr PHX 10:19pm MST\; John Doe Ticket #037871
|
||||
7202638\; seat(s) 17D\; conf #A44XS5\, PRX98G\, FYYJZ4(Operated by United
|
||||
Airlines flight 6256) \nBooked on http://www.americanexpress-travel.com/\;
|
||||
Reference #: 7128 8086 8504\; http://www.americanexpress-travel.com/\; US
|
||||
:1-800-297-2977\, Outside:210-582-2716\; Total Cost: $699.99 \n \n \n\nTri
|
||||
pIt - organize your travel at http://www.tripit.com
|
||||
GEO:33.436111;-112.009444
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
UID:2d4b446e63a94ade7dab0f0e9546b2d1965f011c@tripit.com
|
||||
DTSTART;VALUE=DATE:20111108
|
||||
DTEND;VALUE=DATE:20111111
|
||||
SUMMARY:Redwood City\, CA\, November 2011
|
||||
LOCATION:Redwood City\, CA
|
||||
GEO:37.485215;-122.236355
|
||||
TRANSP:TRANSPARENT
|
||||
DESCRIPTION:John Doe is in Redwood City\, CA from Nov 8 to Nov 1
|
||||
0\, 2011\nView and/or edit details in TripIt : http://www.tripit.com/trip/
|
||||
show/id/24913749\nTripIt - organize your travel at http://www.tripit.com\n
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111108T175700Z
|
||||
SUMMARY:US403 PHX to SFO
|
||||
LOCATION:Phoenix (PHX)
|
||||
UID:item-7de7d829b2f95991de6d01c3d68f24b84770168c@tripit.com
|
||||
DTSTART:20111108T154500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24913749\n \n[Flight] 11/8/2011 US Airways(US) #403 dep PHX 8:45
|
||||
am MST arr SFO 9:57am PST\; John Doe\; seat(s) 21C\; conf #FJDX0
|
||||
J\, I2W8HW \nBooked on http://www.americanexpress-travel.com/\; Reference
|
||||
#: 4129 9623 4732\; http://www.americanexpress-travel.com/\; US:1-800-297-
|
||||
2977\, Outside:210-582-2716 \n \n \n\nTripIt - organize your travel at htt
|
||||
p://www.tripit.com
|
||||
GEO:37.618889;-122.375
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Pick-up Rental Car: Dollar Rent A Car
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-1ac6982fefdd79bc5ea849785f415a6291c450b1@tripit.com
|
||||
DTSTART:20111108T182700Z
|
||||
DTEND:20111108T192700Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24913749\n \n[Car Rental] Dollar Rent A Car\; San Francisco Inte
|
||||
rnational Airport\; primary driver John Doe\; conf #Q0058133 \np
|
||||
ickup 11/8/2011 10:27am\; dropoff 11/10/2011 6:25pm \nEconomy \nBooked on
|
||||
http://www.americanexpress-travel.com/\; Reference #: 4129 9623 4732\; htt
|
||||
p://www.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-
|
||||
2716 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-in: Sofitel San Francisco Bay
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-126e584ffbefbec32a15ca503f0bdf8d3f9cc2f4@tripit.com
|
||||
DTSTART:20111108T195700Z
|
||||
DTEND:20111108T205700Z
|
||||
LOCATION:223 TWIN DOLPHIN DR\, Redwood City\, CA\, 94065-1514
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24913749\n \n[Lodging] Sofitel San Francisco Bay\; primary guest
|
||||
John Doe\; conf #Q0058133 \n223 TWIN DOLPHIN DR\, Redwood City\
|
||||
, CA\, 94065-1514\; tel 1.650.598.9000 \narrive 11/8/2011\; depart 11/10/2
|
||||
011\; rooms: 1 \nBooked on http://www.americanexpress-travel.com/\; Refere
|
||||
nce #: 4129 9623 4732\; http://www.americanexpress-travel.com/\; US:1-800-
|
||||
297-2977\, Outside:210-582-2716 \n \n \n\nTripIt - organize your travel at
|
||||
http://www.tripit.com
|
||||
GEO:37.5232475;-122.261296
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-out: Sofitel San Francisco Bay
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-ff48c502022356ccaa862ebb61761a0de08a1ce9@tripit.com
|
||||
DTSTART:20111111T015500Z
|
||||
DTEND:20111111T025500Z
|
||||
LOCATION:223 TWIN DOLPHIN DR\, Redwood City\, CA\, 94065-1514
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24913749\n \n[Lodging] Sofitel San Francisco Bay\; primary guest
|
||||
John Doe\; conf #Q0058133 \n223 TWIN DOLPHIN DR\, Redwood City\
|
||||
, CA\, 94065-1514\; tel 1.650.598.9000 \narrive 11/8/2011\; depart 11/10/2
|
||||
011\; rooms: 1 \nBooked on http://www.americanexpress-travel.com/\; Refere
|
||||
nce #: 4129 9623 4732\; http://www.americanexpress-travel.com/\; US:1-800-
|
||||
297-2977\, Outside:210-582-2716 \n \n \n\nTripIt - organize your travel at
|
||||
http://www.tripit.com
|
||||
GEO:37.5232475;-122.261296
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Drop-off Rental Car: Dollar Rent A Car
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-c0273c03ddbb68a9b05d5d43a489bc318136ca42@tripit.com
|
||||
DTSTART:20111111T022500Z
|
||||
DTEND:20111111T032500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24913749\n \n[Car Rental] Dollar Rent A Car\; San Francisco Inte
|
||||
rnational Airport\; primary driver John Doe\; conf #Q0058133 \np
|
||||
ickup 11/8/2011 10:27am\; dropoff 11/10/2011 6:25pm \nEconomy \nBooked on
|
||||
http://www.americanexpress-travel.com/\; Reference #: 4129 9623 4732\; htt
|
||||
p://www.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-
|
||||
2716 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111111T055400Z
|
||||
SUMMARY:CO496 SFO to PHX
|
||||
LOCATION:San Francisco (SFO)
|
||||
UID:item-3473cf9275326ac393b37859df3b04306b4849aa@tripit.com
|
||||
DTSTART:20111111T035500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/24913749\n \n[Flight] 11/10/2011 Continental Airlines(CO) #496 d
|
||||
ep SFO 7:55pm PST arr PHX 10:54pm MST\; John Doe\; seat(s) 26B\;
|
||||
conf #FJDX0J\, I2W8HW(Operated by United Airlines flight 496) \nBooked on
|
||||
http://www.americanexpress-travel.com/\; Reference #: 4129 9623 4732\; ht
|
||||
tp://www.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582
|
||||
-2716 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
GEO:33.436111;-112.009444
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
UID:4ee5ded058432990e3d8808f48ca851e04923b6d@tripit.com
|
||||
DTSTART;VALUE=DATE:20111129
|
||||
DTEND;VALUE=DATE:20111202
|
||||
SUMMARY:Milpitas\, CA\, November 2011
|
||||
LOCATION:Milpitas\, CA
|
||||
GEO:37.428272;-121.906624
|
||||
TRANSP:TRANSPARENT
|
||||
DESCRIPTION:John Doe is in Milpitas\, CA from Nov 29 to Dec 1\,
|
||||
2011\nView and/or edit details in TripIt : http://www.tripit.com/trip/show
|
||||
/id/25671681\nTripIt - organize your travel at http://www.tripit.com\n
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111129T172400Z
|
||||
SUMMARY:US282 PHX to SJC
|
||||
LOCATION:Phoenix (PHX)
|
||||
UID:item-644d5973b50d521d50e475ccf5321605d54bd0d5@tripit.com
|
||||
DTSTART:20111129T152500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/25671681\n \n[Flight] 11/29/2011 US Airways(US) #282 dep PHX 8:2
|
||||
5am MST arr SJC 9:24am PST\; John Doe\; seat(s) 17C\; conf #DQKD
|
||||
GY \nBooked on http://www.americanexpress-travel.com/\; Reference #: 4131
|
||||
3301 9911\; http://www.americanexpress-travel.com/\; US:1-800-297-2977\, O
|
||||
utside:210-582-2716 \n \n \n\nTripIt - organize your travel at http://www.
|
||||
tripit.com
|
||||
GEO:37.361111;-121.925556
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Pick-up Rental Car: Alamo
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-10368bbdbc9b6f26f83098500633cc4eb604c751@tripit.com
|
||||
DTSTART:20111129T175400Z
|
||||
DTEND:20111129T185400Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/25671681\n \n[Car Rental] Alamo\; San Jose International Airport
|
||||
\; primary driver John Doe\; conf #372828149COUNT \npickup 11/29
|
||||
/2011 9:54am\; dropoff 12/1/2011 5:45pm \nIntermediate \nBooked on http://
|
||||
www.americanexpress-travel.com/\; Reference #: 4131 3301 9911\; http://www
|
||||
.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-2716 \n
|
||||
\n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-in: The Beverly Heritage Hotel
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-98d8638d3f1c011d03cb8f58b3a14a0f1203339b@tripit.com
|
||||
DTSTART:20111129T192400Z
|
||||
DTEND:20111129T202400Z
|
||||
LOCATION:1820 Barber Lane\, Milpitas\, CA\, 95035
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/25671681\n \n[Lodging] The Beverly Heritage Hotel\; primary gues
|
||||
t John Doe\; conf #372828149COUNT \n1820 Barber Lane\, Milpitas\
|
||||
, CA\, 95035\; tel 1.408.943.9080 \narrive 11/29/2011\; depart 12/1/2011\;
|
||||
rooms: 1 \nBooked on http://www.americanexpress-travel.com/\; Reference #
|
||||
: 4131 3301 9911\; http://www.americanexpress-travel.com/\; US:1-800-297-2
|
||||
977\, Outside:210-582-2716 \n \n \n\nTripIt - organize your travel at http
|
||||
://www.tripit.com
|
||||
GEO:37.4010467;-121.9116284
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111201T194400Z
|
||||
SUMMARY:US273 SJC to PHX
|
||||
LOCATION:San Jose (SJC)
|
||||
UID:item-7b9ee9bb4edfe69743e32b33f9be55753956a883@tripit.com
|
||||
DTSTART:20111201T175900Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/25671681\n \n[Flight] 12/1/2011 US Airways(US) #273 dep SJC 9:59
|
||||
am PST arr PHX 12:44pm MST\; John Doe Ticket #0378727451156\; co
|
||||
nf #EMF71T \nBooked on http://www.americanexpress-travel.com/\; Reference
|
||||
#: 5133 5264 1627\; http://www.americanexpress-travel.com/\; US:1-800-297-
|
||||
2977\, Outside:210-582-2716\; Total Cost: $316.69 \n \n \n\nTripIt - organ
|
||||
ize your travel at http://www.tripit.com
|
||||
GEO:33.436111;-112.009444
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-out: The Beverly Heritage Hotel
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-f79f203072002b8f06598dcb2be0e36af17b625b@tripit.com
|
||||
DTSTART:20111202T011500Z
|
||||
DTEND:20111202T021500Z
|
||||
LOCATION:1820 Barber Lane\, Milpitas\, CA\, 95035
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/25671681\n \n[Lodging] The Beverly Heritage Hotel\; primary gues
|
||||
t John Doe\; conf #372828149COUNT \n1820 Barber Lane\, Milpitas\
|
||||
, CA\, 95035\; tel 1.408.943.9080 \narrive 11/29/2011\; depart 12/1/2011\;
|
||||
rooms: 1 \nBooked on http://www.americanexpress-travel.com/\; Reference #
|
||||
: 4131 3301 9911\; http://www.americanexpress-travel.com/\; US:1-800-297-2
|
||||
977\, Outside:210-582-2716 \n \n \n\nTripIt - organize your travel at http
|
||||
://www.tripit.com
|
||||
GEO:37.4010467;-121.9116284
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Drop-off Rental Car: Alamo
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-69f526ad49fa8ca0a74486f4fc77cc3f9d23a72f@tripit.com
|
||||
DTSTART:20111202T014500Z
|
||||
DTEND:20111202T024500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/25671681\n \n[Car Rental] Alamo\; San Jose International Airport
|
||||
\; primary driver John Doe\; conf #372828149COUNT \npickup 11/29
|
||||
/2011 9:54am\; dropoff 12/1/2011 5:45pm \nIntermediate \nBooked on http://
|
||||
www.americanexpress-travel.com/\; Reference #: 4131 3301 9911\; http://www
|
||||
.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:210-582-2716 \n
|
||||
\n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111202T045900Z
|
||||
SUMMARY:US288 SJC to PHX
|
||||
LOCATION:San Jose (SJC)
|
||||
UID:item-dab68a87c8dd49064ab0ba1dec5ba75ba46ff1d3@tripit.com
|
||||
DTSTART:20111202T031500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/25671681\n \n[Flight] 12/1/2011 US Airways(US) #288 dep SJC 7:15
|
||||
pm PST arr PHX 9:59pm MST\; John Doe\; seat(s) 13C\; conf #DQKDG
|
||||
Y \nBooked on http://www.americanexpress-travel.com/\; Reference #: 4131 3
|
||||
301 9911\; http://www.americanexpress-travel.com/\; US:1-800-297-2977\, Ou
|
||||
tside:210-582-2716 \n \n \n\nTripIt - organize your travel at http://www.t
|
||||
ripit.com
|
||||
GEO:33.436111;-112.009444
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
UID:67d48ddde166a2e9bbac2cf7d93fe493b0860008@tripit.com
|
||||
DTSTART;VALUE=DATE:20111213
|
||||
DTEND;VALUE=DATE:20111216
|
||||
SUMMARY:San Jose\, CA\, December 2011
|
||||
LOCATION:San Jose\, CA
|
||||
GEO:37.339386;-121.894955
|
||||
TRANSP:TRANSPARENT
|
||||
DESCRIPTION:John Doe is in San Jose\, CA from Dec 13 to Dec 15\,
|
||||
2011\nView and/or edit details in TripIt : http://www.tripit.com/trip/sho
|
||||
w/id/27037117\nTripIt - organize your travel at http://www.tripit.com\n
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111213T172400Z
|
||||
SUMMARY:US282 PHX to SJC
|
||||
LOCATION:Phoenix (PHX)
|
||||
UID:item-2b1b9021be548a87dd335f190b60ab78c33b619d@tripit.com
|
||||
DTSTART:20111213T152500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27037117\n \n[Flight] 12/13/2011 US Airways(US) #282 dep PHX 8:2
|
||||
5am MST arr SJC 9:24am PST\; John Doe Ticket #0378728465928\; se
|
||||
at(s) 15C\; conf #GGNV29 \nBooked on http://www.americanexpress-travel.com
|
||||
/\; Reference #: 3134 0525 5102\; http://www.americanexpress-travel.com/\;
|
||||
US:1-800-297-2977\, Outside:210-582-2716\; Total Cost: $406.39 \n \n \n\n
|
||||
TripIt - organize your travel at http://www.tripit.com
|
||||
GEO:37.361111;-121.925556
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Pick-up Rental Car: Advantage
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-619d345bb08aaef68e8767b672277243697f5bff@tripit.com
|
||||
DTSTART:20111213T180000Z
|
||||
DTEND:20111213T190000Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27037117\n \n[Car Rental] Advantage\; San Jose International Air
|
||||
port\; primary driver John Doe\; conf #F31539020E7 \npickup 12/1
|
||||
3/2011 10:00am\; dropoff 12/15/2011 7:00pm \nStandard Convertible \nRefere
|
||||
nce #: 3134 0526 3890 \n \n \n\nTripIt - organize your travel at http://ww
|
||||
w.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-in: Crestview Hotel:
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-fbe6c08e7523c82fac69b40ad1d0899f3d8d5982@tripit.com
|
||||
DTSTART:20111213T192400Z
|
||||
DTEND:20111213T202400Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27037117\n \n[Lodging] Crestview Hotel:\; conf #CR31342159 \ntel
|
||||
650-966-8848 \narrive 12/13/2011\; depart 12/15/2011 \nBooking Rate: 153.
|
||||
30 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-out: Crestview Hotel:
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-7ed8b84628e650a6b37161c7825bac9e72add49f@tripit.com
|
||||
DTSTART:20111216T011500Z
|
||||
DTEND:20111216T021500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27037117\n \n[Lodging] Crestview Hotel:\; conf #CR31342159 \ntel
|
||||
650-966-8848 \narrive 12/13/2011\; depart 12/15/2011 \nBooking Rate: 153.
|
||||
30 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Drop-off Rental Car: Advantage
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-623b54ebe07ffd48845f1a120a86940ce79c698b@tripit.com
|
||||
DTSTART:20111216T030000Z
|
||||
DTEND:20111216T040000Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27037117\n \n[Car Rental] Advantage\; San Jose International Air
|
||||
port\; primary driver John Doe\; conf #F31539020E7 \npickup 12/1
|
||||
3/2011 10:00am\; dropoff 12/15/2011 7:00pm \nStandard Convertible \nRefere
|
||||
nce #: 3134 0526 3890 \n \n \n\nTripIt - organize your travel at http://ww
|
||||
w.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20111216T045900Z
|
||||
SUMMARY:US288 SJC to PHX
|
||||
LOCATION:San Jose (SJC)
|
||||
UID:item-52481e672972d2e88d5eaa5cf49bb801562c6014@tripit.com
|
||||
DTSTART:20111216T031500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27037117\n \n[Flight] 12/15/2011 US Airways(US) #288 dep SJC 7:1
|
||||
5pm PST arr PHX 9:59pm MST\; John Doe Ticket #0378728465928\; se
|
||||
at(s) 7B\; conf #GGNV29 \nBooked on http://www.americanexpress-travel.com/
|
||||
\; Reference #: 3134 0525 5102\; http://www.americanexpress-travel.com/\;
|
||||
US:1-800-297-2977\, Outside:210-582-2716\; Total Cost: $406.39 \n \n \n\nT
|
||||
ripIt - organize your travel at http://www.tripit.com
|
||||
GEO:33.436111;-112.009444
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
UID:7299ff29daed7d5c3e2ed4acc74deec5b7942bd5@tripit.com
|
||||
DTSTART;VALUE=DATE:20120103
|
||||
DTEND;VALUE=DATE:20120106
|
||||
SUMMARY:San Francisco\, CA\, January 2012
|
||||
LOCATION:San Francisco\, CA
|
||||
GEO:37.774929;-122.419415
|
||||
TRANSP:TRANSPARENT
|
||||
DESCRIPTION:John Doe is in San Francisco\, CA from Jan 3 to Jan
|
||||
5\, 2012\nView and/or edit details in TripIt : http://www.tripit.com/trip/
|
||||
show/id/27863159\nTripIt - organize your travel at http://www.tripit.com\n
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20120103T175700Z
|
||||
SUMMARY:US403 PHX to SFO
|
||||
LOCATION:Phoenix (PHX)
|
||||
UID:item-f099e76114bf43ef3b122432579d8b40995412a7@tripit.com
|
||||
DTSTART:20120103T154500Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27863159\n \n[Flight] 1/3/2012 US Airways(US) #403 dep PHX 8:45a
|
||||
m MST arr SFO 9:57am PST\; John Doe Ticket #0378731791515\; conf
|
||||
#FH9B72\, L4F9M5 \nBooked on http://www.americanexpress-travel.com/\; Ref
|
||||
erence #: 6135 7391 6119\; http://www.americanexpress-travel.com/\; US:1-8
|
||||
00-297-2977\, Outside:210-582-2716\; Total Cost: $668.39 \n \n \n\nTripIt
|
||||
- organize your travel at http://www.tripit.com
|
||||
GEO:37.618889;-122.375
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Pick-up Rental Car: Alamo
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-fae4b4b07b66fc87df125238e0aaf645106cf4f3@tripit.com
|
||||
DTSTART:20120103T180000Z
|
||||
DTEND:20120103T190000Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27863159\n \n[Car Rental] Alamo\; San Francisco International Ai
|
||||
rport\; primary driver John Doe\; conf #373525981COUNT \npickup
|
||||
1/3/2012 10:00am\; dropoff 1/5/2012 6:00pm \nCompact \nReference #: 6135 7
|
||||
391 6898 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-in: Grand Hotel Sunnyvale
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-d89a856eb9da9dfdcb4da46f42e49af3a838fcbb@tripit.com
|
||||
DTSTART:20120103T195700Z
|
||||
DTEND:20120103T205700Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27863159\n \n[Lodging] Grand Hotel Sunnyvale\; conf #22084SY0361
|
||||
18 \ntel 1-408-7208500 \narrive 1/3/2012\; depart 1/5/2012 \nBooking Rate:
|
||||
USD 169.00 \nPolicies: Guarantee to valid form of payment is required at
|
||||
time of booking\; Cancel 1 day prior to arrival date to avoid penalty of 1
|
||||
Nights Room Charge. Change fee may apply for early departures and changes
|
||||
made to confirmed reservations.\; \n \n \n\nTripIt - organize your travel
|
||||
at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Check-out: Grand Hotel Sunnyvale
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-6edc82f6411fd0b66f2f7f6baafa41623a8623a9@tripit.com
|
||||
DTSTART:20120106T010900Z
|
||||
DTEND:20120106T020900Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27863159\n \n[Lodging] Grand Hotel Sunnyvale\; conf #22084SY0361
|
||||
18 \ntel 1-408-7208500 \narrive 1/3/2012\; depart 1/5/2012 \nBooking Rate:
|
||||
USD 169.00 \nPolicies: Guarantee to valid form of payment is required at
|
||||
time of booking\; Cancel 1 day prior to arrival date to avoid penalty of 1
|
||||
Nights Room Charge. Change fee may apply for early departures and changes
|
||||
made to confirmed reservations.\; \n \n \n\nTripIt - organize your travel
|
||||
at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
SUMMARY:Drop-off Rental Car: Alamo
|
||||
TRANSP:TRANSPARENT
|
||||
UID:item-58a31b96066ffd09b800af49de59a84f7b7a3a06@tripit.com
|
||||
DTSTART:20120106T020000Z
|
||||
DTEND:20120106T030000Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27863159\n \n[Car Rental] Alamo\; San Francisco International Ai
|
||||
rport\; primary driver John Doe\; conf #373525981COUNT \npickup
|
||||
1/3/2012 10:00am\; dropoff 1/5/2012 6:00pm \nCompact \nReference #: 6135 7
|
||||
391 6898 \n \n \n\nTripIt - organize your travel at http://www.tripit.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20120101T215311Z
|
||||
DTEND:20120106T050500Z
|
||||
SUMMARY:CO496 SFO to PHX
|
||||
LOCATION:San Francisco (SFO)
|
||||
UID:item-7884351ce42d503b90ccc48c33c7c30bd4f44767@tripit.com
|
||||
DTSTART:20120106T030900Z
|
||||
DESCRIPTION:View and/or edit details in TripIt : http://www.tripit.com/tri
|
||||
p/show/id/27863159\n \n[Flight] 1/5/2012 Continental Airlines(CO) #496 dep
|
||||
SFO 7:09pm PST arr PHX 10:05pm MST\; John Doe Ticket #037873179
|
||||
1515\; conf #FH9B72\, L4F9M5(Operated by United Airlines flight 496) \nBoo
|
||||
ked on http://www.americanexpress-travel.com/\; Reference #: 6135 7391 611
|
||||
9\; http://www.americanexpress-travel.com/\; US:1-800-297-2977\, Outside:2
|
||||
10-582-2716\; Total Cost: $668.39 \n \n \n\nTripIt - organize your travel
|
||||
at http://www.tripit.com
|
||||
GEO:33.436111;-112.009444
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@@ -1,41 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Meetup//RemoteApi//EN
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-ORIGINAL-URL:http://www.meetup.com/events/ical/8333638/dfdba2e469216075
|
||||
3404f737feace78d526ff0ce/going
|
||||
X-WR-CALNAME:My Meetups
|
||||
X-MS-OLK-FORCEINSPECTOROPEN:TRUE
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:America/Phoenix
|
||||
TZURL:http://tzurl.org/zoneinfo-outlook/America/Phoenix
|
||||
X-LIC-LOCATION:America/Phoenix
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:-0700
|
||||
TZOFFSETTO:-0700
|
||||
TZNAME:MST
|
||||
DTSTART:19700101T000000
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20111106T155927Z
|
||||
DTSTART;TZID=America/Phoenix:20111109T190000
|
||||
DTEND;TZID=America/Phoenix:20111109T210000
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:Phoenix Drupal User Group Monthly Meetup
|
||||
DESCRIPTION:Phoenix Drupal User Group\nWednesday\, November 9 at 7:00 PM\
|
||||
n\nCustomizing node display with template pages in Drupal 6\n\n Jon Shee
|
||||
han and Matthew Berry of the Office of Knowledge Enterprise Development
|
||||
(OKED) Knowledge...\n\nDetails: http://www.meetup.com/Phoenix-Drupal-Use
|
||||
r-Group/events/33627272/
|
||||
CLASS:PUBLIC
|
||||
CREATED:20100630T083023Z
|
||||
GEO:33.56;-111.90
|
||||
LOCATION:Open Source Project Tempe (1415 E University Dr. #103A\, Tempe\,
|
||||
AZ 85281)
|
||||
URL:http://www.meetup.com/Phoenix-Drupal-User-Group/events/33627272/
|
||||
LAST-MODIFIED:20111102T213309Z
|
||||
UID:event_nsmxnyppbfc@meetup.com
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
1170
modules/default/calendar/vendor/ical.js/test/test6.ics
vendored
@@ -1,16 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:ownCloud Calendar 0.6.3
|
||||
X-WR-CALNAME:Fête Nationale - Férié
|
||||
BEGIN:VEVENT
|
||||
CREATED:20090502T140513Z
|
||||
DTSTAMP:20111106T124709Z
|
||||
UID:FA9831E7-C238-4FEC-95E5-CD46BD466421
|
||||
SUMMARY:Fête Nationale - Férié
|
||||
RRULE:FREQ=YEARLY
|
||||
DTSTART;VALUE=DATE:20120714
|
||||
DTEND;VALUE=DATE:20120715
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:5
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@@ -1,23 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:ownCloud Calendar 0.6.3
|
||||
X-WR-CALNAME:Default calendar
|
||||
BEGIN:VTODO
|
||||
CREATED;VALUE=DATE-TIME:20130714T092804Z
|
||||
UID:0aa462f13c
|
||||
LAST-MODIFIED;VALUE=DATE-TIME:20130714T092804Z
|
||||
DTSTAMP;VALUE=DATE-TIME:20130714T092804Z
|
||||
CATEGORIES:Projets
|
||||
SUMMARY:Migrer le blog
|
||||
PERCENT-COMPLETE:100
|
||||
COMPLETED;VALUE=DATE-TIME;TZID=Europe/Monaco:20130716T105745
|
||||
END:VTODO
|
||||
BEGIN:VTODO
|
||||
CREATED;VALUE=DATE-TIME:20130714T092912Z
|
||||
UID:5e05bbcf34
|
||||
LAST-MODIFIED;VALUE=DATE-TIME:20130714T092912Z
|
||||
DTSTAMP;VALUE=DATE-TIME:20130714T092912Z
|
||||
SUMMARY:Créer test unitaire erreur ical
|
||||
CATEGORIES:Projets
|
||||
END:VTODO
|
||||
END:VCALENDAR
|
@@ -1,21 +0,0 @@
|
||||
BEGIN:VCALENDAR
|
||||
BEGIN:VEVENT
|
||||
UID:eb9e1bd2-ceba-499f-be77-f02773954c72
|
||||
SUMMARY:Event with an alarm
|
||||
DESCRIPTION:This is an event with an alarm.
|
||||
ORGANIZER="mailto:stomlinson@mozilla.com"
|
||||
DTSTART;TZID="America/Los_Angeles":20130418T110000
|
||||
DTEND;TZID="America/Los_Angeles":20130418T120000
|
||||
STATUS:CONFIRMED
|
||||
CLASS:PUBLIC
|
||||
TRANSP:OPAQUE
|
||||
LAST-MODIFIED:20130418T175632Z
|
||||
DTSTAMP:20130418T175632Z
|
||||
SEQUENCE:3
|
||||
BEGIN:VALARM
|
||||
ACTION:DISPLAY
|
||||
TRIGGER;RELATED=START:-PT5M
|
||||
DESCRIPTION:Reminder
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
@@ -1,4 +1,5 @@
|
||||
# Module: Clock
|
||||
|
||||
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.
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
/* global Log, Module, moment, config */
|
||||
/* global SunCalc */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Clock
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("clock",{
|
||||
Module.register("clock", {
|
||||
// Module config defaults.
|
||||
defaults: {
|
||||
displayType: "digital", // options: digital, analog, both
|
||||
@@ -30,18 +31,18 @@ Module.register("clock",{
|
||||
showSunTimes: false,
|
||||
showMoonTimes: false,
|
||||
lat: 47.630539,
|
||||
lon: -122.344147,
|
||||
lon: -122.344147
|
||||
},
|
||||
// Define required scripts.
|
||||
getScripts: function() {
|
||||
getScripts: function () {
|
||||
return ["moment.js", "moment-timezone.js", "suncalc.js"];
|
||||
},
|
||||
// Define styles.
|
||||
getStyles: function() {
|
||||
getStyles: function () {
|
||||
return ["clock_styles.css"];
|
||||
},
|
||||
// Define start sequence.
|
||||
start: function() {
|
||||
start: function () {
|
||||
Log.info("Starting module: " + this.name);
|
||||
|
||||
// Schedule update interval.
|
||||
@@ -50,18 +51,18 @@ Module.register("clock",{
|
||||
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) {
|
||||
var EXTRA_DELAY = 50; //Deliberate imperceptable delay to prevent off-by-one timekeeping errors
|
||||
|
||||
var delayCalculator = function (reducedSeconds) {
|
||||
var EXTRA_DELAY = 50; //Deliberate imperceptable delay to prevent off-by-one timekeeping errors
|
||||
|
||||
if (self.config.displaySeconds) {
|
||||
return 1000 - moment().milliseconds() + EXTRA_DELAY;
|
||||
} else {
|
||||
return ((60 - reducedSeconds) * 1000) - moment().milliseconds() + EXTRA_DELAY;
|
||||
return (60 - reducedSeconds) * 1000 - moment().milliseconds() + EXTRA_DELAY;
|
||||
}
|
||||
};
|
||||
|
||||
//A recursive timeout function instead of interval to avoid drifting
|
||||
var notificationTimer = function() {
|
||||
var notificationTimer = function () {
|
||||
self.updateDom();
|
||||
|
||||
//If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
|
||||
@@ -85,11 +86,9 @@ Module.register("clock",{
|
||||
|
||||
// Set locale.
|
||||
moment.locale(config.language);
|
||||
|
||||
},
|
||||
// Override dom generator.
|
||||
getDom: function() {
|
||||
|
||||
getDom: function () {
|
||||
var wrapper = document.createElement("div");
|
||||
|
||||
/************************************
|
||||
@@ -128,12 +127,12 @@ Module.register("clock",{
|
||||
}
|
||||
|
||||
if (this.config.clockBold === true) {
|
||||
timeString = now.format(hourSymbol + "[<span class=\"bold\">]mm[</span>]");
|
||||
timeString = now.format(hourSymbol + '[<span class="bold">]mm[</span>]');
|
||||
} else {
|
||||
timeString = now.format(hourSymbol + ":mm");
|
||||
}
|
||||
|
||||
if(this.config.showDate){
|
||||
if (this.config.showDate) {
|
||||
dateWrapper.innerHTML = now.format(this.config.dateFormat);
|
||||
}
|
||||
if (this.config.showWeek) {
|
||||
@@ -174,9 +173,18 @@ Module.register("clock",{
|
||||
}
|
||||
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>";
|
||||
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());
|
||||
@@ -191,24 +199,32 @@ Module.register("clock",{
|
||||
}
|
||||
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>";
|
||||
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
|
||||
*/
|
||||
|
||||
if (this.config.displayType !== "digital") {
|
||||
if (this.config.displayType !== "digital") {
|
||||
// If it isn't 'digital', then an 'analog' clock was also requested
|
||||
|
||||
// Calculate the degree offset for each hand of the clock
|
||||
var now = moment();
|
||||
if (this.config.timezone) {
|
||||
now.tz(this.config.timezone);
|
||||
}
|
||||
var second = now.seconds() * 6,
|
||||
var second = now.seconds() * 6,
|
||||
minute = now.minute() * 6 + second / 60,
|
||||
hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
|
||||
|
||||
@@ -219,13 +235,12 @@ Module.register("clock",{
|
||||
clockCircle.style.height = this.config.analogSize;
|
||||
|
||||
if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
|
||||
clockCircle.style.background = "url("+ this.data.path + "faces/" + this.config.analogFace + ".svg)";
|
||||
clockCircle.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)";
|
||||
clockCircle.style.backgroundSize = "100%";
|
||||
|
||||
// The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
|
||||
// clockCircle.style.border = "1px solid black";
|
||||
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
|
||||
|
||||
} else if (this.config.analogFace !== "none") {
|
||||
clockCircle.style.border = "2px solid white";
|
||||
}
|
||||
@@ -291,11 +306,12 @@ Module.register("clock",{
|
||||
// Both clocks have been configured, check position
|
||||
var placement = this.config.analogPlacement;
|
||||
|
||||
analogWrapper = document.createElement("div");
|
||||
var analogWrapper = document.createElement("div");
|
||||
analogWrapper.id = "analog";
|
||||
analogWrapper.style.cssFloat = "none";
|
||||
analogWrapper.appendChild(clockCircle);
|
||||
digitalWrapper = document.createElement("div");
|
||||
|
||||
var digitalWrapper = document.createElement("div");
|
||||
digitalWrapper.id = "digital";
|
||||
digitalWrapper.style.cssFloat = "none";
|
||||
digitalWrapper.appendChild(dateWrapper);
|
||||
@@ -304,9 +320,9 @@ Module.register("clock",{
|
||||
digitalWrapper.appendChild(moonWrapper);
|
||||
digitalWrapper.appendChild(weekWrapper);
|
||||
|
||||
var appendClocks = function(condition, pos1, pos2) {
|
||||
var padding = [0,0,0,0];
|
||||
padding[(placement === condition) ? pos1 : pos2] = "20px";
|
||||
var appendClocks = function (condition, pos1, pos2) {
|
||||
var padding = [0, 0, 0, 0];
|
||||
padding[placement === condition ? pos1 : pos2] = "20px";
|
||||
analogWrapper.style.padding = padding.join(" ");
|
||||
if (placement === condition) {
|
||||
wrapper.appendChild(analogWrapper);
|
||||
|
@@ -1,78 +1,72 @@
|
||||
.clockCircle {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.clockFace {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.clockFace::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin: -3px 0 0 -3px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
content: "";
|
||||
display: block;
|
||||
}
|
||||
|
||||
.clockHour {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
|
||||
padding: 2px 0 2px 25%; /* indicator length & thickness */
|
||||
background: white;
|
||||
-webkit-transform-origin: 100% 50%;
|
||||
-ms-transform-origin: 100% 50%;
|
||||
transform-origin: 100% 50%;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.clockMinute {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -35% -2px 0; /* numbers must match negative length & thickness */
|
||||
padding: 35% 2px 0; /* indicator length & thickness */
|
||||
background: white;
|
||||
-webkit-transform-origin: 50% 100%;
|
||||
-ms-transform-origin: 50% 100%;
|
||||
transform-origin: 50% 100%;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.clockSecond {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
|
||||
padding: 38% 1px 0 0; /* indicator length & thickness */
|
||||
background: #888;
|
||||
-webkit-transform-origin: 50% 100%;
|
||||
-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;
|
||||
}
|
||||
.clockCircle {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.clockFace {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.clockFace::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin: -3px 0 0 -3px;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
content: "";
|
||||
display: block;
|
||||
}
|
||||
|
||||
.clockHour {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
|
||||
padding: 2px 0 2px 25%; /* indicator length & thickness */
|
||||
background: white;
|
||||
transform-origin: 100% 50%;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.clockMinute {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -35% -2px 0; /* numbers must match negative length & thickness */
|
||||
padding: 35% 2px 0; /* indicator length & thickness */
|
||||
background: white;
|
||||
transform-origin: 50% 100%;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.clockSecond {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
|
||||
padding: 38% 1px 0 0; /* indicator length & thickness */
|
||||
background: #888;
|
||||
transform-origin: 50% 100%;
|
||||
}
|
||||
|
||||
.module.clock .sun,
|
||||
.module.clock .moon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.module.clock .sun > *,
|
||||
.module.clock .moon > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
<svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.5px;}</style></defs><title>face-001</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-2" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-2" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-2" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-2" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-2" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-2" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-2" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-2" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-2" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-2" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-2" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-2" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-2" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-2" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-2" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-2" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-2" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-2" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-2" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-2" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-2" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-2" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-2" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-2" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-2" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-2" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-2" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-2" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-2" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-2" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-2" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-2" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-2" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-2" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-2" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-2" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-2" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-2" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-2" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-2" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-2" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-2" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-2" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-2" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-2" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-2" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-2" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/></svg>
|
||||
<svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.5px;}</style></defs><title>face-001</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-2" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-2" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-2" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-2" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-2" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-2" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-2" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-2" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-2" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-2" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-2" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-2" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-2" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-2" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-2" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-2" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-2" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-2" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-2" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-2" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-2" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-2" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-2" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-2" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-2" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-2" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-2" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-2" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-2" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-2" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-2" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-2" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-2" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-2" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-2" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-2" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-2" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-2" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-2" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-2" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-2" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-2" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-2" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-2" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-2" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-2" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-2" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -1 +1 @@
|
||||
<svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:2.98px;}</style></defs><title>face-002</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/></svg>
|
||||
<svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:2.98px;}</style></defs><title>face-002</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -1,4 +1,5 @@
|
||||
# Module: Compliments
|
||||
|
||||
The `compliments` module is one of the default modules of the MagicMirror.
|
||||
This module displays a random compliment.
|
||||
|
||||
|
@@ -1,37 +1,18 @@
|
||||
/* global Log, Module, moment */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Compliments
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("compliments", {
|
||||
|
||||
// Module config defaults.
|
||||
defaults: {
|
||||
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!"
|
||||
],
|
||||
"....-01-01": [
|
||||
"Happy new year!"
|
||||
]
|
||||
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!"],
|
||||
"....-01-01": ["Happy new year!"]
|
||||
},
|
||||
updateInterval: 30000,
|
||||
remoteFile: null,
|
||||
@@ -43,31 +24,31 @@ Module.register("compliments", {
|
||||
random: true,
|
||||
mockDate: null
|
||||
},
|
||||
lastIndexUsed:-1,
|
||||
lastIndexUsed: -1,
|
||||
// Set currentweather from module
|
||||
currentWeatherType: "",
|
||||
|
||||
// Define required scripts.
|
||||
getScripts: function() {
|
||||
getScripts: function () {
|
||||
return ["moment.js"];
|
||||
},
|
||||
|
||||
// Define start sequence.
|
||||
start: function() {
|
||||
start: function () {
|
||||
Log.info("Starting module: " + this.name);
|
||||
|
||||
this.lastComplimentIndex = -1;
|
||||
|
||||
var self = this;
|
||||
if (this.config.remoteFile !== null) {
|
||||
this.complimentFile(function(response) {
|
||||
this.complimentFile(function (response) {
|
||||
self.config.compliments = JSON.parse(response);
|
||||
self.updateDom();
|
||||
});
|
||||
}
|
||||
|
||||
// Schedule update timer.
|
||||
setInterval(function() {
|
||||
setInterval(function () {
|
||||
self.updateDom(self.config.fadeSpeed);
|
||||
}, this.config.updateInterval);
|
||||
},
|
||||
@@ -79,12 +60,12 @@ Module.register("compliments", {
|
||||
*
|
||||
* return Number - Random index.
|
||||
*/
|
||||
randomIndex: function(compliments) {
|
||||
randomIndex: function (compliments) {
|
||||
if (compliments.length === 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var generate = function() {
|
||||
var generate = function () {
|
||||
return Math.floor(Math.random() * compliments.length);
|
||||
};
|
||||
|
||||
@@ -104,7 +85,7 @@ Module.register("compliments", {
|
||||
*
|
||||
* return compliments Array<String> - Array with compliments for the time of the day.
|
||||
*/
|
||||
complimentArray: function() {
|
||||
complimentArray: function () {
|
||||
var hour = moment().hour();
|
||||
var date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
|
||||
var compliments;
|
||||
@@ -113,7 +94,7 @@ Module.register("compliments", {
|
||||
compliments = this.config.compliments.morning.slice(0);
|
||||
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) {
|
||||
compliments = this.config.compliments.afternoon.slice(0);
|
||||
} else if(this.config.compliments.hasOwnProperty("evening")) {
|
||||
} else if (this.config.compliments.hasOwnProperty("evening")) {
|
||||
compliments = this.config.compliments.evening.slice(0);
|
||||
}
|
||||
|
||||
@@ -127,7 +108,7 @@ Module.register("compliments", {
|
||||
|
||||
compliments.push.apply(compliments, this.config.compliments.anytime);
|
||||
|
||||
for (entry in this.config.compliments) {
|
||||
for (var entry in this.config.compliments) {
|
||||
if (new RegExp(entry).test(date)) {
|
||||
compliments.push.apply(compliments, this.config.compliments[entry]);
|
||||
}
|
||||
@@ -139,13 +120,13 @@ Module.register("compliments", {
|
||||
/* complimentFile(callback)
|
||||
* Retrieve a file from the local filesystem
|
||||
*/
|
||||
complimentFile: function(callback) {
|
||||
complimentFile: function (callback) {
|
||||
var xobj = new XMLHttpRequest(),
|
||||
isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0,
|
||||
path = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile);
|
||||
xobj.overrideMimeType("application/json");
|
||||
xobj.open("GET", path, true);
|
||||
xobj.onreadystatechange = function() {
|
||||
xobj.onreadystatechange = function () {
|
||||
if (xobj.readyState === 4 && xobj.status === 200) {
|
||||
callback(xobj.responseText);
|
||||
}
|
||||
@@ -158,27 +139,26 @@ Module.register("compliments", {
|
||||
*
|
||||
* return compliment string - A compliment.
|
||||
*/
|
||||
randomCompliment: function() {
|
||||
randomCompliment: function () {
|
||||
// get the current time of day compliments list
|
||||
var compliments = this.complimentArray();
|
||||
// variable for index to next message to display
|
||||
let index = 0;
|
||||
// are we randomizing
|
||||
if(this.config.random){
|
||||
if (this.config.random) {
|
||||
// yes
|
||||
index = this.randomIndex(compliments);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
// no, sequential
|
||||
// if doing sequential, don't fall off the end
|
||||
index = (this.lastIndexUsed >= (compliments.length-1))?0: ++this.lastIndexUsed;
|
||||
index = this.lastIndexUsed >= compliments.length - 1 ? 0 : ++this.lastIndexUsed;
|
||||
}
|
||||
|
||||
return compliments[index] || "";
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function() {
|
||||
getDom: function () {
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line";
|
||||
// get the compliment text
|
||||
@@ -188,7 +168,7 @@ Module.register("compliments", {
|
||||
// create a span to hold it all
|
||||
var compliment = document.createElement("span");
|
||||
// process all the parts of the compliment text
|
||||
for (part of parts){
|
||||
for (var part of parts) {
|
||||
// create a text element for each part
|
||||
compliment.appendChild(document.createTextNode(part));
|
||||
// add a break `
|
||||
@@ -202,7 +182,7 @@ Module.register("compliments", {
|
||||
},
|
||||
|
||||
// From data currentweather set weather type
|
||||
setCurrentWeatherType: function(data) {
|
||||
setCurrentWeatherType: function (data) {
|
||||
var weatherIconTable = {
|
||||
"01d": "day_sunny",
|
||||
"02d": "day_cloudy",
|
||||
@@ -227,10 +207,9 @@ Module.register("compliments", {
|
||||
},
|
||||
|
||||
// Override notification handler.
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
if (notification === "CURRENTWEATHER_DATA") {
|
||||
this.setCurrentWeatherType(payload.data);
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# Module: Current Weather
|
||||
|
||||
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.
|
||||
|
||||
|
@@ -3,8 +3,6 @@
|
||||
font-size: 75%;
|
||||
line-height: 65px;
|
||||
display: inline-block;
|
||||
-ms-transform: translate(0, -3px); /* IE 9 */
|
||||
-webkit-transform: translate(0, -3px); /* Safari */
|
||||
transform: translate(0, -3px);
|
||||
}
|
||||
|
||||
|
@@ -1,14 +1,10 @@
|
||||
/* global Module */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: CurrentWeather
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("currentweather",{
|
||||
|
||||
Module.register("currentweather", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
location: false,
|
||||
@@ -23,11 +19,11 @@ Module.register("currentweather",{
|
||||
showWindDirection: true,
|
||||
showWindDirectionAsArrow: false,
|
||||
useBeaufort: true,
|
||||
appendLocationNameToHeader: false,
|
||||
useKMPHwind: false,
|
||||
lang: config.language,
|
||||
decimalSymbol: ".",
|
||||
showHumidity: false,
|
||||
showSun: true,
|
||||
degreeLabel: false,
|
||||
showIndoorTemperature: false,
|
||||
showIndoorHumidity: false,
|
||||
@@ -67,7 +63,7 @@ Module.register("currentweather",{
|
||||
"11n": "wi-night-thunderstorm",
|
||||
"13n": "wi-night-snow",
|
||||
"50n": "wi-night-alt-cloudy-windy"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// create a variable for the first upcoming calendar event. Used if no location is specified.
|
||||
@@ -77,17 +73,17 @@ Module.register("currentweather",{
|
||||
fetchedLocationName: "",
|
||||
|
||||
// Define required scripts.
|
||||
getScripts: function() {
|
||||
getScripts: function () {
|
||||
return ["moment.js"];
|
||||
},
|
||||
|
||||
// Define required scripts.
|
||||
getStyles: function() {
|
||||
getStyles: function () {
|
||||
return ["weather-icons.css", "currentweather.css"];
|
||||
},
|
||||
|
||||
// Define required translations.
|
||||
getTranslations: function() {
|
||||
getTranslations: function () {
|
||||
// The translations for the default modules are defined in the core translation files.
|
||||
// Therefor we can just return false. Otherwise we should have returned a dictionary.
|
||||
// If you're trying to build your own module including translations, check out the documentation.
|
||||
@@ -95,7 +91,7 @@ Module.register("currentweather",{
|
||||
},
|
||||
|
||||
// Define start sequence.
|
||||
start: function() {
|
||||
start: function () {
|
||||
Log.info("Starting module: " + this.name);
|
||||
|
||||
// Set locale.
|
||||
@@ -113,13 +109,11 @@ Module.register("currentweather",{
|
||||
this.feelsLike = null;
|
||||
this.loaded = false;
|
||||
this.scheduleUpdate(this.config.initialLoadDelay);
|
||||
|
||||
},
|
||||
|
||||
// add extra information of current weather
|
||||
// windDirection, humidity, sunrise and sunset
|
||||
addExtraInfoWeather: function(wrapper) {
|
||||
|
||||
addExtraInfoWeather: function (wrapper) {
|
||||
var small = document.createElement("div");
|
||||
small.className = "normal medium";
|
||||
|
||||
@@ -134,8 +128,8 @@ Module.register("currentweather",{
|
||||
if (this.config.showWindDirection) {
|
||||
var windDirection = document.createElement("sup");
|
||||
if (this.config.showWindDirectionAsArrow) {
|
||||
if(this.windDeg !== null) {
|
||||
windDirection.innerHTML = " <i class=\"fa fa-long-arrow-down\" style=\"transform:rotate("+this.windDeg+"deg);\"></i> ";
|
||||
if (this.windDeg !== null) {
|
||||
windDirection.innerHTML = ' <i class="fa fa-long-arrow-down" style="transform:rotate(' + this.windDeg + 'deg);"></i> ';
|
||||
}
|
||||
} else {
|
||||
windDirection.innerHTML = " " + this.translate(this.windDirection);
|
||||
@@ -150,31 +144,33 @@ Module.register("currentweather",{
|
||||
var humidity = document.createElement("span");
|
||||
humidity.innerHTML = this.humidity;
|
||||
|
||||
var spacer = document.createElement("sup");
|
||||
spacer.innerHTML = " ";
|
||||
var supspacer = document.createElement("sup");
|
||||
supspacer.innerHTML = " ";
|
||||
|
||||
var humidityIcon = document.createElement("sup");
|
||||
humidityIcon.className = "wi wi-humidity humidityIcon";
|
||||
humidityIcon.innerHTML = " ";
|
||||
|
||||
small.appendChild(humidity);
|
||||
small.appendChild(spacer);
|
||||
small.appendChild(supspacer);
|
||||
small.appendChild(humidityIcon);
|
||||
}
|
||||
|
||||
var sunriseSunsetIcon = document.createElement("span");
|
||||
sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
|
||||
small.appendChild(sunriseSunsetIcon);
|
||||
if (this.config.showSun) {
|
||||
var sunriseSunsetIcon = document.createElement("span");
|
||||
sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
|
||||
small.appendChild(sunriseSunsetIcon);
|
||||
|
||||
var sunriseSunsetTime = document.createElement("span");
|
||||
sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
|
||||
small.appendChild(sunriseSunsetTime);
|
||||
var sunriseSunsetTime = document.createElement("span");
|
||||
sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
|
||||
small.appendChild(sunriseSunsetTime);
|
||||
}
|
||||
|
||||
wrapper.appendChild(small);
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function() {
|
||||
getDom: function () {
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.className = this.config.tableClass;
|
||||
|
||||
@@ -201,17 +197,17 @@ Module.register("currentweather",{
|
||||
if (this.config.units === "metric" || this.config.units === "imperial") {
|
||||
degreeLabel += "°";
|
||||
}
|
||||
if(this.config.degreeLabel) {
|
||||
switch(this.config.units) {
|
||||
case "metric":
|
||||
degreeLabel += "C";
|
||||
break;
|
||||
case "imperial":
|
||||
degreeLabel += "F";
|
||||
break;
|
||||
case "default":
|
||||
degreeLabel += "K";
|
||||
break;
|
||||
if (this.config.degreeLabel) {
|
||||
switch (this.config.units) {
|
||||
case "metric":
|
||||
degreeLabel += "C";
|
||||
break;
|
||||
case "imperial":
|
||||
degreeLabel += "F";
|
||||
break;
|
||||
case "default":
|
||||
degreeLabel += "K";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +250,7 @@ Module.register("currentweather",{
|
||||
|
||||
wrapper.appendChild(large);
|
||||
|
||||
if (this.config.showFeelsLike && this.config.onlyTemp === false){
|
||||
if (this.config.showFeelsLike && this.config.onlyTemp === false) {
|
||||
var small = document.createElement("div");
|
||||
small.className = "normal medium";
|
||||
|
||||
@@ -270,7 +266,7 @@ Module.register("currentweather",{
|
||||
},
|
||||
|
||||
// Override getHeader method.
|
||||
getHeader: function() {
|
||||
getHeader: function () {
|
||||
if (this.config.appendLocationNameToHeader && this.data.header !== undefined) {
|
||||
return this.data.header + " " + this.fetchedLocationName;
|
||||
}
|
||||
@@ -283,10 +279,10 @@ Module.register("currentweather",{
|
||||
},
|
||||
|
||||
// Override notification handler.
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
if (notification === "DOM_OBJECTS_CREATED") {
|
||||
if (this.config.appendLocationNameToHeader) {
|
||||
this.hide(0, {lockString: this.identifier});
|
||||
this.hide(0, { lockString: this.identifier });
|
||||
}
|
||||
}
|
||||
if (notification === "CALENDAR_EVENTS") {
|
||||
@@ -318,7 +314,7 @@ Module.register("currentweather",{
|
||||
* Requests new data from openweather.org.
|
||||
* Calls processWeather on succesfull response.
|
||||
*/
|
||||
updateWeather: function() {
|
||||
updateWeather: function () {
|
||||
if (this.config.appid === "") {
|
||||
Log.error("CurrentWeather: APPID not set!");
|
||||
return;
|
||||
@@ -330,7 +326,7 @@ Module.register("currentweather",{
|
||||
|
||||
var weatherRequest = new XMLHttpRequest();
|
||||
weatherRequest.open("GET", url, true);
|
||||
weatherRequest.onreadystatechange = function() {
|
||||
weatherRequest.onreadystatechange = function () {
|
||||
if (this.readyState === 4) {
|
||||
if (this.status === 200) {
|
||||
self.processWeather(JSON.parse(this.response));
|
||||
@@ -344,7 +340,7 @@ Module.register("currentweather",{
|
||||
}
|
||||
|
||||
if (retry) {
|
||||
self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
|
||||
self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -356,18 +352,18 @@ Module.register("currentweather",{
|
||||
*
|
||||
* return String - URL params.
|
||||
*/
|
||||
getParams: function() {
|
||||
getParams: function () {
|
||||
var params = "?";
|
||||
if(this.config.locationID) {
|
||||
if (this.config.locationID) {
|
||||
params += "id=" + this.config.locationID;
|
||||
} else if(this.config.location) {
|
||||
} else if (this.config.location) {
|
||||
params += "q=" + this.config.location;
|
||||
} else if (this.firstEvent && this.firstEvent.geo) {
|
||||
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
|
||||
} else if (this.firstEvent && this.firstEvent.location) {
|
||||
params += "q=" + this.firstEvent.location;
|
||||
} else {
|
||||
this.hide(this.config.animationSpeed, {lockString:this.identifier});
|
||||
this.hide(this.config.animationSpeed, { lockString: this.identifier });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -383,8 +379,7 @@ Module.register("currentweather",{
|
||||
*
|
||||
* argument data object - Weather information received form openweather.org.
|
||||
*/
|
||||
processWeather: function(data) {
|
||||
|
||||
processWeather: function (data) {
|
||||
if (!data || !data.main || typeof data.main.temp === "undefined") {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
@@ -396,7 +391,7 @@ Module.register("currentweather",{
|
||||
this.fetchedLocationName = data.name;
|
||||
this.feelsLike = 0;
|
||||
|
||||
if (this.config.useBeaufort){
|
||||
if (this.config.useBeaufort) {
|
||||
this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
|
||||
} else if (this.config.useKMPHwind) {
|
||||
this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0);
|
||||
@@ -408,52 +403,59 @@ Module.register("currentweather",{
|
||||
var windInMph = parseFloat(data.wind.speed * 2.23694);
|
||||
|
||||
var tempInF = 0;
|
||||
switch (this.config.units){
|
||||
case "metric": tempInF = 1.8 * this.temperature + 32;
|
||||
break;
|
||||
case "imperial": tempInF = this.temperature;
|
||||
break;
|
||||
case "default":
|
||||
var tc = this.temperature - 273.15;
|
||||
tempInF = 1.8 * tc + 32;
|
||||
break;
|
||||
switch (this.config.units) {
|
||||
case "metric":
|
||||
tempInF = 1.8 * this.temperature + 32;
|
||||
break;
|
||||
case "imperial":
|
||||
tempInF = this.temperature;
|
||||
break;
|
||||
case "default":
|
||||
tempInF = 1.8 * (this.temperature - 273.15) + 32;
|
||||
break;
|
||||
}
|
||||
|
||||
if (windInMph > 3 && tempInF < 50){
|
||||
if (windInMph > 3 && tempInF < 50) {
|
||||
// windchill
|
||||
var windChillInF = Math.round(35.74+0.6215*tempInF-35.75*Math.pow(windInMph,0.16)+0.4275*tempInF*Math.pow(windInMph,0.16));
|
||||
var windChillInC = (windChillInF - 32) * (5/9);
|
||||
var windChillInF = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
|
||||
var windChillInC = (windChillInF - 32) * (5 / 9);
|
||||
// this.feelsLike = windChillInC.toFixed(0);
|
||||
|
||||
switch (this.config.units){
|
||||
case "metric": this.feelsLike = windChillInC.toFixed(0);
|
||||
break;
|
||||
case "imperial": this.feelsLike = windChillInF.toFixed(0);
|
||||
break;
|
||||
case "default":
|
||||
var tc = windChillInC + 273.15;
|
||||
this.feelsLike = tc.toFixed(0);
|
||||
break;
|
||||
switch (this.config.units) {
|
||||
case "metric":
|
||||
this.feelsLike = windChillInC.toFixed(0);
|
||||
break;
|
||||
case "imperial":
|
||||
this.feelsLike = windChillInF.toFixed(0);
|
||||
break;
|
||||
case "default":
|
||||
this.feelsLike = (windChillInC + 273.15).toFixed(0);
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (tempInF > 80 && this.humidity > 40){
|
||||
} else if (tempInF > 80 && this.humidity > 40) {
|
||||
// heat index
|
||||
var Hindex = -42.379 + 2.04901523*tempInF + 10.14333127*this.humidity
|
||||
- 0.22475541*tempInF*this.humidity - 6.83783*Math.pow(10,-3)*tempInF*tempInF
|
||||
- 5.481717*Math.pow(10,-2)*this.humidity*this.humidity
|
||||
+ 1.22874*Math.pow(10,-3)*tempInF*tempInF*this.humidity
|
||||
+ 8.5282*Math.pow(10,-4)*tempInF*this.humidity*this.humidity
|
||||
- 1.99*Math.pow(10,-6)*tempInF*tempInF*this.humidity*this.humidity;
|
||||
var Hindex =
|
||||
-42.379 +
|
||||
2.04901523 * tempInF +
|
||||
10.14333127 * this.humidity -
|
||||
0.22475541 * tempInF * this.humidity -
|
||||
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
|
||||
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
|
||||
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
|
||||
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
|
||||
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
|
||||
|
||||
switch (this.config.units){
|
||||
case "metric": this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
|
||||
break;
|
||||
case "imperial": this.feelsLike = Hindex.toFixed(0);
|
||||
break;
|
||||
case "default":
|
||||
var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
|
||||
this.feelsLike = tc.toFixed(0);
|
||||
break;
|
||||
switch (this.config.units) {
|
||||
case "metric":
|
||||
this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
|
||||
break;
|
||||
case "imperial":
|
||||
this.feelsLike = Hindex.toFixed(0);
|
||||
break;
|
||||
case "default":
|
||||
var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
|
||||
this.feelsLike = tc.toFixed(0);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.feelsLike = parseFloat(this.temperature).toFixed(0);
|
||||
@@ -470,7 +472,7 @@ Module.register("currentweather",{
|
||||
// The moment().format('h') method has a bug on the Raspberry Pi.
|
||||
// So we need to generate the timestring manually.
|
||||
// See issue: https://github.com/MichMich/MagicMirror/issues/181
|
||||
var sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise;
|
||||
var sunriseSunsetDateObject = sunrise < now && sunset > now ? sunset : sunrise;
|
||||
var timeString = moment(sunriseSunsetDateObject).format("HH:mm");
|
||||
if (this.config.timeFormat !== 24) {
|
||||
//var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
|
||||
@@ -489,12 +491,12 @@ Module.register("currentweather",{
|
||||
}
|
||||
|
||||
this.sunriseSunsetTime = timeString;
|
||||
this.sunriseSunsetIcon = (sunrise < now && sunset > now) ? "wi-sunset" : "wi-sunrise";
|
||||
this.sunriseSunsetIcon = sunrise < now && sunset > now ? "wi-sunset" : "wi-sunrise";
|
||||
|
||||
this.show(this.config.animationSpeed, {lockString:this.identifier});
|
||||
this.show(this.config.animationSpeed, { lockString: this.identifier });
|
||||
this.loaded = true;
|
||||
this.updateDom(this.config.animationSpeed);
|
||||
this.sendNotification("CURRENTWEATHER_DATA", {data: data});
|
||||
this.sendNotification("CURRENTWEATHER_DATA", { data: data });
|
||||
},
|
||||
|
||||
/* scheduleUpdate()
|
||||
@@ -502,14 +504,14 @@ Module.register("currentweather",{
|
||||
*
|
||||
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
|
||||
*/
|
||||
scheduleUpdate: function(delay) {
|
||||
scheduleUpdate: function (delay) {
|
||||
var nextLoad = this.config.updateInterval;
|
||||
if (typeof delay !== "undefined" && delay >= 0) {
|
||||
nextLoad = delay;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
self.updateWeather();
|
||||
}, nextLoad);
|
||||
},
|
||||
@@ -518,15 +520,15 @@ Module.register("currentweather",{
|
||||
* Converts m2 to beaufort (windspeed).
|
||||
*
|
||||
* see:
|
||||
* http://www.spc.noaa.gov/faq/tornado/beaufort.html
|
||||
* https://www.spc.noaa.gov/faq/tornado/beaufort.html
|
||||
* https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
|
||||
*
|
||||
* argument ms number - Windspeed in m/s.
|
||||
*
|
||||
* return number - Windspeed in beaufort.
|
||||
*/
|
||||
ms2Beaufort: function(ms) {
|
||||
var kmh = ms * 60 * 60 / 1000;
|
||||
ms2Beaufort: function (ms) {
|
||||
var kmh = (ms * 60 * 60) / 1000;
|
||||
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
|
||||
for (var beaufort in speeds) {
|
||||
var speed = speeds[beaufort];
|
||||
@@ -537,8 +539,8 @@ Module.register("currentweather",{
|
||||
return 12;
|
||||
},
|
||||
|
||||
deg2Cardinal: function(deg) {
|
||||
if (deg>11.25 && deg<=33.75){
|
||||
deg2Cardinal: function (deg) {
|
||||
if (deg > 11.25 && deg <= 33.75) {
|
||||
return "NNE";
|
||||
} else if (deg > 33.75 && deg <= 56.25) {
|
||||
return "NE";
|
||||
@@ -580,9 +582,8 @@ Module.register("currentweather",{
|
||||
*
|
||||
* return string - Rounded Temperature.
|
||||
*/
|
||||
roundValue: function(temperature) {
|
||||
roundValue: function (temperature) {
|
||||
var decimals = this.config.roundTemp ? 0 : 1;
|
||||
return parseFloat(temperature).toFixed(decimals);
|
||||
}
|
||||
|
||||
});
|
||||
|
@@ -1,24 +1,15 @@
|
||||
/* Magic Mirror
|
||||
* Default Modules List
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
// Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
|
||||
|
||||
var defaultModules = [
|
||||
"alert",
|
||||
"calendar",
|
||||
"clock",
|
||||
"compliments",
|
||||
"currentweather",
|
||||
"helloworld",
|
||||
"newsfeed",
|
||||
"weatherforecast",
|
||||
"updatenotification",
|
||||
"weather"
|
||||
];
|
||||
var defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"];
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {module.exports = defaultModules;}
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = defaultModules;
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# Module: Hello World
|
||||
|
||||
The `helloworld` module is one of the default modules of the MagicMirror. It is a simple way to display a static text on the mirror.
|
||||
|
||||
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/helloworld.html).
|
||||
|
@@ -1,14 +1,10 @@
|
||||
/* global Module */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: HelloWorld
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("helloworld",{
|
||||
|
||||
Module.register("helloworld", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
text: "Hello World!"
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# Module: News Feed
|
||||
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.
|
||||
|
||||
The `newsfeed` module is one of the default modules of the MagicMirror.
|
||||
This module displays news headlines based on an RSS feed. Scrolling through news headlines happens time-based (`updateInterval`), but can also be controlled by sending news feed specific notifications to the module.
|
||||
|
||||
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/newsfeed.html).
|
||||
|
@@ -1,13 +1,14 @@
|
||||
/* Magic Mirror
|
||||
* Fetcher
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var FeedMe = require("feedme");
|
||||
var request = require("request");
|
||||
var iconv = require("iconv-lite");
|
||||
const Log = require("../../../js/logger.js");
|
||||
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.
|
||||
@@ -17,7 +18,7 @@ var iconv = require("iconv-lite");
|
||||
* attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article.
|
||||
*/
|
||||
|
||||
var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
|
||||
var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
|
||||
var self = this;
|
||||
if (reloadInterval < 1000) {
|
||||
reloadInterval = 1000;
|
||||
@@ -26,83 +27,74 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
|
||||
var reloadTimer = null;
|
||||
var items = [];
|
||||
|
||||
var fetchFailedCallback = function() {};
|
||||
var itemsReceivedCallback = function() {};
|
||||
var fetchFailedCallback = function () {};
|
||||
var itemsReceivedCallback = function () {};
|
||||
|
||||
/* private methods */
|
||||
|
||||
/* fetchNews()
|
||||
* Request the new items.
|
||||
*/
|
||||
|
||||
var fetchNews = function() {
|
||||
var fetchNews = function () {
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = null;
|
||||
items = [];
|
||||
|
||||
var parser = new FeedMe();
|
||||
|
||||
parser.on("item", function(item) {
|
||||
|
||||
parser.on("item", function (item) {
|
||||
var title = item.title;
|
||||
var description = item.description || item.summary || item.content || "";
|
||||
var pubdate = item.pubdate || item.published || item.updated || item["dc:date"];
|
||||
var url = item.url || item.link || "";
|
||||
|
||||
if (title && pubdate) {
|
||||
|
||||
var regex = /(<([^>]+)>)/ig;
|
||||
var regex = /(<([^>]+)>)/gi;
|
||||
description = description.toString().replace(regex, "");
|
||||
|
||||
items.push({
|
||||
title: title,
|
||||
description: description,
|
||||
pubdate: pubdate,
|
||||
url: url,
|
||||
url: url
|
||||
});
|
||||
|
||||
} else if (logFeedWarnings) {
|
||||
console.log("Can't parse feed item:");
|
||||
console.log(item);
|
||||
console.log("Title: " + title);
|
||||
console.log("Description: " + description);
|
||||
console.log("Pubdate: " + pubdate);
|
||||
Log.warn("Can't parse feed item:");
|
||||
Log.warn(item);
|
||||
Log.warn("Title: " + title);
|
||||
Log.warn("Description: " + description);
|
||||
Log.warn("Pubdate: " + pubdate);
|
||||
}
|
||||
});
|
||||
|
||||
parser.on("end", function() {
|
||||
//console.log("end parsing - " + url);
|
||||
parser.on("end", function () {
|
||||
self.broadcastItems();
|
||||
scheduleTimer();
|
||||
});
|
||||
|
||||
parser.on("error", function(error) {
|
||||
parser.on("error", function (error) {
|
||||
fetchFailedCallback(self, error);
|
||||
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"};
|
||||
var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||
var headers = { "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)", "Cache-Control": "max-age=0, no-cache, no-store, must-revalidate", Pragma: "no-cache" };
|
||||
|
||||
request({uri: url, encoding: null, headers: headers})
|
||||
.on("error", function(error) {
|
||||
request({ uri: url, encoding: null, headers: headers })
|
||||
.on("error", function (error) {
|
||||
fetchFailedCallback(self, error);
|
||||
scheduleTimer();
|
||||
})
|
||||
.pipe(iconv.decodeStream(encoding)).pipe(parser);
|
||||
|
||||
.pipe(iconv.decodeStream(encoding))
|
||||
.pipe(parser);
|
||||
};
|
||||
|
||||
/* scheduleTimer()
|
||||
* Schedule the timer for the next update.
|
||||
*/
|
||||
|
||||
var scheduleTimer = function() {
|
||||
//console.log('Schedule update timer.');
|
||||
var scheduleTimer = function () {
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(function() {
|
||||
reloadTimer = setTimeout(function () {
|
||||
fetchNews();
|
||||
}, reloadInterval);
|
||||
};
|
||||
@@ -114,7 +106,7 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
|
||||
*
|
||||
* attribute interval number - Interval for the update in milliseconds.
|
||||
*/
|
||||
this.setReloadInterval = function(interval) {
|
||||
this.setReloadInterval = function (interval) {
|
||||
if (interval > 1000 && interval < reloadInterval) {
|
||||
reloadInterval = interval;
|
||||
}
|
||||
@@ -123,35 +115,35 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
|
||||
/* startFetch()
|
||||
* Initiate fetchNews();
|
||||
*/
|
||||
this.startFetch = function() {
|
||||
this.startFetch = function () {
|
||||
fetchNews();
|
||||
};
|
||||
|
||||
/* broadcastItems()
|
||||
* Broadcast the existing items.
|
||||
*/
|
||||
this.broadcastItems = function() {
|
||||
this.broadcastItems = function () {
|
||||
if (items.length <= 0) {
|
||||
//console.log('No items to broadcast yet.');
|
||||
Log.info("Newsfeed-Fetcher: No items to broadcast yet.");
|
||||
return;
|
||||
}
|
||||
//console.log('Broadcasting ' + items.length + ' items.');
|
||||
Log.info("Newsfeed-Fetcher: Broadcasting " + items.length + " items.");
|
||||
itemsReceivedCallback(self);
|
||||
};
|
||||
|
||||
this.onReceive = function(callback) {
|
||||
this.onReceive = function (callback) {
|
||||
itemsReceivedCallback = callback;
|
||||
};
|
||||
|
||||
this.onError = function(callback) {
|
||||
this.onError = function (callback) {
|
||||
fetchFailedCallback = callback;
|
||||
};
|
||||
|
||||
this.url = function() {
|
||||
this.url = function () {
|
||||
return url;
|
||||
};
|
||||
|
||||
this.items = function() {
|
||||
this.items = function () {
|
||||
return items;
|
||||
};
|
||||
};
|
||||
|
@@ -1,20 +1,16 @@
|
||||
/* global Module */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: NewsFeed
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("newsfeed",{
|
||||
|
||||
Module.register("newsfeed", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
feeds: [
|
||||
{
|
||||
title: "New York Times",
|
||||
url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml",
|
||||
url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml",
|
||||
encoding: "UTF-8" //ISO-8859-1
|
||||
}
|
||||
],
|
||||
@@ -44,12 +40,12 @@ Module.register("newsfeed",{
|
||||
},
|
||||
|
||||
// Define required scripts.
|
||||
getScripts: function() {
|
||||
getScripts: function () {
|
||||
return ["moment.js"];
|
||||
},
|
||||
|
||||
// Define required translations.
|
||||
getTranslations: function() {
|
||||
getTranslations: function () {
|
||||
// The translations for the default modules are defined in the core translation files.
|
||||
// Therefor we can just return false. Otherwise we should have returned a dictionary.
|
||||
// If you're trying to build your own module including translations, check out the documentation.
|
||||
@@ -57,7 +53,7 @@ Module.register("newsfeed",{
|
||||
},
|
||||
|
||||
// Define start sequence.
|
||||
start: function() {
|
||||
start: function () {
|
||||
Log.info("Starting module: " + this.name);
|
||||
|
||||
// Set locale.
|
||||
@@ -74,7 +70,7 @@ Module.register("newsfeed",{
|
||||
},
|
||||
|
||||
// Override socket notification handler.
|
||||
socketNotificationReceived: function(notification, payload) {
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
if (notification === "NEWS_ITEMS") {
|
||||
this.generateFeed(payload);
|
||||
|
||||
@@ -87,7 +83,7 @@ Module.register("newsfeed",{
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function() {
|
||||
getDom: function () {
|
||||
var wrapper = document.createElement("div");
|
||||
|
||||
if (this.config.feedUrl) {
|
||||
@@ -101,7 +97,6 @@ Module.register("newsfeed",{
|
||||
}
|
||||
|
||||
if (this.newsItems.length > 0) {
|
||||
|
||||
// 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");
|
||||
@@ -116,7 +111,7 @@ Module.register("newsfeed",{
|
||||
if (this.config.showPublishDate) {
|
||||
sourceAndTimestamp.innerHTML += moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow();
|
||||
}
|
||||
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "" || this.config.showPublishDate) {
|
||||
if ((this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") || this.config.showPublishDate) {
|
||||
sourceAndTimestamp.innerHTML += ":";
|
||||
}
|
||||
|
||||
@@ -126,47 +121,42 @@ Module.register("newsfeed",{
|
||||
//Remove selected tags from the beginning of rss feed items (title or description)
|
||||
|
||||
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
|
||||
|
||||
for (f=0; f<this.config.startTags.length;f++) {
|
||||
if (this.newsItems[this.activeItem].title.slice(0,this.config.startTags[f].length) === this.config.startTags[f]) {
|
||||
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(this.config.startTags[f].length,this.newsItems[this.activeItem].title.length);
|
||||
for (let f = 0; f < this.config.startTags.length; f++) {
|
||||
if (this.newsItems[this.activeItem].title.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
|
||||
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].title.length);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
|
||||
|
||||
if (this.isShowingDescription) {
|
||||
for (f=0; f<this.config.startTags.length;f++) {
|
||||
if (this.newsItems[this.activeItem].description.slice(0,this.config.startTags[f].length) === this.config.startTags[f]) {
|
||||
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length,this.newsItems[this.activeItem].description.length);
|
||||
for (let f = 0; f < this.config.startTags.length; f++) {
|
||||
if (this.newsItems[this.activeItem].description.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
|
||||
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].description.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Remove selected tags from the end of rss feed items (title or description)
|
||||
|
||||
if (this.config.removeEndTags) {
|
||||
for (f=0; f<this.config.endTags.length;f++) {
|
||||
if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length)===this.config.endTags[f]) {
|
||||
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(0,-this.config.endTags[f].length);
|
||||
for (let f = 0; f < this.config.endTags.length; f++) {
|
||||
if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
|
||||
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(0, -this.config.endTags[f].length);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isShowingDescription) {
|
||||
for (f=0; f<this.config.endTags.length;f++) {
|
||||
if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length)===this.config.endTags[f]) {
|
||||
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(0,-this.config.endTags[f].length);
|
||||
for (let f = 0; f < this.config.endTags.length; f++) {
|
||||
if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
|
||||
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(0, -this.config.endTags[f].length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(!this.config.showFullArticle){
|
||||
if (!this.config.showFullArticle) {
|
||||
var title = document.createElement("div");
|
||||
title.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : "");
|
||||
title.innerHTML = this.newsItems[this.activeItem].title;
|
||||
@@ -177,7 +167,7 @@ Module.register("newsfeed",{
|
||||
var description = document.createElement("div");
|
||||
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);
|
||||
description.innerHTML = this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc;
|
||||
wrapper.appendChild(description);
|
||||
}
|
||||
|
||||
@@ -199,7 +189,6 @@ Module.register("newsfeed",{
|
||||
if (this.config.hideLoading) {
|
||||
this.show();
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this.config.hideLoading) {
|
||||
this.hide();
|
||||
@@ -212,14 +201,14 @@ Module.register("newsfeed",{
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
getActiveItemURL: function() {
|
||||
getActiveItemURL: function () {
|
||||
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.
|
||||
*/
|
||||
registerFeeds: function() {
|
||||
registerFeeds: function () {
|
||||
for (var f in this.config.feeds) {
|
||||
var feed = this.config.feeds[f];
|
||||
this.sendSocketNotification("ADD_FEED", {
|
||||
@@ -234,7 +223,7 @@ Module.register("newsfeed",{
|
||||
*
|
||||
* attribute feeds object - An object with feeds returned by the node helper.
|
||||
*/
|
||||
generateFeed: function(feeds) {
|
||||
generateFeed: function (feeds) {
|
||||
var newsItems = [];
|
||||
for (var feed in feeds) {
|
||||
var feedItems = feeds[feed];
|
||||
@@ -242,24 +231,24 @@ Module.register("newsfeed",{
|
||||
for (var i in feedItems) {
|
||||
var item = feedItems[i];
|
||||
item.sourceTitle = this.titleForFeed(feed);
|
||||
if (!(this.config.ignoreOldItems && ((Date.now() - new Date(item.pubdate)) > this.config.ignoreOlderThan))) {
|
||||
if (!(this.config.ignoreOldItems && Date.now() - new Date(item.pubdate) > this.config.ignoreOlderThan)) {
|
||||
newsItems.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newsItems.sort(function(a,b) {
|
||||
newsItems.sort(function (a, b) {
|
||||
var dateA = new Date(a.pubdate);
|
||||
var dateB = new Date(b.pubdate);
|
||||
return dateB - dateA;
|
||||
});
|
||||
if(this.config.maxNewsItems > 0) {
|
||||
if (this.config.maxNewsItems > 0) {
|
||||
newsItems = newsItems.slice(0, this.config.maxNewsItems);
|
||||
}
|
||||
|
||||
if(this.config.prohibitedWords.length > 0) {
|
||||
newsItems = newsItems.filter(function(value){
|
||||
for (var i=0; i < this.config.prohibitedWords.length; i++) {
|
||||
if (this.config.prohibitedWords.length > 0) {
|
||||
newsItems = newsItems.filter(function (value) {
|
||||
for (var i = 0; i < this.config.prohibitedWords.length; i++) {
|
||||
if (value["title"].toLowerCase().indexOf(this.config.prohibitedWords[i].toLowerCase()) > -1) {
|
||||
return false;
|
||||
}
|
||||
@@ -270,8 +259,8 @@ Module.register("newsfeed",{
|
||||
|
||||
// get updated news items and broadcast them
|
||||
var updatedItems = [];
|
||||
newsItems.forEach(value => {
|
||||
if (this.newsItems.findIndex(value1 => value1 === value) === -1) {
|
||||
newsItems.forEach((value) => {
|
||||
if (this.newsItems.findIndex((value1) => value1 === value) === -1) {
|
||||
// Add item to updated items list
|
||||
updatedItems.push(value);
|
||||
}
|
||||
@@ -279,7 +268,7 @@ Module.register("newsfeed",{
|
||||
|
||||
// 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.sendNotification("NEWS_FEED_UPDATE", { items: updatedItems });
|
||||
}
|
||||
|
||||
this.newsItems = newsItems;
|
||||
@@ -292,7 +281,7 @@ Module.register("newsfeed",{
|
||||
*
|
||||
* returns bool
|
||||
*/
|
||||
subscribedToFeed: function(feedUrl) {
|
||||
subscribedToFeed: function (feedUrl) {
|
||||
for (var f in this.config.feeds) {
|
||||
var feed = this.config.feeds[f];
|
||||
if (feed.url === feedUrl) {
|
||||
@@ -309,7 +298,7 @@ Module.register("newsfeed",{
|
||||
*
|
||||
* returns string
|
||||
*/
|
||||
titleForFeed: function(feedUrl) {
|
||||
titleForFeed: function (feedUrl) {
|
||||
for (var f in this.config.feeds) {
|
||||
var feed = this.config.feeds[f];
|
||||
if (feed.url === feedUrl) {
|
||||
@@ -322,23 +311,23 @@ Module.register("newsfeed",{
|
||||
/* scheduleUpdateInterval()
|
||||
* Schedule visual update.
|
||||
*/
|
||||
scheduleUpdateInterval: function() {
|
||||
scheduleUpdateInterval: function () {
|
||||
var self = this;
|
||||
|
||||
self.updateDom(self.config.animationSpeed);
|
||||
|
||||
// Broadcast NewsFeed if needed
|
||||
if (self.config.broadcastNewsFeeds) {
|
||||
self.sendNotification("NEWS_FEED", {items: self.newsItems});
|
||||
self.sendNotification("NEWS_FEED", { items: self.newsItems });
|
||||
}
|
||||
|
||||
timer = setInterval(function() {
|
||||
this.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});
|
||||
self.sendNotification("NEWS_FEED", { items: self.newsItems });
|
||||
}
|
||||
}, this.config.updateInterval);
|
||||
},
|
||||
@@ -350,25 +339,25 @@ Module.register("newsfeed",{
|
||||
*
|
||||
* return string - Capitalized output string.
|
||||
*/
|
||||
capitalizeFirstLetter: function(string) {
|
||||
capitalizeFirstLetter: function (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
},
|
||||
|
||||
resetDescrOrFullArticleAndTimer: function() {
|
||||
resetDescrOrFullArticleAndTimer: function () {
|
||||
this.isShowingDescription = this.config.showDescription;
|
||||
this.config.showFullArticle = false;
|
||||
this.scrollPosition = 0;
|
||||
// reset bottom bar alignment
|
||||
document.getElementsByClassName("region bottom bar")[0].style.bottom = "0";
|
||||
document.getElementsByClassName("region bottom bar")[0].style.top = "inherit";
|
||||
if(!timer){
|
||||
if (!this.timer) {
|
||||
this.scheduleUpdateInterval();
|
||||
}
|
||||
},
|
||||
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
if(notification === "ARTICLE_NEXT"){
|
||||
var before = this.activeItem;
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
var before = this.activeItem;
|
||||
if (notification === "ARTICLE_NEXT") {
|
||||
this.activeItem++;
|
||||
if (this.activeItem >= this.newsItems.length) {
|
||||
this.activeItem = 0;
|
||||
@@ -376,8 +365,7 @@ Module.register("newsfeed",{
|
||||
this.resetDescrOrFullArticleAndTimer();
|
||||
Log.info(this.name + " - going from article #" + before + " to #" + this.activeItem + " (of " + this.newsItems.length + ")");
|
||||
this.updateDom(100);
|
||||
} else if(notification === "ARTICLE_PREVIOUS"){
|
||||
var before = this.activeItem;
|
||||
} else if (notification === "ARTICLE_PREVIOUS") {
|
||||
this.activeItem--;
|
||||
if (this.activeItem < 0) {
|
||||
this.activeItem = this.newsItems.length - 1;
|
||||
@@ -387,58 +375,56 @@ Module.register("newsfeed",{
|
||||
this.updateDom(100);
|
||||
}
|
||||
// if "more details" is received the first time: show article summary, on second time show full article
|
||||
else if(notification === "ARTICLE_MORE_DETAILS"){
|
||||
else if (notification === "ARTICLE_MORE_DETAILS") {
|
||||
// full article is already showing, so scrolling down
|
||||
if(this.config.showFullArticle === true){
|
||||
if (this.config.showFullArticle === true) {
|
||||
this.scrollPosition += this.config.scrollLength;
|
||||
window.scrollTo(0, this.scrollPosition);
|
||||
Log.info(this.name + " - scrolling down");
|
||||
Log.info(this.name + " - ARTICLE_MORE_DETAILS, scroll position: " + this.config.scrollLength);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.showFullArticle();
|
||||
}
|
||||
} else if(notification === "ARTICLE_SCROLL_UP"){
|
||||
if(this.config.showFullArticle === true){
|
||||
} else if (notification === "ARTICLE_SCROLL_UP") {
|
||||
if (this.config.showFullArticle === true) {
|
||||
this.scrollPosition -= this.config.scrollLength;
|
||||
window.scrollTo(0, this.scrollPosition);
|
||||
Log.info(this.name + " - scrolling up");
|
||||
Log.info(this.name + " - ARTICLE_SCROLL_UP, scroll position: " + this.config.scrollLength);
|
||||
}
|
||||
} else if(notification === "ARTICLE_LESS_DETAILS"){
|
||||
} else if (notification === "ARTICLE_LESS_DETAILS") {
|
||||
this.resetDescrOrFullArticleAndTimer();
|
||||
Log.info(this.name + " - showing only article titles again");
|
||||
this.updateDom(100);
|
||||
} else if (notification === "ARTICLE_TOGGLE_FULL"){
|
||||
if (this.config.showFullArticle){
|
||||
} else if (notification === "ARTICLE_TOGGLE_FULL") {
|
||||
if (this.config.showFullArticle) {
|
||||
this.activeItem++;
|
||||
this.resetDescrOrFullArticleAndTimer();
|
||||
} else {
|
||||
this.showFullArticle();
|
||||
}
|
||||
} else if (notification === "ARTICLE_INFO_REQUEST"){
|
||||
} else if (notification === "ARTICLE_INFO_REQUEST") {
|
||||
this.sendNotification("ARTICLE_INFO_RESPONSE", {
|
||||
title: this.newsItems[this.activeItem].title,
|
||||
title: this.newsItems[this.activeItem].title,
|
||||
source: this.newsItems[this.activeItem].sourceTitle,
|
||||
date: this.newsItems[this.activeItem].pubdate,
|
||||
desc: this.newsItems[this.activeItem].description,
|
||||
url: this.getActiveItemURL()
|
||||
date: this.newsItems[this.activeItem].pubdate,
|
||||
desc: this.newsItems[this.activeItem].description,
|
||||
url: this.getActiveItemURL()
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showFullArticle: function() {
|
||||
showFullArticle: function () {
|
||||
this.isShowingDescription = !this.isShowingDescription;
|
||||
this.config.showFullArticle = !this.isShowingDescription;
|
||||
// make bottom bar align to top to allow scrolling
|
||||
if(this.config.showFullArticle === true){
|
||||
if (this.config.showFullArticle === true) {
|
||||
document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit";
|
||||
document.getElementsByClassName("region bottom bar")[0].style.top = "-90px";
|
||||
}
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
Log.info(this.name + " - showing " + this.isShowingDescription ? "article description" : "full article");
|
||||
this.updateDom(100);
|
||||
}
|
||||
|
||||
});
|
||||
|
@@ -1,26 +1,26 @@
|
||||
/* Magic Mirror
|
||||
* Node Helper: Newsfeed
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var NodeHelper = require("node_helper");
|
||||
var validUrl = require("valid-url");
|
||||
var Fetcher = require("./fetcher.js");
|
||||
const NodeHelper = require("node_helper");
|
||||
const validUrl = require("valid-url");
|
||||
const Fetcher = require("./fetcher.js");
|
||||
const Log = require("../../../js/logger");
|
||||
|
||||
module.exports = NodeHelper.create({
|
||||
// Subclass start method.
|
||||
start: function() {
|
||||
console.log("Starting module: " + this.name);
|
||||
// Override start method.
|
||||
start: function () {
|
||||
Log.log("Starting node helper for: " + this.name);
|
||||
this.fetchers = [];
|
||||
},
|
||||
|
||||
// Subclass socketNotificationReceived received.
|
||||
socketNotificationReceived: function(notification, payload) {
|
||||
// Override socketNotificationReceived received.
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
if (notification === "ADD_FEED") {
|
||||
this.createFetcher(payload.feed, payload.config);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ module.exports = NodeHelper.create({
|
||||
* attribute feed object - A feed object.
|
||||
* attribute config object - A configuration object containing reload interval in milliseconds.
|
||||
*/
|
||||
createFetcher: function(feed, config) {
|
||||
createFetcher: function (feed, config) {
|
||||
var self = this;
|
||||
|
||||
var url = feed.url || "";
|
||||
@@ -45,14 +45,14 @@ module.exports = NodeHelper.create({
|
||||
|
||||
var fetcher;
|
||||
if (typeof self.fetchers[url] === "undefined") {
|
||||
console.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval);
|
||||
Log.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval);
|
||||
fetcher = new Fetcher(url, reloadInterval, encoding, config.logFeedWarnings);
|
||||
|
||||
fetcher.onReceive(function(fetcher) {
|
||||
fetcher.onReceive(function (fetcher) {
|
||||
self.broadcastFeeds();
|
||||
});
|
||||
|
||||
fetcher.onError(function(fetcher, error) {
|
||||
fetcher.onError(function (fetcher, error) {
|
||||
self.sendSocketNotification("FETCH_ERROR", {
|
||||
url: fetcher.url(),
|
||||
error: error
|
||||
@@ -61,7 +61,7 @@ module.exports = NodeHelper.create({
|
||||
|
||||
self.fetchers[url] = fetcher;
|
||||
} else {
|
||||
console.log("Use existing news fetcher for url: " + url);
|
||||
Log.log("Use existing news fetcher for url: " + url);
|
||||
fetcher = self.fetchers[url];
|
||||
fetcher.setReloadInterval(reloadInterval);
|
||||
fetcher.broadcastItems();
|
||||
@@ -74,7 +74,7 @@ module.exports = NodeHelper.create({
|
||||
* Creates an object with all feed items of the different registered feeds,
|
||||
* and broadcasts these using sendSocketNotification.
|
||||
*/
|
||||
broadcastFeeds: function() {
|
||||
broadcastFeeds: function () {
|
||||
var feeds = {};
|
||||
for (var f in this.fetchers) {
|
||||
feeds[f] = this.fetchers[f].items();
|
||||
|
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"configuration_changed": "Die Konfigurationsoptionen für das Newsfeed-Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation."
|
||||
}
|
||||
"configuration_changed": "Die Konfigurationsoptionen für das Newsfeed-Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation."
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"configuration_changed": "The configuration options for the newsfeed module have changed.\nPlease check the documentation."
|
||||
}
|
||||
"configuration_changed": "The configuration options for the newsfeed module have changed.\nPlease check the documentation."
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"configuration_changed": "Las opciones de configuración para el módulo de suministro de noticias han cambiado. \nVerifique la documentación."
|
||||
}
|
||||
"configuration_changed": "Las opciones de configuración para el módulo de suministro de noticias han cambiado. \nVerifique la documentación."
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"configuration_changed": "Les options de configuration du module newsfeed ont changé. \nVeuillez consulter la documentation."
|
||||
}
|
||||
"configuration_changed": "Les options de configuration du module newsfeed ont changé. \nVeuillez consulter la documentation."
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# Module: Update Notification
|
||||
|
||||
The `updatenotification` module is one of the default modules of the MagicMirror.
|
||||
This will display a message whenever a new version of the MagicMirror application is available.
|
||||
|
||||
|
@@ -1,39 +1,37 @@
|
||||
var SimpleGit = require("simple-git");
|
||||
var simpleGits = [];
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var defaultModules = require(__dirname + "/../defaultmodules.js");
|
||||
var NodeHelper = require("node_helper");
|
||||
const SimpleGit = require("simple-git");
|
||||
const simpleGits = [];
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const defaultModules = require(__dirname + "/../defaultmodules.js");
|
||||
const Log = require(__dirname + "/../../../js/logger.js");
|
||||
const NodeHelper = require("node_helper");
|
||||
|
||||
module.exports = NodeHelper.create({
|
||||
|
||||
config: {},
|
||||
|
||||
updateTimer: null,
|
||||
updateProcessStarted: false,
|
||||
|
||||
start: function () {
|
||||
},
|
||||
|
||||
configureModules: function(modules) {
|
||||
start: function () {},
|
||||
|
||||
configureModules: function (modules) {
|
||||
// Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten
|
||||
// others will be added in front
|
||||
// this method returns promises so we can't wait for every one to resolve before continuing
|
||||
simpleGits.push({"module": "default", "git": SimpleGit(path.normalize(__dirname + "/../../../"))});
|
||||
simpleGits.push({ module: "default", git: SimpleGit(path.normalize(__dirname + "/../../../")) });
|
||||
|
||||
var promises = [];
|
||||
|
||||
for (moduleName in modules) {
|
||||
for (var moduleName in modules) {
|
||||
if (!this.ignoreUpdateChecking(moduleName)) {
|
||||
// Default modules are included in the main MagicMirror repo
|
||||
var moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
|
||||
|
||||
try {
|
||||
//console.log("checking git for module="+moduleName)
|
||||
Log.info("Checking git for module: " + moduleName);
|
||||
let stat = fs.statSync(path.join(moduleFolder, ".git"));
|
||||
promises.push(this.resolveRemote(moduleName, moduleFolder));
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
// Error when directory .git doesn't exist
|
||||
// This module is not managed with git, skip
|
||||
continue;
|
||||
@@ -47,7 +45,7 @@ module.exports = NodeHelper.create({
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
if (notification === "CONFIG") {
|
||||
this.config = payload;
|
||||
} else if(notification === "MODULES") {
|
||||
} else if (notification === "MODULES") {
|
||||
// if this is the 1st time thru the update check process
|
||||
if (!this.updateProcessStarted) {
|
||||
this.updateProcessStarted = true;
|
||||
@@ -56,7 +54,7 @@ module.exports = NodeHelper.create({
|
||||
}
|
||||
},
|
||||
|
||||
resolveRemote: function(moduleName, moduleFolder) {
|
||||
resolveRemote: function (moduleName, moduleFolder) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var git = SimpleGit(moduleFolder);
|
||||
git.getRemotes(true, (err, remotes) => {
|
||||
@@ -65,19 +63,19 @@ module.exports = NodeHelper.create({
|
||||
return resolve();
|
||||
}
|
||||
// Folder has .git and has at least one git remote, watch this folder
|
||||
simpleGits.unshift({"module": moduleName, "git": git});
|
||||
simpleGits.unshift({ module: moduleName, git: git });
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
performFetch: function() {
|
||||
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}, (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);
|
||||
@@ -90,19 +88,19 @@ module.exports = NodeHelper.create({
|
||||
this.scheduleNextFetch(this.config.updateInterval);
|
||||
},
|
||||
|
||||
scheduleNextFetch: function(delay) {
|
||||
scheduleNextFetch: function (delay) {
|
||||
if (delay < 60 * 1000) {
|
||||
delay = 60 * 1000;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
clearTimeout(this.updateTimer);
|
||||
this.updateTimer = setTimeout(function() {
|
||||
this.updateTimer = setTimeout(function () {
|
||||
self.performFetch();
|
||||
}, delay);
|
||||
},
|
||||
|
||||
ignoreUpdateChecking: function(moduleName) {
|
||||
ignoreUpdateChecking: function (moduleName) {
|
||||
// Should not check for updates for default modules
|
||||
if (defaultModules.indexOf(moduleName) >= 0) {
|
||||
return true;
|
||||
@@ -116,5 +114,4 @@ module.exports = NodeHelper.create({
|
||||
// The rest of the modules that passes should check for updates
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
@@ -1,5 +1,10 @@
|
||||
/* Magic Mirror
|
||||
* Module: UpdateNotification
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("updatenotification", {
|
||||
|
||||
defaults: {
|
||||
updateInterval: 10 * 60 * 1000, // every 10 minutes
|
||||
refreshInterval: 24 * 60 * 60 * 1000, // one day
|
||||
@@ -12,7 +17,10 @@ Module.register("updatenotification", {
|
||||
start: function () {
|
||||
var self = this;
|
||||
Log.log("Start updatenotification");
|
||||
setInterval( () => { self.moduleList = {};self.updateDom(2); } , self.config.refreshInterval);
|
||||
setInterval(() => {
|
||||
self.moduleList = {};
|
||||
self.updateDom(2);
|
||||
}, self.config.refreshInterval);
|
||||
},
|
||||
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
@@ -33,16 +41,15 @@ Module.register("updatenotification", {
|
||||
var self = this;
|
||||
if (payload && payload.behind > 0) {
|
||||
// if we haven't seen info for this module
|
||||
if(this.moduleList[payload.module] == undefined){
|
||||
if (this.moduleList[payload.module] === undefined) {
|
||||
// save it
|
||||
this.moduleList[payload.module]=payload;
|
||||
this.moduleList[payload.module] = payload;
|
||||
self.updateDom(2);
|
||||
}
|
||||
//self.show(1000, { lockString: self.identifier });
|
||||
|
||||
} else if (payload && payload.behind == 0){
|
||||
} else if (payload && payload.behind === 0) {
|
||||
// if the module WAS in the list, but shouldn't be
|
||||
if(this.moduleList[payload.module] != undefined){
|
||||
if (this.moduleList[payload.module] !== undefined) {
|
||||
// remove it
|
||||
delete this.moduleList[payload.module];
|
||||
self.updateDom(2);
|
||||
@@ -50,24 +57,19 @@ Module.register("updatenotification", {
|
||||
}
|
||||
},
|
||||
|
||||
diffLink: function(module, text) {
|
||||
diffLink: function (module, text) {
|
||||
var localRef = module.hash;
|
||||
var remoteRef = module.tracking.replace(/.*\//, "");
|
||||
return "<a href=\"https://github.com/MichMich/MagicMirror/compare/"+localRef+"..."+remoteRef+"\" "+
|
||||
"class=\"xsmall dimmed\" "+
|
||||
"style=\"text-decoration: none;\" "+
|
||||
"target=\"_blank\" >" +
|
||||
text +
|
||||
"</a>";
|
||||
return '<a href="https://github.com/MichMich/MagicMirror/compare/' + localRef + "..." + remoteRef + '" ' + 'class="xsmall dimmed" ' + 'style="text-decoration: none;" ' + 'target="_blank" >' + text + "</a>";
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function () {
|
||||
var wrapper = document.createElement("div");
|
||||
if(this.suspended==false){
|
||||
if (this.suspended === false) {
|
||||
// process the hash of module info found
|
||||
for(key of Object.keys(this.moduleList)){
|
||||
let m= this.moduleList[key];
|
||||
for (var key of Object.keys(this.moduleList)) {
|
||||
let m = this.moduleList[key];
|
||||
|
||||
var message = document.createElement("div");
|
||||
message.className = "small bright";
|
||||
@@ -77,7 +79,7 @@ Module.register("updatenotification", {
|
||||
icon.innerHTML = " ";
|
||||
message.appendChild(icon);
|
||||
|
||||
var updateInfoKeyName = m.behind == 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
|
||||
var updateInfoKeyName = m.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
|
||||
|
||||
var subtextHtml = this.translate(updateInfoKeyName, {
|
||||
COMMIT_COUNT: m.behind,
|
||||
@@ -85,9 +87,9 @@ Module.register("updatenotification", {
|
||||
});
|
||||
|
||||
var text = document.createElement("span");
|
||||
if (m.module == "default") {
|
||||
if (m.module === "default") {
|
||||
text.innerHTML = this.translate("UPDATE_NOTIFICATION");
|
||||
subtextHtml = this.diffLink(m,subtextHtml);
|
||||
subtextHtml = this.diffLink(m, subtextHtml);
|
||||
} else {
|
||||
text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", {
|
||||
MODULE_NAME: m.module
|
||||
@@ -106,11 +108,11 @@ Module.register("updatenotification", {
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
suspend: function() {
|
||||
this.suspended=true;
|
||||
suspend: function () {
|
||||
this.suspended = true;
|
||||
},
|
||||
resume: function() {
|
||||
this.suspended=false;
|
||||
resume: function () {
|
||||
this.suspended = false;
|
||||
this.updateDom(2);
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +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 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.
|
||||
|
||||
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html).
|
||||
|
@@ -22,14 +22,16 @@
|
||||
{% if config.showHumidity and current.humidity %}
|
||||
<span>{{ current.humidity | decimalSymbol }}</span><sup> <i class="wi wi-humidity humidityIcon"></i></sup>
|
||||
{% endif %}
|
||||
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
|
||||
<span>
|
||||
{% if current.nextSunAction() === "sunset" %}
|
||||
{{ current.sunset | formatTime }}
|
||||
{% else %}
|
||||
{{ current.sunrise | formatTime }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if config.showSun %}
|
||||
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
|
||||
<span>
|
||||
{% if current.nextSunAction() === "sunset" %}
|
||||
{{ current.sunset | formatTime }}
|
||||
{% else %}
|
||||
{{ current.sunrise | formatTime }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="large light">
|
||||
|
@@ -13,17 +13,17 @@ Table of Contents:
|
||||
|
||||
## The weather provider file: yourprovider.js
|
||||
|
||||
This is the script in which the weather provider will be defined. In it's most simple form, the weather provider must implement the following:
|
||||
This is the script in which the weather provider will be defined. In its most simple form, the weather provider must implement the following:
|
||||
|
||||
````javascript
|
||||
```javascript
|
||||
WeatherProvider.register("yourprovider", {
|
||||
providerName: "YourProvider",
|
||||
providerName: "YourProvider",
|
||||
|
||||
fetchCurrentWeather() {},
|
||||
fetchCurrentWeather() {},
|
||||
|
||||
fetchWeatherForecast() {}
|
||||
fetchWeatherForecast() {}
|
||||
});
|
||||
````
|
||||
```
|
||||
|
||||
### Weather provider methods to implement
|
||||
|
||||
@@ -36,7 +36,7 @@ It will then automatically refresh the module DOM with the new data.
|
||||
|
||||
#### `fetchWeatherForecast()`
|
||||
|
||||
This method is called when the weather module tries to fetch the weather weather of your provider. The implementation of this method is required.
|
||||
This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required.
|
||||
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
|
||||
After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setCurrentWeather(forecast);`.
|
||||
It will then automatically refresh the module DOM with the new data.
|
||||
@@ -89,24 +89,24 @@ A convenience function to make requests. It returns a promise.
|
||||
|
||||
### WeatherObject
|
||||
|
||||
| Property | Type | Value/Unit |
|
||||
| --- | --- | --- |
|
||||
| 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. |
|
||||
| sunrise |`object` | [Moment.js](https://momentjs.com/) object of sunrise. |
|
||||
| sunset |`object` | [Moment.js](https://momentjs.com/) object of sunset. |
|
||||
| temperature | `number` | Current temperature |
|
||||
| minTemperature | `number` | Lowest temperature of the day. |
|
||||
| maxTemperature | `number` | Highest temperature of the day. |
|
||||
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
|
||||
| humidity | `number` | Percentage of humidity |
|
||||
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
||||
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
||||
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
|
||||
| Property | Type | Value/Unit |
|
||||
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| 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. |
|
||||
| sunrise | `object` | [Moment.js](https://momentjs.com/) object of sunrise. |
|
||||
| sunset | `object` | [Moment.js](https://momentjs.com/) object of sunset. |
|
||||
| temperature | `number` | Current temperature |
|
||||
| minTemperature | `number` | Lowest temperature of the day. |
|
||||
| maxTemperature | `number` | Highest temperature of the day. |
|
||||
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
|
||||
| humidity | `number` | Percentage of humidity |
|
||||
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
||||
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
||||
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
|
||||
|
||||
#### Current weather
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
/* global WeatherProvider, WeatherDay */
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
@@ -16,40 +16,42 @@ WeatherProvider.register("darksky", {
|
||||
providerName: "Dark Sky",
|
||||
|
||||
units: {
|
||||
imperial: 'us',
|
||||
metric: 'si'
|
||||
imperial: "us",
|
||||
metric: "si"
|
||||
},
|
||||
|
||||
fetchCurrentWeather() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then(data => {
|
||||
if(!data || !data.currently || typeof data.currently.temperature === "undefined") {
|
||||
.then((data) => {
|
||||
if (!data || !data.currently || typeof data.currently.temperature === "undefined") {
|
||||
// No usable data?
|
||||
return;
|
||||
}
|
||||
|
||||
const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
}).catch(function(request) {
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
fetchWeatherForecast() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then(data => {
|
||||
if(!data || !data.daily || !data.daily.data.length) {
|
||||
.then((data) => {
|
||||
if (!data || !data.daily || !data.daily.data.length) {
|
||||
// No usable data?
|
||||
return;
|
||||
}
|
||||
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
|
||||
this.setWeatherForecast(forecast);
|
||||
}).catch(function(request) {
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
// Create a URL from the config and base URL.
|
||||
@@ -109,12 +111,12 @@ WeatherProvider.register("darksky", {
|
||||
const weatherTypes = {
|
||||
"clear-day": "day-sunny",
|
||||
"clear-night": "night-clear",
|
||||
"rain": "rain",
|
||||
"snow": "snow",
|
||||
"sleet": "snow",
|
||||
"wind": "wind",
|
||||
"fog": "fog",
|
||||
"cloudy": "cloudy",
|
||||
rain: "rain",
|
||||
snow: "snow",
|
||||
sleet: "snow",
|
||||
wind: "wind",
|
||||
fog: "fog",
|
||||
cloudy: "cloudy",
|
||||
"partly-cloudy-day": "day-cloudy",
|
||||
"partly-cloudy-night": "night-cloudy"
|
||||
};
|
||||
|
@@ -3,14 +3,12 @@
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is the blueprint for a weather provider.
|
||||
*/
|
||||
|
||||
WeatherProvider.register("openweathermap", {
|
||||
|
||||
// Set the name of the provider.
|
||||
// This isn't strictly necessary, since it will fallback to the provider identifier
|
||||
// But for debugging (and future alerts) it would be nice to have the real name.
|
||||
@@ -19,7 +17,7 @@ WeatherProvider.register("openweathermap", {
|
||||
// Overwrite the fetchCurrentWeather method.
|
||||
fetchCurrentWeather() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
if (!data || !data.main || typeof data.main.temp === "undefined") {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
@@ -31,16 +29,16 @@ WeatherProvider.register("openweathermap", {
|
||||
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
})
|
||||
.catch(function(request) {
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
// Overwrite the fetchCurrentWeather method.
|
||||
fetchWeatherForecast() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
if (!data || !data.list || !data.list.length) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
@@ -52,10 +50,10 @@ WeatherProvider.register("openweathermap", {
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data.list);
|
||||
this.setWeatherForecast(forecast);
|
||||
})
|
||||
.catch(function(request) {
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
|
||||
@@ -87,7 +85,6 @@ WeatherProvider.register("openweathermap", {
|
||||
* Generate WeatherObjects based on forecast information
|
||||
*/
|
||||
generateWeatherObjectsFromForecast(forecasts) {
|
||||
|
||||
if (this.config.weatherEndpoint === "/forecast") {
|
||||
return this.fetchForecastHourly(forecasts);
|
||||
} else if (this.config.weatherEndpoint === "/forecast/daily") {
|
||||
@@ -114,7 +111,6 @@ WeatherProvider.register("openweathermap", {
|
||||
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
|
||||
for (const forecast of forecasts) {
|
||||
|
||||
if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) {
|
||||
// calculate minimum/maximum temperature, specify rain amount
|
||||
weather.minTemperature = Math.min.apply(null, minTemp);
|
||||
@@ -140,7 +136,6 @@ WeatherProvider.register("openweathermap", {
|
||||
|
||||
// If the first value of today is later than 17:00, we have an icon at least!
|
||||
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
|
||||
|
||||
}
|
||||
|
||||
if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) {
|
||||
@@ -223,7 +218,7 @@ WeatherProvider.register("openweathermap", {
|
||||
days.push(weather);
|
||||
}
|
||||
|
||||
return days;
|
||||
return days;
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -261,16 +256,16 @@ WeatherProvider.register("openweathermap", {
|
||||
*/
|
||||
getParams() {
|
||||
let params = "?";
|
||||
if(this.config.locationID) {
|
||||
if (this.config.locationID) {
|
||||
params += "id=" + this.config.locationID;
|
||||
} else if(this.config.location) {
|
||||
} else if (this.config.location) {
|
||||
params += "q=" + this.config.location;
|
||||
} else if (this.firstEvent && this.firstEvent.geo) {
|
||||
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
|
||||
} else if (this.firstEvent && this.firstEvent.location) {
|
||||
params += "q=" + this.firstEvent.location;
|
||||
} else {
|
||||
this.hide(this.config.animationSpeed, {lockString:this.identifier});
|
||||
this.hide(this.config.animationSpeed, { lockString: this.identifier });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
/* global WeatherProvider, WeatherObject, SunCalc */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
@@ -8,10 +8,7 @@
|
||||
*
|
||||
* 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.
|
||||
@@ -25,9 +22,8 @@ WeatherProvider.register("ukmetoffice", {
|
||||
// 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) {
|
||||
.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;
|
||||
@@ -38,18 +34,17 @@ WeatherProvider.register("ukmetoffice", {
|
||||
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
})
|
||||
.catch(function(request) {
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.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) {
|
||||
.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;
|
||||
@@ -60,14 +55,12 @@ WeatherProvider.register("ukmetoffice", {
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data);
|
||||
this.setWeatherForecast(forecast);
|
||||
})
|
||||
.catch(function(request) {
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
|
||||
|
||||
/** UK Met Office Specific Methods - These are not part of the default provider methods */
|
||||
/*
|
||||
* Gets the complete url for the request
|
||||
@@ -83,24 +76,23 @@ WeatherProvider.register("ukmetoffice", {
|
||||
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 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")
|
||||
for (var 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){
|
||||
for (var 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) {
|
||||
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);
|
||||
@@ -116,7 +108,7 @@ WeatherProvider.register("ukmetoffice", {
|
||||
}
|
||||
|
||||
// determine the sunrise/sunset times - not supplied in UK Met Office data
|
||||
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location)
|
||||
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location);
|
||||
currentWeather.sunrise = times[0];
|
||||
currentWeather.sunset = times[1];
|
||||
|
||||
@@ -127,22 +119,21 @@ WeatherProvider.register("ukmetoffice", {
|
||||
* 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) {
|
||||
for (var 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")
|
||||
const 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.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);
|
||||
@@ -213,7 +204,7 @@ WeatherProvider.register("ukmetoffice", {
|
||||
* Convert temp (from degrees C) if required
|
||||
*/
|
||||
convertTemp(tempInC) {
|
||||
return this.tempUnits === "imperial" ? tempInC * 9 / 5 + 32 : tempInC;
|
||||
return this.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -228,22 +219,22 @@ WeatherProvider.register("ukmetoffice", {
|
||||
*/
|
||||
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
|
||||
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;
|
||||
|
303
modules/default/weather/providers/ukmetofficedatahub.js
Normal file
@@ -0,0 +1,303 @@
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
*
|
||||
* By Malcolm Oakes https://github.com/maloakes
|
||||
* Existing Met Office provider edited for new MetOffice Data Hub by CreepinJesus http://github.com/XBCreepinJesus
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
|
||||
* For more information on Data Hub, see https://www.metoffice.gov.uk/services/data/datapoint/notifications/weather-datahub
|
||||
* Data available:
|
||||
* Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf
|
||||
* 3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf
|
||||
* Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf
|
||||
*/
|
||||
|
||||
/* NOTES
|
||||
* This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider)
|
||||
* Provide the following in your config.js file:
|
||||
* weatherProvider: "ukmetofficedatahub",
|
||||
* apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
|
||||
* apiKey: "[YOUR API KEY]",
|
||||
* apiSecret: "[YOUR API SECRET]]",
|
||||
* lat: [LATITUDE (DECIMAL)],
|
||||
* lon: [LONGITUDE (DECIMAL)],
|
||||
* windUnits: "mps" | "kph" | "mph" (default)
|
||||
* tempUnits: "imperial" | "metric" (default)
|
||||
*
|
||||
* At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when
|
||||
* setting your update intervals. For reference, 360 requests per day is once every 4 minutes.
|
||||
*
|
||||
* Pay attention to the units of the supplied data from the Met Office - it is given in SI/metric units where applicable:
|
||||
* - Temperatures are in degrees Celsius (°C)
|
||||
* - Wind speeds are in metres per second (m/s)
|
||||
* - Wind direction given in degrees (°)
|
||||
* - Pressures are in Pascals (Pa)
|
||||
* - Distances are in metres (m)
|
||||
* - Probabilities and humidity are given as percentages (%)
|
||||
* - Precipitation is measured in millimetres (mm) with rates per hour (mm/h)
|
||||
*
|
||||
* See the PDFs linked above for more information on the data their corresponding units.
|
||||
*/
|
||||
|
||||
WeatherProvider.register("ukmetofficedatahub", {
|
||||
// Set the name of the provider.
|
||||
providerName: "UK Met Office (DataHub)",
|
||||
|
||||
// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
|
||||
getUrl(forecastType) {
|
||||
let queryStrings = "?";
|
||||
queryStrings += "latitude=" + this.config.lat;
|
||||
queryStrings += "&longitude=" + this.config.lon;
|
||||
if (this.config.appendLocationNameToHeader) {
|
||||
queryStrings += "&includeLocationName=" + true;
|
||||
}
|
||||
|
||||
// Return URL, making sure there is a trailing "/" in the base URL.
|
||||
return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings;
|
||||
},
|
||||
|
||||
// Build the list of headers for the request
|
||||
// For DataHub requests, the API key/secret are sent in the headers rather than as query strings.
|
||||
// Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
|
||||
getHeaders() {
|
||||
let headers = {
|
||||
accept: "application/json",
|
||||
"x-ibm-client-id": this.config.apiKey,
|
||||
"x-ibm-client-secret": this.config.apiSecret
|
||||
};
|
||||
|
||||
return headers;
|
||||
},
|
||||
|
||||
// Fetch data using supplied URL and request headers
|
||||
async fetchWeather(url, headers) {
|
||||
const response = await fetch(url, { headers: headers });
|
||||
|
||||
// Return JSON data
|
||||
return response.json();
|
||||
},
|
||||
|
||||
// Fetch hourly forecast data (to use for current weather)
|
||||
fetchCurrentWeather() {
|
||||
this.fetchWeather(this.getUrl("hourly"), this.getHeaders())
|
||||
.then((data) => {
|
||||
// Check data is useable
|
||||
if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
Log.error("Possibly bad current/hourly data?");
|
||||
Log.info(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set location name
|
||||
this.setFetchedLocation(`${data.features[0].properties.location.name}`);
|
||||
|
||||
// Generate current weather data
|
||||
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
})
|
||||
|
||||
// Catch any error(s)
|
||||
.catch((error) => Log.error("Could not load data: " + error.message))
|
||||
|
||||
// Let the module know there're new data available
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
// Create a WeatherObject using current weather data (data for the current hour)
|
||||
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
|
||||
// Extract the actual forecasts
|
||||
let forecastDataHours = currentWeatherData.features[0].properties.timeSeries;
|
||||
|
||||
// Define now
|
||||
let nowUtc = moment.utc();
|
||||
|
||||
// Find hour that contains the current time
|
||||
for (hour in forecastDataHours) {
|
||||
let forecastTime = moment.utc(forecastDataHours[hour].time);
|
||||
if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
|
||||
currentWeather.date = forecastTime;
|
||||
currentWeather.windSpeed = this.convertWindSpeed(forecastDataHours[hour].windSpeed10m);
|
||||
currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m;
|
||||
currentWeather.temperature = this.convertTemp(forecastDataHours[hour].screenTemperature);
|
||||
currentWeather.minTemperature = this.convertTemp(forecastDataHours[hour].minScreenAirTemp);
|
||||
currentWeather.maxTemperature = this.convertTemp(forecastDataHours[hour].maxScreenAirTemp);
|
||||
currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode);
|
||||
currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
|
||||
currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
|
||||
currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
|
||||
currentWeather.precipitation = forecastDataHours[hour].probOfPrecipitation;
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(forecastDataHours[hour].feelsLikeTemperature);
|
||||
|
||||
// Pass on full details so they can be used in custom templates
|
||||
// Note the units of the supplied data when using this (see top of file)
|
||||
currentWeather.rawData = forecastDataHours[hour];
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the sunrise/sunset times - (still) not supplied in UK Met Office data
|
||||
// Passes {longitude, latitude, height} to calcAstroData
|
||||
// Could just pass lat/long from this.config, but returned data from MO also contains elevation
|
||||
let times = this.calcAstroData(currentWeatherData.features[0].geometry.coordinates);
|
||||
currentWeather.sunrise = times[0];
|
||||
currentWeather.sunset = times[1];
|
||||
|
||||
return currentWeather;
|
||||
},
|
||||
|
||||
// Fetch daily forecast data
|
||||
fetchWeatherForecast() {
|
||||
this.fetchWeather(this.getUrl("daily"), this.getHeaders())
|
||||
.then((data) => {
|
||||
// Check data is useable
|
||||
if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
Log.error("Possibly bad forecast data?");
|
||||
Log.info(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set location name
|
||||
this.setFetchedLocation(`${data.features[0].properties.location.name}`);
|
||||
|
||||
// Generate the forecast data
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data);
|
||||
this.setWeatherForecast(forecast);
|
||||
})
|
||||
|
||||
// Catch any error(s)
|
||||
.catch((error) => Log.error("Could not load data: " + error.message))
|
||||
|
||||
// Let the module know there're new data available
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
// Create a WeatherObject for each day using daily forecast data
|
||||
generateWeatherObjectsFromForecast(forecasts) {
|
||||
const dailyForecasts = [];
|
||||
|
||||
// Extract the actual forecasts
|
||||
let forecastDataDays = forecasts.features[0].properties.timeSeries;
|
||||
|
||||
// Define today
|
||||
let today = moment.utc().startOf("date");
|
||||
|
||||
// Go through each day in the forecasts
|
||||
for (day in forecastDataDays) {
|
||||
const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
|
||||
// Get date of forecast
|
||||
let forecastDate = moment.utc(forecastDataDays[day].time);
|
||||
|
||||
// Check if forecast is for today or in the future (i.e., ignore yesterday's forecast)
|
||||
if (forecastDate.isSameOrAfter(today)) {
|
||||
forecastWeather.date = forecastDate;
|
||||
forecastWeather.minTemperature = this.convertTemp(forecastDataDays[day].nightMinScreenTemperature);
|
||||
forecastWeather.maxTemperature = this.convertTemp(forecastDataDays[day].dayMaxScreenTemperature);
|
||||
|
||||
// Using daytime forecast values
|
||||
forecastWeather.windSpeed = this.convertWindSpeed(forecastDataDays[day].midday10MWindSpeed);
|
||||
forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection;
|
||||
forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
|
||||
forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation;
|
||||
forecastWeather.temperature = forecastDataDays[day].dayMaxScreenTemperature;
|
||||
forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
|
||||
forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;
|
||||
forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow;
|
||||
forecastWeather.feelsLikeTemp = this.convertTemp(forecastDataDays[day].dayMaxFeelsLikeTemp);
|
||||
|
||||
// Pass on full details so they can be used in custom templates
|
||||
// Note the units of the supplied data when using this (see top of file)
|
||||
|
||||
forecastWeather.rawData = forecastDataDays[day];
|
||||
|
||||
dailyForecasts.push(forecastWeather);
|
||||
}
|
||||
}
|
||||
|
||||
return dailyForecasts;
|
||||
},
|
||||
|
||||
// Set the fetched location name.
|
||||
setFetchedLocation: function (name) {
|
||||
this.fetchedLocationName = name;
|
||||
},
|
||||
|
||||
// Calculate sunrise/sunset times
|
||||
calcAstroData(location) {
|
||||
const sunTimes = [];
|
||||
|
||||
// Careful to pass values to SunCalc in correct order (latitude, longitude, elevation)
|
||||
let times = SunCalc.getTimes(new Date(), location[1], location[0], location[2]);
|
||||
sunTimes.push(moment(times.sunrise, "X"));
|
||||
sunTimes.push(moment(times.sunset, "X"));
|
||||
|
||||
return sunTimes;
|
||||
},
|
||||
|
||||
// Convert temperatures to Fahrenheit (from degrees C), if required
|
||||
convertTemp(tempInC) {
|
||||
return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
|
||||
},
|
||||
|
||||
// Convert wind speed from metres per second
|
||||
// To keep the supplied metres per second units, use "mps"
|
||||
// To use kilometres per hour, use "kph"
|
||||
// Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based)
|
||||
convertWindSpeed(windInMpS) {
|
||||
if (this.config.windUnits == "mps") {
|
||||
return windInMpS;
|
||||
}
|
||||
|
||||
if (this.config.windUnits == "kph" || this.config.windUnits == "metric") {
|
||||
return windInMpS * 3.6;
|
||||
}
|
||||
|
||||
return windInMpS * 2.23694;
|
||||
},
|
||||
|
||||
// Match the Met Office "significant weather code" to a weathericons.css icon
|
||||
// Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264
|
||||
// and: https://erikflowers.github.io/weather-icons/
|
||||
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;
|
||||
}
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
/* global WeatherProvider, WeatherObject, SunCalc */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
@@ -11,9 +11,7 @@
|
||||
* Note that this is only for US locations (lat and lon) and does not require an API key
|
||||
* Since it is free, there are some items missing - like sunrise, sunset, humidity, etc.
|
||||
*/
|
||||
|
||||
WeatherProvider.register("weathergov", {
|
||||
|
||||
// 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.
|
||||
@@ -22,7 +20,7 @@ WeatherProvider.register("weathergov", {
|
||||
// Overwrite the fetchCurrentWeather method.
|
||||
fetchCurrentWeather() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
@@ -32,16 +30,16 @@ WeatherProvider.register("weathergov", {
|
||||
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties.periods[0]);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
})
|
||||
.catch(function(request) {
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
// Overwrite the fetchCurrentWeather method.
|
||||
fetchWeatherForecast() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
@@ -51,10 +49,10 @@ WeatherProvider.register("weathergov", {
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
|
||||
this.setWeatherForecast(forecast);
|
||||
})
|
||||
.catch(function(request) {
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
})
|
||||
.finally(() => this.updateAvailable())
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
/** Weather.gov Specific Methods - These are not part of the default provider methods */
|
||||
@@ -77,7 +75,7 @@ WeatherProvider.register("weathergov", {
|
||||
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)
|
||||
let times = this.calcAstroData(this.config.lat, this.config.lon);
|
||||
currentWeather.sunrise = times[0];
|
||||
currentWeather.sunset = times[1];
|
||||
|
||||
@@ -106,9 +104,7 @@ WeatherProvider.register("weathergov", {
|
||||
weather.precipitation = 0;
|
||||
|
||||
for (const forecast of forecasts) {
|
||||
|
||||
if (date !== moment(forecast.startTime).format("YYYY-MM-DD")) {
|
||||
|
||||
// calculate minimum/maximum temperature, specify rain amount
|
||||
weather.minTemperature = Math.min.apply(null, minTemp);
|
||||
weather.maxTemperature = Math.max.apply(null, maxTemp);
|
||||
@@ -241,22 +237,22 @@ WeatherProvider.register("weathergov", {
|
||||
*/
|
||||
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
|
||||
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;
|
||||
|
@@ -3,8 +3,6 @@
|
||||
font-size: 75%;
|
||||
line-height: 65px;
|
||||
display: inline-block;
|
||||
-ms-transform: translate(0, -3px); /* IE 9 */
|
||||
-webkit-transform: translate(0, -3px); /* Safari */
|
||||
transform: translate(0, -3px);
|
||||
}
|
||||
|
||||
|
@@ -1,16 +1,14 @@
|
||||
/* global Module, WeatherProvider */
|
||||
/* global WeatherProvider */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("weather",{
|
||||
Module.register("weather", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
updateInterval: 10 * 60 * 1000,
|
||||
weatherProvider: "openweathermap",
|
||||
roundTemp: false,
|
||||
type: "current", //current, forecast
|
||||
@@ -33,6 +31,7 @@ Module.register("weather",{
|
||||
useBeaufort: true,
|
||||
lang: config.language,
|
||||
showHumidity: false,
|
||||
showSun: true,
|
||||
degreeLabel: false,
|
||||
decimalSymbol: ".",
|
||||
showIndoorTemperature: false,
|
||||
@@ -45,7 +44,7 @@ Module.register("weather",{
|
||||
retryDelay: 2500,
|
||||
|
||||
apiVersion: "2.5",
|
||||
apiBase: "http://api.openweathermap.org/data/",
|
||||
apiBase: "https://api.openweathermap.org/data/",
|
||||
weatherEndpoint: "/weather",
|
||||
|
||||
appendLocationNameToHeader: true,
|
||||
@@ -62,23 +61,17 @@ Module.register("weather",{
|
||||
weatherProvider: null,
|
||||
|
||||
// Define required scripts.
|
||||
getStyles: function() {
|
||||
getStyles: function () {
|
||||
return ["font-awesome.css", "weather-icons.css", "weather.css"];
|
||||
},
|
||||
|
||||
// Return the scripts that are necessary for the weather module.
|
||||
getScripts: function () {
|
||||
return [
|
||||
"moment.js",
|
||||
"weatherprovider.js",
|
||||
"weatherobject.js",
|
||||
"suncalc.js",
|
||||
this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")
|
||||
];
|
||||
return ["moment.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")];
|
||||
},
|
||||
|
||||
// Override getHeader method.
|
||||
getHeader: function() {
|
||||
getHeader: function () {
|
||||
if (this.config.appendLocationNameToHeader && this.data.header !== undefined && this.weatherProvider) {
|
||||
return this.data.header + " " + this.weatherProvider.fetchedLocation();
|
||||
}
|
||||
@@ -104,7 +97,7 @@ Module.register("weather",{
|
||||
},
|
||||
|
||||
// Override notification handler.
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
if (notification === "CALENDAR_EVENTS") {
|
||||
var senderClasses = sender.data.classes.toLowerCase().split(" ");
|
||||
if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
|
||||
@@ -147,13 +140,13 @@ Module.register("weather",{
|
||||
},
|
||||
|
||||
// What to do when the weather provider has new information available?
|
||||
updateAvailable: function() {
|
||||
updateAvailable: function () {
|
||||
Log.log("New weather information available.");
|
||||
this.updateDom(0);
|
||||
this.scheduleUpdate();
|
||||
},
|
||||
|
||||
scheduleUpdate: function(delay = null) {
|
||||
scheduleUpdate: function (delay = null) {
|
||||
var nextLoad = this.config.updateInterval;
|
||||
if (delay !== null && delay >= 0) {
|
||||
nextLoad = delay;
|
||||
@@ -168,88 +161,106 @@ Module.register("weather",{
|
||||
}, nextLoad);
|
||||
},
|
||||
|
||||
roundValue: function(temperature) {
|
||||
roundValue: function (temperature) {
|
||||
var decimals = this.config.roundTemp ? 0 : 1;
|
||||
return parseFloat(temperature).toFixed(decimals);
|
||||
},
|
||||
|
||||
addFilters() {
|
||||
this.nunjucksEnvironment().addFilter("formatTime", function(date) {
|
||||
date = moment(date);
|
||||
this.nunjucksEnvironment().addFilter(
|
||||
"formatTime",
|
||||
function (date) {
|
||||
date = moment(date);
|
||||
|
||||
if (this.config.timeFormat !== 24) {
|
||||
if (this.config.showPeriod) {
|
||||
if (this.config.showPeriodUpper) {
|
||||
return date.format("h:mm A");
|
||||
if (this.config.timeFormat !== 24) {
|
||||
if (this.config.showPeriod) {
|
||||
if (this.config.showPeriodUpper) {
|
||||
return date.format("h:mm A");
|
||||
} else {
|
||||
return date.format("h:mm a");
|
||||
}
|
||||
} else {
|
||||
return date.format("h:mm a");
|
||||
}
|
||||
} else {
|
||||
return date.format("h:mm");
|
||||
}
|
||||
}
|
||||
|
||||
return date.format("HH:mm");
|
||||
}.bind(this));
|
||||
|
||||
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
|
||||
if (type === "temperature") {
|
||||
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
|
||||
value += "°";
|
||||
}
|
||||
if (this.config.degreeLabel) {
|
||||
if (this.config.tempUnits === "metric") {
|
||||
value += "C";
|
||||
} else if (this.config.tempUnits === "imperial") {
|
||||
value += "F";
|
||||
} else {
|
||||
value += "K";
|
||||
return date.format("h:mm");
|
||||
}
|
||||
}
|
||||
} else if (type === "precip") {
|
||||
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
|
||||
value = "";
|
||||
} else {
|
||||
if (this.config.weatherProvider === "ukmetoffice") {
|
||||
value += "%";
|
||||
} else {
|
||||
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
|
||||
}
|
||||
|
||||
return date.format("HH:mm");
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.nunjucksEnvironment().addFilter(
|
||||
"unit",
|
||||
function (value, type) {
|
||||
if (type === "temperature") {
|
||||
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
|
||||
value += "°";
|
||||
}
|
||||
if (this.config.degreeLabel) {
|
||||
if (this.config.tempUnits === "metric") {
|
||||
value += "C";
|
||||
} else if (this.config.tempUnits === "imperial") {
|
||||
value += "F";
|
||||
} else {
|
||||
value += "K";
|
||||
}
|
||||
}
|
||||
} else if (type === "precip") {
|
||||
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
|
||||
value = "";
|
||||
} else {
|
||||
if (this.config.weatherProvider === "ukmetoffice" || this.config.weatherProvider === "ukmetofficedatahub") {
|
||||
value += "%";
|
||||
} else {
|
||||
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
|
||||
}
|
||||
}
|
||||
} else if (type === "humidity") {
|
||||
value += "%";
|
||||
}
|
||||
} else if (type === "humidity") {
|
||||
value += "%";
|
||||
}
|
||||
|
||||
return value;
|
||||
}.bind(this));
|
||||
return value;
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.nunjucksEnvironment().addFilter("roundValue", function(value) {
|
||||
return this.roundValue(value);
|
||||
}.bind(this));
|
||||
this.nunjucksEnvironment().addFilter(
|
||||
"roundValue",
|
||||
function (value) {
|
||||
return this.roundValue(value);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.nunjucksEnvironment().addFilter("decimalSymbol", function(value) {
|
||||
return value.toString().replace(/\./g, this.config.decimalSymbol);
|
||||
}.bind(this));
|
||||
this.nunjucksEnvironment().addFilter(
|
||||
"decimalSymbol",
|
||||
function (value) {
|
||||
return value.toString().replace(/\./g, this.config.decimalSymbol);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.nunjucksEnvironment().addFilter("calcNumSteps", function(forecast) {
|
||||
return Math.min(forecast.length, this.config.maxNumberOfDays);
|
||||
}.bind(this));
|
||||
this.nunjucksEnvironment().addFilter(
|
||||
"calcNumSteps",
|
||||
function (forecast) {
|
||||
return Math.min(forecast.length, this.config.maxNumberOfDays);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.nunjucksEnvironment().addFilter("opacity", function(currentStep, numSteps) {
|
||||
if (this.config.fade && this.config.fadePoint < 1) {
|
||||
if (this.config.fadePoint < 0) {
|
||||
this.config.fadePoint = 0;
|
||||
}
|
||||
var startingPoint = numSteps * this.config.fadePoint;
|
||||
var numFadesteps = numSteps - startingPoint;
|
||||
if (currentStep >= startingPoint) {
|
||||
return 1 - (currentStep - startingPoint) / numFadesteps;
|
||||
this.nunjucksEnvironment().addFilter(
|
||||
"opacity",
|
||||
function (currentStep, numSteps) {
|
||||
if (this.config.fade && this.config.fadePoint < 1) {
|
||||
if (this.config.fadePoint < 0) {
|
||||
this.config.fadePoint = 0;
|
||||
}
|
||||
var startingPoint = numSteps * this.config.fadePoint;
|
||||
var numFadesteps = numSteps - startingPoint;
|
||||
if (currentStep >= startingPoint) {
|
||||
return 1 - (currentStep - startingPoint) / numFadesteps;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@@ -1,20 +1,16 @@
|
||||
/* global Class */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is the blueprint for a day which includes weather information.
|
||||
*
|
||||
* Currently this is focused on the information which is necessary for the current weather.
|
||||
* As soon as we start implementing the forecast, mode properties will be added.
|
||||
*/
|
||||
|
||||
// Currently this is focused on the information which is necessary for the current weather.
|
||||
// As soon as we start implementing the forecast, mode properties will be added.
|
||||
|
||||
class WeatherObject {
|
||||
constructor(units, tempUnits, windUnits) {
|
||||
|
||||
this.units = units;
|
||||
this.tempUnits = tempUnits;
|
||||
this.windUnits = windUnits;
|
||||
@@ -32,11 +28,10 @@ class WeatherObject {
|
||||
this.snow = null;
|
||||
this.precipitation = null;
|
||||
this.feelsLikeTemp = null;
|
||||
|
||||
}
|
||||
|
||||
cardinalWindDirection() {
|
||||
if (this.windDirection > 11.25 && this.windDirection <= 33.75){
|
||||
if (this.windDirection > 11.25 && this.windDirection <= 33.75) {
|
||||
return "NNE";
|
||||
} else if (this.windDirection > 33.75 && this.windDirection <= 56.25) {
|
||||
return "NE";
|
||||
@@ -72,7 +67,7 @@ class WeatherObject {
|
||||
}
|
||||
|
||||
beaufortWindSpeed() {
|
||||
const windInKmh = (this.windUnits === "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) {
|
||||
@@ -87,24 +82,28 @@ class WeatherObject {
|
||||
}
|
||||
|
||||
feelsLike() {
|
||||
if (this.feelsLikeTemp) {
|
||||
return this.feelsLikeTemp;
|
||||
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;
|
||||
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) {
|
||||
feelsLike = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
|
||||
} else if (tempInF > 80 && this.humidity > 40) {
|
||||
feelsLike = -42.379 + 2.04901523 * tempInF + 10.14333127 * this.humidity
|
||||
- 0.22475541 * tempInF * this.humidity - 6.83783 * Math.pow(10, -3) * tempInF * tempInF
|
||||
- 5.481717 * Math.pow(10, -2) * this.humidity * this.humidity
|
||||
+ 1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity
|
||||
+ 8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity
|
||||
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
|
||||
feelsLike =
|
||||
-42.379 +
|
||||
2.04901523 * tempInF +
|
||||
10.14333127 * this.humidity -
|
||||
0.22475541 * tempInF * this.humidity -
|
||||
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
|
||||
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
|
||||
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
|
||||
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
|
||||
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
|
||||
}
|
||||
|
||||
return this.tempUnits === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
|
||||
return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9;
|
||||
}
|
||||
}
|
||||
|
@@ -3,15 +3,11 @@
|
||||
/* Magic Mirror
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is the blueprint for a weather provider.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base BluePrint for the WeatherProvider
|
||||
*/
|
||||
var WeatherProvider = Class.extend({
|
||||
// Weather Provider Properties
|
||||
providerName: null,
|
||||
@@ -32,77 +28,77 @@ var WeatherProvider = Class.extend({
|
||||
// All the following methods can be overwritten, although most are good as they are.
|
||||
|
||||
// Called when a weather provider is initialized.
|
||||
init: function(config) {
|
||||
init: function (config) {
|
||||
this.config = config;
|
||||
Log.info(`Weather provider: ${this.providerName} initialized.`);
|
||||
},
|
||||
|
||||
// Called to set the config, this config is the same as the weather module's config.
|
||||
setConfig: function(config) {
|
||||
setConfig: function (config) {
|
||||
this.config = config;
|
||||
Log.info(`Weather provider: ${this.providerName} config set.`, this.config);
|
||||
},
|
||||
|
||||
// Called when the weather provider is about to start.
|
||||
start: function() {
|
||||
start: function () {
|
||||
Log.info(`Weather provider: ${this.providerName} started.`);
|
||||
},
|
||||
|
||||
// This method should start the API request to fetch the current weather.
|
||||
// This method should definitely be overwritten in the provider.
|
||||
fetchCurrentWeather: function() {
|
||||
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 definitely be overwritten in the provider.
|
||||
fetchWeatherForecast: function() {
|
||||
fetchWeatherForecast: function () {
|
||||
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
|
||||
},
|
||||
|
||||
// This returns a WeatherDay object for the current weather.
|
||||
currentWeather: function() {
|
||||
currentWeather: function () {
|
||||
return this.currentWeatherObject;
|
||||
},
|
||||
|
||||
// This returns an array of WeatherDay objects for the weather forecast.
|
||||
weatherForecast: function() {
|
||||
weatherForecast: function () {
|
||||
return this.weatherForecastArray;
|
||||
},
|
||||
|
||||
// This returns the name of the fetched location or an empty string.
|
||||
fetchedLocation: function() {
|
||||
fetchedLocation: function () {
|
||||
return this.fetchedLocationName || "";
|
||||
},
|
||||
|
||||
// Set the currentWeather and notify the delegate that new information is available.
|
||||
setCurrentWeather: function(currentWeatherObject) {
|
||||
setCurrentWeather: function (currentWeatherObject) {
|
||||
// We should check here if we are passing a WeatherDay
|
||||
this.currentWeatherObject = currentWeatherObject;
|
||||
},
|
||||
|
||||
// Set the weatherForecastArray and notify the delegate that new information is available.
|
||||
setWeatherForecast: function(weatherForecastArray) {
|
||||
setWeatherForecast: function (weatherForecastArray) {
|
||||
// We should check here if we are passing a WeatherDay
|
||||
this.weatherForecastArray = weatherForecastArray;
|
||||
},
|
||||
|
||||
// Set the fetched location name.
|
||||
setFetchedLocation: function(name) {
|
||||
setFetchedLocation: function (name) {
|
||||
this.fetchedLocationName = name;
|
||||
},
|
||||
|
||||
// Notify the delegate that new weather is available.
|
||||
updateAvailable: function() {
|
||||
updateAvailable: function () {
|
||||
this.delegate.updateAvailable(this);
|
||||
},
|
||||
|
||||
// A convenience function to make requests. It returns a promise.
|
||||
fetchData: function(url, method = "GET", data = null) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fetchData: function (url, method = "GET", data = null) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open(method, url, true);
|
||||
request.onreadystatechange = function() {
|
||||
request.onreadystatechange = function () {
|
||||
if (this.readyState === 4) {
|
||||
if (this.status === 200) {
|
||||
resolve(JSON.parse(this.response));
|
||||
@@ -124,14 +120,14 @@ WeatherProvider.providers = [];
|
||||
/**
|
||||
* Static method to register a new weather provider.
|
||||
*/
|
||||
WeatherProvider.register = function(providerIdentifier, providerDetails) {
|
||||
WeatherProvider.register = function (providerIdentifier, providerDetails) {
|
||||
WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails);
|
||||
};
|
||||
|
||||
/**
|
||||
* Static method to initialize a new weather provider.
|
||||
*/
|
||||
WeatherProvider.initialize = function(providerIdentifier, delegate) {
|
||||
WeatherProvider.initialize = function (providerIdentifier, delegate) {
|
||||
providerIdentifier = providerIdentifier.toLowerCase();
|
||||
|
||||
var provider = new WeatherProvider.providers[providerIdentifier]();
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# Module: Weather Forecast
|
||||
|
||||
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.
|
||||
|
||||
|
@@ -19,9 +19,9 @@
|
||||
}
|
||||
|
||||
.weatherforecast tr.colored .min-temp {
|
||||
color: #BCDDFF;
|
||||
color: #bcddff;
|
||||
}
|
||||
|
||||
.weatherforecast tr.colored .max-temp {
|
||||
color: #FF8E99;
|
||||
color: #ff8e99;
|
||||
}
|
||||
|
@@ -1,14 +1,10 @@
|
||||
/* global Module */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: WeatherForecast
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("weatherforecast",{
|
||||
|
||||
Module.register("weatherforecast", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
location: false,
|
||||
@@ -59,27 +55,27 @@ Module.register("weatherforecast",{
|
||||
"11n": "wi-night-thunderstorm",
|
||||
"13n": "wi-night-snow",
|
||||
"50n": "wi-night-alt-cloudy-windy"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// create a variable for the first upcoming calendaar event. Used if no location is specified.
|
||||
// create a variable for the first upcoming calendar event. Used if no location is specified.
|
||||
firstEvent: false,
|
||||
|
||||
// create a variable to hold the location name based on the API result.
|
||||
fetchedLocationName: "",
|
||||
|
||||
// Define required scripts.
|
||||
getScripts: function() {
|
||||
getScripts: function () {
|
||||
return ["moment.js"];
|
||||
},
|
||||
|
||||
// Define required scripts.
|
||||
getStyles: function() {
|
||||
getStyles: function () {
|
||||
return ["weather-icons.css", "weatherforecast.css"];
|
||||
},
|
||||
|
||||
// Define required translations.
|
||||
getTranslations: function() {
|
||||
getTranslations: function () {
|
||||
// The translations for the default modules are defined in the core translation files.
|
||||
// Therefor we can just return false. Otherwise we should have returned a dictionary.
|
||||
// If you're trying to build your own module including translations, check out the documentation.
|
||||
@@ -87,7 +83,7 @@ Module.register("weatherforecast",{
|
||||
},
|
||||
|
||||
// Define start sequence.
|
||||
start: function() {
|
||||
start: function () {
|
||||
Log.info("Starting module: " + this.name);
|
||||
|
||||
// Set locale.
|
||||
@@ -98,11 +94,10 @@ Module.register("weatherforecast",{
|
||||
this.scheduleUpdate(this.config.initialLoadDelay);
|
||||
|
||||
this.updateTimer = null;
|
||||
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function() {
|
||||
getDom: function () {
|
||||
var wrapper = document.createElement("div");
|
||||
|
||||
if (this.config.appid === "") {
|
||||
@@ -146,17 +141,17 @@ Module.register("weatherforecast",{
|
||||
if (this.config.units === "metric" || this.config.units === "imperial") {
|
||||
degreeLabel += "°";
|
||||
}
|
||||
if(this.config.scale) {
|
||||
switch(this.config.units) {
|
||||
case "metric":
|
||||
degreeLabel += "C";
|
||||
break;
|
||||
case "imperial":
|
||||
degreeLabel += "F";
|
||||
break;
|
||||
case "default":
|
||||
degreeLabel = "K";
|
||||
break;
|
||||
if (this.config.scale) {
|
||||
switch (this.config.units) {
|
||||
case "metric":
|
||||
degreeLabel += "C";
|
||||
break;
|
||||
case "imperial":
|
||||
degreeLabel += "F";
|
||||
break;
|
||||
case "default":
|
||||
degreeLabel = "K";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +174,7 @@ Module.register("weatherforecast",{
|
||||
if (isNaN(forecast.rain)) {
|
||||
rainCell.innerHTML = "";
|
||||
} else {
|
||||
if(config.units !== "imperial") {
|
||||
if (config.units !== "imperial") {
|
||||
rainCell.innerHTML = parseFloat(forecast.rain).toFixed(1).replace(".", this.config.decimalSymbol) + " mm";
|
||||
} else {
|
||||
rainCell.innerHTML = (parseFloat(forecast.rain) / 25.4).toFixed(2).replace(".", this.config.decimalSymbol) + " in";
|
||||
@@ -197,7 +192,7 @@ Module.register("weatherforecast",{
|
||||
var steps = this.forecast.length - startingPoint;
|
||||
if (f >= startingPoint) {
|
||||
var currentStep = f - startingPoint;
|
||||
row.style.opacity = 1 - (1 / steps * currentStep);
|
||||
row.style.opacity = 1 - (1 / steps) * currentStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +201,7 @@ Module.register("weatherforecast",{
|
||||
},
|
||||
|
||||
// Override getHeader method.
|
||||
getHeader: function() {
|
||||
getHeader: function () {
|
||||
if (this.config.appendLocationNameToHeader) {
|
||||
return this.data.header + " " + this.fetchedLocationName;
|
||||
}
|
||||
@@ -215,10 +210,10 @@ Module.register("weatherforecast",{
|
||||
},
|
||||
|
||||
// Override notification handler.
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
if (notification === "DOM_OBJECTS_CREATED") {
|
||||
if (this.config.appendLocationNameToHeader) {
|
||||
this.hide(0, {lockString: this.identifier});
|
||||
this.hide(0, { lockString: this.identifier });
|
||||
}
|
||||
}
|
||||
if (notification === "CALENDAR_EVENTS") {
|
||||
@@ -242,7 +237,7 @@ Module.register("weatherforecast",{
|
||||
* Requests new data from openweather.org.
|
||||
* Calls processWeather on successful response.
|
||||
*/
|
||||
updateWeather: function() {
|
||||
updateWeather: function () {
|
||||
if (this.config.appid === "") {
|
||||
Log.error("WeatherForecast: APPID not set!");
|
||||
return;
|
||||
@@ -254,7 +249,7 @@ Module.register("weatherforecast",{
|
||||
|
||||
var weatherRequest = new XMLHttpRequest();
|
||||
weatherRequest.open("GET", url, true);
|
||||
weatherRequest.onreadystatechange = function() {
|
||||
weatherRequest.onreadystatechange = function () {
|
||||
if (this.readyState === 4) {
|
||||
if (this.status === 200) {
|
||||
self.processWeather(JSON.parse(this.response));
|
||||
@@ -272,7 +267,7 @@ Module.register("weatherforecast",{
|
||||
}
|
||||
|
||||
if (retry) {
|
||||
self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
|
||||
self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -284,21 +279,23 @@ Module.register("weatherforecast",{
|
||||
*
|
||||
* return String - URL params.
|
||||
*/
|
||||
getParams: function() {
|
||||
getParams: function () {
|
||||
var params = "?";
|
||||
if(this.config.locationID) {
|
||||
if (this.config.locationID) {
|
||||
params += "id=" + this.config.locationID;
|
||||
} else if(this.config.location) {
|
||||
} else if (this.config.location) {
|
||||
params += "q=" + this.config.location;
|
||||
} else if (this.firstEvent && this.firstEvent.geo) {
|
||||
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
|
||||
} else if (this.firstEvent && this.firstEvent.location) {
|
||||
params += "q=" + this.firstEvent.location;
|
||||
} else {
|
||||
this.hide(this.config.animationSpeed, {lockString:this.identifier});
|
||||
this.hide(this.config.animationSpeed, { lockString: this.identifier });
|
||||
return;
|
||||
}
|
||||
|
||||
params += "&cnt=" + (this.config.maxNumberOfDays < 1 || this.config.maxNumberOfDays > 17 ? 7 : this.config.maxNumberOfDays);
|
||||
|
||||
params += "&units=" + this.config.units;
|
||||
params += "&lang=" + this.config.lang;
|
||||
params += "&APPID=" + this.config.appid;
|
||||
@@ -310,12 +307,12 @@ Module.register("weatherforecast",{
|
||||
* parserDataWeather(data)
|
||||
*
|
||||
* Use the parse to keep the same struct between daily and forecast Endpoint
|
||||
* from Openweather
|
||||
* from openweather.org
|
||||
*
|
||||
*/
|
||||
parserDataWeather: function(data) {
|
||||
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;
|
||||
},
|
||||
@@ -325,7 +322,7 @@ Module.register("weatherforecast",{
|
||||
*
|
||||
* argument data object - Weather information received form openweather.org.
|
||||
*/
|
||||
processWeather: function(data) {
|
||||
processWeather: function (data) {
|
||||
this.fetchedLocationName = data.city.name + ", " + data.city.country;
|
||||
|
||||
this.forecast = [];
|
||||
@@ -333,13 +330,12 @@ Module.register("weatherforecast",{
|
||||
var forecastData = {};
|
||||
|
||||
for (var i = 0, count = data.list.length; i < count; i++) {
|
||||
|
||||
var forecast = data.list[i];
|
||||
this.parserDataWeather(forecast); // hack issue #1017
|
||||
|
||||
var day;
|
||||
var hour;
|
||||
if(!!forecast.dt_txt) {
|
||||
if (forecast.dt_txt) {
|
||||
day = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd");
|
||||
hour = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("H");
|
||||
} else {
|
||||
@@ -348,7 +344,7 @@ Module.register("weatherforecast",{
|
||||
}
|
||||
|
||||
if (day !== lastDay) {
|
||||
var forecastData = {
|
||||
forecastData = {
|
||||
day: day,
|
||||
icon: this.config.iconTable[forecast.weather[0].icon],
|
||||
maxTemp: this.roundValue(forecast.temp.max),
|
||||
@@ -378,7 +374,7 @@ Module.register("weatherforecast",{
|
||||
}
|
||||
|
||||
//Log.log(this.forecast);
|
||||
this.show(this.config.animationSpeed, {lockString:this.identifier});
|
||||
this.show(this.config.animationSpeed, { lockString: this.identifier });
|
||||
this.loaded = true;
|
||||
this.updateDom(this.config.animationSpeed);
|
||||
},
|
||||
@@ -388,7 +384,7 @@ Module.register("weatherforecast",{
|
||||
*
|
||||
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
|
||||
*/
|
||||
scheduleUpdate: function(delay) {
|
||||
scheduleUpdate: function (delay) {
|
||||
var nextLoad = this.config.updateInterval;
|
||||
if (typeof delay !== "undefined" && delay >= 0) {
|
||||
nextLoad = delay;
|
||||
@@ -396,7 +392,7 @@ Module.register("weatherforecast",{
|
||||
|
||||
var self = this;
|
||||
clearTimeout(this.updateTimer);
|
||||
this.updateTimer = setTimeout(function() {
|
||||
this.updateTimer = setTimeout(function () {
|
||||
self.updateWeather();
|
||||
}, nextLoad);
|
||||
},
|
||||
@@ -405,15 +401,15 @@ Module.register("weatherforecast",{
|
||||
* Converts m2 to beaufort (windspeed).
|
||||
*
|
||||
* see:
|
||||
* http://www.spc.noaa.gov/faq/tornado/beaufort.html
|
||||
* https://www.spc.noaa.gov/faq/tornado/beaufort.html
|
||||
* https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
|
||||
*
|
||||
* argument ms number - Windspeed in m/s.
|
||||
*
|
||||
* return number - Windspeed in beaufort.
|
||||
*/
|
||||
ms2Beaufort: function(ms) {
|
||||
var kmh = ms * 60 * 60 / 1000;
|
||||
ms2Beaufort: function (ms) {
|
||||
var kmh = (ms * 60 * 60) / 1000;
|
||||
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
|
||||
for (var beaufort in speeds) {
|
||||
var speed = speeds[beaufort];
|
||||
@@ -431,7 +427,7 @@ Module.register("weatherforecast",{
|
||||
*
|
||||
* return string - Rounded Temperature.
|
||||
*/
|
||||
roundValue: function(temperature) {
|
||||
roundValue: function (temperature) {
|
||||
var decimals = this.config.roundTemp ? 0 : 1;
|
||||
return parseFloat(temperature).toFixed(decimals);
|
||||
},
|
||||
@@ -443,29 +439,31 @@ Module.register("weatherforecast",{
|
||||
* 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) {
|
||||
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");
|
||||
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) {
|
||||
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);
|
||||
return daysForecasts
|
||||
.map(function (item) {
|
||||
return Object.values(item.rain)[0];
|
||||
})
|
||||
.reduce(function (a, b) {
|
||||
return a + b;
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|