transition to htmx, made truly infinite scroll

This commit is contained in:
Oleg Belyaev 2025-03-29 23:57:33 +03:00
parent a65037f8ea
commit cd513bd796
26 changed files with 849 additions and 1253 deletions

View file

@ -1,133 +0,0 @@
var md5 = (function() {
var MD5 = function (d) {
return M(V(Y(X(d), 8 * d.length)))
}
function M (d) {
for (var _, m = '0123456789abcdef', f = '', r = 0; r < d.length; r++) {
_ = d.charCodeAt(r)
f += m.charAt(_ >>> 4 & 15) + m.charAt(15 & _)
}
return f
}
function X (d) {
for (var _ = Array(d.length >> 2), m = 0; m < _.length; m++) {
_[m] = 0
}
for (m = 0; m < 8 * d.length; m += 8) {
_[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32
}
return _
}
function V (d) {
for (var _ = '', m = 0; m < 32 * d.length; m += 8) _ += String.fromCharCode(d[m >> 5] >>> m % 32 & 255)
return _
}
function Y (d, _) {
d[_ >> 5] |= 128 << _ % 32
d[14 + (_ + 64 >>> 9 << 4)] = _
for (var m = 1732584193, f = -271733879, r = -1732584194, i = 271733878, n = 0; n < d.length; n += 16) {
var h = m
var t = f
var g = r
var e = i
f = md5ii(f = md5ii(f = md5ii(f = md5ii(f = md5hh(f = md5hh(f = md5hh(f = md5hh(f = md5gg(f = md5gg(f = md5gg(f = md5gg(f = md5ff(f = md5ff(f = md5ff(f = md5ff(f, r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = md5ff(r, i = md5ff(i, m = md5ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = md5gg(r, i = md5gg(i, m = md5gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = md5hh(r, i = md5hh(i, m = md5hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = md5ii(r, i = md5ii(i, m = md5ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551)
m = safeadd(m, h)
f = safeadd(f, t)
r = safeadd(r, g)
i = safeadd(i, e)
}
return [m, f, r, i]
}
function md5cmn (d, _, m, f, r, i) {
return safeadd(bitrol(safeadd(safeadd(_, d), safeadd(f, i)), r), m)
}
function md5ff (d, _, m, f, r, i, n) {
return md5cmn(_ & m | ~_ & f, d, _, r, i, n)
}
function md5gg (d, _, m, f, r, i, n) {
return md5cmn(_ & f | m & ~f, d, _, r, i, n)
}
function md5hh (d, _, m, f, r, i, n) {
return md5cmn(_ ^ m ^ f, d, _, r, i, n)
}
function md5ii (d, _, m, f, r, i, n) {
return md5cmn(m ^ (_ | ~f), d, _, r, i, n)
}
function safeadd (d, _) {
var m = (65535 & d) + (65535 & _)
return (d >> 16) + (_ >> 16) + (m >> 16) << 16 | 65535 & m
}
function bitrol (d, _) {
return d << _ | d >>> 32 - _
}
function MD5Unicode(buffer){
if (!(buffer instanceof Uint8Array)) {
buffer = new TextEncoder().encode(typeof buffer==='string' ? buffer : JSON.stringify(buffer));
}
var binary = [];
var bytes = new Uint8Array(buffer);
for (var i = 0, il = bytes.byteLength; i < il; i++) {
binary.push(String.fromCharCode(bytes[i]));
}
return MD5(binary.join(''));
}
return MD5Unicode;
})();
$( document ).ready(function() {
// GLOBALS;
// Link elements that do not have href out of the box
$('.link').on("click", function () {
window.location=$(this)[0].dataset.href+window.location.search+window.location.hash;
});
$('#abv-search').on("submit", function () {
$('#abv-search-icon').replaceWith('<div class="loader"></div>');
});
// SEARCH RESULTS MODAL
$('#abv-btn-searchResults').on("click", function () {
$('#modal_searchResults')[0].showModal();
});
// Close search modal
$('.abv-close-search').on("click", function () {
$('#modal_searchResults')[0].close();
});
$('#modal_searchResults').on("toggle", async function () {
// let html = "";
async function getResults() {
let url = "./api/search/current";
const response = await fetch(url);
const result = await response.json();
return result;
}
async function getEntryForm(id) {
let url = `./api/entries/${md5(id).toUpperCase()}`;
const response = await fetch(url);
const result = await response.json();
return result.form;
}
searchResults = await getResults();
for (result of searchResults) {
let pos = searchResults.indexOf(result);
// let form = await getEntryForm(result.entry);
let html = `
<tr>
<td>${pos+1}</td>
<td>
<a href="./search/position?p=${pos+1}">${result.entryForm}</a>
</td>
<td>
${result.fragment}
</td>
</tr>
`
$(this).find('tbody').append($(html));
}
// $(this).find('tbody').append($(html));
});
});

View file

@ -14,7 +14,7 @@ Root
:root {}
html {}
body {}
body {height: 100vh;}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@ -30,8 +30,7 @@ h5 {}
h6 {}
body > header {
position: sticky;
top: 0;
max-height: 10vh;
background-color: #FCF9F5;
margin-left: 0;
margin-right: 0;
@ -39,8 +38,8 @@ body > header {
max-width: 100vw;
/* border-bottom-width: thin;
border-bottom-style: solid;*/
margin-bottom: 10px !important;
padding-bottom: 0px !important;
margin-bottom: 0px !important;
padding-top: 0px !important;
/* padding-right: 1em !important;
padding-left: 1em !important;*/
@ -87,7 +86,9 @@ body > header {
body>header>nav,
body>footer>nav {
margin: auto;
margin-right: auto;
margin-left: auto;
max-height: 10vh;
--pico-primary: #8D0B02 !important;
--pico-primary-background: #8D0B02 !important;
--pico-primary-border: #8D0B02 !important;
@ -124,12 +125,15 @@ nav li.abv-menu-re {
filter: gray;
}
main {}
main {
height: 85vh;
overflow-y: scroll;
padding-top: 0 !important;
}
article.abv-entry {
display: grid;
grid-template-columns: minmax(0px,15%) 5% minmax(50%,80%);
align-items: baseline;
}
section {}
@ -292,6 +296,13 @@ table {}
caption {}
col:first-child {}
thead {}
table#abv-search-results thead {
padding-top: 0;
position: sticky;
top: 0;
}
tbody {}
tfoot {}
tr {}
@ -309,19 +320,17 @@ legend {}
form {}
header form {
margin-bottom: 0 !important;
/*table#abv-search-results {
height: 100%;
}
header form button {
padding-left: 1em !important;
padding-right: 1em !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
table#abv-search-results tbody {
height:100%;
overflow-y: scroll;
}*/
header form select {
max-width:35%;
form#abv-search select {
max-width:15%;
}
label {}
@ -466,28 +475,6 @@ dialog > article {
max-height: 85vh !important;
}
dialog#modal_searchResults > article {
padding-top: 0;
padding-bottom: 0;
}
dialog#modal_searchResults > article > table > thead {
position: sticky;
top: 0;
z-index: 1055;
}
dialog#modal_searchResults > article > header {
position: sticky;
top: 0;
z-index: 1055;
}
dialog#modal_searchResults > article > footer {
position: sticky;
bottom: 0;
z-index: 1055;
}
/*dialog#modal_searchResults > article > p > table > tbody {
height:100%;
overflow-y: scroll;
@ -539,25 +526,3 @@ span.abv-text-sc {
This file evolved from Natural Selection:
https://github.com/frontaid/natural-selection
*/
.icons{
text-align: center;
width: 1.5em;
float: left;
padding-right: 0.5vw;
}
.loader {
border: 2px solid #f3f3f3;
border-radius: 50%;
border-top: 2px solid #3498db;
width: 2ex;
height: 2ex;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View file

@ -1,73 +0,0 @@
$( document ).ready(function() {
$('#abv-select-lang').on('input', function() {
let $selected = $(this).children('option:selected');
let url = `./api/languages/${$selected.attr('value')}`;
fetch(url)
.then(res => res.json())
.then(out => {
let $select_word = $('#abv-select-word');
let $select_entry = $('#abv-select-entry');
$select_word.empty();
$select_entry.empty();
$('#btn-index-submit').prop('hidden',true);
for (w of out.words) {
$select_word.append(new Option(w.text,w.id))
}
})
.catch(err => console.log(err));
});
$('#abv-select-word').on('input', function() {
let $selected = $(this).children('option:selected');
let wordid = $selected.attr('value');
let lang = $('#abv-select-lang').children('option:selected').attr('value');
let url = `./api/languages/${lang}/words/${wordid}`;
fetch(url)
.then(res => res.json())
.then(out => {
let $select_entry = $('#abv-select-entry');
$select_entry.empty();
$('#btn-index-submit').prop('hidden',true);
for (e of out.entries) {
url_entry = `./api/entries/${e.id}`;
fetch(url_entry)
.then(res => res.json())
.then(out => {
let entrystring = '';
let glosses = [];
for (r of e.refs) {
if (r.glosses) {
for(g of r.glosses){
glosses.push(`${g}`);
}
}
}
if(glosses.length > 0) {
entrystring = `${glosses.join('; ')}: `
}
opt = $(`<option value="${out.xmlid}" data-abv-xpath="${e.refs[0].path}">${entrystring}<i>${out.form}</i>
${out.glosses.map(g => `${g}`).join(', ')}</option>`);
$select_entry.append(opt)
})
.catch(err => console.log(err));
}
})
.catch(err => console.log(err));
});
$('#abv-select-entry').on("input", function () {
$('#btn-index-submit').removeAttr('hidden');
})
function goToEntry() {
let $selected = $('#abv-select-entry').children('option:selected');
let entry = $selected.attr('value');
let path = $selected.attr('data-abv-xpath');
window.location.replace('./dict/' + entry + `?entry=${entry}&xpath=${path}`);
}
$('#abv-select-entry').on("dblclick", goToEntry);
$('#btn-index-submit').on("click", goToEntry);
});

View file

@ -1,186 +0,0 @@
$( document ).ready(function() {
// GLOBALS;
// Link elements that do not have href out of the box
$('.link').on("click", function () {
window.location=$(this)[0].dataset.href+window.location.search+window.location.hash;
});
$('#abv-search').on("submit", function () {
$('#abv-search-icon').replaceWith('<div class="loader"></div>');
});
// INFINITE SCROLL
// init Infinite Scroll
$('main').infiniteScroll({
path: '.pagination__next',
append: 'div.abv-lex',
//status: '.scroller-status',
//hideNav: '.pagination',
});
// MAP MODAL
// This event binding is done on page load and also whenever new entries are loaded.
bindMapOpen = function () {
let url = `./dict/map-info/${$(this).data('abv-entry')}`;
fetch(url)
.then(res => res.json())
.then(out => {
$('#modal_map').data('map',out);
$('#modal_map')[0].showModal();
})
.catch(err => console.log(err));
};
// Event to open map modal.
$('.abv-map').on("click", bindMapOpen);
$('main').on( 'append.infiniteScroll', function( event, body, path, response ) {
$('.abv-map').on("click", bindMapOpen);
});
// Close map modal
$('.abv-close-map').on("click", function () {
$('#modal_map')[0].close();
});
// Load map data when dialog opens
$('#modal_map').on("toggle", function () {
map = document.getElementById('map_display');
var layout = {
// title: {
// text: 'Canadian cities',
// font: {
// family: 'Droid Serif, serif',
// size: 16
// }
// },
geo: {
scope: 'world',
resolution: 50,
fitbounds: 'locations',
showrivers: true,
rivercolor: '#fff',
showlakes: true,
lakecolor: '#fff',
showland: true,
landcolor: '#EAEAAE',
showcountries: false,
subunitcolor: '#d3d3d3'
}
};
Plotly.newPlot(map, [$(this).data('map')], layout);
});
// HASH NAVIGATION
// If the hash is on the page, scroll a little bit above to account for top nav height
if ( $(decodeURI(document.location.hash)).length > 0) {
window.scrollBy(0,-$('header').height());
}
// If the hash is not on the page, go to the right page by using the ID interface
$( window ).on('hashchange', function() {
const hash = decodeURI(document.location.hash);
if ( hash.startsWith('#entry_') ) {
if ( $(hash).length == 0) {
window.location.replace(document.location.pathname + '/' + document.location.hash.substring(1));
}
else {
window.scrollBy(0,-$('header').height());
}
}
});
// ENTRY LIST SIDE PANEL
// Function to produce HTML for the entry list
function filterEntryList(list, filter, showSubentries) {
let listHtml = "";
for (entry of list) {
let entryID = entry.xmlid;
let entryForm = entry.form;
if (entryForm.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").startsWith(filter)
&& (!entry.subentry || showSubentries)
) {
listHtml += `<li id="link_${entryID}"${entry.subentry ? ' class="abv-menu-re"' : ''}"><a href="./dict/${entryID}">${entryForm}</a></li>`
}
}
listHtml = $(`<ul>${listHtml}</ul>`);
$("#entrylist").append(listHtml);
}
// Fetch entries from the server when page loads
var entries = [];
fetch('./api/entries')
.then(res => res.json())
.then(out => {
entries = out;
filterEntryList(entries, '', false);
})
.then(err => console.log(err));
// Function to scroll the entry list to the selected entry OR to the first entry displayed on the current page if there is no hash in the URL
function scrollView() {
var $container = $('#entrylist')
if (document.location.hash.length > 0) {
var $scrollTo = $(`#link_${$(decodeURI(document.location.hash)).attr('id')}`);
}
else {
var $scrollTo = $(`#link_${$('article:first').attr('id')}`);
}
$container.scrollTop(
$scrollTo.offset().top - $container.offset().top + $container.scrollTop())
}
// Click to open the offcanvas entry list
$( '#open-leftbar' ).on('click', function() {
$("#leftbar").show();
$("main")[0].style.marginLeft = "250px";
$("header nav")[0].style.marginLeft = "250px";
$("footer nav")[0].style.marginLeft = "250px";
$("#open-leftbar").hide();
$("#close-leftbar").show();
scrollView();
});
// Click to close the offcanvas entry list
$( '#close-leftbar' ).on('click', function() {
$("#leftbar").hide();
// $("#leftbar")[0].style.paddingLeft = null;
$("main")[0].style.marginLeft = null;
$("header nav")[0].style.marginLeft = null;
$("footer nav")[0].style.marginLeft = null;
$("#open-leftbar").show();
$("#close-leftbar").hide();
})
// Quick filter functionality
$('#filter-entries').on("keyup", function () {
$("#entrylist > ul").remove();
var value = $(this).val().toLowerCase();
filterEntryList(entries, value, $('#show-re').prop('checked'))
// var value = $(this).val().toLowerCase();
// if (value.length > 0) {
// $("#entrylist > ul > li:not([hidden])").filter(function () {
// return $(this).toggle($(this).children('a').text().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").startsWith(value))
// });
// }
// else {
// $("#entrylist > ul > li").show();
// }
//
// return false;
});
// Event handler to show subentries
$('#show-re').on("change", function () {
$("#entrylist > ul").remove();
filterEntryList(entries, $("#filter-entries").val().toLowerCase(), $(this).prop('checked'));
scrollView();
});
});

1
static/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
static/pico.classless.min.css vendored Normal file

File diff suppressed because one or more lines are too long