commit e816ef129b28396eee408834eed55634c5c29ec9
parent bccb8d2e28f775e2f30bdd6638b9edb3d9cbc590
Author: breadcat <breadcat@users.noreply.github.com>
Date: Tue, 16 Dec 2025 16:31:13 +0000
Add /tag/name/previews function
Can be used with /and/ function too
Diffstat:
3 files changed, 168 insertions(+), 16 deletions(-)
diff --git a/main.go b/main.go
@@ -99,6 +99,13 @@ type VideoFile struct {
EscapedFilename string
}
+type filter struct {
+ Category string
+ Value string
+ Values []string // Expanded values including aliases
+ IsPreviews bool // New field to indicate preview mode
+}
+
func expandTagWithAliases(category, value string) []string {
values := []string{value}
@@ -1013,15 +1020,9 @@ func tagFilterHandler(w http.ResponseWriter, r *http.Request) {
fullPath := strings.TrimPrefix(r.URL.Path, "/tag/")
tagPairs := strings.Split(fullPath, "/and/tag/")
- type filter struct {
- Category string
- Value string
- Values []string // Expanded values including aliases
- }
-
breadcrumbs := []Breadcrumb{
{Name: "Home", URL: "/"},
- {Name: "Tags", URL: "/tags"}, // or wherever your tags overview page is
+ {Name: "Tags", URL: "/tags"},
}
var filters []filter
@@ -1035,12 +1036,13 @@ func tagFilterHandler(w http.ResponseWriter, r *http.Request) {
}
f := filter{
- Category: parts[0],
- Value: parts[1],
+ Category: parts[0],
+ Value: parts[1],
+ IsPreviews: parts[1] == "previews",
}
- // Expand with aliases
- if parts[1] != "unassigned" {
+ // Expand with aliases (unless it's a special tag)
+ if parts[1] != "unassigned" && parts[1] != "previews" {
f.Values = expandTagWithAliases(parts[0], parts[1])
}
@@ -1075,7 +1077,41 @@ func tagFilterHandler(w http.ResponseWriter, r *http.Request) {
})
}
- // Build count query
+ // Check if we're in preview mode for any filter
+ hasPreviewFilter := false
+ for _, f := range filters {
+ if f.IsPreviews {
+ hasPreviewFilter = true
+ break
+ }
+ }
+
+ if hasPreviewFilter {
+ // Handle preview mode
+ files, err := getPreviewFiles(filters)
+ if err != nil {
+ renderError(w, "Failed to fetch preview files", http.StatusInternalServerError)
+ return
+ }
+
+ var titleParts []string
+ for _, f := range filters {
+ titleParts = append(titleParts, fmt.Sprintf("%s: %s", f.Category, f.Value))
+ }
+ title := "Tagged: " + strings.Join(titleParts, " + ")
+
+ pageData := buildPageDataWithPagination(title, ListData{
+ Tagged: files,
+ Untagged: nil,
+ Breadcrumbs: []Breadcrumb{},
+ }, 1, len(files), len(files))
+ pageData.Breadcrumbs = breadcrumbs
+
+ renderTemplate(w, "list.html", pageData)
+ return
+ }
+
+ // Build count query (existing logic)
countQuery := `SELECT COUNT(DISTINCT f.id) FROM files f WHERE 1=1`
countArgs := []interface{}{}
@@ -1120,7 +1156,7 @@ func tagFilterHandler(w http.ResponseWriter, r *http.Request) {
return
}
- // Build main query with pagination
+ // Build main query with pagination (existing logic)
query := `SELECT f.id, f.filename, f.path, COALESCE(f.description, '') as description FROM files f WHERE 1=1`
args := []interface{}{}
@@ -1177,13 +1213,127 @@ func tagFilterHandler(w http.ResponseWriter, r *http.Request) {
pageData := buildPageDataWithPagination(title, ListData{
Tagged: files,
Untagged: nil,
- Breadcrumbs: []Breadcrumb{}, // Empty here
+ Breadcrumbs: []Breadcrumb{},
}, page, total, perPage)
- pageData.Breadcrumbs = breadcrumbs // Set at PageData level
+ pageData.Breadcrumbs = breadcrumbs
renderTemplate(w, "list.html", pageData)
}
+// getPreviewFiles returns one representative file for each tag value in the specified category
+func getPreviewFiles(filters []filter) ([]File, error) {
+ // Find the preview filter category
+ var previewCategory string
+ for _, f := range filters {
+ if f.IsPreviews {
+ previewCategory = f.Category
+ break
+ }
+ }
+
+ if previewCategory == "" {
+ return []File{}, nil
+ }
+
+ // First, get all tag values for the preview category that have files
+ tagQuery := `
+ SELECT DISTINCT t.value
+ FROM tags t
+ JOIN categories c ON t.category_id = c.id
+ JOIN file_tags ft ON ft.tag_id = t.id
+ WHERE c.name = ?
+ ORDER BY t.value`
+
+ tagRows, err := db.Query(tagQuery, previewCategory)
+ if err != nil {
+ return nil, fmt.Errorf("failed to query tag values: %w", err)
+ }
+ defer tagRows.Close()
+
+ var tagValues []string
+ for tagRows.Next() {
+ var tagValue string
+ if err := tagRows.Scan(&tagValue); err != nil {
+ return nil, fmt.Errorf("failed to scan tag value: %w", err)
+ }
+ tagValues = append(tagValues, tagValue)
+ }
+
+
+ if len(tagValues) == 0 {
+ return []File{}, nil
+ }
+
+ // For each tag value, find one representative file
+ var allFiles []File
+ for _, tagValue := range tagValues {
+ // Build query for this specific tag value with all filters applied
+ query := `SELECT f.id, f.filename, f.path, COALESCE(f.description, '') as description
+ FROM files f
+ WHERE 1=1`
+ args := []interface{}{}
+
+ // Apply all filters (including the preview category with this specific value)
+ for _, filter := range filters {
+ if filter.IsPreviews {
+ // For the preview filter, use the current tag value we're iterating over
+ query += `
+ AND EXISTS (
+ SELECT 1
+ FROM file_tags ft
+ JOIN tags t ON ft.tag_id = t.id
+ JOIN categories c ON c.id = t.category_id
+ WHERE ft.file_id = f.id AND c.name = ? AND t.value = ?
+ )`
+ args = append(args, filter.Category, tagValue)
+ } else if filter.Value == "unassigned" {
+ query += `
+ AND NOT EXISTS (
+ SELECT 1
+ FROM file_tags ft
+ JOIN tags t ON ft.tag_id = t.id
+ JOIN categories c ON c.id = t.category_id
+ WHERE ft.file_id = f.id AND c.name = ?
+ )`
+ args = append(args, filter.Category)
+ } else {
+ // Normal filter with aliases
+ placeholders := make([]string, len(filter.Values))
+ for i := range filter.Values {
+ placeholders[i] = "?"
+ }
+
+ query += fmt.Sprintf(`
+ AND EXISTS (
+ SELECT 1
+ FROM file_tags ft
+ JOIN tags t ON ft.tag_id = t.id
+ JOIN categories c ON c.id = t.category_id
+ WHERE ft.file_id = f.id AND c.name = ? AND t.value IN (%s)
+ )`, strings.Join(placeholders, ","))
+
+ args = append(args, filter.Category)
+ for _, v := range filter.Values {
+ args = append(args, v)
+ }
+ }
+ }
+
+ query += ` ORDER BY f.id DESC LIMIT 1`
+
+ files, err := queryFilesWithTags(query, args...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to query files for tag %s: %w", tagValue, err)
+ }
+
+ if len(files) > 0 {
+ allFiles = append(allFiles, files[0])
+ }
+ }
+
+ return allFiles, nil
+}
+
func loadConfig() error {
config = Config{
DatabasePath: "./database.db",
diff --git a/templates/_header.html b/templates/_header.html
@@ -23,7 +23,8 @@
<a href="/tags#tag-{{$cat}}">{{$cat}}</a>
<ul>
{{range $tags}}<li><a href="/tag/{{$cat}}/{{.Value}}">{{.Value}} ({{.Count}})</a></li>
- {{end}}<li><a href="/tag/{{$cat}}/unassigned">Unassigned</a></li>
+ {{end}}<li><a href="/tag/{{$cat}}/previews">Previews</a></li>
+ <li><a href="/tag/{{$cat}}/unassigned">Unassigned</a></li>
</ul>
</li>{{end}}
<li><a href="/bulk-tag">Bulk Editor</a></li>
diff --git a/templates/tags.html b/templates/tags.html
@@ -9,6 +9,7 @@
{{range $tags}}
<li><a href="/tag/{{$cat}}/{{.Value}}">{{.Value}} ({{.Count}})</a></li>
{{end}}
+ <li><a href="/tag/{{$cat}}/previews">Previews</a></li>
<li><a href="/tag/{{$cat}}/unassigned">Unassigned</a></li>
</ul>
</li>