Add URL-based pagination and editable page input to blobs.html
This commit is contained in:
+121
-5
@@ -127,6 +127,30 @@
|
||||
min-width: 8em;
|
||||
}
|
||||
|
||||
/* Editable page number input between the pagination arrows */
|
||||
.inpPage {
|
||||
width: 4.5em;
|
||||
padding: 4px 6px;
|
||||
text-align: center;
|
||||
font-family: var(--font-family);
|
||||
font-size: 0.9em;
|
||||
color: var(--primary-color);
|
||||
background-color: var(--secondary-color);
|
||||
border: var(--border-width) solid var(--muted-color);
|
||||
border-radius: var(--border-radius);
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
.inpPage:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
.inpPage::-webkit-outer-spin-button,
|
||||
.inpPage::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Text blob preview */
|
||||
.divBlobTextPreview {
|
||||
position: absolute;
|
||||
@@ -596,7 +620,8 @@
|
||||
<button id="btnFilterBinary" class="btn btnServer btnFilter active">Binary</button>
|
||||
<span style="margin-left: auto; display: flex; align-items: center; gap: 6px; font-size: 90%; color: #666;">
|
||||
<button id="btnPagePrev" class="btn btnServer btnMuted btnPageNav">◀</button>
|
||||
<span id="pageIndicator"></span>
|
||||
<input id="inpPage" class="inpPage" type="number" min="1" value="1" inputmode="numeric" title="Jump to page" />
|
||||
<span id="pageTotal"></span>
|
||||
(<span id="totalBlobCount">0</span>)
|
||||
<button id="btnPageNext" class="btn btnServer btnMuted btnPageNav">▶</button>
|
||||
</span>
|
||||
@@ -713,6 +738,7 @@
|
||||
let rowsPerPage = 0; // 0 = auto; loaded from user settings on init
|
||||
let currentPage = 0; // zero-indexed
|
||||
let initialBlobRefreshTriggered = false;
|
||||
let preservePageOnNextRefresh = false; // honor URL page on initial load
|
||||
|
||||
// hiddenFilters: categories the user has clicked off. Empty = show all.
|
||||
let hiddenFilters = new Set();
|
||||
@@ -757,7 +783,8 @@
|
||||
const divPostComposerHost = document.getElementById("divPostComposerHost");
|
||||
const btnClosePostPopup = document.getElementById("btnClosePostPopup");
|
||||
const totalBlobCount = document.getElementById("totalBlobCount");
|
||||
const pageIndicator = document.getElementById("pageIndicator");
|
||||
const inpPage = document.getElementById("inpPage");
|
||||
const pageTotal = document.getElementById("pageTotal");
|
||||
const tblBlobsBody = document.getElementById("tblBlobsBody");
|
||||
const divBlobList = document.getElementById("divBlobList");
|
||||
const divBlobGrid = document.getElementById("divBlobGrid");
|
||||
@@ -1624,6 +1651,7 @@
|
||||
currentPage = 0;
|
||||
updateFilterButtons();
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
syncPageToUrl(false);
|
||||
};
|
||||
|
||||
const updateFilterButtons = () => {
|
||||
@@ -1667,10 +1695,40 @@
|
||||
return getFilteredBlobs().slice(currentPage * size, (currentPage + 1) * size);
|
||||
};
|
||||
|
||||
// Read the 1-indexed `page` query param and convert to a 0-indexed page.
|
||||
const getPageFromUrl = () => {
|
||||
const params = new URLSearchParams(window.location.search || '');
|
||||
const raw = parseInt(params.get('page'), 10);
|
||||
return Number.isFinite(raw) && raw > 0 ? raw - 1 : 0;
|
||||
};
|
||||
|
||||
// Reflect the current page into the URL as a `page` query param (1-indexed).
|
||||
// Uses replaceState by default so the URL stays shareable without spamming
|
||||
// history; pass push=true for user-initiated navigation so Back/Forward work.
|
||||
const syncPageToUrl = (push = false) => {
|
||||
const params = new URLSearchParams(window.location.search || '');
|
||||
if (currentPage > 0) {
|
||||
params.set('page', String(currentPage + 1));
|
||||
} else {
|
||||
params.delete('page');
|
||||
}
|
||||
const query = params.toString();
|
||||
const nextUrl = `${window.location.pathname}${query ? `?${query}` : ''}${window.location.hash || ''}`;
|
||||
if (push) {
|
||||
window.history.pushState({ page: currentPage }, '', nextUrl);
|
||||
} else {
|
||||
window.history.replaceState({ page: currentPage }, '', nextUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const updatePaginationControls = () => {
|
||||
const total = getTotalPages();
|
||||
if (pageIndicator) {
|
||||
pageIndicator.textContent = total > 1 ? `${currentPage + 1} / ${total}` : '';
|
||||
if (inpPage) {
|
||||
inpPage.value = String(currentPage + 1);
|
||||
inpPage.max = String(total);
|
||||
}
|
||||
if (pageTotal) {
|
||||
pageTotal.textContent = `/ ${total}`;
|
||||
}
|
||||
if (btnPagePrev) {
|
||||
btnPagePrev.classList.toggle('btnMuted', currentPage === 0);
|
||||
@@ -1682,10 +1740,27 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Jump to a specific 1-indexed page (clamped to the valid range)
|
||||
const goToPage = (pageNum) => {
|
||||
const total = getTotalPages();
|
||||
let target = parseInt(pageNum, 10);
|
||||
if (!Number.isFinite(target)) target = currentPage + 1;
|
||||
target = Math.min(Math.max(target, 1), total) - 1;
|
||||
if (target !== currentPage) {
|
||||
currentPage = target;
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
syncPageToUrl(true);
|
||||
} else {
|
||||
// Normalize the input display even when the page didn't change
|
||||
updatePaginationControls();
|
||||
}
|
||||
};
|
||||
|
||||
const goToPrevPage = () => {
|
||||
if (currentPage > 0) {
|
||||
currentPage--;
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
syncPageToUrl(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1693,6 +1768,7 @@
|
||||
if (currentPage < getTotalPages() - 1) {
|
||||
currentPage++;
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
syncPageToUrl(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1700,6 +1776,7 @@
|
||||
if (currentPage !== 0) {
|
||||
currentPage = 0;
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
syncPageToUrl(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1708,6 +1785,7 @@
|
||||
if (currentPage !== last) {
|
||||
currentPage = last;
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
syncPageToUrl(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2189,7 +2267,15 @@
|
||||
}, []);
|
||||
|
||||
USER_BLOBS = uniqueBlobs.sort((a, b) => b.uploaded - a.uploaded);
|
||||
currentPage = 0;
|
||||
|
||||
if (preservePageOnNextRefresh) {
|
||||
// Initial load: honor the page requested in the URL, clamped to range
|
||||
preservePageOnNextRefresh = false;
|
||||
currentPage = Math.min(currentPage, getTotalPages() - 1);
|
||||
if (currentPage < 0) currentPage = 0;
|
||||
} else {
|
||||
currentPage = 0;
|
||||
}
|
||||
|
||||
console.log(`Total unique blobs across all servers: ${USER_BLOBS.length}`);
|
||||
|
||||
@@ -2199,6 +2285,9 @@
|
||||
updateBlobGrid();
|
||||
}
|
||||
|
||||
// Keep the URL in sync with the resolved page (normalizes/clamps)
|
||||
syncPageToUrl(false);
|
||||
|
||||
console.log(`Loaded ${USER_BLOBS.length} blobs from ${uploadServers.length} server(s)`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load blobs: ${error.message}`);
|
||||
@@ -2452,6 +2541,7 @@
|
||||
currentPage = 0;
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
updateRowsButtons();
|
||||
syncPageToUrl(false);
|
||||
patchUserSettings({ blobs: { gridColumns, rowsPerPage: val } }).catch(() => {});
|
||||
};
|
||||
|
||||
@@ -2574,11 +2664,33 @@
|
||||
if (e.target === divPostPopup) closePostPopup();
|
||||
});
|
||||
|
||||
// Editable page input: commit on Enter or blur/change
|
||||
if (inpPage) {
|
||||
inpPage.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
goToPage(inpPage.value);
|
||||
inpPage.blur();
|
||||
}
|
||||
});
|
||||
inpPage.addEventListener('change', () => goToPage(inpPage.value));
|
||||
inpPage.addEventListener('blur', () => goToPage(inpPage.value));
|
||||
}
|
||||
|
||||
document.getElementById('divGutterLeft').addEventListener('click', goToPrevPage);
|
||||
document.getElementById('divGutterRight').addEventListener('click', goToNextPage);
|
||||
document.getElementById('divGutterLeft').addEventListener('dblclick', goToFirstPage);
|
||||
document.getElementById('divGutterRight').addEventListener('dblclick', goToLastPage);
|
||||
|
||||
// Restore the page when the user navigates with Back/Forward
|
||||
window.addEventListener('popstate', () => {
|
||||
const page = getPageFromUrl();
|
||||
if (page !== currentPage) {
|
||||
currentPage = page;
|
||||
BLOB_VIEW_MODE === 'list' ? updateBlobTable() : updateBlobGrid();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('blossomServerDrop', async (event) => {
|
||||
const { serverUrl, files } = event.detail || {};
|
||||
if (!serverUrl || !Array.isArray(files) || files.length === 0) return;
|
||||
@@ -2695,6 +2807,10 @@
|
||||
}
|
||||
applyGridColumns();
|
||||
|
||||
// Restore page from URL (e.g. a shared link) before loading blobs
|
||||
currentPage = getPageFromUrl();
|
||||
preservePageOnNextRefresh = true;
|
||||
|
||||
// Subscribe to live settings updates (e.g. changed on another device)
|
||||
onUserSettings((settings) => {
|
||||
applyBlobSettings(settings || {});
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.7.107",
|
||||
"VERSION_NUMBER": "0.7.107",
|
||||
"BUILD_DATE": "2026-09-24T20:29:17.829Z"
|
||||
"VERSION": "v0.7.108",
|
||||
"VERSION_NUMBER": "0.7.108",
|
||||
"BUILD_DATE": "2026-09-24T21:55:29.385Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user