// ==UserScript==
// @name Tumblr Dashboard Tracker
// @namespace the.vindicar.scripts
// @description Checks what's going on on your dashboard, loads new posts and plays a sound if something interesting happens.
// @version 1.1.1
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @require https://greasyfork.org/scripts/1884-gm-config/code/GM_config.js?version=4836
// @include http://www.tumblr.com/dashboard
// @include https://www.tumblr.com/dashboard
// ==/UserScript==
(function(){
'use strict';
// ====================================== Configuration ======================================
//If we have no access to Greasemonkey methods, we will need dummy replacements
if (typeof GM_getValue !== 'function') GM_getValue = function (target, deflt) { return deflt; };
//configuration
var cfg = {
track_inbox : !!GM_getValue("track_inbox", true),
track_answers : !!GM_getValue("track_answers", true),
track_replies : !!GM_getValue("track_replies", true),
track_mentions : !!GM_getValue("track_mentions", true),
track_messages : !!GM_getValue("track_messages", true),
track_reblogs_followed : !!GM_getValue("track_reblogs_followed", true),
track_reblogs_other : !!GM_getValue("track_reblogs_other", false),
track_everything : !!GM_getValue("track_everything", false),
notify_mode_default : GM_getValue("notify_mode_default", "last"),
notify_mode : GM_getValue("notify_mode", "auto"),
notify_sound : !!GM_getValue("notify_sound", false),
notify_soundurl : GM_getValue("notify_soundurl", ""),
update_autoload : !!GM_getValue("update_autoload", true),
update_stopafter : parseInt(GM_getValue("update_stopafter", "100"),10),
update_unread_style : GM_getValue("update_unread_style", "border: 3px solid #529ECC;"),
update_unmark_delay : 100,
};
//we set up the configuration panel if possible
if ( (typeof GM_config !== 'undefined') && (typeof GM_setValue === 'function') && (typeof GM_registerMenuCommand === 'function') ) {
//this function is called when user presses "play" button in the configuration panel
var playclick = function () {
var doc = this.ownerDocument.documentElement; //since we're inside iframe, we have to access it's document element this way
try {
var sound = new Audio(doc.querySelector("#field_notify_soundurl").value);
sound.play();
} catch (e) {
alert("Couldn't play the sound.");
}
};
//configuration input fields
var fields = {
"track_inbox" : {
"label" : "Inbox messages",
"title" : "Receive notification if there are new unread messages in your inbox.",
"section" : ["Tracking"],
"type" : "checkbox",
"default" : cfg.track_inbox,
},
"track_answers" : {
"label" : "Answers to your asks",
"title" : "Receive notification every time someone answers an ask you sent.",
"type" : "checkbox",
"default" : cfg.track_answers,
},
"track_replies" : {
"label" : "Replies to your posts",
"title" : "Receive notification every time someone replies to your post.",
"type" : "checkbox",
"default" : cfg.track_replies,
},
"track_mentions" : {
"label" : "Mentions",
"title" : "Receive notification every time someone mentions you in their post.",
"type" : "checkbox",
"default" : cfg.track_mentions,
},
"track_messages" : {
"label" : "Messages",
"title" : "Receive notification every time someone messages you via IM system.",
"type" : "checkbox",
"default" : cfg.track_messages,
},
"track_reblogs_followed" : {
"label" : "Reblogs by blogs you follow",
"title" : "Receive notification every time a blog you follow reblogs your post.",
"type" : "checkbox",
"default" : cfg.track_reblogs_followed,
},
"track_reblogs_other" : {
"label" : "Reblogs by other blogs",
"title" : "Receive notification every time some other blog reblogs your post.",
"type" : "checkbox",
"default" : cfg.track_reblogs_any,
},
"track_everything" : {
"label" : "Any posts",
"title" : "Receive notification every time there are new posts on your dashboard. Are you sure?",
"type" : "checkbox",
"default" : cfg.track_everything,
},
"notify_mode_default" : {
"label" : "By default notifications are",
"title" : "Default state of notifications when reloading the page.\nYou can change the current state with control next to the Tumblr logo.",
"section" : ["Notifications"],
"type" : "select",
"options" : {
"last" : "in the same state as before",
"on" : "always enabled",
"auto" : "enabled when not viewed",
"off" : "always disabled",
},
"default" : cfg.notify_mode_default,
},
"notify_sound" : {
"label" : "Play sound",
"title" : "Play a sound every time you receive a notification.",
"type" : "checkbox",
"default" : cfg.notify_sound,
},
"notify_soundurl" : {
"label" : "Sound file URI",
"title" : "This sound will play every time you receive a notification.\nHint: you can save a sound file right here if you convert it into a 'data:' URI.",
"type" : "text",
"default" : cfg.notify_soundurl,
},
"notify_sound_play" : {
"label" : "Play",
"title" : "Press to play the sound above.",
"type" : "button",
"script" : "("+playclick.toString()+").call(this)",
},
"update_autoload" : {
"label" : "Load unread posts",
"title" : "Unread posts will be loaded and added onto the top of the dashboard.",
"section" : ["Autoupdate"],
"type" : "checkbox",
"default" : cfg.update_autoload,
},
"update_stopafter" : {
"label" : "Post autoloading limit",
"title" : "How many posts should be loaded before disabling autoloading feature.",
"type" : "text",
"size" : "5",
"default" : cfg.update_stopafter,
},
"update_unread_style" : {
"label" : "Unread posts style",
"title" : "These CSS definitions will be applied to any unread posts that have been loaded. Example:\nborder: solid 1px #99F;",
"type" : "text",
"default" : cfg.update_unread_style,
},
save: function() {
var donotsave = ["notify_sound_play"];
for (var key in GM_config.values) {
if (donotsave.indexOf(key) == -1)
GM_setValue(key,GM_config.values[key]);
}
},
};
//styles for configuration form controls
var configFormCSS = [
'.reset_holder { display: none !important; }',
'body {background-color: #FFF;}',
'* {font-family: "Helvetica Neue","HelveticaNeue",Helvetica,Arial,sans-serif; color: #444;}',
'#header {border-bottom: 2px solid #E5E5E5; font-size: 24px; font-weight: normal; line-height: 1; margin: 0px; padding-bottom: 28px;}',
'.section_header {border: 0px none; margin: 16px 0px 16px 0px; padding: 0px; font-size: 20px; font-weight: normal; line-height: 1; background-color: transparent; color: inherit; text-decoration: none;}',
'.config_var {padding: 2px 0px 2px 200px;}',
'.config_var>* {vertical-align:middle;}',
'.config_var .field_label {font-size: 14px !important;line-height: 1.2; display:inline-block; width:200px; margin: 0 0 0 -200px;}',
'#field_notify_soundurl,#field_update_unread_style {width: 100%}',
'button {padding: 4px 7px 5px; font-weight: 700; border-width: 1px; border-style: solid; text-decoration: none; border-radius: 2px; cursor: pointer; display: inline-block; height: 30px; line-height: 20px;}',
'#saveBtn {color: #FFF; border-color: #529ECC; background: #529ECC none repeat scroll 0% 0%;}',
'#cancelBtn {color: #FFF; border-color: #9DA6AF; background: #9DA6AF none repeat scroll 0% 0%;}',
""].join("\n");
//style for the configuration form window. Since it's an iframe element inside the main window, we have to add this style to the main page
GM_addStyle('#GM_config {border-radius: 3px !important; border: 0px none !important;}');
//style for unread posts, if autoloading is enabled
if (cfg.update_autoload && (cfg.update_unread_style.indexOf("}") == -1))
GM_addStyle('.tracker-unread-post .post {'+cfg.update_unread_style+'}');
GM_config.init("Tumblr Dashboard Tracker Settings", fields, configFormCSS);
GM_registerMenuCommand("Tumblr Dashboard Tracker Settings", function() {GM_config.open();});
}
//global styles for notification mode switch
var globalCSS = [
'#tumblr-dashboard-tracker-mode {width: 12px; height: 24px; background: transparent; overflow: hidden; padding: 0; margin: 3px 16px 0px 16px; display:inline-block; border-radius: 6px;}',
'#tumblr-dashboard-tracker-mode > span { border : 0 none; border-radius: 6px; width: 12px; height: 12px; display:inline-block; position: relative; }',
'#tumblr-dashboard-tracker-mode[data-state="on"] {border: 1px solid #FFFFFF;}',
'#tumblr-dashboard-tracker-mode[data-state="auto"] {border: 1px solid #529ECC;}',
'#tumblr-dashboard-tracker-mode[data-state="off"] {border: 1px solid #A1A1A1;}',
'#tumblr-dashboard-tracker-mode[data-state="on"] > span {background-color: #FFFFFF; top: -4px;}',
'#tumblr-dashboard-tracker-mode[data-state="auto"] > span {background-color: #529ECC; top: 3px;}',
'#tumblr-dashboard-tracker-mode[data-state="off"] > span {background-color: #A1A1A1; top: 10px;}'
].join("\n");
GM_addStyle(globalCSS);
// ====================================== Dashboard Tracker ======================================
function Tracker(cfg)
{
this.config = cfg;
this.original = {};
this.loadcounter = 0;
this.knownitems = {};
this.unread_interval_id = null;
this.page_visible = true;
this.sound = null;
this.unreadcount = 0;
this.inboxcount = 0;
this.unreadmessagescount = 0;
}
//Tumblr.Thoth is a Tumblr module that updates unread posts & inbox messages counters.
//we replace some of the methods there with our own, letting Tumblr handle the rest.
//It's also easier to hook up to couple of Tumblr events than to track them ourselves.
Tracker.prototype.install = function () {
//we make sure event handlers are called in correct context (with correct 'this' value).
var that = this;
if (this.track_everything || this.config.track_reblogs_followed || this.config.update_autoload) {
//we have to parse incoming posts to do any of these!
this.original['parse_unread'] = unsafeWindow.Tumblr.Thoth.__proto__.parse_unread;
unsafeWindow.Tumblr.Thoth.__proto__.parse_unread = exportFunction(function(heartbeat){
return that.parse_unread.call(that, heartbeat);
//we completely replace the old function
//return that.original.parse_unread.call(this, heartbeat);
}, unsafeWindow);
//we calculate hashes for all items currently displayed on the Dashboard.
//it will allow us to find out if we have reached the items already displayed when dynamically updating the Dashboard.
//Note: hashes for the items on the next pages are not needed. Probably.
var dashboard = document.querySelectorAll('#posts>li');
for (var i=0; i<dashboard.length; i++)
this.knownitems[this.get_item_hash(dashboard[i])] = true;
}
if (this.config.track_reblogs_other || this.config.track_mentions || this.config.track_replies) {
//these can be taken from incoming notifications
this.original['parse_notifications'] = unsafeWindow.Tumblr.Thoth.__proto__.parse_notifications;
unsafeWindow.Tumblr.Thoth.__proto__.parse_notifications = exportFunction(function(heartbeat){
that.parse_notifications.call(that, heartbeat);
return that.original.parse_notifications.call(this, heartbeat);
}, unsafeWindow);
}
if (this.config.track_inbox) {
//Inbox is handled by this method
this.original['parse_inbox'] = unsafeWindow.Tumblr.Thoth.__proto__.parse_inbox;
unsafeWindow.Tumblr.Thoth.__proto__.parse_inbox = exportFunction(function(heartbeat){
that.parse_inbox.call(that, heartbeat);
return that.original.parse_inbox.call(this, heartbeat);
}, unsafeWindow);
}
if (this.config.track_messages) {
//Instant messaging is handled by this event.
unsafeWindow.Tumblr.Events.listenTo(unsafeWindow.Tumblr.Events, 'toaster:updateMessagingUnreadCounts',
exportFunction(function(heartbeat){
that.parse_messages.call(that, heartbeat);
}, unsafeWindow));
//unsafeWindow.Tumblr.Events.listenTo(unsafeWindow.Tumblr.Events, 'toaster:updateMessagingUnreadCounts', handler);
}
//if we will be autoloading posts, we will need an event handler to mark the posts as read
if (this.config.update_autoload) {
var handler = function(){that.unmark_unread_posts.call(that);};
//set event handler that will trigger when window is scrolled
window.addEventListener("scroll", function(){
if (that.unread_interval_id !== null)
clearTimeout(that.unread_interval_id);
that.unread_interval_id = setTimeout(handler,that.config.update_unmark_delay);
});
}
//we install an event handler that tracks page visibility state
setVisibilityHandler(function(visible){that.page_visible = visible;}, true);
//prepare the notification sound player
if (this.config.notify_sound)
try {
this.sound = new Audio(this.config.notify_soundurl);
} catch (e) {
this.config.notify_sound = false;
}
//select initial notification mode
switch (this.config.notify_mode_default) {
case "on" : this.config.notify_mode = "on"; break;
case "off" : this.config.notify_mode = "off"; break;
case "auto" : this.config.notify_mode = "auto"; break;
case "last" : ; break;
default : ; break;
}
//create and place notification mode switch
this.createModeSwitch(this.config.notify_mode);
};
//Creates and places notification mode switch
Tracker.prototype.createModeSwitch = function (state) {
if (typeof state == 'undefined') state = 'auto';
var outer = document.createElement('span');
outer.setAttribute('id','tumblr-dashboard-tracker-mode');
var inner = document.createElement('span');
outer.appendChild(inner);
var that = this;
outer.addEventListener('click', function() {return that.onModeChange.apply(that, arguments);}, true);
var target = document.querySelector('#user_tools');
target.appendChild(outer);
this.setMode(state, outer);
};
//Changes notification mode, both internally and in UI (yeah, yeah, I know it's bad practice).
Tracker.prototype.setMode = function (newstate, control) {
//we can accept the control that was given, or find it ourselves
if (!control) control = document.querySelector('#tumblr-dashboard-tracker-mode');
var titles = {
on : 'Notify always',
auto : 'Notify if page is not visible',
off : 'Disable notifications',
};
this.config.notify_mode = newstate;
control.setAttribute('data-state', newstate);
control.setAttribute('title', titles[newstate]);
//immediately remember the new mode
GM_setValue('notify_mode', newstate);
};
//This method is triggered when notification mode switch is clicked
Tracker.prototype.onModeChange = function (event) {
event = event || window.event;
var control = event.target; //we find the actual control containing the clicked element
while (control && (control.getAttribute('id') != 'tumblr-dashboard-tracker-mode'))
control = control.parentNode;
if (!control) return;
var newstate;
//logic that determines the new notification mode
switch (control.getAttribute('data-state')) {
case "on": newstate = 'off'; break;
case "off": newstate = 'auto'; break;
case "auto": newstate = 'on'; break;
default : newstate = 'auto'; break;
}
this.setMode(newstate, control);
event.stopPropagation();
return false;
};
//This method handles any dashboard 'event' we have detected
Tracker.prototype.event = function (event) {
//we do nothing if notifications are disabled, or auto-enabled but page is being viewed
if ((this.config.notify_mode == "off") || ((this.config.notify_mode == "auto") && this.page_visible))
return;
if (this.config.notify_sound && this.sound)
this.sound.play();
};
//Parse incoming notifications for the Tumblr.Toaster
Tracker.prototype.parse_notifications = function (heartbeat) {
if (typeof heartbeat.notifications !== 'object') return;
var notifs = heartbeat.notifications;
for (var i=0; i<notifs.length; i++)
switch (notifs[i].type) {
case "reblog":
if (this.config.track_reblogs_other)
this.event({type:'reblog'});
break;
case "user_mention":
if (this.config.track_mentions)
this.event({type:'mention'});
break;
case "answer":
case "ask_answer":
if (this.config.track_answers)
this.event({type:'answer'});
case "reply":
case "photo_reply":
if (this.config.track_replies)
this.event({type:'reply'});
break;
}
};
//Parse unread inbox messages.
Tracker.prototype.parse_inbox = function (heartbeat) {
if (typeof heartbeat.inbox !== "number") return;
//if number of unread messages in the inbox have increased since last time, we send another notification
if (heartbeat.inbox > this.inboxcount) {
this.event({type:'inbox'});
}
//we update the counters if necessary
if (heartbeat.inbox != this.inboxcount) {
this.inboxcount = heartbeat.inbox;
this.update_counters();
}
};
//Parse unread conversations.
Tracker.prototype.parse_messages = function (heartbeat) {
if (typeof heartbeat.unread_messages !== "object") return;
var val = document.querySelector('#messaging_button .tab_notice_value');
var unread = (val && val.innerHTML) ? parseInt(val.innerHTML, 10) : 0;
//if number of unread messages have increased since last time, we send another notification
if (unread > this.unreadmessagescount) {
this.event({type:'IM'});
}
//we update the counters if necessary
if (unread != this.unreadmessagescount) {
this.unreadmessagescount = unread;
this.update_counters();
}
};
//Parse unread posts. This function completely takes over original Tumblr.Thoth method.
Tracker.prototype.parse_unread = function (heartbeat) {
if ((typeof heartbeat.unread !== "number")&&(typeof heartbeat.abacus !== "number")) return;
if (this.unreadcount >= this.config.update_stopafter) {
//we tell Tumblr to stop polling if too many unread posts have accumulated
unsafeWindow.Tumblr.Thoth.options.check_posts = false;
}
var unread = unsafeWindow.Tumblr.Thoth.use_new_abacus ? heartbeat.abacus : heartbeat.unread;
if (unread > 0)
this.load_page(1, this.get_first_post_id());
return unread;
};
//this function loads specified page of dashboard using Tumblr service URL.
//Callback it uses can query next page if necessary
Tracker.prototype.load_page = function (page, id) {
var request = new XMLHttpRequest();
request.open('GET', 'https://www.tumblr.com/svc/dashboard/'+page+'/'+id);
var that = this;
request.onreadystatechange = function() {
if ((request.readyState != 4) || (request.status != 200)) return;
var data;
try { data = JSON.parse(request.responseText); } catch (e) { data = {}; }
if ((typeof data.meta !== 'object') || data.meta.status != 200) return;
var postdata = data.response.DashboardPosts.body;
if (postdata.indexOf("<!-- START POSTS -->") !== -1)
postdata = postdata.split("<!-- START POSTS -->")[1].split("<!-- END POSTS -->")[0];
that.parse_unread_success.call(that, postdata, page);
};
request.send();
};
//parses the posts we have loaded, checks them for events of interest and attaches them to the top of the Dashboard
Tracker.prototype.parse_unread_success = function (html, page) {
//posts container
var posts_container = document.querySelector('#posts');
//first seen element on the dashboard (aside from new post buttons)
var first_seen = posts.querySelector('li:not(#new_post_buttons)');
//remember position of the first seen element
var offset = first_seen.offsetTop;
//position at which we will be adding new items
var insertion_mark = first_seen.previousSibling;
//flag indicating if we had met an item we already saw - in case we have more than one page of new posts
var known_item_found = false;
//last added post id
var last_id;
//load items into a DOM container
var container = document.createElement('body');
container.innerHTML = html;
//amount of posts that were loaded but not displayed (we should count them as unread)
var not_diplayed_unread_count = 0;
//look for posts
var items = container.querySelectorAll('body>li');
//we process items in the order they appear - reverse chronological
for (var i=0; i<items.length; i++) {
//get item hash
var hash = this.get_item_hash(items[i]);
//if we ran into an item we saw before then there is no need to process the rest
if (this.knownitems[hash]) {
known_item_found = true;
break;
}
var post = items[i].querySelector('.post');
if (post) {
last_id = post.getAttribute('id').slice(5);
//if we have a reblogged post and actually want to see if it was reblogged from us by someone we follow
if (this.config.track_reblogs_followed && /\bis_reblog\b/.test(post.className)) {
//parse data attribute thoughtfully provided by Tumblr
var json_data = JSON.parse(post.getAttribute('data-json'));
//if original post was ours, and we follow the blog
if ((typeof json_data['tumblelog-parent-data'] === 'object') &&
(typeof json_data['tumblelog-data'] === 'object') &&
json_data['tumblelog-parent-data']['is_you'] &&
json_data['tumblelog-data']["following"]) {
//we send notification
this.event({type:'reblog'});
}
}
} else if (/\bnotification\b/.test(items[i].className)) { //it's a notification
if (this.config.track_answers && /\bnotification_ask_answer\b/.test(items[i].className))
this.event({type:'answer'});
else if (this.config.track_reblogs_other && /\bnotification_reblog\b/.test(items[i].className))
this.event({type:'reblog'});
else if (this.config.track_mentions && /\bnotification_user_mention\b/.test(items[i].className))
this.event({type:'mention'});
}
//if user wants it, we should now display the item at the Dashboard
if (this.config.update_autoload && (this.loadcounter < this.config.update_stopafter)) {
//add the node to the main DOM tree
insertion_mark = posts_container.insertBefore(items[i],insertion_mark.nextSibling);
//remember the node as seen
this.knownitems[hash] = true;
if (post) {
//mark post as unread
items[i].className += ' tracker-unread-post';
//added posts counter
this.loadcounter++;
}
}
else //otherwise we remember that it's an unread post
not_diplayed_unread_count++;
}
if (this.config.update_autoload) {
if (!known_item_found) {
//we didn't find a known item - we might have more than one page of new posts incoming
//if we saw no posts at all, we have to use the first post id we can get our hands on
if (!last_id) last_id = this.get_first_post_id();
//we query another page, that will, in turn, use this function as callback. Yay, recursion!
this.load_page(page+1, last_id);
}
//meanwhile we take care of the posts we know about
//we scroll the down in such a way that relative position of the first visible element won't change
window.scrollBy(0, first_seen.offsetTop - offset);
//we should tell Tumblr to update it's keyboard navigation, if possible
unsafeWindow.Tumblr.KeyCommands.update_post_positions();
//if we're autoloading posts, then the unread post count can be calculated as sum of numbers of loaded and not loaded unread posts
this.unreadcount = document.querySelectorAll("#posts>.tracker-unread-post").length + not_diplayed_unread_count;
}
else //otherwise, all unread posts are not loaded
this.unreadcount = not_diplayed_unread_count;
this.update_counters();
};
Tracker.prototype.get_first_post_id = function () {
var post = document.querySelector('#posts .post');
if (post !== null)
return post.getAttribute('data-id');
else
throw "get_first_post_id(): No posts found at all!";
};
//computes simple hash of a dashboard item.
//It's necessary for us to find which posts and notifications we have seen already, so we won't duplicate them.
Tracker.prototype.get_item_hash = function (li) {
if (/\bpost_container\b/.test(li.className)) {
return 'POST:'+li.getAttribute('data-pageable');
} else if (/\bnotification\b/.test(li.className)) {
return 'NOTIFICATION:'+li.querySelector('.notification_sentence').innerHTML;
} else
return 'UNKNOWN:'+li.innerHTML;
};
//This methods updates unread posts counter. Unlike it's Tumblr prototype, it can remove the counter if it's reduced to zero.
Tracker.prototype.update_counters = function () {
//any place that show unread post counter
var fields = document.querySelectorAll(".new_post_notice_container");
for (var i=0;i<fields.length;i++) {
//actual element to display the number in
fields[i].querySelector(".tab_notice_value").innerHTML = this.unreadcount.toString(10);
if (this.unreadcount>0) //if there are unread posts
fields[i].className += ' tab-notice--active';
else //if there are none
fields[i].className = fields[i].className.replace(/\s*\btab-notice--active\b/, '');
}
//remove post counter in the title (if any)
var title = document.title.replace(/^(\{[0-9+]+\})?\s*(\([0-9+]\))?\s*(\[[0-9+]+\])?/,'');
//if there are unread posts, we display it in the title too
if (this.unreadmessagescount>0)
title = '{'+this.unreadmessagescount.toString(10)+'}'+title;
if (this.inboxcount>0)
title = '['+this.inboxcount.toString(10)+']'+title;
if (this.unreadcount>0)
title = '('+this.unreadcount.toString(10)+')'+title;
document.title = title;
};
//This method gets called after user has been scrolling.
//It checks if any posts got scrolled by (their top is not hidden above the viewport) and removes unread mark from them.
Tracker.prototype.unmark_unread_posts = function () {
//find all unread posts
var unread = document.querySelectorAll("#posts>.tracker-unread-post");
//we check them in reverse order, from bottom to top.
//it's important, because we can stop the moment we find one that's still hidden
var changed = false;
for (var i=unread.length-1;i>=0;i--) {
var rect = unread[i].getBoundingClientRect();
if (rect.top<0) //if top of the post is above the window border
break; //we consider it invisible, as well as all posts above it
changed = true;
//mark it as read
unread[i].className = unread[i].className.replace(/\s*\btracker-unread-post\b/, '');
}
if (changed) {//if anything has changed, we correct the numbers
//the unread post count
this.unreadcount = document.querySelectorAll("#posts>.tracker-unread-post").length;
this.update_counters();
}
};
// ====================================== Helpers ======================================
//Calls func() every time page visibility changes, with one boolean parameter keeping current visibility state
function setVisibilityHandler(func, invoke_now) {
//Page Visibility API
var method;
var methods = { 'hidden':'visibilitychange', 'mozHidden':'mozvisibilitychange', 'webkitHidden':'webkitvisibilitychange', 'msHidden':'msvisibilitychange', 'oHidden':'ovisibilitychange' };
var handler = function(event) {
event = event || window.event;
if ((event.type == "focus") || (event.type == "focusin"))
func(true);
else if ((event.type == "blur") || (event.type == "focusout"))
func(false);
else
func(!document[method]);
};
window.addEventListener('blur', handler, false);
window.addEventListener('focus', handler, false);
for (method in methods)
if (methods.hasOwnProperty(method) && (method in document)) {
document.addEventListener(methods[method], handler, false);
if (invoke_now) func(!document[method]);
return;
}
};
// ====================================== Main ======================================
var tracker = new Tracker(cfg);
tracker.install();
/*
unsafeWindow.Tumblr.Prima.Events.listenTo(unsafeWindow.Tumblr.Prima.Events, 'all',
exportFunction(function(){
console.log(arguments);
}, unsafeWindow));
*/
})();