@ -2,6 +2,7 @@
/* jshint esversion:6, strict:global */
//<Polyfills>
if ( ! document . scrollingElement ) document . scrollingElement = document . documentElement ;
if ( ! NodeList . prototype . forEach ) NodeList . prototype . forEach = Array . prototype . forEach ;
if ( ! Element . prototype . matches ) Element . prototype . matches = Element . prototype . msMatchesSelector || Element . prototype . mozMatchesSelector || Element . prototype . webkitMatchesSelector ;
if ( ! Element . prototype . closest ) Element . prototype . closest = function ( s ) {
@ -44,18 +45,20 @@ var context;
} ( ) ) ;
//</Global context>
function badAjax ( ) {
function badAjax ( reload ) {
openNotification ( context . i18n . notif _request _failed , 'bad' ) ;
location . reload ( ) ;
if ( reload ) {
setTimeout ( function ( ) { location . reload ( ) ; } , 2000 ) ;
}
return true ;
}
function needsScroll ( elem ) {
const winBottom = document . document Element. scrollTop + document . document Element. clientHeight ,
const winBottom = document . scrolling Element. scrollTop + document . scrolling Element. clientHeight ,
elemTop = elem . offsetParent . offsetTop + elem . offsetTop ,
elemBottom = elemTop + elem . offsetHeight ;
return ( elemTop < document . document Element. scrollTop || elemBottom > winBottom ) ?
elemTop - ( document . document Element. clientHeight / 2 ) : 0 ;
return ( elemTop < document . scrolling Element. scrollTop || elemBottom > winBottom ) ?
elemTop - ( document . scrolling Element. clientHeight / 2 ) : 0 ;
}
function str2int ( str ) {
@ -159,6 +162,29 @@ function incUnreadsTag(tag_id, nb) {
}
}
function removeArticle ( div ) {
if ( ! div || div . classList . contains ( 'not_read' ) || ( context . auto _mark _article && div . classList . contains ( 'active' ) ) ) {
return ;
}
let scrollTop = box _to _follow . scrollTop ;
let dirty = false ;
const p = div . previousElementSibling ,
n = div . nextElementSibling ;
if ( p && p . classList . contains ( 'day' ) && n && n . classList . contains ( 'day' ) ) {
scrollTop -= p . offsetHeight ;
dirty = true ;
p . remove ( ) ;
}
if ( div . offsetHeight > 0 && div . offsetParent . offsetTop + div . offsetTop + div . offsetHeight < scrollTop ) {
scrollTop -= div . offsetHeight ;
dirty = true ;
}
div . remove ( ) ;
if ( dirty ) {
box _to _follow . scrollTop = scrollTop ;
}
}
var pending _entries = { } ,
mark _read _queue = [ ] ;
@ -167,19 +193,19 @@ function send_mark_read_queue(queue, asRead, callback) {
req . open ( 'POST' , '.?c=entry&a=read' + ( asRead ? '' : '&is_read=0' ) , true ) ;
req . responseType = 'json' ;
req . onerror = function ( e ) {
openNotification ( context . i18n . notif _request _failed , 'bad' ) ;
for ( let i = queue . length - 1 ; i >= 0 ; i -- ) {
delete pending _entries [ 'flux_' + queue [ i ] ] ;
}
if ( this . status == 403 ) {
badAjax ( ) ;
}
badAjax ( this . status == 403 ) ;
} ;
req . onload = function ( e ) {
if ( this . status != 200 ) {
return req . onerror ( e ) ;
}
const json = xmlHttpRequestJson ( this ) ;
if ( ! json ) {
return req . onerror ( e ) ;
}
for ( let i = queue . length - 1 ; i >= 0 ; i -- ) {
const div = document . getElementById ( 'flux_' + queue [ i ] ) ,
myIcons = context . icons ;
@ -191,6 +217,9 @@ function send_mark_read_queue(queue, asRead, callback) {
} ) ;
div . querySelectorAll ( 'a.read > .icon' ) . forEach ( function ( img ) { img . outerHTML = myIcons . read ; } ) ;
inc -- ;
if ( context . auto _remove _article ) {
removeArticle ( div ) ;
}
} else {
div . classList . add ( 'not_read' ) ;
div . classList . add ( 'keep_unread' ) ; //Split for IE11
@ -237,14 +266,15 @@ function send_mark_queue_tick(callback) {
mark _read _queue = [ ] ;
send _mark _read _queue ( queue , true , callback ) ;
}
var delayedFunction = send _mark _queue _tick ;
function delayedClick ( a ) {
if ( a ) {
send _mark _queue _tick ( function ( ) { a . click ( ) ; } ) ;
delayedFunction ( function ( ) { a . click ( ) ; } ) ;
}
}
function mark _read ( div , only _not _read ) {
function mark _read ( div , only _not _read , asBatch ) {
if ( ! div || ! div . id || context . anonymous ||
( only _not _read && ! div . classList . contains ( 'not_read' ) ) ) {
return false ;
@ -256,7 +286,7 @@ function mark_read(div, only_not_read) {
const asRead = div . classList . contains ( 'not_read' ) ,
entryId = div . id . replace ( /^flux_/ , '' ) ;
if ( asRead ) {
if ( asRead && asBatch ) {
mark _read _queue . push ( entryId ) ;
if ( send _mark _read _queue _timeout == 0 ) {
send _mark _read _queue _timeout = setTimeout ( function ( ) { send _mark _queue _tick ( null ) ; } , 1000 ) ;
@ -287,17 +317,17 @@ function mark_favorite(div) {
req . open ( 'POST' , url , true ) ;
req . responseType = 'json' ;
req . onerror = function ( e ) {
openNotification ( context . i18n . notif _request _failed , 'bad' ) ;
delete pending _entries [ div . id ] ;
if ( this . status == 403 ) {
badAjax ( ) ;
}
badAjax ( this . status == 403 ) ;
} ;
req . onload = function ( e ) {
if ( this . status != 200 ) {
return req . onerror ( e ) ;
}
const json = xmlHttpRequestJson ( this ) ;
if ( ! json ) {
return req . onerror ( e ) ;
}
let inc = 0 ;
if ( div . classList . contains ( 'favorite' ) ) {
div . classList . remove ( 'favorite' ) ;
@ -357,21 +387,23 @@ function toggleContent(new_active, old_active, skipping) {
if ( old _active ) {
old _active . classList . remove ( 'active' ) ;
old _active . classList . remove ( 'current' ) ; //Split for IE11
if ( context . auto _remove _article ) {
removeArticle ( old _active ) ;
}
}
} else {
new _active . classList . toggle ( 'active' ) ;
}
const relative _move = context . current _view === 'global' ,
box _to _move = relative _move ? document . getElementById ( 'panel' ) : document . document Element;
box _to _move = relative _move ? document . getElementById ( 'panel' ) : document . scrolling Element;
if ( context . sticky _post ) {
if ( context . sticky _post ) { //Stick the article to the top when opened
let prev _article = new _active . previousElementSibling ,
new _pos = new _active . offsetTop + document . documentElement . scrollTop ,
old _scroll = box _to _move . scrollTop ;
new _pos = new _active . offsetParent . offsetTop + new _active . offsetTop ;
if ( prev _article && new _active . offsetTop - prev _article . offsetTop <= 150 ) {
new _pos = prev _article . offsetTop ;
new _pos = prev _article . offsetParent . offsetTop + prev _article . offset Top ;
if ( relative _move ) {
new _pos -= box _to _move . offsetTop ;
}
@ -382,14 +414,14 @@ function toggleContent(new_active, old_active, skipping) {
new _pos -= document . body . clientHeight / 4 ;
}
if ( relative _move ) {
new _pos += old _scroll ;
new _pos += box _to _move . scrollTop ;
}
box _to _move . scrollTop = new _pos ;
}
if ( new _active . classList . contains ( 'active' ) && ! skipping ) {
if ( context . auto _mark _article ) {
mark _read ( new _active , true ) ;
mark _read ( new _active , true , true ) ;
}
new _active . dispatchEvent ( freshrssOpenArticleEvent ) ;
}
@ -529,7 +561,7 @@ function user_filter(key) {
// Force scrolling to the filter div
const scroll = needsScroll ( document . querySelector ( '.header' ) ) ;
if ( scroll !== 0 ) {
document . document Element. scrollTop = scroll ;
document . scrolling Element. scrollTop = scroll ;
}
// Force the key value if there is only one action, so we can trigger it automatically
if ( filters . length === 1 ) {
@ -557,7 +589,7 @@ function auto_share(key) {
// Force scrolling to the share div
const scrollTop = needsScroll ( share . closest ( '.bottom' ) ) ;
if ( scrollTop !== 0 ) {
document . document Element. scrollTop = scrollTop ;
document . scrolling Element. scrollTop = scrollTop ;
}
// Force the key value if there is only one action, so we can trigger it automatically
if ( shares . length === 1 ) {
@ -585,30 +617,9 @@ function onScroll() {
document . querySelectorAll ( '.not_read:not(.keep_unread)' ) . forEach ( function ( div ) {
if ( div . offsetHeight > 0 &&
div . offsetParent . offsetTop + div . offsetTop + div . offsetHeight < minTop ) {
mark _read ( div , true ) ;
}
} ) ;
}
if ( context . auto _remove _article ) {
let maxTop = box _to _follow . scrollTop ,
scrollOffset = 0 ;
document . querySelectorAll ( '.flux:not(.active):not(.keep_unread)' ) . forEach ( function ( div ) {
if ( ! pending _entries [ div . id ] && div . offsetHeight > 0 &&
div . offsetParent . offsetTop + div . offsetTop + div . offsetHeight < maxTop ) {
const p = div . previousElementSibling ,
n = div . nextElementSibling ;
if ( p && p . classList . contains ( 'day' ) && n && n . classList . contains ( 'day' ) ) {
p . remove ( ) ;
}
maxTop -= div . offsetHeight ;
scrollOffset -= div . offsetHeight ;
div . remove ( ) ;
mark _read ( div , true , true ) ;
}
} ) ;
if ( scrollOffset != 0 ) {
box _to _follow . scrollTop += scrollOffset ;
return ; //onscroll will be called again
}
}
if ( context . auto _load _more ) {
const pagination = document . getElementById ( 'mark-read-pagination' ) ;
@ -621,10 +632,10 @@ function onScroll() {
function init _posts ( ) {
if ( context . auto _load _more || context . auto _mark _scroll || context . auto _remove _article ) {
box _to _follow = context . current _view === 'global' ? document . getElementById ( 'panel' ) : document . document Element;
box _to _follow = context . current _view === 'global' ? document . getElementById ( 'panel' ) : document . scrolling Element;
let lastScroll = 0 , //Throttle
timerId = 0 ;
( box _to _follow === document . document Element ? window : box _to _follow ) . onscroll = function ( ) {
( box _to _follow === document . scrolling Element ? window : box _to _follow ) . onscroll = function ( ) {
clearTimeout ( timerId ) ;
if ( lastScroll + 500 < Date . now ( ) ) {
lastScroll = Date . now ( ) ;
@ -681,7 +692,10 @@ function init_column_categories() {
a . href = '#dropdown-' + id ;
div . querySelector ( '.dropdown-target' ) . id = 'dropdown-' + id ;
div . insertAdjacentHTML ( 'beforeend' , template ) ;
div . querySelector ( 'button.confirm' ) . disabled = false ;
const b = div . querySelector ( 'button.confirm' ) ;
if ( b ) {
b . disabled = false ;
}
} else if ( getComputedStyle ( dropdownMenu ) . display === 'none' ) {
const id2 = div . closest ( '.item' ) . id . substr ( 2 ) ;
a . href = '#dropdown-' + id2 ;
@ -745,7 +759,7 @@ function init_shortcuts() {
} else if ( ev . shiftKey ) { // Mark everything as read
document . querySelector ( '.nav_menu .read_all' ) . click ( ) ;
} else { // Toggle the read state
mark _read ( document . querySelector ( '.flux.current' ) , false ) ;
mark _read ( document . querySelector ( '.flux.current' ) , false , false ) ;
}
return false ;
}
@ -787,7 +801,7 @@ function init_shortcuts() {
}
if ( k === s . go _website ) {
if ( context . auto _mark _site ) {
mark _read ( document . querySelector ( '.flux.current' ) , true ) ;
mark _read ( document . querySelector ( '.flux.current' ) , true , false ) ;
}
window . open ( document . querySelector ( '.flux.current a.go_website' ) . href ) ;
return false ;
@ -813,7 +827,7 @@ function init_stream(stream) {
stream . onclick = function ( ev ) {
let el = ev . target . closest ( '.flux a.read' ) ;
if ( el ) {
mark _read ( el . closest ( '.flux' ) , false ) ;
mark _read ( el . closest ( '.flux' ) , false , false ) ;
return false ;
}
@ -882,7 +896,7 @@ function init_stream(stream) {
new _active = el . parentNode ;
if ( ev . target . tagName . toUpperCase ( ) === 'A' ) { //Leave real links alone
if ( context . auto _mark _article ) {
mark _read ( new _active , true ) ;
mark _read ( new _active , true , false ) ;
}
return true ;
}
@ -891,21 +905,28 @@ function init_stream(stream) {
}
} ;
stream . onmouseup = function ( ev ) { // Mouseup enables us to catch middle click
stream . onmouseup = function ( ev ) { // Mouseup enables us to catch middle click, and control+click in IE/Edge
if ( ev . altKey || ev . metaKey || ev . shiftKey ) {
return ;
}
let el = ev . target . closest ( '.item.title > a' ) ;
if ( el ) {
if ( ev . ctrlKey ) {
return ; // CTRL+click, it will be manage by previous rule.
}
if ( ev . which == 2 ) {
// If middle click, we want same behaviour as CTRL+click.
const evc = document . createEvent ( 'click' ) ;
evc . ctrlKey = true ;
el . dispatchEvent ( evc ) ;
} else if ( ev . which == 1 ) {
// Normal click, just toggle article.
el . parentElement . click ( ) ;
if ( ev . which == 1 ) {
if ( ev . ctrlKey ) { //Control+click
if ( context . auto _mark _site ) {
mark _read ( el . closest ( '.flux' ) , true , false ) ;
}
} else {
el . parentElement . click ( ) ; //Normal click, just toggle article.
}
} else if ( ev . which == 2 && ! ev . ctrlKey ) { //Simple middle click: same behaviour as CTRL+click
if ( context . auto _mark _article ) {
const new _active = el . closest ( '.flux' ) ;
mark _read ( new _active , true , false ) ;
}
}
return ;
}
if ( context . auto _mark _site ) {
@ -916,7 +937,7 @@ function init_stream(stream) {
if ( ev . which == 3 ) {
return ;
}
mark _read ( el . closest ( '.flux' ) , true ) ;
mark _read ( el . closest ( '.flux' ) , true , false ) ;
}
}
} ;
@ -937,9 +958,7 @@ function init_stream(stream) {
req . responseType = 'json' ;
req . onerror = function ( e ) {
checkboxTag . checked = ! isChecked ;
if ( this . status == 403 ) {
badAjax ( ) ;
}
badAjax ( this . status == 403 ) ;
} ;
req . onload = function ( e ) {
if ( this . status != 200 ) {
@ -980,10 +999,10 @@ function init_nav_entries() {
} ;
nav _entries . querySelector ( '.up' ) . onclick = function ( e ) {
const active _item = document . querySelector ( '.flux.current' ) ,
windowTop = document . document Element. scrollTop ,
windowTop = document . scrolling Element. scrollTop ,
item _top = active _item . offsetParent . offsetTop + active _item . offsetTop ;
document . document Element. scrollTop = windowTop > item _top ? item _top : 0 ;
document . scrolling Element. scrollTop = windowTop > item _top ? item _top : 0 ;
return false ;
} ;
}
@ -1006,6 +1025,9 @@ function loadDynamicTags(div) {
return req . onerror ( e ) ;
}
const json = xmlHttpRequestJson ( this ) ;
if ( ! json ) {
return req . onerror ( e ) ;
}
let html = '<li class="item"><label><input class="checkboxTag" name="t_0" type="checkbox" /> <input type="text" name="newTag" /></label></li>' ;
if ( json && json . length ) {
for ( let i = 0 ; i < json . length ; i ++ ) {
@ -1031,7 +1053,7 @@ function updateFeed(feeds, feeds_count) {
req . open ( 'POST' , feed . url , true ) ;
req . onloadend = function ( e ) {
if ( this . status != 200 ) {
return badAjax ( ) ;
return badAjax ( false ) ;
}
feed _processed ++ ;
const div = document . getElementById ( 'actualizeProgress' ) ;
@ -1042,7 +1064,7 @@ function updateFeed(feeds, feeds_count) {
const req2 = new XMLHttpRequest ( ) ;
req2 . open ( 'POST' , './?c=feed&a=actualize&id=-1&ajax=1' , true ) ;
req2 . onloadend = function ( e ) {
location . reload ( ) ;
delayedFunction ( function ( ) { location . reload ( ) ; } ) ;
} ;
req2 . setRequestHeader ( 'Content-Type' , 'application/json' ) ;
req2 . send ( JSON . stringify ( {
@ -1074,9 +1096,12 @@ function init_actualize() {
req . responseType = 'json' ;
req . onload = function ( e ) {
if ( this . status != 200 ) {
return badAjax ( ) ;
return badAjax ( false ) ;
}
const json = xmlHttpRequestJson ( this ) ;
if ( ! json ) {
return badAjax ( false ) ;
}
if ( auto && json . feeds . length < 1 ) {
auto = false ;
context . ajax _loading = false ;
@ -1184,10 +1209,12 @@ function notifs_html5_show(nb) {
} ) ;
notification . onclick = function ( ) {
location . reload ( ) ;
window . focus ( ) ;
notification . close ( ) ;
} ;
delayedFunction ( function ( ) {
location . reload ( ) ;
window . focus ( ) ;
notification . close ( ) ;
} ) ;
} ;
if ( context . html5 _notif _timeout !== 0 ) {
setTimeout ( function ( ) {
@ -1211,6 +1238,9 @@ function refreshUnreads() {
req . responseType = 'json' ;
req . onload = function ( e ) {
const json = xmlHttpRequestJson ( this ) ;
if ( ! json ) {
return badAjax ( false ) ;
}
const isAll = document . querySelector ( '.category.all.active' ) ;
let new _articles = false ;
@ -1286,12 +1316,11 @@ function load_more_posts() {
paginationNew = streamAdopted . querySelector ( '.pagination' ) ;
formPagination . replaceChild ( paginationNew , paginationOld ) ;
if ( context . display _order === 'ASC' ) {
document . querySelector ( '#nav_menu_read_all .read_all' ) . formAction =
document . getElementById ( 'bigMarkAsRead' ) . formAction ;
} else {
const bigMarkAsRead = document . getElementById ( 'bigMarkAsRead' ) ;
if ( bigMarkAsRead ) {
const bigMarkAsRead = document . getElementById ( 'bigMarkAsRead' ) ;
if ( bigMarkAsRead ) {
if ( context . display _order === 'ASC' ) {
document . querySelector ( '#nav_menu_read_all .read_all' ) . formAction = bigMarkAsRead . formAction ;
} else {
bigMarkAsRead . formAction = document . querySelector ( '#nav_menu_read_all .read_all' ) . formAction ;
}
}
@ -1305,8 +1334,7 @@ function load_more_posts() {
init _load _more ( box _load _more ) ;
const bigMarkAsRead = document . getElementById ( 'bigMarkAsRead' ) ,
div _load _more = document . getElementById ( 'load_more' ) ;
const div _load _more = document . getElementById ( 'load_more' ) ;
if ( bigMarkAsRead ) {
bigMarkAsRead . removeAttribute ( 'disabled' ) ;
}
@ -1407,6 +1435,12 @@ function init_normal() {
init _shortcuts ( ) ;
init _actualize ( ) ;
faviconNbUnread ( ) ;
window . onbeforeunload = function ( e ) {
if ( mark _read _queue && mark _read _queue . length > 0 ) {
return false ;
}
} ;
}
function init _beforeDOM ( ) {