mirror of
https://github.com/wgh136/nysoure.git
synced 2025-12-16 15:51:14 +00:00
feat: cover
This commit is contained in:
@@ -156,8 +156,8 @@ export const i18nData = {
|
|||||||
"Cloudflare Turnstile Secret Key": "Cloudflare Turnstile 密钥",
|
"Cloudflare Turnstile Secret Key": "Cloudflare Turnstile 密钥",
|
||||||
"If the cloudflare turnstile keys are not empty, the turnstile will be used for register and download.":
|
"If the cloudflare turnstile keys are not empty, the turnstile will be used for register and download.":
|
||||||
"如果设置了 Cloudflare Turnstile 密钥,将在注册和下载时启用验证",
|
"如果设置了 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": "请输入搜索关键词",
|
"Please enter a search keyword": "请输入搜索关键词",
|
||||||
"Searching...": "搜索中...",
|
"Searching...": "搜索中...",
|
||||||
"Create Tag": "创建标签",
|
"Create Tag": "创建标签",
|
||||||
@@ -425,8 +425,8 @@ export const i18nData = {
|
|||||||
"Cloudflare Turnstile Secret Key": "Cloudflare Turnstile 密鑰",
|
"Cloudflare Turnstile Secret Key": "Cloudflare Turnstile 密鑰",
|
||||||
"If the cloudflare turnstile keys are not empty, the turnstile will be used for register and download.":
|
"If the cloudflare turnstile keys are not empty, the turnstile will be used for register and download.":
|
||||||
"如果設置了 Cloudflare Turnstile 密鑰,將在註冊和下載時啟用驗證",
|
"如果設置了 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": "請輸入搜尋關鍵字",
|
"Please enter a search keyword": "請輸入搜尋關鍵字",
|
||||||
"Searching...": "搜尋中...",
|
"Searching...": "搜尋中...",
|
||||||
"Create Tag": "創建標籤",
|
"Create Tag": "創建標籤",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export interface CreateResourceParams {
|
|||||||
tags: number[];
|
tags: number[];
|
||||||
article: string;
|
article: string;
|
||||||
images: number[];
|
images: number[];
|
||||||
|
cover_id?: number;
|
||||||
gallery: number[];
|
gallery: number[];
|
||||||
gallery_nsfw: number[];
|
gallery_nsfw: number[];
|
||||||
characters: CharacterParams[];
|
characters: CharacterParams[];
|
||||||
@@ -94,6 +95,7 @@ export interface ResourceDetails {
|
|||||||
releaseDate?: string;
|
releaseDate?: string;
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
images: Image[];
|
images: Image[];
|
||||||
|
coverId?: number;
|
||||||
files: RFile[];
|
files: RFile[];
|
||||||
author: User;
|
author: User;
|
||||||
views: number;
|
views: number;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default function EditResourcePage() {
|
|||||||
const [tags, setTags] = useState<Tag[]>([]);
|
const [tags, setTags] = useState<Tag[]>([]);
|
||||||
const [article, setArticle] = useState<string>("");
|
const [article, setArticle] = useState<string>("");
|
||||||
const [images, setImages] = useState<number[]>([]);
|
const [images, setImages] = useState<number[]>([]);
|
||||||
|
const [coverId, setCoverId] = useState<number | undefined>(undefined);
|
||||||
const [links, setLinks] = useState<{ label: string; url: string }[]>([]);
|
const [links, setLinks] = useState<{ label: string; url: string }[]>([]);
|
||||||
const [galleryImages, setGalleryImages] = useState<number[]>([]);
|
const [galleryImages, setGalleryImages] = useState<number[]>([]);
|
||||||
const [galleryNsfw, setGalleryNsfw] = useState<number[]>([]);
|
const [galleryNsfw, setGalleryNsfw] = useState<number[]>([]);
|
||||||
@@ -59,6 +60,7 @@ export default function EditResourcePage() {
|
|||||||
setTags(data.tags);
|
setTags(data.tags);
|
||||||
setArticle(data.article);
|
setArticle(data.article);
|
||||||
setImages(data.images.map((i) => i.id));
|
setImages(data.images.map((i) => i.id));
|
||||||
|
setCoverId(data.coverId);
|
||||||
setLinks(data.links ?? []);
|
setLinks(data.links ?? []);
|
||||||
setGalleryImages(data.gallery ?? []);
|
setGalleryImages(data.gallery ?? []);
|
||||||
setGalleryNsfw(data.galleryNsfw ?? []);
|
setGalleryNsfw(data.galleryNsfw ?? []);
|
||||||
@@ -106,6 +108,7 @@ export default function EditResourcePage() {
|
|||||||
tags: tags.map((tag) => tag.id),
|
tags: tags.map((tag) => tag.id),
|
||||||
article: article,
|
article: article,
|
||||||
images: images,
|
images: images,
|
||||||
|
cover_id: coverId,
|
||||||
links: links,
|
links: links,
|
||||||
gallery: galleryImages,
|
gallery: galleryImages,
|
||||||
gallery_nsfw: galleryNsfw,
|
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",
|
"Images will not be displayed automatically, you need to reference them in the description",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>{t("The first image will be used as the cover image")}</p>
|
<p>{t("You can select a cover image using the radio button in the Cover column")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -339,6 +342,7 @@ export default function EditResourcePage() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{t("Preview")}</td>
|
<td>{t("Preview")}</td>
|
||||||
<td>{"Markdown"}</td>
|
<td>{"Markdown"}</td>
|
||||||
|
<td>{t("Cover")}</td>
|
||||||
<td>{t("Gallery")}</td>
|
<td>{t("Gallery")}</td>
|
||||||
<td>{"Nsfw"}</td>
|
<td>{"Nsfw"}</td>
|
||||||
<td>{t("Action")}</td>
|
<td>{t("Action")}</td>
|
||||||
@@ -368,6 +372,15 @@ export default function EditResourcePage() {
|
|||||||
<MdContentCopy />
|
<MdContentCopy />
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="cover"
|
||||||
|
className="radio radio-accent"
|
||||||
|
checked={coverId === image}
|
||||||
|
onChange={() => setCoverId(image)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -409,6 +422,9 @@ export default function EditResourcePage() {
|
|||||||
const newImages = [...images];
|
const newImages = [...images];
|
||||||
newImages.splice(index, 1);
|
newImages.splice(index, 1);
|
||||||
setImages(newImages);
|
setImages(newImages);
|
||||||
|
if (coverId === id) {
|
||||||
|
setCoverId(undefined);
|
||||||
|
}
|
||||||
network.deleteImage(id);
|
network.deleteImage(id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default function PublishPage() {
|
|||||||
const [tags, setTags] = useState<Tag[]>([]);
|
const [tags, setTags] = useState<Tag[]>([]);
|
||||||
const [article, setArticle] = useState<string>("");
|
const [article, setArticle] = useState<string>("");
|
||||||
const [images, setImages] = useState<number[]>([]);
|
const [images, setImages] = useState<number[]>([]);
|
||||||
|
const [coverId, setCoverId] = useState<number | undefined>(undefined);
|
||||||
const [links, setLinks] = useState<{ label: string; url: string }[]>([]);
|
const [links, setLinks] = useState<{ label: string; url: string }[]>([]);
|
||||||
const [galleryImages, setGalleryImages] = useState<number[]>([]);
|
const [galleryImages, setGalleryImages] = useState<number[]>([]);
|
||||||
const [galleryNsfw, setGalleryNsfw] = useState<number[]>([]);
|
const [galleryNsfw, setGalleryNsfw] = useState<number[]>([]);
|
||||||
@@ -48,6 +49,7 @@ export default function PublishPage() {
|
|||||||
setTags(data.tags || []);
|
setTags(data.tags || []);
|
||||||
setArticle(data.article || "");
|
setArticle(data.article || "");
|
||||||
setImages(data.images || []);
|
setImages(data.images || []);
|
||||||
|
setCoverId(data.cover_id || undefined);
|
||||||
setLinks(data.links || []);
|
setLinks(data.links || []);
|
||||||
setGalleryImages(data.gallery || []);
|
setGalleryImages(data.gallery || []);
|
||||||
setGalleryNsfw(data.gallery_nsfw || []);
|
setGalleryNsfw(data.gallery_nsfw || []);
|
||||||
@@ -64,6 +66,7 @@ export default function PublishPage() {
|
|||||||
tags: tags,
|
tags: tags,
|
||||||
article: article,
|
article: article,
|
||||||
images: images,
|
images: images,
|
||||||
|
cover_id: coverId,
|
||||||
links: links,
|
links: links,
|
||||||
gallery: galleryImages,
|
gallery: galleryImages,
|
||||||
gallery_nsfw: galleryNsfw,
|
gallery_nsfw: galleryNsfw,
|
||||||
@@ -73,7 +76,7 @@ export default function PublishPage() {
|
|||||||
const dataString = JSON.stringify(data);
|
const dataString = JSON.stringify(data);
|
||||||
localStorage.setItem("publish_data", dataString);
|
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 navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -120,6 +123,7 @@ export default function PublishPage() {
|
|||||||
tags: tags.map((tag) => tag.id),
|
tags: tags.map((tag) => tag.id),
|
||||||
article: article,
|
article: article,
|
||||||
images: images,
|
images: images,
|
||||||
|
cover_id: coverId,
|
||||||
links: links,
|
links: links,
|
||||||
gallery: galleryImages,
|
gallery: galleryImages,
|
||||||
gallery_nsfw: galleryNsfw,
|
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",
|
"Images will not be displayed automatically, you need to reference them in the description",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>{t("The first image will be used as the cover image")}</p>
|
<p>{t("You can select a cover image using the radio button in the Cover column")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -355,7 +359,8 @@ export default function PublishPage() {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{t("Preview")}</td>
|
<td>{t("Preview")}</td>
|
||||||
<td>{"Markdown"}</td>
|
<td>{"Markdown"}</td>
|
||||||
<td>{"Gallery"}</td>
|
<td>{t("Cover")}</td>
|
||||||
|
<td>{t("Gallery")}</td>
|
||||||
<td>{"Nsfw"}</td>
|
<td>{"Nsfw"}</td>
|
||||||
<td>{t("Action")}</td>
|
<td>{t("Action")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -384,6 +389,15 @@ export default function PublishPage() {
|
|||||||
<MdContentCopy />
|
<MdContentCopy />
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="cover"
|
||||||
|
className="radio radio-accent"
|
||||||
|
checked={coverId === image}
|
||||||
|
onChange={() => setCoverId(image)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -425,6 +439,9 @@ export default function PublishPage() {
|
|||||||
const newImages = [...images];
|
const newImages = [...images];
|
||||||
newImages.splice(index, 1);
|
newImages.splice(index, 1);
|
||||||
setImages(newImages);
|
setImages(newImages);
|
||||||
|
if (coverId === id) {
|
||||||
|
setCoverId(undefined);
|
||||||
|
}
|
||||||
network.deleteImage(id);
|
network.deleteImage(id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ type Resource struct {
|
|||||||
ReleaseDate *time.Time
|
ReleaseDate *time.Time
|
||||||
Article string
|
Article string
|
||||||
Images []Image `gorm:"many2many:resource_images;"`
|
Images []Image `gorm:"many2many:resource_images;"`
|
||||||
Tags []Tag `gorm:"many2many:resource_tags;"`
|
CoverID *uint
|
||||||
Files []File `gorm:"foreignKey:ResourceID"`
|
Tags []Tag `gorm:"many2many:resource_tags;"`
|
||||||
|
Files []File `gorm:"foreignKey:ResourceID"`
|
||||||
UserID uint
|
UserID uint
|
||||||
User User
|
User User
|
||||||
Views uint
|
Views uint
|
||||||
@@ -52,6 +53,7 @@ type ResourceDetailView struct {
|
|||||||
ReleaseDate *time.Time `json:"releaseDate,omitempty"`
|
ReleaseDate *time.Time `json:"releaseDate,omitempty"`
|
||||||
Tags []TagView `json:"tags"`
|
Tags []TagView `json:"tags"`
|
||||||
Images []ImageView `json:"images"`
|
Images []ImageView `json:"images"`
|
||||||
|
CoverID *uint `json:"coverId,omitempty"`
|
||||||
Files []FileView `json:"files"`
|
Files []FileView `json:"files"`
|
||||||
Author UserView `json:"author"`
|
Author UserView `json:"author"`
|
||||||
Views uint `json:"views"`
|
Views uint `json:"views"`
|
||||||
@@ -78,7 +80,18 @@ func (r *Resource) ToView() ResourceView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var image *ImageView
|
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()
|
v := r.Images[0].ToView()
|
||||||
image = &v
|
image = &v
|
||||||
}
|
}
|
||||||
@@ -122,6 +135,7 @@ func (r *Resource) ToDetailView() ResourceDetailView {
|
|||||||
ReleaseDate: r.ReleaseDate,
|
ReleaseDate: r.ReleaseDate,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Images: images,
|
Images: images,
|
||||||
|
CoverID: r.CoverID,
|
||||||
Files: files,
|
Files: files,
|
||||||
Author: r.User.ToView(),
|
Author: r.User.ToView(),
|
||||||
Views: r.Views,
|
Views: r.Views,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type ResourceParams struct {
|
|||||||
Tags []uint `json:"tags"`
|
Tags []uint `json:"tags"`
|
||||||
Article string `json:"article"`
|
Article string `json:"article"`
|
||||||
Images []uint `json:"images"`
|
Images []uint `json:"images"`
|
||||||
|
CoverID *uint `json:"cover_id"`
|
||||||
Gallery []uint `json:"gallery"`
|
Gallery []uint `json:"gallery"`
|
||||||
GalleryNsfw []uint `json:"gallery_nsfw"`
|
GalleryNsfw []uint `json:"gallery_nsfw"`
|
||||||
Characters []CharacterParams `json:"characters"`
|
Characters []CharacterParams `json:"characters"`
|
||||||
@@ -110,6 +111,14 @@ func CreateResource(uid uint, params *ResourceParams) (uint, error) {
|
|||||||
}
|
}
|
||||||
date = &parsedDate
|
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{
|
r := model.Resource{
|
||||||
Title: params.Title,
|
Title: params.Title,
|
||||||
AlternativeTitles: params.AlternativeTitles,
|
AlternativeTitles: params.AlternativeTitles,
|
||||||
@@ -117,6 +126,7 @@ func CreateResource(uid uint, params *ResourceParams) (uint, error) {
|
|||||||
Links: params.Links,
|
Links: params.Links,
|
||||||
ReleaseDate: date,
|
ReleaseDate: date,
|
||||||
Images: images,
|
Images: images,
|
||||||
|
CoverID: coverID,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
UserID: uid,
|
UserID: uid,
|
||||||
Gallery: gallery,
|
Gallery: gallery,
|
||||||
@@ -548,11 +558,21 @@ func UpdateResource(uid, rid uint, params *ResourceParams) error {
|
|||||||
date = &parsedDate
|
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.Title = params.Title
|
||||||
r.AlternativeTitles = params.AlternativeTitles
|
r.AlternativeTitles = params.AlternativeTitles
|
||||||
r.Article = params.Article
|
r.Article = params.Article
|
||||||
r.Links = params.Links
|
r.Links = params.Links
|
||||||
r.ReleaseDate = date
|
r.ReleaseDate = date
|
||||||
|
r.CoverID = coverID
|
||||||
r.Gallery = gallery
|
r.Gallery = gallery
|
||||||
r.GalleryNsfw = nsfw
|
r.GalleryNsfw = nsfw
|
||||||
r.Characters = characters
|
r.Characters = characters
|
||||||
|
|||||||
Reference in New Issue
Block a user