Merge pull request #13 from Accords-Library/develop
Better TOC and Lightbox
This commit is contained in:
		
						commit
						21ad01cf3b
					
				
							
								
								
									
										96
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										96
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -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 <></>; | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										39
									
								
								src/components/LightBox.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/components/LightBox.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| } | ||||
|  | ||||
							
								
								
									
										64
									
								
								src/components/News/PostsPreview.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/components/News/PostsPreview.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
|  | ||||
							
								
								
									
										165
									
								
								src/pages/news/[slug].tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/pages/news/[slug].tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint