mirror of
https://github.com/wgh136/nysoure.git
synced 2025-12-16 15:51:14 +00:00
Compare commits
7 Commits
1544c535de
...
23269ad9d1
| Author | SHA1 | Date | |
|---|---|---|---|
| 23269ad9d1 | |||
| 57b0a10c4d | |||
| 26f5308d9a | |||
| 4f1600296c | |||
| 1a120d2378 | |||
| a0fb279b29 | |||
| 1d78207004 |
@@ -78,6 +78,7 @@ export default function Gallery({
|
|||||||
<GalleryFullscreen
|
<GalleryFullscreen
|
||||||
dialogRef={dialogRef}
|
dialogRef={dialogRef}
|
||||||
images={images}
|
images={images}
|
||||||
|
nsfw={nsfw}
|
||||||
currentIndex={currentIndex}
|
currentIndex={currentIndex}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
goToPrevious={goToPrevious}
|
goToPrevious={goToPrevious}
|
||||||
@@ -124,7 +125,7 @@ export default function Gallery({
|
|||||||
>
|
>
|
||||||
<GalleryImage
|
<GalleryImage
|
||||||
src={network.getImageUrl(images[currentIndex])}
|
src={network.getImageUrl(images[currentIndex])}
|
||||||
nfsw={nsfw.includes(currentIndex)}
|
nfsw={nsfw.includes(images[currentIndex])}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
@@ -188,6 +189,7 @@ export default function Gallery({
|
|||||||
function GalleryFullscreen({
|
function GalleryFullscreen({
|
||||||
dialogRef,
|
dialogRef,
|
||||||
images,
|
images,
|
||||||
|
nsfw,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
direction,
|
direction,
|
||||||
goToPrevious,
|
goToPrevious,
|
||||||
@@ -197,6 +199,7 @@ function GalleryFullscreen({
|
|||||||
}: {
|
}: {
|
||||||
dialogRef: React.RefObject<HTMLDialogElement | null>;
|
dialogRef: React.RefObject<HTMLDialogElement | null>;
|
||||||
images: number[];
|
images: number[];
|
||||||
|
nsfw: number[];
|
||||||
currentIndex: number;
|
currentIndex: number;
|
||||||
direction: number;
|
direction: number;
|
||||||
goToPrevious: () => void;
|
goToPrevious: () => void;
|
||||||
@@ -237,10 +240,12 @@ function GalleryFullscreen({
|
|||||||
|
|
||||||
if (dialogRef.current?.open) {
|
if (dialogRef.current?.open) {
|
||||||
window.addEventListener("mousemove", handleMouseMove);
|
window.addEventListener("mousemove", handleMouseMove);
|
||||||
|
window.addEventListener("touchstart", handleMouseMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("mousemove", handleMouseMove);
|
window.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
window.removeEventListener("touchstart", handleMouseMove);
|
||||||
if (hideTimeoutRef.current) {
|
if (hideTimeoutRef.current) {
|
||||||
clearTimeout(hideTimeoutRef.current);
|
clearTimeout(hideTimeoutRef.current);
|
||||||
}
|
}
|
||||||
@@ -316,7 +321,7 @@ function GalleryFullscreen({
|
|||||||
<img
|
<img
|
||||||
src={network.getImageUrl(images[currentIndex])}
|
src={network.getImageUrl(images[currentIndex])}
|
||||||
alt=""
|
alt=""
|
||||||
className="w-full h-full object-contain rounded-xl"
|
className="w-full h-full object-contain rounded-xl select-none"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
@@ -360,8 +365,8 @@ function GalleryFullscreen({
|
|||||||
key={index}
|
key={index}
|
||||||
className={`flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden transition-all ${
|
className={`flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden transition-all ${
|
||||||
index === currentIndex
|
index === currentIndex
|
||||||
? "ring-2 ring-primary scale-110"
|
? "ring-2 ring-primary scale-110 "
|
||||||
: "opacity-60 hover:opacity-100"
|
: `${nsfw.includes(imageId) ? "blur-sm hover:blur-none" : "opacity-60 hover:opacity-100"}`
|
||||||
}`}
|
}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -373,7 +378,7 @@ function GalleryFullscreen({
|
|||||||
<img
|
<img
|
||||||
src={network.getResampledImageUrl(imageId)}
|
src={network.getResampledImageUrl(imageId)}
|
||||||
alt={`Thumbnail ${index + 1}`}
|
alt={`Thumbnail ${index + 1}`}
|
||||||
className="w-full h-full object-cover"
|
className={`w-full h-full object-cover select-none`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -405,7 +410,7 @@ function GalleryImage({ src, nfsw }: { src: string; nfsw: boolean }) {
|
|||||||
<img
|
<img
|
||||||
src={src}
|
src={src}
|
||||||
alt=""
|
alt=""
|
||||||
className={`w-full h-full object-contain transition-all duration-300 ${!show ? "blur-xl" : ""}`}
|
className={`w-full h-full object-cover transition-all duration-300 ${!show ? "blur-xl" : ""}`}
|
||||||
/>
|
/>
|
||||||
{!show && (
|
{!show && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ export const i18nData = {
|
|||||||
"File Size": "文件大小",
|
"File Size": "文件大小",
|
||||||
"Tag": "标签",
|
"Tag": "标签",
|
||||||
"Optional": "可选",
|
"Optional": "可选",
|
||||||
|
"Download": "下载",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"zh-TW": {
|
"zh-TW": {
|
||||||
@@ -525,6 +526,7 @@ export const i18nData = {
|
|||||||
"File Size": "檔案大小",
|
"File Size": "檔案大小",
|
||||||
"Tag": "標籤",
|
"Tag": "標籤",
|
||||||
"Optional": "可選",
|
"Optional": "可選",
|
||||||
|
"Download": "下載",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ function PinnedResourcesCarousel({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{resources.length > 1 && (
|
{resources.length > 1 && (
|
||||||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2 z-10">
|
<div className="absolute bottom-2 left-1/2 transform -translate-x-1/2 flex gap-2 z-10">
|
||||||
{resources.map((_, index) => (
|
{resources.map((_, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
|
|||||||
@@ -465,141 +465,141 @@ function DeleteResourceDialog({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = createContext<() => void>(() => {});
|
const context = createContext<() => void>(() => { });
|
||||||
|
|
||||||
function Article({ resource }: { resource: ResourceDetails }) {
|
function Article({ resource }: { resource: ResourceDetails }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<article>
|
<article>
|
||||||
<Markdown
|
<Markdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
components={{
|
components={{
|
||||||
p: ({ node, ...props }) => {
|
p: ({ node, ...props }) => {
|
||||||
if (
|
if (
|
||||||
typeof props.children === "object" &&
|
typeof props.children === "object" &&
|
||||||
(props.children as ReactElement).type === "strong"
|
(props.children as ReactElement).type === "strong"
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
|
||||||
const child = (
|
|
||||||
props.children as ReactElement
|
|
||||||
).props.children.toString() as string;
|
|
||||||
if (child.startsWith("<iframe")) {
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let html = child;
|
const child = (
|
||||||
let splits = html.split(" ");
|
props.children as ReactElement
|
||||||
splits = splits.filter((s: string) => {
|
).props.children.toString() as string;
|
||||||
return !(
|
if (child.startsWith("<iframe")) {
|
||||||
s.startsWith("width") ||
|
|
||||||
s.startsWith("height") ||
|
|
||||||
s.startsWith("class") ||
|
|
||||||
s.startsWith("style")
|
|
||||||
);
|
|
||||||
});
|
|
||||||
html = splits.join(" ");
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`w-full my-3 max-w-xl rounded-xl overflow-clip ${html.includes("youtube") ? "aspect-video" : "h-48 sm:h-64"}`}
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: html,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
typeof props.children === "object" &&
|
|
||||||
// @ts-ignore
|
|
||||||
props.children?.props &&
|
|
||||||
// @ts-ignore
|
|
||||||
props.children?.props.href
|
|
||||||
) {
|
|
||||||
const a = props.children as ReactElement;
|
|
||||||
const childProps = a.props as any;
|
|
||||||
const href = childProps.href as string;
|
|
||||||
// @ts-ignore
|
|
||||||
if (childProps.children?.length === 2) {
|
|
||||||
// @ts-ignore
|
|
||||||
const first = childProps.children[0] as ReactNode;
|
|
||||||
// @ts-ignore
|
|
||||||
const second = childProps.children[1] as ReactNode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof first === "object" &&
|
|
||||||
(typeof second === "string" || typeof second === "object")
|
|
||||||
) {
|
|
||||||
const img = first as ReactElement;
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (img.type === "img") {
|
let html = child;
|
||||||
return (
|
let splits = html.split(" ");
|
||||||
<a
|
splits = splits.filter((s: string) => {
|
||||||
className={
|
return !(
|
||||||
"inline-block card shadow bg-base-100 no-underline hover:shadow-md transition-shadow my-2"
|
s.startsWith("width") ||
|
||||||
}
|
s.startsWith("height") ||
|
||||||
target={"_blank"}
|
s.startsWith("class") ||
|
||||||
href={href}
|
s.startsWith("style")
|
||||||
>
|
);
|
||||||
<figure className={"max-h-96 min-w-48 min-h-24"}>
|
});
|
||||||
{img}
|
html = splits.join(" ");
|
||||||
</figure>
|
return (
|
||||||
<div className={"card-body text-base-content text-lg"}>
|
<div
|
||||||
<div className={"flex items-center"}>
|
className={`w-full my-3 max-w-xl rounded-xl overflow-clip ${html.includes("youtube") ? "aspect-video" : "h-48 sm:h-64"}`}
|
||||||
<span className={"flex-1"}>{second}</span>
|
dangerouslySetInnerHTML={{
|
||||||
<span>
|
__html: html,
|
||||||
<MdOutlineOpenInNew size={24} />
|
}}
|
||||||
</span>
|
></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
typeof props.children === "object" &&
|
||||||
|
// @ts-ignore
|
||||||
|
props.children?.props &&
|
||||||
|
// @ts-ignore
|
||||||
|
props.children?.props.href
|
||||||
|
) {
|
||||||
|
const a = props.children as ReactElement;
|
||||||
|
const childProps = a.props as any;
|
||||||
|
const href = childProps.href as string;
|
||||||
|
// @ts-ignore
|
||||||
|
if (childProps.children?.length === 2) {
|
||||||
|
// @ts-ignore
|
||||||
|
const first = childProps.children[0] as ReactNode;
|
||||||
|
// @ts-ignore
|
||||||
|
const second = childProps.children[1] as ReactNode;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof first === "object" &&
|
||||||
|
(typeof second === "string" || typeof second === "object")
|
||||||
|
) {
|
||||||
|
const img = first as ReactElement;
|
||||||
|
// @ts-ignore
|
||||||
|
if (img.type === "img") {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className={
|
||||||
|
"inline-block card shadow bg-base-100 no-underline hover:shadow-md transition-shadow my-2"
|
||||||
|
}
|
||||||
|
target={"_blank"}
|
||||||
|
href={href}
|
||||||
|
>
|
||||||
|
<figure className={"max-h-96 min-w-48 min-h-24"}>
|
||||||
|
{img}
|
||||||
|
</figure>
|
||||||
|
<div className={"card-body text-base-content text-lg"}>
|
||||||
|
<div className={"flex items-center"}>
|
||||||
|
<span className={"flex-1"}>{second}</span>
|
||||||
|
<span>
|
||||||
|
<MdOutlineOpenInNew size={24} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (href.startsWith("https://store.steampowered.com/app/")) {
|
||||||
|
const appId = href
|
||||||
|
.substring("https://store.steampowered.com/app/".length)
|
||||||
|
.split("/")[0];
|
||||||
|
if (!Number.isNaN(Number(appId))) {
|
||||||
|
return (
|
||||||
|
<div className={"max-w-xl h-52 sm:h-48 my-2"}>
|
||||||
|
<iframe
|
||||||
|
className={"scheme-light"}
|
||||||
|
src={`https://store.steampowered.com/widget/${appId}/`}
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (href.startsWith("https://store.steampowered.com/app/")) {
|
return <p {...props}>{props.children}</p>;
|
||||||
const appId = href
|
},
|
||||||
.substring("https://store.steampowered.com/app/".length)
|
a: ({ node, ...props }) => {
|
||||||
.split("/")[0];
|
const href = props.href as string;
|
||||||
if (!Number.isNaN(Number(appId))) {
|
const origin = window.location.origin;
|
||||||
return (
|
|
||||||
<div className={"max-w-xl h-52 sm:h-48 my-2"}>
|
|
||||||
<iframe
|
|
||||||
className={"scheme-light"}
|
|
||||||
src={`https://store.steampowered.com/widget/${appId}/`}
|
|
||||||
></iframe>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <p {...props}>{props.children}</p>;
|
|
||||||
},
|
|
||||||
a: ({ node, ...props }) => {
|
|
||||||
const href = props.href as string;
|
|
||||||
const origin = window.location.origin;
|
|
||||||
|
|
||||||
if (href.startsWith(origin) || href.startsWith("/")) {
|
if (href.startsWith(origin) || href.startsWith("/")) {
|
||||||
let path = href;
|
let path = href;
|
||||||
if (path.startsWith(origin)) {
|
if (path.startsWith(origin)) {
|
||||||
path = path.substring(origin.length);
|
path = path.substring(origin.length);
|
||||||
}
|
}
|
||||||
const content = props.children?.toString();
|
const content = props.children?.toString();
|
||||||
if (path.startsWith("/resources/")) {
|
if (path.startsWith("/resources/")) {
|
||||||
const id = path.substring("/resources/".length);
|
const id = path.substring("/resources/".length);
|
||||||
for (const r of resource.related ?? []) {
|
for (const r of resource.related ?? []) {
|
||||||
if (r.id.toString() === id) {
|
if (r.id.toString() === id) {
|
||||||
return <RelatedResourceCard r={r} content={content} />;
|
return <RelatedResourceCard r={r} content={content} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return <a target={"_blank"} {...props}></a>;
|
return <a target={"_blank"} {...props}></a>;
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{resource.article.replaceAll("\n", " \n")}
|
{resource.article.replaceAll("\n", " \n")}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</article>
|
</article>
|
||||||
<div className="border-b border-base-300 h-8"></div>
|
<div className="border-b border-base-300 h-8"></div>
|
||||||
<Characters characters={resource.characters} />
|
<Characters characters={resource.characters} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -898,27 +898,18 @@ function CloudflarePopup({ file }: { file: RFile }) {
|
|||||||
<h3 className={"font-bold m-2"}>
|
<h3 className={"font-bold m-2"}>
|
||||||
{downloadToken ? t("Verification successful") : t("Verifying your request")}
|
{downloadToken ? t("Verification successful") : t("Verifying your request")}
|
||||||
</h3>
|
</h3>
|
||||||
{!downloadToken && (
|
<div className={"h-20 w-full"}>
|
||||||
<>
|
<Turnstile
|
||||||
<div className={"h-20 w-full"}>
|
siteKey={app.cloudflareTurnstileSiteKey!}
|
||||||
<Turnstile
|
onWidgetLoad={() => {
|
||||||
siteKey={app.cloudflareTurnstileSiteKey!}
|
setLoading(false);
|
||||||
onWidgetLoad={() => {
|
}}
|
||||||
setLoading(false);
|
onSuccess={(token) => {
|
||||||
}}
|
setDownloadToken(token);
|
||||||
onSuccess={(token) => {
|
}}
|
||||||
setDownloadToken(token);
|
></Turnstile>
|
||||||
}}
|
</div>
|
||||||
></Turnstile>
|
{downloadToken ? (
|
||||||
</div>
|
|
||||||
<p className={"text-xs text-base-content/80 m-2"}>
|
|
||||||
{t(
|
|
||||||
"Please check your network if the verification takes too long or the captcha does not appear.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{downloadToken && (
|
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<a
|
<a
|
||||||
href={network.getFileDownloadLink(file.id, downloadToken)}
|
href={network.getFileDownloadLink(file.id, downloadToken)}
|
||||||
@@ -932,7 +923,11 @@ function CloudflarePopup({ file }: { file: RFile }) {
|
|||||||
{t("Download")}
|
{t("Download")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : <p className={"text-xs text-base-content/80 m-2"}>
|
||||||
|
{t(
|
||||||
|
"Please check your network if the verification takes too long or the captcha does not appear.",
|
||||||
|
)}
|
||||||
|
</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2109,7 +2104,7 @@ function CharacterCard({ character }: { character: CharacterParams }) {
|
|||||||
alt={character.name}
|
alt={character.name}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="absolute bottom-1 left-1 right-1 px-1 py-1 border border-base-100/40 rounded-lg bg-base-100/60">
|
<div className="absolute bottom-1 left-1 right-1 px-1 py-1 border border-base-100/40 rounded-lg bg-base-100/60">
|
||||||
<h4 className="font-semibold text-sm leading-tight line-clamp border border-transparent">
|
<h4 className="font-semibold text-sm leading-tight line-clamp border border-transparent">
|
||||||
{character.name}
|
{character.name}
|
||||||
@@ -2123,7 +2118,7 @@ function CharacterCard({ character }: { character: CharacterParams }) {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{character.cv && (
|
{character.cv && (
|
||||||
<button
|
<button
|
||||||
onClick={handleCVClick}
|
onClick={handleCVClick}
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -19,6 +19,8 @@ func main() {
|
|||||||
Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
|
Format: "[${ip}]:${port} ${status} - ${method} ${path}\n",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
app.Use(middleware.UnsupportedRegionMiddleware)
|
||||||
|
|
||||||
app.Use(middleware.ErrorHandler)
|
app.Use(middleware.ErrorHandler)
|
||||||
|
|
||||||
app.Use(middleware.RealUserMiddleware)
|
app.Use(middleware.RealUserMiddleware)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func FrontendMiddleware(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(file); path == "/" || os.IsNotExist(err) {
|
if _, err := os.Stat(file); path == "/" || os.IsNotExist(err) {
|
||||||
|
c.Set("Cache-Control", "no-cache")
|
||||||
return serveIndexHtml(c)
|
return serveIndexHtml(c)
|
||||||
} else {
|
} else {
|
||||||
c.Set("Cache-Control", "public, max-age=31536000, immutable")
|
c.Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||||
|
|||||||
29
server/middleware/unsupported_region_middleware.go
Normal file
29
server/middleware/unsupported_region_middleware.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnsupportedRegionMiddleware(c fiber.Ctx) error {
|
||||||
|
path := string(c.Request().URI().Path())
|
||||||
|
|
||||||
|
// Skip static file requests
|
||||||
|
if strings.Contains(path, ".") {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip API requests
|
||||||
|
if strings.HasPrefix(path, "/api") {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(c.Request().Header.Peek("Unsupported-Region")) == "true" {
|
||||||
|
// Return a 403 Forbidden response with an empty html for unsupported regions
|
||||||
|
c.Response().Header.Add("Content-Type", "text/html")
|
||||||
|
c.Status(fiber.StatusForbidden)
|
||||||
|
return c.SendString("<html></html>")
|
||||||
|
}
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user