mirror of
https://github.com/wgh136/nysoure.git
synced 2025-12-16 07:51:14 +00:00
nsfw images
This commit is contained in:
@@ -48,6 +48,7 @@ export interface CreateResourceParams {
|
|||||||
article: string;
|
article: string;
|
||||||
images: number[];
|
images: number[];
|
||||||
gallery: number[];
|
gallery: number[];
|
||||||
|
gallery_nsfw: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Image {
|
export interface Image {
|
||||||
@@ -86,6 +87,7 @@ export interface ResourceDetails {
|
|||||||
comments: number;
|
comments: number;
|
||||||
related: Resource[];
|
related: Resource[];
|
||||||
gallery: number[];
|
gallery: number[];
|
||||||
|
galleryNsfw: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Storage {
|
export interface Storage {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default function EditResourcePage() {
|
|||||||
const [images, setImages] = useState<number[]>([]);
|
const [images, setImages] = useState<number[]>([]);
|
||||||
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 [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [isSubmitting, setSubmitting] = useState(false);
|
const [isSubmitting, setSubmitting] = useState(false);
|
||||||
const [isLoading, setLoading] = useState(true);
|
const [isLoading, setLoading] = useState(true);
|
||||||
@@ -57,6 +58,7 @@ export default function EditResourcePage() {
|
|||||||
setImages(data.images.map((i) => i.id));
|
setImages(data.images.map((i) => i.id));
|
||||||
setLinks(data.links ?? []);
|
setLinks(data.links ?? []);
|
||||||
setGalleryImages(data.gallery ?? []);
|
setGalleryImages(data.gallery ?? []);
|
||||||
|
setGalleryNsfw(data.galleryNsfw ?? []);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
showToast({ message: t("Failed to load resource"), type: "error" });
|
showToast({ message: t("Failed to load resource"), type: "error" });
|
||||||
@@ -101,6 +103,7 @@ export default function EditResourcePage() {
|
|||||||
images: images,
|
images: images,
|
||||||
links: links,
|
links: links,
|
||||||
gallery: galleryImages,
|
gallery: galleryImages,
|
||||||
|
gallery_nsfw: galleryNsfw,
|
||||||
});
|
});
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
@@ -322,6 +325,7 @@ export default function EditResourcePage() {
|
|||||||
<td>{t("Preview")}</td>
|
<td>{t("Preview")}</td>
|
||||||
<td>{"Markdown"}</td>
|
<td>{"Markdown"}</td>
|
||||||
<td>{t("Gallery")}</td>
|
<td>{t("Gallery")}</td>
|
||||||
|
<td>{"Nsfw"}</td>
|
||||||
<td>{t("Action")}</td>
|
<td>{t("Action")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -365,6 +369,22 @@ export default function EditResourcePage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="checkbox checkbox-accent"
|
||||||
|
checked={galleryNsfw.includes(image)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setGalleryNsfw((prev) => [...prev, image]);
|
||||||
|
} else {
|
||||||
|
setGalleryNsfw((prev) =>
|
||||||
|
prev.filter((id) => id !== image),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
className={"btn btn-square"}
|
className={"btn btn-square"}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default function PublishPage() {
|
|||||||
const [images, setImages] = useState<number[]>([]);
|
const [images, setImages] = useState<number[]>([]);
|
||||||
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 [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [isSubmitting, setSubmitting] = useState(false);
|
const [isSubmitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
@@ -108,6 +109,7 @@ export default function PublishPage() {
|
|||||||
images: images,
|
images: images,
|
||||||
links: links,
|
links: links,
|
||||||
gallery: galleryImages,
|
gallery: galleryImages,
|
||||||
|
gallery_nsfw: galleryNsfw,
|
||||||
});
|
});
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
localStorage.removeItem("publish_data");
|
localStorage.removeItem("publish_data");
|
||||||
@@ -332,6 +334,7 @@ export default function PublishPage() {
|
|||||||
<td>{t("Preview")}</td>
|
<td>{t("Preview")}</td>
|
||||||
<td>{"Markdown"}</td>
|
<td>{"Markdown"}</td>
|
||||||
<td>{"Gallery"}</td>
|
<td>{"Gallery"}</td>
|
||||||
|
<td>{"Nsfw"}</td>
|
||||||
<td>{t("Action")}</td>
|
<td>{t("Action")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -375,6 +378,22 @@ export default function PublishPage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="checkbox checkbox-accent"
|
||||||
|
checked={galleryNsfw.includes(image)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setGalleryNsfw((prev) => [...prev, image]);
|
||||||
|
} else {
|
||||||
|
setGalleryNsfw((prev) =>
|
||||||
|
prev.filter((id) => id !== image),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
className={"btn btn-square"}
|
className={"btn btn-square"}
|
||||||
|
|||||||
@@ -255,12 +255,12 @@ export default function ResourcePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-96 md:w-md lg:w-lg xl:w-xl p-4 hidden sm:flex items-center justify-center">
|
<div className="w-96 md:w-md lg:w-lg xl:w-xl p-4 hidden sm:flex items-center justify-center">
|
||||||
<Gallery images={resource.gallery} />
|
<Gallery images={resource.gallery} nsfw={resource.galleryNsfw} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full p-4 flex sm:hidden items-center justify-center">
|
<div className="w-full p-4 flex sm:hidden items-center justify-center">
|
||||||
<Gallery images={resource.gallery} />
|
<Gallery images={resource.gallery} nsfw={resource.galleryNsfw} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -357,18 +357,20 @@ function Tags({ tags }: { tags: Tag[] }) {
|
|||||||
tagsMap.get(type)?.push(tag);
|
tagsMap.get(type)?.push(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const compactMode = tags.length > 10;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Array.from(tagsMap.entries()).map(([type, tags]) => (
|
{Array.from(tagsMap.entries()).map(([type, tags]) => (
|
||||||
<p key={type} className={"px-4"}>
|
<p key={type} className={"px-4"}>
|
||||||
<Badge className="shadow-xs" key={type}>
|
<Badge className="shadow-xs mr-0.5" key={type}>
|
||||||
{type == "" ? t("Other") : type}
|
{type == "" ? t("Other") : type}
|
||||||
</Badge>
|
</Badge>
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={tag.name}
|
key={tag.name}
|
||||||
className={
|
className={
|
||||||
"m-1 cursor-pointer badge-soft badge-primary shadow-xs"
|
`${compactMode ? "m-0.5" : "m-1"} cursor-pointer badge-soft badge-primary shadow-xs`
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/tag/${encodeURIComponent(tag.name)}`);
|
navigate(`/tag/${encodeURIComponent(tag.name)}`);
|
||||||
@@ -1879,7 +1881,7 @@ function CollectionSelector({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Gallery({ images }: { images: number[] }) {
|
function Gallery({ images, nsfw }: { images: number[], nsfw: number[] }) {
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const [direction, setDirection] = useState(0); // 方向:1=向右,-1=向左
|
const [direction, setDirection] = useState(0); // 方向:1=向右,-1=向左
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
@@ -1919,6 +1921,10 @@ function Gallery({ images }: { images: number[] }) {
|
|||||||
setCurrentIndex(index);
|
setCurrentIndex(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (nsfw == null) {
|
||||||
|
nsfw = [];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative w-full overflow-hidden rounded-xl bg-base-100-tr82 shadow-sm"
|
className="relative w-full overflow-hidden rounded-xl bg-base-100-tr82 shadow-sm"
|
||||||
@@ -1930,10 +1936,8 @@ function Gallery({ images }: { images: number[] }) {
|
|||||||
<div ref={containerRef} className="w-full h-full relative">
|
<div ref={containerRef} className="w-full h-full relative">
|
||||||
{width > 0 && (
|
{width > 0 && (
|
||||||
<AnimatePresence initial={false} custom={direction} mode="sync">
|
<AnimatePresence initial={false} custom={direction} mode="sync">
|
||||||
<motion.img
|
<motion.div
|
||||||
key={currentIndex}
|
key={currentIndex}
|
||||||
src={network.getImageUrl(images[currentIndex])}
|
|
||||||
alt={`Gallery image ${currentIndex + 1}`}
|
|
||||||
className="absolute w-full h-full object-contain"
|
className="absolute w-full h-full object-contain"
|
||||||
variants={{
|
variants={{
|
||||||
enter: (dir: number) => ({
|
enter: (dir: number) => ({
|
||||||
@@ -1952,7 +1956,12 @@ function Gallery({ images }: { images: number[] }) {
|
|||||||
animate="center"
|
animate="center"
|
||||||
exit="exit"
|
exit="exit"
|
||||||
custom={direction}
|
custom={direction}
|
||||||
/>
|
>
|
||||||
|
<GalleryImage
|
||||||
|
src={network.getImageUrl(images[currentIndex])}
|
||||||
|
nfsw={nsfw.includes(images[currentIndex])}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1999,3 +2008,29 @@ function Gallery({ images }: { images: number[] }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function GalleryImage({src, nfsw}: {src: string, nfsw: boolean}) {
|
||||||
|
const [show, setShow] = useState(!nfsw);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt=""
|
||||||
|
className={`w-full h-full object-contain transition-all duration-300 ${!show ? 'blur-xl' : ''}`}
|
||||||
|
/>
|
||||||
|
{!show && (
|
||||||
|
<>
|
||||||
|
<div className="absolute inset-0 bg-base-content/20 cursor-pointer" onClick={() => {
|
||||||
|
setShow(true)
|
||||||
|
}} />
|
||||||
|
<div className="absolute top-4 left-4">
|
||||||
|
<Badge className="badge-error shadow-lg">
|
||||||
|
NSFW
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ type Resource struct {
|
|||||||
Comments uint
|
Comments uint
|
||||||
ModifiedTime time.Time
|
ModifiedTime time.Time
|
||||||
Gallery []uint `gorm:"serializer:json"`
|
Gallery []uint `gorm:"serializer:json"`
|
||||||
|
GalleryNsfw []uint `gorm:"serializer:json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
@@ -54,6 +55,7 @@ type ResourceDetailView struct {
|
|||||||
Comments uint `json:"comments"`
|
Comments uint `json:"comments"`
|
||||||
Related []ResourceView `json:"related"`
|
Related []ResourceView `json:"related"`
|
||||||
Gallery []uint `json:"gallery"`
|
Gallery []uint `json:"gallery"`
|
||||||
|
GalleryNsfw []uint `json:"galleryNsfw"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) ToView() ResourceView {
|
func (r *Resource) ToView() ResourceView {
|
||||||
@@ -107,5 +109,6 @@ func (r *Resource) ToDetailView() ResourceDetailView {
|
|||||||
Downloads: r.Downloads,
|
Downloads: r.Downloads,
|
||||||
Comments: r.Comments,
|
Comments: r.Comments,
|
||||||
Gallery: r.Gallery,
|
Gallery: r.Gallery,
|
||||||
|
GalleryNsfw: r.GalleryNsfw,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type ResourceParams struct {
|
|||||||
Article string `json:"article"`
|
Article string `json:"article"`
|
||||||
Images []uint `json:"images"`
|
Images []uint `json:"images"`
|
||||||
Gallery []uint `json:"gallery"`
|
Gallery []uint `json:"gallery"`
|
||||||
|
GalleryNsfw []uint `json:"gallery_nsfw"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateResource(uid uint, params *ResourceParams) (uint, error) {
|
func CreateResource(uid uint, params *ResourceParams) (uint, error) {
|
||||||
@@ -64,6 +65,7 @@ func CreateResource(uid uint, params *ResourceParams) (uint, error) {
|
|||||||
Tags: tags,
|
Tags: tags,
|
||||||
UserID: uid,
|
UserID: uid,
|
||||||
Gallery: params.Gallery,
|
Gallery: params.Gallery,
|
||||||
|
GalleryNsfw: params.GalleryNsfw,
|
||||||
}
|
}
|
||||||
if r, err = dao.CreateResource(r); err != nil {
|
if r, err = dao.CreateResource(r); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -455,6 +457,7 @@ func EditResource(uid, rid uint, params *ResourceParams) error {
|
|||||||
r.Article = params.Article
|
r.Article = params.Article
|
||||||
r.Links = params.Links
|
r.Links = params.Links
|
||||||
r.Gallery = params.Gallery
|
r.Gallery = params.Gallery
|
||||||
|
r.GalleryNsfw = params.GalleryNsfw
|
||||||
|
|
||||||
images := make([]model.Image, len(params.Images))
|
images := make([]model.Image, len(params.Images))
|
||||||
for i, id := range params.Images {
|
for i, id := range params.Images {
|
||||||
|
|||||||
Reference in New Issue
Block a user