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/Controllers/entryController.php | 13 ++++----- app/Models/EntryDAO.php | 2 +- app/Models/TagDAO.php | 48 +++++++++++++++++++++------------ app/views/entry/read.phtml | 10 ------- app/views/helpers/javascript_vars.phtml | 3 ++- 5 files changed, 41 insertions(+), 35 deletions(-) (limited to 'app') diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index fc0af0639..9c5ee2616 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -97,14 +97,15 @@ class FreshRSS_entry_Controller extends Minz_ActionController { } } } else { - $entryDAO->markRead($id, $is_read); - + $ids = is_array($id) ? $id : array($id); + $entryDAO->markRead($ids, $is_read); $tagDAO = FreshRSS_Factory::createTagDao(); - foreach ($tagDAO->getTagsForEntry($id) as $tag) { - if (!empty($tag['checked'])) { - $this->view->tags[] = $tag['id']; - } + $tagsForEntries = $tagDAO->getTagsForEntries($ids); + $tags = array(); + foreach ($tagsForEntries as $line) { + $tags['t_' . $line['id_tag']][] = $line['id_entry']; } + $this->view->tags = $tags; } if (!$this->ajax) { diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 6d77a33cd..9ae1ed797 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -383,7 +383,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { */ public function markRead($ids, $is_read = true) { FreshRSS_UserDAO::touch(); - if (is_array($ids)) { //Many IDs at once (used by API) + if (is_array($ids)) { //Many IDs at once if (count($ids) < 6) { //Speed heuristics $affected = 0; foreach ($ids as $id) { diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index b55d2b35d..0b4428f17 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -256,42 +256,56 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } - //For API - public function getEntryIdsTagNames($entries) { - $sql = 'SELECT et.id_entry, t.name ' + public function getTagsForEntries($entries) { + $sql = 'SELECT et.id_entry, et.id_tag, t.name ' . 'FROM `' . $this->prefix . 'tag` t ' . 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id'; $values = array(); if (is_array($entries) && count($entries) > 0) { $sql .= ' AND et.id_entry IN (' . str_repeat('?,', count($entries) - 1). '?)'; - foreach ($entries as $entry) { - $values[] = is_array($entry) ? $entry['id'] : $entry->id(); + if (is_array($entries[0])) { + foreach ($entries as $entry) { + $values[] = $entry['id']; + } + } elseif (is_object($entries[0])) { + foreach ($entries as $entry) { + $values[] = $entry->id(); + } + } else { + foreach ($entries as $entry) { + $values[] = $entry; + } } } $stm = $this->bd->prepare($sql); if ($stm && $stm->execute($values)) { - $result = array(); - foreach ($stm->fetchAll(PDO::FETCH_ASSOC) as $line) { - $entryId = 'e_' . $line['id_entry']; - $tagName = $line['name']; - if (empty($result[$entryId])) { - $result[$entryId] = array(); - } - $result[$entryId][] = $tagName; - } - return $result; + return $stm->fetchAll(PDO::FETCH_ASSOC); } else { $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); if ($this->autoUpdateDb($info)) { - return $this->getTagNamesEntryIds($id_entry); + return $this->getTagsForEntries($entries); } - Minz_Log::error('SQL error getTagNamesEntryIds: ' . $info[2]); + Minz_Log::error('SQL error getTagsForEntries: ' . $info[2]); return false; } } + //For API + public function getEntryIdsTagNames($entries) { + $result = array(); + foreach ($this->getTagsForEntries($entries) as $line) { + $entryId = 'e_' . $line['id_entry']; + $tagName = $line['name']; + if (empty($result[$entryId])) { + $result[$entryId] = array(); + } + $result[$entryId][] = $tagName; + } + return $result; + } + public static function daoToTag($listDAO) { $list = array(); if (!is_array($listDAO)) { 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') 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 9cc72c0c4860c96d9d82310e4bb76a9233fd2961 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 3 Feb 2019 21:24:06 +0100 Subject: Fix EntryDAO tags warning for Fever API https://github.com/FreshRSS/FreshRSS/issues/2239 --- app/Models/EntryDAO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 21f17c097..93d1183c9 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -1065,7 +1065,7 @@ SQL; $dao['date'], $dao['is_read'], $dao['is_favorite'], - $dao['tags'] + isset($dao['tags']) ? $dao['tags'] : '' ); if (isset($dao['id'])) { $entry->_id($dao['id']); -- cgit v1.2.3 From 3b3accf6ce5d29449ee4d556b6277d92a1830680 Mon Sep 17 00:00:00 2001 From: Seokseong Jeon <6747391+gsongsong@users.noreply.github.com> Date: Tue, 5 Feb 2019 23:05:40 +0900 Subject: i18n Korean (#2242) --- app/i18n/kr/admin.php | 8 ++++---- app/i18n/kr/conf.php | 6 +++--- app/i18n/kr/feedback.php | 4 ++-- app/i18n/kr/gen.php | 12 ++++++------ app/i18n/kr/index.php | 6 +++--- app/i18n/kr/install.php | 4 ++-- app/i18n/kr/sub.php | 10 +++++----- cli/i18n/ignore/kr.php | 16 ++++++++++++++++ 8 files changed, 41 insertions(+), 25 deletions(-) (limited to 'app') diff --git a/app/i18n/kr/admin.php b/app/i18n/kr/admin.php index 532fe30a5..6312bd3fe 100644 --- a/app/i18n/kr/admin.php +++ b/app/i18n/kr/admin.php @@ -67,8 +67,8 @@ return array( 'ok' => 'JSON 확장 기능이 설치되어 있습니다.', ), 'mbstring' => array( - 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation - 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation + 'nok' => '유니코드 지원을 위한 mbstring 라이브러리를 찾을 수 없습니다.', + 'ok' => '유니코드 지원을 위한 mbstring 라이브러리가 설치되어 있습니다.', ), 'minz' => array( 'nok' => 'Minz 프레임워크를 찾을 수 없습니다.', @@ -163,8 +163,8 @@ return array( 'max-categories' => '사용자별 카테고리 개수 제한', 'max-feeds' => '사용자별 피드 개수 제한', 'cookie-duration' => array( - 'help' => 'in seconds', // @todo translate - 'number' => 'Duration to keep logged in', // @todo translate + 'help' => '초', + 'number' => '로그인 유지 시간', ), 'registration' => array( 'help' => '0: 제한 없음', diff --git a/app/i18n/kr/conf.php b/app/i18n/kr/conf.php index 5c3d95d17..11a8494c5 100644 --- a/app/i18n/kr/conf.php +++ b/app/i18n/kr/conf.php @@ -162,7 +162,7 @@ return array( 'mark_read' => '읽음으로 표시', 'navigation' => '탐색', 'navigation_help' => '"Shift" 키를 누른 상태에선 탐색 단축키가 피드에 적용됩니다.
    "Alt" 키를 누른 상태에선 탐색 단축키가 카테고리에 적용됩니다.', - 'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.', //TODO - Translation + 'navigation_no_mod_help' => '아래 탐색 단축키에는 "Shift"와 "Alt" 키가 적용되지 않습니다.', 'next_article' => '다음 글 보기', 'normal_view' => '일반 모드로 전환', 'other_action' => '다른 동작', @@ -171,8 +171,8 @@ return array( 'rss_view' => '새 탭에서 RSS 피드 열기', 'see_on_website' => '글이 게재된 웹사이트에서 보기', 'shift_for_all_read' => '+ shift를 누른 상태에선 모두 읽음으로 표시', - 'skip_next_article' => 'Focus next without opening', //TODO - Translation - 'skip_previous_article' => 'Focus previous without opening', //TODO - Translation + 'skip_next_article' => '다음 글로 커서 이동', + 'skip_previous_article' => '이전 글로 커서 이동', 'title' => '단축키', 'user_filter' => '사용자 필터 사용하기', 'user_filter_help' => '사용자 필터가 하나만 설정되어 있다면 해당 필터를 사용하고, 그렇지 않다면 필터를 번호로 선택할 수 있습니다.', diff --git a/app/i18n/kr/feedback.php b/app/i18n/kr/feedback.php index 550904894..0e31536f8 100644 --- a/app/i18n/kr/feedback.php +++ b/app/i18n/kr/feedback.php @@ -57,8 +57,8 @@ return array( 'sub' => array( 'actualize' => '피드를 가져오는 중입니다', 'articles' => array( - 'marked_read' => 'The selected articles have been marked as read.', //TODO - Translation - 'marked_unread' => 'The articles have been marked as unread.', //TODO - Translation + 'marked_read' => '선택된 글들을 읽음으로 표시하였습니다.', + 'marked_unread' => '선택된 글들을 읽지 않음으로 표시하였습니다.', ), 'category' => array( 'created' => '%s 카테고리가 생성되었습니다.', diff --git a/app/i18n/kr/gen.php b/app/i18n/kr/gen.php index 86a50e9c4..f7855c499 100644 --- a/app/i18n/kr/gen.php +++ b/app/i18n/kr/gen.php @@ -152,13 +152,13 @@ return array( 'user_profile' => '프로필', ), 'pagination' => array( - 'first' => 'First', - 'last' => 'Last', + 'first' => '처음으로', + 'last' => '마지막으로', 'load_more' => '글 더 불러오기', 'mark_all_read' => '모두 읽음으로 표시', - 'next' => 'Next', + 'next' => '다음', 'nothing_to_load' => '더 이상 글이 없습니다', - 'previous' => 'Previous', + 'previous' => '이전', ), 'share' => array( 'blogotext' => 'Blogotext', @@ -183,13 +183,13 @@ return array( 'short' => array( 'attention' => '경고!', 'blank_to_disable' => '빈 칸으로 두면 비활성화', - 'by_author' => 'By:', + 'by_author' => '글쓴이:', 'by_default' => '기본값', 'damn' => '이런!', 'default_category' => '분류 없음', 'no' => '아니요', 'not_applicable' => '사용할 수 없음', - 'ok' => 'Ok!', + 'ok' => '좋습니다!', 'or' => '또는', 'yes' => '네', ), diff --git a/app/i18n/kr/index.php b/app/i18n/kr/index.php index 3c63fd664..bebc8bdec 100644 --- a/app/i18n/kr/index.php +++ b/app/i18n/kr/index.php @@ -40,7 +40,7 @@ return array( 'mark_all_read' => '모두 읽음으로 표시', 'mark_cat_read' => '카테고리를 읽음으로 표시', 'mark_feed_read' => '피드를 읽음으로 표시', - 'mark_selection_unread' => 'Mark selection as unread', //TODO - Translation + 'mark_selection_unread' => '선택된 글을 읽지 않음으로 표시', 'newer_first' => '최근 글 먼저', 'non-starred' => '즐겨찾기를 제외하고 표시', 'normal_view' => '일반 모드', @@ -53,11 +53,11 @@ return array( 'starred' => '즐겨찾기만 표시', 'stats' => '통계', 'subscription' => '구독 관리', - 'tags' => 'My labels', //TODO - Translation + 'tags' => '내 라벨', 'unread' => '읽지 않은 글만 표시', ), 'share' => '공유', 'tag' => array( - 'related' => '관련 태그', //TODO - Translation + 'related' => '관련 태그', ), ); diff --git a/app/i18n/kr/install.php b/app/i18n/kr/install.php index 6b60f6ffd..65e8726e1 100644 --- a/app/i18n/kr/install.php +++ b/app/i18n/kr/install.php @@ -69,8 +69,8 @@ return array( 'ok' => 'JSON 확장 기능이 설치되어 있습니다.', ), 'mbstring' => array( - 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation - 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation + 'nok' => '유니코드 지원을 위한 mbstring 라이브러리를 찾을 수 없습니다.', + 'ok' => '유니코드 지원을 위한 mbstring 라이브러리가 설치되어 있습니다.', ), 'minz' => array( 'nok' => 'Minz 프레임워크를 찾을 수 없습니다.', diff --git a/app/i18n/kr/sub.php b/app/i18n/kr/sub.php index 9f8967053..9edd85818 100644 --- a/app/i18n/kr/sub.php +++ b/app/i18n/kr/sub.php @@ -27,7 +27,7 @@ return array( 'password' => 'HTTP 암호', 'username' => 'HTTP 사용자 이름', ), - 'clear_cache' => 'Always clear cache', //TODO - Translation + 'clear_cache' => '항상 캐시 지우기', 'css_help' => '글의 일부가 포함된 RSS 피드를 가져옵니다 (주의, 시간이 좀 더 걸립니다!)', 'css_path' => '웹사이트 상의 글 본문에 해당하는 CSS 경로', 'description' => '설명', @@ -47,11 +47,11 @@ return array( ), 'websub' => 'WebSub을 사용한 즉시 알림', 'show' => array( - 'all' => 'Show all feeds', //TODO - Translation - 'error' => 'Show only feeds with error', //TODO - Translation + 'all' => '모든 피드 보기', + 'error' => '오류가 발생한 피드만 보기', ), 'showing' => array( - 'error' => 'Showing only feeds with error', //TODO - Translation + 'error' => '오류가 발생한 피드만 보여주고 있습니다', ), 'ssl_verify' => 'SSL 유효성 검사', 'stats' => '통계', @@ -72,7 +72,7 @@ return array( 'export' => '내보내기', 'export_opml' => '피드 목록 내보내기 (OPML)', 'export_starred' => '즐겨찾기 내보내기', - 'export_labelled' => 'Export your labelled articles', //TODO + 'export_labelled' => '라벨이 표시된 글들 내보내기', 'feed_list' => '%s 개의 글 목록', 'file_to_import' => '불러올 파일
    (OPML, JSON 또는 ZIP)', 'file_to_import_no_zip' => '불러올 파일
    (OPML 또는 JSON)', diff --git a/cli/i18n/ignore/kr.php b/cli/i18n/ignore/kr.php index 24cfbc86a..bbccb2d7d 100644 --- a/cli/i18n/ignore/kr.php +++ b/cli/i18n/ignore/kr.php @@ -8,6 +8,18 @@ return array( 'conf.sharing.shaarli', 'conf.sharing.twitter', 'conf.sharing.wallabag', + 'gen.date.Apr', + 'gen.date.Aug', + 'gen.date.Dec', + 'gen.date.Feb', + 'gen.date.Jan', + 'gen.date.Jul', + 'gen.date.Jun', + 'gen.date.Mar', + 'gen.date.May', + 'gen.date.Nov', + 'gen.date.Oct', + 'gen.date.Sep', 'gen.lang.cz', 'gen.lang.de', 'gen.lang.en', @@ -17,6 +29,7 @@ return array( 'gen.lang.it', 'gen.lang.kr', 'gen.lang.nl', + 'gen.lang.oc', 'gen.lang.pt-br', 'gen.lang.ru', 'gen.lang.tr', @@ -29,8 +42,11 @@ return array( 'gen.share.g+', 'gen.share.gnusocial', 'gen.share.jdh', + 'gen.share.linkedin', 'gen.share.mastodon', 'gen.share.movim', + 'gen.share.pinboard', + 'gen.share.pocket', 'gen.share.shaarli', 'gen.share.twitter', 'gen.share.wallabag', -- 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') 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 75356c64a2b13a89497bd8125c1bfe79293084f9 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 13 Feb 2019 17:18:21 +0100 Subject: Remove deprecated CSP child-src It's been probably long enough since https://github.com/FreshRSS/FreshRSS/pull/1099 --- app/FreshRSS.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 9371081e4..1dc80718e 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -117,7 +117,7 @@ class FreshRSS extends Minz_FrontController { } }, FreshRSS_Context::$user_conf->sharing)); $connectSrc = count($urlToAuthorize) ? sprintf("; connect-src 'self' %s", implode(' ', $urlToAuthorize)) : ''; - header(sprintf("Content-Security-Policy: default-src 'self'; child-src *; frame-src *; img-src * data:; media-src *%s", $connectSrc)); + header(sprintf("Content-Security-Policy: default-src 'self'; frame-src *; img-src * data:; media-src *%s", $connectSrc)); break; case 'stats': header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'"); -- cgit v1.2.3 From 387f763482412d47769ae1a7314d56cc12986420 Mon Sep 17 00:00:00 2001 From: Quentí <33203663+Quenty31@users.noreply.github.com> Date: Sat, 16 Feb 2019 12:33:24 +0100 Subject: Odre des mots --- app/i18n/oc/admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/i18n/oc/admin.php b/app/i18n/oc/admin.php index 4a47374d7..2f8ede873 100644 --- a/app/i18n/oc/admin.php +++ b/app/i18n/oc/admin.php @@ -130,7 +130,7 @@ return array( 'category' => 'Categoria', 'entry_count' => 'Nombre d’articles', 'entry_per_category' => 'Articles per categoria', - 'entry_per_day' => 'Nombre d’articles per jorn (30 darrièrs jorns)', + 'entry_per_day' => 'Nombre d’articles per jorn (darrièrs 30 jorns)', 'entry_per_day_of_week' => 'Per jorn de la setmana (mejana : %.2f messatges)', 'entry_per_hour' => 'Per ora (mejana : %.2f messatges)', 'entry_per_month' => 'Per mes (mejana : %.2f messatges)', -- cgit v1.2.3 From 3d57714bff3462bd7dafb72ffb1ff4b7d640b3cd Mon Sep 17 00:00:00 2001 From: Quentí <33203663+Quenty31@users.noreply.github.com> Date: Sat, 16 Feb 2019 12:33:56 +0100 Subject: Ordre des mots --- app/i18n/oc/gen.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php index 168ea4732..7f9793283 100644 --- a/app/i18n/oc/gen.php +++ b/app/i18n/oc/gen.php @@ -68,8 +68,8 @@ return array( 'Jun' => '\\j\\u\\n\\h', 'jun' => 'junh', 'june' => 'junh', - 'last_3_month' => 'Dempuèi los tres darrièrs meses', - 'last_6_month' => 'Dempuèi los sièis darrièrs meses', + 'last_3_month' => 'Dempuèi los darrièrs tres meses', + 'last_6_month' => 'Dempuèi los darrièrs sièis meses', 'last_month' => 'Dempuèi lo mes passat', 'last_week' => 'Dempuèi la setmana passada', 'last_year' => 'Dempuèi l’annada passada', -- 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') 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 2b2d9583cd2a4dc39c6f95dd73866c009f4480ce Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 2 Mar 2019 20:39:18 +0100 Subject: Fix mark_updated_article_unread https://github.com/FreshRSS/FreshRSS/issues/2200 --- app/Controllers/feedController.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 74c9eacfa..42cb46834 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -353,11 +353,10 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } else { //This entry already exists but has been updated //Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->url(false) . //', old hash ' . $existingHash . ', new hash ' . $entry->hash()); - $mark_updated_article_unread = $feed->attributes('mark_updated_article_unread') !== null ? ( - $feed->attributes('mark_updated_article_unread') - ) : FreshRSS_Context::$user_conf->mark_updated_article_unread; + $mark_updated_article_unread = $feed->attributes('mark_updated_article_unread') !== null ? + $feed->attributes('mark_updated_article_unread') : FreshRSS_Context::$user_conf->mark_updated_article_unread; $needFeedCacheRefresh = $mark_updated_article_unread; - $entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy. + $entry->_isRead($mark_updated_article_unread ? false : null); //Change is_read according to policy. $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if ($entry === null) { -- cgit v1.2.3 From f1978da394eb6e400d9aa58faf5322129ad635ce Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 2 Mar 2019 20:43:51 +0100 Subject: Boolean error --- app/Controllers/feedController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 42cb46834..179a8971e 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -356,7 +356,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $mark_updated_article_unread = $feed->attributes('mark_updated_article_unread') !== null ? $feed->attributes('mark_updated_article_unread') : FreshRSS_Context::$user_conf->mark_updated_article_unread; $needFeedCacheRefresh = $mark_updated_article_unread; - $entry->_isRead($mark_updated_article_unread ? false : null); //Change is_read according to policy. + $entry->_isRead($mark_updated_article_unread ? true : null); //Change is_read according to policy. $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if ($entry === null) { -- cgit v1.2.3 From 7d76acad04f1ac31829611c90a8941104f74b6a4 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 2 Mar 2019 20:56:03 +0100 Subject: Re-introduce Travis workaround --- app/Controllers/feedController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 179a8971e..57c2e392d 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -353,8 +353,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } else { //This entry already exists but has been updated //Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->url(false) . //', old hash ' . $existingHash . ', new hash ' . $entry->hash()); - $mark_updated_article_unread = $feed->attributes('mark_updated_article_unread') !== null ? - $feed->attributes('mark_updated_article_unread') : FreshRSS_Context::$user_conf->mark_updated_article_unread; + $mark_updated_article_unread = $feed->attributes('mark_updated_article_unread') !== null ? ( + $feed->attributes('mark_updated_article_unread') + ) : FreshRSS_Context::$user_conf->mark_updated_article_unread; $needFeedCacheRefresh = $mark_updated_article_unread; $entry->_isRead($mark_updated_article_unread ? true : null); //Change is_read according to policy. -- cgit v1.2.3 From 2856f7b8b47abcddf20589e19be1f5b98caa1ae3 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 2 Mar 2019 21:06:56 +0100 Subject: Revert wrong boolean --- app/Controllers/feedController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 57c2e392d..d5ae235ad 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -357,7 +357,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed->attributes('mark_updated_article_unread') ) : FreshRSS_Context::$user_conf->mark_updated_article_unread; $needFeedCacheRefresh = $mark_updated_article_unread; - $entry->_isRead($mark_updated_article_unread ? true : null); //Change is_read according to policy. + $entry->_isRead($mark_updated_article_unread ? false : null); //Change is_read according to policy. $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if ($entry === null) { -- cgit v1.2.3 From 566799075155546392316b1e4e352bdfc9e3602b Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 9 Mar 2019 15:43:44 +0100 Subject: Change nextGet behaviour Fixes https://github.com/FreshRSS/FreshRSS/issues/2206 Related to https://github.com/FreshRSS/FreshRSS/pull/2255 --- app/Models/Context.php | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) (limited to 'app') diff --git a/app/Models/Context.php b/app/Models/Context.php index 60ec6ff77..95dc47c8c 100644 --- a/app/Models/Context.php +++ b/app/Models/Context.php @@ -252,37 +252,29 @@ class FreshRSS_Context { $found_current_get = false; switch ($get[0]) { case 'f': - // We search the next feed with at least one unread article in - // same category as the currend feed. + // We search the next unread feed with the following priorities: next in same category, or previous in same category, or next, or previous. foreach (self::$categories as $cat) { - if ($cat->id() != self::$current_get['category']) { - // We look into the category of the current feed! - continue; - } - + $sameCat = false; foreach ($cat->feeds() as $feed) { - if ($feed->id() == self::$current_get['feed']) { - // Here is our current feed! Fine, the next one will - // be a potential candidate. + if ($found_current_get) { + if ($feed->nbNotRead() > 0) { + $another_unread_id = $feed->id(); + break 2; + } + } elseif ($feed->id() == self::$current_get['feed']) { $found_current_get = true; - continue; - } - - if ($feed->nbNotRead() > 0) { + } elseif ($feed->nbNotRead() > 0) { $another_unread_id = $feed->id(); - if ($found_current_get) { - // We have found our current feed and now we - // have an feed with unread articles. Leave the - // loop! - break; - } + $sameCat = true; } } - break; + if ($found_current_get && $sameCat) { + break; + } } - // If no feed have been found, next_get is the current category. - self::$next_get = empty($another_unread_id) ? 'c_' . self::$current_get['category'] : 'f_' . $another_unread_id; + // If there is no more unread feed, show main stream + self::$next_get = $another_unread_id == '' ? 'a' : 'f_' . $another_unread_id; break; case 'c': // We search the next category with at least one unread article. @@ -304,8 +296,8 @@ class FreshRSS_Context { } } - // No unread category? The main stream will be our destination! - self::$next_get = empty($another_unread_id) ? 'a' : 'c_' . $another_unread_id; + // If there is no more unread category, show main stream + self::$next_get = $another_unread_id == '' ? 'a' : 'c_' . $another_unread_id; break; } } -- cgit v1.2.3 From e91ef155d42f9624ef0e5d553b4e5fad70ba1bb5 Mon Sep 17 00:00:00 2001 From: Quentí <33203663+Quenty31@users.noreply.github.com> Date: Sun, 10 Mar 2019 19:52:27 +0100 Subject: Pitchoun correciou (#2269) --- app/i18n/oc/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/i18n/oc/index.php b/app/i18n/oc/index.php index 5211fd24a..5cc71c9a9 100644 --- a/app/i18n/oc/index.php +++ b/app/i18n/oc/index.php @@ -6,7 +6,7 @@ return array( 'agpl3' => 'AGPL 3', 'bugs_reports' => 'Senhalament de problèmas', 'credits' => 'Crèdits', - 'credits_content' => 'Unes elements de l’estil venon del projècte Bootstrap encara que FreshRSS utilize pas aqueste framework. Lasicònas venon del projècte GNOME. La polissa Open Sans utilizada foguèt creada per en Steve Matteson. FreshRSS es basat sus Minz, un framework PHP.', + 'credits_content' => 'Unes elements de l’estil venon del projècte Bootstrap encara que FreshRSS utilize pas aqueste framework. Las icònas venon del projècte GNOME. La polissa Open Sans utilizada foguèt creada per en Steve Matteson. FreshRSS es basat sus Minz, un framework PHP.', 'freshrss_description' => 'FreshRSS es un agregador de fluxes RSS per l’auto-albergar tal coma Kriss Feed o Leed. Sa tòca es d’èsser leugièr e de bon utilizar de prima abòrd mas tanben d’èsser potent e parametrable.', 'github' => 'on Github', 'license' => 'Licéncia', -- cgit v1.2.3 From bdf9aef2acb7c608f72737402dd6da5668a5ea9b Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 17 Mar 2019 11:08:42 +0100 Subject: [i18n] Update some translation strings (#2277) See #2086 --- app/i18n/en/conf.php | 7 ++----- app/i18n/fr/conf.php | 10 +++++----- 2 files changed, 7 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php index b3d4d8a5c..fde78f5b5 100644 --- a/app/i18n/en/conf.php +++ b/app/i18n/en/conf.php @@ -158,15 +158,12 @@ return array( 'javascript' => 'JavaScript must be enabled in order to use shortcuts', 'last_article' => 'Open the last article', 'load_more' => 'Load more articles', - 'mark_favorite' => 'Mark as favourite', - 'mark_read' => 'Mark as read', + 'mark_favorite' => 'Toggle favourite', + 'mark_read' => 'Toggle read', 'navigation' => 'Navigation', 'navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.
        With the "Alt" modifier, navigation shortcuts apply on categories.', 'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.', 'next_article' => 'Open the next article', - 'other_action' => 'Other actions', - 'previous_article' => 'Open the previous article', - 'next_article' => 'Open the next article', 'normal_view' => 'Switch to normal view', 'other_action' => 'Other actions', 'previous_article' => 'Open the previous article', diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php index 5c8e91c89..d0d146c89 100644 --- a/app/i18n/fr/conf.php +++ b/app/i18n/fr/conf.php @@ -158,11 +158,11 @@ return array( 'javascript' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.', 'last_article' => 'Passer au dernier article', 'load_more' => 'Charger plus d’articles', - 'mark_favorite' => 'Mettre en favori', - 'mark_read' => 'Marquer comme lu', + 'mark_favorite' => 'Basculer l’indicateur de favori', + 'mark_read' => 'Basculer l’indicateur de lecture', 'navigation' => 'Navigation', 'navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.
        Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.', - 'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.', //TODO - Translation + 'navigation_no_mod_help' => 'Les raccourcis suivant ne supportent pas les modificateurs.', 'next_article' => 'Passer à l’article suivant', 'normal_view' => 'Basculer vers la vue normale', 'other_action' => 'Autres actions', @@ -171,8 +171,8 @@ return array( 'rss_view' => 'Ouvrir le flux RSS dans un nouvel onglet', 'see_on_website' => 'Voir sur le site d’origine', 'shift_for_all_read' => '+ shift pour marquer tous les articles comme lus', - 'skip_next_article' => 'Focus next without opening', //TODO - Translation - 'skip_previous_article' => 'Focus previous without opening', //TODO - Translation + 'skip_next_article' => 'Passer au suivant sans ouvrir', + 'skip_previous_article' => 'Passer au précédent sans ouvrir', 'title' => 'Raccourcis', 'user_filter' => 'Accéder aux filtres utilisateur', 'user_filter_help' => 'S’il n’y a qu’un filtre utilisateur, celui-ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.', -- cgit v1.2.3 From e6888bbf232de5547560ea3af56ed760cb11c4ae Mon Sep 17 00:00:00 2001 From: hoilc Date: Mon, 18 Mar 2019 17:55:50 +0800 Subject: update zh-cn (i18n) (#2280) --- app/i18n/zh-cn/admin.php | 20 ++++++++++---------- app/i18n/zh-cn/conf.php | 26 +++++++++++++------------- app/i18n/zh-cn/feedback.php | 10 +++++----- app/i18n/zh-cn/gen.php | 2 +- app/i18n/zh-cn/index.php | 6 +++--- app/i18n/zh-cn/install.php | 4 ++-- app/i18n/zh-cn/sub.php | 14 +++++++------- cli/i18n/ignore/zh-cn.php | 4 ++++ 8 files changed, 45 insertions(+), 41 deletions(-) (limited to 'app') diff --git a/app/i18n/zh-cn/admin.php b/app/i18n/zh-cn/admin.php index e34070526..74f57b6e8 100644 --- a/app/i18n/zh-cn/admin.php +++ b/app/i18n/zh-cn/admin.php @@ -64,11 +64,11 @@ return array( 'files' => '文件相关', 'json' => array( 'nok' => '找不到 JSON 扩展 (php-json ) 。', - 'ok' => '已找到 JSON 扩展', + 'ok' => '已找到 JSON 扩展。', ), 'mbstring' => array( - 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation - 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation + 'nok' => '找不到推荐的 Unicode 解析库 (mbstring)。', + 'ok' => '已找到推荐的 Unicode 解析库 (mbstring)。', ), 'minz' => array( 'nok' => '找不到 Minz 框架。', @@ -163,8 +163,8 @@ return array( 'max-categories' => '每用户分类限制', 'max-feeds' => '每用户 RSS 源限制', 'cookie-duration' => array( - 'help' => 'in seconds', // @todo translate - 'number' => 'Duration to keep logged in', // @todo translate + 'help' => '单位(秒)', + 'number' => '保持登录的时长', ), 'registration' => array( 'help' => '0 表示无账户数限制', @@ -183,15 +183,15 @@ return array( 'user' => array( 'articles_and_size' => '%s 篇文章 (%s)', 'create' => '创建新用户', - 'delete_users' => 'Delete user', //TODO - Translation + 'delete_users' => '删除用户', 'language' => '语言', - 'number' => '已有 %d 个帐户', - 'numbers' => '已有 %d 个帐户', + 'number' => '已有 %d 个用户', + 'numbers' => '已有 %d 个用户', 'password_form' => '密码
        (用于 Web-form 登录方式)', 'password_format' => '至少 7 个字符', - 'selected' => 'Selected user', //TODO - Translation + 'selected' => '已选中用户', 'title' => '用户管理', - 'update_users' => 'Update user', //TODO - Translation + 'update_users' => '更新用户', 'user_list' => '用户列表', 'username' => '用户名', 'users' => '用户', diff --git a/app/i18n/zh-cn/conf.php b/app/i18n/zh-cn/conf.php index 1216aaaca..535dfd358 100644 --- a/app/i18n/zh-cn/conf.php +++ b/app/i18n/zh-cn/conf.php @@ -28,7 +28,7 @@ return array( 'seconds' => '秒 (0 表示不超时)', 'timeout' => 'HTML5 通知超时时间', ), - 'show_nav_buttons' => 'Show the navigation buttons', //TODO - Translation + 'show_nav_buttons' => '显示导航按钮', 'theme' => '主题', 'title' => '显示', 'width' => array( @@ -53,7 +53,7 @@ return array( 'query' => array( '_' => '自定义查询', 'deprecated' => '此查询不再有效。相关的分类或 RSS 源已被删除。', - 'display' => 'Display user query results', //TODO - Translation + 'display' => '显示查询结果', 'filter' => '生效的过滤器:', 'get_all' => '显示所有文章', 'get_category' => '显示分类 "%s"', @@ -64,7 +64,7 @@ return array( 'number' => '查询 n°%d', 'order_asc' => '由旧到新显示文章', 'order_desc' => '由新到旧显示文章', - 'remove' => 'Remove user query', //TODO - Translation + 'remove' => '删除查询', 'search' => '搜索 "%s"', 'state_0' => '显示所有文章', 'state_1' => '显示已读文章', @@ -128,7 +128,7 @@ return array( ), 'sharing' => array( '_' => '分享', - 'add' => 'Add a sharing method', //TODO - Translation + 'add' => '添加分享方式', 'blogotext' => 'Blogotext', 'diaspora' => 'Diaspora*', 'email' => 'Email', @@ -136,7 +136,7 @@ return array( 'g+' => 'Google+', 'more_information' => '更多信息', 'print' => '打印', - 'remove' => 'Remove sharing method', //TODO - Translation + 'remove' => '删除分享方式', 'shaarli' => 'Shaarli', 'share_name' => '名称', 'share_url' => '地址', @@ -148,31 +148,31 @@ return array( '_' => '快捷键', 'article_action' => '文章操作', 'auto_share' => '分享', - 'auto_share_help' => '如果有多种分享模式,则会按照它们的编号依次访问。', + 'auto_share_help' => '如果有多种分享方式,则会按照它们的编号依次访问。', 'close_dropdown' => '关闭菜单', 'collapse_article' => '收起文章', - 'first_article' => '跳转到第一篇文章', + 'first_article' => '打开第一篇文章', 'focus_search' => '聚焦到搜索框', 'global_view' => '切换到全屏视图', 'help' => '显示帮助文档', 'javascript' => '若要使用快捷键,必须启用 JavaScript', - 'last_article' => '跳转到最后一篇文章', + 'last_article' => '打开最后一篇文章', 'load_more' => '载入更多文章', 'mark_favorite' => '加入收藏', 'mark_read' => '设为已读', 'navigation' => '浏览', 'navigation_help' => '搭配 "Shift" 键,浏览快捷键将生效于 RSS 源。
        搭配 "Alt" 键,浏览快捷键将生效于分类。', - 'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.', //TODO - Translation - 'next_article' => '跳转到下一篇文章', + 'navigation_no_mod_help' => '以下快捷键不支持组合键 (Shift 或 Alt)', + 'next_article' => '打开下一篇文章', 'normal_view' => '切换到普通视图', 'other_action' => '其他操作', - 'previous_article' => '跳转到上一篇文章', + 'previous_article' => '打开上一篇文章', 'reading_view' => '切换到阅读视图', 'rss_view' => '在新标签中打开 RSS 视图', 'see_on_website' => '在原网站上查看', 'shift_for_all_read' => '+ shift 可以将全部文章设为已读', - 'skip_next_article' => 'Focus next without opening', //TODO - Translation - 'skip_previous_article' => 'Focus previous without opening', //TODO - Translation + 'skip_next_article' => '跳转到下一篇文章而不打开', + 'skip_previous_article' => '跳转到上一篇文章而不打开', 'title' => '快捷键', 'user_filter' => '显示自定义查询', 'user_filter_help' => '如果有多个自定义过滤器,则会按照它们的编号依次访问。', diff --git a/app/i18n/zh-cn/feedback.php b/app/i18n/zh-cn/feedback.php index e1778a9f2..e8ee969b0 100644 --- a/app/i18n/zh-cn/feedback.php +++ b/app/i18n/zh-cn/feedback.php @@ -57,8 +57,8 @@ return array( 'sub' => array( 'actualize' => '获取', 'articles' => array( - 'marked_read' => 'The selected articles have been marked as read.', //TODO - Translation - 'marked_unread' => 'The articles have been marked as unread.', //TODO - Translation + 'marked_read' => '选中文章已标记为已读', + 'marked_unread' => '文章已标记为未读', ), 'category' => array( 'created' => '分类 %s 已创建。', @@ -80,7 +80,7 @@ return array( 'already_subscribed' => '你已订阅 %s', 'deleted' => 'RSS 源已删除', 'error' => 'RSS 源更新失败', - 'internal_problem' => 'RSS 源添加失败。检查 FreshRSS 日志 查看详情。', //TODO - Translation + 'internal_problem' => 'RSS 源添加失败。检查 FreshRSS 日志 查看详情。你可以在URL后附加 #force_feed 从而尝试强制添加。', 'invalid_url' => 'URL %s 无效', 'n_actualized' => '%d 个 RSS 源已更新', 'n_entries_deleted' => '%d 篇文章已删除', @@ -109,8 +109,8 @@ return array( 'error' => '用户 %s 删除失败', ), 'updated' => array( - '_' => 'User %s has been updated', //TODO - Translation - 'error' => 'User %s has not been updated', //TODO - Translation + '_' => '用户 %s 已更新', + 'error' => '用户 %s 更新失败', ), ), ); diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php index 1dcd95233..11d4efdb3 100644 --- a/app/i18n/zh-cn/gen.php +++ b/app/i18n/zh-cn/gen.php @@ -19,7 +19,7 @@ return array( 'see_website' => '查看网站', 'submit' => '提交', 'truncate' => '删除所有文章', - 'update' => 'Update', //TODO - Translation + 'update' => '更新', //TODO - Translation ), 'auth' => array( 'email' => 'Email 地址', diff --git a/app/i18n/zh-cn/index.php b/app/i18n/zh-cn/index.php index 3f6b44701..018813c3e 100644 --- a/app/i18n/zh-cn/index.php +++ b/app/i18n/zh-cn/index.php @@ -40,7 +40,7 @@ return array( 'mark_all_read' => '全部设为已读', 'mark_cat_read' => '此分类设为已读', 'mark_feed_read' => '此源设为已读', - 'mark_selection_unread' => 'Mark selection as unread', //TODO - Translation + 'mark_selection_unread' => '选中设为已读', 'newer_first' => '由新到旧', 'non-starred' => '显示未收藏', 'normal_view' => '普通视图', @@ -53,11 +53,11 @@ return array( 'starred' => '显示收藏', 'stats' => '统计', 'subscription' => '订阅管理', - 'tags' => 'My labels', //TODO - Translation + 'tags' => '我的标签', 'unread' => '显示未读', ), 'share' => '分享', 'tag' => array( - 'related' => '相关标签', //TODO - Translation + 'related' => '文章标签', ), ); diff --git a/app/i18n/zh-cn/install.php b/app/i18n/zh-cn/install.php index 29647932a..da231917b 100644 --- a/app/i18n/zh-cn/install.php +++ b/app/i18n/zh-cn/install.php @@ -69,8 +69,8 @@ return array( 'ok' => '已找到推荐的 JSON 解析库。', ), 'mbstring' => array( - 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation - 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation + 'nok' => '找不到推荐的 Unicode 解析库 (mbstring)。', + 'ok' => '已找到推荐的 Unicode 解析库 (mbstring)。', ), 'minz' => array( 'nok' => '找不到 Minz 框架。', diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php index 3a9623468..f84d08cdf 100644 --- a/app/i18n/zh-cn/sub.php +++ b/app/i18n/zh-cn/sub.php @@ -27,7 +27,7 @@ return array( 'password' => 'HTTP 密码', 'username' => 'HTTP 用户名', ), - 'clear_cache' => 'Always clear cache', //TODO - Translation + 'clear_cache' => '总是清除缓存', 'css_help' => '用于获取全文(注意,这将耗费更多时间!)', 'css_path' => '原文的 CSS 选择器', 'description' => '描述', @@ -47,16 +47,16 @@ return array( ), 'websub' => 'WebSub 即时通知', 'show' => array( - 'all' => 'Show all feeds', //TODO - Translation - 'error' => 'Show only feeds with error', //TODO - Translation + 'all' => '显示所有 RSS 源', + 'error' => '仅显示有错误的 RSS 源', ), 'showing' => array( - 'error' => 'Showing only feeds with error', //TODO - Translation + 'error' => '正在显示有错误的 RSS 源', ), - 'ssl_verify' => 'Verify SSL security', //TODO - Translation + 'ssl_verify' => '验证 SSL 安全', 'stats' => '统计', 'think_to_add' => '你可以添加一些 RSS 源。', - 'timeout' => 'Timeout in seconds', //TODO - Translation + 'timeout' => '超时时间(秒)', 'title' => '标题', 'title_add' => '添加 RSS 源', 'ttl' => '最小自动更新时间', @@ -72,7 +72,7 @@ return array( 'export' => '导出', 'export_opml' => '导出 RSS 源列表 (OPML)', 'export_starred' => '导出你的收藏', - 'export_labelled' => 'Export your labelled articles', //TODO + 'export_labelled' => '导出有标签的文章', 'feed_list' => '%s 文章列表', 'file_to_import' => '需要导入的文件
        (OPML, JSON 或 ZIP)', 'file_to_import_no_zip' => '需要导入的文件
        (OPML 或 JSON)', diff --git a/cli/i18n/ignore/zh-cn.php b/cli/i18n/ignore/zh-cn.php index c00ac79d3..d55071d10 100644 --- a/cli/i18n/ignore/zh-cn.php +++ b/cli/i18n/ignore/zh-cn.php @@ -18,6 +18,7 @@ return array( 'gen.lang.it', 'gen.lang.kr', 'gen.lang.nl', + 'gen.lang.oc', 'gen.lang.pt-br', 'gen.lang.ru', 'gen.lang.tr', @@ -29,8 +30,11 @@ return array( 'gen.share.g+', 'gen.share.gnusocial', 'gen.share.jdh', + 'gen.share.linkedin', 'gen.share.mastodon', 'gen.share.movim', + 'gen.share.pinboard', + 'gen.share.pocket', 'gen.share.shaarli', 'gen.share.twitter', 'gen.share.wallabag', -- cgit v1.2.3 From 834ffacce22ff6a2c0f1459476dc4a45e8ea06f9 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 19 Mar 2019 20:14:31 +0100 Subject: No old ID (#2276) * No old ID https://github.com/FreshRSS/FreshRSS/issues/2273 * PostgreSQL insert or ignore --- app/Controllers/feedController.php | 10 +++------- app/Controllers/importExportController.php | 2 +- app/Models/EntryDAOPGSQL.php | 4 +++- lib/lib_rss.php | 7 +------ 4 files changed, 8 insertions(+), 15 deletions(-) (limited to 'app') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index d5ae235ad..7f36e0388 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -377,17 +377,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $read_upon_reception = $feed->attributes('read_upon_reception') !== null ? ( $feed->attributes('read_upon_reception') ) : FreshRSS_Context::$user_conf->mark_when['reception']; - if ($isNewFeed) { - $id = min(time(), $entry_date) . uSecString(); - $entry->_isRead($read_upon_reception); - } elseif ($entry_date < $date_min) { - $id = min(time(), $entry_date) . uSecString(); + $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 { - $id = uTimeString(); $entry->_isRead($read_upon_reception); } - $entry->_id($id); $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if ($entry === null) { diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 80b9868ec..1d7176929 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -585,7 +585,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $feed_id, $item['id'], $item['title'], $author, $content, $url, $published, $is_read, $is_starred ); - $entry->_id(min(time(), $entry->date(true)) . uSecString()); + $entry->_id(uTimeString()); $entry->_tags($tags); if (isset($newGuids[$entry->guid()])) { diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php index aef258b6f..e571e457f 100644 --- a/app/Models/EntryDAOPGSQL.php +++ b/app/Models/EntryDAOPGSQL.php @@ -37,7 +37,9 @@ BEGIN INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) (SELECT rank + row_number() OVER(ORDER BY date) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags FROM `' . $this->prefix . 'entrytmp` AS etmp - WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal WHERE etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid) + WHERE NOT EXISTS ( + SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal + WHERE (etmp.id = ereal.id) OR (etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid)) ORDER BY date); DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= maxrank; END $$;'; diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 89e9ddfea..bff59d5cc 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -303,12 +303,7 @@ function lazyimg($content) { function uTimeString() { $t = @gettimeofday(); - return $t['sec'] . str_pad($t['usec'], 6, '0'); -} - -function uSecString() { - $t = @gettimeofday(); - return str_pad($t['usec'], 6, '0'); + return $t['sec'] . str_pad($t['usec'], 6, '0', STR_PAD_LEFT); } function invalidateHttpCache($username = '') { -- cgit v1.2.3 From e84a90943ab1e4a254b2d33c7cabef18b718b456 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 20 Mar 2019 17:52:31 +0100 Subject: Session fix when form + HTTP auth are used (#2286) https://github.com/Alkarex/FreshRSS/commit/bf51c82d55f6bf1af2a6464ca4f148d6c613d28f https://github.com/FreshRSS/FreshRSS/issues/2125#issuecomment-473873922 --- app/Models/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/Models/Auth.php b/app/Models/Auth.php index 513a9cb2f..16a506f00 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -13,7 +13,7 @@ class FreshRSS_Auth { * This method initializes authentication system. */ public static function init() { - if (Minz_Session::param('REMOTE_USER', '') !== httpAuthUser()) { + if (isset($_SESSION['REMOTE_USER']) && $_SESSION['REMOTE_USER'] !== httpAuthUser()) { //HTTP REMOTE_USER has changed self::removeAccess(); } -- cgit v1.2.3 From ebd8c31c0272f135b1b55f0480d1c8c3875935fe Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 22 Mar 2019 19:05:38 +0100 Subject: Rework CSRF interaction with sessions (#2290) * Rework CSRF interaction with sessions Fix https://github.com/FreshRSS/FreshRSS/issues/2288 Improve security in some edge cases Maybe relevant for https://github.com/FreshRSS/FreshRSS/issues/2125#issuecomment-474992671 * Forgotten mime type --- app/Controllers/authController.php | 6 +++++- app/Controllers/userController.php | 1 + app/FreshRSS.php | 32 ++++++++++++++++++++------------ app/Models/Auth.php | 8 ++++---- p/scripts/main.js | 28 ++++++++++++++++++++++++++-- 5 files changed, 56 insertions(+), 19 deletions(-) (limited to 'app') diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php index 75d4acae0..ca44b1a96 100644 --- a/app/Controllers/authController.php +++ b/app/Controllers/authController.php @@ -69,7 +69,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController { * the user is already connected. */ public function loginAction() { - if (FreshRSS_Auth::hasAccess()) { + if (FreshRSS_Auth::hasAccess() && Minz_Request::param('u', '') == '') { Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } @@ -133,6 +133,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController { // Set session parameter to give access to the user. Minz_Session::_param('currentUser', $username); Minz_Session::_param('passwordHash', $conf->passwordHash); + Minz_Session::_param('csrf'); FreshRSS_Auth::giveAccess(); // Set cookie parameter if nedded. @@ -161,6 +162,8 @@ class FreshRSS_auth_Controller extends Minz_ActionController { return; } + FreshRSS_FormAuth::deleteCookie(); + $conf = get_user_configuration($username); if ($conf == null) { return; @@ -176,6 +179,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController { if ($ok) { Minz_Session::_param('currentUser', $username); Minz_Session::_param('passwordHash', $s); + Minz_Session::_param('csrf'); FreshRSS_Auth::giveAccess(); Minz_Request::good(_t('feedback.auth.login.success'), diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php index 71172b9ef..be3787561 100644 --- a/app/Controllers/userController.php +++ b/app/Controllers/userController.php @@ -247,6 +247,7 @@ class FreshRSS_user_Controller extends Minz_ActionController { $user_conf = get_user_configuration($new_user_name); Minz_Session::_param('currentUser', $new_user_name); Minz_Session::_param('passwordHash', $user_conf->passwordHash); + Minz_Session::_param('csrf'); FreshRSS_Auth::giveAccess(); } diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 1dc80718e..ecf13e4cf 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -57,18 +57,26 @@ class FreshRSS extends Minz_FrontController { private static function initAuth() { FreshRSS_Auth::init(); - if (Minz_Request::isPost() && !(is_referer_from_same_domain() && FreshRSS_Auth::isCsrfOk())) { - // Basic protection against XSRF attacks - FreshRSS_Auth::removeAccess(); - $http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; - Minz_Translate::init('en'); //TODO: Better choice of fallback language - Minz_Error::error( - 403, - array('error' => array( - _t('feedback.access.denied'), - ' [HTTP_REFERER=' . htmlspecialchars($http_referer, ENT_NOQUOTES, 'UTF-8') . ']' - )) - ); + if (Minz_Request::isPost()) { + if (!is_referer_from_same_domain()) { + // Basic protection against XSRF attacks + FreshRSS_Auth::removeAccess(); + $http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; + Minz_Translate::init('en'); //TODO: Better choice of fallback language + Minz_Error::error(403, array('error' => array( + _t('feedback.access.denied'), + ' [HTTP_REFERER=' . htmlspecialchars($http_referer, ENT_NOQUOTES, 'UTF-8') . ']' + ))); + } + if ((!FreshRSS_Auth::isCsrfOk()) && + (Minz_Request::controllerName() !== 'auth' || Minz_Request::actionName() !== 'login')) { + // Token-based protection against XSRF attacks, except for the login form itself + Minz_Translate::init('en'); //TODO: Better choice of fallback language + Minz_Error::error(403, array('error' => array( + _t('feedback.access.denied'), + ' [CSRF]' + ))); + } } } diff --git a/app/Models/Auth.php b/app/Models/Auth.php index 16a506f00..6d079a01f 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -24,6 +24,7 @@ class FreshRSS_Auth { $conf = Minz_Configuration::get('system'); $current_user = $conf->default_user; Minz_Session::_param('currentUser', $current_user); + Minz_Session::_param('csrf'); } if (self::$login_ok) { @@ -56,6 +57,7 @@ class FreshRSS_Auth { $current_user = trim($credentials[0]); Minz_Session::_param('currentUser', $current_user); Minz_Session::_param('passwordHash', trim($credentials[1])); + Minz_Session::_param('csrf'); } return $current_user != ''; case 'http_auth': @@ -63,6 +65,7 @@ class FreshRSS_Auth { $login_ok = $current_user != '' && FreshRSS_UserDAO::exists($current_user); if ($login_ok) { Minz_Session::_param('currentUser', $current_user); + Minz_Session::_param('csrf'); } return $login_ok; case 'none': @@ -196,13 +199,10 @@ class FreshRSS_Auth { } public static function isCsrfOk($token = null) { $csrf = Minz_Session::param('csrf'); - if ($csrf == '') { - return true; //Not logged in yet - } if ($token === null) { $token = Minz_Request::fetchPOST('_csrf'); } - return $token === $csrf; + return $token != '' && $token === $csrf; } } diff --git a/p/scripts/main.js b/p/scripts/main.js index 521adc839..212bf804b 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -44,6 +44,12 @@ var context; }()); // +function badAjax() { + openNotification(context.i18n.notif_request_failed, 'bad'); + location.reload(); + return true; +} + function needsScroll(elem) { const winBottom = document.documentElement.scrollTop + document.documentElement.clientHeight, elemTop = elem.offsetParent.offsetTop + elem.offsetTop, @@ -165,6 +171,9 @@ function send_mark_read_queue(queue, asRead) { for (let i = queue.length - 1; i >= 0; i--) { delete pending_entries['flux_' + queue[i]]; } + if (this.status == 403) { + badAjax(); + } }; req.onload = function (e) { if (this.status != 200) { @@ -269,6 +278,9 @@ function mark_favorite(div) { req.onerror = function (e) { openNotification(context.i18n.notif_request_failed, 'bad'); delete pending_entries[div.id]; + if (this.status == 403) { + badAjax(); + } }; req.onload = function (e) { if (this.status != 200) { @@ -918,6 +930,9 @@ function init_stream(stream) { req.responseType = 'json'; req.onerror = function (e) { checkboxTag.checked = !isChecked; + if (this.status == 403) { + badAjax(); + } }; req.onload = function (e) { if (this.status != 200) { @@ -1008,6 +1023,9 @@ function updateFeed(feeds, feeds_count) { const req = new XMLHttpRequest(); req.open('POST', feed.url, true); req.onloadend = function (e) { + if (this.status != 200) { + return badAjax(); + } feed_processed++; const div = document.getElementById('actualizeProgress'); div.querySelector('.progress').innerHTML = feed_processed + ' / ' + feeds_count; @@ -1045,9 +1063,12 @@ function init_actualize() { context.ajax_loading = true; const req = new XMLHttpRequest(); - req.open('GET', './?c=javascript&a=actualize', true); + req.open('POST', './?c=javascript&a=actualize', true); req.responseType = 'json'; req.onload = function (e) { + if (this.status != 200) { + return badAjax(); + } const json = xmlHttpRequestJson(this); if (auto && json.feeds.length < 1) { auto = false; @@ -1078,7 +1099,10 @@ function init_actualize() { updateFeed(json.feeds, feeds_count); } }; - req.send(); + req.setRequestHeader('Content-Type', 'application/json'); + req.send(JSON.stringify({ + _csrf: context.csrf, + })); return false; }; -- cgit v1.2.3 From e7a57915f9c90c144d95918048d2523418866921 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 23 Mar 2019 12:08:35 +0100 Subject: CLI user-info fixes (#2292) * CLI missing exit codes https://github.com/FreshRSS/FreshRSS/issues/2291 * CLI catch for outdated databases https://github.com/FreshRSS/FreshRSS/issues/2291#issuecomment-475856890 --- app/Models/TagDAO.php | 14 +++++++++++--- cli/list-users.php | 2 ++ cli/user-info.php | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 0b4428f17..297d24c96 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -187,9 +187,17 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function count() { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'tag`'; $stm = $this->bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - return $res[0]['count']; + if ($stm && $stm->execute()) { + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + return $res[0]['count']; + } else { + $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + if ($this->autoUpdateDb($info)) { + return $this->count(); + } + Minz_Log::error('SQL error TagDAO::count: ' . $info[2]); + return false; + } } public function countEntries($id) { diff --git a/cli/list-users.php b/cli/list-users.php index 758bbdb46..b3ce9936f 100755 --- a/cli/list-users.php +++ b/cli/list-users.php @@ -13,3 +13,5 @@ if (FreshRSS_Context::$system_conf->default_user !== '' foreach ($users as $user) { echo $user, "\n"; } + +done(); diff --git a/cli/user-info.php b/cli/user-info.php index 043bebf7c..125408c10 100755 --- a/cli/user-info.php +++ b/cli/user-info.php @@ -51,3 +51,5 @@ foreach ($users as $username) { "\n"; } } + +done(); -- 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') 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 From d3457f9ada962fc02ebc3e1c85dcaa975604826c Mon Sep 17 00:00:00 2001 From: Quentí <33203663+Quenty31@users.noreply.github.com> Date: Sun, 24 Mar 2019 18:38:40 +0100 Subject: [i18n] Update for filter actions (#2299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mesa a jorn pels filtres d’accion --- app/i18n/oc/sub.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php index 51ee0d43f..5a7bb2b57 100644 --- a/app/i18n/oc/sub.php +++ b/app/i18n/oc/sub.php @@ -33,8 +33,8 @@ return array( '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 + '_' => 'Filtre d’accion', + 'help' => 'Escrivètz una recèrca per linha.', ), 'informations' => 'Informacions', 'keep_history' => 'Nombre minimum d’articles de servar', -- cgit v1.2.3