implement post metadata import and views. implemented series paging and reader view.

This commit is contained in:
Bryan Van Deusen
2026-01-22 22:19:11 -05:00
parent f41acb40f6
commit a05aee5a07
16 changed files with 2298 additions and 8 deletions
+42
View File
@@ -112,6 +112,7 @@
updateCount();
loadCommonTags();
dispatchSelectionChanged();
}
function clearSelection() {
@@ -121,6 +122,7 @@
});
updateCount();
updateCommonTagsDisplay();
dispatchSelectionChanged();
}
function updateCount() {
@@ -129,6 +131,39 @@
}
}
function dispatchSelectionChanged() {
document.dispatchEvent(new CustomEvent('selectionChanged', {
detail: { count: state.selectedIds.size }
}));
}
// Enable select mode programmatically, optionally without opening the bulk panel
function enableSelectMode(skipPanel = false) {
if (state.isSelectMode) return;
state.isSelectMode = true;
document.body.classList.add('select-mode');
dom.selectBtn.textContent = 'Cancel';
dom.selectBtn.classList.add('active');
if (!skipPanel) {
dom.panel.classList.add('active');
updateCommonTagsDisplay();
}
}
// Disable select mode
function disableSelectMode() {
if (!state.isSelectMode) return;
state.isSelectMode = false;
document.body.classList.remove('select-mode');
dom.selectBtn.textContent = 'Select';
dom.selectBtn.classList.remove('active');
dom.panel.classList.remove('active');
clearSelection();
}
function updateSelectionUI() {
// Re-apply selected class to any newly loaded images that are in selection
document.querySelectorAll('.img-clickable').forEach(item => {
@@ -416,6 +451,7 @@
archive: '🗜️',
character: '👤',
series: '📺',
fandom: '🎭',
rating: '⚠️',
source: '🌐',
post: '📌'
@@ -435,4 +471,10 @@
} else {
init();
}
// Expose global API for other scripts (like series editor)
window.selectedImages = state.selectedIds;
window.clearSelection = clearSelection;
window.enableSelectMode = enableSelectMode;
window.disableSelectMode = disableSelectMode;
})();
+105
View File
@@ -14,6 +14,14 @@ document.addEventListener('DOMContentLoaded', () => {
const tagInput = tagForm ? tagForm.querySelector('input[name="name"]') : null;
const tagAutocomplete = document.getElementById('tagAutocomplete');
// Series info elements
const seriesInfoEl = document.getElementById('modalSeriesInfo');
const seriesNameEl = document.getElementById('modalSeriesName');
const pageNumberInput = document.getElementById('modalPageNumber');
const updatePageBtn = document.getElementById('updatePageBtn');
const pageUpdateStatus = document.getElementById('pageUpdateStatus');
let currentSeriesPageId = null;
// Autocomplete state
let autocompleteItems = [];
let autocompleteSelectedIndex = -1;
@@ -53,6 +61,7 @@ document.addEventListener('DOMContentLoaded', () => {
archive: '🗜️',
character: '👤',
series: '📺',
fandom: '🎭',
rating: '⚠️',
source: '🌐',
post: '📌'
@@ -87,6 +96,100 @@ document.addEventListener('DOMContentLoaded', () => {
// ignore
}
}
// ---------------------------
// Series info helpers
// ---------------------------
function hideSeriesInfo() {
if (seriesInfoEl) seriesInfoEl.style.display = 'none';
currentSeriesPageId = null;
if (pageUpdateStatus) pageUpdateStatus.textContent = '';
}
function showSeriesInfo(seriesPage) {
if (!seriesInfoEl || !seriesPage) return;
currentSeriesPageId = seriesPage.id;
// Display series name (strip prefix)
const displayName = seriesPage.series_name.includes(':')
? seriesPage.series_name.split(':', 1)[1]
: seriesPage.series_name;
if (seriesNameEl) {
seriesNameEl.textContent = displayName;
seriesNameEl.href = `/gallery?tag=${encodeURIComponent(seriesPage.series_name)}`;
}
// Display page number
if (pageNumberInput) {
pageNumberInput.value = seriesPage.page_number;
}
if (pageUpdateStatus) pageUpdateStatus.textContent = '';
seriesInfoEl.style.display = 'block';
}
async function loadSeriesInfo(imageId) {
hideSeriesInfo();
if (!imageId) return;
try {
const r = await fetch(`/api/image/${imageId}/series-info`);
const j = await r.json();
if (j.ok && j.in_series) {
showSeriesInfo(j.series_page);
}
} catch {
// ignore
}
}
async function updatePageNumber() {
if (!currentSeriesPageId || !pageNumberInput) return;
const newPageNumber = parseInt(pageNumberInput.value);
if (isNaN(newPageNumber) || newPageNumber < 1) {
if (pageUpdateStatus) pageUpdateStatus.textContent = 'Invalid';
return;
}
if (updatePageBtn) updatePageBtn.disabled = true;
if (pageUpdateStatus) pageUpdateStatus.textContent = 'Saving...';
try {
const fd = new FormData();
fd.append('page_number', newPageNumber);
const r = await fetch(`/api/series/page/${currentSeriesPageId}/update`, {
method: 'POST',
body: fd
});
const j = await r.json();
if (j.ok) {
if (pageUpdateStatus) {
pageUpdateStatus.textContent = '✓';
setTimeout(() => { pageUpdateStatus.textContent = ''; }, 2000);
}
} else {
if (pageUpdateStatus) pageUpdateStatus.textContent = j.error || 'Error';
}
} catch (e) {
if (pageUpdateStatus) pageUpdateStatus.textContent = 'Error';
} finally {
if (updatePageBtn) updatePageBtn.disabled = false;
}
}
// Wire up the save button
if (updatePageBtn) {
updatePageBtn.addEventListener('click', updatePageNumber);
}
// Allow Enter key in page number input to save
if (pageNumberInput) {
pageNumberInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
updatePageNumber();
}
});
}
async function addTag(imageId, name) {
const fd = new FormData();
fd.append('name', name);
@@ -299,6 +402,7 @@ document.addEventListener('DOMContentLoaded', () => {
// NEW: load tags for this image into the modal editor
setEditorImageId(imageId);
loadTags(imageId);
loadSeriesInfo(imageId);
}
function openModal(index) {
@@ -324,6 +428,7 @@ document.addEventListener('DOMContentLoaded', () => {
// clear tag UI
setEditorImageId('');
renderTags([]);
hideSeriesInfo();
history.replaceState({}, '', window.location.pathname + window.location.search);
}
+1
View File
@@ -286,6 +286,7 @@
archive: '🗜️',
character: '👤',
series: '📺',
fandom: '🎭',
rating: '⚠️',
source: '🌐',
post: '📌'