Fixed eslint warnings + configure prettier
This commit is contained in:
parent
669d4358e7
commit
77d96a3dc3
|
@ -7,4 +7,5 @@ next.config.js
|
|||
postcss.config.js
|
||||
tailwind.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-mixed-operators": "warn",
|
||||
"no-multi-assign": "warn",
|
||||
"no-multi-str": "warn",
|
||||
// "no-multi-str": "warn",
|
||||
"no-negated-condition": "warn",
|
||||
// "no-nested-ternary": "warn",
|
||||
"no-new": "warn",
|
||||
|
@ -149,24 +149,15 @@ module.exports = {
|
|||
"@typescript-eslint/ban-tslint-comment": "warn",
|
||||
"@typescript-eslint/class-literal-property-style": "warn",
|
||||
"@typescript-eslint/consistent-indexed-object-style": "warn",
|
||||
"@typescript-eslint/consistent-type-assertions": [
|
||||
"warn",
|
||||
{ assertionStyle: "as" },
|
||||
],
|
||||
"@typescript-eslint/consistent-type-assertions": ["warn", { assertionStyle: "as" }],
|
||||
"@typescript-eslint/consistent-type-exports": "error",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "warn",
|
||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
||||
"@typescript-eslint/no-base-to-string": "warn",
|
||||
"@typescript-eslint/no-confusing-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-confusing-void-expression": [
|
||||
"error",
|
||||
{ ignoreArrowShorthand: true },
|
||||
],
|
||||
"@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
|
||||
"@typescript-eslint/no-dynamic-delete": "error",
|
||||
"@typescript-eslint/no-empty-interface": [
|
||||
"error",
|
||||
{ allowSingleExtends: true },
|
||||
],
|
||||
"@typescript-eslint/no-empty-interface": ["error", { allowSingleExtends: true }],
|
||||
"@typescript-eslint/no-invalid-void-type": "error",
|
||||
"@typescript-eslint/no-meaningless-void-operator": "error",
|
||||
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error",
|
||||
|
|
|
@ -8,17 +8,10 @@ module.exports = {
|
|||
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
|
||||
},
|
||||
},
|
||||
documents: [
|
||||
"src/graphql/operations/**/*.graphql",
|
||||
"src/graphql/fragments/*.graphql",
|
||||
],
|
||||
documents: ["src/graphql/operations/**/*.graphql", "src/graphql/fragments/*.graphql"],
|
||||
generates: {
|
||||
"src/graphql/generated.ts": {
|
||||
plugins: [
|
||||
"typescript",
|
||||
"typescript-operations",
|
||||
"typescript-graphql-request",
|
||||
],
|
||||
plugins: ["typescript", "typescript-operations", "typescript-graphql-request"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -24,14 +24,5 @@ module.exports = {
|
|||
hreflang: "ja",
|
||||
},
|
||||
],
|
||||
exclude: [
|
||||
"/en/*",
|
||||
"/fr/*",
|
||||
"/ja/*",
|
||||
"/es/*",
|
||||
"/pt-br/*",
|
||||
"/404",
|
||||
"/500",
|
||||
"/dev/*",
|
||||
],
|
||||
exclude: ["/en/*", "/fr/*", "/ja/*", "/es/*", "/pt-br/*", "/404", "/500", "/dev/*"],
|
||||
};
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"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",
|
||||
"fetch-local-data": "esrun src/graphql/fetchLocalData.ts",
|
||||
"prebuild": "npm run generate && npm run fetch-local-data",
|
||||
"fetch-local-data": "npm run generate && esrun src/graphql/fetchLocalData.ts",
|
||||
"prebuild": "npm run fetch-local-data",
|
||||
"build": "next build",
|
||||
"postbuild": "next-sitemap --config next-sitemap.config.js",
|
||||
"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 => {
|
||||
const { langui } = useAppLayout();
|
||||
return (
|
||||
<ToolTip
|
||||
content={langui.copy_anchor_link}
|
||||
trigger="mouseenter"
|
||||
className="text-sm"
|
||||
>
|
||||
<ToolTip
|
||||
content={langui.anchor_link_copied}
|
||||
trigger="click"
|
||||
className="text-sm"
|
||||
>
|
||||
<ToolTip content={langui.copy_anchor_link} trigger="mouseenter" className="text-sm">
|
||||
<ToolTip content={langui.anchor_link_copied} trigger="click" className="text-sm">
|
||||
<Ico
|
||||
icon={Icon.Link}
|
||||
className={cJoin(
|
||||
"transition-color cursor-pointer hover:text-dark",
|
||||
className
|
||||
)}
|
||||
className={cJoin("transition-color cursor-pointer hover:text-dark", className)}
|
||||
onClick={() => {
|
||||
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 { Button } from "components/Inputs/Button";
|
||||
import { OpenGraph, TITLE_PREFIX, TITLE_SEPARATOR } from "helpers/openGraph";
|
||||
import {
|
||||
useIs1ColumnLayout,
|
||||
useIsScreenAtLeast,
|
||||
} from "hooks/useContainerQuery";
|
||||
import { useIs1ColumnLayout, useIsScreenAtLeast } from "hooks/useContainerQuery";
|
||||
import { useOnResize } from "hooks/useOnResize";
|
||||
import { Ids } from "types/ids";
|
||||
|
||||
|
@ -138,8 +135,7 @@ export const AppLayout = ({
|
|||
const [currencySelect, setCurrencySelect] = useState<number>(-1);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(currency))
|
||||
setCurrencySelect(currencyOptions.indexOf(currency));
|
||||
if (isDefined(currency)) setCurrencySelect(currencyOptions.indexOf(currency));
|
||||
}, [currency, currencyOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -147,14 +143,11 @@ export const AppLayout = ({
|
|||
}, [currencyOptions, currencySelect, setCurrency]);
|
||||
|
||||
const isClient = useIsClient();
|
||||
const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } =
|
||||
useBoolean(false);
|
||||
const { value: hasDisgardSafariWarning, setTrue: disgardSafariWarning } = useBoolean(false);
|
||||
const isSafari = useMemo<boolean>(() => {
|
||||
if (isClient) {
|
||||
const parser = new UAParser();
|
||||
return (
|
||||
parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS"
|
||||
);
|
||||
return parser.getBrowser().name === "Safari" || parser.getOS().name === "iOS";
|
||||
}
|
||||
return false;
|
||||
}, [isClient]);
|
||||
|
@ -164,27 +157,22 @@ export const AppLayout = ({
|
|||
className={cJoin(
|
||||
cIf(darkMode, "set-theme-dark", "set-theme-light"),
|
||||
cIf(dyslexic, "set-theme-font-dyslexic", "set-theme-font-standard")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<div
|
||||
{...handlers}
|
||||
id={Ids.Body}
|
||||
className={cJoin(
|
||||
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
|
||||
[grid-template-areas:'main_sub_content']`,
|
||||
cIf(
|
||||
is1ColumnLayout,
|
||||
"grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']"
|
||||
)
|
||||
cIf(is1ColumnLayout, "grid-rows-[1fr_5rem] [grid-template-areas:'content''navbar']")
|
||||
)}
|
||||
style={{
|
||||
gridTemplateColumns: is1ColumnLayout
|
||||
? "1fr"
|
||||
: `${
|
||||
mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu
|
||||
}rem ${isDefined(subPanel) ? layout.subMenu : 0}rem 1fr`,
|
||||
}}
|
||||
>
|
||||
: `${mainPanelReduced ? layout.mainMenuReduced : layout.mainMenu}rem ${
|
||||
isDefined(subPanel) ? layout.subMenu : 0
|
||||
}rem 1fr`,
|
||||
}}>
|
||||
<Head>
|
||||
<title>{openGraph.title}</title>
|
||||
<meta name="description" content={openGraph.description} />
|
||||
|
@ -197,18 +185,9 @@ export const AppLayout = ({
|
|||
<meta property="og:title" content={openGraph.title} />
|
||||
<meta property="og:description" content={openGraph.description} />
|
||||
<meta property="og:image" content={openGraph.thumbnail.image} />
|
||||
<meta
|
||||
property="og:image:secure_url"
|
||||
content={openGraph.thumbnail.image}
|
||||
/>
|
||||
<meta
|
||||
property="og:image:width"
|
||||
content={openGraph.thumbnail.width.toString()}
|
||||
/>
|
||||
<meta
|
||||
property="og:image:height"
|
||||
content={openGraph.thumbnail.height.toString()}
|
||||
/>
|
||||
<meta property="og:image:secure_url" content={openGraph.thumbnail.image} />
|
||||
<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:type" content="image/jpeg" />
|
||||
</Head>
|
||||
|
@ -230,22 +209,16 @@ export const AppLayout = ({
|
|||
"z-10 [backdrop-filter:blur(2px)]",
|
||||
"pointer-events-none touch-none"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<div
|
||||
className={cJoin(
|
||||
"absolute inset-0 bg-shade transition-opacity duration-500",
|
||||
cIf(
|
||||
(mainPanelOpen || subPanelOpen) && is1ColumnLayout,
|
||||
"opacity-60",
|
||||
"opacity-0"
|
||||
)
|
||||
cIf((mainPanelOpen || subPanelOpen) && is1ColumnLayout, "opacity-60", "opacity-0")
|
||||
)}
|
||||
onClick={() => {
|
||||
setMainPanelOpen(false);
|
||||
setSubPanelOpen(false);
|
||||
}}
|
||||
></div>
|
||||
}}></div>
|
||||
</div>
|
||||
|
||||
{/* Content panel */}
|
||||
|
@ -254,8 +227,7 @@ export const AppLayout = ({
|
|||
className={cJoin(
|
||||
"texture-paper-dots bg-light [grid-area:content]",
|
||||
cIf(contentPanelScroolbar, "overflow-y-scroll")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{isDefined(contentPanel) ? (
|
||||
contentPanel
|
||||
) : (
|
||||
|
@ -280,17 +252,10 @@ export const AppLayout = ({
|
|||
[grid-area:content]`,
|
||||
"[grid-area:sub]"
|
||||
),
|
||||
cIf(
|
||||
is1ColumnLayout && isScreenAtLeastXs,
|
||||
"w-[min(30rem,90%)] border-l-[1px]"
|
||||
),
|
||||
cIf(
|
||||
is1ColumnLayout && !subPanelOpen && !turnSubIntoContent,
|
||||
"translate-x-[100vw]"
|
||||
),
|
||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)] border-l-[1px]"),
|
||||
cIf(is1ColumnLayout && !subPanelOpen && !turnSubIntoContent, "translate-x-[100vw]"),
|
||||
cIf(is1ColumnLayout && turnSubIntoContent, "w-full border-l-0")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{subPanel}
|
||||
</div>
|
||||
)}
|
||||
|
@ -300,15 +265,10 @@ export const AppLayout = ({
|
|||
className={cJoin(
|
||||
`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`,
|
||||
cIf(
|
||||
is1ColumnLayout,
|
||||
"z-10 justify-self-start [grid-area:content]",
|
||||
"[grid-area:main]"
|
||||
),
|
||||
cIf(is1ColumnLayout, "z-10 justify-self-start [grid-area:content]", "[grid-area:main]"),
|
||||
cIf(is1ColumnLayout && isScreenAtLeastXs, "w-[min(30rem,90%)]"),
|
||||
cIf(!mainPanelOpen && is1ColumnLayout, "-translate-x-full")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<MainPanel />
|
||||
</div>
|
||||
|
||||
|
@ -318,8 +278,7 @@ export const AppLayout = ({
|
|||
`texture-paper-dots grid grid-cols-[5rem_1fr_5rem] place-items-center
|
||||
border-t-[1px] border-dotted border-black bg-light [grid-area:navbar]`,
|
||||
cIf(!is1ColumnLayout, "hidden")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<Ico
|
||||
icon={mainPanelOpen ? Icon.Close : Icon.Menu}
|
||||
className="mt-[.1em] cursor-pointer !text-2xl"
|
||||
|
@ -331,19 +290,10 @@ export const AppLayout = ({
|
|||
<p
|
||||
className={cJoin(
|
||||
"overflow-hidden text-center font-headers font-black",
|
||||
cIf(
|
||||
openGraph.title.length > 30,
|
||||
"max-h-14 text-xl",
|
||||
"max-h-16 text-2xl"
|
||||
)
|
||||
)}
|
||||
>
|
||||
{openGraph.title.substring(
|
||||
TITLE_PREFIX.length + TITLE_SEPARATOR.length
|
||||
)
|
||||
? openGraph.title.substring(
|
||||
TITLE_PREFIX.length + TITLE_SEPARATOR.length
|
||||
)
|
||||
cIf(openGraph.title.length > 30, "max-h-14 text-xl", "max-h-16 text-2xl")
|
||||
)}>
|
||||
{openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
|
||||
? openGraph.title.substring(TITLE_PREFIX.length + TITLE_SEPARATOR.length)
|
||||
: "Accord’s Library"}
|
||||
</p>
|
||||
{isDefined(subPanel) && !turnSubIntoContent && (
|
||||
|
@ -358,26 +308,16 @@ export const AppLayout = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<Popup
|
||||
state={isSafari && !hasDisgardSafariWarning}
|
||||
onClose={() => null}
|
||||
>
|
||||
<Popup state={isSafari && !hasDisgardSafariWarning} onClose={() => null}>
|
||||
<h1 className="text-2xl">Hi, you are using Safari!</h1>
|
||||
<p className="max-w-lg text-center">
|
||||
In most cases this wouldn’t be a problem but our website
|
||||
is—for some obscure reason—performing terribly on Safari (WebKit).
|
||||
Because of that, we have decided to display this message instead of
|
||||
letting you have a slow and painful experience. We are looking into
|
||||
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.
|
||||
In most cases this wouldn’t be a problem but our website is—for some obscure
|
||||
reason—performing terribly on Safari (WebKit). Because of that, we have decided to
|
||||
display this message instead of letting you have a slow and painful experience. We are
|
||||
looking into 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>
|
||||
|
||||
<Button
|
||||
text="Let me in regardless"
|
||||
|
@ -394,16 +334,14 @@ export const AppLayout = ({
|
|||
onClose={() => {
|
||||
setConfigPanelOpen(false);
|
||||
umami("[Settings] Close settings");
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<h2 className="text-2xl">{langui.settings}</h2>
|
||||
|
||||
<div
|
||||
className={cJoin(
|
||||
`mt-4 grid justify-items-center gap-16 text-center`,
|
||||
cIf(!is1ColumnLayout, "grid-cols-[auto_auto]")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{router.locales && (
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.languages}</h3>
|
||||
|
@ -420,14 +358,11 @@ export const AppLayout = ({
|
|||
},
|
||||
{
|
||||
insertAt: 1,
|
||||
name:
|
||||
langui.secondary_language ?? "Secondary languages",
|
||||
name: langui.secondary_language ?? "Secondary languages",
|
||||
},
|
||||
]}
|
||||
onChange={(items) => {
|
||||
const newPreferredLanguages = items.map(
|
||||
(item) => item.code
|
||||
);
|
||||
const newPreferredLanguages = items.map((item) => item.code);
|
||||
setPreferredLanguages(newPreferredLanguages);
|
||||
umami("[Settings] Change preferred languages");
|
||||
}}
|
||||
|
@ -439,8 +374,7 @@ export const AppLayout = ({
|
|||
className={cJoin(
|
||||
"grid place-items-center gap-8 text-center",
|
||||
cIf(!is1ColumnLayout, "grid-cols-2")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<div>
|
||||
<h3 className="text-xl">{langui.theme}</h3>
|
||||
<ButtonGroup
|
||||
|
@ -483,9 +417,7 @@ export const AppLayout = ({
|
|||
value={currencySelect}
|
||||
onChange={(newCurrency) => {
|
||||
setCurrencySelect(newCurrency);
|
||||
umami(
|
||||
`[Settings] Change currency (${currencyOptions[newCurrency]})}`
|
||||
);
|
||||
umami(`[Settings] Change currency (${currencyOptions[newCurrency]})}`);
|
||||
}}
|
||||
className="w-28"
|
||||
/>
|
||||
|
@ -500,12 +432,12 @@ export const AppLayout = ({
|
|||
onClick: () => {
|
||||
setFontSize((current) => current / 1.05);
|
||||
umami(
|
||||
`[Settings] Change font size (${(
|
||||
(fontSize / 1.05) *
|
||||
100
|
||||
).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 0,
|
||||
})}%)`
|
||||
`[Settings] Change font size (${((fontSize / 1.05) * 100).toLocaleString(
|
||||
undefined,
|
||||
{
|
||||
maximumFractionDigits: 0,
|
||||
}
|
||||
)}%)`
|
||||
);
|
||||
},
|
||||
icon: Icon.TextDecrease,
|
||||
|
@ -523,13 +455,12 @@ export const AppLayout = ({
|
|||
onClick: () => {
|
||||
setFontSize((current) => current * 1.05);
|
||||
umami(
|
||||
`[Settings] Change font size (${(
|
||||
fontSize *
|
||||
1.05 *
|
||||
100
|
||||
).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 0,
|
||||
})}%)`
|
||||
`[Settings] Change font size (${(fontSize * 1.05 * 100).toLocaleString(
|
||||
undefined,
|
||||
{
|
||||
maximumFractionDigits: 0,
|
||||
}
|
||||
)}%)`
|
||||
);
|
||||
},
|
||||
icon: Icon.TextIncrease,
|
||||
|
@ -589,21 +520,13 @@ interface ContentPlaceholderProps {
|
|||
icon?: Icon;
|
||||
}
|
||||
|
||||
const ContentPlaceholder = ({
|
||||
message,
|
||||
icon,
|
||||
}: ContentPlaceholderProps): JSX.Element => (
|
||||
const ContentPlaceholder = ({ message, icon }: ContentPlaceholderProps): JSX.Element => (
|
||||
<div className="grid h-full place-content-center">
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40"
|
||||
>
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
|
||||
<p
|
||||
className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}
|
||||
>
|
||||
{message}
|
||||
</p>
|
||||
<p className={cJoin("w-64 text-2xl", cIf(!isDefined(icon), "text-center"))}>{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -15,12 +15,11 @@ interface Props {
|
|||
export const Chip = ({ className, text }: Props): JSX.Element => (
|
||||
<div
|
||||
className={cJoin(
|
||||
`grid place-content-center place-items-center whitespace-nowrap rounded-full
|
||||
border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70
|
||||
transition-[color,_opacity,_border-color] hover:opacity-100`,
|
||||
`grid place-content-center place-items-center whitespace-nowrap rounded-full border-[1px]
|
||||
px-1.5 pb-[0.14rem] text-xs opacity-70 transition-[color,_opacity,_border-color]
|
||||
hover:opacity-100`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -17,27 +17,19 @@ interface Props {
|
|||
isActive?: boolean;
|
||||
}
|
||||
|
||||
const ChroniclePreview = ({
|
||||
date,
|
||||
url,
|
||||
title,
|
||||
isActive,
|
||||
}: Props): JSX.Element => (
|
||||
const ChroniclePreview = ({ date, url, title, isActive }: Props): JSX.Element => (
|
||||
<Link
|
||||
href={url}
|
||||
className={cJoin(
|
||||
`flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5
|
||||
text-left align-top outline outline-2 outline-offset-[-2px] outline-mid transition-all
|
||||
hover:bg-mid hover:shadow-inner-sm hover:shadow-shade
|
||||
hover:outline-[transparent] hover:active:shadow-inner hover:active:shadow-shade`,
|
||||
`flex w-full cursor-pointer gap-4 rounded-2xl py-4 px-5 text-left align-top outline outline-2
|
||||
outline-offset-[-2px] outline-mid transition-all hover:bg-mid hover:shadow-inner-sm
|
||||
hover:shadow-shade hover:outline-[transparent] hover:active:shadow-inner
|
||||
hover:active:shadow-shade`,
|
||||
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade outline-[transparent]")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<div className="text-right">
|
||||
<p>{date.year}</p>
|
||||
<p className="text-sm text-dark">
|
||||
{prettyMonthDay(date.month, date.day)}
|
||||
</p>
|
||||
<p className="text-sm text-dark">{prettyMonthDay(date.month, date.day)}</p>
|
||||
</div>
|
||||
<p className="text-lg leading-tight">{title}</p>
|
||||
</Link>
|
||||
|
@ -52,24 +44,13 @@ export const TranslatedChroniclePreview = ({
|
|||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Parameters<typeof ChroniclePreview>[0],
|
||||
"title"
|
||||
>): JSX.Element => {
|
||||
}: TranslatedProps<Parameters<typeof ChroniclePreview>[0], "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
|
||||
return (
|
||||
<ChroniclePreview
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <ChroniclePreview title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useBoolean } from "usehooks-ts";
|
|||
import { TranslatedChroniclePreview } from "./ChroniclePreview";
|
||||
import { GetChroniclesChaptersQuery } from "graphql/generated";
|
||||
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 { compareDate } from "helpers/date";
|
||||
import { TranslatedProps } from "types/TranslatedProps";
|
||||
|
@ -17,20 +17,14 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
|
|||
interface Props {
|
||||
chronicles: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetChroniclesChaptersQuery["chroniclesChapters"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"][number]["attributes"]
|
||||
>["chronicles"]
|
||||
>["data"];
|
||||
currentSlug?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const ChroniclesList = ({
|
||||
chronicles,
|
||||
currentSlug,
|
||||
title,
|
||||
}: Props): JSX.Element => {
|
||||
const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element => {
|
||||
const { value: isOpen, toggle: toggleOpen } = useBoolean(
|
||||
chronicles.some((chronicle) => chronicle.attributes?.slug === currentSlug)
|
||||
);
|
||||
|
@ -38,33 +32,21 @@ const ChroniclesList = ({
|
|||
return (
|
||||
<div>
|
||||
<div className="grid place-content-center">
|
||||
<div
|
||||
className="grid cursor-pointer grid-cols-[1em_1fr] gap-4"
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<Ico
|
||||
className="!text-xl"
|
||||
icon={isOpen ? Icon.ArrowDropUp : Icon.ArrowDropDown}
|
||||
/>
|
||||
<div className="grid cursor-pointer grid-cols-[1em_1fr] gap-4" onClick={toggleOpen}>
|
||||
<Ico className="!text-xl" icon={isOpen ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
|
||||
<p className="mb-4 font-headers text-xl">{title}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
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, [
|
||||
"attributes.contents",
|
||||
"attributes.translations",
|
||||
] as const)
|
||||
.sort((a, b) =>
|
||||
compareDate(a.attributes.date_start, b.attributes.date_start)
|
||||
)
|
||||
.sort((a, b) => compareDate(a.attributes.date_start, b.attributes.date_start))
|
||||
.map((chronicle) => (
|
||||
<div
|
||||
key={chronicle.id}
|
||||
id={`chronicle-${chronicle.attributes.slug}`}
|
||||
>
|
||||
<div key={chronicle.id} id={`chronicle-${chronicle.attributes.slug}`}>
|
||||
{chronicle.attributes.translations.length === 0 &&
|
||||
chronicle.attributes.contents.data.length === 1
|
||||
? filterHasAttributes(chronicle.attributes.contents.data, [
|
||||
|
@ -74,10 +56,9 @@ const ChroniclesList = ({
|
|||
key={index}
|
||||
isActive={chronicle.attributes.slug === currentSlug}
|
||||
date={chronicle.attributes.date_start}
|
||||
translations={filterHasAttributes(
|
||||
content.attributes.translations,
|
||||
["language.data.attributes.code"] as const
|
||||
).map((translation) => ({
|
||||
translations={filterHasAttributes(content.attributes.translations, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
title: prettyInlineTitle(
|
||||
translation.pre_title,
|
||||
translation.title,
|
||||
|
@ -88,24 +69,34 @@ const ChroniclesList = ({
|
|||
fallback={{
|
||||
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 && (
|
||||
<TranslatedChroniclePreview
|
||||
date={chronicle.attributes.date_start}
|
||||
isActive={chronicle.attributes.slug === currentSlug}
|
||||
translations={filterHasAttributes(
|
||||
chronicle.attributes.translations,
|
||||
["language.data.attributes.code", "title"] as const
|
||||
).map((translation) => ({
|
||||
translations={filterHasAttributes(chronicle.attributes.translations, [
|
||||
"language.data.attributes.code",
|
||||
"title",
|
||||
] as const).map((translation) => ({
|
||||
title: translation.title,
|
||||
language: translation.language.data.attributes.code,
|
||||
}))}
|
||||
fallback={{
|
||||
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>
|
||||
|
@ -127,16 +118,8 @@ export const TranslatedChroniclesList = ({
|
|||
}: TranslatedProps<Props, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
|
||||
return (
|
||||
<ChroniclesList
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <ChroniclesList title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||
};
|
||||
|
|
|
@ -13,9 +13,5 @@ interface Props {
|
|||
|
||||
export const HorizontalLine = ({ className }: Props): JSX.Element => (
|
||||
<div
|
||||
className={cJoin(
|
||||
"my-8 h-0 w-full border-t-[3px] border-dotted border-black",
|
||||
className
|
||||
)}
|
||||
></div>
|
||||
className={cJoin("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 => (
|
||||
<span
|
||||
onClick={onClick}
|
||||
className={cJoin(
|
||||
"material-icons [font-size:inherit] [line-height:inherit]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
className={cJoin("material-icons [font-size:inherit] [line-height:inherit]", className)}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -8,10 +8,7 @@ import { getAssetURL, ImageQuality } from "helpers/img";
|
|||
*/
|
||||
|
||||
interface Props
|
||||
extends Omit<
|
||||
DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>,
|
||||
"src"
|
||||
> {
|
||||
extends Omit<DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "src"> {
|
||||
src: UploadImageFragment | string;
|
||||
quality?: ImageQuality;
|
||||
}
|
||||
|
@ -26,15 +23,6 @@ export const Img = ({
|
|||
loading = "lazy",
|
||||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const src =
|
||||
typeof rawSrc === "string" ? rawSrc : getAssetURL(rawSrc.url, quality);
|
||||
return (
|
||||
<img
|
||||
className={className}
|
||||
src={src}
|
||||
alt={alt}
|
||||
loading={loading}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
const src = typeof rawSrc === "string" ? rawSrc : getAssetURL(rawSrc.url, quality);
|
||||
return <img className={className} src={src} alt={alt} loading={loading} {...otherProps} />;
|
||||
};
|
||||
|
|
|
@ -46,8 +46,7 @@ export const Button = ({
|
|||
<ConditionalWrapper
|
||||
isWrapping={isDefinedAndNotEmpty(href)}
|
||||
wrapperProps={{ href: href ?? "", alwaysNewTab }}
|
||||
wrapper={LinkWrapper}
|
||||
>
|
||||
wrapper={LinkWrapper}>
|
||||
<div className="relative">
|
||||
<div
|
||||
draggable={draggable}
|
||||
|
@ -56,36 +55,32 @@ export const Button = ({
|
|||
onFocus={(event) => event.target.blur()}
|
||||
className={cJoin(
|
||||
`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
|
||||
leading-none text-dark transition-all`,
|
||||
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4
|
||||
leading-none text-dark transition-all`,
|
||||
cIf(
|
||||
active,
|
||||
"!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
|
||||
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(disabled, "cursor-not-allowed"),
|
||||
className
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{isDefined(badgeNumber) && (
|
||||
<div
|
||||
className={cJoin(
|
||||
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center
|
||||
rounded-full bg-dark font-bold text-light transition-opacity group-hover:opacity-0`,
|
||||
`absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full bg-dark
|
||||
font-bold text-light transition-opacity group-hover:opacity-0`,
|
||||
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
||||
</div>
|
||||
)}
|
||||
{isDefinedAndNotEmpty(icon) && (
|
||||
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
|
||||
)}
|
||||
{isDefinedAndNotEmpty(text) && (
|
||||
<p className="-translate-y-[0.05em] text-center">{text}</p>
|
||||
)}
|
||||
{isDefinedAndNotEmpty(text) && <p className="-translate-y-[0.05em] text-center">{text}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</ConditionalWrapper>
|
||||
|
@ -103,15 +98,10 @@ export const TranslatedButton = ({
|
|||
}: TranslatedProps<Props, "text">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
|
||||
return (
|
||||
<Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />
|
||||
);
|
||||
return <Button text={selectedTranslation?.text ?? fallback.text} {...otherProps} />;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -124,11 +114,7 @@ interface LinkWrapperProps {
|
|||
alwaysNewTab: boolean;
|
||||
}
|
||||
|
||||
const LinkWrapper = ({
|
||||
children,
|
||||
alwaysNewTab,
|
||||
href,
|
||||
}: LinkWrapperProps & Wrapper) => (
|
||||
const LinkWrapper = ({ children, alwaysNewTab, href }: LinkWrapperProps & Wrapper) => (
|
||||
<Link href={href} alwaysNewTab={alwaysNewTab}>
|
||||
{children}
|
||||
</Link>
|
||||
|
|
|
@ -18,18 +18,14 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ButtonGroup = ({
|
||||
buttonsProps,
|
||||
className,
|
||||
}: Props): JSX.Element => (
|
||||
export const ButtonGroup = ({ buttonsProps, className }: Props): JSX.Element => (
|
||||
<div className={cJoin("grid grid-flow-col", className)}>
|
||||
{buttonsProps.map((buttonProps, index) => (
|
||||
<ConditionalWrapper
|
||||
key={index}
|
||||
isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)}
|
||||
wrapper={ToolTipWrapper}
|
||||
wrapperProps={{ text: buttonProps.tooltip ?? "" }}
|
||||
>
|
||||
wrapperProps={{ text: buttonProps.tooltip ?? "" }}>
|
||||
<Button
|
||||
{...buttonProps}
|
||||
className={
|
||||
|
|
|
@ -49,8 +49,7 @@ export const LanguageSwitcher = ({
|
|||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button
|
||||
badgeNumber={showBadge && locales.size > 1 ? locales.size : undefined}
|
||||
icon={Icon.Translate}
|
||||
|
|
|
@ -48,8 +48,7 @@ export const Link = ({
|
|||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -16,11 +16,7 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const OrderableList = ({
|
||||
onChange,
|
||||
items,
|
||||
insertLabels,
|
||||
}: Props): JSX.Element => {
|
||||
export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Element => {
|
||||
const updateOrder = useCallback(
|
||||
(sourceIndex: number, targetIndex: number) => {
|
||||
console.log("updateOrder");
|
||||
|
@ -57,17 +53,13 @@ export const OrderableList = ({
|
|||
.filter((element) => element.tagName === "DIV")
|
||||
.indexOf(target)
|
||||
: -1;
|
||||
const sourceIndex = parseInt(
|
||||
event.dataTransfer.getData("text"),
|
||||
10
|
||||
);
|
||||
const sourceIndex = parseInt(event.dataTransfer.getData("text"), 10);
|
||||
updateOrder(sourceIndex, targetIndex);
|
||||
}}
|
||||
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
|
||||
hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
|
||||
draggable
|
||||
>
|
||||
draggable>
|
||||
<div className="grid grid-rows-[.8em_.8em] place-items-center">
|
||||
{index > 0 && (
|
||||
<Ico
|
||||
|
|
|
@ -16,12 +16,7 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const PageSelector = ({
|
||||
page,
|
||||
className,
|
||||
pagesCount,
|
||||
onChange,
|
||||
}: Props): JSX.Element => (
|
||||
export const PageSelector = ({ page, className, pagesCount, onChange }: Props): JSX.Element => (
|
||||
<ButtonGroup
|
||||
className={cJoin("flex flex-row place-content-center", className)}
|
||||
buttonsProps={[
|
||||
|
|
|
@ -19,18 +19,8 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const Select = ({
|
||||
className,
|
||||
value,
|
||||
options,
|
||||
allowEmpty,
|
||||
onChange,
|
||||
}: Props): JSX.Element => {
|
||||
const {
|
||||
value: isOpened,
|
||||
setFalse: setClosed,
|
||||
toggle: toggleOpened,
|
||||
} = useBoolean(false);
|
||||
export const Select = ({ className, value, options, allowEmpty, onChange }: Props): JSX.Element => {
|
||||
const { value: isOpened, setFalse: setClosed, toggle: toggleOpened } = useBoolean(false);
|
||||
|
||||
const tryToggling = useCallback(() => {
|
||||
const optionCount = options.length + (value === -1 ? 1 : 0);
|
||||
|
@ -47,16 +37,14 @@ export const Select = ({
|
|||
"relative text-center transition-[filter]",
|
||||
cIf(isOpened, "z-10 drop-shadow-shade-lg"),
|
||||
className
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<div
|
||||
className={cJoin(
|
||||
`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
|
||||
transition-all hover:bg-mid hover:outline-[transparent]`,
|
||||
cIf(isOpened, "rounded-b-none bg-highlight outline-[transparent]")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<p onClick={tryToggling} className="w-full">
|
||||
{value === -1 ? "—" : options[value]}
|
||||
</p>
|
||||
|
@ -70,17 +58,9 @@ export const Select = ({
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
<Ico
|
||||
onClick={tryToggling}
|
||||
icon={isOpened ? Icon.ArrowDropUp : Icon.ArrowDropDown}
|
||||
/>
|
||||
<Ico onClick={tryToggling} icon={isOpened ? Icon.ArrowDropUp : Icon.ArrowDropDown} />
|
||||
</div>
|
||||
<div
|
||||
className={cJoin(
|
||||
"left-0 right-0 rounded-b-[1em]",
|
||||
cIf(isOpened, "absolute", "hidden")
|
||||
)}
|
||||
>
|
||||
<div className={cJoin("left-0 right-0 rounded-b-[1em]", cIf(isOpened, "absolute", "hidden"))}>
|
||||
{options.map((option, index) => (
|
||||
<Fragment key={index}>
|
||||
{index !== value && (
|
||||
|
@ -93,8 +73,7 @@ export const Select = ({
|
|||
onClick={() => {
|
||||
setClosed();
|
||||
onChange(index);
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{option}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -14,12 +14,7 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const Switch = ({
|
||||
value,
|
||||
onClick,
|
||||
className,
|
||||
disabled = false,
|
||||
}: Props): JSX.Element => (
|
||||
export const Switch = ({ value, onClick, className, disabled = false }: Props): JSX.Element => (
|
||||
<div
|
||||
className={cJoin(
|
||||
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
|
||||
|
@ -29,17 +24,11 @@ export const Switch = ({
|
|||
)}
|
||||
onClick={() => {
|
||||
if (!disabled) onClick();
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<div
|
||||
className={cJoin(
|
||||
"absolute aspect-square rounded-full bg-dark transition-transform",
|
||||
cIf(
|
||||
value,
|
||||
"top-[2px] bottom-[2px] left-[2px] translate-x-[120%]",
|
||||
"top-0 bottom-0 left-0"
|
||||
)
|
||||
)}
|
||||
></div>
|
||||
cIf(value, "top-[2px] bottom-[2px] left-[2px] translate-x-[120%]", "top-0 bottom-0 left-0")
|
||||
)}></div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,23 +14,14 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const WithLabel = ({
|
||||
label,
|
||||
children,
|
||||
disabled,
|
||||
}: Props): JSX.Element => (
|
||||
export const WithLabel = ({ label, children, disabled }: Props): JSX.Element => (
|
||||
<div
|
||||
className={cJoin(
|
||||
"flex flex-row place-content-between place-items-center gap-2",
|
||||
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{isDefinedAndNotEmpty(label) && (
|
||||
<p
|
||||
className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}
|
||||
>
|
||||
{label}:
|
||||
</p>
|
||||
<p className={cJoin("text-left", cIf(label.length < 10, "flex-shrink-0"))}>{label}:</p>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -16,11 +16,7 @@ interface Props {
|
|||
export const InsetBox = ({ id, className, children }: Props): JSX.Element => (
|
||||
<div
|
||||
id={id}
|
||||
className={cJoin(
|
||||
"w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade",
|
||||
className
|
||||
)}
|
||||
>
|
||||
className={cJoin("w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Icon } from "components/Ico";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { LibraryItemUserStatus } from "helpers/types";
|
||||
import { LibraryItemUserStatus } from "types/types";
|
||||
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 => {
|
||||
const { libraryItemUserStatus, setLibraryItemUserStatus, langui } =
|
||||
useAppLayout();
|
||||
const { libraryItemUserStatus, setLibraryItemUserStatus, langui } = useAppLayout();
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-row flex-wrap place-content-center place-items-center ${
|
||||
expand ? "gap-4" : "gap-2"
|
||||
}`}
|
||||
>
|
||||
<ToolTip content={langui.want_it} disabled={expand}>
|
||||
<Button
|
||||
icon={Icon.Favorite}
|
||||
text={expand ? langui.want_it : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setLibraryItemUserStatus((current) => {
|
||||
const newLibraryItemUserStatus = { ...current };
|
||||
newLibraryItemUserStatus[id] =
|
||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
||||
? LibraryItemUserStatus.None
|
||||
: LibraryItemUserStatus.Want;
|
||||
return newLibraryItemUserStatus;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ToolTip>
|
||||
<ToolTip content={langui.have_it} disabled={expand}>
|
||||
<Button
|
||||
icon={Icon.BackHand}
|
||||
text={expand ? langui.have_it : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setLibraryItemUserStatus((current) => {
|
||||
const newLibraryItemUserStatus = { ...current };
|
||||
newLibraryItemUserStatus[id] =
|
||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
||||
? LibraryItemUserStatus.None
|
||||
: LibraryItemUserStatus.Have;
|
||||
return newLibraryItemUserStatus;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ToolTip>
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
className={cJoin(
|
||||
"flex flex-row flex-wrap place-content-center place-items-center",
|
||||
cIf(expand, "gap-4", "gap-2")
|
||||
)}>
|
||||
<ToolTip content={langui.want_it} disabled={expand}>
|
||||
<Button
|
||||
icon={Icon.Favorite}
|
||||
text={expand ? langui.want_it : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Want}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setLibraryItemUserStatus((current) => {
|
||||
const newLibraryItemUserStatus = { ...current };
|
||||
newLibraryItemUserStatus[id] =
|
||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Want
|
||||
? LibraryItemUserStatus.None
|
||||
: LibraryItemUserStatus.Want;
|
||||
return newLibraryItemUserStatus;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ToolTip>
|
||||
<ToolTip content={langui.have_it} disabled={expand}>
|
||||
<Button
|
||||
icon={Icon.BackHand}
|
||||
text={expand ? langui.have_it : undefined}
|
||||
active={libraryItemUserStatus[id] === LibraryItemUserStatus.Have}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setLibraryItemUserStatus((current) => {
|
||||
const newLibraryItemUserStatus = { ...current };
|
||||
newLibraryItemUserStatus[id] =
|
||||
newLibraryItemUserStatus[id] === LibraryItemUserStatus.Have
|
||||
? LibraryItemUserStatus.None
|
||||
: LibraryItemUserStatus.Have;
|
||||
return newLibraryItemUserStatus;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ToolTip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,9 +21,7 @@ const SENSIBILITY_SWIPE = 0.5;
|
|||
*/
|
||||
|
||||
interface Props {
|
||||
setState:
|
||||
| Dispatch<SetStateAction<boolean | undefined>>
|
||||
| Dispatch<SetStateAction<boolean>>;
|
||||
setState: Dispatch<SetStateAction<boolean | undefined>> | Dispatch<SetStateAction<boolean>>;
|
||||
state: boolean;
|
||||
images: string[];
|
||||
index: number;
|
||||
|
@ -32,13 +30,7 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const LightBox = ({
|
||||
state,
|
||||
setState,
|
||||
images,
|
||||
index,
|
||||
setIndex,
|
||||
}: Props): JSX.Element => {
|
||||
export const LightBox = ({ state, setState, images, index, setIndex }: Props): JSX.Element => {
|
||||
const handlePrevious = useCallback(() => {
|
||||
if (index > 0) setIndex(index - 1);
|
||||
}, [index, setIndex]);
|
||||
|
@ -71,14 +63,8 @@ export const LightBox = ({
|
|||
} else {
|
||||
handleNext();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Popup
|
||||
onClose={() => setState(false)}
|
||||
state={state}
|
||||
padding={false}
|
||||
fillViewport
|
||||
>
|
||||
}}>
|
||||
<Popup onClose={() => setState(false)} state={state} padding={false} fillViewport>
|
||||
<div
|
||||
{...handlers}
|
||||
className={cJoin(
|
||||
|
@ -88,18 +74,12 @@ export const LightBox = ({
|
|||
`grid-cols-[4em,1fr,4em] [grid-template-areas:"left_image_right"]`,
|
||||
`grid-cols-2 [grid-template-areas:"image_image""left_right"]`
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<div className="ml-4 [grid-area:left]">
|
||||
{index > 0 && (
|
||||
<Button onClick={handlePrevious} icon={Icon.ChevronLeft} />
|
||||
)}
|
||||
{index > 0 && <Button onClick={handlePrevious} icon={Icon.ChevronLeft} />}
|
||||
</div>
|
||||
|
||||
<Img
|
||||
className="max-h-full min-h-fit [grid-area:image]"
|
||||
src={images[index]}
|
||||
/>
|
||||
<Img className="max-h-full min-h-fit [grid-area:image]" src={images[index]} />
|
||||
|
||||
<div className="mr-4 [grid-area:right]">
|
||||
{index < images.length - 1 && (
|
||||
|
|
|
@ -29,10 +29,7 @@ interface MarkdawnProps {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const Markdawn = ({
|
||||
className,
|
||||
text: rawText,
|
||||
}: MarkdawnProps): JSX.Element => {
|
||||
export const Markdawn = ({ className, text: rawText }: MarkdawnProps): JSX.Element => {
|
||||
const { playerName } = useAppLayout();
|
||||
const router = useRouter();
|
||||
const isContentPanelAtLeastLg = useIsContentPanelAtLeast("lg");
|
||||
|
@ -59,18 +56,10 @@ export const Markdawn = ({
|
|||
slugify: slugify,
|
||||
overrides: {
|
||||
a: {
|
||||
component: (compProps: {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (
|
||||
compProps.href.startsWith("/") ||
|
||||
compProps.href.startsWith("#")
|
||||
) {
|
||||
component: (compProps: { href: string; children: React.ReactNode }) => {
|
||||
if (compProps.href.startsWith("/") || compProps.href.startsWith("#")) {
|
||||
return (
|
||||
<a onClick={async () => router.push(compProps.href)}>
|
||||
{compProps.children}
|
||||
</a>
|
||||
<a onClick={async () => router.push(compProps.href)}>{compProps.children}</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
@ -112,11 +101,7 @@ export const Markdawn = ({
|
|||
? slugify(compProps.target)
|
||||
: slugify(compProps.children?.toString());
|
||||
return (
|
||||
<a
|
||||
onClick={async () =>
|
||||
router.replace(`${compProps.page ?? ""}#${slug}`)
|
||||
}
|
||||
>
|
||||
<a onClick={async () => router.replace(`${compProps.page ?? ""}#${slug}`)}>
|
||||
{compProps.children}
|
||||
</a>
|
||||
);
|
||||
|
@ -128,13 +113,8 @@ export const Markdawn = ({
|
|||
<div
|
||||
className={cJoin(
|
||||
"grid gap-x-6 gap-y-2",
|
||||
cIf(
|
||||
isContentPanelAtLeastLg,
|
||||
"grid-cols-[auto_1fr]",
|
||||
"grid-cols-1"
|
||||
)
|
||||
)}
|
||||
>
|
||||
cIf(isContentPanelAtLeastLg, "grid-cols-[auto_1fr]", "grid-cols-1")
|
||||
)}>
|
||||
{compProps.children}
|
||||
</div>
|
||||
),
|
||||
|
@ -147,8 +127,7 @@ export const Markdawn = ({
|
|||
className={cJoin(
|
||||
"!my-0 text-dark/60",
|
||||
cIf(!isContentPanelAtLeastLg, "!-mb-4")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<Markdawn text={compProps.name} />
|
||||
</strong>
|
||||
<p className="whitespace-pre-line">{compProps.children}</p>
|
||||
|
@ -157,9 +136,7 @@ export const Markdawn = ({
|
|||
},
|
||||
|
||||
InsetBox: {
|
||||
component: (compProps) => (
|
||||
<InsetBox className="my-12">{compProps.children}</InsetBox>
|
||||
),
|
||||
component: (compProps) => <InsetBox className="my-12">{compProps.children}</InsetBox>,
|
||||
},
|
||||
|
||||
li: {
|
||||
|
@ -167,13 +144,10 @@ export const Markdawn = ({
|
|||
<li
|
||||
className={
|
||||
isDefined(compProps.children) &&
|
||||
ReactDOMServer.renderToStaticMarkup(
|
||||
<>{compProps.children}</>
|
||||
).length > 100
|
||||
ReactDOMServer.renderToStaticMarkup(<>{compProps.children}</>).length > 100
|
||||
? "my-4"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
}>
|
||||
{compProps.children}
|
||||
</li>
|
||||
),
|
||||
|
@ -195,10 +169,7 @@ export const Markdawn = ({
|
|||
},
|
||||
|
||||
blockquote: {
|
||||
component: (compProps: {
|
||||
children: React.ReactNode;
|
||||
cite?: string;
|
||||
}) => (
|
||||
component: (compProps: { children: React.ReactNode; cite?: string }) => (
|
||||
<blockquote>
|
||||
{isDefinedAndNotEmpty(compProps.cite) ? (
|
||||
<>
|
||||
|
@ -229,8 +200,7 @@ export const Markdawn = ({
|
|||
? getAssetURL(compProps.src, ImageQuality.Large)
|
||||
: compProps.src,
|
||||
]);
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<Img
|
||||
src={
|
||||
compProps.src.startsWith("/uploads/")
|
||||
|
@ -238,14 +208,12 @@ export const Markdawn = ({
|
|||
: compProps.src
|
||||
}
|
||||
quality={ImageQuality.Medium}
|
||||
className="drop-shadow-shade-lg"
|
||||
></Img>
|
||||
className="drop-shadow-shade-lg"></Img>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{text}
|
||||
</Markdown>
|
||||
</>
|
||||
|
@ -269,10 +237,7 @@ export const TableOfContents = ({
|
|||
}: TableOfContentsProps): JSX.Element => {
|
||||
const router = useRouter();
|
||||
const { langui } = useAppLayout();
|
||||
const toc = useMemo(
|
||||
() => getTocFromMarkdawn(preprocessMarkDawn(text), title),
|
||||
[text, title]
|
||||
);
|
||||
const toc = useMemo(() => getTocFromMarkdawn(preprocessMarkDawn(text), title), [text, title]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -283,8 +248,7 @@ export const TableOfContents = ({
|
|||
<div className="max-w-[14.5rem] text-left">
|
||||
<p
|
||||
className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap
|
||||
text-left"
|
||||
>
|
||||
text-left">
|
||||
<a onClick={async () => router.replace(`#${toc.slug}`)}>
|
||||
{<abbr title={toc.title}>{toc.title}</abbr>}
|
||||
</a>
|
||||
|
@ -324,10 +288,7 @@ const Header = ({ level, title, slug }: HeaderProps): JSX.Element => {
|
|||
<div className="font-headers">{title}</div>
|
||||
)}
|
||||
<AnchorShare
|
||||
className={cIf(
|
||||
isHoverable,
|
||||
"opacity-0 transition-opacity group-hover:opacity-100"
|
||||
)}
|
||||
className={cIf(isHoverable, "opacity-0 transition-opacity group-hover:opacity-100")}
|
||||
id={slug}
|
||||
/>
|
||||
</div>
|
||||
|
@ -395,10 +356,7 @@ const TocLevel = ({
|
|||
}: LevelProps): JSX.Element => {
|
||||
const router = useRouter();
|
||||
|
||||
const ids = useMemo(
|
||||
() => tocchildren.map((child) => child.slug),
|
||||
[tocchildren]
|
||||
);
|
||||
const ids = useMemo(() => tocchildren.map((child) => child.slug), [tocchildren]);
|
||||
const currentIntersection = useIntersectionList(ids);
|
||||
|
||||
return (
|
||||
|
@ -408,15 +366,9 @@ const TocLevel = ({
|
|||
<li
|
||||
className={cJoin(
|
||||
"my-2 w-full overflow-x-hidden text-ellipsis whitespace-nowrap",
|
||||
cIf(
|
||||
allowIntersection && currentIntersection === childIndex,
|
||||
"text-dark"
|
||||
)
|
||||
)}
|
||||
>
|
||||
<span className="text-dark">{`${parentNumbering}${
|
||||
childIndex + 1
|
||||
}.`}</span>{" "}
|
||||
cIf(allowIntersection && currentIntersection === childIndex, "text-dark")
|
||||
)}>
|
||||
<span className="text-dark">{`${parentNumbering}${childIndex + 1}.`}</span>{" "}
|
||||
<a onClick={async () => router.replace(`#${child.slug}`)}>
|
||||
{<abbr title={child.title}>{child.title}</abbr>}
|
||||
</a>
|
||||
|
@ -424,9 +376,7 @@ const TocLevel = ({
|
|||
<TocLevel
|
||||
tocchildren={child.children}
|
||||
parentNumbering={`${parentNumbering}${childIndex + 1}.`}
|
||||
allowIntersection={
|
||||
allowIntersection && currentIntersection === childIndex
|
||||
}
|
||||
allowIntersection={allowIntersection && currentIntersection === childIndex}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
@ -442,17 +392,13 @@ const TocLevel = ({
|
|||
const preprocessMarkDawn = (text: string, playerName = ""): string => {
|
||||
if (!text) return "";
|
||||
|
||||
const processedPlayerName = playerName
|
||||
.replaceAll("_", "\\_")
|
||||
.replaceAll("*", "\\*");
|
||||
const processedPlayerName = playerName.replaceAll("_", "\\_").replaceAll("*", "\\*");
|
||||
|
||||
let preprocessed = text
|
||||
.replaceAll("--", "—")
|
||||
.replaceAll(
|
||||
"@player",
|
||||
isDefinedAndNotEmpty(processedPlayerName)
|
||||
? processedPlayerName
|
||||
: "(player)"
|
||||
isDefinedAndNotEmpty(processedPlayerName) ? processedPlayerName : "(player)"
|
||||
);
|
||||
|
||||
let scenebreakIndex = 0;
|
||||
|
@ -511,8 +457,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
|||
let scenebreak = 0;
|
||||
let scenebreakIndex = 0;
|
||||
|
||||
const getTitle = (line: string): string =>
|
||||
line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
|
||||
const getTitle = (line: string): string => line.slice(line.indexOf(`">`) + 2, line.indexOf("</"));
|
||||
|
||||
const getSlug = (line: string): string =>
|
||||
line.slice(line.indexOf(`id="`) + 4, line.indexOf(`">`));
|
||||
|
@ -573,9 +518,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
|||
};
|
||||
|
||||
if (h5 >= 0) {
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push(
|
||||
child
|
||||
);
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push(child);
|
||||
} else if (h4 >= 0) {
|
||||
toc.children[h2].children[h3].children[h4].children.push(child);
|
||||
} else if (h3 >= 0) {
|
||||
|
|
|
@ -47,15 +47,12 @@ export const NavOption = ({
|
|||
content={
|
||||
<div>
|
||||
<h3 className="text-2xl">{title}</h3>
|
||||
{isDefinedAndNotEmpty(subtitle) && (
|
||||
<p className="col-start-2">{subtitle}</p>
|
||||
)}
|
||||
{isDefinedAndNotEmpty(subtitle) && <p className="col-start-2">{subtitle}</p>}
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!reduced}
|
||||
>
|
||||
disabled={!reduced}>
|
||||
<Link
|
||||
href={url}
|
||||
onClick={onClick}
|
||||
|
@ -69,16 +66,13 @@ export const NavOption = ({
|
|||
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
|
||||
),
|
||||
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />}
|
||||
|
||||
{!reduced && (
|
||||
<div>
|
||||
<h3 className="text-2xl">{title}</h3>
|
||||
{isDefinedAndNotEmpty(subtitle) && (
|
||||
<p className="col-start-2">{subtitle}</p>
|
||||
)}
|
||||
{isDefinedAndNotEmpty(subtitle) && <p className="col-start-2">{subtitle}</p>}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
|
@ -98,10 +92,7 @@ export const TranslatedNavOption = ({
|
|||
}: TranslatedProps<Props, "subtitle" | "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
return (
|
||||
<NavOption
|
||||
|
|
|
@ -14,11 +14,7 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const PanelHeader = ({
|
||||
icon,
|
||||
description,
|
||||
title,
|
||||
}: Props): JSX.Element => (
|
||||
export const PanelHeader = ({ icon, description, title }: Props): JSX.Element => (
|
||||
<>
|
||||
<div className="grid w-full place-items-center">
|
||||
{icon && <Ico icon={icon} className="mb-3 !text-4xl" />}
|
||||
|
|
|
@ -22,12 +22,7 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ReturnButton = ({
|
||||
href,
|
||||
title,
|
||||
displayOnlyOn,
|
||||
className,
|
||||
}: Props): JSX.Element => {
|
||||
export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => {
|
||||
const { setSubPanelOpen, langui } = useAppLayout();
|
||||
const is3ColumnsLayout = useIs3ColumnsLayout();
|
||||
|
||||
|
@ -61,16 +56,8 @@ export const TranslatedReturnButton = ({
|
|||
}: TranslatedProps<Props, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
|
||||
return (
|
||||
<ReturnButton
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <ReturnButton title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||
};
|
||||
|
|
|
@ -38,8 +38,7 @@ export const ContentPanel = ({
|
|||
? "max-w-4xl"
|
||||
: "w-full",
|
||||
className
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -29,16 +29,14 @@ export const MainPanel = (): JSX.Element => {
|
|||
className={cJoin(
|
||||
"grid content-start justify-center gap-y-2 p-8 text-center",
|
||||
cIf(mainPanelReduced && is3ColumnsLayout, "px-4")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{/* Reduce/expand main menu */}
|
||||
{is3ColumnsLayout && (
|
||||
<div
|
||||
className={cJoin(
|
||||
"fixed top-1/2",
|
||||
cIf(mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (mainPanelReduced) {
|
||||
|
@ -63,8 +61,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
|
||||
![mask-position:center] hover:bg-dark`,
|
||||
cIf(mainPanelReduced && is3ColumnsLayout, "w-12", "w-1/2")
|
||||
)}
|
||||
></div>
|
||||
)}></div>
|
||||
</Link>
|
||||
|
||||
{(!mainPanelReduced || !is3ColumnsLayout) && (
|
||||
|
@ -74,19 +71,13 @@ export const MainPanel = (): JSX.Element => {
|
|||
<div
|
||||
className={cJoin(
|
||||
"flex flex-wrap gap-2",
|
||||
cIf(
|
||||
mainPanelReduced && is3ColumnsLayout,
|
||||
"flex-col gap-3",
|
||||
"flex-row"
|
||||
)
|
||||
)}
|
||||
>
|
||||
cIf(mainPanelReduced && is3ColumnsLayout, "flex-col gap-3", "flex-row")
|
||||
)}>
|
||||
<ToolTip
|
||||
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
|
||||
placement="right"
|
||||
className="text-left"
|
||||
disabled={!mainPanelReduced}
|
||||
>
|
||||
disabled={!mainPanelReduced}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setConfigPanelOpen(true);
|
||||
|
@ -174,12 +165,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
|
||||
{mainPanelReduced && is3ColumnsLayout ? "" : <HorizontalLine />}
|
||||
|
||||
<div
|
||||
className={cJoin(
|
||||
"text-center",
|
||||
cIf(mainPanelReduced && is3ColumnsLayout, "hidden")
|
||||
)}
|
||||
>
|
||||
<div className={cJoin("text-center", cIf(mainPanelReduced && is3ColumnsLayout, "hidden"))}>
|
||||
{isDefinedAndNotEmpty(langui.licensing_notice) && (
|
||||
<p>
|
||||
<Markdown>{langui.licensing_notice}</Markdown>
|
||||
|
@ -190,8 +176,7 @@ export const MainPanel = (): JSX.Element => {
|
|||
onClick={() => umami("[MainPanel] Visit license")}
|
||||
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]"
|
||||
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
||||
>
|
||||
href="https://creativecommons.org/licenses/by-sa/4.0/">
|
||||
<div
|
||||
className="aspect-square w-6 bg-black transition-colors
|
||||
[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"
|
||||
href="https://github.com/Accords-Library"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
></a>
|
||||
rel="noopener noreferrer"></a>
|
||||
<a
|
||||
aria-label="Follow us on 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"
|
||||
href="https://twitter.com/AccordsLibrary"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
></a>
|
||||
rel="noopener noreferrer"></a>
|
||||
<a
|
||||
aria-label="Join our Discord server!"
|
||||
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"
|
||||
href="/discord"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
></a>
|
||||
rel="noopener noreferrer"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,8 +19,7 @@ export const SubPanel = ({ children }: Props): JSX.Element => {
|
|||
className={cJoin(
|
||||
"grid gap-y-2 text-center",
|
||||
cIf(isSubPanelAtLeastSm, "px-10 pt-10 pb-20", "p-4")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -38,13 +38,8 @@ export const Popup = ({
|
|||
<div
|
||||
className={cJoin(
|
||||
"fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500",
|
||||
cIf(
|
||||
state,
|
||||
"[backdrop-filter:blur(2px)]",
|
||||
"pointer-events-none touch-none"
|
||||
)
|
||||
)}
|
||||
>
|
||||
cIf(state, "[backdrop-filter:blur(2px)]", "pointer-events-none touch-none")
|
||||
)}>
|
||||
<div
|
||||
className={cJoin(
|
||||
"fixed inset-0 bg-shade transition-all duration-500",
|
||||
|
@ -58,14 +53,9 @@ export const Popup = ({
|
|||
"grid place-items-center gap-4 transition-transform",
|
||||
cIf(padding, "p-10"),
|
||||
cIf(state, "scale-100", "scale-0"),
|
||||
cIf(
|
||||
fillViewport,
|
||||
"absolute inset-10",
|
||||
"relative max-h-[80vh] overflow-y-auto"
|
||||
),
|
||||
cIf(fillViewport, "absolute inset-10", "relative max-h-[80vh] overflow-y-auto"),
|
||||
cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { RecorderChip } from "./RecorderChip";
|
|||
import { ThumbnailHeader } from "./ThumbnailHeader";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { PostWithTranslations } from "helpers/types";
|
||||
import { PostWithTranslations } from "types/types";
|
||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
@ -49,21 +49,19 @@ export const PostPage = ({
|
|||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const { langui } = useAppLayout();
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: post.translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: post.translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const { thumbnail, body, title, excerpt } = useMemo(
|
||||
() => ({
|
||||
thumbnail:
|
||||
selectedTranslation?.thumbnail?.data?.attributes ??
|
||||
post.thumbnail?.data?.attributes,
|
||||
selectedTranslation?.thumbnail?.data?.attributes ?? post.thumbnail?.data?.attributes,
|
||||
body: selectedTranslation?.body ?? "",
|
||||
title: selectedTranslation?.title ?? prettySlug(post.slug),
|
||||
excerpt: selectedTranslation?.excerpt ?? "",
|
||||
|
@ -76,11 +74,7 @@ export const PostPage = ({
|
|||
returnHref || returnTitle || displayCredits || displayToc ? (
|
||||
<SubPanel>
|
||||
{returnHref && returnTitle && (
|
||||
<ReturnButton
|
||||
href={returnHref}
|
||||
title={returnTitle}
|
||||
displayOnlyOn={"3ColumnsLayout"}
|
||||
/>
|
||||
<ReturnButton href={returnHref} title={returnTitle} displayOnlyOn={"3ColumnsLayout"} />
|
||||
)}
|
||||
|
||||
{displayCredits && (
|
||||
|
@ -92,12 +86,8 @@ export const PostPage = ({
|
|||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(
|
||||
selectedTranslation.status,
|
||||
langui
|
||||
)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedTranslation.status} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
@ -107,23 +97,20 @@ export const PostPage = ({
|
|||
<div>
|
||||
<p className="font-headers font-bold">{"Authors"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(post.authors.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((author) => (
|
||||
<Fragment key={author.id}>
|
||||
<RecorderChip recorder={author.attributes} />
|
||||
</Fragment>
|
||||
))}
|
||||
{filterHasAttributes(post.authors.data, ["id", "attributes"] as const).map(
|
||||
(author) => (
|
||||
<Fragment key={author.id}>
|
||||
<RecorderChip recorder={author.attributes} />
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{displayToc && (
|
||||
<TableOfContents text={body} title={title} horizontalLine />
|
||||
)}
|
||||
{displayToc && <TableOfContents text={body} title={title} horizontalLine />}
|
||||
</SubPanel>
|
||||
) : undefined,
|
||||
[
|
||||
|
@ -173,9 +160,7 @@ export const PostPage = ({
|
|||
</div>
|
||||
)}
|
||||
{displayTitle && (
|
||||
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">
|
||||
{title}
|
||||
</h1>
|
||||
<h1 className="my-16 flex justify-center gap-3 text-center text-4xl">{title}</h1>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
@ -209,11 +194,5 @@ export const PostPage = ({
|
|||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
{...otherProps}
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
/>
|
||||
);
|
||||
return <AppLayout {...otherProps} contentPanel={contentPanel} subPanel={subPanel} />;
|
||||
};
|
||||
|
|
|
@ -5,18 +5,9 @@ import { Ico, Icon } from "./Ico";
|
|||
import { Img } from "./Img";
|
||||
import { Link } from "./Inputs/Link";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import {
|
||||
DatePickerFragment,
|
||||
PricePickerFragment,
|
||||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import { DatePickerFragment, PricePickerFragment, UploadImageFragment } from "graphql/generated";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import {
|
||||
prettyDate,
|
||||
prettyDuration,
|
||||
prettyPrice,
|
||||
prettyShortenNumber,
|
||||
} from "helpers/formatters";
|
||||
import { prettyDate, prettyDuration, prettyPrice, prettyShortenNumber } from "helpers/formatters";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
|
@ -91,37 +82,25 @@ export const PreviewCard = ({
|
|||
<div className="flex w-full flex-row flex-wrap gap-x-3">
|
||||
{metadata.releaseDate && (
|
||||
<p className="text-sm">
|
||||
<Ico
|
||||
icon={Icon.Event}
|
||||
className="mr-1 translate-y-[.15em] !text-base"
|
||||
/>
|
||||
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyDate(metadata.releaseDate, router.locale)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.price && (
|
||||
<p className="justify-self-end text-sm">
|
||||
<Ico
|
||||
icon={Icon.ShoppingCart}
|
||||
className="mr-1 translate-y-[.15em] !text-base"
|
||||
/>
|
||||
<Ico icon={Icon.ShoppingCart} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyPrice(metadata.price, currencies, currency)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.views && (
|
||||
<p className="text-sm">
|
||||
<Ico
|
||||
icon={Icon.Visibility}
|
||||
className="mr-1 translate-y-[.15em] !text-base"
|
||||
/>
|
||||
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyShortenNumber(metadata.views)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.author && (
|
||||
<p className="text-sm">
|
||||
<Ico
|
||||
icon={Icon.Person}
|
||||
className="mr-1 translate-y-[.15em] !text-base"
|
||||
/>
|
||||
<Ico icon={Icon.Person} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{metadata.author}
|
||||
</p>
|
||||
)}
|
||||
|
@ -136,8 +115,7 @@ export const PreviewCard = ({
|
|||
<Link
|
||||
href={href}
|
||||
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 && (
|
||||
<>
|
||||
<div
|
||||
|
@ -145,14 +123,9 @@ export const PreviewCard = ({
|
|||
`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
|
||||
transition-[top_transform] group-hover:-top-9`,
|
||||
cIf(thumbnailRounded, "rounded-md")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{thumbnail && (
|
||||
<Img
|
||||
className="opacity-30"
|
||||
src={thumbnail}
|
||||
quality={ImageQuality.Medium}
|
||||
/>
|
||||
<Img className="opacity-30" src={thumbnail} quality={ImageQuality.Medium} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
@ -161,14 +134,9 @@ export const PreviewCard = ({
|
|||
`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
|
||||
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
|
||||
cIf(thumbnailRounded, "rounded-md")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{thumbnail && (
|
||||
<Img
|
||||
className="opacity-70"
|
||||
src={thumbnail}
|
||||
quality={ImageQuality.Medium}
|
||||
/>
|
||||
<Img className="opacity-70" src={thumbnail} quality={ImageQuality.Medium} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
@ -178,20 +146,13 @@ export const PreviewCard = ({
|
|||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
aspectRatio: thumbnailForceAspectRatio
|
||||
? thumbnailAspectRatio
|
||||
: "unset",
|
||||
}}
|
||||
>
|
||||
aspectRatio: thumbnailForceAspectRatio ? thumbnailAspectRatio : "unset",
|
||||
}}>
|
||||
<Img
|
||||
className={cJoin(
|
||||
cIf(
|
||||
thumbnailRounded,
|
||||
cIf(
|
||||
keepInfoVisible,
|
||||
"rounded-t-md",
|
||||
"rounded-md notHoverable:rounded-b-none"
|
||||
)
|
||||
cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none")
|
||||
),
|
||||
cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
|
||||
)}
|
||||
|
@ -201,8 +162,7 @@ export const PreviewCard = ({
|
|||
{stackNumber > 0 && (
|
||||
<div
|
||||
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}
|
||||
</div>
|
||||
)}
|
||||
|
@ -210,8 +170,7 @@ export const PreviewCard = ({
|
|||
<>
|
||||
<div
|
||||
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
|
||||
icon={Icon.PlayCircleOutline}
|
||||
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
|
||||
|
@ -219,8 +178,7 @@ export const PreviewCard = ({
|
|||
</div>
|
||||
<div
|
||||
className="absolute right-2 bottom-2 rounded-full bg-black bg-opacity-60 px-2
|
||||
text-light"
|
||||
>
|
||||
text-light">
|
||||
{prettyDuration(hoverlay.duration)}
|
||||
</div>
|
||||
</>
|
||||
|
@ -231,18 +189,12 @@ export const PreviewCard = ({
|
|||
style={{ aspectRatio: thumbnailAspectRatio }}
|
||||
className={cJoin(
|
||||
"relative w-full bg-light",
|
||||
cIf(
|
||||
keepInfoVisible,
|
||||
"rounded-t-md",
|
||||
"rounded-md notHoverable:rounded-b-none"
|
||||
)
|
||||
)}
|
||||
>
|
||||
cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none")
|
||||
)}>
|
||||
{stackNumber > 0 && (
|
||||
<div
|
||||
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}
|
||||
</div>
|
||||
)}
|
||||
|
@ -258,8 +210,7 @@ export const PreviewCard = ({
|
|||
notHoverable:rounded-b-md notHoverable:opacity-100`,
|
||||
"[border-radius:0%_0%_10%_10%_/_0%_0%_3%_3%]"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{metadata?.position === "Top" && metadataJSX}
|
||||
{topChips && topChips.length > 0 && (
|
||||
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
|
||||
|
@ -269,13 +220,9 @@ export const PreviewCard = ({
|
|||
</div>
|
||||
)}
|
||||
<div className="my-1">
|
||||
{pre_title && (
|
||||
<p className="mb-1 break-words leading-none">{pre_title}</p>
|
||||
)}
|
||||
{pre_title && <p className="mb-1 break-words leading-none">{pre_title}</p>}
|
||||
{title && (
|
||||
<p className="break-words font-headers text-lg font-bold leading-none">
|
||||
{title}
|
||||
</p>
|
||||
<p className="break-words font-headers text-lg font-bold leading-none">{title}</p>
|
||||
)}
|
||||
{subtitle && <p className="break-words leading-none">{subtitle}</p>}
|
||||
</div>
|
||||
|
@ -283,8 +230,7 @@ export const PreviewCard = ({
|
|||
{bottomChips && bottomChips.length > 0 && (
|
||||
<div
|
||||
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) => (
|
||||
<Chip key={index} className="text-sm" text={text} />
|
||||
))}
|
||||
|
@ -308,16 +254,10 @@ export const TranslatedPreviewCard = ({
|
|||
translations,
|
||||
fallback,
|
||||
...otherProps
|
||||
}: TranslatedProps<
|
||||
Props,
|
||||
"description" | "pre_title" | "subtitle" | "title"
|
||||
>): JSX.Element => {
|
||||
}: TranslatedProps<Props, "description" | "pre_title" | "subtitle" | "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
return (
|
||||
<PreviewCard
|
||||
|
|
|
@ -38,15 +38,10 @@ const PreviewLine = ({
|
|||
<Link
|
||||
href={href}
|
||||
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 ? (
|
||||
<div className="aspect-[3/2] h-full">
|
||||
<Img
|
||||
className="h-full object-cover"
|
||||
src={thumbnail}
|
||||
quality={ImageQuality.Medium}
|
||||
/>
|
||||
<Img className="h-full object-cover" src={thumbnail} quality={ImageQuality.Medium} />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ aspectRatio: thumbnailAspectRatio }}></div>
|
||||
|
@ -61,9 +56,7 @@ const PreviewLine = ({
|
|||
)}
|
||||
<div className="my-1 flex flex-col">
|
||||
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
|
||||
{title && (
|
||||
<p className="font-headers text-lg font-bold leading-none">{title}</p>
|
||||
)}
|
||||
{title && <p className="font-headers text-lg font-bold leading-none">{title}</p>}
|
||||
{subtitle && <p className="leading-none">{subtitle}</p>}
|
||||
</div>
|
||||
{bottomChips && bottomChips.length > 0 && (
|
||||
|
@ -89,10 +82,7 @@ export const TranslatedPreviewLine = ({
|
|||
}: TranslatedProps<Props, "pre_title" | "subtitle" | "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
return (
|
||||
<PreviewLine
|
||||
|
|
|
@ -40,13 +40,13 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
|
|||
{recorder.languages?.data && recorder.languages.data.length > 0 && (
|
||||
<div className="flex flex-row flex-wrap gap-1">
|
||||
<p>{langui.languages}:</p>
|
||||
{filterHasAttributes(recorder.languages.data, [
|
||||
"attributes",
|
||||
] as const).map((language) => (
|
||||
<Fragment key={language.__typename}>
|
||||
<Chip text={language.attributes.code.toUpperCase()} />
|
||||
</Fragment>
|
||||
))}
|
||||
{filterHasAttributes(recorder.languages.data, ["attributes"] as const).map(
|
||||
(language) => (
|
||||
<Fragment key={language.__typename}>
|
||||
<Chip text={language.attributes.code.toUpperCase()} />
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{recorder.pronouns && (
|
||||
|
@ -60,15 +60,10 @@ export const RecorderChip = ({ recorder }: Props): JSX.Element => {
|
|||
{recorder.bio?.[0] && <Markdawn text={recorder.bio[0].bio ?? ""} />}
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
placement="top">
|
||||
<Chip
|
||||
key={recorder.anonymous_code}
|
||||
text={
|
||||
recorder.anonymize
|
||||
? `Recorder#${recorder.anonymous_code}`
|
||||
: recorder.username
|
||||
}
|
||||
text={recorder.anonymize ? `Recorder#${recorder.anonymous_code}` : recorder.username}
|
||||
/>
|
||||
</ToolTip>
|
||||
);
|
||||
|
|
|
@ -72,10 +72,7 @@ export const SmartList = <T,>({
|
|||
const [page, setPage] = useState(0);
|
||||
const { langui } = useAppLayout();
|
||||
useScrollTopOnChange(Ids.ContentPanel, [page], paginationScroolTop);
|
||||
useEffect(
|
||||
() => setPage(0),
|
||||
[searchingTerm, groupingFunction, groupSortingFunction, items]
|
||||
);
|
||||
useEffect(() => setPage(0), [searchingTerm, groupingFunction, groupSortingFunction, items]);
|
||||
|
||||
const searchFilter = useCallback(() => {
|
||||
if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) {
|
||||
|
@ -118,12 +115,7 @@ export const SmartList = <T,>({
|
|||
});
|
||||
});
|
||||
return memo.sort(groupSortingFunction);
|
||||
}, [
|
||||
groupCountingFunction,
|
||||
groupSortingFunction,
|
||||
groupingFunction,
|
||||
sortedItem,
|
||||
]);
|
||||
}, [groupCountingFunction, groupSortingFunction, groupingFunction, sortedItem]);
|
||||
|
||||
const pages = useMemo(() => {
|
||||
const memo: Group<T>[][] = [];
|
||||
|
@ -174,12 +166,7 @@ export const SmartList = <T,>({
|
|||
return (
|
||||
<>
|
||||
{pages.length > 1 && paginationSelectorTop && (
|
||||
<PageSelector
|
||||
className="mb-12"
|
||||
page={page}
|
||||
pagesCount={pages.length}
|
||||
onChange={setPage}
|
||||
/>
|
||||
<PageSelector className="mb-12" page={page} pagesCount={pages.length} onChange={setPage} />
|
||||
)}
|
||||
|
||||
<div className="mb-8">
|
||||
|
@ -191,8 +178,7 @@ export const SmartList = <T,>({
|
|||
{group.name.length > 0 && (
|
||||
<h2
|
||||
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}
|
||||
<Chip
|
||||
text={`${group.totalCount} ${
|
||||
|
@ -208,8 +194,7 @@ export const SmartList = <T,>({
|
|||
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
|
||||
last-of-type:border-0`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{group.items.map((item) => (
|
||||
<RenderItem item={item} key={getItemId(item)} />
|
||||
))}
|
||||
|
@ -225,12 +210,7 @@ export const SmartList = <T,>({
|
|||
</div>
|
||||
|
||||
{pages.length > 1 && paginationSelectorBottom && (
|
||||
<PageSelector
|
||||
className="mb-12"
|
||||
page={page}
|
||||
pagesCount={pages.length}
|
||||
onChange={setPage}
|
||||
/>
|
||||
<PageSelector 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 grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40"
|
||||
>
|
||||
{is3ColumnsLayout && (
|
||||
<Ico icon={Icon.ChevronLeft} className="!text-[300%]" />
|
||||
)}
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
{is3ColumnsLayout && <Ico icon={Icon.ChevronLeft} className="!text-[300%]" />}
|
||||
<p className="max-w-xs text-2xl">{langui.no_results_message}</p>
|
||||
{!is3ColumnsLayout && (
|
||||
<Ico icon={Icon.ChevronRight} className="!text-[300%]" />
|
||||
)}
|
||||
{!is3ColumnsLayout && <Ico icon={Icon.ChevronRight} className="!text-[300%]" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -64,11 +64,8 @@ export const ThumbnailHeader = ({
|
|||
)}
|
||||
</div>
|
||||
<div
|
||||
id={slugify(
|
||||
prettyInlineTitle(pre_title ?? "", title, subtitle ?? "")
|
||||
)}
|
||||
className="grid place-items-center text-center"
|
||||
>
|
||||
id={slugify(prettyInlineTitle(pre_title ?? "", title, subtitle ?? ""))}
|
||||
className="grid place-items-center text-center">
|
||||
<p className="text-2xl">{pre_title}</p>
|
||||
<h1 className="text-3xl">{title}</h1>
|
||||
<h2 className="text-2xl">{subtitle}</h2>
|
||||
|
@ -82,8 +79,7 @@ export const ThumbnailHeader = ({
|
|||
<div className="flex flex-row flex-wrap">
|
||||
<Chip
|
||||
text={
|
||||
type.data.attributes.titles?.[0]?.title ??
|
||||
prettySlug(type.data.attributes.slug)
|
||||
type.data.attributes.titles?.[0]?.title ?? prettySlug(type.data.attributes.slug)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
@ -94,20 +90,17 @@ export const ThumbnailHeader = ({
|
|||
<div className="flex flex-col place-items-center gap-2">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(categories.data, [
|
||||
"attributes",
|
||||
"id",
|
||||
] as const).map((category) => (
|
||||
<Chip key={category.id} text={category.attributes.name} />
|
||||
))}
|
||||
{filterHasAttributes(categories.data, ["attributes", "id"] as const).map(
|
||||
(category) => (
|
||||
<Chip key={category.id} text={category.attributes.name} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{languageSwitcher}
|
||||
</div>
|
||||
{description && (
|
||||
<InsetBox className="mt-8">{<Markdawn text={description} />}</InsetBox>
|
||||
)}
|
||||
{description && <InsetBox className="mt-8">{<Markdawn text={description} />}</InsetBox>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -25,8 +25,7 @@ export const ToolTip = ({
|
|||
delay={delay}
|
||||
interactive={interactive}
|
||||
animation={animation}
|
||||
{...otherProps}
|
||||
>
|
||||
{...otherProps}>
|
||||
<div>{children}</div>
|
||||
</Tippy>
|
||||
);
|
||||
|
|
|
@ -29,22 +29,13 @@ interface Props {
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
const DefinitionCard = ({
|
||||
source,
|
||||
translations = [],
|
||||
index,
|
||||
categories,
|
||||
}: Props): JSX.Element => {
|
||||
const DefinitionCard = ({ source, translations = [], index, categories }: Props): JSX.Element => {
|
||||
const isContentPanelNoMoreThanMd = useIsContentPanelNoMoreThan("md");
|
||||
const { langui } = useAppLayout();
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: Props["translations"][number]) => item.language,
|
||||
[]
|
||||
),
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback((item: Props["translations"][number]) => item.language, []),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -63,8 +54,7 @@ const DefinitionCard = ({
|
|||
<Separator />
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedTranslation.status} />
|
||||
</ToolTip>
|
||||
</>
|
||||
|
@ -89,8 +79,7 @@ const DefinitionCard = ({
|
|||
className={cJoin(
|
||||
"mt-3 flex place-items-center gap-2",
|
||||
cIf(isContentPanelNoMoreThanMd, "flex-col text-center")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<p>{langui.source}: </p>
|
||||
<Button href={source.url} size="small" text={source.name} />
|
||||
</div>
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import React, {
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import React, { ReactNode, useContext, useEffect, useLayoutEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useLocalStorage } from "usehooks-ts";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types";
|
||||
import { LibraryItemUserStatus, RequiredNonNullable } from "types/types";
|
||||
import { useDarkMode } from "hooks/useDarkMode";
|
||||
import { Currencies, Languages, Langui } from "helpers/localData";
|
||||
import { useCurrencies, useLanguages, useLangui } from "hooks/useLocalData";
|
||||
|
@ -19,27 +13,19 @@ import { useScrollIntoView } from "hooks/useScrollIntoView";
|
|||
interface AppLayoutState {
|
||||
subPanelOpen: boolean;
|
||||
toggleSubPanelOpen: () => void;
|
||||
setSubPanelOpen: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["subPanelOpen"]>
|
||||
>;
|
||||
setSubPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["subPanelOpen"]>>;
|
||||
|
||||
configPanelOpen: boolean;
|
||||
toggleConfigPanelOpen: () => void;
|
||||
setConfigPanelOpen: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["configPanelOpen"]>
|
||||
>;
|
||||
setConfigPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["configPanelOpen"]>>;
|
||||
|
||||
mainPanelReduced: boolean;
|
||||
toggleMainPanelReduced: () => void;
|
||||
setMainPanelReduced: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["mainPanelReduced"]>
|
||||
>;
|
||||
setMainPanelReduced: React.Dispatch<React.SetStateAction<AppLayoutState["mainPanelReduced"]>>;
|
||||
|
||||
mainPanelOpen: boolean;
|
||||
toggleMainPanelOpen: () => void;
|
||||
setMainPanelOpen: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["mainPanelOpen"]>
|
||||
>;
|
||||
setMainPanelOpen: React.Dispatch<React.SetStateAction<AppLayoutState["mainPanelOpen"]>>;
|
||||
|
||||
darkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
|
@ -47,9 +33,7 @@ interface AppLayoutState {
|
|||
|
||||
selectedThemeMode: boolean;
|
||||
toggleSelectedThemeMode: () => void;
|
||||
setSelectedThemeMode: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["selectedThemeMode"]>
|
||||
>;
|
||||
setSelectedThemeMode: React.Dispatch<React.SetStateAction<AppLayoutState["selectedThemeMode"]>>;
|
||||
|
||||
fontSize: number;
|
||||
setFontSize: React.Dispatch<React.SetStateAction<AppLayoutState["fontSize"]>>;
|
||||
|
@ -62,20 +46,14 @@ interface AppLayoutState {
|
|||
setCurrency: React.Dispatch<React.SetStateAction<AppLayoutState["currency"]>>;
|
||||
|
||||
playerName: string;
|
||||
setPlayerName: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["playerName"]>
|
||||
>;
|
||||
setPlayerName: React.Dispatch<React.SetStateAction<AppLayoutState["playerName"]>>;
|
||||
|
||||
preferredLanguages: string[];
|
||||
setPreferredLanguages: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["preferredLanguages"]>
|
||||
>;
|
||||
setPreferredLanguages: React.Dispatch<React.SetStateAction<AppLayoutState["preferredLanguages"]>>;
|
||||
|
||||
menuGestures: boolean;
|
||||
toggleMenuGestures: () => void;
|
||||
setMenuGestures: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["menuGestures"]>
|
||||
>;
|
||||
setMenuGestures: React.Dispatch<React.SetStateAction<AppLayoutState["menuGestures"]>>;
|
||||
|
||||
libraryItemUserStatus: Record<string, LibraryItemUserStatus>;
|
||||
setLibraryItemUserStatus: React.Dispatch<
|
||||
|
@ -83,19 +61,13 @@ interface AppLayoutState {
|
|||
>;
|
||||
|
||||
screenWidth: number;
|
||||
setScreenWidth: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["screenWidth"]>
|
||||
>;
|
||||
setScreenWidth: React.Dispatch<React.SetStateAction<AppLayoutState["screenWidth"]>>;
|
||||
|
||||
contentPanelWidth: number;
|
||||
setContentPanelWidth: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["contentPanelWidth"]>
|
||||
>;
|
||||
setContentPanelWidth: React.Dispatch<React.SetStateAction<AppLayoutState["contentPanelWidth"]>>;
|
||||
|
||||
subPanelWidth: number;
|
||||
setSubPanelWidth: React.Dispatch<
|
||||
React.SetStateAction<AppLayoutState["subPanelWidth"]>
|
||||
>;
|
||||
setSubPanelWidth: React.Dispatch<React.SetStateAction<AppLayoutState["subPanelWidth"]>>;
|
||||
|
||||
langui: Langui;
|
||||
languages: Languages;
|
||||
|
@ -195,28 +167,18 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
|||
initialState.mainPanelOpen
|
||||
);
|
||||
|
||||
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] =
|
||||
useDarkMode("darkMode", initialState.darkMode);
|
||||
|
||||
const [fontSize, setFontSize] = useLocalStorage(
|
||||
"fontSize",
|
||||
initialState.fontSize
|
||||
const [darkMode, selectedThemeMode, setDarkMode, setSelectedThemeMode] = useDarkMode(
|
||||
"darkMode",
|
||||
initialState.darkMode
|
||||
);
|
||||
|
||||
const [dyslexic, setDyslexic] = useLocalStorage(
|
||||
"dyslexic",
|
||||
initialState.dyslexic
|
||||
);
|
||||
const [fontSize, setFontSize] = useLocalStorage("fontSize", initialState.fontSize);
|
||||
|
||||
const [currency, setCurrency] = useLocalStorage(
|
||||
"currency",
|
||||
initialState.currency
|
||||
);
|
||||
const [dyslexic, setDyslexic] = useLocalStorage("dyslexic", initialState.dyslexic);
|
||||
|
||||
const [playerName, setPlayerName] = useLocalStorage(
|
||||
"playerName",
|
||||
initialState.playerName
|
||||
);
|
||||
const [currency, setCurrency] = useLocalStorage("currency", initialState.currency);
|
||||
|
||||
const [playerName, setPlayerName] = useLocalStorage("playerName", initialState.playerName);
|
||||
|
||||
const [preferredLanguages, setPreferredLanguages] = useLocalStorage(
|
||||
"preferredLanguages",
|
||||
|
@ -255,9 +217,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
|||
};
|
||||
|
||||
const toggleSelectedThemeMode = () => {
|
||||
setSelectedThemeMode((current) =>
|
||||
isDefined(current) ? !current : current
|
||||
);
|
||||
setSelectedThemeMode((current) => (isDefined(current) ? !current : current));
|
||||
};
|
||||
|
||||
const toggleDyslexic = () => {
|
||||
|
@ -275,9 +235,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
|||
useEffect(() => {
|
||||
if (preferredLanguages.length === 0) {
|
||||
if (isDefinedAndNotEmpty(router.locale) && router.locales) {
|
||||
setPreferredLanguages(
|
||||
getDefaultPreferredLanguages(router.locale, router.locales)
|
||||
);
|
||||
setPreferredLanguages(getDefaultPreferredLanguages(router.locale, router.locales));
|
||||
}
|
||||
} else if (router.locale !== preferredLanguages[0]) {
|
||||
/*
|
||||
|
@ -292,13 +250,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
|||
250
|
||||
);
|
||||
}
|
||||
}, [
|
||||
preferredLanguages,
|
||||
router,
|
||||
router.locale,
|
||||
router.locales,
|
||||
setPreferredLanguages,
|
||||
]);
|
||||
}, [preferredLanguages, router, router.locale, router.locales, setPreferredLanguages]);
|
||||
|
||||
useEffect(() => {
|
||||
router.events.on("routeChangeStart", () => {
|
||||
|
@ -315,9 +267,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
|||
}, [router.events, setConfigPanelOpen, setMainPanelOpen, setSubPanelOpen]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
document.getElementsByTagName("html")[0].style.fontSize = `${
|
||||
fontSize * 100
|
||||
}%`;
|
||||
document.getElementsByTagName("html")[0].style.fontSize = `${fontSize * 100}%`;
|
||||
}, [fontSize]);
|
||||
|
||||
useScrollIntoView();
|
||||
|
@ -368,8 +318,7 @@ export const AppContextProvider = (props: Props): JSX.Element => {
|
|||
languages,
|
||||
langui,
|
||||
currencies,
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{props.children}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
|
|
|
@ -13,7 +13,7 @@ const LOCAL_DATA_FOLDER = `${process.cwd()}/public/local-data`;
|
|||
const writeLocalData = (name: LocalDataFile, localData: unknown) => {
|
||||
const path = `${LOCAL_DATA_FOLDER}/${name}.json`;
|
||||
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 => {
|
||||
|
@ -24,10 +24,7 @@ const readLocalData = <T>(name: LocalDataFile): T => {
|
|||
const sdk = getReadySdk();
|
||||
|
||||
(async () => {
|
||||
writeLocalData(
|
||||
"websiteInterfaces",
|
||||
await sdk.localDataGetWebsiteInterfaces()
|
||||
);
|
||||
writeLocalData("websiteInterfaces", await sdk.localDataGetWebsiteInterfaces());
|
||||
writeLocalData("currencies", await sdk.localDataGetCurrencies());
|
||||
writeLocalData("languages", await sdk.localDataGetLanguages());
|
||||
})();
|
||||
|
@ -37,7 +34,6 @@ const sdk = getReadySdk();
|
|||
export type LocalDataFile = "currencies" | "languages" | "websiteInterfaces";
|
||||
|
||||
export const getLangui = (locale: string | undefined): Langui => {
|
||||
const websiteInterfaces =
|
||||
readLocalData<LocalDataGetWebsiteInterfacesQuery>("websiteInterfaces");
|
||||
const websiteInterfaces = readLocalData<LocalDataGetWebsiteInterfacesQuery>("websiteInterfaces");
|
||||
return processLangui(websiteInterfaces, locale);
|
||||
};
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { getReadySdk } from "./sdk";
|
||||
import { getLangui } from "./fetchLocalData";
|
||||
import { PostWithTranslations } from "helpers/types";
|
||||
import { PostWithTranslations } from "types/types";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||
import {
|
||||
getDefaultPreferredLanguages,
|
||||
staticSmartLanguage,
|
||||
} from "helpers/locales";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { AppLayoutRequired } from "components/AppLayout";
|
||||
|
@ -35,10 +32,7 @@ export const getPostStaticProps =
|
|||
const selectedTranslation = staticSmartLanguage({
|
||||
items: post.posts.data[0].attributes.translations,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
preferredLanguages: getDefaultPreferredLanguages(
|
||||
context.locale,
|
||||
context.locales
|
||||
),
|
||||
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||
});
|
||||
|
||||
const title = selectedTranslation?.title ?? prettySlug(slug);
|
||||
|
|
|
@ -91,9 +91,7 @@ query getChronicle($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
query getChronologyItems {
|
||||
chronologyItems(
|
||||
pagination: { limit: -1 }
|
||||
sort: ["year:asc", "month:asc", "day:asc"]
|
||||
) {
|
||||
chronologyItems(pagination: { limit: -1 }, sort: ["year:asc", "month:asc", "day:asc"]) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
|
|
|
@ -67,11 +67,7 @@ query getContentText($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -93,11 +89,7 @@ query getContentText($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -109,11 +101,7 @@ query getContentText($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -125,11 +113,7 @@ query getContentText($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -139,11 +123,7 @@ query getContentText($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -257,11 +237,7 @@ query getContentText($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,9 +62,7 @@ query getContentsFolder($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +80,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -130,9 +126,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -144,9 +138,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +148,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -212,11 +202,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -238,11 +224,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -254,11 +236,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -270,11 +248,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -284,11 +258,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -352,11 +322,7 @@ query getLibraryItem($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: {
|
||||
language: { code: { eq: $language_code } }
|
||||
}
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,9 +124,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -148,9 +146,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -162,9 +158,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -176,9 +170,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -188,9 +180,7 @@ query getLibraryItemScans($slug: String, $language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,7 @@ query getLibraryItemsPreview($language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -61,9 +59,7 @@ query getLibraryItemsPreview($language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -75,9 +71,7 @@ query getLibraryItemsPreview($language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -89,9 +83,7 @@ query getLibraryItemsPreview($language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
@ -101,9 +93,7 @@ query getLibraryItemsPreview($language_code: String) {
|
|||
data {
|
||||
attributes {
|
||||
slug
|
||||
titles(
|
||||
filters: { language: { code: { eq: $language_code } } }
|
||||
) {
|
||||
titles(filters: { language: { code: { eq: $language_code } } }) {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,6 +177,9 @@ query localDataGetWebsiteInterfaces {
|
|||
anchor_link_copied
|
||||
folders
|
||||
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,
|
||||
wrapperProps,
|
||||
}: ConditionalWrapperProps<T>): JSX.Element =>
|
||||
isWrapping ? (
|
||||
<Wrapper {...wrapperProps}>{children}</Wrapper>
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
isWrapping ? <Wrapper {...wrapperProps}>{children}</Wrapper> : <>{children}</>;
|
||||
|
|
|
@ -8,10 +8,8 @@ export const compareDate = (
|
|||
if (isUndefined(a) || isUndefined(b)) {
|
||||
return 0;
|
||||
}
|
||||
const dateA =
|
||||
(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 dateA = (a.year ?? Infinity) * 365 + (a.month ?? 12) * 31 + (a.day ?? 31);
|
||||
const dateB = (b.year ?? Infinity) * 365 + (b.month ?? 12) * 31 + (b.day ?? 31);
|
||||
return dateA - dateB;
|
||||
};
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ export const prettyInlineTitle = (
|
|||
return result;
|
||||
};
|
||||
|
||||
export const prettyItemType = (metadata: any, langui: Langui): string => {
|
||||
export const prettyItemType = (metadata: { __typename: string }, langui: Langui): string => {
|
||||
switch (metadata.__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
return langui.audio ?? "Audio";
|
||||
|
@ -242,8 +242,7 @@ export const prettyDuration = (seconds: number): string => {
|
|||
export const prettyLanguage = (code: string, languages: Languages): string => {
|
||||
let result = code;
|
||||
languages.forEach((language) => {
|
||||
if (language.attributes?.code === code)
|
||||
result = language.attributes.localized_name;
|
||||
if (language.attributes?.code === code) result = language.attributes.localized_name;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
@ -254,8 +253,7 @@ export const prettyURL = (url: string): string => {
|
|||
};
|
||||
|
||||
const capitalizeString = (string: string): string => {
|
||||
const capitalizeWord = (word: string): string =>
|
||||
word.charAt(0).toUpperCase() + word.substring(1);
|
||||
const capitalizeWord = (word: string): string => word.charAt(0).toUpperCase() + word.substring(1);
|
||||
|
||||
let words = string.split(" ");
|
||||
words = words.map((word) => capitalizeWord(word));
|
||||
|
@ -281,3 +279,5 @@ export const slugify = (string: string | undefined): string => {
|
|||
.trim()
|
||||
.replace(/ /gu, "-");
|
||||
};
|
||||
|
||||
export const sJoin = (...args: (string | null | undefined)[]): string => args.join("");
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { sJoin } from "./formatters";
|
||||
|
||||
export enum ImageQuality {
|
||||
Small = "small",
|
||||
Medium = "medium",
|
||||
|
@ -17,7 +19,7 @@ export interface OgImage {
|
|||
alt: string;
|
||||
}
|
||||
|
||||
export const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
|
||||
const imageQualityProperties: Record<ImageQuality, ImageProperties> = {
|
||||
small: { maxSize: 512, extension: "webp" },
|
||||
medium: { maxSize: 1024, 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 => {
|
||||
const indexEndPath = url.indexOf("/uploads/") + "/uploads/".length;
|
||||
const indexStartExtension = url.lastIndexOf(".");
|
||||
const assetPathWithoutExtension = url.slice(
|
||||
indexEndPath,
|
||||
indexStartExtension
|
||||
const assetPathWithoutExtension = url.slice(indexEndPath, indexStartExtension);
|
||||
return sJoin(
|
||||
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 = (
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
export const isUntangibleGroupItem = (metadata: any): boolean =>
|
||||
metadata &&
|
||||
import { isDefined } from "./others";
|
||||
|
||||
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.subtype?.data?.attributes?.slug === "variant-set" ||
|
||||
metadata.subtype?.data?.attributes?.slug === "relation-set");
|
||||
|
|
|
@ -5,9 +5,7 @@ import {
|
|||
} from "graphql/generated";
|
||||
|
||||
export type Langui = NonNullable<
|
||||
NonNullable<
|
||||
LocalDataGetWebsiteInterfacesQuery["websiteInterfaces"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<LocalDataGetWebsiteInterfacesQuery["websiteInterfaces"]>["data"][number]["attributes"]
|
||||
>;
|
||||
|
||||
export const processLangui = (
|
||||
|
@ -15,24 +13,19 @@ export const processLangui = (
|
|||
locale: string | undefined
|
||||
): Langui =>
|
||||
websiteInterfaces?.websiteInterfaces?.data.find(
|
||||
(langui) =>
|
||||
langui.attributes?.ui_language?.data?.attributes?.code === locale
|
||||
(langui) => langui.attributes?.ui_language?.data?.attributes?.code === locale
|
||||
)?.attributes ?? {};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export type Currencies = NonNullable<
|
||||
LocalDataGetCurrenciesQuery["currencies"]
|
||||
>["data"];
|
||||
export type Currencies = NonNullable<LocalDataGetCurrenciesQuery["currencies"]>["data"];
|
||||
|
||||
export const processCurrencies = (
|
||||
currencies: LocalDataGetCurrenciesQuery | undefined
|
||||
): Currencies => {
|
||||
if (currencies?.currencies?.data) {
|
||||
currencies.currencies.data.sort((a, b) =>
|
||||
a.attributes && b.attributes
|
||||
? a.attributes.code.localeCompare(b.attributes.code)
|
||||
: 0
|
||||
a.attributes && b.attributes ? a.attributes.code.localeCompare(b.attributes.code) : 0
|
||||
);
|
||||
}
|
||||
return currencies?.currencies?.data ?? [];
|
||||
|
@ -40,13 +33,9 @@ export const processCurrencies = (
|
|||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export type Languages = NonNullable<
|
||||
LocalDataGetLanguagesQuery["languages"]
|
||||
>["data"];
|
||||
export type Languages = NonNullable<LocalDataGetLanguagesQuery["languages"]>["data"];
|
||||
|
||||
export const processLanguages = (
|
||||
languages: LocalDataGetLanguagesQuery | undefined
|
||||
): Languages => {
|
||||
export const processLanguages = (languages: LocalDataGetLanguagesQuery | undefined): Languages => {
|
||||
if (languages?.languages?.data) {
|
||||
languages.languages.data.sort((a, b) =>
|
||||
a.attributes && b.attributes
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { isDefined } from "./others";
|
||||
|
||||
export const getDefaultPreferredLanguages = (
|
||||
routerLocal: string,
|
||||
locales: string[]
|
||||
): string[] => {
|
||||
export const getDefaultPreferredLanguages = (routerLocal: string, locales: string[]): string[] => {
|
||||
let defaultPreferredLanguages: string[] = [];
|
||||
if (routerLocal === "en") {
|
||||
defaultPreferredLanguages = [routerLocal];
|
||||
|
@ -13,8 +10,7 @@ export const getDefaultPreferredLanguages = (
|
|||
} else {
|
||||
defaultPreferredLanguages = [routerLocal, "en"];
|
||||
locales.map((locale) => {
|
||||
if (locale !== routerLocal && locale !== "en")
|
||||
defaultPreferredLanguages.push(locale);
|
||||
if (locale !== routerLocal && locale !== "en") defaultPreferredLanguages.push(locale);
|
||||
});
|
||||
}
|
||||
return defaultPreferredLanguages;
|
||||
|
|
|
@ -5,11 +5,7 @@ export const convertPrice = (
|
|||
pricePicker: PricePickerFragment,
|
||||
targetCurrency: Currencies[number]
|
||||
): number => {
|
||||
if (
|
||||
pricePicker.amount &&
|
||||
pricePicker.currency?.data?.attributes &&
|
||||
targetCurrency.attributes
|
||||
)
|
||||
if (pricePicker.amount && pricePicker.currency?.data?.attributes && targetCurrency.attributes)
|
||||
return (
|
||||
(pricePicker.amount * pricePicker.currency.data.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 =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
|
||||
export const isInteger = (value: string): boolean =>
|
||||
/^[+-]?[0-9]+$/u.test(value);
|
||||
export const isInteger = (value: string): boolean => /^[+-]?[0-9]+$/u.test(value);
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
OgImage,
|
||||
getImgSizesByQuality,
|
||||
ImageQuality,
|
||||
getAssetURL,
|
||||
} from "./img";
|
||||
import { OgImage, getImgSizesByQuality, ImageQuality, getAssetURL } from "./img";
|
||||
import { isDefinedAndNotEmpty } from "./others";
|
||||
import { Langui } from "./localData";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
|
@ -30,21 +25,13 @@ export const getOpenGraph = (
|
|||
description?: string | null | undefined,
|
||||
thumbnail?: UploadImageFragment | null | undefined
|
||||
): OpenGraph => ({
|
||||
title: `${TITLE_PREFIX}${
|
||||
isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""
|
||||
}`,
|
||||
description: isDefinedAndNotEmpty(description)
|
||||
? description
|
||||
: langui.default_description ?? "",
|
||||
title: `${TITLE_PREFIX}${isDefinedAndNotEmpty(title) ? `${TITLE_SEPARATOR}${title}` : ""}`,
|
||||
description: isDefinedAndNotEmpty(description) ? description : langui.default_description ?? "",
|
||||
thumbnail: thumbnail ? getOgImage(thumbnail) : DEFAULT_OG_THUMBNAIL,
|
||||
});
|
||||
|
||||
const getOgImage = (image: UploadImageFragment): OgImage => {
|
||||
const imgSize = getImgSizesByQuality(
|
||||
image.width ?? 0,
|
||||
image.height ?? 0,
|
||||
ImageQuality.Og
|
||||
);
|
||||
const imgSize = getImgSizesByQuality(image.width ?? 0, image.height ?? 0, ImageQuality.Og);
|
||||
return {
|
||||
image: getAssetURL(image.url, ImageQuality.Og),
|
||||
width: imgSize.width,
|
||||
|
|
|
@ -8,14 +8,10 @@ import {
|
|||
|
||||
type SortRangedContentProps =
|
||||
| NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
| NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||
>["contents"];
|
||||
|
||||
export const sortRangedContent = (contents: SortRangedContentProps): void => {
|
||||
|
@ -24,19 +20,13 @@ export const sortRangedContent = (contents: SortRangedContentProps): void => {
|
|||
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
||||
b.attributes?.range[0]?.__typename === "ComponentRangePageRange"
|
||||
) {
|
||||
return (
|
||||
a.attributes.range[0].starting_page -
|
||||
b.attributes.range[0].starting_page
|
||||
);
|
||||
return a.attributes.range[0].starting_page - b.attributes.range[0].starting_page;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
export const getStatusDescription = (
|
||||
status: string,
|
||||
langui: Langui
|
||||
): string | null | undefined => {
|
||||
export const getStatusDescription = (status: string, langui: Langui): string | null | undefined => {
|
||||
switch (status) {
|
||||
case Enum_Componentsetstextset_Status.Incomplete:
|
||||
return langui.status_incomplete;
|
||||
|
@ -55,30 +45,26 @@ export const getStatusDescription = (
|
|||
}
|
||||
};
|
||||
|
||||
export const isDefined = <T>(t: T): t is NonNullable<T> =>
|
||||
t !== null && t !== undefined;
|
||||
export const isDefined = <T>(t: T): t is NonNullable<T> => t !== null && t !== undefined;
|
||||
|
||||
export const isUndefined = <T>(
|
||||
t: T | null | undefined
|
||||
): t is null | undefined => t === null || t === undefined;
|
||||
export const isUndefined = <T>(t: T | null | undefined): t is null | undefined =>
|
||||
t === null || t === undefined;
|
||||
|
||||
export const isDefinedAndNotEmpty = (
|
||||
string: string | null | undefined
|
||||
): string is string => isDefined(string) && string.length > 0;
|
||||
export const isDefinedAndNotEmpty = (string: string | null | undefined): string is string =>
|
||||
isDefined(string) && string.length > 0;
|
||||
|
||||
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
|
||||
isUndefined(t)
|
||||
? []
|
||||
: (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
|
||||
isUndefined(t) ? [] : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
|
||||
|
||||
export const filterHasAttributes = <T, P extends PathDot<T>>(
|
||||
t: T[] | null | undefined,
|
||||
paths: readonly P[]
|
||||
): SelectiveNonNullable<T, typeof paths[number]>[] =>
|
||||
isDefined(t)
|
||||
? (t.filter((item) =>
|
||||
hasAttributes(item, paths)
|
||||
) as unknown as SelectiveNonNullable<T, typeof paths[number]>[])
|
||||
? (t.filter((item) => hasAttributes(item, paths)) as unknown as SelectiveNonNullable<
|
||||
T,
|
||||
typeof paths[number]
|
||||
>[])
|
||||
: [];
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
export const arrayMove = <T>(
|
||||
arr: T[],
|
||||
sourceIndex: number,
|
||||
targetIndex: number
|
||||
): T[] => {
|
||||
export const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number): T[] => {
|
||||
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
|
||||
return arr;
|
||||
};
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { useState } from "react";
|
||||
import { LightBox } from "components/LightBox";
|
||||
|
||||
export const useLightBox = (): [
|
||||
(images: string[], index?: number) => void,
|
||||
() => JSX.Element
|
||||
] => {
|
||||
export const useLightBox = (): [(images: string[], index?: number) => void, () => JSX.Element] => {
|
||||
const [lightboxOpen, setLightboxOpen] = useState(false);
|
||||
const [lightboxImages, setLightboxImages] = useState([""]);
|
||||
const [lightboxIndex, setLightboxIndex] = useState(0);
|
||||
|
|
|
@ -18,10 +18,7 @@ const useFetchLocalData = (name: LocalDataFile) =>
|
|||
export const useLangui = (): Langui => {
|
||||
const { locale } = useRouter();
|
||||
const { data: websiteInterfaces } = useFetchLocalData("websiteInterfaces");
|
||||
return useMemo(
|
||||
() => processLangui(websiteInterfaces, locale),
|
||||
[websiteInterfaces, locale]
|
||||
);
|
||||
return useMemo(() => processLangui(websiteInterfaces, locale), [websiteInterfaces, locale]);
|
||||
};
|
||||
|
||||
export const useCurrencies = (): Currencies => {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
export const useDeviceSupportsHover = (): boolean =>
|
||||
useMediaQuery("(hover: hover)");
|
||||
export const useDeviceSupportsHover = (): boolean => useMediaQuery("(hover: hover)");
|
||||
|
||||
export const usePrefersDarkMode = (): boolean =>
|
||||
useMediaQuery("(prefers-color-scheme: dark)");
|
||||
export const usePrefersDarkMode = (): boolean => useMediaQuery("(prefers-color-scheme: dark)");
|
||||
|
|
|
@ -12,10 +12,7 @@ export const useOnResize = (
|
|||
console.log("[useOnResize]", id);
|
||||
const elem = isClient ? document.querySelector(`#${id}`) : null;
|
||||
const ro = new ResizeObserver((resizeObserverEntry) =>
|
||||
onResize(
|
||||
resizeObserverEntry[0].contentRect.width,
|
||||
resizeObserverEntry[0].contentRect.height
|
||||
)
|
||||
onResize(resizeObserverEntry[0].contentRect.width, resizeObserverEntry[0].contentRect.height)
|
||||
);
|
||||
if (isDefined(elem)) {
|
||||
ro.observe(elem);
|
||||
|
|
|
@ -2,15 +2,9 @@ import { useMemo, useCallback, useEffect } from "react";
|
|||
import { useIsClient } from "usehooks-ts";
|
||||
import { Ids } from "types/ids";
|
||||
|
||||
export const useOnScroll = (
|
||||
id: Ids,
|
||||
onScroll: (scroll: number) => void
|
||||
): void => {
|
||||
export const useOnScroll = (id: Ids, onScroll: (scroll: number) => void): void => {
|
||||
const isClient = useIsClient();
|
||||
const elem = useMemo(
|
||||
() => (isClient ? document.querySelector(`#${id}`) : null),
|
||||
[id, isClient]
|
||||
);
|
||||
const elem = useMemo(() => (isClient ? document.querySelector(`#${id}`) : null), [id, isClient]);
|
||||
const listener = useCallback(() => {
|
||||
if (elem?.scrollTop) {
|
||||
onScroll(elem.scrollTop);
|
||||
|
|
|
@ -2,16 +2,9 @@ import { DependencyList, useEffect } from "react";
|
|||
import { Ids } from "types/ids";
|
||||
|
||||
// Scroll to top of element "id" when "deps" update.
|
||||
export const useScrollTopOnChange = (
|
||||
id: Ids,
|
||||
deps: DependencyList,
|
||||
enabled = true
|
||||
): void => {
|
||||
export const useScrollTopOnChange = (id: Ids, deps: DependencyList, enabled = true): void => {
|
||||
useEffect(() => {
|
||||
if (enabled)
|
||||
document
|
||||
.querySelector(`#${id}`)
|
||||
?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
if (enabled) document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, ...deps, enabled]);
|
||||
};
|
||||
|
|
|
@ -16,11 +16,7 @@ export const useSmartLanguage = <T>({
|
|||
items,
|
||||
languageExtractor,
|
||||
transform = (item) => item,
|
||||
}: Props<T>): [
|
||||
T | undefined,
|
||||
typeof LanguageSwitcher,
|
||||
Parameters<typeof LanguageSwitcher>[0]
|
||||
] => {
|
||||
}: Props<T>): [T | undefined, typeof LanguageSwitcher, Parameters<typeof LanguageSwitcher>[0]] => {
|
||||
const { preferredLanguages } = useAppLayout();
|
||||
const languages = useLanguages();
|
||||
const router = useRouter();
|
||||
|
@ -34,14 +30,10 @@ export const useSmartLanguage = <T>({
|
|||
return memo;
|
||||
}, [items, languageExtractor]);
|
||||
|
||||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
||||
number | undefined
|
||||
>();
|
||||
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<number | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTranslationIndex(
|
||||
getPreferredLanguage(preferredLanguages, availableLocales)
|
||||
);
|
||||
setSelectedTranslationIndex(getPreferredLanguage(preferredLanguages, availableLocales));
|
||||
}, [preferredLanguages, availableLocales, router.locale]);
|
||||
|
||||
const selectedTranslation = useMemo(() => {
|
||||
|
|
|
@ -8,9 +8,7 @@ import Document, {
|
|||
} from "next/document";
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
static async getInitialProps(
|
||||
ctx: DocumentContext
|
||||
): Promise<DocumentInitialProps> {
|
||||
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return { ...initialProps };
|
||||
}
|
||||
|
@ -19,45 +17,20 @@ export default class MyDocument extends Document {
|
|||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
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="apple-touch-icon" sizes="180x180" 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="mask-icon" href="/safari-pinned-tab.svg" color="#9c6644" />
|
||||
<meta name="apple-mobile-web-app-title" content="Accord's Library" />
|
||||
<meta name="application-name" content="Accord's Library" />
|
||||
<meta name="msapplication-TileColor" content="#feecd6" />
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
|
||||
<meta
|
||||
name="theme-color"
|
||||
media="(prefers-color-scheme: light)"
|
||||
content="#feecd6"
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
content="#26221e"
|
||||
/>
|
||||
<meta name="theme-color" 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-status-bar-style"
|
||||
content="black-translucent"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
||||
/*
|
||||
|
|
|
@ -2,10 +2,7 @@ import { useRouter } from "next/router";
|
|||
import { useState } from "react";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { randomInt } from "helpers/numbers";
|
||||
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
||||
|
@ -22,9 +19,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
const { langui } = useAppLayout();
|
||||
const is1ColumnLayout = useIs1ColumnLayout();
|
||||
const [formResponse, setFormResponse] = useState("");
|
||||
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">(
|
||||
"stale"
|
||||
);
|
||||
const [formState, setFormState] = useState<"completed" | "ongoing" | "stale">("stale");
|
||||
|
||||
const [randomNumber1, setRandomNumber1] = useState(randomInt(0, 10));
|
||||
const [randomNumber2, setRandomNumber2] = useState(randomInt(0, 10));
|
||||
|
@ -34,10 +29,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
<form
|
||||
className={cJoin(
|
||||
"grid gap-8",
|
||||
cIf(
|
||||
formState !== "stale",
|
||||
"pointer-events-none cursor-not-allowed touch-none opacity-60"
|
||||
)
|
||||
cIf(formState !== "stale", "pointer-events-none cursor-not-allowed touch-none opacity-60")
|
||||
)}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
|
@ -52,8 +44,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
setFormState("ongoing");
|
||||
|
||||
if (
|
||||
parseInt(fields.verif.value, 10) ===
|
||||
randomNumber1 + randomNumber2 &&
|
||||
parseInt(fields.verif.value, 10) === randomNumber1 + randomNumber2 &&
|
||||
formState !== "completed"
|
||||
) {
|
||||
const content: RequestMailProps = {
|
||||
|
@ -100,8 +91,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
|
||||
router.replace("#send-response");
|
||||
fields.verif.value = "";
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<div className="flex flex-col place-items-center gap-1">
|
||||
<label htmlFor="name">{langui.name}:</label>
|
||||
<input
|
||||
|
@ -124,9 +114,7 @@ const AboutUs = (props: PostStaticProps): JSX.Element => {
|
|||
required
|
||||
disabled={formState !== "stale"}
|
||||
/>
|
||||
<p className="text-sm italic text-dark opacity-70">
|
||||
{langui.email_gdpr_notice}
|
||||
</p>
|
||||
<p className="text-sm italic text-dark opacity-70">{langui.email_gdpr_notice}</p>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<label
|
||||
className="flex-shrink-0"
|
||||
htmlFor="verif"
|
||||
>{`${randomNumber1} + ${randomNumber2} =`}</label>
|
||||
htmlFor="verif">{`${randomNumber1} + ${randomNumber2} =`}</label>
|
||||
<input
|
||||
className="w-24"
|
||||
type="number"
|
||||
|
|
|
@ -30,17 +30,9 @@ const AboutUs = (props: Props): JSX.Element => {
|
|||
|
||||
<HorizontalLine />
|
||||
|
||||
<NavOption
|
||||
title={langui.accords_handbook}
|
||||
url="/about-us/accords-handbook"
|
||||
border
|
||||
/>
|
||||
<NavOption title={langui.accords_handbook} url="/about-us/accords-handbook" border />
|
||||
<NavOption title={langui.legality} url="/about-us/legality" border />
|
||||
<NavOption
|
||||
title={langui.sharing_policy}
|
||||
url="/about-us/sharing-policy"
|
||||
border
|
||||
/>
|
||||
<NavOption title={langui.sharing_policy} url="/about-us/sharing-policy" border />
|
||||
<NavOption title={langui.contact_us} url="/about-us/contact" border />
|
||||
</SubPanel>
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
||||
/*
|
||||
|
|
|
@ -114,16 +114,11 @@ type ResponseMailProps = {
|
|||
revalidated: boolean;
|
||||
};
|
||||
|
||||
const Revalidate = (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<ResponseMailProps>
|
||||
): void => {
|
||||
const Revalidate = (req: NextApiRequest, res: NextApiResponse<ResponseMailProps>): void => {
|
||||
const body = req.body as RequestProps;
|
||||
|
||||
// Check for secret to confirm this is a valid request
|
||||
if (
|
||||
req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`
|
||||
) {
|
||||
if (req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`) {
|
||||
res.status(401).json({ message: "Invalid token", revalidated: false });
|
||||
return;
|
||||
}
|
||||
|
@ -210,9 +205,7 @@ const Revalidate = (
|
|||
i18n.locales.forEach((locale: string) => {
|
||||
if (body.entry.library_item) {
|
||||
paths.push(`/${locale}/library/${body.entry.library_item.slug}`);
|
||||
paths.push(
|
||||
`/${locale}/library/${body.entry.library_item.slug}/scans`
|
||||
);
|
||||
paths.push(`/${locale}/library/${body.entry.library_item.slug}/scans`);
|
||||
}
|
||||
if (body.entry.content) {
|
||||
paths.push(`/${locale}/contents/${body.entry.content.slug}`);
|
||||
|
@ -232,25 +225,19 @@ const Revalidate = (
|
|||
body.entry.subfolders.forEach((subfolder) =>
|
||||
paths.push(`/contents/folder/${subfolder.slug}`)
|
||||
);
|
||||
body.entry.contents.forEach((content) =>
|
||||
paths.push(`/contents/${content.slug}`)
|
||||
);
|
||||
body.entry.contents.forEach((content) => paths.push(`/contents/${content.slug}`));
|
||||
i18n.locales.forEach((locale: string) => {
|
||||
if (body.entry.slug === "root") {
|
||||
paths.push(`/${locale}/contents`);
|
||||
}
|
||||
paths.push(`/${locale}/contents/folder/${body.entry.slug}`);
|
||||
if (body.entry.parent_folder) {
|
||||
paths.push(
|
||||
`/${locale}/contents/folder/${body.entry.parent_folder.slug}`
|
||||
);
|
||||
paths.push(`/${locale}/contents/folder/${body.entry.parent_folder.slug}`);
|
||||
}
|
||||
body.entry.subfolders.forEach((subfolder) =>
|
||||
paths.push(`/${locale}/contents/folder/${subfolder.slug}`)
|
||||
);
|
||||
body.entry.contents.forEach((content) =>
|
||||
paths.push(`/${locale}/contents/${content.slug}`)
|
||||
);
|
||||
body.entry.contents.forEach((content) => paths.push(`/${locale}/contents/${content.slug}`));
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -311,9 +298,7 @@ const Revalidate = (
|
|||
res.json({ message: "Success!", revalidated: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
res
|
||||
.status(500)
|
||||
.send({ message: `Error revalidating: ${error}`, revalidated: false });
|
||||
res.status(500).send({ message: `Error revalidating: ${error}`, revalidated: false });
|
||||
}
|
||||
};
|
||||
export default Revalidate;
|
||||
|
|
|
@ -5,10 +5,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
|||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { GetVideoChannelQuery } from "graphql/generated";
|
||||
|
@ -43,21 +40,16 @@ const DEFAULT_FILTERS_STATE = {
|
|||
*/
|
||||
|
||||
interface Props extends AppLayoutRequired {
|
||||
channel: NonNullable<
|
||||
GetVideoChannelQuery["videoChannels"]
|
||||
>["data"][number]["attributes"];
|
||||
channel: NonNullable<GetVideoChannelQuery["videoChannels"]>["data"][number]["attributes"];
|
||||
}
|
||||
|
||||
const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
||||
useBoolean(true);
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true);
|
||||
const { langui } = useAppLayout();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
);
|
||||
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
|
@ -98,10 +90,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
|||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<SmartList
|
||||
items={filterHasAttributes(channel?.videos?.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const)}
|
||||
items={filterHasAttributes(channel?.videos?.data, ["id", "attributes"] as const)}
|
||||
getItemId={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<PreviewCard
|
||||
|
@ -135,22 +124,10 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
channel?.title,
|
||||
channel?.videos?.data,
|
||||
isContentPanelAtLeast4xl,
|
||||
keepInfoVisible,
|
||||
searchName,
|
||||
]
|
||||
[channel?.title, channel?.videos?.data, isContentPanelAtLeast4xl, keepInfoVisible, searchName]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||
};
|
||||
export default Channel;
|
||||
|
||||
|
@ -163,25 +140,17 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const channel = await sdk.getVideoChannel({
|
||||
channel:
|
||||
context.params && isDefined(context.params.uid)
|
||||
? context.params.uid.toString()
|
||||
: "",
|
||||
channel: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
|
||||
});
|
||||
if (!channel.videoChannels?.data[0].attributes) return { notFound: true };
|
||||
|
||||
channel.videoChannels.data[0].attributes.videos?.data
|
||||
.sort((a, b) =>
|
||||
compareDate(a.attributes?.published_date, b.attributes?.published_date)
|
||||
)
|
||||
.sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date))
|
||||
.reverse();
|
||||
|
||||
const props: Props = {
|
||||
channel: channel.videoChannels.data[0].attributes,
|
||||
openGraph: getOpenGraph(
|
||||
langui,
|
||||
channel.videoChannels.data[0].attributes.title
|
||||
),
|
||||
openGraph: getOpenGraph(langui, channel.videoChannels.data[0].attributes.title),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
@ -196,9 +165,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
|
||||
if (channels.videoChannels?.data)
|
||||
filterHasAttributes(channels.videoChannels.data, [
|
||||
"attributes",
|
||||
] as const).map((channel) => {
|
||||
filterHasAttributes(channels.videoChannels.data, ["attributes"] as const).map((channel) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({
|
||||
params: { uid: channel.attributes.uid },
|
||||
|
|
|
@ -9,10 +9,7 @@ import { TextInput } from "components/Inputs/TextInput";
|
|||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { GetVideosPreviewQuery } from "graphql/generated";
|
||||
|
@ -51,12 +48,9 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
|
|||
const hoverable = useDeviceSupportsHover();
|
||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
||||
useBoolean(true);
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(true);
|
||||
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
);
|
||||
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
|
@ -68,11 +62,7 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
|
|||
className="mb-10"
|
||||
/>
|
||||
|
||||
<PanelHeader
|
||||
icon={Icon.Movie}
|
||||
title="Videos"
|
||||
description={langui.archives_description}
|
||||
/>
|
||||
<PanelHeader icon={Icon.Movie} title="Videos" description={langui.archives_description} />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -132,13 +122,7 @@ const Videos = ({ videos, ...otherProps }: Props): JSX.Element => {
|
|||
),
|
||||
[isContentPanelAtLeast4xl, keepInfoVisible, searchName, videos]
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||
};
|
||||
export default Videos;
|
||||
|
||||
|
@ -153,9 +137,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const videos = await sdk.getVideosPreview();
|
||||
if (!videos.videos) return { notFound: true };
|
||||
videos.videos.data
|
||||
.sort((a, b) =>
|
||||
compareDate(a.attributes?.published_date, b.attributes?.published_date)
|
||||
)
|
||||
.sort((a, b) => compareDate(a.attributes?.published_date, b.attributes?.published_date))
|
||||
.reverse();
|
||||
|
||||
const props: Props = {
|
||||
|
|
|
@ -8,10 +8,7 @@ import { Button } from "components/Inputs/Button";
|
|||
import { InsetBox } from "components/InsetBox";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { GetVideoQuery } from "graphql/generated";
|
||||
|
@ -29,9 +26,7 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
*/
|
||||
|
||||
interface Props extends AppLayoutRequired {
|
||||
video: NonNullable<
|
||||
NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]
|
||||
>;
|
||||
video: NonNullable<NonNullable<GetVideoQuery["videos"]>["data"][number]["attributes"]>;
|
||||
}
|
||||
|
||||
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
|
||||
id="video"
|
||||
className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade"
|
||||
>
|
||||
<div id="video" className="w-full overflow-hidden rounded-xl shadow-lg shadow-shade">
|
||||
{video.gone ? (
|
||||
<video
|
||||
className="w-full"
|
||||
src={getVideoFile(video.uid)}
|
||||
controls
|
||||
></video>
|
||||
<video className="w-full" src={getVideoFile(video.uid)} controls></video>
|
||||
) : (
|
||||
<iframe
|
||||
src={`https://www.youtube-nocookie.com/embed/${video.uid}`}
|
||||
|
@ -104,49 +92,32 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write;
|
||||
encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
allowFullScreen></iframe>
|
||||
)}
|
||||
|
||||
<div className="mt-2 p-6">
|
||||
<h1 className="text-2xl">{video.title}</h1>
|
||||
<div className="flex w-full flex-row flex-wrap gap-x-6">
|
||||
<p>
|
||||
<Ico
|
||||
icon={Icon.Event}
|
||||
className="mr-1 translate-y-[.15em] !text-base"
|
||||
/>
|
||||
<Ico icon={Icon.Event} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyDate(video.published_date, router.locale)}
|
||||
</p>
|
||||
<p>
|
||||
<Ico
|
||||
icon={Icon.Visibility}
|
||||
className="mr-1 translate-y-[.15em] !text-base"
|
||||
/>
|
||||
<Ico icon={Icon.Visibility} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{isContentPanelAtLeast4xl
|
||||
? video.views.toLocaleString()
|
||||
: prettyShortenNumber(video.views)}
|
||||
</p>
|
||||
{video.channel?.data?.attributes && (
|
||||
<p>
|
||||
<Ico
|
||||
icon={Icon.ThumbUp}
|
||||
className="mr-1 translate-y-[.15em] !text-base"
|
||||
/>
|
||||
<Ico icon={Icon.ThumbUp} className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{isContentPanelAtLeast4xl
|
||||
? video.likes.toLocaleString()
|
||||
: prettyShortenNumber(video.likes)}
|
||||
</p>
|
||||
)}
|
||||
<a
|
||||
href={`https://youtu.be/${video.uid}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Button
|
||||
className="!py-0 !px-3"
|
||||
text={`${langui.view_on} ${video.source}`}
|
||||
/>
|
||||
<a href={`https://youtu.be/${video.uid}`} target="_blank" rel="noreferrer">
|
||||
<Button className="!py-0 !px-3" text={`${langui.view_on} ${video.source}`} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -195,13 +166,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||
};
|
||||
export default Video;
|
||||
|
||||
|
@ -214,10 +179,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const videos = await sdk.getVideo({
|
||||
uid:
|
||||
context.params && isDefined(context.params.uid)
|
||||
? context.params.uid.toString()
|
||||
: "",
|
||||
uid: context.params && isDefined(context.params.uid) ? context.params.uid.toString() : "",
|
||||
});
|
||||
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 paths: GetStaticPathsResult["paths"] = [];
|
||||
if (videos.videos?.data)
|
||||
filterHasAttributes(videos.videos.data, ["attributes"] as const).map(
|
||||
(video) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { uid: video.attributes.uid }, locale: local });
|
||||
});
|
||||
}
|
||||
);
|
||||
filterHasAttributes(videos.videos.data, ["attributes"] as const).map((video) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({ params: { uid: video.attributes.uid }, locale: local });
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking",
|
||||
|
|
|
@ -2,7 +2,7 @@ import { GetStaticProps, GetStaticPaths, GetStaticPathsResult } from "next";
|
|||
import { useCallback, useMemo } from "react";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { isDefined, filterHasAttributes } from "helpers/others";
|
||||
import { ChronicleWithTranslations } from "helpers/types";
|
||||
import { ChronicleWithTranslations } from "types/types";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||
|
@ -15,10 +15,7 @@ import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
|||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import {
|
||||
getDefaultPreferredLanguages,
|
||||
staticSmartLanguage,
|
||||
} from "helpers/locales";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { TranslatedChroniclesList } from "components/Chronicles/ChroniclesList";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
@ -31,59 +28,44 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
|
||||
interface Props extends AppLayoutRequired {
|
||||
chronicle: ChronicleWithTranslations;
|
||||
chapters: NonNullable<
|
||||
GetChroniclesChaptersQuery["chroniclesChapters"]
|
||||
>["data"];
|
||||
chapters: NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"];
|
||||
}
|
||||
|
||||
const Chronicle = ({
|
||||
chronicle,
|
||||
chapters,
|
||||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element => {
|
||||
const { langui } = useAppLayout();
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
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 ?? [],
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: chronicle.translations,
|
||||
languageExtractor: useCallback(
|
||||
(
|
||||
item: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
ChronicleWithTranslations["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["translations"]
|
||||
>[number]
|
||||
) => item?.language?.data?.attributes?.code,
|
||||
(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(
|
||||
(
|
||||
item: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<ChronicleWithTranslations["contents"]>["data"][number]["attributes"]
|
||||
>["translations"]
|
||||
>[number]
|
||||
) => item?.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel>
|
||||
|
@ -96,9 +78,7 @@ const Chronicle = ({
|
|||
|
||||
{isDefined(selectedTranslation) ? (
|
||||
<>
|
||||
<h1 className="mb-16 text-center text-3xl">
|
||||
{selectedTranslation.title}
|
||||
</h1>
|
||||
<h1 className="mb-16 text-center text-3xl">{selectedTranslation.title}</h1>
|
||||
|
||||
{languageSwitcherProps.locales.size > 1 && (
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
@ -118,9 +98,7 @@ const Chronicle = ({
|
|||
subtitle={selectedContentTranslation.subtitle}
|
||||
languageSwitcher={
|
||||
ContentLanguageSwitcherProps.locales.size > 1 ? (
|
||||
<ContentLanguageSwitcher
|
||||
{...ContentLanguageSwitcherProps}
|
||||
/>
|
||||
<ContentLanguageSwitcher {...ContentLanguageSwitcherProps} />
|
||||
) : undefined
|
||||
}
|
||||
categories={primaryContent?.categories}
|
||||
|
@ -167,23 +145,22 @@ const Chronicle = ({
|
|||
<HorizontalLine />
|
||||
|
||||
<div className="grid gap-16">
|
||||
{filterHasAttributes(chapters, [
|
||||
"attributes.chronicles",
|
||||
"id",
|
||||
] as const).map((chapter) => (
|
||||
<TranslatedChroniclesList
|
||||
key={chapter.id}
|
||||
chronicles={chapter.attributes.chronicles.data}
|
||||
translations={filterHasAttributes(chapter.attributes.titles, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
title: translation.title,
|
||||
language: translation.language.data.attributes.code,
|
||||
}))}
|
||||
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
||||
currentSlug={chronicle.slug}
|
||||
/>
|
||||
))}
|
||||
{filterHasAttributes(chapters, ["attributes.chronicles", "id"] as const).map(
|
||||
(chapter) => (
|
||||
<TranslatedChroniclesList
|
||||
key={chapter.id}
|
||||
chronicles={chapter.attributes.chronicles.data}
|
||||
translations={filterHasAttributes(chapter.attributes.titles, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
title: translation.title,
|
||||
language: translation.language.data.attributes.code,
|
||||
}))}
|
||||
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
||||
currentSlug={chronicle.slug}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</SubPanel>
|
||||
),
|
||||
|
@ -210,9 +187,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const slug =
|
||||
context.params && isDefined(context.params.slug)
|
||||
? context.params.slug.toString()
|
||||
: "";
|
||||
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||
const chronicle = await sdk.getChronicle({
|
||||
language_code: context.locale ?? "en",
|
||||
slug: slug,
|
||||
|
@ -226,19 +201,11 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const { title, description } = (() => {
|
||||
if (context.locale && context.locales) {
|
||||
if (
|
||||
chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes
|
||||
?.translations
|
||||
) {
|
||||
if (chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes?.translations) {
|
||||
const selectedContentTranslation = staticSmartLanguage({
|
||||
items:
|
||||
chronicle.chronicles.data[0].attributes.contents.data[0].attributes
|
||||
.translations,
|
||||
items: chronicle.chronicles.data[0].attributes.contents.data[0].attributes.translations,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
preferredLanguages: getDefaultPreferredLanguages(
|
||||
context.locale,
|
||||
context.locales
|
||||
),
|
||||
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||
});
|
||||
if (selectedContentTranslation) {
|
||||
return {
|
||||
|
@ -247,30 +214,24 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
selectedContentTranslation.title,
|
||||
selectedContentTranslation.subtitle
|
||||
),
|
||||
description: getDescription(
|
||||
selectedContentTranslation.description,
|
||||
{
|
||||
[langui.type ?? "Type"]: [
|
||||
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?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
}
|
||||
),
|
||||
description: getDescription(selectedContentTranslation.description, {
|
||||
[langui.type ?? "Type"]: [
|
||||
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
|
||||
?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short),
|
||||
}),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const selectedTranslation = staticSmartLanguage({
|
||||
items: chronicle.chronicles.data[0].attributes.translations,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
preferredLanguages: getDefaultPreferredLanguages(
|
||||
context.locale,
|
||||
context.locales
|
||||
),
|
||||
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||
});
|
||||
if (selectedTranslation) {
|
||||
return {
|
||||
|
@ -288,13 +249,12 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const thumbnail =
|
||||
chronicle.chronicles.data[0].attributes.translations.length === 0
|
||||
? chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes
|
||||
?.thumbnail?.data?.attributes
|
||||
? chronicle.chronicles.data[0].attributes.contents?.data[0]?.attributes?.thumbnail?.data
|
||||
?.attributes
|
||||
: undefined;
|
||||
|
||||
const props: Props = {
|
||||
chronicle: chronicle.chronicles.data[0]
|
||||
.attributes as ChronicleWithTranslations,
|
||||
chronicle: chronicle.chronicles.data[0].attributes as ChronicleWithTranslations,
|
||||
chapters: chronicles.chroniclesChapters.data,
|
||||
openGraph: getOpenGraph(langui, title, description, thumbnail),
|
||||
};
|
||||
|
@ -309,16 +269,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const contents = await sdk.getChroniclesSlugs();
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
filterHasAttributes(contents.chronicles?.data, ["attributes"] as const).map(
|
||||
(wikiPage) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({
|
||||
params: { slug: wikiPage.attributes.slug },
|
||||
locale: local,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
filterHasAttributes(contents.chronicles?.data, ["attributes"] as const).map((wikiPage) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({
|
||||
params: { slug: wikiPage.attributes.slug },
|
||||
locale: local,
|
||||
})
|
||||
);
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking",
|
||||
|
|
|
@ -20,9 +20,7 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
*/
|
||||
|
||||
interface Props extends AppLayoutRequired {
|
||||
chapters: NonNullable<
|
||||
GetChroniclesChaptersQuery["chroniclesChapters"]
|
||||
>["data"];
|
||||
chapters: NonNullable<GetChroniclesChaptersQuery["chroniclesChapters"]>["data"];
|
||||
}
|
||||
|
||||
const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
|
||||
|
@ -39,22 +37,21 @@ const Chronicles = ({ chapters, ...otherProps }: Props): JSX.Element => {
|
|||
<HorizontalLine />
|
||||
|
||||
<div className="grid gap-16">
|
||||
{filterHasAttributes(chapters, [
|
||||
"attributes.chronicles",
|
||||
"id",
|
||||
] as const).map((chapter) => (
|
||||
<TranslatedChroniclesList
|
||||
key={chapter.id}
|
||||
chronicles={chapter.attributes.chronicles.data}
|
||||
translations={filterHasAttributes(chapter.attributes.titles, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
title: translation.title,
|
||||
language: translation.language.data.attributes.code,
|
||||
}))}
|
||||
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
||||
/>
|
||||
))}
|
||||
{filterHasAttributes(chapters, ["attributes.chronicles", "id"] as const).map(
|
||||
(chapter) => (
|
||||
<TranslatedChroniclesList
|
||||
key={chapter.id}
|
||||
chronicles={chapter.attributes.chronicles.data}
|
||||
translations={filterHasAttributes(chapter.attributes.titles, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
title: translation.title,
|
||||
language: translation.language.data.attributes.code,
|
||||
}))}
|
||||
fallback={{ title: prettySlug(chapter.attributes.slug) }}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</SubPanel>
|
||||
),
|
||||
|
|
|
@ -20,25 +20,15 @@ import {
|
|||
prettySlug,
|
||||
} from "helpers/formatters";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
getStatusDescription,
|
||||
isDefinedAndNotEmpty,
|
||||
} from "helpers/others";
|
||||
import { ContentWithTranslations } from "helpers/types";
|
||||
import { filterHasAttributes, getStatusDescription, isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { ContentWithTranslations } from "types/types";
|
||||
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import {
|
||||
getDefaultPreferredLanguages,
|
||||
staticSmartLanguage,
|
||||
} from "helpers/locales";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { TranslatedPreviewLine } from "components/PreviewLine";
|
||||
import {
|
||||
useIs1ColumnLayout,
|
||||
useIsContentPanelAtLeast,
|
||||
} from "hooks/useContainerQuery";
|
||||
import { useIs1ColumnLayout, useIsContentPanelAtLeast } from "hooks/useContainerQuery";
|
||||
import { cIf } from "helpers/className";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
@ -58,35 +48,26 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
const is1ColumnLayout = useIs1ColumnLayout();
|
||||
const { langui, languages } = useAppLayout();
|
||||
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: content.translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["content"]["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: content.translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["content"]["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
useScrollTopOnChange(Ids.ContentPanel, [selectedTranslation]);
|
||||
|
||||
const { previousContent, nextContent } = useMemo(
|
||||
() => ({
|
||||
previousContent:
|
||||
content.folder?.data?.attributes?.contents &&
|
||||
content.folder.data.attributes.sequence
|
||||
? getPreviousContent(
|
||||
content.folder.data.attributes.contents.data,
|
||||
content.slug
|
||||
)
|
||||
content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence
|
||||
? getPreviousContent(content.folder.data.attributes.contents.data, content.slug)
|
||||
: undefined,
|
||||
nextContent:
|
||||
content.folder?.data?.attributes?.contents &&
|
||||
content.folder.data.attributes.sequence
|
||||
? getNextContent(
|
||||
content.folder.data.attributes.contents.data,
|
||||
content.slug
|
||||
)
|
||||
content.folder?.data?.attributes?.contents && content.folder.data.attributes.sequence
|
||||
? getNextContent(content.folder.data.attributes.contents.data, content.slug)
|
||||
: undefined,
|
||||
}),
|
||||
[content.folder, content.slug]
|
||||
|
@ -98,10 +79,9 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
? `/contents/folder/${content.folder.data.attributes.slug}`
|
||||
: "/contents",
|
||||
|
||||
translations: filterHasAttributes(
|
||||
content.folder?.data?.attributes?.titles,
|
||||
["language.data.attributes.code"] as const
|
||||
).map((title) => ({
|
||||
translations: filterHasAttributes(content.folder?.data?.attributes?.titles, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((title) => ({
|
||||
language: title.language.data.attributes.code,
|
||||
title: title.title,
|
||||
})),
|
||||
|
@ -118,34 +98,26 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<TranslatedReturnButton
|
||||
{...returnButtonProps}
|
||||
displayOnlyOn="3ColumnsLayout"
|
||||
/>
|
||||
<TranslatedReturnButton {...returnButtonProps} displayOnlyOn="3ColumnsLayout" />
|
||||
|
||||
{selectedTranslation?.text_set?.source_language?.data?.attributes
|
||||
?.code !== undefined && (
|
||||
{selectedTranslation?.text_set?.source_language?.data?.attributes?.code !== undefined && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<div className="grid gap-5">
|
||||
<h2 className="text-xl">
|
||||
{selectedTranslation.text_set.source_language.data.attributes
|
||||
.code === selectedTranslation.language?.data?.attributes?.code
|
||||
{selectedTranslation.text_set.source_language.data.attributes.code ===
|
||||
selectedTranslation.language?.data?.attributes?.code
|
||||
? langui.transcript_notice
|
||||
: langui.translation_notice}
|
||||
</h2>
|
||||
|
||||
{selectedTranslation.text_set.source_language.data.attributes
|
||||
.code !==
|
||||
{selectedTranslation.text_set.source_language.data.attributes.code !==
|
||||
selectedTranslation.language?.data?.attributes?.code && (
|
||||
<div className="grid place-items-center gap-2">
|
||||
<p className="font-headers font-bold">
|
||||
{langui.source_language}:
|
||||
</p>
|
||||
<p className="font-headers font-bold">{langui.source_language}:</p>
|
||||
<Chip
|
||||
text={prettyLanguage(
|
||||
selectedTranslation.text_set.source_language.data
|
||||
.attributes.code,
|
||||
selectedTranslation.text_set.source_language.data.attributes.code,
|
||||
languages
|
||||
)}
|
||||
/>
|
||||
|
@ -156,12 +128,8 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(
|
||||
selectedTranslation.text_set.status,
|
||||
langui
|
||||
)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
content={getStatusDescription(selectedTranslation.text_set.status, langui)}
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedTranslation.text_set.status} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
@ -169,14 +137,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{selectedTranslation.text_set.transcribers &&
|
||||
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.transcribers}:
|
||||
</p>
|
||||
<p className="font-headers font-bold">{langui.transcribers}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(
|
||||
selectedTranslation.text_set.transcribers.data,
|
||||
["attributes", "id"] as const
|
||||
).map((recorder) => (
|
||||
{filterHasAttributes(selectedTranslation.text_set.transcribers.data, [
|
||||
"attributes",
|
||||
"id",
|
||||
] as const).map((recorder) => (
|
||||
<Fragment key={recorder.id}>
|
||||
<RecorderChip recorder={recorder.attributes} />
|
||||
</Fragment>
|
||||
|
@ -188,14 +154,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{selectedTranslation.text_set.translators &&
|
||||
selectedTranslation.text_set.translators.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.translators}:
|
||||
</p>
|
||||
<p className="font-headers font-bold">{langui.translators}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(
|
||||
selectedTranslation.text_set.translators.data,
|
||||
["attributes", "id"] as const
|
||||
).map((recorder) => (
|
||||
{filterHasAttributes(selectedTranslation.text_set.translators.data, [
|
||||
"attributes",
|
||||
"id",
|
||||
] as const).map((recorder) => (
|
||||
<Fragment key={recorder.id}>
|
||||
<RecorderChip recorder={recorder.attributes} />
|
||||
</Fragment>
|
||||
|
@ -207,14 +171,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{selectedTranslation.text_set.proofreaders &&
|
||||
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.proofreaders}:
|
||||
</p>
|
||||
<p className="font-headers font-bold">{langui.proofreaders}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(
|
||||
selectedTranslation.text_set.proofreaders.data,
|
||||
["attributes", "id"] as const
|
||||
).map((recorder) => (
|
||||
{filterHasAttributes(selectedTranslation.text_set.proofreaders.data, [
|
||||
"attributes",
|
||||
"id",
|
||||
] as const).map((recorder) => (
|
||||
<Fragment key={recorder.id}>
|
||||
<RecorderChip recorder={recorder.attributes} />
|
||||
</Fragment>
|
||||
|
@ -249,68 +211,56 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
</>
|
||||
)}
|
||||
|
||||
{content.ranged_contents?.data &&
|
||||
content.ranged_contents.data.length > 0 && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<div>
|
||||
<p className="font-headers text-2xl font-bold">
|
||||
{langui.source}
|
||||
</p>
|
||||
<div className="mt-6 grid place-items-center gap-6">
|
||||
{filterHasAttributes(content.ranged_contents.data, [
|
||||
"attributes.library_item.data.attributes",
|
||||
"attributes.library_item.data.id",
|
||||
] as const).map((rangedContent) => {
|
||||
const libraryItem =
|
||||
rangedContent.attributes.library_item.data;
|
||||
return (
|
||||
<div
|
||||
key={libraryItem.attributes.slug}
|
||||
className={cIf(is1ColumnLayout, "w-3/4")}
|
||||
>
|
||||
<PreviewCard
|
||||
href={`/library/${libraryItem.attributes.slug}`}
|
||||
title={libraryItem.attributes.title}
|
||||
subtitle={libraryItem.attributes.subtitle}
|
||||
thumbnail={
|
||||
libraryItem.attributes.thumbnail?.data?.attributes
|
||||
}
|
||||
thumbnailAspectRatio="21/29.7"
|
||||
thumbnailRounded={false}
|
||||
topChips={
|
||||
libraryItem.attributes.metadata &&
|
||||
libraryItem.attributes.metadata.length > 0 &&
|
||||
libraryItem.attributes.metadata[0]
|
||||
? [
|
||||
prettyItemSubType(
|
||||
libraryItem.attributes.metadata[0]
|
||||
),
|
||||
]
|
||||
: []
|
||||
}
|
||||
bottomChips={filterHasAttributes(
|
||||
libraryItem.attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short)}
|
||||
metadata={{
|
||||
releaseDate: libraryItem.attributes.release_date,
|
||||
price: libraryItem.attributes.price,
|
||||
position: "Bottom",
|
||||
}}
|
||||
infoAppend={
|
||||
!isUntangibleGroupItem(
|
||||
libraryItem.attributes.metadata?.[0]
|
||||
) && <PreviewCardCTAs id={libraryItem.id} />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{content.ranged_contents?.data && content.ranged_contents.data.length > 0 && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<div>
|
||||
<p className="font-headers text-2xl font-bold">{langui.source}</p>
|
||||
<div className="mt-6 grid place-items-center gap-6">
|
||||
{filterHasAttributes(content.ranged_contents.data, [
|
||||
"attributes.library_item.data.attributes",
|
||||
"attributes.library_item.data.id",
|
||||
] as const).map((rangedContent) => {
|
||||
const libraryItem = rangedContent.attributes.library_item.data;
|
||||
return (
|
||||
<div
|
||||
key={libraryItem.attributes.slug}
|
||||
className={cIf(is1ColumnLayout, "w-3/4")}>
|
||||
<PreviewCard
|
||||
href={`/library/${libraryItem.attributes.slug}`}
|
||||
title={libraryItem.attributes.title}
|
||||
subtitle={libraryItem.attributes.subtitle}
|
||||
thumbnail={libraryItem.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="21/29.7"
|
||||
thumbnailRounded={false}
|
||||
topChips={
|
||||
libraryItem.attributes.metadata &&
|
||||
libraryItem.attributes.metadata.length > 0 &&
|
||||
libraryItem.attributes.metadata[0]
|
||||
? [prettyItemSubType(libraryItem.attributes.metadata[0])]
|
||||
: []
|
||||
}
|
||||
bottomChips={filterHasAttributes(libraryItem.attributes.categories?.data, [
|
||||
"attributes",
|
||||
] as const).map((category) => category.attributes.short)}
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
</SubPanel>
|
||||
),
|
||||
[
|
||||
|
@ -350,15 +300,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{previousContent?.attributes && (
|
||||
<div className="mt-12 mb-8 w-full">
|
||||
<h2 className="mb-4 text-center text-2xl">
|
||||
{langui.previous_content}
|
||||
</h2>
|
||||
<h2 className="mb-4 text-center text-2xl">{langui.previous_content}</h2>
|
||||
<TranslatedPreviewLine
|
||||
href={`/contents/${previousContent.attributes.slug}`}
|
||||
translations={filterHasAttributes(
|
||||
previousContent.attributes.translations,
|
||||
["language.data.attributes.code"] as const
|
||||
).map((translation) => ({
|
||||
translations={filterHasAttributes(previousContent.attributes.translations, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
pre_title: translation.pre_title,
|
||||
title: translation.title,
|
||||
subtitle: translation.subtitle,
|
||||
|
@ -367,22 +314,14 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
fallback={{
|
||||
title: prettySlug(previousContent.attributes.slug),
|
||||
}}
|
||||
thumbnail={
|
||||
previousContent.attributes.thumbnail?.data?.attributes
|
||||
}
|
||||
thumbnail={previousContent.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
topChips={
|
||||
isContentPanelAtLeast2xl &&
|
||||
previousContent.attributes.type?.data?.attributes
|
||||
isContentPanelAtLeast2xl && previousContent.attributes.type?.data?.attributes
|
||||
? [
|
||||
previousContent.attributes.type.data.attributes
|
||||
.titles?.[0]
|
||||
? previousContent.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
previousContent.attributes.type.data.attributes
|
||||
.slug
|
||||
),
|
||||
previousContent.attributes.type.data.attributes.titles?.[0]
|
||||
? previousContent.attributes.type.data.attributes.titles[0]?.title
|
||||
: prettySlug(previousContent.attributes.type.data.attributes.slug),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
|
@ -407,15 +346,12 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
{nextContent?.attributes && (
|
||||
<>
|
||||
<HorizontalLine />
|
||||
<h2 className="mb-4 text-center text-2xl">
|
||||
{langui.followup_content}
|
||||
</h2>
|
||||
<h2 className="mb-4 text-center text-2xl">{langui.followup_content}</h2>
|
||||
<TranslatedPreviewLine
|
||||
href={`/contents/${nextContent.attributes.slug}`}
|
||||
translations={filterHasAttributes(
|
||||
nextContent.attributes.translations,
|
||||
["language.data.attributes.code"] as const
|
||||
).map((translation) => ({
|
||||
translations={filterHasAttributes(nextContent.attributes.translations, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((translation) => ({
|
||||
pre_title: translation.pre_title,
|
||||
title: translation.title,
|
||||
subtitle: translation.subtitle,
|
||||
|
@ -425,15 +361,11 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
topChips={
|
||||
isContentPanelAtLeast2xl &&
|
||||
nextContent.attributes.type?.data?.attributes
|
||||
isContentPanelAtLeast2xl && nextContent.attributes.type?.data?.attributes
|
||||
? [
|
||||
nextContent.attributes.type.data.attributes.titles?.[0]
|
||||
? nextContent.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
: prettySlug(
|
||||
nextContent.attributes.type.data.attributes.slug
|
||||
),
|
||||
? nextContent.attributes.type.data.attributes.titles[0]?.title
|
||||
: prettySlug(nextContent.attributes.type.data.attributes.slug),
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
|
@ -469,13 +401,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
|
|||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
|
||||
};
|
||||
export default Content;
|
||||
|
||||
|
@ -502,10 +428,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const selectedTranslation = staticSmartLanguage({
|
||||
items: content.contents.data[0].attributes.translations,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
preferredLanguages: getDefaultPreferredLanguages(
|
||||
context.locale,
|
||||
context.locales
|
||||
),
|
||||
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||
});
|
||||
if (selectedTranslation) {
|
||||
return {
|
||||
|
@ -516,8 +439,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
),
|
||||
description: getDescription(selectedTranslation.description, {
|
||||
[langui.type ?? "Type"]: [
|
||||
content.contents.data[0].attributes.type?.data?.attributes
|
||||
?.titles?.[0]?.title,
|
||||
content.contents.data[0].attributes.type?.data?.attributes?.titles?.[0]?.title,
|
||||
],
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
content.contents.data[0].attributes.categories?.data,
|
||||
|
@ -533,8 +455,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
};
|
||||
})();
|
||||
|
||||
const thumbnail =
|
||||
content.contents.data[0].attributes.thumbnail?.data?.attributes;
|
||||
const thumbnail = content.contents.data[0].attributes.thumbnail?.data?.attributes;
|
||||
|
||||
const props: Props = {
|
||||
content: content.contents.data[0].attributes as ContentWithTranslations,
|
||||
|
@ -551,16 +472,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const contents = await sdk.getContentsSlugs();
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
|
||||
(item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({
|
||||
params: { slug: item.attributes.slug },
|
||||
locale: local,
|
||||
});
|
||||
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({
|
||||
params: { slug: item.attributes.slug },
|
||||
locale: local,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking",
|
||||
|
@ -574,9 +493,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
|
||||
type FolderContents = NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<ContentWithTranslations["folder"]>["data"]
|
||||
>["attributes"]
|
||||
NonNullable<NonNullable<ContentWithTranslations["folder"]>["data"]>["attributes"]
|
||||
>["contents"]
|
||||
>["data"];
|
||||
|
||||
|
@ -595,10 +512,7 @@ const getPreviousContent = (contents: FolderContents, currentSlug: string) => {
|
|||
const getNextContent = (contents: FolderContents, currentSlug: string) => {
|
||||
for (let index = 0; index < contents.length; index++) {
|
||||
const content = contents[index];
|
||||
if (
|
||||
content.attributes?.slug === currentSlug &&
|
||||
index < contents.length - 1
|
||||
) {
|
||||
if (content.attributes?.slug === currentSlug && index < contents.length - 1) {
|
||||
return contents[index + 1];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
|||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyInlineTitle, prettySlug } from "helpers/formatters";
|
||||
|
@ -18,11 +15,7 @@ import { WithLabel } from "components/Inputs/WithLabel";
|
|||
import { Button } from "components/Inputs/Button";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import { Icon } from "components/Ico";
|
||||
import {
|
||||
filterDefined,
|
||||
filterHasAttributes,
|
||||
isDefinedAndNotEmpty,
|
||||
} from "helpers/others";
|
||||
import { filterDefined, filterHasAttributes, isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { GetContentsQuery } from "graphql/generated";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { SelectiveNonNullable } from "types/SelectiveNonNullable";
|
||||
|
@ -68,9 +61,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
);
|
||||
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||
|
||||
const groupingFunction = useCallback(
|
||||
(
|
||||
|
@ -81,10 +72,9 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
): string[] => {
|
||||
switch (groupingMethod) {
|
||||
case 0: {
|
||||
const categories = filterHasAttributes(
|
||||
item.attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
);
|
||||
const categories = filterHasAttributes(item.attributes.categories?.data, [
|
||||
"attributes",
|
||||
] as const);
|
||||
if (categories.length > 0) {
|
||||
return categories.map((category) => category.attributes.name);
|
||||
}
|
||||
|
@ -107,17 +97,11 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
);
|
||||
|
||||
const filteringFunction = useCallback(
|
||||
(
|
||||
item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id">
|
||||
) => {
|
||||
(item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id">) => {
|
||||
if (searchName.length > 1) {
|
||||
if (
|
||||
filterDefined(item.attributes.translations).find((translation) =>
|
||||
prettyInlineTitle(
|
||||
translation.pre_title,
|
||||
translation.title,
|
||||
translation.subtitle
|
||||
)
|
||||
prettyInlineTitle(translation.pre_title, translation.title, translation.subtitle)
|
||||
.toLowerCase()
|
||||
.includes(searchName.toLowerCase())
|
||||
)
|
||||
|
@ -142,12 +126,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
<HorizontalLine />
|
||||
|
||||
<Button
|
||||
href="/contents"
|
||||
/* TODO: Langui */
|
||||
text={"Switch to folder view"}
|
||||
icon={Icon.Folder}
|
||||
/>
|
||||
<Button href="/contents" text={langui.switch_to_folder_view} icon={Icon.Folder} />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -173,9 +152,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
onChange={(value) => {
|
||||
setGroupingMethod(value);
|
||||
umami(
|
||||
`[Contents/All] Change grouping method (${
|
||||
["none", "category", "type"][value + 1]
|
||||
})`
|
||||
`[Contents/All] Change grouping method (${["none", "category", "type"][value + 1]})`
|
||||
);
|
||||
}}
|
||||
allowEmpty
|
||||
|
@ -188,11 +165,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
value={keepInfoVisible}
|
||||
onClick={() => {
|
||||
toggleKeepInfoVisible();
|
||||
umami(
|
||||
`[Contents/All] Always ${
|
||||
keepInfoVisible ? "hide" : "show"
|
||||
} info`
|
||||
);
|
||||
umami(`[Contents/All] Always ${keepInfoVisible ? "hide" : "show"} info`);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
@ -222,6 +195,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
langui.group_by,
|
||||
langui.reset_all_filters,
|
||||
langui.search_title,
|
||||
langui.switch_to_folder_view,
|
||||
langui.type,
|
||||
searchName,
|
||||
setKeepInfoVisible,
|
||||
|
@ -281,11 +255,7 @@ const Contents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
${item.attributes.slug}
|
||||
${filterDefined(item.attributes.translations)
|
||||
.map((translation) =>
|
||||
prettyInlineTitle(
|
||||
translation.pre_title,
|
||||
translation.title,
|
||||
translation.subtitle
|
||||
)
|
||||
prettyInlineTitle(translation.pre_title, translation.title, translation.subtitle)
|
||||
)
|
||||
.join(" ")}`
|
||||
}
|
||||
|
|
|
@ -2,18 +2,12 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
|||
import { useCallback, useMemo } from "react";
|
||||
import naturalCompare from "string-natural-compare";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { filterHasAttributes } from "helpers/others";
|
||||
import { GetContentsFolderQuery } from "graphql/generated";
|
||||
import {
|
||||
getDefaultPreferredLanguages,
|
||||
staticSmartLanguage,
|
||||
} from "helpers/locales";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
|
@ -37,17 +31,11 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
|
||||
interface Props extends AppLayoutRequired {
|
||||
folder: NonNullable<
|
||||
NonNullable<
|
||||
GetContentsFolderQuery["contentsFolders"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<GetContentsFolderQuery["contentsFolders"]>["data"][number]["attributes"]
|
||||
>;
|
||||
}
|
||||
|
||||
const ContentsFolder = ({
|
||||
openGraph,
|
||||
folder,
|
||||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const ContentsFolder = ({ openGraph, folder, ...otherProps }: Props): JSX.Element => {
|
||||
const { langui } = useAppLayout();
|
||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||
|
||||
|
@ -62,15 +50,10 @@ const ContentsFolder = ({
|
|||
|
||||
<HorizontalLine />
|
||||
|
||||
<Button
|
||||
href="/contents/all"
|
||||
/* TODO: Langui */
|
||||
text={"Switch to grid view"}
|
||||
icon={Icon.Apps}
|
||||
/>
|
||||
<Button href="/contents/all" text={langui.switch_to_grid_view} icon={Icon.Apps} />
|
||||
</SubPanel>
|
||||
),
|
||||
[langui.contents, langui.contents_description]
|
||||
[langui.contents, langui.contents_description, langui.switch_to_grid_view]
|
||||
);
|
||||
|
||||
const contentPanel = useMemo(
|
||||
|
@ -84,10 +67,9 @@ const ContentsFolder = ({
|
|||
) : (
|
||||
<TranslatedButton
|
||||
href={`/contents/folder/${folder.parent_folder.data.attributes.slug}`}
|
||||
translations={filterHasAttributes(
|
||||
folder.parent_folder.data.attributes.titles,
|
||||
["language.data.attributes.code"] as const
|
||||
).map((title) => ({
|
||||
translations={filterHasAttributes(folder.parent_folder.data.attributes.titles, [
|
||||
"language.data.attributes.code",
|
||||
] as const).map((title) => ({
|
||||
language: title.language.data.attributes.code,
|
||||
text: title.title,
|
||||
}))}
|
||||
|
@ -119,10 +101,7 @@ const ContentsFolder = ({
|
|||
</div>
|
||||
|
||||
<SmartList
|
||||
items={filterHasAttributes(folder.subfolders?.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const)}
|
||||
items={filterHasAttributes(folder.subfolders?.data, ["id", "attributes"] as const)}
|
||||
getItemId={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TranslatedPreviewFolder
|
||||
|
@ -149,10 +128,7 @@ const ContentsFolder = ({
|
|||
/>
|
||||
|
||||
<SmartList
|
||||
items={filterHasAttributes(folder.contents?.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const)}
|
||||
items={filterHasAttributes(folder.contents?.data, ["id", "attributes"] as const)}
|
||||
getItemId={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<TranslatedPreviewCard
|
||||
|
@ -193,8 +169,9 @@ const ContentsFolder = ({
|
|||
groupingFunction={() => [langui.contents ?? "Contents"]}
|
||||
/>
|
||||
|
||||
{folder.contents?.data.length === 0 &&
|
||||
folder.subfolders?.data.length === 0 && <NoContentNorFolderMessage />}
|
||||
{folder.contents?.data.length === 0 && folder.subfolders?.data.length === 0 && (
|
||||
<NoContentNorFolderMessage />
|
||||
)}
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
|
@ -240,15 +217,15 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
|
||||
const subFolders = {
|
||||
// eslint-disable-next-line id-denylist
|
||||
data: filterHasAttributes(folder.subfolders?.data, [
|
||||
"attributes.slug",
|
||||
]).sort((a, b) => naturalCompare(a.attributes.slug, b.attributes.slug)),
|
||||
data: filterHasAttributes(folder.subfolders?.data, ["attributes.slug"]).sort((a, b) =>
|
||||
naturalCompare(a.attributes.slug, b.attributes.slug)
|
||||
),
|
||||
};
|
||||
|
||||
const contents = {
|
||||
// eslint-disable-next-line id-denylist
|
||||
data: filterHasAttributes(folder.contents?.data, ["attributes.slug"]).sort(
|
||||
(a, b) => naturalCompare(a.attributes.slug, b.attributes.slug)
|
||||
data: filterHasAttributes(folder.contents?.data, ["attributes.slug"]).sort((a, b) =>
|
||||
naturalCompare(a.attributes.slug, b.attributes.slug)
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -263,10 +240,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const selectedTranslation = staticSmartLanguage({
|
||||
items: folder.titles,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
preferredLanguages: getDefaultPreferredLanguages(
|
||||
context.locale,
|
||||
context.locales
|
||||
),
|
||||
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||
});
|
||||
if (selectedTranslation) {
|
||||
return selectedTranslation.title;
|
||||
|
@ -290,9 +264,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const contents = await sdk.getContentsFoldersSlugs();
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
filterHasAttributes(contents.contentsFolders?.data, [
|
||||
"attributes",
|
||||
] as const).map((item) => {
|
||||
filterHasAttributes(contents.contentsFolders?.data, ["attributes"] as const).map((item) => {
|
||||
context.locales?.map((local) => {
|
||||
paths.push({
|
||||
params: { slug: item.attributes.slug },
|
||||
|
@ -320,13 +292,8 @@ const PreviewFolder = ({ href, title }: PreviewFolderProps): JSX.Element => (
|
|||
<Link
|
||||
href={href}
|
||||
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]"
|
||||
>
|
||||
{title && (
|
||||
<p className="text-center font-headers text-lg font-bold leading-none">
|
||||
{title}
|
||||
</p>
|
||||
)}
|
||||
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>}
|
||||
</Link>
|
||||
);
|
||||
|
||||
|
@ -339,17 +306,9 @@ const TranslatedPreviewFolder = ({
|
|||
}: TranslatedProps<PreviewFolderProps, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
return (
|
||||
<PreviewFolder
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <PreviewFolder title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
@ -360,8 +319,7 @@ const NoContentNorFolderMessage = () => {
|
|||
<div className="grid place-content-center">
|
||||
<div
|
||||
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
|
||||
border-dark p-8 text-dark opacity-40"
|
||||
>
|
||||
border-dark p-8 text-dark opacity-40">
|
||||
<p className="max-w-xs text-2xl">{langui.empty_folder_message}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import ContentsFolder, {
|
||||
getStaticProps as folderGetStaticProps,
|
||||
} from "./folder/[slug]";
|
||||
import ContentsFolder, { getStaticProps as folderGetStaticProps } from "./folder/[slug]";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -3,10 +3,7 @@ import { useMemo } from "react";
|
|||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { DevGetContentsQuery } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
|
@ -14,6 +11,7 @@ import { filterDefined, filterHasAttributes } from "helpers/others";
|
|||
import { Report, Severity } from "types/Report";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sJoin } from "helpers/formatters";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -49,20 +47,9 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
|
|||
<div
|
||||
key={index}
|
||||
className="mb-2 grid 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
|
||||
href={line.backendUrl}
|
||||
className="w-4 text-xs"
|
||||
text="B"
|
||||
alwaysNewTab
|
||||
/>
|
||||
justify-items-start gap-2">
|
||||
<Button 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.name}</p>
|
||||
<Chip text={line.type} />
|
||||
|
@ -121,164 +108,157 @@ const testingContent = (contents: Props["contents"]): Report => {
|
|||
lines: [],
|
||||
};
|
||||
|
||||
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
|
||||
(content) => {
|
||||
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`;
|
||||
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
|
||||
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map((content) => {
|
||||
const backendUrl = sJoin(
|
||||
process.env.NEXT_PUBLIC_URL_CMS,
|
||||
"/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) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Category",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Category.",
|
||||
recommandation: "Select a Category in relation with the Content",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
if (content.attributes.categories?.data.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Category",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Category.",
|
||||
recommandation: "Select a Category in relation with the Content",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
|
||||
if (!content.attributes.type?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Type",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Type.",
|
||||
recommandation: 'If unsure, use the "Other" Type.',
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
if (!content.attributes.type?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Type",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Type.",
|
||||
recommandation: 'If unsure, use the "Other" Type.',
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
|
||||
if (content.attributes.ranged_contents?.data.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Ranged Content",
|
||||
type: "Improvement",
|
||||
severity: Severity.Low,
|
||||
description: "The Content has no Ranged Content.",
|
||||
recommandation:
|
||||
"If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
if (content.attributes.ranged_contents?.data.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Ranged Content",
|
||||
type: "Improvement",
|
||||
severity: Severity.Low,
|
||||
description: "The Content has no Ranged Content.",
|
||||
recommandation:
|
||||
"If this Content is available in one or multiple Library Item(s),\
|
||||
create a Range Content to connect the Content to its Library Item(s).",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
|
||||
if (!content.attributes.thumbnail?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Thumbnail",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Thumbnail.",
|
||||
recommandation: "",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
if (!content.attributes.thumbnail?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Thumbnail",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Thumbnail.",
|
||||
recommandation: "",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
|
||||
if (content.attributes.translations?.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Titles",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Titles.",
|
||||
recommandation: "",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
if (content.attributes.translations?.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [content.attributes.slug],
|
||||
name: "No Titles",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description: "The Content has no Titles.",
|
||||
recommandation: "",
|
||||
backendUrl: backendUrl,
|
||||
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 {
|
||||
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 {
|
||||
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,
|
||||
});
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -3,10 +3,7 @@ import { useMemo } from "react";
|
|||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import {
|
||||
DevGetLibraryItemsQuery,
|
||||
|
@ -16,6 +13,7 @@ import { getReadySdk } from "graphql/sdk";
|
|||
import { Report, Severity } from "types/Report";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
import { sJoin } from "helpers/formatters";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -26,10 +24,7 @@ interface Props extends AppLayoutRequired {
|
|||
libraryItems: DevGetLibraryItemsQuery;
|
||||
}
|
||||
|
||||
const CheckupLibraryItems = ({
|
||||
libraryItems,
|
||||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const CheckupLibraryItems = ({ libraryItems, ...otherProps }: Props): JSX.Element => {
|
||||
const testReport = testingLibraryItem(libraryItems);
|
||||
|
||||
const contentPanel = useMemo(
|
||||
|
@ -54,20 +49,9 @@ const CheckupLibraryItems = ({
|
|||
<div
|
||||
key={index}
|
||||
className="mb-2 grid
|
||||
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
|
||||
href={line.backendUrl}
|
||||
className="w-4 text-xs"
|
||||
text="B"
|
||||
alwaysNewTab
|
||||
/>
|
||||
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 href={line.backendUrl} className="w-4 text-xs" text="B" alwaysNewTab />
|
||||
<p>{line.subitems.join(" -> ")}</p>
|
||||
<p>{line.name}</p>
|
||||
<Chip text={line.type} />
|
||||
|
@ -129,8 +113,16 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
|
||||
libraryItems.libraryItems?.data.map((item) => {
|
||||
if (item.attributes) {
|
||||
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::library-item.library-item/${item.id}`;
|
||||
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/library/${item.attributes.slug}`;
|
||||
const backendUrl = sJoin(
|
||||
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) {
|
||||
report.lines.push({
|
||||
|
@ -145,17 +137,13 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!item.attributes.root_item &&
|
||||
item.attributes.subitem_of?.data.length === 0
|
||||
) {
|
||||
if (!item.attributes.root_item && item.attributes.subitem_of?.data.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [item.attributes.slug],
|
||||
name: "Disconnected Item",
|
||||
type: "Error",
|
||||
severity: Severity.VeryHigh,
|
||||
description:
|
||||
"The Item is neither a Root Item, nor is it a subitem of another item.",
|
||||
description: "The Item is neither a Root Item, nor is it a subitem of another item.",
|
||||
recommandation: "",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
|
@ -207,10 +195,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
if (image.language?.data?.id) {
|
||||
if (image.language.data.id in imagesLanguages) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "Duplicate Language",
|
||||
type: "Error",
|
||||
severity: Severity.High,
|
||||
|
@ -224,10 +209,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
} else {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Language",
|
||||
type: "Error",
|
||||
severity: Severity.VeryHigh,
|
||||
|
@ -240,10 +222,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
|
||||
if (!image.source_language?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Source Language",
|
||||
type: "Error",
|
||||
severity: Severity.VeryHigh,
|
||||
|
@ -254,15 +233,9 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
});
|
||||
}
|
||||
|
||||
if (
|
||||
image.status !==
|
||||
Enum_Componentcollectionscomponentlibraryimages_Status.Done
|
||||
) {
|
||||
if (image.status !== Enum_Componentcollectionscomponentlibraryimages_Status.Done) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "Not Done Status",
|
||||
type: "Improvement",
|
||||
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.scanners?.data.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Scanners",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description:
|
||||
"The Item is a Scan but doesn't credit any Scanners.",
|
||||
description: "The Item is a Scan but doesn't credit any Scanners.",
|
||||
recommandation: "Add the appropriate Scanners.",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
|
@ -292,36 +261,26 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (image.cleaners?.data.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Cleaners",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description:
|
||||
"The Item is a Scan but doesn't credit any Cleaners.",
|
||||
description: "The Item is a Scan but doesn't credit any Cleaners.",
|
||||
recommandation: "Add the appropriate Cleaners.",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
}
|
||||
if (
|
||||
image.typesetters?.data &&
|
||||
image.typesetters.data.length > 0
|
||||
) {
|
||||
if (image.typesetters?.data && image.typesetters.data.length > 0) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "Credited Typesetters",
|
||||
type: "Error",
|
||||
severity: Severity.High,
|
||||
description:
|
||||
"The Item is a Scan but credits one or more Typesetters.",
|
||||
description: "The Item is a Scan but credits one or more Typesetters.",
|
||||
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,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
|
@ -329,15 +288,11 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
} else {
|
||||
if (image.typesetters?.data.length === 0) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Typesetters",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
description:
|
||||
"The Item is a Scanlation but doesn't credit any Typesetters.",
|
||||
description: "The Item is a Scanlation but doesn't credit any Typesetters.",
|
||||
recommandation: "Add the appropriate Typesetters.",
|
||||
backendUrl: backendUrl,
|
||||
frontendUrl: frontendUrl,
|
||||
|
@ -345,17 +300,14 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (image.scanners?.data && image.scanners.data.length > 0) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "Credited Scanners",
|
||||
type: "Error",
|
||||
severity: Severity.High,
|
||||
description:
|
||||
"The Item is a Scanlation but credits one or more Scanners.",
|
||||
description: "The Item is a Scanlation but credits one or more Scanners.",
|
||||
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,
|
||||
frontendUrl: frontendUrl,
|
||||
});
|
||||
|
@ -365,11 +317,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
if (image.cover) {
|
||||
if (!image.cover.front?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Cover",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||
name: "No Front",
|
||||
type: "Missing",
|
||||
severity: Severity.VeryHigh,
|
||||
|
@ -381,11 +329,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.cover.spine?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Cover",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||
name: "No spine",
|
||||
type: "Missing",
|
||||
severity: Severity.Low,
|
||||
|
@ -397,11 +341,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.cover.back?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Cover",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||
name: "No Back",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
|
@ -413,11 +353,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.cover.full?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Cover",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Cover"],
|
||||
name: "No Full",
|
||||
type: "Missing",
|
||||
severity: Severity.Low,
|
||||
|
@ -429,10 +365,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
} else {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Cover",
|
||||
type: "Missing",
|
||||
severity: Severity.Medium,
|
||||
|
@ -542,10 +475,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
} else {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Dust Jacket",
|
||||
type: "Missing",
|
||||
severity: Severity.VeryLow,
|
||||
|
@ -559,11 +489,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
if (image.obi_belt) {
|
||||
if (!image.obi_belt.front?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Obi Belt",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||
name: "No Front",
|
||||
type: "Missing",
|
||||
severity: Severity.VeryHigh,
|
||||
|
@ -575,11 +501,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.obi_belt.spine?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Obi Belt",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||
name: "No spine",
|
||||
type: "Missing",
|
||||
severity: Severity.Low,
|
||||
|
@ -591,11 +513,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.obi_belt.back?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Obi Belt",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||
name: "No Back",
|
||||
type: "Missing",
|
||||
severity: Severity.High,
|
||||
|
@ -607,11 +525,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.obi_belt.full?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Obi Belt",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||
name: "No Full",
|
||||
type: "Missing",
|
||||
severity: Severity.Low,
|
||||
|
@ -623,11 +537,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.obi_belt.flap_front?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Obi Belt",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||
name: "No Flap Front",
|
||||
type: "Missing",
|
||||
severity: Severity.Medium,
|
||||
|
@ -639,11 +549,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
if (!image.obi_belt.flap_back?.data?.id) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
"Obi Belt",
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`, "Obi Belt"],
|
||||
name: "No Flap Back",
|
||||
type: "Missing",
|
||||
severity: Severity.Medium,
|
||||
|
@ -655,10 +561,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
} else {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Images ${imageIndex.toString()}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Images ${imageIndex.toString()}`],
|
||||
name: "No Obi Belt",
|
||||
type: "Missing",
|
||||
severity: Severity.VeryLow,
|
||||
|
@ -672,20 +575,14 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
});
|
||||
}
|
||||
|
||||
if (
|
||||
item.attributes.descriptions &&
|
||||
item.attributes.descriptions.length > 0
|
||||
) {
|
||||
if (item.attributes.descriptions && item.attributes.descriptions.length > 0) {
|
||||
const descriptionLanguages: string[] = [];
|
||||
|
||||
item.attributes.descriptions.map((description, descriptionIndex) => {
|
||||
if (description && item.attributes) {
|
||||
if (description.description.length < 10) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Description ${descriptionIndex}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
|
||||
name: "No Text",
|
||||
type: "Missing",
|
||||
severity: Severity.VeryHigh,
|
||||
|
@ -699,10 +596,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
if (description.language?.data?.id) {
|
||||
if (description.language.data.id in descriptionLanguages) {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Description ${descriptionIndex}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
|
||||
name: "Duplicate Language",
|
||||
type: "Error",
|
||||
severity: Severity.High,
|
||||
|
@ -716,10 +610,7 @@ const testingLibraryItem = (libraryItems: Props["libraryItems"]): Report => {
|
|||
}
|
||||
} else {
|
||||
report.lines.push({
|
||||
subitems: [
|
||||
item.attributes.slug,
|
||||
`Description ${descriptionIndex}`,
|
||||
],
|
||||
subitems: [item.attributes.slug, `Description ${descriptionIndex}`],
|
||||
name: "No Language",
|
||||
type: "Error",
|
||||
severity: Severity.VeryHigh,
|
||||
|
|
|
@ -4,10 +4,7 @@ import TurndownService from "turndown";
|
|||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { Popup } from "components/Popup";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { Icon } from "components/Ico";
|
||||
|
@ -59,19 +56,14 @@ const Editor = (props: Props): JSX.Element => {
|
|||
);
|
||||
|
||||
const wrap = useCallback(
|
||||
(
|
||||
wrapper: string,
|
||||
properties?: Record<string, string>,
|
||||
addInnerNewLines?: boolean
|
||||
) => {
|
||||
(wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
|
||||
transformationWrapper((value, selectionStart, selectionEnd) => {
|
||||
let prepend = wrapper;
|
||||
let append = wrapper;
|
||||
|
||||
if (properties) {
|
||||
prepend = `<${wrapper}${Object.entries(properties).map(
|
||||
([propertyName, propertyValue]) =>
|
||||
` ${propertyName}="${propertyValue}"`
|
||||
([propertyName, propertyValue]) => ` ${propertyName}="${propertyValue}"`
|
||||
)}>`;
|
||||
append = `</${wrapper}>`;
|
||||
}
|
||||
|
@ -107,17 +99,12 @@ const Editor = (props: Props): JSX.Element => {
|
|||
);
|
||||
|
||||
const toggleWrap = useCallback(
|
||||
(
|
||||
wrapper: string,
|
||||
properties?: Record<string, string>,
|
||||
addInnerNewLines?: boolean
|
||||
) => {
|
||||
(wrapper: string, properties?: Record<string, string>, addInnerNewLines?: boolean) => {
|
||||
if (textAreaRef.current) {
|
||||
const { value, selectionStart, selectionEnd } = textAreaRef.current;
|
||||
|
||||
if (
|
||||
value.slice(selectionStart - wrapper.length, selectionStart) ===
|
||||
wrapper &&
|
||||
value.slice(selectionStart - wrapper.length, selectionStart) === wrapper &&
|
||||
value.slice(selectionEnd, selectionEnd + wrapper.length) === wrapper
|
||||
) {
|
||||
unwrap(wrapper);
|
||||
|
@ -132,8 +119,7 @@ const Editor = (props: Props): JSX.Element => {
|
|||
const preline = useCallback(
|
||||
(prepend: string) => {
|
||||
transformationWrapper((value, selectionStart) => {
|
||||
const lastNewLine =
|
||||
value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
||||
const lastNewLine = value.slice(0, selectionStart).lastIndexOf("\n") + 1;
|
||||
|
||||
let newValue = "";
|
||||
newValue += value.slice(0, lastNewLine);
|
||||
|
@ -173,10 +159,7 @@ const Editor = (props: Props): JSX.Element => {
|
|||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<Popup
|
||||
onClose={() => setConverterOpened(false)}
|
||||
state={converterOpened}
|
||||
>
|
||||
<Popup onClose={() => setConverterOpened(false)} state={converterOpened}>
|
||||
<div className="text-center">
|
||||
<h2 className="mt-4">Convert HTML to markdown</h2>
|
||||
<p>
|
||||
|
@ -184,8 +167,7 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<br />
|
||||
The text will immediatly be converted to valid Markdown.
|
||||
<br />
|
||||
You can then copy the converted text and paste it anywhere you
|
||||
want in the editor
|
||||
You can then copy the converted text and paste it anywhere you want in the editor
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
|
@ -226,22 +208,15 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<Button onClick={() => preline("##### ")} text={"H5"} />
|
||||
<Button onClick={() => preline("###### ")} text={"H6"} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button icon={Icon.Title} />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={<h3 className="text-lg">Toggle Bold</h3>}
|
||||
>
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Bold</h3>}>
|
||||
<Button onClick={() => toggleWrap("**")} icon={Icon.FormatBold} />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={<h3 className="text-lg">Toggle Italic</h3>}
|
||||
>
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Toggle Italic</h3>}>
|
||||
<Button onClick={() => toggleWrap("_")} icon={Icon.FormatItalic} />
|
||||
</ToolTip>
|
||||
|
||||
|
@ -251,12 +226,11 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<>
|
||||
<h3 className="text-lg">Toggle Inline Code</h3>
|
||||
<p>
|
||||
Makes the text monospace (like text from a computer terminal).
|
||||
Usually used for stylistic purposes in transcripts.
|
||||
Makes the text monospace (like text from a computer terminal). Usually used for
|
||||
stylistic purposes in transcripts.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button onClick={() => toggleWrap("`")} icon={Icon.Code} />
|
||||
</ToolTip>
|
||||
|
||||
|
@ -267,8 +241,7 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<h3 className="text-lg">Insert footnote</h3>
|
||||
<p>When inserted “x”</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insert("[^x]");
|
||||
|
@ -284,8 +257,8 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<>
|
||||
<h3 className="text-lg">Transcripts</h3>
|
||||
<p>
|
||||
Use this to create dialogues and transcripts. Start by adding
|
||||
a container, then add transcript speech line within.
|
||||
Use this to create dialogues and transcripts. Start by adding a container, then
|
||||
add transcript speech line within.
|
||||
</p>
|
||||
<div className="grid gap-2">
|
||||
<ToolTip
|
||||
|
@ -294,12 +267,8 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<>
|
||||
<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
|
||||
placement="right"
|
||||
|
@ -307,13 +276,11 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<>
|
||||
<h3 className="text-lg">Transcript speech line</h3>
|
||||
<p>
|
||||
Use to add a dialogue/transcript line. Change the{" "}
|
||||
<kbd>name</kbd> property to chang the name of the
|
||||
speaker
|
||||
Use to add a dialogue/transcript line. Change the <kbd>name</kbd> property
|
||||
to chang the name of the speaker
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button
|
||||
onClick={() => wrap("Line", { name: "speaker" })}
|
||||
icon={Icon.RecordVoiceOver}
|
||||
|
@ -321,24 +288,14 @@ const Editor = (props: Props): JSX.Element => {
|
|||
</ToolTip>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button icon={Icon.RecordVoiceOver} />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={<h3 className="text-lg">Inset box</h3>}
|
||||
>
|
||||
<Button
|
||||
onClick={() => wrap("InsetBox", {}, true)}
|
||||
icon={Icon.CheckBoxOutlineBlank}
|
||||
/>
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Inset box</h3>}>
|
||||
<Button onClick={() => wrap("InsetBox", {}, true)} icon={Icon.CheckBoxOutlineBlank} />
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={<h3 className="text-lg">Scene break</h3>}
|
||||
>
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Scene break</h3>}>
|
||||
<Button onClick={() => insert("\n* * *\n")} icon={Icon.MoreHoriz} />
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
|
@ -350,12 +307,9 @@ const Editor = (props: Props): JSX.Element => {
|
|||
content={
|
||||
<>
|
||||
<h3 className="text-lg">External Link</h3>
|
||||
<p className="text-xs">
|
||||
Provides a link to another webpage / website
|
||||
</p>
|
||||
<p className="text-xs">Provides a link to another webpage / website</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button
|
||||
onClick={() => insert("[Link name](https://domain.com)")}
|
||||
icon={Icon.Link}
|
||||
|
@ -369,12 +323,10 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<>
|
||||
<h3 className="text-lg">Intralink</h3>
|
||||
<p className="text-xs">
|
||||
Interlinks are used to add links to a header within the
|
||||
same document
|
||||
Interlinks are used to add links to a header within the same document
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button
|
||||
onClick={() => wrap("IntraLink", {})}
|
||||
icon={Icon.Link}
|
||||
|
@ -387,12 +339,11 @@ const Editor = (props: Props): JSX.Element => {
|
|||
<>
|
||||
<h3 className="text-lg">Intralink (with target)</h3>{" "}
|
||||
<p className="text-xs">
|
||||
Use this one if you want the intralink text to be
|
||||
different from the target header’s name.
|
||||
Use this one if you want the intralink text to be different from the target
|
||||
header’s name.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button
|
||||
onClick={() => wrap("IntraLink", { target: "target" })}
|
||||
icon={Icon.Link}
|
||||
|
@ -400,24 +351,17 @@ const Editor = (props: Props): JSX.Element => {
|
|||
/>
|
||||
</ToolTip>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button icon={Icon.Link} />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={
|
||||
<h3 className="text-lg">Player’s name placeholder</h3>
|
||||
}
|
||||
>
|
||||
content={<h3 className="text-lg">Player’s name placeholder</h3>}>
|
||||
<Button onClick={() => insert("@player")} icon={Icon.Person} />
|
||||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
placement="bottom"
|
||||
content={<h3 className="text-lg">Open HTML Converter</h3>}
|
||||
>
|
||||
<ToolTip placement="bottom" content={<h3 className="text-lg">Open HTML Converter</h3>}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setConverterOpened(true);
|
||||
|
@ -455,16 +399,7 @@ const Editor = (props: Props): JSX.Element => {
|
|||
</div>
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
appendDoc,
|
||||
converterOpened,
|
||||
handleInput,
|
||||
insert,
|
||||
markdown,
|
||||
preline,
|
||||
toggleWrap,
|
||||
wrap,
|
||||
]
|
||||
[appendDoc, converterOpened, handleInput, insert, markdown, preline, toggleWrap, wrap]
|
||||
);
|
||||
|
||||
return <AppLayout contentPanel={contentPanel} {...props} />;
|
||||
|
|
|
@ -3,10 +3,7 @@ import { useCallback, useMemo, useRef, useState } from "react";
|
|||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
|
@ -30,10 +27,7 @@ const replaceSelection = (
|
|||
selectionStart: number,
|
||||
selectionEnd: number,
|
||||
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 => {
|
||||
for (let index = 0; index < swaps.length; index++) {
|
||||
|
@ -60,10 +54,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
|
||||
const updateLineIndex = useCallback(() => {
|
||||
if (textAreaRef.current) {
|
||||
const subText = textAreaRef.current.value.substring(
|
||||
0,
|
||||
textAreaRef.current.selectionStart
|
||||
);
|
||||
const subText = textAreaRef.current.value.substring(0, textAreaRef.current.selectionStart);
|
||||
setLineIndex(subText.split("\n").length - 1);
|
||||
}
|
||||
}, []);
|
||||
|
@ -202,10 +193,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
textAreaRef.current.selectionStart,
|
||||
textAreaRef.current.selectionEnd
|
||||
);
|
||||
const selection = textAreaRef.current.value.substring(
|
||||
selectionStart,
|
||||
selectionEnd
|
||||
);
|
||||
const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
|
||||
if (selection.length === 1) {
|
||||
let newSelection = selection;
|
||||
|
||||
|
@ -301,10 +289,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
textAreaRef.current.selectionStart,
|
||||
textAreaRef.current.selectionEnd
|
||||
);
|
||||
const selection = textAreaRef.current.value.substring(
|
||||
selectionStart,
|
||||
selectionEnd
|
||||
);
|
||||
const selection = textAreaRef.current.value.substring(selectionStart, selectionEnd);
|
||||
if (selection.length === 1) {
|
||||
let newSelection = selection;
|
||||
|
||||
|
@ -376,10 +361,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel
|
||||
width={ContentPanelWidthSizes.Full}
|
||||
className="overflow-hidden !pr-0 !pt-4"
|
||||
>
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full} className="overflow-hidden !pr-0 !pt-4">
|
||||
<div className="grid grid-flow-col grid-cols-[1fr_5rem]">
|
||||
<textarea
|
||||
ref={textAreaRef}
|
||||
|
@ -387,18 +369,14 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
onClick={updateLineIndex}
|
||||
onKeyUp={updateLineIndex}
|
||||
title="Input textarea"
|
||||
className="whitespace-pre"
|
||||
></textarea>
|
||||
className="whitespace-pre"></textarea>
|
||||
|
||||
<p
|
||||
className="h-[80vh] whitespace-nowrap font-[initial] font-bold
|
||||
[writing-mode:vertical-rl] [transform-origin:top_right]"
|
||||
style={{
|
||||
transform: `scale(${fontSize}) translateX(${
|
||||
fontSize * xOffset
|
||||
}px)`,
|
||||
}}
|
||||
>
|
||||
transform: `scale(${fontSize}) translateX(${fontSize * xOffset}px)`,
|
||||
}}>
|
||||
{text.split("\n")[lineIndex]}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -412,10 +390,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
min="0"
|
||||
max="100"
|
||||
value={xOffset * 10}
|
||||
onChange={(event) =>
|
||||
setXOffset(parseInt(event.target.value, 10) / 10)
|
||||
}
|
||||
></input>
|
||||
onChange={(event) => setXOffset(parseInt(event.target.value, 10) / 10)}></input>
|
||||
</div>
|
||||
|
||||
<div className="grid place-items-center">
|
||||
|
@ -428,8 +403,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
value={fontSize * SIZE_MULTIPLIER}
|
||||
onChange={(event) =>
|
||||
setFontSize(parseInt(event.target.value, 10) / SIZE_MULTIPLIER)
|
||||
}
|
||||
></input>
|
||||
}></input>
|
||||
</div>
|
||||
<ToolTip content="Automatically convert Western punctuations to Japanese ones.">
|
||||
<Button text=". ⟹ 。" onClick={convertPunctuation} />
|
||||
|
@ -526,8 +500,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
]}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button text={"Quotations"} />
|
||||
</ToolTip>
|
||||
<ToolTip
|
||||
|
@ -543,8 +516,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
<Button text={"〇"} onClick={() => insert("〇")} />
|
||||
<Button text={'" "'} onClick={() => insert(" ")} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
}>
|
||||
<Button text="Insert" />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
@ -565,13 +537,7 @@ const Transcript = (props: Props): JSX.Element => {
|
|||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
contentPanelScroolbar={false}
|
||||
/>
|
||||
);
|
||||
return <AppLayout contentPanel={contentPanel} {...props} contentPanelScroolbar={false} />;
|
||||
};
|
||||
export default Transcript;
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { PostPage } from "components/PostPage";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
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]"
|
||||
/>
|
||||
<h1 className="mb-0 text-5xl">Accord’s Library</h1>
|
||||
<h2 className="-mt-5 text-xl">
|
||||
Discover • Analyze • Translate • Archive
|
||||
</h2>
|
||||
<h2 className="-mt-5 text-xl">Discover • Analyze • Translate • Archive</h2>
|
||||
</div>
|
||||
}
|
||||
displayTitle={false}
|
||||
|
|
|
@ -11,10 +11,7 @@ import { InsetBox } from "components/InsetBox";
|
|||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
@ -64,13 +61,7 @@ import { Ids } from "types/ids";
|
|||
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
|
||||
*/
|
||||
|
||||
const intersectionIds = [
|
||||
"summary",
|
||||
"gallery",
|
||||
"details",
|
||||
"subitems",
|
||||
"contents",
|
||||
];
|
||||
const intersectionIds = ["summary", "gallery", "details", "subitems", "contents"];
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -78,14 +69,8 @@ const intersectionIds = [
|
|||
*/
|
||||
|
||||
interface Props extends AppLayoutRequired {
|
||||
item: NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>;
|
||||
itemId: NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
>["data"][number]["id"];
|
||||
item: NonNullable<NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["attributes"]>;
|
||||
itemId: NonNullable<GetLibraryItemQuery["libraryItems"]>["data"][number]["id"];
|
||||
}
|
||||
|
||||
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||
|
@ -97,8 +82,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
const hoverable = useDeviceSupportsHover();
|
||||
const router = useRouter();
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } =
|
||||
useBoolean(false);
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
|
||||
|
||||
useScrollTopOnChange(Ids.ContentPanel, [item]);
|
||||
const currentIntersection = useIntersectionList(intersectionIds);
|
||||
|
@ -113,8 +97,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
const displayOpenScans = useMemo(
|
||||
() =>
|
||||
item.contents?.data.some(
|
||||
(content) =>
|
||||
content.attributes?.scan_set && content.attributes.scan_set.length > 0
|
||||
(content) => content.attributes?.scan_set && content.attributes.scan_set.length > 0
|
||||
),
|
||||
[item.contents?.data]
|
||||
);
|
||||
|
@ -122,11 +105,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library}
|
||||
displayOnlyOn="3ColumnsLayout"
|
||||
/>
|
||||
<ReturnButton href="/library/" title={langui.library} displayOnlyOn="3ColumnsLayout" />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -174,14 +153,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
</div>
|
||||
</SubPanel>
|
||||
),
|
||||
[
|
||||
currentIntersection,
|
||||
isVariantSet,
|
||||
item.contents,
|
||||
item.gallery,
|
||||
item.subitems,
|
||||
langui,
|
||||
]
|
||||
[currentIntersection, isVariantSet, item.contents, item.gallery, item.subitems, langui]
|
||||
);
|
||||
|
||||
const contentPanel = useMemo(
|
||||
|
@ -203,15 +175,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
)}
|
||||
onClick={() => {
|
||||
if (item.thumbnail?.data?.attributes) {
|
||||
openLightBox([
|
||||
getAssetURL(
|
||||
item.thumbnail.data.attributes.url,
|
||||
ImageQuality.Large
|
||||
),
|
||||
]);
|
||||
openLightBox([getAssetURL(item.thumbnail.data.attributes.url, ImageQuality.Large)]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{item.thumbnail?.data?.attributes ? (
|
||||
<Img
|
||||
src={item.thumbnail.data.attributes}
|
||||
|
@ -245,39 +211,28 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{!isUntangibleGroupItem(item.metadata?.[0]) &&
|
||||
isDefinedAndNotEmpty(itemId) && (
|
||||
<PreviewCardCTAs id={itemId} expand />
|
||||
)}
|
||||
{!isUntangibleGroupItem(item.metadata?.[0]) && isDefinedAndNotEmpty(itemId) && (
|
||||
<PreviewCardCTAs id={itemId} expand />
|
||||
)}
|
||||
|
||||
{item.descriptions?.[0] && (
|
||||
<p className="text-justify">
|
||||
{item.descriptions[0].description}
|
||||
</p>
|
||||
<p className="text-justify">{item.descriptions[0].description}</p>
|
||||
)}
|
||||
{!(
|
||||
item.metadata &&
|
||||
item.metadata[0]?.__typename === "ComponentMetadataGroup" &&
|
||||
(item.metadata[0].subtype?.data?.attributes?.slug ===
|
||||
"variant-set" ||
|
||||
item.metadata[0].subtype?.data?.attributes?.slug ===
|
||||
"relation-set")
|
||||
(item.metadata[0].subtype?.data?.attributes?.slug === "variant-set" ||
|
||||
item.metadata[0].subtype?.data?.attributes?.slug === "relation-set")
|
||||
) && (
|
||||
<>
|
||||
{item.urls?.length ? (
|
||||
<div className="flex flex-row place-items-center gap-3">
|
||||
<p>{langui.available_at}</p>
|
||||
{filterHasAttributes(item.urls, ["url"] as const).map(
|
||||
(url, index) => (
|
||||
<Fragment key={index}>
|
||||
<Button
|
||||
href={url.url}
|
||||
text={prettyURL(url.url)}
|
||||
alwaysNewTab
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
{filterHasAttributes(item.urls, ["url"] as const).map((url, index) => (
|
||||
<Fragment key={index}>
|
||||
<Button href={url.url} text={prettyURL(url.url)} alwaysNewTab />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p>{langui.item_not_available}</p>
|
||||
|
@ -288,41 +243,34 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
</InsetBox>
|
||||
|
||||
{item.gallery && item.gallery.data.length > 0 && (
|
||||
<div
|
||||
id={intersectionIds[1]}
|
||||
className="grid w-full place-items-center gap-8"
|
||||
>
|
||||
<div id={intersectionIds[1]} className="grid w-full place-items-center gap-8">
|
||||
<h2 className="text-2xl">{langui.gallery}</h2>
|
||||
<div
|
||||
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
|
||||
gap-8"
|
||||
>
|
||||
{filterHasAttributes(item.gallery.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((galleryItem, index) => (
|
||||
<Fragment key={galleryItem.id}>
|
||||
<div
|
||||
className="relative aspect-square cursor-pointer
|
||||
gap-8">
|
||||
{filterHasAttributes(item.gallery.data, ["id", "attributes"] as const).map(
|
||||
(galleryItem, index) => (
|
||||
<Fragment key={galleryItem.id}>
|
||||
<div
|
||||
className="relative aspect-square cursor-pointer
|
||||
transition-transform hover:scale-[1.02]"
|
||||
onClick={() => {
|
||||
const images: string[] = filterHasAttributes(
|
||||
item.gallery?.data,
|
||||
["attributes"] as const
|
||||
).map((image) =>
|
||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
);
|
||||
openLightBox(images, index);
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
className="h-full w-full rounded-lg
|
||||
onClick={() => {
|
||||
const images: string[] = filterHasAttributes(item.gallery?.data, [
|
||||
"attributes",
|
||||
] as const).map((image) =>
|
||||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
);
|
||||
openLightBox(images, index);
|
||||
}}>
|
||||
<Img
|
||||
className="h-full w-full rounded-lg
|
||||
bg-light object-cover drop-shadow-shade-md"
|
||||
src={galleryItem.attributes}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
src={galleryItem.attributes}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -333,12 +281,8 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
<div
|
||||
className={cJoin(
|
||||
"grid place-items-center gap-y-8",
|
||||
cIf(
|
||||
!isContentPanelNoMoreThan3xl,
|
||||
"grid-flow-col place-content-between"
|
||||
)
|
||||
)}
|
||||
>
|
||||
cIf(!isContentPanelNoMoreThan3xl, "grid-flow-col place-content-between")
|
||||
)}>
|
||||
{item.metadata?.[0] && (
|
||||
<div className="grid place-content-start place-items-center">
|
||||
<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
|
||||
)}
|
||||
</p>
|
||||
{item.price.currency?.data?.attributes?.code !==
|
||||
currency && (
|
||||
{item.price.currency?.data?.attributes?.code !== currency && (
|
||||
<p>
|
||||
{prettyPrice(item.price, currencies, currency)} <br />(
|
||||
{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">
|
||||
<h3 className="text-xl">{langui.categories}</h3>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(item.categories.data, [
|
||||
"attributes",
|
||||
] as const).map((category) => (
|
||||
<Chip key={category.id} text={category.attributes.name} />
|
||||
))}
|
||||
{filterHasAttributes(item.categories.data, ["attributes"] as const).map(
|
||||
(category) => (
|
||||
<Chip key={category.id} text={category.attributes.name} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -396,8 +339,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
className={cJoin(
|
||||
"grid gap-4",
|
||||
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<h3 className="text-xl">{langui.size}</h3>
|
||||
<div
|
||||
className={cJoin(
|
||||
|
@ -407,8 +349,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"grid-flow-row place-content-center gap-8",
|
||||
"grid-flow-col place-content-between"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<div
|
||||
className={cJoin(
|
||||
"grid gap-x-4",
|
||||
|
@ -417,8 +358,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"place-items-center",
|
||||
"grid-flow-col place-items-start"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<p className="font-bold">{langui.width}:</p>
|
||||
<div>
|
||||
<p>{item.size.width} mm</p>
|
||||
|
@ -433,8 +373,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"place-items-center",
|
||||
"grid-flow-col place-items-start"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<p className="font-bold">{langui.height}:</p>
|
||||
<div>
|
||||
<p>{item.size.height} mm</p>
|
||||
|
@ -450,8 +389,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
"place-items-center",
|
||||
"grid-flow-col place-items-start"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<p className="font-bold">{langui.thickness}:</p>
|
||||
<div>
|
||||
<p>{item.size.thickness} mm</p>
|
||||
|
@ -469,12 +407,10 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
className={cJoin(
|
||||
"grid gap-4",
|
||||
cIf(isContentPanelNoMoreThan3xl, "place-items-center")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
<h3 className="text-xl">{langui.type_information}</h3>
|
||||
<div className="flex flex-wrap place-content-between gap-x-8">
|
||||
{item.metadata?.[0]?.__typename ===
|
||||
"ComponentMetadataBooks" && (
|
||||
{item.metadata?.[0]?.__typename === "ComponentMetadataBooks" && (
|
||||
<>
|
||||
<div className="flex flex-row place-content-start gap-4">
|
||||
<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">
|
||||
<p className="font-bold">{langui.languages}:</p>
|
||||
{item.metadata[0]?.languages?.data.map((lang) => (
|
||||
<p key={lang.attributes?.code}>
|
||||
{lang.attributes?.name}
|
||||
</p>
|
||||
<p key={lang.attributes?.code}>{lang.attributes?.name}</p>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
|
@ -521,130 +455,109 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
</InsetBox>
|
||||
|
||||
{item.subitems && item.subitems.data.length > 0 && (
|
||||
<div
|
||||
id={intersectionIds[3]}
|
||||
className="grid w-full place-items-center gap-8"
|
||||
>
|
||||
<h2 className="text-2xl">
|
||||
{isVariantSet ? langui.variants : langui.subitems}
|
||||
</h2>
|
||||
<div id={intersectionIds[3]} className="grid w-full place-items-center gap-8">
|
||||
<h2 className="text-2xl">{isVariantSet ? langui.variants : langui.subitems}</h2>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={langui.always_show_info}>
|
||||
<Switch
|
||||
onClick={toggleKeepInfoVisible}
|
||||
value={keepInfoVisible}
|
||||
/>
|
||||
<Switch onClick={toggleKeepInfoVisible} value={keepInfoVisible} />
|
||||
</WithLabel>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="grid w-full grid-cols-[repeat(auto-fill,minmax(13rem,1fr))]
|
||||
items-end gap-8"
|
||||
>
|
||||
{filterHasAttributes(item.subitems.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((subitem) => (
|
||||
<Fragment key={subitem.id}>
|
||||
<PreviewCard
|
||||
href={`/library/${subitem.attributes.slug}`}
|
||||
title={subitem.attributes.title}
|
||||
subtitle={subitem.attributes.subtitle}
|
||||
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="21/29.7"
|
||||
thumbnailRounded={false}
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
topChips={
|
||||
subitem.attributes.metadata &&
|
||||
subitem.attributes.metadata.length > 0 &&
|
||||
subitem.attributes.metadata[0]
|
||||
? [prettyItemSubType(subitem.attributes.metadata[0])]
|
||||
: []
|
||||
}
|
||||
bottomChips={subitem.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)}
|
||||
metadata={{
|
||||
releaseDate: subitem.attributes.release_date,
|
||||
price: subitem.attributes.price,
|
||||
position: "Bottom",
|
||||
}}
|
||||
infoAppend={
|
||||
!isUntangibleGroupItem(
|
||||
subitem.attributes.metadata?.[0]
|
||||
) && <PreviewCardCTAs id={subitem.id} />
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
items-end gap-8">
|
||||
{filterHasAttributes(item.subitems.data, ["id", "attributes"] as const).map(
|
||||
(subitem) => (
|
||||
<Fragment key={subitem.id}>
|
||||
<PreviewCard
|
||||
href={`/library/${subitem.attributes.slug}`}
|
||||
title={subitem.attributes.title}
|
||||
subtitle={subitem.attributes.subtitle}
|
||||
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="21/29.7"
|
||||
thumbnailRounded={false}
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
topChips={
|
||||
subitem.attributes.metadata &&
|
||||
subitem.attributes.metadata.length > 0 &&
|
||||
subitem.attributes.metadata[0]
|
||||
? [prettyItemSubType(subitem.attributes.metadata[0])]
|
||||
: []
|
||||
}
|
||||
bottomChips={subitem.attributes.categories?.data.map(
|
||||
(category) => category.attributes?.short ?? ""
|
||||
)}
|
||||
metadata={{
|
||||
releaseDate: subitem.attributes.release_date,
|
||||
price: subitem.attributes.price,
|
||||
position: "Bottom",
|
||||
}}
|
||||
infoAppend={
|
||||
!isUntangibleGroupItem(subitem.attributes.metadata?.[0]) && (
|
||||
<PreviewCardCTAs id={subitem.id} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.contents && item.contents.data.length > 0 && (
|
||||
<div
|
||||
id={intersectionIds[4]}
|
||||
className="grid w-full place-items-center gap-8"
|
||||
>
|
||||
<div id={intersectionIds[4]} className="grid w-full place-items-center gap-8">
|
||||
<h2 className="-mb-6 text-2xl">{langui.contents}</h2>
|
||||
{displayOpenScans && (
|
||||
<Button
|
||||
href={`/library/${item.slug}/scans`}
|
||||
text={langui.view_scans}
|
||||
/>
|
||||
<Button href={`/library/${item.slug}/scans`} text={langui.view_scans} />
|
||||
)}
|
||||
<div className="max-w- grid w-full gap-4">
|
||||
{filterHasAttributes(item.contents.data, [
|
||||
"attributes",
|
||||
] as const).map((rangedContent) => (
|
||||
<ContentLine
|
||||
content={
|
||||
rangedContent.attributes.content?.data?.attributes
|
||||
? {
|
||||
translations: filterDefined(
|
||||
rangedContent.attributes.content.data.attributes
|
||||
.translations
|
||||
).map((translation) => ({
|
||||
pre_title: translation.pre_title,
|
||||
title: translation.title,
|
||||
subtitle: translation.subtitle,
|
||||
language:
|
||||
translation.language?.data?.attributes?.code,
|
||||
})),
|
||||
categories: filterHasAttributes(
|
||||
rangedContent.attributes.content.data.attributes
|
||||
.categories?.data,
|
||||
["attributes"]
|
||||
).map((category) => category.attributes.short),
|
||||
type:
|
||||
rangedContent.attributes.content.data.attributes
|
||||
.type?.data?.attributes?.titles?.[0]?.title ??
|
||||
prettySlug(
|
||||
rangedContent.attributes.content.data.attributes
|
||||
.type?.data?.attributes?.slug
|
||||
),
|
||||
slug: rangedContent.attributes.content.data
|
||||
.attributes.slug,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
rangeStart={
|
||||
rangedContent.attributes.range[0]?.__typename ===
|
||||
"ComponentRangePageRange"
|
||||
? `${rangedContent.attributes.range[0].starting_page}`
|
||||
: ""
|
||||
}
|
||||
slug={rangedContent.attributes.slug}
|
||||
parentSlug={item.slug}
|
||||
key={rangedContent.id}
|
||||
hasScanSet={
|
||||
isDefined(rangedContent.attributes.scan_set) &&
|
||||
rangedContent.attributes.scan_set.length > 0
|
||||
}
|
||||
condensed={isContentPanelNoMoreThan3xl}
|
||||
/>
|
||||
))}
|
||||
{filterHasAttributes(item.contents.data, ["attributes"] as const).map(
|
||||
(rangedContent) => (
|
||||
<ContentLine
|
||||
content={
|
||||
rangedContent.attributes.content?.data?.attributes
|
||||
? {
|
||||
translations: filterDefined(
|
||||
rangedContent.attributes.content.data.attributes.translations
|
||||
).map((translation) => ({
|
||||
pre_title: translation.pre_title,
|
||||
title: translation.title,
|
||||
subtitle: translation.subtitle,
|
||||
language: translation.language?.data?.attributes?.code,
|
||||
})),
|
||||
categories: filterHasAttributes(
|
||||
rangedContent.attributes.content.data.attributes.categories?.data,
|
||||
["attributes"]
|
||||
).map((category) => category.attributes.short),
|
||||
type:
|
||||
rangedContent.attributes.content.data.attributes.type?.data
|
||||
?.attributes?.titles?.[0]?.title ??
|
||||
prettySlug(
|
||||
rangedContent.attributes.content.data.attributes.type?.data
|
||||
?.attributes?.slug
|
||||
),
|
||||
slug: rangedContent.attributes.content.data.attributes.slug,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
rangeStart={
|
||||
rangedContent.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
||||
? `${rangedContent.attributes.range[0].starting_page}`
|
||||
: ""
|
||||
}
|
||||
slug={rangedContent.attributes.slug}
|
||||
parentSlug={item.slug}
|
||||
key={rangedContent.id}
|
||||
hasScanSet={
|
||||
isDefined(rangedContent.attributes.scan_set) &&
|
||||
rangedContent.attributes.scan_set.length > 0
|
||||
}
|
||||
condensed={isContentPanelNoMoreThan3xl}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -684,13 +597,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
|
||||
};
|
||||
export default LibrarySlug;
|
||||
|
||||
|
@ -703,10 +610,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const item = await sdk.getLibraryItem({
|
||||
slug:
|
||||
context.params && isDefined(context.params.slug)
|
||||
? context.params.slug.toString()
|
||||
: "",
|
||||
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
||||
language_code: context.locale ?? "en",
|
||||
});
|
||||
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,
|
||||
["attributes.short"]
|
||||
).map((category) => category.attributes.short),
|
||||
[langui.type ?? "Type"]: item.libraryItems.data[0].attributes
|
||||
.metadata?.[0]
|
||||
[langui.type ?? "Type"]: item.libraryItems.data[0].attributes.metadata?.[0]
|
||||
? [prettyItemSubType(item.libraryItems.data[0].attributes.metadata[0])]
|
||||
: [],
|
||||
[langui.release_date ?? "Release date"]: [
|
||||
item.libraryItems.data[0].attributes.release_date
|
||||
? prettyDate(
|
||||
item.libraryItems.data[0].attributes.release_date,
|
||||
context.locale
|
||||
)
|
||||
? prettyDate(item.libraryItems.data[0].attributes.release_date, context.locale)
|
||||
: undefined,
|
||||
],
|
||||
}
|
||||
|
@ -739,12 +639,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const props: Props = {
|
||||
item: item.libraryItems.data[0].attributes,
|
||||
itemId: item.libraryItems.data[0].id,
|
||||
openGraph: getOpenGraph(
|
||||
langui,
|
||||
title,
|
||||
description,
|
||||
thumbnail?.data?.attributes
|
||||
),
|
||||
openGraph: getOpenGraph(langui, title, description, thumbnail?.data?.attributes),
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
|
@ -757,9 +652,7 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const libraryItems = await sdk.getLibraryItemsSlugs();
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
filterHasAttributes(libraryItems.libraryItems?.data, [
|
||||
"attributes",
|
||||
] as const).map((item) => {
|
||||
filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"] as const).map((item) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||
);
|
||||
|
@ -808,9 +701,7 @@ const ContentLine = ({
|
|||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: content?.translations ?? [],
|
||||
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 && (
|
||||
<Button
|
||||
href={`/library/${parentSlug}/scans#${slug}`}
|
||||
text={langui.view_scans}
|
||||
/>
|
||||
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
|
||||
)}
|
||||
{isDefined(content) && (
|
||||
<Button
|
||||
href={`/contents/${content.slug}`}
|
||||
text={langui.open_content}
|
||||
/>
|
||||
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
/* TODO: Add to langui */
|
||||
"The content is not available"
|
||||
langui.content_is_not_available
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -870,8 +754,7 @@ const ContentLine = ({
|
|||
className={cJoin(
|
||||
"grid gap-2 rounded-lg px-4",
|
||||
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">
|
||||
<a>
|
||||
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
||||
|
@ -893,35 +776,25 @@ const ContentLine = ({
|
|||
</div>
|
||||
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
|
||||
<p>{rangeStart}</p>
|
||||
{content?.type && (
|
||||
<Chip className="justify-self-end" text={content.type} />
|
||||
)}
|
||||
{content?.type && <Chip className="justify-self-end" text={content.type} />}
|
||||
</div>
|
||||
<div
|
||||
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
||||
isOpened ? "grid" : "hidden"
|
||||
}`}
|
||||
>
|
||||
}`}>
|
||||
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
||||
|
||||
{hasScanSet || isDefined(content) ? (
|
||||
<>
|
||||
{hasScanSet && (
|
||||
<Button
|
||||
href={`/library/${parentSlug}/scans#${slug}`}
|
||||
text={langui.view_scans}
|
||||
/>
|
||||
<Button href={`/library/${parentSlug}/scans#${slug}`} text={langui.view_scans} />
|
||||
)}
|
||||
{isDefined(content) && (
|
||||
<Button
|
||||
href={`/contents/${content.slug}`}
|
||||
text={langui.open_content}
|
||||
/>
|
||||
<Button href={`/contents/${content.slug}`} text={langui.open_content} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
/* TODO: Add to langui */
|
||||
"The content is not available"
|
||||
langui.content_is_not_available
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,21 +2,11 @@ import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
|||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import {
|
||||
GetLibraryItemScansQuery,
|
||||
UploadImageFragment,
|
||||
} from "graphql/generated";
|
||||
import { GetLibraryItemScansQuery, UploadImageFragment } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import {
|
||||
prettyInlineTitle,
|
||||
prettySlug,
|
||||
prettyItemSubType,
|
||||
} from "helpers/formatters";
|
||||
import { prettyInlineTitle, prettySlug, prettyItemSubType } from "helpers/formatters";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
getStatusDescription,
|
||||
|
@ -41,10 +31,7 @@ import { useSmartLanguage } from "hooks/useSmartLanguage";
|
|||
import { TranslatedProps } from "types/TranslatedProps";
|
||||
import { TranslatedNavOption } from "components/PanelComponents/NavOption";
|
||||
import { useIntersectionList } from "hooks/useIntersectionList";
|
||||
import {
|
||||
useIs1ColumnLayout,
|
||||
useIsContentPanelNoMoreThan,
|
||||
} from "hooks/useContainerQuery";
|
||||
import { useIs1ColumnLayout, useIsContentPanelNoMoreThan } from "hooks/useContainerQuery";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { getLangui } from "graphql/fetchLocalData";
|
||||
|
@ -56,13 +43,9 @@ import { getLangui } from "graphql/fetchLocalData";
|
|||
|
||||
interface Props extends AppLayoutRequired {
|
||||
item: NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
>;
|
||||
itemId: NonNullable<
|
||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]
|
||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||
>;
|
||||
itemId: NonNullable<NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["id"]>;
|
||||
}
|
||||
|
||||
const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
||||
|
@ -72,9 +55,9 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
const ids = useMemo(
|
||||
() =>
|
||||
filterHasAttributes(item.contents?.data, [
|
||||
"attributes.slug",
|
||||
] as const).map((content) => content.attributes.slug),
|
||||
filterHasAttributes(item.contents?.data, ["attributes.slug"] as const).map(
|
||||
(content) => content.attributes.slug
|
||||
),
|
||||
[item.contents?.data]
|
||||
);
|
||||
const currentIntersection = useIntersectionList(ids);
|
||||
|
@ -103,18 +86,16 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
? [prettyItemSubType(item.metadata[0])]
|
||||
: []
|
||||
}
|
||||
bottomChips={filterHasAttributes(item.categories?.data, [
|
||||
"attributes",
|
||||
] as const).map((category) => category.attributes.short)}
|
||||
bottomChips={filterHasAttributes(item.categories?.data, ["attributes"] as const).map(
|
||||
(category) => category.attributes.short
|
||||
)}
|
||||
metadata={{
|
||||
releaseDate: item.release_date,
|
||||
price: item.price,
|
||||
position: "Bottom",
|
||||
}}
|
||||
infoAppend={
|
||||
!isUntangibleGroupItem(item.metadata?.[0]) && (
|
||||
<PreviewCardCTAs id={itemId} />
|
||||
)
|
||||
!isUntangibleGroupItem(item.metadata?.[0]) && <PreviewCardCTAs id={itemId} />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
@ -122,54 +103,46 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
<HorizontalLine />
|
||||
|
||||
<p className="mb-4 font-headers text-2xl font-bold">
|
||||
{langui.contents}
|
||||
</p>
|
||||
<p className="mb-4 font-headers text-2xl font-bold">{langui.contents}</p>
|
||||
|
||||
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map(
|
||||
(content, index) => (
|
||||
<>
|
||||
{content.attributes.scan_set &&
|
||||
content.attributes.scan_set.length > 0 && (
|
||||
<TranslatedNavOption
|
||||
key={content.id}
|
||||
url={`#${content.attributes.slug}`}
|
||||
translations={filterHasAttributes(
|
||||
content.attributes.content?.data?.attributes
|
||||
?.translations,
|
||||
["language.data.attributes"] as const
|
||||
).map((translation) => ({
|
||||
language: translation.language.data.attributes.code,
|
||||
title: prettyInlineTitle(
|
||||
translation.pre_title,
|
||||
translation.title,
|
||||
translation.subtitle
|
||||
),
|
||||
subtitle:
|
||||
content.attributes.range[0]?.__typename ===
|
||||
"ComponentRangePageRange"
|
||||
? `${content.attributes.range[0].starting_page}` +
|
||||
`→` +
|
||||
`${content.attributes.range[0].ending_page}`
|
||||
: undefined,
|
||||
}))}
|
||||
fallback={{
|
||||
title: prettySlug(content.attributes.slug, item.slug),
|
||||
subtitle:
|
||||
content.attributes.range[0]?.__typename ===
|
||||
"ComponentRangePageRange"
|
||||
? `${content.attributes.range[0].starting_page}` +
|
||||
`→` +
|
||||
`${content.attributes.range[0].ending_page}`
|
||||
: undefined,
|
||||
}}
|
||||
border
|
||||
active={index === currentIntersection}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
{filterHasAttributes(item.contents?.data, ["attributes"] as const).map((content, index) => (
|
||||
<>
|
||||
{content.attributes.scan_set && content.attributes.scan_set.length > 0 && (
|
||||
<TranslatedNavOption
|
||||
key={content.id}
|
||||
url={`#${content.attributes.slug}`}
|
||||
translations={filterHasAttributes(
|
||||
content.attributes.content?.data?.attributes?.translations,
|
||||
["language.data.attributes"] as const
|
||||
).map((translation) => ({
|
||||
language: translation.language.data.attributes.code,
|
||||
title: prettyInlineTitle(
|
||||
translation.pre_title,
|
||||
translation.title,
|
||||
translation.subtitle
|
||||
),
|
||||
subtitle:
|
||||
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
||||
? `${content.attributes.range[0].starting_page}` +
|
||||
`→` +
|
||||
`${content.attributes.range[0].ending_page}`
|
||||
: undefined,
|
||||
}))}
|
||||
fallback={{
|
||||
title: prettySlug(content.attributes.slug, item.slug),
|
||||
subtitle:
|
||||
content.attributes.range[0]?.__typename === "ComponentRangePageRange"
|
||||
? `${content.attributes.range[0].starting_page}` +
|
||||
`→` +
|
||||
`${content.attributes.range[0].ending_page}`
|
||||
: undefined,
|
||||
}}
|
||||
border
|
||||
active={index === currentIntersection}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</SubPanel>
|
||||
),
|
||||
[
|
||||
|
@ -201,9 +174,7 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
className="mb-10"
|
||||
/>
|
||||
|
||||
{item.images && (
|
||||
<ScanSetCover images={item.images} openLightBox={openLightBox} />
|
||||
)}
|
||||
{item.images && <ScanSetCover images={item.images} openLightBox={openLightBox} />}
|
||||
|
||||
{item.contents?.data.map((content) => (
|
||||
<Fragment key={content.id}>
|
||||
|
@ -233,23 +204,10 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => {
|
|||
))}
|
||||
</ContentPanel>
|
||||
),
|
||||
[
|
||||
LightBox,
|
||||
openLightBox,
|
||||
item.contents?.data,
|
||||
item.images,
|
||||
item.slug,
|
||||
langui,
|
||||
]
|
||||
[LightBox, openLightBox, item.contents?.data, item.images, item.slug, langui]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
contentPanel={contentPanel}
|
||||
subPanel={subPanel}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <AppLayout contentPanel={contentPanel} subPanel={subPanel} {...otherProps} />;
|
||||
};
|
||||
export default LibrarySlug;
|
||||
|
||||
|
@ -262,10 +220,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const item = await sdk.getLibraryItemScans({
|
||||
slug:
|
||||
context.params && isDefined(context.params.slug)
|
||||
? context.params.slug.toString()
|
||||
: "",
|
||||
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
||||
language_code: context.locale ?? "en",
|
||||
});
|
||||
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 libraryItems = await sdk.getLibraryItemsSlugs({});
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
filterHasAttributes(libraryItems.libraryItems?.data, [
|
||||
"attributes",
|
||||
] as const).map((item) => {
|
||||
filterHasAttributes(libraryItems.libraryItems?.data, ["attributes"] as const).map((item) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||
);
|
||||
|
@ -323,9 +276,7 @@ interface ScanSetProps {
|
|||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["scan_set"]
|
||||
|
@ -336,66 +287,53 @@ interface ScanSetProps {
|
|||
content: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||
>["contents"]
|
||||
>["data"][number]["attributes"]
|
||||
>["content"];
|
||||
}
|
||||
|
||||
const ScanSet = ({
|
||||
openLightBox,
|
||||
scanSet,
|
||||
id,
|
||||
title,
|
||||
content,
|
||||
}: ScanSetProps): JSX.Element => {
|
||||
const ScanSet = ({ openLightBox, scanSet, id, title, content }: ScanSetProps): JSX.Element => {
|
||||
const is1ColumnLayout = useIsContentPanelNoMoreThan("2xl");
|
||||
const { langui } = useAppLayout();
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: scanSet,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
transform: useCallback(
|
||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) => {
|
||||
item.pages?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes &&
|
||||
b.attributes &&
|
||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||
isDefinedAndNotEmpty(b.attributes.url)
|
||||
) {
|
||||
let aName = getAssetFilename(a.attributes.url);
|
||||
let bName = getAssetFilename(b.attributes.url);
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: scanSet,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<ScanSetProps["scanSet"][number]>) => item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
transform: useCallback((item: NonNullable<ScanSetProps["scanSet"][number]>) => {
|
||||
item.pages?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes &&
|
||||
b.attributes &&
|
||||
isDefinedAndNotEmpty(a.attributes.url) &&
|
||||
isDefinedAndNotEmpty(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
|
||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
||||
*/
|
||||
if (aName.replaceAll("0", "").length === 0) {
|
||||
aName = (1 - aName.length).toString(10);
|
||||
}
|
||||
if (bName.replaceAll("0", "").length === 0) {
|
||||
bName = (1 - bName.length).toString(10);
|
||||
}
|
||||
/*
|
||||
* If the number is a succession of 0s, make the number
|
||||
* incrementally smaller than 0 (i.e: 00 becomes -1)
|
||||
*/
|
||||
if (aName.replaceAll("0", "").length === 0) {
|
||||
aName = (1 - aName.length).toString(10);
|
||||
}
|
||||
if (bName.replaceAll("0", "").length === 0) {
|
||||
bName = (1 - bName.length).toString(10);
|
||||
}
|
||||
|
||||
if (isInteger(aName) && isInteger(bName)) {
|
||||
return parseInt(aName, 10) - parseInt(bName, 10);
|
||||
}
|
||||
return a.attributes.url.localeCompare(b.attributes.url);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return item;
|
||||
},
|
||||
[]
|
||||
),
|
||||
});
|
||||
if (isInteger(aName) && isInteger(bName)) {
|
||||
return parseInt(aName, 10) - parseInt(bName, 10);
|
||||
}
|
||||
return a.attributes.url.localeCompare(b.attributes.url);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return item;
|
||||
}, []),
|
||||
});
|
||||
|
||||
const pages = useMemo(
|
||||
() => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
|
||||
|
@ -408,8 +346,7 @@ const ScanSet = ({
|
|||
<div>
|
||||
<div
|
||||
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">
|
||||
{title}
|
||||
</h2>
|
||||
|
@ -425,13 +362,12 @@ const ScanSet = ({
|
|||
</div>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||
{content?.data?.attributes &&
|
||||
isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
||||
<Button
|
||||
href={`/contents/${content.data.attributes.slug}`}
|
||||
text={langui.open_content}
|
||||
/>
|
||||
)}
|
||||
{content?.data?.attributes && isDefinedAndNotEmpty(content.data.attributes.slug) && (
|
||||
<Button
|
||||
href={`/contents/${content.data.attributes.slug}`}
|
||||
text={langui.open_content}
|
||||
/>
|
||||
)}
|
||||
|
||||
{languageSwitcherProps.locales.size > 1 && (
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
@ -441,8 +377,7 @@ const ScanSet = ({
|
|||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedScan.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedScan.status} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
@ -479,24 +414,21 @@ const ScanSet = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{selectedScan.typesetters &&
|
||||
selectedScan.typesetters.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.typesetters}:
|
||||
</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((typesetter) => (
|
||||
<Fragment key={typesetter.id}>
|
||||
<RecorderChip recorder={typesetter.attributes} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.typesetters}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((typesetter) => (
|
||||
<Fragment key={typesetter.id}>
|
||||
<RecorderChip recorder={typesetter.attributes} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||
<ToolTip content={selectedScan.notes}>
|
||||
|
@ -514,8 +446,7 @@ const ScanSet = ({
|
|||
"grid-cols-2 gap-[4vmin]",
|
||||
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{pages.map((page, index) => (
|
||||
<div
|
||||
key={page.id}
|
||||
|
@ -526,8 +457,7 @@ const ScanSet = ({
|
|||
getAssetURL(image.attributes.url, ImageQuality.Large)
|
||||
);
|
||||
openLightBox(images, index);
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<Img src={page.attributes} quality={ImageQuality.Small} />
|
||||
</div>
|
||||
))}
|
||||
|
@ -547,18 +477,10 @@ const TranslatedScanSet = ({
|
|||
}: TranslatedProps<ScanSetProps, "title">): JSX.Element => {
|
||||
const [selectedTranslation] = useSmartLanguage({
|
||||
items: translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: { language: string }): string => item.language,
|
||||
[]
|
||||
),
|
||||
languageExtractor: useCallback((item: { language: string }): string => item.language, []),
|
||||
});
|
||||
|
||||
return (
|
||||
<ScanSet
|
||||
title={selectedTranslation?.title ?? fallback.title}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <ScanSet title={selectedTranslation?.title ?? fallback.title} {...otherProps} />;
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
@ -567,28 +489,22 @@ interface ScanSetCoverProps {
|
|||
openLightBox: (images: string[], index?: number) => void;
|
||||
images: NonNullable<
|
||||
NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemScansQuery["libraryItems"]
|
||||
>["data"][number]["attributes"]
|
||||
NonNullable<GetLibraryItemScansQuery["libraryItems"]>["data"][number]["attributes"]
|
||||
>["images"]
|
||||
>;
|
||||
}
|
||||
|
||||
const ScanSetCover = ({
|
||||
openLightBox,
|
||||
images,
|
||||
}: ScanSetCoverProps): JSX.Element => {
|
||||
const ScanSetCover = ({ openLightBox, images }: ScanSetCoverProps): JSX.Element => {
|
||||
const is1ColumnLayout = useIsContentPanelNoMoreThan("4xl");
|
||||
const { langui } = useAppLayout();
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: images,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
const [selectedScan, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: images,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<ScanSetCoverProps["images"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const coverImages = useMemo(() => {
|
||||
const memo: UploadImageFragment[] = [];
|
||||
|
@ -613,8 +529,7 @@ const ScanSetCover = ({
|
|||
<div>
|
||||
<div
|
||||
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">
|
||||
{langui.cover}
|
||||
</h2>
|
||||
|
@ -636,8 +551,7 @@ const ScanSetCover = ({
|
|||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedScan.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
maxWidth={"20rem"}>
|
||||
<Chip text={selectedScan.status} />
|
||||
</ToolTip>
|
||||
</div>
|
||||
|
@ -674,24 +588,21 @@ const ScanSetCover = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{selectedScan.typesetters &&
|
||||
selectedScan.typesetters.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.typesetters}:
|
||||
</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((typesetter) => (
|
||||
<Fragment key={typesetter.id}>
|
||||
<RecorderChip recorder={typesetter.attributes} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
{selectedScan.typesetters && selectedScan.typesetters.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers font-bold">{langui.typesetters}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data, [
|
||||
"id",
|
||||
"attributes",
|
||||
] as const).map((typesetter) => (
|
||||
<Fragment key={typesetter.id}>
|
||||
<RecorderChip recorder={typesetter.attributes} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -703,21 +614,17 @@ const ScanSetCover = ({
|
|||
"grid-cols-2 gap-[4vmin]",
|
||||
"grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))]"
|
||||
)
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{coverImages.map((image, index) => (
|
||||
<div
|
||||
key={image.url}
|
||||
className="cursor-pointer transition-transform
|
||||
drop-shadow-shade-lg hover:scale-[1.02]"
|
||||
onClick={() => {
|
||||
const imgs = coverImages.map((img) =>
|
||||
getAssetURL(img.url, ImageQuality.Large)
|
||||
);
|
||||
const imgs = coverImages.map((img) => getAssetURL(img.url, ImageQuality.Large));
|
||||
|
||||
openLightBox(imgs, index);
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<Img src={image} quality={ImageQuality.Small} />
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -6,15 +6,12 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
|||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyInlineTitle, prettyItemSubType } from "helpers/formatters";
|
||||
import { LibraryItemUserStatus } from "helpers/types";
|
||||
import { LibraryItemUserStatus } from "types/types";
|
||||
import { Icon } from "components/Ico";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
|
@ -24,12 +21,7 @@ import { isUntangibleGroupItem } from "helpers/libraryItem";
|
|||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
isUndefined,
|
||||
} from "helpers/others";
|
||||
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/others";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { convertPrice } from "helpers/numbers";
|
||||
import { SmartList } from "components/SmartList";
|
||||
|
@ -74,9 +66,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
const { libraryItemUserStatus } = useAppLayout();
|
||||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
);
|
||||
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||
|
||||
const {
|
||||
value: showSubitems,
|
||||
|
@ -102,27 +92,20 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const [sortingMethod, setSortingMethod] = useState<number>(
|
||||
DEFAULT_FILTERS_STATE.sortingMethod
|
||||
);
|
||||
const [sortingMethod, setSortingMethod] = useState<number>(DEFAULT_FILTERS_STATE.sortingMethod);
|
||||
|
||||
const [groupingMethod, setGroupingMethod] = useState<number>(
|
||||
DEFAULT_FILTERS_STATE.groupingMethod
|
||||
);
|
||||
|
||||
const [filterUserStatus, setFilterUserStatus] = useState<
|
||||
LibraryItemUserStatus | undefined
|
||||
>(DEFAULT_FILTERS_STATE.filterUserStatus);
|
||||
const [filterUserStatus, setFilterUserStatus] = useState<LibraryItemUserStatus | undefined>(
|
||||
DEFAULT_FILTERS_STATE.filterUserStatus
|
||||
);
|
||||
|
||||
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 &&
|
||||
isUntangibleGroupItem(item.attributes.metadata?.[0])
|
||||
) {
|
||||
if (showSubitems && isUntangibleGroupItem(item.attributes.metadata?.[0])) {
|
||||
return false;
|
||||
}
|
||||
if (item.attributes.primary && !showPrimaryItems) return false;
|
||||
|
@ -142,13 +125,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
}
|
||||
return true;
|
||||
},
|
||||
[
|
||||
libraryItemUserStatus,
|
||||
filterUserStatus,
|
||||
showPrimaryItems,
|
||||
showSecondaryItems,
|
||||
showSubitems,
|
||||
]
|
||||
[libraryItemUserStatus, filterUserStatus, showPrimaryItems, showSecondaryItems, showSubitems]
|
||||
);
|
||||
|
||||
const sortingFunction = useCallback(
|
||||
|
@ -158,16 +135,8 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
) => {
|
||||
switch (sortingMethod) {
|
||||
case 0: {
|
||||
const titleA = prettyInlineTitle(
|
||||
"",
|
||||
a.attributes.title,
|
||||
a.attributes.subtitle
|
||||
);
|
||||
const titleB = prettyInlineTitle(
|
||||
"",
|
||||
b.attributes.title,
|
||||
b.attributes.subtitle
|
||||
);
|
||||
const titleA = prettyInlineTitle("", a.attributes.title, a.attributes.subtitle);
|
||||
const titleB = prettyInlineTitle("", b.attributes.title, b.attributes.subtitle);
|
||||
return naturalCompare(titleA, titleB);
|
||||
}
|
||||
case 1: {
|
||||
|
@ -180,10 +149,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
return priceA - priceB;
|
||||
}
|
||||
case 2: {
|
||||
return compareDate(
|
||||
a.attributes.release_date,
|
||||
b.attributes.release_date
|
||||
);
|
||||
return compareDate(a.attributes.release_date, b.attributes.release_date);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
|
@ -193,15 +159,12 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
);
|
||||
|
||||
const groupingFunction = useCallback(
|
||||
(
|
||||
item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
|
||||
): string[] => {
|
||||
(item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">): string[] => {
|
||||
switch (groupingMethod) {
|
||||
case 0: {
|
||||
const categories = filterHasAttributes(
|
||||
item.attributes.categories?.data,
|
||||
["attributes"] as const
|
||||
);
|
||||
const categories = filterHasAttributes(item.attributes.categories?.data, [
|
||||
"attributes",
|
||||
] as const);
|
||||
if (categories.length > 0) {
|
||||
return categories.map((category) => category.attributes.name);
|
||||
}
|
||||
|
@ -221,10 +184,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
case "ComponentMetadataOther":
|
||||
return [langui.other ?? "Other"];
|
||||
case "ComponentMetadataGroup": {
|
||||
switch (
|
||||
item.attributes.metadata[0]?.subitems_type?.data?.attributes
|
||||
?.slug
|
||||
) {
|
||||
switch (item.attributes.metadata[0]?.subitems_type?.data?.attributes?.slug) {
|
||||
case "audio":
|
||||
return [langui.audio ?? "Audio"];
|
||||
case "video":
|
||||
|
@ -318,9 +278,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
onChange={(value) => {
|
||||
setSortingMethod(value);
|
||||
umami(
|
||||
`[Library] Change sorting method (${
|
||||
["name", "price", "release date"][value]
|
||||
})`
|
||||
`[Library] Change sorting method (${["name", "price", "release date"][value]})`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -341,9 +299,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
value={showPrimaryItems}
|
||||
onClick={() => {
|
||||
toggleShowPrimaryItems();
|
||||
umami(
|
||||
`[Library] ${showPrimaryItems ? "Hide" : "Show"} primary items`
|
||||
);
|
||||
umami(`[Library] ${showPrimaryItems ? "Hide" : "Show"} primary items`);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
@ -353,11 +309,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
value={showSecondaryItems}
|
||||
onClick={() => {
|
||||
toggleShowSecondaryItems();
|
||||
umami(
|
||||
`[Library] ${
|
||||
showSecondaryItems ? "Hide" : "Show"
|
||||
} secondary items`
|
||||
);
|
||||
umami(`[Library] ${showSecondaryItems ? "Hide" : "Show"} secondary items`);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
@ -368,9 +320,7 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
value={keepInfoVisible}
|
||||
onClick={() => {
|
||||
toggleKeepInfoVisible();
|
||||
umami(
|
||||
`[Library] Always ${keepInfoVisible ? "hide" : "show"} info`
|
||||
);
|
||||
umami(`[Library] Always ${keepInfoVisible ? "hide" : "show"} info`);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
@ -497,20 +447,13 @@ const Library = ({ items, ...otherProps }: Props): JSX.Element => {
|
|||
)}
|
||||
className={cJoin(
|
||||
"grid-cols-2 items-end",
|
||||
cIf(
|
||||
isContentPanelAtLeast4xl,
|
||||
"grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]"
|
||||
)
|
||||
cIf(isContentPanelAtLeast4xl, "grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]")
|
||||
)}
|
||||
searchingTerm={searchName}
|
||||
sortingFunction={sortingFunction}
|
||||
groupingFunction={groupingFunction}
|
||||
searchingBy={(item) =>
|
||||
prettyInlineTitle(
|
||||
"",
|
||||
item.attributes.title,
|
||||
item.attributes.subtitle
|
||||
)
|
||||
prettyInlineTitle("", item.attributes.title, item.attributes.subtitle)
|
||||
}
|
||||
filteringFunction={filteringFunction}
|
||||
paginationItemPerPage={25}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
getPostStaticProps,
|
||||
PostStaticProps,
|
||||
} from "graphql/getPostStaticProps";
|
||||
import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
|
@ -37,9 +34,7 @@ export default LibrarySlug;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const slug =
|
||||
context.params && isDefined(context.params.slug)
|
||||
? context.params.slug.toString()
|
||||
: "";
|
||||
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||
return await getPostStaticProps(slug)(context);
|
||||
};
|
||||
|
||||
|
@ -50,13 +45,11 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
const posts = await sdk.getPostsSlugs();
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
|
||||
filterHasAttributes(posts.posts?.data, ["attributes"] as const).map(
|
||||
(item) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||
);
|
||||
}
|
||||
);
|
||||
filterHasAttributes(posts.posts?.data, ["attributes"] as const).map((item) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({ params: { slug: item.attributes.slug }, locale: local })
|
||||
);
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking",
|
||||
|
|
|
@ -4,10 +4,7 @@ import { useBoolean } from "usehooks-ts";
|
|||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { GetPostsPreviewQuery } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
|
@ -51,9 +48,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
|||
const isContentPanelAtLeast4xl = useIsContentPanelAtLeast("4xl");
|
||||
const { langui } = useAppLayout();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
);
|
||||
const [searchName, setSearchName] = useState(DEFAULT_FILTERS_STATE.searchName);
|
||||
const {
|
||||
value: keepInfoVisible,
|
||||
toggle: toggleKeepInfoVisible,
|
||||
|
@ -63,11 +58,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
|||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<PanelHeader
|
||||
icon={Icon.Feed}
|
||||
title={langui.news}
|
||||
description={langui.news_description}
|
||||
/>
|
||||
<PanelHeader icon={Icon.Feed} title={langui.news} description={langui.news_description} />
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
|
@ -91,9 +82,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
|||
value={keepInfoVisible}
|
||||
onClick={() => {
|
||||
toggleKeepInfoVisible();
|
||||
umami(
|
||||
`[News] Always ${keepInfoVisible ? "hide" : "show"} info`
|
||||
);
|
||||
umami(`[News] Always ${keepInfoVisible ? "hide" : "show"} info`);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
@ -111,14 +100,7 @@ const News = ({ posts, ...otherProps }: Props): JSX.Element => {
|
|||
/>
|
||||
</SubPanel>
|
||||
),
|
||||
[
|
||||
hoverable,
|
||||
keepInfoVisible,
|
||||
langui,
|
||||
searchName,
|
||||
setKeepInfoVisible,
|
||||
toggleKeepInfoVisible,
|
||||
]
|
||||
[hoverable, keepInfoVisible, langui, searchName, setKeepInfoVisible, toggleKeepInfoVisible]
|
||||
);
|
||||
|
||||
const contentPanel = useMemo(
|
||||
|
@ -207,6 +189,4 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
*/
|
||||
|
||||
const sortPosts = (posts: Props["posts"]): Props["posts"] =>
|
||||
posts
|
||||
.sort((a, b) => compareDate(a.attributes?.date, b.attributes?.date))
|
||||
.reverse();
|
||||
posts.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 { Img } from "components/Img";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import {
|
||||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import DefinitionCard from "components/Wiki/DefinitionCard";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
} from "helpers/others";
|
||||
import { WikiPageWithTranslations } from "helpers/types";
|
||||
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { WikiPageWithTranslations } from "types/types";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { prettySlug, sJoin } from "helpers/formatters";
|
||||
import { useLightBox } from "hooks/useLightBox";
|
||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import {
|
||||
getDefaultPreferredLanguages,
|
||||
staticSmartLanguage,
|
||||
} from "helpers/locales";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useIs3ColumnsLayout } from "hooks/useContainerQuery";
|
||||
|
@ -44,15 +34,14 @@ interface Props extends AppLayoutRequired {
|
|||
|
||||
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
||||
const { langui } = useAppLayout();
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
items: page.translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["page"]["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({
|
||||
items: page.translations,
|
||||
languageExtractor: useCallback(
|
||||
(item: NonNullable<Props["page"]["translations"][number]>) =>
|
||||
item.language?.data?.attributes?.code,
|
||||
[]
|
||||
),
|
||||
});
|
||||
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
const is3ColumnsLayout = useIs3ColumnsLayout();
|
||||
|
@ -60,11 +49,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href={`/wiki`}
|
||||
title={langui.wiki}
|
||||
displayOnlyOn={"3ColumnsLayout"}
|
||||
/>
|
||||
<ReturnButton href={`/wiki`} title={langui.wiki} displayOnlyOn={"3ColumnsLayout"} />
|
||||
</SubPanel>
|
||||
),
|
||||
[langui]
|
||||
|
@ -84,14 +69,11 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
<div className="flex flex-wrap place-content-center gap-3">
|
||||
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
||||
{selectedTranslation?.aliases &&
|
||||
selectedTranslation.aliases.length > 0 && (
|
||||
<p className="mr-3 text-center text-2xl">
|
||||
{`(${selectedTranslation.aliases
|
||||
.map((alias) => alias?.alias)
|
||||
.join("・")})`}
|
||||
</p>
|
||||
)}
|
||||
{selectedTranslation?.aliases && selectedTranslation.aliases.length > 0 && (
|
||||
<p className="mr-3 text-center text-2xl">
|
||||
{`(${selectedTranslation.aliases.map((alias) => alias?.alias).join("・")})`}
|
||||
</p>
|
||||
)}
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
</div>
|
||||
|
||||
|
@ -103,8 +85,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
className={cJoin(
|
||||
"mb-8 overflow-hidden rounded-lg bg-mid text-center",
|
||||
cIf(is3ColumnsLayout, "float-right ml-8 w-[25rem]")
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{page.thumbnail?.data?.attributes && (
|
||||
<Img
|
||||
src={page.thumbnail.data.attributes}
|
||||
|
@ -113,10 +94,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
onClick={() => {
|
||||
if (page.thumbnail?.data?.attributes?.url) {
|
||||
openLightBox([
|
||||
getAssetURL(
|
||||
page.thumbnail.data.attributes.url,
|
||||
ImageQuality.Large
|
||||
),
|
||||
getAssetURL(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">
|
||||
{page.categories?.data && page.categories.data.length > 0 && (
|
||||
<>
|
||||
<p className="font-headers text-xl font-bold">
|
||||
{langui.categories}
|
||||
</p>
|
||||
<p className="font-headers text-xl font-bold">{langui.categories}</p>
|
||||
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(page.categories.data, [
|
||||
"attributes",
|
||||
] as const).map((category) => (
|
||||
<Chip
|
||||
key={category.id}
|
||||
text={category.attributes.name}
|
||||
/>
|
||||
))}
|
||||
{filterHasAttributes(page.categories.data, ["attributes"] as const).map(
|
||||
(category) => (
|
||||
<Chip key={category.id} text={category.attributes.name} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{page.tags?.data && page.tags.data.length > 0 && (
|
||||
<>
|
||||
<p className="font-headers text-xl font-bold">
|
||||
{langui.tags}
|
||||
</p>
|
||||
<p className="font-headers text-xl font-bold">{langui.tags}</p>
|
||||
<div className="flex flex-row flex-wrap place-content-center gap-2">
|
||||
{filterHasAttributes(page.tags.data, [
|
||||
"attributes",
|
||||
] as const).map((tag) => (
|
||||
{filterHasAttributes(page.tags.data, ["attributes"] as const).map((tag) => (
|
||||
<Chip
|
||||
key={tag.id}
|
||||
text={
|
||||
tag.attributes.titles?.[0]?.title ??
|
||||
prettySlug(tag.attributes.slug)
|
||||
tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
@ -167,40 +135,41 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
|
||||
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
||||
<div className="mb-12">
|
||||
<p className="font-headers text-lg font-bold">
|
||||
{langui.summary}
|
||||
</p>
|
||||
<p className="font-headers text-lg font-bold">{langui.summary}</p>
|
||||
<p>{selectedTranslation.summary}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filterHasAttributes(page.definitions, [
|
||||
"translations",
|
||||
] as const).map((definition, index) => (
|
||||
<div key={index} className="mb-12">
|
||||
<DefinitionCard
|
||||
source={{
|
||||
name: definition.source?.data?.attributes?.name,
|
||||
url: definition.source?.data?.attributes?.content?.data
|
||||
?.attributes?.slug
|
||||
? `/contents/${definition.source.data.attributes.content.data.attributes.slug}`
|
||||
: `/library/${definition.source?.data?.attributes?.ranged_content?.data?.attributes?.library_item?.data?.attributes?.slug}`,
|
||||
}}
|
||||
translations={definition.translations.map(
|
||||
(translation) => ({
|
||||
{filterHasAttributes(page.definitions, ["translations"] as const).map(
|
||||
(definition, index) => (
|
||||
<div key={index} className="mb-12">
|
||||
<DefinitionCard
|
||||
source={{
|
||||
name: definition.source?.data?.attributes?.name,
|
||||
url: definition.source?.data?.attributes?.content?.data?.attributes?.slug
|
||||
? sJoin(
|
||||
"/contents/",
|
||||
definition.source.data.attributes.content.data.attributes.slug
|
||||
)
|
||||
: cJoin(
|
||||
"/library/",
|
||||
definition.source?.data?.attributes?.ranged_content?.data?.attributes
|
||||
?.library_item?.data?.attributes?.slug
|
||||
),
|
||||
}}
|
||||
translations={definition.translations.map((translation) => ({
|
||||
language: translation?.language?.data?.attributes?.code,
|
||||
definition: translation?.definition,
|
||||
status: translation?.status,
|
||||
})
|
||||
)}
|
||||
index={index + 1}
|
||||
categories={filterHasAttributes(
|
||||
definition.categories?.data,
|
||||
["attributes"] as const
|
||||
).map((category) => category.attributes.short)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
}))}
|
||||
index={index + 1}
|
||||
categories={filterHasAttributes(definition.categories?.data, [
|
||||
"attributes",
|
||||
] as const).map((category) => category.attributes.short)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -221,13 +190,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
|
|||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
return <AppLayout subPanel={subPanel} contentPanel={contentPanel} {...otherProps} />;
|
||||
};
|
||||
export default WikiPage;
|
||||
|
||||
|
@ -240,24 +203,19 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const langui = getLangui(context.locale);
|
||||
const slug =
|
||||
context.params && isDefined(context.params.slug)
|
||||
? context.params.slug.toString()
|
||||
: "";
|
||||
context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "";
|
||||
const page = await sdk.getWikiPage({
|
||||
language_code: context.locale ?? "en",
|
||||
slug: slug,
|
||||
});
|
||||
if (!page.wikiPages?.data[0].attributes?.translations)
|
||||
return { notFound: true };
|
||||
if (!page.wikiPages?.data[0].attributes?.translations) return { notFound: true };
|
||||
|
||||
const { title, description } = (() => {
|
||||
const chipsGroups = {
|
||||
[langui.tags ?? "Tags"]: filterHasAttributes(
|
||||
page.wikiPages.data[0].attributes.tags?.data,
|
||||
["attributes"] as const
|
||||
).map(
|
||||
(tag) =>
|
||||
tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||
[langui.tags ?? "Tags"]: filterHasAttributes(page.wikiPages.data[0].attributes.tags?.data, [
|
||||
"attributes",
|
||||
] as const).map(
|
||||
(tag) => tag.attributes.titles?.[0]?.title ?? prettySlug(tag.attributes.slug)
|
||||
),
|
||||
[langui.categories ?? "Categories"]: filterHasAttributes(
|
||||
page.wikiPages.data[0].attributes.categories?.data,
|
||||
|
@ -269,10 +227,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
const selectedTranslation = staticSmartLanguage({
|
||||
items: page.wikiPages.data[0].attributes.translations,
|
||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
||||
preferredLanguages: getDefaultPreferredLanguages(
|
||||
context.locale,
|
||||
context.locales
|
||||
),
|
||||
preferredLanguages: getDefaultPreferredLanguages(context.locale, context.locales),
|
||||
});
|
||||
if (selectedTranslation) {
|
||||
return {
|
||||
|
@ -288,8 +243,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
};
|
||||
})();
|
||||
|
||||
const thumbnail =
|
||||
page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
|
||||
const thumbnail = page.wikiPages.data[0].attributes.thumbnail?.data?.attributes;
|
||||
|
||||
const props: Props = {
|
||||
page: page.wikiPages.data[0].attributes as WikiPageWithTranslations,
|
||||
|
@ -306,16 +260,14 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
|
|||
const sdk = getReadySdk();
|
||||
const contents = await sdk.getWikiPagesSlugs();
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map(
|
||||
(wikiPage) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({
|
||||
params: { slug: wikiPage.attributes.slug },
|
||||
locale: local,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map((wikiPage) => {
|
||||
context.locales?.map((local) =>
|
||||
paths.push({
|
||||
params: { slug: wikiPage.attributes.slug },
|
||||
locale: local,
|
||||
})
|
||||
);
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue