Improved stuff
This commit is contained in:
		
							parent
							
								
									6ae54c39d4
								
							
						
					
					
						commit
						bc0764c0d0
					
				@ -27,7 +27,7 @@ module.exports = {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        source: "/gallery",
 | 
			
		||||
        destination: "https://gallery.accords-library.com/",
 | 
			
		||||
        destination: "https://gallery.accords-library.com/posts",
 | 
			
		||||
        permanent: false,
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,13 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { cIf, cJoin } from "helpers/className";
 | 
			
		||||
import { prettyLanguage, prettySlug } from "helpers/formatters";
 | 
			
		||||
import { getOgImage, ImageQuality } from "helpers/img";
 | 
			
		||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
 | 
			
		||||
import {
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  isDefined,
 | 
			
		||||
  isDefinedAndNotEmpty,
 | 
			
		||||
  isUndefined,
 | 
			
		||||
  iterateMap,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
// import { getClient, Indexes, search, SearchResult } from "helpers/search";
 | 
			
		||||
 | 
			
		||||
import { useMediaMobile } from "hooks/useMediaQuery";
 | 
			
		||||
@ -19,6 +25,7 @@ import { ButtonGroup } from "./Inputs/ButtonGroup";
 | 
			
		||||
import { OrderableList } from "./Inputs/OrderableList";
 | 
			
		||||
import { Select } from "./Inputs/Select";
 | 
			
		||||
import { TextInput } from "./Inputs/TextInput";
 | 
			
		||||
import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder";
 | 
			
		||||
import { MainPanel } from "./Panels/MainPanel";
 | 
			
		||||
import { Popup } from "./Popup";
 | 
			
		||||
 | 
			
		||||
@ -98,7 +105,7 @@ export function AppLayout(props: Props): JSX.Element {
 | 
			
		||||
        if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
 | 
			
		||||
        if (mainPanelOpen === true) {
 | 
			
		||||
          setMainPanelOpen(false);
 | 
			
		||||
        } else if (subPanel === true && contentPanel === true) {
 | 
			
		||||
        } else if (isDefined(subPanel) && isDefined(contentPanel)) {
 | 
			
		||||
          setSubPanelOpen(true);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@ -116,7 +123,7 @@ export function AppLayout(props: Props): JSX.Element {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const turnSubIntoContent = useMemo(
 | 
			
		||||
    () => isDefined(subPanel) && isDefined(contentPanel),
 | 
			
		||||
    () => isDefined(subPanel) && isUndefined(contentPanel),
 | 
			
		||||
    [contentPanel, subPanel]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
@ -173,11 +180,8 @@ export function AppLayout(props: Props): JSX.Element {
 | 
			
		||||
 | 
			
		||||
  const currencyOptions = useMemo(() => {
 | 
			
		||||
    const list: string[] = [];
 | 
			
		||||
    currencies.map((currentCurrency) => {
 | 
			
		||||
      if (
 | 
			
		||||
        currentCurrency.attributes &&
 | 
			
		||||
        isDefinedAndNotEmpty(currentCurrency.attributes.code)
 | 
			
		||||
      )
 | 
			
		||||
    filterHasAttributes(currencies).map((currentCurrency) => {
 | 
			
		||||
      if (isDefinedAndNotEmpty(currentCurrency.attributes.code))
 | 
			
		||||
        list.push(currentCurrency.attributes.code);
 | 
			
		||||
    });
 | 
			
		||||
    return list;
 | 
			
		||||
@ -283,15 +287,10 @@ export function AppLayout(props: Props): JSX.Element {
 | 
			
		||||
          {isDefined(contentPanel) ? (
 | 
			
		||||
            contentPanel
 | 
			
		||||
          ) : (
 | 
			
		||||
            <div className="grid h-full place-content-center">
 | 
			
		||||
              <div
 | 
			
		||||
                className="grid grid-flow-col place-items-center gap-9 rounded-2xl
 | 
			
		||||
                border-2 border-dotted border-dark p-8 text-dark opacity-40"
 | 
			
		||||
              >
 | 
			
		||||
                <p className="text-4xl">❮</p>
 | 
			
		||||
                <p className="w-64 text-2xl">{langui.select_option_sidebar}</p>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <ContentPlaceholder
 | 
			
		||||
              message={langui.select_option_sidebar ?? ""}
 | 
			
		||||
              icon={Icon.ChevronLeft}
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -299,11 +298,10 @@ export function AppLayout(props: Props): JSX.Element {
 | 
			
		||||
        {isDefined(subPanel) && (
 | 
			
		||||
          <div
 | 
			
		||||
            className={cJoin(
 | 
			
		||||
              `texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
 | 
			
		||||
              border-black bg-light transition-transform duration-300 [grid-area:sub]
 | 
			
		||||
              [scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
 | 
			
		||||
              mobile:justify-self-end mobile:border-r-0 mobile:border-l-[1px]
 | 
			
		||||
              mobile:[grid-area:content]`,
 | 
			
		||||
              `texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
 | 
			
		||||
              transition-transform duration-300 [grid-area:sub] [scrollbar-width:none]
 | 
			
		||||
              webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%] mobile:justify-self-end
 | 
			
		||||
              mobile:border-r-0 mobile:border-l-[1px] mobile:[grid-area:content]`,
 | 
			
		||||
              turnSubIntoContent
 | 
			
		||||
                ? "mobile:w-full mobile:border-l-0"
 | 
			
		||||
                : subPanelOpen === true
 | 
			
		||||
@ -318,10 +316,10 @@ export function AppLayout(props: Props): JSX.Element {
 | 
			
		||||
        {/* Main panel */}
 | 
			
		||||
        <div
 | 
			
		||||
          className={cJoin(
 | 
			
		||||
            `texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
 | 
			
		||||
            border-black bg-light transition-transform duration-300 [grid-area:main]
 | 
			
		||||
            [scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
 | 
			
		||||
            mobile:justify-self-start mobile:[grid-area:content]`,
 | 
			
		||||
            `texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
 | 
			
		||||
            transition-transform duration-300 [grid-area:main] [scrollbar-width:none]
 | 
			
		||||
            webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%] mobile:justify-self-start
 | 
			
		||||
            mobile:[grid-area:content]`,
 | 
			
		||||
            cIf(mainPanelOpen === false, "mobile:-translate-x-full")
 | 
			
		||||
          )}
 | 
			
		||||
        >
 | 
			
		||||
@ -399,8 +397,9 @@ export function AppLayout(props: Props): JSX.Element {
 | 
			
		||||
                      ])
 | 
			
		||||
                    }
 | 
			
		||||
                    onChange={(items) => {
 | 
			
		||||
                      const newPreferredLanguages = [...items].map(
 | 
			
		||||
                        ([code]) => code
 | 
			
		||||
                      const newPreferredLanguages = iterateMap(
 | 
			
		||||
                        items,
 | 
			
		||||
                        (code) => code
 | 
			
		||||
                      );
 | 
			
		||||
                      setPreferredLanguages(newPreferredLanguages);
 | 
			
		||||
                      if (router.locale !== newPreferredLanguages[0]) {
 | 
			
		||||
 | 
			
		||||
@ -63,9 +63,8 @@ export function Button(props: Props): JSX.Element {
 | 
			
		||||
            text-dark transition-all`,
 | 
			
		||||
            cIf(
 | 
			
		||||
              active,
 | 
			
		||||
              "!border-black bg-black text-light drop-shadow-black-lg",
 | 
			
		||||
              `cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg
 | 
			
		||||
              active:border-black active:bg-black active:text-light active:drop-shadow-black-lg`
 | 
			
		||||
              "!border-black bg-black !text-light drop-shadow-black-lg",
 | 
			
		||||
              "cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
 | 
			
		||||
            ),
 | 
			
		||||
            className
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { Icon } from "components/Ico";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { cJoin } from "helpers/className";
 | 
			
		||||
import { prettyLanguage } from "helpers/formatters";
 | 
			
		||||
import { iterateMap } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { Fragment } from "react";
 | 
			
		||||
import { ToolTip } from "../ToolTip";
 | 
			
		||||
@ -22,15 +23,13 @@ export function LanguageSwitcher(props: Props): JSX.Element {
 | 
			
		||||
    <ToolTip
 | 
			
		||||
      content={
 | 
			
		||||
        <div className={cJoin("flex flex-col gap-2", className)}>
 | 
			
		||||
          {[...locales].map(([locale, value], index) => (
 | 
			
		||||
          {iterateMap(locales, (locale, value, index) => (
 | 
			
		||||
            <Fragment key={index}>
 | 
			
		||||
              {locale && (
 | 
			
		||||
                <Button
 | 
			
		||||
                  active={value === localesIndex}
 | 
			
		||||
                  onClick={() => onLanguageChanged(value)}
 | 
			
		||||
                  text={prettyLanguage(locale, props.languages)}
 | 
			
		||||
                />
 | 
			
		||||
              )}
 | 
			
		||||
              <Button
 | 
			
		||||
                active={value === localesIndex}
 | 
			
		||||
                onClick={() => onLanguageChanged(value)}
 | 
			
		||||
                text={prettyLanguage(locale, props.languages)}
 | 
			
		||||
              />
 | 
			
		||||
            </Fragment>
 | 
			
		||||
          ))}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Ico, Icon } from "components/Ico";
 | 
			
		||||
import { arrayMove, isDefinedAndNotEmpty } from "helpers/others";
 | 
			
		||||
import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { Fragment, useCallback, useState } from "react";
 | 
			
		||||
 | 
			
		||||
@ -16,17 +16,16 @@ export function OrderableList(props: Props): JSX.Element {
 | 
			
		||||
 | 
			
		||||
  const updateOrder = useCallback(
 | 
			
		||||
    (sourceIndex: number, targetIndex: number) => {
 | 
			
		||||
      const newItems = arrayMove([...items], sourceIndex, targetIndex);
 | 
			
		||||
      const map = new Map(newItems);
 | 
			
		||||
      setItems(map);
 | 
			
		||||
      onChange?.(map);
 | 
			
		||||
      const newItems = mapMoveEntry(items, sourceIndex, targetIndex);
 | 
			
		||||
      setItems(newItems);
 | 
			
		||||
      onChange?.(newItems);
 | 
			
		||||
    },
 | 
			
		||||
    [items, onChange]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="grid gap-2">
 | 
			
		||||
      {[...items].map(([key, value], index) => (
 | 
			
		||||
      {iterateMap(items, (key, value, index) => (
 | 
			
		||||
        <Fragment key={key}>
 | 
			
		||||
          {props.insertLabels &&
 | 
			
		||||
            isDefinedAndNotEmpty(props.insertLabels.get(index)) && (
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,7 @@ export function Select(props: Props): JSX.Element {
 | 
			
		||||
            className="!text-xs"
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              setState(-1);
 | 
			
		||||
              toggleOpened();
 | 
			
		||||
              setOpened(false);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ interface Props {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Switch(props: Props): JSX.Element {
 | 
			
		||||
  const { state, setState, className, disabled } = props;
 | 
			
		||||
  const { state, setState, className, disabled = false } = props;
 | 
			
		||||
  const toggleState = useToggle(setState);
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
@ -26,7 +26,7 @@ export function Switch(props: Props): JSX.Element {
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      onClick={() => {
 | 
			
		||||
        if (disabled === false) toggleState();
 | 
			
		||||
        if (!disabled) toggleState();
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,9 @@ import { Ico, Icon } from "components/Ico";
 | 
			
		||||
import { Button } from "components/Inputs/Button";
 | 
			
		||||
import { GetLibraryItemQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { cIf, cJoin } from "helpers/className";
 | 
			
		||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
 | 
			
		||||
import { filterHasAttributes } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { useToggle } from "hooks/useToggle";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
@ -29,9 +31,10 @@ export function ContentLine(props: Props): JSX.Element {
 | 
			
		||||
  if (content.attributes) {
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        className={`grid gap-2 rounded-lg px-4 ${
 | 
			
		||||
          opened && "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade"
 | 
			
		||||
        }`}
 | 
			
		||||
        className={cJoin(
 | 
			
		||||
          "grid gap-2 rounded-lg px-4",
 | 
			
		||||
          cIf(opened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center
 | 
			
		||||
@ -52,11 +55,11 @@ export function ContentLine(props: Props): JSX.Element {
 | 
			
		||||
            </h3>
 | 
			
		||||
          </a>
 | 
			
		||||
          <div className="flex flex-row flex-wrap gap-1">
 | 
			
		||||
            {content.attributes.content?.data?.attributes?.categories?.data.map(
 | 
			
		||||
              (category) => (
 | 
			
		||||
                <Chip key={category.id}>{category.attributes?.short}</Chip>
 | 
			
		||||
              )
 | 
			
		||||
            )}
 | 
			
		||||
            {filterHasAttributes(
 | 
			
		||||
              content.attributes.content?.data?.attributes?.categories?.data
 | 
			
		||||
            ).map((category) => (
 | 
			
		||||
              <Chip key={category.id}>{category.attributes.short}</Chip>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
          <p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
 | 
			
		||||
          <p>
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,15 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
 | 
			
		||||
import { isInteger } from "helpers/numbers";
 | 
			
		||||
import { getStatusDescription, isDefinedAndNotEmpty } from "helpers/others";
 | 
			
		||||
import {
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  getStatusDescription,
 | 
			
		||||
  isDefined,
 | 
			
		||||
  isDefinedAndNotEmpty,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
 | 
			
		||||
import { Fragment } from "react";
 | 
			
		||||
import { Fragment, useMemo } from "react";
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  openLightBox: (images: string[], index?: number) => void;
 | 
			
		||||
@ -81,9 +86,14 @@ export function ScanSet(props: Props): JSX.Element {
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const pages = useMemo(
 | 
			
		||||
    () => selectedScan && filterHasAttributes(selectedScan.pages?.data),
 | 
			
		||||
    [selectedScan]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      {selectedScan && (
 | 
			
		||||
      {selectedScan && isDefined(pages) && (
 | 
			
		||||
        <div>
 | 
			
		||||
          <div
 | 
			
		||||
            className="flex flex-row flex-wrap place-items-center
 | 
			
		||||
@ -126,16 +136,16 @@ export function ScanSet(props: Props): JSX.Element {
 | 
			
		||||
              <div>
 | 
			
		||||
                <p className="font-headers">{"Scanners"}:</p>
 | 
			
		||||
                <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                  {selectedScan.scanners.data.map((scanner) => (
 | 
			
		||||
                    <Fragment key={scanner.id}>
 | 
			
		||||
                      {scanner.attributes && (
 | 
			
		||||
                  {filterHasAttributes(selectedScan.scanners.data).map(
 | 
			
		||||
                    (scanner) => (
 | 
			
		||||
                      <Fragment key={scanner.id}>
 | 
			
		||||
                        <RecorderChip
 | 
			
		||||
                          langui={langui}
 | 
			
		||||
                          recorder={scanner.attributes}
 | 
			
		||||
                        />
 | 
			
		||||
                      )}
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    )
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
@ -144,16 +154,18 @@ export function ScanSet(props: Props): JSX.Element {
 | 
			
		||||
              <div>
 | 
			
		||||
                <p className="font-headers">{"Cleaners"}:</p>
 | 
			
		||||
                <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                  {selectedScan.cleaners.data.map((cleaner) => (
 | 
			
		||||
                    <Fragment key={cleaner.id}>
 | 
			
		||||
                      {cleaner.attributes && (
 | 
			
		||||
                        <RecorderChip
 | 
			
		||||
                          langui={langui}
 | 
			
		||||
                          recorder={cleaner.attributes}
 | 
			
		||||
                        />
 | 
			
		||||
                      )}
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                  {filterHasAttributes(selectedScan.cleaners.data).map(
 | 
			
		||||
                    (cleaner) => (
 | 
			
		||||
                      <Fragment key={cleaner.id}>
 | 
			
		||||
                        {cleaner.attributes && (
 | 
			
		||||
                          <RecorderChip
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                            recorder={cleaner.attributes}
 | 
			
		||||
                          />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    )
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
@ -163,16 +175,18 @@ export function ScanSet(props: Props): JSX.Element {
 | 
			
		||||
                <div>
 | 
			
		||||
                  <p className="font-headers">{"Typesetters"}:</p>
 | 
			
		||||
                  <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                    {selectedScan.typesetters.data.map((typesetter) => (
 | 
			
		||||
                      <Fragment key={typesetter.id}>
 | 
			
		||||
                        {typesetter.attributes && (
 | 
			
		||||
                          <RecorderChip
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                            recorder={typesetter.attributes}
 | 
			
		||||
                          />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    ))}
 | 
			
		||||
                    {filterHasAttributes(selectedScan.typesetters.data).map(
 | 
			
		||||
                      (typesetter) => (
 | 
			
		||||
                        <Fragment key={typesetter.id}>
 | 
			
		||||
                          {typesetter.attributes && (
 | 
			
		||||
                            <RecorderChip
 | 
			
		||||
                              langui={langui}
 | 
			
		||||
                              recorder={typesetter.attributes}
 | 
			
		||||
                            />
 | 
			
		||||
                          )}
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                      )
 | 
			
		||||
                    )}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
@ -188,18 +202,15 @@ export function ScanSet(props: Props): JSX.Element {
 | 
			
		||||
            className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
 | 
			
		||||
             desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] mobile:grid-cols-2"
 | 
			
		||||
          >
 | 
			
		||||
            {selectedScan.pages?.data.map((page, index) => (
 | 
			
		||||
            {pages.map((page, index) => (
 | 
			
		||||
              <div
 | 
			
		||||
                key={page.id}
 | 
			
		||||
                className="cursor-pointer transition-transform
 | 
			
		||||
                drop-shadow-shade-lg hover:scale-[1.02]"
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  const images: string[] = [];
 | 
			
		||||
                  selectedScan.pages?.data.map((image) => {
 | 
			
		||||
                    if (
 | 
			
		||||
                      image.attributes &&
 | 
			
		||||
                      isDefinedAndNotEmpty(image.attributes.url)
 | 
			
		||||
                    )
 | 
			
		||||
                  pages.map((image) => {
 | 
			
		||||
                    if (isDefinedAndNotEmpty(image.attributes.url))
 | 
			
		||||
                      images.push(
 | 
			
		||||
                        getAssetURL(image.attributes.url, ImageQuality.Large)
 | 
			
		||||
                      );
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ import {
 | 
			
		||||
} from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getAssetURL, ImageQuality } from "helpers/img";
 | 
			
		||||
import { getStatusDescription } from "helpers/others";
 | 
			
		||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
 | 
			
		||||
import { Fragment } from "react";
 | 
			
		||||
@ -87,16 +87,16 @@ export function ScanSetCover(props: Props): JSX.Element {
 | 
			
		||||
                <div>
 | 
			
		||||
                  <p className="font-headers">{"Scanners"}:</p>
 | 
			
		||||
                  <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                    {selectedScan.scanners.data.map((scanner) => (
 | 
			
		||||
                      <Fragment key={scanner.id}>
 | 
			
		||||
                        {scanner.attributes && (
 | 
			
		||||
                    {filterHasAttributes(selectedScan.scanners.data).map(
 | 
			
		||||
                      (scanner) => (
 | 
			
		||||
                        <Fragment key={scanner.id}>
 | 
			
		||||
                          <RecorderChip
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                            recorder={scanner.attributes}
 | 
			
		||||
                          />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    ))}
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                      )
 | 
			
		||||
                    )}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
@ -105,16 +105,16 @@ export function ScanSetCover(props: Props): JSX.Element {
 | 
			
		||||
                <div>
 | 
			
		||||
                  <p className="font-headers">{"Cleaners"}:</p>
 | 
			
		||||
                  <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                    {selectedScan.cleaners.data.map((cleaner) => (
 | 
			
		||||
                      <Fragment key={cleaner.id}>
 | 
			
		||||
                        {cleaner.attributes && (
 | 
			
		||||
                    {filterHasAttributes(selectedScan.cleaners.data).map(
 | 
			
		||||
                      (cleaner) => (
 | 
			
		||||
                        <Fragment key={cleaner.id}>
 | 
			
		||||
                          <RecorderChip
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                            recorder={cleaner.attributes}
 | 
			
		||||
                          />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    ))}
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                      )
 | 
			
		||||
                    )}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
@ -124,16 +124,16 @@ export function ScanSetCover(props: Props): JSX.Element {
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <p className="font-headers">{"Typesetters"}:</p>
 | 
			
		||||
                    <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                      {selectedScan.typesetters.data.map((typesetter) => (
 | 
			
		||||
                        <Fragment key={typesetter.id}>
 | 
			
		||||
                          {typesetter.attributes && (
 | 
			
		||||
                      {filterHasAttributes(selectedScan.typesetters.data).map(
 | 
			
		||||
                        (typesetter) => (
 | 
			
		||||
                          <Fragment key={typesetter.id}>
 | 
			
		||||
                            <RecorderChip
 | 
			
		||||
                              langui={langui}
 | 
			
		||||
                              recorder={typesetter.attributes}
 | 
			
		||||
                            />
 | 
			
		||||
                          )}
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                      ))}
 | 
			
		||||
                          </Fragment>
 | 
			
		||||
                        )
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                )}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								src/components/PanelComponents/ContentPlaceholder.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/components/PanelComponents/ContentPlaceholder.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
import { Ico, Icon } from "components/Ico";
 | 
			
		||||
import { cIf, cJoin } from "helpers/className";
 | 
			
		||||
import { isDefined } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  message: string;
 | 
			
		||||
  icon?: Icon;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function ContentPlaceholder(props: Props): JSX.Element {
 | 
			
		||||
  const { message, icon } = props;
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="grid h-full place-content-center">
 | 
			
		||||
      <div
 | 
			
		||||
        className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
 | 
			
		||||
        border-dark p-8 text-dark opacity-40"
 | 
			
		||||
      >
 | 
			
		||||
        {isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
 | 
			
		||||
        <p
 | 
			
		||||
          className={cJoin(
 | 
			
		||||
            "w-64 text-2xl",
 | 
			
		||||
            cIf(!isDefined(icon), "text-center")
 | 
			
		||||
          )}
 | 
			
		||||
        >
 | 
			
		||||
          {message}
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -17,7 +17,15 @@ interface Props {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function NavOption(props: Props): JSX.Element {
 | 
			
		||||
  const { url, icon, title, subtitle, border, reduced, onClick } = props;
 | 
			
		||||
  const {
 | 
			
		||||
    url,
 | 
			
		||||
    icon,
 | 
			
		||||
    title,
 | 
			
		||||
    subtitle,
 | 
			
		||||
    border = false,
 | 
			
		||||
    reduced = false,
 | 
			
		||||
    onClick,
 | 
			
		||||
  } = props;
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
  const isActive = useMemo(
 | 
			
		||||
    () => router.asPath.startsWith(url),
 | 
			
		||||
@ -36,7 +44,7 @@ export function NavOption(props: Props): JSX.Element {
 | 
			
		||||
      }
 | 
			
		||||
      placement="right"
 | 
			
		||||
      className="text-left"
 | 
			
		||||
      disabled={reduced === false}
 | 
			
		||||
      disabled={!reduced}
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        onClick={(event) => {
 | 
			
		||||
@ -63,7 +71,7 @@ export function NavOption(props: Props): JSX.Element {
 | 
			
		||||
      >
 | 
			
		||||
        {icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />}
 | 
			
		||||
 | 
			
		||||
        {reduced === false && (
 | 
			
		||||
        {!reduced && (
 | 
			
		||||
          <div>
 | 
			
		||||
            <h3 className="text-2xl">{title}</h3>
 | 
			
		||||
            {isDefinedAndNotEmpty(subtitle) && (
 | 
			
		||||
 | 
			
		||||
@ -15,10 +15,10 @@ export function ContentPanel(props: Props): JSX.Element {
 | 
			
		||||
  const { width = ContentPanelWidthSizes.Default, children } = props;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={`grid px-4 pt-10 pb-20 desktop:py-20 desktop:px-10`}>
 | 
			
		||||
    <div className={`grid h-full px-4 desktop:px-10`}>
 | 
			
		||||
      <main
 | 
			
		||||
        className={cJoin(
 | 
			
		||||
          "place-self-center",
 | 
			
		||||
          "justify-self-center pt-10 pb-20 desktop:pt-20 desktop:pb-32",
 | 
			
		||||
          width === ContentPanelWidthSizes.Default
 | 
			
		||||
            ? "max-w-2xl"
 | 
			
		||||
            : width === ContentPanelWidthSizes.Large
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ export function MainPanel(props: Props): JSX.Element {
 | 
			
		||||
          </Link>
 | 
			
		||||
 | 
			
		||||
          {(!mainPanelReduced || !isDesktop) && (
 | 
			
		||||
            <h2 className="text-3xl">Accord’s Library</h2>
 | 
			
		||||
            <h2 className="mb-4 text-3xl">Accord’s Library</h2>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          <div
 | 
			
		||||
@ -155,7 +155,7 @@ export function MainPanel(props: Props): JSX.Element {
 | 
			
		||||
      */}
 | 
			
		||||
 | 
			
		||||
      <NavOption
 | 
			
		||||
        url="https://gallery.accords-library.com/"
 | 
			
		||||
        url="https://gallery.accords-library.com/posts/"
 | 
			
		||||
        icon={Icon.Collections}
 | 
			
		||||
        title={langui.gallery}
 | 
			
		||||
        reduced={mainPanelReduced && isDesktop}
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ export function Popup(props: Props): JSX.Element {
 | 
			
		||||
  const { setMenuGestures } = useAppLayout();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setMenuGestures(state);
 | 
			
		||||
    setMenuGestures(!state);
 | 
			
		||||
  }, [setMenuGestures, state]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getDescription } from "helpers/description";
 | 
			
		||||
import { prettySlug } from "helpers/formatters";
 | 
			
		||||
import { getStatusDescription } from "helpers/others";
 | 
			
		||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
 | 
			
		||||
import { PostWithTranslations } from "helpers/types";
 | 
			
		||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
 | 
			
		||||
import { Fragment, useMemo } from "react";
 | 
			
		||||
@ -102,14 +102,12 @@ export function PostPage(props: Props): JSX.Element {
 | 
			
		||||
              <div>
 | 
			
		||||
                <p className="font-headers">{"Authors"}:</p>
 | 
			
		||||
                <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                  {post.authors.data.map((author) => (
 | 
			
		||||
                  {filterHasAttributes(post.authors.data).map((author) => (
 | 
			
		||||
                    <Fragment key={author.id}>
 | 
			
		||||
                      {author.attributes && (
 | 
			
		||||
                        <RecorderChip
 | 
			
		||||
                          langui={langui}
 | 
			
		||||
                          recorder={author.attributes}
 | 
			
		||||
                        />
 | 
			
		||||
                      )}
 | 
			
		||||
                      <RecorderChip
 | 
			
		||||
                        langui={langui}
 | 
			
		||||
                        recorder={author.attributes}
 | 
			
		||||
                      />
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
@ -244,7 +244,8 @@ export function PreviewCard(props: Props): JSX.Element {
 | 
			
		||||
            cIf(
 | 
			
		||||
              !keepInfoVisible,
 | 
			
		||||
              `-inset-x-0.5 bottom-2 opacity-0 group-hover:opacity-100 hoverable:absolute
 | 
			
		||||
              hoverable:drop-shadow-shade-lg notHoverable:rounded-b-md`
 | 
			
		||||
              hoverable:drop-shadow-shade-lg notHoverable:opacity-100
 | 
			
		||||
              notHoverable:rounded-b-md`
 | 
			
		||||
            )
 | 
			
		||||
          )}
 | 
			
		||||
        >
 | 
			
		||||
@ -302,9 +303,7 @@ interface TranslatedProps
 | 
			
		||||
  languages: AppStaticProps["languages"];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function TranslatedPreviewCard(
 | 
			
		||||
  props: Immutable<TranslatedProps>
 | 
			
		||||
): JSX.Element {
 | 
			
		||||
export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element {
 | 
			
		||||
  const {
 | 
			
		||||
    translations = [{ title: props.slug, language: "default" }],
 | 
			
		||||
    slug,
 | 
			
		||||
 | 
			
		||||
@ -89,7 +89,7 @@ interface TranslatedProps
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function TranslatedPreviewLine(
 | 
			
		||||
  props: Immutable<TranslatedProps>
 | 
			
		||||
  props: TranslatedProps
 | 
			
		||||
): JSX.Element {
 | 
			
		||||
  const {
 | 
			
		||||
    translations = [{ title: props.slug, language: "default" }],
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { Chip } from "components/Chip";
 | 
			
		||||
import { RecorderChipFragment } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { ImageQuality } from "helpers/img";
 | 
			
		||||
import { filterHasAttributes } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { Fragment } from "react";
 | 
			
		||||
import { Img } from "./Img";
 | 
			
		||||
@ -33,13 +34,13 @@ export function RecorderChip(props: Props): JSX.Element {
 | 
			
		||||
              {recorder.languages?.data && recorder.languages.data.length > 0 && (
 | 
			
		||||
                <div className="flex flex-row flex-wrap gap-1">
 | 
			
		||||
                  <p>{langui.languages}:</p>
 | 
			
		||||
                  {recorder.languages.data.map((language) => (
 | 
			
		||||
                    <Fragment key={language.attributes?.code}>
 | 
			
		||||
                      {language.attributes && (
 | 
			
		||||
                  {filterHasAttributes(recorder.languages.data).map(
 | 
			
		||||
                    (language) => (
 | 
			
		||||
                      <Fragment key={language.attributes.code}>
 | 
			
		||||
                        <Chip>{language.attributes.code.toUpperCase()}</Chip>
 | 
			
		||||
                      )}
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    )
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
              {recorder.pronouns && (
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import { GetContentTextQuery, UploadImageFragment } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters";
 | 
			
		||||
import { getAssetURL, ImageQuality } from "helpers/img";
 | 
			
		||||
import { filterHasAttributes } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { useLightBox } from "hooks/useLightBox";
 | 
			
		||||
 | 
			
		||||
@ -89,9 +90,11 @@ export function ThumbnailHeader(props: Props): JSX.Element {
 | 
			
		||||
          <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">
 | 
			
		||||
              {categories.data.map((category) => (
 | 
			
		||||
                <Chip key={category.id}>{category.attributes?.name}</Chip>
 | 
			
		||||
              ))}
 | 
			
		||||
              {filterHasAttributes(categories.data).map(
 | 
			
		||||
                (category) => (
 | 
			
		||||
                  <Chip key={category.id}>{category.attributes.name}</Chip>
 | 
			
		||||
                )
 | 
			
		||||
              )}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,11 @@ import {
 | 
			
		||||
  GetChronologyItemsQuery,
 | 
			
		||||
} from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getStatusDescription } from "helpers/others";
 | 
			
		||||
import {
 | 
			
		||||
  filterDefined,
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  getStatusDescription,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { Fragment } from "react";
 | 
			
		||||
 | 
			
		||||
@ -44,13 +48,16 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
 | 
			
		||||
        </p>
 | 
			
		||||
 | 
			
		||||
        <div className="col-start-2 row-span-2 row-start-1 grid gap-4">
 | 
			
		||||
          {props.item.attributes.events?.map((event) => (
 | 
			
		||||
            <Fragment key={event?.id}>
 | 
			
		||||
              {event && (
 | 
			
		||||
          {props.item.attributes.events &&
 | 
			
		||||
            filterHasAttributes(props.item.attributes.events, [
 | 
			
		||||
              "id",
 | 
			
		||||
              "translations",
 | 
			
		||||
            ]).map((event) => (
 | 
			
		||||
              <Fragment key={event.id}>
 | 
			
		||||
                <div className="m-0">
 | 
			
		||||
                  {event.translations?.map((translation, translationIndex) => (
 | 
			
		||||
                    <Fragment key={translationIndex}>
 | 
			
		||||
                      {translation && (
 | 
			
		||||
                  {filterDefined(event.translations).map(
 | 
			
		||||
                    (translation, translationIndex) => (
 | 
			
		||||
                      <Fragment key={translationIndex}>
 | 
			
		||||
                        <Fragment>
 | 
			
		||||
                          <div
 | 
			
		||||
                            className="grid
 | 
			
		||||
@ -94,9 +101,9 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
 | 
			
		||||
                            ""
 | 
			
		||||
                          )}
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                      )}
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    )
 | 
			
		||||
                  )}
 | 
			
		||||
 | 
			
		||||
                  <p className="mt-1 grid grid-flow-col gap-1 place-self-start text-xs text-dark">
 | 
			
		||||
                    {event.source?.data ? (
 | 
			
		||||
@ -109,9 +116,8 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
 | 
			
		||||
                    )}
 | 
			
		||||
                  </p>
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
            </Fragment>
 | 
			
		||||
          ))}
 | 
			
		||||
              </Fragment>
 | 
			
		||||
            ))}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import { GetLibraryItemsPreviewQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { prettyinlineTitle, prettyDate } from "./formatters";
 | 
			
		||||
import { convertPrice } from "./numbers";
 | 
			
		||||
import { isDefined } from "./others";
 | 
			
		||||
import { isDefined, mapRemoveEmptyValues } from "./others";
 | 
			
		||||
import { LibraryItemUserStatus } from "./types";
 | 
			
		||||
type Items = NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
 | 
			
		||||
type GroupLibraryItems = Map<string, Items>;
 | 
			
		||||
@ -13,67 +13,67 @@ export function getGroups(
 | 
			
		||||
  groupByType: number,
 | 
			
		||||
  items: Items
 | 
			
		||||
): GroupLibraryItems {
 | 
			
		||||
  const groups: GroupLibraryItems = new Map();
 | 
			
		||||
 | 
			
		||||
  switch (groupByType) {
 | 
			
		||||
    case 0: {
 | 
			
		||||
      const typeGroup = new Map();
 | 
			
		||||
      typeGroup.set("Drakengard 1", []);
 | 
			
		||||
      typeGroup.set("Drakengard 1.3", []);
 | 
			
		||||
      typeGroup.set("Drakengard 2", []);
 | 
			
		||||
      typeGroup.set("Drakengard 3", []);
 | 
			
		||||
      typeGroup.set("Drakengard 4", []);
 | 
			
		||||
      typeGroup.set("NieR Gestalt", []);
 | 
			
		||||
      typeGroup.set("NieR Replicant", []);
 | 
			
		||||
      typeGroup.set("NieR Replicant ver.1.22474487139...", []);
 | 
			
		||||
      typeGroup.set("NieR:Automata", []);
 | 
			
		||||
      typeGroup.set("NieR Re[in]carnation", []);
 | 
			
		||||
      typeGroup.set("SINoALICE", []);
 | 
			
		||||
      typeGroup.set("Voice of Cards", []);
 | 
			
		||||
      typeGroup.set("Final Fantasy XIV", []);
 | 
			
		||||
      typeGroup.set("Thou Shalt Not Die", []);
 | 
			
		||||
      typeGroup.set("Bakuken", []);
 | 
			
		||||
      typeGroup.set("YoRHa", []);
 | 
			
		||||
      typeGroup.set("YoRHa Boys", []);
 | 
			
		||||
      typeGroup.set(langui.no_category, []);
 | 
			
		||||
      const noCategory = langui.no_category ?? "No category";
 | 
			
		||||
      groups.set("Drakengard 1", []);
 | 
			
		||||
      groups.set("Drakengard 1.3", []);
 | 
			
		||||
      groups.set("Drakengard 2", []);
 | 
			
		||||
      groups.set("Drakengard 3", []);
 | 
			
		||||
      groups.set("Drakengard 4", []);
 | 
			
		||||
      groups.set("NieR Gestalt", []);
 | 
			
		||||
      groups.set("NieR Replicant", []);
 | 
			
		||||
      groups.set("NieR Replicant ver.1.22474487139...", []);
 | 
			
		||||
      groups.set("NieR:Automata", []);
 | 
			
		||||
      groups.set("NieR Re[in]carnation", []);
 | 
			
		||||
      groups.set("SINoALICE", []);
 | 
			
		||||
      groups.set("Voice of Cards", []);
 | 
			
		||||
      groups.set("Final Fantasy XIV", []);
 | 
			
		||||
      groups.set("Thou Shalt Not Die", []);
 | 
			
		||||
      groups.set("Bakuken", []);
 | 
			
		||||
      groups.set("YoRHa", []);
 | 
			
		||||
      groups.set("YoRHa Boys", []);
 | 
			
		||||
      groups.set(noCategory, []);
 | 
			
		||||
 | 
			
		||||
      items.map((item) => {
 | 
			
		||||
        if (item.attributes?.categories?.data.length === 0) {
 | 
			
		||||
          typeGroup.get(langui.no_category)?.push(item);
 | 
			
		||||
          groups.get(noCategory)?.push(item);
 | 
			
		||||
        } else {
 | 
			
		||||
          item.attributes?.categories?.data.map((category) => {
 | 
			
		||||
            typeGroup.get(category.attributes?.name)?.push(item);
 | 
			
		||||
            groups.get(category.attributes?.name ?? noCategory)?.push(item);
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return typeGroup;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case 1: {
 | 
			
		||||
      const group = new Map();
 | 
			
		||||
      group.set(langui.audio ?? "Audio", []);
 | 
			
		||||
      group.set(langui.game ?? "Game", []);
 | 
			
		||||
      group.set(langui.textual ?? "Textual", []);
 | 
			
		||||
      group.set(langui.video ?? "Video", []);
 | 
			
		||||
      group.set(langui.other ?? "Other", []);
 | 
			
		||||
      group.set(langui.group ?? "Group", []);
 | 
			
		||||
      group.set(langui.no_type ?? "No type", []);
 | 
			
		||||
      groups.set(langui.audio ?? "Audio", []);
 | 
			
		||||
      groups.set(langui.game ?? "Game", []);
 | 
			
		||||
      groups.set(langui.textual ?? "Textual", []);
 | 
			
		||||
      groups.set(langui.video ?? "Video", []);
 | 
			
		||||
      groups.set(langui.other ?? "Other", []);
 | 
			
		||||
      groups.set(langui.group ?? "Group", []);
 | 
			
		||||
      groups.set(langui.no_type ?? "No type", []);
 | 
			
		||||
      items.map((item) => {
 | 
			
		||||
        if (item.attributes?.metadata && item.attributes.metadata.length > 0) {
 | 
			
		||||
          switch (item.attributes.metadata[0]?.__typename) {
 | 
			
		||||
            case "ComponentMetadataAudio":
 | 
			
		||||
              group.get(langui.audio ?? "Audio")?.push(item);
 | 
			
		||||
              groups.get(langui.audio ?? "Audio")?.push(item);
 | 
			
		||||
              break;
 | 
			
		||||
            case "ComponentMetadataGame":
 | 
			
		||||
              group.get(langui.game ?? "Game")?.push(item);
 | 
			
		||||
              groups.get(langui.game ?? "Game")?.push(item);
 | 
			
		||||
              break;
 | 
			
		||||
            case "ComponentMetadataBooks":
 | 
			
		||||
              group.get(langui.textual ?? "Textual")?.push(item);
 | 
			
		||||
              groups.get(langui.textual ?? "Textual")?.push(item);
 | 
			
		||||
              break;
 | 
			
		||||
            case "ComponentMetadataVideo":
 | 
			
		||||
              group.get(langui.video ?? "Video")?.push(item);
 | 
			
		||||
              groups.get(langui.video ?? "Video")?.push(item);
 | 
			
		||||
              break;
 | 
			
		||||
            case "ComponentMetadataOther":
 | 
			
		||||
              group.get(langui.other ?? "Other")?.push(item);
 | 
			
		||||
              groups.get(langui.other ?? "Other")?.push(item);
 | 
			
		||||
              break;
 | 
			
		||||
            case "ComponentMetadataGroup":
 | 
			
		||||
              switch (
 | 
			
		||||
@ -81,19 +81,19 @@ export function getGroups(
 | 
			
		||||
                  ?.slug
 | 
			
		||||
              ) {
 | 
			
		||||
                case "audio":
 | 
			
		||||
                  group.get(langui.audio ?? "Audio")?.push(item);
 | 
			
		||||
                  groups.get(langui.audio ?? "Audio")?.push(item);
 | 
			
		||||
                  break;
 | 
			
		||||
                case "video":
 | 
			
		||||
                  group.get(langui.video ?? "Video")?.push(item);
 | 
			
		||||
                  groups.get(langui.video ?? "Video")?.push(item);
 | 
			
		||||
                  break;
 | 
			
		||||
                case "game":
 | 
			
		||||
                  group.get(langui.game ?? "Game")?.push(item);
 | 
			
		||||
                  groups.get(langui.game ?? "Game")?.push(item);
 | 
			
		||||
                  break;
 | 
			
		||||
                case "textual":
 | 
			
		||||
                  group.get(langui.textual ?? "Textual")?.push(item);
 | 
			
		||||
                  groups.get(langui.textual ?? "Textual")?.push(item);
 | 
			
		||||
                  break;
 | 
			
		||||
                case "mixed":
 | 
			
		||||
                  group.get(langui.group ?? "Group")?.push(item);
 | 
			
		||||
                  groups.get(langui.group ?? "Group")?.push(item);
 | 
			
		||||
                  break;
 | 
			
		||||
                default: {
 | 
			
		||||
                  throw new Error(
 | 
			
		||||
@ -107,10 +107,10 @@ export function getGroups(
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          group.get(langui.no_type ?? "No type")?.push(item);
 | 
			
		||||
          groups.get(langui.no_type ?? "No type")?.push(item);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      return group;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case 2: {
 | 
			
		||||
@ -121,29 +121,28 @@ export function getGroups(
 | 
			
		||||
            years.push(item.attributes.release_date.year);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      const group = new Map();
 | 
			
		||||
 | 
			
		||||
      years.sort((a, b) => a - b);
 | 
			
		||||
      years.map((year) => {
 | 
			
		||||
        group.set(year.toString(), []);
 | 
			
		||||
        groups.set(year.toString(), []);
 | 
			
		||||
      });
 | 
			
		||||
      group.set(langui.no_year ?? "No year", []);
 | 
			
		||||
      groups.set(langui.no_year ?? "No year", []);
 | 
			
		||||
      items.map((item) => {
 | 
			
		||||
        if (item.attributes?.release_date?.year) {
 | 
			
		||||
          group.get(item.attributes.release_date.year.toString())?.push(item);
 | 
			
		||||
          groups.get(item.attributes.release_date.year.toString())?.push(item);
 | 
			
		||||
        } else {
 | 
			
		||||
          group.get(langui.no_year ?? "No year")?.push(item);
 | 
			
		||||
          groups.get(langui.no_year ?? "No year")?.push(item);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return group;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default: {
 | 
			
		||||
      const group = new Map();
 | 
			
		||||
      group.set("", items);
 | 
			
		||||
      return group;
 | 
			
		||||
      groups.set("", items);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return mapRemoveEmptyValues(groups);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function filterItems(
 | 
			
		||||
@ -155,7 +154,7 @@ export function filterItems(
 | 
			
		||||
  showSecondaryItems: boolean,
 | 
			
		||||
  filterUserStatus: LibraryItemUserStatus | undefined
 | 
			
		||||
): Items {
 | 
			
		||||
  return [...items].filter((item) => {
 | 
			
		||||
  return items.filter((item) => {
 | 
			
		||||
    if (!showSubitems && !item.attributes?.root_item) return false;
 | 
			
		||||
    if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) {
 | 
			
		||||
      return false;
 | 
			
		||||
@ -212,7 +211,7 @@ export function sortBy(
 | 
			
		||||
): Items {
 | 
			
		||||
  switch (orderByType) {
 | 
			
		||||
    case 0:
 | 
			
		||||
      return [...items].sort((a, b) => {
 | 
			
		||||
      return items.sort((a, b) => {
 | 
			
		||||
        const titleA = prettyinlineTitle(
 | 
			
		||||
          "",
 | 
			
		||||
          a.attributes?.title,
 | 
			
		||||
@ -226,7 +225,7 @@ export function sortBy(
 | 
			
		||||
        return titleA.localeCompare(titleB);
 | 
			
		||||
      });
 | 
			
		||||
    case 1:
 | 
			
		||||
      return [...items].sort((a, b) => {
 | 
			
		||||
      return items.sort((a, b) => {
 | 
			
		||||
        const priceA = a.attributes?.price
 | 
			
		||||
          ? convertPrice(a.attributes.price, currencies[0])
 | 
			
		||||
          : 99999;
 | 
			
		||||
@ -236,7 +235,7 @@ export function sortBy(
 | 
			
		||||
        return priceA - priceB;
 | 
			
		||||
      });
 | 
			
		||||
    case 2:
 | 
			
		||||
      return [...items].sort((a, b) => {
 | 
			
		||||
      return items.sort((a, b) => {
 | 
			
		||||
        const dateA = a.attributes?.release_date
 | 
			
		||||
          ? prettyDate(a.attributes.release_date)
 | 
			
		||||
          : "9999";
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import {
 | 
			
		||||
  GetLibraryItemScansQuery,
 | 
			
		||||
} from "graphql/generated";
 | 
			
		||||
import { AppStaticProps } from "../graphql/getAppStaticProps";
 | 
			
		||||
import { SelectiveRequiredNonNullable } from "./types";
 | 
			
		||||
 | 
			
		||||
type SortContentProps =
 | 
			
		||||
  | NonNullable<
 | 
			
		||||
@ -59,11 +60,6 @@ export function getStatusDescription(
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function arrayMove<T>(arr: T[], old_index: number, new_index: number) {
 | 
			
		||||
  arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
 | 
			
		||||
  return arr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isDefined<T>(t: T): t is NonNullable<T> {
 | 
			
		||||
  return t !== null && t !== undefined;
 | 
			
		||||
}
 | 
			
		||||
@ -78,6 +74,51 @@ export function isDefinedAndNotEmpty(
 | 
			
		||||
  return isDefined(string) && string.length > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function filterDefined<T>(t: T[]): NonNullable<T>[] {
 | 
			
		||||
export function filterDefined<T>(t: T[] | undefined | null): NonNullable<T>[] {
 | 
			
		||||
  if (isUndefined(t)) return [];
 | 
			
		||||
  return t.filter((item) => isDefined(item)) as NonNullable<T>[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function filterHasAttributes<T, P extends keyof NonNullable<T>>(
 | 
			
		||||
  t: T[] | undefined | null,
 | 
			
		||||
  attributes?: P[]
 | 
			
		||||
): SelectiveRequiredNonNullable<NonNullable<T>, P>[] {
 | 
			
		||||
  if (isUndefined(t)) return [];
 | 
			
		||||
  return t.filter((item) => {
 | 
			
		||||
    if (isDefined(item)) {
 | 
			
		||||
      const attributesToCheck = attributes ?? (Object.keys(item) as P[]);
 | 
			
		||||
      return attributesToCheck.every((attribute) => isDefined(item[attribute]));
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function iterateMap<K, V, U>(
 | 
			
		||||
  map: Map<K, V>,
 | 
			
		||||
  callbackfn: (key: K, value: V, index: number) => U
 | 
			
		||||
): U[] {
 | 
			
		||||
  const result: U[] = [];
 | 
			
		||||
  let index = 0;
 | 
			
		||||
  for (const [key, value] of map.entries()) {
 | 
			
		||||
    result.push(callbackfn(key, value, index));
 | 
			
		||||
    index += 1;
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mapMoveEntry<K, V>(
 | 
			
		||||
  map: Map<K, V>,
 | 
			
		||||
  sourceIndex: number,
 | 
			
		||||
  targetIndex: number
 | 
			
		||||
) {
 | 
			
		||||
  return new Map(arrayMove([...map], sourceIndex, targetIndex));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function arrayMove<T>(arr: T[], sourceIndex: number, targetIndex: number) {
 | 
			
		||||
  arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
 | 
			
		||||
  return arr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mapRemoveEmptyValues<K, V>(groups: Map<K, V[]>): Map<K, V[]> {
 | 
			
		||||
  return new Map([...groups].filter(([_, items]) => items.length > 0));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -30,9 +30,13 @@ export interface WikiPageWithTranslations
 | 
			
		||||
  translations: NonNullable<WikiPage["translations"]>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type RequiredNonNullable<T> = Required<{
 | 
			
		||||
  [P in keyof T]: NonNullable<T[P]>;
 | 
			
		||||
}>;
 | 
			
		||||
export type RequiredNonNullable<T> = {
 | 
			
		||||
  [P in keyof T]-?: NonNullable<T[P]>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type SelectiveRequiredNonNullable<T, K extends keyof T> = Omit<T, K> & {
 | 
			
		||||
  [P in K]-?: NonNullable<T[P]>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export enum LibraryItemUserStatus {
 | 
			
		||||
  None = 0,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
 | 
			
		||||
import { useAppLayout } from "contexts/AppLayoutContext";
 | 
			
		||||
import { AppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { isDefined } from "helpers/others";
 | 
			
		||||
import { filterDefined, isDefined } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { useRouter } from "next/router";
 | 
			
		||||
import { useEffect, useMemo, useState } from "react";
 | 
			
		||||
@ -39,11 +39,9 @@ export function useSmartLanguage<T>(
 | 
			
		||||
 | 
			
		||||
  const availableLocales = useMemo(() => {
 | 
			
		||||
    const map = new Map<string, number>();
 | 
			
		||||
    items.map((elem, index) => {
 | 
			
		||||
      if (isDefined(elem)) {
 | 
			
		||||
        const result = languageExtractor(elem);
 | 
			
		||||
        if (isDefined(result)) map.set(result, index);
 | 
			
		||||
      }
 | 
			
		||||
    filterDefined(items).map((elem, index) => {
 | 
			
		||||
      const result = languageExtractor(elem);
 | 
			
		||||
      if (isDefined(result)) map.set(result, index);
 | 
			
		||||
    });
 | 
			
		||||
    return map;
 | 
			
		||||
  }, [items, languageExtractor]);
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ import { Fragment, useState } from "react";
 | 
			
		||||
import { Icon } from "components/Ico";
 | 
			
		||||
import { useMediaHoverable } from "hooks/useMediaQuery";
 | 
			
		||||
import { WithLabel } from "components/Inputs/WithLabel";
 | 
			
		||||
import { isDefined } from "helpers/others";
 | 
			
		||||
import { filterHasAttributes, isDefined } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
interface Props extends AppStaticProps {
 | 
			
		||||
  channel: NonNullable<
 | 
			
		||||
@ -74,27 +74,25 @@ export default function Channel(props: Props): JSX.Element {
 | 
			
		||||
        className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
 | 
			
		||||
        desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2"
 | 
			
		||||
      >
 | 
			
		||||
        {channel?.videos?.data.map((video) => (
 | 
			
		||||
        {filterHasAttributes(channel?.videos?.data).map((video) => (
 | 
			
		||||
          <Fragment key={video.id}>
 | 
			
		||||
            {video.attributes && (
 | 
			
		||||
              <PreviewCard
 | 
			
		||||
                href={`/archives/videos/v/${video.attributes.uid}`}
 | 
			
		||||
                title={video.attributes.title}
 | 
			
		||||
                thumbnail={getVideoThumbnailURL(video.attributes.uid)}
 | 
			
		||||
                thumbnailAspectRatio="16/9"
 | 
			
		||||
                keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                metadata={{
 | 
			
		||||
                  release_date: video.attributes.published_date,
 | 
			
		||||
                  views: video.attributes.views,
 | 
			
		||||
                  author: channel.title,
 | 
			
		||||
                  position: "Top",
 | 
			
		||||
                }}
 | 
			
		||||
                hoverlay={{
 | 
			
		||||
                  __typename: "Video",
 | 
			
		||||
                  duration: video.attributes.duration,
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            <PreviewCard
 | 
			
		||||
              href={`/archives/videos/v/${video.attributes.uid}`}
 | 
			
		||||
              title={video.attributes.title}
 | 
			
		||||
              thumbnail={getVideoThumbnailURL(video.attributes.uid)}
 | 
			
		||||
              thumbnailAspectRatio="16/9"
 | 
			
		||||
              keepInfoVisible={keepInfoVisible}
 | 
			
		||||
              metadata={{
 | 
			
		||||
                release_date: video.attributes.published_date,
 | 
			
		||||
                views: video.attributes.views,
 | 
			
		||||
                author: channel?.title,
 | 
			
		||||
                position: "Top",
 | 
			
		||||
              }}
 | 
			
		||||
              hoverlay={{
 | 
			
		||||
                __typename: "Video",
 | 
			
		||||
                duration: video.attributes.duration,
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </Fragment>
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
@ -137,13 +135,12 @@ export async function getStaticPaths(
 | 
			
		||||
  const channels = await sdk.getVideoChannelsSlugs();
 | 
			
		||||
  const paths: GetStaticPathsResult["paths"] = [];
 | 
			
		||||
  if (channels.videoChannels?.data)
 | 
			
		||||
    channels.videoChannels.data.map((channel) => {
 | 
			
		||||
    filterHasAttributes(channels.videoChannels.data).map((channel) => {
 | 
			
		||||
      context.locales?.map((local) => {
 | 
			
		||||
        if (channel.attributes)
 | 
			
		||||
          paths.push({
 | 
			
		||||
            params: { uid: channel.attributes.uid },
 | 
			
		||||
            locale: local,
 | 
			
		||||
          });
 | 
			
		||||
        paths.push({
 | 
			
		||||
          params: { uid: channel.attributes.uid },
 | 
			
		||||
          locale: local,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  return {
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import { GetVideosPreviewQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getReadySdk } from "graphql/sdk";
 | 
			
		||||
import { prettyDate } from "helpers/formatters";
 | 
			
		||||
import { filterHasAttributes } from "helpers/others";
 | 
			
		||||
import { getVideoThumbnailURL } from "helpers/videos";
 | 
			
		||||
import { useMediaHoverable } from "hooks/useMediaQuery";
 | 
			
		||||
import { GetStaticPropsContext } from "next";
 | 
			
		||||
@ -95,27 +96,25 @@ export default function Videos(props: Props): JSX.Element {
 | 
			
		||||
        desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
 | 
			
		||||
        thin:grid-cols-1"
 | 
			
		||||
      >
 | 
			
		||||
        {paginatedVideos[page].map((video) => (
 | 
			
		||||
        {filterHasAttributes(paginatedVideos[page]).map((video) => (
 | 
			
		||||
          <Fragment key={video.id}>
 | 
			
		||||
            {video.attributes && (
 | 
			
		||||
              <PreviewCard
 | 
			
		||||
                href={`/archives/videos/v/${video.attributes.uid}`}
 | 
			
		||||
                title={video.attributes.title}
 | 
			
		||||
                thumbnail={getVideoThumbnailURL(video.attributes.uid)}
 | 
			
		||||
                thumbnailAspectRatio="16/9"
 | 
			
		||||
                keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                metadata={{
 | 
			
		||||
                  release_date: video.attributes.published_date,
 | 
			
		||||
                  views: video.attributes.views,
 | 
			
		||||
                  author: video.attributes.channel?.data?.attributes?.title,
 | 
			
		||||
                  position: "Top",
 | 
			
		||||
                }}
 | 
			
		||||
                hoverlay={{
 | 
			
		||||
                  __typename: "Video",
 | 
			
		||||
                  duration: video.attributes.duration,
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            <PreviewCard
 | 
			
		||||
              href={`/archives/videos/v/${video.attributes.uid}`}
 | 
			
		||||
              title={video.attributes.title}
 | 
			
		||||
              thumbnail={getVideoThumbnailURL(video.attributes.uid)}
 | 
			
		||||
              thumbnailAspectRatio="16/9"
 | 
			
		||||
              keepInfoVisible={keepInfoVisible}
 | 
			
		||||
              metadata={{
 | 
			
		||||
                release_date: video.attributes.published_date,
 | 
			
		||||
                views: video.attributes.views,
 | 
			
		||||
                author: video.attributes.channel?.data?.attributes?.title,
 | 
			
		||||
                position: "Top",
 | 
			
		||||
              }}
 | 
			
		||||
              hoverlay={{
 | 
			
		||||
                __typename: "Video",
 | 
			
		||||
                duration: video.attributes.duration,
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </Fragment>
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ import { GetVideoQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getReadySdk } from "graphql/sdk";
 | 
			
		||||
import { prettyDate, prettyShortenNumber } from "helpers/formatters";
 | 
			
		||||
import { isDefined } from "helpers/others";
 | 
			
		||||
import { filterHasAttributes, isDefined } from "helpers/others";
 | 
			
		||||
import { getVideoFile } from "helpers/videos";
 | 
			
		||||
import { useMediaMobile } from "hooks/useMediaQuery";
 | 
			
		||||
import {
 | 
			
		||||
@ -213,10 +213,9 @@ export async function getStaticPaths(
 | 
			
		||||
  const videos = await sdk.getVideosSlugs();
 | 
			
		||||
  const paths: GetStaticPathsResult["paths"] = [];
 | 
			
		||||
  if (videos.videos?.data)
 | 
			
		||||
    videos.videos.data.map((video) => {
 | 
			
		||||
    filterHasAttributes(videos.videos.data).map((video) => {
 | 
			
		||||
      context.locales?.map((local) => {
 | 
			
		||||
        if (video.attributes)
 | 
			
		||||
          paths.push({ params: { uid: video.attributes.uid }, locale: local });
 | 
			
		||||
        paths.push({ params: { uid: video.attributes.uid }, locale: local });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  return {
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,11 @@ import {
 | 
			
		||||
  prettySlug,
 | 
			
		||||
} from "helpers/formatters";
 | 
			
		||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
 | 
			
		||||
import { getStatusDescription } from "helpers/others";
 | 
			
		||||
import {
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  getStatusDescription,
 | 
			
		||||
  isDefinedAndNotEmpty,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
import { ContentWithTranslations } from "helpers/types";
 | 
			
		||||
import { useMediaMobile } from "hooks/useMediaQuery";
 | 
			
		||||
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
 | 
			
		||||
@ -82,31 +86,29 @@ export default function Content(props: Props): JSX.Element {
 | 
			
		||||
        horizontalLine
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {selectedTranslation?.text_set && (
 | 
			
		||||
      {selectedTranslation?.text_set?.source_language?.data?.attributes
 | 
			
		||||
        ?.code !== undefined && (
 | 
			
		||||
        <div className="grid gap-5">
 | 
			
		||||
          <h2 className="text-xl">
 | 
			
		||||
            {selectedTranslation.text_set.source_language?.data?.attributes
 | 
			
		||||
              ?.code === selectedTranslation.language?.data?.attributes?.code
 | 
			
		||||
            {selectedTranslation.text_set.source_language.data.attributes
 | 
			
		||||
              .code === selectedTranslation.language?.data?.attributes?.code
 | 
			
		||||
              ? langui.transcript_notice
 | 
			
		||||
              : langui.translation_notice}
 | 
			
		||||
          </h2>
 | 
			
		||||
 | 
			
		||||
          {selectedTranslation.text_set.source_language?.data?.attributes
 | 
			
		||||
            ?.code &&
 | 
			
		||||
            selectedTranslation.text_set.source_language.data.attributes
 | 
			
		||||
              .code !==
 | 
			
		||||
              selectedTranslation.language?.data?.attributes?.code && (
 | 
			
		||||
              <div className="grid place-items-center gap-2">
 | 
			
		||||
                <p className="font-headers">{langui.source_language}:</p>
 | 
			
		||||
                <Chip>
 | 
			
		||||
                  {prettyLanguage(
 | 
			
		||||
                    selectedTranslation.text_set.source_language.data.attributes
 | 
			
		||||
                      .code,
 | 
			
		||||
                    languages
 | 
			
		||||
                  )}
 | 
			
		||||
                </Chip>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
          {selectedTranslation.text_set.source_language.data.attributes.code !==
 | 
			
		||||
            selectedTranslation.language?.data?.attributes?.code && (
 | 
			
		||||
            <div className="grid place-items-center gap-2">
 | 
			
		||||
              <p className="font-headers">{langui.source_language}:</p>
 | 
			
		||||
              <Chip>
 | 
			
		||||
                {prettyLanguage(
 | 
			
		||||
                  selectedTranslation.text_set.source_language.data.attributes
 | 
			
		||||
                    .code,
 | 
			
		||||
                  languages
 | 
			
		||||
                )}
 | 
			
		||||
              </Chip>
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          <div className="grid grid-flow-col place-content-center place-items-center gap-2">
 | 
			
		||||
            <p className="font-headers">{langui.status}:</p>
 | 
			
		||||
@ -127,18 +129,16 @@ export default function Content(props: Props): JSX.Element {
 | 
			
		||||
              <div>
 | 
			
		||||
                <p className="font-headers">{langui.transcribers}:</p>
 | 
			
		||||
                <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                  {selectedTranslation.text_set.transcribers.data.map(
 | 
			
		||||
                    (recorder) => (
 | 
			
		||||
                      <Fragment key={recorder.id}>
 | 
			
		||||
                        {recorder.attributes && (
 | 
			
		||||
                          <RecorderChip
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                            recorder={recorder.attributes}
 | 
			
		||||
                          />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    )
 | 
			
		||||
                  )}
 | 
			
		||||
                  {filterHasAttributes(
 | 
			
		||||
                    selectedTranslation.text_set.transcribers.data
 | 
			
		||||
                  ).map((recorder) => (
 | 
			
		||||
                    <Fragment key={recorder.id}>
 | 
			
		||||
                      <RecorderChip
 | 
			
		||||
                        langui={langui}
 | 
			
		||||
                        recorder={recorder.attributes}
 | 
			
		||||
                      />
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
@ -148,18 +148,16 @@ export default function Content(props: Props): JSX.Element {
 | 
			
		||||
              <div>
 | 
			
		||||
                <p className="font-headers">{langui.translators}:</p>
 | 
			
		||||
                <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                  {selectedTranslation.text_set.translators.data.map(
 | 
			
		||||
                    (recorder) => (
 | 
			
		||||
                      <Fragment key={recorder.id}>
 | 
			
		||||
                        {recorder.attributes && (
 | 
			
		||||
                          <RecorderChip
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                            recorder={recorder.attributes}
 | 
			
		||||
                          />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    )
 | 
			
		||||
                  )}
 | 
			
		||||
                  {filterHasAttributes(
 | 
			
		||||
                    selectedTranslation.text_set.translators.data
 | 
			
		||||
                  ).map((recorder) => (
 | 
			
		||||
                    <Fragment key={recorder.id}>
 | 
			
		||||
                      <RecorderChip
 | 
			
		||||
                        langui={langui}
 | 
			
		||||
                        recorder={recorder.attributes}
 | 
			
		||||
                      />
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
@ -169,23 +167,21 @@ export default function Content(props: Props): JSX.Element {
 | 
			
		||||
              <div>
 | 
			
		||||
                <p className="font-headers">{langui.proofreaders}:</p>
 | 
			
		||||
                <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
                  {selectedTranslation.text_set.proofreaders.data.map(
 | 
			
		||||
                    (recorder) => (
 | 
			
		||||
                      <Fragment key={recorder.id}>
 | 
			
		||||
                        {recorder.attributes && (
 | 
			
		||||
                          <RecorderChip
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                            recorder={recorder.attributes}
 | 
			
		||||
                          />
 | 
			
		||||
                        )}
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    )
 | 
			
		||||
                  )}
 | 
			
		||||
                  {filterHasAttributes(
 | 
			
		||||
                    selectedTranslation.text_set.proofreaders.data
 | 
			
		||||
                  ).map((recorder) => (
 | 
			
		||||
                    <Fragment key={recorder.id}>
 | 
			
		||||
                      <RecorderChip
 | 
			
		||||
                        langui={langui}
 | 
			
		||||
                        recorder={recorder.attributes}
 | 
			
		||||
                      />
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
 | 
			
		||||
          {selectedTranslation.text_set.notes && (
 | 
			
		||||
          {isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
 | 
			
		||||
            <div>
 | 
			
		||||
              <p className="font-headers">{"Notes"}:</p>
 | 
			
		||||
              <div className="grid place-content-center place-items-center gap-2">
 | 
			
		||||
@ -450,13 +446,12 @@ export async function getStaticPaths(
 | 
			
		||||
  const sdk = getReadySdk();
 | 
			
		||||
  const contents = await sdk.getContentsSlugs();
 | 
			
		||||
  const paths: GetStaticPathsResult["paths"] = [];
 | 
			
		||||
  contents.contents?.data.map((item) => {
 | 
			
		||||
  filterHasAttributes(contents.contents?.data).map((item) => {
 | 
			
		||||
    context.locales?.map((local) => {
 | 
			
		||||
      if (item.attributes)
 | 
			
		||||
        paths.push({
 | 
			
		||||
          params: { slug: item.attributes.slug },
 | 
			
		||||
          locale: local,
 | 
			
		||||
        });
 | 
			
		||||
      paths.push({
 | 
			
		||||
        params: { slug: item.attributes.slug },
 | 
			
		||||
        locale: local,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  return {
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,12 @@ import { WithLabel } from "components/Inputs/WithLabel";
 | 
			
		||||
import { Button } from "components/Inputs/Button";
 | 
			
		||||
import { TextInput } from "components/Inputs/TextInput";
 | 
			
		||||
import { useMediaHoverable } from "hooks/useMediaQuery";
 | 
			
		||||
import {
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  iterateMap,
 | 
			
		||||
  mapRemoveEmptyValues,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
 | 
			
		||||
 | 
			
		||||
interface Props extends AppStaticProps {
 | 
			
		||||
  contents: NonNullable<GetContentsQuery["contents"]>["data"];
 | 
			
		||||
@ -128,8 +134,18 @@ export default function Contents(props: Props): JSX.Element {
 | 
			
		||||
  );
 | 
			
		||||
  const contentPanel = (
 | 
			
		||||
    <ContentPanel width={ContentPanelWidthSizes.Full}>
 | 
			
		||||
      {[...groups].map(
 | 
			
		||||
        ([name, items], index) =>
 | 
			
		||||
      {/* TODO: Add to langui */}
 | 
			
		||||
      {groups.size === 0 && (
 | 
			
		||||
        <ContentPlaceholder
 | 
			
		||||
          message={
 | 
			
		||||
            "No results. You can try changing or resetting the search parameters."
 | 
			
		||||
          }
 | 
			
		||||
          icon={Icon.ChevronLeft}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {iterateMap(
 | 
			
		||||
        groups,
 | 
			
		||||
        (name, items, index) =>
 | 
			
		||||
          items.length > 0 && (
 | 
			
		||||
            <Fragment key={index}>
 | 
			
		||||
              {name && (
 | 
			
		||||
@ -140,7 +156,10 @@ export default function Contents(props: Props): JSX.Element {
 | 
			
		||||
                  {name}
 | 
			
		||||
                  <Chip>{`${items.reduce((currentSum, item) => {
 | 
			
		||||
                    if (effectiveCombineRelatedContent) {
 | 
			
		||||
                      if (item.attributes?.group?.data?.attributes?.combine) {
 | 
			
		||||
                      if (
 | 
			
		||||
                        item.attributes?.group?.data?.attributes?.combine ===
 | 
			
		||||
                        true
 | 
			
		||||
                      ) {
 | 
			
		||||
                        return (
 | 
			
		||||
                          currentSum +
 | 
			
		||||
                          (item.attributes.group.data.attributes.contents?.data
 | 
			
		||||
@ -161,50 +180,49 @@ export default function Contents(props: Props): JSX.Element {
 | 
			
		||||
                className="grid grid-cols-2 items-end gap-8
 | 
			
		||||
                desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
 | 
			
		||||
              >
 | 
			
		||||
                {items.map((item) => (
 | 
			
		||||
                {filterHasAttributes(items).map((item) => (
 | 
			
		||||
                  <Fragment key={item.id}>
 | 
			
		||||
                    {item.attributes && (
 | 
			
		||||
                      <TranslatedPreviewCard
 | 
			
		||||
                        href={`/contents/${item.attributes.slug}`}
 | 
			
		||||
                        translations={item.attributes.translations?.map(
 | 
			
		||||
                          (translation) => ({
 | 
			
		||||
                            pre_title: translation?.pre_title,
 | 
			
		||||
                            title: translation?.title,
 | 
			
		||||
                            subtitle: translation?.subtitle,
 | 
			
		||||
                            language:
 | 
			
		||||
                              translation?.language?.data?.attributes?.code,
 | 
			
		||||
                          })
 | 
			
		||||
                        )}
 | 
			
		||||
                        slug={item.attributes.slug}
 | 
			
		||||
                        languages={languages}
 | 
			
		||||
                        thumbnail={item.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                        thumbnailAspectRatio="3/2"
 | 
			
		||||
                        thumbnailForceAspectRatio
 | 
			
		||||
                        stackNumber={
 | 
			
		||||
                          effectiveCombineRelatedContent &&
 | 
			
		||||
                          item.attributes.group?.data?.attributes?.combine
 | 
			
		||||
                            ? item.attributes.group.data.attributes.contents
 | 
			
		||||
                                ?.data.length
 | 
			
		||||
                            : 0
 | 
			
		||||
                        }
 | 
			
		||||
                        topChips={
 | 
			
		||||
                          item.attributes.type?.data?.attributes
 | 
			
		||||
                            ? [
 | 
			
		||||
                                item.attributes.type.data.attributes.titles?.[0]
 | 
			
		||||
                                  ? item.attributes.type.data.attributes
 | 
			
		||||
                                      .titles[0]?.title
 | 
			
		||||
                                  : prettySlug(
 | 
			
		||||
                                      item.attributes.type.data.attributes.slug
 | 
			
		||||
                                    ),
 | 
			
		||||
                              ]
 | 
			
		||||
                            : undefined
 | 
			
		||||
                        }
 | 
			
		||||
                        bottomChips={item.attributes.categories?.data.map(
 | 
			
		||||
                          (category) => category.attributes?.short ?? ""
 | 
			
		||||
                        )}
 | 
			
		||||
                        keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                      />
 | 
			
		||||
                    )}
 | 
			
		||||
                    <TranslatedPreviewCard
 | 
			
		||||
                      href={`/contents/${item.attributes.slug}`}
 | 
			
		||||
                      translations={item.attributes.translations?.map(
 | 
			
		||||
                        (translation) => ({
 | 
			
		||||
                          pre_title: translation?.pre_title,
 | 
			
		||||
                          title: translation?.title,
 | 
			
		||||
                          subtitle: translation?.subtitle,
 | 
			
		||||
                          language:
 | 
			
		||||
                            translation?.language?.data?.attributes?.code,
 | 
			
		||||
                        })
 | 
			
		||||
                      )}
 | 
			
		||||
                      slug={item.attributes.slug}
 | 
			
		||||
                      languages={languages}
 | 
			
		||||
                      thumbnail={item.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                      thumbnailAspectRatio="3/2"
 | 
			
		||||
                      thumbnailForceAspectRatio
 | 
			
		||||
                      stackNumber={
 | 
			
		||||
                        effectiveCombineRelatedContent &&
 | 
			
		||||
                        item.attributes.group?.data?.attributes?.combine ===
 | 
			
		||||
                          true
 | 
			
		||||
                          ? item.attributes.group.data.attributes.contents?.data
 | 
			
		||||
                              .length
 | 
			
		||||
                          : 0
 | 
			
		||||
                      }
 | 
			
		||||
                      topChips={
 | 
			
		||||
                        item.attributes.type?.data?.attributes
 | 
			
		||||
                          ? [
 | 
			
		||||
                              item.attributes.type.data.attributes.titles?.[0]
 | 
			
		||||
                                ? item.attributes.type.data.attributes.titles[0]
 | 
			
		||||
                                    ?.title
 | 
			
		||||
                                : prettySlug(
 | 
			
		||||
                                    item.attributes.type.data.attributes.slug
 | 
			
		||||
                                  ),
 | 
			
		||||
                            ]
 | 
			
		||||
                          : undefined
 | 
			
		||||
                      }
 | 
			
		||||
                      bottomChips={item.attributes.categories?.data.map(
 | 
			
		||||
                        (category) => category.attributes?.short ?? ""
 | 
			
		||||
                      )}
 | 
			
		||||
                      keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                    />
 | 
			
		||||
                  </Fragment>
 | 
			
		||||
                ))}
 | 
			
		||||
              </div>
 | 
			
		||||
@ -252,71 +270,72 @@ function getGroups(
 | 
			
		||||
  groupByType: number,
 | 
			
		||||
  items: Props["contents"]
 | 
			
		||||
): GroupContentItems {
 | 
			
		||||
  const groups: GroupContentItems = new Map();
 | 
			
		||||
 | 
			
		||||
  switch (groupByType) {
 | 
			
		||||
    case 0: {
 | 
			
		||||
      const group = new Map();
 | 
			
		||||
      group.set("Drakengard 1", []);
 | 
			
		||||
      group.set("Drakengard 1.3", []);
 | 
			
		||||
      group.set("Drakengard 2", []);
 | 
			
		||||
      group.set("Drakengard 3", []);
 | 
			
		||||
      group.set("Drakengard 4", []);
 | 
			
		||||
      group.set("NieR Gestalt", []);
 | 
			
		||||
      group.set("NieR Replicant", []);
 | 
			
		||||
      group.set("NieR Replicant ver.1.22474487139...", []);
 | 
			
		||||
      group.set("NieR:Automata", []);
 | 
			
		||||
      group.set("NieR Re[in]carnation", []);
 | 
			
		||||
      group.set("SINoALICE", []);
 | 
			
		||||
      group.set("Voice of Cards", []);
 | 
			
		||||
      group.set("Final Fantasy XIV", []);
 | 
			
		||||
      group.set("Thou Shalt Not Die", []);
 | 
			
		||||
      group.set("Bakuken", []);
 | 
			
		||||
      group.set("YoRHa", []);
 | 
			
		||||
      group.set("YoRHa Boys", []);
 | 
			
		||||
      group.set(langui.no_category, []);
 | 
			
		||||
      const noCategory = langui.no_category ?? "No category";
 | 
			
		||||
      groups.set("Drakengard 1", []);
 | 
			
		||||
      groups.set("Drakengard 1.3", []);
 | 
			
		||||
      groups.set("Drakengard 2", []);
 | 
			
		||||
      groups.set("Drakengard 3", []);
 | 
			
		||||
      groups.set("Drakengard 4", []);
 | 
			
		||||
      groups.set("NieR Gestalt", []);
 | 
			
		||||
      groups.set("NieR Replicant", []);
 | 
			
		||||
      groups.set("NieR Replicant ver.1.22474487139...", []);
 | 
			
		||||
      groups.set("NieR:Automata", []);
 | 
			
		||||
      groups.set("NieR Re[in]carnation", []);
 | 
			
		||||
      groups.set("SINoALICE", []);
 | 
			
		||||
      groups.set("Voice of Cards", []);
 | 
			
		||||
      groups.set("Final Fantasy XIV", []);
 | 
			
		||||
      groups.set("Thou Shalt Not Die", []);
 | 
			
		||||
      groups.set("Bakuken", []);
 | 
			
		||||
      groups.set("YoRHa", []);
 | 
			
		||||
      groups.set("YoRHa Boys", []);
 | 
			
		||||
      groups.set(noCategory, []);
 | 
			
		||||
 | 
			
		||||
      items.map((item) => {
 | 
			
		||||
        if (item.attributes?.categories?.data.length === 0) {
 | 
			
		||||
          group.get(langui.no_category)?.push(item);
 | 
			
		||||
          groups.get(noCategory)?.push(item);
 | 
			
		||||
        } else {
 | 
			
		||||
          item.attributes?.categories?.data.map((category) => {
 | 
			
		||||
            group.get(category.attributes?.name)?.push(item);
 | 
			
		||||
            groups.get(category.attributes?.name ?? noCategory)?.push(item);
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      return group;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case 1: {
 | 
			
		||||
      const group = new Map();
 | 
			
		||||
      items.map((item) => {
 | 
			
		||||
        const noType = langui.no_type ?? "No type";
 | 
			
		||||
        const type =
 | 
			
		||||
          item.attributes?.type?.data?.attributes?.titles?.[0]?.title ??
 | 
			
		||||
          item.attributes?.type?.data?.attributes?.slug
 | 
			
		||||
            ? prettySlug(item.attributes.type.data.attributes.slug)
 | 
			
		||||
            : langui.no_type;
 | 
			
		||||
        if (!group.has(type)) group.set(type, []);
 | 
			
		||||
        group.get(type)?.push(item);
 | 
			
		||||
        if (!groups.has(type ?? noType)) groups.set(type ?? noType, []);
 | 
			
		||||
        groups.get(type ?? noType)?.push(item);
 | 
			
		||||
      });
 | 
			
		||||
      return group;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default: {
 | 
			
		||||
      const group: GroupContentItems = new Map();
 | 
			
		||||
      group.set("", items);
 | 
			
		||||
      return group;
 | 
			
		||||
      groups.set("", items);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return mapRemoveEmptyValues(groups);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function filterContents(
 | 
			
		||||
  contents: Immutable<Props["contents"]>,
 | 
			
		||||
  contents: Props["contents"],
 | 
			
		||||
  combineRelatedContent: boolean,
 | 
			
		||||
  searchName: string
 | 
			
		||||
): Immutable<Props["contents"]> {
 | 
			
		||||
): Props["contents"] {
 | 
			
		||||
  return contents.filter((content) => {
 | 
			
		||||
    if (
 | 
			
		||||
      combineRelatedContent &&
 | 
			
		||||
      content.attributes?.group?.data?.attributes?.combine &&
 | 
			
		||||
      content.attributes?.group?.data?.attributes?.combine === true &&
 | 
			
		||||
      content.attributes.group.data.attributes.contents?.data[0].id !==
 | 
			
		||||
        content.id
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { ToolTip } from "components/ToolTip";
 | 
			
		||||
import { DevGetContentsQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getReadySdk } from "graphql/sdk";
 | 
			
		||||
import { filterDefined, filterHasAttributes } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { GetStaticPropsContext } from "next";
 | 
			
		||||
 | 
			
		||||
@ -116,321 +117,319 @@ function testingContent(contents: Props["contents"]): Report {
 | 
			
		||||
    lines: [],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  contents.contents?.data.map((content) => {
 | 
			
		||||
    if (content.attributes) {
 | 
			
		||||
      const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`;
 | 
			
		||||
      const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
 | 
			
		||||
  filterHasAttributes(contents.contents?.data).map((content) => {
 | 
			
		||||
    const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`;
 | 
			
		||||
    const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
 | 
			
		||||
 | 
			
		||||
      if (content.attributes.categories?.data.length === 0) {
 | 
			
		||||
        report.lines.push({
 | 
			
		||||
          subitems: [content.attributes.slug],
 | 
			
		||||
          name: "No Category",
 | 
			
		||||
          type: "Missing",
 | 
			
		||||
          severity: "Medium",
 | 
			
		||||
          description: "The Content has no Category.",
 | 
			
		||||
          recommandation: "Select a Category in relation with the Content",
 | 
			
		||||
          backendUrl: backendUrl,
 | 
			
		||||
          frontendUrl: frontendUrl,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    if (content.attributes.categories?.data.length === 0) {
 | 
			
		||||
      report.lines.push({
 | 
			
		||||
        subitems: [content.attributes.slug],
 | 
			
		||||
        name: "No Category",
 | 
			
		||||
        type: "Missing",
 | 
			
		||||
        severity: "Medium",
 | 
			
		||||
        description: "The Content has no Category.",
 | 
			
		||||
        recommandation: "Select a Category in relation with the Content",
 | 
			
		||||
        backendUrl: backendUrl,
 | 
			
		||||
        frontendUrl: frontendUrl,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      if (!content.attributes.type?.data?.id) {
 | 
			
		||||
        report.lines.push({
 | 
			
		||||
          subitems: [content.attributes.slug],
 | 
			
		||||
          name: "No Category",
 | 
			
		||||
          type: "Missing",
 | 
			
		||||
          severity: "Medium",
 | 
			
		||||
          description: "The Content has no Type.",
 | 
			
		||||
          recommandation: 'If unsure, use the "Other" Type.',
 | 
			
		||||
          backendUrl: backendUrl,
 | 
			
		||||
          frontendUrl: frontendUrl,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    if (!content.attributes.type?.data?.id) {
 | 
			
		||||
      report.lines.push({
 | 
			
		||||
        subitems: [content.attributes.slug],
 | 
			
		||||
        name: "No Category",
 | 
			
		||||
        type: "Missing",
 | 
			
		||||
        severity: "Medium",
 | 
			
		||||
        description: "The Content has no Type.",
 | 
			
		||||
        recommandation: 'If unsure, use the "Other" Type.',
 | 
			
		||||
        backendUrl: backendUrl,
 | 
			
		||||
        frontendUrl: frontendUrl,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      if (content.attributes.ranged_contents?.data.length === 0) {
 | 
			
		||||
        report.lines.push({
 | 
			
		||||
          subitems: [content.attributes.slug],
 | 
			
		||||
          name: "No Ranged Content",
 | 
			
		||||
          type: "Improvement",
 | 
			
		||||
          severity: "Low",
 | 
			
		||||
          description: "The Content has no Ranged Content.",
 | 
			
		||||
          recommandation:
 | 
			
		||||
            "If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).",
 | 
			
		||||
          backendUrl: backendUrl,
 | 
			
		||||
          frontendUrl: frontendUrl,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    if (content.attributes.ranged_contents?.data.length === 0) {
 | 
			
		||||
      report.lines.push({
 | 
			
		||||
        subitems: [content.attributes.slug],
 | 
			
		||||
        name: "No Ranged Content",
 | 
			
		||||
        type: "Improvement",
 | 
			
		||||
        severity: "Low",
 | 
			
		||||
        description: "The Content has no Ranged Content.",
 | 
			
		||||
        recommandation:
 | 
			
		||||
          "If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).",
 | 
			
		||||
        backendUrl: backendUrl,
 | 
			
		||||
        frontendUrl: frontendUrl,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      if (!content.attributes.thumbnail?.data?.id) {
 | 
			
		||||
        report.lines.push({
 | 
			
		||||
          subitems: [content.attributes.slug],
 | 
			
		||||
          name: "No Thumbnail",
 | 
			
		||||
          type: "Missing",
 | 
			
		||||
          severity: "High",
 | 
			
		||||
          description: "The Content has no Thumbnail.",
 | 
			
		||||
          recommandation: "",
 | 
			
		||||
          backendUrl: backendUrl,
 | 
			
		||||
          frontendUrl: frontendUrl,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    if (!content.attributes.thumbnail?.data?.id) {
 | 
			
		||||
      report.lines.push({
 | 
			
		||||
        subitems: [content.attributes.slug],
 | 
			
		||||
        name: "No Thumbnail",
 | 
			
		||||
        type: "Missing",
 | 
			
		||||
        severity: "High",
 | 
			
		||||
        description: "The Content has no Thumbnail.",
 | 
			
		||||
        recommandation: "",
 | 
			
		||||
        backendUrl: backendUrl,
 | 
			
		||||
        frontendUrl: frontendUrl,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      if (content.attributes.translations?.length === 0) {
 | 
			
		||||
        report.lines.push({
 | 
			
		||||
          subitems: [content.attributes.slug],
 | 
			
		||||
          name: "No Titles",
 | 
			
		||||
          type: "Missing",
 | 
			
		||||
          severity: "High",
 | 
			
		||||
          description: "The Content has no Titles.",
 | 
			
		||||
          recommandation: "",
 | 
			
		||||
          backendUrl: backendUrl,
 | 
			
		||||
          frontendUrl: frontendUrl,
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        const titleLanguages: string[] = [];
 | 
			
		||||
    if (content.attributes.translations?.length === 0) {
 | 
			
		||||
      report.lines.push({
 | 
			
		||||
        subitems: [content.attributes.slug],
 | 
			
		||||
        name: "No Titles",
 | 
			
		||||
        type: "Missing",
 | 
			
		||||
        severity: "High",
 | 
			
		||||
        description: "The Content has no Titles.",
 | 
			
		||||
        recommandation: "",
 | 
			
		||||
        backendUrl: backendUrl,
 | 
			
		||||
        frontendUrl: frontendUrl,
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      const titleLanguages: string[] = [];
 | 
			
		||||
 | 
			
		||||
        content.attributes.translations?.map((translation, titleIndex) => {
 | 
			
		||||
          if (translation && content.attributes) {
 | 
			
		||||
            if (translation.language?.data?.id) {
 | 
			
		||||
              if (translation.language.data.id in titleLanguages) {
 | 
			
		||||
                report.lines.push({
 | 
			
		||||
                  subitems: [
 | 
			
		||||
                    content.attributes.slug,
 | 
			
		||||
                    `Title ${titleIndex.toString()}`,
 | 
			
		||||
                  ],
 | 
			
		||||
                  name: "Duplicate Language",
 | 
			
		||||
                  type: "Error",
 | 
			
		||||
                  severity: "High",
 | 
			
		||||
                  description: "",
 | 
			
		||||
                  recommandation: "",
 | 
			
		||||
                  backendUrl: backendUrl,
 | 
			
		||||
                  frontendUrl: frontendUrl,
 | 
			
		||||
                });
 | 
			
		||||
              } else {
 | 
			
		||||
                titleLanguages.push(translation.language.data.id);
 | 
			
		||||
              }
 | 
			
		||||
            } else {
 | 
			
		||||
      filterDefined(content.attributes.translations).map(
 | 
			
		||||
        (translation, titleIndex) => {
 | 
			
		||||
          if (translation.language?.data?.id) {
 | 
			
		||||
            if (translation.language.data.id in titleLanguages) {
 | 
			
		||||
              report.lines.push({
 | 
			
		||||
                subitems: [
 | 
			
		||||
                  content.attributes.slug,
 | 
			
		||||
                  `Title ${titleIndex.toString()}`,
 | 
			
		||||
                ],
 | 
			
		||||
                name: "No Language",
 | 
			
		||||
                name: "Duplicate Language",
 | 
			
		||||
                type: "Error",
 | 
			
		||||
                severity: "Very High",
 | 
			
		||||
                severity: "High",
 | 
			
		||||
                description: "",
 | 
			
		||||
                recommandation: "",
 | 
			
		||||
                backendUrl: backendUrl,
 | 
			
		||||
                frontendUrl: frontendUrl,
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
            if (!translation.description) {
 | 
			
		||||
              report.lines.push({
 | 
			
		||||
                subitems: [
 | 
			
		||||
                  content.attributes.slug,
 | 
			
		||||
                  `Title ${titleIndex.toString()}`,
 | 
			
		||||
                ],
 | 
			
		||||
                name: "No Description",
 | 
			
		||||
                type: "Missing",
 | 
			
		||||
                severity: "Medium",
 | 
			
		||||
                description: "",
 | 
			
		||||
                recommandation: "",
 | 
			
		||||
                backendUrl: backendUrl,
 | 
			
		||||
                frontendUrl: frontendUrl,
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (translation.text_set) {
 | 
			
		||||
              report.lines.push({
 | 
			
		||||
                subitems: [content.attributes.slug],
 | 
			
		||||
                name: "No Text Set",
 | 
			
		||||
                type: "Missing",
 | 
			
		||||
                severity: "Medium",
 | 
			
		||||
                description: "The Content has no Text Set.",
 | 
			
		||||
                recommandation: "",
 | 
			
		||||
                backendUrl: backendUrl,
 | 
			
		||||
                frontendUrl: frontendUrl,
 | 
			
		||||
              });
 | 
			
		||||
            } else {
 | 
			
		||||
              /*
 | 
			
		||||
               *const textSetLanguages: string[] = [];
 | 
			
		||||
               *if (content.attributes && textSet) {
 | 
			
		||||
               *  if (textSet.language?.data?.id) {
 | 
			
		||||
               *    if (textSet.language.data.id in textSetLanguages) {
 | 
			
		||||
               *      report.lines.push({
 | 
			
		||||
               *        subitems: [
 | 
			
		||||
               *          content.attributes.slug,
 | 
			
		||||
               *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *        ],
 | 
			
		||||
               *        name: "Duplicate Language",
 | 
			
		||||
               *        type: "Error",
 | 
			
		||||
               *        severity: "High",
 | 
			
		||||
               *        description: "",
 | 
			
		||||
               *        recommandation: "",
 | 
			
		||||
               *        backendUrl: backendUrl,
 | 
			
		||||
               *        frontendUrl: frontendUrl,
 | 
			
		||||
               *      });
 | 
			
		||||
               *    } else {
 | 
			
		||||
               *      textSetLanguages.push(textSet.language.data.id);
 | 
			
		||||
               *    }
 | 
			
		||||
               *  } else {
 | 
			
		||||
               *    report.lines.push({
 | 
			
		||||
               *      subitems: [
 | 
			
		||||
               *        content.attributes.slug,
 | 
			
		||||
               *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *      ],
 | 
			
		||||
               *      name: "No Language",
 | 
			
		||||
               *      type: "Error",
 | 
			
		||||
               *      severity: "Very High",
 | 
			
		||||
               *      description: "",
 | 
			
		||||
               *      recommandation: "",
 | 
			
		||||
               *      backendUrl: backendUrl,
 | 
			
		||||
               *      frontendUrl: frontendUrl,
 | 
			
		||||
               *    });
 | 
			
		||||
               *  }
 | 
			
		||||
               *
 | 
			
		||||
               *  if (!textSet.source_language?.data?.id) {
 | 
			
		||||
               *    report.lines.push({
 | 
			
		||||
               *      subitems: [
 | 
			
		||||
               *        content.attributes.slug,
 | 
			
		||||
               *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *      ],
 | 
			
		||||
               *      name: "No Source Language",
 | 
			
		||||
               *      type: "Error",
 | 
			
		||||
               *      severity: "High",
 | 
			
		||||
               *      description: "",
 | 
			
		||||
               *      recommandation: "",
 | 
			
		||||
               *      backendUrl: backendUrl,
 | 
			
		||||
               *      frontendUrl: frontendUrl,
 | 
			
		||||
               *    });
 | 
			
		||||
               *  }
 | 
			
		||||
               *
 | 
			
		||||
               *  if (textSet.status !== Enum_Componentsetstextset_Status.Done) {
 | 
			
		||||
               *    report.lines.push({
 | 
			
		||||
               *      subitems: [
 | 
			
		||||
               *        content.attributes.slug,
 | 
			
		||||
               *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *      ],
 | 
			
		||||
               *      name: "Not Done Status",
 | 
			
		||||
               *      type: "Improvement",
 | 
			
		||||
               *      severity: "Low",
 | 
			
		||||
               *      description: "",
 | 
			
		||||
               *      recommandation: "",
 | 
			
		||||
               *      backendUrl: backendUrl,
 | 
			
		||||
               *      frontendUrl: frontendUrl,
 | 
			
		||||
               *    });
 | 
			
		||||
               *  }
 | 
			
		||||
               *
 | 
			
		||||
               *  if (!textSet.text || textSet.text.length < 10) {
 | 
			
		||||
               *    report.lines.push({
 | 
			
		||||
               *      subitems: [
 | 
			
		||||
               *        content.attributes.slug,
 | 
			
		||||
               *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *      ],
 | 
			
		||||
               *      name: "No Text",
 | 
			
		||||
               *      type: "Missing",
 | 
			
		||||
               *      severity: "Medium",
 | 
			
		||||
               *      description: "",
 | 
			
		||||
               *      recommandation: "",
 | 
			
		||||
               *      backendUrl: backendUrl,
 | 
			
		||||
               *      frontendUrl: frontendUrl,
 | 
			
		||||
               *    });
 | 
			
		||||
               *  }
 | 
			
		||||
               *
 | 
			
		||||
               *  if (
 | 
			
		||||
               *    textSet.source_language?.data?.id ===
 | 
			
		||||
               *    textSet.language?.data?.id
 | 
			
		||||
               *  ) {
 | 
			
		||||
               *    if (textSet.transcribers?.data.length === 0) {
 | 
			
		||||
               *      report.lines.push({
 | 
			
		||||
               *        subitems: [
 | 
			
		||||
               *          content.attributes.slug,
 | 
			
		||||
               *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *        ],
 | 
			
		||||
               *        name: "No Transcribers",
 | 
			
		||||
               *        type: "Missing",
 | 
			
		||||
               *        severity: "High",
 | 
			
		||||
               *        description:
 | 
			
		||||
               *          "The Content is a Transcription but doesn't credit any Transcribers.",
 | 
			
		||||
               *        recommandation: "Add the appropriate Transcribers.",
 | 
			
		||||
               *        backendUrl: backendUrl,
 | 
			
		||||
               *        frontendUrl: frontendUrl,
 | 
			
		||||
               *      });
 | 
			
		||||
               *    }
 | 
			
		||||
               *    if (
 | 
			
		||||
               *      textSet.translators?.data &&
 | 
			
		||||
               *      textSet.translators.data.length > 0
 | 
			
		||||
               *    ) {
 | 
			
		||||
               *      report.lines.push({
 | 
			
		||||
               *        subitems: [
 | 
			
		||||
               *          content.attributes.slug,
 | 
			
		||||
               *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *        ],
 | 
			
		||||
               *        name: "Credited Translators",
 | 
			
		||||
               *        type: "Error",
 | 
			
		||||
               *        severity: "High",
 | 
			
		||||
               *        description:
 | 
			
		||||
               *          "The Content is a Transcription but credits one or more Translators.",
 | 
			
		||||
               *        recommandation:
 | 
			
		||||
               *          "If appropriate, create a Translation Text Set with the Translator credited there.",
 | 
			
		||||
               *        backendUrl: backendUrl,
 | 
			
		||||
               *        frontendUrl: frontendUrl,
 | 
			
		||||
               *      });
 | 
			
		||||
               *    }
 | 
			
		||||
               *  } else {
 | 
			
		||||
               *    if (textSet.translators?.data.length === 0) {
 | 
			
		||||
               *      report.lines.push({
 | 
			
		||||
               *        subitems: [
 | 
			
		||||
               *          content.attributes.slug,
 | 
			
		||||
               *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *        ],
 | 
			
		||||
               *        name: "No Translators",
 | 
			
		||||
               *        type: "Missing",
 | 
			
		||||
               *        severity: "High",
 | 
			
		||||
               *        description:
 | 
			
		||||
               *          "The Content is a Transcription but doesn't credit any Translators.",
 | 
			
		||||
               *        recommandation: "Add the appropriate Translators.",
 | 
			
		||||
               *        backendUrl: backendUrl,
 | 
			
		||||
               *        frontendUrl: frontendUrl,
 | 
			
		||||
               *      });
 | 
			
		||||
               *    }
 | 
			
		||||
               *    if (
 | 
			
		||||
               *      textSet.transcribers?.data &&
 | 
			
		||||
               *      textSet.transcribers.data.length > 0
 | 
			
		||||
               *    ) {
 | 
			
		||||
               *      report.lines.push({
 | 
			
		||||
               *        subitems: [
 | 
			
		||||
               *          content.attributes.slug,
 | 
			
		||||
               *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
               *        ],
 | 
			
		||||
               *        name: "Credited Transcribers",
 | 
			
		||||
               *        type: "Error",
 | 
			
		||||
               *        severity: "High",
 | 
			
		||||
               *        description:
 | 
			
		||||
               *          "The Content is a Translation but credits one or more Transcribers.",
 | 
			
		||||
               *        recommandation:
 | 
			
		||||
               *          "If appropriate, create a Transcription Text Set with the Transcribers credited there.",
 | 
			
		||||
               *        backendUrl: backendUrl,
 | 
			
		||||
               *        frontendUrl: frontendUrl,
 | 
			
		||||
               *      });
 | 
			
		||||
               *    }
 | 
			
		||||
               *  }
 | 
			
		||||
               *}
 | 
			
		||||
               */
 | 
			
		||||
              titleLanguages.push(translation.language.data.id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
          } else {
 | 
			
		||||
            report.lines.push({
 | 
			
		||||
              subitems: [content.attributes.slug],
 | 
			
		||||
              name: "No Sets",
 | 
			
		||||
              type: "Missing",
 | 
			
		||||
              severity: "Medium",
 | 
			
		||||
              description: "The Content has no Sets.",
 | 
			
		||||
              subitems: [
 | 
			
		||||
                content.attributes.slug,
 | 
			
		||||
                `Title ${titleIndex.toString()}`,
 | 
			
		||||
              ],
 | 
			
		||||
              name: "No Language",
 | 
			
		||||
              type: "Error",
 | 
			
		||||
              severity: "Very High",
 | 
			
		||||
              description: "",
 | 
			
		||||
              recommandation: "",
 | 
			
		||||
              backendUrl: backendUrl,
 | 
			
		||||
              frontendUrl: frontendUrl,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
          if (!translation.description) {
 | 
			
		||||
            report.lines.push({
 | 
			
		||||
              subitems: [
 | 
			
		||||
                content.attributes.slug,
 | 
			
		||||
                `Title ${titleIndex.toString()}`,
 | 
			
		||||
              ],
 | 
			
		||||
              name: "No Description",
 | 
			
		||||
              type: "Missing",
 | 
			
		||||
              severity: "Medium",
 | 
			
		||||
              description: "",
 | 
			
		||||
              recommandation: "",
 | 
			
		||||
              backendUrl: backendUrl,
 | 
			
		||||
              frontendUrl: frontendUrl,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (translation.text_set) {
 | 
			
		||||
            report.lines.push({
 | 
			
		||||
              subitems: [content.attributes.slug],
 | 
			
		||||
              name: "No Text Set",
 | 
			
		||||
              type: "Missing",
 | 
			
		||||
              severity: "Medium",
 | 
			
		||||
              description: "The Content has no Text Set.",
 | 
			
		||||
              recommandation: "",
 | 
			
		||||
              backendUrl: backendUrl,
 | 
			
		||||
              frontendUrl: frontendUrl,
 | 
			
		||||
            });
 | 
			
		||||
          } else {
 | 
			
		||||
            /*
 | 
			
		||||
             *const textSetLanguages: string[] = [];
 | 
			
		||||
             *if (content.attributes && textSet) {
 | 
			
		||||
             *  if (textSet.language?.data?.id) {
 | 
			
		||||
             *    if (textSet.language.data.id in textSetLanguages) {
 | 
			
		||||
             *      report.lines.push({
 | 
			
		||||
             *        subitems: [
 | 
			
		||||
             *          content.attributes.slug,
 | 
			
		||||
             *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *        ],
 | 
			
		||||
             *        name: "Duplicate Language",
 | 
			
		||||
             *        type: "Error",
 | 
			
		||||
             *        severity: "High",
 | 
			
		||||
             *        description: "",
 | 
			
		||||
             *        recommandation: "",
 | 
			
		||||
             *        backendUrl: backendUrl,
 | 
			
		||||
             *        frontendUrl: frontendUrl,
 | 
			
		||||
             *      });
 | 
			
		||||
             *    } else {
 | 
			
		||||
             *      textSetLanguages.push(textSet.language.data.id);
 | 
			
		||||
             *    }
 | 
			
		||||
             *  } else {
 | 
			
		||||
             *    report.lines.push({
 | 
			
		||||
             *      subitems: [
 | 
			
		||||
             *        content.attributes.slug,
 | 
			
		||||
             *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *      ],
 | 
			
		||||
             *      name: "No Language",
 | 
			
		||||
             *      type: "Error",
 | 
			
		||||
             *      severity: "Very High",
 | 
			
		||||
             *      description: "",
 | 
			
		||||
             *      recommandation: "",
 | 
			
		||||
             *      backendUrl: backendUrl,
 | 
			
		||||
             *      frontendUrl: frontendUrl,
 | 
			
		||||
             *    });
 | 
			
		||||
             *  }
 | 
			
		||||
             *
 | 
			
		||||
             *  if (!textSet.source_language?.data?.id) {
 | 
			
		||||
             *    report.lines.push({
 | 
			
		||||
             *      subitems: [
 | 
			
		||||
             *        content.attributes.slug,
 | 
			
		||||
             *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *      ],
 | 
			
		||||
             *      name: "No Source Language",
 | 
			
		||||
             *      type: "Error",
 | 
			
		||||
             *      severity: "High",
 | 
			
		||||
             *      description: "",
 | 
			
		||||
             *      recommandation: "",
 | 
			
		||||
             *      backendUrl: backendUrl,
 | 
			
		||||
             *      frontendUrl: frontendUrl,
 | 
			
		||||
             *    });
 | 
			
		||||
             *  }
 | 
			
		||||
             *
 | 
			
		||||
             *  if (textSet.status !== Enum_Componentsetstextset_Status.Done) {
 | 
			
		||||
             *    report.lines.push({
 | 
			
		||||
             *      subitems: [
 | 
			
		||||
             *        content.attributes.slug,
 | 
			
		||||
             *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *      ],
 | 
			
		||||
             *      name: "Not Done Status",
 | 
			
		||||
             *      type: "Improvement",
 | 
			
		||||
             *      severity: "Low",
 | 
			
		||||
             *      description: "",
 | 
			
		||||
             *      recommandation: "",
 | 
			
		||||
             *      backendUrl: backendUrl,
 | 
			
		||||
             *      frontendUrl: frontendUrl,
 | 
			
		||||
             *    });
 | 
			
		||||
             *  }
 | 
			
		||||
             *
 | 
			
		||||
             *  if (!textSet.text || textSet.text.length < 10) {
 | 
			
		||||
             *    report.lines.push({
 | 
			
		||||
             *      subitems: [
 | 
			
		||||
             *        content.attributes.slug,
 | 
			
		||||
             *        `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *      ],
 | 
			
		||||
             *      name: "No Text",
 | 
			
		||||
             *      type: "Missing",
 | 
			
		||||
             *      severity: "Medium",
 | 
			
		||||
             *      description: "",
 | 
			
		||||
             *      recommandation: "",
 | 
			
		||||
             *      backendUrl: backendUrl,
 | 
			
		||||
             *      frontendUrl: frontendUrl,
 | 
			
		||||
             *    });
 | 
			
		||||
             *  }
 | 
			
		||||
             *
 | 
			
		||||
             *  if (
 | 
			
		||||
             *    textSet.source_language?.data?.id ===
 | 
			
		||||
             *    textSet.language?.data?.id
 | 
			
		||||
             *  ) {
 | 
			
		||||
             *    if (textSet.transcribers?.data.length === 0) {
 | 
			
		||||
             *      report.lines.push({
 | 
			
		||||
             *        subitems: [
 | 
			
		||||
             *          content.attributes.slug,
 | 
			
		||||
             *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *        ],
 | 
			
		||||
             *        name: "No Transcribers",
 | 
			
		||||
             *        type: "Missing",
 | 
			
		||||
             *        severity: "High",
 | 
			
		||||
             *        description:
 | 
			
		||||
             *          "The Content is a Transcription but doesn't credit any Transcribers.",
 | 
			
		||||
             *        recommandation: "Add the appropriate Transcribers.",
 | 
			
		||||
             *        backendUrl: backendUrl,
 | 
			
		||||
             *        frontendUrl: frontendUrl,
 | 
			
		||||
             *      });
 | 
			
		||||
             *    }
 | 
			
		||||
             *    if (
 | 
			
		||||
             *      textSet.translators?.data &&
 | 
			
		||||
             *      textSet.translators.data.length > 0
 | 
			
		||||
             *    ) {
 | 
			
		||||
             *      report.lines.push({
 | 
			
		||||
             *        subitems: [
 | 
			
		||||
             *          content.attributes.slug,
 | 
			
		||||
             *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *        ],
 | 
			
		||||
             *        name: "Credited Translators",
 | 
			
		||||
             *        type: "Error",
 | 
			
		||||
             *        severity: "High",
 | 
			
		||||
             *        description:
 | 
			
		||||
             *          "The Content is a Transcription but credits one or more Translators.",
 | 
			
		||||
             *        recommandation:
 | 
			
		||||
             *          "If appropriate, create a Translation Text Set with the Translator credited there.",
 | 
			
		||||
             *        backendUrl: backendUrl,
 | 
			
		||||
             *        frontendUrl: frontendUrl,
 | 
			
		||||
             *      });
 | 
			
		||||
             *    }
 | 
			
		||||
             *  } else {
 | 
			
		||||
             *    if (textSet.translators?.data.length === 0) {
 | 
			
		||||
             *      report.lines.push({
 | 
			
		||||
             *        subitems: [
 | 
			
		||||
             *          content.attributes.slug,
 | 
			
		||||
             *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *        ],
 | 
			
		||||
             *        name: "No Translators",
 | 
			
		||||
             *        type: "Missing",
 | 
			
		||||
             *        severity: "High",
 | 
			
		||||
             *        description:
 | 
			
		||||
             *          "The Content is a Transcription but doesn't credit any Translators.",
 | 
			
		||||
             *        recommandation: "Add the appropriate Translators.",
 | 
			
		||||
             *        backendUrl: backendUrl,
 | 
			
		||||
             *        frontendUrl: frontendUrl,
 | 
			
		||||
             *      });
 | 
			
		||||
             *    }
 | 
			
		||||
             *    if (
 | 
			
		||||
             *      textSet.transcribers?.data &&
 | 
			
		||||
             *      textSet.transcribers.data.length > 0
 | 
			
		||||
             *    ) {
 | 
			
		||||
             *      report.lines.push({
 | 
			
		||||
             *        subitems: [
 | 
			
		||||
             *          content.attributes.slug,
 | 
			
		||||
             *          `TextSet ${textSetIndex.toString()}`,
 | 
			
		||||
             *        ],
 | 
			
		||||
             *        name: "Credited Transcribers",
 | 
			
		||||
             *        type: "Error",
 | 
			
		||||
             *        severity: "High",
 | 
			
		||||
             *        description:
 | 
			
		||||
             *          "The Content is a Translation but credits one or more Transcribers.",
 | 
			
		||||
             *        recommandation:
 | 
			
		||||
             *          "If appropriate, create a Transcription Text Set with the Transcribers credited there.",
 | 
			
		||||
             *        backendUrl: backendUrl,
 | 
			
		||||
             *        frontendUrl: frontendUrl,
 | 
			
		||||
             *      });
 | 
			
		||||
             *    }
 | 
			
		||||
             *  }
 | 
			
		||||
             *}
 | 
			
		||||
             */
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          report.lines.push({
 | 
			
		||||
            subitems: [content.attributes.slug],
 | 
			
		||||
            name: "No Sets",
 | 
			
		||||
            type: "Missing",
 | 
			
		||||
            severity: "Medium",
 | 
			
		||||
            description: "The Content has no Sets.",
 | 
			
		||||
            recommandation: "",
 | 
			
		||||
            backendUrl: backendUrl,
 | 
			
		||||
            frontendUrl: frontendUrl,
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  return report;
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,12 @@ import {
 | 
			
		||||
} from "helpers/formatters";
 | 
			
		||||
import { getAssetURL, ImageQuality } from "helpers/img";
 | 
			
		||||
import { convertMmToInch } from "helpers/numbers";
 | 
			
		||||
import { isDefined, sortContent } from "helpers/others";
 | 
			
		||||
import {
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  isDefined,
 | 
			
		||||
  isDefinedAndNotEmpty,
 | 
			
		||||
  sortContent,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { useLightBox } from "hooks/useLightBox";
 | 
			
		||||
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
 | 
			
		||||
@ -172,7 +177,9 @@ export default function LibrarySlug(props: Props): JSX.Element {
 | 
			
		||||
            )}
 | 
			
		||||
            <div className="grid place-items-center text-center">
 | 
			
		||||
              <h1 className="text-3xl">{item?.title}</h1>
 | 
			
		||||
              {item?.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>}
 | 
			
		||||
              {item && isDefinedAndNotEmpty(item.subtitle) && (
 | 
			
		||||
                <h2 className="text-2xl">{item.subtitle}</h2>
 | 
			
		||||
              )}
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <PreviewCardCTAs
 | 
			
		||||
@ -196,19 +203,15 @@ export default function LibrarySlug(props: Props): JSX.Element {
 | 
			
		||||
                {item?.urls && item.urls.length ? (
 | 
			
		||||
                  <div className="flex flex-row place-items-center gap-3">
 | 
			
		||||
                    <p>{langui.available_at}</p>
 | 
			
		||||
                    {item.urls
 | 
			
		||||
                      .filter((url) => url)
 | 
			
		||||
                      .map((url, index) => (
 | 
			
		||||
                        <Fragment key={index}>
 | 
			
		||||
                          {url?.url && (
 | 
			
		||||
                            <Button
 | 
			
		||||
                              href={url.url}
 | 
			
		||||
                              target={"_blank"}
 | 
			
		||||
                              text={prettyURL(url.url)}
 | 
			
		||||
                            />
 | 
			
		||||
                          )}
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    {filterHasAttributes(item.urls).map((url, index) => (
 | 
			
		||||
                      <Fragment key={index}>
 | 
			
		||||
                        <Button
 | 
			
		||||
                          href={url.url}
 | 
			
		||||
                          target={"_blank"}
 | 
			
		||||
                          text={prettyURL(url.url)}
 | 
			
		||||
                        />
 | 
			
		||||
                      </Fragment>
 | 
			
		||||
                    ))}
 | 
			
		||||
                  </div>
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <p>{langui.item_not_available}</p>
 | 
			
		||||
@ -225,26 +228,19 @@ export default function LibrarySlug(props: Props): JSX.Element {
 | 
			
		||||
              className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
 | 
			
		||||
              gap-8"
 | 
			
		||||
            >
 | 
			
		||||
              {item.gallery.data.map((galleryItem, index) => (
 | 
			
		||||
                <Fragment key={galleryItem.id}>
 | 
			
		||||
                  {galleryItem.attributes && (
 | 
			
		||||
              {filterHasAttributes(item.gallery.data).map(
 | 
			
		||||
                (galleryItem, index) => (
 | 
			
		||||
                  <Fragment key={galleryItem.id}>
 | 
			
		||||
                    <div
 | 
			
		||||
                      className="relative aspect-square cursor-pointer
 | 
			
		||||
                      transition-transform hover:scale-[1.02]"
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        if (item.gallery?.data) {
 | 
			
		||||
                          const images: string[] = [];
 | 
			
		||||
                          item.gallery.data.map((image) => {
 | 
			
		||||
                            if (image.attributes)
 | 
			
		||||
                              images.push(
 | 
			
		||||
                                getAssetURL(
 | 
			
		||||
                                  image.attributes.url,
 | 
			
		||||
                                  ImageQuality.Large
 | 
			
		||||
                                )
 | 
			
		||||
                              );
 | 
			
		||||
                          });
 | 
			
		||||
                          openLightBox(images, index);
 | 
			
		||||
                        }
 | 
			
		||||
                        const images: string[] = filterHasAttributes(
 | 
			
		||||
                          item.gallery?.data
 | 
			
		||||
                        ).map((image) =>
 | 
			
		||||
                          getAssetURL(image.attributes.url, ImageQuality.Large)
 | 
			
		||||
                        );
 | 
			
		||||
                        openLightBox(images, index);
 | 
			
		||||
                      }}
 | 
			
		||||
                    >
 | 
			
		||||
                      <Img
 | 
			
		||||
@ -253,9 +249,9 @@ export default function LibrarySlug(props: Props): JSX.Element {
 | 
			
		||||
                        image={galleryItem.attributes}
 | 
			
		||||
                      />
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
                </Fragment>
 | 
			
		||||
              ))}
 | 
			
		||||
                  </Fragment>
 | 
			
		||||
                )
 | 
			
		||||
              )}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
@ -437,46 +433,44 @@ export default function LibrarySlug(props: Props): JSX.Element {
 | 
			
		||||
              className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]
 | 
			
		||||
              items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1"
 | 
			
		||||
            >
 | 
			
		||||
              {item.subitems.data.map((subitem) => (
 | 
			
		||||
              {filterHasAttributes(item.subitems.data).map((subitem) => (
 | 
			
		||||
                <Fragment key={subitem.id}>
 | 
			
		||||
                  {subitem.attributes && subitem.id && (
 | 
			
		||||
                    <PreviewCard
 | 
			
		||||
                      href={`/library/${subitem.attributes.slug}`}
 | 
			
		||||
                      title={subitem.attributes.title}
 | 
			
		||||
                      subtitle={subitem.attributes.subtitle}
 | 
			
		||||
                      thumbnail={subitem.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                      thumbnailAspectRatio="21/29.7"
 | 
			
		||||
                      thumbnailRounded={false}
 | 
			
		||||
                      keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                      topChips={
 | 
			
		||||
                        subitem.attributes.metadata &&
 | 
			
		||||
                        subitem.attributes.metadata.length > 0 &&
 | 
			
		||||
                        subitem.attributes.metadata[0]
 | 
			
		||||
                          ? [prettyItemSubType(subitem.attributes.metadata[0])]
 | 
			
		||||
                          : []
 | 
			
		||||
                      }
 | 
			
		||||
                      bottomChips={subitem.attributes.categories?.data.map(
 | 
			
		||||
                        (category) => category.attributes?.short ?? ""
 | 
			
		||||
                      )}
 | 
			
		||||
                      metadata={{
 | 
			
		||||
                        currencies: currencies,
 | 
			
		||||
                        release_date: subitem.attributes.release_date,
 | 
			
		||||
                        price: subitem.attributes.price,
 | 
			
		||||
                        position: "Bottom",
 | 
			
		||||
                      }}
 | 
			
		||||
                      infoAppend={
 | 
			
		||||
                        <PreviewCardCTAs
 | 
			
		||||
                          id={subitem.id}
 | 
			
		||||
                          langui={langui}
 | 
			
		||||
                          displayCTAs={
 | 
			
		||||
                            !isUntangibleGroupItem(
 | 
			
		||||
                              subitem.attributes.metadata?.[0]
 | 
			
		||||
                            )
 | 
			
		||||
                          }
 | 
			
		||||
                        />
 | 
			
		||||
                      }
 | 
			
		||||
                    />
 | 
			
		||||
                  )}
 | 
			
		||||
                  <PreviewCard
 | 
			
		||||
                    href={`/library/${subitem.attributes.slug}`}
 | 
			
		||||
                    title={subitem.attributes.title}
 | 
			
		||||
                    subtitle={subitem.attributes.subtitle}
 | 
			
		||||
                    thumbnail={subitem.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                    thumbnailAspectRatio="21/29.7"
 | 
			
		||||
                    thumbnailRounded={false}
 | 
			
		||||
                    keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                    topChips={
 | 
			
		||||
                      subitem.attributes.metadata &&
 | 
			
		||||
                      subitem.attributes.metadata.length > 0 &&
 | 
			
		||||
                      subitem.attributes.metadata[0]
 | 
			
		||||
                        ? [prettyItemSubType(subitem.attributes.metadata[0])]
 | 
			
		||||
                        : []
 | 
			
		||||
                    }
 | 
			
		||||
                    bottomChips={subitem.attributes.categories?.data.map(
 | 
			
		||||
                      (category) => category.attributes?.short ?? ""
 | 
			
		||||
                    )}
 | 
			
		||||
                    metadata={{
 | 
			
		||||
                      currencies: currencies,
 | 
			
		||||
                      release_date: subitem.attributes.release_date,
 | 
			
		||||
                      price: subitem.attributes.price,
 | 
			
		||||
                      position: "Bottom",
 | 
			
		||||
                    }}
 | 
			
		||||
                    infoAppend={
 | 
			
		||||
                      <PreviewCardCTAs
 | 
			
		||||
                        id={subitem.id}
 | 
			
		||||
                        langui={langui}
 | 
			
		||||
                        displayCTAs={
 | 
			
		||||
                          !isUntangibleGroupItem(
 | 
			
		||||
                            subitem.attributes.metadata?.[0]
 | 
			
		||||
                          )
 | 
			
		||||
                        }
 | 
			
		||||
                      />
 | 
			
		||||
                    }
 | 
			
		||||
                  />
 | 
			
		||||
                </Fragment>
 | 
			
		||||
              ))}
 | 
			
		||||
            </div>
 | 
			
		||||
@ -548,13 +542,12 @@ export async function getStaticPaths(
 | 
			
		||||
  const sdk = getReadySdk();
 | 
			
		||||
  const libraryItems = await sdk.getLibraryItemsSlugs();
 | 
			
		||||
  const paths: GetStaticPathsResult["paths"] = [];
 | 
			
		||||
  if (libraryItems.libraryItems) {
 | 
			
		||||
    libraryItems.libraryItems.data.map((item) => {
 | 
			
		||||
      context.locales?.map((local) => {
 | 
			
		||||
        paths.push({ params: { slug: item.attributes?.slug }, locale: local });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  filterHasAttributes(libraryItems.libraryItems?.data).map((item) => {
 | 
			
		||||
    context.locales?.map((local) =>
 | 
			
		||||
      paths.push({ params: { slug: item.attributes?.slug }, locale: local })
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    paths,
 | 
			
		||||
    fallback: "blocking",
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getReadySdk } from "graphql/sdk";
 | 
			
		||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
 | 
			
		||||
import { isDefined, sortContent } from "helpers/others";
 | 
			
		||||
import { filterHasAttributes, isDefined, sortContent } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import { useLightBox } from "hooks/useLightBox";
 | 
			
		||||
import {
 | 
			
		||||
@ -146,14 +146,12 @@ export async function getStaticPaths(
 | 
			
		||||
  const sdk = getReadySdk();
 | 
			
		||||
  const libraryItems = await sdk.getLibraryItemsSlugs({});
 | 
			
		||||
  const paths: GetStaticPathsResult["paths"] = [];
 | 
			
		||||
  if (libraryItems.libraryItems) {
 | 
			
		||||
    libraryItems.libraryItems.data.map((item) => {
 | 
			
		||||
      context.locales?.map((local) => {
 | 
			
		||||
        if (item.attributes)
 | 
			
		||||
          paths.push({ params: { slug: item.attributes.slug }, locale: local });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  filterHasAttributes(libraryItems.libraryItems?.data).map((item) => {
 | 
			
		||||
    context.locales?.map((local) =>
 | 
			
		||||
      paths.push({ params: { slug: item.attributes.slug }, locale: local })
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    paths,
 | 
			
		||||
    fallback: "blocking",
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,13 @@ import {
 | 
			
		||||
import { PreviewCard } from "components/PreviewCard";
 | 
			
		||||
import { useMediaHoverable } from "hooks/useMediaQuery";
 | 
			
		||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
 | 
			
		||||
import { isDefinedAndNotEmpty, isUndefined } from "helpers/others";
 | 
			
		||||
import {
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  isDefinedAndNotEmpty,
 | 
			
		||||
  isUndefined,
 | 
			
		||||
  iterateMap,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
 | 
			
		||||
 | 
			
		||||
interface Props extends AppStaticProps {
 | 
			
		||||
  items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
 | 
			
		||||
@ -234,73 +240,74 @@ export default function Library(props: Props): JSX.Element {
 | 
			
		||||
  );
 | 
			
		||||
  const contentPanel = (
 | 
			
		||||
    <ContentPanel width={ContentPanelWidthSizes.Full}>
 | 
			
		||||
      {[...groups].map(([name, items]) => (
 | 
			
		||||
      {/* TODO: Add to langui */}
 | 
			
		||||
      {groups.size === 0 && (
 | 
			
		||||
        <ContentPlaceholder
 | 
			
		||||
          message={
 | 
			
		||||
            "No results. You can try changing or resetting the search parameters."
 | 
			
		||||
          }
 | 
			
		||||
          icon={Icon.ChevronLeft}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {iterateMap(groups, (name, items) => (
 | 
			
		||||
        <Fragment key={name}>
 | 
			
		||||
          {items.length > 0 && (
 | 
			
		||||
            <>
 | 
			
		||||
              {name && (
 | 
			
		||||
                <h2
 | 
			
		||||
                  className="flex flex-row place-items-center gap-2
 | 
			
		||||
          {isDefinedAndNotEmpty(name) && (
 | 
			
		||||
            <h2
 | 
			
		||||
              className="flex flex-row place-items-center gap-2
 | 
			
		||||
                  pb-2 pt-10 text-2xl first-of-type:pt-0"
 | 
			
		||||
                >
 | 
			
		||||
                  {name}
 | 
			
		||||
                  <Chip>{`${items.length} ${
 | 
			
		||||
                    items.length <= 1
 | 
			
		||||
                      ? langui.result?.toLowerCase() ?? "result"
 | 
			
		||||
                      : langui.results?.toLowerCase() ?? "results"
 | 
			
		||||
                  }`}</Chip>
 | 
			
		||||
                </h2>
 | 
			
		||||
              )}
 | 
			
		||||
              <div
 | 
			
		||||
                className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
 | 
			
		||||
            >
 | 
			
		||||
              {name}
 | 
			
		||||
              <Chip>{`${items.length} ${
 | 
			
		||||
                items.length <= 1
 | 
			
		||||
                  ? langui.result?.toLowerCase() ?? "result"
 | 
			
		||||
                  : langui.results?.toLowerCase() ?? "results"
 | 
			
		||||
              }`}</Chip>
 | 
			
		||||
            </h2>
 | 
			
		||||
          )}
 | 
			
		||||
          <div
 | 
			
		||||
            className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
 | 
			
		||||
                last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]
 | 
			
		||||
                mobile:grid-cols-2 mobile:gap-4"
 | 
			
		||||
              >
 | 
			
		||||
                {items.map((item) => (
 | 
			
		||||
                  <Fragment key={item.id}>
 | 
			
		||||
                    {isDefinedAndNotEmpty(item.id) && item.attributes && (
 | 
			
		||||
                      <PreviewCard
 | 
			
		||||
                        href={`/library/${item.attributes.slug}`}
 | 
			
		||||
                        title={item.attributes.title}
 | 
			
		||||
                        subtitle={item.attributes.subtitle}
 | 
			
		||||
                        thumbnail={item.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                        thumbnailAspectRatio="21/29.7"
 | 
			
		||||
                        thumbnailRounded={false}
 | 
			
		||||
                        keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                        topChips={
 | 
			
		||||
                          item.attributes.metadata &&
 | 
			
		||||
                          item.attributes.metadata.length > 0 &&
 | 
			
		||||
                          item.attributes.metadata[0]
 | 
			
		||||
                            ? [prettyItemSubType(item.attributes.metadata[0])]
 | 
			
		||||
                            : []
 | 
			
		||||
                        }
 | 
			
		||||
                        bottomChips={item.attributes.categories?.data.map(
 | 
			
		||||
                          (category) => category.attributes?.short ?? ""
 | 
			
		||||
                        )}
 | 
			
		||||
                        metadata={{
 | 
			
		||||
                          currencies: currencies,
 | 
			
		||||
                          release_date: item.attributes.release_date,
 | 
			
		||||
                          price: item.attributes.price,
 | 
			
		||||
                          position: "Bottom",
 | 
			
		||||
                        }}
 | 
			
		||||
                        infoAppend={
 | 
			
		||||
                          <PreviewCardCTAs
 | 
			
		||||
                            id={item.id}
 | 
			
		||||
                            displayCTAs={
 | 
			
		||||
                              !isUntangibleGroupItem(
 | 
			
		||||
                                item.attributes.metadata?.[0]
 | 
			
		||||
                              )
 | 
			
		||||
                            }
 | 
			
		||||
                            langui={langui}
 | 
			
		||||
                          />
 | 
			
		||||
                        }
 | 
			
		||||
                      />
 | 
			
		||||
                    )}
 | 
			
		||||
                  </Fragment>
 | 
			
		||||
                ))}
 | 
			
		||||
              </div>
 | 
			
		||||
            </>
 | 
			
		||||
          )}
 | 
			
		||||
          >
 | 
			
		||||
            {filterHasAttributes(items).map((item) => (
 | 
			
		||||
              <Fragment key={item.id}>
 | 
			
		||||
                <PreviewCard
 | 
			
		||||
                  href={`/library/${item.attributes.slug}`}
 | 
			
		||||
                  title={item.attributes.title}
 | 
			
		||||
                  subtitle={item.attributes.subtitle}
 | 
			
		||||
                  thumbnail={item.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                  thumbnailAspectRatio="21/29.7"
 | 
			
		||||
                  thumbnailRounded={false}
 | 
			
		||||
                  keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                  topChips={
 | 
			
		||||
                    item.attributes.metadata &&
 | 
			
		||||
                    item.attributes.metadata.length > 0 &&
 | 
			
		||||
                    item.attributes.metadata[0]
 | 
			
		||||
                      ? [prettyItemSubType(item.attributes.metadata[0])]
 | 
			
		||||
                      : []
 | 
			
		||||
                  }
 | 
			
		||||
                  bottomChips={item.attributes.categories?.data.map(
 | 
			
		||||
                    (category) => category.attributes?.short ?? ""
 | 
			
		||||
                  )}
 | 
			
		||||
                  metadata={{
 | 
			
		||||
                    currencies: currencies,
 | 
			
		||||
                    release_date: item.attributes.release_date,
 | 
			
		||||
                    price: item.attributes.price,
 | 
			
		||||
                    position: "Bottom",
 | 
			
		||||
                  }}
 | 
			
		||||
                  infoAppend={
 | 
			
		||||
                    <PreviewCardCTAs
 | 
			
		||||
                      id={item.id}
 | 
			
		||||
                      displayCTAs={
 | 
			
		||||
                        !isUntangibleGroupItem(item.attributes.metadata?.[0])
 | 
			
		||||
                      }
 | 
			
		||||
                      langui={langui}
 | 
			
		||||
                    />
 | 
			
		||||
                  }
 | 
			
		||||
                />
 | 
			
		||||
              </Fragment>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
        </Fragment>
 | 
			
		||||
      ))}
 | 
			
		||||
    </ContentPanel>
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import {
 | 
			
		||||
  PostStaticProps,
 | 
			
		||||
} from "graphql/getPostStaticProps";
 | 
			
		||||
import { getReadySdk } from "graphql/sdk";
 | 
			
		||||
import { isDefined } from "helpers/others";
 | 
			
		||||
import { filterHasAttributes, isDefined } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  GetStaticPathsContext,
 | 
			
		||||
@ -48,13 +48,12 @@ export async function getStaticPaths(
 | 
			
		||||
  const sdk = getReadySdk();
 | 
			
		||||
  const posts = await sdk.getPostsSlugs();
 | 
			
		||||
  const paths: GetStaticPathsResult["paths"] = [];
 | 
			
		||||
  if (posts.posts)
 | 
			
		||||
    posts.posts.data.map((item) => {
 | 
			
		||||
      context.locales?.map((local) => {
 | 
			
		||||
        if (item.attributes)
 | 
			
		||||
          paths.push({ params: { slug: item.attributes.slug }, locale: local });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  filterHasAttributes(posts.posts?.data).map((item) => {
 | 
			
		||||
    context.locales?.map((local) =>
 | 
			
		||||
      paths.push({ params: { slug: item.attributes.slug }, locale: local })
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  return {
 | 
			
		||||
    paths,
 | 
			
		||||
    fallback: "blocking",
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ import { WithLabel } from "components/Inputs/WithLabel";
 | 
			
		||||
import { TextInput } from "components/Inputs/TextInput";
 | 
			
		||||
import { Button } from "components/Inputs/Button";
 | 
			
		||||
import { useMediaHoverable } from "hooks/useMediaQuery";
 | 
			
		||||
import { filterHasAttributes } from "helpers/others";
 | 
			
		||||
 | 
			
		||||
interface Props extends AppStaticProps {
 | 
			
		||||
  posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"];
 | 
			
		||||
@ -87,29 +88,27 @@ export default function News(props: Props): JSX.Element {
 | 
			
		||||
        className="grid grid-cols-1 items-end gap-8
 | 
			
		||||
        desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
 | 
			
		||||
      >
 | 
			
		||||
        {filteredItems.map((post) => (
 | 
			
		||||
        {filterHasAttributes(filteredItems).map((post) => (
 | 
			
		||||
          <Fragment key={post.id}>
 | 
			
		||||
            {post.attributes && (
 | 
			
		||||
              <PreviewCard
 | 
			
		||||
                href={`/news/${post.attributes.slug}`}
 | 
			
		||||
                title={
 | 
			
		||||
                  post.attributes.translations?.[0]?.title ??
 | 
			
		||||
                  prettySlug(post.attributes.slug)
 | 
			
		||||
                }
 | 
			
		||||
                description={post.attributes.translations?.[0]?.excerpt}
 | 
			
		||||
                thumbnail={post.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                thumbnailAspectRatio="3/2"
 | 
			
		||||
                thumbnailForceAspectRatio
 | 
			
		||||
                bottomChips={post.attributes.categories?.data.map(
 | 
			
		||||
                  (category) => category.attributes?.short ?? ""
 | 
			
		||||
                )}
 | 
			
		||||
                keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                metadata={{
 | 
			
		||||
                  release_date: post.attributes.date,
 | 
			
		||||
                  position: "Top",
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            <PreviewCard
 | 
			
		||||
              href={`/news/${post.attributes.slug}`}
 | 
			
		||||
              title={
 | 
			
		||||
                post.attributes.translations?.[0]?.title ??
 | 
			
		||||
                prettySlug(post.attributes.slug)
 | 
			
		||||
              }
 | 
			
		||||
              description={post.attributes.translations?.[0]?.excerpt}
 | 
			
		||||
              thumbnail={post.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
              thumbnailAspectRatio="3/2"
 | 
			
		||||
              thumbnailForceAspectRatio
 | 
			
		||||
              bottomChips={post.attributes.categories?.data.map(
 | 
			
		||||
                (category) => category.attributes?.short ?? ""
 | 
			
		||||
              )}
 | 
			
		||||
              keepInfoVisible={keepInfoVisible}
 | 
			
		||||
              metadata={{
 | 
			
		||||
                release_date: post.attributes.date,
 | 
			
		||||
                position: "Top",
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </Fragment>
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
@ -145,19 +144,17 @@ export async function getStaticProps(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sortPosts(posts: Props["posts"]): Props["posts"] {
 | 
			
		||||
  const sortedPosts = [...posts];
 | 
			
		||||
  sortedPosts
 | 
			
		||||
  return posts
 | 
			
		||||
    .sort((a, b) => {
 | 
			
		||||
      const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999";
 | 
			
		||||
      const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999";
 | 
			
		||||
      return dateA.localeCompare(dateB);
 | 
			
		||||
    })
 | 
			
		||||
    .reverse();
 | 
			
		||||
  return sortedPosts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function filterItems(posts: Props["posts"], searchName: string) {
 | 
			
		||||
  return [...posts].filter((post) => {
 | 
			
		||||
  return posts.filter((post) => {
 | 
			
		||||
    if (searchName.length > 1) {
 | 
			
		||||
      if (
 | 
			
		||||
        post.attributes?.translations?.[0]?.title
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,11 @@ import { SubPanel } from "components/Panels/SubPanel";
 | 
			
		||||
import DefinitionCard from "components/Wiki/DefinitionCard";
 | 
			
		||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getReadySdk } from "graphql/sdk";
 | 
			
		||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
 | 
			
		||||
import {
 | 
			
		||||
  filterHasAttributes,
 | 
			
		||||
  isDefined,
 | 
			
		||||
  isDefinedAndNotEmpty,
 | 
			
		||||
} from "helpers/others";
 | 
			
		||||
import { WikiPageWithTranslations } from "helpers/types";
 | 
			
		||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
 | 
			
		||||
import {
 | 
			
		||||
@ -89,20 +93,24 @@ export default function WikiPage(props: Props): JSX.Element {
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          {page.definitions?.map((definition, index) => (
 | 
			
		||||
            <DefinitionCard
 | 
			
		||||
              key={index}
 | 
			
		||||
              source={definition?.source?.data?.attributes?.name}
 | 
			
		||||
              translations={definition?.translations?.map((translation) => ({
 | 
			
		||||
                language: translation?.language?.data?.attributes?.code,
 | 
			
		||||
                definition: translation?.definition,
 | 
			
		||||
                status: translation?.status,
 | 
			
		||||
              }))}
 | 
			
		||||
              index={index + 1}
 | 
			
		||||
              languages={languages}
 | 
			
		||||
              langui={langui}
 | 
			
		||||
            />
 | 
			
		||||
          ))}
 | 
			
		||||
          {filterHasAttributes(page.definitions, ["translations"]).map(
 | 
			
		||||
            (definition, index) => (
 | 
			
		||||
              <DefinitionCard
 | 
			
		||||
                key={index}
 | 
			
		||||
                source={definition.source?.data?.attributes?.name}
 | 
			
		||||
                translations={filterHasAttributes(definition.translations).map(
 | 
			
		||||
                  (translation) => ({
 | 
			
		||||
                    language: translation.language.data?.attributes?.code,
 | 
			
		||||
                    definition: translation.definition,
 | 
			
		||||
                    status: translation.status,
 | 
			
		||||
                  })
 | 
			
		||||
                )}
 | 
			
		||||
                index={index + 1}
 | 
			
		||||
                languages={languages}
 | 
			
		||||
                langui={langui}
 | 
			
		||||
              />
 | 
			
		||||
            )
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </ContentPanel>
 | 
			
		||||
@ -147,14 +155,13 @@ export async function getStaticPaths(
 | 
			
		||||
  const sdk = getReadySdk();
 | 
			
		||||
  const contents = await sdk.getWikiPagesSlugs();
 | 
			
		||||
  const paths: GetStaticPathsResult["paths"] = [];
 | 
			
		||||
  contents.wikiPages?.data.map((wikiPage) => {
 | 
			
		||||
    context.locales?.map((local) => {
 | 
			
		||||
      if (wikiPage.attributes)
 | 
			
		||||
        paths.push({
 | 
			
		||||
          params: { slug: wikiPage.attributes.slug },
 | 
			
		||||
          locale: local,
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
  filterHasAttributes(contents.wikiPages?.data).map((wikiPage) => {
 | 
			
		||||
    context.locales?.map((local) =>
 | 
			
		||||
      paths.push({
 | 
			
		||||
        params: { slug: wikiPage.attributes.slug },
 | 
			
		||||
        locale: local,
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  return {
 | 
			
		||||
    paths,
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
 | 
			
		||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
 | 
			
		||||
import { getReadySdk } from "graphql/sdk";
 | 
			
		||||
import { prettySlug } from "helpers/formatters";
 | 
			
		||||
import { isDefined } from "helpers/others";
 | 
			
		||||
import { filterHasAttributes, isDefined } from "helpers/others";
 | 
			
		||||
import { GetStaticPropsContext } from "next";
 | 
			
		||||
import { Fragment } from "react";
 | 
			
		||||
 | 
			
		||||
@ -70,22 +70,20 @@ export default function Chronology(props: Props): JSX.Element {
 | 
			
		||||
        horizontalLine
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      {chronologyEras.map((era) => (
 | 
			
		||||
      {filterHasAttributes(chronologyEras).map((era) => (
 | 
			
		||||
        <Fragment key={era.id}>
 | 
			
		||||
          {era.attributes && (
 | 
			
		||||
            <NavOption
 | 
			
		||||
              url={`#${era.attributes.slug}`}
 | 
			
		||||
              title={
 | 
			
		||||
                era.attributes.title &&
 | 
			
		||||
                era.attributes.title.length > 0 &&
 | 
			
		||||
                era.attributes.title[0]
 | 
			
		||||
                  ? era.attributes.title[0].title
 | 
			
		||||
                  : prettySlug(era.attributes.slug)
 | 
			
		||||
              }
 | 
			
		||||
              subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`}
 | 
			
		||||
              border
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
          <NavOption
 | 
			
		||||
            url={`#${era.attributes.slug}`}
 | 
			
		||||
            title={
 | 
			
		||||
              era.attributes.title &&
 | 
			
		||||
              era.attributes.title.length > 0 &&
 | 
			
		||||
              era.attributes.title[0]
 | 
			
		||||
                ? era.attributes.title[0].title
 | 
			
		||||
                : prettySlug(era.attributes.slug)
 | 
			
		||||
            }
 | 
			
		||||
            subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`}
 | 
			
		||||
            border
 | 
			
		||||
          />
 | 
			
		||||
        </Fragment>
 | 
			
		||||
      ))}
 | 
			
		||||
    </SubPanel>
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,8 @@ import { Switch } from "components/Inputs/Switch";
 | 
			
		||||
import { TextInput } from "components/Inputs/TextInput";
 | 
			
		||||
import { WithLabel } from "components/Inputs/WithLabel";
 | 
			
		||||
import { useMediaHoverable } from "hooks/useMediaQuery";
 | 
			
		||||
import { filterHasAttributes } from "helpers/others";
 | 
			
		||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
 | 
			
		||||
 | 
			
		||||
interface Props extends AppStaticProps {
 | 
			
		||||
  pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"];
 | 
			
		||||
@ -93,30 +95,37 @@ export default function Wiki(props: Props): JSX.Element {
 | 
			
		||||
        className="grid grid-cols-2 items-end gap-8
 | 
			
		||||
        desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4"
 | 
			
		||||
      >
 | 
			
		||||
        {filteredPages.map((page) => (
 | 
			
		||||
        {/* TODO: Add to langui */}
 | 
			
		||||
        {filteredPages.length === 0 && (
 | 
			
		||||
          <ContentPlaceholder
 | 
			
		||||
            message={
 | 
			
		||||
              "No results. You can try changing or resetting the search parameters."
 | 
			
		||||
            }
 | 
			
		||||
            icon={Icon.ChevronLeft}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
        {filterHasAttributes(filteredPages).map((page) => (
 | 
			
		||||
          <Fragment key={page.id}>
 | 
			
		||||
            {page.attributes && (
 | 
			
		||||
              <TranslatedPreviewCard
 | 
			
		||||
                href={`/wiki/${page.attributes.slug}`}
 | 
			
		||||
                translations={page.attributes.translations?.map(
 | 
			
		||||
                  (translation) => ({
 | 
			
		||||
                    title: translation?.title,
 | 
			
		||||
                    description: translation?.summary,
 | 
			
		||||
                    language: translation?.language?.data?.attributes?.code,
 | 
			
		||||
                  })
 | 
			
		||||
                )}
 | 
			
		||||
                thumbnail={page.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
                thumbnailAspectRatio={"4/3"}
 | 
			
		||||
                thumbnailRounded
 | 
			
		||||
                thumbnailForceAspectRatio
 | 
			
		||||
                languages={languages}
 | 
			
		||||
                slug={page.attributes.slug}
 | 
			
		||||
                keepInfoVisible={keepInfoVisible}
 | 
			
		||||
                bottomChips={page.attributes.categories?.data.map(
 | 
			
		||||
                  (category) => category.attributes?.short ?? ""
 | 
			
		||||
                )}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            <TranslatedPreviewCard
 | 
			
		||||
              href={`/wiki/${page.attributes.slug}`}
 | 
			
		||||
              translations={page.attributes.translations?.map(
 | 
			
		||||
                (translation) => ({
 | 
			
		||||
                  title: translation?.title,
 | 
			
		||||
                  description: translation?.summary,
 | 
			
		||||
                  language: translation?.language?.data?.attributes?.code,
 | 
			
		||||
                })
 | 
			
		||||
              )}
 | 
			
		||||
              thumbnail={page.attributes.thumbnail?.data?.attributes}
 | 
			
		||||
              thumbnailAspectRatio={"4/3"}
 | 
			
		||||
              thumbnailRounded
 | 
			
		||||
              thumbnailForceAspectRatio
 | 
			
		||||
              languages={languages}
 | 
			
		||||
              slug={page.attributes.slug}
 | 
			
		||||
              keepInfoVisible={keepInfoVisible}
 | 
			
		||||
              bottomChips={page.attributes.categories?.data.map(
 | 
			
		||||
                (category) => category.attributes?.short ?? ""
 | 
			
		||||
              )}
 | 
			
		||||
            />
 | 
			
		||||
          </Fragment>
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
@ -150,17 +159,15 @@ export async function getStaticProps(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sortPages(pages: Props["pages"]): Props["pages"] {
 | 
			
		||||
  const sortedPages = [...pages];
 | 
			
		||||
  sortedPages.sort((a, b) => {
 | 
			
		||||
  return pages.sort((a, b) => {
 | 
			
		||||
    const slugA = a.attributes?.slug ?? "";
 | 
			
		||||
    const slugB = b.attributes?.slug ?? "";
 | 
			
		||||
    return slugA.localeCompare(slugB);
 | 
			
		||||
  });
 | 
			
		||||
  return sortedPages;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function filterPages(posts: Props["pages"], searchName: string) {
 | 
			
		||||
  return [...posts].filter((post) => {
 | 
			
		||||
  return posts.filter((post) => {
 | 
			
		||||
    if (searchName.length > 1) {
 | 
			
		||||
      if (
 | 
			
		||||
        post.attributes?.translations?.[0]?.title
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ mark {
 | 
			
		||||
/* SCROLLBARS STYLING */
 | 
			
		||||
 | 
			
		||||
* {
 | 
			
		||||
  @apply [scrollbar-color:theme(colors.dark)_transparent] [scrollbar-width:thin];
 | 
			
		||||
  @apply [scrollbar-color:theme(colors.dark/1)_transparent] [scrollbar-width:thin];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*::-webkit-scrollbar {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user