diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index a119c68..c4b0ae4 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -156,8 +156,8 @@ export const i18nData = { "Cloudflare Turnstile Secret Key": "Cloudflare Turnstile 密钥", "If the cloudflare turnstile keys are not empty, the turnstile will be used for register and download.": "如果设置了 Cloudflare Turnstile 密钥,将在注册和下载时启用验证", - "The first image will be used as the cover image": - "第一张图片将用作封面图片", + "You can select a cover image using the radio button in the Cover column": + "您可以使用封面列中的单选按钮选择封面图片", "Please enter a search keyword": "请输入搜索关键词", "Searching...": "搜索中...", "Create Tag": "创建标签", @@ -425,8 +425,8 @@ export const i18nData = { "Cloudflare Turnstile Secret Key": "Cloudflare Turnstile 密鑰", "If the cloudflare turnstile keys are not empty, the turnstile will be used for register and download.": "如果設置了 Cloudflare Turnstile 密鑰,將在註冊和下載時啟用驗證", - "The first image will be used as the cover image": - "第一張圖片將用作封面圖片", + "You can select a cover image using the radio button in the Cover column": + "您可以使用封面列中的單選按鈕選擇封面圖片", "Please enter a search keyword": "請輸入搜尋關鍵字", "Searching...": "搜尋中...", "Create Tag": "創建標籤", diff --git a/frontend/src/network/models.ts b/frontend/src/network/models.ts index 4a15852..1e6b59b 100644 --- a/frontend/src/network/models.ts +++ b/frontend/src/network/models.ts @@ -48,6 +48,7 @@ export interface CreateResourceParams { tags: number[]; article: string; images: number[]; + cover_id?: number; gallery: number[]; gallery_nsfw: number[]; characters: CharacterParams[]; @@ -94,6 +95,7 @@ export interface ResourceDetails { releaseDate?: string; tags: Tag[]; images: Image[]; + coverId?: number; files: RFile[]; author: User; views: number; diff --git a/frontend/src/pages/edit_resource_page.tsx b/frontend/src/pages/edit_resource_page.tsx index 55714e9..31945fb 100644 --- a/frontend/src/pages/edit_resource_page.tsx +++ b/frontend/src/pages/edit_resource_page.tsx @@ -29,6 +29,7 @@ export default function EditResourcePage() { const [tags, setTags] = useState([]); const [article, setArticle] = useState(""); const [images, setImages] = useState([]); + const [coverId, setCoverId] = useState(undefined); const [links, setLinks] = useState<{ label: string; url: string }[]>([]); const [galleryImages, setGalleryImages] = useState([]); const [galleryNsfw, setGalleryNsfw] = useState([]); @@ -59,6 +60,7 @@ export default function EditResourcePage() { setTags(data.tags); setArticle(data.article); setImages(data.images.map((i) => i.id)); + setCoverId(data.coverId); setLinks(data.links ?? []); setGalleryImages(data.gallery ?? []); setGalleryNsfw(data.galleryNsfw ?? []); @@ -106,6 +108,7 @@ export default function EditResourcePage() { tags: tags.map((tag) => tag.id), article: article, images: images, + cover_id: coverId, links: links, gallery: galleryImages, gallery_nsfw: galleryNsfw, @@ -328,7 +331,7 @@ export default function EditResourcePage() { "Images will not be displayed automatically, you need to reference them in the description", )}

-

{t("The first image will be used as the cover image")}

+

{t("You can select a cover image using the radio button in the Cover column")}

{t("Preview")} {"Markdown"} + {t("Cover")} {t("Gallery")} {"Nsfw"} {t("Action")} @@ -368,6 +372,15 @@ export default function EditResourcePage() { + + setCoverId(image)} + /> + diff --git a/frontend/src/pages/publish_page.tsx b/frontend/src/pages/publish_page.tsx index 61431ce..c4706cf 100644 --- a/frontend/src/pages/publish_page.tsx +++ b/frontend/src/pages/publish_page.tsx @@ -28,6 +28,7 @@ export default function PublishPage() { const [tags, setTags] = useState([]); const [article, setArticle] = useState(""); const [images, setImages] = useState([]); + const [coverId, setCoverId] = useState(undefined); const [links, setLinks] = useState<{ label: string; url: string }[]>([]); const [galleryImages, setGalleryImages] = useState([]); const [galleryNsfw, setGalleryNsfw] = useState([]); @@ -48,6 +49,7 @@ export default function PublishPage() { setTags(data.tags || []); setArticle(data.article || ""); setImages(data.images || []); + setCoverId(data.cover_id || undefined); setLinks(data.links || []); setGalleryImages(data.gallery || []); setGalleryNsfw(data.gallery_nsfw || []); @@ -64,6 +66,7 @@ export default function PublishPage() { tags: tags, article: article, images: images, + cover_id: coverId, links: links, gallery: galleryImages, gallery_nsfw: galleryNsfw, @@ -73,7 +76,7 @@ export default function PublishPage() { const dataString = JSON.stringify(data); localStorage.setItem("publish_data", dataString); } - }, [altTitles, article, images, tags, title, links, galleryImages, galleryNsfw, characters, releaseDate]); + }, [altTitles, article, images, coverId, tags, title, links, galleryImages, galleryNsfw, characters, releaseDate]); const navigate = useNavigate(); const { t } = useTranslation(); @@ -120,6 +123,7 @@ export default function PublishPage() { tags: tags.map((tag) => tag.id), article: article, images: images, + cover_id: coverId, links: links, gallery: galleryImages, gallery_nsfw: galleryNsfw, @@ -344,7 +348,7 @@ export default function PublishPage() { "Images will not be displayed automatically, you need to reference them in the description", )}

-

{t("The first image will be used as the cover image")}

+

{t("You can select a cover image using the radio button in the Cover column")}

{t("Preview")} {"Markdown"} - {"Gallery"} + {t("Cover")} + {t("Gallery")} {"Nsfw"} {t("Action")} @@ -384,6 +389,15 @@ export default function PublishPage() { + + setCoverId(image)} + /> + diff --git a/server/model/resource.go b/server/model/resource.go index 230cb9d..58869d4 100644 --- a/server/model/resource.go +++ b/server/model/resource.go @@ -14,8 +14,9 @@ type Resource struct { ReleaseDate *time.Time Article string Images []Image `gorm:"many2many:resource_images;"` - Tags []Tag `gorm:"many2many:resource_tags;"` - Files []File `gorm:"foreignKey:ResourceID"` + CoverID *uint + Tags []Tag `gorm:"many2many:resource_tags;"` + Files []File `gorm:"foreignKey:ResourceID"` UserID uint User User Views uint @@ -52,6 +53,7 @@ type ResourceDetailView struct { ReleaseDate *time.Time `json:"releaseDate,omitempty"` Tags []TagView `json:"tags"` Images []ImageView `json:"images"` + CoverID *uint `json:"coverId,omitempty"` Files []FileView `json:"files"` Author UserView `json:"author"` Views uint `json:"views"` @@ -78,7 +80,18 @@ func (r *Resource) ToView() ResourceView { } var image *ImageView - if len(r.Images) > 0 { + if r.CoverID != nil { + // Use the cover image if specified + for _, img := range r.Images { + if img.ID == *r.CoverID { + v := img.ToView() + image = &v + break + } + } + } + // If no cover is set or cover image not found, use the first image + if image == nil && len(r.Images) > 0 { v := r.Images[0].ToView() image = &v } @@ -122,6 +135,7 @@ func (r *Resource) ToDetailView() ResourceDetailView { ReleaseDate: r.ReleaseDate, Tags: tags, Images: images, + CoverID: r.CoverID, Files: files, Author: r.User.ToView(), Views: r.Views, diff --git a/server/service/resource.go b/server/service/resource.go index f917953..f9ac8b8 100644 --- a/server/service/resource.go +++ b/server/service/resource.go @@ -34,6 +34,7 @@ type ResourceParams struct { Tags []uint `json:"tags"` Article string `json:"article"` Images []uint `json:"images"` + CoverID *uint `json:"cover_id"` Gallery []uint `json:"gallery"` GalleryNsfw []uint `json:"gallery_nsfw"` Characters []CharacterParams `json:"characters"` @@ -110,6 +111,14 @@ func CreateResource(uid uint, params *ResourceParams) (uint, error) { } date = &parsedDate } + // Validate CoverID if provided + var coverID *uint + if params.CoverID != nil && *params.CoverID != 0 { + if !slices.Contains(params.Images, *params.CoverID) { + return 0, model.NewRequestError("Cover ID must be one of the resource images") + } + coverID = params.CoverID + } r := model.Resource{ Title: params.Title, AlternativeTitles: params.AlternativeTitles, @@ -117,6 +126,7 @@ func CreateResource(uid uint, params *ResourceParams) (uint, error) { Links: params.Links, ReleaseDate: date, Images: images, + CoverID: coverID, Tags: tags, UserID: uid, Gallery: gallery, @@ -548,11 +558,21 @@ func UpdateResource(uid, rid uint, params *ResourceParams) error { date = &parsedDate } + // Validate CoverID if provided + var coverID *uint + if params.CoverID != nil && *params.CoverID != 0 { + if !slices.Contains(params.Images, *params.CoverID) { + return model.NewRequestError("Cover ID must be one of the resource images") + } + coverID = params.CoverID + } + r.Title = params.Title r.AlternativeTitles = params.AlternativeTitles r.Article = params.Article r.Links = params.Links r.ReleaseDate = date + r.CoverID = coverID r.Gallery = gallery r.GalleryNsfw = nsfw r.Characters = characters