Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 946f61bec8
Bump @fontsource/opendyslexic from 5.0.2 to 5.0.3
Bumps [@fontsource/opendyslexic](https://github.com/fontsource/font-files/tree/HEAD/fonts/other/opendyslexic) from 5.0.2 to 5.0.3.
- [Changelog](https://github.com/fontsource/font-files/blob/main/fonts/other/opendyslexic/CHANGELOG.md)
- [Commits](https://github.com/fontsource/font-files/commits/HEAD/fonts/other/opendyslexic)

---
updated-dependencies:
- dependency-name: "@fontsource/opendyslexic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 02:59:28 +00:00
34 changed files with 5442 additions and 4916 deletions

View File

@ -161,6 +161,7 @@ module.exports = {
"@typescript-eslint/no-invalid-void-type": "error",
"@typescript-eslint/no-meaningless-void-operator": "error",
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",
"@typescript-eslint/no-parameter-properties": "error",
"@typescript-eslint/no-require-imports": "error",
// "@typescript-eslint/no-type-alias": "warn",
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn",
@ -181,6 +182,7 @@ module.exports = {
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/sort-type-union-intersection-members": "warn",
// "@typescript-eslint/strict-boolean-expressions": [
// "error",
// { allowAny: true },
@ -190,6 +192,7 @@ module.exports = {
"@typescript-eslint/unified-signatures": "error",
/* EXTENSION OF ESLINT */
"@typescript-eslint/no-duplicate-imports": "error",
"@typescript-eslint/default-param-last": "warn",
"@typescript-eslint/dot-notation": "warn",
"@typescript-eslint/init-declarations": "warn",

View File

@ -3,3 +3,5 @@ interactive: true
format: "group"
reject:
- "react-hotkeys-hook" # we are stuck at version 3.4.7 because 4.X is not working well. Need more experimenting.
- "graphql-request" # we are stuck at version 5.1.0 because 5.2.0 has a typescript bug see https://github.com/dotansimha/graphql-code-generator/issues/9046
- "@graphql-codegen/typescript-graphql-request" # same as for "graphql-request"

6345
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,71 +21,71 @@
"upgrade": "ncu"
},
"dependencies": {
"@fontsource/noto-serif-jp": "^5.0.7",
"@fontsource/opendyslexic": "^5.0.7",
"@fontsource/share-tech-mono": "^5.0.8",
"@fontsource/vollkorn": "^5.0.9",
"@fontsource/zen-maru-gothic": "^5.0.7",
"@formatjs/icu-messageformat-parser": "^2.6.0",
"@fontsource/noto-serif-jp": "^5.0.2",
"@fontsource/opendyslexic": "^5.0.3",
"@fontsource/share-tech-mono": "^5.0.2",
"@fontsource/vollkorn": "^5.0.2",
"@fontsource/zen-maru-gothic": "^5.0.2",
"@formatjs/icu-messageformat-parser": "^2.4.0",
"@tippyjs/react": "^4.2.6",
"autoprefixer": "^10.4.15",
"autoprefixer": "^10.4.14",
"cuid": "^2.1.8",
"html-to-text": "^9.0.5",
"intl-messageformat": "^10.5.0",
"isomorphic-dompurify": "^1.8.0",
"jotai": "^2.3.1",
"markdown-to-jsx": "^7.3.2",
"marked": "^7.0.3",
"material-symbols": "^0.10.4",
"meilisearch": "^0.34.1",
"next": "^13.4.17",
"nodemailer": "^6.9.4",
"patch-package": "^8.0.0",
"rc-slider": "^10.2.1",
"intl-messageformat": "^10.3.5",
"isomorphic-dompurify": "^1.6.0",
"jotai": "^2.1.1",
"markdown-to-jsx": "^7.2.1",
"marked": "^4.3.0",
"material-symbols": "^0.5.5",
"meilisearch": "^0.33.0",
"next": "^13.4.4",
"nodemailer": "^6.9.3",
"patch-package": "^7.0.0",
"rc-slider": "^10.2.0",
"react": "^18.2.0",
"react-collapsible": "^2.10.0",
"react-dom": "18.2.0",
"react-hotkeys-hook": "^3.4.7",
"react-swipeable": "^7.0.1",
"react-zoom-pan-pinch": "^3.1.0",
"react-zoom-pan-pinch": "^3.0.8",
"string-natural-compare": "^3.0.1",
"throttle-debounce": "^5.0.0",
"tippy.js": "^6.3.7",
"turndown": "^7.1.2",
"ua-parser-js": "^1.0.35",
"usehooks-ts": "^2.9.1",
"zod": "^3.22.1"
"zod": "^3.21.4"
},
"devDependencies": {
"@digitak/esrun": "3.2.24",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-graphql-request": "5.0.0",
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/typescript": "3.0.4",
"@graphql-codegen/typescript-graphql-request": "^4.5.9",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@types/html-to-text": "^9.0.1",
"@types/marked": "^5.0.1",
"@types/node": "20.5.0",
"@types/nodemailer": "^6.4.9",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/marked": "^4.3.0",
"@types/node": "20.2.5",
"@types/nodemailer": "^6.4.8",
"@types/react": "^18.2.9",
"@types/react-dom": "^18.2.4",
"@types/string-natural-compare": "^3.0.2",
"@types/throttle-debounce": "^5.0.0",
"@types/turndown": "^5.0.1",
"@types/ua-parser-js": "^0.7.36",
"@typescript-eslint/eslint-plugin": "^6.4.0",
"@typescript-eslint/parser": "^6.4.0",
"chalk": "^5.3.0",
"dotenv": "^16.3.1",
"eslint": "^8.47.0",
"eslint-config-next": "13.4.17",
"eslint-plugin-import": "^2.28.0",
"graphql": "16.8.0",
"graphql-request": "6.1.0",
"next-sitemap": "^4.2.2",
"prettier": "^3.0.2",
"prettier-plugin-tailwindcss": "^0.5.3",
"tailwindcss": "^3.3.3",
"ts-unused-exports": "^10.0.0",
"typescript": "^5.1.6"
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"chalk": "^5.2.0",
"dotenv": "^16.1.4",
"eslint": "^8.42.0",
"eslint-config-next": "13.4.4",
"eslint-plugin-import": "^2.27.5",
"graphql": "^16.6.0",
"graphql-request": "5.1.0",
"next-sitemap": "^4.1.3",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2",
"ts-unused-exports": "^9.0.4",
"typescript": "^5.1.3"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -162,10 +162,9 @@ export const AppLayout = ({
</div>
{/* Background when navbar is opened */}
<div
className={cJoin(
`absolute inset-0 z-40 transition-filter duration-500
`absolute inset-0 transition-filter duration-500
[grid-area:content]`,
cIf(
(isMainPanelOpened || isSubPanelOpened) && is1ColumnLayout,

View File

@ -19,11 +19,10 @@ interface Props {
export const ChroniclesLists = ({ chapters, currentChronicleSlug }: Props): JSX.Element => {
const [openedIndex, setOpenedIndex] = useState(
currentChronicleSlug
? chapters.findIndex(
(chapter) =>
chapter.attributes?.chronicles?.data.some(
(chronicle) => chronicle.attributes?.slug === currentChronicleSlug
)
? chapters.findIndex((chapter) =>
chapter.attributes?.chronicles?.data.some(
(chronicle) => chronicle.attributes?.slug === currentChronicleSlug
)
)
: -1
);

View File

@ -5,7 +5,7 @@ import { atoms } from "contexts/atoms";
import { isUndefined } from "helpers/asserts";
import { useAtomGetter } from "helpers/atoms";
import { useFormat } from "hooks/useFormat";
import { useScrollTopOnChange } from "hooks/useScrollOnChange";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { Ids } from "types/ids";
/*

View File

@ -1,60 +0,0 @@
import { useRef } from "react";
import { Button, TranslatedButton } from "components/Inputs/Button";
import { atoms } from "contexts/atoms";
import { ParentFolderPreviewFragment } from "graphql/generated";
import { useAtomSetter } from "helpers/atoms";
import { useScrollRightOnChange } from "hooks/useScrollOnChange";
import { Ids } from "types/ids";
import { filterHasAttributes } from "helpers/asserts";
import { prettySlug } from "helpers/formatters";
import { Ico } from "components/Ico";
interface Props {
path: ParentFolderPreviewFragment[];
}
export const FolderPath = ({ path }: Props): JSX.Element => {
useScrollRightOnChange(Ids.ContentsFolderPath, [path]);
const setMenuGesturesEnabled = useAtomSetter(atoms.layout.menuGesturesEnabled);
const gestureReenableTimeout = useRef<NodeJS.Timeout>();
return (
<div className="grid">
<div
id={Ids.ContentsFolderPath}
onPointerEnter={() => {
if (gestureReenableTimeout.current) clearTimeout(gestureReenableTimeout.current);
setMenuGesturesEnabled(false);
}}
onPointerLeave={() => {
gestureReenableTimeout.current = setTimeout(() => setMenuGesturesEnabled(true), 500);
}}
className={`-mx-4 flex place-items-center justify-start gap-x-1 gap-y-4
overflow-x-auto px-4 pb-10 scrollbar-none`}>
{path.map((pathFolder, index) => (
<>
{pathFolder.slug === "root" ? (
<Button href="/contents" icon="home" active={index === path.length - 1} />
) : (
<TranslatedButton
className="w-max"
href={`/contents/folder/${pathFolder.slug}`}
translations={filterHasAttributes(pathFolder.titles, [
"language.data.attributes.code",
]).map((title) => ({
language: title.language.data.attributes.code,
text: title.title,
}))}
fallback={{
text: prettySlug(pathFolder.slug),
}}
active={index === path.length - 1}
/>
)}
{index < path.length - 1 && <Ico icon="chevron_right" />}
</>
))}
</div>
</div>
);
};

View File

@ -2,8 +2,10 @@ import { useCallback } from "react";
import { Button } from "components/Inputs/Button";
import { TranslatedProps } from "types/TranslatedProps";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { isUndefined } from "helpers/asserts";
import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms";
import { useFormat } from "hooks/useFormat";
import { cJoin } from "helpers/className";
/*
*
@ -13,18 +15,27 @@ import { cJoin } from "helpers/className";
interface Props {
href: string;
title: string | null | undefined;
displayOnlyOn?: "1ColumnLayout" | "3ColumnsLayout";
className?: string;
}
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ReturnButton = ({ href, title, className }: Props): JSX.Element => {
export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => {
const { format } = useFormat();
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
return (
<div className={cJoin("mx-auto w-full max-w-lg place-self-center", className)}>
<Button href={href} text={format("return_to_x", { x: title })} icon="navigate_before" />
</div>
<>
{((is3ColumnsLayout && displayOnlyOn === "3ColumnsLayout") ||
(!is3ColumnsLayout && displayOnlyOn === "1ColumnLayout") ||
isUndefined(displayOnlyOn)) && (
<div className={className}>
<Button href={href} text={format("return_to_x", { x: title })} icon="navigate_before" />
</div>
)}
</>
);
};

View File

@ -1,4 +1,4 @@
import { useCallback } from "react";
import { Fragment, useCallback } from "react";
import { AppLayout, AppLayoutRequired } from "./AppLayout";
import { getTocFromMarkdawn, Markdawn, TableOfContents } from "./Markdown/Markdawn";
import { ReturnButton } from "./PanelComponents/ReturnButton";
@ -91,8 +91,13 @@ export const PostPage = ({
const contentPanel = (
<ContentPanel>
{is1ColumnLayout && returnHref && returnTitle && (
<ReturnButton href={returnHref} title={returnTitle} className="mb-10" />
{returnHref && returnTitle && (
<ReturnButton
href={returnHref}
title={returnTitle}
displayOnlyOn={"1ColumnLayout"}
className="mb-10"
/>
)}
{displayThumbnailHeader ? (
@ -104,7 +109,6 @@ export const PostPage = ({
categories={filterHasAttributes(post.categories?.data, ["attributes"]).map((category) =>
formatCategory(category.attributes.slug)
)}
releaseDate={post.date}
languageSwitcher={
languageSwitcherProps.locales.size > 1 ? (
<LanguageSwitcher {...languageSwitcherProps} />

View File

@ -14,7 +14,6 @@ import { TranslatedProps } from "types/TranslatedProps";
import { atoms } from "contexts/atoms";
import { useAtomGetter } from "helpers/atoms";
import { useFormat } from "hooks/useFormat";
import { isDefined } from "helpers/asserts";
/*
*
@ -85,7 +84,7 @@ export const PreviewCard = ({
const metadataJSX = (
<>
{metadata && (isDefined(metadata.releaseDate) || isDefined(metadata.price)) && (
{metadata && (metadata.releaseDate || metadata.price) && (
<div className="flex w-full flex-row flex-wrap gap-x-3">
{metadata.releaseDate && (
<p className="text-sm">

View File

@ -2,7 +2,7 @@ import { Chip } from "components/Chip";
import { Img } from "components/Img";
import { InsetBox } from "components/Containers/InsetBox";
import { Markdawn } from "components/Markdown/Markdawn";
import { DatePickerFragment, UploadImageFragment } from "graphql/generated";
import { UploadImageFragment } from "graphql/generated";
import { prettyInlineTitle, slugify } from "helpers/formatters";
import { ImageQuality } from "helpers/img";
import { useAtomGetter } from "helpers/atoms";
@ -21,7 +21,6 @@ interface Props {
description?: string | null | undefined;
type?: string;
categories?: string[];
releaseDate?: DatePickerFragment;
thumbnail?: UploadImageFragment | null | undefined;
className?: string;
languageSwitcher?: JSX.Element;
@ -38,10 +37,9 @@ export const ThumbnailHeader = ({
categories,
description,
languageSwitcher,
releaseDate,
className,
}: Props): JSX.Element => {
const { format, formatDate } = useFormat();
const { format } = useFormat();
const { showLightBox } = useAtomGetter(atoms.lightBox);
return (
@ -78,15 +76,6 @@ export const ThumbnailHeader = ({
</div>
)}
{releaseDate && (
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{format("release_date")}</h3>
<div className="flex flex-row flex-wrap">
<Chip text={formatDate(releaseDate)} />
</div>
</div>
)}
{categories && categories.length > 0 && (
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{format("category", { count: categories.length })}</h3>

View File

@ -1,6 +1,6 @@
import { convert } from "html-to-text";
import { sanitize } from "isomorphic-dompurify";
import { Renderer, marked } from "marked";
import { marked } from "marked";
import { isDefinedAndNotEmpty } from "./asserts";
export const prettySlug = (slug?: string, parentSlug?: string): string => {
@ -101,7 +101,7 @@ export const prettyMarkdown = (markdown: string): string => {
const newline = () => "\n";
const empty = () => "";
const TxtRenderer: Renderer = {
const TxtRenderer: marked.Renderer = {
// Block elements
code: escapeBlock,
blockquote: block,

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { MeiliSearch } from "meilisearch";
import type {
SearchParams,
@ -75,6 +73,7 @@ export const filterHitsWithHighlight = <T extends MeiliDocumentsType["documents"
return result;
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const meiliSearch = async <I extends MeiliDocumentsType["index"]>(
indexName: I,
query: string,

View File

@ -14,15 +14,3 @@ export const useScrollTopOnChange = (id: Ids, deps: DependencyList, enabled = tr
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, ...deps, enabled]);
};
// Scroll to top of element "id" when "deps" update.
export const useScrollRightOnChange = (id: Ids, deps: DependencyList, enabled = true): void => {
useEffect(() => {
if (enabled) {
logger.log("Change detected. Scrolling to right");
const elem = document.querySelector(`#${CSS.escape(id)}`);
elem?.scrollTo({ left: elem.scrollWidth, behavior: "smooth" });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, ...deps, enabled]);
};

View File

@ -22,12 +22,11 @@ import { useTypedRouter } from "hooks/useTypedRouter";
import { Select } from "components/Inputs/Select";
import { sendAnalytics } from "helpers/analytics";
import { Button } from "components/Inputs/Button";
import { GetVideoChannelQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { Paginator } from "components/Containers/Paginator";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
*
@ -55,18 +54,15 @@ const queryParamSchema = z.object({
*/
interface Props extends AppLayoutRequired {
channel: {
uid: string;
title: string;
subscribers: number;
};
channel: NonNullable<
NonNullable<GetVideoChannelQuery["videoChannels"]>["data"][number]["attributes"]
>;
}
const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
const { format } = useFormat();
const hoverable = useDeviceSupportsHover();
const router = useTypedRouter(queryParamSchema);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const sortingMethods = useMemo(
() => [
@ -151,27 +147,14 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos/Channel", "Change search term");
} else {
sendAnalytics("Videos/Channel", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
{!is1ColumnLayout && (
<ReturnButton href="/archives/videos" title={format("videos")} className="mb-10" />
)}
<ReturnButton
href="/archives/videos"
title={format("videos")}
displayOnlyOn={"3ColumnsLayout"}
className="mb-10"
/>
<PanelHeader
icon="movie"
@ -183,7 +166,20 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos", "Change search term");
} else {
sendAnalytics("Videos", "Clear search term");
}
}}
/>
<WithLabel label={format("order_by")}>
<Select
@ -194,7 +190,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
setPage(1);
setSortingMethod(newSort);
sendAnalytics(
"Videos/Channel",
"Videos",
`Change sorting method (${
sortingMethods.map((item) => item.meiliAttribute)[newSort]
})`
@ -228,7 +224,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
setQuery(DEFAULT_FILTERS_STATE.searchName);
setSortingMethod(DEFAULT_FILTERS_STATE.sortingMethod);
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
sendAnalytics("Videos/Channel", "Reset all filters");
sendAnalytics("Videos", "Reset all filters");
}}
/>
</SubPanel>
@ -236,7 +232,6 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={videos?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start
@ -283,23 +278,14 @@ export default Channel;
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const { format } = getFormat(context.locale);
const videoChannel = (
await sdk.getVideoChannel({
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
})
).videoChannels?.data[0]?.attributes;
if (!videoChannel) return { notFound: true };
const channel: Props["channel"] = {
uid: videoChannel.uid,
subscribers: videoChannel.subscribers,
title: videoChannel.title,
};
const channel = await sdk.getVideoChannel({
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
});
if (!channel.videoChannels?.data[0]?.attributes) return { notFound: true };
const props: Props = {
channel,
openGraph: getOpenGraph(format, channel.title),
channel: channel.videoChannels.data[0].attributes,
openGraph: getOpenGraph(format, channel.videoChannels.data[0].attributes.title),
};
return {
props: props,

View File

@ -25,8 +25,6 @@ import { Button } from "components/Inputs/Button";
import { Paginator } from "components/Containers/Paginator";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
*
@ -59,7 +57,6 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
const { format } = useFormat();
const hoverable = useDeviceSupportsHover();
const router = useTypedRouter(queryParamSchema);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const sortingMethods = useMemo(
() => [
@ -144,25 +141,14 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos", "Change search term");
} else {
sendAnalytics("Videos", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
{!is1ColumnLayout && <ReturnButton href="/archives/" title={"Archives"} className="mb-10" />}
<ReturnButton
href="/archives/"
title={"Archives"}
displayOnlyOn={"3ColumnsLayout"}
className="mb-10"
/>
<PanelHeader
icon="movie"
@ -172,7 +158,20 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos", "Change search term");
} else {
sendAnalytics("Videos", "Clear search term");
}
}}
/>
<WithLabel label={format("order_by")}>
<Select
@ -227,7 +226,6 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={videos?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -9,10 +9,10 @@ import { NavOption } from "components/PanelComponents/NavOption";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
import { SubPanel } from "components/Containers/SubPanel";
import { Enum_Video_Source } from "graphql/generated";
import { GetVideoQuery } from "graphql/generated";
import { getReadySdk } from "graphql/sdk";
import { prettyShortenNumber } from "helpers/formatters";
import { filterHasAttributes, isDefined, isUndefined } from "helpers/asserts";
import { filterHasAttributes, isDefined } from "helpers/asserts";
import { getVideoFile, getVideoThumbnailURL } from "helpers/videos";
import { getOpenGraph } from "helpers/openGraph";
import { atoms } from "contexts/atoms";
@ -29,38 +29,24 @@ import { Markdown } from "components/Markdown/Markdown";
*/
interface Props extends AppLayoutRequired {
video: {
isGone: boolean;
uid: string;
title: string;
description: string;
publishedDate: string;
views: number;
likes: number;
source?: Enum_Video_Source;
};
channel?: {
title: string;
href: string;
subscribers: number;
};
video: NonNullable<NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]>;
}
const Video = ({ video, channel, ...otherProps }: Props): JSX.Element => {
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
const { format } = useFormat();
const { format, formatDate } = useFormat();
const subPanel = (
<SubPanel>
{!is1ColumnLayout && (
<>
<ReturnButton href="/archives/videos/" title={format("videos")} />
<HorizontalLine />
</>
)}
<ReturnButton
href="/archives/videos/"
title={format("videos")}
displayOnlyOn={"3ColumnsLayout"}
/>
<HorizontalLine />
<NavOption title={format("video")} url="#video" border onClick={closeSubPanel} />
<NavOption title={format("channel")} url="#channel" border onClick={closeSubPanel} />
@ -70,13 +56,16 @@ const Video = ({ video, channel, ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && (
<ReturnButton href="/library/" title={format("library")} className="mb-10" />
)}
<ReturnButton
href="/library/"
title={format("library")}
displayOnlyOn={"1ColumnLayout"}
className="mb-10"
/>
<div className="grid place-items-center gap-12">
<div id="video" className="w-full overflow-hidden rounded-xl shadow-xl shadow-shade/80">
{video.isGone ? (
{video.gone ? (
<VideoPlayer className="w-full" src={getVideoFile(video.uid)} rounded={false} />
) : (
<iframe
@ -94,7 +83,7 @@ const Video = ({ video, channel, ...otherProps }: Props): JSX.Element => {
<div className="flex w-full flex-row flex-wrap place-items-center gap-x-6">
<p>
<Ico icon="event" className="mr-1 translate-y-[.15em] !text-base" />
{video.publishedDate}
{formatDate(video.published_date)}
</p>
<p>
<Ico icon="visibility" className="mr-1 translate-y-[.15em] !text-base" />
@ -102,7 +91,7 @@ const Video = ({ video, channel, ...otherProps }: Props): JSX.Element => {
? video.views.toLocaleString()
: prettyShortenNumber(video.views)}
</p>
{video.likes > 0 && (
{video.channel?.data?.attributes && (
<p>
<Ico icon="thumb_up" className="mr-1 translate-y-[.15em] !text-base" />
{isContentPanelAtLeast4xl
@ -122,14 +111,18 @@ const Video = ({ video, channel, ...otherProps }: Props): JSX.Element => {
</div>
</div>
{channel && (
{video.channel?.data?.attributes && (
<InsetBox id="channel" className="grid place-items-center">
<div className="grid w-[clamp(0px,100%,42rem)] place-items-center gap-4 text-center">
<h2 className="text-2xl">{format("channel")}</h2>
<div>
<Button href={channel.href} text={channel.title} />
<Button
href={`/archives/videos/c/${video.channel.data.attributes.uid}\
?page=1&query=&sort=1&gone=`}
text={video.channel.data.attributes.title}
/>
<p>
{`${channel.subscribers.toLocaleString()}
{`${video.channel.data.attributes.subscribers.toLocaleString()}
${format("subscribers").toLowerCase()}`}
</p>
</div>
@ -158,48 +151,29 @@ export default Video;
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const { format, formatDate } = getFormat(context.locale);
const { format } = getFormat(context.locale);
const videos = await sdk.getVideo({
uid: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
});
const rawVideo = videos.videos?.data[0]?.attributes;
if (isUndefined(rawVideo)) return { notFound: true };
const channel: Props["channel"] = rawVideo.channel?.data?.attributes
? {
href: `/archives/videos/c/${rawVideo.channel.data.attributes.uid}`,
subscribers: rawVideo.channel.data.attributes.subscribers,
title: rawVideo.channel.data.attributes.title,
}
: undefined;
const video: Props["video"] = {
uid: rawVideo.uid,
isGone: rawVideo.gone,
description: rawVideo.description,
likes: rawVideo.likes,
source: rawVideo.source ?? undefined,
publishedDate: formatDate(rawVideo.published_date),
title: rawVideo.title,
views: rawVideo.views,
};
if (!videos.videos?.data[0]?.attributes) return { notFound: true };
const props: Props = {
video,
channel,
video: videos.videos.data[0].attributes,
openGraph: getOpenGraph(
format,
rawVideo.title,
getDescription(rawVideo.description, {
[format("channel")]: [rawVideo.channel?.data?.attributes?.title],
videos.videos.data[0].attributes.title,
getDescription(videos.videos.data[0].attributes.description, {
[format("channel")]: [videos.videos.data[0].attributes.channel?.data?.attributes?.title],
}),
getVideoThumbnailURL(rawVideo.uid),
getVideoThumbnailURL(videos.videos.data[0].attributes.uid),
undefined,
rawVideo.gone ? getVideoFile(rawVideo.uid) : undefined
videos.videos.data[0].attributes.gone
? getVideoFile(videos.videos.data[0].attributes.uid)
: undefined
),
};
return {
props: JSON.parse(JSON.stringify(props)),
props: props,
};
};

View File

@ -16,14 +16,12 @@ import { ReturnButton } from "components/PanelComponents/ReturnButton";
import { getOpenGraph } from "helpers/openGraph";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { getDescription } from "helpers/description";
import { useScrollTopOnChange } from "hooks/useScrollOnChange";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { Ids } from "types/ids";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { ElementsSeparator } from "helpers/component";
import { ChroniclesLists } from "components/Chronicles/ChroniclesLists";
import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
*
@ -37,7 +35,6 @@ interface Props extends AppLayoutRequired {
const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element => {
const { format, formatContentType, formatCategory } = useFormat();
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
useScrollTopOnChange(Ids.ContentPanel, [chronicle.slug]);
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
@ -70,22 +67,24 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element =
const subPanel = (
<SubPanel>
{!is1ColumnLayout && (
<>
<ReturnButton href="/chronicles" title={format("chronicles")} />
<HorizontalLine />
</>
)}
<ReturnButton
displayOnlyOn={"3ColumnsLayout"}
href="/chronicles"
title={format("chronicles")}
/>
<HorizontalLine />
<ChroniclesLists chapters={chapters} currentChronicleSlug={chronicle.slug} />
</SubPanel>
);
const contentPanel = (
<ContentPanel>
{is1ColumnLayout && (
<ReturnButton href="/chronicles" title={format("chronicles")} className="mb-10" />
)}
<ReturnButton
displayOnlyOn={"1ColumnLayout"}
href="/chronicles"
title={format("chronicles")}
className="mb-10"
/>
{isDefined(selectedTranslation) ? (
<>

View File

@ -14,7 +14,7 @@ import { prettyInlineTitle, prettySlug } from "helpers/formatters";
import { isUntangibleGroupItem } from "helpers/libraryItem";
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
import { ContentWithTranslations } from "types/types";
import { useScrollTopOnChange } from "hooks/useScrollOnChange";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { getOpenGraph } from "helpers/openGraph";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
@ -226,7 +226,11 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <TranslatedReturnButton {...returnButtonProps} className="mb-10" />}
<TranslatedReturnButton
{...returnButtonProps}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
<div className="grid place-items-center">
<ElementsSeparator

View File

@ -29,7 +29,7 @@ import { prettySlug } from "helpers/formatters";
import { Paginator } from "components/Containers/Paginator";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
import { useAtomSetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
@ -64,7 +64,6 @@ const Contents = (props: Props): JSX.Element => {
const { format, formatCategory, formatContentType, formatLanguage } = useFormat();
const router = useTypedRouter(queryParamSchema);
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const sortingMethods = useMemo(
() => [
@ -153,22 +152,6 @@ const Contents = (props: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Contents/All", "Change search term");
} else {
sendAnalytics("Contents/All", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -188,7 +171,20 @@ const Contents = (props: Props): JSX.Element => {
<HorizontalLine />
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Contents/All", "Change search term");
} else {
sendAnalytics("Contents/All", "Clear search term");
}
}}
/>
<WithLabel label={format("order_by")}>
<Select
@ -256,7 +252,6 @@ const Contents = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={contents?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -1,13 +1,15 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
import { getOpenGraph } from "helpers/openGraph";
import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes } from "helpers/asserts";
import { ParentFolderPreviewFragment, UploadImageFragment } from "graphql/generated";
import { GetContentsFolderQuery, ParentFolderPreviewFragment } from "graphql/generated";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { prettySlug } from "helpers/formatters";
import { Button } from "components/Inputs/Button";
import { Ico } from "components/Ico";
import { Button, TranslatedButton } from "components/Inputs/Button";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Containers/SubPanel";
import { TranslatedPreviewCard } from "components/PreviewCard";
@ -19,7 +21,6 @@ import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { Chip } from "components/Chip";
import { FolderPath } from "components/Contents/FolderPath";
/*
*
@ -27,35 +28,25 @@ import { FolderPath } from "components/Contents/FolderPath";
*/
interface Props extends AppLayoutRequired {
subfolders: {
slug: string;
href: string;
translations: { language: string; title: string }[];
fallback: { title: string };
}[];
contents: {
slug: string;
href: string;
translations: { language: string; pre_title?: string; title: string; subtitle?: string }[];
fallback: { title: string };
thumbnail?: UploadImageFragment;
topChips?: string[];
bottomChips?: string[];
}[];
folder: NonNullable<
NonNullable<GetContentsFolderQuery["contentsFolders"]>["data"][number]["attributes"]
>;
path: ParentFolderPreviewFragment[];
}
const ContentsFolder = ({
openGraph,
path,
contents,
subfolders,
...otherProps
}: Props): JSX.Element => {
const { format } = useFormat();
const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.Element => {
const { format, formatCategory, formatContentType } = useFormat();
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
const filteredPath = useMemo(
() =>
path.filter(
(_, index) => isContentPanelAtLeast4xl || index === 0 || index === path.length - 1
),
[path, isContentPanelAtLeast4xl]
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -77,51 +68,102 @@ const ContentsFolder = ({
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<FolderPath path={path} />
<div className="mb-10 flex flex-wrap place-items-center justify-start gap-x-1 gap-y-4">
{filteredPath.map((pathFolder, index) => (
<>
{pathFolder.slug === "root" ? (
<Button href="/contents" icon="home" active={index === filteredPath.length - 1} />
) : (
<TranslatedButton
href={`/contents/folder/${pathFolder.slug}`}
translations={filterHasAttributes(pathFolder.titles, [
"language.data.attributes.code",
]).map((title) => ({
language: title.language.data.attributes.code,
text: title.title,
}))}
fallback={{
text: prettySlug(pathFolder.slug),
}}
active={index === filteredPath.length - 1}
/>
)}
{index < filteredPath.length - 1 && <Ico icon="chevron_right" />}
</>
))}
</div>
{subfolders.length > 0 && (
{folder.subfolders?.data && folder.subfolders.data.length > 0 && (
<div className="mb-8">
<div className="mb-2 flex place-items-center gap-2">
<h2 className="text-2xl">{format("folders")}</h2>
<Chip text={format("x_results", { x: subfolders.length })} />
<Chip text={format("x_results", { x: folder.subfolders.data.length })} />
</div>
<div
className={cJoin(
"grid items-start pb-12",
"grid items-start gap-8 pb-12",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-4"
)
)}>
{subfolders.map((subfolder) => (
<TranslatedPreviewFolder key={subfolder.slug} {...subfolder} />
{filterHasAttributes(folder.subfolders.data, ["id", "attributes"]).map((subfolder) => (
<TranslatedPreviewFolder
key={subfolder.id}
href={`/contents/folder/${subfolder.attributes.slug}`}
translations={filterHasAttributes(subfolder.attributes.titles, [
"language.data.attributes.code",
]).map((title) => ({
title: title.title,
language: title.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(subfolder.attributes.slug) }}
/>
))}
</div>
</div>
)}
{contents.length > 0 && (
{folder.contents?.data && folder.contents.data.length > 0 && (
<div className="mb-8">
<div className="mb-2 flex place-items-center gap-2">
<h2 className="text-2xl">{format("contents")}</h2>
<Chip text={format("x_results", { x: contents.length })} />
<Chip text={format("x_results", { x: folder.contents.data.length })} />
</div>
<div
className={cJoin(
"grid items-start pb-12",
"grid items-start gap-8 pb-12",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
"grid-cols-2 gap-4"
)
)}>
{contents.map((item) => (
{filterHasAttributes(folder.contents.data, ["id", "attributes"]).map((item) => (
<TranslatedPreviewCard
key={item.slug}
{...item}
key={item.id}
href={`/contents/${item.attributes.slug}`}
translations={filterHasAttributes(item.attributes.translations, [
"language.data.attributes.code",
]).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
language: translation.language.data.attributes.code,
}))}
fallback={{ title: prettySlug(item.attributes.slug) }}
thumbnail={item.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="3/2"
thumbnailForceAspectRatio
topChips={
item.attributes.type?.data?.attributes
? [formatContentType(item.attributes.type.data.attributes.slug)]
: undefined
}
bottomChips={filterHasAttributes(item.attributes.categories?.data, [
"attributes",
]).map((category) => formatCategory(category.attributes.slug))}
keepInfoVisible
/>
))}
@ -129,7 +171,9 @@ const ContentsFolder = ({
</div>
)}
{contents.length === 0 && subfolders.length === 0 && <NoContentNorFolderMessage />}
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
<NoContentNorFolderMessage />
)}
</ContentPanel>
);
@ -151,7 +195,7 @@ export default ContentsFolder;
export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk();
const { format, formatContentType, formatCategory } = getFormat(context.locale);
const { format } = getFormat(context.locale);
const slug = context.params?.slug ? context.params.slug.toString() : "";
const contentsFolder = await sdk.getContentsFolder({ slug: slug });
if (!contentsFolder.contentsFolders?.data[0]?.attributes) {
@ -177,51 +221,13 @@ export const getStaticProps: GetStaticProps = async (context) => {
return prettySlug(folder.slug);
})();
const subfolders: Props["subfolders"] = filterHasAttributes(folder.subfolders?.data, [
"attributes",
]).map(({ attributes }) => ({
slug: attributes.slug,
href: `/contents/folder/${attributes.slug}`,
translations: filterHasAttributes(attributes.titles, ["language.data.attributes.code"]).map(
(translation) => ({
title: translation.title,
language: translation.language.data.attributes.code,
})
),
fallback: { title: prettySlug(attributes.slug) },
}));
const contents: Props["contents"] = filterHasAttributes(folder.contents?.data, [
"attributes",
]).map(({ attributes }) => ({
slug: attributes.slug,
href: `/contents/${attributes.slug}`,
translations: filterHasAttributes(attributes.translations, [
"language.data.attributes.code",
]).map((translation) => ({
pre_title: translation.pre_title ?? undefined,
title: translation.title,
subtitle: translation.subtitle ?? undefined,
language: translation.language.data.attributes.code,
})),
fallback: { title: prettySlug(attributes.slug) },
thumbnail: attributes.thumbnail?.data?.attributes ?? undefined,
topChips: attributes.type?.data?.attributes
? [formatContentType(attributes.type.data.attributes.slug)]
: undefined,
bottomChips: filterHasAttributes(attributes.categories?.data, ["attributes"]).map((category) =>
formatCategory(category.attributes.slug)
),
}));
const props: Props = {
openGraph: getOpenGraph(format, title),
subfolders,
contents,
folder,
path: getRecursiveParentFolderPreview(folder),
};
return {
props: JSON.parse(JSON.stringify(props)),
props: props,
};
};

View File

@ -29,7 +29,7 @@ import {
isDefined,
isDefinedAndNotEmpty,
} from "helpers/asserts";
import { useScrollTopOnChange } from "hooks/useScrollOnChange";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { getScanArchiveURL, getTrackURL, isUntangibleGroupItem } from "helpers/libraryItem";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel";
@ -95,7 +95,7 @@ const LibrarySlug = ({
const isContentPanelAtLeast3xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast3xl);
const isContentPanelAtLeastSm = useAtomGetter(atoms.containerQueries.isContentPanelAtLeastSm);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
const hoverable = useDeviceSupportsHover();
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
@ -111,8 +111,13 @@ const LibrarySlug = ({
<SubPanel>
<ElementsSeparator>
{[
!is1ColumnLayout && (
<ReturnButton key="ReturnButton" href="/library/" title={format("library")} />
is3ColumnsLayout && (
<ReturnButton
key="ReturnButton"
href="/library/"
title={format("library")}
displayOnlyOn="3ColumnsLayout"
/>
),
<div className="grid gap-4" key="NavOption">
<NavOption
@ -172,10 +177,12 @@ const LibrarySlug = ({
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && (
<ReturnButton href="/library/" title={format("library")} className="mb-10" />
)}
<ReturnButton
href="/library/"
title={format("library")}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
<div className="grid place-items-center gap-12">
<div
className={cJoin(
@ -807,7 +814,9 @@ const ContentItem = ({
<div className="grid grid-cols-[auto_auto_1fr_auto] items-center gap-3">
<h3>{title}</h3>
<div className="flex flex-wrap place-content-center gap-1">
{content?.categories?.map((category, index) => <Chip key={index} text={category} />)}
{content?.categories?.map((category, index) => (
<Chip key={index} text={category} />
))}
</div>
<p className="h-4 w-full border-b-2 border-dotted border-mid" />
{content?.type && <Chip className="justify-self-end" text={content.type} />}

View File

@ -33,8 +33,6 @@ import { useLibraryItemUserStatus } from "hooks/useLibraryItemUserStatus";
import { Paginator } from "components/Containers/Paginator";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
*
@ -73,7 +71,6 @@ const Library = (props: Props): JSX.Element => {
const hoverable = useDeviceSupportsHover();
const { format, formatCategory, formatLibraryItemSubType } = useFormat();
const { libraryItemUserStatus } = useLibraryItemUserStatus();
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const sortingMethods = useMemo(
() => [
@ -237,22 +234,6 @@ const Library = (props: Props): JSX.Element => {
if (isDefined(totalPages) && totalPages < page && totalPages >= 1) setPage(totalPages);
}, [libraryItems?.totalPages, page]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Library", "Change search term");
} else {
sendAnalytics("Library", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -263,7 +244,20 @@ const Library = (props: Props): JSX.Element => {
<HorizontalLine />
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Library", "Change search term");
} else {
sendAnalytics("Library", "Clear search term");
}
}}
/>
<WithLabel label={format("order_by")}>
<Select
@ -394,7 +388,6 @@ const Library = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={libraryItems?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(12rem,1fr))] items-end

View File

@ -63,7 +63,6 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
const hoverable = useDeviceSupportsHover();
const router = useTypedRouter(queryParamSchema);
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
@ -137,22 +136,6 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("News", "Change search term");
} else {
sendAnalytics("News", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -163,7 +146,20 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("News", "Change search term");
} else {
sendAnalytics("News", "Clear search term");
}
}}
/>
<WithLabel label={format("language", { count: Infinity })}>
<Select
@ -212,7 +208,6 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={posts?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -39,7 +39,6 @@ interface Props extends AppLayoutRequired {
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
const { format, formatCategory, formatWikiTag } = useFormat();
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const router = useRouter();
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
@ -52,30 +51,41 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
[]
),
});
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
const toc = getTocFromMarkdawn(selectedTranslation?.body?.body, selectedTranslation?.title);
const subPanel =
isDefined(toc) || !is1ColumnLayout ? (
<SubPanel>
<ElementsSeparator>
{[
!is1ColumnLayout && <ReturnButton key="return" href={`/wiki`} title={format("wiki")} />,
toc && (
<TableOfContents
key="toc"
toc={toc}
onContentClicked={() => setSubPanelOpened(false)}
/>
),
]}
</ElementsSeparator>
</SubPanel>
) : undefined;
const subPanel = is3ColumnsLayout ? (
<SubPanel>
<ElementsSeparator>
{[
<ReturnButton
key="return"
href={`/wiki`}
title={format("wiki")}
displayOnlyOn={"3ColumnsLayout"}
/>,
toc && (
<TableOfContents
key="toc"
toc={toc}
onContentClicked={() => setSubPanelOpened(false)}
/>
),
]}
</ElementsSeparator>
</SubPanel>
) : undefined;
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
{is1ColumnLayout && <ReturnButton href={`/wiki`} title={format("wiki")} className="mb-10" />}
<ReturnButton
href={`/wiki`}
title={format("wiki")}
displayOnlyOn={"1ColumnLayout"}
className="mb-10"
/>
<div className="flex flex-wrap place-content-center gap-3">
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
{selectedTranslation?.aliases && selectedTranslation.aliases.length > 0 && (
@ -93,7 +103,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
<div
className={cJoin(
"mb-8 overflow-hidden rounded-lg bg-mid text-center",
cIf(!is1ColumnLayout, "float-right ml-8 w-96")
cIf(is3ColumnsLayout, "float-right ml-8 w-96")
)}>
{page.thumbnail?.data?.attributes && (
<Img
@ -204,18 +214,18 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
page.definitions && page.definitions.length > 0
? `${filterHasAttributes(page.definitions, ["translations"]).map(
(definition, index) =>
`${prettyTerminalUnderlinedTitle(
format("definition_x", { x: index + 1 })
)}${staticSmartLanguage({
items: filterHasAttributes(definition.translations, [
"language.data.attributes.code",
]),
languageExtractor: (item) => item.language.data.attributes.code,
preferredLanguages: getDefaultPreferredLanguages(
router.locale ?? "en",
router.locales ?? ["en"]
),
})?.definition}`
`${prettyTerminalUnderlinedTitle(format("definition_x", { x: index + 1 }))}${
staticSmartLanguage({
items: filterHasAttributes(definition.translations, [
"language.data.attributes.code",
]),
languageExtractor: (item) => item.language.data.attributes.code,
preferredLanguages: getDefaultPreferredLanguages(
router.locale ?? "en",
router.locales ?? ["en"]
),
})?.definition
}`
)}`
: ""
}${

View File

@ -27,7 +27,7 @@ import { useIntersectionList } from "hooks/useIntersectionList";
import { HorizontalLine } from "components/HorizontalLine";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
import { useAtomSetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
@ -43,7 +43,6 @@ interface Props extends AppLayoutRequired {
const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props): JSX.Element => {
const { format } = useFormat();
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
const ids = useMemo(
() => filterHasAttributes(chronologyEras, ["attributes"]).map((era) => era.attributes.slug),
@ -54,7 +53,7 @@ const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props):
const subPanel = (
<SubPanel>
{!is1ColumnLayout && <ReturnButton href="/wiki" title={format("wiki")} />}
<ReturnButton href="/wiki" title={format("wiki")} displayOnlyOn="3ColumnsLayout" />
<HorizontalLine />
@ -84,7 +83,12 @@ const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props):
const contentPanel = (
<ContentPanel>
{is1ColumnLayout && <ReturnButton href="/wiki" title={format("wiki")} className="mb-10" />}
<ReturnButton
href="/wiki"
title={format("wiki")}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
{filterHasAttributes(chronologyEras, ["attributes"]).map((era) => (
<TranslatedChronologyEra

View File

@ -29,7 +29,7 @@ import {
import { Paginator } from "components/Containers/Paginator";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
import { useAtomSetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
import { Select } from "components/Inputs/Select";
@ -64,7 +64,6 @@ const Wiki = (props: Props): JSX.Element => {
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
const router = useTypedRouter(queryParamSchema);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const languageOptions = useMemo(() => {
const memo =
@ -138,22 +137,6 @@ const Wiki = (props: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Wiki", "Change search term");
} else {
sendAnalytics("Wiki", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -164,7 +147,20 @@ const Wiki = (props: Props): JSX.Element => {
<HorizontalLine />
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Wiki", "Change search term");
} else {
sendAnalytics("Wiki", "Clear search term");
}
}}
/>
<WithLabel label={format("language", { count: Infinity })}>
<Select
@ -230,7 +226,6 @@ const Wiki = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={wikiPages?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -73,19 +73,17 @@ const WeaponPage = ({ weapon, primaryName, aliases, ...otherProps }: Props): JSX
const currentIntersection = useIntersectionList(intersectionIds);
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
const searchInput = (
<ReturnButton
key="return-button"
href="/wiki/weapons"
title={format("weapon", { count: Infinity })}
/>
);
const subPanel = (
<SubPanel>
<ElementsSeparator>
{[
is3ColumnsLayout && searchInput,
is3ColumnsLayout && (
<ReturnButton
key="return-button"
href="/wiki/weapons"
title={format("weapon", { count: Infinity })}
/>
),
<Fragment key="nav-options">
{intersectionIds.map((id, index) => (
@ -128,8 +126,12 @@ const WeaponPage = ({ weapon, primaryName, aliases, ...otherProps }: Props): JSX
const contentPanel = (
<ContentPanel>
{!is3ColumnsLayout && <div className="mb-10">{searchInput}</div>}
<ReturnButton
href="/wiki/weapons"
title={format("weapon", { count: Infinity })}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
<ThumbnailHeader
title={primaryName}
subtitle={aliases.join("・")}

View File

@ -30,8 +30,6 @@ import { WithLabel } from "components/Inputs/WithLabel";
import { Switch } from "components/Inputs/Switch";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import { Select } from "components/Inputs/Select";
import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
*
@ -62,7 +60,6 @@ const Weapons = (props: Props): JSX.Element => {
const { format, formatCategory, formatWeaponType, formatLanguage } = useFormat();
const hoverable = useDeviceSupportsHover();
const router = useTypedRouter(queryParamSchema);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const languageOptions = useMemo(() => {
const memo =
@ -135,35 +132,36 @@ const Weapons = (props: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Weapons", "Change search term");
} else {
sendAnalytics("Weapons", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
{!is1ColumnLayout && <ReturnButton href="/wiki" title={format("wiki")} className="mb-10" />}
<ReturnButton
href="/wiki"
title={format("wiki")}
displayOnlyOn="3ColumnsLayout"
className="mb-10"
/>
<PanelHeader
icon="shield"
title={format("weapon", { count: Infinity })}
description={format("weapons_description")}
/>
<HorizontalLine />
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Weapons", "Change search term");
} else {
sendAnalytics("Weapons", "Clear search term");
}
}}
/>
<WithLabel label={format("language", { count: Infinity })}>
<Select
@ -212,12 +210,12 @@ const Weapons = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && (
<>
<ReturnButton href="/wiki" title={format("wiki")} className="mb-6" />
<div className="mx-auto mb-12 max-w-lg">{searchInput}</div>
</>
)}
<ReturnButton
href="/wiki"
title={format("wiki")}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={weapons?.totalPages}>
<div

File diff suppressed because it is too large Load Diff

View File

@ -3,5 +3,4 @@ export enum Ids {
ContentPanel = "contentPanel495922447721572",
SubPanel = "subPanelz9e8rs2d3f18zer98ze",
LightBox = "lightBoxqsd564az89e732s1",
ContentsFolderPath = "contentsfolderpath8zer6az4",
}