mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-05 16:58:59 +00:00
551 lines
21 KiB
JavaScript
551 lines
21 KiB
JavaScript
if (typeof NEWSBLUR.Globals == 'undefined') NEWSBLUR.Globals = {};
|
|
|
|
/* ============================= */
|
|
/* = Core NewsBlur Javascript = */
|
|
/* ============================= */
|
|
|
|
var URL_REGEX = /((https?\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi;
|
|
|
|
if (!window.console || !window.console.log) {
|
|
window.console = {};
|
|
window.console.log = function() {};
|
|
}
|
|
|
|
NEWSBLUR.log = function(msg) {
|
|
try {
|
|
if (typeof o == "object")
|
|
{
|
|
var new_m = [];
|
|
for (var i=0; i < msg.length; i++) {
|
|
if (i!=0) new_m.push(msg[i]);
|
|
}
|
|
console.debug(msg[0], new_m);
|
|
}
|
|
else
|
|
{
|
|
console.log(msg);
|
|
}
|
|
} catch(e) {
|
|
console =
|
|
{
|
|
log: function() {}
|
|
};
|
|
}
|
|
};
|
|
|
|
(function($) {
|
|
|
|
$.fn.extend({
|
|
|
|
isScrollVisible: function($elem, partial) {
|
|
var docViewTop = 0; // $(this).scrollTop();
|
|
var docViewBottom = docViewTop + $(this).height();
|
|
var docOffset = $(this).offset().top;
|
|
|
|
var elemTop = $elem.offset().top - docOffset;
|
|
var elemBottom = elemTop + $elem.outerHeight();
|
|
|
|
// NEWSBLUR.log(['isScrollVisible', docViewTop, docViewBottom, elemTop, elemBottom]);
|
|
|
|
if (partial) {
|
|
var topVisible = ((elemTop >= docViewTop) && (elemTop <= docViewBottom));
|
|
var bottomVisible = ((elemBottom <= docViewBottom) && (elemBottom >= docViewTop));
|
|
var centerVisible = (elemTop <= docViewTop) && (elemBottom >= docViewBottom);
|
|
return topVisible || bottomVisible || centerVisible;
|
|
} else {
|
|
return ((elemTop >= docViewTop) && (elemBottom <= docViewBottom));
|
|
}
|
|
},
|
|
|
|
// Align an element relative to a target element's coordinates. Forces the
|
|
// element to be absolutely positioned. Element must be visible.
|
|
// Position string format is: "top -right".
|
|
// You can pass an optional offset object with top and left offsets specified.
|
|
align : function(target, pos, offset) {
|
|
var el = this;
|
|
pos = pos || '';
|
|
offset = offset || {};
|
|
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || 0;
|
|
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || 0;
|
|
var clientWidth = document.documentElement.clientWidth;
|
|
var clientHeight = document.documentElement.clientHeight;
|
|
|
|
if (target == window) {
|
|
var b = {
|
|
left : scrollLeft,
|
|
top : scrollTop,
|
|
right : $(window).width() - scrollLeft,
|
|
bottom : $(window).height() - scrollTop,
|
|
width : $(window).width(),
|
|
height : $(window).height()
|
|
};
|
|
} else {
|
|
target = $(target);
|
|
var targOff = target.offset();
|
|
var b = {
|
|
left : targOff.left,
|
|
top : targOff.top,
|
|
right : clientWidth - targOff.left,
|
|
bottom : clientHeight - targOff.top,
|
|
width : target.innerWidth(),
|
|
height : target.innerHeight()
|
|
};
|
|
}
|
|
|
|
var elb = {
|
|
width : el.innerWidth(),
|
|
height : el.innerHeight()
|
|
};
|
|
|
|
var left, top, bottom, right;
|
|
|
|
if (pos.indexOf('-left') >= 0) {
|
|
left = b.left;
|
|
} else if (pos.indexOf('left') >= 0) {
|
|
left = b.left - elb.width;
|
|
} else if (pos.indexOf('-right') >= 0) {
|
|
right = b.right - elb.width;
|
|
} else if (pos.indexOf('right') >= 0) {
|
|
right = b.right;
|
|
} else { // Centered.
|
|
left = b.left + (b.width - elb.width) / 2;
|
|
}
|
|
|
|
if (pos.indexOf('-top') >= 0) {
|
|
bottom = b.bottom - elb.height;
|
|
} else if (pos.indexOf('top') >= 0) {
|
|
bottom = b.bottom;
|
|
} else if (pos.indexOf('-bottom') >= 0) {
|
|
top = b.top;
|
|
} else if (pos.indexOf('bottom') >= 0) {
|
|
top = b.top + elb.height;
|
|
} else { // Centered.
|
|
top = b.top + (b.height - elb.height) / 2;
|
|
}
|
|
|
|
var constrain = (pos.indexOf('no-constraint') >= 0) ? false : true;
|
|
|
|
left += offset.left || 0;
|
|
top += offset.top || 0;
|
|
bottom += offset.top || 0;
|
|
right += offset.left || 0;
|
|
|
|
if (constrain) {
|
|
left = Math.max(scrollLeft, Math.min(left, scrollLeft + clientWidth - elb.width));
|
|
top = Math.max(scrollTop, Math.min(top, scrollTop + clientHeight - elb.height));
|
|
bottom = Math.max(scrollTop, Math.min(bottom, scrollTop + clientHeight - elb.height));
|
|
right = Math.max(scrollTop, Math.min(right, scrollLeft + clientWidth - elb.height));
|
|
}
|
|
|
|
// var offParent;
|
|
// if (offParent = el.offsetParent()) {
|
|
// left -= offParent.offset().left;
|
|
// top -= offParent.offset().top;
|
|
// }
|
|
$(el).css({position : 'absolute'});
|
|
if (pos.indexOf('bottom') >= 0) {
|
|
$(el).css({top : top + 'px', bottom: 'auto'});
|
|
} else {
|
|
$(el).css({bottom : bottom + 'px', top: 'auto'});
|
|
}
|
|
if (pos.indexOf('right') >= 0) {
|
|
$(el).css({right : right + 'px', left: 'auto'});
|
|
} else {
|
|
$(el).css({left : left + 'px', right: 'auto'});
|
|
}
|
|
return el;
|
|
},
|
|
|
|
// When the next click or keypress happens, anywhere on the screen, hide the
|
|
// element. 'clickable' makes the element and its contents clickable without
|
|
// hiding. The 'onHide' callback runs when the hide fires, and has a chance
|
|
// to cancel it.
|
|
autohide : function(options) {
|
|
var me = this;
|
|
options = _.extend({clickable : null, onHide : null}, options || {});
|
|
me._autoignore = true;
|
|
setTimeout(function(){ delete me._autoignore; }, 0);
|
|
|
|
if (!me._autohider) {
|
|
me.forceHide = function(e) {
|
|
if (!e && options.onHide) options.onHide();
|
|
me.hide();
|
|
me.removeHide();
|
|
};
|
|
me.removeHide = function() {
|
|
$(document).unbind('click.autohide', me._autohider);
|
|
$(document).unbind('keypress.autohide', me._autohider);
|
|
$(document).unbind('keyup.autohide', me._checkesc);
|
|
me._autohider = null;
|
|
me._checkesc = null;
|
|
me.forceHide = null;
|
|
};
|
|
me._autohider = function(e) {
|
|
// console.log(['autohide', $(e.target), $(e.target).closest(me[0]), $(e.target).parents(), me[0], _.include($(e.target).parents(), me[0])]);
|
|
if (me._autoignore) return;
|
|
if (options.clickable && (me[0] == e.target || _.include($(e.target).parents(), me[0]))) return;
|
|
if (options.onHide && !options.onHide(e, _.bind(me.forceHide, me))) return;
|
|
me.forceHide(e);
|
|
};
|
|
me._checkesc = function(e) {
|
|
if (e.keyCode == 27) {
|
|
options.clickable = false;
|
|
me._autohider(e);
|
|
}
|
|
};
|
|
$(document).bind('click.autohide', this._autohider);
|
|
$(document).bind('keypress.autohide', this._autohider);
|
|
$(document).bind('keyup.autohide', this._checkesc);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
});
|
|
|
|
$.extend({
|
|
|
|
// Color format: {r: 1, g: .5, b: 0}
|
|
textColor: function(background_color) {
|
|
var contrast = function (color1, color2) {
|
|
var lum1 = luminosity(color1);
|
|
var lum2 = luminosity(color2);
|
|
if(lum1 > lum2)
|
|
return (lum1 + 0.05) / (lum2 + 0.05);
|
|
return (lum2 + 0.05) / (lum1 + 0.05);
|
|
};
|
|
|
|
var luminosity = function (color) {
|
|
var r = color.r, g = color.g, b = color.b;
|
|
var red = (r <= 0.03928) ? r/12.92 : Math.pow(((r + 0.055)/1.055), 2.4);
|
|
var green = (g <= 0.03928) ? g/12.92 : Math.pow(((g + 0.055)/1.055), 2.4);
|
|
var blue = (b <= 0.03928) ? b/12.92 : Math.pow(((b + 0.055)/1.055), 2.4);
|
|
|
|
return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
|
|
};
|
|
|
|
if (contrast(background_color, {r: 1, g: 1, b: 1}) >
|
|
contrast(background_color, {r: .5, g: .5, b: .5})) {
|
|
return 'white';
|
|
} else {
|
|
return 'black';
|
|
}
|
|
},
|
|
|
|
favicon: function (feed, empty_on_missing) {
|
|
var empty_icon = NEWSBLUR.Globals.MEDIA_URL + '/img/icons/nouns/world.svg';
|
|
if (!feed) return empty_icon;
|
|
// console.log(['Favicon', feed]);
|
|
|
|
// Feed is a string
|
|
if (_.isNumber(feed)) {
|
|
return NEWSBLUR.URLs.favicon.replace('{id}', feed);
|
|
} else if (_.isString(feed)) {
|
|
var feed_id = feed;
|
|
if (_.string.startsWith(feed_id, 'search:')) {
|
|
feed_id = feed_id.substring('search:'.length);
|
|
}
|
|
|
|
if (feed_id == 'river:')
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/all-stories.svg';
|
|
if (feed_id == 'river:infrequent')
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/circular/noun_turtle.png';
|
|
if (feed_id == 'river:blurblogs')
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/all-shares.svg';
|
|
if (feed_id == 'river:global')
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/global-shares.svg';
|
|
if (_.string.startsWith(feed_id, 'river:'))
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/folder-open.svg';
|
|
if (feed_id == "read")
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/indicator-unread.svg';
|
|
if (feed_id == "starred")
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/saved-stories.svg';
|
|
if (feed_id == "searches")
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/search.svg';
|
|
if (_.string.startsWith(feed_id, 'starred:'))
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/icons/nouns/tag.svg';
|
|
if (_.string.startsWith(feed_id, 'feed:'))
|
|
return $.favicon(parseInt(feed_id.replace('feed:', ''), 10));
|
|
if (_.string.startsWith(feed_id, 'social:'))
|
|
return $.favicon(NEWSBLUR.assets.get_feed(feed_id));
|
|
}
|
|
|
|
// Feed is a model
|
|
if (feed.get('favicon') && feed.get('favicon').length && feed.get('favicon').indexOf('data:image/png;base64,') != -1)
|
|
return feed.get('favicon');
|
|
if (feed.get('favicon') && feed.get('favicon').length)
|
|
return 'data:image/png;base64,' + feed.get('favicon');
|
|
if (feed.get('favicon_url') && !empty_on_missing)
|
|
return feed.get('favicon_url');
|
|
if (feed.get('photo_url'))
|
|
return feed.get('photo_url');
|
|
if (_.string.include(feed.id, 'social:'))
|
|
return NEWSBLUR.Globals.MEDIA_URL + 'img/reader/default_profile_photo.png';
|
|
if (empty_on_missing)
|
|
return 'data:image/png;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
|
|
if (_.isNumber(feed.id))
|
|
return NEWSBLUR.URLs.favicon.replace('{id}', feed.id);
|
|
if (feed.get('favicon_url'))
|
|
return feed.get('favicon_url');
|
|
if (feed.is_starred())
|
|
return NEWSBLUR.Globals.MEDIA_URL + '/img/icons/nouns/tag.svg';
|
|
if (feed.get('is_newsletter'))
|
|
return NEWSBLUR.Globals.MEDIA_URL + '/img/icons/nouns/email.svg';
|
|
|
|
return empty_icon;
|
|
},
|
|
|
|
deepCopy: function(obj) {
|
|
var type = $.typeOf(obj);
|
|
switch (type) {
|
|
case 'object':
|
|
var o = {};
|
|
for (key in obj) {
|
|
o[key] = $.deepCopy(obj[key]);
|
|
};
|
|
return o;
|
|
case 'array':
|
|
var a = [];
|
|
for (var i = 0; i < obj.length; i++) {
|
|
a.push($.deepCopy(obj[i]));
|
|
}
|
|
return a;
|
|
default:
|
|
return obj;
|
|
};
|
|
},
|
|
|
|
typeOf: function(value) {
|
|
var s = typeof value;
|
|
if (s == 'object') {
|
|
if (value) {
|
|
if (typeof value.length == 'number' &&
|
|
!(value.propertyIsEnumerable('length')) &&
|
|
typeof value.splice == 'function') {
|
|
s = 'array';
|
|
}
|
|
} else {
|
|
s = 'null';
|
|
}
|
|
}
|
|
return s;
|
|
},
|
|
|
|
targetIs: function(e, opts, callback){
|
|
if(!e || !opts){ return false; }
|
|
// defaults
|
|
// (want to make this explicit, since it's a little weird)
|
|
opts = {
|
|
childOf: opts.childOf || null,
|
|
tagSelector: opts.tagSelector || null,
|
|
cancelBubbling: opts.cancelBubbling || false
|
|
};
|
|
var target = e.target;
|
|
var $t = $(target);
|
|
var $p = null;
|
|
var fails = false;
|
|
if(opts.childOf){
|
|
$p = $t.closest(opts.childOf);
|
|
if(!$p.length){
|
|
fails = true;
|
|
}
|
|
}
|
|
if(opts.tagSelector){
|
|
var ts = opts.tagSelector;
|
|
if(!$t.is(ts)){
|
|
if(opts.cancelBubbling){
|
|
fails = true;
|
|
}else{
|
|
var $tp = $t.closest(ts);
|
|
if(!$tp.length){
|
|
fails = true;
|
|
}else{
|
|
// we are going to assume dev
|
|
// wants the $elem we bubbled to
|
|
$t = $tp;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
if(fails){
|
|
return false;
|
|
}else{
|
|
if(callback && typeof callback == 'function'){
|
|
// NEWSBLUR.log(['Target', e, opts.tagSelector, opts]);
|
|
callback($t, $p);
|
|
}
|
|
return true;
|
|
}
|
|
},
|
|
|
|
entity: function(str) {
|
|
var e = document.createElement('div');
|
|
|
|
e.innerHTML = String(str);
|
|
|
|
return e.innerHTML;
|
|
},
|
|
|
|
make: function(){
|
|
var $elem, text, children, type, name, props;
|
|
var args = arguments;
|
|
var tagname = args[0];
|
|
|
|
// Second argument can be TextNode or Attributes
|
|
// $.make('div', 'inner text') || $.make('div', { className: 'etc' })
|
|
if (args[1]) {
|
|
if (typeof args[1] == 'string' || typeof args[1] == 'number') {
|
|
text = args[1];
|
|
} else if (typeof args[1] == 'object' && args[1].push) {
|
|
children = args[1];
|
|
} else {
|
|
props = args[1];
|
|
if (props.className) {
|
|
props['class'] = props.className;
|
|
delete props.className;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Third argument can be TextNode or an array of additional $.make
|
|
if (args[2] != null) {
|
|
if (typeof args[2] == 'string' || typeof args[2] == 'number') {
|
|
text = args[2];
|
|
} else if (typeof args[1] == 'object' && args[2].push) {
|
|
children = args[2];
|
|
}
|
|
}
|
|
|
|
$elem = $(document.createElement(tagname));
|
|
if (props) {
|
|
for (var propname in props) {
|
|
if (props.hasOwnProperty(propname)) {
|
|
if ($elem.is(':input') && propname == 'value') {
|
|
$elem.val(props[propname]);
|
|
} else {
|
|
$elem.attr(propname, props[propname]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (children) {
|
|
for (var i = 0; i < children.length; i++) {
|
|
if (children[i]) {
|
|
$elem.append(children[i]);
|
|
}
|
|
}
|
|
}
|
|
if (text != null) {
|
|
$elem.html(text);
|
|
}
|
|
return $elem;
|
|
},
|
|
|
|
rescope: function(func, thisArg){
|
|
return function(a, b, c, d, e, f){
|
|
func.call(thisArg, this, a, b, c, d, e, f);
|
|
};
|
|
},
|
|
|
|
redirectPost: function(location, args) {
|
|
var form = '';
|
|
$.each( args, function( key, value ) {
|
|
value = value.split('"').join('\"')
|
|
form += '<input type="hidden" name="'+key+'" value="'+value+'">';
|
|
});
|
|
$('<form action="' + location + '" method="POST">' + form + '</form>').appendTo($(document.body)).submit();
|
|
},
|
|
|
|
closest: function(value, array) {
|
|
var offset = 0;
|
|
var index = 0;
|
|
var closest = Math.abs(array[index] - value);
|
|
for (var i in array) {
|
|
var next_value = array[i] - value;
|
|
if (next_value <= offset && Math.abs(next_value) < closest) {
|
|
index = parseInt(i, 10);
|
|
closest = Math.abs(array[index] - value);
|
|
} else if (next_value > offset) {
|
|
// NEWSBLUR.log(['Not Closest', index, next_value, value, closest]);
|
|
return index;
|
|
}
|
|
}
|
|
return index;
|
|
},
|
|
|
|
getQueryString: function (name) {
|
|
var params = {},
|
|
e,
|
|
a = /\+/g, // Regex for replacing addition symbol with a space
|
|
r = /([^&=]+)=?([^&]*)/g,
|
|
d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
|
|
q = window.location.search.substring(1);
|
|
|
|
while (e = r.exec(q))
|
|
params[d(e[1])] = d(e[2]);
|
|
|
|
return params[name];
|
|
},
|
|
|
|
updateQueryString: function(key, value, url) {
|
|
if (!url) url = window.location.href;
|
|
var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"),
|
|
hash;
|
|
|
|
if (re.test(url)) {
|
|
if (typeof value !== 'undefined' && value !== null)
|
|
return url.replace(re, '$1' + key + "=" + value + '$2$3');
|
|
else {
|
|
hash = url.split('#');
|
|
url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '');
|
|
if (typeof hash[1] !== 'undefined' && hash[1] !== null)
|
|
url += '#' + hash[1];
|
|
return url;
|
|
}
|
|
}
|
|
else {
|
|
if (typeof value !== 'undefined' && value !== null) {
|
|
var separator = url.indexOf('?') !== -1 ? '&' : '?';
|
|
hash = url.split('#');
|
|
url = hash[0] + separator + key + '=' + value;
|
|
if (typeof hash[1] !== 'undefined' && hash[1] !== null)
|
|
url += '#' + hash[1];
|
|
return url;
|
|
}
|
|
else
|
|
return url;
|
|
}
|
|
}
|
|
|
|
});
|
|
})(jQuery);
|
|
|
|
// ------- IE Debug -------- //
|
|
|
|
(function($) {
|
|
var _$ied;
|
|
$.extend({
|
|
iedebug: function(msg){
|
|
if(!_$ied){
|
|
_$ied = $.make('div', [
|
|
$.make('ol')
|
|
]).css({
|
|
'position': 'absolute',
|
|
'top': 10,
|
|
'left': 10,
|
|
'zIndex': 20000,
|
|
'border': '1px solid #000',
|
|
'padding': '10px',
|
|
'backgroundColor': '#fff',
|
|
'fontFamily': 'arial,helvetica,sans-serif',
|
|
'fontSize': '11px'
|
|
});
|
|
$('body').append(_$ied);
|
|
_$ied.draggable();
|
|
}
|
|
_$ied.find('ol').append($.make('li', msg).css({
|
|
'borderBottom': '1px solid #999999'
|
|
}));
|
|
}
|
|
});
|
|
})(jQuery);
|