This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
bvandeusen 3944605d33 feat(bulk): consensus ML suggestions + frontend polish
Bulk editor now loads consensus ML suggestions across the current
selection via a new POST /api/suggestions/bulk endpoint, powered by
get_bulk_suggestions() in tag_suggestions.py. A tag is surfaced only if
it was suggested for or already applied to >= 80% of the selection;
coverage counts include images that already have the tag so a near-
universal tag isn't penalized for dropping out of suggestion lists.
Accept-only chips (no reject) match the rest of the suggestions UX.

Frontend polish in the same commit:
- Showcase session dedup (exclude seen ids, reset on second lap) and
  aspect-ratio-aware placeholder heights for better column balancing
- Modal touch-swipe navigation on the image wrapper, auto-focus of the
  tag input on desktop (>=768px), and autocomplete flip-up when the
  dropdown would cover the Suggestions section
- Settings template inline styles replaced with utility classes
- Inline series-editor script extracted to gallery-series-editor.js

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 18:39:47 -04:00

191 lines
7.2 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- /app/templates/gallery.html -->
{% extends "layout.html" %}
{% block title %}Gallery{% endblock %}
{% block content %}
<!-- Floating Select Button (in navbar area) -->
<div id="floatingSelectBtn" class="floating-select-btn">
<button id="selectModeBtn" class="btn secondary-btn">Select</button>
</div>
<div class="gallery-infinite-container">
<!-- Timeline Scrollbar (left sidebar) -->
<aside id="timelineSidebar" class="timeline-sidebar">
<div id="timelineTrack" class="timeline-track">
<!-- Populated by JS with year-month markers -->
<div class="timeline-loading">Loading...</div>
</div>
</aside>
<!-- Main Gallery Content -->
<main class="gallery-main">
<div class="gallery-header">
<h1>Gallery</h1>
{% if active_tag %}
<div class="active-filter">
Filtered by: <span class="filter-tag">{{ active_tag }}</span>
<a href="{{ url_for('main.gallery') }}" class="clear-filter" title="Clear filter">×</a>
</div>
{% endif %}
</div>
{% if post_metadata %}
<!-- Post Info Header -->
<div class="post-info-header">
<div class="post-info-content">
{% if post_metadata.title %}
<h2 class="post-title">{{ post_metadata.title }}</h2>
{% endif %}
<div class="post-meta">
{% if post_metadata.artist %}
<span class="post-artist">{{ post_metadata.artist }}</span>
{% endif %}
{% if post_metadata.platform %}
<span class="post-platform post-platform-{{ post_metadata.platform }}">{{ post_metadata.platform }}</span>
{% endif %}
{% if post_metadata.published_at %}
<span class="post-date">{{ post_metadata.published_at.strftime('%B %d, %Y') }}</span>
{% endif %}
{% if post_metadata.attachment_count > 1 %}
<span class="post-count">{{ post_metadata.attachment_count }} images</span>
{% endif %}
</div>
{% if post_metadata.description %}
<div class="post-description">
{{ post_metadata.description | safe }}
</div>
{% endif %}
{% if post_metadata.source_url %}
<a href="{{ post_metadata.source_url }}" target="_blank" rel="noopener noreferrer" class="post-source-link">
View original post
</a>
{% endif %}
</div>
</div>
{% endif %}
{% if series_info %}
<!-- Series Header -->
<div class="series-header">
<div class="series-header-content">
<div class="series-header-left">
<h2 class="series-title">{{ series_info.name }}</h2>
<span id="seriesPageCount" class="series-page-count">{{ series_info.page_count }} pages</span>
</div>
<div class="series-header-actions">
{% if series_info.page_count > 0 %}
<a href="{{ url_for('main.read_series', tag_id=series_info.tag_id) }}" class="btn primary-btn">
Read Series
</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
<!-- Gallery Grid with Date Sections -->
<div id="galleryInfinite" class="gallery-infinite">
{% for year_month_key, year_month_label, images in grouped_images %}
<section class="date-section" data-year-month="{{ year_month_key }}">
<h2 class="date-divider" id="section-{{ year_month_key }}">
<span class="date-divider-text">{{ year_month_label }}</span>
<span class="date-divider-count">{{ images|length }} images</span>
</h2>
<div class="gallery-grid">
{% for image in images %}
{% include '_gallery_item.html' %}
{% endfor %}
</div>
</section>
{% else %}
<div class="gallery-empty">
<p>No images found{% if active_tag %} for tag "{{ active_tag }}"{% endif %}.</p>
</div>
{% endfor %}
</div>
<!-- Loading indicator -->
<div id="galleryLoading" class="gallery-loading" style="display:none;">
<span class="loading-spinner"></span>
<span>Loading more images...</span>
</div>
<!-- End of content marker -->
<div id="galleryEnd" class="gallery-end" style="display:none;">
No more images
</div>
</main>
</div>
<!-- Modal -->
{% include '_gallery_modal.html' %}
<!-- Bulk Editor Panel -->
<aside id="bulkEditorPanel" class="bulk-editor-panel">
<div class="bulk-editor-header">
<h3>Bulk Edit</h3>
<button id="closeBulkEditor" class="modal-close-btn">×</button>
</div>
<div class="bulk-editor-stats">
<span id="selectedCount">0</span> images selected
</div>
<div class="bulk-editor-section">
<h4>Add Tags</h4>
<form id="bulkAddTagForm" class="tag-form" autocomplete="off">
<div class="tag-form-wrapper">
<input type="text" id="bulkTagInput" name="name" placeholder="Add tag..." autocomplete="off">
<div id="bulkTagAutocomplete" class="tag-autocomplete"></div>
</div>
<button type="submit">Add</button>
</form>
</div>
<div class="bulk-editor-section">
<h4>Remove Tags</h4>
<p class="form-help">Common tags across selected images:</p>
<div id="commonTagsList" class="tags"></div>
</div>
<div class="bulk-editor-section">
<h4>Suggestions</h4>
<p class="form-help" id="bulkSuggestionsHint">Select 2+ images to see consensus suggestions.</p>
<div id="bulkSuggestionsList" class="suggestions-list"></div>
</div>
{% if series_info %}
<div class="bulk-editor-section bulk-editor-series">
<h4>📚 Add to Series</h4>
<p class="form-help">Add selected images as pages to "{{ series_info.name }}"</p>
<div class="series-editor-controls">
<div class="series-editor-input">
<label for="startPageInput">Starting page:</label>
<input type="number" id="startPageInput" value="{{ series_info.page_count + 1 }}" min="1" class="form-input">
</div>
<button id="bulkAddToSeriesBtn" class="btn primary-btn" disabled>
Add to Series
</button>
</div>
<div id="seriesEditorFeedback" class="series-editor-feedback"></div>
</div>
{% endif %}
<div class="bulk-editor-actions">
<button id="clearSelectionBtn" class="btn secondary-btn">Clear Selection</button>
</div>
</aside>
<!-- Pass initial state to JS -->
<script>
window.galleryState = {
cursor: {{ initial_cursor | tojson | safe if initial_cursor else 'null' }},
hasMore: {{ 'true' if has_more else 'false' }},
activeTag: {{ active_tag | tojson | safe if active_tag else 'null' }},
seriesInfo: {{ series_info | tojson | safe if series_info else 'null' }}
};
</script>
<script src="{{ url_for('static', filename='js/tag-utils.js') }}"></script>
<script src="{{ url_for('static', filename='js/gallery-infinite.js') }}"></script>
<script src="{{ url_for('static', filename='js/view-modal.js') }}"></script>
<script src="{{ url_for('static', filename='js/bulk-select.js') }}"></script>
{% if series_info %}
<script src="{{ url_for('static', filename='js/gallery-series-editor.js') }}"></script>
{% endif %}
{% endblock %}