From 4888f919f104b2d170302565e481a0b731eb4145 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 25 Dec 2018 01:30:28 +0100 Subject: Prepare for batch mark as read --- app/views/entry/read.phtml | 10 ---------- app/views/helpers/javascript_vars.phtml | 3 ++- 2 files changed, 2 insertions(+), 11 deletions(-) (limited to 'app/views') diff --git a/app/views/entry/read.phtml b/app/views/entry/read.phtml index fb9e129f2..44193da9c 100755 --- a/app/views/entry/read.phtml +++ b/app/views/entry/read.phtml @@ -1,17 +1,7 @@ Minz_Request::controllerName(), - 'a' => Minz_Request::actionName(), - 'params' => Minz_Request::fetchGET(), -); - -$url['params']['is_read'] = Minz_Request::param('is_read', true) ? '0' : '1'; - FreshRSS::loadStylesAndScripts(); echo json_encode(array( - 'url' => str_ireplace('&', '&', Minz_Url::display($url)), - 'icon' => _i($url['params']['is_read'] === '1' ? 'unread' : 'read'), 'tags' => $this->tags, )); diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index a434a04a3..b950b6a1f 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -56,6 +56,7 @@ echo htmlspecialchars(json_encode(array( 'category_empty' => _t('gen.js.category_empty'), ), 'icons' => array( - 'close' => _i('close'), + 'read' => rawurlencode(_i('read')), + 'unread' => rawurlencode(_i('unread')), ), ), JSON_UNESCAPED_UNICODE), ENT_NOQUOTES, 'UTF-8'); -- cgit v1.2.3 From eefeb2341915d677e446cd733fc58b45efb7f5d9 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 25 Dec 2018 05:11:38 +0100 Subject: jQuery ES6 partial refactoring Prefix jQuery variables with $ Start avoiding jQuery in easy cases Use let/const --- app/views/helpers/javascript_vars.phtml | 2 +- p/scripts/main.js | 681 ++++++++++++++++---------------- 2 files changed, 346 insertions(+), 337 deletions(-) (limited to 'app/views') diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index b950b6a1f..b62263ecf 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -42,7 +42,7 @@ echo htmlspecialchars(json_encode(array( 'reading_view' => @$s['reading_view'], 'rss_view' => @$s['rss_view'], ), - 'url' => array( + 'urls' => array( 'index' => _url('index', 'index'), 'login' => Minz_Url::display(array('c' => 'auth', 'a' => 'login'), 'php'), 'logout' => Minz_Url::display(array('c' => 'auth', 'a' => 'logout'), 'php'), diff --git a/p/scripts/main.js b/p/scripts/main.js index 65310fe9f..555e92a40 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -1,13 +1,44 @@ "use strict"; -/* globals $, jQuery, context, i18n, shortcut, shortcuts, url */ -/* jshint strict:global */ +/* globals $, jQuery, shortcut */ +/* jshint esversion:6, strict:global */ + +// +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) { + let el = this; + if (!document.documentElement.contains(el)) return null; + do { + if (el.matches(s)) return el; + el = el.parentElement || el.parentNode; + } while (el !== null && el.nodeType == 1); + return null; + }; +} +// + +// +var context, i18n, icons, shortcuts, urls; + +(function parseJsonVars() { + const jsonVars = document.getElementById('jsonVars'), + json = JSON.parse(jsonVars.innerHTML); + jsonVars.outerHTML = ''; + context = json.context; + i18n = json.i18n; + shortcuts = json.shortcuts; + urls = json.urls; + icons = json.icons; + icons.read = decodeURIComponent(icons.read); + icons.unread = decodeURIComponent(icons.unread); +}()); var $stream = null, + ajax_loading = false, isCollapsed = true, - shares = 0, - ajax_loading = false; - -if (!NodeList.prototype.forEach) { NodeList.prototype.forEach = Array.prototype.forEach; } //IE11 + $nav_entries = null; +// function redirect(url, new_tab) { if (url) { @@ -20,7 +51,7 @@ function redirect(url, new_tab) { } function needsScroll($elem) { - var $win = $(window), + const $win = $(window), winTop = $win.scrollTop(), winHeight = $win.height(), winBottom = winTop + winHeight, @@ -42,10 +73,10 @@ function numberFormat(nStr) { } // http://www.mredkj.com/javascript/numberFormat.html nStr += ''; - var x = nStr.split('.'), - x1 = x[0], + const x = nStr.split('.'), x2 = x.length > 1 ? '.' + x[1] : '', rgx = /(\d+)(\d{3})/; + let x1 = x[0]; while (rgx.test(x1)) { x1 = x1.replace(rgx, '$1' + ' ' + '$2'); } @@ -53,29 +84,29 @@ function numberFormat(nStr) { } function incLabel(p, inc, spaceAfter) { - var i = str2int(p) + inc; + const i = str2int(p) + inc; return i > 0 ? ((spaceAfter ? '' : ' ') + '(' + numberFormat(i) + ')' + (spaceAfter ? ' ' : '')) : ''; } function incUnreadsFeed(article, feed_id, nb) { //Update unread: feed - var elem = $('#' + feed_id).get(0), + let elem = document.getElementById(feed_id), feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0, feed_priority = elem ? str2int(elem.getAttribute('data-priority')) : 0; if (elem) { elem.setAttribute('data-unread', feed_unreads + nb); - elem = $(elem).children('.item-title').get(0); + elem = elem.querySelector('.item-title'); if (elem) { elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); } } //Update unread: category - elem = $('#' + feed_id).parents('.category').get(0); + elem = document.getElementById(feed_id).closest('.category'); feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; if (elem) { elem.setAttribute('data-unread', feed_unreads + nb); - elem = $(elem).find('.title').get(0); + elem = elem.querySelector('.title'); if (elem) { elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); } @@ -83,7 +114,7 @@ function incUnreadsFeed(article, feed_id, nb) { //Update unread: all if (feed_priority > 0) { - elem = $('#aside_feed .all .title').get(0); + elem = document.querySelector('#aside_feed .all .title'); if (elem) { feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); @@ -91,22 +122,22 @@ function incUnreadsFeed(article, feed_id, nb) { } //Update unread: favourites - if (article && $(article).closest('div').hasClass('favorite')) { - elem = $('#aside_feed .favorites .title').get(0); + if (article && article.closest('div').classList.contains('favorite')) { + elem = document.querySelector('#aside_feed .favorites .title'); if (elem) { feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); } } - var isCurrentView = false; + let isCurrentView = false; // Update unread: title document.title = document.title.replace(/^((?:\([ 0-9]+\) )?)/, function (m, p1) { - var $feed = $('#' + feed_id); - if (article || ($feed.closest('.active').length > 0 && $feed.siblings('.active').length === 0)) { + const feed = document.getElementById(feed_id); + if (article || (feed.closest('.active') && $(feed).siblings('.active').length === 0)) { isCurrentView = true; return incLabel(p1, nb, true); - } else if ($('.all.active').length > 0) { + } else if (document.querySelector('.all.active')) { isCurrentView = feed_priority > 0; return incLabel(p1, feed_priority > 0 ? nb : 0, true); } else { @@ -117,19 +148,22 @@ function incUnreadsFeed(article, feed_id, nb) { } function incUnreadsTag(tag_id, nb) { - var $t = $('#' + tag_id); - var unreads = str2int($t.attr('data-unread')); - $t.attr('data-unread', unreads + nb) - .children('.item-title').attr('data-unread', numberFormat(unreads + nb)); - - $t = $('.category.tags').find('.title'); - unreads = str2int($t.attr('data-unread')); - $t.attr('data-unread', numberFormat(unreads + nb)); + let t = document.getElementById(tag_id); + if (t) { + let unreads = str2int(t.getAttribute('data-unread')); + t.setAttribute('data-unread', unreads + nb); + t.querySelector('.item-title').setAttribute('data-unread', numberFormat(unreads + nb)); + } + t = document.querySelector('.category.tags .title'); + if (t) { + let unreads = str2int(t.getAttribute('data-unread')); + t.setAttribute('data-unread', numberFormat(unreads + nb)); + } } var pending_entries = {}; -function mark_read($active, only_not_read) { - let div = $active ? $active[0] : null; + +function mark_read(div, only_not_read) { if (!div || !div.id || context.anonymous || (only_not_read && !div.classList.contains('not_read'))) { return false; @@ -187,20 +221,21 @@ function mark_read($active, only_not_read) { }); } -function mark_favorite(active) { - if (active.length === 0) { +function mark_favorite(div) { + if (!div) { return false; } - var url = active.find("a.bookmark").attr("href"); - if (url === undefined) { + let a = div.querySelector('a.bookmark'), + url = a ? a.getAttribute('href') : ''; + if (!url) { return false; } - if (pending_entries[active.attr('id')]) { + if (pending_entries[div.id]) { return false; } - pending_entries[active.attr('id')] = true; + pending_entries[div.id] = true; $.ajax({ type: 'POST', @@ -210,77 +245,77 @@ function mark_favorite(active) { _csrf: context.csrf, }, }).done(function (data) { - var $b = active.find("a.bookmark").attr("href", data.url), - inc = 0; - if (active.hasClass("favorite")) { - active.removeClass("favorite"); + let inc = 0; + if (div.classList.contains('favorite')) { + div.classList.remove('favorite'); inc--; } else { - active.addClass("favorite").find('.bookmark'); + div.classList.add('favorite'); inc++; } - $b.find('.icon').replaceWith(data.icon); + div.querySelectorAll('a.bookmark').forEach(function (a) { a.setAttribute('href', data.url); }); + div.querySelectorAll('a.bookmark > .icon').forEach(function (img) { img.outerHTML = data.icon; }); - var favourites = $('#aside_feed .favorites .title').contents().last().get(0); + const favourites = $('#aside_feed .favorites .title').contents().last().get(0); if (favourites && favourites.textContent) { favourites.textContent = favourites.textContent.replace(/((?: \([ 0-9]+\))?\s*)$/, function (m, p1) { return incLabel(p1, inc, false); }); } - if (active.closest('div').hasClass('not_read')) { - var elem = $('#aside_feed .favorites .title').get(0), + if (div.classList.contains('not_read')) { + const elem = document.querySelector('#aside_feed .favorites .title'), feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; if (elem) { elem.setAttribute('data-unread', numberFormat(feed_unreads + inc)); } } - delete pending_entries[active.attr('id')]; + delete pending_entries[div.id]; }).fail(function (data) { openNotification(i18n.notif_request_failed, 'bad'); - delete pending_entries[active.attr('id')]; + delete pending_entries[div.id]; }); } -function toggleContent(new_active, old_active, skipping) { +function toggleContent($new_active, $old_active, skipping) { // If skipping, move current without activating or marking as read - if (new_active.length === 0) { + if ($new_active.length === 0) { return; } if (context.does_lazyload && !skipping) { - new_active.find('img[data-original], iframe[data-original]').each(function () { + $new_active.find('img[data-original], iframe[data-original]').each(function () { this.setAttribute('src', this.getAttribute('data-original')); this.removeAttribute('data-original'); }); } - if (old_active[0] !== new_active[0]) { + if ($old_active[0] !== $new_active[0]) { if (isCollapsed && !skipping) { // BUG?: isCollapsed can only ever be true - new_active.addClass("active"); + $new_active.addClass('active'); } - old_active.removeClass("active current"); - new_active.addClass("current"); - if (context.auto_remove_article && !old_active.hasClass('not_read') && !skipping) { - auto_remove(old_active); + $old_active.removeClass('active current'); + $new_active.addClass('current'); + if (context.auto_remove_article && !$old_active.hasClass('not_read') && !skipping) { + auto_remove($old_active); } } else { // collapse_entry calls toggleContent(flux_current, flux_current, false) - new_active.toggleClass('active'); + $new_active.toggleClass('active'); } - var relative_move = context.current_view === 'global', - box_to_move = $(relative_move ? "#panel" : "html,body"); + const relative_move = context.current_view === 'global', + $box_to_move = $(relative_move ? '#panel' : 'html,body'); if (context.sticky_post) { - var prev_article = new_active.prevAll('.flux'), - new_pos = new_active.offset().top, - old_scroll = box_to_move.scrollTop(); + let prev_article = $new_active.prevAll('.flux'), + new_pos = $new_active.offset().top, + old_scroll = $box_to_move.scrollTop(); if (prev_article.length > 0 && new_pos - prev_article.offset().top <= 150) { new_pos = prev_article.offset().top; if (relative_move) { - new_pos -= box_to_move.offset().top; + new_pos -= $box_to_move.offset().top; } } @@ -293,104 +328,104 @@ function toggleContent(new_active, old_active, skipping) { new_pos += old_scroll; } - new_active.children(".flux_content").first().each(function () { - box_to_move.scrollTop(new_pos).scrollTop(); + $new_active.children('.flux_content').first().each(function () { + $box_to_move.scrollTop(new_pos).scrollTop(); }); } else { if (relative_move) { new_pos += old_scroll; } - box_to_move.scrollTop(new_pos).scrollTop(); + $box_to_move.scrollTop(new_pos).scrollTop(); } } - if (context.auto_mark_article && new_active.hasClass('active') && !skipping) { - mark_read(new_active, true); + if (context.auto_mark_article && $new_active.hasClass('active') && !skipping) { + mark_read($new_active[0], true); } } -function auto_remove(element) { - var p = element.prev(); - var n = element.next(); - if (p.hasClass('day') && n.hasClass('day')) { - p.remove(); +function auto_remove($element) { + let $p = $element.prev(), + $n = $element.next(); + if ($p.hasClass('day') && $n.hasClass('day')) { + $p.remove(); } - element.remove(); + $element.remove(); $('#stream > .flux:not(.not_read):not(.active)').remove(); } function prev_entry() { - var old_active = $(".flux.current"), - new_active = old_active.length === 0 ? $(".flux:last") : old_active.prevAll(".flux:first"); - toggleContent(new_active, old_active, false); + let $old_active = $('.flux.current'), + $new_active = $old_active.length === 0 ? $('.flux:last') : $old_active.prevAll('.flux:first'); + toggleContent($new_active, $old_active, false); } function next_entry() { - var old_active = $(".flux.current"), - new_active = old_active.length === 0 ? $(".flux:first") : old_active.nextAll(".flux:first"); - toggleContent(new_active, old_active, false); + let $old_active = $('.flux.current'), + $new_active = $old_active.length === 0 ? $('.flux:first') : $old_active.nextAll('.flux:first'); + toggleContent($new_active, $old_active, false); - if (new_active.nextAll().length < 3) { + if ($new_active.nextAll().length < 3) { load_more_posts(); } } function skip_prev_entry() { - var old_active = $(".flux.current"), - new_active = old_active.length === 0 ? $(".flux:last") : old_active.prevAll(".flux:first"); - toggleContent(new_active, old_active, true); + let $old_active = $('.flux.current'), + $new_active = $old_active.length === 0 ? $('.flux:last') : $old_active.prevAll('.flux:first'); + toggleContent($new_active, $old_active, true); } function skip_next_entry() { - var old_active = $(".flux.current"), - new_active = old_active.length === 0 ? $(".flux:first") : old_active.nextAll(".flux:first"); - toggleContent(new_active, old_active, true); + let $old_active = $('.flux.current'), + $new_active = $old_active.length === 0 ? $('.flux:first') : $old_active.nextAll('.flux:first'); + toggleContent($new_active, $old_active, true); - if (new_active.nextAll().length < 3) { + if ($new_active.nextAll().length < 3) { load_more_posts(); } } function prev_feed() { - var active_feed = $("#aside_feed .tree-folder-items .item.active"); - if (active_feed.length > 0) { - active_feed.prevAll(':visible:first').find('a').each(function(){this.click();}); + let $active_feed = $('#aside_feed .tree-folder-items .item.active'); + if ($active_feed.length > 0) { + $active_feed.prevAll(':visible:first').find('a').each(function () { this.click(); }); } else { last_feed(); } } function next_feed() { - var active_feed = $("#aside_feed .tree-folder-items .item.active"); - if (active_feed.length > 0) { - active_feed.nextAll(':visible:first').find('a').each(function(){this.click();}); + let $active_feed = $('#aside_feed .tree-folder-items .item.active'); + if ($active_feed.length > 0) { + $active_feed.nextAll(':visible:first').find('a').each(function () { this.click(); }); } else { first_feed(); } } function first_feed() { - var feed = $("#aside_feed .tree-folder-items.active .item:visible:first"); - if (feed.length > 0) { - feed.find('a')[1].click(); + let $feed = $('#aside_feed .tree-folder-items.active .item:visible:first'); + if ($feed.length > 0) { + $feed.find('a')[1].click(); } } function last_feed() { - var feed = $("#aside_feed .tree-folder-items.active .item:visible:last"); - if (feed.length > 0) { - feed.find('a')[1].click(); + let $feed = $('#aside_feed .tree-folder-items.active .item:visible:last'); + if ($feed.length > 0) { + $feed.find('a')[1].click(); } } function prev_category() { - var active_cat = $("#aside_feed .tree-folder.active"); + let $active_cat = $('#aside_feed .tree-folder.active'); - if (active_cat.length > 0) { - var prev_cat = active_cat.prevAll(':visible:first').find('.tree-folder-title .title'); - if (prev_cat.length > 0) { - prev_cat[0].click(); + if ($active_cat.length > 0) { + let $prev_cat = $active_cat.prevAll(':visible:first').find('.tree-folder-title .title'); + if ($prev_cat.length > 0) { + $prev_cat[0].click(); } } else { last_category(); @@ -399,12 +434,12 @@ function prev_category() { } function next_category() { - var active_cat = $("#aside_feed .tree-folder.active"); + let $active_cat = $('#aside_feed .tree-folder.active'); - if (active_cat.length > 0) { - var next_cat = active_cat.nextAll(':visible:first').find('.tree-folder-title .title'); - if (next_cat.length > 0) { - next_cat[0].click(); + if ($active_cat.length > 0) { + let $next_cat = $active_cat.nextAll(':visible:first').find('.tree-folder-title .title'); + if ($next_cat.length > 0) { + $next_cat[0].click(); } } else { first_category(); @@ -413,35 +448,35 @@ function next_category() { } function first_category() { - var cat = $("#aside_feed .tree-folder:visible:first"); - if (cat.length > 0) { - cat.find('.tree-folder-title .title')[0].click(); + let $cat = $('#aside_feed .tree-folder:visible:first'); + if ($cat.length > 0) { + $cat.find('.tree-folder-title .title')[0].click(); } } function last_category() { - var cat = $("#aside_feed .tree-folder:visible:last"); - if (cat.length > 0) { - cat.find('.tree-folder-title .title')[0].click(); + let $cat = $('#aside_feed .tree-folder:visible:last'); + if ($cat.length > 0) { + $cat.find('.tree-folder-title .title')[0].click(); } } function collapse_entry() { - var flux_current = $(".flux.current"); - toggleContent(flux_current, flux_current, false); + let $flux_current = $('.flux.current'); + toggleContent($flux_current, $flux_current, false); } function user_filter(key) { - var filter = $('#dropdown-query'); - var filters = filter.siblings('.dropdown-menu').find('.item.query a'); - if (typeof key === "undefined") { + const filter = $('#dropdown-query'), + filters = filter.siblings('.dropdown-menu').find('.item.query a'); + if (typeof key === 'undefined') { if (!filter.length) { return; } // Display the filter div window.location.hash = filter.attr('id'); // Force scrolling to the filter div - var scroll = needsScroll($('.header')); + const scroll = needsScroll($('.header')); if (scroll !== 0) { $('html,body').scrollTop(scroll); } @@ -460,21 +495,21 @@ function user_filter(key) { } function auto_share(key) { - var share = $(".flux.current.active").find('.dropdown-target[id^="dropdown-share"]'); - var shares = share.siblings('.dropdown-menu').find('.item a'); - if (typeof key === "undefined") { - if (!share.length) { + const $share = $('.flux.current.active').find('.dropdown-target[id^="dropdown-share"]'), + $shares = $share.siblings('.dropdown-menu').find('.item a'); + if (typeof key === 'undefined') { + if (!$share.length) { return; } // Display the share div - window.location.hash = share.attr('id'); + window.location.hash = $share.attr('id'); // Force scrolling to the share div - var scroll = needsScroll(share.closest('.bottom')); + const scroll = needsScroll($share.closest('.bottom')); if (scroll !== 0) { $('html,body').scrollTop(scroll); } // Force the key value if there is only one action, so we can trigger it automatically - if (shares.length === 1) { + if ($shares.length === 1) { key = 1; } else { return; @@ -482,54 +517,54 @@ function auto_share(key) { } // Trigger selected share action and hide the share div key = parseInt(key); - if (key <= shares.length) { - shares[key - 1].click(); - share.siblings('.dropdown-menu').find('.dropdown-close a')[0].click(); + if (key <= $shares.length) { + $shares[key - 1].click(); + $share.siblings('.dropdown-menu').find('.dropdown-close a')[0].click(); } } -function scrollAsRead(box_to_follow) { - var minTop = 40 + (context.current_view === 'global' ? box_to_follow.offset().top : box_to_follow.scrollTop()); +function scrollAsRead($box_to_follow) { + const minTop = 40 + (context.current_view === 'global' ? $box_to_follow.offset().top : $box_to_follow.scrollTop()); $('.not_read:not(.keep_unread):visible').each(function () { - var $this = $(this); + const $this = $(this); if ($this.offset().top + $this.height() < minTop) { - mark_read($this, true); + mark_read(this, true); } }); } function init_posts() { - var box_to_follow = context.current_view === 'global' ? $("#panel") : $(window); + let $box_to_follow = context.current_view === 'global' ? $('#panel') : $(window); if (context.auto_mark_scroll) { - var lastScroll = 0, //Throttle + let lastScroll = 0, //Throttle timerId = 0; - box_to_follow.scroll(function () { + $box_to_follow.scroll(function () { window.clearTimeout(timerId); if (lastScroll + 500 < Date.now()) { lastScroll = Date.now(); - scrollAsRead(box_to_follow); + scrollAsRead($box_to_follow); } else { timerId = window.setTimeout(function() { - scrollAsRead(box_to_follow); + scrollAsRead($box_to_follow); }, 500); } }); } if (context.auto_load_more) { - box_to_follow.scroll(function () { - var load_more = $("#load_more"); - if (!load_more.is(':visible')) { + $box_to_follow.scroll(function () { + const $load_more = $('#load_more'); + if (!$load_more.is(':visible')) { return; } - var boxBot = box_to_follow.scrollTop() + box_to_follow.height(), - load_more_top = load_more.offset().top; + const boxBot = $box_to_follow.scrollTop() + $box_to_follow.height(), + load_more_top = $load_more.offset().top; if (boxBot >= load_more_top) { load_more_posts(); } }); - box_to_follow.scroll(); + $box_to_follow.scroll(); } } @@ -548,9 +583,9 @@ function init_column_categories() { this.alt = '▽'; } }); - $(this).parent().next(".tree-folder-items").slideToggle(300, function () { + $(this).parent().next('.tree-folder-items').slideToggle(300, function () { //Workaround for Gecko bug in Firefox 64-65(+?): - var sidebar = document.getElementById('sidebar'); + const sidebar = document.getElementById('sidebar'); if (sidebar && sidebar.scrollHeight > sidebar.clientHeight && //if needs scrollbar sidebar.scrollWidth >= sidebar.offsetWidth) { //but no scrollbar sidebar.style['overflow-y'] = 'scroll'; //then force scrollbar @@ -561,7 +596,7 @@ function init_column_categories() { }); $('#aside_feed').on('click', '.tree-folder-items .feed .dropdown-toggle', function () { - var itemId = $(this).closest('.item').attr('id'), + const itemId = $(this).closest('.item').attr('id'), templateId = itemId.substring(0, 2) === 't_' ? 'tag_config_template' : 'feed_config_template', id = itemId.substr(2), feed_web = $(this).data('fweb'), @@ -572,10 +607,10 @@ function init_column_categories() { .append(template).find('button.confirm').removeAttr('disabled'); } else { if ($(this).next('.dropdown-menu').css('display') === 'none') { - id = $(this).closest('.item').attr('id').substr(2); - $(this).attr('href', '#dropdown-' + id); + const id2 = $(this).closest('.item').attr('id').substr(2); + $(this).attr('href', '#dropdown-' + id2); } else { - $(this).attr('href', "#close"); + $(this).attr('href', '#close'); } } }); @@ -592,21 +627,19 @@ function init_shortcuts() { // Manipulation shortcuts shortcut.add(shortcuts.mark_read, function () { // Toggle the read state - var active = $(".flux.current"); - mark_read(active, false); + mark_read(document.querySelector('.flux.current'), false); }, { 'disable_in_input': true }); - shortcut.add("shift+" + shortcuts.mark_read, function () { + shortcut.add('shift+' + shortcuts.mark_read, function () { // Mark everything as read - $(".nav_menu .read_all").click(); + $('.nav_menu .read_all').click(); }, { 'disable_in_input': true }); shortcut.add(shortcuts.mark_favorite, function () { // Toggle the favorite state - var active = $(".flux.current"); - mark_favorite(active); + mark_favorite(document.querySelector('.flux.current')); }, { 'disable_in_input': true }); @@ -637,7 +670,7 @@ function init_shortcuts() { auto_share(String.fromCharCode(evt.keyCode)); } } - for (var i = 1; i < 10; i++) { + for (let i = 1; i < 10; i++) { shortcut.add(i.toString(), addShortcut, { 'disable_in_input': true }); @@ -651,11 +684,11 @@ function init_shortcuts() { 'disable_in_input': true }); shortcut.add(shortcuts.first_entry, function () { - var old_active = $(".flux.current"), - first = $(".flux:first"); + const $old_active = $('.flux.current'), + $first = $('.flux:first'); - if (first.hasClass("flux")) { - toggleContent(first, old_active, false); + if ($first.hasClass('flux')) { + toggleContent($first, $old_active, false); } }, { 'disable_in_input': true @@ -667,48 +700,48 @@ function init_shortcuts() { 'disable_in_input': true }); shortcut.add(shortcuts.last_entry, function () { - var old_active = $(".flux.current"), - last = $(".flux:last"); + const $old_active = $('.flux.current'), + $last = $('.flux:last'); - if (last.hasClass("flux")) { - toggleContent(last, old_active, false); + if ($last.hasClass('flux')) { + toggleContent($last, $old_active, false); } }, { 'disable_in_input': true }); // Feed navigation shortcuts - shortcut.add("shift+" + shortcuts.prev_entry, prev_feed, { + shortcut.add('shift+' + shortcuts.prev_entry, prev_feed, { 'disable_in_input': true }); - shortcut.add("shift+" + shortcuts.next_entry, next_feed, { + shortcut.add('shift+' + shortcuts.next_entry, next_feed, { 'disable_in_input': true }); - shortcut.add("shift+" + shortcuts.first_entry, first_feed, { + shortcut.add('shift+' + shortcuts.first_entry, first_feed, { 'disable_in_input': true }); - shortcut.add("shift+" + shortcuts.last_entry, last_feed, { + shortcut.add('shift+' + shortcuts.last_entry, last_feed, { 'disable_in_input': true }); // Category navigation shortcuts - shortcut.add("alt+" + shortcuts.prev_entry, prev_category, { + shortcut.add('alt+' + shortcuts.prev_entry, prev_category, { 'disable_in_input': true }); - shortcut.add("alt+" + shortcuts.next_entry, next_category, { + shortcut.add('alt+' + shortcuts.next_entry, next_category, { 'disable_in_input': true }); - shortcut.add("alt+" + shortcuts.first_entry, first_category, { + shortcut.add('alt+' + shortcuts.first_entry, first_category, { 'disable_in_input': true }); - shortcut.add("alt+" + shortcuts.last_entry, last_category, { + shortcut.add('alt+' + shortcuts.last_entry, last_category, { 'disable_in_input': true }); shortcut.add(shortcuts.go_website, function () { - var url_website = $('.flux.current a.go_website').attr("href"); + const url_website = $('.flux.current a.go_website').attr('href'); if (context.auto_mark_site) { - $(".flux.current").each(function () { - mark_read($(this), true); + $('.flux.current').each(function () { + mark_read(this, true); }); } @@ -730,7 +763,7 @@ function init_shortcuts() { }); shortcut.add(shortcuts.help, function () { - redirect(url.help, true); + redirect(urls.help, true); }, { 'disable_in_input': true }); @@ -775,8 +808,8 @@ function init_stream(divStream) { // setting for not-closing after clicking outside article area return; } - var old_active = $(".flux.current"), - new_active = $(this).parent(); + const old_active = document.querySelector('.flux.current'), + new_active = this.parentNode; isCollapsed = true; if (e.target.tagName.toUpperCase() === 'A') { //Leave real links alone if (context.auto_mark_article) { @@ -784,21 +817,20 @@ function init_stream(divStream) { } return true; } - toggleContent(new_active, old_active, false); + toggleContent($(new_active), $(old_active), false); }); divStream.on('click', '.flux a.read', function () { - var active = $(this).parents(".flux"); - if (context.auto_remove_article && active.hasClass('not_read')) { - auto_remove(active); + const $active = $(this).parents('.flux'); + if (context.auto_remove_article && $active.hasClass('not_read')) { + auto_remove($active); } - mark_read(active, false); + mark_read($active[0], false); return false; }); divStream.on('click', '.flux a.bookmark', function () { - var active = $(this).parents(".flux"); - mark_favorite(active); + mark_favorite(this.closest('.flux')); return false; }); @@ -815,7 +847,7 @@ function init_stream(divStream) { if (e.which == 2) { // If middle click, we want same behaviour as CTRL+click. - var ev = jQuery.Event("click"); + const ev = jQuery.Event('click'); ev.ctrlKey = true; $(this).trigger(ev); } else if(e.which == 1) { @@ -838,13 +870,11 @@ function init_stream(divStream) { return; } - mark_read($(this).parents(".flux"), true); + mark_read(this.closest('.flux'), true); }); } } -var $nav_entries = null; - function init_nav_entries() { $nav_entries = $('#nav_entries'); $nav_entries.find('.previous_entry').click(function () { @@ -856,14 +886,14 @@ function init_nav_entries() { return false; }); $nav_entries.find('.up').click(function () { - var active_item = $(".flux.current"), + const $active_item = $('.flux.current'), windowTop = $(window).scrollTop(), - item_top = active_item.offset().top; + item_top = $active_item.offset().top; if (windowTop > item_top) { - $("html,body").scrollTop(item_top); + $('html,body').scrollTop(item_top); } else { - $("html,body").scrollTop(0); + $('html,body').scrollTop(0); } return false; }); @@ -872,14 +902,14 @@ function init_nav_entries() { function loadDynamicTags($div) { $div.removeClass('dynamictags'); $div.find('li.item').remove(); - var entryId = $div.closest('div.flux').attr('id').replace(/^flux_/, ''); + const entryId = $div.closest('div.flux').attr('id').replace(/^flux_/, ''); $.getJSON('./?c=tag&a=getTagsForEntry&id_entry=' + entryId) .done(function (data) { - var $ul = $div.find('.dropdown-menu'); + const $ul = $div.find('.dropdown-menu'); $ul.append('
  • '); if (data && data.length) { - for (var i = 0; i < data.length; i++) { - var tag = data[i]; + for (let i = 0; i < data.length; i++) { + const tag = data[i]; $ul.append('
  • '); } @@ -897,13 +927,13 @@ function init_dynamic_tags() { }); $stream.on('change', '.checkboxTag', function (ev) { - var $checkbox = $(this); + const $checkbox = $(this), + isChecked = $checkbox.prop('checked'), + tagId = $checkbox.attr('name').replace(/^t_/, ''), + tagName = $checkbox.siblings('input[name]').val(), + $entry = $checkbox.closest('div.flux'), + entryId = $entry.attr('id').replace(/^flux_/, ''); $checkbox.prop('disabled', true); - var isChecked = $checkbox.prop('checked'); - var tagId = $checkbox.attr('name').replace(/^t_/, ''); - var tagName = $checkbox.siblings('input[name]').val(); - var $entry = $checkbox.closest('div.flux'); - var entryId = $entry.attr('id').replace(/^flux_/, ''); $.ajax({ type: 'POST', url: './?c=tag&a=tagEntry', @@ -936,7 +966,7 @@ function init_dynamic_tags() { var feed_processed = 0; function updateFeed(feeds, feeds_count) { - var feed = feeds.pop(); + const feed = feeds.pop(); if (!feed) { return; } @@ -949,8 +979,8 @@ function updateFeed(feeds, feeds_count) { }, }).always(function (data) { feed_processed++; - $("#actualizeProgress .progress").html(feed_processed + " / " + feeds_count); - $("#actualizeProgress .title").html(feed.title); + $('#actualizeProgress .progress').html(feed_processed + ' / ' + feeds_count); + $('#actualizeProgress .title').html(feed.title); if (feed_processed === feeds_count) { $.ajax({ //Empty request to commit new articles @@ -970,9 +1000,9 @@ function updateFeed(feeds, feeds_count) { } function init_actualize() { - var auto = false; + let auto = false; - $("#actualize").click(function () { + $('#actualize').click(function () { if (ajax_loading) { return false; } @@ -985,7 +1015,7 @@ function init_actualize() { return false; } if (data.feeds.length === 0) { - openNotification(data.feedback_no_refresh, "good"); + openNotification(data.feedback_no_refresh, 'good'); $.ajax({ //Empty request to force refresh server database cache type: 'POST', url: './?c=feed&a=actualize&id=-1&ajax=1', @@ -999,11 +1029,11 @@ function init_actualize() { return; } //Progress bar - var feeds_count = data.feeds.length; + const feeds_count = data.feeds.length; $('body').after('
    ' + data.feedback_actualize + '
    /
    0 / ' + feeds_count + '
    '); - for (var i = 10; i > 0; i--) { + for (let i = 10; i > 0; i--) { updateFeed(data.feeds, feeds_count); } }); @@ -1013,13 +1043,13 @@ function init_actualize() { if (context.auto_actualize_feeds) { auto = true; - $("#actualize").click(); + $('#actualize').click(); } } // // -var notification = null, +var $notification = null, notification_interval = null, notification_working = false; @@ -1030,19 +1060,19 @@ function openNotification(msg, status) { notification_working = true; - notification.removeClass(); - notification.addClass("notification"); - notification.addClass(status); - notification.find(".msg").html(msg); - notification.fadeIn(300); + $notification.removeClass(); + $notification.addClass('notification'); + $notification.addClass(status); + $notification.find('.msg').html(msg); + $notification.fadeIn(300); notification_interval = window.setTimeout(closeNotification, 4000); } function closeNotification() { - notification.fadeOut(600, function() { - notification.removeClass(); - notification.addClass('closed'); + $notification.fadeOut(600, function() { + $notification.removeClass(); + $notification.addClass('closed'); window.clearInterval(notification_interval); notification_working = false; @@ -1050,14 +1080,14 @@ function closeNotification() { } function init_notifications() { - notification = $("#notification"); + $notification = $('#notification'); - notification.find("a.close").click(function () { + $notification.find('a.close').click(function () { closeNotification(); return false; }); - if (notification.find(".msg").html().length > 0) { + if ($notification.find('.msg').html().length > 0) { notification_working = true; notification_interval = window.setTimeout(closeNotification, 4000); } @@ -1078,12 +1108,12 @@ function notifs_html5_ask_permission() { } function notifs_html5_show(nb) { - if (notifs_html5_permission !== "granted") { + if (notifs_html5_permission !== 'granted') { return; } - var notification = new window.Notification(i18n.notif_title_articles, { - icon: "../themes/icons/favicon-256.png", + const notification = new window.Notification(i18n.notif_title_articles, { + icon: '../themes/icons/favicon-256.png', body: i18n.notif_body_articles.replace('%d', nb), tag: 'freshRssNewArticles', }); @@ -1112,12 +1142,12 @@ function init_notifs_html5() { function refreshUnreads() { $.getJSON('./?c=javascript&a=nbUnreadsPerFeed').done(function (data) { - var isAll = $('.category.all.active').length > 0, - new_articles = false; + const isAll = document.querySelector('.category.all.active'); + let new_articles = false; $.each(data.feeds, function(feed_id, nbUnreads) { feed_id = 'f_' + feed_id; - var elem = $('#' + feed_id).get(0), + const elem = document.getElementById(feed_id), feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; if ((incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads) || isAll) && //Update of current view? @@ -1127,7 +1157,7 @@ function refreshUnreads() { } }); - var nbUnreadTags = 0; + let nbUnreadTags = 0; $.each(data.tags, function(tag_id, nbUnreads) { nbUnreadTags += nbUnreads; @@ -1138,7 +1168,7 @@ function refreshUnreads() { $('.category.tags').attr('data-unread', nbUnreadTags) .find('.title').attr('data-unread', numberFormat(nbUnreadTags)); - var nb_unreads = str2int($('.category.all .title').attr('data-unread')); + const nb_unreads = str2int($('.category.all .title').attr('data-unread')); if (nb_unreads > 0 && new_articles) { faviconNbUnread(nb_unreads); @@ -1148,7 +1178,7 @@ function refreshUnreads() { } // -var url_load_more = "", +var url_load_more = '', load_more = false, box_load_more = null; @@ -1158,7 +1188,7 @@ function load_more_posts() { } load_more = true; - $('#load_more').addClass('loading'); + document.getElementById('load_more').classList.add('loading'); $.get(url_load_more, function (data) { box_load_more.children('.flux:last').after($('#stream', data).children('.flux, .day')); $('.pagination').replaceWith($('.pagination', data)); @@ -1173,7 +1203,7 @@ function load_more_posts() { } $('[id^=day_]').each(function (i) { - var ids = $('[id="' + this.id + '"]'); + const ids = $('[id="' + this.id + '"]'); if (ids.length > 1) { $('[id="' + this.id + '"]:gt(0)').remove(); } @@ -1181,8 +1211,8 @@ function load_more_posts() { init_load_more(box_load_more); - $('#load_more').removeClass('loading'); - $('#bigMarkAsRead').removeAttr('disabled'); + document.getElementById('load_more').classList.remove('loading'); + document.getElementById('bigMarkAsRead').removeAttribute('disabled'); load_more = false; }); } @@ -1198,14 +1228,14 @@ function init_load_more(box) { box_load_more = box; document.body.dispatchEvent(freshrssLoadMoreEvent); - var $next_link = $("#load_more"); + const $next_link = $('#load_more'); if (!$next_link.length) { // no more article to load - url_load_more = ""; + url_load_more = ''; return; } - url_load_more = $next_link.attr("href"); + url_load_more = $next_link.attr('href'); $next_link.click(function () { load_more_posts(); @@ -1216,9 +1246,9 @@ function init_load_more(box) { // function poormanSalt() { //If crypto.getRandomValues is not available - var text = '$2a$04$', - base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; - for (var i = 22; i > 0; i--) { + const base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; + let text = '$2a$04$'; + for (let i = 22; i > 0; i--) { text += base.charAt(Math.floor(Math.random() * 64)); } return text; @@ -1226,7 +1256,7 @@ function poormanSalt() { //If crypto.getRandomValues is not available function init_crypto_form() { /* globals dcodeIO */ - var $crypto_form = $('#crypto-form'); + const $crypto_form = $('#crypto-form'); if ($crypto_form.length === 0) { return; } @@ -1240,10 +1270,10 @@ function init_crypto_form() { } $crypto_form.on('submit', function() { - var $submit_button = $(this).find('button[type="submit"]'); + const $submit_button = $(this).find('button[type="submit"]'); $submit_button.attr('disabled', ''); - var success = false; + let success = false; $.ajax({ url: './?c=javascript&a=nonce&user=' + $('#username').val(), dataType: 'json', @@ -1253,7 +1283,7 @@ function init_crypto_form() { openNotification('Invalid user!', 'bad'); } else { try { - var strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), + const strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1), c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? dcodeIO.bcrypt.genSaltSync(4) : poormanSalt()); $('#challenge').val(c); @@ -1278,7 +1308,7 @@ function init_crypto_form() { function init_confirm_action() { $('body').on('click', '.confirm', function () { - var str_confirmation = $(this).attr('data-str-confirm'); + let str_confirmation = $(this).attr('data-str-confirm'); if (!str_confirmation) { str_confirmation = i18n.confirmation_default; } @@ -1290,15 +1320,15 @@ function init_confirm_action() { function init_print_action() { $('.item.share > a[href="#"]').click(function (e) { - var content = "" + + const content = '' + $(e.target).closest('.flux_content').find('.content').html() + - ""; + ''; - var tmp_window = window.open(); + const tmp_window = window.open(); tmp_window.document.writeln(content); tmp_window.document.close(); tmp_window.focus(); @@ -1310,25 +1340,27 @@ function init_print_action() { } function init_post_action() { - $('.item.share > a[href="POST"]').click(function (event) { - event.preventDefault(); - var form = $(this).next('form'); - $.post(form.data('url'), form.serialize()); + $('.item.share > a[href="POST"]').click(function (e) { + e.preventDefault(); + const $form = $(this).next('form'); + $.post($form.data('url'), $form.serialize()); }); } +var shares = 0; + function init_share_observers() { shares = $('.group-share').length; - $('.share.add').on('click', function(e) { - var opt = $(this).siblings('select').find(':selected'); - var row = $(this).parents('form').data(opt.data('form')); - row = row.replace(/##label##/g, opt.html().trim()); - row = row.replace(/##type##/g, opt.val()); - row = row.replace(/##help##/g, opt.data('help')); + $('.share.add').on('click', function (e) { + const $opt = $(this).siblings('select').find(':selected'); + let row = $(this).parents('form').data($opt.data('form')); + row = row.replace(/##label##/g, $opt.html().trim()); + row = row.replace(/##type##/g, $opt.val()); + row = row.replace(/##help##/g, $opt.data('help')); row = row.replace(/##key##/g, shares); - row = row.replace(/##method##/g, opt.data('method')); - row = row.replace(/##field##/g, opt.data('field')); + row = row.replace(/##method##/g, $opt.data('method')); + row = row.replace(/##field##/g, $opt.data('field')); $(this).parents('.form-group').before(row); shares++; @@ -1343,44 +1375,39 @@ function init_stats_observers() { } function init_remove_observers() { - $('.post').on('click', 'a.remove', function(e) { - var remove_what = $(this).attr('data-remove'); - + $('.post').on('click', 'a.remove', function (e) { + const remove_what = $(this).attr('data-remove'); if (remove_what !== undefined) { - var remove_obj = $('#' + remove_what); - remove_obj.remove(); + $('#' + remove_what).remove(); } - return false; }); } function init_feed_observers() { - $('select[id="category"]').on('change', function() { - var detail = $('#new_category_name').parent(); + $('select[id="category"]').on('change', function () { + const $detail = $('#new_category_name').parent(); if ($(this).val() === 'nc') { - detail.attr('aria-hidden', 'false').show(); - detail.find('input').focus(); + $detail.attr('aria-hidden', 'false').show(); + $detail.find('input').focus(); } else { - detail.attr('aria-hidden', 'true').hide(); + $detail.attr('aria-hidden', 'true').hide(); } }); } function init_password_observers() { - $('.toggle-password').on('mousedown', function(e) { - var button = $(this); - var passwordField = $('#' + button.attr('data-toggle')); - passwordField.attr('type', 'text'); - button.addClass('active'); - + $('.toggle-password').on('mousedown', function (e) { + const $button = $(this), + $passwordField = $('#' + $button.attr('data-toggle')); + $passwordField.attr('type', 'text'); + $button.addClass('active'); return false; - }).on('mouseup', function(e) { - var button = $(this); - var passwordField = $('#' + button.attr('data-toggle')); - passwordField.attr('type', 'password'); - button.removeClass('active'); - + }).on('mouseup', function (e) { + const $button = $(this), + $passwordField = $('#' + $button.attr('data-toggle')); + $passwordField.attr('type', 'password'); + $button.removeClass('active'); return false; }); } @@ -1390,16 +1417,16 @@ function faviconNbUnread(n) { n = str2int($('.category.all .title').attr('data-unread')); } //http://remysharp.com/2010/08/24/dynamic-favicons/ - var canvas = document.createElement('canvas'), + const canvas = document.createElement('canvas'), link = document.getElementById('favicon').cloneNode(true); if (canvas.getContext && link) { canvas.height = canvas.width = 16; - var img = document.createElement('img'); + const img = document.createElement('img'); img.onload = function () { - var ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d'); ctx.drawImage(this, 0, 0, canvas.width, canvas.height); if (n > 0) { - var text = ''; + let text = ''; if (n < 1000) { text = n; } else if (n < 100000) { @@ -1422,9 +1449,9 @@ function faviconNbUnread(n) { } function init_slider_observers() { - var slider = $('#slider'), - closer = $('#close-slider'); - if (slider.length < 1) { + const $slider = $('#slider'), + $closer = $('#close-slider'); + if ($slider.length < 1) { return; } @@ -1434,51 +1461,47 @@ function init_slider_observers() { } ajax_loading = true; - var url_slide = $(this).attr('href'); $.ajax({ type: 'GET', - url: url_slide, + url: $(this).attr('href'), data: { ajax: true } }).done(function (data) { - slider.html(data); - closer.addClass('active'); - slider.addClass('active'); + $slider.html(data); + $closer.addClass('active'); + $slider.addClass('active'); ajax_loading = false; }); return false; }); - closer.on('click', function() { - closer.removeClass('active'); - slider.removeClass('active'); + $closer.on('click', function() { + $closer.removeClass('active'); + $slider.removeClass('active'); return false; }); } function init_configuration_alert() { - $(window).on('submit', function(e) { + $(window).on('submit', function (e) { window.hasSubmit = true; }); - $(window).on('beforeunload', function(e) { + $(window).on('beforeunload', function (e) { if (window.hasSubmit) { return; } - var fields = $("[data-leave-validation]"); - for (var i = 0; i < fields.length; i++) { - if ($(fields[i]).attr('type') === 'checkbox' || $(fields[i]).attr('type') === 'radio') { - // The use of != is done on purpose to check boolean against integer - if ($(fields[i]).is(':checked') != $(fields[i]).attr('data-leave-validation')) { - return false; - } - } else { - if ($(fields[i]).attr('data-leave-validation') !== $(fields[i]).val()) { + const inputs = document.querySelectorAll('[data-leave-validation]'); + for (let i = inputs.length - 1; i >= 0; i--) { + const input = inputs[i]; + if (input.type === 'checkbox' || input.type === 'radio') { + if (input.checked != input.getAttribute('data-leave-validation')) { return false; } + } else if (input.value != input.getAttribute('data-leave-validation')) { + return false; } } - return; }); } @@ -1488,19 +1511,6 @@ function init_subscription() { }); } -function parseJsonVars() { - var jsonVars = document.getElementById('jsonVars'), - json = JSON.parse(jsonVars.innerHTML); - jsonVars.outerHTML = ''; - window.context = json.context; - window.shortcuts = json.shortcuts; - window.url = json.url; - window.i18n = json.i18n; - window.icons = json.icons; - icons.read = decodeURIComponent(icons.read); - icons.unread = decodeURIComponent(icons.unread); -} - function init_normal() { $stream = $('#stream'); if ($stream.length < 1) { @@ -1567,7 +1577,6 @@ function init_afterDOM() { } } -parseJsonVars(); init_beforeDOM(); //Can be called before DOM is fully loaded if (document.readyState && document.readyState !== 'loading') { -- cgit v1.2.3 From 2374374ba972eb4cca84d7f71b1900f806c2b914 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 13 Feb 2019 15:06:28 +0100 Subject: Less jQuery (#2234) * Less jQuery Follow-up of https://github.com/FreshRSS/FreshRSS/pull/2199 * Even less jQuery + global view unread title fix * Even less jQuery * Yet even less jQuery * Even less jQuery * Reduce some events * Even less jQuery * jQuery gone from main view +Fixed English i18n * Fix feed folded view * Remove Firefox 64 workaround Remove workaround for Gecko bug 1514498 in Firefox 64, fixed in Firefox 65 * Split to extra.js Avoid loading unneeded JavaScript code for the main view. + several adjustements * Improve CSS transition fold category * Rewrite shortcuts Remove library. Much faster, shorter, one listener instead of many. Control of the shortcut context. Fix https://github.com/FreshRSS/FreshRSS/issues/2215 * Remove debug * Minor syntax * Filter out unwanted shortcut modifiers * Menu overflow fix * Typo * Fix unfolding in mobile view * Remove jQuery from category.js * Remove jQuery from Global view --- README.fr.md | 1 - README.md | 1 - app/Controllers/authController.php | 3 +- app/Controllers/indexController.php | 1 + app/Controllers/statsController.php | 1 + app/Controllers/subscriptionController.php | 3 +- app/Controllers/userController.php | 4 +- app/FreshRSS.php | 5 +- app/i18n/en/conf.php | 2 +- app/i18n/ru/conf.php | 2 +- app/i18n/tr/conf.php | 2 +- app/layout/aside_feed.phtml | 6 +- app/views/helpers/index/normal/entry_bottom.phtml | 2 +- app/views/index/reader.phtml | 2 +- lib/Minz/Request.php | 25 + p/scripts/category.js | 178 ++- p/scripts/extra.js | 235 +++ p/scripts/global_view.js | 128 +- p/scripts/main.js | 1589 +++++++++------------ p/scripts/repartition.js | 4 +- p/scripts/shortcut.js | 225 --- p/scripts/stats.js | 8 +- p/themes/base-theme/template.css | 40 +- 23 files changed, 1180 insertions(+), 1287 deletions(-) create mode 100644 p/scripts/extra.js delete mode 100644 p/scripts/shortcut.js (limited to 'app/views') diff --git a/README.fr.md b/README.fr.md index e40ab8296..58647ae4a 100644 --- a/README.fr.md +++ b/README.fr.md @@ -210,7 +210,6 @@ Tout client supportant une API de type Fever ; Sélection : * [php-http-304](https://alexandre.alapetite.fr/doc-alex/php-http-304/) * [jQuery](https://jquery.com/) * [lib_opml](https://github.com/marienfressinaud/lib_opml) -* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) * [flotr2](http://www.humblesoftware.com/flotr2) ## Uniquement pour certaines options ou configurations diff --git a/README.md b/README.md index a5a2af725..190ee5639 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,6 @@ Supported clients are: * [php-http-304](https://alexandre.alapetite.fr/doc-alex/php-http-304/) * [jQuery](https://jquery.com/) * [lib_opml](https://github.com/marienfressinaud/lib_opml) -* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) * [flotr2](http://www.humblesoftware.com/flotr2) ## Only for some options or configurations diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php index 3b2d78b19..75d4acae0 100644 --- a/app/Controllers/authController.php +++ b/app/Controllers/authController.php @@ -109,8 +109,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController { public function formLoginAction() { invalidateHttpCache(); - $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'); - Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime)); + Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'))); $conf = Minz_Configuration::get('system'); $limits = $conf->limits; diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index fa914ef87..824d65815 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -104,6 +104,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { return; } + Minz_View::appendScript(Minz_Url::display('/scripts/extra.js?' . @filemtime(PUBLIC_PATH . '/scripts/extra.js'))); Minz_View::appendScript(Minz_Url::display('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); try { diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index acfacb890..1d0d9c124 100644 --- a/app/Controllers/statsController.php +++ b/app/Controllers/statsController.php @@ -52,6 +52,7 @@ class FreshRSS_stats_Controller extends Minz_ActionController { */ public function indexAction() { $statsDAO = FreshRSS_Factory::createStatsDAO(); + Minz_View::prependScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js'))); Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); $this->view->repartition = $statsDAO->calculateEntryRepartition(); $entryCount = $statsDAO->calculateEntryCount(); diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php index 46503fc19..62fb3d384 100644 --- a/app/Controllers/subscriptionController.php +++ b/app/Controllers/subscriptionController.php @@ -29,8 +29,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { * It displays categories and associated feeds. */ public function indexAction() { - Minz_View::appendScript(Minz_Url::display('/scripts/category.js?' . - @filemtime(PUBLIC_PATH . '/scripts/category.js'))); + Minz_View::appendScript(Minz_Url::display('/scripts/category.js?' . @filemtime(PUBLIC_PATH . '/scripts/category.js'))); Minz_View::prependTitle(_t('sub.title') . ' · '); $this->view->onlyFeedsWithError = Minz_Request::paramTernary('error'); diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php index 2338c8b2a..71172b9ef 100644 --- a/app/Controllers/userController.php +++ b/app/Controllers/userController.php @@ -128,9 +128,7 @@ class FreshRSS_user_Controller extends Minz_ActionController { public function profileAction() { Minz_View::prependTitle(_t('conf.profile.title') . ' · '); - Minz_View::appendScript(Minz_Url::display( - '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js') - )); + Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'))); if (Minz_Request::isPost()) { $passwordPlain = Minz_Request::param('newPasswordPlain', '', true); diff --git a/app/FreshRSS.php b/app/FreshRSS.php index dec446a8e..9371081e4 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -94,9 +94,10 @@ class FreshRSS extends Minz_FrontController { } } //Use prepend to insert before extensions. Added in reverse order. + if (Minz_Request::controllerName() !== 'index') { + Minz_View::prependScript(Minz_Url::display('/scripts/extra.js?' . @filemtime(PUBLIC_PATH . '/scripts/extra.js'))); + } Minz_View::prependScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); - Minz_View::prependScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); - Minz_View::prependScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js'))); } private static function loadNotifications() { diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php index c6471426f..b3d4d8a5c 100644 --- a/app/i18n/en/conf.php +++ b/app/i18n/en/conf.php @@ -92,7 +92,7 @@ return array( 'auto_remove_article' => 'Hide articles after reading', 'confirm_enabled' => 'Display a confirmation dialog on “mark all as read” actions', 'display_articles_unfolded' => 'Show articles unfolded by default', - 'display_categories_unfolded' => 'Show categories folded by default', + 'display_categories_unfolded' => 'Show categories unfolded by default', 'hide_read_feeds' => 'Hide categories & feeds with no unread articles (does not work with “Show all articles” configuration)', 'img_with_lazyload' => 'Use "lazy load" mode to load pictures', 'jump_next' => 'jump to next unread sibling (feed or category)', diff --git a/app/i18n/ru/conf.php b/app/i18n/ru/conf.php index 59ac480bc..841477964 100644 --- a/app/i18n/ru/conf.php +++ b/app/i18n/ru/conf.php @@ -92,7 +92,7 @@ return array( 'auto_remove_article' => 'Hide articles after reading', //TODO - Translation 'confirm_enabled' => 'Display a confirmation dialog on “mark all as read” actions', //TODO - Translation 'display_articles_unfolded' => 'Show articles unfolded by default', //TODO - Translation - 'display_categories_unfolded' => 'Show categories folded by default', //TODO - Translation + 'display_categories_unfolded' => 'Show categories unfolded by default', //TODO - Translation 'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)', //TODO - Translation 'img_with_lazyload' => 'Use "lazy load" mode to load pictures', //TODO - Translation 'jump_next' => 'jump to next unread sibling (feed or category)', //TODO - Translation diff --git a/app/i18n/tr/conf.php b/app/i18n/tr/conf.php index 507558487..6c57d39da 100644 --- a/app/i18n/tr/conf.php +++ b/app/i18n/tr/conf.php @@ -92,7 +92,7 @@ return array( 'auto_remove_article' => 'Okuduktan sonra makaleleri gizle', 'confirm_enabled' => '"Hepsini okundu say" eylemi için onay iste', 'display_articles_unfolded' => 'Show articles unfolded by default', - 'display_categories_unfolded' => 'Show categories folded by default', + 'display_categories_unfolded' => 'Show categories unfolded by default', 'hide_read_feeds' => 'Okunmamış makalesi olmayan kategori veya akışı gizle ("Tüm makaleleri göster" komutunda çalışmaz)', 'img_with_lazyload' => 'Resimleri yüklemek için "tembel modu" kullan', 'jump_next' => 'Bir sonraki benzer okunmamışa geç (akış veya kategori)', diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 5efaa54d1..637acf4a4 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -37,13 +37,14 @@ display_categories; ?>
  • -
      +
        tags as $tag): ?> @@ -64,8 +65,7 @@ $feeds = $cat->feeds(); if (!empty($feeds)) { $c_active = FreshRSS_Context::isCurrentGet('c_' . $cat->id()); - $c_show = $c_active && (!FreshRSS_Context::$user_conf->display_categories || - FreshRSS_Context::$current_get['feed']); + $c_show = $c_active || FreshRSS_Context::$user_conf->display_categories; ?>
      • diff --git a/app/views/helpers/index/normal/entry_bottom.phtml b/app/views/helpers/index/normal/entry_bottom.phtml index 1f35318e3..c0edbdf7d 100644 --- a/app/views/helpers/index/normal/entry_bottom.phtml +++ b/app/views/helpers/index/normal/entry_bottom.phtml @@ -93,7 +93,7 @@ name(); ?> name(); ?> -
        +
        diff --git a/app/views/index/reader.phtml b/app/views/index/reader.phtml index 1485a1997..fbe37d2e3 100644 --- a/app/views/index/reader.phtml +++ b/app/views/index/reader.phtml @@ -42,7 +42,7 @@ if (!empty($this->entries)) { isFavorite() ? 'starred' : 'non-starred'); ?> - + ✇ name(); ?>

        title(); ?>

        diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 8b2b610d6..912c354ac 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -95,6 +95,7 @@ class Minz_Request { */ public static function init() { self::magicQuotesOff(); + self::initJSON(); } /** @@ -237,6 +238,30 @@ class Minz_Request { } } + /** + * Allows receiving POST data as application/json + */ + private static function initJSON() { + $contentType = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : ''; + if ($contentType == '') { //PHP < 5.3.16 + $contentType = isset($_SERVER['HTTP_CONTENT_TYPE']) ? $_SERVER['HTTP_CONTENT_TYPE'] : ''; + } + $contentType = strtolower(trim($contentType)); + if ($contentType === 'application/json') { + $ORIGINAL_INPUT = file_get_contents('php://input', false, null, 0, 1048576); + if ($ORIGINAL_INPUT != '') { + $json = json_decode($ORIGINAL_INPUT, true); + if ($json != null) { + foreach ($json as $k => $v) { + if (!isset($_POST[$k])) { + $_POST[$k] = $v; + } + } + } + } + } + } + /** * Permet de récupérer une variable de type $_POST * @param $param nom de la variable diff --git a/p/scripts/category.js b/p/scripts/category.js index caa4fa22f..86d8542f1 100644 --- a/p/scripts/category.js +++ b/p/scripts/category.js @@ -1,6 +1,6 @@ "use strict"; -/* globals i18n */ -/* jshint globalstrict: true */ +/* globals context */ +/* jshint esversion:6, strict:global */ var loading = false, dnd_successful = false; @@ -9,7 +9,7 @@ function dragend_process(t) { t.setAttribute('draggable', 'false'); if (loading) { - window.setTimeout(function() { + setTimeout(function() { dragend_process(t); }, 50); return; @@ -20,11 +20,11 @@ function dragend_process(t) { t.style.opacity = ''; t.setAttribute('draggable', 'true'); } else { - var parent = $(t.parentNode); - $(t).remove(); + const p = t.parentElement; + t.remove(); - if (parent.children().length <= 0) { - parent.append('
      • ' + i18n.category_empty + '
      • '); + if (p.childElementCount <= 0) { + p.insertAdjacentHTML('beforeend', '
      • ' + context.i18n.category_empty + '
      • '); } } } @@ -33,89 +33,109 @@ var dragFeedId = '', dragHtml = ''; function init_draggable() { - if (!(window.$ && window.i18n)) { + if (!window.context) { if (window.console) { - console.log('FreshRSS waiting for JS…'); + console.log('FreshRSS category waiting for JS…'); } - window.setTimeout(init_draggable, 50); + setTimeout(init_draggable, 50); return; } - var draggable = '[draggable="true"]', - dropzone = '[dropzone="move"]'; - - $('.drop-section').on('dragstart', draggable, function(e) { - var drag = $(e.target).closest('[draggable]')[0]; - e.originalEvent.dataTransfer.effectAllowed = 'move'; - dragHtml = drag.outerHTML; - dragFeedId = drag.getAttribute('data-feed-id'); - e.originalEvent.dataTransfer.setData('text', dragFeedId); - drag.style.opacity = 0.3; - - dnd_successful = false; - }); - $('.drop-section').on('dragend', draggable, function(e) { - dragend_process(e.target); - }); - - $('.drop-section').on('dragenter', dropzone, function(e) { - $(this).addClass('drag-hover'); - - e.preventDefault(); - }); - $('.drop-section').on('dragleave', dropzone, function(e) { - var pos_this = $(this).position(), - scroll_top = $(document).scrollTop(), - top = pos_this.top, - left = pos_this.left, - right = left + $(this).width(), - bottom = top + $(this).height(), - mouse_x = e.originalEvent.screenX, - mouse_y = e.originalEvent.clientY + scroll_top; - - if (left <= mouse_x && mouse_x <= right && - top <= mouse_y && mouse_y <= bottom) { - // HACK because dragleave is triggered when hovering children! - return; - } - $(this).removeClass('drag-hover'); - }); - $('.drop-section').on('dragover', dropzone, function(e) { - e.originalEvent.dataTransfer.dropEffect = "move"; - - e.preventDefault(); - return false; - }); - $('.drop-section').on('drop', dropzone, function(e) { - loading = true; - - $.ajax({ - type: 'POST', - url: './?c=feed&a=move', - data: { - f_id: dragFeedId, - c_id: e.target.parentNode.getAttribute('data-cat-id'), - _csrf: context.csrf, + const draggable = '[draggable="true"]', + dropzone = '[dropzone="move"]', + dropSection = document.querySelector('.drop-section'); + + dropSection.ondragstart = function(ev) { + const li = ev.target.closest ? ev.target.closest(draggable) : null; + if (li) { + const drag = ev.target.closest('[draggable]'); + ev.dataTransfer.effectAllowed = 'move'; + dragHtml = drag.outerHTML; + dragFeedId = drag.getAttribute('data-feed-id'); + ev.dataTransfer.setData('text', dragFeedId); + drag.style.opacity = 0.3; + dnd_successful = false; } - }).done(function() { - $(e.target).after(dragHtml); - if ($(e.target).hasClass('disabled')) { - $(e.target).remove(); + }; + + dropSection.ondragend = function(ev) { + const li = ev.target.closest ? ev.target.closest(draggable) : null; + if (li) { + dragend_process(li); } - dnd_successful = true; - }).always(function() { - loading = false; - dragFeedId = ''; - dragHtml = ''; - }); + }; - $(this).removeClass('drag-hover'); + dropSection.ondragenter = function(ev) { + const li = ev.target.closest ? ev.target.closest(dropzone) : null; + if (li) { + li.classList.add('drag-hover'); + return false; + } + }; + + dropSection.onddragleave = function(ev) { + const li = ev.target.closest ? ev.target.closest(dropzone) : null; + if (li) { + const scroll_top = document.documentElement.scrollTop, + top = li.offsetTop, + left = li.offsetLeft, + right = left + li.clientWidth, + bottom = top + li.clientHeight, + mouse_x = ev.screenX, + mouse_y = ev.clientY + scroll_top; + + if (left <= mouse_x && mouse_x <= right && + top <= mouse_y && mouse_y <= bottom) { + // HACK because dragleave is triggered when hovering children! + return; + } + li.classList.remove('drag-hover'); + } + }; - e.preventDefault(); - }); + dropSection.ondragover = function(ev) { + const li = ev.target.closest ? ev.target.closest(dropzone) : null; + if (li) { + ev.dataTransfer.dropEffect = "move"; + return false; + } + }; + + dropSection.ondrop = function(ev) { + const li = ev.target.closest ? ev.target.closest(dropzone) : null; + if (li) { + loading = true; + + const req = new XMLHttpRequest(); + req.open('POST', './?c=feed&a=move', true); + req.responseType = 'json'; + req.onload = function (e) { + if (this.status == 200) { + li.insertAdjacentHTML('afterend', dragHtml); + if (li.classList.contains('disabled')) { + li.remove(); + } + dnd_successful = true; + } + }; + req.onloadend = function (e) { + loading = false; + dragFeedId = ''; + dragHtml = ''; + }; + req.setRequestHeader('Content-Type', 'application/json'); + req.send(JSON.stringify({ + f_id: dragFeedId, + c_id: li.parentElement.getAttribute('data-cat-id'), + _csrf: context.csrf, + })); + + li.classList.remove('drag-hover'); + return false; + } + }; } - if (document.readyState && document.readyState !== 'loading') { init_draggable(); } else if (document.addEventListener) { diff --git a/p/scripts/extra.js b/p/scripts/extra.js new file mode 100644 index 000000000..7a0ef0477 --- /dev/null +++ b/p/scripts/extra.js @@ -0,0 +1,235 @@ +"use strict"; +/* globals context, openNotification, xmlHttpRequestJson */ +/* jshint esversion:6, strict:global */ + +// +function poormanSalt() { //If crypto.getRandomValues is not available + const base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; + let text = '$2a$04$'; + for (let i = 22; i > 0; i--) { + text += base.charAt(Math.floor(Math.random() * 64)); + } + return text; +} + +function init_crypto_form() { + /* globals dcodeIO */ + const crypto_form = document.getElementById('crypto-form'); + if (!crypto_form) { + return; + } + + if (!(window.dcodeIO)) { + if (window.console) { + console.log('FreshRSS waiting for bcrypt.js…'); + } + setTimeout(init_crypto_form, 100); + return; + } + + crypto_form.onsubmit = function (e) { + const submit_button = this.querySelector('button[type="submit"]'); + submit_button.disabled = true; + let success = false; + + const req = new XMLHttpRequest(); + req.open('GET', './?c=javascript&a=nonce&user=' + document.getElementById('username').value, false); + req.onerror = function () { + openNotification('Communication error!', 'bad'); + }; + req.send(); + if (req.status == 200) { + const json = xmlHttpRequestJson(req); + if (!json.salt1 || !json.nonce) { + openNotification('Invalid user!', 'bad'); + } else { + try { + const strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), + s = dcodeIO.bcrypt.hashSync(document.getElementById('passwordPlain').value, json.salt1), + c = dcodeIO.bcrypt.hashSync(json.nonce + s, strong ? dcodeIO.bcrypt.genSaltSync(4) : poormanSalt()); + document.getElementById('challenge').value = c; + if (!s || !c) { + openNotification('Crypto error!', 'bad'); + } else { + success = true; + } + } catch (ex) { + openNotification('Crypto exception! ' + ex, 'bad'); + } + } + } else { + req.onerror(); + } + + submit_button.disabled = false; + return success; + }; +} +// + +function init_share_observers() { + let shares = document.querySelectorAll('.group-share').length; + const shareAdd = document.querySelector('.share.add'); + if (shareAdd) { + shareAdd.onclick = function (ev) { + const s = this.parentElement.querySelector('select'), + opt = s.options[s.selectedIndex]; + let row = this.closest('form').getAttribute('data-' + opt.getAttribute('data-form')); + row = row.replace(/##label##/g, opt.text); + row = row.replace(/##type##/g, opt.value); + row = row.replace(/##help##/g, opt.getAttribute('data-help')); + row = row.replace(/##key##/g, shares); + row = row.replace(/##method##/g, opt.getAttribute('data-method')); + row = row.replace(/##field##/g, opt.getAttribute('data-field')); + this.closest('.form-group').insertAdjacentHTML('beforebegin', row); + shares++; + return false; + }; + } +} + + +function init_remove_observers() { + document.querySelectorAll('.post').forEach(function (div) { + div.onclick = function (ev) { + const a = ev.target.closest('a.remove'); + if (a) { + const remove_what = a.getAttribute('data-remove'); + if (remove_what !== undefined) { + const d = document.getElementById(remove_what); + if (d) { + d.remove(); + } + } + return false; + } + }; + }); +} + +function init_feed_observers() { + const s = document.getElementById('category'); + if (s && s.matches('select')) { + s.onchange = function (ev) { + const detail = document.getElementById('new_category_name').parentElement; + if (this.value === 'nc') { + detail.setAttribute('aria-hidden', 'false'); + detail.querySelector('input').focus(); + } else { + detail.setAttribute('aria-hidden', 'true'); + } + }; + } +} + +function init_password_observers() { + document.querySelectorAll('.toggle-password').forEach(function (a) { + a.onmousedown = function (ev) { + const passwordField = document.getElementById(this.getAttribute('data-toggle')); + passwordField.setAttribute('type', 'text'); + this.classList.add('active'); + return false; + }; + a.onmouseup = function (ev) { + const passwordField = document.getElementById(this.getAttribute('data-toggle')); + passwordField.setAttribute('type', 'password'); + this.classList.remove('active'); + return false; + }; + }); +} + +function init_select_observers() { + document.querySelectorAll('.select-change').forEach(function (s) { + s.onchange = function (ev) { + const opt = s.options[s.selectedIndex]; + location.href = opt.getAttribute('data-url'); + }; + }); +} + +function init_slider_observers() { + const slider = document.getElementById('slider'), + closer = document.getElementById('close-slider'); + if (!slider) { + return; + } + + document.querySelector('.post').onclick = function (ev) { + const a = ev.target.closest('.open-slider'); + if (a) { + if (!context.ajax_loading) { + context.ajax_loading = true; + + const req = new XMLHttpRequest(); + req.open('GET', a.href + '&ajax=1', true); + req.responseType = 'document'; + req.onload = function (e) { + slider.innerHTML = this.response.body.innerHTML; + slider.classList.add('active'); + closer.classList.add('active'); + context.ajax_loading = false; + }; + req.send(); + return false; + } + } + }; + + closer.onclick = function (ev) { + closer.classList.remove('active'); + slider.classList.remove('active'); + return false; + }; +} + +function init_configuration_alert() { + window.onsubmit = function (e) { + window.hasSubmit = true; + }; + window.onbeforeunload = function (e) { + if (window.hasSubmit) { + return; + } + const ds = document.querySelectorAll('[data-leave-validation]'); + for (let i = ds.length - 1; i >= 0; i--) { + const input = ds[i]; + if (input.type === 'checkbox' || input.type === 'radio') { + if (input.checked != input.getAttribute('data-leave-validation')) { + return false; + } + } else if (input.value != input.getAttribute('data-leave-validation')) { + return false; + } + } + }; +} + +function init_extra() { + if (!window.context) { + if (window.console) { + console.log('FreshRSS extra waiting for JS…'); + } + window.setTimeout(init_extra, 50); //Wait for all js to be loaded + return; + } + init_crypto_form(); + init_share_observers(); + init_remove_observers(); + init_feed_observers(); + init_password_observers(); + init_select_observers(); + init_slider_observers(); + init_configuration_alert(); +} + +if (document.readyState && document.readyState !== 'loading') { + init_extra(); +} else { + document.addEventListener('DOMContentLoaded', function () { + if (window.console) { + console.log('FreshRSS extra waiting for DOMContentLoaded…'); + } + init_extra(); + }, false); +} diff --git a/p/scripts/global_view.js b/p/scripts/global_view.js index c5aaa48b1..b1581614a 100644 --- a/p/scripts/global_view.js +++ b/p/scripts/global_view.js @@ -1,6 +1,6 @@ "use strict"; -/* globals init_load_more, init_posts, init_stream */ -/* jshint globalstrict: true */ +/* globals context, init_load_more, init_posts, init_stream */ +/* jshint esversion:6, strict:global */ var panel_loading = false; @@ -11,68 +11,88 @@ function load_panel(link) { panel_loading = true; - $.get(link, function (data) { - $("#panel").append($(".nav_menu, #stream .day, #stream .flux, #stream .pagination, #stream.prompt", data)); - - $("#panel .nav_menu").children().not("#nav_menu_read_all").remove(); - - init_load_more($("#panel")); - init_posts(); - - $("#overlay").fadeIn(); - $("#panel").slideToggle(); - - // force le démarrage du scroll en haut. - // Sans ça, si l'on scroll en lisant une catégorie par exemple, - // en en ouvrant une autre ensuite, on se retrouve au même point de scroll - $("#panel").scrollTop(0); - $(window).scrollTop(0); - - $('#panel').on('click', '#nav_menu_read_all button, #bigMarkAsRead', function () { - console.log($(this).attr("formaction")); - $.ajax({ - type: "POST", - url: $(this).attr("formaction"), - data: { - _csrf: context.csrf, - }, - async: false - }); - window.location.reload(false); - return false; - }); - - panel_loading = false; - }); + const req = new XMLHttpRequest(); + req.open('GET', link, true); + req.responseType = 'document'; + req.onload = function (e) { + if (this.status != 200) { + return; + } + const html = this.response, + foreign = html.querySelectorAll('.nav_menu, #stream .day, #stream .flux, #stream .pagination, #stream.prompt'), + panel = document.getElementById('panel'); + foreign.forEach(function (el) { + panel.appendChild(document.adoptNode(el)); + }); + panel.querySelectorAll('.nav_menu > :not([id="nav_menu_read_all"])').forEach(function (el) { + el.remove(); + }); + + init_load_more(panel); + init_posts(); + + document.getElementById('overlay').classList.add('visible'); + panel.classList.add('visible'); + + // force le démarrage du scroll en haut. + // Sans ça, si l'on scroll en lisant une catégorie par exemple, + // en en ouvrant une autre ensuite, on se retrouve au même point de scroll + panel.scrollTop = 0; + document.documentElement.scrollTop = 0; + + //We already have a click listener in main.js + panel.addEventListener('click', function (ev) { + const b = ev.target.closest('#nav_menu_read_all button, #bigMarkAsRead'); + if (b) { + console.log(b.formAction); + + const req2 = new XMLHttpRequest(); + req2.open('POST', b.formAction, false); + req2.setRequestHeader('Content-Type', 'application/json'); + req2.send(JSON.stringify({ + _csrf: context.csrf, + })); + if (req2.status == 200) { + location.reload(false); + return false; + } + } + }); + + panel_loading = false; + }; + req.send(); } function init_close_panel() { - $("#overlay .close").click(function () { - $("#panel").html(''); - $("#panel").slideToggle(); - $("#overlay").fadeOut(); - - return false; - }); + const panel = document.getElementById('panel'); + document.querySelector('#overlay .close').onclick = function (ev) { + panel.innerHTML = ''; + panel.classList.remove('visible'); + document.getElementById('overlay').classList.remove('visible'); + return false; + }; } function init_global_view() { - // TODO: should be based on generic classes. - $(".box a").click(function () { - var link = $(this).attr("href"); - - load_panel(link); - - return false; - }); + // TODO: should be based on generic classes + document.querySelectorAll('.box a').forEach(function (a) { + a.onclick = function (ev) { + load_panel(a.href); + return false; + }; + }); - $(".nav_menu #nav_menu_read_all, .nav_menu .toggle_aside").remove(); + document.querySelectorAll('.nav_menu #nav_menu_read_all, .nav_menu .toggle_aside').forEach(function (el) { + el.remove(); + }); - init_stream($("#panel")); + const panel = document.getElementById('panel'); + init_stream(panel); } function init_all_global_view() { - if (!(window.$ && window.init_stream)) { + if (!window.context) { if (window.console) { console.log('FreshRSS Global view waiting for JS…'); } @@ -85,7 +105,7 @@ function init_all_global_view() { if (document.readyState && document.readyState !== 'loading') { init_all_global_view(); -} else if (document.addEventListener) { +} else { document.addEventListener('DOMContentLoaded', function () { init_all_global_view(); }, false); diff --git a/p/scripts/main.js b/p/scripts/main.js index 82dc79ce7..345cbe749 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -1,5 +1,4 @@ "use strict"; -/* globals $, jQuery, shortcut */ /* jshint esversion:6, strict:global */ // @@ -16,36 +15,34 @@ if (!Element.prototype.closest) Element.prototype.closest = function (s) { if (!Element.prototype.remove) Element.prototype.remove = function () { if (this.parentNode) this.parentNode.removeChild(this); }; // -// -var context, i18n, icons, shortcuts, urls; +// +function xmlHttpRequestJson(req) { + let json = req.response; + if (req.responseType !== 'json') { //IE11 + try { json = JSON.parse(req.responseText); } + catch (ex) { json = null; } + } + return json; +} +// + +// +var context; (function parseJsonVars() { const jsonVars = document.getElementById('jsonVars'), json = JSON.parse(jsonVars.innerHTML); jsonVars.outerHTML = ''; context = json.context; - i18n = json.i18n; - shortcuts = json.shortcuts; - urls = json.urls; - icons = json.icons; - icons.read = decodeURIComponent(icons.read); - icons.unread = decodeURIComponent(icons.unread); + context.ajax_loading = false; + context.i18n = json.i18n; + context.shortcuts = json.shortcuts; + context.urls = json.urls; + context.icons = json.icons; + context.icons.read = decodeURIComponent(context.icons.read); + context.icons.unread = decodeURIComponent(context.icons.unread); }()); - -var $stream = null, - ajax_loading = false, - $nav_entries = null; -// - -function redirect(url, new_tab) { - if (url) { - if (new_tab) { - window.open(url); - } else { - location.href = url; - } - } -} +// function needsScroll(elem) { const winBottom = document.documentElement.scrollTop + document.documentElement.clientHeight, @@ -160,54 +157,64 @@ var pending_entries = {}, mark_read_queue = []; function send_mark_read_queue(queue, asRead) { - $.ajax({ - type: 'POST', - url: '.?c=entry&a=read' + (asRead ? '' : '&is_read=0'), - data: { - ajax: true, - _csrf: context.csrf, - 'id[]': queue, - }, - }).done(function (data) { - for (let i = queue.length - 1; i >= 0; i--) { - const div = document.getElementById('flux_' + queue[i]), - myIcons = icons; - let inc = 0; - if (div.classList.contains('not_read')) { - div.classList.remove('not_read'); - div.querySelectorAll('a.read').forEach(function (a) { a.setAttribute('href', a.getAttribute('href').replace('&is_read=0', '') + '&is_read=1'); }); - div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.read; }); - inc--; - } else { - div.classList.add('not_read'); - div.classList.add('keep_unread'); //Split for IE11 - div.querySelectorAll('a.read').forEach(function (a) { a.setAttribute('href', a.getAttribute('href').replace('&is_read=1', '')); }); - div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.unread; }); - inc++; + const req = new XMLHttpRequest(); + 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]]; } - let feed_link = div.querySelector('.website > a'); - if (feed_link) { - let feed_url = feed_link.getAttribute('href'); - let feed_id = feed_url.substr(feed_url.lastIndexOf('f_')); - incUnreadsFeed(div, feed_id, inc); + }; + req.onload = function (e) { + if (this.status != 200) { + return req.onerror(e); } - delete pending_entries['flux_' + queue[i]]; - } - faviconNbUnread(); - if (data.tags) { - let tagIds = Object.keys(data.tags); - for (let i = tagIds.length - 1; i >= 0; i--) { - let tagId = tagIds[i]; - incUnreadsTag(tagId, (asRead ? -1 : 1) * data.tags[tagId].length); + const json = xmlHttpRequestJson(this); + for (let i = queue.length - 1; i >= 0; i--) { + const div = document.getElementById('flux_' + queue[i]), + myIcons = context.icons; + let inc = 0; + if (div.classList.contains('not_read')) { + div.classList.remove('not_read'); + div.querySelectorAll('a.read').forEach(function (a) { + a.href = a.href.replace('&is_read=0', '') + '&is_read=1'; + }); + div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.read; }); + inc--; + } else { + div.classList.add('not_read'); + div.classList.add('keep_unread'); //Split for IE11 + div.querySelectorAll('a.read').forEach(function (a) { + a.href = a.href.replace('&is_read=1', ''); + }); + div.querySelectorAll('a.read > .icon').forEach(function (img) { img.outerHTML = myIcons.unread; }); + inc++; + } + let feed_link = div.querySelector('.website > a, a.website'); + if (feed_link) { + let feed_url = feed_link.href; + let feed_id = feed_url.substr(feed_url.lastIndexOf('f_')); + incUnreadsFeed(div, feed_id, inc); + } + delete pending_entries['flux_' + queue[i]]; } - } - onScroll(); - }).fail(function (data) { - openNotification(i18n.notif_request_failed, 'bad'); - for (let i = queue.length - 1; i >= 0; i--) { - delete pending_entries['flux_' + queue[i]]; - } - }); + faviconNbUnread(); + if (json.tags) { + let tagIds = Object.keys(json.tags); + for (let i = tagIds.length - 1; i >= 0; i--) { + let tagId = tagIds[i]; + incUnreadsTag(tagId, (asRead ? -1 : 1) * json.tags[tagId].length); + } + } + onScroll(); + }; + req.setRequestHeader('Content-Type', 'application/json'); + req.send(JSON.stringify({ + ajax: true, + _csrf: context.csrf, + id: queue, + })); } var send_mark_read_queue_timeout = 0; @@ -246,7 +253,7 @@ function mark_favorite(div) { } let a = div.querySelector('a.bookmark'), - url = a ? a.getAttribute('href') : ''; + url = a ? a.href : ''; if (!url) { return false; } @@ -256,45 +263,51 @@ function mark_favorite(div) { } pending_entries[div.id] = true; - $.ajax({ - type: 'POST', - url: url, - data: { - ajax: true, - _csrf: context.csrf, - }, - }).done(function (data) { - let inc = 0; - if (div.classList.contains('favorite')) { - div.classList.remove('favorite'); - inc--; - } else { - div.classList.add('favorite'); - inc++; - } - div.querySelectorAll('a.bookmark').forEach(function (a) { a.setAttribute('href', data.url); }); - div.querySelectorAll('a.bookmark > .icon').forEach(function (img) { img.outerHTML = data.icon; }); + const req = new XMLHttpRequest(); + req.open('POST', url, true); + req.responseType = 'json'; + req.onerror = function (e) { + openNotification(context.i18n.notif_request_failed, 'bad'); + delete pending_entries[div.id]; + }; + req.onload = function (e) { + if (this.status != 200) { + return req.onerror(e); + } + const json = xmlHttpRequestJson(this); + let inc = 0; + if (div.classList.contains('favorite')) { + div.classList.remove('favorite'); + inc--; + } else { + div.classList.add('favorite'); + inc++; + } + div.querySelectorAll('a.bookmark').forEach(function (a) { a.href = json.url; }); + div.querySelectorAll('a.bookmark > .icon').forEach(function (img) { img.outerHTML = json.icon; }); - const favourites = document.querySelector('#aside_feed .favorites .title'); - if (favourites) { - favourites.textContent = favourites.textContent.replace(/((?: \([ 0-9]+\))?\s*)$/, function (m, p1) { - return incLabel(p1, inc, false); - }); - } + const favourites = document.querySelector('#aside_feed .favorites .title'); + if (favourites) { + favourites.textContent = favourites.textContent.replace(/((?: \([ 0-9]+\))?\s*)$/, function (m, p1) { + return incLabel(p1, inc, false); + }); + } - if (div.classList.contains('not_read')) { - const elem = document.querySelector('#aside_feed .favorites .title'), - feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; - if (elem) { - elem.setAttribute('data-unread', numberFormat(feed_unreads + inc)); + if (div.classList.contains('not_read')) { + const elem = document.querySelector('#aside_feed .favorites .title'), + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; + if (elem) { + elem.setAttribute('data-unread', numberFormat(feed_unreads + inc)); + } } - } - delete pending_entries[div.id]; - }).fail(function (data) { - openNotification(i18n.notif_request_failed, 'bad'); - delete pending_entries[div.id]; - }); + delete pending_entries[div.id]; + }; + req.setRequestHeader('Content-Type', 'application/json'); + req.send(JSON.stringify({ + ajax: true, + _csrf: context.csrf, + })); } var freshrssOpenArticleEvent = document.createEvent('Event'); @@ -307,9 +320,9 @@ function toggleContent(new_active, old_active, skipping) { } if (context.does_lazyload && !skipping) { - new_active.querySelectorAll('img[data-original], iframe[data-original]').forEach(function (elem) { - elem.setAttribute('src', elem.getAttribute('data-original')); - elem.removeAttribute('data-original'); + new_active.querySelectorAll('img[data-original], iframe[data-original]').forEach(function (el) { + el.src = el.getAttribute('data-original'); + el.removeAttribute('data-original'); }); } @@ -322,12 +335,12 @@ function toggleContent(new_active, old_active, skipping) { old_active.classList.remove('active'); old_active.classList.remove('current'); //Split for IE11 } - } else { // collapse_entry calls toggleContent(flux_current, flux_current, false) + } else { new_active.classList.toggle('active'); } const relative_move = context.current_view === 'global', - box_to_move = relative_move ? document.getElementById('#panel') : document.documentElement; + box_to_move = relative_move ? document.getElementById('panel') : document.documentElement; if (context.sticky_post) { let prev_article = new_active.previousElementSibling, @@ -355,7 +368,7 @@ function toggleContent(new_active, old_active, skipping) { if (context.auto_mark_article) { mark_read(new_active, true); } - new_active[0].dispatchEvent(freshrssOpenArticleEvent); + new_active.dispatchEvent(freshrssOpenArticleEvent); } onScroll(); } @@ -373,18 +386,28 @@ function next_entry(skipping) { } function prev_feed() { - const $active_feed = $('#aside_feed .tree-folder-items .item.active'); - if ($active_feed.length > 0) { - $active_feed.prevAll(':visible:first').find('a').each(function () { this.click(); }); + const active_feed = document.querySelector('#aside_feed .feed.active'); + if (active_feed) { + let feed = active_feed; + do feed = feed.previousElementSibling; + while (feed && getComputedStyle(feed).display === 'none'); + if (feed) { + feed.querySelector('a.item-title').click(); + } } else { last_feed(); } } function next_feed() { - const $active_feed = $('#aside_feed .tree-folder-items .item.active'); - if ($active_feed.length > 0) { - $active_feed.nextAll(':visible:first').find('a').each(function () { this.click(); }); + const active_feed = document.querySelector('#aside_feed .feed.active'); + if (active_feed) { + let feed = active_feed; + do feed = feed.nextElementSibling; + while (feed && getComputedStyle(feed).display === 'none'); + if (feed) { + feed.querySelector('a.item-title').click(); + } } else { first_feed(); } @@ -405,31 +428,31 @@ function last_feed() { } function prev_category() { - const $active_cat = $('#aside_feed .tree-folder.active'); - - if ($active_cat.length > 0) { - const $prev_cat = $active_cat.prevAll(':visible:first').find('.tree-folder-title .title'); - if ($prev_cat.length > 0) { - $prev_cat[0].click(); + const active_cat = document.querySelector('#aside_feed .category.active'); + if (active_cat) { + let cat = active_cat; + do cat = cat.previousElementSibling; + while (cat && getComputedStyle(cat).display === 'none'); + if (cat) { + cat.querySelector('a.title').click(); } } else { last_category(); } - return; } function next_category() { - const $active_cat = $('#aside_feed .tree-folder.active'); - - if ($active_cat.length > 0) { - const $next_cat = $active_cat.nextAll(':visible:first').find('.tree-folder-title .title'); - if ($next_cat.length > 0) { - $next_cat[0].click(); + const active_cat = document.querySelector('#aside_feed .category.active'); + if (active_cat) { + let cat = active_cat; + do cat = cat.nextElementSibling; + while (cat && getComputedStyle(cat).display === 'none'); + if (cat) { + cat.querySelector('a.title').click(); } } else { first_category(); } - return; } function first_category() { @@ -452,21 +475,21 @@ function collapse_entry() { } function user_filter(key) { - const $filter = $('#dropdown-query'), - $filters = $filter.siblings('.dropdown-menu').find('.item.query a'); + const filter = document.getElementById('dropdown-query'), + filters = filter.parentElement.querySelectorAll('.dropdown-menu > .query > a'); if (typeof key === 'undefined') { - if (!$filters.length) { + if (!filters.length) { return; } // Display the filter div - location.hash = $filters.attr('id'); + location.hash = filter.id; // Force scrolling to the filter div const scroll = needsScroll(document.querySelector('.header')); if (scroll !== 0) { document.documentElement.scrollTop = scroll; } // Force the key value if there is only one action, so we can trigger it automatically - if ($filters.length === 1) { + if (filters.length === 1) { key = 1; } else { return; @@ -474,8 +497,8 @@ function user_filter(key) { } // Trigger selected share action key = parseInt(key); - if (key <= $filters.length) { - $filters[key - 1].click(); + if (key <= filters.length) { + filters[key - 1].click(); } } @@ -576,384 +599,378 @@ function init_column_categories() { return; } - $('#aside_feed').on('click', '.tree-folder>.tree-folder-title>a.dropdown-toggle', function () { - $(this).children().each(function () { - if (this.alt === '▽') { - this.src = this.src.replace('/icons/down.', '/icons/up.'); - this.alt = '△'; + document.getElementById('aside_feed').onclick = function (ev) { + let a = ev.target.closest('.tree-folder > .tree-folder-title > a.dropdown-toggle'); + if (a) { + const img = a.querySelector('img'); + if (img.alt === '▽') { + img.src = img.src.replace('/icons/down.', '/icons/up.'); + img.alt = '△'; } else { - this.src = this.src.replace('/icons/up.', '/icons/down.'); - this.alt = '▽'; + img.src = img.src.replace('/icons/up.', '/icons/down.'); + img.alt = '▽'; } - }); - $(this).parent().next('.tree-folder-items').slideToggle(300, function () { - //Workaround for Gecko bug 1514498 in Firefox 64 - const sidebar = document.getElementById('sidebar'); - if (sidebar && sidebar.scrollHeight > sidebar.clientHeight && //if needs scrollbar - sidebar.scrollWidth >= sidebar.offsetWidth) { //but no scrollbar - sidebar.style['overflow-y'] = 'scroll'; //then force scrollbar - setTimeout(function () { sidebar.style['overflow-y'] = ''; }, 0); + + const ul = a.closest('li').querySelector('.tree-folder-items'); + let nbVisibleItems = 0; + for (let i = ul.children.length - 1; i >= 0; i--) { + if (ul.children[i].offsetHeight) { + nbVisibleItems++; + } } - }); - return false; - }); + ul.classList.toggle('active'); + //CSS transition does not work on max-height:auto + ul.style.maxHeight = ul.classList.contains('active') ? (nbVisibleItems * 4) + 'em' : 0; + return false; + } - $('#aside_feed').on('click', '.tree-folder-items .feed .dropdown-toggle', function () { - const itemId = $(this).closest('.item').attr('id'), - templateId = itemId.substring(0, 2) === 't_' ? 'tag_config_template' : 'feed_config_template', - id = itemId.substr(2), - feed_web = $(this).data('fweb'), - template = $('#' + templateId) - .html().replace(/------/g, id).replace('http://example.net/', feed_web); - if ($(this).next('.dropdown-menu').length === 0) { - $(this).attr('href', '#dropdown-' + id).prev('.dropdown-target').attr('id', 'dropdown-' + id).parent() - .append(template).find('button.confirm').removeAttr('disabled'); - } else { - if ($(this).next('.dropdown-menu').css('display') === 'none') { - const id2 = $(this).closest('.item').attr('id').substr(2); - $(this).attr('href', '#dropdown-' + id2); + a = ev.target.closest('.tree-folder-items > .feed .dropdown-toggle'); + if (a) { + const itemId = a.closest('.item').id, + templateId = itemId.substring(0, 2) === 't_' ? 'tag_config_template' : 'feed_config_template', + id = itemId.substr(2), + feed_web = a.getAttribute('data-fweb'), + div = a.parentElement, + dropdownMenu = div.querySelector('.dropdown-menu'), + template = document.getElementById(templateId) + .innerHTML.replace(/------/g, id).replace('http://example.net/', feed_web); + if (!dropdownMenu) { + a.href = '#dropdown-' + id; + div.querySelector('.dropdown-target').id = 'dropdown-' + id; + div.insertAdjacentHTML('beforeend', template); + div.querySelector('button.confirm').disabled = false; + } else if (getComputedStyle(dropdownMenu).display === 'none') { + const id2 = div.closest('.item').id.substr(2); + a.href = '#dropdown-' + id2; } else { - $(this).attr('href', '#close'); + a.href = '#close'; } + return true; } - }); + + return true; + }; } function init_shortcuts() { - if (!(window.shortcut)) { - if (window.console) { - console.log('FreshRSS waiting for shortcut.js…'); - } - setTimeout(init_shortcuts, 200); - return; - } - // Manipulation shortcuts - shortcut.add(shortcuts.mark_read, function () { - // Toggle the read state - mark_read(document.querySelector('.flux.current'), false); - }, { - 'disable_in_input': true - }); - shortcut.add('shift+' + shortcuts.mark_read, function () { - // Mark everything as read - $('.nav_menu .read_all').click(); - }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.mark_favorite, function () { - // Toggle the favorite state - mark_favorite(document.querySelector('.flux.current')); - }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.collapse_entry, function () { - // Toggle the collapse state - collapse_entry(); - }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.auto_share, function () { - // Display the share options - auto_share(); - }, { - 'disable_in_input': true - }); + Object.keys(context.shortcuts).forEach(function (k) { + context.shortcuts[k] = (context.shortcuts[k] || '').toUpperCase(); + }); - shortcut.add(shortcuts.user_filter, function () { - // Display the user filters - user_filter(); - }, { - 'disable_in_input': true - }); + document.body.onkeydown = function (ev) { + if (ev.target.closest('input, textarea') || + ev.ctrlKey || ev.metaKey || (ev.altKey && ev.shiftKey)) { + return true; + } - function addShortcut(evt) { - if ($('#dropdown-query').siblings('.dropdown-menu').is(':visible')) { - user_filter(String.fromCharCode(evt.keyCode)); - } else { - auto_share(String.fromCharCode(evt.keyCode)); - } - } - for (let i = 1; i < 10; i++) { - shortcut.add(i.toString(), addShortcut, { - 'disable_in_input': true - }); - } + const s = context.shortcuts, + k = ev.key.toUpperCase(); + if (location.hash.match(/^#dropdown-/)) { + const n = parseInt(k); + if (n) { + if (location.hash === '#dropdown-query') { + user_filter(n); + } else { + auto_share(n); + } + return false; + } + } + if (k === s.next_entry) { + if (ev.altKey) { + next_category(); + } else if (ev.shiftKey) { + next_feed(); + } else { + next_entry(false); + } + return false; + } + if (k === s.prev_entry) { + if (ev.altKey) { + prev_category(); + } else if (ev.shiftKey) { + prev_feed(); + } else { + prev_entry(false); + } + return false; + } + if (k === s.mark_read) { + if (ev.altKey) { + return true; + } 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); + } + return false; + } + if (k === s.first_entry) { + if (ev.altKey) { + first_category(); + } else if (ev.shiftKey) { + first_feed(); + } else { + const old_active = document.querySelector('.flux.current'), + first = document.querySelector('.flux'); + if (first.classList.contains('flux')) { + toggleContent(first, old_active, false); + } + } + return false; + } + if (k === s.last_entry) { + if (ev.altKey) { + last_category(); + } else if (ev.shiftKey) { + last_feed(); + } else { + const old_active = document.querySelector('.flux.current'), + last = document.querySelector('.flux:last-of-type'); + if (last.classList.contains('flux')) { + toggleContent(last, old_active, false); + } + } + return false; + } - // Entry navigation shortcuts - shortcut.add(shortcuts.prev_entry, function () { prev_entry(false); }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.skip_prev_entry, function () { prev_entry(true); }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.first_entry, function () { - const $old_active = $('.flux.current'), - $first = $('.flux:first'); + if (ev.altKey || ev.shiftKey) { + return true; + } + if (k === s.mark_favorite) { // Toggle the favorite state + mark_favorite(document.querySelector('.flux.current')); + return false; + } + if (k === s.go_website) { + if (context.auto_mark_site) { + mark_read(document.querySelector('.flux.current'), true); + } + window.open(document.querySelector('.flux.current a.go_website').href); + return false; + } + if (k === s.skip_next_entry) { next_entry(true); return false; } + if (k === s.skip_prev_entry) { prev_entry(true); return false; } + if (k === s.collapse_entry) { collapse_entry(); return false; } + if (k === s.auto_share) { auto_share(); return false; } + if (k === s.user_filter) { user_filter(); return false; } + if (k === s.load_more) { load_more_posts(); return false; } + if (k === s.close_dropdown) { location.hash = null; return false; } + if (k === s.help) { window.open(context.urls.help); return false; } + if (k === s.focus_search) { document.getElementById('search').focus(); return false; } + if (k === s.normal_view) { document.querySelector('#nav_menu_views .view-normal').click(); return false; } + if (k === s.reading_view) { document.querySelector('#nav_menu_views .view-reader').click(); return false; } + if (k === s.global_view) { document.querySelector('#nav_menu_views .view-global').click(); return false; } + if (k === s.rss_view) { document.querySelector('#nav_menu_views .view-rss').click(); return false; } + return true; + }; +} - if ($first.hasClass('flux')) { - toggleContent($first, $old_active, false); +function init_stream(stream) { + stream.onclick = function (ev) { + let el = ev.target.closest('.flux a.read'); + if (el) { + mark_read(el.closest('.flux'), false); + return false; } - }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.next_entry, function () { next_entry(false); }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.skip_next_entry, function () { next_entry(true); }, { - 'disable_in_input': true - }); - shortcut.add(shortcuts.last_entry, function () { - const $old_active = $('.flux.current'), - $last = $('.flux:last'); - if ($last.hasClass('flux')) { - toggleContent($last, $old_active, false); + el = ev.target.closest('.flux a.bookmark'); + if (el) { + mark_favorite(el.closest('.flux')); + return false; } - }, { - 'disable_in_input': true - }); - // Feed navigation shortcuts - shortcut.add('shift+' + shortcuts.prev_entry, prev_feed, { - 'disable_in_input': true - }); - shortcut.add('shift+' + shortcuts.next_entry, next_feed, { - 'disable_in_input': true - }); - shortcut.add('shift+' + shortcuts.first_entry, first_feed, { - 'disable_in_input': true - }); - shortcut.add('shift+' + shortcuts.last_entry, last_feed, { - 'disable_in_input': true - }); - // Category navigation shortcuts - shortcut.add('alt+' + shortcuts.prev_entry, prev_category, { - 'disable_in_input': true - }); - shortcut.add('alt+' + shortcuts.next_entry, next_category, { - 'disable_in_input': true - }); - shortcut.add('alt+' + shortcuts.first_entry, first_category, { - 'disable_in_input': true - }); - shortcut.add('alt+' + shortcuts.last_entry, last_category, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.go_website, function () { - const url_website = $('.flux.current a.go_website').attr('href'); - if (context.auto_mark_site) { - $('.flux.current').each(function () { - mark_read(this, true); - }); + el = ev.target.closest('.dynamictags'); + if (el) { + loadDynamicTags(el); + return true; } - redirect(url_website, true); - }, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.load_more, load_more_posts, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.focus_search, focus_search, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.help, function () { - redirect(urls.help, true); - }, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.close_dropdown, function () { - location.hash = null; - }, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.normal_view, function () { - $('#nav_menu_views .view-normal').get(0).click(); - }, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.global_view, function () { - $('#nav_menu_views .view-global').get(0).click(); - }, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.reading_view, function () { - $('#nav_menu_views .view-reader').get(0).click(); - }, { - 'disable_in_input': true - }); - - shortcut.add(shortcuts.rss_view, function () { - $('#nav_menu_views .view-rss').get(0).click(); - }, { - 'disable_in_input': true - }); -} - -function init_stream(divStream) { - divStream.on('click', '.flux_header,.flux_content', function (e) { //flux_toggle - if ($(e.target).closest('.content, .item.website, .item.link, .dropdown-menu').length > 0) { - return; - } - if (!context.sides_close_article && $(e.target).is('div.flux_content')) { - // setting for not-closing after clicking outside article area - return; + el = ev.target.closest('.item.title > a'); + if (el) { // Allow default control-click behaviour such as open in backround-tab + return ev.ctrlKey; } - const old_active = document.querySelector('.flux.current'), - new_active = this.parentNode; - if (e.target.tagName.toUpperCase() === 'A') { //Leave real links alone - if (context.auto_mark_article) { - mark_read(new_active, true); + + el = ev.target.closest('.flux .content a'); + if (el) { + if (!el.closest('div').classList.contains('author')) { + el.target = '_blank'; + el.rel = 'noreferrer'; } return true; } - toggleContent(new_active, old_active, false); - }); - - divStream.on('click', '.flux a.read', function () { - mark_read(this.closest('.flux'), false); - return false; - }); - divStream.on('click', '.flux a.bookmark', function () { - mark_favorite(this.closest('.flux')); - return false; - }); + el = ev.target.closest('.item.share > a[href="#"]'); + if (el) { //Print + const content = '' + + el.closest('.flux_content').querySelector('.content').innerHTML + + ''; + const tmp_window = window.open(); + tmp_window.document.writeln(content); + tmp_window.document.close(); + tmp_window.focus(); + tmp_window.print(); + tmp_window.close(); + return false; + } - divStream.on('click', '.item.title > a', function (e) { - // Allow default control-click behaviour such as open in backround-tab. - return e.ctrlKey; - }); - divStream.on('mouseup', '.item.title > a', function (e) { - // Mouseup enables us to catch middle click. - if (e.ctrlKey) { - // CTRL+click, it will be manage by previous rule. - return; + el = ev.target.closest('.item.share > a[href="POST"]'); + if (el) { //Share by POST + const f = el.parentElement.querySelector('form'); + f.disabled = false; + f.submit(); + return false; } - if (e.which == 2) { - // If middle click, we want same behaviour as CTRL+click. - const ev = jQuery.Event('click'); - ev.ctrlKey = true; - $(this).trigger(ev); - } else if (e.which == 1) { - // Normal click, just toggle article. - $(this).parent().click(); + el = ev.target.closest('.flux_header, .flux_content'); + if (el) { //flux_toggle + if (ev.target.closest('.content, .item.website, .item.link, .dropdown-menu')) { + return true; + } + if (!context.sides_close_article && ev.target.matches('div.flux_content')) { + // setting for not-closing after clicking outside article area + return false; + } + const old_active = document.querySelector('.flux.current'), + new_active = el.parentNode; + if (ev.target.tagName.toUpperCase() === 'A') { //Leave real links alone + if (context.auto_mark_article) { + mark_read(new_active, true); + } + return true; + } + toggleContent(new_active, old_active, false); + return false; } - }); + }; - divStream.on('click', '.flux .content a', function () { - if (!$(this).closest('div').hasClass('author')) { - $(this).attr('target', '_blank').attr('rel', 'noreferrer'); + stream.onmouseup = function (ev) { // Mouseup enables us to catch middle click + 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 (context.auto_mark_site) { - // catch mouseup instead of click so we can have the correct behaviour - // with middle button click (scroll button). - divStream.on('mouseup', '.flux .link > a', function (e) { - if (e.which == 3) { - return; + if (context.auto_mark_site) { + // catch mouseup instead of click so we can have the correct behaviour + // with middle button click (scroll button). + el = ev.target.closest('.flux .link > a'); + if (el) { + if (ev.which == 3) { + return; + } + mark_read(el.closest('.flux'), true); } + } + }; - mark_read(this.closest('.flux'), true); - }); - } + stream.onchange = function (ev) { + const checkboxTag = ev.target.closest('.checkboxTag'); + if (checkboxTag) { //Dynamic tags + ev.stopPropagation(); + const isChecked = checkboxTag.checked, + tagId = checkboxTag.name.replace(/^t_/, ''), + tagName = checkboxTag.nextElementSibling ? checkboxTag.nextElementSibling.value : '', + entry = checkboxTag.closest('div.flux'), + entryId = entry.id.replace(/^flux_/, ''); + checkboxTag.disabled = true; + + const req = new XMLHttpRequest(); + req.open('POST', './?c=tag&a=tagEntry', true); + req.responseType = 'json'; + req.onerror = function (e) { + checkboxTag.checked = !isChecked; + }; + req.onload = function (e) { + if (this.status != 200) { + return req.onerror(e); + } + if (entry.classList.contains('not_read')) { + incUnreadsTag('t_' + tagId, isChecked ? 1 : -1); + } + }; + req.onloadend = function (e) { + checkboxTag.disabled = false; + if (tagId == 0) { + loadDynamicTags(checkboxTag.closest('div.dropdown')); + } + }; + req.setRequestHeader('Content-Type', 'application/json'); + req.send(JSON.stringify({ + _csrf: context.csrf, + id_tag: tagId, + name_tag: tagId == 0 ? tagName : '', + id_entry: entryId, + checked: isChecked, + })); + } + }; } function init_nav_entries() { - $nav_entries = $('#nav_entries'); - $nav_entries.find('.previous_entry').click(function () { - prev_entry(false); - return false; - }); - $nav_entries.find('.next_entry').click(function () { - next_entry(false); - return false; - }); - $nav_entries.find('.up').click(function () { - const $active_item = $('.flux.current'), - windowTop = $(window).scrollTop(), - item_top = $active_item.offset().top; + const nav_entries = document.getElementById('nav_entries'); + if (nav_entries) { + nav_entries.querySelector('.previous_entry').onclick = function (e) { + prev_entry(false); + return false; + }; + nav_entries.querySelector('.next_entry').onclick = function (e) { + next_entry(false); + return false; + }; + nav_entries.querySelector('.up').onclick = function (e) { + const active_item = document.querySelector('.flux.current'), + windowTop = document.documentElement.scrollTop, + item_top = active_item.offsetParent.offsetTop + active_item.offsetTop; - if (windowTop > item_top) { - $('html,body').scrollTop(item_top); - } else { - $('html,body').scrollTop(0); - } - return false; - }); + document.documentElement.scrollTop = windowTop > item_top ? item_top : 0; + return false; + }; + } } -function loadDynamicTags($div) { - $div.removeClass('dynamictags'); - $div.find('li.item').remove(); - const entryId = $div.closest('div.flux').attr('id').replace(/^flux_/, ''); - $.getJSON('./?c=tag&a=getTagsForEntry&id_entry=' + entryId) - .done(function (data) { - const $ul = $div.find('.dropdown-menu'); - $ul.append('
      • '); - if (data && data.length) { - for (let i = 0; i < data.length; i++) { - const tag = data[i]; - $ul.append('
      • '); - } +function loadDynamicTags(div) { + div.classList.remove('dynamictags'); + div.querySelectorAll('li.item').forEach(function (li) { li.remove(); }); + const entryId = div.closest('div.flux').id.replace(/^flux_/, ''); + + const req = new XMLHttpRequest(); + req.open('GET', './?c=tag&a=getTagsForEntry&id_entry=' + entryId, true); + req.responseType = 'json'; + req.onerror = function (e) { + div.querySelectorAll('li.item').forEach(function (li) { li.remove(); }); + div.classList.add('dynamictags'); + }; + req.onload = function (e) { + if (this.status != 200) { + return req.onerror(e); } - }) - .fail(function () { - $div.find('li.item').remove(); - $div.addClass('dynamictags'); - }); -} - -function init_dynamic_tags() { - $stream.on('click', '.dynamictags', function () { - loadDynamicTags($(this)); - }); - - $stream.on('change', '.checkboxTag', function (ev) { - const $checkbox = $(this), - isChecked = $checkbox.prop('checked'), - tagId = $checkbox.attr('name').replace(/^t_/, ''), - tagName = $checkbox.siblings('input[name]').val(), - $entry = $checkbox.closest('div.flux'), - entryId = $entry.attr('id').replace(/^flux_/, ''); - $checkbox.prop('disabled', true); - $.ajax({ - type: 'POST', - url: './?c=tag&a=tagEntry', - data: { - _csrf: context.csrf, - id_tag: tagId, - name_tag: tagId == 0 ? tagName : '', - id_entry: entryId, - checked: isChecked, - }, - }) - .done(function () { - if ($entry.hasClass('not_read')) { - incUnreadsTag('t_' + tagId, isChecked ? 1 : -1); - } - }) - .fail(function () { - $checkbox.prop('checked', !isChecked); - }) - .always(function () { - $checkbox.prop('disabled', false); - if (tagId == 0) { - loadDynamicTags($checkbox.closest('div.dropdown')); + const json = xmlHttpRequestJson(this); + let html = '
      • '; + if (json && json.length) { + for (let i = 0; i < json.length; i++) { + const tag = json[i]; + html += '
      • '; } - }); - }); + } + div.querySelector('.dropdown-menu').insertAdjacentHTML('beforeend', html); + }; + req.send(); } // @@ -964,86 +981,93 @@ function updateFeed(feeds, feeds_count) { if (!feed) { return; } - $.ajax({ - type: 'POST', - url: feed.url, - data: { - _csrf: context.csrf, - noCommit: 1, - }, - }).always(function (data) { - feed_processed++; - $('#actualizeProgress .progress').html(feed_processed + ' / ' + feeds_count); - $('#actualizeProgress .title').html(feed.title); - - if (feed_processed === feeds_count) { - $.ajax({ //Empty request to commit new articles - type: 'POST', - url: './?c=feed&a=actualize&id=-1&ajax=1', - data: { + const req = new XMLHttpRequest(); + req.open('POST', feed.url, true); + req.onloadend = function (e) { + feed_processed++; + const div = document.getElementById('actualizeProgress'); + div.querySelector('.progress').innerHTML = feed_processed + ' / ' + feeds_count; + div.querySelector('.title').innerHTML = feed.title; + if (feed_processed === feeds_count) { + //Empty request to commit new articles + const req2 = new XMLHttpRequest(); + req2.open('POST', './?c=feed&a=actualize&id=-1&ajax=1', true); + req2.onloadend = function (e) { + location.reload(); + }; + req2.setRequestHeader('Content-Type', 'application/json'); + req2.send(JSON.stringify({ _csrf: context.csrf, noCommit: 0, - }, - }).always(function (data) { - location.reload(); - }); - } else { - updateFeed(feeds, feeds_count); - } - }); + })); + } else { + updateFeed(feeds, feeds_count); + } + }; + req.setRequestHeader('Content-Type', 'application/json'); + req.send(JSON.stringify({ + _csrf: context.csrf, + noCommit: 1, + })); } function init_actualize() { let auto = false; - $('#actualize').click(function () { - if (ajax_loading) { + document.getElementById('actualize').onclick = function () { + if (context.ajax_loading) { return false; } - ajax_loading = true; - - $.getJSON('./?c=javascript&a=actualize').done(function (data) { - if (auto && data.feeds.length < 1) { - auto = false; - ajax_loading = false; - return false; - } - if (data.feeds.length === 0) { - openNotification(data.feedback_no_refresh, 'good'); - $.ajax({ //Empty request to force refresh server database cache - type: 'POST', - url: './?c=feed&a=actualize&id=-1&ajax=1', - data: { - _csrf: context.csrf, - noCommit: 0, - }, - }).always(function (data) { - ajax_loading = false; - }); - return; - } - //Progress bar - const feeds_count = data.feeds.length; - $('body').after('
        ' + data.feedback_actualize + - '
        /
        0 / ' + feeds_count + - '
        '); - for (let i = 10; i > 0; i--) { - updateFeed(data.feeds, feeds_count); - } - }); + context.ajax_loading = true; + + const req = new XMLHttpRequest(); + req.open('GET', './?c=javascript&a=actualize', true); + req.responseType = 'json'; + req.onload = function (e) { + const json = xmlHttpRequestJson(this); + if (auto && json.feeds.length < 1) { + auto = false; + context.ajax_loading = false; + return false; + } + if (json.feeds.length === 0) { + openNotification(json.feedback_no_refresh, 'good'); + //Empty request to commit new articles + const req2 = new XMLHttpRequest(); + req2.open('POST', './?c=feed&a=actualize&id=-1&ajax=1', true); + req2.onloadend = function (e) { + context.ajax_loading = false; + }; + req2.setRequestHeader('Content-Type', 'application/json'); + req2.send(JSON.stringify({ + _csrf: context.csrf, + noCommit: 0, + })); + return; + } + //Progress bar + const feeds_count = json.feeds.length; + document.body.insertAdjacentHTML('beforeend', '
        ' + + json.feedback_actualize + '
        /
        0 / ' + + feeds_count + '
        '); + for (let i = 10; i > 0; i--) { + updateFeed(json.feeds, feeds_count); + } + }; + req.send(); return false; - }); + }; if (context.auto_actualize_feeds) { auto = true; - $('#actualize').click(); + document.getElementById('actualize').click(); } } //
        // -var $notification = null, +var notification = null, notification_interval = null, notification_working = false; @@ -1051,37 +1075,29 @@ function openNotification(msg, status) { if (notification_working === true) { return false; } - notification_working = true; - - $notification.removeClass(); - $notification.addClass('notification'); - $notification.addClass(status); - $notification.find('.msg').html(msg); - $notification.fadeIn(300); + notification.querySelector('.msg').innerHTML = msg; + notification.className = 'notification'; + notification.classList.add(status); notification_interval = setTimeout(closeNotification, 4000); } function closeNotification() { - $notification.fadeOut(600, function () { - $notification.removeClass(); - $notification.addClass('closed'); - - clearInterval(notification_interval); - notification_working = false; - }); + notification.classList.add('closed'); + clearInterval(notification_interval); + notification_working = false; } function init_notifications() { - $notification = $('#notification'); + notification = document.getElementById('notification'); - $notification.find('a.close').click(function () { - closeNotification(); - return false; - }); + notification.querySelector('a.close').onclick = function () { + closeNotification(); + return false; + }; - if ($notification.find('.msg').html().length > 0) { + if (notification.querySelector('.msg').innerHTML.length > 0) { notification_working = true; notification_interval = setTimeout(closeNotification, 4000); } @@ -1106,9 +1122,9 @@ function notifs_html5_show(nb) { return; } - const notification = new window.Notification(i18n.notif_title_articles, { + const notification = new window.Notification(context.i18n.notif_title_articles, { icon: '../themes/icons/favicon-256.png', - body: i18n.notif_body_articles.replace('%d', nb), + body: context.i18n.notif_body_articles.replace('%d', nb), tag: 'freshRssNewArticles', }); @@ -1135,40 +1151,56 @@ function init_notifs_html5() { // function refreshUnreads() { - $.getJSON('./?c=javascript&a=nbUnreadsPerFeed').done(function (data) { - const isAll = document.querySelector('.category.all.active'); - let new_articles = false; - - $.each(data.feeds, function (feed_id, nbUnreads) { - feed_id = 'f_' + feed_id; - const elem = document.getElementById(feed_id), - feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; - - if ((incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads) || isAll) && //Update of current view? - (nbUnreads - feed_unreads > 0)) { - $('#new-article').attr('aria-hidden', 'false').show(); - new_articles = true; - } - }); + const req = new XMLHttpRequest(); + req.open('GET', './?c=javascript&a=nbUnreadsPerFeed', true); + req.responseType = 'json'; + req.onload = function (e) { + const json = xmlHttpRequestJson(this); + const isAll = document.querySelector('.category.all.active'); + let new_articles = false; + + Object.keys(json.feeds).forEach(function (feed_id) { + const nbUnreads = json.feeds[feed_id]; + feed_id = 'f_' + feed_id; + const elem = document.getElementById(feed_id), + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; + + if ((incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads) || isAll) && //Update of current view? + (nbUnreads - feed_unreads > 0)) { + const newArticle = document.getElementById('new-article'); + newArticle.setAttribute('aria-hidden', 'false'); + newArticle.style.display = 'block'; + new_articles = true; + } + }); - let nbUnreadTags = 0; + let nbUnreadTags = 0; - $.each(data.tags, function (tag_id, nbUnreads) { - nbUnreadTags += nbUnreads; - $('#t_' + tag_id).attr('data-unread', nbUnreads) - .children('.item-title').attr('data-unread', numberFormat(nbUnreads)); - }); + Object.keys(json.tags).forEach(function (tag_id) { + const nbUnreads = json.tags[tag_id]; + nbUnreadTags += nbUnreads; + const tag = document.getElementById('t_' + tag_id); + if (tag) { + tag.setAttribute('data-unread', nbUnreads); + tag.querySelector('.item-title').setAttribute('data-unread', numberFormat(nbUnreads)); + } + }); - $('.category.tags').attr('data-unread', nbUnreadTags) - .find('.title').attr('data-unread', numberFormat(nbUnreadTags)); + const tags = document.querySelector('.category.tags'); + if (tags) { + tags.setAttribute('data-unread', nbUnreadTags); + tags.querySelector('.title').setAttribute('data-unread', numberFormat(nbUnreadTags)); + } - const nb_unreads = str2int($('.category.all .title').attr('data-unread')); + const title = document.querySelector('.category.all .title'), + nb_unreads = title ? str2int(title.getAttribute('data-unread')) : 0; - if (nb_unreads > 0 && new_articles) { - faviconNbUnread(nb_unreads); - notifs_html5_show(nb_unreads); - } - }); + if (nb_unreads > 0 && new_articles) { + faviconNbUnread(nb_unreads); + notifs_html5_show(nb_unreads); + } + }; + req.send(); } // @@ -1177,49 +1209,59 @@ var url_load_more = '', box_load_more = null; function load_more_posts() { - if (load_more || url_load_more === '' || box_load_more === null) { + if (load_more || !url_load_more || !box_load_more) { return; } - load_more = true; document.getElementById('load_more').classList.add('loading'); - $.get(url_load_more, function (data) { - box_load_more.children('.flux:last').after($('#stream', data).children('.flux, .day')); - $('.pagination').replaceWith($('.pagination', data)); - if (context.display_order === 'ASC') { - $('#nav_menu_read_all .read_all').attr( - 'formaction', $('#bigMarkAsRead').attr('formaction') - ); - } else { - $('#bigMarkAsRead').attr( - 'formaction', $('#nav_menu_read_all .read_all').attr('formaction') - ); - } - $('[id^=day_]').each(function (i) { - const ids = $('[id="' + this.id + '"]'); - if (ids.length > 1) { - $('[id="' + this.id + '"]:gt(0)').remove(); + const req = new XMLHttpRequest(); + req.open('GET', url_load_more, true); + req.responseType = 'document'; + req.onload = function (e) { + const html = this.response, + formPagination = document.getElementById('mark-read-pagination'); + + const streamAdopted = document.adoptNode(html.getElementById('stream')); + streamAdopted.querySelectorAll('.flux, .day').forEach(function (div) { + box_load_more.insertBefore(div, formPagination); + }); + + const paginationOld = formPagination.querySelector('.pagination'), + 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) { + bigMarkAsRead.formAction = document.querySelector('#nav_menu_read_all .read_all').formAction; + } } - }); - init_load_more(box_load_more); + document.querySelectorAll('[id^=day_]').forEach(function (div) { + const ids = document.querySelectorAll('[id="' + div.id + '"]'); + for (let i = ids.length - 1; i > 0; i--) { //Keep only the first + ids[i].remove(); + } + }); - const bigMarkAsRead = document.getElementById('bigMarkAsRead'), - div_load_more = document.getElementById('load_more'); - if (bigMarkAsRead) { - bigMarkAsRead.removeAttribute('disabled'); - } - if (div_load_more) { - div_load_more.classList.remove('loading'); - } + init_load_more(box_load_more); - load_more = false; - }); -} + const bigMarkAsRead = document.getElementById('bigMarkAsRead'), + div_load_more = document.getElementById('load_more'); + if (bigMarkAsRead) { + bigMarkAsRead.removeAttribute('disabled'); + } + if (div_load_more) { + div_load_more.classList.remove('loading'); + } -function focus_search() { - $('#search').focus(); + load_more = false; + }; + req.send(); } var freshrssLoadMoreEvent = document.createEvent('Event'); @@ -1229,193 +1271,40 @@ function init_load_more(box) { box_load_more = box; document.body.dispatchEvent(freshrssLoadMoreEvent); - const $next_link = $('#load_more'); - if (!$next_link.length) { + const next_link = document.getElementById('load_more'); + if (!next_link) { // no more article to load url_load_more = ''; return; } - url_load_more = $next_link.attr('href'); + url_load_more = next_link.href; - $next_link.click(function () { - load_more_posts(); - return false; - }); + next_link.onclick = function (e) { + load_more_posts(); + return false; + }; } // -// -function poormanSalt() { //If crypto.getRandomValues is not available - const base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; - let text = '$2a$04$'; - for (let i = 22; i > 0; i--) { - text += base.charAt(Math.floor(Math.random() * 64)); - } - return text; -} - -function init_crypto_form() { - /* globals dcodeIO */ - const $crypto_form = $('#crypto-form'); - if ($crypto_form.length === 0) { - return; - } - - if (!(window.dcodeIO)) { - if (window.console) { - console.log('FreshRSS waiting for bcrypt.js…'); - } - setTimeout(init_crypto_form, 100); - return; - } - - $crypto_form.on('submit', function () { - const $submit_button = $(this).find('button[type="submit"]'); - $submit_button.attr('disabled', ''); - - let success = false; - $.ajax({ - url: './?c=javascript&a=nonce&user=' + $('#username').val(), - dataType: 'json', - async: false - }).done(function (data) { - if (!data.salt1 || !data.nonce) { - openNotification('Invalid user!', 'bad'); - } else { - try { - const strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), - s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1), - c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? dcodeIO.bcrypt.genSaltSync(4) : poormanSalt()); - $('#challenge').val(c); - if (!s || !c) { - openNotification('Crypto error!', 'bad'); - } else { - success = true; - } - } catch (e) { - openNotification('Crypto exception! ' + e, 'bad'); +function init_confirm_action() { + document.body.onclick = function (ev) { + const b = ev.target.closest('.confirm'); + if (b) { + let str_confirmation = this.getAttribute('data-str-confirm'); + if (!str_confirmation) { + str_confirmation = context.i18n.confirmation_default; } + return confirm(str_confirmation); } - }).fail(function () { - openNotification('Communication error!', 'bad'); - }); - - $submit_button.removeAttr('disabled'); - return success; - }); -} -// - -function init_confirm_action() { - $('body').on('click', '.confirm', function () { - let str_confirmation = $(this).attr('data-str-confirm'); - if (!str_confirmation) { - str_confirmation = i18n.confirmation_default; - } - - return confirm(str_confirmation); - }); - $('button.confirm').removeAttr('disabled'); -} - -function init_print_action() { - $('.item.share > a[href="#"]').click(function (e) { - const content = '' + - $(e.target).closest('.flux_content').find('.content').html() + - ''; - - const tmp_window = window.open(); - tmp_window.document.writeln(content); - tmp_window.document.close(); - tmp_window.focus(); - tmp_window.print(); - tmp_window.close(); - - return false; - }); -} - -function init_post_action() { - $('.item.share > a[href="POST"]').click(function (e) { - e.preventDefault(); - const $form = $(this).next('form'); - $.post($form.data('url'), $form.serialize()); - }); -} - -var shares = 0; - -function init_share_observers() { - shares = $('.group-share').length; - - $('.share.add').on('click', function (e) { - const $opt = $(this).siblings('select').find(':selected'); - let row = $(this).parents('form').data($opt.data('form')); - row = row.replace(/##label##/g, $opt.html().trim()); - row = row.replace(/##type##/g, $opt.val()); - row = row.replace(/##help##/g, $opt.data('help')); - row = row.replace(/##key##/g, shares); - row = row.replace(/##method##/g, $opt.data('method')); - row = row.replace(/##field##/g, $opt.data('field')); - $(this).parents('.form-group').before(row); - shares++; - - return false; - }); -} - -function init_stats_observers() { - $('.select-change').on('change', function (e) { - redirect($(this).find(':selected').data('url')); - }); -} - -function init_remove_observers() { - $('.post').on('click', 'a.remove', function (e) { - const remove_what = $(this).attr('data-remove'); - if (remove_what !== undefined) { - $('#' + remove_what).remove(); - } - return false; - }); -} - -function init_feed_observers() { - $('select[id="category"]').on('change', function () { - const $detail = $('#new_category_name').parent(); - if ($(this).val() === 'nc') { - $detail.attr('aria-hidden', 'false').show(); - $detail.find('input').focus(); - } else { - $detail.attr('aria-hidden', 'true').hide(); - } - }); -} - -function init_password_observers() { - $('.toggle-password').on('mousedown', function (e) { - const $button = $(this), - $passwordField = $('#' + $button.attr('data-toggle')); - $passwordField.attr('type', 'text'); - $button.addClass('active'); - return false; - }).on('mouseup', function (e) { - const $button = $(this), - $passwordField = $('#' + $button.attr('data-toggle')); - $passwordField.attr('type', 'password'); - $button.removeClass('active'); - return false; - }); + }; + document.querySelectorAll('button.confirm').forEach(function (b) { b.disabled = false; }); } function faviconNbUnread(n) { if (typeof n === 'undefined') { - n = str2int($('.category.all .title').attr('data-unread')); + const t = document.querySelector('.category.all .title'); + n = t ? str2int(t.getAttribute('data-unread')) : 0; } //http://remysharp.com/2010/08/24/dynamic-favicons/ const canvas = document.createElement('canvas'), @@ -1442,79 +1331,16 @@ function faviconNbUnread(n) { ctx.fillText(text, 0, canvas.height - 1); } link.href = canvas.toDataURL('image/png'); - $('link[rel~=icon]').remove(); + document.querySelector('link[rel~=icon]').remove(); document.head.appendChild(link); }; img.src = '../favicon.ico'; } } -function init_slider_observers() { - const $slider = $('#slider'), - $closer = $('#close-slider'); - if ($slider.length < 1) { - return; - } - - $('.post').on('click', '.open-slider', function () { - if (ajax_loading) { - return false; - } - - ajax_loading = true; - - $.ajax({ - type: 'GET', - url: $(this).attr('href'), - data: { ajax: true } - }).done(function (data) { - $slider.html(data); - $closer.addClass('active'); - $slider.addClass('active'); - ajax_loading = false; - }); - - return false; - }); - - $closer.on('click', function () { - $closer.removeClass('active'); - $slider.removeClass('active'); - return false; - }); -} - -function init_configuration_alert() { - $(window).on('submit', function (e) { - window.hasSubmit = true; - }); - $(window).on('beforeunload', function (e) { - if (window.hasSubmit) { - return; - } - const inputs = document.querySelectorAll('[data-leave-validation]'); - for (let i = inputs.length - 1; i >= 0; i--) { - const input = inputs[i]; - if (input.type === 'checkbox' || input.type === 'radio') { - if (input.checked != input.getAttribute('data-leave-validation')) { - return false; - } - } else if (input.value != input.getAttribute('data-leave-validation')) { - return false; - } - } - }); -} - -function init_subscription() { - $('body').on('click', '.bookmarkClick', function (e) { - return false; - }); -} - function init_normal() { - $stream = $('#stream'); - if ($stream.length < 1) { + const stream = document.getElementById('stream'); + if (!stream) { if (window.console) { console.log('FreshRSS waiting for content…'); } @@ -1522,59 +1348,32 @@ function init_normal() { return; } init_column_categories(); - init_stream($stream); + init_stream(stream); init_shortcuts(); init_actualize(); faviconNbUnread(); } function init_beforeDOM() { - if (!window.$) { - if (window.console) { - console.log('FreshRSS waiting for jQuery…'); - } - setTimeout(init_beforeDOM, 100); - return; - } if (['normal', 'reader', 'global'].indexOf(context.current_view) >= 0) { init_normal(); } } function init_afterDOM() { - if (!window.$) { - if (window.console) { - console.log('FreshRSS waiting again for jQuery…'); - } - setTimeout(init_afterDOM, 100); - return; - } init_notifications(); init_confirm_action(); - $stream = $('#stream'); - if ($stream.length > 0) { - init_load_more($stream); + const stream = document.getElementById('stream'); + if (stream) { + init_load_more(stream); init_posts(); init_nav_entries(); - init_dynamic_tags(); - init_print_action(); - init_post_action(); init_notifs_html5(); setInterval(refreshUnreads, 120000); - } else { - init_subscription(); - init_crypto_form(); - init_share_observers(); - init_remove_observers(); - init_feed_observers(); - init_password_observers(); - init_stats_observers(); - init_slider_observers(); - init_configuration_alert(); } if (window.console) { - console.log('FreshRSS init done.'); + console.log('FreshRSS main init done.'); } } @@ -1582,11 +1381,11 @@ init_beforeDOM(); //Can be called before DOM is fully loaded if (document.readyState && document.readyState !== 'loading') { init_afterDOM(); -} else if (document.addEventListener) { +} else { document.addEventListener('DOMContentLoaded', function () { - if (window.console) { - console.log('FreshRSS waiting for DOMContentLoaded…'); - } - init_afterDOM(); - }, false); + if (window.console) { + console.log('FreshRSS waiting for DOMContentLoaded…'); + } + init_afterDOM(); + }, false); } diff --git a/p/scripts/repartition.js b/p/scripts/repartition.js index be70456fa..e71fa71c4 100644 --- a/p/scripts/repartition.js +++ b/p/scripts/repartition.js @@ -1,6 +1,6 @@ "use strict"; /* globals Flotr, numberFormat */ -/* jshint globalstrict: true */ +/* jshint esversion:6, strict:global */ function initStats() { if (!window.Flotr) { @@ -10,7 +10,7 @@ function initStats() { window.setTimeout(initStats, 50); return; } - var jsonRepartition = document.getElementById('jsonRepartition'), + const jsonRepartition = document.getElementById('jsonRepartition'), stats = JSON.parse(jsonRepartition.innerHTML); jsonRepartition.outerHTML = ''; // Entry per hour diff --git a/p/scripts/shortcut.js b/p/scripts/shortcut.js deleted file mode 100644 index e78cf6f5e..000000000 --- a/p/scripts/shortcut.js +++ /dev/null @@ -1,225 +0,0 @@ -/** - * http://www.openjs.com/scripts/events/keyboard_shortcuts/ - * Version : 2.01.B - * By Binny V A - * License : BSD - */ -shortcut = { - 'all_shortcuts':{},//All the shortcuts are stored in this array - 'add': function(shortcut_combination,callback,opt) { - //Provide a set of default options - var default_options = { - 'type':'keydown', - 'propagate':false, - 'disable_in_input':false, - 'target':document, - 'keycode':false - } - if(!opt) opt = default_options; - else { - for(var dfo in default_options) { - if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo]; - } - } - - var ele = opt.target; - if(typeof opt.target == 'string') ele = document.getElementById(opt.target); - var ths = this; - shortcut_combination = shortcut_combination.toLowerCase(); - - //The function to be called at keypress - var func = function(e) { - e = e || window.event; - - if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields - var element; - if(e.target) element=e.target; - else if(e.srcElement) element=e.srcElement; - if(element.nodeType==3) element=element.parentNode; - - if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return; - } - - //Find Which key is pressed - if (e.keyCode) code = e.keyCode; - else if (e.which) code = e.which; - if (code == 32 || (code >= 48 && code <= 90) || (code >= 96 && code <= 111) || (code >= 186 && code <= 192) || (code >= 219 && code <= 222)) { //FreshRSS - var character = String.fromCharCode(code).toLowerCase(); - } - - if(code == 188) character=","; //If the user presses , when the type is onkeydown - if(code == 190) character="."; //If the user presses , when the type is onkeydown - - var keys = shortcut_combination.split("+"); - //Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked - var kp = 0; - - //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken - var shift_nums = { - "`":"~", - "1":"!", - "2":"@", - "3":"#", - "4":"$", - "5":"%", - "6":"^", - "7":"&", - "8":"*", - "9":"(", - "0":")", - "-":"_", - "=":"+", - ";":":", - "'":"\"", - ",":"<", - ".":">", - "/":"?", - "\\":"|" - } - //Special Keys - and their codes - var special_keys = { - 'esc':27, - 'escape':27, - 'tab':9, - 'space':32, - 'return':13, - 'enter':13, - 'backspace':8, - - 'scrolllock':145, - 'scroll_lock':145, - 'scroll':145, - 'capslock':20, - 'caps_lock':20, - 'caps':20, - 'numlock':144, - 'num_lock':144, - 'num':144, - - 'pause':19, - 'break':19, - - 'insert':45, - 'home':36, - 'delete':46, - 'end':35, - - 'pageup':33, - 'page_up':33, - 'pu':33, - - 'pagedown':34, - 'page_down':34, - 'pd':34, - - 'left':37, - 'up':38, - 'right':39, - 'down':40, - - 'f1':112, - 'f2':113, - 'f3':114, - 'f4':115, - 'f5':116, - 'f6':117, - 'f7':118, - 'f8':119, - 'f9':120, - 'f10':121, - 'f11':122, - 'f12':123 - } - - var modifiers = { - shift: { wanted:false, pressed:false}, - ctrl : { wanted:false, pressed:false}, - alt : { wanted:false, pressed:false}, - meta : { wanted:false, pressed:false} //Meta is Mac specific - }; - - if(e.ctrlKey) modifiers.ctrl.pressed = true; - if(e.shiftKey) modifiers.shift.pressed = true; - if(e.altKey) modifiers.alt.pressed = true; - if(e.metaKey) modifiers.meta.pressed = true; - - for(var i=0; k=keys[i],i 1) { //If it is a special key - if(special_keys[k] == code) kp++; - - } else if(opt['keycode']) { - if(opt['keycode'] == code) kp++; - - } else { //The special keys did not match - if(character == k) kp++; - else { - if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase - character = shift_nums[character]; - if(character == k) kp++; - } - } - } - } - - if(kp == keys.length && - modifiers.ctrl.pressed == modifiers.ctrl.wanted && - modifiers.shift.pressed == modifiers.shift.wanted && - modifiers.alt.pressed == modifiers.alt.wanted && - modifiers.meta.pressed == modifiers.meta.wanted) { - callback(e); - - if(!opt['propagate']) { //Stop the event - //e.cancelBubble is supported by IE - this will kill the bubbling process. - e.cancelBubble = true; - e.returnValue = false; - - //e.stopPropagation works in Firefox. - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - return false; - } - } - } - this.all_shortcuts[shortcut_combination] = { - 'callback':func, - 'target':ele, - 'event': opt['type'] - }; - //Attach the function with the event - if(ele.addEventListener) ele.addEventListener(opt['type'], func, false); - else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func); - else ele['on'+opt['type']] = func; - }, - - //Remove the shortcut - just specify the shortcut and I will remove the binding - 'remove':function(shortcut_combination) { - shortcut_combination = shortcut_combination.toLowerCase(); - var binding = this.all_shortcuts[shortcut_combination]; - delete(this.all_shortcuts[shortcut_combination]) - if(!binding) return; - var type = binding['event']; - var ele = binding['target']; - var callback = binding['callback']; - - if(ele.detachEvent) ele.detachEvent('on'+type, callback); - else if(ele.removeEventListener) ele.removeEventListener(type, callback, false); - else ele['on'+type] = false; - } -} \ No newline at end of file diff --git a/p/scripts/stats.js b/p/scripts/stats.js index 9cd14721c..b47188d77 100644 --- a/p/scripts/stats.js +++ b/p/scripts/stats.js @@ -1,6 +1,6 @@ "use strict"; /* globals Flotr, numberFormat */ -/* jshint globalstrict: true */ +/* jshint esversion:6, strict:global */ function initStats() { if (!window.Flotr) { @@ -10,12 +10,12 @@ function initStats() { window.setTimeout(initStats, 50); return; } - var jsonStats = document.getElementById('jsonStats'), + const jsonStats = document.getElementById('jsonStats'), stats = JSON.parse(jsonStats.innerHTML); jsonStats.outerHTML = ''; // Entry per day - var avg = []; - for (var i = -31; i <= 0; i++) { + const avg = []; + for (let i = -31; i <= 0; i++) { avg.push([i, stats.average]); } Flotr.draw(document.getElementById('statsEntryPerDay'), diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css index 099aee916..2ea058786 100644 --- a/p/themes/base-theme/template.css +++ b/p/themes/base-theme/template.css @@ -280,7 +280,7 @@ a.btn { left: 0; right: 0; display: block; z-index: -10; - cursor: default; + cursor: default; } .separator { display: block; @@ -418,8 +418,10 @@ a.btn { } .tree-folder-items { - padding: 0; list-style: none; + max-height: 200em; + padding: 0; + transition: max-height .3s linear; } .tree-folder-title { display: block; @@ -502,7 +504,8 @@ a.btn { padding: 0px 15px; } .aside_feed .tree-folder-items:not(.active) { - display: none; + max-height: 0; + overflow: hidden; } .aside_feed .tree-folder-items .dropdown { vertical-align: top; @@ -632,9 +635,13 @@ br + br + br { z-index: 10; background: #fff; border: 1px solid #aaa; + opacity: 1; + visibility: visible; + transition: visibility 0s, opacity .3s linear; } .notification.closed { - display: none; + opacity: 0; + visibility: hidden; } .notification a.close { position: absolute; @@ -710,15 +717,15 @@ br + br + br { /*=== LOGIN VIEW */ /*================*/ .formLogin .header > .item { - padding: 10px 30px; + padding: 10px 30px; } .formLogin .header > .item.title { - text-align: left; + text-align: left; } .formLogin .header > .item.configure { - text-align: right; + text-align: right; } @@ -731,14 +738,29 @@ br + br + br { #stream.global .box { text-align: left; } - +#global > #panel { + bottom: 99vh; + display: block; + transition: visibility .3s, bottom .3s; + visibility: hidden; +} +#global > #panel.visible { + bottom: 1em; + visibility: visible; +} /*=== Panel */ #overlay { - display: none; position: fixed; top: 0; bottom: 0; left: 0; right: 0; background: rgba(0, 0, 0, 0.9); + opacity: 0; + transition: visibility .3s, opacity .3s; + visibility: hidden; +} +#overlay.visible { + opacity: 1; + visibility: visible; } #panel { display: none; -- cgit v1.2.3 From b869c2944a01c5060d05a093d5e0c797d48bb159 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 23 Feb 2019 14:39:20 +0100 Subject: JavaScript fixes + new navigation loop behaviour (#2255) * Fixed user configuration 404 https://github.com/FreshRSS/FreshRSS/pull/2234#issuecomment-466561555 * Fixed "SPACE" shortcut bug https://github.com/FreshRSS/FreshRSS/pull/2234#issuecomment-466626412 * Use next feed / previous feed when reaching last / first article instead of looping * Jump to next / previous category when reaching last / first feed instead of looping --- app/views/user/manage.phtml | 2 +- p/scripts/extra.js | 10 ++++++-- p/scripts/main.js | 62 +++++++++++++++++++++++++++++++-------------- 3 files changed, 52 insertions(+), 22 deletions(-) (limited to 'app/views') diff --git a/app/views/user/manage.phtml b/app/views/user/manage.phtml index 9d457f7a5..d0e5928ef 100644 --- a/app/views/user/manage.phtml +++ b/app/views/user/manage.phtml @@ -53,7 +53,7 @@
        - diff --git a/p/scripts/extra.js b/p/scripts/extra.js index 7a0ef0477..c0d0c89e1 100644 --- a/p/scripts/extra.js +++ b/p/scripts/extra.js @@ -142,8 +142,14 @@ function init_password_observers() { function init_select_observers() { document.querySelectorAll('.select-change').forEach(function (s) { s.onchange = function (ev) { - const opt = s.options[s.selectedIndex]; - location.href = opt.getAttribute('data-url'); + const opt = s.options[s.selectedIndex], + url = opt.getAttribute('data-url'); + if (url) { + s.form.querySelectorAll('[type=submit]').forEach(function (b) { + b.disabled = true; + }); + location.href = url; + } }; }); } diff --git a/p/scripts/main.js b/p/scripts/main.js index 345cbe749..90a41d767 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -374,41 +374,65 @@ function toggleContent(new_active, old_active, skipping) { } function prev_entry(skipping) { - const old_active = document.querySelector('.flux.current'), - new_active = old_active ? old_active.previousElementSibling : document.querySelector('.flux'); + const old_active = document.querySelector('.flux.current'); + let new_active = old_active; + if (new_active) { + do new_active = new_active.previousElementSibling; + while (new_active && !new_active.classList.contains('flux')); + if (!new_active) { + prev_feed(); + } + } else { + new_active = document.querySelector('.flux'); + } toggleContent(new_active, old_active, skipping); } function next_entry(skipping) { - const old_active = document.querySelector('.flux.current'), - new_active = old_active ? old_active.nextElementSibling : document.querySelector('.flux'); + const old_active = document.querySelector('.flux.current'); + let new_active = old_active; + if (new_active) { + do new_active = new_active.nextElementSibling; + while (new_active && !new_active.classList.contains('flux')); + if (!new_active) { + next_feed(); + } + } else { + new_active = document.querySelector('.flux'); + } toggleContent(new_active, old_active, skipping); } function prev_feed() { - const active_feed = document.querySelector('#aside_feed .feed.active'); - if (active_feed) { - let feed = active_feed; - do feed = feed.previousElementSibling; - while (feed && getComputedStyle(feed).display === 'none'); - if (feed) { + let found = false; + const feeds = document.querySelectorAll('#aside_feed .feed'); + for (let i = feeds.length - 1; i >= 0; i--) { + const feed = feeds[i]; + if (found && getComputedStyle(feed).display !== 'none') { feed.querySelector('a.item-title').click(); + break; + } else if (feed.classList.contains('active')) { + found = true; } - } else { + } + if (!found) { last_feed(); } } function next_feed() { - const active_feed = document.querySelector('#aside_feed .feed.active'); - if (active_feed) { - let feed = active_feed; - do feed = feed.nextElementSibling; - while (feed && getComputedStyle(feed).display === 'none'); - if (feed) { + let found = false; + const feeds = document.querySelectorAll('#aside_feed .feed'); + for (let i = 0; i < feeds.length; i++) { + const feed = feeds[i]; + if (found && getComputedStyle(feed).display !== 'none') { feed.querySelector('a.item-title').click(); + break; + } else if (feed.classList.contains('active')) { + found = true; } - } else { + } + if (!found) { first_feed(); } } @@ -664,7 +688,7 @@ function init_shortcuts() { } const s = context.shortcuts, - k = ev.key.toUpperCase(); + k = (ev.key.trim() || ev.code).toUpperCase(); if (location.hash.match(/^#dropdown-/)) { const n = parseInt(k); if (n) { -- cgit v1.2.3 From 1804c0e0bc095487b9a1ad13cbc9f55f6cef2a2a Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 23 Mar 2019 22:52:47 +0100 Subject: Filter actions (#2275) * Draft of filter actions * Travis * Implement UI + finish logic * Travis --- app/Controllers/feedController.php | 27 +++---- app/Controllers/subscriptionController.php | 4 +- app/Models/Entry.php | 113 +++++++++++++++++++++++++++++ app/Models/Feed.php | 104 ++++++++++++++++++++++++++ app/Models/FilterAction.php | 45 ++++++++++++ app/i18n/cz/sub.php | 4 + app/i18n/de/sub.php | 4 + app/i18n/en/sub.php | 4 + app/i18n/es/sub.php | 4 + app/i18n/fr/sub.php | 4 + app/i18n/he/sub.php | 4 + app/i18n/it/sub.php | 4 + app/i18n/kr/sub.php | 4 + app/i18n/nl/sub.php | 4 + app/i18n/oc/sub.php | 4 + app/i18n/pt-br/sub.php | 4 + app/i18n/ru/sub.php | 4 + app/i18n/tr/sub.php | 4 + app/i18n/zh-cn/sub.php | 4 + app/views/helpers/feed/update.phtml | 13 ++++ 20 files changed, 345 insertions(+), 17 deletions(-) create mode 100644 app/Models/FilterAction.php (limited to 'app/views') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 7f36e0388..0aed9b0a1 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -289,7 +289,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } $ttl = $feed->ttl(); if ((!$simplePiePush) && (!$feed_id) && - ($feed->lastUpdate() + 10 >= time() - ($ttl == FreshRSS_Feed::TTL_DEFAULT ? FreshRSS_Context::$user_conf->ttl_default : $ttl))) { + ($feed->lastUpdate() + 10 >= time() - ( + $ttl == FreshRSS_Feed::TTL_DEFAULT ? FreshRSS_Context::$user_conf->ttl_default : $ttl))) { //Too early to refresh from source, but check whether the feed was updated by another user $mtime = $feed->cacheModifiedTime(); if ($feed->lastUpdate() + 10 >= $mtime) { @@ -347,8 +348,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $entry_date = $entry->date(true); if (isset($existingHashForGuids[$entry->guid()])) { $existingHash = $existingHashForGuids[$entry->guid()]; - if (strcasecmp($existingHash, $entry->hash()) === 0 || trim($existingHash, '0') == '') { - //This entry already exists and is unchanged. TODO: Remove the test with the zero'ed hash in FreshRSS v1.3 + if (strcasecmp($existingHash, $entry->hash()) === 0) { + //This entry already exists and is unchanged. $oldGuids[] = $entry->guid(); } else { //This entry already exists but has been updated //Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->url(false) . @@ -374,17 +375,14 @@ class FreshRSS_feed_Controller extends Minz_ActionController { // This entry should not be added considering configuration and date. $oldGuids[] = $entry->guid(); } else { - $read_upon_reception = $feed->attributes('read_upon_reception') !== null ? ( - $feed->attributes('read_upon_reception') - ) : FreshRSS_Context::$user_conf->mark_when['reception']; $id = uTimeString(); $entry->_id($id); if ($entry_date < $date_min) { $entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read - } else { - $entry->_isRead($read_upon_reception); } + $entry->applyFilterActions(); + $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if ($entry === null) { // An extension has returned a null value, there is nothing to insert. @@ -392,7 +390,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } if ($pubSubHubbubEnabled && !$simplePiePush) { //We use push, but have discovered an article by pull! - $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . ' GUID ' . $entry->guid(); + $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . + ' GUID ' . $entry->guid(); Minz_Log::warning($text, PSHB_LOG); Minz_Log::warning($text); $pubSubHubbubEnabled = false; @@ -416,9 +415,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $entryDAO->beginTransaction(); } - $nb = $entryDAO->cleanOldEntries($feed->id(), - $date_min, - max($feed_history, count($entries) + 10)); + $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, max($feed_history, count($entries) + 10)); if ($nb > 0) { $needFeedCacheRefresh = true; Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']'); @@ -598,11 +595,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController { if (self::moveFeed($feed_id, $cat_id)) { // TODO: return something useful // Log a notice to prevent "Empty IF statement" warning in PHP_CodeSniffer - Minz_Log::notice('Moved feed `' . $feed_id . '` ' . - 'in the category `' . $cat_id . '`');; + Minz_Log::notice('Moved feed `' . $feed_id . '` in the category `' . $cat_id . '`'); } else { - Minz_Log::warning('Cannot move feed `' . $feed_id . '` ' . - 'in the category `' . $cat_id . '`'); + Minz_Log::warning('Cannot move feed `' . $feed_id . '` in the category `' . $cat_id . '`'); Minz_Error::error(404); } } diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php index 62fb3d384..9cf41ed0b 100644 --- a/app/Controllers/subscriptionController.php +++ b/app/Controllers/subscriptionController.php @@ -110,6 +110,8 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { $feed->_attributes('timeout', null); } + $feed->_filtersAction('read', preg_split('/[\n\r]+/', Minz_Request::param('filteractions_read', ''))); + $values = array( 'name' => Minz_Request::param('name', ''), 'description' => sanitizeHTML(Minz_Request::param('description', '', true)), @@ -121,7 +123,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController { 'httpAuth' => $httpAuth, 'keep_history' => intval(Minz_Request::param('keep_history', FreshRSS_Feed::KEEP_HISTORY_DEFAULT)), 'ttl' => $ttl * ($mute ? -1 : 1), - 'attributes' => $feed->attributes() + 'attributes' => $feed->attributes(), ); invalidateHttpCache(); diff --git a/app/Models/Entry.php b/app/Models/Entry.php index f2f3d08fe..3bb977283 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -185,6 +185,119 @@ class FreshRSS_Entry extends Minz_Model { $this->tags = $value; } + public function matches($booleanSearch) { + if (!$booleanSearch || count($booleanSearch->searches()) <= 0) { + return true; + } + foreach ($booleanSearch->searches() as $filter) { + $ok = true; + if ($ok && $filter->getMinPubdate()) { + $ok &= $this->date >= $filter->getMinPubdate(); + } + if ($ok && $filter->getMaxPubdate()) { + $ok &= $this->date <= $filter->getMaxPubdate(); + } + if ($ok && $filter->getMinDate()) { + $ok &= strnatcmp($this->id, $filter->getMinDate() . '000000') >= 0; + } + if ($ok && $filter->getMaxDate()) { + $ok &= strnatcmp($this->id, $filter->getMaxDate() . '000000') <= 0; + } + if ($ok && $filter->getInurl()) { + foreach ($filter->getInurl() as $url) { + $ok &= stripos($this->link, $url) !== false; + } + } + if ($ok && $filter->getNotInurl()) { + foreach ($filter->getNotInurl() as $url) { + $ok &= stripos($this->link, $url) === false; + } + } + if ($ok && $filter->getAuthor()) { + foreach ($filter->getAuthor() as $author) { + $ok &= stripos($this->authors, $author) !== false; + } + } + if ($ok && $filter->getNotAuthor()) { + foreach ($filter->getNotAuthor() as $author) { + $ok &= stripos($this->authors, $author) === false; + } + } + if ($ok && $filter->getIntitle()) { + foreach ($filter->getIntitle() as $title) { + $ok &= stripos($this->title, $title) !== false; + } + } + if ($ok && $filter->getNotIntitle()) { + foreach ($filter->getNotIntitle() as $title) { + $ok &= stripos($this->title, $title) === false; + } + } + if ($ok && $filter->getTags()) { + foreach ($filter->getTags() as $tag2) { + $found = false; + foreach ($this->tags as $tag1) { + if (strcasecmp($tag1, $tag2) === 0) { + $found = true; + } + } + $ok &= $found; + } + } + if ($ok && $filter->getNotTags()) { + foreach ($filter->getNotTags() as $tag2) { + $found = false; + foreach ($this->tags as $tag1) { + if (strcasecmp($tag1, $tag2) === 0) { + $found = true; + } + } + $ok &= !$found; + } + } + if ($ok && $filter->getSearch()) { + foreach ($filter->getSearch() as $needle) { + $ok &= (stripos($this->title, $needle) !== false || stripos($this->content, $needle) !== false); + } + } + if ($ok && $filter->getNotSearch()) { + foreach ($filter->getNotSearch() as $needle) { + $ok &= (stripos($this->title, $needle) === false && stripos($this->content, $needle) === false); + } + } + if ($ok) { + return true; + } + } + return false; + } + + public function applyFilterActions() { + if ($this->feed != null) { + if ($this->feed->attributes('read_upon_reception') || + ($this->feed->attributes('read_upon_reception') === null && FreshRSS_Context::$user_conf->mark_when['reception'])) { + $this->_isRead(true); + } + foreach ($this->feed->filterActions() as $filterAction) { + if ($this->matches($filterAction->booleanSearch())) { + foreach ($filterAction->actions() as $action => $params) { + switch ($action) { + case 'read': + $this->_isRead(true); + break; + case 'star': + $this->_is_favorite(true); + break; + case 'label': + //TODO: Implement more actions + break; + } + } + } + } + } + } + public function isDay($day, $today) { $date = $this->dateAdded(true); switch ($day) { diff --git a/app/Models/Feed.php b/app/Models/Feed.php index b21a8bbbe..89989236c 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -32,6 +32,7 @@ class FreshRSS_Feed extends Minz_Model { private $lockPath = ''; private $hubUrl = ''; private $selfUrl = ''; + private $filterActions = null; public function __construct($url, $validate = true) { if ($validate) { @@ -498,6 +499,109 @@ class FreshRSS_Feed extends Minz_Model { @unlink($this->lockPath); } + public function filterActions() { + if ($this->filterActions == null) { + $this->filterActions = array(); + $filters = $this->attributes('filters'); + if (is_array($filters)) { + foreach ($filters as $filter) { + $filterAction = FreshRSS_FilterAction::fromJSON($filter); + if ($filterAction != null) { + $this->filterActions[] = $filterAction; + } + } + } + } + return $this->filterActions; + } + + private function _filterActions($filterActions) { + $this->filterActions = $filterActions; + if (is_array($this->filterActions) && !empty($this->filterActions)) { + $this->_attributes('filters', array_map(function ($af) { + return $af == null ? null : $af->toJSON(); + }, $this->filterActions)); + } else { + $this->_attributes('filters', null); + } + } + + public function filtersAction($action) { + $action = trim($action); + if ($action == '') { + return array(); + } + $filters = array(); + $filterActions = $this->filterActions(); + for ($i = count($filterActions) - 1; $i >= 0; $i--) { + $filterAction = $filterActions[$i]; + if ($filterAction != null && $filterAction->booleanSearch() != null && + $filterAction->actions() != null && in_array($action, $filterAction->actions(), true)) { + $filters[] = $filterAction->booleanSearch(); + } + } + return $filters; + } + + public function _filtersAction($action, $filters) { + $action = trim($action); + if ($action == '' || !is_array($filters)) { + return false; + } + $filters = array_unique(array_map('trim', $filters)); + $filterActions = $this->filterActions(); + + //Check existing filters + for ($i = count($filterActions) - 1; $i >= 0; $i--) { + $filterAction = $filterActions[$i]; + if ($filterAction == null || !is_array($filterAction->actions()) || + $filterAction->booleanSearch() == null || trim($filterAction->booleanSearch()->getRawInput()) == '') { + array_splice($filterAction, $i, 1); + continue; + } + $actions = $filterAction->actions(); + //Remove existing rules with same action + for ($j = count($actions) - 1; $j >= 0; $j--) { + if ($actions[$j] === $action) { + array_splice($actions, $j, 1); + } + } + //Update existing filter with new action + for ($k = count($filters) - 1; $k >= 0; $k --) { + $filter = $filters[$k]; + if ($filter === $filterAction->booleanSearch()->getRawInput()) { + $actions[] = $action; + array_splice($filters, $k, 1); + } + } + //Save result + if (empty($actions)) { + array_splice($filterActions, $i, 1); + } else { + $filterAction->_actions($actions); + } + } + + //Add new filters + for ($k = count($filters) - 1; $k >= 0; $k --) { + $filter = $filters[$k]; + if ($filter != '') { + $filterAction = FreshRSS_FilterAction::fromJSON(array( + 'search' => $filter, + 'actions' => array($action), + )); + if ($filterAction != null) { + $filterActions[] = $filterAction; + } + } + } + + if (empty($filterActions)) { + $filterActions = null; + } + $this->_filterActions($filterActions); + } + // public function pubSubHubbubEnabled() { diff --git a/app/Models/FilterAction.php b/app/Models/FilterAction.php new file mode 100644 index 000000000..23a45d14e --- /dev/null +++ b/app/Models/FilterAction.php @@ -0,0 +1,45 @@ +booleanSearch = $booleanSearch; + $this->_actions($actions); + } + + public function booleanSearch() { + return $this->booleanSearch; + } + + public function actions() { + return $this->actions; + } + + public function _actions($actions) { + if (is_array($actions)) { + $this->actions = array_unique($actions); + } else { + $this->actions = null; + } + } + + public function toJSON() { + if (is_array($this->actions) && $this->booleanSearch != null) { + return array( + 'search' => $this->booleanSearch->getRawInput(), + 'actions' => $this->actions, + ); + } + return ''; + } + + public static function fromJSON($json) { + if (!empty($json['search']) && !empty($json['actions']) && is_array($json['actions'])) { + return new FreshRSS_FilterAction(new FreshRSS_BooleanSearch($json['search']), $json['actions']); + } + return null; + } +} diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php index 5b5634fed..2e81c928d 100644 --- a/app/i18n/cz/sub.php +++ b/app/i18n/cz/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Popis', 'empty' => 'Kanál je prázdný. Ověřte prosím zda je ještě autorem udržován.', 'error' => 'Vyskytl se problém s kanálem. Ověřte že je vždy dostupný, prosím, a poté jej aktualizujte.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Informace', 'keep_history' => 'Zachovat tento minimální počet článků', 'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do %s.', diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index 27e893177..bd050671e 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Beschreibung', 'empty' => 'Dieser Feed ist leer. Bitte stellen Sie sicher, dass er noch gepflegt wird.', 'error' => 'Dieser Feed ist auf ein Problem gestoßen. Bitte stellen Sie sicher, dass er immer lesbar ist und aktualisieren Sie ihn dann.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Information', 'keep_history' => 'Minimale Anzahl an Artikeln, die behalten wird', 'moved_category_deleted' => 'Wenn Sie eine Kategorie entfernen, werden deren Feeds automatisch in die Kategorie %s eingefügt.', diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php index 4efd81ba4..f11eb9b99 100644 --- a/app/i18n/en/sub.php +++ b/app/i18n/en/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Description', 'empty' => 'This feed is empty. Please verify that it is still maintained.', 'error' => 'This feed has encountered a problem. Please verify that it is always reachable then update it.', + 'filteractions' => array( + '_' => 'Filter actions', + 'help' => 'Write one search filter per line.', + ), 'informations' => 'Information', 'keep_history' => 'Minimum number of articles to keep', 'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under %s.', diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php index 854984891..c0526106f 100755 --- a/app/i18n/es/sub.php +++ b/app/i18n/es/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Descripción', 'empty' => 'La fuente está vacía. Por favor, verifica que siga activa.', 'error' => 'Hay un problema con esta fuente. Por favor, veritica que esté disponible y prueba de nuevo.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Información', 'keep_history' => 'Número mínimo de artículos a conservar', 'moved_category_deleted' => 'Al borrar una categoría todas sus fuentes pasan automáticamente a la categoría %s.', diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php index d9964ac6e..b71019faa 100644 --- a/app/i18n/fr/sub.php +++ b/app/i18n/fr/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Description', 'empty' => 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.', 'error' => 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.', + 'filteractions' => array( + '_' => 'Filtres d’action', + 'help' => 'Écrivez une recherche par ligne.', + ), 'informations' => 'Informations', 'keep_history' => 'Nombre minimum d’articles à conserver', 'moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans %s.', diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php index 6d824e349..bb2025bc3 100644 --- a/app/i18n/he/sub.php +++ b/app/i18n/he/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'תיאור', 'empty' => 'הזנה זו ריקה. אנא ודאו שהיא עדיין מתוחזקת.', 'error' => 'הזנה זו נתקלה בשגיאה, אנא ודאו שהיא תקינה ואז נסו שנית.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'מידע', 'keep_history' => 'מסםר מינימלי של מאמרים לשמור', 'moved_category_deleted' => 'כאשר הקטגוריה נמחקת ההזנות שבתוכה אוטומטית מקוטלגות תחת %s.', diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php index ff7fa6f1d..bf279e059 100644 --- a/app/i18n/it/sub.php +++ b/app/i18n/it/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Descrizione', 'empty' => 'Questo feed non contiene articoli. Per favore verifica il sito direttamente.', 'error' => 'Questo feed ha generato un errore. Per favore verifica se ancora disponibile.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Informazioni', 'keep_history' => 'Numero minimo di articoli da mantenere', 'moved_category_deleted' => 'Cancellando una categoria i feed al suo interno verranno classificati automaticamente come %s.', diff --git a/app/i18n/kr/sub.php b/app/i18n/kr/sub.php index 9edd85818..151775c1c 100644 --- a/app/i18n/kr/sub.php +++ b/app/i18n/kr/sub.php @@ -33,6 +33,10 @@ return array( 'description' => '설명', 'empty' => '이 피드는 비어있습니다. 피드가 계속 운영되고 있는지 확인하세요.', 'error' => '이 피드에 문제가 발생했습니다. 이 피드에 접근 권한이 있는지 확인하세요.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => '정보', 'keep_history' => '최소 유지 글 개수', 'moved_category_deleted' => '카테고리를 삭제하면, 해당 카테고리 아래에 있던 피드들은 자동적으로 %s 아래로 분류됩니다.', diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php index 1d6c9f806..8ba9c020d 100644 --- a/app/i18n/nl/sub.php +++ b/app/i18n/nl/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Omschrijving', 'empty' => 'Deze feed is leeg. Controleer of deze nog actueel is.', 'error' => 'Deze feed heeft problemen. Verifieer a.u.b het doeladres en actualiseer het.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Informatie', 'keep_history' => 'Minimum aantal artikelen om te houden', 'moved_category_deleted' => 'Als u een categorie verwijderd, worden de feeds automatisch geclassificeerd onder %s.', diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php index fc5a0cc1f..51ee0d43f 100644 --- a/app/i18n/oc/sub.php +++ b/app/i18n/oc/sub.php @@ -32,6 +32,10 @@ return array( 'description' => 'Descripcion', 'empty' => 'Aqueste flux es void. Assegurats-vos qu’es totjorn mantengut.', 'error' => 'Aqueste flux a rescontrat un problèma. Volgatz verificar que siá totjorn accessible puèi actualizatz-lo.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Informacions', 'keep_history' => 'Nombre minimum d’articles de servar', 'moved_category_deleted' => 'Quand escafatz una categoria, sos fluxes son automaticament classats dins %s.', diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php index 58b2fc1f9..fc26e89e7 100644 --- a/app/i18n/pt-br/sub.php +++ b/app/i18n/pt-br/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Descrição', 'empty' => 'Este feed está vazio. Por favor verifique ele ainda é mantido.', 'error' => 'Este feed encontra-se com problema. Por favor verifique se ele ainda está disponível e atualize-o.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Informações', 'keep_history' => 'Número mínimo de artigos para manter', 'moved_category_deleted' => 'Quando você deleta uma categoria, seus feeds são automaticamente classificados como %s.', diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php index 62f8a8e3a..e125d549e 100644 --- a/app/i18n/ru/sub.php +++ b/app/i18n/ru/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Description', //TODO - Translation 'empty' => 'This feed is empty. Please verify that it is still maintained.', //TODO - Translation 'error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.', //TODO - Translation + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Information', //TODO - Translation 'keep_history' => 'Minimum number of articles to keep', //TODO - Translation 'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under %s.', //TODO - Translation diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php index 7f29633be..9f4945c0a 100644 --- a/app/i18n/tr/sub.php +++ b/app/i18n/tr/sub.php @@ -33,6 +33,10 @@ return array( 'description' => 'Tanım', 'empty' => 'Bu akış boş. Lütfen akışın aktif olduğuna emin olun.', 'error' => 'Bu akışda bir hatayla karşılaşıldı. Lütfen akışın sürekli ulaşılabilir olduğuna emin olun.', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => 'Bilgi', 'keep_history' => 'En az tutulacak makale sayısı', 'moved_category_deleted' => 'Bir kategoriyi silerseniz, içerisindeki akışlar %s içerisine yerleşir.', diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php index f84d08cdf..90f9fd942 100644 --- a/app/i18n/zh-cn/sub.php +++ b/app/i18n/zh-cn/sub.php @@ -33,6 +33,10 @@ return array( 'description' => '描述', 'empty' => '此源为空。请确认它是否正常更新。', 'error' => '此源遇到一些问题。请在确认是否能正常访问后重试。', + 'filteractions' => array( + '_' => 'Filter actions', //TODO - Translation + 'help' => 'Write one search filter per line.', //TODO - Translation + ), 'informations' => '信息', 'keep_history' => '至少保存的文章数', 'moved_category_deleted' => '删除分类时,其中的 RSS 源会自动归类到 %s', diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml index bc90ba456..be8034c0d 100644 --- a/app/views/helpers/feed/update.phtml +++ b/app/views/helpers/feed/update.phtml @@ -234,6 +234,19 @@
        + +
        + +
        + + +
        +
        +
        -- cgit v1.2.3