feat: include title in watchlist csv export
This commit is contained in:
@@ -210,23 +210,36 @@ func (s *Service) ImportWatchlist(ctx context.Context, userID string, r io.Reade
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, record := range records {
|
for i, record := range records {
|
||||||
if len(record) < 4 {
|
// New format: anime_id,title,status,current_episode,current_time_seconds
|
||||||
log.Printf("skipping row %d: insufficient columns", i+2) // i+2 because i is 0-indexed record after header
|
// Old format: anime_id,status,current_episode,current_time_seconds
|
||||||
|
var animeIDStr, status, episodeStr, timeStr string
|
||||||
|
|
||||||
|
if len(record) >= 5 {
|
||||||
|
animeIDStr = record[0]
|
||||||
|
status = record[2]
|
||||||
|
episodeStr = record[3]
|
||||||
|
timeStr = record[4]
|
||||||
|
} else if len(record) >= 4 {
|
||||||
|
animeIDStr = record[0]
|
||||||
|
status = record[1]
|
||||||
|
episodeStr = record[2]
|
||||||
|
timeStr = record[3]
|
||||||
|
} else {
|
||||||
|
log.Printf("skipping row %d: insufficient columns", i+2)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
animeID, err := strconv.ParseInt(record[0], 10, 64)
|
animeID, err := strconv.ParseInt(animeIDStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("row %d: invalid anime id: %w", i+2, err)
|
return fmt.Errorf("row %d: invalid anime id: %w", i+2, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
status := record[1]
|
|
||||||
if _, ok := validStatuses[status]; !ok {
|
if _, ok := validStatuses[status]; !ok {
|
||||||
status = "plan_to_watch"
|
status = "plan_to_watch"
|
||||||
}
|
}
|
||||||
|
|
||||||
currentEpisode, _ := strconv.ParseInt(record[2], 10, 64)
|
currentEpisode, _ := strconv.ParseInt(episodeStr, 10, 64)
|
||||||
currentTimeSeconds, _ := strconv.ParseFloat(record[3], 64)
|
currentTimeSeconds, _ := strconv.ParseFloat(timeStr, 64)
|
||||||
|
|
||||||
if err := s.ensureAnimeExists(ctx, animeID); err != nil {
|
if err := s.ensureAnimeExists(ctx, animeID); err != nil {
|
||||||
return fmt.Errorf("row %d: failed to ensure anime: %w", i+2, err)
|
return fmt.Errorf("row %d: failed to ensure anime: %w", i+2, err)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
<div id="watchlist-items" class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6">
|
<div id="watchlist-items" class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6">
|
||||||
{{range $.AllEntries}}
|
{{range $.AllEntries}}
|
||||||
<div class="watchlist-item flex w-full flex-col gap-2" data-status="{{.Status}}" data-updated-at="{{.UpdatedAt.Unix}}" data-episode="{{.CurrentEpisode.Int64}}" data-time="{{.CurrentTimeSeconds}}">
|
<div class="watchlist-item flex w-full flex-col gap-2" data-status="{{.Status}}" data-updated-at="{{.UpdatedAt.Unix}}" data-episode="{{.CurrentEpisode.Int64}}" data-time="{{.CurrentTimeSeconds}}" data-title="{{.DisplayTitle}}">
|
||||||
<div class="group relative flex aspect-2/3 w-full flex-col overflow-hidden bg-white/5 after:absolute after:inset-0 after:bg-black/80 after:opacity-0 hover:after:opacity-100 after:transition-opacity">
|
<div class="group relative flex aspect-2/3 w-full flex-col overflow-hidden bg-white/5 after:absolute after:inset-0 after:bg-black/80 after:opacity-0 hover:after:opacity-100 after:transition-opacity">
|
||||||
<a href="/anime/{{.AnimeID}}" class="absolute inset-0 z-10"></a>
|
<a href="/anime/{{.AnimeID}}" class="absolute inset-0 z-10"></a>
|
||||||
<img src="{{.ImageUrl}}" alt="{{.DisplayTitle}}" class="h-full w-full object-cover" loading="lazy" />
|
<img src="{{.ImageUrl}}" alt="{{.DisplayTitle}}" class="h-full w-full object-cover" loading="lazy" />
|
||||||
@@ -154,13 +154,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let csv = 'anime_id,status,current_episode,current_time_seconds\n';
|
let csv = 'anime_id,title,status,current_episode,current_time_seconds\n';
|
||||||
items.forEach(function(item) {
|
items.forEach(function(item) {
|
||||||
const animeId = item.querySelector('a').href.split('/').pop();
|
const animeId = item.querySelector('a').href.split('/').pop();
|
||||||
|
const title = (item.dataset.title || '').replace(/"/g, '""');
|
||||||
const status = item.dataset.status || 'plan_to_watch';
|
const status = item.dataset.status || 'plan_to_watch';
|
||||||
const episode = item.dataset.episode || '0';
|
const episode = item.dataset.episode || '0';
|
||||||
const time = item.dataset.time || '0';
|
const time = item.dataset.time || '0';
|
||||||
csv += `${animeId},${status},${episode},${time}\n`;
|
csv += `${animeId},"${title}",${status},${episode},${time}\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const blob = new Blob([csv], { type: 'text/csv' });
|
const blob = new Blob([csv], { type: 'text/csv' });
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="watchlist-section" data-status="{{$status}}">
|
<div class="watchlist-section" data-status="{{$status}}">
|
||||||
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6">
|
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6">
|
||||||
{{range $entries}}
|
{{range $entries}}
|
||||||
<div class="watchlist-item flex w-full flex-col gap-2" data-status="{{.Status}}" data-updated-at="{{.UpdatedAt.Unix}}" data-episode="{{.CurrentEpisode.Int64}}" data-time="{{.CurrentTimeSeconds}}">
|
<div class="watchlist-item flex w-full flex-col gap-2" data-status="{{.Status}}" data-updated-at="{{.UpdatedAt.Unix}}" data-episode="{{.CurrentEpisode.Int64}}" data-time="{{.CurrentTimeSeconds}}" data-title="{{.DisplayTitle}}">
|
||||||
<div class="group relative flex aspect-2/3 w-full flex-col overflow-hidden bg-white/5 after:absolute after:inset-0 after:bg-black/80 after:opacity-0 hover:after:opacity-100 after:transition-opacity">
|
<div class="group relative flex aspect-2/3 w-full flex-col overflow-hidden bg-white/5 after:absolute after:inset-0 after:bg-black/80 after:opacity-0 hover:after:opacity-100 after:transition-opacity">
|
||||||
<a href="/anime/{{.AnimeID}}" class="absolute inset-0"></a>
|
<a href="/anime/{{.AnimeID}}" class="absolute inset-0"></a>
|
||||||
<img src="{{.ImageUrl}}" alt="{{.DisplayTitle}}" class="h-full w-full object-cover" loading="lazy" />
|
<img src="{{.ImageUrl}}" alt="{{.DisplayTitle}}" class="h-full w-full object-cover" loading="lazy" />
|
||||||
@@ -155,13 +155,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let csv = 'anime_id,status,current_episode,current_time_seconds\n';
|
let csv = 'anime_id,title,status,current_episode,current_time_seconds\n';
|
||||||
items.forEach(function(item) {
|
items.forEach(function(item) {
|
||||||
const animeId = item.querySelector('a').href.split('/').pop();
|
const animeId = item.querySelector('a').href.split('/').pop();
|
||||||
|
const title = (item.dataset.title || '').replace(/"/g, '""');
|
||||||
const status = item.dataset.status || 'plan_to_watch';
|
const status = item.dataset.status || 'plan_to_watch';
|
||||||
const episode = item.dataset.episode || '0';
|
const episode = item.dataset.episode || '0';
|
||||||
const time = item.dataset.time || '0';
|
const time = item.dataset.time || '0';
|
||||||
csv += `${animeId},${status},${episode},${time}\n`;
|
csv += `${animeId},"${title}",${status},${episode},${time}\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const blob = new Blob([csv], { type: 'text/csv' });
|
const blob = new Blob([csv], { type: 'text/csv' });
|
||||||
|
|||||||
Reference in New Issue
Block a user