implement post metadata import and views. implemented series paging and reader view.
This commit is contained in:
@@ -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;
|
||||
})();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -286,6 +286,7 @@
|
||||
archive: '🗜️',
|
||||
character: '👤',
|
||||
series: '📺',
|
||||
fandom: '🎭',
|
||||
rating: '⚠️',
|
||||
source: '🌐',
|
||||
post: '📌'
|
||||
|
||||
Reference in New Issue
Block a user