Better TOC and Lightbox #13
|
@ -16,6 +16,7 @@
|
|||
"next": "^12.1.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-swipeable": "^6.2.0",
|
||||
"turndown": "^7.1.1"
|
||||
},
|
||||
|
@ -1707,6 +1708,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exenv": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
|
||||
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
|
@ -2982,7 +2988,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
|
@ -3055,11 +3060,46 @@
|
|||
"react": "17.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-image-lightbox": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-image-lightbox/-/react-image-lightbox-5.1.4.tgz",
|
||||
"integrity": "sha512-kTiAODz091bgT7SlWNHab0LSMZAPJtlNWDGKv7pLlLY1krmf7FuG1zxE0wyPpeA8gPdwfr3cu6sPwZRqWsc3Eg==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.7.2",
|
||||
"react-modal": "^3.11.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "16.x || 17.x",
|
||||
"react-dom": "16.x || 17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"node_modules/react-modal": {
|
||||
"version": "3.14.4",
|
||||
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.14.4.tgz",
|
||||
"integrity": "sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg==",
|
||||
"dependencies": {
|
||||
"exenv": "^1.2.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-lifecycles-compat": "^3.0.0",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16 || ^17",
|
||||
"react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17"
|
||||
}
|
||||
},
|
||||
"node_modules/react-swipeable": {
|
||||
"version": "6.2.0",
|
||||
|
@ -3577,6 +3617,14 @@
|
|||
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -4858,6 +4906,11 @@
|
|||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true
|
||||
},
|
||||
"exenv": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
|
||||
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
|
@ -5761,7 +5814,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
|
@ -5805,11 +5857,35 @@
|
|||
"scheduler": "^0.20.2"
|
||||
}
|
||||
},
|
||||
"react-image-lightbox": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-image-lightbox/-/react-image-lightbox-5.1.4.tgz",
|
||||
"integrity": "sha512-kTiAODz091bgT7SlWNHab0LSMZAPJtlNWDGKv7pLlLY1krmf7FuG1zxE0wyPpeA8gPdwfr3cu6sPwZRqWsc3Eg==",
|
||||
"requires": {
|
||||
"prop-types": "^15.7.2",
|
||||
"react-modal": "^3.11.1"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-modal": {
|
||||
"version": "3.14.4",
|
||||
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.14.4.tgz",
|
||||
"integrity": "sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg==",
|
||||
"requires": {
|
||||
"exenv": "^1.2.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-lifecycles-compat": "^3.0.0",
|
||||
"warning": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"react-swipeable": {
|
||||
"version": "6.2.0",
|
||||
|
@ -6171,6 +6247,14 @@
|
|||
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
|
||||
"dev": true
|
||||
},
|
||||
"warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"next": "^12.1.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-swipeable": "^6.2.0",
|
||||
"turndown": "^7.1.1"
|
||||
},
|
||||
|
|
|
@ -111,6 +111,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||
|
||||
return (
|
||||
<div
|
||||
id="MyAppLayout"
|
||||
className={`${
|
||||
appLayout.darkMode ? "set-theme-dark" : "set-theme-light"
|
||||
} ${
|
||||
|
|
|
@ -8,7 +8,7 @@ type ChipProps = {
|
|||
export default function Chip(props: ChipProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className={`grid relative place-content-center place-items-center text-xs pb-[0.14rem] px-1.5 border-[1px] rounded-full opacity-70 transition-[color,_opacity,_border-color] hover:opacity-100 ${props.className}`}
|
||||
className={`grid place-content-center place-items-center text-xs pb-[0.14rem] whitespace-nowrap px-1.5 border-[1px] rounded-full opacity-70 transition-[color,_opacity,_border-color] hover:opacity-100 ${props.className}`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
|
|
|
@ -3,95 +3,88 @@ import {
|
|||
GetWebsiteInterfaceQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { prettyinlineTitle, prettySlug, slugify } from "queries/helpers";
|
||||
import Button from "components/Button";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import Chip from "components/Chip";
|
||||
|
||||
export type ThumbnailHeaderProps = {
|
||||
content: {
|
||||
slug: GetContentQuery["contents"]["data"][number]["attributes"]["slug"];
|
||||
thumbnail: GetContentQuery["contents"]["data"][number]["attributes"]["thumbnail"];
|
||||
titles: GetContentQuery["contents"]["data"][number]["attributes"]["titles"];
|
||||
type: GetContentQuery["contents"]["data"][number]["attributes"]["type"];
|
||||
categories: GetContentQuery["contents"]["data"][number]["attributes"]["categories"];
|
||||
};
|
||||
pre_title?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
type?: GetContentQuery["contents"]["data"][number]["attributes"]["type"];
|
||||
categories?: GetContentQuery["contents"]["data"][number]["attributes"]["categories"];
|
||||
thumbnail?: GetContentQuery["contents"]["data"][number]["attributes"]["thumbnail"];
|
||||
langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"];
|
||||
};
|
||||
|
||||
export default function ThumbnailHeader(
|
||||
props: ThumbnailHeaderProps
|
||||
): JSX.Element {
|
||||
const content = props.content;
|
||||
const langui = props.langui;
|
||||
const {
|
||||
langui,
|
||||
pre_title,
|
||||
title,
|
||||
subtitle,
|
||||
thumbnail,
|
||||
type,
|
||||
categories,
|
||||
description,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid place-items-center gap-12 mb-12">
|
||||
<div className="drop-shadow-shade-lg">
|
||||
{content.thumbnail.data ? (
|
||||
{thumbnail && thumbnail.data ? (
|
||||
<Img
|
||||
className=" rounded-xl"
|
||||
image={content.thumbnail.data.attributes}
|
||||
image={thumbnail.data.attributes}
|
||||
quality={ImageQuality.Medium}
|
||||
priority
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full aspect-[4/3] bg-light rounded-xl"></div>
|
||||
<div className="w-96 aspect-[4/3] bg-light rounded-xl"></div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
id={slugify(
|
||||
content.titles.length > 0
|
||||
? prettyinlineTitle(
|
||||
content.titles[0].pre_title,
|
||||
content.titles[0].title,
|
||||
content.titles[0].subtitle
|
||||
)
|
||||
: prettySlug(content.slug)
|
||||
prettyinlineTitle(pre_title || "", title, subtitle || "")
|
||||
)}
|
||||
className="grid place-items-center text-center"
|
||||
>
|
||||
{content.titles.length > 0 ? (
|
||||
<>
|
||||
<p className="text-2xl">{content.titles[0].pre_title}</p>
|
||||
<h1 className="text-3xl">{content.titles[0].title}</h1>
|
||||
<h2 className="text-2xl">{content.titles[0].subtitle}</h2>
|
||||
</>
|
||||
) : (
|
||||
<h1 className="text-3xl">{prettySlug(content.slug)}</h1>
|
||||
)}
|
||||
<p className="text-2xl">{pre_title}</p>
|
||||
<h1 className="text-3xl">{title}</h1>
|
||||
<h2 className="text-2xl">{subtitle}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-flow-col gap-8">
|
||||
{content.type && (
|
||||
{type && type.data && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.type}</h3>
|
||||
<div className="flex flex-row flex-wrap">
|
||||
<Chip>
|
||||
{content.type.data.attributes.titles.length > 0
|
||||
? content.type.data.attributes.titles[0].title
|
||||
: prettySlug(content.type.data.attributes.slug)}
|
||||
{type.data.attributes.titles.length > 0
|
||||
? type.data.attributes.titles[0].title
|
||||
: prettySlug(type.data.attributes.slug)}
|
||||
</Chip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{content.categories.data.length > 0 && (
|
||||
{categories && categories.data.length > 0 && (
|
||||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{content.categories.data.map((category) => (
|
||||
{categories.data.map((category) => (
|
||||
<Chip key={category.id}>{category.attributes.name}</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{content.titles.length > 0 && content.titles[0].description && (
|
||||
<InsetBox className="mt-8">{content.titles[0].description}</InsetBox>
|
||||
)}
|
||||
{description && <InsetBox className="mt-8">{description}</InsetBox>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export function getImgSizesByQuality(
|
|||
|
||||
type ImgProps = {
|
||||
className?: string;
|
||||
image: StrapiImage;
|
||||
image?: StrapiImage;
|
||||
quality?: ImageQuality;
|
||||
alt?: ImageProps["alt"];
|
||||
layout?: ImageProps["layout"];
|
||||
|
@ -60,42 +60,46 @@ type ImgProps = {
|
|||
};
|
||||
|
||||
export default function Img(props: ImgProps): JSX.Element {
|
||||
const imgSize = getImgSizesByQuality(
|
||||
props.image.width,
|
||||
props.image.height,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
);
|
||||
if (props.image) {
|
||||
const imgSize = getImgSizesByQuality(
|
||||
props.image.width,
|
||||
props.image.height,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
);
|
||||
|
||||
if (props.rawImg) {
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
className={props.className}
|
||||
src={getAssetURL(
|
||||
props.image.url,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
)}
|
||||
alt={props.alt ? props.alt : props.image.alternativeText}
|
||||
width={imgSize.width}
|
||||
height={imgSize.height}
|
||||
/>
|
||||
);
|
||||
if (props.rawImg) {
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
className={props.className}
|
||||
src={getAssetURL(
|
||||
props.image.url,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
)}
|
||||
alt={props.alt ? props.alt : props.image.alternativeText}
|
||||
width={imgSize.width}
|
||||
height={imgSize.height}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Image
|
||||
className={props.className}
|
||||
src={getAssetURL(
|
||||
props.image.url,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
)}
|
||||
alt={props.alt ? props.alt : props.image.alternativeText}
|
||||
width={props.layout === "fill" ? undefined : imgSize.width}
|
||||
height={props.layout === "fill" ? undefined : imgSize.height}
|
||||
layout={props.layout}
|
||||
objectFit={props.objectFit}
|
||||
priority={props.priority}
|
||||
unoptimized
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<Image
|
||||
className={props.className}
|
||||
src={getAssetURL(
|
||||
props.image.url,
|
||||
props.quality ? props.quality : ImageQuality.Small
|
||||
)}
|
||||
alt={props.alt ? props.alt : props.image.alternativeText}
|
||||
width={props.layout === "fill" ? undefined : imgSize.width}
|
||||
height={props.layout === "fill" ? undefined : imgSize.height}
|
||||
layout={props.layout}
|
||||
objectFit={props.objectFit}
|
||||
priority={props.priority}
|
||||
unoptimized
|
||||
/>
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import Lightbox from "react-image-lightbox";
|
||||
|
||||
export type LightBoxProps = {
|
||||
setState:
|
||||
| Dispatch<SetStateAction<boolean>>
|
||||
| Dispatch<SetStateAction<boolean | undefined>>;
|
||||
state: boolean;
|
||||
images: string[];
|
||||
index: number;
|
||||
setIndex: Dispatch<SetStateAction<number>>;
|
||||
};
|
||||
|
||||
export default function LightBox(props: LightBoxProps): JSX.Element {
|
||||
const { state, setState, images, index, setIndex } = props;
|
||||
const mobile = useMediaMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
{state && (
|
||||
<Lightbox
|
||||
reactModalProps={{
|
||||
parentSelector: () => document.getElementById("MyAppLayout"),
|
||||
}}
|
||||
mainSrc={images[index]}
|
||||
prevSrc={index > 0 ? images[index - 1] : undefined}
|
||||
nextSrc={index < images.length ? images[index + 1] : undefined}
|
||||
onMovePrevRequest={() => setIndex(index - 1)}
|
||||
onMoveNextRequest={() => setIndex(index + 1)}
|
||||
imageCaption=""
|
||||
imageTitle=""
|
||||
onCloseRequest={() => setState(false)}
|
||||
imagePadding={mobile ? 0 : 70}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import HorizontalLine from "components/HorizontalLine";
|
||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import LightBox from "components/LightBox";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import { slugify } from "queries/helpers";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
type ScenBreakProps = {
|
||||
className?: string;
|
||||
|
@ -15,177 +17,353 @@ export default function Markdawn(props: ScenBreakProps): JSX.Element {
|
|||
const appLayout = useAppLayout();
|
||||
const text = preprocessMarkDawn(props.text);
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
||||
if (text) {
|
||||
return (
|
||||
<Markdown
|
||||
className={`formatted ${props.className}`}
|
||||
options={{
|
||||
slugify: slugify,
|
||||
overrides: {
|
||||
h2: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h2 id={props.id} style={props.style}>
|
||||
<>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
<Markdown
|
||||
className={`formatted ${props.className}`}
|
||||
options={{
|
||||
slugify: slugify,
|
||||
overrides: {
|
||||
h1: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h1 id={props.id} style={props.style}>
|
||||
{props.children}
|
||||
</h1>
|
||||
<HeaderToolTip id={props.id} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
h2: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h2 id={props.id} style={props.style}>
|
||||
{props.children}
|
||||
</h2>
|
||||
<HeaderToolTip id={props.id} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
h3: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h3 id={props.id} style={props.style}>
|
||||
{props.children}
|
||||
</h3>
|
||||
<HeaderToolTip id={props.id} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
h4: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h4 id={props.id} style={props.style}>
|
||||
{props.children}
|
||||
</h4>
|
||||
<HeaderToolTip id={props.id} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
h5: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h5 id={props.id} style={props.style}>
|
||||
{props.children}
|
||||
</h5>
|
||||
<HeaderToolTip id={props.id} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
h6: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h6 id={props.id} style={props.style}>
|
||||
{props.children}
|
||||
</h6>
|
||||
<HeaderToolTip id={props.id} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
Sep: {
|
||||
component: () => {
|
||||
return <div className="my-24"></div>;
|
||||
},
|
||||
},
|
||||
SceneBreak: {
|
||||
component: (props: { id: string }) => {
|
||||
return (
|
||||
<div
|
||||
id={props.id}
|
||||
className={
|
||||
"h-0 text-center text-3xl text-dark mt-16 mb-20"
|
||||
}
|
||||
>
|
||||
* * *
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
player: {
|
||||
component: () => {
|
||||
return (
|
||||
<span className="text-dark opacity-70">
|
||||
{appLayout.playerName ? appLayout.playerName : "<player>"}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
Transcript: {
|
||||
component: (props) => {
|
||||
return (
|
||||
<div className="grid grid-cols-[auto_1fr] mobile:grid-cols-1 gap-x-6 gap-y-2">
|
||||
{props.children}
|
||||
</h2>
|
||||
<ToolTip content={"Copy anchor link"} trigger="mouseenter">
|
||||
<ToolTip content={"Copied! 👍"} trigger="click">
|
||||
<span
|
||||
className="material-icons transition-color hover:text-dark cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
process.env.NEXT_PUBLIC_URL_SELF +
|
||||
window.location.pathname +
|
||||
"#" +
|
||||
props.id
|
||||
);
|
||||
}}
|
||||
>
|
||||
link
|
||||
</span>
|
||||
</ToolTip>
|
||||
</ToolTip>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
h3: {
|
||||
component: (props: {
|
||||
id: string;
|
||||
style: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center place-content-center gap-3">
|
||||
<h3 id={props.id} style={props.style}>
|
||||
Line: {
|
||||
component: (props) => {
|
||||
return (
|
||||
<>
|
||||
<strong className="text-dark opacity-60 mobile:!-mb-4">
|
||||
{props.name}
|
||||
</strong>
|
||||
<p className="whitespace-pre-line">{props.children}</p>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
InsetBox: {
|
||||
component: (props) => {
|
||||
return (
|
||||
<InsetBox className="my-12">{props.children}</InsetBox>
|
||||
);
|
||||
},
|
||||
},
|
||||
li: {
|
||||
component: (props: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<li
|
||||
className={
|
||||
props.children &&
|
||||
props.children?.toString().length > 100
|
||||
? "my-4"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</h3>
|
||||
<ToolTip content={"Copy anchor link"} trigger="mouseenter">
|
||||
<ToolTip content={"Copied! 👍"} trigger="click">
|
||||
<span
|
||||
className="material-icons transition-color hover:text-dark cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
process.env.NEXT_PUBLIC_URL_SELF +
|
||||
window.location.pathname +
|
||||
"#" +
|
||||
props.id
|
||||
);
|
||||
}}
|
||||
>
|
||||
link
|
||||
</span>
|
||||
</ToolTip>
|
||||
</ToolTip>
|
||||
</div>
|
||||
);
|
||||
</li>
|
||||
);
|
||||
},
|
||||
},
|
||||
Highlight: {
|
||||
component: (props: { children: React.ReactNode }) => {
|
||||
return <mark>{props.children}</mark>;
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
component: (props: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<div>{props.children}</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
img: {
|
||||
component: (props: {
|
||||
alt: string;
|
||||
src: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
caption?: string;
|
||||
name?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="my-8 cursor-pointer"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages([
|
||||
props.src.startsWith("/uploads/")
|
||||
? getAssetURL(props.src, ImageQuality.Large)
|
||||
: props.src,
|
||||
]);
|
||||
setLightboxIndex(0);
|
||||
}}
|
||||
>
|
||||
{props.src.startsWith("/uploads/") ? (
|
||||
<div className="relative w-full aspect-video">
|
||||
<Img
|
||||
image={{
|
||||
__typename: "UploadFile",
|
||||
alternativeText: props.alt,
|
||||
url: props.src,
|
||||
width: props.width || 1500,
|
||||
height: props.height || 1000,
|
||||
caption: props.caption || "",
|
||||
name: props.name || "",
|
||||
}}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
quality={ImageQuality.Medium}
|
||||
></Img>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid place-content-center">
|
||||
<img {...props} className="max-h-[50vh] " />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
Sep: {
|
||||
component: () => {
|
||||
return <div className="my-24"></div>;
|
||||
},
|
||||
},
|
||||
SceneBreak: {
|
||||
component: (props: { id: string }) => {
|
||||
return (
|
||||
<div
|
||||
id={props.id}
|
||||
className={"h-0 text-center text-3xl text-dark mt-16 mb-20"}
|
||||
>
|
||||
* * *
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
player: {
|
||||
component: () => {
|
||||
return (
|
||||
<span className="text-dark opacity-70">
|
||||
{appLayout.playerName ? appLayout.playerName : "<player>"}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
Transcript: {
|
||||
component: (props) => {
|
||||
return (
|
||||
<div className="grid grid-cols-[auto_1fr] mobile:grid-cols-1 gap-x-6 gap-y-2">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
Line: {
|
||||
component: (props) => {
|
||||
return (
|
||||
<>
|
||||
<strong className="text-dark opacity-60 mobile:!-mb-4">
|
||||
{props.name}
|
||||
</strong>
|
||||
<p className="whitespace-pre-line">{props.children}</p>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
InsetBox: {
|
||||
component: (props) => {
|
||||
return <InsetBox>{props.children}</InsetBox>;
|
||||
},
|
||||
},
|
||||
li: {
|
||||
component: (props: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<li
|
||||
className={
|
||||
props.children && props.children?.toString().length > 100
|
||||
? "my-4"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</li>
|
||||
);
|
||||
},
|
||||
},
|
||||
Highlight: {
|
||||
component: (props: { children: React.ReactNode }) => {
|
||||
return <mark>{props.children}</mark>;
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
component: (props: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<div>{props.children}</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Markdown>
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Markdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function HeaderToolTip(props: { id: string }) {
|
||||
return (
|
||||
<ToolTip content={"Copy anchor link"} trigger="mouseenter">
|
||||
<ToolTip content={"Copied! 👍"} trigger="click">
|
||||
<span
|
||||
className="material-icons transition-color hover:text-dark cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
process.env.NEXT_PUBLIC_URL_SELF +
|
||||
window.location.pathname +
|
||||
"#" +
|
||||
props.id
|
||||
);
|
||||
}}
|
||||
>
|
||||
link
|
||||
</span>
|
||||
</ToolTip>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
export function preprocessMarkDawn(text: string): string {
|
||||
let scenebreakIndex = 0;
|
||||
const visitedSlugs: string[] = [];
|
||||
|
||||
const result = text.split("\n").map((line) => {
|
||||
if (line === "* * *" || line === "---") {
|
||||
scenebreakIndex++;
|
||||
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
|
||||
}
|
||||
|
||||
if (line.startsWith("# ")) {
|
||||
return markdawnHeadersParser(headerLevels.h1, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("## ")) {
|
||||
return markdawnHeadersParser(headerLevels.h2, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h3, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("#### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h4, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("##### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h5, line, visitedSlugs);
|
||||
}
|
||||
|
||||
if (line.startsWith("###### ")) {
|
||||
return markdawnHeadersParser(headerLevels.h6, line, visitedSlugs);
|
||||
}
|
||||
|
||||
return line;
|
||||
});
|
||||
return result.join("\n");
|
||||
}
|
||||
|
||||
enum headerLevels {
|
||||
h1 = 1,
|
||||
h2 = 2,
|
||||
h3 = 3,
|
||||
h4 = 4,
|
||||
h5 = 5,
|
||||
h6 = 6,
|
||||
}
|
||||
|
||||
function markdawnHeadersParser(
|
||||
headerLevel: headerLevels,
|
||||
line: string,
|
||||
visitedSlugs: string[]
|
||||
): string {
|
||||
const lineText = line.slice(headerLevel + 1);
|
||||
let slug = slugify(lineText);
|
||||
let newSlug = slug;
|
||||
let index = 2;
|
||||
while (visitedSlugs.includes(newSlug)) {
|
||||
newSlug = `${slug}-${index}`;
|
||||
index++;
|
||||
}
|
||||
visitedSlugs.push(newSlug);
|
||||
return `<${headerLevels[headerLevel]} id="${newSlug}">${lineText}</${headerLevels[headerLevel]}>`;
|
||||
}
|
||||
function getAssetUrl(): React.SetStateAction<string[]> {
|
||||
throw new Error("Function not implemented.");
|
||||
}
|
||||
|
|
|
@ -13,44 +13,55 @@ export default function TOC(props: TOCProps): JSX.Element {
|
|||
const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<h3 className="text-xl">Table of content</h3>
|
||||
<ol className="text-left max-w-[14.5rem]">
|
||||
<li className="my-2 overflow-x-hidden relative text-ellipsis whitespace-nowrap">
|
||||
<div className="text-left max-w-[14.5rem]">
|
||||
<p className="my-2 overflow-x-hidden relative text-ellipsis whitespace-nowrap text-left">
|
||||
<a className="" onClick={() => router.replace(`#${toc.slug}`)}>
|
||||
{<abbr title={toc.title}>{toc.title}</abbr>}
|
||||
</a>
|
||||
</li>
|
||||
{toc.children.map((h2, h2Index) => (
|
||||
<>
|
||||
<li
|
||||
key={h2.slug}
|
||||
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<span className="text-dark">{`${h2Index + 1}. `}</span>
|
||||
<a onClick={() => router.replace(`#${h2.slug}`)}>
|
||||
{<abbr title={h2.title}>{h2.title}</abbr>}
|
||||
</a>
|
||||
</li>
|
||||
<ol className="pl-4 text-left">
|
||||
{h2.children.map((h3, h3Index) => (
|
||||
<li
|
||||
key={h3.slug}
|
||||
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<span className="text-dark">{`${h2Index + 1}.${
|
||||
h3Index + 1
|
||||
}. `}</span>
|
||||
<a onClick={() => router.replace(`#${h3.slug}`)}>
|
||||
{<abbr title={h3.title}>{h3.title}</abbr>}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</p>
|
||||
<TOCLevel
|
||||
tocchildren={toc.children}
|
||||
parentNumbering=""
|
||||
router={router}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type TOCLevelProps = {
|
||||
tocchildren: TOC[];
|
||||
parentNumbering: string;
|
||||
router: NextRouter;
|
||||
};
|
||||
|
||||
function TOCLevel(props: TOCLevelProps): JSX.Element {
|
||||
const { tocchildren, parentNumbering, router } = props;
|
||||
return (
|
||||
<ol className="pl-4 text-left">
|
||||
{tocchildren.map((child, childIndex) => (
|
||||
<>
|
||||
<li
|
||||
key={child.slug}
|
||||
className="my-2 overflow-x-hidden w-full text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<span className="text-dark">{`${parentNumbering}${
|
||||
childIndex + 1
|
||||
}.`}</span>
|
||||
<a onClick={() => router.replace(`#${child.slug}`)}>
|
||||
{<abbr title={child.title}>{child.title}</abbr>}
|
||||
</a>
|
||||
</li>
|
||||
<TOCLevel
|
||||
tocchildren={child.children}
|
||||
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
||||
router={router}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -69,13 +80,23 @@ export function getTocFromMarkdawn(text: string, title?: string): TOC {
|
|||
let h5 = -1;
|
||||
let scenebreak = 0;
|
||||
let scenebreakIndex = 0;
|
||||
|
||||
function getTitle(line: string): string {
|
||||
return line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
|
||||
}
|
||||
|
||||
function getSlug(line: string): string {
|
||||
return line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
|
||||
}
|
||||
|
||||
text.split("\n").map((line) => {
|
||||
if (line.startsWith("# ")) {
|
||||
toc.slug = slugify(line);
|
||||
} else if (line.startsWith("## ")) {
|
||||
if (line.startsWith("<h1 id=")) {
|
||||
toc.title = getTitle(line);
|
||||
toc.slug = getSlug(line);
|
||||
} else if (line.startsWith("<h2 id=")) {
|
||||
toc.children.push({
|
||||
title: line.slice("## ".length),
|
||||
slug: slugify(line),
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h2++;
|
||||
|
@ -83,74 +104,64 @@ export function getTocFromMarkdawn(text: string, title?: string): TOC {
|
|||
h4 = -1;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("### ")) {
|
||||
} else if (line.startsWith("<h3 id=")) {
|
||||
toc.children[h2].children.push({
|
||||
title: line.slice("### ".length),
|
||||
slug: slugify(line),
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h3++;
|
||||
h4 = -1;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("#### ")) {
|
||||
} else if (line.startsWith("<h4 id=")) {
|
||||
toc.children[h2].children[h3].children.push({
|
||||
title: line.slice("#### ".length),
|
||||
slug: slugify(line),
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h4++;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("##### ")) {
|
||||
} else if (line.startsWith("<h5 id=")) {
|
||||
toc.children[h2].children[h3].children[h4].children.push({
|
||||
title: line.slice("##### ".length),
|
||||
slug: slugify(line),
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h5++;
|
||||
scenebreak = 0;
|
||||
} else if (line.startsWith("###### ")) {
|
||||
} else if (line.startsWith("<h6 id=")) {
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
||||
title: line.slice("###### ".length),
|
||||
slug: slugify(line),
|
||||
title: getTitle(line),
|
||||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
} else if (line.startsWith(`<SceneBreak`)) {
|
||||
scenebreak++;
|
||||
scenebreakIndex++;
|
||||
|
||||
const child = {
|
||||
title: `Scene break ${scenebreak}`,
|
||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||
children: [],
|
||||
};
|
||||
|
||||
if (h5 >= 0) {
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
||||
title: `Scene break ${scenebreak}`,
|
||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||
children: [],
|
||||
});
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push(
|
||||
child
|
||||
);
|
||||
} else if (h4 >= 0) {
|
||||
toc.children[h2].children[h3].children[h4].children.push({
|
||||
title: `Scene break ${scenebreak}`,
|
||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||
children: [],
|
||||
});
|
||||
toc.children[h2].children[h3].children[h4].children.push(child);
|
||||
} else if (h3 >= 0) {
|
||||
toc.children[h2].children[h3].children.push({
|
||||
title: `Scene break ${scenebreak}`,
|
||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||
children: [],
|
||||
});
|
||||
toc.children[h2].children[h3].children.push(child);
|
||||
} else if (h2 >= 0) {
|
||||
toc.children[h2].children.push({
|
||||
title: `Scene break ${scenebreak}`,
|
||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||
children: [],
|
||||
});
|
||||
toc.children[h2].children.push(child);
|
||||
} else {
|
||||
toc.children.push({
|
||||
title: `Scene break ${scenebreak}`,
|
||||
slug: slugify(`scene-break-${scenebreakIndex}`),
|
||||
children: [],
|
||||
});
|
||||
toc.children.push(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return toc;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import Link from "next/link";
|
||||
import { prettyDate, prettySlug } from "queries/helpers";
|
||||
import Chip from "components/Chip";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import { GetPostsPreviewQuery } from "graphql/operations-types";
|
||||
|
||||
export type PostPreviewProps = {
|
||||
post: {
|
||||
slug: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["slug"];
|
||||
thumbnail: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["thumbnail"];
|
||||
translations: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["translations"];
|
||||
categories: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["categories"];
|
||||
date: GetPostsPreviewQuery["posts"]["data"][number]["attributes"]["date"];
|
||||
};
|
||||
};
|
||||
|
||||
export default function PostPreview(props: PostPreviewProps): JSX.Element {
|
||||
const post = props.post;
|
||||
|
||||
return (
|
||||
<Link href={"/news/" + post.slug} passHref>
|
||||
<div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] transition-transform">
|
||||
{post.thumbnail.data ? (
|
||||
<Img
|
||||
className="rounded-md rounded-b-none"
|
||||
image={post.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Medium}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full aspect-[3/2] bg-light rounded-lg"></div>
|
||||
)}
|
||||
<div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
|
||||
<div className="grid grid-flow-col w-full">
|
||||
{post.date && (
|
||||
<p className="mobile:text-xs text-sm">
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
event
|
||||
</span>
|
||||
{prettyDate(post.date)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{post.translations.length > 0 ? (
|
||||
<>
|
||||
<h1 className="text-xl">{post.translations[0].title}</h1>
|
||||
<p>{post.translations[0].excerpt}</p>
|
||||
</>
|
||||
) : (
|
||||
<h1 className="text-lg">{prettySlug(post.slug)}</h1>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
|
||||
{post.categories.data.map((category) => (
|
||||
<Chip key={category.id} className="text-sm">
|
||||
{category.attributes.short}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -2,12 +2,17 @@ import { Dispatch, SetStateAction } from "react";
|
|||
import Button from "./Button";
|
||||
|
||||
export type PopupProps = {
|
||||
setState: Dispatch<SetStateAction<boolean | undefined>>;
|
||||
setState:
|
||||
| Dispatch<SetStateAction<boolean>>
|
||||
| Dispatch<SetStateAction<boolean | undefined>>;
|
||||
state?: boolean;
|
||||
children: React.ReactNode;
|
||||
fillViewport?: boolean;
|
||||
hideBackground?: boolean;
|
||||
};
|
||||
|
||||
export default function Popup(props: PopupProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500 ${
|
||||
|
@ -15,6 +20,10 @@ export default function Popup(props: PopupProps): JSX.Element {
|
|||
? "[backdrop-filter:blur(2px)]"
|
||||
: "pointer-events-none touch-none"
|
||||
}`}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key.match("Escape")) props.setState(false);
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className={`fixed bg-shade inset-0 transition-all duration-500 ${
|
||||
|
@ -25,8 +34,12 @@ export default function Popup(props: PopupProps): JSX.Element {
|
|||
}}
|
||||
/>
|
||||
<div
|
||||
className={`relative p-10 bg-light rounded-lg shadow-2xl shadow-shade grid gap-4 place-items-center transition-transform ${
|
||||
className={`p-10 grid gap-4 place-items-center transition-transform ${
|
||||
props.state ? "scale-100" : "scale-0"
|
||||
} ${props.fillViewport ? "absolute inset-10 top-20" : "relative"} ${
|
||||
props.hideBackground
|
||||
? ""
|
||||
: "bg-light rounded-lg shadow-2xl shadow-shade"
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
|
|
|
@ -1155,8 +1155,12 @@ query getPost($slug: String, $language_code: String) {
|
|||
id
|
||||
attributes {
|
||||
slug
|
||||
publishedAt
|
||||
updatedAt
|
||||
date {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
authors {
|
||||
data {
|
||||
id
|
||||
|
@ -1200,8 +1204,20 @@ query getPost($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
hidden
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
translations(filters: { language: { code: { eq: $language_code } } }) {
|
||||
Status
|
||||
status
|
||||
title
|
||||
excerpt
|
||||
thumbnail {
|
||||
|
@ -1222,3 +1238,66 @@ query getPost($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getPostsSlugs {
|
||||
posts(filters: { hidden: { eq: false } }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getPostsPreview($language_code: String) {
|
||||
posts(filters: { hidden: { eq: false } }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
slug
|
||||
date {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
translations(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
excerpt
|
||||
thumbnail {
|
||||
data {
|
||||
attributes {
|
||||
name
|
||||
alternativeText
|
||||
caption
|
||||
width
|
||||
height
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1554,8 +1554,13 @@ export type GetPostQuery = {
|
|||
attributes: {
|
||||
__typename: "Post";
|
||||
slug: string;
|
||||
publishedAt: any;
|
||||
updatedAt: any;
|
||||
date: {
|
||||
__typename: "ComponentBasicsDatepicker";
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
};
|
||||
hidden: boolean;
|
||||
authors: {
|
||||
__typename: "RecorderRelationResponseCollection";
|
||||
|
@ -1609,9 +1614,24 @@ export type GetPostQuery = {
|
|||
};
|
||||
}>;
|
||||
};
|
||||
thumbnail: {
|
||||
__typename: "UploadFileEntityResponse";
|
||||
data: {
|
||||
__typename: "UploadFileEntity";
|
||||
attributes: {
|
||||
__typename: "UploadFile";
|
||||
name: string;
|
||||
alternativeText: string;
|
||||
caption: string;
|
||||
width: number;
|
||||
height: number;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
translations: Array<{
|
||||
__typename: "ComponentTranslationsPosts";
|
||||
Status: Enum_Componenttranslationsposts_Status;
|
||||
status: Enum_Componenttranslationsposts_Status;
|
||||
title: string;
|
||||
excerpt: string;
|
||||
body: string;
|
||||
|
@ -1635,3 +1655,85 @@ export type GetPostQuery = {
|
|||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetPostsSlugsQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type GetPostsSlugsQuery = {
|
||||
__typename: "Query";
|
||||
posts: {
|
||||
__typename: "PostEntityResponseCollection";
|
||||
data: Array<{
|
||||
__typename: "PostEntity";
|
||||
id: string;
|
||||
attributes: { __typename: "Post"; slug: string };
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetPostsPreviewQueryVariables = Exact<{
|
||||
language_code: InputMaybe<Scalars["String"]>;
|
||||
}>;
|
||||
|
||||
export type GetPostsPreviewQuery = {
|
||||
__typename: "Query";
|
||||
posts: {
|
||||
__typename: "PostEntityResponseCollection";
|
||||
data: Array<{
|
||||
__typename: "PostEntity";
|
||||
id: string;
|
||||
attributes: {
|
||||
__typename: "Post";
|
||||
slug: string;
|
||||
date: {
|
||||
__typename: "ComponentBasicsDatepicker";
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
};
|
||||
categories: {
|
||||
__typename: "CategoryRelationResponseCollection";
|
||||
data: Array<{
|
||||
__typename: "CategoryEntity";
|
||||
id: string;
|
||||
attributes: { __typename: "Category"; short: string };
|
||||
}>;
|
||||
};
|
||||
thumbnail: {
|
||||
__typename: "UploadFileEntityResponse";
|
||||
data: {
|
||||
__typename: "UploadFileEntity";
|
||||
attributes: {
|
||||
__typename: "UploadFile";
|
||||
name: string;
|
||||
alternativeText: string;
|
||||
caption: string;
|
||||
width: number;
|
||||
height: number;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
translations: Array<{
|
||||
__typename: "ComponentTranslationsPosts";
|
||||
title: string;
|
||||
excerpt: string;
|
||||
thumbnail: {
|
||||
__typename: "UploadFileEntityResponse";
|
||||
data: {
|
||||
__typename: "UploadFileEntity";
|
||||
attributes: {
|
||||
__typename: "UploadFile";
|
||||
name: string;
|
||||
alternativeText: string;
|
||||
caption: string;
|
||||
width: number;
|
||||
height: number;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -25,6 +25,10 @@ import {
|
|||
GetLibraryItemsSlugsQueryVariables,
|
||||
GetPostQuery,
|
||||
GetPostQueryVariables,
|
||||
GetPostsPreviewQuery,
|
||||
GetPostsPreviewQueryVariables,
|
||||
GetPostsSlugsQuery,
|
||||
GetPostsSlugsQueryVariables,
|
||||
GetWebsiteInterfaceQuery,
|
||||
GetWebsiteInterfaceQueryVariables,
|
||||
} from "graphql/operations-types";
|
||||
|
@ -150,3 +154,17 @@ export async function getPost(
|
|||
const query = getQueryFromOperations("getPost");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getPostsSlugs(
|
||||
variables: GetPostsSlugsQueryVariables
|
||||
): Promise<GetPostsSlugsQuery> {
|
||||
const query = getQueryFromOperations("getPostsSlugs");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
||||
export async function getPostsPreview(
|
||||
variables: GetPostsPreviewQueryVariables
|
||||
): Promise<GetPostsPreviewQuery> {
|
||||
const query = getQueryFromOperations("getPostsPreview");
|
||||
return await graphQL(query, JSON.stringify(variables));
|
||||
}
|
||||
|
|
|
@ -26,11 +26,6 @@ type ResponseCollectionMeta {
|
|||
pagination: Pagination!
|
||||
}
|
||||
|
||||
enum PublicationState {
|
||||
LIVE
|
||||
PREVIEW
|
||||
}
|
||||
|
||||
input IDFilterInput {
|
||||
and: [ID]
|
||||
or: [ID]
|
||||
|
@ -387,6 +382,8 @@ input ComponentCollectionsComponentLibraryObiBeltInput {
|
|||
back: ID
|
||||
full: ID
|
||||
inside_full: ID
|
||||
flap_front: ID
|
||||
flap_back: ID
|
||||
}
|
||||
|
||||
type ComponentCollectionsComponentLibraryObiBelt {
|
||||
|
@ -396,6 +393,8 @@ type ComponentCollectionsComponentLibraryObiBelt {
|
|||
back: UploadFileEntityResponse
|
||||
full: UploadFileEntityResponse
|
||||
inside_full: UploadFileEntityResponse
|
||||
flap_front: UploadFileEntityResponse
|
||||
flap_back: UploadFileEntityResponse
|
||||
}
|
||||
|
||||
input ComponentCollectionsComponentTitlesFiltersInput {
|
||||
|
@ -1057,11 +1056,11 @@ enum ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS {
|
|||
}
|
||||
|
||||
input ComponentTranslationsPostsFiltersInput {
|
||||
Status: StringFilterInput
|
||||
title: StringFilterInput
|
||||
excerpt: StringFilterInput
|
||||
body: StringFilterInput
|
||||
language: LanguageFiltersInput
|
||||
status: StringFilterInput
|
||||
and: [ComponentTranslationsPostsFiltersInput]
|
||||
or: [ComponentTranslationsPostsFiltersInput]
|
||||
not: ComponentTranslationsPostsFiltersInput
|
||||
|
@ -1069,22 +1068,22 @@ input ComponentTranslationsPostsFiltersInput {
|
|||
|
||||
input ComponentTranslationsPostsInput {
|
||||
id: ID
|
||||
Status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS
|
||||
title: String
|
||||
excerpt: String
|
||||
thumbnail: ID
|
||||
body: String
|
||||
language: ID
|
||||
status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS
|
||||
}
|
||||
|
||||
type ComponentTranslationsPosts {
|
||||
id: ID!
|
||||
Status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS!
|
||||
title: String!
|
||||
excerpt: String
|
||||
thumbnail: UploadFileEntityResponse
|
||||
body: String
|
||||
language: LanguageEntityResponse
|
||||
status: ENUM_COMPONENTTRANSLATIONSPOSTS_STATUS!
|
||||
}
|
||||
|
||||
enum ENUM_COMPONENTTRANSLATIONSSCANSET_STATUS {
|
||||
|
@ -2126,7 +2125,6 @@ input PostFiltersInput {
|
|||
hidden: BooleanFilterInput
|
||||
createdAt: DateTimeFilterInput
|
||||
updatedAt: DateTimeFilterInput
|
||||
publishedAt: DateTimeFilterInput
|
||||
and: [PostFiltersInput]
|
||||
or: [PostFiltersInput]
|
||||
not: PostFiltersInput
|
||||
|
@ -2138,7 +2136,7 @@ input PostInput {
|
|||
categories: [ID]
|
||||
translations: [ComponentTranslationsPostsInput]
|
||||
hidden: Boolean
|
||||
publishedAt: DateTime
|
||||
thumbnail: ID
|
||||
}
|
||||
|
||||
type Post {
|
||||
|
@ -2159,9 +2157,9 @@ type Post {
|
|||
sort: [String] = []
|
||||
): [ComponentTranslationsPosts]
|
||||
hidden: Boolean!
|
||||
thumbnail: UploadFileEntityResponse
|
||||
createdAt: DateTime
|
||||
updatedAt: DateTime
|
||||
publishedAt: DateTime
|
||||
}
|
||||
|
||||
type PostEntity {
|
||||
|
@ -3206,7 +3204,6 @@ type Query {
|
|||
filters: PostFiltersInput
|
||||
pagination: PaginationArg = {}
|
||||
sort: [String] = []
|
||||
publicationState: PublicationState = LIVE
|
||||
): PostEntityResponseCollection
|
||||
rangedContent(id: ID): RangedContentEntityResponse
|
||||
rangedContents(
|
||||
|
|
|
@ -40,7 +40,28 @@ export default function ContentIndex(props: ContentIndexProps): JSX.Element {
|
|||
className="mb-10"
|
||||
/>
|
||||
<div className="grid place-items-center">
|
||||
<ThumbnailHeader content={content} langui={langui} />
|
||||
<ThumbnailHeader
|
||||
thumbnail={content.thumbnail}
|
||||
pre_title={
|
||||
content.titles.length > 0 ? content.titles[0].pre_title : undefined
|
||||
}
|
||||
title={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].title
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
subtitle={
|
||||
content.titles.length > 0 ? content.titles[0].subtitle : undefined
|
||||
}
|
||||
description={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].description
|
||||
: undefined
|
||||
}
|
||||
type={content.type}
|
||||
categories={content.categories}
|
||||
langui={langui}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -65,6 +86,31 @@ export default function ContentIndex(props: ContentIndexProps): JSX.Element {
|
|||
</ContentPanel>
|
||||
);
|
||||
|
||||
let description = "";
|
||||
if (content.type.data) {
|
||||
description += `${langui.type}: `;
|
||||
if (content.type.data.attributes.titles.length > 0) {
|
||||
description += content.type.data.attributes.titles[0].title;
|
||||
} else {
|
||||
description += prettySlug(content.type.data.attributes.slug);
|
||||
}
|
||||
description += "\n";
|
||||
}
|
||||
if (content.categories.data.length > 0) {
|
||||
description += `${langui.categories}: `;
|
||||
description += content.categories.data
|
||||
.map((category) => {
|
||||
return category.attributes.short;
|
||||
})
|
||||
.join(" | ");
|
||||
description += "\n";
|
||||
}
|
||||
|
||||
if (content.titles.length > 0 && content.titles[0].description) {
|
||||
description += "\n";
|
||||
description += content.titles[0].description;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle="Contents"
|
||||
|
@ -80,22 +126,7 @@ export default function ContentIndex(props: ContentIndexProps): JSX.Element {
|
|||
thumbnail={content.thumbnail.data?.attributes}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
description={`${langui.type}: ${
|
||||
content.type.data.attributes.titles.length > 0
|
||||
? content.type.data.attributes.titles[0].title
|
||||
: prettySlug(content.type.data.attributes.slug)
|
||||
}
|
||||
${langui.categories}: ${
|
||||
content.categories.data.length > 0 &&
|
||||
content.categories.data
|
||||
.map((category) => {
|
||||
return category.attributes.short;
|
||||
})
|
||||
.join(" | ")
|
||||
}
|
||||
|
||||
${content.titles.length > 0 ? content.titles[0].description : undefined}
|
||||
`}
|
||||
description={description}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { GetStaticPaths, GetStaticProps } from "next";
|
||||
import { getContentsSlugs, getContentText } from "graphql/operations";
|
||||
import {
|
||||
Enum_Componentsetstextset_Status,
|
||||
GetContentTextQuery,
|
||||
} from "graphql/operations-types";
|
||||
import { GetContentTextQuery } from "graphql/operations-types";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
|
@ -49,7 +46,7 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
|||
horizontalLine
|
||||
/>
|
||||
|
||||
{content.text_set.length > 0 && (
|
||||
{content.text_set.length > 0 && content.text_set[0].source_language.data && (
|
||||
<div className="grid gap-5">
|
||||
<h2 className="text-xl">
|
||||
{content.text_set[0].source_language.data.attributes.code ===
|
||||
|
@ -158,13 +155,34 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
|||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href={`/contents/${content.slug}`}
|
||||
title={"Content"}
|
||||
title={langui.content}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.Mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
<div className="grid place-items-center">
|
||||
<ThumbnailHeader content={content} langui={langui} />
|
||||
<ThumbnailHeader
|
||||
thumbnail={content.thumbnail}
|
||||
pre_title={
|
||||
content.titles.length > 0 ? content.titles[0].pre_title : undefined
|
||||
}
|
||||
title={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].title
|
||||
: prettySlug(content.slug)
|
||||
}
|
||||
subtitle={
|
||||
content.titles.length > 0 ? content.titles[0].subtitle : undefined
|
||||
}
|
||||
description={
|
||||
content.titles.length > 0
|
||||
? content.titles[0].description
|
||||
: undefined
|
||||
}
|
||||
type={content.type}
|
||||
categories={content.categories}
|
||||
langui={langui}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -175,6 +193,26 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
|||
</ContentPanel>
|
||||
);
|
||||
|
||||
let description = "";
|
||||
if (content.type.data) {
|
||||
description += `${langui.type}: `;
|
||||
if (content.type.data.attributes.titles.length > 0) {
|
||||
description += content.type.data.attributes.titles[0].title;
|
||||
} else {
|
||||
description += prettySlug(content.type.data.attributes.slug);
|
||||
}
|
||||
description += "\n";
|
||||
}
|
||||
if (content.categories.data.length > 0) {
|
||||
description += `${langui.categories}: `;
|
||||
description += content.categories.data
|
||||
.map((category) => {
|
||||
return category.attributes.short;
|
||||
})
|
||||
.join(" | ");
|
||||
description += "\n";
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle="Contents"
|
||||
|
@ -190,22 +228,7 @@ export default function ContentRead(props: ContentReadProps): JSX.Element {
|
|||
thumbnail={content.thumbnail.data?.attributes}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
description={`${langui.type}: ${
|
||||
content.type.data.attributes.titles.length > 0
|
||||
? content.type.data.attributes.titles[0].title
|
||||
: prettySlug(content.type.data.attributes.slug)
|
||||
}
|
||||
${langui.categories}: ${
|
||||
content.categories.data.length > 0 &&
|
||||
content.categories.data
|
||||
.map((category) => {
|
||||
return category.attributes.short;
|
||||
})
|
||||
.join(" | ")
|
||||
}
|
||||
|
||||
${content.titles.length > 0 ? content.titles[0].description : undefined}
|
||||
`}
|
||||
description={description}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -308,42 +331,43 @@ export function useTesting(props: ContentReadProps) {
|
|||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (textset.source_language.data.attributes.code === router.locale) {
|
||||
// This is a transcript
|
||||
if (textset.transcribers.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing transcribers attribution",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (textset.translators.data.length > 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Transcripts shouldn't have translators",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// This is a translation
|
||||
if (textset.translators.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing translators attribution",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (textset.transcribers.data.length > 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Translations shouldn't have transcribers",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
if (textset.source_language.data.attributes.code === router.locale) {
|
||||
// This is a transcript
|
||||
if (textset.transcribers.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing transcribers attribution",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (textset.translators.data.length > 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Transcripts shouldn't have translators",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// This is a translation
|
||||
if (textset.translators.data.length === 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Missing translators attribution",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
if (textset.transcribers.data.length > 0) {
|
||||
prettyTestError(
|
||||
router,
|
||||
"Translations shouldn't have transcribers",
|
||||
["content", "text_set"],
|
||||
contentURL
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,13 @@ import Button from "components/Button";
|
|||
import AppLayout from "components/AppLayout";
|
||||
import LibraryItemsPreview from "components/Library/LibraryItemsPreview";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import Img, { ImageQuality } from "components/Img";
|
||||
import Img, { getAssetURL, ImageQuality } from "components/Img";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { useRouter } from "next/router";
|
||||
import ContentTOCLine from "components/Library/ContentTOCLine";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { useState } from "react";
|
||||
import LightBox from "components/LightBox";
|
||||
|
||||
interface LibrarySlugProps extends AppStaticProps {
|
||||
item: GetLibraryItemQuery["libraryItems"]["data"][number]["attributes"];
|
||||
|
@ -52,6 +54,10 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
|
||||
sortContent(item.contents);
|
||||
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
|
@ -104,6 +110,14 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<LightBox
|
||||
state={lightboxOpen}
|
||||
setState={setLightboxOpen}
|
||||
images={lightboxImages}
|
||||
index={lightboxIndex}
|
||||
setIndex={setLightboxIndex}
|
||||
/>
|
||||
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library}
|
||||
|
@ -112,11 +126,23 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
className="mb-10"
|
||||
/>
|
||||
<div className="grid place-items-center gap-12">
|
||||
<div className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[60vh] desktop:mb-16 relative cursor-pointer">
|
||||
<div
|
||||
className="drop-shadow-shade-xl w-full h-[50vh] mobile:h-[60vh] desktop:mb-16 relative cursor-pointer"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages([
|
||||
getAssetURL(
|
||||
item.thumbnail.data.attributes.url,
|
||||
ImageQuality.Large
|
||||
),
|
||||
]);
|
||||
setLightboxIndex(0);
|
||||
}}
|
||||
>
|
||||
{item.thumbnail.data ? (
|
||||
<Img
|
||||
image={item.thumbnail.data.attributes}
|
||||
quality={ImageQuality.Medium}
|
||||
quality={ImageQuality.Large}
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
priority
|
||||
|
@ -156,10 +182,22 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element {
|
|||
<div id="gallery" className="grid place-items-center gap-8 w-full">
|
||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||
<div className="grid w-full gap-8 items-end grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]">
|
||||
{item.gallery.data.map((galleryItem) => (
|
||||
{item.gallery.data.map((galleryItem, index) => (
|
||||
<div
|
||||
key={galleryItem.id}
|
||||
className="relative aspect-square hover:scale-[1.02] transition-transform cursor-pointer"
|
||||
onClick={() => {
|
||||
setLightboxOpen(true);
|
||||
setLightboxImages(
|
||||
item.gallery.data.map((image) => {
|
||||
return getAssetURL(
|
||||
image.attributes.url,
|
||||
ImageQuality.Large
|
||||
);
|
||||
})
|
||||
);
|
||||
setLightboxIndex(index);
|
||||
}}
|
||||
>
|
||||
<div className="bg-light absolute inset-0 rounded-lg drop-shadow-shade-md"></div>
|
||||
<Img
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Chip from "components/Chip";
|
||||
import ThumbnailHeader from "components/Content/ThumbnailHeader";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import Markdawn from "components/Markdown/Markdawn";
|
||||
import TOC from "components/Markdown/TOC";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import RecorderChip from "components/RecorderChip";
|
||||
import ToolTip from "components/ToolTip";
|
||||
import { getPost, getPostsSlugs } from "graphql/operations";
|
||||
import { GetPostQuery } from "graphql/operations-types";
|
||||
import { GetStaticPaths, GetStaticProps } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { prettySlug, getStatusDescription } from "queries/helpers";
|
||||
|
||||
interface PostProps extends AppStaticProps {
|
||||
post: GetPostQuery["posts"]["data"][number]["attributes"];
|
||||
postId: GetPostQuery["posts"]["data"][number]["id"];
|
||||
}
|
||||
|
||||
export default function LibrarySlug(props: PostProps): JSX.Element {
|
||||
const { post, postId, langui } = props;
|
||||
const router = useRouter();
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/news"
|
||||
title={langui.news}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.Desktop}
|
||||
horizontalLine
|
||||
/>
|
||||
|
||||
{post.translations.length > 0 && (
|
||||
<div className="grid grid-flow-col place-items-center place-content-center gap-2">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(post.translations[0].status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
<Chip>{post.translations[0].status}</Chip>
|
||||
</ToolTip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{post.authors.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Authors"}:</p>
|
||||
<div className="grid place-items-center place-content-center gap-2">
|
||||
{post.authors.data.map((author) => (
|
||||
<RecorderChip key={author.id} langui={langui} recorder={author} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
{post.translations.length > 0 && post.translations[0].body && (
|
||||
<TOC
|
||||
text={post.translations[0].body}
|
||||
router={router}
|
||||
title={post.translations[0].title}
|
||||
/>
|
||||
)}
|
||||
</SubPanel>
|
||||
);
|
||||
const contentPanel = (
|
||||
<ContentPanel>
|
||||
<ReturnButton
|
||||
href="/news"
|
||||
title={langui.news}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.Mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
<ThumbnailHeader
|
||||
thumbnail={
|
||||
post.translations.length > 0 && post.translations[0].thumbnail.data
|
||||
? post.translations[0].thumbnail
|
||||
: post.thumbnail
|
||||
}
|
||||
title={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
description={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].excerpt
|
||||
: undefined
|
||||
}
|
||||
langui={langui}
|
||||
categories={post.categories}
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
{post.translations.length > 0 && post.translations[0].body && (
|
||||
<Markdawn text={post.translations[0].body} />
|
||||
)}
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.news}
|
||||
title={
|
||||
post.translations.length > 0
|
||||
? post.translations[0].title
|
||||
: prettySlug(post.slug)
|
||||
}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
thumbnail={post.translations[0].thumbnail.data?.attributes}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const post = (
|
||||
await getPost({
|
||||
slug: context.params?.slug?.toString() || "",
|
||||
language_code: context.locale || "en",
|
||||
})
|
||||
).posts.data[0];
|
||||
const props: PostProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
post: post.attributes,
|
||||
postId: post.id,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||
type Path = {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
locale: string;
|
||||
};
|
||||
|
||||
const data = await getPostsSlugs({});
|
||||
const paths: Path[] = [];
|
||||
data.posts.data.map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local });
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
};
|
|
@ -3,11 +3,17 @@ import PanelHeader from "components/PanelComponents/PanelHeader";
|
|||
import { GetStaticProps } from "next";
|
||||
import AppLayout from "components/AppLayout";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { GetPostsPreviewQuery } from "graphql/operations-types";
|
||||
import { getPostsPreview } from "graphql/operations";
|
||||
import ContentPanel, { ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import PostsPreview from "components/News/PostsPreview";
|
||||
|
||||
interface NewsProps extends AppStaticProps {}
|
||||
interface NewsProps extends AppStaticProps {
|
||||
posts: GetPostsPreviewQuery["posts"]["data"];
|
||||
}
|
||||
|
||||
export default function News(props: NewsProps): JSX.Element {
|
||||
const { langui } = props;
|
||||
const { langui, posts } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
|
@ -18,12 +24,32 @@ export default function News(props: NewsProps): JSX.Element {
|
|||
</SubPanel>
|
||||
);
|
||||
|
||||
return <AppLayout navTitle={langui.news} subPanel={subPanel} {...props} />;
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<div className="grid gap-8 items-end grid-cols-1 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]">
|
||||
{posts.map((post) => (
|
||||
<PostsPreview key={post.id} post={post.attributes} />
|
||||
))}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.news}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const props: NewsProps = {
|
||||
...(await getAppStaticProps(context)),
|
||||
posts: await (
|
||||
await getPostsPreview({ language_code: context.locale || "en" })
|
||||
).posts.data,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
|
296
src/tailwind.css
296
src/tailwind.css
|
@ -143,6 +143,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* TIPPY */
|
||||
|
||||
.tippy-box[data-animation="fade"][data-state="hidden"] {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
@ -205,3 +207,297 @@
|
|||
.tippy-content {
|
||||
@apply relative px-6 py-4 z-10;
|
||||
}
|
||||
|
||||
/* LIGHTBOX */
|
||||
|
||||
@keyframes closeWindow {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ril__outer {
|
||||
@apply h-full w-full touch-none outline-none bg-shade bg-opacity-50 [backdrop-filter:blur(2px)];
|
||||
}
|
||||
|
||||
.ril__outerClosing {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ril__inner {
|
||||
@apply absolute inset-0;
|
||||
}
|
||||
|
||||
.ril__image,
|
||||
.ril__imagePrev,
|
||||
.ril__imageNext {
|
||||
@apply absolute inset-0 m-auto max-w-none touch-none;
|
||||
}
|
||||
|
||||
.ril__image {
|
||||
@apply drop-shadow-shade-2xl;
|
||||
}
|
||||
|
||||
.ril__navButtons {
|
||||
@apply absolute inset-y-0 w-5 h-8 px-10 py-8 cursor-pointer m-auto;
|
||||
}
|
||||
.ril__navButtons:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.ril__navButtons:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.ril__navButtonPrev {
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.2)
|
||||
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDE5LDMgLTIsLTIgLTE2LDE2IDE2LDE2IDEsLTEgLTE1LC0xNSAxNSwtMTUgeiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__navButtonNext {
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.2)
|
||||
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDEsMyAyLC0yIDE2LDE2IC0xNiwxNiAtMSwtMSAxNSwtMTUgLTE1LC0xNSB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__caption,
|
||||
.ril__toolbar {
|
||||
@apply bg-shade bg-opacity-50 absolute inset-x-0 flex justify-between;
|
||||
}
|
||||
|
||||
.ril__caption {
|
||||
bottom: 0;
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ril__captionContent {
|
||||
padding: 10px 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ril__toolbar {
|
||||
@apply top-0 h-12;
|
||||
}
|
||||
|
||||
.ril__toolbarSide {
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ril__toolbarLeftSide {
|
||||
padding-left: 20px;
|
||||
padding-right: 0;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ril__toolbarRightSide {
|
||||
padding-left: 0;
|
||||
padding-right: 20px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.ril__toolbarItem {
|
||||
display: inline-block;
|
||||
line-height: 50px;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
font-size: 120%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ril__toolbarItemChild {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ril__builtinButton {
|
||||
width: 40px;
|
||||
height: 35px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.ril__builtinButton:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.ril__builtinButton:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ril__builtinButtonDisabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.ril__builtinButtonDisabled:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.ril__closeButton {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj48cGF0aCBkPSJtIDEsMyAxLjI1LC0xLjI1IDcuNSw3LjUgNy41LC03LjUgMS4yNSwxLjI1IC03LjUsNy41IDcuNSw3LjUgLTEuMjUsMS4yNSAtNy41LC03LjUgLTcuNSw3LjUgLTEuMjUsLTEuMjUgNy41LC03LjUgLTcuNSwtNy41IHoiIGZpbGw9IiNGRkYiLz48L3N2Zz4=")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__zoomInButton {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PHBhdGggZD0iTTEyIDV2NiIvPjwvZz48Y2lyY2xlIGN4PSIxMiIgY3k9IjgiIHI9IjciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__zoomOutButton {
|
||||
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PC9nPjxjaXJjbGUgY3g9IjEyIiBjeT0iOCIgcj0iNyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=")
|
||||
no-repeat center;
|
||||
}
|
||||
|
||||
.ril__outerAnimating {
|
||||
animation-name: closeWindow;
|
||||
}
|
||||
|
||||
@keyframes pointFade {
|
||||
0%,
|
||||
19.999%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ril__loadingCircle {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ril__loadingCirclePoint {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.ril__loadingCirclePoint::before {
|
||||
content: "";
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 11%;
|
||||
height: 30%;
|
||||
background-color: #fff;
|
||||
border-radius: 30%;
|
||||
animation: pointFade 800ms infinite ease-in-out both;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(1) {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(1)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(7)::before {
|
||||
animation-delay: -800ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(2) {
|
||||
transform: rotate(30deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(8) {
|
||||
transform: rotate(210deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(2)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(8)::before {
|
||||
animation-delay: -666ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(3) {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(9) {
|
||||
transform: rotate(240deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(3)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(9)::before {
|
||||
animation-delay: -533ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(4) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(10) {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(4)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(10)::before {
|
||||
animation-delay: -400ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(5) {
|
||||
transform: rotate(120deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(11) {
|
||||
transform: rotate(300deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(5)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(11)::before {
|
||||
animation-delay: -266ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(6) {
|
||||
transform: rotate(150deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(12) {
|
||||
transform: rotate(330deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(6)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(12)::before {
|
||||
animation-delay: -133ms;
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(13) {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
.ril__loadingCirclePoint:nth-of-type(7)::before,
|
||||
.ril__loadingCirclePoint:nth-of-type(13)::before {
|
||||
animation-delay: 0ms;
|
||||
}
|
||||
|
||||
.ril__loadingContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.ril__imagePrev .ril__loadingContainer,
|
||||
.ril__imageNext .ril__loadingContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ril__errorContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
.ril__imagePrev .ril__errorContainer,
|
||||
.ril__imageNext .ril__errorContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ril__loadingContainer__icon {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue