Added intersection for improved UX on page navigation
This commit is contained in:
parent
b7ebda4f4f
commit
e947fd7a0e
|
@ -1,6 +1,7 @@
|
||||||
import { Ico, Icon } from "./Ico";
|
import { Ico, Icon } from "./Ico";
|
||||||
import { ToolTip } from "./ToolTip";
|
import { ToolTip } from "./ToolTip";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { cJoin } from "helpers/className";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -10,11 +11,12 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const AnchorShare = ({ id, langui }: Props): JSX.Element => (
|
export const AnchorShare = ({ id, langui, className }: Props): JSX.Element => (
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={langui.copy_anchor_link}
|
content={langui.copy_anchor_link}
|
||||||
trigger="mouseenter"
|
trigger="mouseenter"
|
||||||
|
@ -27,7 +29,10 @@ export const AnchorShare = ({ id, langui }: Props): JSX.Element => (
|
||||||
>
|
>
|
||||||
<Ico
|
<Ico
|
||||||
icon={Icon.Link}
|
icon={Icon.Link}
|
||||||
className="transition-color cursor-pointer hover:text-dark"
|
className={cJoin(
|
||||||
|
"transition-color cursor-pointer hover:text-dark",
|
||||||
|
className
|
||||||
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${
|
`${
|
||||||
|
|
|
@ -64,7 +64,6 @@ const ChroniclesList = ({
|
||||||
<div
|
<div
|
||||||
key={chronicle.id}
|
key={chronicle.id}
|
||||||
id={`chronicle-${chronicle.attributes.slug}`}
|
id={`chronicle-${chronicle.attributes.slug}`}
|
||||||
className="scroll-m-[45vh]"
|
|
||||||
>
|
>
|
||||||
{chronicle.attributes.translations.length === 0 &&
|
{chronicle.attributes.translations.length === 0 &&
|
||||||
chronicle.attributes.contents.data.length === 1
|
chronicle.attributes.contents.data.length === 1
|
||||||
|
|
|
@ -7,12 +7,14 @@ import { Img } from "components/Img";
|
||||||
import { InsetBox } from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { slugify } from "helpers/formatters";
|
import { slugify } from "helpers/formatters";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
import { AnchorShare } from "components/AnchorShare";
|
import { AnchorShare } from "components/AnchorShare";
|
||||||
|
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||||
|
import { Ico, Icon } from "components/Ico";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -79,92 +81,30 @@ export const Markdawn = ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
h1: {
|
Header: {
|
||||||
component: (compProps: {
|
component: (compProps: {
|
||||||
id: string;
|
id: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
children: React.ReactNode;
|
children: string;
|
||||||
|
level: string;
|
||||||
}) => (
|
}) => (
|
||||||
<h1 id={compProps.id} style={compProps.style}>
|
<Header
|
||||||
{compProps.children}
|
title={compProps.children}
|
||||||
<AnchorShare id={compProps.id} langui={langui} />
|
langui={langui}
|
||||||
</h1>
|
level={parseInt(compProps.level, 10)}
|
||||||
),
|
slug={compProps.id}
|
||||||
},
|
/>
|
||||||
|
|
||||||
h2: {
|
|
||||||
component: (compProps: {
|
|
||||||
id: string;
|
|
||||||
style: React.CSSProperties;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<h2 id={compProps.id} style={compProps.style}>
|
|
||||||
{compProps.children}
|
|
||||||
<AnchorShare id={compProps.id} langui={langui} />
|
|
||||||
</h2>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
h3: {
|
|
||||||
component: (compProps: {
|
|
||||||
id: string;
|
|
||||||
style: React.CSSProperties;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<h3 id={compProps.id} style={compProps.style}>
|
|
||||||
{compProps.children}
|
|
||||||
<AnchorShare id={compProps.id} langui={langui} />
|
|
||||||
</h3>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
h4: {
|
|
||||||
component: (compProps: {
|
|
||||||
id: string;
|
|
||||||
style: React.CSSProperties;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<h4 id={compProps.id} style={compProps.style}>
|
|
||||||
{compProps.children}
|
|
||||||
<AnchorShare id={compProps.id} langui={langui} />
|
|
||||||
</h4>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
h5: {
|
|
||||||
component: (compProps: {
|
|
||||||
id: string;
|
|
||||||
style: React.CSSProperties;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<h5 id={compProps.id} style={compProps.style}>
|
|
||||||
{compProps.children}
|
|
||||||
<AnchorShare id={compProps.id} langui={langui} />
|
|
||||||
</h5>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
h6: {
|
|
||||||
component: (compProps: {
|
|
||||||
id: string;
|
|
||||||
style: React.CSSProperties;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => (
|
|
||||||
<h6 id={compProps.id} style={compProps.style}>
|
|
||||||
{compProps.children}
|
|
||||||
<AnchorShare id={compProps.id} langui={langui} />
|
|
||||||
</h6>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
SceneBreak: {
|
SceneBreak: {
|
||||||
component: (compProps: { id: string }) => (
|
component: (compProps: { id: string }) => (
|
||||||
<div
|
<Header
|
||||||
id={compProps.id}
|
title={"* * *"}
|
||||||
className={"mt-16 mb-20 h-0 text-center text-3xl text-dark"}
|
langui={langui}
|
||||||
>
|
level={6}
|
||||||
* * *
|
slug={compProps.id}
|
||||||
</div>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -310,14 +250,14 @@ interface TableOfContentsProps {
|
||||||
text: string;
|
text: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
|
horizontalLine?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
||||||
|
|
||||||
export const TableOfContents = ({
|
export const TableOfContents = ({
|
||||||
text,
|
text,
|
||||||
title,
|
title,
|
||||||
langui,
|
langui,
|
||||||
|
horizontalLine = false,
|
||||||
}: TableOfContentsProps): JSX.Element => {
|
}: TableOfContentsProps): JSX.Element => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toc = useMemo(
|
const toc = useMemo(
|
||||||
|
@ -327,9 +267,15 @@ export const TableOfContents = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{toc.children.length > 0 && (
|
||||||
|
<>
|
||||||
|
{horizontalLine && <HorizontalLine />}
|
||||||
<h3 className="text-xl">{langui.table_of_contents}</h3>
|
<h3 className="text-xl">{langui.table_of_contents}</h3>
|
||||||
<div className="max-w-[14.5rem] text-left">
|
<div className="max-w-[14.5rem] text-left">
|
||||||
<p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left">
|
<p
|
||||||
|
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
|
||||||
|
text-left"
|
||||||
|
>
|
||||||
<a onClick={async () => router.replace(`#${toc.slug}`)}>
|
<a onClick={async () => router.replace(`#${toc.slug}`)}>
|
||||||
{<abbr title={toc.title}>{toc.title}</abbr>}
|
{<abbr title={toc.title}>{toc.title}</abbr>}
|
||||||
</a>
|
</a>
|
||||||
|
@ -337,6 +283,8 @@ export const TableOfContents = ({
|
||||||
<TocLevel tocchildren={toc.children} parentNumbering="" />
|
<TocLevel tocchildren={toc.children} parentNumbering="" />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -345,6 +293,80 @@ export const TableOfContents = ({
|
||||||
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
interface HeaderProps {
|
||||||
|
level: number;
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
langui: AppStaticProps["langui"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Header = ({ level, title, slug, langui }: HeaderProps): JSX.Element => {
|
||||||
|
const innerComponent = useMemo(
|
||||||
|
() => (
|
||||||
|
<>
|
||||||
|
<div className="mt-8 mr-2 mb-12 flex place-items-center gap-4">
|
||||||
|
{title === "* * *" ? (
|
||||||
|
<div className="space-x-3 text-dark">
|
||||||
|
<Ico icon={Icon.Emergency} />
|
||||||
|
<Ico icon={Icon.Emergency} />
|
||||||
|
<Ico icon={Icon.Emergency} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="font-headers">{title}</div>
|
||||||
|
)}
|
||||||
|
<AnchorShare
|
||||||
|
className="opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
|
id={slug}
|
||||||
|
langui={langui}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
[langui, slug, title]
|
||||||
|
);
|
||||||
|
|
||||||
|
const className = "group";
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case 1:
|
||||||
|
return (
|
||||||
|
<h1 id={slug} className={className}>
|
||||||
|
{innerComponent}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
case 2:
|
||||||
|
return (
|
||||||
|
<h2 id={slug} className={className}>
|
||||||
|
{innerComponent}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
case 3:
|
||||||
|
return (
|
||||||
|
<h3 id={slug} className={className}>
|
||||||
|
{innerComponent}
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
case 4:
|
||||||
|
return (
|
||||||
|
<h4 id={slug} className={className}>
|
||||||
|
{innerComponent}
|
||||||
|
</h4>
|
||||||
|
);
|
||||||
|
case 5:
|
||||||
|
return (
|
||||||
|
<h5 id={slug} className={className}>
|
||||||
|
{innerComponent}
|
||||||
|
</h5>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<h6 id={slug} className={className}>
|
||||||
|
{innerComponent}
|
||||||
|
</h6>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
interface TocInterface {
|
interface TocInterface {
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
@ -354,19 +376,35 @@ interface TocInterface {
|
||||||
interface LevelProps {
|
interface LevelProps {
|
||||||
tocchildren: TocInterface[];
|
tocchildren: TocInterface[];
|
||||||
parentNumbering: string;
|
parentNumbering: string;
|
||||||
|
allowIntersection?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TocLevel = ({
|
const TocLevel = ({
|
||||||
tocchildren,
|
tocchildren,
|
||||||
parentNumbering,
|
parentNumbering,
|
||||||
|
allowIntersection = true,
|
||||||
}: LevelProps): JSX.Element => {
|
}: LevelProps): JSX.Element => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const ids = useMemo(
|
||||||
|
() => tocchildren.map((child) => child.slug),
|
||||||
|
[tocchildren]
|
||||||
|
);
|
||||||
|
const currentIntersection = useIntersectionList(ids);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ol className="pl-4 text-left">
|
<ol className="pl-4 text-left">
|
||||||
{tocchildren.map((child, childIndex) => (
|
{tocchildren.map((child, childIndex) => (
|
||||||
<Fragment key={child.slug}>
|
<Fragment key={child.slug}>
|
||||||
<li className="my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap">
|
<li
|
||||||
|
className={cJoin(
|
||||||
|
"my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap",
|
||||||
|
cIf(
|
||||||
|
allowIntersection && currentIntersection === childIndex,
|
||||||
|
"text-dark"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>
|
||||||
<span className="text-dark">{`${parentNumbering}${
|
<span className="text-dark">{`${parentNumbering}${
|
||||||
childIndex + 1
|
childIndex + 1
|
||||||
}.`}</span>{" "}
|
}.`}</span>{" "}
|
||||||
|
@ -377,6 +415,9 @@ const TocLevel = ({
|
||||||
<TocLevel
|
<TocLevel
|
||||||
tocchildren={child.children}
|
tocchildren={child.children}
|
||||||
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
||||||
|
allowIntersection={
|
||||||
|
allowIntersection && currentIntersection === childIndex
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
@ -385,19 +426,10 @@ const TocLevel = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭──────────────────────╮
|
* ╭───────────────────╮
|
||||||
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum HeaderLevels {
|
|
||||||
H1 = 1,
|
|
||||||
H2 = 2,
|
|
||||||
H3 = 3,
|
|
||||||
H4 = 4,
|
|
||||||
H5 = 5,
|
|
||||||
H6 = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
|
|
||||||
|
@ -425,28 +457,8 @@ const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
||||||
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
|
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith("# ")) {
|
if (/^[#]+ /u.test(line)) {
|
||||||
return markdawnHeadersParser(HeaderLevels.H1, line, visitedSlugs);
|
return markdawnHeadersParser(line.indexOf(" "), line, visitedSlugs);
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("## ")) {
|
|
||||||
return markdawnHeadersParser(HeaderLevels.H2, line, visitedSlugs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("### ")) {
|
|
||||||
return markdawnHeadersParser(HeaderLevels.H3, line, visitedSlugs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("#### ")) {
|
|
||||||
return markdawnHeadersParser(HeaderLevels.H4, line, visitedSlugs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("##### ")) {
|
|
||||||
return markdawnHeadersParser(HeaderLevels.H5, line, visitedSlugs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith("###### ")) {
|
|
||||||
return markdawnHeadersParser(HeaderLevels.H6, line, visitedSlugs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return line;
|
return line;
|
||||||
|
@ -459,7 +471,7 @@ const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
const markdawnHeadersParser = (
|
const markdawnHeadersParser = (
|
||||||
headerLevel: HeaderLevels,
|
headerLevel: number,
|
||||||
line: string,
|
line: string,
|
||||||
visitedSlugs: string[]
|
visitedSlugs: string[]
|
||||||
): string => {
|
): string => {
|
||||||
|
@ -472,7 +484,7 @@ const markdawnHeadersParser = (
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
visitedSlugs.push(newSlug);
|
visitedSlugs.push(newSlug);
|
||||||
return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`;
|
return `<Header level="${headerLevel}" id="${newSlug}">${lineText}</Header>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
@ -497,10 +509,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
||||||
line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
|
line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
|
||||||
|
|
||||||
text.split("\n").map((line) => {
|
text.split("\n").map((line) => {
|
||||||
if (line.startsWith("<h1 id=")) {
|
if (line.startsWith('<Header level="2"')) {
|
||||||
toc.title = getTitle(line);
|
|
||||||
toc.slug = getSlug(line);
|
|
||||||
} else if (line.startsWith("<h2 id=")) {
|
|
||||||
toc.children.push({
|
toc.children.push({
|
||||||
title: getTitle(line),
|
title: getTitle(line),
|
||||||
slug: getSlug(line),
|
slug: getSlug(line),
|
||||||
|
@ -511,7 +520,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
||||||
h4 = -1;
|
h4 = -1;
|
||||||
h5 = -1;
|
h5 = -1;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (h2 >= 0 && line.startsWith("<h3 id=")) {
|
} else if (h2 >= 0 && line.startsWith('<Header level="3"')) {
|
||||||
toc.children[h2].children.push({
|
toc.children[h2].children.push({
|
||||||
title: getTitle(line),
|
title: getTitle(line),
|
||||||
slug: getSlug(line),
|
slug: getSlug(line),
|
||||||
|
@ -521,7 +530,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
||||||
h4 = -1;
|
h4 = -1;
|
||||||
h5 = -1;
|
h5 = -1;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (h3 >= 0 && line.startsWith("<h4 id=")) {
|
} else if (h3 >= 0 && line.startsWith('<Header level="4"')) {
|
||||||
toc.children[h2].children[h3].children.push({
|
toc.children[h2].children[h3].children.push({
|
||||||
title: getTitle(line),
|
title: getTitle(line),
|
||||||
slug: getSlug(line),
|
slug: getSlug(line),
|
||||||
|
@ -530,7 +539,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
||||||
h4++;
|
h4++;
|
||||||
h5 = -1;
|
h5 = -1;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (h4 >= 0 && line.startsWith("<h5 id=")) {
|
} else if (h4 >= 0 && line.startsWith('<Header level="5"')) {
|
||||||
toc.children[h2].children[h3].children[h4].children.push({
|
toc.children[h2].children[h3].children[h4].children.push({
|
||||||
title: getTitle(line),
|
title: getTitle(line),
|
||||||
slug: getSlug(line),
|
slug: getSlug(line),
|
||||||
|
@ -538,7 +547,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
||||||
});
|
});
|
||||||
h5++;
|
h5++;
|
||||||
scenebreak = 0;
|
scenebreak = 0;
|
||||||
} else if (h5 >= 0 && line.startsWith("<h6 id=")) {
|
} else if (h5 >= 0 && line.startsWith('<Header level="6"')) {
|
||||||
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
||||||
title: getTitle(line),
|
title: getTitle(line),
|
||||||
slug: getSlug(line),
|
slug: getSlug(line),
|
||||||
|
|
|
@ -20,6 +20,7 @@ interface Props {
|
||||||
subtitle?: string | null | undefined;
|
subtitle?: string | null | undefined;
|
||||||
border?: boolean;
|
border?: boolean;
|
||||||
reduced?: boolean;
|
reduced?: boolean;
|
||||||
|
active?: boolean;
|
||||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,12 +33,13 @@ export const NavOption = ({
|
||||||
subtitle,
|
subtitle,
|
||||||
border = false,
|
border = false,
|
||||||
reduced = false,
|
reduced = false,
|
||||||
|
active = false,
|
||||||
onClick,
|
onClick,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isActive = useMemo(
|
const isActive = useMemo(
|
||||||
() => router.asPath.startsWith(url),
|
() => active || router.asPath.startsWith(url),
|
||||||
[url, router.asPath]
|
[active, router.asPath, url]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => (
|
||||||
<div className="grid grid-flow-col place-content-start place-items-center gap-6">
|
<div className="grid grid-flow-col place-content-start place-items-center gap-6">
|
||||||
{recorder.avatar?.data?.attributes && (
|
{recorder.avatar?.data?.attributes && (
|
||||||
<Img
|
<Img
|
||||||
className="w-20 rounded-full border-4 border-mid aspect-square object-cover"
|
className="aspect-square w-20 rounded-full border-4 border-mid object-cover"
|
||||||
src={recorder.avatar.data.attributes}
|
src={recorder.avatar.data.attributes}
|
||||||
quality={ImageQuality.Small}
|
quality={ImageQuality.Small}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { throttle } from "throttle-debounce";
|
||||||
|
import { useIsClient } from "usehooks-ts";
|
||||||
|
import { useOnScroll, AnchorIds } from "./useScrollTopOnChange";
|
||||||
|
import { isDefined } from "helpers/others";
|
||||||
|
|
||||||
|
export const useIntersectionList = (ids: string[]): number => {
|
||||||
|
const [currentIntersection, setCurrentIntersection] = useState(-1);
|
||||||
|
|
||||||
|
const isClient = useIsClient();
|
||||||
|
|
||||||
|
const contentPanel = useMemo(
|
||||||
|
() => (isClient ? document.getElementById(AnchorIds.ContentPanel) : null),
|
||||||
|
[isClient]
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshCurrentIntersection = useCallback(
|
||||||
|
(scroll: number) => {
|
||||||
|
console.log("update");
|
||||||
|
|
||||||
|
if (!isDefined(contentPanel)) {
|
||||||
|
setCurrentIntersection(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let idIndex = 0; idIndex < ids.length; idIndex++) {
|
||||||
|
const elem = document.getElementById(ids[ids.length - 1 - idIndex]);
|
||||||
|
const halfScreenOffset = window.screen.height / 2;
|
||||||
|
|
||||||
|
if (isDefined(elem) && scroll > elem.offsetTop - halfScreenOffset) {
|
||||||
|
setCurrentIntersection(ids.length - 1 - idIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCurrentIntersection(-1);
|
||||||
|
},
|
||||||
|
[ids, contentPanel]
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const throttledRefreshCurrentIntersection = useCallback(
|
||||||
|
throttle(100, refreshCurrentIntersection),
|
||||||
|
[refreshCurrentIntersection]
|
||||||
|
);
|
||||||
|
|
||||||
|
useOnScroll(AnchorIds.ContentPanel, throttledRefreshCurrentIntersection);
|
||||||
|
|
||||||
|
useEffect(() => refreshCurrentIntersection(0), [refreshCurrentIntersection]);
|
||||||
|
|
||||||
|
return currentIntersection;
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
import { DependencyList, useEffect } from "react";
|
import { DependencyList, useCallback, useEffect, useMemo } from "react";
|
||||||
|
import { useIsClient } from "usehooks-ts";
|
||||||
|
|
||||||
export enum AnchorIds {
|
export enum AnchorIds {
|
||||||
ContentPanel = "contentPanel495922447721572",
|
ContentPanel = "contentPanel495922447721572",
|
||||||
|
@ -18,3 +19,23 @@ export const useScrollTopOnChange = (
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [id, ...deps, enabled]);
|
}, [id, ...deps, enabled]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useOnScroll = (
|
||||||
|
id: AnchorIds,
|
||||||
|
onScroll: (scroll: number) => void
|
||||||
|
): void => {
|
||||||
|
const isClient = useIsClient();
|
||||||
|
const elem = useMemo(
|
||||||
|
() => (isClient ? document.querySelector(`#${id}`) : null),
|
||||||
|
[id, isClient]
|
||||||
|
);
|
||||||
|
const listener = useCallback(() => {
|
||||||
|
if (elem?.scrollTop) {
|
||||||
|
onScroll(elem.scrollTop);
|
||||||
|
}
|
||||||
|
}, [elem?.scrollTop, onScroll]);
|
||||||
|
useEffect(() => {
|
||||||
|
elem?.addEventListener("scroll", listener);
|
||||||
|
return () => elem?.removeEventListener("scrool", listener);
|
||||||
|
}, [elem, listener]);
|
||||||
|
};
|
||||||
|
|
|
@ -41,10 +41,7 @@ export const useSmartLanguage = <T>({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedTranslationIndex(
|
setSelectedTranslationIndex(
|
||||||
getPreferredLanguage(
|
getPreferredLanguage(preferredLanguages, availableLocales)
|
||||||
preferredLanguages,
|
|
||||||
availableLocales
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}, [preferredLanguages, availableLocales, router.locale]);
|
}, [preferredLanguages, availableLocales, router.locale]);
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,7 @@ const Revalidate = (
|
||||||
|
|
||||||
case "content": {
|
case "content": {
|
||||||
paths.push(`/contents`);
|
paths.push(`/contents`);
|
||||||
|
paths.push(`/contents/all`);
|
||||||
paths.push(`/contents/${body.entry.slug}`);
|
paths.push(`/contents/${body.entry.slug}`);
|
||||||
if (body.entry.folder?.slug) {
|
if (body.entry.folder?.slug) {
|
||||||
paths.push(`/contents/folder/${body.entry.folder.slug}`);
|
paths.push(`/contents/folder/${body.entry.folder.slug}`);
|
||||||
|
|
|
@ -123,11 +123,12 @@ const Content = ({
|
||||||
<TranslatedReturnButton
|
<TranslatedReturnButton
|
||||||
{...returnButtonProps}
|
{...returnButtonProps}
|
||||||
displayOn={ReturnButtonType.Desktop}
|
displayOn={ReturnButtonType.Desktop}
|
||||||
horizontalLine
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.source_language?.data?.attributes
|
{selectedTranslation?.text_set?.source_language?.data?.attributes
|
||||||
?.code !== undefined && (
|
?.code !== undefined && (
|
||||||
|
<>
|
||||||
|
<HorizontalLine />
|
||||||
<div className="grid gap-5">
|
<div className="grid gap-5">
|
||||||
<h2 className="text-xl">
|
<h2 className="text-xl">
|
||||||
{selectedTranslation.text_set.source_language.data.attributes
|
{selectedTranslation.text_set.source_language.data.attributes
|
||||||
|
@ -145,8 +146,8 @@ const Content = ({
|
||||||
</p>
|
</p>
|
||||||
<Chip
|
<Chip
|
||||||
text={prettyLanguage(
|
text={prettyLanguage(
|
||||||
selectedTranslation.text_set.source_language.data.attributes
|
selectedTranslation.text_set.source_language.data
|
||||||
.code,
|
.attributes.code,
|
||||||
languages
|
languages
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -245,6 +246,22 @@ const Content = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedTranslation?.text_set?.text && (
|
||||||
|
<>
|
||||||
|
<TableOfContents
|
||||||
|
text={selectedTranslation.text_set.text}
|
||||||
|
title={prettyInlineTitle(
|
||||||
|
selectedTranslation.pre_title,
|
||||||
|
selectedTranslation.title,
|
||||||
|
selectedTranslation.subtitle
|
||||||
|
)}
|
||||||
|
langui={langui}
|
||||||
|
horizontalLine
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{content.ranged_contents?.data &&
|
{content.ranged_contents?.data &&
|
||||||
|
@ -315,21 +332,6 @@ const Content = ({
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.text && (
|
|
||||||
<>
|
|
||||||
<HorizontalLine />
|
|
||||||
<TableOfContents
|
|
||||||
text={selectedTranslation.text_set.text}
|
|
||||||
title={prettyInlineTitle(
|
|
||||||
selectedTranslation.pre_title,
|
|
||||||
selectedTranslation.title,
|
|
||||||
selectedTranslation.subtitle
|
|
||||||
)}
|
|
||||||
langui={langui}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
|
|
|
@ -56,6 +56,20 @@ import { cJoin, cIf } from "helpers/className";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import { getDescription } from "helpers/description";
|
import { getDescription } from "helpers/description";
|
||||||
|
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ╭─────────────╮
|
||||||
|
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
|
||||||
|
*/
|
||||||
|
|
||||||
|
const intersectionIds = [
|
||||||
|
"summary",
|
||||||
|
"gallery",
|
||||||
|
"details",
|
||||||
|
"subitems",
|
||||||
|
"contents",
|
||||||
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -90,6 +104,8 @@ const LibrarySlug = ({
|
||||||
|
|
||||||
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
useScrollTopOnChange(AnchorIds.ContentPanel, [item]);
|
||||||
|
|
||||||
|
const currentIntersection = useIntersectionList(intersectionIds);
|
||||||
|
|
||||||
const isVariantSet = useMemo(
|
const isVariantSet = useMemo(
|
||||||
() =>
|
() =>
|
||||||
item.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
|
item.metadata?.[0]?.__typename === "ComponentMetadataGroup" &&
|
||||||
|
@ -118,29 +134,57 @@ const LibrarySlug = ({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<NavOption title={langui.summary} url="#summary" border />
|
<NavOption
|
||||||
|
title={langui.summary}
|
||||||
|
url={`#${intersectionIds[0]}`}
|
||||||
|
border
|
||||||
|
active={currentIntersection === 0}
|
||||||
|
/>
|
||||||
|
|
||||||
{item.gallery && item.gallery.data.length > 0 && (
|
{item.gallery && item.gallery.data.length > 0 && (
|
||||||
<NavOption title={langui.gallery} url="#gallery" border />
|
<NavOption
|
||||||
|
title={langui.gallery}
|
||||||
|
url={`#${intersectionIds[1]}`}
|
||||||
|
border
|
||||||
|
active={currentIntersection === 1}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<NavOption title={langui.details} url="#details" border />
|
<NavOption
|
||||||
|
title={langui.details}
|
||||||
|
url={`#${intersectionIds[2]}`}
|
||||||
|
border
|
||||||
|
active={currentIntersection === 2}
|
||||||
|
/>
|
||||||
|
|
||||||
{item.subitems && item.subitems.data.length > 0 && (
|
{item.subitems && item.subitems.data.length > 0 && (
|
||||||
<NavOption
|
<NavOption
|
||||||
title={isVariantSet ? langui.variants : langui.subitems}
|
title={isVariantSet ? langui.variants : langui.subitems}
|
||||||
url={isVariantSet ? "#variants" : "#subitems"}
|
url={`#${intersectionIds[3]}`}
|
||||||
border
|
border
|
||||||
|
active={currentIntersection === 3}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{item.contents && item.contents.data.length > 0 && (
|
{item.contents && item.contents.data.length > 0 && (
|
||||||
<NavOption title={langui.contents} url="#contents" border />
|
<NavOption
|
||||||
|
title={langui.contents}
|
||||||
|
url={`#${intersectionIds[4]}`}
|
||||||
|
border
|
||||||
|
active={currentIntersection === 4}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[isVariantSet, item.contents, item.gallery, item.subitems, langui]
|
[
|
||||||
|
currentIntersection,
|
||||||
|
isVariantSet,
|
||||||
|
item.contents,
|
||||||
|
item.gallery,
|
||||||
|
item.subitems,
|
||||||
|
langui,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
@ -181,7 +225,7 @@ const LibrarySlug = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InsetBox id="summary" className="grid place-items-center">
|
<InsetBox id={intersectionIds[0]} className="grid place-items-center">
|
||||||
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-8">
|
||||||
{item.subitem_of?.data[0]?.attributes && (
|
{item.subitem_of?.data[0]?.attributes && (
|
||||||
<div className="grid place-items-center">
|
<div className="grid place-items-center">
|
||||||
|
@ -246,7 +290,10 @@ const LibrarySlug = ({
|
||||||
</InsetBox>
|
</InsetBox>
|
||||||
|
|
||||||
{item.gallery && item.gallery.data.length > 0 && (
|
{item.gallery && item.gallery.data.length > 0 && (
|
||||||
<div id="gallery" className="grid w-full place-items-center gap-8">
|
<div
|
||||||
|
id={intersectionIds[1]}
|
||||||
|
className="grid w-full place-items-center gap-8"
|
||||||
|
>
|
||||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||||
<div
|
<div
|
||||||
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
||||||
|
@ -282,7 +329,7 @@ const LibrarySlug = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<InsetBox id="details" className="grid place-items-center">
|
<InsetBox id={intersectionIds[2]} className="grid place-items-center">
|
||||||
<div className="place-items grid w-[clamp(0px,100%,42rem)] gap-8">
|
<div className="place-items grid w-[clamp(0px,100%,42rem)] gap-8">
|
||||||
<h2 className="text-center text-2xl">{langui.details}</h2>
|
<h2 className="text-center text-2xl">{langui.details}</h2>
|
||||||
<div
|
<div
|
||||||
|
@ -438,7 +485,7 @@ const LibrarySlug = ({
|
||||||
|
|
||||||
{item.subitems && item.subitems.data.length > 0 && (
|
{item.subitems && item.subitems.data.length > 0 && (
|
||||||
<div
|
<div
|
||||||
id={isVariantSet ? "variants" : "subitems"}
|
id={intersectionIds[3]}
|
||||||
className="grid w-full place-items-center gap-8"
|
className="grid w-full place-items-center gap-8"
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl">
|
<h2 className="text-2xl">
|
||||||
|
@ -500,7 +547,10 @@ const LibrarySlug = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{item.contents && item.contents.data.length > 0 && (
|
{item.contents && item.contents.data.length > 0 && (
|
||||||
<div id="contents" className="grid w-full place-items-center gap-8">
|
<div
|
||||||
|
id={intersectionIds[4]}
|
||||||
|
className="grid w-full place-items-center gap-8"
|
||||||
|
>
|
||||||
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
||||||
{displayOpenScans && (
|
{displayOpenScans && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -44,6 +44,7 @@ import { isInteger } from "helpers/numbers";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||||
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
||||||
|
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -71,6 +72,15 @@ const LibrarySlug = ({
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
|
|
||||||
|
const ids = useMemo(
|
||||||
|
() =>
|
||||||
|
filterHasAttributes(item.contents?.data, [
|
||||||
|
"attributes.slug",
|
||||||
|
] as const).map((content) => content.attributes.slug),
|
||||||
|
[item.contents?.data]
|
||||||
|
);
|
||||||
|
const currentIntersection = useIntersectionList(ids);
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -121,7 +131,7 @@ const LibrarySlug = ({
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map(
|
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map(
|
||||||
(content) => (
|
(content, index) => (
|
||||||
<>
|
<>
|
||||||
{content.attributes.scan_set &&
|
{content.attributes.scan_set &&
|
||||||
content.attributes.scan_set.length > 0 && (
|
content.attributes.scan_set.length > 0 && (
|
||||||
|
@ -158,6 +168,7 @@ const LibrarySlug = ({
|
||||||
: undefined,
|
: undefined,
|
||||||
}}
|
}}
|
||||||
border
|
border
|
||||||
|
active={index === currentIntersection}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -167,6 +178,7 @@ const LibrarySlug = ({
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
currencies,
|
currencies,
|
||||||
|
currentIntersection,
|
||||||
item.categories?.data,
|
item.categories?.data,
|
||||||
item.contents?.data,
|
item.contents?.data,
|
||||||
item.metadata,
|
item.metadata,
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { AnchorShare } from "components/AnchorShare";
|
||||||
import { datePickerToDate } from "helpers/date";
|
import { datePickerToDate } from "helpers/date";
|
||||||
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
import { TranslatedProps } from "helpers/types/TranslatedProps";
|
||||||
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
||||||
|
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -52,6 +53,16 @@ const Chronology = ({
|
||||||
languages,
|
languages,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
|
const ids = useMemo(
|
||||||
|
() =>
|
||||||
|
filterHasAttributes(chronologyEras, ["attributes"] as const).map(
|
||||||
|
(era) => era.attributes.slug
|
||||||
|
),
|
||||||
|
[chronologyEras]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentIntersection = useIntersectionList(ids);
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
|
@ -64,7 +75,7 @@ const Chronology = ({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{filterHasAttributes(chronologyEras, ["attributes", "id"] as const).map(
|
{filterHasAttributes(chronologyEras, ["attributes", "id"] as const).map(
|
||||||
(era) => (
|
(era, index) => (
|
||||||
<Fragment key={era.id}>
|
<Fragment key={era.id}>
|
||||||
<TranslatedNavOption
|
<TranslatedNavOption
|
||||||
translations={filterHasAttributes(era.attributes.title, [
|
translations={filterHasAttributes(era.attributes.title, [
|
||||||
|
@ -80,13 +91,14 @@ const Chronology = ({
|
||||||
}}
|
}}
|
||||||
url={`#${era.attributes.slug}`}
|
url={`#${era.attributes.slug}`}
|
||||||
border
|
border
|
||||||
|
active={currentIntersection === index}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[chronologyEras, langui]
|
[chronologyEras, currentIntersection, langui]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@apply box-border scroll-m-8 scroll-smooth font-body font-medium;
|
@apply box-border scroll-m-[40vh] scroll-smooth font-body font-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
|
Loading…
Reference in New Issue