Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a9a66fcc5 | ||
|
|
2df3ecbf36 | ||
|
|
9da193e718 | ||
|
|
7f64b92b9d | ||
|
|
b9645752f5 | ||
|
|
9c8337d0a8 |
@@ -207,6 +207,12 @@ CREATE TABLE IF NOT EXISTS media_items (
|
|||||||
-- Summary (distinct from description - may merge with Calibre description)
|
-- Summary (distinct from description - may merge with Calibre description)
|
||||||
summary TEXT, -- Summary from ComicInfo.xml (may be merged with description from Calibre)
|
summary TEXT, -- Summary from ComicInfo.xml (may be merged with description from Calibre)
|
||||||
|
|
||||||
|
-- Per-field user-override tracking. Column names listed here (e.g.
|
||||||
|
-- 'description', 'tags', 'cover_image_path') are user-customized and MUST
|
||||||
|
-- NOT be overwritten by library scans or per-book rescans; only the
|
||||||
|
-- reset-to-scanned-defaults action clears them.
|
||||||
|
metadata_overrides TEXT[] NOT NULL DEFAULT '{}',
|
||||||
|
|
||||||
-- Chapter metadata for reader navigation and progress tracking
|
-- Chapter metadata for reader navigation and progress tracking
|
||||||
-- Caches detected chapter structure to avoid re-parsing files
|
-- Caches detected chapter structure to avoid re-parsing files
|
||||||
-- Populated by ReaderService.DetectChapters() on first read
|
-- Populated by ReaderService.DetectChapters() on first read
|
||||||
@@ -232,6 +238,11 @@ CREATE INDEX IF NOT EXISTS idx_media_items_contributors_gin ON media_items USING
|
|||||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS tags_search TEXT[];
|
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS tags_search TEXT[];
|
||||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS contributors_search TEXT[];
|
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS contributors_search TEXT[];
|
||||||
|
|
||||||
|
-- Per-field user-override tracking (idempotent backfill for existing
|
||||||
|
-- installs; the column is also declared in the media_items table
|
||||||
|
-- definition above for fresh databases)
|
||||||
|
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS metadata_overrides TEXT[] NOT NULL DEFAULT '{}';
|
||||||
|
|
||||||
-- Create GIN indexes for fast search field searches
|
-- Create GIN indexes for fast search field searches
|
||||||
CREATE INDEX IF NOT EXISTS idx_media_items_tags_search ON media_items USING GIN (tags_search);
|
CREATE INDEX IF NOT EXISTS idx_media_items_tags_search ON media_items USING GIN (tags_search);
|
||||||
CREATE INDEX IF NOT EXISTS idx_media_items_contributors_search ON media_items USING GIN (contributors_search);
|
CREATE INDEX IF NOT EXISTS idx_media_items_contributors_search ON media_items USING GIN (contributors_search);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.30.0
|
// sqlc v1.31.1
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.30.0
|
// sqlc v1.31.1
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
@@ -306,6 +306,7 @@ type MediaItems struct {
|
|||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
// Summary from ComicInfo.xml (may be merged with description from Calibre)
|
// Summary from ComicInfo.xml (may be merged with description from Calibre)
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
||||||
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
||||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.30.0
|
// sqlc v1.31.1
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
@@ -30,6 +30,9 @@ type Querier interface {
|
|||||||
ClearDeviceSyncQueue(ctx context.Context, deviceID pgtype.UUID) error
|
ClearDeviceSyncQueue(ctx context.Context, deviceID pgtype.UUID) error
|
||||||
ClearKoboShelf(ctx context.Context, deviceID pgtype.UUID) error
|
ClearKoboShelf(ctx context.Context, deviceID pgtype.UUID) error
|
||||||
ClearKoboShelfByName(ctx context.Context, arg ClearKoboShelfByNameParams) error
|
ClearKoboShelfByName(ctx context.Context, arg ClearKoboShelfByNameParams) error
|
||||||
|
// Reset an item to scanned defaults: clears per-field user overrides so the
|
||||||
|
// next rescan can freely overwrite user-customized metadata.
|
||||||
|
ClearMediaItemMetadataOverrides(ctx context.Context, id pgtype.UUID) error
|
||||||
CountAdmins(ctx context.Context) (int64, error)
|
CountAdmins(ctx context.Context) (int64, error)
|
||||||
// Count unlinked books for a device
|
// Count unlinked books for a device
|
||||||
CountUnlinkedBooks(ctx context.Context, deviceID pgtype.UUID) (int64, error)
|
CountUnlinkedBooks(ctx context.Context, deviceID pgtype.UUID) (int64, error)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.30.0
|
// sqlc v1.31.1
|
||||||
// source: queries.sql
|
// source: queries.sql
|
||||||
|
|
||||||
package database
|
package database
|
||||||
@@ -226,6 +226,18 @@ func (q *Queries) ClearKoboShelfByName(ctx context.Context, arg ClearKoboShelfBy
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ClearMediaItemMetadataOverrides = `-- name: ClearMediaItemMetadataOverrides :exec
|
||||||
|
UPDATE media_items SET metadata_overrides = '{}', updated_at = NOW()
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
// Reset an item to scanned defaults: clears per-field user overrides so the
|
||||||
|
// next rescan can freely overwrite user-customized metadata.
|
||||||
|
func (q *Queries) ClearMediaItemMetadataOverrides(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, ClearMediaItemMetadataOverrides, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const CountAdmins = `-- name: CountAdmins :one
|
const CountAdmins = `-- name: CountAdmins :one
|
||||||
SELECT COUNT(*) FROM users WHERE role = 'admin'
|
SELECT COUNT(*) FROM users WHERE role = 'admin'
|
||||||
`
|
`
|
||||||
@@ -900,7 +912,7 @@ const CreateMediaItem = `-- name: CreateMediaItem :one
|
|||||||
INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, tags_search, asin, date_published, publisher, contributors, contributors_search, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, library_type_name)
|
INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, tags_search, asin, date_published, publisher, contributors, contributors_search, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, library_type_name)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44)
|
||||||
ON CONFLICT (library_id, file_path) DO UPDATE SET updated_at = NOW()
|
ON CONFLICT (library_id, file_path) DO UPDATE SET updated_at = NOW()
|
||||||
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateMediaItemParams struct {
|
type CreateMediaItemParams struct {
|
||||||
@@ -1053,6 +1065,7 @@ func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -2447,7 +2460,7 @@ func (q *Queries) GetAnnotationsForBook(ctx context.Context, arg GetAnnotationsF
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetBooksByTag = `-- name: GetBooksByTag :many
|
const GetBooksByTag = `-- name: GetBooksByTag :many
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items
|
||||||
WHERE library_id = $1 AND tags @> ARRAY[$2::text]
|
WHERE library_id = $1 AND tags @> ARRAY[$2::text]
|
||||||
ORDER BY title ASC
|
ORDER BY title ASC
|
||||||
`
|
`
|
||||||
@@ -2520,6 +2533,7 @@ func (q *Queries) GetBooksByTag(ctx context.Context, arg GetBooksByTagParams) ([
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -2619,7 +2633,7 @@ func (q *Queries) GetCollectionItems(ctx context.Context, collectionID pgtype.UU
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetCollectionItemsForDashboard = `-- name: GetCollectionItemsForDashboard :many
|
const GetCollectionItemsForDashboard = `-- name: GetCollectionItemsForDashboard :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, ci.excluded FROM media_items mi
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, ci.excluded FROM media_items mi
|
||||||
INNER JOIN collection_items ci ON ci.media_item_id = mi.id
|
INNER JOIN collection_items ci ON ci.media_item_id = mi.id
|
||||||
WHERE ci.collection_id = $1
|
WHERE ci.collection_id = $1
|
||||||
AND ($2::uuid IS NULL OR mi.library_id = $2::uuid)
|
AND ($2::uuid IS NULL OR mi.library_id = $2::uuid)
|
||||||
@@ -2687,6 +2701,7 @@ type GetCollectionItemsForDashboardRow struct {
|
|||||||
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
||||||
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
||||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||||
@@ -2761,6 +2776,7 @@ func (q *Queries) GetCollectionItemsForDashboard(ctx context.Context, arg GetCol
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -2913,7 +2929,7 @@ func (q *Queries) GetCollectionsForBook(ctx context.Context, mediaItemID pgtype.
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetContinueReadingItems = `-- name: GetContinueReadingItems :many
|
const GetContinueReadingItems = `-- name: GetContinueReadingItems :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
||||||
INNER JOIN (
|
INNER JOIN (
|
||||||
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
|
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
|
||||||
FROM reading_progress
|
FROM reading_progress
|
||||||
@@ -2997,6 +3013,7 @@ func (q *Queries) GetContinueReadingItems(ctx context.Context, arg GetContinueRe
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -3030,7 +3047,7 @@ WITH user_series_progress AS (
|
|||||||
GROUP BY mi.series
|
GROUP BY mi.series
|
||||||
),
|
),
|
||||||
next_books AS (
|
next_books AS (
|
||||||
SELECT DISTINCT ON (mi.series) mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence,
|
SELECT DISTINCT ON (mi.series) mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence,
|
||||||
usp.last_read_at
|
usp.last_read_at
|
||||||
FROM media_items mi
|
FROM media_items mi
|
||||||
JOIN user_series_progress usp ON mi.series = usp.series
|
JOIN user_series_progress usp ON mi.series = usp.series
|
||||||
@@ -3038,7 +3055,7 @@ next_books AS (
|
|||||||
AND (mi.series_number > usp.max_read_number OR usp.max_read_number IS NULL)
|
AND (mi.series_number > usp.max_read_number OR usp.max_read_number IS NULL)
|
||||||
ORDER BY mi.series, mi.series_number ASC NULLS LAST
|
ORDER BY mi.series, mi.series_number ASC NULLS LAST
|
||||||
)
|
)
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence, last_read_at FROM next_books
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence, last_read_at FROM next_books
|
||||||
ORDER BY last_read_at DESC NULLS LAST
|
ORDER BY last_read_at DESC NULLS LAST
|
||||||
LIMIT $1
|
LIMIT $1
|
||||||
`
|
`
|
||||||
@@ -3103,6 +3120,7 @@ type GetContinueSeriesItemsRow struct {
|
|||||||
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
||||||
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
||||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||||
@@ -3177,6 +3195,7 @@ func (q *Queries) GetContinueSeriesItems(ctx context.Context, arg GetContinueSer
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -4306,7 +4325,7 @@ func (q *Queries) GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetLibraryItems = `-- name: GetLibraryItems :many
|
const GetLibraryItems = `-- name: GetLibraryItems :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
||||||
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
||||||
ORDER BY mi.created_at DESC
|
ORDER BY mi.created_at DESC
|
||||||
`
|
`
|
||||||
@@ -4374,6 +4393,7 @@ func (q *Queries) GetLibraryItems(ctx context.Context, libraryID pgtype.UUID) ([
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -4805,7 +4825,7 @@ func (q *Queries) GetMediaHighlights(ctx context.Context, arg GetMediaHighlights
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItem = `-- name: GetMediaItem :one
|
const GetMediaItem = `-- name: GetMediaItem :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE id = $1
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) {
|
func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) {
|
||||||
@@ -4865,6 +4885,7 @@ func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems,
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -4878,7 +4899,7 @@ func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItemByFilePath = `-- name: GetMediaItemByFilePath :one
|
const GetMediaItemByFilePath = `-- name: GetMediaItemByFilePath :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 AND library_id = $2
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 AND library_id = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetMediaItemByFilePathParams struct {
|
type GetMediaItemByFilePathParams struct {
|
||||||
@@ -4943,6 +4964,7 @@ func (q *Queries) GetMediaItemByFilePath(ctx context.Context, arg GetMediaItemBy
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -4956,7 +4978,7 @@ func (q *Queries) GetMediaItemByFilePath(ctx context.Context, arg GetMediaItemBy
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItemByFilePathAnyLibrary = `-- name: GetMediaItemByFilePathAnyLibrary :one
|
const GetMediaItemByFilePathAnyLibrary = `-- name: GetMediaItemByFilePathAnyLibrary :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 LIMIT 1
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath string) (MediaItems, error) {
|
func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath string) (MediaItems, error) {
|
||||||
@@ -5016,6 +5038,7 @@ func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -5030,7 +5053,7 @@ func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath
|
|||||||
|
|
||||||
const GetMediaItemByFilePathForSync = `-- name: GetMediaItemByFilePathForSync :one
|
const GetMediaItemByFilePathForSync = `-- name: GetMediaItemByFilePathForSync :one
|
||||||
|
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -5093,6 +5116,7 @@ func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath st
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -5106,7 +5130,7 @@ func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath st
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItemByKoboContentId = `-- name: GetMediaItemByKoboContentId :one
|
const GetMediaItemByKoboContentId = `-- name: GetMediaItemByKoboContentId :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE kobo_content_id = $1
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE kobo_content_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error) {
|
func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error) {
|
||||||
@@ -5166,6 +5190,7 @@ func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -5179,7 +5204,7 @@ func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItemByOPFIdentifier = `-- name: GetMediaItemByOPFIdentifier :one
|
const GetMediaItemByOPFIdentifier = `-- name: GetMediaItemByOPFIdentifier :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_identifier = $1
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_identifier = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
// Get media item by OPF identifier
|
// Get media item by OPF identifier
|
||||||
@@ -5240,6 +5265,7 @@ func (q *Queries) GetMediaItemByOPFIdentifier(ctx context.Context, opfIdentifier
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -5253,7 +5279,7 @@ func (q *Queries) GetMediaItemByOPFIdentifier(ctx context.Context, opfIdentifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItemByOPFUUID = `-- name: GetMediaItemByOPFUUID :one
|
const GetMediaItemByOPFUUID = `-- name: GetMediaItemByOPFUUID :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_uuid = $1
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_uuid = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
// Get media item by OPF UUID
|
// Get media item by OPF UUID
|
||||||
@@ -5314,6 +5340,7 @@ func (q *Queries) GetMediaItemByOPFUUID(ctx context.Context, opfUuid pgtype.Text
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -5327,7 +5354,7 @@ func (q *Queries) GetMediaItemByOPFUUID(ctx context.Context, opfUuid pgtype.Text
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItemBySHA256 = `-- name: GetMediaItemBySHA256 :one
|
const GetMediaItemBySHA256 = `-- name: GetMediaItemBySHA256 :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
// Get media item by SHA-256 hash
|
// Get media item by SHA-256 hash
|
||||||
@@ -5388,6 +5415,7 @@ func (q *Queries) GetMediaItemBySHA256(ctx context.Context, fileSha256 pgtype.Te
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -5401,7 +5429,7 @@ func (q *Queries) GetMediaItemBySHA256(ctx context.Context, fileSha256 pgtype.Te
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetMediaItemBySHA256AndLibrary = `-- name: GetMediaItemBySHA256AndLibrary :one
|
const GetMediaItemBySHA256AndLibrary = `-- name: GetMediaItemBySHA256AndLibrary :one
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 AND library_id = $2
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 AND library_id = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetMediaItemBySHA256AndLibraryParams struct {
|
type GetMediaItemBySHA256AndLibraryParams struct {
|
||||||
@@ -5467,6 +5495,7 @@ func (q *Queries) GetMediaItemBySHA256AndLibrary(ctx context.Context, arg GetMed
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -5807,7 +5836,7 @@ func (q *Queries) GetNextRetryTime(ctx context.Context) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetNotStartedItems = `-- name: GetNotStartedItems :many
|
const GetNotStartedItems = `-- name: GetNotStartedItems :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
||||||
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1 FROM reading_progress rp
|
SELECT 1 FROM reading_progress rp
|
||||||
@@ -5888,6 +5917,7 @@ func (q *Queries) GetNotStartedItems(ctx context.Context, arg GetNotStartedItems
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -6207,7 +6237,7 @@ func (q *Queries) GetReadingSpeed(ctx context.Context, arg GetReadingSpeedParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetRecentlyAddedItems = `-- name: GetRecentlyAddedItems :many
|
const GetRecentlyAddedItems = `-- name: GetRecentlyAddedItems :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
||||||
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
WHERE ($1::uuid IS NULL OR mi.library_id = $1::uuid)
|
||||||
ORDER BY mi.imported_at DESC NULLS LAST, mi.created_at DESC
|
ORDER BY mi.imported_at DESC NULLS LAST, mi.created_at DESC
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
@@ -6281,6 +6311,7 @@ func (q *Queries) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAdde
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -6301,7 +6332,7 @@ func (q *Queries) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAdde
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetRecentlyReadItems = `-- name: GetRecentlyReadItems :many
|
const GetRecentlyReadItems = `-- name: GetRecentlyReadItems :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi
|
||||||
INNER JOIN (
|
INNER JOIN (
|
||||||
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
|
SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at
|
||||||
FROM reading_progress
|
FROM reading_progress
|
||||||
@@ -6383,6 +6414,7 @@ func (q *Queries) GetRecentlyReadItems(ctx context.Context, arg GetRecentlyReadI
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -6503,7 +6535,7 @@ func (q *Queries) GetSavedFilters(ctx context.Context, arg GetSavedFiltersParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GetSeriesBooks = `-- name: GetSeriesBooks :many
|
const GetSeriesBooks = `-- name: GetSeriesBooks :many
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items
|
||||||
WHERE series = $1
|
WHERE series = $1
|
||||||
ORDER BY series_number ASC NULLS LAST
|
ORDER BY series_number ASC NULLS LAST
|
||||||
`
|
`
|
||||||
@@ -6571,6 +6603,7 @@ func (q *Queries) GetSeriesBooks(ctx context.Context, series pgtype.Text) ([]Med
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -8354,7 +8387,7 @@ func (q *Queries) ListLibraries(ctx context.Context) ([]ListLibrariesRow, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ListMediaItems = `-- name: ListMediaItems :many
|
const ListMediaItems = `-- name: ListMediaItems :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
||||||
FROM media_items mi
|
FROM media_items mi
|
||||||
JOIN libraries l ON mi.library_id = l.id
|
JOIN libraries l ON mi.library_id = l.id
|
||||||
JOIN library_types lt ON l.library_type_id = lt.id
|
JOIN library_types lt ON l.library_type_id = lt.id
|
||||||
@@ -8420,6 +8453,7 @@ type ListMediaItemsRow struct {
|
|||||||
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
||||||
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
||||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||||
@@ -8495,6 +8529,7 @@ func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams)
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -8517,7 +8552,7 @@ func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ListMediaItemsByLibrary = `-- name: ListMediaItemsByLibrary :many
|
const ListMediaItemsByLibrary = `-- name: ListMediaItemsByLibrary :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
||||||
FROM media_items mi
|
FROM media_items mi
|
||||||
JOIN libraries l ON mi.library_id = l.id
|
JOIN libraries l ON mi.library_id = l.id
|
||||||
JOIN library_types lt ON l.library_type_id = lt.id
|
JOIN library_types lt ON l.library_type_id = lt.id
|
||||||
@@ -8579,6 +8614,7 @@ type ListMediaItemsByLibraryRow struct {
|
|||||||
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
||||||
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
||||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||||
@@ -8654,6 +8690,7 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype.
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -8676,7 +8713,7 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype.
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ListMediaItemsBySHA256AndLibrary = `-- name: ListMediaItemsBySHA256AndLibrary :many
|
const ListMediaItemsBySHA256AndLibrary = `-- name: ListMediaItemsBySHA256AndLibrary :many
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 AND library_id = $2 ORDER BY file_path
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 AND library_id = $2 ORDER BY file_path
|
||||||
`
|
`
|
||||||
|
|
||||||
type ListMediaItemsBySHA256AndLibraryParams struct {
|
type ListMediaItemsBySHA256AndLibraryParams struct {
|
||||||
@@ -8748,6 +8785,7 @@ func (q *Queries) ListMediaItemsBySHA256AndLibrary(ctx context.Context, arg List
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -8768,7 +8806,7 @@ func (q *Queries) ListMediaItemsBySHA256AndLibrary(ctx context.Context, arg List
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ListMediaItemsMissingHash = `-- name: ListMediaItemsMissingHash :many
|
const ListMediaItemsMissingHash = `-- name: ListMediaItemsMissingHash :many
|
||||||
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 IS NULL ORDER BY created_at
|
SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 IS NULL ORDER BY created_at
|
||||||
`
|
`
|
||||||
|
|
||||||
// List media items that have no stored SHA-256 (imported before hashing existed)
|
// List media items that have no stored SHA-256 (imported before hashing existed)
|
||||||
@@ -8835,6 +8873,7 @@ func (q *Queries) ListMediaItemsMissingHash(ctx context.Context) ([]MediaItems,
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -8855,7 +8894,7 @@ func (q *Queries) ListMediaItemsMissingHash(ctx context.Context) ([]MediaItems,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ListMediaItemsSorted = `-- name: ListMediaItemsSorted :many
|
const ListMediaItemsSorted = `-- name: ListMediaItemsSorted :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
||||||
FROM media_items mi
|
FROM media_items mi
|
||||||
JOIN libraries l ON mi.library_id = l.id
|
JOIN libraries l ON mi.library_id = l.id
|
||||||
JOIN library_types lt ON l.library_type_id = lt.id
|
JOIN library_types lt ON l.library_type_id = lt.id
|
||||||
@@ -8990,6 +9029,7 @@ type ListMediaItemsSortedRow struct {
|
|||||||
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
||||||
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
||||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||||
@@ -9070,6 +9110,7 @@ func (q *Queries) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSo
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -10193,7 +10234,7 @@ func (q *Queries) SearchLanguageValues(ctx context.Context, arg SearchLanguageVa
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SearchMediaItems = `-- name: SearchMediaItems :many
|
const SearchMediaItems = `-- name: SearchMediaItems :many
|
||||||
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.imported_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.metadata_overrides, mi.chapter_metadata, mi.library_type_name, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name
|
||||||
FROM media_items mi
|
FROM media_items mi
|
||||||
JOIN libraries l ON mi.library_id = l.id
|
JOIN libraries l ON mi.library_id = l.id
|
||||||
JOIN library_types lt ON l.library_type_id = lt.id
|
JOIN library_types lt ON l.library_type_id = lt.id
|
||||||
@@ -10281,6 +10322,7 @@ type SearchMediaItemsRow struct {
|
|||||||
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
|
||||||
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
LibraryTypeName pgtype.Text `db:"library_type_name" json:"library_type_name"`
|
||||||
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
TagsSearch []string `db:"tags_search" json:"tags_search"`
|
||||||
@@ -10363,6 +10405,7 @@ func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsPara
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -11834,9 +11877,10 @@ UPDATE media_items SET
|
|||||||
alternate_info = $35,
|
alternate_info = $35,
|
||||||
scan_information = $36,
|
scan_information = $36,
|
||||||
summary = $37,
|
summary = $37,
|
||||||
|
metadata_overrides = $38,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateMediaItemParams struct {
|
type UpdateMediaItemParams struct {
|
||||||
@@ -11877,6 +11921,7 @@ type UpdateMediaItemParams struct {
|
|||||||
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
AlternateInfo []byte `db:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
|
||||||
Summary pgtype.Text `db:"summary" json:"summary"`
|
Summary pgtype.Text `db:"summary" json:"summary"`
|
||||||
|
MetadataOverrides []string `db:"metadata_overrides" json:"metadata_overrides"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) {
|
func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) {
|
||||||
@@ -11918,6 +11963,7 @@ func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams
|
|||||||
arg.AlternateInfo,
|
arg.AlternateInfo,
|
||||||
arg.ScanInformation,
|
arg.ScanInformation,
|
||||||
arg.Summary,
|
arg.Summary,
|
||||||
|
arg.MetadataOverrides,
|
||||||
)
|
)
|
||||||
var i MediaItems
|
var i MediaItems
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@@ -11974,6 +12020,7 @@ func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -11990,7 +12037,7 @@ const UpdateMediaItemChapterMetadata = `-- name: UpdateMediaItemChapterMetadata
|
|||||||
UPDATE media_items
|
UPDATE media_items
|
||||||
SET chapter_metadata = $2, updated_at = NOW()
|
SET chapter_metadata = $2, updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateMediaItemChapterMetadataParams struct {
|
type UpdateMediaItemChapterMetadataParams struct {
|
||||||
@@ -12055,6 +12102,7 @@ func (q *Queries) UpdateMediaItemChapterMetadata(ctx context.Context, arg Update
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -12162,7 +12210,7 @@ SET
|
|||||||
hash_confidence = $5,
|
hash_confidence = $5,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateMediaItemIdentifiersParams struct {
|
type UpdateMediaItemIdentifiersParams struct {
|
||||||
@@ -12241,6 +12289,7 @@ func (q *Queries) UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMedi
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
@@ -12261,7 +12310,7 @@ SET entitlement_id = $2,
|
|||||||
kobo_metadata = $5,
|
kobo_metadata = $5,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, imported_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, metadata_overrides, chapter_metadata, library_type_name, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateMediaItemKoboMetadataParams struct {
|
type UpdateMediaItemKoboMetadataParams struct {
|
||||||
@@ -12335,6 +12384,7 @@ func (q *Queries) UpdateMediaItemKoboMetadata(ctx context.Context, arg UpdateMed
|
|||||||
&i.AlternateInfo,
|
&i.AlternateInfo,
|
||||||
&i.ScanInformation,
|
&i.ScanInformation,
|
||||||
&i.Summary,
|
&i.Summary,
|
||||||
|
&i.MetadataOverrides,
|
||||||
&i.ChapterMetadata,
|
&i.ChapterMetadata,
|
||||||
&i.LibraryTypeName,
|
&i.LibraryTypeName,
|
||||||
&i.TagsSearch,
|
&i.TagsSearch,
|
||||||
|
|||||||
@@ -282,10 +282,17 @@ UPDATE media_items SET
|
|||||||
alternate_info = $35,
|
alternate_info = $35,
|
||||||
scan_information = $36,
|
scan_information = $36,
|
||||||
summary = $37,
|
summary = $37,
|
||||||
|
metadata_overrides = $38,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: ClearMediaItemMetadataOverrides :exec
|
||||||
|
-- Reset an item to scanned defaults: clears per-field user overrides so the
|
||||||
|
-- next rescan can freely overwrite user-customized metadata.
|
||||||
|
UPDATE media_items SET metadata_overrides = '{}', updated_at = NOW()
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: DeleteMediaItem :exec
|
-- name: DeleteMediaItem :exec
|
||||||
DELETE FROM media_items WHERE id = $1;
|
DELETE FROM media_items WHERE id = $1;
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ func parseTableNames() ([]string, error) {
|
|||||||
tables := make(map[string]bool)
|
tables := make(map[string]bool)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
|
// Skip SQL comments: a doc line like "-- ... declared in CREATE
|
||||||
|
// TABLE above" would otherwise register a phantom table and fail
|
||||||
|
// startup verification.
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(line), "--") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
matches := pattern.FindStringSubmatch(line)
|
matches := pattern.FindStringSubmatch(line)
|
||||||
if len(matches) > 1 {
|
if len(matches) > 1 {
|
||||||
tableName := matches[1]
|
tableName := matches[1]
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestParseTableNamesIgnoresComments guards against phantom tables parsed out
|
||||||
|
// of SQL comments (e.g. "-- ... declared in CREATE TABLE above" once
|
||||||
|
// registered a table named "above" and crashed startup verification).
|
||||||
|
func TestParseTableNamesIgnoresComments(t *testing.T) {
|
||||||
|
tables, err := parseTableNames()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseTableNames() error: %v", err)
|
||||||
|
}
|
||||||
|
if len(tables) == 0 {
|
||||||
|
t.Fatal("parseTableNames() returned no tables")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range tables {
|
||||||
|
// Every parsed table must correspond to a real CREATE TABLE statement
|
||||||
|
// at the start of a (non-comment) line.
|
||||||
|
stmt := regexp.MustCompile(`(?m)^CREATE TABLE (?:IF NOT EXISTS )?(?:\w+\.)?` + regexp.QuoteMeta(name) + `\s`)
|
||||||
|
if !stmt.MatchString(SchemaFile) {
|
||||||
|
t.Errorf("parseTableNames() returned phantom table %q with no matching CREATE TABLE statement", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
-55
@@ -63,41 +63,41 @@ type CreateMediaItemRequest struct {
|
|||||||
|
|
||||||
// UpdateMediaItemRequest represents the request for updating a media item
|
// UpdateMediaItemRequest represents the request for updating a media item
|
||||||
type UpdateMediaItemRequest struct {
|
type UpdateMediaItemRequest struct {
|
||||||
Title string `form:"title" json:"title" validate:"required,min=1,max=500"`
|
Title string `form:"title" json:"title" validate:"required,min=1,max=500"`
|
||||||
Author string `form:"author" json:"author"`
|
Author string `form:"author" json:"author"`
|
||||||
ISBN string `form:"isbn" json:"isbn"`
|
ISBN string `form:"isbn" json:"isbn"`
|
||||||
Description string `form:"description" json:"description"`
|
Description string `form:"description" json:"description"`
|
||||||
CoverImagePath string `form:"cover_image_path" json:"cover_image_path"`
|
CoverImagePath string `form:"cover_image_path" json:"cover_image_path"`
|
||||||
CoverAction string `form:"cover_action" json:"cover_action"`
|
CoverAction string `form:"cover_action" json:"cover_action"`
|
||||||
Series string `form:"series" json:"series"`
|
Series string `form:"series" json:"series"`
|
||||||
SeriesNumber int32 `form:"series_number" json:"series_number"`
|
SeriesNumber int32 `form:"series_number" json:"series_number"`
|
||||||
Tags []string `form:"tags" json:"tags"`
|
Tags []string `form:"tags" json:"tags"`
|
||||||
ASIN string `form:"asin" json:"asin"`
|
ASIN string `form:"asin" json:"asin"`
|
||||||
DatePublished string `form:"date_published" json:"date_published"`
|
DatePublished string `form:"date_published" json:"date_published"`
|
||||||
Publisher string `form:"publisher" json:"publisher"`
|
Publisher string `form:"publisher" json:"publisher"`
|
||||||
Contributors []string `form:"contributors" json:"contributors"`
|
Contributors []string `form:"contributors" json:"contributors"`
|
||||||
Language string `form:"language" json:"language"`
|
Language string `form:"language" json:"language"`
|
||||||
Edition string `form:"edition" json:"edition"`
|
Edition string `form:"edition" json:"edition"`
|
||||||
PageCount int32 `form:"page_count" json:"page_count"`
|
PageCount int32 `form:"page_count" json:"page_count"`
|
||||||
Genre string `form:"genre" json:"genre"`
|
Genre string `form:"genre" json:"genre"`
|
||||||
CopyrightYear int32 `form:"copyright_year" json:"copyright_year"`
|
CopyrightYear int32 `form:"copyright_year" json:"copyright_year"`
|
||||||
GoodreadsID string `form:"goodreads_id" json:"goodreads_id"`
|
GoodreadsID string `form:"goodreads_id" json:"goodreads_id"`
|
||||||
OpenlibraryID string `form:"openlibrary_id" json:"openlibrary_id"`
|
OpenlibraryID string `form:"openlibrary_id" json:"openlibrary_id"`
|
||||||
GoogleBooksID string `form:"google_books_id" json:"google_books_id"`
|
GoogleBooksID string `form:"google_books_id" json:"google_books_id"`
|
||||||
MangaType string `form:"manga_type" json:"manga_type"`
|
MangaType string `form:"manga_type" json:"manga_type"`
|
||||||
ReadingDirection string `form:"reading_direction" json:"reading_direction"`
|
ReadingDirection string `form:"reading_direction" json:"reading_direction"`
|
||||||
SeriesCount int32 `form:"series_count" json:"series_count"`
|
SeriesCount int32 `form:"series_count" json:"series_count"`
|
||||||
Volume int32 `form:"volume" json:"volume"`
|
Volume int32 `form:"volume" json:"volume"`
|
||||||
Imprint string `form:"imprint" json:"imprint"`
|
Imprint string `form:"imprint" json:"imprint"`
|
||||||
AgeRating string `form:"age_rating" json:"age_rating"`
|
AgeRating string `form:"age_rating" json:"age_rating"`
|
||||||
WebURL string `form:"web_url" json:"web_url"`
|
WebURL string `form:"web_url" json:"web_url"`
|
||||||
MetadataNotes string `form:"metadata_notes" json:"metadata_notes"`
|
MetadataNotes string `form:"metadata_notes" json:"metadata_notes"`
|
||||||
CommunityRating float64 `form:"community_rating" json:"community_rating"`
|
CommunityRating float64 `form:"community_rating" json:"community_rating"`
|
||||||
StoryArc string `form:"story_arc" json:"story_arc"`
|
StoryArc string `form:"story_arc" json:"story_arc"`
|
||||||
IsBlackAndWhite bool `form:"is_black_and_white" json:"is_black_and_white"`
|
IsBlackAndWhite bool `form:"is_black_and_white" json:"is_black_and_white"`
|
||||||
AlternateInfo string `form:"alternate_info" json:"alternate_info"`
|
AlternateInfo string `form:"alternate_info" json:"alternate_info"`
|
||||||
ScanInformation string `form:"scan_information" json:"scan_information"`
|
ScanInformation string `form:"scan_information" json:"scan_information"`
|
||||||
Summary string `form:"summary" json:"summary"`
|
Summary string `form:"summary" json:"summary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMediaNoteRequest represents the request for creating a media note
|
// CreateMediaNoteRequest represents the request for creating a media note
|
||||||
@@ -572,24 +572,33 @@ func (h *MediaHandler) HandleBulkUpdate(c *echo.Context) error {
|
|||||||
Summary: existingMedia.Summary,
|
Summary: existingMedia.Summary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bulk edits are user customizations: keep existing overrides and mark
|
||||||
|
// each applied update as overridden so scans preserve it.
|
||||||
|
overrides := existingMedia.MetadataOverrides
|
||||||
if update.Updates.Title != nil {
|
if update.Updates.Title != nil {
|
||||||
updateParams.Title = *update.Updates.Title
|
updateParams.Title = *update.Updates.Title
|
||||||
|
overrides = utils.MergeOverrides(overrides, utils.OverrideTitle)
|
||||||
}
|
}
|
||||||
if update.Updates.Author != nil {
|
if update.Updates.Author != nil {
|
||||||
updateParams.Author = pgtype.Text{String: *update.Updates.Author, Valid: true}
|
updateParams.Author = pgtype.Text{String: *update.Updates.Author, Valid: true}
|
||||||
|
overrides = utils.MergeOverrides(overrides, utils.OverrideAuthor)
|
||||||
}
|
}
|
||||||
if update.Updates.Genre != nil {
|
if update.Updates.Genre != nil {
|
||||||
updateParams.Genre = pgtype.Text{String: *update.Updates.Genre, Valid: true}
|
updateParams.Genre = pgtype.Text{String: *update.Updates.Genre, Valid: true}
|
||||||
|
overrides = utils.MergeOverrides(overrides, utils.OverrideGenre)
|
||||||
}
|
}
|
||||||
if update.Updates.Language != nil {
|
if update.Updates.Language != nil {
|
||||||
updateParams.Language = pgtype.Text{String: *update.Updates.Language, Valid: true}
|
updateParams.Language = pgtype.Text{String: *update.Updates.Language, Valid: true}
|
||||||
|
overrides = utils.MergeOverrides(overrides, utils.OverrideLanguage)
|
||||||
}
|
}
|
||||||
if len(update.Updates.Tags) > 0 {
|
if len(update.Updates.Tags) > 0 {
|
||||||
normalizedTags := utils.NormalizeTags(update.Updates.Tags)
|
normalizedTags := utils.NormalizeTags(update.Updates.Tags)
|
||||||
updateParams.Tags = normalizedTags
|
updateParams.Tags = normalizedTags
|
||||||
tagsSearch := utils.NormalizeTagsSearch(update.Updates.Tags)
|
tagsSearch := utils.NormalizeTagsSearch(update.Updates.Tags)
|
||||||
updateParams.TagsSearch = tagsSearch
|
updateParams.TagsSearch = tagsSearch
|
||||||
|
overrides = utils.MergeOverrides(overrides, utils.OverrideTags)
|
||||||
}
|
}
|
||||||
|
updateParams.MetadataOverrides = overrides
|
||||||
|
|
||||||
_, err = h.db.UpdateMediaItem(c.Request().Context(), updateParams)
|
_, err = h.db.UpdateMediaItem(c.Request().Context(), updateParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -925,22 +934,22 @@ func (mh *MediaHandler) GetMediaReadingProgress(c *echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp := map[string]interface{}{
|
resp := map[string]interface{}{
|
||||||
"id": progress.ID,
|
"id": progress.ID,
|
||||||
"media_item_id": progress.MediaItemID,
|
"media_item_id": progress.MediaItemID,
|
||||||
"user_id": progress.UserID,
|
"user_id": progress.UserID,
|
||||||
"current_page": progress.CurrentPage,
|
"current_page": progress.CurrentPage,
|
||||||
"total_pages": progress.TotalPages,
|
"total_pages": progress.TotalPages,
|
||||||
"last_read_at": progress.LastReadAt,
|
"last_read_at": progress.LastReadAt,
|
||||||
"percentage": progress.Percentage,
|
"percentage": progress.Percentage,
|
||||||
"character_offset": progress.CharacterOffset,
|
"character_offset": progress.CharacterOffset,
|
||||||
"epubcfi": progress.Epubcfi,
|
"epubcfi": progress.Epubcfi,
|
||||||
"chapter": progress.Chapter,
|
"chapter": progress.Chapter,
|
||||||
"chapter_progress": progress.ChapterProgress,
|
"chapter_progress": progress.ChapterProgress,
|
||||||
"format_group": progress.FormatGroup,
|
"format_group": progress.FormatGroup,
|
||||||
"total_characters": progress.TotalCharacters,
|
"total_characters": progress.TotalCharacters,
|
||||||
"chapter_count": progress.ChapterCount,
|
"chapter_count": progress.ChapterCount,
|
||||||
"last_sync_device": progress.LastSyncDevice,
|
"last_sync_device": progress.LastSyncDevice,
|
||||||
"last_sync_source": progress.LastSyncSource,
|
"last_sync_source": progress.LastSyncSource,
|
||||||
"last_sync_timestamp": progress.LastSyncTimestamp,
|
"last_sync_timestamp": progress.LastSyncTimestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1240,6 +1249,7 @@ func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coverPath := existing.CoverImagePath.String
|
coverPath := existing.CoverImagePath.String
|
||||||
|
coverUploaded := false
|
||||||
|
|
||||||
if req.CoverAction == "remove" {
|
if req.CoverAction == "remove" {
|
||||||
coverPath = ""
|
coverPath = ""
|
||||||
@@ -1251,6 +1261,7 @@ func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error {
|
|||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to save cover image"})
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to save cover image"})
|
||||||
}
|
}
|
||||||
coverPath = savedPath
|
coverPath = savedPath
|
||||||
|
coverUploaded = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1259,7 +1270,7 @@ func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error {
|
|||||||
alternateInfoBytes = []byte(req.AlternateInfo)
|
alternateInfoBytes = []byte(req.AlternateInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
item, err := mh.db.UpdateMediaItem(c.Request().Context(), database.UpdateMediaItemParams{
|
params := database.UpdateMediaItemParams{
|
||||||
ID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
ID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Author: pgtype.Text{String: req.Author, Valid: req.Author != ""},
|
Author: pgtype.Text{String: req.Author, Valid: req.Author != ""},
|
||||||
@@ -1297,7 +1308,17 @@ func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error {
|
|||||||
AlternateInfo: alternateInfoBytes,
|
AlternateInfo: alternateInfoBytes,
|
||||||
ScanInformation: pgtype.Text{String: req.ScanInformation, Valid: req.ScanInformation != ""},
|
ScanInformation: pgtype.Text{String: req.ScanInformation, Valid: req.ScanInformation != ""},
|
||||||
Summary: pgtype.Text{String: req.Summary, Valid: req.Summary != ""},
|
Summary: pgtype.Text{String: req.Summary, Valid: req.Summary != ""},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Fields the user actually changed become overrides so future scans keep
|
||||||
|
// the custom values. An explicit cover upload/removal always overrides.
|
||||||
|
overrides := utils.DetectMetadataOverrides(params, existing)
|
||||||
|
if req.CoverAction == "remove" || coverUploaded {
|
||||||
|
overrides = utils.MergeOverrides(overrides, utils.OverrideCoverImagePath)
|
||||||
|
}
|
||||||
|
params.MetadataOverrides = overrides
|
||||||
|
|
||||||
|
item, err := mh.db.UpdateMediaItem(c.Request().Context(), params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
@@ -1333,7 +1354,11 @@ func (mh *MediaHandler) RescanMediaItem(c *echo.Context) error {
|
|||||||
scanner := services.NewMediaScanner(mh.db)
|
scanner := services.NewMediaScanner(mh.db)
|
||||||
defer scanner.Close()
|
defer scanner.Close()
|
||||||
|
|
||||||
if err := scanner.RescanMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true}); err != nil {
|
// reset_overrides=true discards user customizations first, returning the
|
||||||
|
// item to pure scanned defaults (the "Reset to Scanned" action).
|
||||||
|
resetOverrides := c.QueryParam("reset_overrides") == "true"
|
||||||
|
|
||||||
|
if err := scanner.RescanMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true}, resetOverrides); err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2224,7 +2249,17 @@ func (mh *MediaHandler) saveCoverImage(c echo.Context, mediaUUID uuid.UUID, file
|
|||||||
return "", fmt.Errorf("media item has no file path")
|
return "", fmt.Errorf("media item has no file path")
|
||||||
}
|
}
|
||||||
|
|
||||||
coverRelPath := relativeFilePath + ".cover.jpg"
|
// Custom covers live at a dedicated sidecar path (distinct from the
|
||||||
|
// scanner-generated {file}.cover.jpg) so scans can never overwrite a
|
||||||
|
// user-uploaded cover and the metadata_overrides set can protect it.
|
||||||
|
ext := ".jpg"
|
||||||
|
switch contentType {
|
||||||
|
case "image/png":
|
||||||
|
ext = ".png"
|
||||||
|
case "image/webp":
|
||||||
|
ext = ".webp"
|
||||||
|
}
|
||||||
|
coverRelPath := relativeFilePath + ".custom_cover" + ext
|
||||||
|
|
||||||
coverFullPath, err := mh.libraryService.ResolveMediaPath(c.Request().Context(), mediaItem.LibraryID, coverRelPath)
|
coverFullPath, err := mh.libraryService.ResolveMediaPath(c.Request().Context(), mediaItem.LibraryID, coverRelPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import "bookhoard/internal/database"
|
|||||||
type MediaDetail struct {
|
type MediaDetail struct {
|
||||||
database.MediaItems // Embedded - ALL book fields available
|
database.MediaItems // Embedded - ALL book fields available
|
||||||
|
|
||||||
|
// Absolute on-disk location (library folder + relative path), resolved by
|
||||||
|
// the page handler so the UI can show where the file lives.
|
||||||
|
FileLocation string `json:"file_location"`
|
||||||
|
|
||||||
// User-specific data
|
// User-specific data
|
||||||
Rating *database.MediaRatings `json:"rating,omitempty"`
|
Rating *database.MediaRatings `json:"rating,omitempty"`
|
||||||
Collections []database.Collections `json:"collections"`
|
Collections []database.Collections `json:"collections"`
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -984,12 +986,12 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
}
|
}
|
||||||
totalData := counts.ProgressCount + counts.HighlightsCount + counts.BookmarksCount + counts.NotesCount + counts.CollectionsCount
|
totalData := counts.ProgressCount + counts.HighlightsCount + counts.BookmarksCount + counts.NotesCount + counts.CollectionsCount
|
||||||
conflict.Items = append(conflict.Items, templates.HashConflictItemData{
|
conflict.Items = append(conflict.Items, templates.HashConflictItemData{
|
||||||
ID: uuid.UUID(mi.ID.Bytes).String(),
|
ID: uuid.UUID(mi.ID.Bytes).String(),
|
||||||
Title: mi.Title,
|
Title: mi.Title,
|
||||||
Author: mi.Author.String,
|
Author: mi.Author.String,
|
||||||
FilePath: mi.FilePath,
|
FilePath: mi.FilePath,
|
||||||
FileSize: mi.FileSize.Int64,
|
FileSize: mi.FileSize.Int64,
|
||||||
UsageSummary: fmt.Sprintf("%d progress, %d highlights, %d bookmarks, %d notes, %d collections",
|
UsageSummary: fmt.Sprintf("%d progress, %d highlights, %d bookmarks, %d notes, %d collections",
|
||||||
counts.ProgressCount, counts.HighlightsCount, counts.BookmarksCount, counts.NotesCount, counts.CollectionsCount),
|
counts.ProgressCount, counts.HighlightsCount, counts.BookmarksCount, counts.NotesCount, counts.CollectionsCount),
|
||||||
HasReadingData: totalData > 0,
|
HasReadingData: totalData > 0,
|
||||||
})
|
})
|
||||||
@@ -1090,9 +1092,9 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
// already have their own dedicated UI cards (timezone dropdown, scan
|
// already have their own dedicated UI cards (timezone dropdown, scan
|
||||||
// settings) so they aren't listed twice.
|
// settings) so they aren't listed twice.
|
||||||
dedicatedUI := map[string]bool{
|
dedicatedUI := map[string]bool{
|
||||||
"default_timezone": true,
|
"default_timezone": true,
|
||||||
"scan_poll_interval_seconds": true,
|
"scan_poll_interval_seconds": true,
|
||||||
"auto_scan_enabled": true,
|
"auto_scan_enabled": true,
|
||||||
}
|
}
|
||||||
var tunableSettings []templates.SettingEntry
|
var tunableSettings []templates.SettingEntry
|
||||||
if cfg.Settings != nil {
|
if cfg.Settings != nil {
|
||||||
@@ -1267,6 +1269,23 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
mediaItem.CoverImagePath = pgtype.Text{String: resolvedPath, Valid: true}
|
mediaItem.CoverImagePath = pgtype.Text{String: resolvedPath, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve the on-disk location (library folder + relative path) so the
|
||||||
|
// detail page can show where the file lives, even for sparse metadata.
|
||||||
|
// Admin-only: everyday users never receive the absolute path.
|
||||||
|
fileLocation := ""
|
||||||
|
if user.Role == "admin" {
|
||||||
|
fileLocation = mediaItem.FilePath
|
||||||
|
if folders, ferr := cfg.Queries.GetLibraryFolders(c.Request().Context(), mediaItem.LibraryID); ferr == nil {
|
||||||
|
for _, folder := range folders {
|
||||||
|
candidate := filepath.Join(folder.FolderPath, mediaItem.FilePath)
|
||||||
|
if _, serr := os.Stat(candidate); serr == nil {
|
||||||
|
fileLocation = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch rating
|
// Fetch rating
|
||||||
var rating *database.MediaRatings
|
var rating *database.MediaRatings
|
||||||
userRating, err := cfg.Queries.GetMediaRating(c.Request().Context(), database.GetMediaRatingParams{
|
userRating, err := cfg.Queries.GetMediaRating(c.Request().Context(), database.GetMediaRatingParams{
|
||||||
@@ -1330,6 +1349,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
// Assemble response (no field duplication!)
|
// Assemble response (no field duplication!)
|
||||||
detail := handlers.MediaDetail{
|
detail := handlers.MediaDetail{
|
||||||
MediaItems: mediaItem, // Embedded - ALL fields available
|
MediaItems: mediaItem, // Embedded - ALL fields available
|
||||||
|
FileLocation: fileLocation,
|
||||||
Rating: rating,
|
Rating: rating,
|
||||||
Collections: collections,
|
Collections: collections,
|
||||||
ReadingProgress: progress,
|
ReadingProgress: progress,
|
||||||
|
|||||||
@@ -157,18 +157,18 @@ type CalibreOPFMetadata struct {
|
|||||||
// never closed.
|
// never closed.
|
||||||
func NewMediaScanner(db *database.Queries) *MediaScanner {
|
func NewMediaScanner(db *database.Queries) *MediaScanner {
|
||||||
return &MediaScanner{
|
return &MediaScanner{
|
||||||
db: db,
|
db: db,
|
||||||
watcher: nil,
|
watcher: nil,
|
||||||
settingsCache: NewSettingsCache(30 * time.Second),
|
settingsCache: NewSettingsCache(30 * time.Second),
|
||||||
dirtyDirs: make(map[string]time.Time),
|
dirtyDirs: make(map[string]time.Time),
|
||||||
fileStability: make(map[string]*atomic.Bool),
|
fileStability: make(map[string]*atomic.Bool),
|
||||||
watching: atomic.Bool{},
|
watching: atomic.Bool{},
|
||||||
scanInProgress: atomic.Bool{},
|
scanInProgress: atomic.Bool{},
|
||||||
folders: []string{},
|
folders: []string{},
|
||||||
adminID: pgtype.UUID{},
|
adminID: pgtype.UUID{},
|
||||||
defaultLibraryID: pgtype.UUID{Valid: false},
|
defaultLibraryID: pgtype.UUID{Valid: false},
|
||||||
libraryTypes: make(map[string][]string),
|
libraryTypes: make(map[string][]string),
|
||||||
logger: NewScannerLogger(),
|
logger: NewScannerLogger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,7 +697,7 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool,
|
|||||||
if s.forceRescan {
|
if s.forceRescan {
|
||||||
fmt.Printf("Force rescan enabled, updating existing media item: %s\n", path)
|
fmt.Printf("Force rescan enabled, updating existing media item: %s\n", path)
|
||||||
// Use UPDATE instead of DELETE+INSERT to preserve created_at
|
// Use UPDATE instead of DELETE+INSERT to preserve created_at
|
||||||
if err := s.updateMediaItem(ctx, existingItem.ID, path, info); err != nil {
|
if err := s.updateMediaItem(ctx, existingItem, path); err != nil {
|
||||||
fmt.Printf("Warning: failed to update existing media item: %v\n", err)
|
fmt.Printf("Warning: failed to update existing media item: %v\n", err)
|
||||||
}
|
}
|
||||||
// Recompute hash identifiers too - a force rescan is the admin's
|
// Recompute hash identifiers too - a force rescan is the admin's
|
||||||
@@ -708,7 +708,7 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool,
|
|||||||
// Normal behavior: check if file has changed (by size)
|
// Normal behavior: check if file has changed (by size)
|
||||||
if existingItem.FileSize.Int64 != info.Size() {
|
if existingItem.FileSize.Int64 != info.Size() {
|
||||||
fmt.Printf("File size changed, updating media item: %s\n", path)
|
fmt.Printf("File size changed, updating media item: %s\n", path)
|
||||||
_ = s.updateMediaItem(ctx, existingItem.ID, path, info)
|
_ = s.updateMediaItem(ctx, existingItem, path)
|
||||||
// The bytes changed, so any stored hash is stale.
|
// The bytes changed, so any stored hash is stale.
|
||||||
s.recomputeHashInfo(ctx, existingItem.ID, libraryID, path)
|
s.recomputeHashInfo(ctx, existingItem.ID, libraryID, path)
|
||||||
return false, nil
|
return false, nil
|
||||||
@@ -758,7 +758,7 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) (bool,
|
|||||||
fmt.Printf("Media item with same SHA-256 already exists in library (path %q), skipping duplicate: %s\n",
|
fmt.Printf("Media item with same SHA-256 already exists in library (path %q), skipping duplicate: %s\n",
|
||||||
existingByHash.FilePath, path)
|
existingByHash.FilePath, path)
|
||||||
if s.forceRescan {
|
if s.forceRescan {
|
||||||
_ = s.updateMediaItem(ctx, existingByHash.ID, path, info)
|
_ = s.updateMediaItem(ctx, existingByHash, path)
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
} else if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
} else if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
@@ -2147,7 +2147,11 @@ func (s *MediaScanner) renderPDFCoverPage(pdfPath string) string {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
outPrefix := filepath.Join(tmpDir, "cover")
|
outPrefix := filepath.Join(tmpDir, "cover")
|
||||||
cmd := exec.Command("pdftoppm", "-jpeg", "-f", "1", "-l", "1", "-singlefile", "-r", "150", pdfPath, outPrefix)
|
// -cropbox renders the CropBox (the viewer-visible region, matching pdf.js)
|
||||||
|
// rather than the MediaBox; poppler falls back to the MediaBox when no
|
||||||
|
// CropBox is defined. This matters for PDFs whose page 1 is a full print
|
||||||
|
// cover wrap (back + spine + front) with a CropBox covering just the front.
|
||||||
|
cmd := exec.Command("pdftoppm", "-jpeg", "-f", "1", "-l", "1", "-singlefile", "-cropbox", "-r", "150", pdfPath, outPrefix)
|
||||||
if output, err := cmd.CombinedOutput(); err != nil {
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
fmt.Printf("Warning: failed to render PDF cover from %s: %v, output: %s\n", pdfPath, err, string(output))
|
fmt.Printf("Warning: failed to render PDF cover from %s: %v, output: %s\n", pdfPath, err, string(output))
|
||||||
return ""
|
return ""
|
||||||
@@ -2601,7 +2605,11 @@ func countArchiveImages(filePath string) (int, error) {
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MediaScanner) updateMediaItem(ctx context.Context, mediaItemID pgtype.UUID, path string, _ os.FileInfo) error {
|
// updateMediaItem re-extracts metadata for an existing media item and writes
|
||||||
|
// it back. Fields listed in the item's metadata_overrides set are preserved
|
||||||
|
// from the existing row so rescans never clobber user customizations; only
|
||||||
|
// reset-to-scanned-defaults (RescanMediaItem with reset) clears them.
|
||||||
|
func (s *MediaScanner) updateMediaItem(ctx context.Context, existing database.MediaItems, path string) error {
|
||||||
// Re-extract metadata for the update
|
// Re-extract metadata for the update
|
||||||
metadata, err := s.extractMetadata(path)
|
metadata, err := s.extractMetadata(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2620,8 +2628,8 @@ func (s *MediaScanner) updateMediaItem(ctx context.Context, mediaItemID pgtype.U
|
|||||||
if metadata.AlternateInfo != "" {
|
if metadata.AlternateInfo != "" {
|
||||||
alternateInfoBytes = []byte(metadata.AlternateInfo)
|
alternateInfoBytes = []byte(metadata.AlternateInfo)
|
||||||
}
|
}
|
||||||
_, err = s.db.UpdateMediaItem(ctx, database.UpdateMediaItemParams{
|
params := database.UpdateMediaItemParams{
|
||||||
ID: mediaItemID,
|
ID: existing.ID,
|
||||||
Title: metadata.Title,
|
Title: metadata.Title,
|
||||||
Author: pgtype.Text{String: metadata.Author, Valid: metadata.Author != ""},
|
Author: pgtype.Text{String: metadata.Author, Valid: metadata.Author != ""},
|
||||||
Isbn: pgtype.Text{String: utils.NormalizeISBNSafe(metadata.ISBN), Valid: metadata.ISBN != ""},
|
Isbn: pgtype.Text{String: utils.NormalizeISBNSafe(metadata.ISBN), Valid: metadata.ISBN != ""},
|
||||||
@@ -2653,19 +2661,34 @@ func (s *MediaScanner) updateMediaItem(ctx context.Context, mediaItemID pgtype.U
|
|||||||
AlternateInfo: alternateInfoBytes,
|
AlternateInfo: alternateInfoBytes,
|
||||||
ScanInformation: pgtype.Text{String: metadata.ScanInformation, Valid: metadata.ScanInformation != ""},
|
ScanInformation: pgtype.Text{String: metadata.ScanInformation, Valid: metadata.ScanInformation != ""},
|
||||||
Summary: pgtype.Text{String: metadata.Summary, Valid: metadata.Summary != ""},
|
Summary: pgtype.Text{String: metadata.Summary, Valid: metadata.Summary != ""},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Keep user-customized fields, and keep the override set itself intact.
|
||||||
|
utils.ApplyMetadataOverrides(¶ms, existing)
|
||||||
|
params.MetadataOverrides = existing.MetadataOverrides
|
||||||
|
|
||||||
|
_, err = s.db.UpdateMediaItem(ctx, params)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RescanMediaItem re-extracts metadata for a single media item and updates it.
|
// RescanMediaItem re-extracts metadata for a single media item and updates it.
|
||||||
// It is the per-book rescan used by the Edit Metadata dialog and backfills
|
// It is the per-book rescan used by the Edit Metadata dialog and backfills
|
||||||
// covers for items imported before the PDF render fallback existed.
|
// covers for items imported before the PDF render fallback existed.
|
||||||
func (s *MediaScanner) RescanMediaItem(ctx context.Context, mediaItemID pgtype.UUID) error {
|
// With resetOverrides, user customizations are discarded first: the item is
|
||||||
|
// returned to pure scanned defaults (the "Reset to Scanned" action).
|
||||||
|
func (s *MediaScanner) RescanMediaItem(ctx context.Context, mediaItemID pgtype.UUID, resetOverrides bool) error {
|
||||||
item, err := s.db.GetMediaItem(ctx, mediaItemID)
|
item, err := s.db.GetMediaItem(ctx, mediaItemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("media item not found: %w", err)
|
return fmt.Errorf("media item not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resetOverrides {
|
||||||
|
if err := s.db.ClearMediaItemMetadataOverrides(ctx, mediaItemID); err != nil {
|
||||||
|
return fmt.Errorf("failed to clear metadata overrides: %w", err)
|
||||||
|
}
|
||||||
|
item.MetadataOverrides = nil
|
||||||
|
}
|
||||||
|
|
||||||
folders, err := s.db.GetLibraryFolders(ctx, item.LibraryID)
|
folders, err := s.db.GetLibraryFolders(ctx, item.LibraryID)
|
||||||
if err != nil || len(folders) == 0 {
|
if err != nil || len(folders) == 0 {
|
||||||
return fmt.Errorf("no library folders found for library")
|
return fmt.Errorf("no library folders found for library")
|
||||||
@@ -2689,12 +2712,11 @@ func (s *MediaScanner) RescanMediaItem(ctx context.Context, mediaItemID pgtype.U
|
|||||||
return fmt.Errorf("media file not found on disk: %s", item.FilePath)
|
return fmt.Errorf("media file not found on disk: %s", item.FilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := os.Stat(fullPath)
|
if _, err := os.Stat(fullPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to stat media file: %w", err)
|
return fmt.Errorf("failed to stat media file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.updateMediaItem(ctx, mediaItemID, fullPath, info); err != nil {
|
if err := s.updateMediaItem(ctx, item, fullPath); err != nil {
|
||||||
return fmt.Errorf("failed to update media item: %w", err)
|
return fmt.Errorf("failed to update media item: %w", err)
|
||||||
}
|
}
|
||||||
s.recomputeHashInfo(ctx, mediaItemID, item.LibraryID, fullPath)
|
s.recomputeHashInfo(ctx, mediaItemID, item.LibraryID, fullPath)
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"bookhoard/internal/database"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Per-field user-override tracking for media item metadata.
|
||||||
|
//
|
||||||
|
// metadata_overrides is a TEXT[] column on media_items holding the names of
|
||||||
|
// columns the user has customized via the metadata editor. Library scans and
|
||||||
|
// per-book rescans MUST preserve those columns (applyMetadataOverrides);
|
||||||
|
// only the reset-to-scanned-defaults action clears the set.
|
||||||
|
|
||||||
|
const (
|
||||||
|
OverrideTitle = "title"
|
||||||
|
OverrideAuthor = "author"
|
||||||
|
OverrideISBN = "isbn"
|
||||||
|
OverrideDescription = "description"
|
||||||
|
OverrideCoverImagePath = "cover_image_path"
|
||||||
|
OverrideSeries = "series"
|
||||||
|
OverrideSeriesNumber = "series_number"
|
||||||
|
OverrideTags = "tags"
|
||||||
|
OverrideAsin = "asin"
|
||||||
|
OverrideDatePublished = "date_published"
|
||||||
|
OverridePublisher = "publisher"
|
||||||
|
OverrideContributors = "contributors"
|
||||||
|
OverrideLanguage = "language"
|
||||||
|
OverrideEdition = "edition"
|
||||||
|
OverridePageCount = "page_count"
|
||||||
|
OverrideGenre = "genre"
|
||||||
|
OverrideCopyrightYear = "copyright_year"
|
||||||
|
OverrideGoodreadsID = "goodreads_id"
|
||||||
|
OverrideOpenlibraryID = "openlibrary_id"
|
||||||
|
OverrideGoogleBooksID = "google_books_id"
|
||||||
|
OverrideMangaType = "manga_type"
|
||||||
|
OverrideReadingDirection = "reading_direction"
|
||||||
|
OverrideSeriesCount = "series_count"
|
||||||
|
OverrideVolume = "volume"
|
||||||
|
OverrideImprint = "imprint"
|
||||||
|
OverrideAgeRating = "age_rating"
|
||||||
|
OverrideWebURL = "web_url"
|
||||||
|
OverrideMetadataNotes = "metadata_notes"
|
||||||
|
OverrideCommunityRating = "community_rating"
|
||||||
|
OverrideStoryArc = "story_arc"
|
||||||
|
OverrideIsBlackAndWhite = "is_black_and_white"
|
||||||
|
OverrideAlternateInfo = "alternate_info"
|
||||||
|
OverrideScanInformation = "scan_information"
|
||||||
|
OverrideSummary = "summary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasOverride reports whether key is in the override set.
|
||||||
|
func hasOverride(overrides []string, key string) bool {
|
||||||
|
for _, k := range overrides {
|
||||||
|
if k == key {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeOverrides unions existing and added, preserving order and dropping
|
||||||
|
// duplicates. Returns a non-nil slice so it always satisfies NOT NULL columns.
|
||||||
|
func MergeOverrides(existing []string, added ...string) []string {
|
||||||
|
seen := make(map[string]bool, len(existing)+len(added))
|
||||||
|
merged := make([]string, 0, len(existing)+len(added))
|
||||||
|
for _, k := range existing {
|
||||||
|
if k != "" && !seen[k] {
|
||||||
|
seen[k] = true
|
||||||
|
merged = append(merged, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, k := range added {
|
||||||
|
if k != "" && !seen[k] {
|
||||||
|
seen[k] = true
|
||||||
|
merged = append(merged, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalizers turn nullable column values into comparable strings so that
|
||||||
|
// "unset" (invalid/zero) forms compare equal regardless of which side they
|
||||||
|
// come from.
|
||||||
|
|
||||||
|
func normText(t pgtype.Text) string {
|
||||||
|
if !t.Valid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t.String
|
||||||
|
}
|
||||||
|
|
||||||
|
func normInt(i pgtype.Int4) string {
|
||||||
|
if !i.Valid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(int64(i.Int32), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normFloat(f pgtype.Float8) string {
|
||||||
|
if !f.Valid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strconv.FormatFloat(f.Float64, 'g', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normBool(b pgtype.Bool) string {
|
||||||
|
if !b.Valid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strconv.FormatBool(b.Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normDate(d pgtype.Date) string {
|
||||||
|
if !d.Valid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return d.Time.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
func normStringSlice(s []string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Join(s, "\x1f")
|
||||||
|
}
|
||||||
|
|
||||||
|
func normBytes(b []byte) string {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectMetadataOverridesDiff returns the keys whose value in params differs
|
||||||
|
// from the existing row. Used by the metadata editor save path to grow the
|
||||||
|
// override set with exactly the fields the user changed.
|
||||||
|
func detectMetadataOverridesDiff(params database.UpdateMediaItemParams, existing database.MediaItems) []string {
|
||||||
|
var changed []string
|
||||||
|
add := func(key string, differs bool) {
|
||||||
|
if differs {
|
||||||
|
changed = append(changed, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(OverrideTitle, params.Title != existing.Title)
|
||||||
|
add(OverrideAuthor, normText(params.Author) != normText(existing.Author))
|
||||||
|
add(OverrideISBN, normText(params.Isbn) != normText(existing.Isbn))
|
||||||
|
add(OverrideDescription, normText(params.Description) != normText(existing.Description))
|
||||||
|
add(OverrideSeries, normText(params.Series) != normText(existing.Series))
|
||||||
|
add(OverrideSeriesNumber, normInt(params.SeriesNumber) != normInt(existing.SeriesNumber))
|
||||||
|
add(OverrideTags, normStringSlice(params.Tags) != normStringSlice(existing.Tags))
|
||||||
|
add(OverrideAsin, normText(params.Asin) != normText(existing.Asin))
|
||||||
|
add(OverrideDatePublished, normDate(params.DatePublished) != normDate(existing.DatePublished))
|
||||||
|
add(OverridePublisher, normText(params.Publisher) != normText(existing.Publisher))
|
||||||
|
add(OverrideContributors, normStringSlice(params.Contributors) != normStringSlice(existing.Contributors))
|
||||||
|
add(OverrideLanguage, normText(params.Language) != normText(existing.Language))
|
||||||
|
add(OverrideEdition, normText(params.Edition) != normText(existing.Edition))
|
||||||
|
add(OverridePageCount, normInt(params.PageCount) != normInt(existing.PageCount))
|
||||||
|
add(OverrideGenre, normText(params.Genre) != normText(existing.Genre))
|
||||||
|
add(OverrideCopyrightYear, normInt(params.CopyrightYear) != normInt(existing.CopyrightYear))
|
||||||
|
add(OverrideGoodreadsID, normText(params.GoodreadsID) != normText(existing.GoodreadsID))
|
||||||
|
add(OverrideOpenlibraryID, normText(params.OpenlibraryID) != normText(existing.OpenlibraryID))
|
||||||
|
add(OverrideGoogleBooksID, normText(params.GoogleBooksID) != normText(existing.GoogleBooksID))
|
||||||
|
add(OverrideMangaType, normText(params.MangaType) != normText(existing.MangaType))
|
||||||
|
add(OverrideReadingDirection, normText(params.ReadingDirection) != normText(existing.ReadingDirection))
|
||||||
|
add(OverrideSeriesCount, normInt(params.SeriesCount) != normInt(existing.SeriesCount))
|
||||||
|
add(OverrideVolume, normInt(params.Volume) != normInt(existing.Volume))
|
||||||
|
add(OverrideImprint, normText(params.Imprint) != normText(existing.Imprint))
|
||||||
|
add(OverrideAgeRating, normText(params.AgeRating) != normText(existing.AgeRating))
|
||||||
|
add(OverrideWebURL, normText(params.WebUrl) != normText(existing.WebUrl))
|
||||||
|
add(OverrideMetadataNotes, normText(params.MetadataNotes) != normText(existing.MetadataNotes))
|
||||||
|
add(OverrideCommunityRating, normFloat(params.CommunityRating) != normFloat(existing.CommunityRating))
|
||||||
|
add(OverrideStoryArc, normText(params.StoryArc) != normText(existing.StoryArc))
|
||||||
|
add(OverrideIsBlackAndWhite, normBool(params.IsBlackAndWhite) != normBool(existing.IsBlackAndWhite))
|
||||||
|
add(OverrideAlternateInfo, normBytes(params.AlternateInfo) != normBytes(existing.AlternateInfo))
|
||||||
|
add(OverrideScanInformation, normText(params.ScanInformation) != normText(existing.ScanInformation))
|
||||||
|
add(OverrideSummary, normText(params.Summary) != normText(existing.Summary))
|
||||||
|
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectMetadataOverrides returns the union of the existing override set and
|
||||||
|
// any fields whose incoming (user-submitted) values differ from the stored
|
||||||
|
// row. Overrides accumulate: a field stays protected until an explicit reset,
|
||||||
|
// even if a later save reverts the value.
|
||||||
|
func DetectMetadataOverrides(params database.UpdateMediaItemParams, existing database.MediaItems) []string {
|
||||||
|
return MergeOverrides(existing.MetadataOverrides, detectMetadataOverridesDiff(params, existing)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyMetadataOverrides restores every overridden field in params from the
|
||||||
|
// existing row so scanner updates cannot clobber user customizations. Derived
|
||||||
|
// search columns are restored together with their base column.
|
||||||
|
func ApplyMetadataOverrides(params *database.UpdateMediaItemParams, existing database.MediaItems) {
|
||||||
|
o := existing.MetadataOverrides
|
||||||
|
|
||||||
|
if hasOverride(o, OverrideTitle) {
|
||||||
|
params.Title = existing.Title
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideAuthor) {
|
||||||
|
params.Author = existing.Author
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideISBN) {
|
||||||
|
params.Isbn = existing.Isbn
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideDescription) {
|
||||||
|
params.Description = existing.Description
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideCoverImagePath) {
|
||||||
|
params.CoverImagePath = existing.CoverImagePath
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideSeries) {
|
||||||
|
params.Series = existing.Series
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideSeriesNumber) {
|
||||||
|
params.SeriesNumber = existing.SeriesNumber
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideTags) {
|
||||||
|
params.Tags = existing.Tags
|
||||||
|
params.TagsSearch = existing.TagsSearch
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideAsin) {
|
||||||
|
params.Asin = existing.Asin
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideDatePublished) {
|
||||||
|
params.DatePublished = existing.DatePublished
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverridePublisher) {
|
||||||
|
params.Publisher = existing.Publisher
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideContributors) {
|
||||||
|
params.Contributors = existing.Contributors
|
||||||
|
params.ContributorsSearch = existing.ContributorsSearch
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideLanguage) {
|
||||||
|
params.Language = existing.Language
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideEdition) {
|
||||||
|
params.Edition = existing.Edition
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverridePageCount) {
|
||||||
|
params.PageCount = existing.PageCount
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideGenre) {
|
||||||
|
params.Genre = existing.Genre
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideCopyrightYear) {
|
||||||
|
params.CopyrightYear = existing.CopyrightYear
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideGoodreadsID) {
|
||||||
|
params.GoodreadsID = existing.GoodreadsID
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideOpenlibraryID) {
|
||||||
|
params.OpenlibraryID = existing.OpenlibraryID
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideGoogleBooksID) {
|
||||||
|
params.GoogleBooksID = existing.GoogleBooksID
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideMangaType) {
|
||||||
|
params.MangaType = existing.MangaType
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideReadingDirection) {
|
||||||
|
params.ReadingDirection = existing.ReadingDirection
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideSeriesCount) {
|
||||||
|
params.SeriesCount = existing.SeriesCount
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideVolume) {
|
||||||
|
params.Volume = existing.Volume
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideImprint) {
|
||||||
|
params.Imprint = existing.Imprint
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideAgeRating) {
|
||||||
|
params.AgeRating = existing.AgeRating
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideWebURL) {
|
||||||
|
params.WebUrl = existing.WebUrl
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideMetadataNotes) {
|
||||||
|
params.MetadataNotes = existing.MetadataNotes
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideCommunityRating) {
|
||||||
|
params.CommunityRating = existing.CommunityRating
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideStoryArc) {
|
||||||
|
params.StoryArc = existing.StoryArc
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideIsBlackAndWhite) {
|
||||||
|
params.IsBlackAndWhite = existing.IsBlackAndWhite
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideAlternateInfo) {
|
||||||
|
params.AlternateInfo = existing.AlternateInfo
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideScanInformation) {
|
||||||
|
params.ScanInformation = existing.ScanInformation
|
||||||
|
}
|
||||||
|
if hasOverride(o, OverrideSummary) {
|
||||||
|
params.Summary = existing.Summary
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"bookhoard/internal/database"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeOverrides(t *testing.T) {
|
||||||
|
got := MergeOverrides([]string{"title", "tags"}, "tags", "description", "")
|
||||||
|
want := []string{"title", "tags", "description"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("MergeOverrides() = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
if MergeOverrides(nil) == nil {
|
||||||
|
t.Error("MergeOverrides(nil) must return non-nil slice")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectMetadataOverrides(t *testing.T) {
|
||||||
|
existing := database.MediaItems{
|
||||||
|
Title: "Scanned Title",
|
||||||
|
Description: pgtype.Text{String: "Scanned description", Valid: true},
|
||||||
|
Tags: []string{"foo", "bar"},
|
||||||
|
}
|
||||||
|
params := database.UpdateMediaItemParams{
|
||||||
|
// unchanged
|
||||||
|
Title: "Scanned Title",
|
||||||
|
// changed
|
||||||
|
Description: pgtype.Text{String: "My custom description", Valid: true},
|
||||||
|
Tags: []string{"foo", "bar"},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := DetectMetadataOverrides(params, existing)
|
||||||
|
if !reflect.DeepEqual(got, []string{"description"}) {
|
||||||
|
t.Errorf("DetectMetadataOverrides() = %v, want [description]", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides accumulate: an existing override survives a later save.
|
||||||
|
existing.MetadataOverrides = []string{"publisher"}
|
||||||
|
got = DetectMetadataOverrides(params, existing)
|
||||||
|
want := []string{"publisher", "description"}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("DetectMetadataOverrides() accumulate = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An untouched save detects nothing new.
|
||||||
|
noop := database.UpdateMediaItemParams{
|
||||||
|
Title: existing.Title,
|
||||||
|
Description: existing.Description,
|
||||||
|
Tags: existing.Tags,
|
||||||
|
}
|
||||||
|
got = DetectMetadataOverrides(noop, existing)
|
||||||
|
if !reflect.DeepEqual(got, []string{"publisher"}) {
|
||||||
|
t.Errorf("DetectMetadataOverrides() noop = %v, want [publisher]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectMetadataOverridesUnsetFormsEqual(t *testing.T) {
|
||||||
|
// Zero-value params must not look "changed" against NULL-ish columns.
|
||||||
|
existing := database.MediaItems{
|
||||||
|
PageCount: pgtype.Int4{Int32: 0, Valid: false},
|
||||||
|
SeriesNumber: pgtype.Int4{Int32: 5, Valid: true},
|
||||||
|
CommunityRating: pgtype.Float8{Float64: 0, Valid: false},
|
||||||
|
}
|
||||||
|
params := database.UpdateMediaItemParams{
|
||||||
|
PageCount: pgtype.Int4{Int32: 0, Valid: false},
|
||||||
|
SeriesNumber: pgtype.Int4{Int32: 0, Valid: false}, // cleared by user -> changed
|
||||||
|
CommunityRating: pgtype.Float8{Float64: 0, Valid: false},
|
||||||
|
}
|
||||||
|
got := DetectMetadataOverrides(params, existing)
|
||||||
|
if !reflect.DeepEqual(got, []string{"series_number"}) {
|
||||||
|
t.Errorf("DetectMetadataOverrides() = %v, want [series_number]", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyMetadataOverrides(t *testing.T) {
|
||||||
|
existing := database.MediaItems{
|
||||||
|
Title: "Custom Title",
|
||||||
|
Description: pgtype.Text{String: "Custom desc", Valid: true},
|
||||||
|
Tags: []string{"mine"},
|
||||||
|
TagsSearch: []string{"mine"},
|
||||||
|
CoverImagePath: pgtype.Text{String: "books/x.epub.custom_cover.jpg", Valid: true},
|
||||||
|
}
|
||||||
|
params := database.UpdateMediaItemParams{
|
||||||
|
Title: "Scanned Title",
|
||||||
|
Description: pgtype.Text{String: "Scanned desc", Valid: true},
|
||||||
|
Tags: []string{"scanned"},
|
||||||
|
TagsSearch: []string{"scanned"},
|
||||||
|
CoverImagePath: pgtype.Text{String: "books/x.epub.cover.jpg", Valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only title and tags overridden; scanned description and cover win.
|
||||||
|
existing.MetadataOverrides = []string{"title", "tags"}
|
||||||
|
ApplyMetadataOverrides(¶ms, existing)
|
||||||
|
|
||||||
|
if params.Title != "Custom Title" {
|
||||||
|
t.Errorf("Title = %q, want %q", params.Title, "Custom Title")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(params.Tags, []string{"mine"}) || !reflect.DeepEqual(params.TagsSearch, []string{"mine"}) {
|
||||||
|
t.Error("Tags/TagsSearch must be restored together")
|
||||||
|
}
|
||||||
|
if params.Description.String != "Scanned desc" {
|
||||||
|
t.Errorf("Description = %q, want scanned value", params.Description.String)
|
||||||
|
}
|
||||||
|
if params.CoverImagePath.String != "books/x.epub.cover.jpg" {
|
||||||
|
t.Errorf("CoverImagePath = %q, want scanned value", params.CoverImagePath.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,10 +107,12 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
|
|||||||
<span class="badge ml-1" style="background-color: var(--accent-muted); color: var(--accent);">{ book.NotesCount + book.HighlightsCount }</span>
|
<span class="badge ml-1" style="background-color: var(--accent-muted); color: var(--accent);">{ book.NotesCount + book.HighlightsCount }</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
if user.Role == "admin" {
|
||||||
<button @click="showMetadataEditor()" class="btn btn-secondary px-5 py-2.5">
|
<button @click="showMetadataEditor()" class="btn btn-secondary px-5 py-2.5">
|
||||||
@Icon("edit", "h-4 w-4")
|
@Icon("edit", "h-4 w-4")
|
||||||
<span>Edit</span>
|
<span>Edit</span>
|
||||||
</button>
|
</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<!-- Rating -->
|
<!-- Rating -->
|
||||||
<div class="mb-5" @mouseleave="ratingHover = 0">
|
<div class="mb-5" @mouseleave="ratingHover = 0">
|
||||||
@@ -370,12 +372,18 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
|
|||||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Format</p>
|
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Format</p>
|
||||||
<p style="color: var(--text-primary)">{ book.MimeType.String }</p>
|
<p style="color: var(--text-primary)">{ book.MimeType.String }</p>
|
||||||
</div>
|
</div>
|
||||||
if book.FileSize.Valid && book.FileSize.Int64 > 0 {
|
if book.FileSize.Valid && book.FileSize.Int64 > 0 {
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">File Size</p>
|
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">File Size</p>
|
||||||
<p style="color: var(--text-primary)">{ formatFileSize(book.FileSize.Int64) }</p>
|
<p style="color: var(--text-primary)">{ formatFileSize(book.FileSize.Int64) }</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
if user.Role == "admin" {
|
||||||
|
<div class="md:col-span-2 lg:col-span-3">
|
||||||
|
<p class="text-xs uppercase tracking-wide" style="color: var(--text-secondary)">Location</p>
|
||||||
|
<p class="font-mono text-xs break-all select-all" style="color: var(--text-secondary)">{ book.FileLocation }</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
if book.GoodreadsID.Valid || book.OpenlibraryID.Valid || book.GoogleBooksID.Valid || book.Asin.Valid || book.Isbn.Valid || book.WebUrl.Valid {
|
if book.GoodreadsID.Valid || book.OpenlibraryID.Valid || book.GoogleBooksID.Valid || book.Asin.Valid || book.Isbn.Valid || book.WebUrl.Valid {
|
||||||
<div class="mt-5 pt-4 border-t flex flex-wrap gap-2" style="border-color: color-mix(in srgb, var(--text-primary) 7%, transparent);">
|
<div class="mt-5 pt-4 border-t flex flex-wrap gap-2" style="border-color: color-mix(in srgb, var(--text-primary) 7%, transparent);">
|
||||||
@@ -426,7 +434,9 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
|
|||||||
</div>
|
</div>
|
||||||
@ProgressSyncModal(user, book)
|
@ProgressSyncModal(user, book)
|
||||||
@NotesHighlightsModal(user, book)
|
@NotesHighlightsModal(user, book)
|
||||||
@MetadataEditorModal(book)
|
if user.Role == "admin" {
|
||||||
|
@MetadataEditorModal(book)
|
||||||
|
}
|
||||||
@ErrorToast(errorMessage)
|
@ErrorToast(errorMessage)
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -290,25 +290,6 @@ templ MetadataEditorModal(book handlers.MediaDetail) {
|
|||||||
<input type="file" id="cover-upload-input" accept="image/jpeg,image/png,image/webp" class="hidden"
|
<input type="file" id="cover-upload-input" accept="image/jpeg,image/png,image/webp" class="hidden"
|
||||||
@change="handleCoverUpload($event)" />
|
@change="handleCoverUpload($event)" />
|
||||||
<div class="w-64 space-y-2">
|
<div class="w-64 space-y-2">
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary w-full"
|
|
||||||
@click="generateCover()"
|
|
||||||
x-show="coverGenerating"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
@Icon("refresh", "h-4 w-4")
|
|
||||||
Generating...
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary w-full"
|
|
||||||
@click="generateCover()"
|
|
||||||
x-show="!coverGenerating"
|
|
||||||
>
|
|
||||||
@Icon("refresh", "h-4 w-4")
|
|
||||||
Generate Cover
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-ghost w-full"
|
class="btn btn-ghost w-full"
|
||||||
@@ -593,34 +574,53 @@ templ MetadataEditorModal(book handlers.MediaDetail) {
|
|||||||
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Contributors (comma-separated)</label>
|
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Contributors (comma-separated)</label>
|
||||||
<input type="text" name="contributors" value={ stringSliceToString(book.Contributors) } class="input" />
|
<input type="text" name="contributors" value={ stringSliceToString(book.Contributors) } class="input" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Format</label>
|
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">Format</label>
|
||||||
<input type="text" value={ textToString(book.MimeType) } readonly
|
<input type="text" value={ textToString(book.MimeType) } readonly
|
||||||
class="input opacity-60" />
|
class="input opacity-60" />
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">File Size</label>
|
|
||||||
<input type="text" value={ fmt.Sprintf("%.1f MB", float64(book.FileSize.Int64)/1024/1024) } readonly
|
|
||||||
class="input opacity-60" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">File Size</label>
|
||||||
|
<input type="text" value={ fmt.Sprintf("%.1f MB", float64(book.FileSize.Int64)/1024/1024) } readonly
|
||||||
|
class="input opacity-60" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold uppercase tracking-wide mb-1" style="color: var(--text-secondary);">File Path</label>
|
||||||
|
<input type="text" value={ book.FileLocation } readonly
|
||||||
|
class="input opacity-60 font-mono text-xs" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between p-6 border-t flex-shrink-0" style="border-color: var(--border);">
|
<div class="flex items-center justify-between p-6 border-t flex-shrink-0" style="border-color: var(--border);">
|
||||||
<button
|
<div class="flex items-center gap-2">
|
||||||
@click="rescanBook()"
|
<button
|
||||||
:disabled="rescanning"
|
@click="rescanBook()"
|
||||||
class="btn btn-secondary"
|
:disabled="rescanning"
|
||||||
>
|
class="btn btn-secondary"
|
||||||
<span x-show="!rescanning" class="inline-flex items-center gap-2">@Icon("sync", "h-4 w-4")<span>Rescan</span></span>
|
>
|
||||||
<svg x-show="rescanning" class="animate-spin inline-block h-5 w-5" viewBox="0 0 24 24" fill="none">
|
<span x-show="!rescanning" class="inline-flex items-center gap-2">@Icon("sync", "h-4 w-4")<span>Rescan</span></span>
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<svg x-show="rescanning" class="animate-spin inline-block h-5 w-5" viewBox="0 0 24 24" fill="none">
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
</svg>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||||
</button>
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="resetMetadata()"
|
||||||
|
:disabled="resetting"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
title="Discard all manual edits and restore scanned metadata"
|
||||||
|
>
|
||||||
|
<span x-show="!resetting" class="inline-flex items-center gap-2">@Icon("refresh", "h-4 w-4")<span>Reset to Scanned</span></span>
|
||||||
|
<svg x-show="resetting" class="animate-spin inline-block h-5 w-5" viewBox="0 0 24 24" fill="none">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<button
|
<button
|
||||||
@click="hideMetadataEditor()"
|
@click="hideMetadataEditor()"
|
||||||
|
|||||||
@@ -689,23 +689,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<div class=\"absolute inset-0 bg-black bg-opacity-40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center\"><span class=\"text-white text-sm font-semibold\">Click to upload</span></div></div><input type=\"file\" id=\"cover-upload-input\" accept=\"image/jpeg,image/png,image/webp\" class=\"hidden\" @change=\"handleCoverUpload($event)\"><div class=\"w-64 space-y-2\"><button type=\"button\" class=\"btn btn-primary w-full\" @click=\"generateCover()\" x-show=\"coverGenerating\" disabled>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<div class=\"absolute inset-0 bg-black bg-opacity-40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center\"><span class=\"text-white text-sm font-semibold\">Click to upload</span></div></div><input type=\"file\" id=\"cover-upload-input\" accept=\"image/jpeg,image/png,image/webp\" class=\"hidden\" @change=\"handleCoverUpload($event)\"><div class=\"w-64 space-y-2\"><button type=\"button\" class=\"btn btn-ghost w-full\" @click=\"removeCover()\" x-show=\"hasExistingCover || newCoverPreview\">Remove Cover</button></div></div><div class=\"flex-1 min-w-0 space-y-2\"><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('basic')\"><span class=\"font-semibold\">Basic Info</span> <span class=\"inline-flex\"><span x-show=\"openSections.basic\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = Icon("refresh", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "Generating...</button> <button type=\"button\" class=\"btn btn-primary w-full\" @click=\"generateCover()\" x-show=\"!coverGenerating\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = Icon("refresh", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "Generate Cover</button> <button type=\"button\" class=\"btn btn-ghost w-full\" @click=\"removeCover()\" x-show=\"hasExistingCover || newCoverPreview\">Remove Cover</button></div></div><div class=\"flex-1 min-w-0 space-y-2\"><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('basic')\"><span class=\"font-semibold\">Basic Info</span> <span class=\"inline-flex\"><span x-show=\"openSections.basic\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -713,7 +697,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</span> <span x-show=\"!openSections.basic\" x-cloak>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</span> <span x-show=\"!openSections.basic\" x-cloak>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -721,59 +705,59 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</span></span></button><div x-show=\"openSections.basic\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Title</label> <input type=\"text\" name=\"title\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</span></span></button><div x-show=\"openSections.basic\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Title</label> <input type=\"text\" name=\"title\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var34 string
|
var templ_7745c5c3_Var34 string
|
||||||
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(book.Title)
|
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(book.Title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 336, Col: 58}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 317, Col: 58}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Author</label> <input type=\"text\" name=\"author\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Author</label> <input type=\"text\" name=\"author\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var35 string
|
var templ_7745c5c3_Var35 string
|
||||||
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Author))
|
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Author))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 340, Col: 74}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 321, Col: 74}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Description</label> <textarea name=\"description\" rows=\"3\" class=\"input\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Description</label> <textarea name=\"description\" rows=\"3\" class=\"input\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var36 string
|
var templ_7745c5c3_Var36 string
|
||||||
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(textToString(book.Description))
|
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(textToString(book.Description))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 345, Col: 41}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 326, Col: 41}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</textarea></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Summary</label> <textarea name=\"summary\" rows=\"2\" class=\"input\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</textarea></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Summary</label> <textarea name=\"summary\" rows=\"2\" class=\"input\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var37 string
|
var templ_7745c5c3_Var37 string
|
||||||
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(textToString(book.Summary))
|
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(textToString(book.Summary))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 350, Col: 37}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 331, Col: 37}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</textarea></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Tags</label><div class=\"flex flex-wrap gap-1.5 mb-2\"><template x-for=\"(tag, idx) in editorTags\" :key=\"idx\"><span class=\"chip\"><span x-text=\"tag\"></span> <button type=\"button\" class=\"hover:bg-surface-hover rounded leading-none\" @click=\"removeEditorTag(idx)\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</textarea></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Tags</label><div class=\"flex flex-wrap gap-1.5 mb-2\"><template x-for=\"(tag, idx) in editorTags\" :key=\"idx\"><span class=\"chip\"><span x-text=\"tag\"></span> <button type=\"button\" class=\"hover:bg-surface-hover rounded leading-none\" @click=\"removeEditorTag(idx)\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -781,43 +765,43 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</button></span></template></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</button></span></template></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, tag := range book.Tags {
|
for _, tag := range book.Tags {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "<span data-editor-tag=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<span data-editor-tag=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var38 string
|
var templ_7745c5c3_Var38 string
|
||||||
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.ResolveAttributeValue(tag)
|
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.ResolveAttributeValue(tag)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 363, Col: 36}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 344, Col: 36}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var38)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var38)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\" class=\"hidden\"></span>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\" class=\"hidden\"></span>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<div><input type=\"text\" x-model=\"tagSearch\" @input.debounce.300ms=\"searchEditorTags()\" @keydown=\"onTagKeydown($event)\" @blur=\"hideEditorTagDropdown()\" placeholder=\"Add tag...\" class=\"input\"><div x-show=\"showTagDropdown\" x-transition x-cloak class=\"mt-1 w-full rounded-lg border max-h-48 overflow-y-auto\" style=\"background-color: var(--bg-secondary); border-color: var(--border); box-shadow: var(--shadow-card);\"><template x-for=\"sug in tagSuggestions\" :key=\"sug.value\"><button type=\"button\" class=\"w-full text-left px-3 py-2 text-sm flex justify-between items-center hover:bg-surface-hover\" :class=\"highlightedTagIndex === tagSuggestions.indexOf(sug) ? 'bg-surface-hover' : ''\" @click=\"selectEditorTagSuggestion(sug.value)\"><span x-text=\"sug.value\"></span> <span class=\"text-xs\" style=\"color: var(--text-secondary);\" x-text=\"sug.count + ' books'\"></span></button></template></div></div></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Community Rating (0-10)</label> <input type=\"number\" name=\"community_rating\" min=\"0\" max=\"10\" step=\"0.1\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "<div><input type=\"text\" x-model=\"tagSearch\" @input.debounce.300ms=\"searchEditorTags()\" @keydown=\"onTagKeydown($event)\" @blur=\"hideEditorTagDropdown()\" placeholder=\"Add tag...\" class=\"input\"><div x-show=\"showTagDropdown\" x-transition x-cloak class=\"mt-1 w-full rounded-lg border max-h-48 overflow-y-auto\" style=\"background-color: var(--bg-secondary); border-color: var(--border); box-shadow: var(--shadow-card);\"><template x-for=\"sug in tagSuggestions\" :key=\"sug.value\"><button type=\"button\" class=\"w-full text-left px-3 py-2 text-sm flex justify-between items-center hover:bg-surface-hover\" :class=\"highlightedTagIndex === tagSuggestions.indexOf(sug) ? 'bg-surface-hover' : ''\" @click=\"selectEditorTagSuggestion(sug.value)\"><span x-text=\"sug.value\"></span> <span class=\"text-xs\" style=\"color: var(--text-secondary);\" x-text=\"sug.count + ' books'\"></span></button></template></div></div></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Community Rating (0-10)</label> <input type=\"number\" name=\"community_rating\" min=\"0\" max=\"10\" step=\"0.1\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var39 string
|
var templ_7745c5c3_Var39 string
|
||||||
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%.1f", book.CommunityRating.Float64))
|
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%.1f", book.CommunityRating.Float64))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 397, Col: 66}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 378, Col: 66}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var39)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var39)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "\" class=\"input\"></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('publication')\"><span class=\"font-semibold\">Publication</span> <span class=\"inline-flex\"><span x-show=\"openSections.publication\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\" class=\"input\"></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('publication')\"><span class=\"font-semibold\">Publication</span> <span class=\"inline-flex\"><span x-show=\"openSections.publication\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -825,7 +809,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</span> <span x-show=\"!openSections.publication\" x-cloak>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "</span> <span x-show=\"!openSections.publication\" x-cloak>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -833,85 +817,85 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "</span></span></button><div x-show=\"openSections.publication\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Publisher</label> <input type=\"text\" name=\"publisher\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</span></span></button><div x-show=\"openSections.publication\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Publisher</label> <input type=\"text\" name=\"publisher\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var40 string
|
var templ_7745c5c3_Var40 string
|
||||||
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Publisher))
|
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Publisher))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 415, Col: 80}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 396, Col: 80}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Date Published</label> <input type=\"date\" name=\"date_published\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Date Published</label> <input type=\"date\" name=\"date_published\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var41 string
|
var templ_7745c5c3_Var41 string
|
||||||
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(formatDateForInput(book.DatePublished))
|
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(formatDateForInput(book.DatePublished))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 420, Col: 55}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 401, Col: 55}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Edition</label> <input type=\"text\" name=\"edition\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Edition</label> <input type=\"text\" name=\"edition\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var42 string
|
var templ_7745c5c3_Var42 string
|
||||||
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Edition))
|
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Edition))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 425, Col: 76}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 406, Col: 76}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Language</label> <input type=\"text\" name=\"language\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Language</label> <input type=\"text\" name=\"language\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var43 string
|
var templ_7745c5c3_Var43 string
|
||||||
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Language))
|
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Language))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 429, Col: 78}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 410, Col: 78}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var43)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var43)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Genre</label> <input type=\"text\" name=\"genre\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Genre</label> <input type=\"text\" name=\"genre\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var44 string
|
var templ_7745c5c3_Var44 string
|
||||||
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Genre))
|
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Genre))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 433, Col: 72}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 414, Col: 72}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var44)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var44)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Copyright Year</label> <input type=\"number\" name=\"copyright_year\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Copyright Year</label> <input type=\"number\" name=\"copyright_year\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var45 string
|
var templ_7745c5c3_Var45 string
|
||||||
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.CopyrightYear.Int32))
|
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.CopyrightYear.Int32))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 438, Col: 60}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 419, Col: 60}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var45)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var45)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "\" class=\"input\"></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('series')\"><span class=\"font-semibold\">Series</span> <span class=\"inline-flex\"><span x-show=\"openSections.series\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "\" class=\"input\"></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('series')\"><span class=\"font-semibold\">Series</span> <span class=\"inline-flex\"><span x-show=\"openSections.series\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -919,7 +903,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</span> <span x-show=\"!openSections.series\" x-cloak>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</span> <span x-show=\"!openSections.series\" x-cloak>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -927,59 +911,59 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</span></span></button><div x-show=\"openSections.series\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Series</label> <input type=\"text\" name=\"series\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</span></span></button><div x-show=\"openSections.series\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Series</label> <input type=\"text\" name=\"series\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var46 string
|
var templ_7745c5c3_Var46 string
|
||||||
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Series))
|
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Series))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 456, Col: 74}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 437, Col: 74}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var46)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var46)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "\" class=\"input\"></div><div class=\"grid grid-cols-3 gap-3\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Number</label> <input type=\"number\" name=\"series_number\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "\" class=\"input\"></div><div class=\"grid grid-cols-3 gap-3\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Number</label> <input type=\"number\" name=\"series_number\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var47 string
|
var templ_7745c5c3_Var47 string
|
||||||
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.SeriesNumber.Int32))
|
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.SeriesNumber.Int32))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 462, Col: 60}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 443, Col: 60}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var47)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var47)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Count</label> <input type=\"number\" name=\"series_count\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Count</label> <input type=\"number\" name=\"series_count\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var48 string
|
var templ_7745c5c3_Var48 string
|
||||||
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.SeriesCount.Int32))
|
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.SeriesCount.Int32))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 468, Col: 59}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 449, Col: 59}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var48)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var48)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Volume</label> <input type=\"number\" name=\"volume\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Volume</label> <input type=\"number\" name=\"volume\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var49 string
|
var templ_7745c5c3_Var49 string
|
||||||
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.Volume.Int32))
|
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.Volume.Int32))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 474, Col: 54}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 455, Col: 54}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var49)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var49)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "\" class=\"input\"></div></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('identifiers')\"><span class=\"font-semibold\">Identifiers</span> <span class=\"inline-flex\"><span x-show=\"openSections.identifiers\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "\" class=\"input\"></div></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('identifiers')\"><span class=\"font-semibold\">Identifiers</span> <span class=\"inline-flex\"><span x-show=\"openSections.identifiers\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -987,7 +971,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</span> <span x-show=\"!openSections.identifiers\" x-cloak>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</span> <span x-show=\"!openSections.identifiers\" x-cloak>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -995,85 +979,85 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "</span></span></button><div x-show=\"openSections.identifiers\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">ISBN</label> <input type=\"text\" name=\"isbn\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</span></span></button><div x-show=\"openSections.identifiers\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">ISBN</label> <input type=\"text\" name=\"isbn\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var50 string
|
var templ_7745c5c3_Var50 string
|
||||||
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Isbn))
|
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Isbn))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 493, Col: 70}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 474, Col: 70}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var50)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var50)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">ASIN</label> <input type=\"text\" name=\"asin\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">ASIN</label> <input type=\"text\" name=\"asin\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var51 string
|
var templ_7745c5c3_Var51 string
|
||||||
templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Asin))
|
templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Asin))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 497, Col: 70}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 478, Col: 70}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var51)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var51)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Goodreads ID</label> <input type=\"text\" name=\"goodreads_id\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Goodreads ID</label> <input type=\"text\" name=\"goodreads_id\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var52 string
|
var templ_7745c5c3_Var52 string
|
||||||
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.GoodreadsID))
|
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.GoodreadsID))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 501, Col: 85}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 482, Col: 85}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var52)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var52)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">OpenLibrary ID</label> <input type=\"text\" name=\"openlibrary_id\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">OpenLibrary ID</label> <input type=\"text\" name=\"openlibrary_id\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var53 string
|
var templ_7745c5c3_Var53 string
|
||||||
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.OpenlibraryID))
|
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.OpenlibraryID))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 505, Col: 89}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 486, Col: 89}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var53)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var53)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Google Books ID</label> <input type=\"text\" name=\"google_books_id\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Google Books ID</label> <input type=\"text\" name=\"google_books_id\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var54 string
|
var templ_7745c5c3_Var54 string
|
||||||
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.GoogleBooksID))
|
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.GoogleBooksID))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 509, Col: 90}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 490, Col: 90}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Web URL</label> <input type=\"url\" name=\"web_url\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Web URL</label> <input type=\"url\" name=\"web_url\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var55 string
|
var templ_7745c5c3_Var55 string
|
||||||
templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.WebUrl))
|
templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.WebUrl))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 513, Col: 74}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 494, Col: 74}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var55)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var55)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "\" class=\"input\"></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('comic')\"><span class=\"font-semibold\">Comic/Manga</span> <span class=\"inline-flex\"><span x-show=\"openSections.comic\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "\" class=\"input\"></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('comic')\"><span class=\"font-semibold\">Comic/Manga</span> <span class=\"inline-flex\"><span x-show=\"openSections.comic\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1081,7 +1065,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "</span> <span x-show=\"!openSections.comic\" x-cloak>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</span> <span x-show=\"!openSections.comic\" x-cloak>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1089,186 +1073,186 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</span></span></button><div x-show=\"openSections.comic\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label for=\"manga_type\" class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Manga Type</label> <select id=\"manga_type\" name=\"manga_type\" class=\"input\"><option value=\"unknown\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</span></span></button><div x-show=\"openSections.comic\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label for=\"manga_type\" class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Manga Type</label> <select id=\"manga_type\" name=\"manga_type\" class=\"input\"><option value=\"unknown\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var56 string
|
var templ_7745c5c3_Var56 string
|
||||||
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "unknown")
|
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "unknown")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 531, Col: 85}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 512, Col: 85}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "\">Unknown</option> <option value=\"no\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "\">Unknown</option> <option value=\"no\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var57 string
|
var templ_7745c5c3_Var57 string
|
||||||
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "no")
|
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "no")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 532, Col: 75}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 513, Col: 75}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">No</option> <option value=\"yes\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "\">No</option> <option value=\"yes\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var58 string
|
var templ_7745c5c3_Var58 string
|
||||||
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "yes")
|
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "yes")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 533, Col: 77}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 514, Col: 77}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var58)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var58)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "\">Yes</option> <option value=\"yes_and_right_to_left\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "\">Yes</option> <option value=\"yes_and_right_to_left\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var59 string
|
var templ_7745c5c3_Var59 string
|
||||||
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "yes_and_right_to_left")
|
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MangaType) == "yes_and_right_to_left")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 534, Col: 113}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 515, Col: 113}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "\">Yes (Right to Left)</option></select></div><div><label for=\"reading_direction\" class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Reading Direction</label> <select id=\"reading_direction\" name=\"reading_direction\" class=\"input\"><option value=\"auto\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">Yes (Right to Left)</option></select></div><div><label for=\"reading_direction\" class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Reading Direction</label> <select id=\"reading_direction\" name=\"reading_direction\" class=\"input\"><option value=\"auto\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var60 string
|
var templ_7745c5c3_Var60 string
|
||||||
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "auto")
|
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "auto")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 540, Col: 86}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 521, Col: 86}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "\">Auto</option> <option value=\"ltr\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "\">Auto</option> <option value=\"ltr\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var61 string
|
var templ_7745c5c3_Var61 string
|
||||||
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "ltr")
|
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "ltr")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 541, Col: 84}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 522, Col: 84}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\">Left to Right</option> <option value=\"rtl\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "\">Left to Right</option> <option value=\"rtl\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var62 string
|
var templ_7745c5c3_Var62 string
|
||||||
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "rtl")
|
templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "rtl")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 542, Col: 84}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 523, Col: 84}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var62)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var62)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "\">Right to Left</option> <option value=\"vertical\" selected=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "\">Right to Left</option> <option value=\"vertical\" selected=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var63 string
|
var templ_7745c5c3_Var63 string
|
||||||
templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "vertical")
|
templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ReadingDirection) == "vertical")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 543, Col: 94}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 524, Col: 94}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "\">Vertical</option></select></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Age Rating</label> <input type=\"text\" name=\"age_rating\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\">Vertical</option></select></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Age Rating</label> <input type=\"text\" name=\"age_rating\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var64 string
|
var templ_7745c5c3_Var64 string
|
||||||
templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.AgeRating))
|
templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.AgeRating))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 548, Col: 81}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 529, Col: 81}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Story Arc</label> <input type=\"text\" name=\"story_arc\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Story Arc</label> <input type=\"text\" name=\"story_arc\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var65 string
|
var templ_7745c5c3_Var65 string
|
||||||
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.StoryArc))
|
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.StoryArc))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 552, Col: 79}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 533, Col: 79}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var65)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var65)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Imprint</label> <input type=\"text\" name=\"imprint\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Imprint</label> <input type=\"text\" name=\"imprint\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var66 string
|
var templ_7745c5c3_Var66 string
|
||||||
templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Imprint))
|
templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.Imprint))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 556, Col: 76}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 537, Col: 76}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Scan Information</label> <input type=\"text\" name=\"scan_information\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Scan Information</label> <input type=\"text\" name=\"scan_information\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var67 string
|
var templ_7745c5c3_Var67 string
|
||||||
templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ScanInformation))
|
templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.ScanInformation))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 560, Col: 93}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 541, Col: 93}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var67)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var67)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Metadata Notes</label> <textarea name=\"metadata_notes\" rows=\"2\" class=\"input\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Metadata Notes</label> <textarea name=\"metadata_notes\" rows=\"2\" class=\"input\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var68 string
|
var templ_7745c5c3_Var68 string
|
||||||
templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(textToString(book.MetadataNotes))
|
templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(textToString(book.MetadataNotes))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 565, Col: 43}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 546, Col: 43}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</textarea></div><div class=\"flex items-center gap-2\"><input type=\"checkbox\" name=\"is_black_and_white\" id=\"is_black_and_white\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "</textarea></div><div class=\"flex items-center gap-2\"><input type=\"checkbox\" name=\"is_black_and_white\" id=\"is_black_and_white\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if book.IsBlackAndWhite.Bool {
|
if book.IsBlackAndWhite.Bool {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, " checked")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, " checked")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, " class=\"rounded\"> <label for=\"is_black_and_white\" class=\"text-sm\" style=\"color: var(--text-secondary);\">Black & White</label></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('technical')\"><span class=\"font-semibold\">Technical</span> <span class=\"inline-flex\"><span x-show=\"openSections.technical\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, " class=\"rounded\"> <label for=\"is_black_and_white\" class=\"text-sm\" style=\"color: var(--text-secondary);\">Black & White</label></div></div></div><div class=\"rounded-xl border overflow-hidden\" style=\"border-color: var(--border);\"><button type=\"button\" class=\"w-full px-4 py-3 flex justify-between items-center hover:bg-surface-hover\" style=\"color: var(--text-primary);\" @click=\"toggleSection('technical')\"><span class=\"font-semibold\">Technical</span> <span class=\"inline-flex\"><span x-show=\"openSections.technical\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1276,7 +1260,7 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "</span> <span x-show=\"!openSections.technical\" x-cloak>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</span> <span x-show=\"!openSections.technical\" x-cloak>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1284,59 +1268,72 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</span></span></button><div x-show=\"openSections.technical\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Page Count</label> <input type=\"number\" name=\"page_count\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</span></span></button><div x-show=\"openSections.technical\" x-transition class=\"p-4 space-y-3 border-t\" style=\"border-color: var(--border);\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Page Count</label> <input type=\"number\" name=\"page_count\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var69 string
|
var templ_7745c5c3_Var69 string
|
||||||
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.PageCount.Int32))
|
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", book.PageCount.Int32))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 589, Col: 56}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 570, Col: 56}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Contributors (comma-separated)</label> <input type=\"text\" name=\"contributors\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "\" class=\"input\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Contributors (comma-separated)</label> <input type=\"text\" name=\"contributors\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var70 string
|
var templ_7745c5c3_Var70 string
|
||||||
templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.ResolveAttributeValue(stringSliceToString(book.Contributors))
|
templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.ResolveAttributeValue(stringSliceToString(book.Contributors))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 594, Col: 93}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 575, Col: 93}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var70)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var70)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "\" class=\"input\"></div><div class=\"grid grid-cols-2 gap-3\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Format</label> <input type=\"text\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "\" class=\"input\"></div><div class=\"grid grid-cols-2 gap-3\"><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">Format</label> <input type=\"text\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var71 string
|
var templ_7745c5c3_Var71 string
|
||||||
templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MimeType))
|
templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(textToString(book.MimeType))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 599, Col: 63}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 580, Col: 62}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "\" readonly class=\"input opacity-60\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">File Size</label> <input type=\"text\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "\" readonly class=\"input opacity-60\"></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">File Size</label> <input type=\"text\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var72 string
|
var templ_7745c5c3_Var72 string
|
||||||
templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%.1f MB", float64(book.FileSize.Int64)/1024/1024))
|
templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%.1f MB", float64(book.FileSize.Int64)/1024/1024))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 604, Col: 98}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 585, Col: 97}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "\" readonly class=\"input opacity-60\"></div></div></div></div></div></div><div class=\"flex items-center justify-between p-6 border-t flex-shrink-0\" style=\"border-color: var(--border);\"><button @click=\"rescanBook()\" :disabled=\"rescanning\" class=\"btn btn-secondary\"><span x-show=\"!rescanning\" class=\"inline-flex items-center gap-2\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "\" readonly class=\"input opacity-60\"></div></div><div><label class=\"block text-xs font-semibold uppercase tracking-wide mb-1\" style=\"color: var(--text-secondary);\">File Path</label> <input type=\"text\" value=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var73 string
|
||||||
|
templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.ResolveAttributeValue(book.FileLocation)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/book_detail_modals.templ`, Line: 591, Col: 51}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var73)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "\" readonly class=\"input opacity-60 font-mono text-xs\"></div></div></div></div></div><div class=\"flex items-center justify-between p-6 border-t flex-shrink-0\" style=\"border-color: var(--border);\"><div class=\"flex items-center gap-2\"><button @click=\"rescanBook()\" :disabled=\"rescanning\" class=\"btn btn-secondary\"><span x-show=\"!rescanning\" class=\"inline-flex items-center gap-2\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1344,7 +1341,15 @@ func MetadataEditorModal(book handlers.MediaDetail) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "<span>Rescan</span></span> <svg x-show=\"rescanning\" class=\"animate-spin inline-block h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\"><circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle> <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path></svg></button><div class=\"flex space-x-3\"><button @click=\"hideMetadataEditor()\" class=\"btn btn-secondary\">Cancel</button> <button @click=\"saveMetadata()\" class=\"btn btn-primary\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "<span>Rescan</span></span> <svg x-show=\"rescanning\" class=\"animate-spin inline-block h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\"><circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle> <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path></svg></button> <button @click=\"resetMetadata()\" :disabled=\"resetting\" class=\"btn btn-secondary\" title=\"Discard all manual edits and restore scanned metadata\"><span x-show=\"!resetting\" class=\"inline-flex items-center gap-2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = Icon("refresh", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "<span>Reset to Scanned</span></span> <svg x-show=\"resetting\" class=\"animate-spin inline-block h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\"><circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle> <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path></svg></button></div><div class=\"flex space-x-3\"><button @click=\"hideMetadataEditor()\" class=\"btn btn-secondary\">Cancel</button> <button @click=\"saveMetadata()\" class=\"btn btn-primary\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
+391
-360
File diff suppressed because it is too large
Load Diff
+38
-37
@@ -1,6 +1,5 @@
|
|||||||
import { Alpine } from "./alpine";
|
import { Alpine } from "./alpine";
|
||||||
import { showToast } from "./toast";
|
import { showToast } from "./toast";
|
||||||
import { generateCoverBlob } from "./cover-generator";
|
|
||||||
import { searchTagSuggestions, type TagSuggestion } from "./tag-dropdown";
|
import { searchTagSuggestions, type TagSuggestion } from "./tag-dropdown";
|
||||||
|
|
||||||
function getMediaId(): string {
|
function getMediaId(): string {
|
||||||
@@ -91,13 +90,13 @@ export {
|
|||||||
|
|
||||||
interface MetadataEditorState {
|
interface MetadataEditorState {
|
||||||
openSections: Record<string, boolean>;
|
openSections: Record<string, boolean>;
|
||||||
coverGenerating: boolean;
|
|
||||||
hasExistingCover: boolean;
|
hasExistingCover: boolean;
|
||||||
newCoverPreview: string;
|
newCoverPreview: string;
|
||||||
coverFile: Blob | null;
|
coverFile: Blob | null;
|
||||||
coverAction: string;
|
coverAction: string;
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
rescanning: boolean;
|
rescanning: boolean;
|
||||||
|
resetting: boolean;
|
||||||
userRating: number;
|
userRating: number;
|
||||||
ratingHover: number;
|
ratingHover: number;
|
||||||
ratingSaving: boolean;
|
ratingSaving: boolean;
|
||||||
@@ -108,10 +107,10 @@ interface MetadataEditorState {
|
|||||||
showMetadataEditor(): void;
|
showMetadataEditor(): void;
|
||||||
hideMetadataEditor(): void;
|
hideMetadataEditor(): void;
|
||||||
handleCoverUpload(event: Event): void;
|
handleCoverUpload(event: Event): void;
|
||||||
generateCover(): Promise<void>;
|
|
||||||
removeCover(): void;
|
removeCover(): void;
|
||||||
saveMetadata(): Promise<void>;
|
saveMetadata(): Promise<void>;
|
||||||
rescanBook(): Promise<void>;
|
rescanBook(): Promise<void>;
|
||||||
|
resetMetadata(): Promise<void>;
|
||||||
starFill(i: number): string;
|
starFill(i: number): string;
|
||||||
ratingText(): string;
|
ratingText(): string;
|
||||||
setRating(value: number): Promise<void>;
|
setRating(value: number): Promise<void>;
|
||||||
@@ -130,12 +129,6 @@ Alpine.data("bookDetail", () => {
|
|||||||
coverImg?.src &&
|
coverImg?.src &&
|
||||||
!coverImg.src.includes("placeholder-book.svg");
|
!coverImg.src.includes("placeholder-book.svg");
|
||||||
|
|
||||||
const coverPreviewEl = document.querySelector(
|
|
||||||
".aspect-\\[2\\/3\\] img",
|
|
||||||
) as HTMLImageElement | null;
|
|
||||||
const coverSrc = coverPreviewEl?.src || "";
|
|
||||||
const fileUrl = coverSrc && !coverSrc.includes("placeholder") ? coverSrc : "";
|
|
||||||
|
|
||||||
const initialTags: string[] = [];
|
const initialTags: string[] = [];
|
||||||
const tagBadges = document.querySelectorAll("#metadata-editor-modal [data-editor-tag]");
|
const tagBadges = document.querySelectorAll("#metadata-editor-modal [data-editor-tag]");
|
||||||
tagBadges.forEach((el) => {
|
tagBadges.forEach((el) => {
|
||||||
@@ -145,13 +138,13 @@ Alpine.data("bookDetail", () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
openSections: { basic: true } as Record<string, boolean>,
|
openSections: { basic: true } as Record<string, boolean>,
|
||||||
coverGenerating: false,
|
|
||||||
hasExistingCover: !!hasCover,
|
hasExistingCover: !!hasCover,
|
||||||
newCoverPreview: "",
|
newCoverPreview: "",
|
||||||
coverFile: null as Blob | null,
|
coverFile: null as Blob | null,
|
||||||
coverAction: "keep",
|
coverAction: "keep",
|
||||||
saving: false,
|
saving: false,
|
||||||
rescanning: false,
|
rescanning: false,
|
||||||
|
resetting: false,
|
||||||
userRating: 0,
|
userRating: 0,
|
||||||
ratingHover: 0,
|
ratingHover: 0,
|
||||||
ratingSaving: false,
|
ratingSaving: false,
|
||||||
@@ -462,33 +455,6 @@ Alpine.data("bookDetail", () => {
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
},
|
},
|
||||||
|
|
||||||
async generateCover() {
|
|
||||||
this.coverGenerating = true;
|
|
||||||
try {
|
|
||||||
const formatGroup =
|
|
||||||
document
|
|
||||||
.querySelector('[data-format-group]')
|
|
||||||
?.getAttribute("data-format-group") || "reflowable";
|
|
||||||
|
|
||||||
const blob = await generateCoverBlob(fileUrl, formatGroup);
|
|
||||||
if (!blob) return;
|
|
||||||
|
|
||||||
this.coverFile = blob;
|
|
||||||
this.coverAction = "upload";
|
|
||||||
|
|
||||||
const preview = document.getElementById(
|
|
||||||
"metadata-cover-preview",
|
|
||||||
) as HTMLImageElement;
|
|
||||||
if (preview) {
|
|
||||||
preview.src = URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
this.newCoverPreview = URL.createObjectURL(blob);
|
|
||||||
showToast("Cover generated successfully", "success");
|
|
||||||
} finally {
|
|
||||||
this.coverGenerating = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
removeCover() {
|
removeCover() {
|
||||||
this.coverAction = "remove";
|
this.coverAction = "remove";
|
||||||
this.coverFile = null;
|
this.coverFile = null;
|
||||||
@@ -587,6 +553,41 @@ Alpine.data("bookDetail", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async resetMetadata() {
|
||||||
|
if (this.resetting) return;
|
||||||
|
if (
|
||||||
|
!window.confirm(
|
||||||
|
"Reset all metadata to scanned defaults? Manual edits and custom covers will be discarded.",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.resetting = true;
|
||||||
|
const mediaId = getMediaId();
|
||||||
|
try {
|
||||||
|
const resp = await fetch(
|
||||||
|
`/api/media-items/${mediaId}/rescan?reset_overrides=true`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { Authorization: getAuthHeader() },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = await resp.json().catch(() => ({}));
|
||||||
|
throw new Error(err.error || "Failed to reset metadata");
|
||||||
|
}
|
||||||
|
showToast("Metadata reset to scanned defaults", "success");
|
||||||
|
setTimeout(() => window.location.reload(), 500);
|
||||||
|
} catch (e) {
|
||||||
|
showToast(
|
||||||
|
e instanceof Error ? e.message : "Failed to reset metadata",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.resetting = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async searchEditorTags() {
|
async searchEditorTags() {
|
||||||
const libraryId = document.body.getAttribute("data-library-id") || "";
|
const libraryId = document.body.getAttribute("data-library-id") || "";
|
||||||
if (!this.tagSearch || this.tagSearch.length < 2 || !libraryId) {
|
if (!this.tagSearch || this.tagSearch.length < 2 || !libraryId) {
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
import { showToast } from "./toast";
|
|
||||||
|
|
||||||
function getToken(): string {
|
|
||||||
return localStorage.getItem("token") || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateCoverBlob(
|
|
||||||
fileUrl: string,
|
|
||||||
formatGroup: string,
|
|
||||||
): Promise<Blob | null> {
|
|
||||||
try {
|
|
||||||
const View = (await import("foliate-js/view.js")).default;
|
|
||||||
const view = new View();
|
|
||||||
|
|
||||||
const resp = await fetch(fileUrl, {
|
|
||||||
headers: { Authorization: `Bearer ${getToken()}` },
|
|
||||||
});
|
|
||||||
if (!resp.ok) {
|
|
||||||
showToast("Failed to fetch book file for cover generation", "error");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = await resp.blob();
|
|
||||||
const file = new File([blob], "book", { type: blob.type });
|
|
||||||
|
|
||||||
const pdfOptions =
|
|
||||||
formatGroup === "fixed_layout"
|
|
||||||
? {
|
|
||||||
pdf: {
|
|
||||||
cMapUrl: "/static/vendor/pdfjs/cmaps/",
|
|
||||||
standardFontDataUrl: "/static/vendor/pdfjs/standard_fonts/",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
|
|
||||||
await view.open(file, pdfOptions);
|
|
||||||
|
|
||||||
if (!view.book?.sections?.length) {
|
|
||||||
showToast("Could not read book sections", "error");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatGroup === "fixed_layout") {
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
await view.renderer?.renderPage(view.book.sections[0], canvas);
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
canvas.toBlob(
|
|
||||||
(b) => resolve(b),
|
|
||||||
"image/jpeg",
|
|
||||||
0.85,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const coverHref = view.book.cover;
|
|
||||||
if (coverHref) {
|
|
||||||
const coverBlob = await coverHref.blob();
|
|
||||||
if (coverBlob.type.startsWith("image/")) {
|
|
||||||
return coverBlob;
|
|
||||||
}
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
const c = document.createElement("canvas");
|
|
||||||
c.width = img.naturalWidth;
|
|
||||||
c.height = img.naturalHeight;
|
|
||||||
c.getContext("2d")?.drawImage(img, 0, 0);
|
|
||||||
c.toBlob((b) => resolve(b), "image/jpeg", 0.85);
|
|
||||||
};
|
|
||||||
img.onerror = () => resolve(null);
|
|
||||||
img.src = URL.createObjectURL(coverBlob);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showToast("No cover found in book file", "error");
|
|
||||||
return null;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Cover generation failed:", e);
|
|
||||||
showToast("Cover generation failed", "error");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user