Compare commits
1 Commits
main
...
dependabot
Author | SHA1 | Date |
---|---|---|
dependabot[bot] | 946f61bec8 |
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
88
package.json
88
package.json
|
@ -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
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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";
|
||||
|
||||
/*
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
};
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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) ? (
|
||||
<>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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} />}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}`
|
||||
)}`
|
||||
: ""
|
||||
}${
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("・")}
|
||||
|
|
|
@ -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
|
@ -3,5 +3,4 @@ export enum Ids {
|
|||
ContentPanel = "contentPanel495922447721572",
|
||||
SubPanel = "subPanelz9e8rs2d3f18zer98ze",
|
||||
LightBox = "lightBoxqsd564az89e732s1",
|
||||
ContentsFolderPath = "contentsfolderpath8zer6az4",
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue