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(); ?>
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 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/i18n')
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/i18n')
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 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/i18n')
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/i18n')
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/i18n')
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 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/i18n')
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 @@
+