user page: frequent tags per category

This commit is contained in:
perennial
2024-10-22 20:34:14 +11:00
parent 7a10cd6277
commit 13ef45834d
2 changed files with 159 additions and 115 deletions
+99 -5
View File
@@ -92,10 +92,10 @@
</small>
</div>
<div class="border border-2 border-neutral-700 rounded-5 p-4">
<!-- <div class="border border-2 border-neutral-700 rounded-5 p-4">
<h3><i class="bi bi-tags me-2"></i>Frequently used tags</h3>
<div class="d-flex flex-wrap">
{{- range .User.FrequentTags }}
{{- range .User.Categories.illustrations.FrequentTags }}
<div class="me-2 mb-2">
<small>
<a href="/tags/{{ escapeString(.Name) }}" class="text-primary-emphasis fw-semibold text-decoration-none text-wrap">#{{ .Name }}</a>
@@ -106,7 +106,8 @@
</div>
{{- end }}
</div>
</div>
</div> -->
</div>
</div>
</div>
@@ -138,6 +139,22 @@
<!-- Illustrations -->
{{ if len(.User.Illustrations) != 0 }}
<h2 class="d-flex align-items-end">Illustrations<span class="fs-6 bg-charcoal-surface1 rounded px-2 py-1 ms-2">{{ .User.WorkCounts.Illustrations }}</span></h2>
<div class="d-flex flex-nowrap overflow-scroll mb-3">
{{- range .User.Categories.illustrations.FrequentTags }}
<div class="bg-charcoal-surface1 rounded px-3 py-2 me-2 mb-2 w-auto">
<small class="d-flex flex-column align-items-center">
<span>
<a href="/tags/{{ escapeString(.Name) }}" class="text-primary-emphasis fw-semibold text-decoration-none text-nowrap">#{{ .Name }}</a>
</span>
{{- if .TranslatedName }}
<small class="text-body-secondary fw-medium text-nowrap">({{ .TranslatedName }})</small>
{{- end }}
</small>
</div>
{{- end }}
</div>
<div class="row row-cols-2 row-cols-md-4 row-cols-lg-6 g-4">
{{- include "fragments/small-tn" .User.Illustrations }}
</div>
@@ -155,8 +172,23 @@
<hr class="my-4">
<h2 class="d-flex align-items-end">Manga<span class="fs-6 bg-charcoal-surface1 rounded px-2 py-1 ms-2">{{ .User.WorkCounts.Manga }}</span></h2>
<div class="d-flex flex-nowrap overflow-scroll mb-3">
{{- range .User.Categories.manga.FrequentTags }}
<div class="bg-charcoal-surface1 rounded px-3 py-2 me-2 mb-2 w-auto">
<small class="d-flex flex-column align-items-center">
<span>
<a href="/tags/{{ escapeString(.Name) }}" class="text-primary-emphasis fw-semibold text-decoration-none text-nowrap">#{{ .Name }}</a>
</span>
{{- if .TranslatedName }}
<small class="text-body-secondary fw-medium text-nowrap">({{ .TranslatedName }})</small>
{{- end }}
</small>
</div>
{{- end }}
</div>
{{- if len(.User.MangaSeries) != 0 }}
<div class="overflow-scroll mb-5" id="horizontal-scroll">
<div class="overflow-scroll mb-4" id="horizontal-scroll">
<div class="row flex-nowrap">
{{- range .User.MangaSeries }}
{{- include "fragments/mangaseries-tn" . }}
@@ -181,8 +213,23 @@
<hr class="my-4">
<h2 class="d-flex align-items-end">Novels<span class="fs-6 bg-charcoal-surface1 rounded px-2 py-1 ms-2">{{ .User.WorkCounts.Novels }}</span></h2>
<div class="d-flex flex-nowrap overflow-scroll mb-3">
{{- range .User.Categories.novels.FrequentTags }}
<div class="bg-charcoal-surface1 rounded px-3 py-2 me-2 mb-2 w-auto">
<small class="d-flex flex-column align-items-center">
<span>
<a href="/tags/{{ escapeString(.Name) }}" class="text-primary-emphasis fw-semibold text-decoration-none text-nowrap">#{{ .Name }}</a>
</span>
{{- if .TranslatedName }}
<small class="text-body-secondary fw-medium text-nowrap">({{ .TranslatedName }})</small>
{{- end }}
</small>
</div>
{{- end }}
</div>
{{- if len(.User.NovelSeries) != 0 }}
<div class="overflow-scroll mb-5" id="horizontal-scroll">
<div class="overflow-scroll mb-4" id="horizontal-scroll">
<div class="row flex-nowrap">
{{- range .User.NovelSeries }}
{{- include "fragments/novelseries-tn" . }}
@@ -218,6 +265,22 @@
{{- combinedUrl := "/artworks-multi/" + joinArtworkIds(.User.Illustrations) }}
<!-- <a href="{{combinedUrl}}" class="custom-btn-secondary">View all</a> -->
</div>
<div class="d-flex flex-nowrap overflow-scroll mb-3">
{{- range .User.Categories.illustrations.FrequentTags }}
<div class="bg-charcoal-surface1 rounded px-3 py-2 me-2 mb-2 w-auto">
<small class="d-flex flex-column align-items-center">
<span>
<a href="/tags/{{ escapeString(.Name) }}" class="text-primary-emphasis fw-semibold text-decoration-none text-nowrap">#{{ .Name }}</a>
</span>
{{- if .TranslatedName }}
<small class="text-body-secondary fw-medium text-nowrap">({{ .TranslatedName }})</small>
{{- end }}
</small>
</div>
{{- end }}
</div>
<div class="row row-cols-2 row-cols-md-4 row-cols-lg-6 g-4">
{{- include "fragments/small-tn" .User.Illustrations }}
</div>
@@ -231,6 +294,21 @@
<!-- <a href="{{combinedUrl}}" class="custom-btn-secondary">View all</a> -->
</div>
<div class="d-flex flex-nowrap overflow-scroll mb-3">
{{- range .User.Categories.manga.FrequentTags }}
<div class="bg-charcoal-surface1 rounded px-3 py-2 me-2 mb-2 w-auto">
<small class="d-flex flex-column align-items-center">
<span>
<a href="/tags/{{ escapeString(.Name) }}" class="text-primary-emphasis fw-semibold text-decoration-none text-nowrap">#{{ .Name }}</a>
</span>
{{- if .TranslatedName }}
<small class="text-body-secondary fw-medium text-nowrap">({{ .TranslatedName }})</small>
{{- end }}
</small>
</div>
{{- end }}
</div>
{{- if len(.User.MangaSeries) != 0 }}
<div class="overflow-scroll mb-4" id="horizontal-scroll">
<div class="row flex-nowrap">
@@ -251,6 +329,22 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="d-flex align-items-end mb-0">Novels<span class="fs-6 bg-charcoal-surface1 rounded px-2 py-1 ms-2">{{ .User.WorkCounts.Novels }}</span></h2>
</div>
<div class="d-flex flex-nowrap overflow-scroll mb-3">
{{- range .User.Categories.novels.FrequentTags }}
<div class="bg-charcoal-surface1 rounded px-3 py-2 me-2 mb-2 w-auto">
<small class="d-flex flex-column align-items-center">
<span>
<a href="/tags/{{ escapeString(.Name) }}" class="text-primary-emphasis fw-semibold text-decoration-none text-nowrap">#{{ .Name }}</a>
</span>
{{- if .TranslatedName }}
<small class="text-body-secondary fw-medium text-nowrap">({{ .TranslatedName }})</small>
{{- end }}
</small>
</div>
{{- end }}
</div>
<div class="row row-cols-1 row-cols-lg-2 g-4">
{{- range .User.Novels }}
<div class="col mt-1">
+60 -110
View File
@@ -15,17 +15,18 @@ import (
"go.uber.org/zap"
)
// UserWorkCategory is a struct that represents a category of user work with a page limit.
// UserWorkCategory is a struct that represents a category of user work with a page limit and frequent tags.
//
// pixivfe internal data type. not used by pixiv.
type UserWorkCategory struct {
Value string
PageLimit int
Value string
PageLimit int
FrequentTags []FrequentTag
}
// NewUserWorkCategory creates UserWorkCategory instances
func NewUserWorkCategory(value string) UserWorkCategory {
return UserWorkCategory{Value: value, PageLimit: 0}
return UserWorkCategory{Value: value, PageLimit: 0, FrequentTags: []FrequentTag{}}
}
func (c *UserWorkCategory) SetPageLimit(limit int) {
@@ -103,6 +104,8 @@ type User struct {
// Used to display the number of works for a given category
WorkCounts WorkCounts
Categories map[string]*UserWorkCategory
}
// GetUserProfile retrieves the user profile, including counts, artworks/bookmarks, and social data.
@@ -296,15 +299,45 @@ func getPopulatedWorks(auditor *audit.Auditor, r *http.Request, user *User, id s
auditor.Logger.Debug("NovelBriefs populated", zap.Int("novelsCount", len(novels)))
}
// Fetch a user's frequently used tags if requested
// Initialize the user categories
user.Categories = map[string]*UserWorkCategory{
"illustrations": {Value: "illustrations"},
"manga": {Value: "manga"},
"novels": {Value: "novels"},
}
// Define the categories with their IDs
categories := []struct {
name string
ids string
workCategory *UserWorkCategory
}{
{
name: "illustrations",
ids: illustrationIDs,
workCategory: user.Categories["illustrations"],
},
{
name: "manga",
ids: mangaIDs,
workCategory: user.Categories["manga"],
},
{
name: "novels",
ids: novelIDs,
workCategory: user.Categories["novels"],
},
}
// Fetch frequent tags
if getTags {
auditor.Logger.Debug("Fetching frequent tags")
user.FrequentTags, err = populateFrequentTags(auditor, r, id, currentCategory, illustrationIDs, mangaIDs, novelIDs)
err := populateFrequentTags(auditor, r, id, categories)
if err != nil {
auditor.SugaredLogger.Error("Failed to populate frequent tags", zap.Error(err))
return err
}
auditor.Logger.Debug("Frequent tags populated", zap.Int("tagsCount", len(user.FrequentTags)))
auditor.Logger.Debug("Frequent tags populated")
}
// Handle series data
@@ -330,109 +363,24 @@ func populateFrequentTags(
auditor *audit.Auditor,
r *http.Request,
id string,
currentCategory *UserWorkCategory,
illustrationIDs string,
mangaIDs string,
novelIDs string,
) ([]FrequentTag, error) {
var (
primaryTagsIDs []string // For illustrationIDs and mangaIDs
secondaryTagsIDs []string // For novelIDs
)
// Append illustrationIDs to primaryTagsIDs if applicable
if illustrationIDs != "" {
primaryTagsIDs = append(primaryTagsIDs, illustrationIDs)
auditor.Logger.Debug(
"illustrationIDs found, appended to primaryTagsIDs",
// zap.Strings("primaryTagsIDs", primaryTagsIDs),
// zap.String("illustrationIDs", illustrationIDs),
)
}
// Append mangaIDs to primaryTagsIDs if applicable
if mangaIDs != "" {
primaryTagsIDs = append(primaryTagsIDs, mangaIDs)
auditor.Logger.Debug(
"mangaIDs found, appended to primaryTagsIDs",
// zap.Strings("primaryTagsIDs", primaryTagsIDs),
// zap.String("mangaIDs", mangaIDs),
)
}
// Append novelIDs to secondaryTagsIDs if applicable
if novelIDs != "" {
secondaryTagsIDs = append(secondaryTagsIDs, novelIDs)
auditor.Logger.Debug(
"novelIDs found, appended to secondaryTagsIDs",
// zap.Strings("secondaryTagsIDs", secondaryTagsIDs),
// zap.String("novelIDs", novelIDs),
)
}
// Function to count the number of items based on delimiter
countIDs := func(ids []string) int {
count := 0
for _, idStr := range ids {
// Each idStr contains multiple "&ids[]=" entries, so we split by "&ids[]=" and count non-empty parts
parts := strings.Split(idStr, "&ids[]=")
for _, part := range parts {
if part != "" {
count++
}
}
categories []struct {
name string
ids string
workCategory *UserWorkCategory
},
) error {
for _, cat := range categories {
if cat.ids == "" {
continue
}
return count
}
// Count the number of items in each group
primaryCount := countIDs(primaryTagsIDs)
secondaryCount := countIDs(secondaryTagsIDs)
auditor.Logger.Debug(
"Counts of primary and secondary tags",
zap.Int("primaryCount", primaryCount),
zap.Int("secondaryCount", secondaryCount),
)
var finalTagsIDs []string
// Decide which tagsIDs to use based on the count
if primaryCount > secondaryCount {
finalTagsIDs = primaryTagsIDs
auditor.Logger.Debug(
"Selected primaryTagsIDs as finalTagsIDs",
zap.Strings("finalTagsIDs", finalTagsIDs),
)
} else {
finalTagsIDs = secondaryTagsIDs
auditor.Logger.Debug(
"Selected secondaryTagsIDs as finalTagsIDs",
zap.Strings("finalTagsIDs", finalTagsIDs),
)
}
// Check if any tags were found
if len(finalTagsIDs) != 0 {
// Concatenate IDs to form a single string
ids := strings.Join(finalTagsIDs, "")
frequentTags, err := fetchFrequentTags(auditor, r, ids, currentCategory)
tags, err := fetchFrequentTags(auditor, r, cat.ids, cat.workCategory)
if err != nil {
return nil, fmt.Errorf("failed to get user frequent tags: %w", err)
auditor.SugaredLogger.Error("Failed to fetch frequent tags", zap.String("category", cat.name), zap.Error(err))
return err
}
return frequentTags, nil
cat.workCategory.FrequentTags = tags
}
auditor.Logger.Warn(
"No tags found to fetch, setting FrequentTags to empty slice",
zap.String("currentCategory", currentCategory.Value),
zap.Strings("primaryTagsIDs", primaryTagsIDs),
zap.Strings("secondaryTagsIDs", secondaryTagsIDs),
zap.String("illustrationIDs", illustrationIDs),
zap.String("mangaIDs", mangaIDs),
zap.String("novelIDs", novelIDs),
)
return []FrequentTag{}, nil
return nil
}
// The HasID interface allows for generic handling of the ArtworkBrief
@@ -783,15 +731,17 @@ func handleSeriesData(auditor *audit.Auditor, mangaSeriesData, novelSeriesData j
}
// fetchFrequentTags fetches a user's frequently used tags, based on category.
func fetchFrequentTags(auditor *audit.Auditor, r *http.Request, ids string, currentCategory *UserWorkCategory) ([]FrequentTag, error) {
func fetchFrequentTags(auditor *audit.Auditor, r *http.Request, ids string, workCategory *UserWorkCategory) ([]FrequentTag, error) {
var tags []FrequentTag
var URL string
// TODO: If overall, request both?
if currentCategory != &CategoryNovels {
switch workCategory.Value {
case "illustrations", "manga":
URL = GetUserFrequentArtworkTagsURL(ids)
} else {
case "novels":
URL = GetUserFrequentNovelTagsURL(ids)
default:
return tags, fmt.Errorf("unsupported category: %s", workCategory.Value)
}
// Return early if there are no IDs