[mod] infinite_scroll as preference
* oscar theme: code from searx/plugins/infinite_scroll.py * simple theme: new implementation
This commit is contained in:
parent
de32d543bc
commit
83a47ef70b
@ -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',)
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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, []),
|
||||
|
@ -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'))
|
||||
};
|
||||
|
50
searx/static/themes/oscar/src/js/infinite_scroll.js
Normal file
50
searx/static/themes/oscar/src/js/infinite_scroll.js
Normal file
@ -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('<div class="loading-spinner"></div>');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: $('#search_form').prop('action'),
|
||||
data: formData,
|
||||
dataType: 'html',
|
||||
success: function(data) {
|
||||
var body = $(data);
|
||||
$('#pagination').remove();
|
||||
$('#main_results').append('<hr/>');
|
||||
$('#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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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) {
|
||||
|
76
searx/static/themes/simple/src/js/main/infinite_scroll.js
Normal file
76
searx/static/themes/simple/src/js/main/infinite_scroll.js
Normal file
@ -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 = '<div class="loader"></div>';
|
||||
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 <hr> 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 = '<div class="dialog-error" role="alert"><div><p>' +
|
||||
searxng.translations.error_loading_next_page +
|
||||
'</p></div></div>';
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
});
|
@ -100,6 +100,7 @@
|
||||
<script src="{{ url_for('static', filename='js/searxng.min.js') }}"
|
||||
data-method="{{ method or 'POST' }}"
|
||||
data-autocompleter="{% if autocomplete %}true{% else %}false{% endif %}"
|
||||
data-infinite-scroll="{% if infinite_scroll %}true{% else %}false{% endif %}"
|
||||
data-translations="{{ translations }}"></script>
|
||||
{% for script in scripts %}
|
||||
{{""}}<script src="{{ url_for('static', filename=script) }}"></script>
|
||||
|
@ -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') }}
|
||||
<select class="form-control {{ custom_select_class(rtl) }}" name="infinite_scroll" id="infinite_scroll">
|
||||
<option value="1" {% if infinite_scroll %}selected="selected"{% endif %}>{{ _('On') }}</option>
|
||||
<option value="0" {% if not infinite_scroll %}selected="selected"{% endif %}>{{ _('Off')}}</option>
|
||||
</select>
|
||||
{{ preferences_item_footer(info, label, rtl) }}
|
||||
{% endif %}
|
||||
|
||||
{{ plugin_of_category('ui' )}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -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 }}"></script>
|
||||
|
@ -226,6 +226,18 @@
|
||||
<div class="description">{{_('Open result links on new browser tabs') }}</div>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% if 'infinite_scroll' not in locked_preferences %}
|
||||
<fieldset>
|
||||
<legend>{{ _('Infinite scroll') }}</legend>
|
||||
<p class="value">
|
||||
<select name='infinite_scroll'>
|
||||
<option value="1" {% if infinite_scroll %}selected="selected"{% endif %}>{{ _('On') }}</option>
|
||||
<option value="0" {% if not infinite_scroll %}selected="selected"{% endif %}>{{ _('Off')}}</option>
|
||||
</select>
|
||||
</p>
|
||||
<div class="description">{{ _('Automatically load next page when scrolling to bottom of current page') }}</div>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{{ plugin_preferences('ui') }}
|
||||
{{ tab_footer() }}
|
||||
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user