Fixed eslint warnings + configure prettier
This commit is contained in:
parent
669d4358e7
commit
77d96a3dc3
|
@ -7,4 +7,5 @@ next.config.js
|
||||||
postcss.config.js
|
postcss.config.js
|
||||||
tailwind.config.js
|
tailwind.config.js
|
||||||
design.config.js
|
design.config.js
|
||||||
graphql.config.js
|
graphql.config.js
|
||||||
|
prettier.config.js
|
17
.eslintrc.js
17
.eslintrc.js
|
@ -81,7 +81,7 @@ module.exports = {
|
||||||
// "no-magic-numbers": "warn",
|
// "no-magic-numbers": "warn",
|
||||||
// "no-mixed-operators": "warn",
|
// "no-mixed-operators": "warn",
|
||||||
"no-multi-assign": "warn",
|
"no-multi-assign": "warn",
|
||||||
"no-multi-str": "warn",
|
// "no-multi-str": "warn",
|
||||||
"no-negated-condition": "warn",
|
"no-negated-condition": "warn",
|
||||||
// "no-nested-ternary": "warn",
|
// "no-nested-ternary": "warn",
|
||||||
"no-new": "warn",
|
"no-new": "warn",
|
||||||
|
@ -149,24 +149,15 @@ module.exports = {
|
||||||
"@typescript-eslint/ban-tslint-comment": "warn",
|
"@typescript-eslint/ban-tslint-comment": "warn",
|
||||||
"@typescript-eslint/class-literal-property-style": "warn",
|
"@typescript-eslint/class-literal-property-style": "warn",
|
||||||
"@typescript-eslint/consistent-indexed-object-style": "warn",
|
"@typescript-eslint/consistent-indexed-object-style": "warn",
|
||||||
"@typescript-eslint/consistent-type-assertions": [
|
"@typescript-eslint/consistent-type-assertions": ["warn", { assertionStyle: "as" }],
|
||||||
"warn",
|
|
||||||
{ assertionStyle: "as" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/consistent-type-exports": "error",
|
"@typescript-eslint/consistent-type-exports": "error",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "warn",
|
"@typescript-eslint/explicit-module-boundary-types": "warn",
|
||||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
"@typescript-eslint/method-signature-style": ["error", "property"],
|
||||||
"@typescript-eslint/no-base-to-string": "warn",
|
"@typescript-eslint/no-base-to-string": "warn",
|
||||||
"@typescript-eslint/no-confusing-non-null-assertion": "warn",
|
"@typescript-eslint/no-confusing-non-null-assertion": "warn",
|
||||||
"@typescript-eslint/no-confusing-void-expression": [
|
"@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
|
||||||
"error",
|
|
||||||
{ ignoreArrowShorthand: true },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-dynamic-delete": "error",
|
"@typescript-eslint/no-dynamic-delete": "error",
|
||||||
"@typescript-eslint/no-empty-interface": [
|
"@typescript-eslint/no-empty-interface": ["error", { allowSingleExtends: true }],
|
||||||
"error",
|
|
||||||
{ allowSingleExtends: true },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-invalid-void-type": "error",
|
"@typescript-eslint/no-invalid-void-type": "error",
|
||||||
"@typescript-eslint/no-meaningless-void-operator": "error",
|
"@typescript-eslint/no-meaningless-void-operator": "error",
|
||||||
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",
|
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",
|
||||||
|
|
|
@ -8,17 +8,10 @@ module.exports = {
|
||||||
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
|
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
documents: [
|
documents: ["src/graphql/operations/**/*.graphql", "src/graphql/fragments/*.graphql"],
|
||||||
"src/graphql/operations/**/*.graphql",
|
|
||||||
"src/graphql/fragments/*.graphql",
|
|
||||||
],
|
|
||||||
generates: {
|
generates: {
|
||||||
"src/graphql/generated.ts": {
|
"src/graphql/generated.ts": {
|
||||||
plugins: [
|
plugins: ["typescript", "typescript-operations", "typescript-graphql-request"],
|
||||||
"typescript",
|
|
||||||
"typescript-operations",
|
|
||||||
"typescript-graphql-request",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,14 +24,5 @@ module.exports = {
|
||||||
hreflang: "ja",
|
hreflang: "ja",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
exclude: [
|
exclude: ["/en/*", "/fr/*", "/ja/*", "/es/*", "/pt-br/*", "/404", "/500", "/dev/*"],
|
||||||
"/en/*",
|
|
||||||
"/fr/*",
|
|
||||||
"/ja/*",
|
|
||||||
"/es/*",
|
|
||||||
"/pt-br/*",
|
|
||||||
"/404",
|
|
||||||
"/500",
|
|
||||||
"/dev/*",
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 12499",
|
"dev": "next dev -p 12499",
|
||||||
"precommit": "npm run generate && npm run fetch-local-data && npm run prettier && npm run unused-exports && npm run eslint && npm run tsc && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
|
"precommit": "npm run fetch-local-data && npm run prettier && npm run unused-exports && npm run eslint && npm run tsc && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
|
||||||
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated",
|
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated",
|
||||||
"fetch-local-data": "esrun src/graphql/fetchLocalData.ts",
|
"fetch-local-data": "npm run generate && esrun src/graphql/fetchLocalData.ts",
|
||||||
"prebuild": "npm run generate && npm run fetch-local-data",
|
"prebuild": "npm run fetch-local-data",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"postbuild": "next-sitemap --config next-sitemap.config.js",
|
"postbuild": "next-sitemap --config next-sitemap.config.js",
|
||||||
"start": "next start -p 12500",
|
"start": "next start -p 12500",
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
module.exports = {
|
||||||
|
printWidth: 100,
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: false,
|
||||||
|
quoteProps: "as-needed",
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
trailingComma: "es5",
|
||||||
|
bracketSpacing: true,
|
||||||
|
bracketSameLine: true,
|
||||||
|
arrowParens: "always",
|
||||||
|
rangeStart: 0,
|
||||||
|
rangeEnd: Infinity,
|
||||||
|
requirePragma: false,
|
||||||
|
insertPragma: false,
|
||||||
|
proseWrap: "preserve",
|
||||||
|
htmlWhitespaceSensitivity: "ignore",
|
||||||
|
endOfLine: "lf",
|
||||||
|
singleAttributePerLine: false,
|
||||||
|
};
|
|
@ -1 +1,91 @@
|
||||||
{"currencies":{"data":[{"id":"1","attributes":{"code":"EUR","symbol":"€","rate_to_usd":1.1062771,"display_decimals":true}},{"id":"2","attributes":{"code":"CAD","symbol":"$","rate_to_usd":0.79319156,"display_decimals":true}},{"id":"3","attributes":{"code":"USD","symbol":"$","rate_to_usd":1,"display_decimals":true}},{"id":"4","attributes":{"code":"JPY","symbol":"¥","rate_to_usd":0.0083864261,"display_decimals":false}},{"id":"5","attributes":{"code":"BRL","symbol":"R$","rate_to_usd":0.19904328,"display_decimals":true}},{"id":"6","attributes":{"code":"GBP","symbol":"£","rate_to_usd":1.3181323,"display_decimals":true}},{"id":"7","attributes":{"code":"AUD","symbol":"$","rate_to_usd":0.7422,"display_decimals":true}},{"id":"8","attributes":{"code":"INR","symbol":"₹","rate_to_usd":0.013162881,"display_decimals":false}},{"id":"9","attributes":{"code":"NZD","symbol":"$","rate_to_usd":0.69089984,"display_decimals":true}},{"id":"10","attributes":{"code":"CHF","symbol":"CHF","rate_to_usd":1.0728706,"display_decimals":true}}]}}
|
{
|
||||||
|
"currencies": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"attributes": {
|
||||||
|
"code": "EUR",
|
||||||
|
"symbol": "€",
|
||||||
|
"rate_to_usd": 1.1062771,
|
||||||
|
"display_decimals": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"attributes": {
|
||||||
|
"code": "CAD",
|
||||||
|
"symbol": "$",
|
||||||
|
"rate_to_usd": 0.79319156,
|
||||||
|
"display_decimals": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"attributes": { "code": "USD", "symbol": "$", "rate_to_usd": 1, "display_decimals": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"attributes": {
|
||||||
|
"code": "JPY",
|
||||||
|
"symbol": "¥",
|
||||||
|
"rate_to_usd": 0.0083864261,
|
||||||
|
"display_decimals": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"attributes": {
|
||||||
|
"code": "BRL",
|
||||||
|
"symbol": "R$",
|
||||||
|
"rate_to_usd": 0.19904328,
|
||||||
|
"display_decimals": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"attributes": {
|
||||||
|
"code": "GBP",
|
||||||
|
"symbol": "£",
|
||||||
|
"rate_to_usd": 1.3181323,
|
||||||
|
"display_decimals": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7",
|
||||||
|
"attributes": {
|
||||||
|
"code": "AUD",
|
||||||
|
"symbol": "$",
|
||||||
|
"rate_to_usd": 0.7422,
|
||||||
|
"display_decimals": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8",
|
||||||
|
"attributes": {
|
||||||
|
"code": "INR",
|
||||||
|
"symbol": "₹",
|
||||||
|
"rate_to_usd": 0.013162881,
|
||||||
|
"display_decimals": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9",
|
||||||
|
"attributes": {
|
||||||
|
"code": "NZD",
|
||||||
|
"symbol": "$",
|
||||||
|
"rate_to_usd": 0.69089984,
|
||||||
|
"display_decimals": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "10",
|
||||||
|
"attributes": {
|
||||||
|
"code": "CHF",
|
||||||
|
"symbol": "CHF",
|
||||||
|
"rate_to_usd": 1.0728706,
|
||||||
|
"display_decimals": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,36 @@
|
||||||
{"languages":{"data":[{"id":"1","attributes":{"name":"French","code":"fr","localized_name":"Français"}},{"id":"2","attributes":{"name":"English","code":"en","localized_name":"English"}},{"id":"3","attributes":{"name":"Japanese","code":"ja","localized_name":"日本語"}},{"id":"4","attributes":{"name":"Spanish","code":"es","localized_name":"Español"}},{"id":"6","attributes":{"name":"Portuguese (Brazil)","code":"pt-br","localized_name":"Português (Brasil)"}},{"id":"8","attributes":{"name":"German","code":"de","localized_name":"Deutsch"}},{"id":"9","attributes":{"name":"Italian","code":"it","localized_name":"Italiano"}},{"id":"10","attributes":{"name":"Russian","code":"ru","localized_name":"русский"}},{"id":"11","attributes":{"name":"Korean","code":"ko","localized_name":"한국어"}},{"id":"12","attributes":{"name":"Chinese (Traditional)","code":"zh-cht","localized_name":"中文(繁體)"}}]}}
|
{
|
||||||
|
"languages": {
|
||||||
|
"data": [
|
||||||
|
{ "id": "1", "attributes": { "name": "French", "code": "fr", "localized_name": "Français" } },
|
||||||
|
{ "id": "2", "attributes": { "name": "English", "code": "en", "localized_name": "English" } },
|
||||||
|
{ "id": "3", "attributes": { "name": "Japanese", "code": "ja", "localized_name": "日本語" } },
|
||||||
|
{ "id": "4", "attributes": { "name": "Spanish", "code": "es", "localized_name": "Español" } },
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"attributes": {
|
||||||
|
"name": "Portuguese (Brazil)",
|
||||||
|
"code": "pt-br",
|
||||||
|
"localized_name": "Português (Brasil)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "id": "8", "attributes": { "name": "German", "code": "de", "localized_name": "Deutsch" } },
|
||||||
|
{
|
||||||
|
"id": "9",
|
||||||
|
"attributes": { "name": "Italian", "code": "it", "localized_name": "Italiano" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "10",
|
||||||
|
"attributes": { "name": "Russian", "code": "ru", "localized_name": "русский" }
|
||||||
|
},
|
||||||
|
{ "id": "11", "attributes": { "name": "Korean", "code": "ko", "localized_name": "한국어" } },
|
||||||
|
{
|
||||||
|
"id": "12",
|
||||||
|
"attributes": {
|
||||||
|
"name": "Chinese (Traditional)",
|
||||||
|
"code": "zh-cht",
|
||||||
|
"localized_name": "中文(繁體)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -18,27 +18,14 @@ interface Props {
|
||||||
export const AnchorShare = ({ id, className }: Props): JSX.Element => {
|
export const AnchorShare = ({ id, className }: Props): JSX.Element => {
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
return (
|
return (
|
||||||
<ToolTip
|
<ToolTip content={langui.copy_anchor_link} trigger="mouseenter" className="text-sm">
|
||||||
content={langui.copy_anchor_link}
|
<ToolTip content={langui.anchor_link_copied} trigger="click" className="text-sm">
|
||||||
trigger="mouseenter"
|
|
||||||
className="text-sm"
|
|
||||||
>
|
|
||||||
<ToolTip
|
|
||||||
content={langui.anchor_link_copied}
|
|
||||||
trigger="click"
|
|
||||||
className="text-sm"
|
|
||||||
>
|
|
||||||
<Ico
|
<Ico
|
||||||
icon={Icon.Link}
|
icon={Icon.Link}
|
||||||
className={cJoin(
|
className={cJoin("transition-color cursor-pointer hover:text-dark", className)}
|
||||||
"transition-color cursor-pointer hover:text-dark",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${
|
`${process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname}#${id}`
|
||||||
process.env.NEXT_PUBLIC_URL_SELF + window.location.pathname
|
|
||||||
}#${id}`
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,10 +19,7 @@ import { cIf, cJoin } from "helpers/className";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph";
|
import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph";
|
||||||
import {
|
import { useIs1ColumnLayout, useIsScreenAtLeast } from "hooks/useContainerQuery";
|
||||||
useIs1ColumnLayout,
|
|
||||||
useIsScreenAtLeast,
|
|
||||||
} from "hooks/useContainerQuery";
|
|
||||||
import { useOnResize } from "hooks/useOnResize";
|
import { useOnResize } from "hooks/useOnResize";
|
||||||
import { Ids } from "types/ids";
|
import { Ids } from "types/ids";
|
||||||
|
|
||||||
|
@ -138,8 +135,7 @@ export const AppLayout = ({
|
||||||
const [currencySelect, setCurrencySelect] = useState<number>(-1);
|
const [currencySelect, setCurrencySelect] = useState<number>(-1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDefined(currency))
|
if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency));
|
||||||
setCurrencySelect(currencyOptions.indexOf(currency));
|
|
||||||
}, [currency, currencyOptions]);
|
}, [currency, currencyOptions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -147,14 +143,11 @@ export const AppLayout = ({
|
||||||
}, [currencyOptions, currencySelect, setCurrency]);
|
}, [currencyOptions, currencySelect, setCurrency]);
|
||||||
|
|
||||||
const isClient = useIsClient();
|
const isClient = useIsClient();
|
||||||
const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } =
|
const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } = useBoolean(false);
|
||||||
useBoolean(false);
|
|
||||||
const isSafari = useMemo<boolean>(() => {
|
const isSafari = useMemo<boolean>(() => {
|
||||||
if (isClient) {
|
if (isClient) {
|
||||||
const parser = new UAParser();
|
const parser = new UAParser();
|
||||||
return (
|
return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS";
|
||||||
parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}, [isClient]);
|
}, [isClient]);
|
||||||
|
@ -164,27 +157,22 @@ export const AppLayout = ({
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
cIf(darkMode, "set-theme-dark", "set-theme-light"),
|
cIf(darkMode, "set-theme-dark", "set-theme-light"),
|
||||||
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard")
|
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
{...handlers}
|
{...handlers}
|
||||||
id={Ids.Body}
|
id={Ids.Body}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
|
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
|
||||||
[grid-template-areas:'main_sub_content']`,
|
[grid-template-areas:'main_sub_content']`,
|
||||||
cIf(
|
cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']")
|
||||||
is1ColumnLayout,
|
|
||||||
"grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']"
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns: is1ColumnLayout
|
gridTemplateColumns: is1ColumnLayout
|
||||||
? "1fr"
|
? "1fr"
|
||||||
: `${
|
: `${mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu}rem ${
|
||||||
mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu
|
isDefined(subPanel) ? layout.subMenu : 0
|
||||||
}rem ${isDefined(subPanel) ? layout.subMenu : 0}rem 1fr`,
|
}rem 1fr`,
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<Head>
|
<Head>
|
||||||
<title>{openGraph.title}</title>
|
<title>{openGraph.title}</title>
|
||||||
<meta name="description" content={openGraph.description} />
|
<meta name="description" content={openGraph.description} />
|
||||||
|
@ -197,18 +185,9 @@ export const AppLayout = ({
|
||||||
<meta property="og:title" content={openGraph.title} />
|
<meta property="og:title" content={openGraph.title} />
|
||||||
<meta property="og:description" content={openGraph.description} />
|
<meta property="og:description" content={openGraph.description} />
|
||||||
<meta property="og:image" content={openGraph.thumbnail.image} />
|
<meta property="og:image" content={openGraph.thumbnail.image} />
|
||||||
<meta
|
<meta property="og:image:secure_url" content={openGraph.thumbnail.image} />
|
||||||
property="og:image:secure_url"
|
<meta property="og:image:width" content={openGraph.thumbnail.width.toString()} />
|
||||||
content={openGraph.thumbnail.image}
|
<meta property="og:image:height" content={openGraph.thumbnail.height.toString()} />
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:image:width"
|
|
||||||
content={openGraph.thumbnail.width.toString()}
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:image:height"
|
|
||||||
content={openGraph.thumbnail.height.toString()}
|
|
||||||
/>
|
|
||||||
<meta property="og:image:alt" content={openGraph.thumbnail.alt} />
|
<meta property="og:image:alt" content={openGraph.thumbnail.alt} />
|
||||||
<meta property="og:image:type" content="image/jpeg" />
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
</Head>
|
</Head>
|
||||||
|
@ -230,22 +209,16 @@ export const AppLayout = ({
|
||||||
"z-10 [backdrop-filter:blur(2px)]",
|
"z-10 [backdrop-filter:blur(2px)]",
|
||||||
"pointer-events-none touch-none"
|
"pointer-events-none touch-none"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"absolute inset-0 bg-shade transition-opacity duration-500",
|
"absolute inset-0 bg-shade transition-opacity duration-500",
|
||||||
cIf(
|
cIf((mainPanelOpen || subPanelOpen) && is1ColumnLayout, "opacity-60", "opacity-0")
|
||||||
(mainPanelOpen || subPanelOpen) && is1ColumnLayout,
|
|
||||||
"opacity-60",
|
|
||||||
"opacity-0"
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMainPanelOpen(false);
|
setMainPanelOpen(false);
|
||||||
setSubPanelOpen(false);
|
setSubPanelOpen(false);
|
||||||
}}
|
}}></div>
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content panel */}
|
{/* Content panel */}
|
||||||
|
@ -254,8 +227,7 @@ export const AppLayout = ({
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"texture-paper-dots bg-light [grid-area:content]",
|
"texture-paper-dots bg-light [grid-area:content]",
|
||||||
cIf(contentPanelScroolbar, "overflow-y-scroll")
|
cIf(contentPanelScroolbar, "overflow-y-scroll")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{isDefined(contentPanel) ? (
|
{isDefined(contentPanel) ? (
|
||||||
contentPanel
|
contentPanel
|
||||||
) : (
|
) : (
|
||||||
|
@ -280,17 +252,10 @@ export const AppLayout = ({
|
||||||
[grid-area:content]`,
|
[grid-area:content]`,
|
||||||
"[grid-area:sub]"
|
"[grid-area:sub]"
|
||||||
),
|
),
|
||||||
cIf(
|
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l-[1px]"),
|
||||||
is1ColumnLayout && isScreenAtLeastXs,
|
cIf(is1ColumnLayout && !subPanelOpen && !turnSubIntoContent, "translate-x-[100vw]"),
|
||||||
"w-[min(30rem,90%)] border-l-[1px]"
|
|
||||||
),
|
|
||||||
cIf(
|
|
||||||
is1ColumnLayout && !subPanelOpen && !turnSubIntoContent,
|
|
||||||
"translate-x-[100vw]"
|
|
||||||
),
|
|
||||||
cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0")
|
cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{subPanel}
|
{subPanel}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -300,15 +265,10 @@ export const AppLayout = ({
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
|
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
|
||||||
transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
|
transition-transform duration-300 [scrollbar-width:none] webkit-scrollbar:w-0`,
|
||||||
cIf(
|
cIf(is1ColumnLayout, "z-10 justify-self-start [grid-area:content]", "[grid-area:main]"),
|
||||||
is1ColumnLayout,
|
|
||||||
"z-10 justify-self-start [grid-area:content]",
|
|
||||||
"[grid-area:main]"
|
|
||||||
),
|
|
||||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
||||||
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
|
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<MainPanel />
|
<MainPanel />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -318,8 +278,7 @@ export const AppLayout = ({
|
||||||
`texture-paper-dots grid grid-cols-[5rem_1fr_5rem] place-items-center
|
`texture-paper-dots grid grid-cols-[5rem_1fr_5rem] place-items-center
|
||||||
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`,
|
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`,
|
||||||
cIf(!is1ColumnLayout, "hidden")
|
cIf(!is1ColumnLayout, "hidden")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<Ico
|
<Ico
|
||||||
icon={mainPanelOpen ? Icon.Close : Icon.Menu}
|
icon={mainPanelOpen ? Icon.Close : Icon.Menu}
|
||||||
className="mt-[.1em] cursor-pointer !text-2xl"
|
className="mt-[.1em] cursor-pointer !text-2xl"
|
||||||
|
@ -331,19 +290,10 @@ export const AppLayout = ({
|
||||||
<p
|
<p
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"overflow-hidden text-center font-headers font-black",
|
"overflow-hidden text-center font-headers font-black",
|
||||||
cIf(
|
cIf(openGraph.title.length > 30, "max-h-14 text-xl", "max-h-16 text-2xl")
|
||||||
openGraph.title.length > 30,
|
)}>
|
||||||
"max-h-14 text-xl",
|
{openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
|
||||||
"max-h-16 text-2xl"
|
? openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
|
||||||
)
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{openGraph.title.substring(
|
|
||||||
TITLE_PREFIX.length + TITLE_SEPARATOR.length
|
|
||||||
)
|
|
||||||
? openGraph.title.substring(
|
|
||||||
TITLE_PREFIX.length + TITLE_SEPARATOR.length
|
|
||||||
)
|
|
||||||
: "Accord’s Library"}
|
: "Accord’s Library"}
|
||||||
</p>
|
</p>
|
||||||
{isDefined(subPanel) && !turnSubIntoContent && (
|
{isDefined(subPanel) && !turnSubIntoContent && (
|
||||||
|
@ -358,26 +308,16 @@ export const AppLayout = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Popup
|
<Popup state={isSafari && !hasDisgardSafariWarning} onClose={() => null}>
|
||||||
state={isSafari && !hasDisgardSafariWarning}
|
|
||||||
onClose={() => null}
|
|
||||||
>
|
|
||||||
<h1 className="text-2xl">Hi, you are using Safari!</h1>
|
<h1 className="text-2xl">Hi, you are using Safari!</h1>
|
||||||
<p className="max-w-lg text-center">
|
<p className="max-w-lg text-center">
|
||||||
In most cases this wouldn’t be a problem but our website
|
In most cases this wouldn’t be a problem but our website is—for some obscure
|
||||||
is—for some obscure reason—performing terribly on Safari (WebKit).
|
reason—performing terribly on Safari (WebKit). Because of that, we have decided to
|
||||||
Because of that, we have decided to display this message instead of
|
display this message instead of letting you have a slow and painful experience. We are
|
||||||
letting you have a slow and painful experience. We are looking into
|
looking into the problem, and are hoping to fix this soon.
|
||||||
the problem, and are hoping to fix this soon.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
In the meanwhile, if you are using an iPhone/iPad, please try using
|
|
||||||
another device.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you are on macOS, please use another browser such as Firefox or
|
|
||||||
Chrome.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>In the meanwhile, if you are using an iPhone/iPad, please try using another device.</p>
|
||||||
|
<p>If you are on macOS, please use another browser such as Firefox or Chrome.</p>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
text="Let me in regardless"
|
text="Let me in regardless"
|
||||||
|
@ -394,16 +334,14 @@ export const AppLayout = ({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setConfigPanelOpen(false);
|
setConfigPanelOpen(false);
|
||||||
umami("[Settings] Close settings");
|
umami("[Settings] Close settings");
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<h2 className="text-2xl">{langui.settings}</h2>
|
<h2 className="text-2xl">{langui.settings}</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`mt-4 grid justify-items-center gap-16 text-center`,
|
`mt-4 grid justify-items-center gap-16 text-center`,
|
||||||
cIf(!is1ColumnLayout, "grid-cols-[auto_auto]")
|
cIf(!is1ColumnLayout, "grid-cols-[auto_auto]")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{router.locales && (
|
{router.locales && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl">{langui.languages}</h3>
|
<h3 className="text-xl">{langui.languages}</h3>
|
||||||
|
@ -420,14 +358,11 @@ export const AppLayout = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
insertAt: 1,
|
insertAt: 1,
|
||||||
name:
|
name: langui.secondary_language ?? "Secondary languages",
|
||||||
langui.secondary_language ?? "Secondary languages",
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onChange={(items) => {
|
onChange={(items) => {
|
||||||
const newPreferredLanguages = items.map(
|
const newPreferredLanguages = items.map((item) => item.code);
|
||||||
(item) => item.code
|
|
||||||
);
|
|
||||||
setPreferredLanguages(newPreferredLanguages);
|
setPreferredLanguages(newPreferredLanguages);
|
||||||
umami("[Settings] Change preferred languages");
|
umami("[Settings] Change preferred languages");
|
||||||
}}
|
}}
|
||||||
|
@ -439,8 +374,7 @@ export const AppLayout = ({
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid place-items-center gap-8 text-center",
|
"grid place-items-center gap-8 text-center",
|
||||||
cIf(!is1ColumnLayout, "grid-cols-2")
|
cIf(!is1ColumnLayout, "grid-cols-2")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl">{langui.theme}</h3>
|
<h3 className="text-xl">{langui.theme}</h3>
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
|
@ -483,9 +417,7 @@ export const AppLayout = ({
|
||||||
value={currencySelect}
|
value={currencySelect}
|
||||||
onChange={(newCurrency) => {
|
onChange={(newCurrency) => {
|
||||||
setCurrencySelect(newCurrency);
|
setCurrencySelect(newCurrency);
|
||||||
umami(
|
umami(`[Settings] Change currency (${currencyOptions[newCurrency]})}`);
|
||||||
`[Settings] Change currency (${currencyOptions[newCurrency]})}`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
className="w-28"
|
className="w-28"
|
||||||
/>
|
/>
|
||||||
|
@ -500,12 +432,12 @@ export const AppLayout = ({
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setFontSize((current) => current / 1.05);
|
setFontSize((current) => current / 1.05);
|
||||||
umami(
|
umami(
|
||||||
`[Settings] Change font size (${(
|
`[Settings] Change font size (${((fontSize / 1.05) * 100).toLocaleString(
|
||||||
(fontSize / 1.05) *
|
undefined,
|
||||||
100
|
{
|
||||||
).toLocaleString(undefined, {
|
maximumFractionDigits: 0,
|
||||||
maximumFractionDigits: 0,
|
}
|
||||||
})}%)`
|
)}%)`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: Icon.TextDecrease,
|
icon: Icon.TextDecrease,
|
||||||
|
@ -523,13 +455,12 @@ export const AppLayout = ({
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setFontSize((current) => current * 1.05);
|
setFontSize((current) => current * 1.05);
|
||||||
umami(
|
umami(
|
||||||
`[Settings] Change font size (${(
|
`[Settings] Change font size (${(fontSize * 1.05 * 100).toLocaleString(
|
||||||
fontSize *
|
undefined,
|
||||||
1.05 *
|
{
|
||||||
100
|
maximumFractionDigits: 0,
|
||||||
).toLocaleString(undefined, {
|
}
|
||||||
maximumFractionDigits: 0,
|
)}%)`
|
||||||
})}%)`
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: Icon.TextIncrease,
|
icon: Icon.TextIncrease,
|
||||||
|
@ -589,21 +520,13 @@ interface ContentPlaceholderProps {
|
||||||
icon?: Icon;
|
icon?: Icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentPlaceholder = ({
|
const ContentPlaceholder = ({ message, icon }: ContentPlaceholderProps): JSX.Element => (
|
||||||
message,
|
|
||||||
icon,
|
|
||||||
}: ContentPlaceholderProps): JSX.Element => (
|
|
||||||
<div className="grid h-full place-content-center">
|
<div className="grid h-full place-content-center">
|
||||||
<div
|
<div
|
||||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||||
border-dark p-8 text-dark opacity-40"
|
border-dark p-8 text-dark opacity-40">
|
||||||
>
|
|
||||||
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
|
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
|
||||||
<p
|
<p className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}>{message}</p>
|
||||||
className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,12 +15,11 @@ interface Props {
|
||||||
export const Chip = ({ className, text }: Props): JSX.Element => (
|
export const Chip = ({ className, text }: Props): JSX.Element => (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`grid place-content-center place-items-center whitespace-nowrap rounded-full
|
`grid place-content-center place-items-center whitespace-nowrap rounded-full border-[1px]
|
||||||
border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70
|
px-1.5 pb-[0.14rem] text-xs opacity-70 transition-[color,_opacity,_border-color]
|
||||||
transition-[color,_opacity,_border-color] hover:opacity-100`,
|
hover:opacity-100`,
|
||||||
className
|
className
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{text}
|
{text}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,27 +17,19 @@ interface Props {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChroniclePreview = ({
|
const ChroniclePreview = ({ date, url, title, isActive }: Props): JSX.Element => (
|
||||||
date,
|
|
||||||
url,
|
|
||||||
title,
|
|
||||||
isActive,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<Link
|
<Link
|
||||||
href={url}
|
href={url}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5
|
`flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5 text-left align-top outline outline-2
|
||||||
text-left align-top outline outline-2 outline-offset-[-2px] outline-mid transition-all
|
outline-offset-[-2px] outline-mid transition-all hover:bg-mid hover:shadow-inner-sm
|
||||||
hover:bg-mid hover:shadow-inner-sm hover:shadow-shade
|
hover:shadow-shade hover:outline-[transparent] hover:active:shadow-inner
|
||||||
hover:outline-[transparent] hover:active:shadow-inner hover:active:shadow-shade`,
|
hover:active:shadow-shade`,
|
||||||
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-[transparent]")
|
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-[transparent]")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p>{date.year}</p>
|
<p>{date.year}</p>
|
||||||
<p className="text-sm text-dark">
|
<p className="text-sm text-dark">{prettyMonthDay(date.month, date.day)}</p>
|
||||||
{prettyMonthDay(date.month, date.day)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg leading-tight">{title}</p>
|
<p className="text-lg leading-tight">{title}</p>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -52,24 +44,13 @@ export const TranslatedChroniclePreview = ({
|
||||||
translations,
|
translations,
|
||||||
fallback,
|
fallback,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: TranslatedProps<
|
}: TranslatedProps<Parameters<typeof ChroniclePreview>[0], "title">): JSX.Element => {
|
||||||
Parameters<typeof ChroniclePreview>[0],
|
|
||||||
"title"
|
|
||||||
>): JSX.Element => {
|
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return <ChroniclePreview title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||||
<ChroniclePreview
|
|
||||||
title={selectedTranslation?.title ?? fallback.title}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useBoolean } from "usehooks-ts";
|
||||||
import { TranslatedChroniclePreview } from "./ChroniclePreview";
|
import { TranslatedChroniclePreview } from "./ChroniclePreview";
|
||||||
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
||||||
import { filterHasAttributes } from "helpers/others";
|
import { filterHasAttributes } from "helpers/others";
|
||||||
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
import { prettyInlineTitle, prettySlug, sJoin } from "helpers/formatters";
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
import { compareDate } from "helpers/date";
|
import { compareDate } from "helpers/date";
|
||||||
import { TranslatedProps } from "types/TranslatedProps";
|
import { TranslatedProps } from "types/TranslatedProps";
|
||||||
|
@ -17,20 +17,14 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
interface Props {
|
interface Props {
|
||||||
chronicles: NonNullable<
|
chronicles: NonNullable<
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"][number]["attributes"]
|
||||||
GetChroniclesChaptersQuery["chroniclesChapters"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["chronicles"]
|
>["chronicles"]
|
||||||
>["data"];
|
>["data"];
|
||||||
currentSlug?: string;
|
currentSlug?: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChroniclesList = ({
|
const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element => {
|
||||||
chronicles,
|
|
||||||
currentSlug,
|
|
||||||
title,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const { value: isOpen, toggle: toggleOpen } = useBoolean(
|
const { value: isOpen, toggle: toggleOpen } = useBoolean(
|
||||||
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
|
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
|
||||||
);
|
);
|
||||||
|
@ -38,33 +32,21 @@ const ChroniclesList = ({
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="grid place-content-center">
|
<div className="grid place-content-center">
|
||||||
<div
|
<div className="grid cursor-pointer grid-cols-[1em_1fr] gap-4" onClick={toggleOpen}>
|
||||||
className="grid cursor-pointer grid-cols-[1em_1fr] gap-4"
|
<Ico className="!text-xl" icon={isOpen ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
|
||||||
onClick={toggleOpen}
|
|
||||||
>
|
|
||||||
<Ico
|
|
||||||
className="!text-xl"
|
|
||||||
icon={isOpen ? Icon.ArrowDropUp : Icon.ArrowDropDown}
|
|
||||||
/>
|
|
||||||
<p className="mb-4 font-headers text-xl">{title}</p>
|
<p className="mb-4 font-headers text-xl">{title}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="grid gap-4 overflow-hidden transition-[max-height] duration-500"
|
className="grid gap-4 overflow-hidden transition-[max-height] duration-500"
|
||||||
style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}
|
style={{ maxHeight: isOpen ? `${8 * chronicles.length}rem` : 0 }}>
|
||||||
>
|
|
||||||
{filterHasAttributes(chronicles, [
|
{filterHasAttributes(chronicles, [
|
||||||
"attributes.contents",
|
"attributes.contents",
|
||||||
"attributes.translations",
|
"attributes.translations",
|
||||||
] as const)
|
] as const)
|
||||||
.sort((a, b) =>
|
.sort((a, b) => compareDate(a.attributes.date_start, b.attributes.date_start))
|
||||||
compareDate(a.attributes.date_start, b.attributes.date_start)
|
|
||||||
)
|
|
||||||
.map((chronicle) => (
|
.map((chronicle) => (
|
||||||
<div
|
<div key={chronicle.id} id={`chronicle-${chronicle.attributes.slug}`}>
|
||||||
key={chronicle.id}
|
|
||||||
id={`chronicle-${chronicle.attributes.slug}`}
|
|
||||||
>
|
|
||||||
{chronicle.attributes.translations.length === 0 &&
|
{chronicle.attributes.translations.length === 0 &&
|
||||||
chronicle.attributes.contents.data.length === 1
|
chronicle.attributes.contents.data.length === 1
|
||||||
? filterHasAttributes(chronicle.attributes.contents.data, [
|
? filterHasAttributes(chronicle.attributes.contents.data, [
|
||||||
|
@ -74,10 +56,9 @@ const ChroniclesList = ({
|
||||||
key={index}
|
key={index}
|
||||||
isActive={chronicle.attributes.slug === currentSlug}
|
isActive={chronicle.attributes.slug === currentSlug}
|
||||||
date={chronicle.attributes.date_start}
|
date={chronicle.attributes.date_start}
|
||||||
translations={filterHasAttributes(
|
translations={filterHasAttributes(content.attributes.translations, [
|
||||||
content.attributes.translations,
|
"language.data.attributes.code",
|
||||||
["language.data.attributes.code"] as const
|
] as const).map((translation) => ({
|
||||||
).map((translation) => ({
|
|
||||||
title: prettyInlineTitle(
|
title: prettyInlineTitle(
|
||||||
translation.pre_title,
|
translation.pre_title,
|
||||||
translation.title,
|
translation.title,
|
||||||
|
@ -88,24 +69,34 @@ const ChroniclesList = ({
|
||||||
fallback={{
|
fallback={{
|
||||||
title: prettySlug(chronicle.attributes.slug),
|
title: prettySlug(chronicle.attributes.slug),
|
||||||
}}
|
}}
|
||||||
url={`/chronicles/${chronicle.attributes.slug}/#chronicle-${chronicle.attributes.slug}`}
|
url={sJoin(
|
||||||
|
"/chronicles/",
|
||||||
|
chronicle.attributes.slug,
|
||||||
|
"/#chronicle-",
|
||||||
|
chronicle.attributes.slug
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: chronicle.attributes.translations.length > 0 && (
|
: chronicle.attributes.translations.length > 0 && (
|
||||||
<TranslatedChroniclePreview
|
<TranslatedChroniclePreview
|
||||||
date={chronicle.attributes.date_start}
|
date={chronicle.attributes.date_start}
|
||||||
isActive={chronicle.attributes.slug === currentSlug}
|
isActive={chronicle.attributes.slug === currentSlug}
|
||||||
translations={filterHasAttributes(
|
translations={filterHasAttributes(chronicle.attributes.translations, [
|
||||||
chronicle.attributes.translations,
|
"language.data.attributes.code",
|
||||||
["language.data.attributes.code", "title"] as const
|
"title",
|
||||||
).map((translation) => ({
|
] as const).map((translation) => ({
|
||||||
title: translation.title,
|
title: translation.title,
|
||||||
language: translation.language.data.attributes.code,
|
language: translation.language.data.attributes.code,
|
||||||
}))}
|
}))}
|
||||||
fallback={{
|
fallback={{
|
||||||
title: prettySlug(chronicle.attributes.slug),
|
title: prettySlug(chronicle.attributes.slug),
|
||||||
}}
|
}}
|
||||||
url={`/chronicles/${chronicle.attributes.slug}/#chronicle-${chronicle.attributes.slug}`}
|
url={sJoin(
|
||||||
|
"/chronicles/",
|
||||||
|
chronicle.attributes.slug,
|
||||||
|
"/#chronicle-",
|
||||||
|
chronicle.attributes.slug
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,16 +118,8 @@ export const TranslatedChroniclesList = ({
|
||||||
}: TranslatedProps<Props, "title">): JSX.Element => {
|
}: TranslatedProps<Props, "title">): JSX.Element => {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return <ChroniclesList title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||||
<ChroniclesList
|
|
||||||
title={selectedTranslation?.title ?? fallback.title}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,9 +13,5 @@ interface Props {
|
||||||
|
|
||||||
export const HorizontalLine = ({ className }: Props): JSX.Element => (
|
export const HorizontalLine = ({ className }: Props): JSX.Element => (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin("my-8 h-0 w-full border-t-[3px] border-dotted border-black", className)}></div>
|
||||||
"my-8 h-0 w-full border-t-[3px] border-dotted border-black",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
></div>
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,11 +17,7 @@ interface Props {
|
||||||
export const Ico = ({ onClick, icon, className }: Props): JSX.Element => (
|
export const Ico = ({ onClick, icon, className }: Props): JSX.Element => (
|
||||||
<span
|
<span
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cJoin(
|
className={cJoin("material-icons [font-size:inherit] [line-height:inherit]", className)}>
|
||||||
"material-icons [font-size:inherit] [line-height:inherit]",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{icon}
|
{icon}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,10 +8,7 @@ import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
extends Omit<
|
extends Omit<DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "src"> {
|
||||||
DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>,
|
|
||||||
"src"
|
|
||||||
> {
|
|
||||||
src: UploadImageFragment | string;
|
src: UploadImageFragment | string;
|
||||||
quality?: ImageQuality;
|
quality?: ImageQuality;
|
||||||
}
|
}
|
||||||
|
@ -26,15 +23,6 @@ export const Img = ({
|
||||||
loading = "lazy",
|
loading = "lazy",
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const src =
|
const src = typeof rawSrc === "string" ? rawSrc : getAssetURL(rawSrc.url, quality);
|
||||||
typeof rawSrc === "string" ? rawSrc : getAssetURL(rawSrc.url, quality);
|
return <img className={className} src={src} alt={alt} loading={loading} {...otherProps} />;
|
||||||
return (
|
|
||||||
<img
|
|
||||||
className={className}
|
|
||||||
src={src}
|
|
||||||
alt={alt}
|
|
||||||
loading={loading}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,8 +46,7 @@ export const Button = ({
|
||||||
<ConditionalWrapper
|
<ConditionalWrapper
|
||||||
isWrapping={isDefinedAndNotEmpty(href)}
|
isWrapping={isDefinedAndNotEmpty(href)}
|
||||||
wrapperProps={{ href: href ?? "", alwaysNewTab }}
|
wrapperProps={{ href: href ?? "", alwaysNewTab }}
|
||||||
wrapper={LinkWrapper}
|
wrapper={LinkWrapper}>
|
||||||
>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div
|
<div
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
|
@ -56,36 +55,32 @@ export const Button = ({
|
||||||
onFocus={(event) => event.target.blur()}
|
onFocus={(event) => event.target.blur()}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`group grid cursor-pointer select-none grid-flow-col place-content-center
|
`group grid cursor-pointer select-none grid-flow-col place-content-center
|
||||||
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
|
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
|
||||||
leading-none text-dark transition-all`,
|
leading-none text-dark transition-all`,
|
||||||
cIf(
|
cIf(
|
||||||
active,
|
active,
|
||||||
"!border-black bg-black !text-light drop-shadow-black-lg",
|
"!border-black bg-black !text-light drop-shadow-black-lg",
|
||||||
`hover:bg-dark hover:text-light hover:drop-shadow-shade-lg active:hover:!border-black
|
`hover:bg-dark hover:text-light hover:drop-shadow-shade-lg active:hover:!border-black
|
||||||
active:hover:bg-black active:hover:!text-light active:hover:drop-shadow-black-lg`
|
active:hover:bg-black active:hover:!text-light active:hover:drop-shadow-black-lg`
|
||||||
),
|
),
|
||||||
cIf(size === "small", "px-3 py-1 text-xs"),
|
cIf(size === "small", "px-3 py-1 text-xs"),
|
||||||
cIf(disabled, "cursor-not-allowed"),
|
cIf(disabled, "cursor-not-allowed"),
|
||||||
className
|
className
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{isDefined(badgeNumber) && (
|
{isDefined(badgeNumber) && (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center
|
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full bg-dark
|
||||||
rounded-full bg-dark font-bold text-light transition-opacity group-hover:opacity-0`,
|
font-bold text-light transition-opacity group-hover:opacity-0`,
|
||||||
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
|
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isDefinedAndNotEmpty(icon) && (
|
{isDefinedAndNotEmpty(icon) && (
|
||||||
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
|
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
|
||||||
)}
|
)}
|
||||||
{isDefinedAndNotEmpty(text) && (
|
{isDefinedAndNotEmpty(text) && <p className="-translate-y-[0.05em] text-center">{text}</p>}
|
||||||
<p className="-translate-y-[0.05em] text-center">{text}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ConditionalWrapper>
|
</ConditionalWrapper>
|
||||||
|
@ -103,15 +98,10 @@ export const TranslatedButton = ({
|
||||||
}: TranslatedProps<Props, "text">): JSX.Element => {
|
}: TranslatedProps<Props, "text">): JSX.Element => {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return <Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />;
|
||||||
<Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -124,11 +114,7 @@ interface LinkWrapperProps {
|
||||||
alwaysNewTab: boolean;
|
alwaysNewTab: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkWrapper = ({
|
const LinkWrapper = ({ children, alwaysNewTab, href }: LinkWrapperProps & Wrapper) => (
|
||||||
children,
|
|
||||||
alwaysNewTab,
|
|
||||||
href,
|
|
||||||
}: LinkWrapperProps & Wrapper) => (
|
|
||||||
<Link href={href} alwaysNewTab={alwaysNewTab}>
|
<Link href={href} alwaysNewTab={alwaysNewTab}>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -18,18 +18,14 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const ButtonGroup = ({
|
export const ButtonGroup = ({ buttonsProps, className }: Props): JSX.Element => (
|
||||||
buttonsProps,
|
|
||||||
className,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<div className={cJoin("grid grid-flow-col", className)}>
|
<div className={cJoin("grid grid-flow-col", className)}>
|
||||||
{buttonsProps.map((buttonProps, index) => (
|
{buttonsProps.map((buttonProps, index) => (
|
||||||
<ConditionalWrapper
|
<ConditionalWrapper
|
||||||
key={index}
|
key={index}
|
||||||
isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)}
|
isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)}
|
||||||
wrapper={ToolTipWrapper}
|
wrapper={ToolTipWrapper}
|
||||||
wrapperProps={{ text: buttonProps.tooltip ?? "" }}
|
wrapperProps={{ text: buttonProps.tooltip ?? "" }}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
className={
|
className={
|
||||||
|
|
|
@ -49,8 +49,7 @@ export const LanguageSwitcher = ({
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
badgeNumber={showBadge && locales.size > 1 ? locales.size : undefined}
|
badgeNumber={showBadge && locales.size > 1 ? locales.size : undefined}
|
||||||
icon={Icon.Translate}
|
icon={Icon.Translate}
|
||||||
|
|
|
@ -48,8 +48,7 @@ export const Link = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,11 +16,7 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const OrderableList = ({
|
export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Element => {
|
||||||
onChange,
|
|
||||||
items,
|
|
||||||
insertLabels,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const updateOrder = useCallback(
|
const updateOrder = useCallback(
|
||||||
(sourceIndex: number, targetIndex: number) => {
|
(sourceIndex: number, targetIndex: number) => {
|
||||||
console.log("updateOrder");
|
console.log("updateOrder");
|
||||||
|
@ -57,17 +53,13 @@ export const OrderableList = ({
|
||||||
.filter((element) => element.tagName === "DIV")
|
.filter((element) => element.tagName === "DIV")
|
||||||
.indexOf(target)
|
.indexOf(target)
|
||||||
: -1;
|
: -1;
|
||||||
const sourceIndex = parseInt(
|
const sourceIndex = parseInt(event.dataTransfer.getData("text"), 10);
|
||||||
event.dataTransfer.getData("text"),
|
|
||||||
10
|
|
||||||
);
|
|
||||||
updateOrder(sourceIndex, targetIndex);
|
updateOrder(sourceIndex, targetIndex);
|
||||||
}}
|
}}
|
||||||
className="grid cursor-grab select-none grid-cols-[auto_1fr] place-content-center gap-2
|
className="grid cursor-grab select-none grid-cols-[auto_1fr] place-content-center gap-2
|
||||||
rounded-full border-[1px] border-dark bg-light px-1 py-2 pr-4 text-dark transition-all
|
rounded-full border-[1px] border-dark bg-light px-1 py-2 pr-4 text-dark transition-all
|
||||||
hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
|
hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
|
||||||
draggable
|
draggable>
|
||||||
>
|
|
||||||
<div className="grid grid-rows-[.8em_.8em] place-items-center">
|
<div className="grid grid-rows-[.8em_.8em] place-items-center">
|
||||||
{index > 0 && (
|
{index > 0 && (
|
||||||
<Ico
|
<Ico
|
||||||
|
|
|
@ -16,12 +16,7 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const PageSelector = ({
|
export const PageSelector = ({ page, className, pagesCount, onChange }: Props): JSX.Element => (
|
||||||
page,
|
|
||||||
className,
|
|
||||||
pagesCount,
|
|
||||||
onChange,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
className={cJoin("flex flex-row place-content-center", className)}
|
className={cJoin("flex flex-row place-content-center", className)}
|
||||||
buttonsProps={[
|
buttonsProps={[
|
||||||
|
|
|
@ -19,18 +19,8 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const Select = ({
|
export const Select = ({ className, value, options, allowEmpty, onChange }: Props): JSX.Element => {
|
||||||
className,
|
const { value: isOpened, setFalse: setClosed, toggle: toggleOpened } = useBoolean(false);
|
||||||
value,
|
|
||||||
options,
|
|
||||||
allowEmpty,
|
|
||||||
onChange,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const {
|
|
||||||
value: isOpened,
|
|
||||||
setFalse: setClosed,
|
|
||||||
toggle: toggleOpened,
|
|
||||||
} = useBoolean(false);
|
|
||||||
|
|
||||||
const tryToggling = useCallback(() => {
|
const tryToggling = useCallback(() => {
|
||||||
const optionCount = options.length + (value === -1 ? 1 : 0);
|
const optionCount = options.length + (value === -1 ? 1 : 0);
|
||||||
|
@ -47,16 +37,14 @@ export const Select = ({
|
||||||
"relative text-center transition-[filter]",
|
"relative text-center transition-[filter]",
|
||||||
cIf(isOpened, "z-10 drop-shadow-shade-lg"),
|
cIf(isOpened, "z-10 drop-shadow-shade-lg"),
|
||||||
className
|
className
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`grid cursor-pointer grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
|
`grid cursor-pointer grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
|
||||||
rounded-[1em] bg-light p-1 outline outline-2 outline-offset-[-2px] outline-mid
|
rounded-[1em] bg-light p-1 outline outline-2 outline-offset-[-2px] outline-mid
|
||||||
transition-all hover:bg-mid hover:outline-[transparent]`,
|
transition-all hover:bg-mid hover:outline-[transparent]`,
|
||||||
cIf(isOpened, "rounded-b-none bg-highlight outline-[transparent]")
|
cIf(isOpened, "rounded-b-none bg-highlight outline-[transparent]")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<p onClick={tryToggling} className="w-full">
|
<p onClick={tryToggling} className="w-full">
|
||||||
{value === -1 ? "—" : options[value]}
|
{value === -1 ? "—" : options[value]}
|
||||||
</p>
|
</p>
|
||||||
|
@ -70,17 +58,9 @@ export const Select = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Ico
|
<Ico onClick={tryToggling} icon={isOpened ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
|
||||||
onClick={tryToggling}
|
|
||||||
icon={isOpened ? Icon.ArrowDropUp : Icon.ArrowDropDown}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className={cJoin("left-0 right-0 rounded-b-[1em]", cIf(isOpened, "absolute", "hidden"))}>
|
||||||
className={cJoin(
|
|
||||||
"left-0 right-0 rounded-b-[1em]",
|
|
||||||
cIf(isOpened, "absolute", "hidden")
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{index !== value && (
|
{index !== value && (
|
||||||
|
@ -93,8 +73,7 @@ export const Select = ({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setClosed();
|
setClosed();
|
||||||
onChange(index);
|
onChange(index);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{option}
|
{option}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -14,12 +14,7 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const Switch = ({
|
export const Switch = ({ value, onClick, className, disabled = false }: Props): JSX.Element => (
|
||||||
value,
|
|
||||||
onClick,
|
|
||||||
className,
|
|
||||||
disabled = false,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
|
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
|
||||||
|
@ -29,17 +24,11 @@ export const Switch = ({
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!disabled) onClick();
|
if (!disabled) onClick();
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"absolute aspect-square rounded-full bg-dark transition-transform",
|
"absolute aspect-square rounded-full bg-dark transition-transform",
|
||||||
cIf(
|
cIf(value, "top-[2px] bottom-[2px] left-[2px] translate-x-[120%]", "top-0 bottom-0 left-0")
|
||||||
value,
|
)}></div>
|
||||||
"top-[2px] bottom-[2px] left-[2px] translate-x-[120%]",
|
|
||||||
"top-0 bottom-0 left-0"
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,23 +14,14 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const WithLabel = ({
|
export const WithLabel = ({ label, children, disabled }: Props): JSX.Element => (
|
||||||
label,
|
|
||||||
children,
|
|
||||||
disabled,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"flex flex-row place-content-between place-items-center gap-2",
|
"flex flex-row place-content-between place-items-center gap-2",
|
||||||
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
|
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{isDefinedAndNotEmpty(label) && (
|
{isDefinedAndNotEmpty(label) && (
|
||||||
<p
|
<p className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}>{label}:</p>
|
||||||
className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}
|
|
||||||
>
|
|
||||||
{label}:
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,11 +16,7 @@ interface Props {
|
||||||
export const InsetBox = ({ id, className, children }: Props): JSX.Element => (
|
export const InsetBox = ({ id, className, children }: Props): JSX.Element => (
|
||||||
<div
|
<div
|
||||||
id={id}
|
id={id}
|
||||||
className={cJoin(
|
className={cJoin("w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade", className)}>
|
||||||
"w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { LibraryItemUserStatus } from "helpers/types";
|
import { LibraryItemUserStatus } from "types/types";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { cIf, cJoin } from "helpers/className";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭─────────────╮
|
* ╭─────────────╮
|
||||||
|
@ -17,52 +18,49 @@ interface Props {
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
|
export const PreviewCardCTAs = ({ id, expand = false }: Props): JSX.Element => {
|
||||||
const { libraryItemUserStatus, setLibraryItemUserStatus, langui } =
|
const { libraryItemUserStatus, setLibraryItemUserStatus, langui } = useAppLayout();
|
||||||
useAppLayout();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
<div
|
className={cJoin(
|
||||||
className={`flex flex-row flex-wrap place-content-center place-items-center ${
|
"flex flex-row flex-wrap place-content-center place-items-center",
|
||||||
expand ? "gap-4" : "gap-2"
|
cIf(expand, "gap-4", "gap-2")
|
||||||
}`}
|
)}>
|
||||||
>
|
<ToolTip content={langui.want_it} disabled={expand}>
|
||||||
<ToolTip content={langui.want_it} disabled={expand}>
|
<Button
|
||||||
<Button
|
icon={Icon.Favorite}
|
||||||
icon={Icon.Favorite}
|
text={expand ? langui.want_it : undefined}
|
||||||
text={expand ? langui.want_it : undefined}
|
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
|
||||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
|
onClick={(event) => {
|
||||||
onClick={(event) => {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
setLibraryItemUserStatus((current) => {
|
||||||
setLibraryItemUserStatus((current) => {
|
const newLibraryItemUserStatus = { ...current };
|
||||||
const newLibraryItemUserStatus = { ...current };
|
newLibraryItemUserStatus[id] =
|
||||||
newLibraryItemUserStatus[id] =
|
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
||||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
? LibraryItemUserStatus.None
|
||||||
? LibraryItemUserStatus.None
|
: LibraryItemUserStatus.Want;
|
||||||
: LibraryItemUserStatus.Want;
|
return newLibraryItemUserStatus;
|
||||||
return newLibraryItemUserStatus;
|
});
|
||||||
});
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</ToolTip>
|
||||||
</ToolTip>
|
<ToolTip content={langui.have_it} disabled={expand}>
|
||||||
<ToolTip content={langui.have_it} disabled={expand}>
|
<Button
|
||||||
<Button
|
icon={Icon.BackHand}
|
||||||
icon={Icon.BackHand}
|
text={expand ? langui.have_it : undefined}
|
||||||
text={expand ? langui.have_it : undefined}
|
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
|
||||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
|
onClick={(event) => {
|
||||||
onClick={(event) => {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
setLibraryItemUserStatus((current) => {
|
||||||
setLibraryItemUserStatus((current) => {
|
const newLibraryItemUserStatus = { ...current };
|
||||||
const newLibraryItemUserStatus = { ...current };
|
newLibraryItemUserStatus[id] =
|
||||||
newLibraryItemUserStatus[id] =
|
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
||||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
? LibraryItemUserStatus.None
|
||||||
? LibraryItemUserStatus.None
|
: LibraryItemUserStatus.Have;
|
||||||
: LibraryItemUserStatus.Have;
|
return newLibraryItemUserStatus;
|
||||||
return newLibraryItemUserStatus;
|
});
|
||||||
});
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</ToolTip>
|
||||||
</ToolTip>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,9 +21,7 @@ const SENSIBILITY_SWIPE = 0.5;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setState:
|
setState: Dispatch<SetStateAction<boolean | undefined>> | Dispatch<SetStateAction<boolean>>;
|
||||||
| Dispatch<SetStateAction<boolean | undefined>>
|
|
||||||
| Dispatch<SetStateAction<boolean>>;
|
|
||||||
state: boolean;
|
state: boolean;
|
||||||
images: string[];
|
images: string[];
|
||||||
index: number;
|
index: number;
|
||||||
|
@ -32,13 +30,7 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const LightBox = ({
|
export const LightBox = ({ state, setState, images, index, setIndex }: Props): JSX.Element => {
|
||||||
state,
|
|
||||||
setState,
|
|
||||||
images,
|
|
||||||
index,
|
|
||||||
setIndex,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const handlePrevious = useCallback(() => {
|
const handlePrevious = useCallback(() => {
|
||||||
if (index > 0) setIndex(index - 1);
|
if (index > 0) setIndex(index - 1);
|
||||||
}, [index, setIndex]);
|
}, [index, setIndex]);
|
||||||
|
@ -71,14 +63,8 @@ export const LightBox = ({
|
||||||
} else {
|
} else {
|
||||||
handleNext();
|
handleNext();
|
||||||
}
|
}
|
||||||
}}
|
}}>
|
||||||
>
|
<Popup onClose={() => setState(false)} state={state} padding={false} fillViewport>
|
||||||
<Popup
|
|
||||||
onClose={() => setState(false)}
|
|
||||||
state={state}
|
|
||||||
padding={false}
|
|
||||||
fillViewport
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
{...handlers}
|
{...handlers}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
|
@ -88,18 +74,12 @@ export const LightBox = ({
|
||||||
`grid-cols-[4em,1fr,4em] [grid-template-areas:"left_image_right"]`,
|
`grid-cols-[4em,1fr,4em] [grid-template-areas:"left_image_right"]`,
|
||||||
`grid-cols-2 [grid-template-areas:"image_image""left_right"]`
|
`grid-cols-2 [grid-template-areas:"image_image""left_right"]`
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div className="ml-4 [grid-area:left]">
|
<div className="ml-4 [grid-area:left]">
|
||||||
{index > 0 && (
|
{index > 0 && <Button onClick={handlePrevious} icon={Icon.ChevronLeft} />}
|
||||||
<Button onClick={handlePrevious} icon={Icon.ChevronLeft} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Img
|
<Img className="max-h-full min-h-fit [grid-area:image]" src={images[index]} />
|
||||||
className="max-h-full min-h-fit [grid-area:image]"
|
|
||||||
src={images[index]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mr-4 [grid-area:right]">
|
<div className="mr-4 [grid-area:right]">
|
||||||
{index < images.length - 1 && (
|
{index < images.length - 1 && (
|
||||||
|
|
|
@ -29,10 +29,7 @@ interface MarkdawnProps {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const Markdawn = ({
|
export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => {
|
||||||
className,
|
|
||||||
text: rawText,
|
|
||||||
}: MarkdawnProps): JSX.Element => {
|
|
||||||
const { playerName } = useAppLayout();
|
const { playerName } = useAppLayout();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isContentPanelAtLeastLg = useIsContentPanelAtLeast("lg");
|
const isContentPanelAtLeastLg = useIsContentPanelAtLeast("lg");
|
||||||
|
@ -59,18 +56,10 @@ export const Markdawn = ({
|
||||||
slugify: slugify,
|
slugify: slugify,
|
||||||
overrides: {
|
overrides: {
|
||||||
a: {
|
a: {
|
||||||
component: (compProps: {
|
component: (compProps: { href: string; children: React.ReactNode }) => {
|
||||||
href: string;
|
if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) {
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
if (
|
|
||||||
compProps.href.startsWith("/") ||
|
|
||||||
compProps.href.startsWith("#")
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<a onClick={async () => router.push(compProps.href)}>
|
<a onClick={async () => router.push(compProps.href)}>{compProps.children}</a>
|
||||||
{compProps.children}
|
|
||||||
</a>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -112,11 +101,7 @@ export const Markdawn = ({
|
||||||
? slugify(compProps.target)
|
? slugify(compProps.target)
|
||||||
: slugify(compProps.children?.toString());
|
: slugify(compProps.children?.toString());
|
||||||
return (
|
return (
|
||||||
<a
|
<a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}>
|
||||||
onClick={async () =>
|
|
||||||
router.replace(`${compProps.page ?? ""}#${slug}`)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -128,13 +113,8 @@ export const Markdawn = ({
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid gap-x-6 gap-y-2",
|
"grid gap-x-6 gap-y-2",
|
||||||
cIf(
|
cIf(isContentPanelAtLeastLg, "grid-cols-[auto_1fr]", "grid-cols-1")
|
||||||
isContentPanelAtLeastLg,
|
)}>
|
||||||
"grid-cols-[auto_1fr]",
|
|
||||||
"grid-cols-1"
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
@ -147,8 +127,7 @@ export const Markdawn = ({
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"!my-0 text-dark/60",
|
"!my-0 text-dark/60",
|
||||||
cIf(!isContentPanelAtLeastLg, "!-mb-4")
|
cIf(!isContentPanelAtLeastLg, "!-mb-4")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<Markdawn text={compProps.name} />
|
<Markdawn text={compProps.name} />
|
||||||
</strong>
|
</strong>
|
||||||
<p className="whitespace-pre-line">{compProps.children}</p>
|
<p className="whitespace-pre-line">{compProps.children}</p>
|
||||||
|
@ -157,9 +136,7 @@ export const Markdawn = ({
|
||||||
},
|
},
|
||||||
|
|
||||||
InsetBox: {
|
InsetBox: {
|
||||||
component: (compProps) => (
|
component: (compProps) => <InsetBox className="my-12">{compProps.children}</InsetBox>,
|
||||||
<InsetBox className="my-12">{compProps.children}</InsetBox>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
li: {
|
li: {
|
||||||
|
@ -167,13 +144,10 @@ export const Markdawn = ({
|
||||||
<li
|
<li
|
||||||
className={
|
className={
|
||||||
isDefined(compProps.children) &&
|
isDefined(compProps.children) &&
|
||||||
ReactDOMServer.renderToStaticMarkup(
|
ReactDOMServer.renderToStaticMarkup(<>{compProps.children}</>).length > 100
|
||||||
<>{compProps.children}</>
|
|
||||||
).length > 100
|
|
||||||
? "my-4"
|
? "my-4"
|
||||||
: ""
|
: ""
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
{compProps.children}
|
{compProps.children}
|
||||||
</li>
|
</li>
|
||||||
),
|
),
|
||||||
|
@ -195,10 +169,7 @@ export const Markdawn = ({
|
||||||
},
|
},
|
||||||
|
|
||||||
blockquote: {
|
blockquote: {
|
||||||
component: (compProps: {
|
component: (compProps: { children: React.ReactNode; cite?: string }) => (
|
||||||
children: React.ReactNode;
|
|
||||||
cite?: string;
|
|
||||||
}) => (
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
{isDefinedAndNotEmpty(compProps.cite) ? (
|
{isDefinedAndNotEmpty(compProps.cite) ? (
|
||||||
<>
|
<>
|
||||||
|
@ -229,8 +200,7 @@ export const Markdawn = ({
|
||||||
? getAssetURL(compProps.src, ImageQuality.Large)
|
? getAssetURL(compProps.src, ImageQuality.Large)
|
||||||
: compProps.src,
|
: compProps.src,
|
||||||
]);
|
]);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<Img
|
<Img
|
||||||
src={
|
src={
|
||||||
compProps.src.startsWith("/uploads/")
|
compProps.src.startsWith("/uploads/")
|
||||||
|
@ -238,14 +208,12 @@ export const Markdawn = ({
|
||||||
: compProps.src
|
: compProps.src
|
||||||
}
|
}
|
||||||
quality={ImageQuality.Medium}
|
quality={ImageQuality.Medium}
|
||||||
className="drop-shadow-shade-lg"
|
className="drop-shadow-shade-lg"></Img>
|
||||||
></Img>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{text}
|
{text}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</>
|
</>
|
||||||
|
@ -269,10 +237,7 @@ export const TableOfContents = ({
|
||||||
}: TableOfContentsProps): JSX.Element => {
|
}: TableOfContentsProps): JSX.Element => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const toc = useMemo(
|
const toc = useMemo(() => getTocFromMarkdawn(preprocessMarkDawn(text), title), [text, title]);
|
||||||
() => getTocFromMarkdawn(preprocessMarkDawn(text), title),
|
|
||||||
[text, title]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -283,8 +248,7 @@ export const TableOfContents = ({
|
||||||
<div className="max-w-[14.5rem] text-left">
|
<div className="max-w-[14.5rem] text-left">
|
||||||
<p
|
<p
|
||||||
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
|
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
|
||||||
text-left"
|
text-left">
|
||||||
>
|
|
||||||
<a onClick={async () => router.replace(`#${toc.slug}`)}>
|
<a onClick={async () => router.replace(`#${toc.slug}`)}>
|
||||||
{<abbr title={toc.title}>{toc.title}</abbr>}
|
{<abbr title={toc.title}>{toc.title}</abbr>}
|
||||||
</a>
|
</a>
|
||||||
|
@ -324,10 +288,7 @@ const Header = ({ level, title, slug }: HeaderProps): JSX.Element => {
|
||||||
<div className="font-headers">{title}</div>
|
<div className="font-headers">{title}</div>
|
||||||
)}
|
)}
|
||||||
<AnchorShare
|
<AnchorShare
|
||||||
className={cIf(
|
className={cIf(isHoverable, "opacity-0 transition-opacity group-hover:opacity-100")}
|
||||||
isHoverable,
|
|
||||||
"opacity-0 transition-opacity group-hover:opacity-100"
|
|
||||||
)}
|
|
||||||
id={slug}
|
id={slug}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -395,10 +356,7 @@ const TocLevel = ({
|
||||||
}: LevelProps): JSX.Element => {
|
}: LevelProps): JSX.Element => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const ids = useMemo(
|
const ids = useMemo(() => tocchildren.map((child) => child.slug), [tocchildren]);
|
||||||
() => tocchildren.map((child) => child.slug),
|
|
||||||
[tocchildren]
|
|
||||||
);
|
|
||||||
const currentIntersection = useIntersectionList(ids);
|
const currentIntersection = useIntersectionList(ids);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -408,15 +366,9 @@ const TocLevel = ({
|
||||||
<li
|
<li
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap",
|
"my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap",
|
||||||
cIf(
|
cIf(allowIntersection && currentIntersection === childIndex, "text-dark")
|
||||||
allowIntersection && currentIntersection === childIndex,
|
)}>
|
||||||
"text-dark"
|
<span className="text-dark">{`${parentNumbering}${childIndex + 1}.`}</span>{" "}
|
||||||
)
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="text-dark">{`${parentNumbering}${
|
|
||||||
childIndex + 1
|
|
||||||
}.`}</span>{" "}
|
|
||||||
<a onClick={async () => router.replace(`#${child.slug}`)}>
|
<a onClick={async () => router.replace(`#${child.slug}`)}>
|
||||||
{<abbr title={child.title}>{child.title}</abbr>}
|
{<abbr title={child.title}>{child.title}</abbr>}
|
||||||
</a>
|
</a>
|
||||||
|
@ -424,9 +376,7 @@ const TocLevel = ({
|
||||||
<TocLevel
|
<TocLevel
|
||||||
tocchildren={child.children}
|
tocchildren={child.children}
|
||||||
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
||||||
allowIntersection={
|
allowIntersection={allowIntersection && currentIntersection === childIndex}
|
||||||
allowIntersection && currentIntersection === childIndex
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
@ -442,17 +392,13 @@ const TocLevel = ({
|
||||||
const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
|
|
||||||
const processedPlayerName = playerName
|
const processedPlayerName = playerName.replaceAll("_", "\\_").replaceAll("*", "\\*");
|
||||||
.replaceAll("_", "\\_")
|
|
||||||
.replaceAll("*", "\\*");
|
|
||||||
|
|
||||||
let preprocessed = text
|
let preprocessed = text
|
||||||
.replaceAll("--", "—")
|
.replaceAll("--", "—")
|
||||||
.replaceAll(
|
.replaceAll(
|
||||||
"@player",
|
"@player",
|
||||||
isDefinedAndNotEmpty(processedPlayerName)
|
isDefinedAndNotEmpty(processedPlayerName) ? processedPlayerName : "(player)"
|
||||||
? processedPlayerName
|
|
||||||
: "(player)"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let scenebreakIndex = 0;
|
let scenebreakIndex = 0;
|
||||||
|
@ -511,8 +457,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
||||||
let scenebreak = 0;
|
let scenebreak = 0;
|
||||||
let scenebreakIndex = 0;
|
let scenebreakIndex = 0;
|
||||||
|
|
||||||
const getTitle = (line: string): string =>
|
const getTitle = (line: string): string => line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
|
||||||
line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
|
|
||||||
|
|
||||||
const getSlug = (line: string): string =>
|
const getSlug = (line: string): string =>
|
||||||
line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
|
line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
|
||||||
|
@ -573,9 +518,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (h5 >= 0) {
|
if (h5 >= 0) {
|
||||||
toc.children[h2].children[h3].children[h4].children[h5].children.push(
|
toc.children[h2].children[h3].children[h4].children[h5].children.push(child);
|
||||||
child
|
|
||||||
);
|
|
||||||
} else if (h4 >= 0) {
|
} else if (h4 >= 0) {
|
||||||
toc.children[h2].children[h3].children[h4].children.push(child);
|
toc.children[h2].children[h3].children[h4].children.push(child);
|
||||||
} else if (h3 >= 0) {
|
} else if (h3 >= 0) {
|
||||||
|
|
|
@ -47,15 +47,12 @@ export const NavOption = ({
|
||||||
content={
|
content={
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl">{title}</h3>
|
<h3 className="text-2xl">{title}</h3>
|
||||||
{isDefinedAndNotEmpty(subtitle) && (
|
{isDefinedAndNotEmpty(subtitle) && <p className="col-start-2">{subtitle}</p>}
|
||||||
<p className="col-start-2">{subtitle}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
placement="right"
|
placement="right"
|
||||||
className="text-left"
|
className="text-left"
|
||||||
disabled={!reduced}
|
disabled={!reduced}>
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
href={url}
|
href={url}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
@ -69,16 +66,13 @@ export const NavOption = ({
|
||||||
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
|
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
|
||||||
),
|
),
|
||||||
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade")
|
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />}
|
{icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />}
|
||||||
|
|
||||||
{!reduced && (
|
{!reduced && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl">{title}</h3>
|
<h3 className="text-2xl">{title}</h3>
|
||||||
{isDefinedAndNotEmpty(subtitle) && (
|
{isDefinedAndNotEmpty(subtitle) && <p className="col-start-2">{subtitle}</p>}
|
||||||
<p className="col-start-2">{subtitle}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -98,10 +92,7 @@ export const TranslatedNavOption = ({
|
||||||
}: TranslatedProps<Props, "subtitle" | "title">): JSX.Element => {
|
}: TranslatedProps<Props, "subtitle" | "title">): JSX.Element => {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<NavOption
|
<NavOption
|
||||||
|
|
|
@ -14,11 +14,7 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const PanelHeader = ({
|
export const PanelHeader = ({ icon, description, title }: Props): JSX.Element => (
|
||||||
icon,
|
|
||||||
description,
|
|
||||||
title,
|
|
||||||
}: Props): JSX.Element => (
|
|
||||||
<>
|
<>
|
||||||
<div className="grid w-full place-items-center">
|
<div className="grid w-full place-items-center">
|
||||||
{icon && <Ico icon={icon} className="mb-3 !text-4xl" />}
|
{icon && <Ico icon={icon} className="mb-3 !text-4xl" />}
|
||||||
|
|
|
@ -22,12 +22,7 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export const ReturnButton = ({
|
export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => {
|
||||||
href,
|
|
||||||
title,
|
|
||||||
displayOnlyOn,
|
|
||||||
className,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const { setSubPanelOpen, langui } = useAppLayout();
|
const { setSubPanelOpen, langui } = useAppLayout();
|
||||||
const is3ColumnsLayout = useIs3ColumnsLayout();
|
const is3ColumnsLayout = useIs3ColumnsLayout();
|
||||||
|
|
||||||
|
@ -61,16 +56,8 @@ export const TranslatedReturnButton = ({
|
||||||
}: TranslatedProps<Props, "title">): JSX.Element => {
|
}: TranslatedProps<Props, "title">): JSX.Element => {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return <ReturnButton title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||||
<ReturnButton
|
|
||||||
title={selectedTranslation?.title ?? fallback.title}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,8 +38,7 @@ export const ContentPanel = ({
|
||||||
? "max-w-4xl"
|
? "max-w-4xl"
|
||||||
: "w-full",
|
: "w-full",
|
||||||
className
|
className
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,16 +29,14 @@ export const MainPanel = (): JSX.Element => {
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid content-start justify-center gap-y-2 p-8 text-center",
|
"grid content-start justify-center gap-y-2 p-8 text-center",
|
||||||
cIf(mainPanelReduced && is3ColumnsLayout, "px-4")
|
cIf(mainPanelReduced && is3ColumnsLayout, "px-4")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{/* Reduce/expand main menu */}
|
{/* Reduce/expand main menu */}
|
||||||
{is3ColumnsLayout && (
|
{is3ColumnsLayout && (
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"fixed top-1/2",
|
"fixed top-1/2",
|
||||||
cIf(mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]")
|
cIf(mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (mainPanelReduced) {
|
if (mainPanelReduced) {
|
||||||
|
@ -63,8 +61,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
|
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
|
||||||
![mask-position:center] hover:bg-dark`,
|
![mask-position:center] hover:bg-dark`,
|
||||||
cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2")
|
cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2")
|
||||||
)}
|
)}></div>
|
||||||
></div>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{(!mainPanelReduced || !is3ColumnsLayout) && (
|
{(!mainPanelReduced || !is3ColumnsLayout) && (
|
||||||
|
@ -74,19 +71,13 @@ export const MainPanel = (): JSX.Element => {
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"flex flex-wrap gap-2",
|
"flex flex-wrap gap-2",
|
||||||
cIf(
|
cIf(mainPanelReduced && is3ColumnsLayout, "flex-col gap-3", "flex-row")
|
||||||
mainPanelReduced && is3ColumnsLayout,
|
)}>
|
||||||
"flex-col gap-3",
|
|
||||||
"flex-row"
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
||||||
placement="right"
|
placement="right"
|
||||||
className="text-left"
|
className="text-left"
|
||||||
disabled={!mainPanelReduced}
|
disabled={!mainPanelReduced}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setConfigPanelOpen(true);
|
setConfigPanelOpen(true);
|
||||||
|
@ -174,12 +165,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
|
|
||||||
{mainPanelReduced && is3ColumnsLayout ? "" : <HorizontalLine />}
|
{mainPanelReduced && is3ColumnsLayout ? "" : <HorizontalLine />}
|
||||||
|
|
||||||
<div
|
<div className={cJoin("text-center", cIf(mainPanelReduced && is3ColumnsLayout, "hidden"))}>
|
||||||
className={cJoin(
|
|
||||||
"text-center",
|
|
||||||
cIf(mainPanelReduced && is3ColumnsLayout, "hidden")
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isDefinedAndNotEmpty(langui.licensing_notice) && (
|
{isDefinedAndNotEmpty(langui.licensing_notice) && (
|
||||||
<p>
|
<p>
|
||||||
<Markdown>{langui.licensing_notice}</Markdown>
|
<Markdown>{langui.licensing_notice}</Markdown>
|
||||||
|
@ -190,8 +176,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
onClick={() => umami("[MainPanel] Visit license")}
|
onClick={() => umami("[MainPanel] Visit license")}
|
||||||
aria-label="Read more about the license we use for this website"
|
aria-label="Read more about the license we use for this website"
|
||||||
className="group grid grid-flow-col place-content-center gap-1 transition-[filter]"
|
className="group grid grid-flow-col place-content-center gap-1 transition-[filter]"
|
||||||
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
href="https://creativecommons.org/licenses/by-sa/4.0/">
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="aspect-square w-6 bg-black transition-colors
|
className="aspect-square w-6 bg-black transition-colors
|
||||||
[mask:url('/icons/creative-commons-brands.svg')] ![mask-size:contain]
|
[mask:url('/icons/creative-commons-brands.svg')] ![mask-size:contain]
|
||||||
|
@ -223,8 +208,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
|
||||||
href="https://github.com/Accords-Library"
|
href="https://github.com/Accords-Library"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"></a>
|
||||||
></a>
|
|
||||||
<a
|
<a
|
||||||
aria-label="Follow us on Twitter"
|
aria-label="Follow us on Twitter"
|
||||||
onClick={() => umami("[MainPanel] Visit Twitter")}
|
onClick={() => umami("[MainPanel] Visit Twitter")}
|
||||||
|
@ -233,8 +217,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
|
||||||
href="https://twitter.com/AccordsLibrary"
|
href="https://twitter.com/AccordsLibrary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"></a>
|
||||||
></a>
|
|
||||||
<a
|
<a
|
||||||
aria-label="Join our Discord server!"
|
aria-label="Join our Discord server!"
|
||||||
onClick={() => umami("[MainPanel] Visit Discord")}
|
onClick={() => umami("[MainPanel] Visit Discord")}
|
||||||
|
@ -243,8 +226,7 @@ export const MainPanel = (): JSX.Element => {
|
||||||
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
|
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark"
|
||||||
href="/discord"
|
href="/discord"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"></a>
|
||||||
></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,8 +19,7 @@ export const SubPanel = ({ children }: Props): JSX.Element => {
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid gap-y-2 text-center",
|
"grid gap-y-2 text-center",
|
||||||
cIf(isSubPanelAtLeastSm, "px-10 pt-10 pb-20", "p-4")
|
cIf(isSubPanelAtLeastSm, "px-10 pt-10 pb-20", "p-4")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -38,13 +38,8 @@ export const Popup = ({
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500",
|
"fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500",
|
||||||
cIf(
|
cIf(state, "[backdrop-filter:blur(2px)]", "pointer-events-none touch-none")
|
||||||
state,
|
)}>
|
||||||
"[backdrop-filter:blur(2px)]",
|
|
||||||
"pointer-events-none touch-none"
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"fixed inset-0 bg-shade transition-all duration-500",
|
"fixed inset-0 bg-shade transition-all duration-500",
|
||||||
|
@ -58,14 +53,9 @@ export const Popup = ({
|
||||||
"grid place-items-center gap-4 transition-transform",
|
"grid place-items-center gap-4 transition-transform",
|
||||||
cIf(padding, "p-10"),
|
cIf(padding, "p-10"),
|
||||||
cIf(state, "scale-100", "scale-0"),
|
cIf(state, "scale-100", "scale-0"),
|
||||||
cIf(
|
cIf(fillViewport, "absolute inset-10", "relative max-h-[80vh] overflow-y-auto"),
|
||||||
fillViewport,
|
|
||||||
"absolute inset-10",
|
|
||||||
"relative max-h-[80vh] overflow-y-auto"
|
|
||||||
),
|
|
||||||
cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade")
|
cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { RecorderChip } from "./RecorderChip";
|
||||||
import { ThumbnailHeader } from "./ThumbnailHeader";
|
import { ThumbnailHeader } from "./ThumbnailHeader";
|
||||||
import { ToolTip } from "./ToolTip";
|
import { ToolTip } from "./ToolTip";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { PostWithTranslations } from "helpers/types";
|
import { PostWithTranslations } from "types/types";
|
||||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||||
import { prettySlug } from "helpers/formatters";
|
import { prettySlug } from "helpers/formatters";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
@ -49,21 +49,19 @@ export const PostPage = ({
|
||||||
...otherProps
|
...otherProps
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
useSmartLanguage({
|
items: post.translations,
|
||||||
items: post.translations,
|
languageExtractor: useCallback(
|
||||||
languageExtractor: useCallback(
|
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
|
||||||
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
|
item.language?.data?.attributes?.code,
|
||||||
item.language?.data?.attributes?.code,
|
[]
|
||||||
[]
|
),
|
||||||
),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const { thumbnail, body, title, excerpt } = useMemo(
|
const { thumbnail, body, title, excerpt } = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
thumbnail:
|
thumbnail:
|
||||||
selectedTranslation?.thumbnail?.data?.attributes ??
|
selectedTranslation?.thumbnail?.data?.attributes ?? post.thumbnail?.data?.attributes,
|
||||||
post.thumbnail?.data?.attributes,
|
|
||||||
body: selectedTranslation?.body ?? "",
|
body: selectedTranslation?.body ?? "",
|
||||||
title: selectedTranslation?.title ?? prettySlug(post.slug),
|
title: selectedTranslation?.title ?? prettySlug(post.slug),
|
||||||
excerpt: selectedTranslation?.excerpt ?? "",
|
excerpt: selectedTranslation?.excerpt ?? "",
|
||||||
|
@ -76,11 +74,7 @@ export const PostPage = ({
|
||||||
returnHref || returnTitle || displayCredits || displayToc ? (
|
returnHref || returnTitle || displayCredits || displayToc ? (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
{returnHref && returnTitle && (
|
{returnHref && returnTitle && (
|
||||||
<ReturnButton
|
<ReturnButton href={returnHref} title={returnTitle} displayOnlyOn={"3ColumnsLayout"} />
|
||||||
href={returnHref}
|
|
||||||
title={returnTitle}
|
|
||||||
displayOnlyOn={"3ColumnsLayout"}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{displayCredits && (
|
{displayCredits && (
|
||||||
|
@ -92,12 +86,8 @@ export const PostPage = ({
|
||||||
<p className="font-headers font-bold">{langui.status}:</p>
|
<p className="font-headers font-bold">{langui.status}:</p>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={getStatusDescription(
|
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||||
selectedTranslation.status,
|
maxWidth={"20rem"}>
|
||||||
langui
|
|
||||||
)}
|
|
||||||
maxWidth={"20rem"}
|
|
||||||
>
|
|
||||||
<Chip text={selectedTranslation.status} />
|
<Chip text={selectedTranslation.status} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,23 +97,20 @@ export const PostPage = ({
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers font-bold">{"Authors"}:</p>
|
<p className="font-headers font-bold">{"Authors"}:</p>
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(post.authors.data, [
|
{filterHasAttributes(post.authors.data, ["id", "attributes"] as const).map(
|
||||||
"id",
|
(author) => (
|
||||||
"attributes",
|
<Fragment key={author.id}>
|
||||||
] as const).map((author) => (
|
<RecorderChip recorder={author.attributes} />
|
||||||
<Fragment key={author.id}>
|
</Fragment>
|
||||||
<RecorderChip recorder={author.attributes} />
|
)
|
||||||
</Fragment>
|
)}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{displayToc && (
|
{displayToc && <TableOfContents text={body} title={title} horizontalLine />}
|
||||||
<TableOfContents text={body} title={title} horizontalLine />
|
|
||||||
)}
|
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
) : undefined,
|
) : undefined,
|
||||||
[
|
[
|
||||||
|
@ -173,9 +160,7 @@ export const PostPage = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{displayTitle && (
|
{displayTitle && (
|
||||||
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">
|
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">{title}</h1>
|
||||||
{title}
|
|
||||||
</h1>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -209,11 +194,5 @@ export const PostPage = ({
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout {...otherProps} contentPanel={contentPanel} subPanel={subPanel} />;
|
||||||
<AppLayout
|
|
||||||
{...otherProps}
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
subPanel={subPanel}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,18 +5,9 @@ import { Ico, Icon } from "./Ico";
|
||||||
import { Img } from "./Img";
|
import { Img } from "./Img";
|
||||||
import { Link } from "./Inputs/Link";
|
import { Link } from "./Inputs/Link";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import {
|
import { DatePickerFragment, PricePickerFragment, UploadImageFragment } from "graphql/generated";
|
||||||
DatePickerFragment,
|
|
||||||
PricePickerFragment,
|
|
||||||
UploadImageFragment,
|
|
||||||
} from "graphql/generated";
|
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import {
|
import { prettyDate, prettyDuration, prettyPrice, prettyShortenNumber } from "helpers/formatters";
|
||||||
prettyDate,
|
|
||||||
prettyDuration,
|
|
||||||
prettyPrice,
|
|
||||||
prettyShortenNumber,
|
|
||||||
} from "helpers/formatters";
|
|
||||||
import { ImageQuality } from "helpers/img";
|
import { ImageQuality } from "helpers/img";
|
||||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
@ -91,37 +82,25 @@ export const PreviewCard = ({
|
||||||
<div className="flex w-full flex-row flex-wrap gap-x-3">
|
<div className="flex w-full flex-row flex-wrap gap-x-3">
|
||||||
{metadata.releaseDate && (
|
{metadata.releaseDate && (
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
<Ico
|
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
|
||||||
icon={Icon.Event}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{prettyDate(metadata.releaseDate, router.locale)}
|
{prettyDate(metadata.releaseDate, router.locale)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{metadata.price && (
|
{metadata.price && (
|
||||||
<p className="justify-self-end text-sm">
|
<p className="justify-self-end text-sm">
|
||||||
<Ico
|
<Ico icon={Icon.ShoppingCart} className="mr-1 translate-y-[.15em] !text-base" />
|
||||||
icon={Icon.ShoppingCart}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{prettyPrice(metadata.price, currencies, currency)}
|
{prettyPrice(metadata.price, currencies, currency)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{metadata.views && (
|
{metadata.views && (
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
<Ico
|
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
|
||||||
icon={Icon.Visibility}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{prettyShortenNumber(metadata.views)}
|
{prettyShortenNumber(metadata.views)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{metadata.author && (
|
{metadata.author && (
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
<Ico
|
<Ico icon={Icon.Person} className="mr-1 translate-y-[.15em] !text-base" />
|
||||||
icon={Icon.Person}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{metadata.author}
|
{metadata.author}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
@ -136,8 +115,7 @@ export const PreviewCard = ({
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
className="group grid cursor-pointer items-end text-left transition-transform
|
className="group grid cursor-pointer items-end text-left transition-transform
|
||||||
drop-shadow-shade-xl hover:scale-[1.02]"
|
drop-shadow-shade-xl hover:scale-[1.02]">
|
||||||
>
|
|
||||||
{stackNumber > 0 && (
|
{stackNumber > 0 && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
@ -145,14 +123,9 @@ export const PreviewCard = ({
|
||||||
`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
|
`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
|
||||||
transition-[top_transform] group-hover:-top-9`,
|
transition-[top_transform] group-hover:-top-9`,
|
||||||
cIf(thumbnailRounded, "rounded-md")
|
cIf(thumbnailRounded, "rounded-md")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{thumbnail && (
|
{thumbnail && (
|
||||||
<Img
|
<Img className="opacity-30" src={thumbnail} quality={ImageQuality.Medium} />
|
||||||
className="opacity-30"
|
|
||||||
src={thumbnail}
|
|
||||||
quality={ImageQuality.Medium}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -161,14 +134,9 @@ export const PreviewCard = ({
|
||||||
`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
|
`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
|
||||||
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
|
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
|
||||||
cIf(thumbnailRounded, "rounded-md")
|
cIf(thumbnailRounded, "rounded-md")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{thumbnail && (
|
{thumbnail && (
|
||||||
<Img
|
<Img className="opacity-70" src={thumbnail} quality={ImageQuality.Medium} />
|
||||||
className="opacity-70"
|
|
||||||
src={thumbnail}
|
|
||||||
quality={ImageQuality.Medium}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -178,20 +146,13 @@ export const PreviewCard = ({
|
||||||
<div
|
<div
|
||||||
className="relative"
|
className="relative"
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: thumbnailForceAspectRatio
|
aspectRatio: thumbnailForceAspectRatio ? thumbnailAspectRatio : "unset",
|
||||||
? thumbnailAspectRatio
|
}}>
|
||||||
: "unset",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Img
|
<Img
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
cIf(
|
cIf(
|
||||||
thumbnailRounded,
|
thumbnailRounded,
|
||||||
cIf(
|
cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none")
|
||||||
keepInfoVisible,
|
|
||||||
"rounded-t-md",
|
|
||||||
"rounded-md notHoverable:rounded-b-none"
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
|
cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
|
||||||
)}
|
)}
|
||||||
|
@ -201,8 +162,7 @@ export const PreviewCard = ({
|
||||||
{stackNumber > 0 && (
|
{stackNumber > 0 && (
|
||||||
<div
|
<div
|
||||||
className="absolute right-2 top-2 rounded-full bg-black
|
className="absolute right-2 top-2 rounded-full bg-black
|
||||||
bg-opacity-60 px-2 text-light"
|
bg-opacity-60 px-2 text-light">
|
||||||
>
|
|
||||||
{stackNumber}
|
{stackNumber}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -210,8 +170,7 @@ export const PreviewCard = ({
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 grid place-content-center bg-shade bg-opacity-0
|
className="absolute inset-0 grid place-content-center bg-shade bg-opacity-0
|
||||||
text-light transition-colors drop-shadow-shade-lg group-hover:bg-opacity-50"
|
text-light transition-colors drop-shadow-shade-lg group-hover:bg-opacity-50">
|
||||||
>
|
|
||||||
<Ico
|
<Ico
|
||||||
icon={Icon.PlayCircleOutline}
|
icon={Icon.PlayCircleOutline}
|
||||||
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
|
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
|
@ -219,8 +178,7 @@ export const PreviewCard = ({
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="absolute right-2 bottom-2 rounded-full bg-black bg-opacity-60 px-2
|
className="absolute right-2 bottom-2 rounded-full bg-black bg-opacity-60 px-2
|
||||||
text-light"
|
text-light">
|
||||||
>
|
|
||||||
{prettyDuration(hoverlay.duration)}
|
{prettyDuration(hoverlay.duration)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -231,18 +189,12 @@ export const PreviewCard = ({
|
||||||
style={{ aspectRatio: thumbnailAspectRatio }}
|
style={{ aspectRatio: thumbnailAspectRatio }}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"relative w-full bg-light",
|
"relative w-full bg-light",
|
||||||
cIf(
|
cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none")
|
||||||
keepInfoVisible,
|
)}>
|
||||||
"rounded-t-md",
|
|
||||||
"rounded-md notHoverable:rounded-b-none"
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{stackNumber > 0 && (
|
{stackNumber > 0 && (
|
||||||
<div
|
<div
|
||||||
className="absolute right-2 top-2 rounded-full bg-black
|
className="absolute right-2 top-2 rounded-full bg-black
|
||||||
bg-opacity-60 px-2 text-light"
|
bg-opacity-60 px-2 text-light">
|
||||||
>
|
|
||||||
{stackNumber}
|
{stackNumber}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -258,8 +210,7 @@ export const PreviewCard = ({
|
||||||
notHoverable:rounded-b-md notHoverable:opacity-100`,
|
notHoverable:rounded-b-md notHoverable:opacity-100`,
|
||||||
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
|
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{metadata?.position === "Top" && metadataJSX}
|
{metadata?.position === "Top" && metadataJSX}
|
||||||
{topChips && topChips.length > 0 && (
|
{topChips && topChips.length > 0 && (
|
||||||
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
|
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
|
||||||
|
@ -269,13 +220,9 @@ export const PreviewCard = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
{pre_title && (
|
{pre_title && <p className="mb-1 break-words leading-none">{pre_title}</p>}
|
||||||
<p className="mb-1 break-words leading-none">{pre_title}</p>
|
|
||||||
)}
|
|
||||||
{title && (
|
{title && (
|
||||||
<p className="break-words font-headers text-lg font-bold leading-none">
|
<p className="break-words font-headers text-lg font-bold leading-none">{title}</p>
|
||||||
{title}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
{subtitle && <p className="break-words leading-none">{subtitle}</p>}
|
{subtitle && <p className="break-words leading-none">{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -283,8 +230,7 @@ export const PreviewCard = ({
|
||||||
{bottomChips && bottomChips.length > 0 && (
|
{bottomChips && bottomChips.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className="grid grid-flow-col place-content-start gap-1 overflow-x-scroll
|
className="grid grid-flow-col place-content-start gap-1 overflow-x-scroll
|
||||||
[scrollbar-width:none] webkit-scrollbar:h-0"
|
[scrollbar-width:none] webkit-scrollbar:h-0">
|
||||||
>
|
|
||||||
{bottomChips.map((text, index) => (
|
{bottomChips.map((text, index) => (
|
||||||
<Chip key={index} className="text-sm" text={text} />
|
<Chip key={index} className="text-sm" text={text} />
|
||||||
))}
|
))}
|
||||||
|
@ -308,16 +254,10 @@ export const TranslatedPreviewCard = ({
|
||||||
translations,
|
translations,
|
||||||
fallback,
|
fallback,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: TranslatedProps<
|
}: TranslatedProps<Props, "description" | "pre_title" | "subtitle" | "title">): JSX.Element => {
|
||||||
Props,
|
|
||||||
"description" | "pre_title" | "subtitle" | "title"
|
|
||||||
>): JSX.Element => {
|
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
|
|
|
@ -38,15 +38,10 @@ const PreviewLine = ({
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden
|
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden
|
||||||
rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
|
rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]">
|
||||||
>
|
|
||||||
{thumbnail ? (
|
{thumbnail ? (
|
||||||
<div className="aspect-[3/2] h-full">
|
<div className="aspect-[3/2] h-full">
|
||||||
<Img
|
<Img className="h-full object-cover" src={thumbnail} quality={ImageQuality.Medium} />
|
||||||
className="h-full object-cover"
|
|
||||||
src={thumbnail}
|
|
||||||
quality={ImageQuality.Medium}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
|
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
|
||||||
|
@ -61,9 +56,7 @@ const PreviewLine = ({
|
||||||
)}
|
)}
|
||||||
<div className="my-1 flex flex-col">
|
<div className="my-1 flex flex-col">
|
||||||
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
|
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
|
||||||
{title && (
|
{title && <p className="font-headers text-lg font-bold leading-none">{title}</p>}
|
||||||
<p className="font-headers text-lg font-bold leading-none">{title}</p>
|
|
||||||
)}
|
|
||||||
{subtitle && <p className="leading-none">{subtitle}</p>}
|
{subtitle && <p className="leading-none">{subtitle}</p>}
|
||||||
</div>
|
</div>
|
||||||
{bottomChips && bottomChips.length > 0 && (
|
{bottomChips && bottomChips.length > 0 && (
|
||||||
|
@ -89,10 +82,7 @@ export const TranslatedPreviewLine = ({
|
||||||
}: TranslatedProps<Props, "pre_title" | "subtitle" | "title">): JSX.Element => {
|
}: TranslatedProps<Props, "pre_title" | "subtitle" | "title">): JSX.Element => {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<PreviewLine
|
<PreviewLine
|
||||||
|
|
|
@ -40,13 +40,13 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
|
||||||
{recorder.languages?.data && recorder.languages.data.length > 0 && (
|
{recorder.languages?.data && recorder.languages.data.length > 0 && (
|
||||||
<div className="flex flex-row flex-wrap gap-1">
|
<div className="flex flex-row flex-wrap gap-1">
|
||||||
<p>{langui.languages}:</p>
|
<p>{langui.languages}:</p>
|
||||||
{filterHasAttributes(recorder.languages.data, [
|
{filterHasAttributes(recorder.languages.data, ["attributes"] as const).map(
|
||||||
"attributes",
|
(language) => (
|
||||||
] as const).map((language) => (
|
<Fragment key={language.__typename}>
|
||||||
<Fragment key={language.__typename}>
|
<Chip text={language.attributes.code.toUpperCase()} />
|
||||||
<Chip text={language.attributes.code.toUpperCase()} />
|
</Fragment>
|
||||||
</Fragment>
|
)
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{recorder.pronouns && (
|
{recorder.pronouns && (
|
||||||
|
@ -60,15 +60,10 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
|
||||||
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
|
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
placement="top"
|
placement="top">
|
||||||
>
|
|
||||||
<Chip
|
<Chip
|
||||||
key={recorder.anonymous_code}
|
key={recorder.anonymous_code}
|
||||||
text={
|
text={recorder.anonymize ? `Recorder#${recorder.anonymous_code}` : recorder.username}
|
||||||
recorder.anonymize
|
|
||||||
? `Recorder#${recorder.anonymous_code}`
|
|
||||||
: recorder.username
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
);
|
);
|
||||||
|
|
|
@ -72,10 +72,7 @@ export const SmartList = <T,>({
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
useScrollTopOnChange(Ids.ContentPanel, [page], paginationScroolTop);
|
useScrollTopOnChange(Ids.ContentPanel, [page], paginationScroolTop);
|
||||||
useEffect(
|
useEffect(() => setPage(0), [searchingTerm, groupingFunction, groupSortingFunction, items]);
|
||||||
() => setPage(0),
|
|
||||||
[searchingTerm, groupingFunction, groupSortingFunction, items]
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchFilter = useCallback(() => {
|
const searchFilter = useCallback(() => {
|
||||||
if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) {
|
if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) {
|
||||||
|
@ -118,12 +115,7 @@ export const SmartList = <T,>({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return memo.sort(groupSortingFunction);
|
return memo.sort(groupSortingFunction);
|
||||||
}, [
|
}, [groupCountingFunction, groupSortingFunction, groupingFunction, sortedItem]);
|
||||||
groupCountingFunction,
|
|
||||||
groupSortingFunction,
|
|
||||||
groupingFunction,
|
|
||||||
sortedItem,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const pages = useMemo(() => {
|
const pages = useMemo(() => {
|
||||||
const memo: Group<T>[][] = [];
|
const memo: Group<T>[][] = [];
|
||||||
|
@ -174,12 +166,7 @@ export const SmartList = <T,>({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{pages.length > 1 && paginationSelectorTop && (
|
{pages.length > 1 && paginationSelectorTop && (
|
||||||
<PageSelector
|
<PageSelector className="mb-12" page={page} pagesCount={pages.length} onChange={setPage} />
|
||||||
className="mb-12"
|
|
||||||
page={page}
|
|
||||||
pagesCount={pages.length}
|
|
||||||
onChange={setPage}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
|
@ -191,8 +178,7 @@ export const SmartList = <T,>({
|
||||||
{group.name.length > 0 && (
|
{group.name.length > 0 && (
|
||||||
<h2
|
<h2
|
||||||
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
||||||
first-of-type:pt-0"
|
first-of-type:pt-0">
|
||||||
>
|
|
||||||
{group.name}
|
{group.name}
|
||||||
<Chip
|
<Chip
|
||||||
text={`${group.totalCount} ${
|
text={`${group.totalCount} ${
|
||||||
|
@ -208,8 +194,7 @@ export const SmartList = <T,>({
|
||||||
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
|
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
|
||||||
last-of-type:border-0`,
|
last-of-type:border-0`,
|
||||||
className
|
className
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{group.items.map((item) => (
|
{group.items.map((item) => (
|
||||||
<RenderItem item={item} key={getItemId(item)} />
|
<RenderItem item={item} key={getItemId(item)} />
|
||||||
))}
|
))}
|
||||||
|
@ -225,12 +210,7 @@ export const SmartList = <T,>({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{pages.length > 1 && paginationSelectorBottom && (
|
{pages.length > 1 && paginationSelectorBottom && (
|
||||||
<PageSelector
|
<PageSelector className="mb-12" page={page} pagesCount={pages.length} onChange={setPage} />
|
||||||
className="mb-12"
|
|
||||||
page={page}
|
|
||||||
pagesCount={pages.length}
|
|
||||||
onChange={setPage}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -248,15 +228,10 @@ const DefaultRenderWhenEmpty = () => {
|
||||||
<div className="grid h-full place-content-center">
|
<div className="grid h-full place-content-center">
|
||||||
<div
|
<div
|
||||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||||
border-dark p-8 text-dark opacity-40"
|
border-dark p-8 text-dark opacity-40">
|
||||||
>
|
{is3ColumnsLayout && <Ico icon={Icon.ChevronLeft} className="!text-[300%]" />}
|
||||||
{is3ColumnsLayout && (
|
|
||||||
<Ico icon={Icon.ChevronLeft} className="!text-[300%]" />
|
|
||||||
)}
|
|
||||||
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
|
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
|
||||||
{!is3ColumnsLayout && (
|
{!is3ColumnsLayout && <Ico icon={Icon.ChevronRight} className="!text-[300%]" />}
|
||||||
<Ico icon={Icon.ChevronRight} className="!text-[300%]" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -64,11 +64,8 @@ export const ThumbnailHeader = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id={slugify(
|
id={slugify(prettyInlineTitle(pre_title ?? "", title, subtitle ?? ""))}
|
||||||
prettyInlineTitle(pre_title ?? "", title, subtitle ?? "")
|
className="grid place-items-center text-center">
|
||||||
)}
|
|
||||||
className="grid place-items-center text-center"
|
|
||||||
>
|
|
||||||
<p className="text-2xl">{pre_title}</p>
|
<p className="text-2xl">{pre_title}</p>
|
||||||
<h1 className="text-3xl">{title}</h1>
|
<h1 className="text-3xl">{title}</h1>
|
||||||
<h2 className="text-2xl">{subtitle}</h2>
|
<h2 className="text-2xl">{subtitle}</h2>
|
||||||
|
@ -82,8 +79,7 @@ export const ThumbnailHeader = ({
|
||||||
<div className="flex flex-row flex-wrap">
|
<div className="flex flex-row flex-wrap">
|
||||||
<Chip
|
<Chip
|
||||||
text={
|
text={
|
||||||
type.data.attributes.titles?.[0]?.title ??
|
type.data.attributes.titles?.[0]?.title ?? prettySlug(type.data.attributes.slug)
|
||||||
prettySlug(type.data.attributes.slug)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,20 +90,17 @@ export const ThumbnailHeader = ({
|
||||||
<div className="flex flex-col place-items-center gap-2">
|
<div className="flex flex-col place-items-center gap-2">
|
||||||
<h3 className="text-xl">{langui.categories}</h3>
|
<h3 className="text-xl">{langui.categories}</h3>
|
||||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||||
{filterHasAttributes(categories.data, [
|
{filterHasAttributes(categories.data, ["attributes", "id"] as const).map(
|
||||||
"attributes",
|
(category) => (
|
||||||
"id",
|
<Chip key={category.id} text={category.attributes.name} />
|
||||||
] as const).map((category) => (
|
)
|
||||||
<Chip key={category.id} text={category.attributes.name} />
|
)}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{languageSwitcher}
|
{languageSwitcher}
|
||||||
</div>
|
</div>
|
||||||
{description && (
|
{description && <InsetBox className="mt-8">{<Markdawn text={description} />}</InsetBox>}
|
||||||
<InsetBox className="mt-8">{<Markdawn text={description} />}</InsetBox>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,8 +25,7 @@ export const ToolTip = ({
|
||||||
delay={delay}
|
delay={delay}
|
||||||
interactive={interactive}
|
interactive={interactive}
|
||||||
animation={animation}
|
animation={animation}
|
||||||
{...otherProps}
|
{...otherProps}>
|
||||||
>
|
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
</Tippy>
|
</Tippy>
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,22 +29,13 @@ interface Props {
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
const DefinitionCard = ({
|
const DefinitionCard = ({ source, translations = [], index, categories }: Props): JSX.Element => {
|
||||||
source,
|
|
||||||
translations = [],
|
|
||||||
index,
|
|
||||||
categories,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const isContentPanelNoMoreThanMd = useIsContentPanelNoMoreThan("md");
|
const isContentPanelNoMoreThanMd = useIsContentPanelNoMoreThan("md");
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
useSmartLanguage({
|
items: translations,
|
||||||
items: translations,
|
languageExtractor: useCallback((item: Props["translations"][number]) => item.language, []),
|
||||||
languageExtractor: useCallback(
|
});
|
||||||
(item: Props["translations"][number]) => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -63,8 +54,7 @@ const DefinitionCard = ({
|
||||||
<Separator />
|
<Separator />
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={getStatusDescription(selectedTranslation.status, langui)}
|
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||||
maxWidth={"20rem"}
|
maxWidth={"20rem"}>
|
||||||
>
|
|
||||||
<Chip text={selectedTranslation.status} />
|
<Chip text={selectedTranslation.status} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</>
|
</>
|
||||||
|
@ -89,8 +79,7 @@ const DefinitionCard = ({
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"mt-3 flex place-items-center gap-2",
|
"mt-3 flex place-items-center gap-2",
|
||||||
cIf(isContentPanelNoMoreThanMd, "flex-col text-center")
|
cIf(isContentPanelNoMoreThanMd, "flex-col text-center")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<p>{langui.source}: </p>
|
<p>{langui.source}: </p>
|
||||||
<Button href={source.url} size="small" text={source.name} />
|
<Button href={source.url} size="small" text={source.name} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
import React, {
|
import React, { ReactNode, useContext, useEffect, useLayoutEffect, useState } from "react";
|
||||||
ReactNode,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useLayoutEffect,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useLocalStorage } from "usehooks-ts";
|
import { useLocalStorage } from "usehooks-ts";
|
||||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types";
|
import { LibraryItemUserStatus, RequiredNonNullable } from "types/types";
|
||||||
import { useDarkMode } from "hooks/useDarkMode";
|
import { useDarkMode } from "hooks/useDarkMode";
|
||||||
import { Currencies, Languages, Langui } from "helpers/localData";
|
import { Currencies, Languages, Langui } from "helpers/localData";
|
||||||
import { useCurrencies, useLanguages, useLangui } from "hooks/useLocalData";
|
import { useCurrencies, useLanguages, useLangui } from "hooks/useLocalData";
|
||||||
|
@ -19,27 +13,19 @@ import { useScrollIntoView } from "hooks/useScrollIntoView";
|
||||||
interface AppLayoutState {
|
interface AppLayoutState {
|
||||||
subPanelOpen: boolean;
|
subPanelOpen: boolean;
|
||||||
toggleSubPanelOpen: () => void;
|
toggleSubPanelOpen: () => void;
|
||||||
setSubPanelOpen: React.Dispatch<
|
setSubPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["subPanelOpen"]>>;
|
||||||
React.SetStateAction<AppLayoutState["subPanelOpen"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
configPanelOpen: boolean;
|
configPanelOpen: boolean;
|
||||||
toggleConfigPanelOpen: () => void;
|
toggleConfigPanelOpen: () => void;
|
||||||
setConfigPanelOpen: React.Dispatch<
|
setConfigPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["configPanelOpen"]>>;
|
||||||
React.SetStateAction<AppLayoutState["configPanelOpen"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
mainPanelReduced: boolean;
|
mainPanelReduced: boolean;
|
||||||
toggleMainPanelReduced: () => void;
|
toggleMainPanelReduced: () => void;
|
||||||
setMainPanelReduced: React.Dispatch<
|
setMainPanelReduced: React.Dispatch<React.SetStateAction<AppLayoutState["mainPanelReduced"]>>;
|
||||||
React.SetStateAction<AppLayoutState["mainPanelReduced"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
mainPanelOpen: boolean;
|
mainPanelOpen: boolean;
|
||||||
toggleMainPanelOpen: () => void;
|
toggleMainPanelOpen: () => void;
|
||||||
setMainPanelOpen: React.Dispatch<
|
setMainPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["mainPanelOpen"]>>;
|
||||||
React.SetStateAction<AppLayoutState["mainPanelOpen"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
toggleDarkMode: () => void;
|
toggleDarkMode: () => void;
|
||||||
|
@ -47,9 +33,7 @@ interface AppLayoutState {
|
||||||
|
|
||||||
selectedThemeMode: boolean;
|
selectedThemeMode: boolean;
|
||||||
toggleSelectedThemeMode: () => void;
|
toggleSelectedThemeMode: () => void;
|
||||||
setSelectedThemeMode: React.Dispatch<
|
setSelectedThemeMode: React.Dispatch<React.SetStateAction<AppLayoutState["selectedThemeMode"]>>;
|
||||||
React.SetStateAction<AppLayoutState["selectedThemeMode"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
|
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
|
||||||
|
@ -62,20 +46,14 @@ interface AppLayoutState {
|
||||||
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
|
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
|
||||||
|
|
||||||
playerName: string;
|
playerName: string;
|
||||||
setPlayerName: React.Dispatch<
|
setPlayerName: React.Dispatch<React.SetStateAction<AppLayoutState["playerName"]>>;
|
||||||
React.SetStateAction<AppLayoutState["playerName"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
preferredLanguages: string[];
|
preferredLanguages: string[];
|
||||||
setPreferredLanguages: React.Dispatch<
|
setPreferredLanguages: React.Dispatch<React.SetStateAction<AppLayoutState["preferredLanguages"]>>;
|
||||||
React.SetStateAction<AppLayoutState["preferredLanguages"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
menuGestures: boolean;
|
menuGestures: boolean;
|
||||||
toggleMenuGestures: () => void;
|
toggleMenuGestures: () => void;
|
||||||
setMenuGestures: React.Dispatch<
|
setMenuGestures: React.Dispatch<React.SetStateAction<AppLayoutState["menuGestures"]>>;
|
||||||
React.SetStateAction<AppLayoutState["menuGestures"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
libraryItemUserStatus: Record<string, LibraryItemUserStatus>;
|
libraryItemUserStatus: Record<string, LibraryItemUserStatus>;
|
||||||
setLibraryItemUserStatus: React.Dispatch<
|
setLibraryItemUserStatus: React.Dispatch<
|
||||||
|
@ -83,19 +61,13 @@ interface AppLayoutState {
|
||||||
>;
|
>;
|
||||||
|
|
||||||
screenWidth: number;
|
screenWidth: number;
|
||||||
setScreenWidth: React.Dispatch<
|
setScreenWidth: React.Dispatch<React.SetStateAction<AppLayoutState["screenWidth"]>>;
|
||||||
React.SetStateAction<AppLayoutState["screenWidth"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
contentPanelWidth: number;
|
contentPanelWidth: number;
|
||||||
setContentPanelWidth: React.Dispatch<
|
setContentPanelWidth: React.Dispatch<React.SetStateAction<AppLayoutState["contentPanelWidth"]>>;
|
||||||
React.SetStateAction<AppLayoutState["contentPanelWidth"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
subPanelWidth: number;
|
subPanelWidth: number;
|
||||||
setSubPanelWidth: React.Dispatch<
|
setSubPanelWidth: React.Dispatch<React.SetStateAction<AppLayoutState["subPanelWidth"]>>;
|
||||||
React.SetStateAction<AppLayoutState["subPanelWidth"]>
|
|
||||||
>;
|
|
||||||
|
|
||||||
langui: Langui;
|
langui: Langui;
|
||||||
languages: Languages;
|
languages: Languages;
|
||||||
|
@ -195,28 +167,18 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
||||||
initialState.mainPanelOpen
|
initialState.mainPanelOpen
|
||||||
);
|
);
|
||||||
|
|
||||||
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] =
|
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] = useDarkMode(
|
||||||
useDarkMode("darkMode", initialState.darkMode);
|
"darkMode",
|
||||||
|
initialState.darkMode
|
||||||
const [fontSize, setFontSize] = useLocalStorage(
|
|
||||||
"fontSize",
|
|
||||||
initialState.fontSize
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const [dyslexic, setDyslexic] = useLocalStorage(
|
const [fontSize, setFontSize] = useLocalStorage("fontSize", initialState.fontSize);
|
||||||
"dyslexic",
|
|
||||||
initialState.dyslexic
|
|
||||||
);
|
|
||||||
|
|
||||||
const [currency, setCurrency] = useLocalStorage(
|
const [dyslexic, setDyslexic] = useLocalStorage("dyslexic", initialState.dyslexic);
|
||||||
"currency",
|
|
||||||
initialState.currency
|
|
||||||
);
|
|
||||||
|
|
||||||
const [playerName, setPlayerName] = useLocalStorage(
|
const [currency, setCurrency] = useLocalStorage("currency", initialState.currency);
|
||||||
"playerName",
|
|
||||||
initialState.playerName
|
const [playerName, setPlayerName] = useLocalStorage("playerName", initialState.playerName);
|
||||||
);
|
|
||||||
|
|
||||||
const [preferredLanguages, setPreferredLanguages] = useLocalStorage(
|
const [preferredLanguages, setPreferredLanguages] = useLocalStorage(
|
||||||
"preferredLanguages",
|
"preferredLanguages",
|
||||||
|
@ -255,9 +217,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleSelectedThemeMode = () => {
|
const toggleSelectedThemeMode = () => {
|
||||||
setSelectedThemeMode((current) =>
|
setSelectedThemeMode((current) => (isDefined(current) ? !current : current));
|
||||||
isDefined(current) ? !current : current
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDyslexic = () => {
|
const toggleDyslexic = () => {
|
||||||
|
@ -275,9 +235,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (preferredLanguages.length === 0) {
|
if (preferredLanguages.length === 0) {
|
||||||
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
|
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
|
||||||
setPreferredLanguages(
|
setPreferredLanguages(getDefaultPreferredLanguages(router.locale, router.locales));
|
||||||
getDefaultPreferredLanguages(router.locale, router.locales)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (router.locale !== preferredLanguages[0]) {
|
} else if (router.locale !== preferredLanguages[0]) {
|
||||||
/*
|
/*
|
||||||
|
@ -292,13 +250,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
||||||
250
|
250
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [preferredLanguages, router, router.locale, router.locales, setPreferredLanguages]);
|
||||||
preferredLanguages,
|
|
||||||
router,
|
|
||||||
router.locale,
|
|
||||||
router.locales,
|
|
||||||
setPreferredLanguages,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
router.events.on("routeChangeStart", () => {
|
router.events.on("routeChangeStart", () => {
|
||||||
|
@ -315,9 +267,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
||||||
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
|
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
document.getElementsByTagName("html")[0].style.fontSize = `${
|
document.getElementsByTagName("html")[0].style.fontSize = `${fontSize * 100}%`;
|
||||||
fontSize * 100
|
|
||||||
}%`;
|
|
||||||
}, [fontSize]);
|
}, [fontSize]);
|
||||||
|
|
||||||
useScrollIntoView();
|
useScrollIntoView();
|
||||||
|
@ -368,8 +318,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
||||||
languages,
|
languages,
|
||||||
langui,
|
langui,
|
||||||
currencies,
|
currencies,
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{props.children}
|
{props.children}
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,7 +13,7 @@ const LOCAL_DATA_FOLDER = `${process.cwd()}/public/local-data`;
|
||||||
const writeLocalData = (name: LocalDataFile, localData: unknown) => {
|
const writeLocalData = (name: LocalDataFile, localData: unknown) => {
|
||||||
const path = `${LOCAL_DATA_FOLDER}/${name}.json`;
|
const path = `${LOCAL_DATA_FOLDER}/${name}.json`;
|
||||||
writeFileSync(path, JSON.stringify(localData), { encoding: "utf-8" });
|
writeFileSync(path, JSON.stringify(localData), { encoding: "utf-8" });
|
||||||
console.log(`${path} has been written!`)
|
console.log(`${path} has been written!`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const readLocalData = <T>(name: LocalDataFile): T => {
|
const readLocalData = <T>(name: LocalDataFile): T => {
|
||||||
|
@ -24,10 +24,7 @@ const readLocalData = <T>(name: LocalDataFile): T => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
writeLocalData(
|
writeLocalData("websiteInterfaces", await sdk.localDataGetWebsiteInterfaces());
|
||||||
"websiteInterfaces",
|
|
||||||
await sdk.localDataGetWebsiteInterfaces()
|
|
||||||
);
|
|
||||||
writeLocalData("currencies", await sdk.localDataGetCurrencies());
|
writeLocalData("currencies", await sdk.localDataGetCurrencies());
|
||||||
writeLocalData("languages", await sdk.localDataGetLanguages());
|
writeLocalData("languages", await sdk.localDataGetLanguages());
|
||||||
})();
|
})();
|
||||||
|
@ -37,7 +34,6 @@ const sdk = getReadySdk();
|
||||||
export type LocalDataFile = "currencies" | "languages" | "websiteInterfaces";
|
export type LocalDataFile = "currencies" | "languages" | "websiteInterfaces";
|
||||||
|
|
||||||
export const getLangui = (locale: string | undefined): Langui => {
|
export const getLangui = (locale: string | undefined): Langui => {
|
||||||
const websiteInterfaces =
|
const websiteInterfaces = readLocalData<LocalDataGetWebsiteInterfacesQuery>("websiteInterfaces");
|
||||||
readLocalData<LocalDataGetWebsiteInterfacesQuery>("websiteInterfaces");
|
|
||||||
return processLangui(websiteInterfaces, locale);
|
return processLangui(websiteInterfaces, locale);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import { GetStaticProps } from "next";
|
import { GetStaticProps } from "next";
|
||||||
import { getReadySdk } from "./sdk";
|
import { getReadySdk } from "./sdk";
|
||||||
import { getLangui } from "./fetchLocalData";
|
import { getLangui } from "./fetchLocalData";
|
||||||
import { PostWithTranslations } from "helpers/types";
|
import { PostWithTranslations } from "types/types";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import { prettyDate, prettySlug } from "helpers/formatters";
|
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||||
import {
|
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||||
getDefaultPreferredLanguages,
|
|
||||||
staticSmartLanguage,
|
|
||||||
} from "helpers/locales";
|
|
||||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||||
import { getDescription } from "helpers/description";
|
import { getDescription } from "helpers/description";
|
||||||
import { AppLayoutRequired } from "components/AppLayout";
|
import { AppLayoutRequired } from "components/AppLayout";
|
||||||
|
@ -35,10 +32,7 @@ export const getPostStaticProps =
|
||||||
const selectedTranslation = staticSmartLanguage({
|
const selectedTranslation = staticSmartLanguage({
|
||||||
items: post.posts.data[0].attributes.translations,
|
items: post.posts.data[0].attributes.translations,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
preferredLanguages: getDefaultPreferredLanguages(
|
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||||
context.locale,
|
|
||||||
context.locales
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = selectedTranslation?.title ?? prettySlug(slug);
|
const title = selectedTranslation?.title ?? prettySlug(slug);
|
||||||
|
|
|
@ -91,9 +91,7 @@ query getChronicle($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
query getChronologyItems {
|
query getChronologyItems {
|
||||||
chronologyItems(
|
chronologyItems(pagination: { limit: -1 }, sort: ["year:asc", "month:asc", "day:asc"]) {
|
||||||
pagination: { limit: -1 }
|
|
||||||
sort: ["year:asc", "month:asc", "day:asc"]
|
|
||||||
) {
|
|
||||||
data {
|
data {
|
||||||
id
|
id
|
||||||
attributes {
|
attributes {
|
||||||
|
|
|
@ -67,11 +67,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,11 +89,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,11 +101,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,11 +113,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,11 +123,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,11 +237,7 @@ query getContentText($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,7 @@ query getContentsFolder($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,9 +80,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,9 +126,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,9 +138,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,9 +148,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,11 +202,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,11 +224,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,11 +236,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,11 +248,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,11 +258,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,11 +322,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: {
|
|
||||||
language: { code: { eq: $language_code } }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,9 +124,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,9 +146,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,9 +158,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,9 +170,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,9 +180,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,7 @@ query getLibraryItemsPreview($language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,9 +59,7 @@ query getLibraryItemsPreview($language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,9 +71,7 @@ query getLibraryItemsPreview($language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,9 +83,7 @@ query getLibraryItemsPreview($language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,9 +93,7 @@ query getLibraryItemsPreview($language_code: String) {
|
||||||
data {
|
data {
|
||||||
attributes {
|
attributes {
|
||||||
slug
|
slug
|
||||||
titles(
|
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||||
filters: { language: { code: { eq: $language_code } } }
|
|
||||||
) {
|
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,9 @@ query localDataGetWebsiteInterfaces {
|
||||||
anchor_link_copied
|
anchor_link_copied
|
||||||
folders
|
folders
|
||||||
empty_folder_message
|
empty_folder_message
|
||||||
|
switch_to_grid_view
|
||||||
|
switch_to_folder_view
|
||||||
|
content_is_not_available
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,4 @@ export const ConditionalWrapper = <T,>({
|
||||||
wrapper: Wrapper,
|
wrapper: Wrapper,
|
||||||
wrapperProps,
|
wrapperProps,
|
||||||
}: ConditionalWrapperProps<T>): JSX.Element =>
|
}: ConditionalWrapperProps<T>): JSX.Element =>
|
||||||
isWrapping ? (
|
isWrapping ? <Wrapper {...wrapperProps}>{children}</Wrapper> : <>{children}</>;
|
||||||
<Wrapper {...wrapperProps}>{children}</Wrapper>
|
|
||||||
) : (
|
|
||||||
<>{children}</>
|
|
||||||
);
|
|
||||||
|
|
|
@ -8,10 +8,8 @@ export const compareDate = (
|
||||||
if (isUndefined(a) || isUndefined(b)) {
|
if (isUndefined(a) || isUndefined(b)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const dateA =
|
const dateA = (a.year ?? Infinity) * 365 + (a.month ?? 12) * 31 + (a.day ?? 31);
|
||||||
(a.year ?? Infinity) * 365 + (a.month ?? 12) * 31 + (a.day ?? 31);
|
const dateB = (b.year ?? Infinity) * 365 + (b.month ?? 12) * 31 + (b.day ?? 31);
|
||||||
const dateB =
|
|
||||||
(b.year ?? Infinity) * 365 + (b.month ?? 12) * 31 + (b.day ?? 31);
|
|
||||||
return dateA - dateB;
|
return dateA - dateB;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const prettyInlineTitle = (
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const prettyItemType = (metadata: any, langui: Langui): string => {
|
export const prettyItemType = (metadata: { __typename: string }, langui: Langui): string => {
|
||||||
switch (metadata.__typename) {
|
switch (metadata.__typename) {
|
||||||
case "ComponentMetadataAudio":
|
case "ComponentMetadataAudio":
|
||||||
return langui.audio ?? "Audio";
|
return langui.audio ?? "Audio";
|
||||||
|
@ -242,8 +242,7 @@ export const prettyDuration = (seconds: number): string => {
|
||||||
export const prettyLanguage = (code: string, languages: Languages): string => {
|
export const prettyLanguage = (code: string, languages: Languages): string => {
|
||||||
let result = code;
|
let result = code;
|
||||||
languages.forEach((language) => {
|
languages.forEach((language) => {
|
||||||
if (language.attributes?.code === code)
|
if (language.attributes?.code === code) result = language.attributes.localized_name;
|
||||||
result = language.attributes.localized_name;
|
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
@ -254,8 +253,7 @@ export const prettyURL = (url: string): string => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const capitalizeString = (string: string): string => {
|
const capitalizeString = (string: string): string => {
|
||||||
const capitalizeWord = (word: string): string =>
|
const capitalizeWord = (word: string): string => word.charAt(0).toUpperCase() + word.substring(1);
|
||||||
word.charAt(0).toUpperCase() + word.substring(1);
|
|
||||||
|
|
||||||
let words = string.split(" ");
|
let words = string.split(" ");
|
||||||
words = words.map((word) => capitalizeWord(word));
|
words = words.map((word) => capitalizeWord(word));
|
||||||
|
@ -281,3 +279,5 @@ export const slugify = (string: string | undefined): string => {
|
||||||
.trim()
|
.trim()
|
||||||
.replace(/ /gu, "-");
|
.replace(/ /gu, "-");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sJoin = (...args: (string | null | undefined)[]): string => args.join("");
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { sJoin } from "./formatters";
|
||||||
|
|
||||||
export enum ImageQuality {
|
export enum ImageQuality {
|
||||||
Small = "small",
|
Small = "small",
|
||||||
Medium = "medium",
|
Medium = "medium",
|
||||||
|
@ -17,7 +19,7 @@ export interface OgImage {
|
||||||
alt: string;
|
alt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
|
const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
|
||||||
small: { maxSize: 512, extension: "webp" },
|
small: { maxSize: 512, extension: "webp" },
|
||||||
medium: { maxSize: 1024, extension: "webp" },
|
medium: { maxSize: 1024, extension: "webp" },
|
||||||
large: { maxSize: 2048, extension: "webp" },
|
large: { maxSize: 2048, extension: "webp" },
|
||||||
|
@ -37,11 +39,16 @@ export const getAssetFilename = (path: string): string => {
|
||||||
export const getAssetURL = (url: string, quality: ImageQuality): string => {
|
export const getAssetURL = (url: string, quality: ImageQuality): string => {
|
||||||
const indexEndPath = url.indexOf("/uploads/") + "/uploads/".length;
|
const indexEndPath = url.indexOf("/uploads/") + "/uploads/".length;
|
||||||
const indexStartExtension = url.lastIndexOf(".");
|
const indexStartExtension = url.lastIndexOf(".");
|
||||||
const assetPathWithoutExtension = url.slice(
|
const assetPathWithoutExtension = url.slice(indexEndPath, indexStartExtension);
|
||||||
indexEndPath,
|
return sJoin(
|
||||||
indexStartExtension
|
process.env.NEXT_PUBLIC_URL_IMG,
|
||||||
|
"/",
|
||||||
|
quality,
|
||||||
|
"/",
|
||||||
|
assetPathWithoutExtension,
|
||||||
|
".",
|
||||||
|
imageQualityProperties[quality].extension
|
||||||
);
|
);
|
||||||
return `${process.env.NEXT_PUBLIC_URL_IMG}/${quality}/${assetPathWithoutExtension}.${imageQualityProperties[quality].extension}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImgSizesByMaxSize = (
|
const getImgSizesByMaxSize = (
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
export const isUntangibleGroupItem = (metadata: any): boolean =>
|
import { isDefined } from "./others";
|
||||||
metadata &&
|
|
||||||
|
export const isUntangibleGroupItem = (
|
||||||
|
metadata:
|
||||||
|
| {
|
||||||
|
__typename: string;
|
||||||
|
// eslint-disable-next-line id-denylist
|
||||||
|
subtype?: { data?: { attributes?: { slug: string } | null } | null } | null;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
|
): boolean =>
|
||||||
|
isDefined(metadata) &&
|
||||||
metadata.__typename === "ComponentMetadataGroup" &&
|
metadata.__typename === "ComponentMetadataGroup" &&
|
||||||
(metadata.subtype?.data?.attributes?.slug === "variant-set" ||
|
(metadata.subtype?.data?.attributes?.slug === "variant-set" ||
|
||||||
metadata.subtype?.data?.attributes?.slug === "relation-set");
|
metadata.subtype?.data?.attributes?.slug === "relation-set");
|
||||||
|
|
|
@ -5,9 +5,7 @@ import {
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
|
|
||||||
export type Langui = NonNullable<
|
export type Langui = NonNullable<
|
||||||
NonNullable<
|
NonNullable<LocalDataGetWebsiteInterfacesQuery["websiteInterfaces"]>["data"][number]["attributes"]
|
||||||
LocalDataGetWebsiteInterfacesQuery["websiteInterfaces"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const processLangui = (
|
export const processLangui = (
|
||||||
|
@ -15,24 +13,19 @@ export const processLangui = (
|
||||||
locale: string | undefined
|
locale: string | undefined
|
||||||
): Langui =>
|
): Langui =>
|
||||||
websiteInterfaces?.websiteInterfaces?.data.find(
|
websiteInterfaces?.websiteInterfaces?.data.find(
|
||||||
(langui) =>
|
(langui) => langui.attributes?.ui_language?.data?.attributes?.code === locale
|
||||||
langui.attributes?.ui_language?.data?.attributes?.code === locale
|
|
||||||
)?.attributes ?? {};
|
)?.attributes ?? {};
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export type Currencies = NonNullable<
|
export type Currencies = NonNullable<LocalDataGetCurrenciesQuery["currencies"]>["data"];
|
||||||
LocalDataGetCurrenciesQuery["currencies"]
|
|
||||||
>["data"];
|
|
||||||
|
|
||||||
export const processCurrencies = (
|
export const processCurrencies = (
|
||||||
currencies: LocalDataGetCurrenciesQuery | undefined
|
currencies: LocalDataGetCurrenciesQuery | undefined
|
||||||
): Currencies => {
|
): Currencies => {
|
||||||
if (currencies?.currencies?.data) {
|
if (currencies?.currencies?.data) {
|
||||||
currencies.currencies.data.sort((a, b) =>
|
currencies.currencies.data.sort((a, b) =>
|
||||||
a.attributes && b.attributes
|
a.attributes && b.attributes ? a.attributes.code.localeCompare(b.attributes.code) : 0
|
||||||
? a.attributes.code.localeCompare(b.attributes.code)
|
|
||||||
: 0
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return currencies?.currencies?.data ?? [];
|
return currencies?.currencies?.data ?? [];
|
||||||
|
@ -40,13 +33,9 @@ export const processCurrencies = (
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
|
||||||
export type Languages = NonNullable<
|
export type Languages = NonNullable<LocalDataGetLanguagesQuery["languages"]>["data"];
|
||||||
LocalDataGetLanguagesQuery["languages"]
|
|
||||||
>["data"];
|
|
||||||
|
|
||||||
export const processLanguages = (
|
export const processLanguages = (languages: LocalDataGetLanguagesQuery | undefined): Languages => {
|
||||||
languages: LocalDataGetLanguagesQuery | undefined
|
|
||||||
): Languages => {
|
|
||||||
if (languages?.languages?.data) {
|
if (languages?.languages?.data) {
|
||||||
languages.languages.data.sort((a, b) =>
|
languages.languages.data.sort((a, b) =>
|
||||||
a.attributes && b.attributes
|
a.attributes && b.attributes
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { isDefined } from "./others";
|
import { isDefined } from "./others";
|
||||||
|
|
||||||
export const getDefaultPreferredLanguages = (
|
export const getDefaultPreferredLanguages = (routerLocal: string, locales: string[]): string[] => {
|
||||||
routerLocal: string,
|
|
||||||
locales: string[]
|
|
||||||
): string[] => {
|
|
||||||
let defaultPreferredLanguages: string[] = [];
|
let defaultPreferredLanguages: string[] = [];
|
||||||
if (routerLocal === "en") {
|
if (routerLocal === "en") {
|
||||||
defaultPreferredLanguages = [routerLocal];
|
defaultPreferredLanguages = [routerLocal];
|
||||||
|
@ -13,8 +10,7 @@ export const getDefaultPreferredLanguages = (
|
||||||
} else {
|
} else {
|
||||||
defaultPreferredLanguages = [routerLocal, "en"];
|
defaultPreferredLanguages = [routerLocal, "en"];
|
||||||
locales.map((locale) => {
|
locales.map((locale) => {
|
||||||
if (locale !== routerLocal && locale !== "en")
|
if (locale !== routerLocal && locale !== "en") defaultPreferredLanguages.push(locale);
|
||||||
defaultPreferredLanguages.push(locale);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return defaultPreferredLanguages;
|
return defaultPreferredLanguages;
|
||||||
|
|
|
@ -5,11 +5,7 @@ export const convertPrice = (
|
||||||
pricePicker: PricePickerFragment,
|
pricePicker: PricePickerFragment,
|
||||||
targetCurrency: Currencies[number]
|
targetCurrency: Currencies[number]
|
||||||
): number => {
|
): number => {
|
||||||
if (
|
if (pricePicker.amount && pricePicker.currency?.data?.attributes && targetCurrency.attributes)
|
||||||
pricePicker.amount &&
|
|
||||||
pricePicker.currency?.data?.attributes &&
|
|
||||||
targetCurrency.attributes
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) /
|
(pricePicker.amount * pricePicker.currency.data.attributes.rate_to_usd) /
|
||||||
targetCurrency.attributes.rate_to_usd
|
targetCurrency.attributes.rate_to_usd
|
||||||
|
@ -23,5 +19,4 @@ export const convertMmToInch = (mm: number | null | undefined): string =>
|
||||||
export const randomInt = (min: number, max: number): number =>
|
export const randomInt = (min: number, max: number): number =>
|
||||||
Math.floor(Math.random() * (max - min)) + min;
|
Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
|
||||||
export const isInteger = (value: string): boolean =>
|
export const isInteger = (value: string): boolean => /^[+-]?[0-9]+$/u.test(value);
|
||||||
/^[+-]?[0-9]+$/u.test(value);
|
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { OgImage, getImgSizesByQuality, ImageQuality, getAssetURL } from "./img";
|
||||||
OgImage,
|
|
||||||
getImgSizesByQuality,
|
|
||||||
ImageQuality,
|
|
||||||
getAssetURL,
|
|
||||||
} from "./img";
|
|
||||||
import { isDefinedAndNotEmpty } from "./others";
|
import { isDefinedAndNotEmpty } from "./others";
|
||||||
import { Langui } from "./localData";
|
import { Langui } from "./localData";
|
||||||
import { UploadImageFragment } from "graphql/generated";
|
import { UploadImageFragment } from "graphql/generated";
|
||||||
|
@ -30,21 +25,13 @@ export const getOpenGraph = (
|
||||||
description?: string | null | undefined,
|
description?: string | null | undefined,
|
||||||
thumbnail?: UploadImageFragment | null | undefined
|
thumbnail?: UploadImageFragment | null | undefined
|
||||||
): OpenGraph => ({
|
): OpenGraph => ({
|
||||||
title: `${TITLE_PREFIX}${
|
title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""}`,
|
||||||
isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""
|
description: isDefinedAndNotEmpty(description) ? description : langui.default_description ?? "",
|
||||||
}`,
|
|
||||||
description: isDefinedAndNotEmpty(description)
|
|
||||||
? description
|
|
||||||
: langui.default_description ?? "",
|
|
||||||
thumbnail: thumbnail ? getOgImage(thumbnail) : DEFAULT_OG_THUMBNAIL,
|
thumbnail: thumbnail ? getOgImage(thumbnail) : DEFAULT_OG_THUMBNAIL,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOgImage = (image: UploadImageFragment): OgImage => {
|
const getOgImage = (image: UploadImageFragment): OgImage => {
|
||||||
const imgSize = getImgSizesByQuality(
|
const imgSize = getImgSizesByQuality(image.width ?? 0, image.height ?? 0, ImageQuality.Og);
|
||||||
image.width ?? 0,
|
|
||||||
image.height ?? 0,
|
|
||||||
ImageQuality.Og
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
image: getAssetURL(image.url, ImageQuality.Og),
|
image: getAssetURL(image.url, ImageQuality.Og),
|
||||||
width: imgSize.width,
|
width: imgSize.width,
|
||||||
|
|
|
@ -8,14 +8,10 @@ import {
|
||||||
|
|
||||||
type SortRangedContentProps =
|
type SortRangedContentProps =
|
||||||
| NonNullable<
|
| NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["attributes"]
|
||||||
GetLibraryItemQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["contents"]
|
>["contents"]
|
||||||
| NonNullable<
|
| NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||||
GetLibraryItemScansQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["contents"];
|
>["contents"];
|
||||||
|
|
||||||
export const sortRangedContent = (contents: SortRangedContentProps): void => {
|
export const sortRangedContent = (contents: SortRangedContentProps): void => {
|
||||||
|
@ -24,19 +20,13 @@ export const sortRangedContent = (contents: SortRangedContentProps): void => {
|
||||||
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
||||||
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
||||||
) {
|
) {
|
||||||
return (
|
return a.attributes.range[0].starting_page - b.attributes.range[0].starting_page;
|
||||||
a.attributes.range[0].starting_page -
|
|
||||||
b.attributes.range[0].starting_page
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStatusDescription = (
|
export const getStatusDescription = (status: string, langui: Langui): string | null | undefined => {
|
||||||
status: string,
|
|
||||||
langui: Langui
|
|
||||||
): string | null | undefined => {
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case Enum_Componentsetstextset_Status.Incomplete:
|
case Enum_Componentsetstextset_Status.Incomplete:
|
||||||
return langui.status_incomplete;
|
return langui.status_incomplete;
|
||||||
|
@ -55,30 +45,26 @@ export const getStatusDescription = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isDefined = <T>(t: T): t is NonNullable<T> =>
|
export const isDefined = <T>(t: T): t is NonNullable<T> => t !== null && t !== undefined;
|
||||||
t !== null && t !== undefined;
|
|
||||||
|
|
||||||
export const isUndefined = <T>(
|
export const isUndefined = <T>(t: T | null | undefined): t is null | undefined =>
|
||||||
t: T | null | undefined
|
t === null || t === undefined;
|
||||||
): t is null | undefined => t === null || t === undefined;
|
|
||||||
|
|
||||||
export const isDefinedAndNotEmpty = (
|
export const isDefinedAndNotEmpty = (string: string | null | undefined): string is string =>
|
||||||
string: string | null | undefined
|
isDefined(string) && string.length > 0;
|
||||||
): string is string => isDefined(string) && string.length > 0;
|
|
||||||
|
|
||||||
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
|
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
|
||||||
isUndefined(t)
|
isUndefined(t) ? [] : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
|
||||||
? []
|
|
||||||
: (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
|
|
||||||
|
|
||||||
export const filterHasAttributes = <T, P extends PathDot<T>>(
|
export const filterHasAttributes = <T, P extends PathDot<T>>(
|
||||||
t: T[] | null | undefined,
|
t: T[] | null | undefined,
|
||||||
paths: readonly P[]
|
paths: readonly P[]
|
||||||
): SelectiveNonNullable<T, typeof paths[number]>[] =>
|
): SelectiveNonNullable<T, typeof paths[number]>[] =>
|
||||||
isDefined(t)
|
isDefined(t)
|
||||||
? (t.filter((item) =>
|
? (t.filter((item) => hasAttributes(item, paths)) as unknown as SelectiveNonNullable<
|
||||||
hasAttributes(item, paths)
|
T,
|
||||||
) as unknown as SelectiveNonNullable<T, typeof paths[number]>[])
|
typeof paths[number]
|
||||||
|
>[])
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean =>
|
const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean =>
|
||||||
|
@ -112,11 +98,7 @@ export const iterateMap = <K, V, U>(
|
||||||
return toList.map(([key, value], index) => callbackfn(key, value, index));
|
return toList.map(([key, value], index) => callbackfn(key, value, index));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const arrayMove = <T>(
|
export const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number): T[] => {
|
||||||
arr: T[],
|
|
||||||
sourceIndex: number,
|
|
||||||
targetIndex: number
|
|
||||||
): T[] => {
|
|
||||||
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
|
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
|
||||||
return arr;
|
return arr;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { LightBox } from "components/LightBox";
|
import { LightBox } from "components/LightBox";
|
||||||
|
|
||||||
export const useLightBox = (): [
|
export const useLightBox = (): [(images: string[], index?: number) => void, () => JSX.Element] => {
|
||||||
(images: string[], index?: number) => void,
|
|
||||||
() => JSX.Element
|
|
||||||
] => {
|
|
||||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||||
|
|
|
@ -18,10 +18,7 @@ const useFetchLocalData = (name: LocalDataFile) =>
|
||||||
export const useLangui = (): Langui => {
|
export const useLangui = (): Langui => {
|
||||||
const { locale } = useRouter();
|
const { locale } = useRouter();
|
||||||
const { data: websiteInterfaces } = useFetchLocalData("websiteInterfaces");
|
const { data: websiteInterfaces } = useFetchLocalData("websiteInterfaces");
|
||||||
return useMemo(
|
return useMemo(() => processLangui(websiteInterfaces, locale), [websiteInterfaces, locale]);
|
||||||
() => processLangui(websiteInterfaces, locale),
|
|
||||||
[websiteInterfaces, locale]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCurrencies = (): Currencies => {
|
export const useCurrencies = (): Currencies => {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { useMediaQuery } from "usehooks-ts";
|
import { useMediaQuery } from "usehooks-ts";
|
||||||
|
|
||||||
export const useDeviceSupportsHover = (): boolean =>
|
export const useDeviceSupportsHover = (): boolean => useMediaQuery("(hover: hover)");
|
||||||
useMediaQuery("(hover: hover)");
|
|
||||||
|
|
||||||
export const usePrefersDarkMode = (): boolean =>
|
export const usePrefersDarkMode = (): boolean => useMediaQuery("(prefers-color-scheme: dark)");
|
||||||
useMediaQuery("(prefers-color-scheme: dark)");
|
|
||||||
|
|
|
@ -12,10 +12,7 @@ export const useOnResize = (
|
||||||
console.log("[useOnResize]", id);
|
console.log("[useOnResize]", id);
|
||||||
const elem = isClient ? document.querySelector(`#${id}`) : null;
|
const elem = isClient ? document.querySelector(`#${id}`) : null;
|
||||||
const ro = new ResizeObserver((resizeObserverEntry) =>
|
const ro = new ResizeObserver((resizeObserverEntry) =>
|
||||||
onResize(
|
onResize(resizeObserverEntry[0].contentRect.width, resizeObserverEntry[0].contentRect.height)
|
||||||
resizeObserverEntry[0].contentRect.width,
|
|
||||||
resizeObserverEntry[0].contentRect.height
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
if (isDefined(elem)) {
|
if (isDefined(elem)) {
|
||||||
ro.observe(elem);
|
ro.observe(elem);
|
||||||
|
|
|
@ -2,15 +2,9 @@ import { useMemo, useCallback, useEffect } from "react";
|
||||||
import { useIsClient } from "usehooks-ts";
|
import { useIsClient } from "usehooks-ts";
|
||||||
import { Ids } from "types/ids";
|
import { Ids } from "types/ids";
|
||||||
|
|
||||||
export const useOnScroll = (
|
export const useOnScroll = (id: Ids, onScroll: (scroll: number) => void): void => {
|
||||||
id: Ids,
|
|
||||||
onScroll: (scroll: number) => void
|
|
||||||
): void => {
|
|
||||||
const isClient = useIsClient();
|
const isClient = useIsClient();
|
||||||
const elem = useMemo(
|
const elem = useMemo(() => (isClient ? document.querySelector(`#${id}`) : null), [id, isClient]);
|
||||||
() => (isClient ? document.querySelector(`#${id}`) : null),
|
|
||||||
[id, isClient]
|
|
||||||
);
|
|
||||||
const listener = useCallback(() => {
|
const listener = useCallback(() => {
|
||||||
if (elem?.scrollTop) {
|
if (elem?.scrollTop) {
|
||||||
onScroll(elem.scrollTop);
|
onScroll(elem.scrollTop);
|
||||||
|
|
|
@ -2,16 +2,9 @@ import { DependencyList, useEffect } from "react";
|
||||||
import { Ids } from "types/ids";
|
import { Ids } from "types/ids";
|
||||||
|
|
||||||
// Scroll to top of element "id" when "deps" update.
|
// Scroll to top of element "id" when "deps" update.
|
||||||
export const useScrollTopOnChange = (
|
export const useScrollTopOnChange = (id: Ids, deps: DependencyList, enabled = true): void => {
|
||||||
id: Ids,
|
|
||||||
deps: DependencyList,
|
|
||||||
enabled = true
|
|
||||||
): void => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (enabled)
|
if (enabled) document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
document
|
|
||||||
.querySelector(`#${id}`)
|
|
||||||
?.scrollTo({ top: 0, behavior: "smooth" });
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [id, ...deps, enabled]);
|
}, [id, ...deps, enabled]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,11 +16,7 @@ export const useSmartLanguage = <T>({
|
||||||
items,
|
items,
|
||||||
languageExtractor,
|
languageExtractor,
|
||||||
transform = (item) => item,
|
transform = (item) => item,
|
||||||
}: Props<T>): [
|
}: Props<T>): [T | undefined, typeof LanguageSwitcher, Parameters<typeof LanguageSwitcher>[0]] => {
|
||||||
T | undefined,
|
|
||||||
typeof LanguageSwitcher,
|
|
||||||
Parameters<typeof LanguageSwitcher>[0]
|
|
||||||
] => {
|
|
||||||
const { preferredLanguages } = useAppLayout();
|
const { preferredLanguages } = useAppLayout();
|
||||||
const languages = useLanguages();
|
const languages = useLanguages();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -34,14 +30,10 @@ export const useSmartLanguage = <T>({
|
||||||
return memo;
|
return memo;
|
||||||
}, [items, languageExtractor]);
|
}, [items, languageExtractor]);
|
||||||
|
|
||||||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<number | undefined>();
|
||||||
number | undefined
|
|
||||||
>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedTranslationIndex(
|
setSelectedTranslationIndex(getPreferredLanguage(preferredLanguages, availableLocales));
|
||||||
getPreferredLanguage(preferredLanguages, availableLocales)
|
|
||||||
);
|
|
||||||
}, [preferredLanguages, availableLocales, router.locale]);
|
}, [preferredLanguages, availableLocales, router.locale]);
|
||||||
|
|
||||||
const selectedTranslation = useMemo(() => {
|
const selectedTranslation = useMemo(() => {
|
||||||
|
|
|
@ -8,9 +8,7 @@ import Document, {
|
||||||
} from "next/document";
|
} from "next/document";
|
||||||
|
|
||||||
export default class MyDocument extends Document {
|
export default class MyDocument extends Document {
|
||||||
static async getInitialProps(
|
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
|
||||||
ctx: DocumentContext
|
|
||||||
): Promise<DocumentInitialProps> {
|
|
||||||
const initialProps = await Document.getInitialProps(ctx);
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
return { ...initialProps };
|
return { ...initialProps };
|
||||||
}
|
}
|
||||||
|
@ -19,45 +17,20 @@ export default class MyDocument extends Document {
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head>
|
<Head>
|
||||||
<link
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
rel="apple-touch-icon"
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
sizes="180x180"
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
href="/apple-touch-icon.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="32x32"
|
|
||||||
href="/favicon-32x32.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="16x16"
|
|
||||||
href="/favicon-16x16.png"
|
|
||||||
/>
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#9c6644" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#9c6644" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Accord's Library" />
|
<meta name="apple-mobile-web-app-title" content="Accord's Library" />
|
||||||
<meta name="application-name" content="Accord's Library" />
|
<meta name="application-name" content="Accord's Library" />
|
||||||
<meta name="msapplication-TileColor" content="#feecd6" />
|
<meta name="msapplication-TileColor" content="#feecd6" />
|
||||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
|
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
|
||||||
<meta
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#feecd6" />
|
||||||
name="theme-color"
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#26221e" />
|
||||||
media="(prefers-color-scheme: light)"
|
|
||||||
content="#feecd6"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="theme-color"
|
|
||||||
media="(prefers-color-scheme: dark)"
|
|
||||||
content="#26221e"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
name="apple-mobile-web-app-status-bar-style"
|
|
||||||
content="black-translucent"
|
|
||||||
/>
|
|
||||||
</Head>
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
<Main />
|
<Main />
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { PostPage } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import {
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
getPostStaticProps,
|
|
||||||
PostStaticProps,
|
|
||||||
} from "graphql/getPostStaticProps";
|
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -2,10 +2,7 @@ import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { InsetBox } from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import { PostPage } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import {
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
getPostStaticProps,
|
|
||||||
PostStaticProps,
|
|
||||||
} from "graphql/getPostStaticProps";
|
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { randomInt } from "helpers/numbers";
|
import { randomInt } from "helpers/numbers";
|
||||||
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
||||||
|
@ -22,9 +19,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const is1ColumnLayout = useIs1ColumnLayout();
|
const is1ColumnLayout = useIs1ColumnLayout();
|
||||||
const [formResponse, setFormResponse] = useState("");
|
const [formResponse, setFormResponse] = useState("");
|
||||||
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">(
|
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">("stale");
|
||||||
"stale"
|
|
||||||
);
|
|
||||||
|
|
||||||
const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10));
|
const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10));
|
||||||
const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10));
|
const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10));
|
||||||
|
@ -34,10 +29,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
||||||
<form
|
<form
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid gap-8",
|
"grid gap-8",
|
||||||
cIf(
|
cIf(formState !== "stale", "pointer-events-none cursor-not-allowed touch-none opacity-60")
|
||||||
formState !== "stale",
|
|
||||||
"pointer-events-none cursor-not-allowed touch-none opacity-60"
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
onSubmit={(event) => {
|
onSubmit={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -52,8 +44,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
||||||
setFormState("ongoing");
|
setFormState("ongoing");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
parseInt(fields.verif.value, 10) ===
|
parseInt(fields.verif.value, 10) === randomNumber1 + randomNumber2 &&
|
||||||
randomNumber1 + randomNumber2 &&
|
|
||||||
formState !== "completed"
|
formState !== "completed"
|
||||||
) {
|
) {
|
||||||
const content: RequestMailProps = {
|
const content: RequestMailProps = {
|
||||||
|
@ -100,8 +91,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
||||||
|
|
||||||
router.replace("#send-response");
|
router.replace("#send-response");
|
||||||
fields.verif.value = "";
|
fields.verif.value = "";
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<div className="flex flex-col place-items-center gap-1">
|
<div className="flex flex-col place-items-center gap-1">
|
||||||
<label htmlFor="name">{langui.name}:</label>
|
<label htmlFor="name">{langui.name}:</label>
|
||||||
<input
|
<input
|
||||||
|
@ -124,9 +114,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
||||||
required
|
required
|
||||||
disabled={formState !== "stale"}
|
disabled={formState !== "stale"}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm italic text-dark opacity-70">
|
<p className="text-sm italic text-dark opacity-70">{langui.email_gdpr_notice}</p>
|
||||||
{langui.email_gdpr_notice}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex w-full flex-col place-items-center gap-1">
|
<div className="flex w-full flex-col place-items-center gap-1">
|
||||||
|
@ -145,8 +133,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
||||||
<div className="flex flex-row place-items-center gap-2">
|
<div className="flex flex-row place-items-center gap-2">
|
||||||
<label
|
<label
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
htmlFor="verif"
|
htmlFor="verif">{`${randomNumber1} + ${randomNumber2} =`}</label>
|
||||||
>{`${randomNumber1} + ${randomNumber2} =`}</label>
|
|
||||||
<input
|
<input
|
||||||
className="w-24"
|
className="w-24"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
|
@ -30,17 +30,9 @@ const AboutUs = (props: Props): JSX.Element => {
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<NavOption
|
<NavOption title={langui.accords_handbook} url="/about-us/accords-handbook" border />
|
||||||
title={langui.accords_handbook}
|
|
||||||
url="/about-us/accords-handbook"
|
|
||||||
border
|
|
||||||
/>
|
|
||||||
<NavOption title={langui.legality} url="/about-us/legality" border />
|
<NavOption title={langui.legality} url="/about-us/legality" border />
|
||||||
<NavOption
|
<NavOption title={langui.sharing_policy} url="/about-us/sharing-policy" border />
|
||||||
title={langui.sharing_policy}
|
|
||||||
url="/about-us/sharing-policy"
|
|
||||||
border
|
|
||||||
/>
|
|
||||||
<NavOption title={langui.contact_us} url="/about-us/contact" border />
|
<NavOption title={langui.contact_us} url="/about-us/contact" border />
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { PostPage } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import {
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
getPostStaticProps,
|
|
||||||
PostStaticProps,
|
|
||||||
} from "graphql/getPostStaticProps";
|
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { PostPage } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import {
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
getPostStaticProps,
|
|
||||||
PostStaticProps,
|
|
||||||
} from "graphql/getPostStaticProps";
|
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -114,16 +114,11 @@ type ResponseMailProps = {
|
||||||
revalidated: boolean;
|
revalidated: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Revalidate = (
|
const Revalidate = (req: NextApiRequest, res: NextApiResponse<ResponseMailProps>): void => {
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse<ResponseMailProps>
|
|
||||||
): void => {
|
|
||||||
const body = req.body as RequestProps;
|
const body = req.body as RequestProps;
|
||||||
|
|
||||||
// Check for secret to confirm this is a valid request
|
// Check for secret to confirm this is a valid request
|
||||||
if (
|
if (req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`) {
|
||||||
req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`
|
|
||||||
) {
|
|
||||||
res.status(401).json({ message: "Invalid token", revalidated: false });
|
res.status(401).json({ message: "Invalid token", revalidated: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -210,9 +205,7 @@ const Revalidate = (
|
||||||
i18n.locales.forEach((locale: string) => {
|
i18n.locales.forEach((locale: string) => {
|
||||||
if (body.entry.library_item) {
|
if (body.entry.library_item) {
|
||||||
paths.push(`/${locale}/library/${body.entry.library_item.slug}`);
|
paths.push(`/${locale}/library/${body.entry.library_item.slug}`);
|
||||||
paths.push(
|
paths.push(`/${locale}/library/${body.entry.library_item.slug}/scans`);
|
||||||
`/${locale}/library/${body.entry.library_item.slug}/scans`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (body.entry.content) {
|
if (body.entry.content) {
|
||||||
paths.push(`/${locale}/contents/${body.entry.content.slug}`);
|
paths.push(`/${locale}/contents/${body.entry.content.slug}`);
|
||||||
|
@ -232,25 +225,19 @@ const Revalidate = (
|
||||||
body.entry.subfolders.forEach((subfolder) =>
|
body.entry.subfolders.forEach((subfolder) =>
|
||||||
paths.push(`/contents/folder/${subfolder.slug}`)
|
paths.push(`/contents/folder/${subfolder.slug}`)
|
||||||
);
|
);
|
||||||
body.entry.contents.forEach((content) =>
|
body.entry.contents.forEach((content) => paths.push(`/contents/${content.slug}`));
|
||||||
paths.push(`/contents/${content.slug}`)
|
|
||||||
);
|
|
||||||
i18n.locales.forEach((locale: string) => {
|
i18n.locales.forEach((locale: string) => {
|
||||||
if (body.entry.slug === "root") {
|
if (body.entry.slug === "root") {
|
||||||
paths.push(`/${locale}/contents`);
|
paths.push(`/${locale}/contents`);
|
||||||
}
|
}
|
||||||
paths.push(`/${locale}/contents/folder/${body.entry.slug}`);
|
paths.push(`/${locale}/contents/folder/${body.entry.slug}`);
|
||||||
if (body.entry.parent_folder) {
|
if (body.entry.parent_folder) {
|
||||||
paths.push(
|
paths.push(`/${locale}/contents/folder/${body.entry.parent_folder.slug}`);
|
||||||
`/${locale}/contents/folder/${body.entry.parent_folder.slug}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
body.entry.subfolders.forEach((subfolder) =>
|
body.entry.subfolders.forEach((subfolder) =>
|
||||||
paths.push(`/${locale}/contents/folder/${subfolder.slug}`)
|
paths.push(`/${locale}/contents/folder/${subfolder.slug}`)
|
||||||
);
|
);
|
||||||
body.entry.contents.forEach((content) =>
|
body.entry.contents.forEach((content) => paths.push(`/${locale}/contents/${content.slug}`));
|
||||||
paths.push(`/${locale}/contents/${content.slug}`)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -311,9 +298,7 @@ const Revalidate = (
|
||||||
res.json({ message: "Success!", revalidated: true });
|
res.json({ message: "Success!", revalidated: true });
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res
|
res.status(500).send({ message: `Error revalidating: ${error}`, revalidated: false });
|
||||||
.status(500)
|
|
||||||
.send({ message: `Error revalidating: ${error}`, revalidated: false });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export default Revalidate;
|
export default Revalidate;
|
||||||
|
|
|
@ -5,10 +5,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Switch } from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { PreviewCard } from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { GetVideoChannelQuery } from "graphql/generated";
|
import { GetVideoChannelQuery } from "graphql/generated";
|
||||||
|
@ -43,21 +40,16 @@ const DEFAULT_FILTERS_STATE = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
interface Props extends AppLayoutRequired {
|
||||||
channel: NonNullable<
|
channel: NonNullable<GetVideoChannelQuery["videoChannels"]>["data"][number]["attributes"];
|
||||||
GetVideoChannelQuery["videoChannels"]
|
|
||||||
>["data"][number]["attributes"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
||||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true);
|
||||||
useBoolean(true);
|
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const hoverable = useDeviceSupportsHover();
|
const hoverable = useDeviceSupportsHover();
|
||||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
|
||||||
);
|
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -98,10 +90,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
||||||
() => (
|
() => (
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
<SmartList
|
<SmartList
|
||||||
items={filterHasAttributes(channel?.videos?.data, [
|
items={filterHasAttributes(channel?.videos?.data, ["id", "attributes"] as const)}
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const)}
|
|
||||||
getItemId={(item) => item.id}
|
getItemId={(item) => item.id}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
|
@ -135,22 +124,10 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
),
|
),
|
||||||
[
|
[channel?.title, channel?.videos?.data, isContentPanelAtLeast4xl, keepInfoVisible, searchName]
|
||||||
channel?.title,
|
|
||||||
channel?.videos?.data,
|
|
||||||
isContentPanelAtLeast4xl,
|
|
||||||
keepInfoVisible,
|
|
||||||
searchName,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||||
<AppLayout
|
|
||||||
subPanel={subPanel}
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default Channel;
|
export default Channel;
|
||||||
|
|
||||||
|
@ -163,25 +140,17 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const langui = getLangui(context.locale);
|
const langui = getLangui(context.locale);
|
||||||
const channel = await sdk.getVideoChannel({
|
const channel = await sdk.getVideoChannel({
|
||||||
channel:
|
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
|
||||||
context.params && isDefined(context.params.uid)
|
|
||||||
? context.params.uid.toString()
|
|
||||||
: "",
|
|
||||||
});
|
});
|
||||||
if (!channel.videoChannels?.data[0].attributes) return { notFound: true };
|
if (!channel.videoChannels?.data[0].attributes) return { notFound: true };
|
||||||
|
|
||||||
channel.videoChannels.data[0].attributes.videos?.data
|
channel.videoChannels.data[0].attributes.videos?.data
|
||||||
.sort((a, b) =>
|
.sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date))
|
||||||
compareDate(a.attributes?.published_date, b.attributes?.published_date)
|
|
||||||
)
|
|
||||||
.reverse();
|
.reverse();
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
channel: channel.videoChannels.data[0].attributes,
|
channel: channel.videoChannels.data[0].attributes,
|
||||||
openGraph: getOpenGraph(
|
openGraph: getOpenGraph(langui, channel.videoChannels.data[0].attributes.title),
|
||||||
langui,
|
|
||||||
channel.videoChannels.data[0].attributes.title
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
|
@ -196,9 +165,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
|
|
||||||
if (channels.videoChannels?.data)
|
if (channels.videoChannels?.data)
|
||||||
filterHasAttributes(channels.videoChannels.data, [
|
filterHasAttributes(channels.videoChannels.data, ["attributes"] as const).map((channel) => {
|
||||||
"attributes",
|
|
||||||
] as const).map((channel) => {
|
|
||||||
context.locales?.map((local) => {
|
context.locales?.map((local) => {
|
||||||
paths.push({
|
paths.push({
|
||||||
params: { uid: channel.attributes.uid },
|
params: { uid: channel.attributes.uid },
|
||||||
|
|
|
@ -9,10 +9,7 @@ import { TextInput } from "components/Inputs/TextInput";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { PreviewCard } from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { GetVideosPreviewQuery } from "graphql/generated";
|
import { GetVideosPreviewQuery } from "graphql/generated";
|
||||||
|
@ -51,12 +48,9 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
|
||||||
const hoverable = useDeviceSupportsHover();
|
const hoverable = useDeviceSupportsHover();
|
||||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||||
|
|
||||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true);
|
||||||
useBoolean(true);
|
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
|
||||||
);
|
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -68,11 +62,7 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
|
||||||
className="mb-10"
|
className="mb-10"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PanelHeader
|
<PanelHeader icon={Icon.Movie} title="Videos" description={langui.archives_description} />
|
||||||
icon={Icon.Movie}
|
|
||||||
title="Videos"
|
|
||||||
description={langui.archives_description}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
|
@ -132,13 +122,7 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
|
||||||
),
|
),
|
||||||
[isContentPanelAtLeast4xl, keepInfoVisible, searchName, videos]
|
[isContentPanelAtLeast4xl, keepInfoVisible, searchName, videos]
|
||||||
);
|
);
|
||||||
return (
|
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||||
<AppLayout
|
|
||||||
subPanel={subPanel}
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default Videos;
|
export default Videos;
|
||||||
|
|
||||||
|
@ -153,9 +137,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const videos = await sdk.getVideosPreview();
|
const videos = await sdk.getVideosPreview();
|
||||||
if (!videos.videos) return { notFound: true };
|
if (!videos.videos) return { notFound: true };
|
||||||
videos.videos.data
|
videos.videos.data
|
||||||
.sort((a, b) =>
|
.sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date))
|
||||||
compareDate(a.attributes?.published_date, b.attributes?.published_date)
|
|
||||||
)
|
|
||||||
.reverse();
|
.reverse();
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
|
|
|
@ -8,10 +8,7 @@ import { Button } from "components/Inputs/Button";
|
||||||
import { InsetBox } from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import { NavOption } from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { GetVideoQuery } from "graphql/generated";
|
import { GetVideoQuery } from "graphql/generated";
|
||||||
|
@ -29,9 +26,7 @@ import { getLangui } from "graphql/fetchLocalData";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
interface Props extends AppLayoutRequired {
|
||||||
video: NonNullable<
|
video: NonNullable<NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]>;
|
||||||
NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||||
|
@ -86,16 +81,9 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid place-items-center gap-12">
|
<div className="grid place-items-center gap-12">
|
||||||
<div
|
<div id="video" className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade">
|
||||||
id="video"
|
|
||||||
className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade"
|
|
||||||
>
|
|
||||||
{video.gone ? (
|
{video.gone ? (
|
||||||
<video
|
<video className="w-full" src={getVideoFile(video.uid)} controls></video>
|
||||||
className="w-full"
|
|
||||||
src={getVideoFile(video.uid)}
|
|
||||||
controls
|
|
||||||
></video>
|
|
||||||
) : (
|
) : (
|
||||||
<iframe
|
<iframe
|
||||||
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
|
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
|
||||||
|
@ -104,49 +92,32 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
allow="accelerometer; autoplay; clipboard-write;
|
allow="accelerometer; autoplay; clipboard-write;
|
||||||
encrypted-media; gyroscope; picture-in-picture"
|
encrypted-media; gyroscope; picture-in-picture"
|
||||||
allowFullScreen
|
allowFullScreen></iframe>
|
||||||
></iframe>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-2 p-6">
|
<div className="mt-2 p-6">
|
||||||
<h1 className="text-2xl">{video.title}</h1>
|
<h1 className="text-2xl">{video.title}</h1>
|
||||||
<div className="flex w-full flex-row flex-wrap gap-x-6">
|
<div className="flex w-full flex-row flex-wrap gap-x-6">
|
||||||
<p>
|
<p>
|
||||||
<Ico
|
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
|
||||||
icon={Icon.Event}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{prettyDate(video.published_date, router.locale)}
|
{prettyDate(video.published_date, router.locale)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<Ico
|
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
|
||||||
icon={Icon.Visibility}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{isContentPanelAtLeast4xl
|
{isContentPanelAtLeast4xl
|
||||||
? video.views.toLocaleString()
|
? video.views.toLocaleString()
|
||||||
: prettyShortenNumber(video.views)}
|
: prettyShortenNumber(video.views)}
|
||||||
</p>
|
</p>
|
||||||
{video.channel?.data?.attributes && (
|
{video.channel?.data?.attributes && (
|
||||||
<p>
|
<p>
|
||||||
<Ico
|
<Ico icon={Icon.ThumbUp} className="mr-1 translate-y-[.15em] !text-base" />
|
||||||
icon={Icon.ThumbUp}
|
|
||||||
className="mr-1 translate-y-[.15em] !text-base"
|
|
||||||
/>
|
|
||||||
{isContentPanelAtLeast4xl
|
{isContentPanelAtLeast4xl
|
||||||
? video.likes.toLocaleString()
|
? video.likes.toLocaleString()
|
||||||
: prettyShortenNumber(video.likes)}
|
: prettyShortenNumber(video.likes)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<a
|
<a href={`https://youtu.be/${video.uid}`} target="_blank" rel="noreferrer">
|
||||||
href={`https://youtu.be/${video.uid}`}
|
<Button className="!py-0 !px-3" text={`${langui.view_on} ${video.source}`} />
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className="!py-0 !px-3"
|
|
||||||
text={`${langui.view_on} ${video.source}`}
|
|
||||||
/>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -195,13 +166,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||||
<AppLayout
|
|
||||||
subPanel={subPanel}
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default Video;
|
export default Video;
|
||||||
|
|
||||||
|
@ -214,10 +179,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const langui = getLangui(context.locale);
|
const langui = getLangui(context.locale);
|
||||||
const videos = await sdk.getVideo({
|
const videos = await sdk.getVideo({
|
||||||
uid:
|
uid: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
|
||||||
context.params && isDefined(context.params.uid)
|
|
||||||
? context.params.uid.toString()
|
|
||||||
: "",
|
|
||||||
});
|
});
|
||||||
if (!videos.videos?.data[0]?.attributes) return { notFound: true };
|
if (!videos.videos?.data[0]?.attributes) return { notFound: true };
|
||||||
|
|
||||||
|
@ -237,13 +199,11 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const videos = await sdk.getVideosSlugs();
|
const videos = await sdk.getVideosSlugs();
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
if (videos.videos?.data)
|
if (videos.videos?.data)
|
||||||
filterHasAttributes(videos.videos.data, ["attributes"] as const).map(
|
filterHasAttributes(videos.videos.data, ["attributes"] as const).map((video) => {
|
||||||
(video) => {
|
context.locales?.map((local) => {
|
||||||
context.locales?.map((local) => {
|
paths.push({ params: { uid: video.attributes.uid }, locale: local });
|
||||||
paths.push({ params: { uid: video.attributes.uid }, locale: local });
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
paths,
|
paths,
|
||||||
fallback: "blocking",
|
fallback: "blocking",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { GetStaticProps, GetStaticPaths, GetStaticPathsResult } from "next";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { isDefined, filterHasAttributes } from "helpers/others";
|
import { isDefined, filterHasAttributes } from "helpers/others";
|
||||||
import { ChronicleWithTranslations } from "helpers/types";
|
import { ChronicleWithTranslations } from "types/types";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||||
|
@ -15,10 +15,7 @@ import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import {
|
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||||
getDefaultPreferredLanguages,
|
|
||||||
staticSmartLanguage,
|
|
||||||
} from "helpers/locales";
|
|
||||||
import { getDescription } from "helpers/description";
|
import { getDescription } from "helpers/description";
|
||||||
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
@ -31,59 +28,44 @@ import { getLangui } from "graphql/fetchLocalData";
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
interface Props extends AppLayoutRequired {
|
||||||
chronicle: ChronicleWithTranslations;
|
chronicle: ChronicleWithTranslations;
|
||||||
chapters: NonNullable<
|
chapters: NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"];
|
||||||
GetChroniclesChaptersQuery["chroniclesChapters"]
|
|
||||||
>["data"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Chronicle = ({
|
const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element => {
|
||||||
chronicle,
|
|
||||||
chapters,
|
|
||||||
...otherProps
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
useSmartLanguage({
|
items: chronicle.translations,
|
||||||
items: chronicle.translations,
|
|
||||||
languageExtractor: useCallback(
|
|
||||||
(item: ChronicleWithTranslations["translations"][number]) =>
|
|
||||||
item?.language?.data?.attributes?.code,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const primaryContent = useMemo<
|
|
||||||
NonNullable<
|
|
||||||
ChronicleWithTranslations["contents"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>(
|
|
||||||
() =>
|
|
||||||
filterHasAttributes(chronicle.contents?.data, [
|
|
||||||
"attributes.translations",
|
|
||||||
] as const)[0]?.attributes,
|
|
||||||
[chronicle.contents?.data]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [
|
|
||||||
selectedContentTranslation,
|
|
||||||
ContentLanguageSwitcher,
|
|
||||||
ContentLanguageSwitcherProps,
|
|
||||||
] = useSmartLanguage({
|
|
||||||
items: primaryContent?.translations ?? [],
|
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback(
|
||||||
(
|
(item: ChronicleWithTranslations["translations"][number]) =>
|
||||||
item: NonNullable<
|
item?.language?.data?.attributes?.code,
|
||||||
NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
ChronicleWithTranslations["contents"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["translations"]
|
|
||||||
>[number]
|
|
||||||
) => item?.language?.data?.attributes?.code,
|
|
||||||
[]
|
[]
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const primaryContent = useMemo<
|
||||||
|
NonNullable<ChronicleWithTranslations["contents"]>["data"][number]["attributes"]
|
||||||
|
>(
|
||||||
|
() =>
|
||||||
|
filterHasAttributes(chronicle.contents?.data, ["attributes.translations"] as const)[0]
|
||||||
|
?.attributes,
|
||||||
|
[chronicle.contents?.data]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedContentTranslation, ContentLanguageSwitcher, ContentLanguageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
|
items: primaryContent?.translations ?? [],
|
||||||
|
languageExtractor: useCallback(
|
||||||
|
(
|
||||||
|
item: NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<ChronicleWithTranslations["contents"]>["data"][number]["attributes"]
|
||||||
|
>["translations"]
|
||||||
|
>[number]
|
||||||
|
) => item?.language?.data?.attributes?.code,
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<ContentPanel>
|
<ContentPanel>
|
||||||
|
@ -96,9 +78,7 @@ const Chronicle = ({
|
||||||
|
|
||||||
{isDefined(selectedTranslation) ? (
|
{isDefined(selectedTranslation) ? (
|
||||||
<>
|
<>
|
||||||
<h1 className="mb-16 text-center text-3xl">
|
<h1 className="mb-16 text-center text-3xl">{selectedTranslation.title}</h1>
|
||||||
{selectedTranslation.title}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{languageSwitcherProps.locales.size > 1 && (
|
{languageSwitcherProps.locales.size > 1 && (
|
||||||
<LanguageSwitcher {...languageSwitcherProps} />
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
|
@ -118,9 +98,7 @@ const Chronicle = ({
|
||||||
subtitle={selectedContentTranslation.subtitle}
|
subtitle={selectedContentTranslation.subtitle}
|
||||||
languageSwitcher={
|
languageSwitcher={
|
||||||
ContentLanguageSwitcherProps.locales.size > 1 ? (
|
ContentLanguageSwitcherProps.locales.size > 1 ? (
|
||||||
<ContentLanguageSwitcher
|
<ContentLanguageSwitcher {...ContentLanguageSwitcherProps} />
|
||||||
{...ContentLanguageSwitcherProps}
|
|
||||||
/>
|
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
categories={primaryContent?.categories}
|
categories={primaryContent?.categories}
|
||||||
|
@ -167,23 +145,22 @@ const Chronicle = ({
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<div className="grid gap-16">
|
<div className="grid gap-16">
|
||||||
{filterHasAttributes(chapters, [
|
{filterHasAttributes(chapters, ["attributes.chronicles", "id"] as const).map(
|
||||||
"attributes.chronicles",
|
(chapter) => (
|
||||||
"id",
|
<TranslatedChroniclesList
|
||||||
] as const).map((chapter) => (
|
key={chapter.id}
|
||||||
<TranslatedChroniclesList
|
chronicles={chapter.attributes.chronicles.data}
|
||||||
key={chapter.id}
|
translations={filterHasAttributes(chapter.attributes.titles, [
|
||||||
chronicles={chapter.attributes.chronicles.data}
|
"language.data.attributes.code",
|
||||||
translations={filterHasAttributes(chapter.attributes.titles, [
|
] as const).map((translation) => ({
|
||||||
"language.data.attributes.code",
|
title: translation.title,
|
||||||
] as const).map((translation) => ({
|
language: translation.language.data.attributes.code,
|
||||||
title: translation.title,
|
}))}
|
||||||
language: translation.language.data.attributes.code,
|
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
||||||
}))}
|
currentSlug={chronicle.slug}
|
||||||
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
/>
|
||||||
currentSlug={chronicle.slug}
|
)
|
||||||
/>
|
)}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
|
@ -210,9 +187,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const langui = getLangui(context.locale);
|
const langui = getLangui(context.locale);
|
||||||
const slug =
|
const slug =
|
||||||
context.params && isDefined(context.params.slug)
|
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||||
? context.params.slug.toString()
|
|
||||||
: "";
|
|
||||||
const chronicle = await sdk.getChronicle({
|
const chronicle = await sdk.getChronicle({
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
slug: slug,
|
slug: slug,
|
||||||
|
@ -226,19 +201,11 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
|
|
||||||
const { title, description } = (() => {
|
const { title, description } = (() => {
|
||||||
if (context.locale && context.locales) {
|
if (context.locale && context.locales) {
|
||||||
if (
|
if (chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes?.translations) {
|
||||||
chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes
|
|
||||||
?.translations
|
|
||||||
) {
|
|
||||||
const selectedContentTranslation = staticSmartLanguage({
|
const selectedContentTranslation = staticSmartLanguage({
|
||||||
items:
|
items: chronicle.chronicles.data[0].attributes.contents.data[0].attributes.translations,
|
||||||
chronicle.chronicles.data[0].attributes.contents.data[0].attributes
|
|
||||||
.translations,
|
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
preferredLanguages: getDefaultPreferredLanguages(
|
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||||
context.locale,
|
|
||||||
context.locales
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
if (selectedContentTranslation) {
|
if (selectedContentTranslation) {
|
||||||
return {
|
return {
|
||||||
|
@ -247,30 +214,24 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
selectedContentTranslation.title,
|
selectedContentTranslation.title,
|
||||||
selectedContentTranslation.subtitle
|
selectedContentTranslation.subtitle
|
||||||
),
|
),
|
||||||
description: getDescription(
|
description: getDescription(selectedContentTranslation.description, {
|
||||||
selectedContentTranslation.description,
|
[langui.type ?? "Type"]: [
|
||||||
{
|
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.type?.data
|
||||||
[langui.type ?? "Type"]: [
|
?.attributes?.titles?.[0]?.title,
|
||||||
chronicle.chronicles.data[0].attributes.contents.data[0]
|
],
|
||||||
.attributes.type?.data?.attributes?.titles?.[0]?.title,
|
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||||
],
|
chronicle.chronicles.data[0].attributes.contents.data[0].attributes.categories
|
||||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
?.data,
|
||||||
chronicle.chronicles.data[0].attributes.contents.data[0]
|
["attributes"] as const
|
||||||
.attributes.categories?.data,
|
).map((category) => category.attributes.short),
|
||||||
["attributes"] as const
|
}),
|
||||||
).map((category) => category.attributes.short),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const selectedTranslation = staticSmartLanguage({
|
const selectedTranslation = staticSmartLanguage({
|
||||||
items: chronicle.chronicles.data[0].attributes.translations,
|
items: chronicle.chronicles.data[0].attributes.translations,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
preferredLanguages: getDefaultPreferredLanguages(
|
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||||
context.locale,
|
|
||||||
context.locales
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
if (selectedTranslation) {
|
if (selectedTranslation) {
|
||||||
return {
|
return {
|
||||||
|
@ -288,13 +249,12 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
|
|
||||||
const thumbnail =
|
const thumbnail =
|
||||||
chronicle.chronicles.data[0].attributes.translations.length === 0
|
chronicle.chronicles.data[0].attributes.translations.length === 0
|
||||||
? chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes
|
? chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes?.thumbnail?.data
|
||||||
?.thumbnail?.data?.attributes
|
?.attributes
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
chronicle: chronicle.chronicles.data[0]
|
chronicle: chronicle.chronicles.data[0].attributes as ChronicleWithTranslations,
|
||||||
.attributes as ChronicleWithTranslations,
|
|
||||||
chapters: chronicles.chroniclesChapters.data,
|
chapters: chronicles.chroniclesChapters.data,
|
||||||
openGraph: getOpenGraph(langui, title, description, thumbnail),
|
openGraph: getOpenGraph(langui, title, description, thumbnail),
|
||||||
};
|
};
|
||||||
|
@ -309,16 +269,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const contents = await sdk.getChroniclesSlugs();
|
const contents = await sdk.getChroniclesSlugs();
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
filterHasAttributes(contents.chronicles?.data, ["attributes"] as const).map(
|
filterHasAttributes(contents.chronicles?.data, ["attributes"] as const).map((wikiPage) => {
|
||||||
(wikiPage) => {
|
context.locales?.map((local) =>
|
||||||
context.locales?.map((local) =>
|
paths.push({
|
||||||
paths.push({
|
params: { slug: wikiPage.attributes.slug },
|
||||||
params: { slug: wikiPage.attributes.slug },
|
locale: local,
|
||||||
locale: local,
|
})
|
||||||
})
|
);
|
||||||
);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
paths,
|
paths,
|
||||||
fallback: "blocking",
|
fallback: "blocking",
|
||||||
|
|
|
@ -20,9 +20,7 @@ import { getLangui } from "graphql/fetchLocalData";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
interface Props extends AppLayoutRequired {
|
||||||
chapters: NonNullable<
|
chapters: NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"];
|
||||||
GetChroniclesChaptersQuery["chroniclesChapters"]
|
|
||||||
>["data"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
|
const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
|
||||||
|
@ -39,22 +37,21 @@ const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<div className="grid gap-16">
|
<div className="grid gap-16">
|
||||||
{filterHasAttributes(chapters, [
|
{filterHasAttributes(chapters, ["attributes.chronicles", "id"] as const).map(
|
||||||
"attributes.chronicles",
|
(chapter) => (
|
||||||
"id",
|
<TranslatedChroniclesList
|
||||||
] as const).map((chapter) => (
|
key={chapter.id}
|
||||||
<TranslatedChroniclesList
|
chronicles={chapter.attributes.chronicles.data}
|
||||||
key={chapter.id}
|
translations={filterHasAttributes(chapter.attributes.titles, [
|
||||||
chronicles={chapter.attributes.chronicles.data}
|
"language.data.attributes.code",
|
||||||
translations={filterHasAttributes(chapter.attributes.titles, [
|
] as const).map((translation) => ({
|
||||||
"language.data.attributes.code",
|
title: translation.title,
|
||||||
] as const).map((translation) => ({
|
language: translation.language.data.attributes.code,
|
||||||
title: translation.title,
|
}))}
|
||||||
language: translation.language.data.attributes.code,
|
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
||||||
}))}
|
/>
|
||||||
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
)
|
||||||
/>
|
)}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
|
|
|
@ -20,25 +20,15 @@ import {
|
||||||
prettySlug,
|
prettySlug,
|
||||||
} from "helpers/formatters";
|
} from "helpers/formatters";
|
||||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||||
import {
|
import { filterHasAttributes, getStatusDescription, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
filterHasAttributes,
|
import { ContentWithTranslations } from "types/types";
|
||||||
getStatusDescription,
|
|
||||||
isDefinedAndNotEmpty,
|
|
||||||
} from "helpers/others";
|
|
||||||
import { ContentWithTranslations } from "helpers/types";
|
|
||||||
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import {
|
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||||
getDefaultPreferredLanguages,
|
|
||||||
staticSmartLanguage,
|
|
||||||
} from "helpers/locales";
|
|
||||||
import { getDescription } from "helpers/description";
|
import { getDescription } from "helpers/description";
|
||||||
import { TranslatedPreviewLine } from "components/PreviewLine";
|
import { TranslatedPreviewLine } from "components/PreviewLine";
|
||||||
import {
|
import { useIs1ColumnLayout, useIsContentPanelAtLeast } from "hooks/useContainerQuery";
|
||||||
useIs1ColumnLayout,
|
|
||||||
useIsContentPanelAtLeast,
|
|
||||||
} from "hooks/useContainerQuery";
|
|
||||||
import { cIf } from "helpers/className";
|
import { cIf } from "helpers/className";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
import { getLangui } from "graphql/fetchLocalData";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
@ -58,35 +48,26 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
const is1ColumnLayout = useIs1ColumnLayout();
|
const is1ColumnLayout = useIs1ColumnLayout();
|
||||||
const { langui, languages } = useAppLayout();
|
const { langui, languages } = useAppLayout();
|
||||||
|
|
||||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
useSmartLanguage({
|
items: content.translations,
|
||||||
items: content.translations,
|
languageExtractor: useCallback(
|
||||||
languageExtractor: useCallback(
|
(item: NonNullable<Props["content"]["translations"][number]>) =>
|
||||||
(item: NonNullable<Props["content"]["translations"][number]>) =>
|
item.language?.data?.attributes?.code,
|
||||||
item.language?.data?.attributes?.code,
|
[]
|
||||||
[]
|
),
|
||||||
),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
useScrollTopOnChange(Ids.ContentPanel, [selectedTranslation]);
|
useScrollTopOnChange(Ids.ContentPanel, [selectedTranslation]);
|
||||||
|
|
||||||
const { previousContent, nextContent } = useMemo(
|
const { previousContent, nextContent } = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
previousContent:
|
previousContent:
|
||||||
content.folder?.data?.attributes?.contents &&
|
content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence
|
||||||
content.folder.data.attributes.sequence
|
? getPreviousContent(content.folder.data.attributes.contents.data, content.slug)
|
||||||
? getPreviousContent(
|
|
||||||
content.folder.data.attributes.contents.data,
|
|
||||||
content.slug
|
|
||||||
)
|
|
||||||
: undefined,
|
: undefined,
|
||||||
nextContent:
|
nextContent:
|
||||||
content.folder?.data?.attributes?.contents &&
|
content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence
|
||||||
content.folder.data.attributes.sequence
|
? getNextContent(content.folder.data.attributes.contents.data, content.slug)
|
||||||
? getNextContent(
|
|
||||||
content.folder.data.attributes.contents.data,
|
|
||||||
content.slug
|
|
||||||
)
|
|
||||||
: undefined,
|
: undefined,
|
||||||
}),
|
}),
|
||||||
[content.folder, content.slug]
|
[content.folder, content.slug]
|
||||||
|
@ -98,10 +79,9 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
? `/contents/folder/${content.folder.data.attributes.slug}`
|
? `/contents/folder/${content.folder.data.attributes.slug}`
|
||||||
: "/contents",
|
: "/contents",
|
||||||
|
|
||||||
translations: filterHasAttributes(
|
translations: filterHasAttributes(content.folder?.data?.attributes?.titles, [
|
||||||
content.folder?.data?.attributes?.titles,
|
"language.data.attributes.code",
|
||||||
["language.data.attributes.code"] as const
|
] as const).map((title) => ({
|
||||||
).map((title) => ({
|
|
||||||
language: title.language.data.attributes.code,
|
language: title.language.data.attributes.code,
|
||||||
title: title.title,
|
title: title.title,
|
||||||
})),
|
})),
|
||||||
|
@ -118,34 +98,26 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<TranslatedReturnButton
|
<TranslatedReturnButton {...returnButtonProps} displayOnlyOn="3ColumnsLayout" />
|
||||||
{...returnButtonProps}
|
|
||||||
displayOnlyOn="3ColumnsLayout"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{selectedTranslation?.text_set?.source_language?.data?.attributes
|
{selectedTranslation?.text_set?.source_language?.data?.attributes?.code !== undefined && (
|
||||||
?.code !== undefined && (
|
|
||||||
<>
|
<>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<div className="grid gap-5">
|
<div className="grid gap-5">
|
||||||
<h2 className="text-xl">
|
<h2 className="text-xl">
|
||||||
{selectedTranslation.text_set.source_language.data.attributes
|
{selectedTranslation.text_set.source_language.data.attributes.code ===
|
||||||
.code === selectedTranslation.language?.data?.attributes?.code
|
selectedTranslation.language?.data?.attributes?.code
|
||||||
? langui.transcript_notice
|
? langui.transcript_notice
|
||||||
: langui.translation_notice}
|
: langui.translation_notice}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{selectedTranslation.text_set.source_language.data.attributes
|
{selectedTranslation.text_set.source_language.data.attributes.code !==
|
||||||
.code !==
|
|
||||||
selectedTranslation.language?.data?.attributes?.code && (
|
selectedTranslation.language?.data?.attributes?.code && (
|
||||||
<div className="grid place-items-center gap-2">
|
<div className="grid place-items-center gap-2">
|
||||||
<p className="font-headers font-bold">
|
<p className="font-headers font-bold">{langui.source_language}:</p>
|
||||||
{langui.source_language}:
|
|
||||||
</p>
|
|
||||||
<Chip
|
<Chip
|
||||||
text={prettyLanguage(
|
text={prettyLanguage(
|
||||||
selectedTranslation.text_set.source_language.data
|
selectedTranslation.text_set.source_language.data.attributes.code,
|
||||||
.attributes.code,
|
|
||||||
languages
|
languages
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -156,12 +128,8 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
<p className="font-headers font-bold">{langui.status}:</p>
|
<p className="font-headers font-bold">{langui.status}:</p>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={getStatusDescription(
|
content={getStatusDescription(selectedTranslation.text_set.status, langui)}
|
||||||
selectedTranslation.text_set.status,
|
maxWidth={"20rem"}>
|
||||||
langui
|
|
||||||
)}
|
|
||||||
maxWidth={"20rem"}
|
|
||||||
>
|
|
||||||
<Chip text={selectedTranslation.text_set.status} />
|
<Chip text={selectedTranslation.text_set.status} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -169,14 +137,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
{selectedTranslation.text_set.transcribers &&
|
{selectedTranslation.text_set.transcribers &&
|
||||||
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers font-bold">
|
<p className="font-headers font-bold">{langui.transcribers}:</p>
|
||||||
{langui.transcribers}:
|
|
||||||
</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(
|
{filterHasAttributes(selectedTranslation.text_set.transcribers.data, [
|
||||||
selectedTranslation.text_set.transcribers.data,
|
"attributes",
|
||||||
["attributes", "id"] as const
|
"id",
|
||||||
).map((recorder) => (
|
] as const).map((recorder) => (
|
||||||
<Fragment key={recorder.id}>
|
<Fragment key={recorder.id}>
|
||||||
<RecorderChip recorder={recorder.attributes} />
|
<RecorderChip recorder={recorder.attributes} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -188,14 +154,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
{selectedTranslation.text_set.translators &&
|
{selectedTranslation.text_set.translators &&
|
||||||
selectedTranslation.text_set.translators.data.length > 0 && (
|
selectedTranslation.text_set.translators.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers font-bold">
|
<p className="font-headers font-bold">{langui.translators}:</p>
|
||||||
{langui.translators}:
|
|
||||||
</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(
|
{filterHasAttributes(selectedTranslation.text_set.translators.data, [
|
||||||
selectedTranslation.text_set.translators.data,
|
"attributes",
|
||||||
["attributes", "id"] as const
|
"id",
|
||||||
).map((recorder) => (
|
] as const).map((recorder) => (
|
||||||
<Fragment key={recorder.id}>
|
<Fragment key={recorder.id}>
|
||||||
<RecorderChip recorder={recorder.attributes} />
|
<RecorderChip recorder={recorder.attributes} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -207,14 +171,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
{selectedTranslation.text_set.proofreaders &&
|
{selectedTranslation.text_set.proofreaders &&
|
||||||
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<p className="font-headers font-bold">
|
<p className="font-headers font-bold">{langui.proofreaders}:</p>
|
||||||
{langui.proofreaders}:
|
|
||||||
</p>
|
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{filterHasAttributes(
|
{filterHasAttributes(selectedTranslation.text_set.proofreaders.data, [
|
||||||
selectedTranslation.text_set.proofreaders.data,
|
"attributes",
|
||||||
["attributes", "id"] as const
|
"id",
|
||||||
).map((recorder) => (
|
] as const).map((recorder) => (
|
||||||
<Fragment key={recorder.id}>
|
<Fragment key={recorder.id}>
|
||||||
<RecorderChip recorder={recorder.attributes} />
|
<RecorderChip recorder={recorder.attributes} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -249,68 +211,56 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{content.ranged_contents?.data &&
|
{content.ranged_contents?.data && content.ranged_contents.data.length > 0 && (
|
||||||
content.ranged_contents.data.length > 0 && (
|
<>
|
||||||
<>
|
<HorizontalLine />
|
||||||
<HorizontalLine />
|
<div>
|
||||||
<div>
|
<p className="font-headers text-2xl font-bold">{langui.source}</p>
|
||||||
<p className="font-headers text-2xl font-bold">
|
<div className="mt-6 grid place-items-center gap-6">
|
||||||
{langui.source}
|
{filterHasAttributes(content.ranged_contents.data, [
|
||||||
</p>
|
"attributes.library_item.data.attributes",
|
||||||
<div className="mt-6 grid place-items-center gap-6">
|
"attributes.library_item.data.id",
|
||||||
{filterHasAttributes(content.ranged_contents.data, [
|
] as const).map((rangedContent) => {
|
||||||
"attributes.library_item.data.attributes",
|
const libraryItem = rangedContent.attributes.library_item.data;
|
||||||
"attributes.library_item.data.id",
|
return (
|
||||||
] as const).map((rangedContent) => {
|
<div
|
||||||
const libraryItem =
|
key={libraryItem.attributes.slug}
|
||||||
rangedContent.attributes.library_item.data;
|
className={cIf(is1ColumnLayout, "w-3/4")}>
|
||||||
return (
|
<PreviewCard
|
||||||
<div
|
href={`/library/${libraryItem.attributes.slug}`}
|
||||||
key={libraryItem.attributes.slug}
|
title={libraryItem.attributes.title}
|
||||||
className={cIf(is1ColumnLayout, "w-3/4")}
|
subtitle={libraryItem.attributes.subtitle}
|
||||||
>
|
thumbnail={libraryItem.attributes.thumbnail?.data?.attributes}
|
||||||
<PreviewCard
|
thumbnailAspectRatio="21/29.7"
|
||||||
href={`/library/${libraryItem.attributes.slug}`}
|
thumbnailRounded={false}
|
||||||
title={libraryItem.attributes.title}
|
topChips={
|
||||||
subtitle={libraryItem.attributes.subtitle}
|
libraryItem.attributes.metadata &&
|
||||||
thumbnail={
|
libraryItem.attributes.metadata.length > 0 &&
|
||||||
libraryItem.attributes.thumbnail?.data?.attributes
|
libraryItem.attributes.metadata[0]
|
||||||
}
|
? [prettyItemSubType(libraryItem.attributes.metadata[0])]
|
||||||
thumbnailAspectRatio="21/29.7"
|
: []
|
||||||
thumbnailRounded={false}
|
}
|
||||||
topChips={
|
bottomChips={filterHasAttributes(libraryItem.attributes.categories?.data, [
|
||||||
libraryItem.attributes.metadata &&
|
"attributes",
|
||||||
libraryItem.attributes.metadata.length > 0 &&
|
] as const).map((category) => category.attributes.short)}
|
||||||
libraryItem.attributes.metadata[0]
|
metadata={{
|
||||||
? [
|
releaseDate: libraryItem.attributes.release_date,
|
||||||
prettyItemSubType(
|
price: libraryItem.attributes.price,
|
||||||
libraryItem.attributes.metadata[0]
|
position: "Bottom",
|
||||||
),
|
}}
|
||||||
]
|
infoAppend={
|
||||||
: []
|
!isUntangibleGroupItem(libraryItem.attributes.metadata?.[0]) && (
|
||||||
}
|
<PreviewCardCTAs id={libraryItem.id} />
|
||||||
bottomChips={filterHasAttributes(
|
)
|
||||||
libraryItem.attributes.categories?.data,
|
}
|
||||||
["attributes"] as const
|
/>
|
||||||
).map((category) => category.attributes.short)}
|
</div>
|
||||||
metadata={{
|
);
|
||||||
releaseDate: libraryItem.attributes.release_date,
|
})}
|
||||||
price: libraryItem.attributes.price,
|
|
||||||
position: "Bottom",
|
|
||||||
}}
|
|
||||||
infoAppend={
|
|
||||||
!isUntangibleGroupItem(
|
|
||||||
libraryItem.attributes.metadata?.[0]
|
|
||||||
) && <PreviewCardCTAs id={libraryItem.id} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
|
@ -350,15 +300,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
{previousContent?.attributes && (
|
{previousContent?.attributes && (
|
||||||
<div className="mt-12 mb-8 w-full">
|
<div className="mt-12 mb-8 w-full">
|
||||||
<h2 className="mb-4 text-center text-2xl">
|
<h2 className="mb-4 text-center text-2xl">{langui.previous_content}</h2>
|
||||||
{langui.previous_content}
|
|
||||||
</h2>
|
|
||||||
<TranslatedPreviewLine
|
<TranslatedPreviewLine
|
||||||
href={`/contents/${previousContent.attributes.slug}`}
|
href={`/contents/${previousContent.attributes.slug}`}
|
||||||
translations={filterHasAttributes(
|
translations={filterHasAttributes(previousContent.attributes.translations, [
|
||||||
previousContent.attributes.translations,
|
"language.data.attributes.code",
|
||||||
["language.data.attributes.code"] as const
|
] as const).map((translation) => ({
|
||||||
).map((translation) => ({
|
|
||||||
pre_title: translation.pre_title,
|
pre_title: translation.pre_title,
|
||||||
title: translation.title,
|
title: translation.title,
|
||||||
subtitle: translation.subtitle,
|
subtitle: translation.subtitle,
|
||||||
|
@ -367,22 +314,14 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
fallback={{
|
fallback={{
|
||||||
title: prettySlug(previousContent.attributes.slug),
|
title: prettySlug(previousContent.attributes.slug),
|
||||||
}}
|
}}
|
||||||
thumbnail={
|
thumbnail={previousContent.attributes.thumbnail?.data?.attributes}
|
||||||
previousContent.attributes.thumbnail?.data?.attributes
|
|
||||||
}
|
|
||||||
thumbnailAspectRatio="3/2"
|
thumbnailAspectRatio="3/2"
|
||||||
topChips={
|
topChips={
|
||||||
isContentPanelAtLeast2xl &&
|
isContentPanelAtLeast2xl && previousContent.attributes.type?.data?.attributes
|
||||||
previousContent.attributes.type?.data?.attributes
|
|
||||||
? [
|
? [
|
||||||
previousContent.attributes.type.data.attributes
|
previousContent.attributes.type.data.attributes.titles?.[0]
|
||||||
.titles?.[0]
|
? previousContent.attributes.type.data.attributes.titles[0]?.title
|
||||||
? previousContent.attributes.type.data.attributes
|
: prettySlug(previousContent.attributes.type.data.attributes.slug),
|
||||||
.titles[0]?.title
|
|
||||||
: prettySlug(
|
|
||||||
previousContent.attributes.type.data.attributes
|
|
||||||
.slug
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
@ -407,15 +346,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
{nextContent?.attributes && (
|
{nextContent?.attributes && (
|
||||||
<>
|
<>
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
<h2 className="mb-4 text-center text-2xl">
|
<h2 className="mb-4 text-center text-2xl">{langui.followup_content}</h2>
|
||||||
{langui.followup_content}
|
|
||||||
</h2>
|
|
||||||
<TranslatedPreviewLine
|
<TranslatedPreviewLine
|
||||||
href={`/contents/${nextContent.attributes.slug}`}
|
href={`/contents/${nextContent.attributes.slug}`}
|
||||||
translations={filterHasAttributes(
|
translations={filterHasAttributes(nextContent.attributes.translations, [
|
||||||
nextContent.attributes.translations,
|
"language.data.attributes.code",
|
||||||
["language.data.attributes.code"] as const
|
] as const).map((translation) => ({
|
||||||
).map((translation) => ({
|
|
||||||
pre_title: translation.pre_title,
|
pre_title: translation.pre_title,
|
||||||
title: translation.title,
|
title: translation.title,
|
||||||
subtitle: translation.subtitle,
|
subtitle: translation.subtitle,
|
||||||
|
@ -425,15 +361,11 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
||||||
thumbnailAspectRatio="3/2"
|
thumbnailAspectRatio="3/2"
|
||||||
topChips={
|
topChips={
|
||||||
isContentPanelAtLeast2xl &&
|
isContentPanelAtLeast2xl && nextContent.attributes.type?.data?.attributes
|
||||||
nextContent.attributes.type?.data?.attributes
|
|
||||||
? [
|
? [
|
||||||
nextContent.attributes.type.data.attributes.titles?.[0]
|
nextContent.attributes.type.data.attributes.titles?.[0]
|
||||||
? nextContent.attributes.type.data.attributes
|
? nextContent.attributes.type.data.attributes.titles[0]?.title
|
||||||
.titles[0]?.title
|
: prettySlug(nextContent.attributes.type.data.attributes.slug),
|
||||||
: prettySlug(
|
|
||||||
nextContent.attributes.type.data.attributes.slug
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
@ -469,13 +401,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
|
||||||
<AppLayout
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
subPanel={subPanel}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default Content;
|
export default Content;
|
||||||
|
|
||||||
|
@ -502,10 +428,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const selectedTranslation = staticSmartLanguage({
|
const selectedTranslation = staticSmartLanguage({
|
||||||
items: content.contents.data[0].attributes.translations,
|
items: content.contents.data[0].attributes.translations,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
preferredLanguages: getDefaultPreferredLanguages(
|
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||||
context.locale,
|
|
||||||
context.locales
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
if (selectedTranslation) {
|
if (selectedTranslation) {
|
||||||
return {
|
return {
|
||||||
|
@ -516,8 +439,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
),
|
),
|
||||||
description: getDescription(selectedTranslation.description, {
|
description: getDescription(selectedTranslation.description, {
|
||||||
[langui.type ?? "Type"]: [
|
[langui.type ?? "Type"]: [
|
||||||
content.contents.data[0].attributes.type?.data?.attributes
|
content.contents.data[0].attributes.type?.data?.attributes?.titles?.[0]?.title,
|
||||||
?.titles?.[0]?.title,
|
|
||||||
],
|
],
|
||||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||||
content.contents.data[0].attributes.categories?.data,
|
content.contents.data[0].attributes.categories?.data,
|
||||||
|
@ -533,8 +455,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const thumbnail =
|
const thumbnail = content.contents.data[0].attributes.thumbnail?.data?.attributes;
|
||||||
content.contents.data[0].attributes.thumbnail?.data?.attributes;
|
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
content: content.contents.data[0].attributes as ContentWithTranslations,
|
content: content.contents.data[0].attributes as ContentWithTranslations,
|
||||||
|
@ -551,16 +472,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const contents = await sdk.getContentsSlugs();
|
const contents = await sdk.getContentsSlugs();
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
|
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map((item) => {
|
||||||
(item) => {
|
context.locales?.map((local) => {
|
||||||
context.locales?.map((local) => {
|
paths.push({
|
||||||
paths.push({
|
params: { slug: item.attributes.slug },
|
||||||
params: { slug: item.attributes.slug },
|
locale: local,
|
||||||
locale: local,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
});
|
||||||
return {
|
return {
|
||||||
paths,
|
paths,
|
||||||
fallback: "blocking",
|
fallback: "blocking",
|
||||||
|
@ -574,9 +493,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
|
|
||||||
type FolderContents = NonNullable<
|
type FolderContents = NonNullable<
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<NonNullable<ContentWithTranslations["folder"]>["data"]>["attributes"]
|
||||||
NonNullable<ContentWithTranslations["folder"]>["data"]
|
|
||||||
>["attributes"]
|
|
||||||
>["contents"]
|
>["contents"]
|
||||||
>["data"];
|
>["data"];
|
||||||
|
|
||||||
|
@ -595,10 +512,7 @@ const getPreviousContent = (contents: FolderContents, currentSlug: string) => {
|
||||||
const getNextContent = (contents: FolderContents, currentSlug: string) => {
|
const getNextContent = (contents: FolderContents, currentSlug: string) => {
|
||||||
for (let index = 0; index < contents.length; index++) {
|
for (let index = 0; index < contents.length; index++) {
|
||||||
const content = contents[index];
|
const content = contents[index];
|
||||||
if (
|
if (content.attributes?.slug === currentSlug && index < contents.length - 1) {
|
||||||
content.attributes?.slug === currentSlug &&
|
|
||||||
index < contents.length - 1
|
|
||||||
) {
|
|
||||||
return contents[index + 1];
|
return contents[index + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Select } from "components/Inputs/Select";
|
import { Select } from "components/Inputs/Select";
|
||||||
import { Switch } from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
||||||
|
@ -18,11 +15,7 @@ import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import {
|
import { filterDefined, filterHasAttributes, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
filterDefined,
|
|
||||||
filterHasAttributes,
|
|
||||||
isDefinedAndNotEmpty,
|
|
||||||
} from "helpers/others";
|
|
||||||
import { GetContentsQuery } from "graphql/generated";
|
import { GetContentsQuery } from "graphql/generated";
|
||||||
import { SmartList } from "components/SmartList";
|
import { SmartList } from "components/SmartList";
|
||||||
import { SelectiveNonNullable } from "types/SelectiveNonNullable";
|
import { SelectiveNonNullable } from "types/SelectiveNonNullable";
|
||||||
|
@ -68,9 +61,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
setValue: setKeepInfoVisible,
|
setValue: setKeepInfoVisible,
|
||||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
|
||||||
);
|
|
||||||
|
|
||||||
const groupingFunction = useCallback(
|
const groupingFunction = useCallback(
|
||||||
(
|
(
|
||||||
|
@ -81,10 +72,9 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
): string[] => {
|
): string[] => {
|
||||||
switch (groupingMethod) {
|
switch (groupingMethod) {
|
||||||
case 0: {
|
case 0: {
|
||||||
const categories = filterHasAttributes(
|
const categories = filterHasAttributes(item.attributes.categories?.data, [
|
||||||
item.attributes.categories?.data,
|
"attributes",
|
||||||
["attributes"] as const
|
] as const);
|
||||||
);
|
|
||||||
if (categories.length > 0) {
|
if (categories.length > 0) {
|
||||||
return categories.map((category) => category.attributes.name);
|
return categories.map((category) => category.attributes.name);
|
||||||
}
|
}
|
||||||
|
@ -107,17 +97,11 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteringFunction = useCallback(
|
const filteringFunction = useCallback(
|
||||||
(
|
(item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id">) => {
|
||||||
item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id">
|
|
||||||
) => {
|
|
||||||
if (searchName.length > 1) {
|
if (searchName.length > 1) {
|
||||||
if (
|
if (
|
||||||
filterDefined(item.attributes.translations).find((translation) =>
|
filterDefined(item.attributes.translations).find((translation) =>
|
||||||
prettyInlineTitle(
|
prettyInlineTitle(translation.pre_title, translation.title, translation.subtitle)
|
||||||
translation.pre_title,
|
|
||||||
translation.title,
|
|
||||||
translation.subtitle
|
|
||||||
)
|
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(searchName.toLowerCase())
|
.includes(searchName.toLowerCase())
|
||||||
)
|
)
|
||||||
|
@ -142,12 +126,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<Button
|
<Button href="/contents" text={langui.switch_to_folder_view} icon={Icon.Folder} />
|
||||||
href="/contents"
|
|
||||||
/* TODO: Langui */
|
|
||||||
text={"Switch to folder view"}
|
|
||||||
icon={Icon.Folder}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
|
@ -173,9 +152,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setGroupingMethod(value);
|
setGroupingMethod(value);
|
||||||
umami(
|
umami(
|
||||||
`[Contents/All] Change grouping method (${
|
`[Contents/All] Change grouping method (${["none", "category", "type"][value + 1]})`
|
||||||
["none", "category", "type"][value + 1]
|
|
||||||
})`
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
allowEmpty
|
allowEmpty
|
||||||
|
@ -188,11 +165,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
value={keepInfoVisible}
|
value={keepInfoVisible}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleKeepInfoVisible();
|
toggleKeepInfoVisible();
|
||||||
umami(
|
umami(`[Contents/All] Always ${keepInfoVisible ? "hide" : "show"} info`);
|
||||||
`[Contents/All] Always ${
|
|
||||||
keepInfoVisible ? "hide" : "show"
|
|
||||||
} info`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</WithLabel>
|
</WithLabel>
|
||||||
|
@ -222,6 +195,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
langui.group_by,
|
langui.group_by,
|
||||||
langui.reset_all_filters,
|
langui.reset_all_filters,
|
||||||
langui.search_title,
|
langui.search_title,
|
||||||
|
langui.switch_to_folder_view,
|
||||||
langui.type,
|
langui.type,
|
||||||
searchName,
|
searchName,
|
||||||
setKeepInfoVisible,
|
setKeepInfoVisible,
|
||||||
|
@ -281,11 +255,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
${item.attributes.slug}
|
${item.attributes.slug}
|
||||||
${filterDefined(item.attributes.translations)
|
${filterDefined(item.attributes.translations)
|
||||||
.map((translation) =>
|
.map((translation) =>
|
||||||
prettyInlineTitle(
|
prettyInlineTitle(translation.pre_title, translation.title, translation.subtitle)
|
||||||
translation.pre_title,
|
|
||||||
translation.title,
|
|
||||||
translation.subtitle
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.join(" ")}`
|
.join(" ")}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,12 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import naturalCompare from "string-natural-compare";
|
import naturalCompare from "string-natural-compare";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { filterHasAttributes } from "helpers/others";
|
import { filterHasAttributes } from "helpers/others";
|
||||||
import { GetContentsFolderQuery } from "graphql/generated";
|
import { GetContentsFolderQuery } from "graphql/generated";
|
||||||
import {
|
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||||
getDefaultPreferredLanguages,
|
|
||||||
staticSmartLanguage,
|
|
||||||
} from "helpers/locales";
|
|
||||||
import { prettySlug } from "helpers/formatters";
|
import { prettySlug } from "helpers/formatters";
|
||||||
import { SmartList } from "components/SmartList";
|
import { SmartList } from "components/SmartList";
|
||||||
import { Ico, Icon } from "components/Ico";
|
import { Ico, Icon } from "components/Ico";
|
||||||
|
@ -37,17 +31,11 @@ import { getLangui } from "graphql/fetchLocalData";
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
interface Props extends AppLayoutRequired {
|
||||||
folder: NonNullable<
|
folder: NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetContentsFolderQuery["contentsFolders"]>["data"][number]["attributes"]
|
||||||
GetContentsFolderQuery["contentsFolders"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentsFolder = ({
|
const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => {
|
||||||
openGraph,
|
|
||||||
folder,
|
|
||||||
...otherProps
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||||
|
|
||||||
|
@ -62,15 +50,10 @@ const ContentsFolder = ({
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<Button
|
<Button href="/contents/all" text={langui.switch_to_grid_view} icon={Icon.Apps} />
|
||||||
href="/contents/all"
|
|
||||||
/* TODO: Langui */
|
|
||||||
text={"Switch to grid view"}
|
|
||||||
icon={Icon.Apps}
|
|
||||||
/>
|
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[langui.contents, langui.contents_description]
|
[langui.contents, langui.contents_description, langui.switch_to_grid_view]
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
@ -84,10 +67,9 @@ const ContentsFolder = ({
|
||||||
) : (
|
) : (
|
||||||
<TranslatedButton
|
<TranslatedButton
|
||||||
href={`/contents/folder/${folder.parent_folder.data.attributes.slug}`}
|
href={`/contents/folder/${folder.parent_folder.data.attributes.slug}`}
|
||||||
translations={filterHasAttributes(
|
translations={filterHasAttributes(folder.parent_folder.data.attributes.titles, [
|
||||||
folder.parent_folder.data.attributes.titles,
|
"language.data.attributes.code",
|
||||||
["language.data.attributes.code"] as const
|
] as const).map((title) => ({
|
||||||
).map((title) => ({
|
|
||||||
language: title.language.data.attributes.code,
|
language: title.language.data.attributes.code,
|
||||||
text: title.title,
|
text: title.title,
|
||||||
}))}
|
}))}
|
||||||
|
@ -119,10 +101,7 @@ const ContentsFolder = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SmartList
|
<SmartList
|
||||||
items={filterHasAttributes(folder.subfolders?.data, [
|
items={filterHasAttributes(folder.subfolders?.data, ["id", "attributes"] as const)}
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const)}
|
|
||||||
getItemId={(item) => item.id}
|
getItemId={(item) => item.id}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<TranslatedPreviewFolder
|
<TranslatedPreviewFolder
|
||||||
|
@ -149,10 +128,7 @@ const ContentsFolder = ({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SmartList
|
<SmartList
|
||||||
items={filterHasAttributes(folder.contents?.data, [
|
items={filterHasAttributes(folder.contents?.data, ["id", "attributes"] as const)}
|
||||||
"id",
|
|
||||||
"attributes",
|
|
||||||
] as const)}
|
|
||||||
getItemId={(item) => item.id}
|
getItemId={(item) => item.id}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<TranslatedPreviewCard
|
<TranslatedPreviewCard
|
||||||
|
@ -193,8 +169,9 @@ const ContentsFolder = ({
|
||||||
groupingFunction={() => [langui.contents ?? "Contents"]}
|
groupingFunction={() => [langui.contents ?? "Contents"]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{folder.contents?.data.length === 0 &&
|
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
|
||||||
folder.subfolders?.data.length === 0 && <NoContentNorFolderMessage />}
|
<NoContentNorFolderMessage />
|
||||||
|
)}
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
|
@ -240,15 +217,15 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
|
|
||||||
const subFolders = {
|
const subFolders = {
|
||||||
// eslint-disable-next-line id-denylist
|
// eslint-disable-next-line id-denylist
|
||||||
data: filterHasAttributes(folder.subfolders?.data, [
|
data: filterHasAttributes(folder.subfolders?.data, ["attributes.slug"]).sort((a, b) =>
|
||||||
"attributes.slug",
|
naturalCompare(a.attributes.slug, b.attributes.slug)
|
||||||
]).sort((a, b) => naturalCompare(a.attributes.slug, b.attributes.slug)),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const contents = {
|
const contents = {
|
||||||
// eslint-disable-next-line id-denylist
|
// eslint-disable-next-line id-denylist
|
||||||
data: filterHasAttributes(folder.contents?.data, ["attributes.slug"]).sort(
|
data: filterHasAttributes(folder.contents?.data, ["attributes.slug"]).sort((a, b) =>
|
||||||
(a, b) => naturalCompare(a.attributes.slug, b.attributes.slug)
|
naturalCompare(a.attributes.slug, b.attributes.slug)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -263,10 +240,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const selectedTranslation = staticSmartLanguage({
|
const selectedTranslation = staticSmartLanguage({
|
||||||
items: folder.titles,
|
items: folder.titles,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
preferredLanguages: getDefaultPreferredLanguages(
|
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||||
context.locale,
|
|
||||||
context.locales
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
if (selectedTranslation) {
|
if (selectedTranslation) {
|
||||||
return selectedTranslation.title;
|
return selectedTranslation.title;
|
||||||
|
@ -290,9 +264,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const contents = await sdk.getContentsFoldersSlugs();
|
const contents = await sdk.getContentsFoldersSlugs();
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
filterHasAttributes(contents.contentsFolders?.data, [
|
filterHasAttributes(contents.contentsFolders?.data, ["attributes"] as const).map((item) => {
|
||||||
"attributes",
|
|
||||||
] as const).map((item) => {
|
|
||||||
context.locales?.map((local) => {
|
context.locales?.map((local) => {
|
||||||
paths.push({
|
paths.push({
|
||||||
params: { slug: item.attributes.slug },
|
params: { slug: item.attributes.slug },
|
||||||
|
@ -320,13 +292,8 @@ const PreviewFolder = ({ href, title }: PreviewFolderProps): JSX.Element => (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
className="flex w-full cursor-pointer flex-row place-content-center place-items-center gap-4
|
className="flex w-full cursor-pointer flex-row place-content-center place-items-center gap-4
|
||||||
rounded-md bg-light p-6 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
|
rounded-md bg-light p-6 transition-transform drop-shadow-shade-xl hover:scale-[1.02]">
|
||||||
>
|
{title && <p className="text-center font-headers text-lg font-bold leading-none">{title}</p>}
|
||||||
{title && (
|
|
||||||
<p className="text-center font-headers text-lg font-bold leading-none">
|
|
||||||
{title}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -339,17 +306,9 @@ const TranslatedPreviewFolder = ({
|
||||||
}: TranslatedProps<PreviewFolderProps, "title">): JSX.Element => {
|
}: TranslatedProps<PreviewFolderProps, "title">): JSX.Element => {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
return (
|
return <PreviewFolder title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||||
<PreviewFolder
|
|
||||||
title={selectedTranslation?.title ?? fallback.title}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
@ -360,8 +319,7 @@ const NoContentNorFolderMessage = () => {
|
||||||
<div className="grid place-content-center">
|
<div className="grid place-content-center">
|
||||||
<div
|
<div
|
||||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||||
border-dark p-8 text-dark opacity-40"
|
border-dark p-8 text-dark opacity-40">
|
||||||
>
|
|
||||||
<p className="max-w-xs text-2xl">{langui.empty_folder_message}</p>
|
<p className="max-w-xs text-2xl">{langui.empty_folder_message}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { GetStaticProps } from "next";
|
import { GetStaticProps } from "next";
|
||||||
import ContentsFolder, {
|
import ContentsFolder, { getStaticProps as folderGetStaticProps } from "./folder/[slug]";
|
||||||
getStaticProps as folderGetStaticProps,
|
|
||||||
} from "./folder/[slug]";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
|
|
@ -3,10 +3,7 @@ import { useMemo } from "react";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Chip } from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { DevGetContentsQuery } from "graphql/generated";
|
import { DevGetContentsQuery } from "graphql/generated";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
@ -14,6 +11,7 @@ import { filterDefined, filterHasAttributes } from "helpers/others";
|
||||||
import { Report, Severity } from "types/Report";
|
import { Report, Severity } from "types/Report";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
import { getLangui } from "graphql/fetchLocalData";
|
||||||
|
import { sJoin } from "helpers/formatters";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -49,20 +47,9 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="mb-2 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center
|
className="mb-2 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center
|
||||||
justify-items-start gap-2"
|
justify-items-start gap-2">
|
||||||
>
|
<Button href={line.frontendUrl} className="w-4 text-xs" text="F" alwaysNewTab />
|
||||||
<Button
|
<Button href={line.backendUrl} className="w-4 text-xs" text="B" alwaysNewTab />
|
||||||
href={line.frontendUrl}
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="F"
|
|
||||||
alwaysNewTab
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
href={line.backendUrl}
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="B"
|
|
||||||
alwaysNewTab
|
|
||||||
/>
|
|
||||||
<p>{line.subitems.join(" -> ")}</p>
|
<p>{line.subitems.join(" -> ")}</p>
|
||||||
<p>{line.name}</p>
|
<p>{line.name}</p>
|
||||||
<Chip text={line.type} />
|
<Chip text={line.type} />
|
||||||
|
@ -121,164 +108,157 @@ const testingContent = (contents: Props["contents"]): Report => {
|
||||||
lines: [],
|
lines: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
|
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map((content) => {
|
||||||
(content) => {
|
const backendUrl = sJoin(
|
||||||
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`;
|
process.env.NEXT_PUBLIC_URL_CMS,
|
||||||
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
|
"/admin/content-manager/collectionType/api::content.content/",
|
||||||
|
content.id
|
||||||
|
);
|
||||||
|
const frontendUrl = sJoin(
|
||||||
|
process.env.NEXT_PUBLIC_URL_SELF,
|
||||||
|
"/contents/",
|
||||||
|
content.attributes.slug
|
||||||
|
);
|
||||||
|
|
||||||
if (content.attributes.categories?.data.length === 0) {
|
if (content.attributes.categories?.data.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [content.attributes.slug],
|
subitems: [content.attributes.slug],
|
||||||
name: "No Category",
|
name: "No Category",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description: "The Content has no Category.",
|
description: "The Content has no Category.",
|
||||||
recommandation: "Select a Category in relation with the Content",
|
recommandation: "Select a Category in relation with the Content",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content.attributes.type?.data?.id) {
|
if (!content.attributes.type?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [content.attributes.slug],
|
subitems: [content.attributes.slug],
|
||||||
name: "No Type",
|
name: "No Type",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description: "The Content has no Type.",
|
description: "The Content has no Type.",
|
||||||
recommandation: 'If unsure, use the "Other" Type.',
|
recommandation: 'If unsure, use the "Other" Type.',
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.attributes.ranged_contents?.data.length === 0) {
|
if (content.attributes.ranged_contents?.data.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [content.attributes.slug],
|
subitems: [content.attributes.slug],
|
||||||
name: "No Ranged Content",
|
name: "No Ranged Content",
|
||||||
type: "Improvement",
|
type: "Improvement",
|
||||||
severity: Severity.Low,
|
severity: Severity.Low,
|
||||||
description: "The Content has no Ranged Content.",
|
description: "The Content has no Ranged Content.",
|
||||||
recommandation:
|
recommandation:
|
||||||
"If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).",
|
"If this Content is available in one or multiple Library Item(s),\
|
||||||
backendUrl: backendUrl,
|
create a Range Content to connect the Content to its Library Item(s).",
|
||||||
frontendUrl: frontendUrl,
|
backendUrl: backendUrl,
|
||||||
});
|
frontendUrl: frontendUrl,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!content.attributes.thumbnail?.data?.id) {
|
if (!content.attributes.thumbnail?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [content.attributes.slug],
|
subitems: [content.attributes.slug],
|
||||||
name: "No Thumbnail",
|
name: "No Thumbnail",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description: "The Content has no Thumbnail.",
|
description: "The Content has no Thumbnail.",
|
||||||
recommandation: "",
|
recommandation: "",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.attributes.translations?.length === 0) {
|
if (content.attributes.translations?.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [content.attributes.slug],
|
subitems: [content.attributes.slug],
|
||||||
name: "No Titles",
|
name: "No Titles",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description: "The Content has no Titles.",
|
description: "The Content has no Titles.",
|
||||||
recommandation: "",
|
recommandation: "",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const titleLanguages: string[] = [];
|
||||||
|
|
||||||
|
if (content.attributes.translations && content.attributes.translations.length > 0) {
|
||||||
|
filterDefined(content.attributes.translations).map((translation, titleIndex) => {
|
||||||
|
if (translation.language?.data?.id) {
|
||||||
|
if (translation.language.data.id in titleLanguages) {
|
||||||
|
report.lines.push({
|
||||||
|
subitems: [content.attributes.slug, `Title ${titleIndex.toString()}`],
|
||||||
|
name: "Duplicate Language",
|
||||||
|
type: "Error",
|
||||||
|
severity: Severity.High,
|
||||||
|
description: "",
|
||||||
|
recommandation: "",
|
||||||
|
backendUrl: backendUrl,
|
||||||
|
frontendUrl: frontendUrl,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
titleLanguages.push(translation.language.data.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
report.lines.push({
|
||||||
|
subitems: [content.attributes.slug, `Title ${titleIndex.toString()}`],
|
||||||
|
name: "No Language",
|
||||||
|
type: "Error",
|
||||||
|
severity: Severity.VeryHigh,
|
||||||
|
description: "",
|
||||||
|
recommandation: "",
|
||||||
|
backendUrl: backendUrl,
|
||||||
|
frontendUrl: frontendUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!translation.description) {
|
||||||
|
report.lines.push({
|
||||||
|
subitems: [content.attributes.slug, `Title ${titleIndex.toString()}`],
|
||||||
|
name: "No Description",
|
||||||
|
type: "Missing",
|
||||||
|
severity: Severity.Medium,
|
||||||
|
description: "",
|
||||||
|
recommandation: "",
|
||||||
|
backendUrl: backendUrl,
|
||||||
|
frontendUrl: frontendUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!translation.text_set) {
|
||||||
|
report.lines.push({
|
||||||
|
subitems: [
|
||||||
|
content.attributes.slug,
|
||||||
|
translation.language?.data?.attributes?.code ?? "",
|
||||||
|
],
|
||||||
|
name: "No Text Set",
|
||||||
|
type: "Missing",
|
||||||
|
severity: Severity.High,
|
||||||
|
description: "The Content has no Text Set.",
|
||||||
|
recommandation: "",
|
||||||
|
backendUrl: backendUrl,
|
||||||
|
frontendUrl: frontendUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const titleLanguages: string[] = [];
|
report.lines.push({
|
||||||
|
subitems: [content.attributes.slug],
|
||||||
if (
|
name: "No Translations",
|
||||||
content.attributes.translations &&
|
type: "Missing",
|
||||||
content.attributes.translations.length > 0
|
severity: Severity.High,
|
||||||
) {
|
description: "The Content has no Translations.",
|
||||||
filterDefined(content.attributes.translations).map(
|
recommandation: "",
|
||||||
(translation, titleIndex) => {
|
backendUrl: backendUrl,
|
||||||
if (translation.language?.data?.id) {
|
frontendUrl: frontendUrl,
|
||||||
if (translation.language.data.id in titleLanguages) {
|
});
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`Title ${titleIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Duplicate Language",
|
|
||||||
type: "Error",
|
|
||||||
severity: Severity.High,
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
titleLanguages.push(translation.language.data.id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`Title ${titleIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Language",
|
|
||||||
type: "Error",
|
|
||||||
severity: Severity.VeryHigh,
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!translation.description) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
`Title ${titleIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Description",
|
|
||||||
type: "Missing",
|
|
||||||
severity: Severity.Medium,
|
|
||||||
description: "",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!translation.text_set) {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [
|
|
||||||
content.attributes.slug,
|
|
||||||
translation.language?.data?.attributes?.code ?? "",
|
|
||||||
],
|
|
||||||
name: "No Text Set",
|
|
||||||
type: "Missing",
|
|
||||||
severity: Severity.High,
|
|
||||||
description: "The Content has no Text Set.",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
report.lines.push({
|
|
||||||
subitems: [content.attributes.slug],
|
|
||||||
name: "No Translations",
|
|
||||||
type: "Missing",
|
|
||||||
severity: Severity.High,
|
|
||||||
description: "The Content has no Translations.",
|
|
||||||
recommandation: "",
|
|
||||||
backendUrl: backendUrl,
|
|
||||||
frontendUrl: frontendUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
return report;
|
return report;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,10 +3,7 @@ import { useMemo } from "react";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Chip } from "components/Chip";
|
import { Chip } from "components/Chip";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import {
|
import {
|
||||||
DevGetLibraryItemsQuery,
|
DevGetLibraryItemsQuery,
|
||||||
|
@ -16,6 +13,7 @@ import { getReadySdk } from "graphql/sdk";
|
||||||
import { Report, Severity } from "types/Report";
|
import { Report, Severity } from "types/Report";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
import { getLangui } from "graphql/fetchLocalData";
|
||||||
|
import { sJoin } from "helpers/formatters";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -26,10 +24,7 @@ interface Props extends AppLayoutRequired {
|
||||||
libraryItems: DevGetLibraryItemsQuery;
|
libraryItems: DevGetLibraryItemsQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckupLibraryItems = ({
|
const CheckupLibraryItems = ({ libraryItems, ...otherProps }: Props): JSX.Element => {
|
||||||
libraryItems,
|
|
||||||
...otherProps
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const testReport = testingLibraryItem(libraryItems);
|
const testReport = testingLibraryItem(libraryItems);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
@ -54,20 +49,9 @@ const CheckupLibraryItems = ({
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="mb-2 grid
|
className="mb-2 grid
|
||||||
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2"
|
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2">
|
||||||
>
|
<Button href={line.frontendUrl} className="w-4 text-xs" text="F" alwaysNewTab />
|
||||||
<Button
|
<Button href={line.backendUrl} className="w-4 text-xs" text="B" alwaysNewTab />
|
||||||
href={line.frontendUrl}
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="F"
|
|
||||||
alwaysNewTab
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
href={line.backendUrl}
|
|
||||||
className="w-4 text-xs"
|
|
||||||
text="B"
|
|
||||||
alwaysNewTab
|
|
||||||
/>
|
|
||||||
<p>{line.subitems.join(" -> ")}</p>
|
<p>{line.subitems.join(" -> ")}</p>
|
||||||
<p>{line.name}</p>
|
<p>{line.name}</p>
|
||||||
<Chip text={line.type} />
|
<Chip text={line.type} />
|
||||||
|
@ -129,8 +113,16 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
|
|
||||||
libraryItems.libraryItems?.data.map((item) => {
|
libraryItems.libraryItems?.data.map((item) => {
|
||||||
if (item.attributes) {
|
if (item.attributes) {
|
||||||
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::library-item.library-item/${item.id}`;
|
const backendUrl = sJoin(
|
||||||
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/library/${item.attributes.slug}`;
|
process.env.NEXT_PUBLIC_URL_CMS,
|
||||||
|
"/admin/content-manager/collectionType/api::library-item.library-item/",
|
||||||
|
item.id
|
||||||
|
);
|
||||||
|
const frontendUrl = sJoin(
|
||||||
|
process.env.NEXT_PUBLIC_URL_SELF,
|
||||||
|
"/library/",
|
||||||
|
item.attributes.slug
|
||||||
|
);
|
||||||
|
|
||||||
if (item.attributes.categories?.data.length === 0) {
|
if (item.attributes.categories?.data.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
|
@ -145,17 +137,13 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!item.attributes.root_item && item.attributes.subitem_of?.data.length === 0) {
|
||||||
!item.attributes.root_item &&
|
|
||||||
item.attributes.subitem_of?.data.length === 0
|
|
||||||
) {
|
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [item.attributes.slug],
|
subitems: [item.attributes.slug],
|
||||||
name: "Disconnected Item",
|
name: "Disconnected Item",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.VeryHigh,
|
severity: Severity.VeryHigh,
|
||||||
description:
|
description: "The Item is neither a Root Item, nor is it a subitem of another item.",
|
||||||
"The Item is neither a Root Item, nor is it a subitem of another item.",
|
|
||||||
recommandation: "",
|
recommandation: "",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
|
@ -207,10 +195,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
if (image.language?.data?.id) {
|
if (image.language?.data?.id) {
|
||||||
if (image.language.data.id in imagesLanguages) {
|
if (image.language.data.id in imagesLanguages) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Duplicate Language",
|
name: "Duplicate Language",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
|
@ -224,10 +209,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Language",
|
name: "No Language",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.VeryHigh,
|
severity: Severity.VeryHigh,
|
||||||
|
@ -240,10 +222,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
|
|
||||||
if (!image.source_language?.data?.id) {
|
if (!image.source_language?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Source Language",
|
name: "No Source Language",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.VeryHigh,
|
severity: Severity.VeryHigh,
|
||||||
|
@ -254,15 +233,9 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (image.status !== Enum_Componentcollectionscomponentlibraryimages_Status.Done) {
|
||||||
image.status !==
|
|
||||||
Enum_Componentcollectionscomponentlibraryimages_Status.Done
|
|
||||||
) {
|
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Not Done Status",
|
name: "Not Done Status",
|
||||||
type: "Improvement",
|
type: "Improvement",
|
||||||
severity: Severity.Low,
|
severity: Severity.Low,
|
||||||
|
@ -276,15 +249,11 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
if (image.source_language?.data?.id === image.language?.data?.id) {
|
if (image.source_language?.data?.id === image.language?.data?.id) {
|
||||||
if (image.scanners?.data.length === 0) {
|
if (image.scanners?.data.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Scanners",
|
name: "No Scanners",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description:
|
description: "The Item is a Scan but doesn't credit any Scanners.",
|
||||||
"The Item is a Scan but doesn't credit any Scanners.",
|
|
||||||
recommandation: "Add the appropriate Scanners.",
|
recommandation: "Add the appropriate Scanners.",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
|
@ -292,36 +261,26 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (image.cleaners?.data.length === 0) {
|
if (image.cleaners?.data.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Cleaners",
|
name: "No Cleaners",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description:
|
description: "The Item is a Scan but doesn't credit any Cleaners.",
|
||||||
"The Item is a Scan but doesn't credit any Cleaners.",
|
|
||||||
recommandation: "Add the appropriate Cleaners.",
|
recommandation: "Add the appropriate Cleaners.",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (image.typesetters?.data && image.typesetters.data.length > 0) {
|
||||||
image.typesetters?.data &&
|
|
||||||
image.typesetters.data.length > 0
|
|
||||||
) {
|
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Credited Typesetters",
|
name: "Credited Typesetters",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description:
|
description: "The Item is a Scan but credits one or more Typesetters.",
|
||||||
"The Item is a Scan but credits one or more Typesetters.",
|
|
||||||
recommandation:
|
recommandation:
|
||||||
"If appropriate, create a Scanlation Images Set Set with the Typesetters credited there.",
|
"If appropriate, create a Scanlation Images Set\
|
||||||
|
with the Typesetters credited there.",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
|
@ -329,15 +288,11 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
} else {
|
} else {
|
||||||
if (image.typesetters?.data.length === 0) {
|
if (image.typesetters?.data.length === 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Typesetters",
|
name: "No Typesetters",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description:
|
description: "The Item is a Scanlation but doesn't credit any Typesetters.",
|
||||||
"The Item is a Scanlation but doesn't credit any Typesetters.",
|
|
||||||
recommandation: "Add the appropriate Typesetters.",
|
recommandation: "Add the appropriate Typesetters.",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
|
@ -345,17 +300,14 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (image.scanners?.data && image.scanners.data.length > 0) {
|
if (image.scanners?.data && image.scanners.data.length > 0) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "Credited Scanners",
|
name: "Credited Scanners",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
description:
|
description: "The Item is a Scanlation but credits one or more Scanners.",
|
||||||
"The Item is a Scanlation but credits one or more Scanners.",
|
|
||||||
recommandation:
|
recommandation:
|
||||||
"If appropriate, create a Scanners Images Set Set with the Scanners credited there.",
|
"If appropriate, create a Scanners Images Set\
|
||||||
|
with the Scanners credited there.",
|
||||||
backendUrl: backendUrl,
|
backendUrl: backendUrl,
|
||||||
frontendUrl: frontendUrl,
|
frontendUrl: frontendUrl,
|
||||||
});
|
});
|
||||||
|
@ -365,11 +317,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
if (image.cover) {
|
if (image.cover) {
|
||||||
if (!image.cover.front?.data?.id) {
|
if (!image.cover.front?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Cover",
|
|
||||||
],
|
|
||||||
name: "No Front",
|
name: "No Front",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.VeryHigh,
|
severity: Severity.VeryHigh,
|
||||||
|
@ -381,11 +329,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.cover.spine?.data?.id) {
|
if (!image.cover.spine?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Cover",
|
|
||||||
],
|
|
||||||
name: "No spine",
|
name: "No spine",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.Low,
|
severity: Severity.Low,
|
||||||
|
@ -397,11 +341,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.cover.back?.data?.id) {
|
if (!image.cover.back?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Cover",
|
|
||||||
],
|
|
||||||
name: "No Back",
|
name: "No Back",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
|
@ -413,11 +353,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.cover.full?.data?.id) {
|
if (!image.cover.full?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Cover",
|
|
||||||
],
|
|
||||||
name: "No Full",
|
name: "No Full",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.Low,
|
severity: Severity.Low,
|
||||||
|
@ -429,10 +365,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Cover",
|
name: "No Cover",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.Medium,
|
severity: Severity.Medium,
|
||||||
|
@ -542,10 +475,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Dust Jacket",
|
name: "No Dust Jacket",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.VeryLow,
|
severity: Severity.VeryLow,
|
||||||
|
@ -559,11 +489,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
if (image.obi_belt) {
|
if (image.obi_belt) {
|
||||||
if (!image.obi_belt.front?.data?.id) {
|
if (!image.obi_belt.front?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Obi Belt",
|
|
||||||
],
|
|
||||||
name: "No Front",
|
name: "No Front",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.VeryHigh,
|
severity: Severity.VeryHigh,
|
||||||
|
@ -575,11 +501,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.obi_belt.spine?.data?.id) {
|
if (!image.obi_belt.spine?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Obi Belt",
|
|
||||||
],
|
|
||||||
name: "No spine",
|
name: "No spine",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.Low,
|
severity: Severity.Low,
|
||||||
|
@ -591,11 +513,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.obi_belt.back?.data?.id) {
|
if (!image.obi_belt.back?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Obi Belt",
|
|
||||||
],
|
|
||||||
name: "No Back",
|
name: "No Back",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
|
@ -607,11 +525,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.obi_belt.full?.data?.id) {
|
if (!image.obi_belt.full?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Obi Belt",
|
|
||||||
],
|
|
||||||
name: "No Full",
|
name: "No Full",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.Low,
|
severity: Severity.Low,
|
||||||
|
@ -623,11 +537,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.obi_belt.flap_front?.data?.id) {
|
if (!image.obi_belt.flap_front?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Obi Belt",
|
|
||||||
],
|
|
||||||
name: "No Flap Front",
|
name: "No Flap Front",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.Medium,
|
severity: Severity.Medium,
|
||||||
|
@ -639,11 +549,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
if (!image.obi_belt.flap_back?.data?.id) {
|
if (!image.obi_belt.flap_back?.data?.id) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
"Obi Belt",
|
|
||||||
],
|
|
||||||
name: "No Flap Back",
|
name: "No Flap Back",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.Medium,
|
severity: Severity.Medium,
|
||||||
|
@ -655,10 +561,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Images ${imageIndex.toString()}`,
|
|
||||||
],
|
|
||||||
name: "No Obi Belt",
|
name: "No Obi Belt",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.VeryLow,
|
severity: Severity.VeryLow,
|
||||||
|
@ -672,20 +575,14 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (item.attributes.descriptions && item.attributes.descriptions.length > 0) {
|
||||||
item.attributes.descriptions &&
|
|
||||||
item.attributes.descriptions.length > 0
|
|
||||||
) {
|
|
||||||
const descriptionLanguages: string[] = [];
|
const descriptionLanguages: string[] = [];
|
||||||
|
|
||||||
item.attributes.descriptions.map((description, descriptionIndex) => {
|
item.attributes.descriptions.map((description, descriptionIndex) => {
|
||||||
if (description && item.attributes) {
|
if (description && item.attributes) {
|
||||||
if (description.description.length < 10) {
|
if (description.description.length < 10) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Description ${descriptionIndex}`,
|
|
||||||
],
|
|
||||||
name: "No Text",
|
name: "No Text",
|
||||||
type: "Missing",
|
type: "Missing",
|
||||||
severity: Severity.VeryHigh,
|
severity: Severity.VeryHigh,
|
||||||
|
@ -699,10 +596,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
if (description.language?.data?.id) {
|
if (description.language?.data?.id) {
|
||||||
if (description.language.data.id in descriptionLanguages) {
|
if (description.language.data.id in descriptionLanguages) {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Description ${descriptionIndex}`,
|
|
||||||
],
|
|
||||||
name: "Duplicate Language",
|
name: "Duplicate Language",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.High,
|
severity: Severity.High,
|
||||||
|
@ -716,10 +610,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report.lines.push({
|
report.lines.push({
|
||||||
subitems: [
|
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
|
||||||
item.attributes.slug,
|
|
||||||
`Description ${descriptionIndex}`,
|
|
||||||
],
|
|
||||||
name: "No Language",
|
name: "No Language",
|
||||||
type: "Error",
|
type: "Error",
|
||||||
severity: Severity.VeryHigh,
|
severity: Severity.VeryHigh,
|
||||||
|
|
|
@ -4,10 +4,7 @@ import TurndownService from "turndown";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { Popup } from "components/Popup";
|
import { Popup } from "components/Popup";
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
|
@ -59,19 +56,14 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const wrap = useCallback(
|
const wrap = useCallback(
|
||||||
(
|
(wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
|
||||||
wrapper: string,
|
|
||||||
properties?: Record<string, string>,
|
|
||||||
addInnerNewLines?: boolean
|
|
||||||
) => {
|
|
||||||
transformationWrapper((value, selectionStart, selectionEnd) => {
|
transformationWrapper((value, selectionStart, selectionEnd) => {
|
||||||
let prepend = wrapper;
|
let prepend = wrapper;
|
||||||
let append = wrapper;
|
let append = wrapper;
|
||||||
|
|
||||||
if (properties) {
|
if (properties) {
|
||||||
prepend = `<${wrapper}${Object.entries(properties).map(
|
prepend = `<${wrapper}${Object.entries(properties).map(
|
||||||
([propertyName, propertyValue]) =>
|
([propertyName, propertyValue]) => ` ${propertyName}="${propertyValue}"`
|
||||||
` ${propertyName}="${propertyValue}"`
|
|
||||||
)}>`;
|
)}>`;
|
||||||
append = `</${wrapper}>`;
|
append = `</${wrapper}>`;
|
||||||
}
|
}
|
||||||
|
@ -107,17 +99,12 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleWrap = useCallback(
|
const toggleWrap = useCallback(
|
||||||
(
|
(wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
|
||||||
wrapper: string,
|
|
||||||
properties?: Record<string, string>,
|
|
||||||
addInnerNewLines?: boolean
|
|
||||||
) => {
|
|
||||||
if (textAreaRef.current) {
|
if (textAreaRef.current) {
|
||||||
const { value, selectionStart, selectionEnd } = textAreaRef.current;
|
const { value, selectionStart, selectionEnd } = textAreaRef.current;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
value.slice(selectionStart - wrapper.length, selectionStart) ===
|
value.slice(selectionStart - wrapper.length, selectionStart) === wrapper &&
|
||||||
wrapper &&
|
|
||||||
value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper
|
value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper
|
||||||
) {
|
) {
|
||||||
unwrap(wrapper);
|
unwrap(wrapper);
|
||||||
|
@ -132,8 +119,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
const preline = useCallback(
|
const preline = useCallback(
|
||||||
(prepend: string) => {
|
(prepend: string) => {
|
||||||
transformationWrapper((value, selectionStart) => {
|
transformationWrapper((value, selectionStart) => {
|
||||||
const lastNewLine =
|
const lastNewLine = value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
||||||
value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
|
||||||
|
|
||||||
let newValue = "";
|
let newValue = "";
|
||||||
newValue += value.slice(0, lastNewLine);
|
newValue += value.slice(0, lastNewLine);
|
||||||
|
@ -173,10 +159,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||||
<Popup
|
<Popup onClose={() => setConverterOpened(false)} state={converterOpened}>
|
||||||
onClose={() => setConverterOpened(false)}
|
|
||||||
state={converterOpened}
|
|
||||||
>
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="mt-4">Convert HTML to markdown</h2>
|
<h2 className="mt-4">Convert HTML to markdown</h2>
|
||||||
<p>
|
<p>
|
||||||
|
@ -184,8 +167,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<br />
|
<br />
|
||||||
The text will immediatly be converted to valid Markdown.
|
The text will immediatly be converted to valid Markdown.
|
||||||
<br />
|
<br />
|
||||||
You can then copy the converted text and paste it anywhere you
|
You can then copy the converted text and paste it anywhere you want in the editor
|
||||||
want in the editor
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -226,22 +208,15 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<Button onClick={() => preline("##### ")} text={"H5"} />
|
<Button onClick={() => preline("##### ")} text={"H5"} />
|
||||||
<Button onClick={() => preline("###### ")} text={"H6"} />
|
<Button onClick={() => preline("###### ")} text={"H6"} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button icon={Icon.Title} />
|
<Button icon={Icon.Title} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Bold</h3>}>
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Toggle Bold</h3>}
|
|
||||||
>
|
|
||||||
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
|
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Italic</h3>}>
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Toggle Italic</h3>}
|
|
||||||
>
|
|
||||||
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
|
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
|
@ -251,12 +226,11 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">Toggle Inline Code</h3>
|
<h3 className="text-lg">Toggle Inline Code</h3>
|
||||||
<p>
|
<p>
|
||||||
Makes the text monospace (like text from a computer terminal).
|
Makes the text monospace (like text from a computer terminal). Usually used for
|
||||||
Usually used for stylistic purposes in transcripts.
|
stylistic purposes in transcripts.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
|
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
|
@ -267,8 +241,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<h3 className="text-lg">Insert footnote</h3>
|
<h3 className="text-lg">Insert footnote</h3>
|
||||||
<p>When inserted “x”</p>
|
<p>When inserted “x”</p>
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
insert("[^x]");
|
insert("[^x]");
|
||||||
|
@ -284,8 +257,8 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">Transcripts</h3>
|
<h3 className="text-lg">Transcripts</h3>
|
||||||
<p>
|
<p>
|
||||||
Use this to create dialogues and transcripts. Start by adding
|
Use this to create dialogues and transcripts. Start by adding a container, then
|
||||||
a container, then add transcript speech line within.
|
add transcript speech line within.
|
||||||
</p>
|
</p>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<ToolTip
|
<ToolTip
|
||||||
|
@ -294,12 +267,8 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">Transcript container</h3>
|
<h3 className="text-lg">Transcript container</h3>
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
<Button onClick={() => wrap("Transcript", {}, true)} icon={Icon.AddBox} />
|
||||||
<Button
|
|
||||||
onClick={() => wrap("Transcript", {}, true)}
|
|
||||||
icon={Icon.AddBox}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
<ToolTip
|
<ToolTip
|
||||||
placement="right"
|
placement="right"
|
||||||
|
@ -307,13 +276,11 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">Transcript speech line</h3>
|
<h3 className="text-lg">Transcript speech line</h3>
|
||||||
<p>
|
<p>
|
||||||
Use to add a dialogue/transcript line. Change the{" "}
|
Use to add a dialogue/transcript line. Change the <kbd>name</kbd> property
|
||||||
<kbd>name</kbd> property to chang the name of the
|
to chang the name of the speaker
|
||||||
speaker
|
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => wrap("Line", { name: "speaker" })}
|
onClick={() => wrap("Line", { name: "speaker" })}
|
||||||
icon={Icon.RecordVoiceOver}
|
icon={Icon.RecordVoiceOver}
|
||||||
|
@ -321,24 +288,14 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button icon={Icon.RecordVoiceOver} />
|
<Button icon={Icon.RecordVoiceOver} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip placement="bottom" content={<h3 className="text-lg">Inset box</h3>}>
|
||||||
placement="bottom"
|
<Button onClick={() => wrap("InsetBox", {}, true)} icon={Icon.CheckBoxOutlineBlank} />
|
||||||
content={<h3 className="text-lg">Inset box</h3>}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => wrap("InsetBox", {}, true)}
|
|
||||||
icon={Icon.CheckBoxOutlineBlank}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
<ToolTip
|
<ToolTip placement="bottom" content={<h3 className="text-lg">Scene break</h3>}>
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Scene break</h3>}
|
|
||||||
>
|
|
||||||
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
|
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
<ToolTip
|
<ToolTip
|
||||||
|
@ -350,12 +307,9 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">External Link</h3>
|
<h3 className="text-lg">External Link</h3>
|
||||||
<p className="text-xs">
|
<p className="text-xs">Provides a link to another webpage / website</p>
|
||||||
Provides a link to another webpage / website
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => insert("[Link name](https://domain.com)")}
|
onClick={() => insert("[Link name](https://domain.com)")}
|
||||||
icon={Icon.Link}
|
icon={Icon.Link}
|
||||||
|
@ -369,12 +323,10 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">Intralink</h3>
|
<h3 className="text-lg">Intralink</h3>
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
Interlinks are used to add links to a header within the
|
Interlinks are used to add links to a header within the same document
|
||||||
same document
|
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => wrap("IntraLink", {})}
|
onClick={() => wrap("IntraLink", {})}
|
||||||
icon={Icon.Link}
|
icon={Icon.Link}
|
||||||
|
@ -387,12 +339,11 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
<h3 className="text-lg">Intralink (with target)</h3>{" "}
|
<h3 className="text-lg">Intralink (with target)</h3>{" "}
|
||||||
<p className="text-xs">
|
<p className="text-xs">
|
||||||
Use this one if you want the intralink text to be
|
Use this one if you want the intralink text to be different from the target
|
||||||
different from the target header’s name.
|
header’s name.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => wrap("IntraLink", { target: "target" })}
|
onClick={() => wrap("IntraLink", { target: "target" })}
|
||||||
icon={Icon.Link}
|
icon={Icon.Link}
|
||||||
|
@ -400,24 +351,17 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button icon={Icon.Link} />
|
<Button icon={Icon.Link} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
content={
|
content={<h3 className="text-lg">Player’s name placeholder</h3>}>
|
||||||
<h3 className="text-lg">Player’s name placeholder</h3>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button onClick={() => insert("@player")} icon={Icon.Person} />
|
<Button onClick={() => insert("@player")} icon={Icon.Person} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
|
||||||
<ToolTip
|
<ToolTip placement="bottom" content={<h3 className="text-lg">Open HTML Converter</h3>}>
|
||||||
placement="bottom"
|
|
||||||
content={<h3 className="text-lg">Open HTML Converter</h3>}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setConverterOpened(true);
|
setConverterOpened(true);
|
||||||
|
@ -455,16 +399,7 @@ const Editor = (props: Props): JSX.Element => {
|
||||||
</div>
|
</div>
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
),
|
),
|
||||||
[
|
[appendDoc, converterOpened, handleInput, insert, markdown, preline, toggleWrap, wrap]
|
||||||
appendDoc,
|
|
||||||
converterOpened,
|
|
||||||
handleInput,
|
|
||||||
insert,
|
|
||||||
markdown,
|
|
||||||
preline,
|
|
||||||
toggleWrap,
|
|
||||||
wrap,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return <AppLayout contentPanel={contentPanel} {...props} />;
|
return <AppLayout contentPanel={contentPanel} {...props} />;
|
||||||
|
|
|
@ -3,10 +3,7 @@ import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
import { getLangui } from "graphql/fetchLocalData";
|
||||||
|
@ -30,10 +27,7 @@ const replaceSelection = (
|
||||||
selectionStart: number,
|
selectionStart: number,
|
||||||
selectionEnd: number,
|
selectionEnd: number,
|
||||||
newSelectedText: string
|
newSelectedText: string
|
||||||
) =>
|
) => text.substring(0, selectionStart) + newSelectedText + text.substring(selectionEnd);
|
||||||
text.substring(0, selectionStart) +
|
|
||||||
newSelectedText +
|
|
||||||
text.substring(selectionEnd);
|
|
||||||
|
|
||||||
const swapChar = (char: string, swaps: string[]): string => {
|
const swapChar = (char: string, swaps: string[]): string => {
|
||||||
for (let index = 0; index < swaps.length; index++) {
|
for (let index = 0; index < swaps.length; index++) {
|
||||||
|
@ -60,10 +54,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
|
|
||||||
const updateLineIndex = useCallback(() => {
|
const updateLineIndex = useCallback(() => {
|
||||||
if (textAreaRef.current) {
|
if (textAreaRef.current) {
|
||||||
const subText = textAreaRef.current.value.substring(
|
const subText = textAreaRef.current.value.substring(0, textAreaRef.current.selectionStart);
|
||||||
0,
|
|
||||||
textAreaRef.current.selectionStart
|
|
||||||
);
|
|
||||||
setLineIndex(subText.split("\n").length - 1);
|
setLineIndex(subText.split("\n").length - 1);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -202,10 +193,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
textAreaRef.current.selectionStart,
|
textAreaRef.current.selectionStart,
|
||||||
textAreaRef.current.selectionEnd
|
textAreaRef.current.selectionEnd
|
||||||
);
|
);
|
||||||
const selection = textAreaRef.current.value.substring(
|
const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
|
||||||
selectionStart,
|
|
||||||
selectionEnd
|
|
||||||
);
|
|
||||||
if (selection.length === 1) {
|
if (selection.length === 1) {
|
||||||
let newSelection = selection;
|
let newSelection = selection;
|
||||||
|
|
||||||
|
@ -301,10 +289,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
textAreaRef.current.selectionStart,
|
textAreaRef.current.selectionStart,
|
||||||
textAreaRef.current.selectionEnd
|
textAreaRef.current.selectionEnd
|
||||||
);
|
);
|
||||||
const selection = textAreaRef.current.value.substring(
|
const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
|
||||||
selectionStart,
|
|
||||||
selectionEnd
|
|
||||||
);
|
|
||||||
if (selection.length === 1) {
|
if (selection.length === 1) {
|
||||||
let newSelection = selection;
|
let newSelection = selection;
|
||||||
|
|
||||||
|
@ -376,10 +361,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<ContentPanel
|
<ContentPanel width={ContentPanelWidthSizes.Full} className="overflow-hidden !pr-0 !pt-4">
|
||||||
width={ContentPanelWidthSizes.Full}
|
|
||||||
className="overflow-hidden !pr-0 !pt-4"
|
|
||||||
>
|
|
||||||
<div className="grid grid-flow-col grid-cols-[1fr_5rem]">
|
<div className="grid grid-flow-col grid-cols-[1fr_5rem]">
|
||||||
<textarea
|
<textarea
|
||||||
ref={textAreaRef}
|
ref={textAreaRef}
|
||||||
|
@ -387,18 +369,14 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
onClick={updateLineIndex}
|
onClick={updateLineIndex}
|
||||||
onKeyUp={updateLineIndex}
|
onKeyUp={updateLineIndex}
|
||||||
title="Input textarea"
|
title="Input textarea"
|
||||||
className="whitespace-pre"
|
className="whitespace-pre"></textarea>
|
||||||
></textarea>
|
|
||||||
|
|
||||||
<p
|
<p
|
||||||
className="h-[80vh] whitespace-nowrap font-[initial] font-bold
|
className="h-[80vh] whitespace-nowrap font-[initial] font-bold
|
||||||
[writing-mode:vertical-rl] [transform-origin:top_right]"
|
[writing-mode:vertical-rl] [transform-origin:top_right]"
|
||||||
style={{
|
style={{
|
||||||
transform: `scale(${fontSize}) translateX(${
|
transform: `scale(${fontSize}) translateX(${fontSize * xOffset}px)`,
|
||||||
fontSize * xOffset
|
}}>
|
||||||
}px)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text.split("\n")[lineIndex]}
|
{text.split("\n")[lineIndex]}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -412,10 +390,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={xOffset * 10}
|
value={xOffset * 10}
|
||||||
onChange={(event) =>
|
onChange={(event) => setXOffset(parseInt(event.target.value, 10) / 10)}></input>
|
||||||
setXOffset(parseInt(event.target.value, 10) / 10)
|
|
||||||
}
|
|
||||||
></input>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid place-items-center">
|
<div className="grid place-items-center">
|
||||||
|
@ -428,8 +403,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
value={fontSize * SIZE_MULTIPLIER}
|
value={fontSize * SIZE_MULTIPLIER}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setFontSize(parseInt(event.target.value, 10) / SIZE_MULTIPLIER)
|
setFontSize(parseInt(event.target.value, 10) / SIZE_MULTIPLIER)
|
||||||
}
|
}></input>
|
||||||
></input>
|
|
||||||
</div>
|
</div>
|
||||||
<ToolTip content="Automatically convert Western punctuations to Japanese ones.">
|
<ToolTip content="Automatically convert Western punctuations to Japanese ones.">
|
||||||
<Button text=". ⟹ 。" onClick={convertPunctuation} />
|
<Button text=". ⟹ 。" onClick={convertPunctuation} />
|
||||||
|
@ -526,8 +500,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button text={"Quotations"} />
|
<Button text={"Quotations"} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
<ToolTip
|
<ToolTip
|
||||||
|
@ -543,8 +516,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
<Button text={"〇"} onClick={() => insert("〇")} />
|
<Button text={"〇"} onClick={() => insert("〇")} />
|
||||||
<Button text={'" "'} onClick={() => insert(" ")} />
|
<Button text={'" "'} onClick={() => insert(" ")} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<Button text="Insert" />
|
<Button text="Insert" />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -565,13 +537,7 @@ const Transcript = (props: Props): JSX.Element => {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout contentPanel={contentPanel} {...props} contentPanelScroolbar={false} />;
|
||||||
<AppLayout
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
{...props}
|
|
||||||
contentPanelScroolbar={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default Transcript;
|
export default Transcript;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { PostPage } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import {
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
getPostStaticProps,
|
|
||||||
PostStaticProps,
|
|
||||||
} from "graphql/getPostStaticProps";
|
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -23,9 +20,7 @@ const Home = ({ ...otherProps }: PostStaticProps): JSX.Element => {
|
||||||
[mask-size:contain] [mask-repeat:no-repeat] [mask-position:center]"
|
[mask-size:contain] [mask-repeat:no-repeat] [mask-position:center]"
|
||||||
/>
|
/>
|
||||||
<h1 className="mb-0 text-5xl">Accord’s Library</h1>
|
<h1 className="mb-0 text-5xl">Accord’s Library</h1>
|
||||||
<h2 className="-mt-5 text-xl">
|
<h2 className="-mt-5 text-xl">Discover • Analyze • Translate • Archive</h2>
|
||||||
Discover • Analyze • Translate • Archive
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
displayTitle={false}
|
displayTitle={false}
|
||||||
|
|
|
@ -11,10 +11,7 @@ import { InsetBox } from "components/InsetBox";
|
||||||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||||
import { NavOption } from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { PreviewCard } from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
@ -64,13 +61,7 @@ import { Ids } from "types/ids";
|
||||||
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
|
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const intersectionIds = [
|
const intersectionIds = ["summary", "gallery", "details", "subitems", "contents"];
|
||||||
"summary",
|
|
||||||
"gallery",
|
|
||||||
"details",
|
|
||||||
"subitems",
|
|
||||||
"contents",
|
|
||||||
];
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ╭────────╮
|
* ╭────────╮
|
||||||
|
@ -78,14 +69,8 @@ const intersectionIds = [
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
interface Props extends AppLayoutRequired {
|
||||||
item: NonNullable<
|
item: NonNullable<NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["attributes"]>;
|
||||||
NonNullable<
|
itemId: NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["id"];
|
||||||
GetLibraryItemQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>;
|
|
||||||
itemId: NonNullable<
|
|
||||||
GetLibraryItemQuery["libraryItems"]
|
|
||||||
>["data"][number]["id"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
|
@ -97,8 +82,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
const hoverable = useDeviceSupportsHover();
|
const hoverable = useDeviceSupportsHover();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
|
||||||
useBoolean(false);
|
|
||||||
|
|
||||||
useScrollTopOnChange(Ids.ContentPanel, [item]);
|
useScrollTopOnChange(Ids.ContentPanel, [item]);
|
||||||
const currentIntersection = useIntersectionList(intersectionIds);
|
const currentIntersection = useIntersectionList(intersectionIds);
|
||||||
|
@ -113,8 +97,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
const displayOpenScans = useMemo(
|
const displayOpenScans = useMemo(
|
||||||
() =>
|
() =>
|
||||||
item.contents?.data.some(
|
item.contents?.data.some(
|
||||||
(content) =>
|
(content) => content.attributes?.scan_set && content.attributes.scan_set.length > 0
|
||||||
content.attributes?.scan_set && content.attributes.scan_set.length > 0
|
|
||||||
),
|
),
|
||||||
[item.contents?.data]
|
[item.contents?.data]
|
||||||
);
|
);
|
||||||
|
@ -122,11 +105,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<ReturnButton
|
<ReturnButton href="/library/" title={langui.library} displayOnlyOn="3ColumnsLayout" />
|
||||||
href="/library/"
|
|
||||||
title={langui.library}
|
|
||||||
displayOnlyOn="3ColumnsLayout"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
|
@ -174,14 +153,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
</div>
|
</div>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[
|
[currentIntersection, isVariantSet, item.contents, item.gallery, item.subitems, langui]
|
||||||
currentIntersection,
|
|
||||||
isVariantSet,
|
|
||||||
item.contents,
|
|
||||||
item.gallery,
|
|
||||||
item.subitems,
|
|
||||||
langui,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
@ -203,15 +175,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item.thumbnail?.data?.attributes) {
|
if (item.thumbnail?.data?.attributes) {
|
||||||
openLightBox([
|
openLightBox([getAssetURL(item.thumbnail.data.attributes.url, ImageQuality.Large)]);
|
||||||
getAssetURL(
|
|
||||||
item.thumbnail.data.attributes.url,
|
|
||||||
ImageQuality.Large
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{item.thumbnail?.data?.attributes ? (
|
{item.thumbnail?.data?.attributes ? (
|
||||||
<Img
|
<Img
|
||||||
src={item.thumbnail.data.attributes}
|
src={item.thumbnail.data.attributes}
|
||||||
|
@ -245,39 +211,28 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isUntangibleGroupItem(item.metadata?.[0]) &&
|
{!isUntangibleGroupItem(item.metadata?.[0]) && isDefinedAndNotEmpty(itemId) && (
|
||||||
isDefinedAndNotEmpty(itemId) && (
|
<PreviewCardCTAs id={itemId} expand />
|
||||||
<PreviewCardCTAs id={itemId} expand />
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{item.descriptions?.[0] && (
|
{item.descriptions?.[0] && (
|
||||||
<p className="text-justify">
|
<p className="text-justify">{item.descriptions[0].description}</p>
|
||||||
{item.descriptions[0].description}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
{!(
|
{!(
|
||||||
item.metadata &&
|
item.metadata &&
|
||||||
item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
|
item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
|
||||||
(item.metadata[0].subtype?.data?.attributes?.slug ===
|
(item.metadata[0].subtype?.data?.attributes?.slug === "variant-set" ||
|
||||||
"variant-set" ||
|
item.metadata[0].subtype?.data?.attributes?.slug === "relation-set")
|
||||||
item.metadata[0].subtype?.data?.attributes?.slug ===
|
|
||||||
"relation-set")
|
|
||||||
) && (
|
) && (
|
||||||
<>
|
<>
|
||||||
{item.urls?.length ? (
|
{item.urls?.length ? (
|
||||||
<div className="flex flex-row place-items-center gap-3">
|
<div className="flex flex-row place-items-center gap-3">
|
||||||
<p>{langui.available_at}</p>
|
<p>{langui.available_at}</p>
|
||||||
{filterHasAttributes(item.urls, ["url"] as const).map(
|
{filterHasAttributes(item.urls, ["url"] as const).map((url, index) => (
|
||||||
(url, index) => (
|
<Fragment key={index}>
|
||||||
<Fragment key={index}>
|
<Button href={url.url} text={prettyURL(url.url)} alwaysNewTab />
|
||||||
<Button
|
</Fragment>
|
||||||
href={url.url}
|
))}
|
||||||
text={prettyURL(url.url)}
|
|
||||||
alwaysNewTab
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p>{langui.item_not_available}</p>
|
<p>{langui.item_not_available}</p>
|
||||||
|
@ -288,41 +243,34 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
</InsetBox>
|
</InsetBox>
|
||||||
|
|
||||||
{item.gallery && item.gallery.data.length > 0 && (
|
{item.gallery && item.gallery.data.length > 0 && (
|
||||||
<div
|
<div id={intersectionIds[1]} className="grid w-full place-items-center gap-8">
|
||||||
id={intersectionIds[1]}
|
|
||||||
className="grid w-full place-items-center gap-8"
|
|
||||||
>
|
|
||||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||||
<div
|
<div
|
||||||
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
||||||
gap-8"
|
gap-8">
|
||||||
>
|
{filterHasAttributes(item.gallery.data, ["id", "attributes"] as const).map(
|
||||||
{filterHasAttributes(item.gallery.data, [
|
(galleryItem, index) => (
|
||||||
"id",
|
<Fragment key={galleryItem.id}>
|
||||||
"attributes",
|
<div
|
||||||
] as const).map((galleryItem, index) => (
|
className="relative aspect-square cursor-pointer
|
||||||
<Fragment key={galleryItem.id}>
|
|
||||||
<div
|
|
||||||
className="relative aspect-square cursor-pointer
|
|
||||||
transition-transform hover:scale-[1.02]"
|
transition-transform hover:scale-[1.02]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const images: string[] = filterHasAttributes(
|
const images: string[] = filterHasAttributes(item.gallery?.data, [
|
||||||
item.gallery?.data,
|
"attributes",
|
||||||
["attributes"] as const
|
] as const).map((image) =>
|
||||||
).map((image) =>
|
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
);
|
||||||
);
|
openLightBox(images, index);
|
||||||
openLightBox(images, index);
|
}}>
|
||||||
}}
|
<Img
|
||||||
>
|
className="h-full w-full rounded-lg
|
||||||
<Img
|
|
||||||
className="h-full w-full rounded-lg
|
|
||||||
bg-light object-cover drop-shadow-shade-md"
|
bg-light object-cover drop-shadow-shade-md"
|
||||||
src={galleryItem.attributes}
|
src={galleryItem.attributes}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -333,12 +281,8 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid place-items-center gap-y-8",
|
"grid place-items-center gap-y-8",
|
||||||
cIf(
|
cIf(!isContentPanelNoMoreThan3xl, "grid-flow-col place-content-between")
|
||||||
!isContentPanelNoMoreThan3xl,
|
)}>
|
||||||
"grid-flow-col place-content-between"
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{item.metadata?.[0] && (
|
{item.metadata?.[0] && (
|
||||||
<div className="grid place-content-start place-items-center">
|
<div className="grid place-content-start place-items-center">
|
||||||
<h3 className="text-xl">{langui.type}</h3>
|
<h3 className="text-xl">{langui.type}</h3>
|
||||||
|
@ -367,8 +311,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
item.price.currency?.data?.attributes?.code
|
item.price.currency?.data?.attributes?.code
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{item.price.currency?.data?.attributes?.code !==
|
{item.price.currency?.data?.attributes?.code !== currency && (
|
||||||
currency && (
|
|
||||||
<p>
|
<p>
|
||||||
{prettyPrice(item.price, currencies, currency)} <br />(
|
{prettyPrice(item.price, currencies, currency)} <br />(
|
||||||
{langui.calculated?.toLowerCase()})
|
{langui.calculated?.toLowerCase()})
|
||||||
|
@ -382,11 +325,11 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
<div className="flex flex-col place-items-center gap-2">
|
<div className="flex flex-col place-items-center gap-2">
|
||||||
<h3 className="text-xl">{langui.categories}</h3>
|
<h3 className="text-xl">{langui.categories}</h3>
|
||||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||||
{filterHasAttributes(item.categories.data, [
|
{filterHasAttributes(item.categories.data, ["attributes"] as const).map(
|
||||||
"attributes",
|
(category) => (
|
||||||
] as const).map((category) => (
|
<Chip key={category.id} text={category.attributes.name} />
|
||||||
<Chip key={category.id} text={category.attributes.name} />
|
)
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -396,8 +339,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid gap-4",
|
"grid gap-4",
|
||||||
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
|
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<h3 className="text-xl">{langui.size}</h3>
|
<h3 className="text-xl">{langui.size}</h3>
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
|
@ -407,8 +349,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
"grid-flow-row place-content-center gap-8",
|
"grid-flow-row place-content-center gap-8",
|
||||||
"grid-flow-col place-content-between"
|
"grid-flow-col place-content-between"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid gap-x-4",
|
"grid gap-x-4",
|
||||||
|
@ -417,8 +358,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
"place-items-center",
|
"place-items-center",
|
||||||
"grid-flow-col place-items-start"
|
"grid-flow-col place-items-start"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<p className="font-bold">{langui.width}:</p>
|
<p className="font-bold">{langui.width}:</p>
|
||||||
<div>
|
<div>
|
||||||
<p>{item.size.width} mm</p>
|
<p>{item.size.width} mm</p>
|
||||||
|
@ -433,8 +373,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
"place-items-center",
|
"place-items-center",
|
||||||
"grid-flow-col place-items-start"
|
"grid-flow-col place-items-start"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<p className="font-bold">{langui.height}:</p>
|
<p className="font-bold">{langui.height}:</p>
|
||||||
<div>
|
<div>
|
||||||
<p>{item.size.height} mm</p>
|
<p>{item.size.height} mm</p>
|
||||||
|
@ -450,8 +389,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
"place-items-center",
|
"place-items-center",
|
||||||
"grid-flow-col place-items-start"
|
"grid-flow-col place-items-start"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<p className="font-bold">{langui.thickness}:</p>
|
<p className="font-bold">{langui.thickness}:</p>
|
||||||
<div>
|
<div>
|
||||||
<p>{item.size.thickness} mm</p>
|
<p>{item.size.thickness} mm</p>
|
||||||
|
@ -469,12 +407,10 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid gap-4",
|
"grid gap-4",
|
||||||
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
|
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<h3 className="text-xl">{langui.type_information}</h3>
|
<h3 className="text-xl">{langui.type_information}</h3>
|
||||||
<div className="flex flex-wrap place-content-between gap-x-8">
|
<div className="flex flex-wrap place-content-between gap-x-8">
|
||||||
{item.metadata?.[0]?.__typename ===
|
{item.metadata?.[0]?.__typename === "ComponentMetadataBooks" && (
|
||||||
"ComponentMetadataBooks" && (
|
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row place-content-start gap-4">
|
<div className="flex flex-row place-content-start gap-4">
|
||||||
<p className="font-bold">{langui.pages}:</p>
|
<p className="font-bold">{langui.pages}:</p>
|
||||||
|
@ -507,9 +443,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
<div className="flex flex-row place-content-start gap-4">
|
<div className="flex flex-row place-content-start gap-4">
|
||||||
<p className="font-bold">{langui.languages}:</p>
|
<p className="font-bold">{langui.languages}:</p>
|
||||||
{item.metadata[0]?.languages?.data.map((lang) => (
|
{item.metadata[0]?.languages?.data.map((lang) => (
|
||||||
<p key={lang.attributes?.code}>
|
<p key={lang.attributes?.code}>{lang.attributes?.name}</p>
|
||||||
{lang.attributes?.name}
|
|
||||||
</p>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -521,130 +455,109 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
</InsetBox>
|
</InsetBox>
|
||||||
|
|
||||||
{item.subitems && item.subitems.data.length > 0 && (
|
{item.subitems && item.subitems.data.length > 0 && (
|
||||||
<div
|
<div id={intersectionIds[3]} className="grid w-full place-items-center gap-8">
|
||||||
id={intersectionIds[3]}
|
<h2 className="text-2xl">{isVariantSet ? langui.variants : langui.subitems}</h2>
|
||||||
className="grid w-full place-items-center gap-8"
|
|
||||||
>
|
|
||||||
<h2 className="text-2xl">
|
|
||||||
{isVariantSet ? langui.variants : langui.subitems}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{hoverable && (
|
{hoverable && (
|
||||||
<WithLabel label={langui.always_show_info}>
|
<WithLabel label={langui.always_show_info}>
|
||||||
<Switch
|
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||||
onClick={toggleKeepInfoVisible}
|
|
||||||
value={keepInfoVisible}
|
|
||||||
/>
|
|
||||||
</WithLabel>
|
</WithLabel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="grid w-full grid-cols-[repeat(auto-fill,minmax(13rem,1fr))]
|
className="grid w-full grid-cols-[repeat(auto-fill,minmax(13rem,1fr))]
|
||||||
items-end gap-8"
|
items-end gap-8">
|
||||||
>
|
{filterHasAttributes(item.subitems.data, ["id", "attributes"] as const).map(
|
||||||
{filterHasAttributes(item.subitems.data, [
|
(subitem) => (
|
||||||
"id",
|
<Fragment key={subitem.id}>
|
||||||
"attributes",
|
<PreviewCard
|
||||||
] as const).map((subitem) => (
|
href={`/library/${subitem.attributes.slug}`}
|
||||||
<Fragment key={subitem.id}>
|
title={subitem.attributes.title}
|
||||||
<PreviewCard
|
subtitle={subitem.attributes.subtitle}
|
||||||
href={`/library/${subitem.attributes.slug}`}
|
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
|
||||||
title={subitem.attributes.title}
|
thumbnailAspectRatio="21/29.7"
|
||||||
subtitle={subitem.attributes.subtitle}
|
thumbnailRounded={false}
|
||||||
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
|
keepInfoVisible={keepInfoVisible}
|
||||||
thumbnailAspectRatio="21/29.7"
|
topChips={
|
||||||
thumbnailRounded={false}
|
subitem.attributes.metadata &&
|
||||||
keepInfoVisible={keepInfoVisible}
|
subitem.attributes.metadata.length > 0 &&
|
||||||
topChips={
|
subitem.attributes.metadata[0]
|
||||||
subitem.attributes.metadata &&
|
? [prettyItemSubType(subitem.attributes.metadata[0])]
|
||||||
subitem.attributes.metadata.length > 0 &&
|
: []
|
||||||
subitem.attributes.metadata[0]
|
}
|
||||||
? [prettyItemSubType(subitem.attributes.metadata[0])]
|
bottomChips={subitem.attributes.categories?.data.map(
|
||||||
: []
|
(category) => category.attributes?.short ?? ""
|
||||||
}
|
)}
|
||||||
bottomChips={subitem.attributes.categories?.data.map(
|
metadata={{
|
||||||
(category) => category.attributes?.short ?? ""
|
releaseDate: subitem.attributes.release_date,
|
||||||
)}
|
price: subitem.attributes.price,
|
||||||
metadata={{
|
position: "Bottom",
|
||||||
releaseDate: subitem.attributes.release_date,
|
}}
|
||||||
price: subitem.attributes.price,
|
infoAppend={
|
||||||
position: "Bottom",
|
!isUntangibleGroupItem(subitem.attributes.metadata?.[0]) && (
|
||||||
}}
|
<PreviewCardCTAs id={subitem.id} />
|
||||||
infoAppend={
|
)
|
||||||
!isUntangibleGroupItem(
|
}
|
||||||
subitem.attributes.metadata?.[0]
|
/>
|
||||||
) && <PreviewCardCTAs id={subitem.id} />
|
</Fragment>
|
||||||
}
|
)
|
||||||
/>
|
)}
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{item.contents && item.contents.data.length > 0 && (
|
{item.contents && item.contents.data.length > 0 && (
|
||||||
<div
|
<div id={intersectionIds[4]} className="grid w-full place-items-center gap-8">
|
||||||
id={intersectionIds[4]}
|
|
||||||
className="grid w-full place-items-center gap-8"
|
|
||||||
>
|
|
||||||
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
||||||
{displayOpenScans && (
|
{displayOpenScans && (
|
||||||
<Button
|
<Button href={`/library/${item.slug}/scans`} text={langui.view_scans} />
|
||||||
href={`/library/${item.slug}/scans`}
|
|
||||||
text={langui.view_scans}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<div className="max-w- grid w-full gap-4">
|
<div className="max-w- grid w-full gap-4">
|
||||||
{filterHasAttributes(item.contents.data, [
|
{filterHasAttributes(item.contents.data, ["attributes"] as const).map(
|
||||||
"attributes",
|
(rangedContent) => (
|
||||||
] as const).map((rangedContent) => (
|
<ContentLine
|
||||||
<ContentLine
|
content={
|
||||||
content={
|
rangedContent.attributes.content?.data?.attributes
|
||||||
rangedContent.attributes.content?.data?.attributes
|
? {
|
||||||
? {
|
translations: filterDefined(
|
||||||
translations: filterDefined(
|
rangedContent.attributes.content.data.attributes.translations
|
||||||
rangedContent.attributes.content.data.attributes
|
).map((translation) => ({
|
||||||
.translations
|
pre_title: translation.pre_title,
|
||||||
).map((translation) => ({
|
title: translation.title,
|
||||||
pre_title: translation.pre_title,
|
subtitle: translation.subtitle,
|
||||||
title: translation.title,
|
language: translation.language?.data?.attributes?.code,
|
||||||
subtitle: translation.subtitle,
|
})),
|
||||||
language:
|
categories: filterHasAttributes(
|
||||||
translation.language?.data?.attributes?.code,
|
rangedContent.attributes.content.data.attributes.categories?.data,
|
||||||
})),
|
["attributes"]
|
||||||
categories: filterHasAttributes(
|
).map((category) => category.attributes.short),
|
||||||
rangedContent.attributes.content.data.attributes
|
type:
|
||||||
.categories?.data,
|
rangedContent.attributes.content.data.attributes.type?.data
|
||||||
["attributes"]
|
?.attributes?.titles?.[0]?.title ??
|
||||||
).map((category) => category.attributes.short),
|
prettySlug(
|
||||||
type:
|
rangedContent.attributes.content.data.attributes.type?.data
|
||||||
rangedContent.attributes.content.data.attributes
|
?.attributes?.slug
|
||||||
.type?.data?.attributes?.titles?.[0]?.title ??
|
),
|
||||||
prettySlug(
|
slug: rangedContent.attributes.content.data.attributes.slug,
|
||||||
rangedContent.attributes.content.data.attributes
|
}
|
||||||
.type?.data?.attributes?.slug
|
: undefined
|
||||||
),
|
}
|
||||||
slug: rangedContent.attributes.content.data
|
rangeStart={
|
||||||
.attributes.slug,
|
rangedContent.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
||||||
}
|
? `${rangedContent.attributes.range[0].starting_page}`
|
||||||
: undefined
|
: ""
|
||||||
}
|
}
|
||||||
rangeStart={
|
slug={rangedContent.attributes.slug}
|
||||||
rangedContent.attributes.range[0]?.__typename ===
|
parentSlug={item.slug}
|
||||||
"ComponentRangePageRange"
|
key={rangedContent.id}
|
||||||
? `${rangedContent.attributes.range[0].starting_page}`
|
hasScanSet={
|
||||||
: ""
|
isDefined(rangedContent.attributes.scan_set) &&
|
||||||
}
|
rangedContent.attributes.scan_set.length > 0
|
||||||
slug={rangedContent.attributes.slug}
|
}
|
||||||
parentSlug={item.slug}
|
condensed={isContentPanelNoMoreThan3xl}
|
||||||
key={rangedContent.id}
|
/>
|
||||||
hasScanSet={
|
)
|
||||||
isDefined(rangedContent.attributes.scan_set) &&
|
)}
|
||||||
rangedContent.attributes.scan_set.length > 0
|
|
||||||
}
|
|
||||||
condensed={isContentPanelNoMoreThan3xl}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -684,13 +597,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
|
||||||
<AppLayout
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
subPanel={subPanel}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default LibrarySlug;
|
export default LibrarySlug;
|
||||||
|
|
||||||
|
@ -703,10 +610,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const langui = getLangui(context.locale);
|
const langui = getLangui(context.locale);
|
||||||
const item = await sdk.getLibraryItem({
|
const item = await sdk.getLibraryItem({
|
||||||
slug:
|
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
||||||
context.params && isDefined(context.params.slug)
|
|
||||||
? context.params.slug.toString()
|
|
||||||
: "",
|
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
});
|
});
|
||||||
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
||||||
|
@ -721,16 +625,12 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
item.libraryItems.data[0].attributes.categories?.data,
|
item.libraryItems.data[0].attributes.categories?.data,
|
||||||
["attributes.short"]
|
["attributes.short"]
|
||||||
).map((category) => category.attributes.short),
|
).map((category) => category.attributes.short),
|
||||||
[langui.type ?? "Type"]: item.libraryItems.data[0].attributes
|
[langui.type ?? "Type"]: item.libraryItems.data[0].attributes.metadata?.[0]
|
||||||
.metadata?.[0]
|
|
||||||
? [prettyItemSubType(item.libraryItems.data[0].attributes.metadata[0])]
|
? [prettyItemSubType(item.libraryItems.data[0].attributes.metadata[0])]
|
||||||
: [],
|
: [],
|
||||||
[langui.release_date ?? "Release date"]: [
|
[langui.release_date ?? "Release date"]: [
|
||||||
item.libraryItems.data[0].attributes.release_date
|
item.libraryItems.data[0].attributes.release_date
|
||||||
? prettyDate(
|
? prettyDate(item.libraryItems.data[0].attributes.release_date, context.locale)
|
||||||
item.libraryItems.data[0].attributes.release_date,
|
|
||||||
context.locale
|
|
||||||
)
|
|
||||||
: undefined,
|
: undefined,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -739,12 +639,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
item: item.libraryItems.data[0].attributes,
|
item: item.libraryItems.data[0].attributes,
|
||||||
itemId: item.libraryItems.data[0].id,
|
itemId: item.libraryItems.data[0].id,
|
||||||
openGraph: getOpenGraph(
|
openGraph: getOpenGraph(langui, title, description, thumbnail?.data?.attributes),
|
||||||
langui,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
thumbnail?.data?.attributes
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
|
@ -757,9 +652,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const libraryItems = await sdk.getLibraryItemsSlugs();
|
const libraryItems = await sdk.getLibraryItemsSlugs();
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
filterHasAttributes(libraryItems.libraryItems?.data, [
|
filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"] as const).map((item) => {
|
||||||
"attributes",
|
|
||||||
] as const).map((item) => {
|
|
||||||
context.locales?.map((local) =>
|
context.locales?.map((local) =>
|
||||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||||
);
|
);
|
||||||
|
@ -808,9 +701,7 @@ const ContentLine = ({
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: content?.translations ?? [],
|
items: content?.translations ?? [],
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback(
|
||||||
(
|
(item: NonNullable<ContentLineProps["content"]>["translations"][number]) => item.language,
|
||||||
item: NonNullable<ContentLineProps["content"]>["translations"][number]
|
|
||||||
) => item.language,
|
|
||||||
[]
|
[]
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
@ -844,21 +735,14 @@ const ContentLine = ({
|
||||||
{hasScanSet || isDefined(content) ? (
|
{hasScanSet || isDefined(content) ? (
|
||||||
<>
|
<>
|
||||||
{hasScanSet && (
|
{hasScanSet && (
|
||||||
<Button
|
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
|
||||||
href={`/library/${parentSlug}/scans#${slug}`}
|
|
||||||
text={langui.view_scans}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{isDefined(content) && (
|
{isDefined(content) && (
|
||||||
<Button
|
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||||
href={`/contents/${content.slug}`}
|
|
||||||
text={langui.open_content}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
/* TODO: Add to langui */
|
langui.content_is_not_available
|
||||||
"The content is not available"
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -870,8 +754,7 @@ const ContentLine = ({
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid gap-2 rounded-lg px-4",
|
"grid gap-2 rounded-lg px-4",
|
||||||
cIf(isOpened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
|
cIf(isOpened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
<div className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center gap-4">
|
<div className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center gap-4">
|
||||||
<a>
|
<a>
|
||||||
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
||||||
|
@ -893,35 +776,25 @@ const ContentLine = ({
|
||||||
</div>
|
</div>
|
||||||
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
|
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
|
||||||
<p>{rangeStart}</p>
|
<p>{rangeStart}</p>
|
||||||
{content?.type && (
|
{content?.type && <Chip className="justify-self-end" text={content.type} />}
|
||||||
<Chip className="justify-self-end" text={content.type} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
||||||
isOpened ? "grid" : "hidden"
|
isOpened ? "grid" : "hidden"
|
||||||
}`}
|
}`}>
|
||||||
>
|
|
||||||
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
||||||
|
|
||||||
{hasScanSet || isDefined(content) ? (
|
{hasScanSet || isDefined(content) ? (
|
||||||
<>
|
<>
|
||||||
{hasScanSet && (
|
{hasScanSet && (
|
||||||
<Button
|
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
|
||||||
href={`/library/${parentSlug}/scans#${slug}`}
|
|
||||||
text={langui.view_scans}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{isDefined(content) && (
|
{isDefined(content) && (
|
||||||
<Button
|
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||||
href={`/contents/${content.slug}`}
|
|
||||||
text={langui.open_content}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
/* TODO: Add to langui */
|
langui.content_is_not_available
|
||||||
"The content is not available"
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,21 +2,11 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
import { Fragment, useCallback, useMemo } from "react";
|
import { Fragment, useCallback, useMemo } from "react";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import {
|
import { GetLibraryItemScansQuery, UploadImageFragment } from "graphql/generated";
|
||||||
GetLibraryItemScansQuery,
|
|
||||||
UploadImageFragment,
|
|
||||||
} from "graphql/generated";
|
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import {
|
import { prettyInlineTitle, prettySlug, prettyItemSubType } from "helpers/formatters";
|
||||||
prettyInlineTitle,
|
|
||||||
prettySlug,
|
|
||||||
prettyItemSubType,
|
|
||||||
} from "helpers/formatters";
|
|
||||||
import {
|
import {
|
||||||
filterHasAttributes,
|
filterHasAttributes,
|
||||||
getStatusDescription,
|
getStatusDescription,
|
||||||
|
@ -41,10 +31,7 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { TranslatedProps } from "types/TranslatedProps";
|
import { TranslatedProps } from "types/TranslatedProps";
|
||||||
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
||||||
import { useIntersectionList } from "hooks/useIntersectionList";
|
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||||
import {
|
import { useIs1ColumnLayout, useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
|
||||||
useIs1ColumnLayout,
|
|
||||||
useIsContentPanelNoMoreThan,
|
|
||||||
} from "hooks/useContainerQuery";
|
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { getLangui } from "graphql/fetchLocalData";
|
import { getLangui } from "graphql/fetchLocalData";
|
||||||
|
@ -56,13 +43,9 @@ import { getLangui } from "graphql/fetchLocalData";
|
||||||
|
|
||||||
interface Props extends AppLayoutRequired {
|
interface Props extends AppLayoutRequired {
|
||||||
item: NonNullable<
|
item: NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||||
GetLibraryItemScansQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>;
|
|
||||||
itemId: NonNullable<
|
|
||||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]
|
|
||||||
>;
|
>;
|
||||||
|
itemId: NonNullable<NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
|
@ -72,9 +55,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
const ids = useMemo(
|
const ids = useMemo(
|
||||||
() =>
|
() =>
|
||||||
filterHasAttributes(item.contents?.data, [
|
filterHasAttributes(item.contents?.data, ["attributes.slug"] as const).map(
|
||||||
"attributes.slug",
|
(content) => content.attributes.slug
|
||||||
] as const).map((content) => content.attributes.slug),
|
),
|
||||||
[item.contents?.data]
|
[item.contents?.data]
|
||||||
);
|
);
|
||||||
const currentIntersection = useIntersectionList(ids);
|
const currentIntersection = useIntersectionList(ids);
|
||||||
|
@ -103,18 +86,16 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
? [prettyItemSubType(item.metadata[0])]
|
? [prettyItemSubType(item.metadata[0])]
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
bottomChips={filterHasAttributes(item.categories?.data, [
|
bottomChips={filterHasAttributes(item.categories?.data, ["attributes"] as const).map(
|
||||||
"attributes",
|
(category) => category.attributes.short
|
||||||
] as const).map((category) => category.attributes.short)}
|
)}
|
||||||
metadata={{
|
metadata={{
|
||||||
releaseDate: item.release_date,
|
releaseDate: item.release_date,
|
||||||
price: item.price,
|
price: item.price,
|
||||||
position: "Bottom",
|
position: "Bottom",
|
||||||
}}
|
}}
|
||||||
infoAppend={
|
infoAppend={
|
||||||
!isUntangibleGroupItem(item.metadata?.[0]) && (
|
!isUntangibleGroupItem(item.metadata?.[0]) && <PreviewCardCTAs id={itemId} />
|
||||||
<PreviewCardCTAs id={itemId} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -122,54 +103,46 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
<p className="mb-4 font-headers text-2xl font-bold">
|
<p className="mb-4 font-headers text-2xl font-bold">{langui.contents}</p>
|
||||||
{langui.contents}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map(
|
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map((content, index) => (
|
||||||
(content, index) => (
|
<>
|
||||||
<>
|
{content.attributes.scan_set && content.attributes.scan_set.length > 0 && (
|
||||||
{content.attributes.scan_set &&
|
<TranslatedNavOption
|
||||||
content.attributes.scan_set.length > 0 && (
|
key={content.id}
|
||||||
<TranslatedNavOption
|
url={`#${content.attributes.slug}`}
|
||||||
key={content.id}
|
translations={filterHasAttributes(
|
||||||
url={`#${content.attributes.slug}`}
|
content.attributes.content?.data?.attributes?.translations,
|
||||||
translations={filterHasAttributes(
|
["language.data.attributes"] as const
|
||||||
content.attributes.content?.data?.attributes
|
).map((translation) => ({
|
||||||
?.translations,
|
language: translation.language.data.attributes.code,
|
||||||
["language.data.attributes"] as const
|
title: prettyInlineTitle(
|
||||||
).map((translation) => ({
|
translation.pre_title,
|
||||||
language: translation.language.data.attributes.code,
|
translation.title,
|
||||||
title: prettyInlineTitle(
|
translation.subtitle
|
||||||
translation.pre_title,
|
),
|
||||||
translation.title,
|
subtitle:
|
||||||
translation.subtitle
|
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
||||||
),
|
? `${content.attributes.range[0].starting_page}` +
|
||||||
subtitle:
|
`→` +
|
||||||
content.attributes.range[0]?.__typename ===
|
`${content.attributes.range[0].ending_page}`
|
||||||
"ComponentRangePageRange"
|
: undefined,
|
||||||
? `${content.attributes.range[0].starting_page}` +
|
}))}
|
||||||
`→` +
|
fallback={{
|
||||||
`${content.attributes.range[0].ending_page}`
|
title: prettySlug(content.attributes.slug, item.slug),
|
||||||
: undefined,
|
subtitle:
|
||||||
}))}
|
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
||||||
fallback={{
|
? `${content.attributes.range[0].starting_page}` +
|
||||||
title: prettySlug(content.attributes.slug, item.slug),
|
`→` +
|
||||||
subtitle:
|
`${content.attributes.range[0].ending_page}`
|
||||||
content.attributes.range[0]?.__typename ===
|
: undefined,
|
||||||
"ComponentRangePageRange"
|
}}
|
||||||
? `${content.attributes.range[0].starting_page}` +
|
border
|
||||||
`→` +
|
active={index === currentIntersection}
|
||||||
`${content.attributes.range[0].ending_page}`
|
/>
|
||||||
: undefined,
|
)}
|
||||||
}}
|
</>
|
||||||
border
|
))}
|
||||||
active={index === currentIntersection}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
|
@ -201,9 +174,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
className="mb-10"
|
className="mb-10"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{item.images && (
|
{item.images && <ScanSetCover images={item.images} openLightBox={openLightBox} />}
|
||||||
<ScanSetCover images={item.images} openLightBox={openLightBox} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{item.contents?.data.map((content) => (
|
{item.contents?.data.map((content) => (
|
||||||
<Fragment key={content.id}>
|
<Fragment key={content.id}>
|
||||||
|
@ -233,23 +204,10 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||||
))}
|
))}
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
),
|
),
|
||||||
[
|
[LightBox, openLightBox, item.contents?.data, item.images, item.slug, langui]
|
||||||
LightBox,
|
|
||||||
openLightBox,
|
|
||||||
item.contents?.data,
|
|
||||||
item.images,
|
|
||||||
item.slug,
|
|
||||||
langui,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
|
||||||
<AppLayout
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
subPanel={subPanel}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default LibrarySlug;
|
export default LibrarySlug;
|
||||||
|
|
||||||
|
@ -262,10 +220,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const langui = getLangui(context.locale);
|
const langui = getLangui(context.locale);
|
||||||
const item = await sdk.getLibraryItemScans({
|
const item = await sdk.getLibraryItemScans({
|
||||||
slug:
|
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
||||||
context.params && isDefined(context.params.slug)
|
|
||||||
? context.params.slug.toString()
|
|
||||||
: "",
|
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
});
|
});
|
||||||
if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id)
|
if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id)
|
||||||
|
@ -293,9 +248,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const libraryItems = await sdk.getLibraryItemsSlugs({});
|
const libraryItems = await sdk.getLibraryItemsSlugs({});
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
filterHasAttributes(libraryItems.libraryItems?.data, [
|
filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"] as const).map((item) => {
|
||||||
"attributes",
|
|
||||||
] as const).map((item) => {
|
|
||||||
context.locales?.map((local) =>
|
context.locales?.map((local) =>
|
||||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||||
);
|
);
|
||||||
|
@ -323,9 +276,7 @@ interface ScanSetProps {
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||||
GetLibraryItemScansQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["contents"]
|
>["contents"]
|
||||||
>["data"][number]["attributes"]
|
>["data"][number]["attributes"]
|
||||||
>["scan_set"]
|
>["scan_set"]
|
||||||
|
@ -336,66 +287,53 @@ interface ScanSetProps {
|
||||||
content: NonNullable<
|
content: NonNullable<
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||||
GetLibraryItemScansQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["contents"]
|
>["contents"]
|
||||||
>["data"][number]["attributes"]
|
>["data"][number]["attributes"]
|
||||||
>["content"];
|
>["content"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScanSet = ({
|
const ScanSet = ({ openLightBox, scanSet, id, title, content }: ScanSetProps): JSX.Element => {
|
||||||
openLightBox,
|
|
||||||
scanSet,
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
content,
|
|
||||||
}: ScanSetProps): JSX.Element => {
|
|
||||||
const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl");
|
const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl");
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
useSmartLanguage({
|
items: scanSet,
|
||||||
items: scanSet,
|
languageExtractor: useCallback(
|
||||||
languageExtractor: useCallback(
|
(item: NonNullable<ScanSetProps["scanSet"][number]>) => item.language?.data?.attributes?.code,
|
||||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) =>
|
[]
|
||||||
item.language?.data?.attributes?.code,
|
),
|
||||||
[]
|
transform: useCallback((item: NonNullable<ScanSetProps["scanSet"][number]>) => {
|
||||||
),
|
item.pages?.data.sort((a, b) => {
|
||||||
transform: useCallback(
|
if (
|
||||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) => {
|
a.attributes &&
|
||||||
item.pages?.data.sort((a, b) => {
|
b.attributes &&
|
||||||
if (
|
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||||
a.attributes &&
|
isDefinedAndNotEmpty(b.attributes.url)
|
||||||
b.attributes &&
|
) {
|
||||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
let aName = getAssetFilename(a.attributes.url);
|
||||||
isDefinedAndNotEmpty(b.attributes.url)
|
let bName = getAssetFilename(b.attributes.url);
|
||||||
) {
|
|
||||||
let aName = getAssetFilename(a.attributes.url);
|
|
||||||
let bName = getAssetFilename(b.attributes.url);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the number is a succession of 0s, make the number
|
* If the number is a succession of 0s, make the number
|
||||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
||||||
*/
|
*/
|
||||||
if (aName.replaceAll("0", "").length === 0) {
|
if (aName.replaceAll("0", "").length === 0) {
|
||||||
aName = (1 - aName.length).toString(10);
|
aName = (1 - aName.length).toString(10);
|
||||||
}
|
}
|
||||||
if (bName.replaceAll("0", "").length === 0) {
|
if (bName.replaceAll("0", "").length === 0) {
|
||||||
bName = (1 - bName.length).toString(10);
|
bName = (1 - bName.length).toString(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInteger(aName) && isInteger(bName)) {
|
if (isInteger(aName) && isInteger(bName)) {
|
||||||
return parseInt(aName, 10) - parseInt(bName, 10);
|
return parseInt(aName, 10) - parseInt(bName, 10);
|
||||||
}
|
}
|
||||||
return a.attributes.url.localeCompare(b.attributes.url);
|
return a.attributes.url.localeCompare(b.attributes.url);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
return item;
|
return item;
|
||||||
},
|
}, []),
|
||||||
[]
|
});
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const pages = useMemo(
|
const pages = useMemo(
|
||||||
() => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
|
() => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
|
||||||
|
@ -408,8 +346,7 @@ const ScanSet = ({
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row flex-wrap place-items-center
|
className="flex flex-row flex-wrap place-items-center
|
||||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
gap-6 pt-10 text-base first-of-type:pt-0">
|
||||||
>
|
|
||||||
<h2 id={id} className="text-2xl">
|
<h2 id={id} className="text-2xl">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -425,13 +362,12 @@ const ScanSet = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||||
{content?.data?.attributes &&
|
{content?.data?.attributes && isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
||||||
isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
<Button
|
||||||
<Button
|
href={`/contents/${content.data.attributes.slug}`}
|
||||||
href={`/contents/${content.data.attributes.slug}`}
|
text={langui.open_content}
|
||||||
text={langui.open_content}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{languageSwitcherProps.locales.size > 1 && (
|
{languageSwitcherProps.locales.size > 1 && (
|
||||||
<LanguageSwitcher {...languageSwitcherProps} />
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
|
@ -441,8 +377,7 @@ const ScanSet = ({
|
||||||
<p className="font-headers font-bold">{langui.status}:</p>
|
<p className="font-headers font-bold">{langui.status}:</p>
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={getStatusDescription(selectedScan.status, langui)}
|
content={getStatusDescription(selectedScan.status, langui)}
|
||||||
maxWidth={"20rem"}
|
maxWidth={"20rem"}>
|
||||||
>
|
|
||||||
<Chip text={selectedScan.status} />
|
<Chip text={selectedScan.status} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -479,24 +414,21 @@ const ScanSet = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedScan.typesetters &&
|
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
|
||||||
selectedScan.typesetters.data.length > 0 && (
|
<div>
|
||||||
<div>
|
<p className="font-headers font-bold">{langui.typesetters}:</p>
|
||||||
<p className="font-headers font-bold">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{langui.typesetters}:
|
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||||
</p>
|
"id",
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
"attributes",
|
||||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
] as const).map((typesetter) => (
|
||||||
"id",
|
<Fragment key={typesetter.id}>
|
||||||
"attributes",
|
<RecorderChip recorder={typesetter.attributes} />
|
||||||
] as const).map((typesetter) => (
|
</Fragment>
|
||||||
<Fragment key={typesetter.id}>
|
))}
|
||||||
<RecorderChip recorder={typesetter.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||||
<ToolTip content={selectedScan.notes}>
|
<ToolTip content={selectedScan.notes}>
|
||||||
|
@ -514,8 +446,7 @@ const ScanSet = ({
|
||||||
"grid-cols-2 gap-[4vmin]",
|
"grid-cols-2 gap-[4vmin]",
|
||||||
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{pages.map((page, index) => (
|
{pages.map((page, index) => (
|
||||||
<div
|
<div
|
||||||
key={page.id}
|
key={page.id}
|
||||||
|
@ -526,8 +457,7 @@ const ScanSet = ({
|
||||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||||
);
|
);
|
||||||
openLightBox(images, index);
|
openLightBox(images, index);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<Img src={page.attributes} quality={ImageQuality.Small} />
|
<Img src={page.attributes} quality={ImageQuality.Small} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -547,18 +477,10 @@ const TranslatedScanSet = ({
|
||||||
}: TranslatedProps<ScanSetProps, "title">): JSX.Element => {
|
}: TranslatedProps<ScanSetProps, "title">): JSX.Element => {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languageExtractor: useCallback(
|
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||||
(item: { language: string }): string => item.language,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return <ScanSet title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||||
<ScanSet
|
|
||||||
title={selectedTranslation?.title ?? fallback.title}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
@ -567,28 +489,22 @@ interface ScanSetCoverProps {
|
||||||
openLightBox: (images: string[], index?: number) => void;
|
openLightBox: (images: string[], index?: number) => void;
|
||||||
images: NonNullable<
|
images: NonNullable<
|
||||||
NonNullable<
|
NonNullable<
|
||||||
NonNullable<
|
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||||
GetLibraryItemScansQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["images"]
|
>["images"]
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScanSetCover = ({
|
const ScanSetCover = ({ openLightBox, images }: ScanSetCoverProps): JSX.Element => {
|
||||||
openLightBox,
|
|
||||||
images,
|
|
||||||
}: ScanSetCoverProps): JSX.Element => {
|
|
||||||
const is1ColumnLayout = useIsContentPanelNoMoreThan("4xl");
|
const is1ColumnLayout = useIsContentPanelNoMoreThan("4xl");
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
useSmartLanguage({
|
items: images,
|
||||||
items: images,
|
languageExtractor: useCallback(
|
||||||
languageExtractor: useCallback(
|
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
|
||||||
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
|
item.language?.data?.attributes?.code,
|
||||||
item.language?.data?.attributes?.code,
|
[]
|
||||||
[]
|
),
|
||||||
),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const coverImages = useMemo(() => {
|
const coverImages = useMemo(() => {
|
||||||
const memo: UploadImageFragment[] = [];
|
const memo: UploadImageFragment[] = [];
|
||||||
|
@ -613,8 +529,7 @@ const ScanSetCover = ({
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row flex-wrap place-items-center
|
className="flex flex-row flex-wrap place-items-center
|
||||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
gap-6 pt-10 text-base first-of-type:pt-0">
|
||||||
>
|
|
||||||
<h2 id={"cover"} className="text-2xl">
|
<h2 id={"cover"} className="text-2xl">
|
||||||
{langui.cover}
|
{langui.cover}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -636,8 +551,7 @@ const ScanSetCover = ({
|
||||||
<p className="font-headers font-bold">{langui.status}:</p>
|
<p className="font-headers font-bold">{langui.status}:</p>
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={getStatusDescription(selectedScan.status, langui)}
|
content={getStatusDescription(selectedScan.status, langui)}
|
||||||
maxWidth={"20rem"}
|
maxWidth={"20rem"}>
|
||||||
>
|
|
||||||
<Chip text={selectedScan.status} />
|
<Chip text={selectedScan.status} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -674,24 +588,21 @@ const ScanSetCover = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedScan.typesetters &&
|
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
|
||||||
selectedScan.typesetters.data.length > 0 && (
|
<div>
|
||||||
<div>
|
<p className="font-headers font-bold">{langui.typesetters}:</p>
|
||||||
<p className="font-headers font-bold">
|
<div className="grid place-content-center place-items-center gap-2">
|
||||||
{langui.typesetters}:
|
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||||
</p>
|
"id",
|
||||||
<div className="grid place-content-center place-items-center gap-2">
|
"attributes",
|
||||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
] as const).map((typesetter) => (
|
||||||
"id",
|
<Fragment key={typesetter.id}>
|
||||||
"attributes",
|
<RecorderChip recorder={typesetter.attributes} />
|
||||||
] as const).map((typesetter) => (
|
</Fragment>
|
||||||
<Fragment key={typesetter.id}>
|
))}
|
||||||
<RecorderChip recorder={typesetter.attributes} />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -703,21 +614,17 @@ const ScanSetCover = ({
|
||||||
"grid-cols-2 gap-[4vmin]",
|
"grid-cols-2 gap-[4vmin]",
|
||||||
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
||||||
)
|
)
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{coverImages.map((image, index) => (
|
{coverImages.map((image, index) => (
|
||||||
<div
|
<div
|
||||||
key={image.url}
|
key={image.url}
|
||||||
className="cursor-pointer transition-transform
|
className="cursor-pointer transition-transform
|
||||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
drop-shadow-shade-lg hover:scale-[1.02]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const imgs = coverImages.map((img) =>
|
const imgs = coverImages.map((img) => getAssetURL(img.url, ImageQuality.Large));
|
||||||
getAssetURL(img.url, ImageQuality.Large)
|
|
||||||
);
|
|
||||||
|
|
||||||
openLightBox(imgs, index);
|
openLightBox(imgs, index);
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<Img src={image} quality={ImageQuality.Small} />
|
<Img src={image} quality={ImageQuality.Small} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -6,15 +6,12 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Select } from "components/Inputs/Select";
|
import { Select } from "components/Inputs/Select";
|
||||||
import { Switch } from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { prettyInlineTitle, prettyItemSubType } from "helpers/formatters";
|
import { prettyInlineTitle, prettyItemSubType } from "helpers/formatters";
|
||||||
import { LibraryItemUserStatus } from "helpers/types";
|
import { LibraryItemUserStatus } from "types/types";
|
||||||
import { Icon } from "components/Ico";
|
import { Icon } from "components/Ico";
|
||||||
import { WithLabel } from "components/Inputs/WithLabel";
|
import { WithLabel } from "components/Inputs/WithLabel";
|
||||||
import { TextInput } from "components/Inputs/TextInput";
|
import { TextInput } from "components/Inputs/TextInput";
|
||||||
|
@ -24,12 +21,7 @@ import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||||
import { PreviewCard } from "components/PreviewCard";
|
import { PreviewCard } from "components/PreviewCard";
|
||||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||||
import {
|
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
||||||
filterHasAttributes,
|
|
||||||
isDefined,
|
|
||||||
isDefinedAndNotEmpty,
|
|
||||||
isUndefined,
|
|
||||||
} from "helpers/others";
|
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
import { convertPrice } from "helpers/numbers";
|
import { convertPrice } from "helpers/numbers";
|
||||||
import { SmartList } from "components/SmartList";
|
import { SmartList } from "components/SmartList";
|
||||||
|
@ -74,9 +66,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
const { libraryItemUserStatus } = useAppLayout();
|
const { libraryItemUserStatus } = useAppLayout();
|
||||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||||
|
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
value: showSubitems,
|
value: showSubitems,
|
||||||
|
@ -102,27 +92,20 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
setValue: setKeepInfoVisible,
|
setValue: setKeepInfoVisible,
|
||||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||||
|
|
||||||
const [sortingMethod, setSortingMethod] = useState<number>(
|
const [sortingMethod, setSortingMethod] = useState<number>(DEFAULT_FILTERS_STATE.sortingMethod);
|
||||||
DEFAULT_FILTERS_STATE.sortingMethod
|
|
||||||
);
|
|
||||||
|
|
||||||
const [groupingMethod, setGroupingMethod] = useState<number>(
|
const [groupingMethod, setGroupingMethod] = useState<number>(
|
||||||
DEFAULT_FILTERS_STATE.groupingMethod
|
DEFAULT_FILTERS_STATE.groupingMethod
|
||||||
);
|
);
|
||||||
|
|
||||||
const [filterUserStatus, setFilterUserStatus] = useState<
|
const [filterUserStatus, setFilterUserStatus] = useState<LibraryItemUserStatus | undefined>(
|
||||||
LibraryItemUserStatus | undefined
|
DEFAULT_FILTERS_STATE.filterUserStatus
|
||||||
>(DEFAULT_FILTERS_STATE.filterUserStatus);
|
);
|
||||||
|
|
||||||
const filteringFunction = useCallback(
|
const filteringFunction = useCallback(
|
||||||
(
|
(item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">) => {
|
||||||
item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
|
|
||||||
) => {
|
|
||||||
if (!showSubitems && !item.attributes.root_item) return false;
|
if (!showSubitems && !item.attributes.root_item) return false;
|
||||||
if (
|
if (showSubitems && isUntangibleGroupItem(item.attributes.metadata?.[0])) {
|
||||||
showSubitems &&
|
|
||||||
isUntangibleGroupItem(item.attributes.metadata?.[0])
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (item.attributes.primary && !showPrimaryItems) return false;
|
if (item.attributes.primary && !showPrimaryItems) return false;
|
||||||
|
@ -142,13 +125,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
[
|
[libraryItemUserStatus, filterUserStatus, showPrimaryItems, showSecondaryItems, showSubitems]
|
||||||
libraryItemUserStatus,
|
|
||||||
filterUserStatus,
|
|
||||||
showPrimaryItems,
|
|
||||||
showSecondaryItems,
|
|
||||||
showSubitems,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortingFunction = useCallback(
|
const sortingFunction = useCallback(
|
||||||
|
@ -158,16 +135,8 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
) => {
|
) => {
|
||||||
switch (sortingMethod) {
|
switch (sortingMethod) {
|
||||||
case 0: {
|
case 0: {
|
||||||
const titleA = prettyInlineTitle(
|
const titleA = prettyInlineTitle("", a.attributes.title, a.attributes.subtitle);
|
||||||
"",
|
const titleB = prettyInlineTitle("", b.attributes.title, b.attributes.subtitle);
|
||||||
a.attributes.title,
|
|
||||||
a.attributes.subtitle
|
|
||||||
);
|
|
||||||
const titleB = prettyInlineTitle(
|
|
||||||
"",
|
|
||||||
b.attributes.title,
|
|
||||||
b.attributes.subtitle
|
|
||||||
);
|
|
||||||
return naturalCompare(titleA, titleB);
|
return naturalCompare(titleA, titleB);
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
|
@ -180,10 +149,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
return priceA - priceB;
|
return priceA - priceB;
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
return compareDate(
|
return compareDate(a.attributes.release_date, b.attributes.release_date);
|
||||||
a.attributes.release_date,
|
|
||||||
b.attributes.release_date
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -193,15 +159,12 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupingFunction = useCallback(
|
const groupingFunction = useCallback(
|
||||||
(
|
(item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">): string[] => {
|
||||||
item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
|
|
||||||
): string[] => {
|
|
||||||
switch (groupingMethod) {
|
switch (groupingMethod) {
|
||||||
case 0: {
|
case 0: {
|
||||||
const categories = filterHasAttributes(
|
const categories = filterHasAttributes(item.attributes.categories?.data, [
|
||||||
item.attributes.categories?.data,
|
"attributes",
|
||||||
["attributes"] as const
|
] as const);
|
||||||
);
|
|
||||||
if (categories.length > 0) {
|
if (categories.length > 0) {
|
||||||
return categories.map((category) => category.attributes.name);
|
return categories.map((category) => category.attributes.name);
|
||||||
}
|
}
|
||||||
|
@ -221,10 +184,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
case "ComponentMetadataOther":
|
case "ComponentMetadataOther":
|
||||||
return [langui.other ?? "Other"];
|
return [langui.other ?? "Other"];
|
||||||
case "ComponentMetadataGroup": {
|
case "ComponentMetadataGroup": {
|
||||||
switch (
|
switch (item.attributes.metadata[0]?.subitems_type?.data?.attributes?.slug) {
|
||||||
item.attributes.metadata[0]?.subitems_type?.data?.attributes
|
|
||||||
?.slug
|
|
||||||
) {
|
|
||||||
case "audio":
|
case "audio":
|
||||||
return [langui.audio ?? "Audio"];
|
return [langui.audio ?? "Audio"];
|
||||||
case "video":
|
case "video":
|
||||||
|
@ -318,9 +278,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSortingMethod(value);
|
setSortingMethod(value);
|
||||||
umami(
|
umami(
|
||||||
`[Library] Change sorting method (${
|
`[Library] Change sorting method (${["name", "price", "release date"][value]})`
|
||||||
["name", "price", "release date"][value]
|
|
||||||
})`
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -341,9 +299,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
value={showPrimaryItems}
|
value={showPrimaryItems}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleShowPrimaryItems();
|
toggleShowPrimaryItems();
|
||||||
umami(
|
umami(`[Library] ${showPrimaryItems ? "Hide" : "Show"} primary items`);
|
||||||
`[Library] ${showPrimaryItems ? "Hide" : "Show"} primary items`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</WithLabel>
|
</WithLabel>
|
||||||
|
@ -353,11 +309,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
value={showSecondaryItems}
|
value={showSecondaryItems}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleShowSecondaryItems();
|
toggleShowSecondaryItems();
|
||||||
umami(
|
umami(`[Library] ${showSecondaryItems ? "Hide" : "Show"} secondary items`);
|
||||||
`[Library] ${
|
|
||||||
showSecondaryItems ? "Hide" : "Show"
|
|
||||||
} secondary items`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</WithLabel>
|
</WithLabel>
|
||||||
|
@ -368,9 +320,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
value={keepInfoVisible}
|
value={keepInfoVisible}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleKeepInfoVisible();
|
toggleKeepInfoVisible();
|
||||||
umami(
|
umami(`[Library] Always ${keepInfoVisible ? "hide" : "show"} info`);
|
||||||
`[Library] Always ${keepInfoVisible ? "hide" : "show"} info`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</WithLabel>
|
</WithLabel>
|
||||||
|
@ -497,20 +447,13 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
||||||
)}
|
)}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"grid-cols-2 items-end",
|
"grid-cols-2 items-end",
|
||||||
cIf(
|
cIf(isContentPanelAtLeast4xl, "grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]")
|
||||||
isContentPanelAtLeast4xl,
|
|
||||||
"grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]"
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
searchingTerm={searchName}
|
searchingTerm={searchName}
|
||||||
sortingFunction={sortingFunction}
|
sortingFunction={sortingFunction}
|
||||||
groupingFunction={groupingFunction}
|
groupingFunction={groupingFunction}
|
||||||
searchingBy={(item) =>
|
searchingBy={(item) =>
|
||||||
prettyInlineTitle(
|
prettyInlineTitle("", item.attributes.title, item.attributes.subtitle)
|
||||||
"",
|
|
||||||
item.attributes.title,
|
|
||||||
item.attributes.subtitle
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
filteringFunction={filteringFunction}
|
filteringFunction={filteringFunction}
|
||||||
paginationItemPerPage={25}
|
paginationItemPerPage={25}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||||
import { PostPage } from "components/PostPage";
|
import { PostPage } from "components/PostPage";
|
||||||
import {
|
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||||
getPostStaticProps,
|
|
||||||
PostStaticProps,
|
|
||||||
} from "graphql/getPostStaticProps";
|
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
@ -37,9 +34,7 @@ export default LibrarySlug;
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async (context) => {
|
export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const slug =
|
const slug =
|
||||||
context.params && isDefined(context.params.slug)
|
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||||
? context.params.slug.toString()
|
|
||||||
: "";
|
|
||||||
return await getPostStaticProps(slug)(context);
|
return await getPostStaticProps(slug)(context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,13 +45,11 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const posts = await sdk.getPostsSlugs();
|
const posts = await sdk.getPostsSlugs();
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
|
|
||||||
filterHasAttributes(posts.posts?.data, ["attributes"] as const).map(
|
filterHasAttributes(posts.posts?.data, ["attributes"] as const).map((item) => {
|
||||||
(item) => {
|
context.locales?.map((local) =>
|
||||||
context.locales?.map((local) =>
|
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
);
|
||||||
);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
paths,
|
paths,
|
||||||
fallback: "blocking",
|
fallback: "blocking",
|
||||||
|
|
|
@ -4,10 +4,7 @@ import { useBoolean } from "usehooks-ts";
|
||||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||||
import { Switch } from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { GetPostsPreviewQuery } from "graphql/generated";
|
import { GetPostsPreviewQuery } from "graphql/generated";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
|
@ -51,9 +48,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
||||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const hoverable = useDeviceSupportsHover();
|
const hoverable = useDeviceSupportsHover();
|
||||||
const [searchName, setSearchName] = useState(
|
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||||
DEFAULT_FILTERS_STATE.searchName
|
|
||||||
);
|
|
||||||
const {
|
const {
|
||||||
value: keepInfoVisible,
|
value: keepInfoVisible,
|
||||||
toggle: toggleKeepInfoVisible,
|
toggle: toggleKeepInfoVisible,
|
||||||
|
@ -63,11 +58,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<PanelHeader
|
<PanelHeader icon={Icon.Feed} title={langui.news} description={langui.news_description} />
|
||||||
icon={Icon.Feed}
|
|
||||||
title={langui.news}
|
|
||||||
description={langui.news_description}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
|
||||||
|
@ -91,9 +82,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
||||||
value={keepInfoVisible}
|
value={keepInfoVisible}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
toggleKeepInfoVisible();
|
toggleKeepInfoVisible();
|
||||||
umami(
|
umami(`[News] Always ${keepInfoVisible ? "hide" : "show"} info`);
|
||||||
`[News] Always ${keepInfoVisible ? "hide" : "show"} info`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</WithLabel>
|
</WithLabel>
|
||||||
|
@ -111,14 +100,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[
|
[hoverable, keepInfoVisible, langui, searchName, setKeepInfoVisible, toggleKeepInfoVisible]
|
||||||
hoverable,
|
|
||||||
keepInfoVisible,
|
|
||||||
langui,
|
|
||||||
searchName,
|
|
||||||
setKeepInfoVisible,
|
|
||||||
toggleKeepInfoVisible,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentPanel = useMemo(
|
const contentPanel = useMemo(
|
||||||
|
@ -207,6 +189,4 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const sortPosts = (posts: Props["posts"]): Props["posts"] =>
|
const sortPosts = (posts: Props["posts"]): Props["posts"] =>
|
||||||
posts
|
posts.sort((a, b) => compareDate(a.attributes?.date, b.attributes?.date)).reverse();
|
||||||
.sort((a, b) => compareDate(a.attributes?.date, b.attributes?.date))
|
|
||||||
.reverse();
|
|
||||||
|
|
|
@ -5,28 +5,18 @@ import { Chip } from "components/Chip";
|
||||||
import { HorizontalLine } from "components/HorizontalLine";
|
import { HorizontalLine } from "components/HorizontalLine";
|
||||||
import { Img } from "components/Img";
|
import { Img } from "components/Img";
|
||||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||||
import {
|
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||||
ContentPanel,
|
|
||||||
ContentPanelWidthSizes,
|
|
||||||
} from "components/Panels/ContentPanel";
|
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import DefinitionCard from "components/Wiki/DefinitionCard";
|
import DefinitionCard from "components/Wiki/DefinitionCard";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import {
|
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||||
filterHasAttributes,
|
import { WikiPageWithTranslations } from "types/types";
|
||||||
isDefined,
|
|
||||||
isDefinedAndNotEmpty,
|
|
||||||
} from "helpers/others";
|
|
||||||
import { WikiPageWithTranslations } from "helpers/types";
|
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { prettySlug } from "helpers/formatters";
|
import { prettySlug, sJoin } from "helpers/formatters";
|
||||||
import { useLightBox } from "hooks/useLightBox";
|
import { useLightBox } from "hooks/useLightBox";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { getOpenGraph } from "helpers/openGraph";
|
import { getOpenGraph } from "helpers/openGraph";
|
||||||
import {
|
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||||
getDefaultPreferredLanguages,
|
|
||||||
staticSmartLanguage,
|
|
||||||
} from "helpers/locales";
|
|
||||||
import { getDescription } from "helpers/description";
|
import { getDescription } from "helpers/description";
|
||||||
import { cIf, cJoin } from "helpers/className";
|
import { cIf, cJoin } from "helpers/className";
|
||||||
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
|
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
|
||||||
|
@ -44,15 +34,14 @@ interface Props extends AppLayoutRequired {
|
||||||
|
|
||||||
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
const { langui } = useAppLayout();
|
const { langui } = useAppLayout();
|
||||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||||
useSmartLanguage({
|
items: page.translations,
|
||||||
items: page.translations,
|
languageExtractor: useCallback(
|
||||||
languageExtractor: useCallback(
|
(item: NonNullable<Props["page"]["translations"][number]>) =>
|
||||||
(item: NonNullable<Props["page"]["translations"][number]>) =>
|
item.language?.data?.attributes?.code,
|
||||||
item.language?.data?.attributes?.code,
|
[]
|
||||||
[]
|
),
|
||||||
),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
const is3ColumnsLayout = useIs3ColumnsLayout();
|
const is3ColumnsLayout = useIs3ColumnsLayout();
|
||||||
|
@ -60,11 +49,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<SubPanel>
|
<SubPanel>
|
||||||
<ReturnButton
|
<ReturnButton href={`/wiki`} title={langui.wiki} displayOnlyOn={"3ColumnsLayout"} />
|
||||||
href={`/wiki`}
|
|
||||||
title={langui.wiki}
|
|
||||||
displayOnlyOn={"3ColumnsLayout"}
|
|
||||||
/>
|
|
||||||
</SubPanel>
|
</SubPanel>
|
||||||
),
|
),
|
||||||
[langui]
|
[langui]
|
||||||
|
@ -84,14 +69,11 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
<div className="flex flex-wrap place-content-center gap-3">
|
<div className="flex flex-wrap place-content-center gap-3">
|
||||||
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
||||||
{selectedTranslation?.aliases &&
|
{selectedTranslation?.aliases && selectedTranslation.aliases.length > 0 && (
|
||||||
selectedTranslation.aliases.length > 0 && (
|
<p className="mr-3 text-center text-2xl">
|
||||||
<p className="mr-3 text-center text-2xl">
|
{`(${selectedTranslation.aliases.map((alias) => alias?.alias).join("・")})`}
|
||||||
{`(${selectedTranslation.aliases
|
</p>
|
||||||
.map((alias) => alias?.alias)
|
)}
|
||||||
.join("・")})`}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<LanguageSwitcher {...languageSwitcherProps} />
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -103,8 +85,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
"mb-8 overflow-hidden rounded-lg bg-mid text-center",
|
"mb-8 overflow-hidden rounded-lg bg-mid text-center",
|
||||||
cIf(is3ColumnsLayout, "float-right ml-8 w-[25rem]")
|
cIf(is3ColumnsLayout, "float-right ml-8 w-[25rem]")
|
||||||
)}
|
)}>
|
||||||
>
|
|
||||||
{page.thumbnail?.data?.attributes && (
|
{page.thumbnail?.data?.attributes && (
|
||||||
<Img
|
<Img
|
||||||
src={page.thumbnail.data.attributes}
|
src={page.thumbnail.data.attributes}
|
||||||
|
@ -113,10 +94,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (page.thumbnail?.data?.attributes?.url) {
|
if (page.thumbnail?.data?.attributes?.url) {
|
||||||
openLightBox([
|
openLightBox([
|
||||||
getAssetURL(
|
getAssetURL(page.thumbnail.data.attributes.url, ImageQuality.Large),
|
||||||
page.thumbnail.data.attributes.url,
|
|
||||||
ImageQuality.Large
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -125,37 +103,27 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
<div className="my-4 grid gap-4 p-4">
|
<div className="my-4 grid gap-4 p-4">
|
||||||
{page.categories?.data && page.categories.data.length > 0 && (
|
{page.categories?.data && page.categories.data.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<p className="font-headers text-xl font-bold">
|
<p className="font-headers text-xl font-bold">{langui.categories}</p>
|
||||||
{langui.categories}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||||
{filterHasAttributes(page.categories.data, [
|
{filterHasAttributes(page.categories.data, ["attributes"] as const).map(
|
||||||
"attributes",
|
(category) => (
|
||||||
] as const).map((category) => (
|
<Chip key={category.id} text={category.attributes.name} />
|
||||||
<Chip
|
)
|
||||||
key={category.id}
|
)}
|
||||||
text={category.attributes.name}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{page.tags?.data && page.tags.data.length > 0 && (
|
{page.tags?.data && page.tags.data.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<p className="font-headers text-xl font-bold">
|
<p className="font-headers text-xl font-bold">{langui.tags}</p>
|
||||||
{langui.tags}
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||||
{filterHasAttributes(page.tags.data, [
|
{filterHasAttributes(page.tags.data, ["attributes"] as const).map((tag) => (
|
||||||
"attributes",
|
|
||||||
] as const).map((tag) => (
|
|
||||||
<Chip
|
<Chip
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
text={
|
text={
|
||||||
tag.attributes.titles?.[0]?.title ??
|
tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||||
prettySlug(tag.attributes.slug)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -167,40 +135,41 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
|
|
||||||
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
||||||
<div className="mb-12">
|
<div className="mb-12">
|
||||||
<p className="font-headers text-lg font-bold">
|
<p className="font-headers text-lg font-bold">{langui.summary}</p>
|
||||||
{langui.summary}
|
|
||||||
</p>
|
|
||||||
<p>{selectedTranslation.summary}</p>
|
<p>{selectedTranslation.summary}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{filterHasAttributes(page.definitions, [
|
{filterHasAttributes(page.definitions, ["translations"] as const).map(
|
||||||
"translations",
|
(definition, index) => (
|
||||||
] as const).map((definition, index) => (
|
<div key={index} className="mb-12">
|
||||||
<div key={index} className="mb-12">
|
<DefinitionCard
|
||||||
<DefinitionCard
|
source={{
|
||||||
source={{
|
name: definition.source?.data?.attributes?.name,
|
||||||
name: definition.source?.data?.attributes?.name,
|
url: definition.source?.data?.attributes?.content?.data?.attributes?.slug
|
||||||
url: definition.source?.data?.attributes?.content?.data
|
? sJoin(
|
||||||
?.attributes?.slug
|
"/contents/",
|
||||||
? `/contents/${definition.source.data.attributes.content.data.attributes.slug}`
|
definition.source.data.attributes.content.data.attributes.slug
|
||||||
: `/library/${definition.source?.data?.attributes?.ranged_content?.data?.attributes?.library_item?.data?.attributes?.slug}`,
|
)
|
||||||
}}
|
: cJoin(
|
||||||
translations={definition.translations.map(
|
"/library/",
|
||||||
(translation) => ({
|
definition.source?.data?.attributes?.ranged_content?.data?.attributes
|
||||||
|
?.library_item?.data?.attributes?.slug
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
translations={definition.translations.map((translation) => ({
|
||||||
language: translation?.language?.data?.attributes?.code,
|
language: translation?.language?.data?.attributes?.code,
|
||||||
definition: translation?.definition,
|
definition: translation?.definition,
|
||||||
status: translation?.status,
|
status: translation?.status,
|
||||||
})
|
}))}
|
||||||
)}
|
index={index + 1}
|
||||||
index={index + 1}
|
categories={filterHasAttributes(definition.categories?.data, [
|
||||||
categories={filterHasAttributes(
|
"attributes",
|
||||||
definition.categories?.data,
|
] as const).map((category) => category.attributes.short)}
|
||||||
["attributes"] as const
|
/>
|
||||||
).map((category) => category.attributes.short)}
|
</div>
|
||||||
/>
|
)
|
||||||
</div>
|
)}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -221,13 +190,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||||
<AppLayout
|
|
||||||
subPanel={subPanel}
|
|
||||||
contentPanel={contentPanel}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default WikiPage;
|
export default WikiPage;
|
||||||
|
|
||||||
|
@ -240,24 +203,19 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const langui = getLangui(context.locale);
|
const langui = getLangui(context.locale);
|
||||||
const slug =
|
const slug =
|
||||||
context.params && isDefined(context.params.slug)
|
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||||
? context.params.slug.toString()
|
|
||||||
: "";
|
|
||||||
const page = await sdk.getWikiPage({
|
const page = await sdk.getWikiPage({
|
||||||
language_code: context.locale ?? "en",
|
language_code: context.locale ?? "en",
|
||||||
slug: slug,
|
slug: slug,
|
||||||
});
|
});
|
||||||
if (!page.wikiPages?.data[0].attributes?.translations)
|
if (!page.wikiPages?.data[0].attributes?.translations) return { notFound: true };
|
||||||
return { notFound: true };
|
|
||||||
|
|
||||||
const { title, description } = (() => {
|
const { title, description } = (() => {
|
||||||
const chipsGroups = {
|
const chipsGroups = {
|
||||||
[langui.tags ?? "Tags"]: filterHasAttributes(
|
[langui.tags ?? "Tags"]: filterHasAttributes(page.wikiPages.data[0].attributes.tags?.data, [
|
||||||
page.wikiPages.data[0].attributes.tags?.data,
|
"attributes",
|
||||||
["attributes"] as const
|
] as const).map(
|
||||||
).map(
|
(tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||||
(tag) =>
|
|
||||||
tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
|
||||||
),
|
),
|
||||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||||
page.wikiPages.data[0].attributes.categories?.data,
|
page.wikiPages.data[0].attributes.categories?.data,
|
||||||
|
@ -269,10 +227,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
const selectedTranslation = staticSmartLanguage({
|
const selectedTranslation = staticSmartLanguage({
|
||||||
items: page.wikiPages.data[0].attributes.translations,
|
items: page.wikiPages.data[0].attributes.translations,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||||
preferredLanguages: getDefaultPreferredLanguages(
|
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||||
context.locale,
|
|
||||||
context.locales
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
if (selectedTranslation) {
|
if (selectedTranslation) {
|
||||||
return {
|
return {
|
||||||
|
@ -288,8 +243,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const thumbnail =
|
const thumbnail = page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
|
||||||
page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
|
|
||||||
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
page: page.wikiPages.data[0].attributes as WikiPageWithTranslations,
|
page: page.wikiPages.data[0].attributes as WikiPageWithTranslations,
|
||||||
|
@ -306,16 +260,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
||||||
const sdk = getReadySdk();
|
const sdk = getReadySdk();
|
||||||
const contents = await sdk.getWikiPagesSlugs();
|
const contents = await sdk.getWikiPagesSlugs();
|
||||||
const paths: GetStaticPathsResult["paths"] = [];
|
const paths: GetStaticPathsResult["paths"] = [];
|
||||||
filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map(
|
filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map((wikiPage) => {
|
||||||
(wikiPage) => {
|
context.locales?.map((local) =>
|
||||||
context.locales?.map((local) =>
|
paths.push({
|
||||||
paths.push({
|
params: { slug: wikiPage.attributes.slug },
|
||||||
params: { slug: wikiPage.attributes.slug },
|
locale: local,
|
||||||
locale: local,
|
})
|
||||||
})
|
);
|
||||||
);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
paths,
|
paths,
|
||||||
fallback: "blocking",
|
fallback: "blocking",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue