From 83a47ef70b8387c50d41af74d0bbd75e9fe9831c Mon Sep 17 00:00:00 2001 From: Alexandre Flament Date: Sun, 23 Jan 2022 11:37:57 +0100 Subject: [PATCH] [mod] infinite_scroll as preference * oscar theme: code from searx/plugins/infinite_scroll.py * simple theme: new implementation --- searx/plugins/infinite_scroll.py | 9 --- searx/preferences.py | 11 +++ searx/settings_defaults.py | 1 + searx/static/themes/oscar/src/js/01_init.js | 1 + .../themes/oscar/src/js/infinite_scroll.js | 50 ++++++++++++ .../oscar/src/less/infinite_scroll.less} | 4 +- .../oscar/src/less/logicodev-dark/oscar.less | 1 + .../oscar/src/less/logicodev/oscar.less | 1 + .../themes/oscar/src/less/pointhi/oscar.less | 1 + .../themes/simple/src/js/main/00_toolkit.js | 63 ++++++++------- .../simple/src/js/main/infinite_scroll.js | 76 +++++++++++++++++++ searx/templates/oscar/base.html | 1 + searx/templates/oscar/preferences.html | 11 +++ searx/templates/simple/base.html | 2 +- searx/templates/simple/preferences.html | 12 +++ searx/webapp.py | 3 + 16 files changed, 203 insertions(+), 44 deletions(-) delete mode 100644 searx/plugins/infinite_scroll.py create mode 100644 searx/static/themes/oscar/src/js/infinite_scroll.js rename searx/static/{plugins/css/infinite_scroll.css => themes/oscar/src/less/infinite_scroll.less} (91%) create mode 100644 searx/static/themes/simple/src/js/main/infinite_scroll.js diff --git a/searx/plugins/infinite_scroll.py b/searx/plugins/infinite_scroll.py deleted file mode 100644 index e3726671a..000000000 --- a/searx/plugins/infinite_scroll.py +++ /dev/null @@ -1,9 +0,0 @@ -from flask_babel import gettext - -name = gettext('Infinite scroll') -description = gettext('Automatically load next page when scrolling to bottom of current page') -default_on = False -preference_section = 'ui' - -js_dependencies = ('plugins/js/infinite_scroll.js',) -css_dependencies = ('plugins/css/infinite_scroll.css',) diff --git a/searx/preferences.py b/searx/preferences.py index 570d0901b..e493dadc0 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -394,6 +394,17 @@ class Preferences: 'False': False } ), + 'infinite_scroll': MapSetting( + settings['ui']['infinite_scroll'], + locked=is_locked('infinite_scroll'), + map={ + '': settings['ui']['infinite_scroll'], + '0': False, + '1': True, + 'True': True, + 'False': False + } + ), # fmt: on } diff --git a/searx/settings_defaults.py b/searx/settings_defaults.py index 15b4524c6..0721899a2 100644 --- a/searx/settings_defaults.py +++ b/searx/settings_defaults.py @@ -186,6 +186,7 @@ SCHEMA = { 'results_on_new_tab': SettingsValue(bool, False), 'advanced_search': SettingsValue(bool, False), 'query_in_title': SettingsValue(bool, False), + 'infinite_scroll': SettingsValue(bool, False), }, 'preferences': { 'lock': SettingsValue(list, []), diff --git a/searx/static/themes/oscar/src/js/01_init.js b/searx/static/themes/oscar/src/js/01_init.js index 8853d9909..f72b0078b 100644 --- a/searx/static/themes/oscar/src/js/01_init.js +++ b/searx/static/themes/oscar/src/js/01_init.js @@ -19,6 +19,7 @@ window.searxng = (function(d) { return { autocompleter: script.getAttribute('data-autocompleter') === 'true', + infinite_scroll: script.getAttribute('data-infinite-scroll') === 'true', method: script.getAttribute('data-method'), translations: JSON.parse(script.getAttribute('data-translations')) }; diff --git a/searx/static/themes/oscar/src/js/infinite_scroll.js b/searx/static/themes/oscar/src/js/infinite_scroll.js new file mode 100644 index 000000000..f8b9a01a7 --- /dev/null +++ b/searx/static/themes/oscar/src/js/infinite_scroll.js @@ -0,0 +1,50 @@ +/** + * @license + * (C) Copyright Contributors to the SearXNG project. + * (C) Copyright Contributors to the searx project (2014 - 2021). + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +$(document).ready(function() { + function hasScrollbar() { + var root = document.compatMode=='BackCompat'? document.body : document.documentElement; + return root.scrollHeight>root.clientHeight; + } + + function loadNextPage() { + var formData = $('#pagination form:last').serialize(); + if (formData) { + $('#pagination').html('
'); + $.ajax({ + type: "POST", + url: $('#search_form').prop('action'), + data: formData, + dataType: 'html', + success: function(data) { + var body = $(data); + $('#pagination').remove(); + $('#main_results').append('
'); + $('#main_results').append(body.find('.result')); + $('#main_results').append(body.find('#pagination')); + if(!hasScrollbar()) { + loadNextPage(); + } + } + }); + } + } + + if (searxng.infinite_scroll) { + var win = $(window); + $("html").addClass('infinite_scroll'); + if(!hasScrollbar()) { + loadNextPage(); + } + win.on('scroll', function() { + if ($(document).height() - win.height() - win.scrollTop() < 150) { + loadNextPage(); + } + }); + } + +}); diff --git a/searx/static/plugins/css/infinite_scroll.css b/searx/static/themes/oscar/src/less/infinite_scroll.less similarity index 91% rename from searx/static/plugins/css/infinite_scroll.css rename to searx/static/themes/oscar/src/less/infinite_scroll.less index 07b9f6de9..f66373651 100644 --- a/searx/static/plugins/css/infinite_scroll.css +++ b/searx/static/themes/oscar/src/less/infinite_scroll.less @@ -2,6 +2,7 @@ 0% { transform: rotate(0deg) } 100% { transform: rotate(360deg) } } + .loading-spinner { animation-duration: 0.75s; animation-iteration-count: infinite; @@ -14,6 +15,7 @@ border-radius: 50% !important; margin: 0 auto; } -#pagination button { + +html.infinite_scroll #pagination button { visibility: hidden; } diff --git a/searx/static/themes/oscar/src/less/logicodev-dark/oscar.less b/searx/static/themes/oscar/src/less/logicodev-dark/oscar.less index 14f23111f..71821a259 100644 --- a/searx/static/themes/oscar/src/less/logicodev-dark/oscar.less +++ b/searx/static/themes/oscar/src/less/logicodev-dark/oscar.less @@ -4,6 +4,7 @@ @import "../../../../__common__/less/result_templates.less"; @import "../../less/result_templates.less"; @import "../../less/preferences.less"; +@import "../infinite_scroll.less"; @import "../../generated/pygments-logicodev.less"; @stacked-bar-chart: rgb(213, 216, 215, 1); diff --git a/searx/static/themes/oscar/src/less/logicodev/oscar.less b/searx/static/themes/oscar/src/less/logicodev/oscar.less index 187368f71..61e03745b 100644 --- a/searx/static/themes/oscar/src/less/logicodev/oscar.less +++ b/searx/static/themes/oscar/src/less/logicodev/oscar.less @@ -4,6 +4,7 @@ @import "../../../../__common__/less/result_templates.less"; @import "../../less/result_templates.less"; @import "../../less/preferences.less"; +@import "../infinite_scroll.less"; @import "../../generated/pygments-logicodev.less"; @import "navbar.less"; diff --git a/searx/static/themes/oscar/src/less/pointhi/oscar.less b/searx/static/themes/oscar/src/less/pointhi/oscar.less index e9851458d..d54fa28d9 100644 --- a/searx/static/themes/oscar/src/less/pointhi/oscar.less +++ b/searx/static/themes/oscar/src/less/pointhi/oscar.less @@ -4,6 +4,7 @@ @import "../../../../__common__/less/result_templates.less"; @import "../../less/result_templates.less"; @import "../../less/preferences.less"; +@import "../infinite_scroll.less"; @import "../../generated/pygments-pointhi.less"; @import "footer.less"; diff --git a/searx/static/themes/simple/src/js/main/00_toolkit.js b/searx/static/themes/simple/src/js/main/00_toolkit.js index c5b7fe578..1a7213ac5 100644 --- a/searx/static/themes/simple/src/js/main/00_toolkit.js +++ b/searx/static/themes/simple/src/js/main/00_toolkit.js @@ -59,43 +59,40 @@ window.searxng = (function (w, d) { } }; - searxng.http = function (method, url) { - var req = new XMLHttpRequest(), - resolve = function () {}, - reject = function () {}, - promise = { - then: function (callback) { resolve = callback; return promise; }, - catch: function (callback) { reject = callback; return promise; } - }; + searxng.http = function (method, url, data = null) { + return new Promise(function (resolve, reject) { + try { + var req = new XMLHttpRequest(); + req.open(method, url, true); - try { - req.open(method, url, true); + // On load + req.onload = function () { + if (req.status == 200) { + resolve(req.response, req.responseType); + } else { + reject(Error(req.statusText)); + } + }; - // On load - req.onload = function () { - if (req.status == 200) { - resolve(req.response, req.responseType); + // Handle network errors + req.onerror = function () { + reject(Error("Network Error")); + }; + + req.onabort = function () { + reject(Error("Transaction is aborted")); + }; + + // Make the request + if (data) { + req.send(data) } else { - reject(Error(req.statusText)); + req.send(); } - }; - - // Handle network errors - req.onerror = function () { - reject(Error("Network Error")); - }; - - req.onabort = function () { - reject(Error("Transaction is aborted")); - }; - - // Make the request - req.send(); - } catch (ex) { - reject(ex); - } - - return promise; + } catch (ex) { + reject(ex); + } + }); }; searxng.loadStyle = function (src) { diff --git a/searx/static/themes/simple/src/js/main/infinite_scroll.js b/searx/static/themes/simple/src/js/main/infinite_scroll.js new file mode 100644 index 000000000..4d771c5d3 --- /dev/null +++ b/searx/static/themes/simple/src/js/main/infinite_scroll.js @@ -0,0 +1,76 @@ +/** + * @license + * (C) Copyright Contributors to the SearXNG project. + * (C) Copyright Contributors to the searx project (2014 - 2021). + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +/* global searxng */ + +searxng.ready(function () { + 'use strict'; + + let w = window; + let d = document; + var root = d.compatMode == 'BackCompat' ? d.body : d.documentElement; + + function hasScrollbar () { + return root.scrollHeight > root.clientHeight; + } + + function loadNextPage () { + var form = d.querySelector('#pagination form.next_page'); + if (!form) { + return + } + var formData = new FormData(form); + d.querySelector('#pagination').innerHTML = '
'; + searxng.http('POST', d.querySelector('#search').getAttribute('action'), formData).then( + function (response) { + var scrollYBeforeInsert = w.scrollY; + var nextPageDoc = new DOMParser().parseFromString(response, 'text/html'); + var articleList = nextPageDoc.querySelectorAll('#urls article'); + var paginationElement = nextPageDoc.querySelector('#pagination'); + var onlyImages = d.getElementById('results').classList.contains('only_template_images'); + d.querySelector('#pagination').remove(); + if (articleList.length > 0 && !onlyImages) { + // do not add
element when there are only images + d.querySelector('#urls').appendChild(d.createElement('hr')); + } + articleList.forEach(articleElement => { + d.querySelector('#urls').appendChild(articleElement); + }); + searxng.image_thumbnail_layout.align(); + w.scrollTo(w.scrollX, scrollYBeforeInsert); + if (paginationElement) { + d.querySelector('#results').appendChild(paginationElement); + if (!hasScrollbar()) { + loadNextPage(); + } + } + } + ).catch( + function (err) { + console.log(err); + d.querySelector('#pagination').innerHTML = ''; + } + ) + } + + function onScroll () { + let clientRect = root.getBoundingClientRect(); + if (root.scrollHeight - root.scrollTop - clientRect.height < 150) { + loadNextPage(); + } + } + + if (searxng.infinite_scroll) { + d.getElementsByTagName('html')[0].classList.add('infinite_scroll') + if (!hasScrollbar()) { + loadNextPage(); + } + d.addEventListener('scroll', onScroll, { passive: true }); + } + +}); diff --git a/searx/templates/oscar/base.html b/searx/templates/oscar/base.html index de7d05bf6..dbc0699df 100644 --- a/searx/templates/oscar/base.html +++ b/searx/templates/oscar/base.html @@ -100,6 +100,7 @@ {% for script in scripts %} {{""}} diff --git a/searx/templates/oscar/preferences.html b/searx/templates/oscar/preferences.html index f731a8fc1..c43d96ee0 100644 --- a/searx/templates/oscar/preferences.html +++ b/searx/templates/oscar/preferences.html @@ -248,6 +248,17 @@ {{ preferences_item_footer(info, label, rtl) }} {% endif %} + {% if 'infinite_scroll' not in locked_preferences %} + {% set label = _('Infinite scroll') %} + {% set info = _('Automatically load next page when scrolling to bottom of current page') %} + {{ preferences_item_header(info, label, rtl, 'infinite_scroll') }} + + {{ preferences_item_footer(info, label, rtl) }} + {% endif %} + {{ plugin_of_category('ui' )}} diff --git a/searx/templates/simple/base.html b/searx/templates/simple/base.html index 33b7a2cbe..92658e60e 100644 --- a/searx/templates/simple/base.html +++ b/searx/templates/simple/base.html @@ -23,7 +23,7 @@ data-method="{{ method or 'POST' }}" data-autocompleter="{% if autocomplete %}true{% else %}false{% endif %}" data-search-on-category-select="{{ 'true' if 'plugins/js/search_on_category_select.js' in scripts else 'false'}}" - data-infinite-scroll="{{ 'true' if 'plugins/js/infinite_scroll.js' in scripts else 'false' }}" + data-infinite-scroll="{% if infinite_scroll %}true{% else %}false{% endif %}" data-hotkeys="{{ 'true' if 'plugins/js/vim_hotkeys.js' in scripts else 'false' }}" data-static-path="{{ url_for('static', filename='themes/simple') }}/" data-translations="{{ translations }}"> diff --git a/searx/templates/simple/preferences.html b/searx/templates/simple/preferences.html index b674d5d02..fb550d26a 100644 --- a/searx/templates/simple/preferences.html +++ b/searx/templates/simple/preferences.html @@ -226,6 +226,18 @@
{{_('Open result links on new browser tabs') }}
{% endif %} + {% if 'infinite_scroll' not in locked_preferences %} +
+ {{ _('Infinite scroll') }} +

+ +

+
{{ _('Automatically load next page when scrolling to bottom of current page') }}
+
+ {% endif %} {{ plugin_preferences('ui') }} {{ tab_footer() }} diff --git a/searx/webapp.py b/searx/webapp.py index 2e6e388e5..0b545a54c 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -431,6 +431,8 @@ def get_translations(): 'no_item_found': gettext('No item found'), # /preferences: the source of the engine description (wikipedata, wikidata, website) 'Source': gettext('Source'), + # infinite scroll + 'error_loading_next_page': gettext('Error loading the next page'), } @@ -463,6 +465,7 @@ def render(template_name: str, override_theme: str = None, **kwargs): kwargs['preferences'] = request.preferences kwargs['method'] = request.preferences.get_value('method') kwargs['autocomplete'] = request.preferences.get_value('autocomplete') + kwargs['infinite_scroll'] = request.preferences.get_value('infinite_scroll') kwargs['results_on_new_tab'] = request.preferences.get_value('results_on_new_tab') kwargs['advanced_search'] = request.preferences.get_value('advanced_search') kwargs['query_in_title'] = request.preferences.get_value('query_in_title')