From 7aeb85e4f93e2f87b9bedf75e7bffccb9e4e116d Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Thu, 16 Feb 2023 09:19:44 +0100 Subject: [PATCH] Weapon stories --- package-lock.json | 302 +++++------ package.json | 18 +- public/local-data/websiteInterfaces.json | 30 +- src/components/Containers/Paginator.tsx | 8 + src/components/Markdown/Markdawn.tsx | 20 +- src/components/Panels/SearchPopup.tsx | 67 ++- src/components/PostPage.tsx | 91 ++-- src/components/PreviewCard.tsx | 9 +- src/graphql/icuParams.ts | 4 + src/graphql/operations/getWeapon.graphql | 92 ++++ .../operations/getWeaponsSlugs.graphql | 10 + .../localDataGetWebsiteInterfaces.graphql | 4 + src/hooks/useFormat.ts | 8 +- src/hooks/useReaderSettings.ts | 4 +- src/pages/contents/[slug].tsx | 23 +- src/pages/dev/editor.tsx | 13 +- src/pages/library/[slug]/index.tsx | 106 ++-- src/pages/news/index.tsx | 1 + src/pages/wiki/chronology.tsx | 15 +- src/pages/wiki/index.tsx | 19 +- src/pages/wiki/weapons/[slug].tsx | 297 +++++++++++ src/pages/wiki/weapons/index.tsx | 237 +++++++++ .../meilisearch-graphql-typings/generated.ts | 494 +++++++++++++++--- .../meilisearch-graphql-typings/meiliTypes.ts | 45 +- src/types/types.ts | 23 + 25 files changed, 1578 insertions(+), 362 deletions(-) create mode 100644 src/graphql/operations/getWeapon.graphql create mode 100644 src/graphql/operations/getWeaponsSlugs.graphql create mode 100644 src/pages/wiki/weapons/[slug].tsx create mode 100644 src/pages/wiki/weapons/index.tsx diff --git a/package-lock.json b/package-lock.json index 343de73..0c4a27f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "autoprefixer": "^10.4.13", "cuid": "^2.1.8", "intl-messageformat": "^10.3.0", - "isomorphic-dompurify": "^0.26.0", + "isomorphic-dompurify": "^1.0.0", "jotai": "^2.0.1", "markdown-to-jsx": "^7.1.9", "marked": "^4.2.12", @@ -28,7 +28,7 @@ "react-dom": "18.2.0", "react-hotkeys-hook": "^3.4.7", "react-swipeable": "^7.0.0", - "react-zoom-pan-pinch": "^2.5.0", + "react-zoom-pan-pinch": "^2.6.1", "string-natural-compare": "^3.0.1", "throttle-debounce": "^5.0.0", "tippy.js": "^6.3.7", @@ -46,25 +46,25 @@ "@types/marked": "^4.0.8", "@types/node": "18.13.0", "@types/nodemailer": "^6.4.7", - "@types/react": "^18.0.27", - "@types/react-dom": "^18.0.10", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", "@types/string-natural-compare": "^3.0.2", "@types/throttle-debounce": "^5.0.0", "@types/turndown": "^5.0.1", "@types/ua-parser-js": "^0.7.36", - "@typescript-eslint/eslint-plugin": "^5.51.0", - "@typescript-eslint/parser": "^5.51.0", + "@typescript-eslint/eslint-plugin": "^5.52.0", + "@typescript-eslint/parser": "^5.52.0", "dotenv": "^16.0.3", - "eslint": "^8.33.0", + "eslint": "^8.34.0", "eslint-config-next": "13.1.6", "eslint-plugin-import": "^2.27.5", "graphql": "^16.6.0", "graphql-request": "^5.1.0", "next-sitemap": "^3.1.52", "prettier": "^2.8.4", - "prettier-plugin-tailwindcss": "^0.2.2", + "prettier-plugin-tailwindcss": "^0.2.3", "tailwindcss": "^3.2.6", - "ts-unused-exports": "^9.0.3", + "ts-unused-exports": "^9.0.4", "typescript": "^4.9.5" } }, @@ -3690,9 +3690,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.0.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.27.tgz", - "integrity": "sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==", + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -3701,9 +3701,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.0.10", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", - "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", "dev": true, "dependencies": { "@types/react": "*" @@ -3760,14 +3760,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", - "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz", + "integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/type-utils": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/type-utils": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -3794,14 +3794,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", - "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz", + "integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "debug": "^4.3.4" }, "engines": { @@ -3821,13 +3821,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", - "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz", + "integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0" + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3838,13 +3838,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", - "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz", + "integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/typescript-estree": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3865,9 +3865,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", - "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz", + "integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3878,13 +3878,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", - "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz", + "integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3905,16 +3905,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", - "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz", + "integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -3953,12 +3953,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", - "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz", + "integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/types": "5.52.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -5297,9 +5297,9 @@ "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" }, "node_modules/dompurify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", - "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.0.tgz", + "integrity": "sha512-0g/yr2IJn4nTbxwL785YxS7/AvvgGFJw6LLWP+BzWzB1+BYOqPUT9Hy0rXrZh5HLdHnxH72aDdzvC9SdTjsuaA==" }, "node_modules/dot-case": { "version": "3.0.4", @@ -5624,9 +5624,9 @@ } }, "node_modules/eslint": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", - "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", + "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", "dev": true, "dependencies": { "@eslint/eslintrc": "^1.4.1", @@ -7467,13 +7467,13 @@ "dev": true }, "node_modules/isomorphic-dompurify": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-0.26.0.tgz", - "integrity": "sha512-70gPadd/NJPTBuTtM5PsWimmc7S4fcBENzOFMHfBtIPacaygUvI9n63qFkUTc91WDDC9yB68mtmluW9/NhhTJw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-1.0.0.tgz", + "integrity": "sha512-rxkJ2b2rwsgN/uvtaW+Z2JGfD9CYcixYMj0eTIa4V2hG47VDRMiehtUypi4lkpuwW41UrnOR65Dy0POiH3Lbjg==", "dependencies": { - "@types/dompurify": "^2.3.4", - "dompurify": "^2.4.1", - "jsdom": "^21.0.0" + "@types/dompurify": "^2.4.0", + "dompurify": "^3.0.0", + "jsdom": "^21.1.0" } }, "node_modules/isomorphic-fetch": { @@ -7535,9 +7535,9 @@ } }, "node_modules/jsdom": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.0.0.tgz", - "integrity": "sha512-AIw+3ZakSUtDYvhwPwWHiZsUi3zHugpMEKlNPaurviseYoBqo0zBd3zqoUi3LPCNtPFlEP8FiW9MqCZdjb2IYA==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz", + "integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==", "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", @@ -8860,14 +8860,15 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.2.tgz", - "integrity": "sha512-5RjUbWRe305pUpc48MosoIp6uxZvZxrM6GyOgsbGLTce+ehePKNm7ziW2dLG2air9aXbGuXlHVSQQw4Lbosq3w==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.3.tgz", + "integrity": "sha512-s2N5Dh7Ao5KTV1mao5ZBnn8EKtUcDPJEkGViZIjI0Ij9TTI5zgTz4IHOxW33jOdjHKa8CSjM88scelUiC5TNRQ==", "dev": true, "engines": { "node": ">=12.17.0" }, "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-php": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", @@ -8885,6 +8886,9 @@ "prettier-plugin-twig-melody": "*" }, "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, "@prettier/plugin-php": { "optional": true }, @@ -9102,9 +9106,9 @@ } }, "node_modules/react-zoom-pan-pinch": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.5.0.tgz", - "integrity": "sha512-5o3DKACPRNXM5Yr1dfQ5sZsGpstEsam3ih3dNmaDUfpghwD7ENwCVAupbNiBHlJ1Jbowd6o9qamp/2FiWu2Jcg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.6.1.tgz", + "integrity": "sha512-4Cgdnn6OwN4DomY/E9NpAf0TyCtslEgwdYn96ZV/f5LKuw/FE3gcIBJiaKFmMGThDGV0yKN5mzO8noi34+UE4Q==", "engines": { "node": ">=8", "npm": ">=5" @@ -10042,9 +10046,9 @@ "dev": true }, "node_modules/ts-unused-exports": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-9.0.3.tgz", - "integrity": "sha512-LCGLYL0EVdXNj1O/cGfpP2Fx+zfqoV936iMyIhvSVnXk4RUjwmSjMzzCNXI9b1j9PCs946a2TbRMhJh7/XUyUA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-9.0.4.tgz", + "integrity": "sha512-/PPy0B1zhOJkDTUd1XVyaCqE/yA3IL2FrQ8W5/6cQ2g0kKC/06q8LEoPeXI6ELfI6Bivmv3MMvsUup5u3WH+BQ==", "dev": true, "dependencies": { "chalk": "^4.0.0", @@ -13538,9 +13542,9 @@ "dev": true }, "@types/react": { - "version": "18.0.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.27.tgz", - "integrity": "sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==", + "version": "18.0.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", + "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", "dev": true, "requires": { "@types/prop-types": "*", @@ -13549,9 +13553,9 @@ } }, "@types/react-dom": { - "version": "18.0.10", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", - "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", "dev": true, "requires": { "@types/react": "*" @@ -13608,14 +13612,14 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", - "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.52.0.tgz", + "integrity": "sha512-lHazYdvYVsBokwCdKOppvYJKaJ4S41CgKBcPvyd0xjZNbvQdhn/pnJlGtQksQ/NhInzdaeaSarlBjDXHuclEbg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/type-utils": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/type-utils": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -13626,53 +13630,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", - "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.52.0.tgz", + "integrity": "sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", - "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz", + "integrity": "sha512-AR7sxxfBKiNV0FWBSARxM8DmNxrwgnYMPwmpkC1Pl1n+eT8/I2NAUPuwDy/FmDcC6F8pBfmOcaxcxRHspgOBMw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0" + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0" } }, "@typescript-eslint/type-utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", - "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.52.0.tgz", + "integrity": "sha512-tEKuUHfDOv852QGlpPtB3lHOoig5pyFQN/cUiZtpw99D93nEBjexRLre5sQZlkMoHry/lZr8qDAt2oAHLKA6Jw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.51.0", - "@typescript-eslint/utils": "5.51.0", + "@typescript-eslint/typescript-estree": "5.52.0", + "@typescript-eslint/utils": "5.52.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", - "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.52.0.tgz", + "integrity": "sha512-oV7XU4CHYfBhk78fS7tkum+/Dpgsfi91IIDy7fjCyq2k6KB63M6gMC0YIvy+iABzmXThCRI6xpCEyVObBdWSDQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", - "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.52.0.tgz", + "integrity": "sha512-WeWnjanyEwt6+fVrSR0MYgEpUAuROxuAH516WPjUblIrClzYJj0kBbjdnbQXLpgAN8qbEuGywiQsXUVDiAoEuQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/visitor-keys": "5.51.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/visitor-keys": "5.52.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -13681,16 +13685,16 @@ } }, "@typescript-eslint/utils": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", - "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.52.0.tgz", + "integrity": "sha512-As3lChhrbwWQLNk2HC8Ree96hldKIqk98EYvypd3It8Q1f8d5zWyIoaZEp2va5667M4ZyE7X8UUR+azXrFl+NA==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.51.0", - "@typescript-eslint/types": "5.51.0", - "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/scope-manager": "5.52.0", + "@typescript-eslint/types": "5.52.0", + "@typescript-eslint/typescript-estree": "5.52.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -13715,12 +13719,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", - "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "version": "5.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz", + "integrity": "sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/types": "5.52.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -14738,9 +14742,9 @@ "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" }, "dompurify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", - "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.0.tgz", + "integrity": "sha512-0g/yr2IJn4nTbxwL785YxS7/AvvgGFJw6LLWP+BzWzB1+BYOqPUT9Hy0rXrZh5HLdHnxH72aDdzvC9SdTjsuaA==" }, "dot-case": { "version": "3.0.4", @@ -15001,9 +15005,9 @@ } }, "eslint": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", - "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", + "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", "dev": true, "requires": { "@eslint/eslintrc": "^1.4.1", @@ -16358,13 +16362,13 @@ "dev": true }, "isomorphic-dompurify": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-0.26.0.tgz", - "integrity": "sha512-70gPadd/NJPTBuTtM5PsWimmc7S4fcBENzOFMHfBtIPacaygUvI9n63qFkUTc91WDDC9yB68mtmluW9/NhhTJw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-1.0.0.tgz", + "integrity": "sha512-rxkJ2b2rwsgN/uvtaW+Z2JGfD9CYcixYMj0eTIa4V2hG47VDRMiehtUypi4lkpuwW41UrnOR65Dy0POiH3Lbjg==", "requires": { - "@types/dompurify": "^2.3.4", - "dompurify": "^2.4.1", - "jsdom": "^21.0.0" + "@types/dompurify": "^2.4.0", + "dompurify": "^3.0.0", + "jsdom": "^21.1.0" } }, "isomorphic-fetch": { @@ -16411,9 +16415,9 @@ } }, "jsdom": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.0.0.tgz", - "integrity": "sha512-AIw+3ZakSUtDYvhwPwWHiZsUi3zHugpMEKlNPaurviseYoBqo0zBd3zqoUi3LPCNtPFlEP8FiW9MqCZdjb2IYA==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.0.tgz", + "integrity": "sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==", "requires": { "abab": "^2.0.6", "acorn": "^8.8.1", @@ -17352,9 +17356,9 @@ "dev": true }, "prettier-plugin-tailwindcss": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.2.tgz", - "integrity": "sha512-5RjUbWRe305pUpc48MosoIp6uxZvZxrM6GyOgsbGLTce+ehePKNm7ziW2dLG2air9aXbGuXlHVSQQw4Lbosq3w==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.3.tgz", + "integrity": "sha512-s2N5Dh7Ao5KTV1mao5ZBnn8EKtUcDPJEkGViZIjI0Ij9TTI5zgTz4IHOxW33jOdjHKa8CSjM88scelUiC5TNRQ==", "dev": true, "requires": {} }, @@ -17484,9 +17488,9 @@ "requires": {} }, "react-zoom-pan-pinch": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.5.0.tgz", - "integrity": "sha512-5o3DKACPRNXM5Yr1dfQ5sZsGpstEsam3ih3dNmaDUfpghwD7ENwCVAupbNiBHlJ1Jbowd6o9qamp/2FiWu2Jcg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.6.1.tgz", + "integrity": "sha512-4Cgdnn6OwN4DomY/E9NpAf0TyCtslEgwdYn96ZV/f5LKuw/FE3gcIBJiaKFmMGThDGV0yKN5mzO8noi34+UE4Q==", "requires": {} }, "read-cache": { @@ -18202,9 +18206,9 @@ } }, "ts-unused-exports": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-9.0.3.tgz", - "integrity": "sha512-LCGLYL0EVdXNj1O/cGfpP2Fx+zfqoV936iMyIhvSVnXk4RUjwmSjMzzCNXI9b1j9PCs946a2TbRMhJh7/XUyUA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-9.0.4.tgz", + "integrity": "sha512-/PPy0B1zhOJkDTUd1XVyaCqE/yA3IL2FrQ8W5/6cQ2g0kKC/06q8LEoPeXI6ELfI6Bivmv3MMvsUup5u3WH+BQ==", "dev": true, "requires": { "chalk": "^4.0.0", diff --git a/package.json b/package.json index eb2b0ea..669f010 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "autoprefixer": "^10.4.13", "cuid": "^2.1.8", "intl-messageformat": "^10.3.0", - "isomorphic-dompurify": "^0.26.0", + "isomorphic-dompurify": "^1.0.0", "jotai": "^2.0.1", "markdown-to-jsx": "^7.1.9", "marked": "^4.2.12", @@ -40,7 +40,7 @@ "react-dom": "18.2.0", "react-hotkeys-hook": "^3.4.7", "react-swipeable": "^7.0.0", - "react-zoom-pan-pinch": "^2.5.0", + "react-zoom-pan-pinch": "^2.6.1", "string-natural-compare": "^3.0.1", "throttle-debounce": "^5.0.0", "tippy.js": "^6.3.7", @@ -58,25 +58,25 @@ "@types/marked": "^4.0.8", "@types/node": "18.13.0", "@types/nodemailer": "^6.4.7", - "@types/react": "^18.0.27", - "@types/react-dom": "^18.0.10", + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", "@types/string-natural-compare": "^3.0.2", "@types/throttle-debounce": "^5.0.0", "@types/turndown": "^5.0.1", "@types/ua-parser-js": "^0.7.36", - "@typescript-eslint/eslint-plugin": "^5.51.0", - "@typescript-eslint/parser": "^5.51.0", + "@typescript-eslint/eslint-plugin": "^5.52.0", + "@typescript-eslint/parser": "^5.52.0", "dotenv": "^16.0.3", - "eslint": "^8.33.0", + "eslint": "^8.34.0", "eslint-config-next": "13.1.6", "eslint-plugin-import": "^2.27.5", "graphql": "^16.6.0", "graphql-request": "^5.1.0", "next-sitemap": "^3.1.52", "prettier": "^2.8.4", - "prettier-plugin-tailwindcss": "^0.2.2", + "prettier-plugin-tailwindcss": "^0.2.3", "tailwindcss": "^3.2.6", - "ts-unused-exports": "^9.0.3", + "ts-unused-exports": "^9.0.4", "typescript": "^4.9.5" }, "overrides": { diff --git a/public/local-data/websiteInterfaces.json b/public/local-data/websiteInterfaces.json index 215c8dc..97a7fa6 100644 --- a/public/local-data/websiteInterfaces.json +++ b/public/local-data/websiteInterfaces.json @@ -180,7 +180,11 @@ "definition_x": "Definition {x}", "subitem_of_x": "Subitem of {x}", "variant_of_x": "Variant of {x}", - "dark_mode_extension_warning": "This website offers a light and dark theme. If you are using a \"dark mode\" browser extension, make sure to disable it for an optimal experience." + "dark_mode_extension_warning": "This website offers a light and dark theme. If you are using a \"dark mode\" browser extension, make sure to disable it for an optimal experience.", + "weapon": "{ count, plural, =0 {No weapons} one {Weapon} other {Weapons} }", + "weapons_description": "A list of all the weapons across all of the games. All distinguished weapons come with an “account.” It’s a document with various details like how the weapon was forged and how it’s been used in the past.", + "level_x": "Level {x}", + "story_x": "Story {x}" } }, { @@ -362,7 +366,11 @@ "definition_x": "Définition {x}", "subitem_of_x": "Sous-item de {x}", "variant_of_x": "Variante de {x}", - "dark_mode_extension_warning": "Ce site web propose un thème clair et un thème sombre. Si vous utilisez une extension de navigateur qui simule un thème sombre, veillez à la désactiver pour une expérience optimale." + "dark_mode_extension_warning": "Ce site web propose un thème clair et un thème sombre. Si vous utilisez une extension de navigateur qui simule un thème sombre, veillez à la désactiver pour une expérience optimale.", + "weapon": null, + "weapons_description": null, + "level_x": null, + "story_x": null } }, { @@ -544,7 +552,11 @@ "definition_x": null, "subitem_of_x": null, "variant_of_x": null, - "dark_mode_extension_warning": null + "dark_mode_extension_warning": null, + "weapon": null, + "weapons_description": null, + "level_x": null, + "story_x": null } }, { @@ -726,7 +738,11 @@ "definition_x": null, "subitem_of_x": null, "variant_of_x": null, - "dark_mode_extension_warning": null + "dark_mode_extension_warning": null, + "weapon": null, + "weapons_description": null, + "level_x": null, + "story_x": null } }, { @@ -908,7 +924,11 @@ "definition_x": null, "subitem_of_x": null, "variant_of_x": null, - "dark_mode_extension_warning": null + "dark_mode_extension_warning": null, + "weapon": null, + "weapons_description": null, + "level_x": null, + "story_x": null } } ] diff --git a/src/components/Containers/Paginator.tsx b/src/components/Containers/Paginator.tsx index 547036a..bfbe274 100644 --- a/src/components/Containers/Paginator.tsx +++ b/src/components/Containers/Paginator.tsx @@ -1,3 +1,4 @@ +import { useHotkeys } from "react-hotkeys-hook"; import { Ico } from "components/Ico"; import { PageSelector } from "components/Inputs/PageSelector"; import { atoms } from "contexts/atoms"; @@ -21,8 +22,14 @@ export const Paginator = ({ children, }: Props): JSX.Element => { useScrollTopOnChange(Ids.ContentPanel, [page]); + useHotkeys("left", () => onPageChange(page - 1), { enabled: page > 1 }, [page]); + useHotkeys("right", () => onPageChange(page + 1), { enabled: page < (totalNumberOfPages ?? 0) }, [ + page, + ]); + if (totalNumberOfPages === 0) return ; if (isUndefined(totalNumberOfPages) || totalNumberOfPages < 2) return <>{children}; + return ( <> { const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout); const { format } = useFormat(); + return (
; } -export const TableOfContents = ({ - text, - title, - onContentClicked, -}: TableOfContentsProps): JSX.Element => { +export const TableOfContents = ({ toc, onContentClicked }: TableOfContentsProps): JSX.Element => { const { format } = useFormat(); - const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title); return ( <> @@ -435,7 +429,14 @@ const markdawnHeadersParser = ( // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -const getTocFromMarkdawn = (text: string, title?: string): TocInterface => { +export const getTocFromMarkdawn = ( + markdawn: string | null | undefined, + title?: string +): TocInterface | undefined => { + if (isUndefined(markdawn)) return undefined; + + const text = preprocessMarkDawn(markdawn); + const toc: TocInterface = { title: title ?? "Return to top", slug: slugify(title), @@ -522,5 +523,6 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => { } }); + if (toc.children.length === 0) return undefined; return toc; }; diff --git a/src/components/Panels/SearchPopup.tsx b/src/components/Panels/SearchPopup.tsx index 4942154..0ad0aec 100644 --- a/src/components/Panels/SearchPopup.tsx +++ b/src/components/Panels/SearchPopup.tsx @@ -14,6 +14,7 @@ import { MeiliLibraryItem, MeiliPost, MeiliVideo, + MeiliWeapon, MeiliWikiPage, } from "shared/meilisearch-graphql-typings/meiliTypes"; import { getVideoThumbnailURL } from "helpers/videos"; @@ -43,6 +44,7 @@ export const SearchPopup = (): JSX.Element => { const [videos, setVideos] = useState>(); const [posts, setPosts] = useState>(); const [wikiPages, setWikiPages] = useState>(); + const [weapons, setWeapons] = useState>(); useEffect(() => { const fetchLibraryItems = async () => { @@ -129,6 +131,25 @@ export const SearchPopup = (): JSX.Element => { setPosts(searchResult); }; + const fetchWeapons = async () => { + const searchResult = await meiliSearch(MeiliIndices.WEAPON, query, { + limit: SEARCH_LIMIT, + attributesToRetrieve: ["*"], + attributesToHighlight: ["translations.description", "translations.names"], + attributesToCrop: ["translations.description"], + sort: ["slug:asc"], + }); + searchResult.hits = searchResult.hits.map((item) => { + if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { + item._formatted.translations = filterDefined(item._formatted.translations).filter( + (translation) => JSON.stringify(translation).includes("") + ); + } + return item; + }); + setWeapons(searchResult); + }; + const fetchWikiPages = async () => { const searchResult = await meiliSearch(MeiliIndices.WIKI_PAGE, query, { limit: SEARCH_LIMIT, @@ -160,12 +181,14 @@ export const SearchPopup = (): JSX.Element => { setContents(undefined); setVideos(undefined); setPosts(undefined); + setWeapons(undefined); } else { fetchWikiPages(); fetchLibraryItems(); fetchContents(); fetchVideos(); fetchPosts(); + fetchWeapons(); } }, [query]); @@ -411,6 +434,48 @@ export const SearchPopup = (): JSX.Element => {
)} + + {isDefined(weapons) && ( + +
+ {weapons.hits.map((item) => ( + ({ + language: language.data.attributes.code, + title: primaryName, + subtitle: aliases.join("・"), + description: containsHighlight(description) ? description : undefined, + }) + )} + fallback={{ title: prettySlug(item.slug) }} + thumbnail={item.thumbnail?.data?.attributes} + thumbnailAspectRatio="1/1" + thumbnailForceAspectRatio + thumbnailFitMethod="contain" + keepInfoVisible + topChips={ + item.type?.data?.attributes?.slug + ? [prettySlug(item.type.data.attributes.slug)] + : undefined + } + bottomChips={filterHasAttributes(item.categories, [ + "attributes.short", + ] as const).map((category) => category.attributes.short)} + /> + ))} +
+
+ )}
); @@ -442,7 +507,7 @@ const SearchResultSection = ({ className="grid grid-cols-[auto_1fr] place-items-center gap-6 px-6 py-4" href={href} onClick={() => setSearchOpened(false)}> - +

{title}

{isDefined(totalHits) && totalHits > SEARCH_LIMIT && ( diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx index cd793da..8999de7 100644 --- a/src/components/PostPage.tsx +++ b/src/components/PostPage.tsx @@ -2,7 +2,7 @@ import { Fragment, useCallback } from "react"; import { AppLayout, AppLayoutRequired } from "./AppLayout"; import { Chip } from "./Chip"; import { HorizontalLine } from "./HorizontalLine"; -import { Markdawn, TableOfContents } from "./Markdown/Markdawn"; +import { getTocFromMarkdawn, Markdawn, TableOfContents } from "./Markdown/Markdawn"; import { ReturnButton } from "./PanelComponents/ReturnButton"; import { ContentPanel } from "./Containers/ContentPanel"; import { SubPanel } from "./Containers/SubPanel"; @@ -70,56 +70,55 @@ export const PostPage = ({ const title = selectedTranslation?.title ?? prettySlug(post.slug); const excerpt = selectedTranslation?.excerpt ?? ""; - const subPanel = ( - - - {[ - returnHref && returnTitle && !is1ColumnLayout && ( - - ), + const toc = getTocFromMarkdawn(body, title); - displayCredits && ( - <> - {selectedTranslation && ( -
-

{format("status")}:

+ const subPanelElems = [ + returnHref && returnTitle && !is1ColumnLayout && ( + + ), - - - -
+ displayCredits && ( + <> + {selectedTranslation && ( +
+

{format("status")}:

+ + + + +
+ )} + + {post.authors && post.authors.data.length > 0 && ( +
+

{"Authors"}:

+
+ {filterHasAttributes(post.authors.data, ["id", "attributes"] as const).map( + (author) => ( + + + + ) )} +
+
+ )} + + ), - {post.authors && post.authors.data.length > 0 && ( -
-

{"Authors"}:

-
- {filterHasAttributes(post.authors.data, ["id", "attributes"] as const).map( - (author) => ( - - - - ) - )} -
-
- )} - - ), + displayToc && isDefined(toc) && ( + setSubPanelOpened(false)} /> + ), + ]; - displayToc && ( - setSubPanelOpened(false)} - /> - ), - ]} -
-
- ); + const subPanel = + subPanelElems.filter(Boolean).length > 0 ? ( + + {subPanelElems} + + ) : undefined; const contentPanel = ( diff --git a/src/components/PreviewCard.tsx b/src/components/PreviewCard.tsx index 1e1fe09..5b7708d 100644 --- a/src/components/PreviewCard.tsx +++ b/src/components/PreviewCard.tsx @@ -24,6 +24,7 @@ interface Props { thumbnail?: UploadImageFragment | string | null | undefined; thumbnailAspectRatio?: string; thumbnailForceAspectRatio?: boolean; + thumbnailFitMethod?: "contain" | "cover"; thumbnailRounded?: boolean; href: string; pre_title?: string | null | undefined; @@ -60,6 +61,7 @@ export const PreviewCard = ({ thumbnail, thumbnailAspectRatio = "4/3", thumbnailForceAspectRatio = false, + thumbnailFitMethod = "cover", thumbnailRounded = true, pre_title, title, @@ -133,7 +135,12 @@ export const PreviewCard = ({ thumbnailRounded, cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none") ), - cIf(thumbnailForceAspectRatio, "h-full w-full object-cover") + cIf(thumbnailForceAspectRatio, "h-full w-full"), + cIf( + thumbnailForceAspectRatio && thumbnailFitMethod === "contain", + "object-contain", + "object-cover" + ) )} src={thumbnail} quality={ImageQuality.Medium} diff --git a/src/graphql/icuParams.ts b/src/graphql/icuParams.ts index c3b1216..555bab1 100644 --- a/src/graphql/icuParams.ts +++ b/src/graphql/icuParams.ts @@ -179,4 +179,8 @@ export interface ICUParams { subitem_of_x: { x: Date | boolean | number | string }; variant_of_x: { x: Date | boolean | number | string }; dark_mode_extension_warning: never; + weapon: { count: number }; + weapons_description: never; + level_x: { x: Date | boolean | number | string }; + story_x: { x: Date | boolean | number | string }; } diff --git a/src/graphql/operations/getWeapon.graphql b/src/graphql/operations/getWeapon.graphql new file mode 100644 index 0000000..20565fa --- /dev/null +++ b/src/graphql/operations/getWeapon.graphql @@ -0,0 +1,92 @@ +query getWeapon($slug: String, $language_code: String) { + weaponStories(filters: { slug: { eq: $slug } }) { + data { + attributes { + ...sharedWeaponFragment + stories(pagination: { limit: -1 }) { + id + categories(pagination: { limit: -1 }) { + data { + id + attributes { + name + short + } + } + } + translations(pagination: { limit: -1 }) { + id + description + level_1 + level_2 + level_3 + level_4 + status + language { + data { + attributes { + code + } + } + } + } + } + weapon_group { + data { + attributes { + slug + weapons(pagination: { limit: -1 }, filters: { slug: { ne: $slug } }) { + data { + id + attributes { + ...sharedWeaponFragment + } + } + } + } + } + } + } + } + } +} + +fragment sharedWeaponFragment on WeaponStory { + type { + data { + id + attributes { + slug + translations(filters: { language: { code: { eq: $language_code } } }) { + name + language { + data { + attributes { + code + } + } + } + } + } + } + } + name(pagination: { limit: -1 }) { + id + name + language { + data { + attributes { + code + } + } + } + } + slug + thumbnail { + data { + attributes { + ...uploadImage + } + } + } +} diff --git a/src/graphql/operations/getWeaponsSlugs.graphql b/src/graphql/operations/getWeaponsSlugs.graphql new file mode 100644 index 0000000..bcd22c4 --- /dev/null +++ b/src/graphql/operations/getWeaponsSlugs.graphql @@ -0,0 +1,10 @@ +query getWeaponsSlugs { + weaponStories(pagination: { limit: -1 }) { + data { + id + attributes { + slug + } + } + } +} diff --git a/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql b/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql index d26bf19..0a519e5 100644 --- a/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql +++ b/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql @@ -186,6 +186,10 @@ query localDataGetWebsiteInterfaces { subitem_of_x variant_of_x dark_mode_extension_warning + weapon + weapons_description + level_x + story_x } } } diff --git a/src/hooks/useFormat.ts b/src/hooks/useFormat.ts index 1285b8f..ae72fe5 100644 --- a/src/hooks/useFormat.ts +++ b/src/hooks/useFormat.ts @@ -59,7 +59,13 @@ export const useFormat = (): { if (isDefinedAndNotEmpty(result)) { return result; } - return new IntlMessageFormat(fallbackLangui[key] ?? "").format(processedValues).toString(); + const fallback = new IntlMessageFormat(fallbackLangui[key] ?? "") + .format(processedValues) + .toString(); + if (isDefinedAndNotEmpty(fallback)) { + return fallback; + } + return key; }, [langui, fallbackLangui] ); diff --git a/src/hooks/useReaderSettings.ts b/src/hooks/useReaderSettings.ts index ac58a21..ecbee34 100644 --- a/src/hooks/useReaderSettings.ts +++ b/src/hooks/useReaderSettings.ts @@ -17,10 +17,10 @@ interface ReaderSettings extends FilterSettings { const DEFAULT_READER_SETTINGS: ReaderSettings = { bookFold: true, - lighting: true, + lighting: false, paperTexture: true, teint: 0.1, - dropShadow: true, + dropShadow: false, pageQuality: ImageQuality.Large, isSidePagesEnabled: true, }; diff --git a/src/pages/contents/[slug].tsx b/src/pages/contents/[slug].tsx index b82d96e..832f20a 100644 --- a/src/pages/contents/[slug].tsx +++ b/src/pages/contents/[slug].tsx @@ -4,7 +4,7 @@ import naturalCompare from "string-natural-compare"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Chip } from "components/Chip"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; -import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; +import { getTocFromMarkdawn, Markdawn, TableOfContents } from "components/Markdown/Markdawn"; import { TranslatedReturnButton } from "components/PanelComponents/ReturnButton"; import { ContentPanel } from "components/Containers/ContentPanel"; import { SubPanel } from "components/Containers/SubPanel"; @@ -91,6 +91,15 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => { }, }; + const toc = getTocFromMarkdawn( + selectedTranslation?.text_set?.text, + prettyInlineTitle( + selectedTranslation?.pre_title, + selectedTranslation?.title, + selectedTranslation?.subtitle + ) + ); + const subPanel = ( @@ -191,17 +200,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
), - selectedTranslation?.text_set?.text && ( - setSubPanelOpened(false)} - /> - ), + toc && setSubPanelOpened(false)} />, content.ranged_contents?.data && content.ranged_contents.data.length > 0 && (
diff --git a/src/pages/dev/editor.tsx b/src/pages/dev/editor.tsx index 07535f2..aed49c3 100644 --- a/src/pages/dev/editor.tsx +++ b/src/pages/dev/editor.tsx @@ -3,12 +3,13 @@ import { useCallback, useRef, useState } from "react"; import TurndownService from "turndown"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Button } from "components/Inputs/Button"; -import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; +import { getTocFromMarkdawn, Markdawn, TableOfContents } from "components/Markdown/Markdawn"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { Popup } from "components/Containers/Popup"; import { ToolTip } from "components/ToolTip"; import { getOpenGraph } from "helpers/openGraph"; import { getFormat } from "helpers/i18n"; +import { isDefined } from "helpers/asserts"; /* * ╭────────╮ @@ -155,6 +156,8 @@ const Editor = (props: Props): JSX.Element => { [transformationWrapper] ); + const toc = getTocFromMarkdawn(markdown); + const contentPanel = ( setConverterOpened(false)}> @@ -388,9 +391,11 @@ const Editor = (props: Props): JSX.Element => {
-
- -
+ {isDefined(toc) && ( +
+ +
+ )} ); diff --git a/src/pages/library/[slug]/index.tsx b/src/pages/library/[slug]/index.tsx index 2b267cf..1fdb080 100644 --- a/src/pages/library/[slug]/index.tsx +++ b/src/pages/library/[slug]/index.tsx @@ -47,13 +47,13 @@ import { useSmartLanguage } from "hooks/useSmartLanguage"; import { getOpenGraph } from "helpers/openGraph"; import { getDescription } from "helpers/description"; import { useIntersectionList } from "hooks/useIntersectionList"; -import { HorizontalLine } from "components/HorizontalLine"; import { Ids } from "types/ids"; import { atoms } from "contexts/atoms"; import { useAtomGetter, useAtomSetter } from "helpers/atoms"; import { Link } from "components/Inputs/Link"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; +import { ElementsSeparator } from "helpers/component"; /* * ╭─────────────╮ @@ -99,59 +99,69 @@ const LibrarySlug = ({ item, itemId, ...otherProps }: Props): JSX.Element => { (content) => content.attributes?.scan_set && content.attributes.scan_set.length > 0 ); + const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout); + const subPanel = ( - + + {[ + is3ColumnsLayout && ( + + ), +
+ - + {item.gallery && item.gallery.data.length > 0 && ( + + )} -
- + - {item.gallery && item.gallery.data.length > 0 && ( - - )} + {item.subitems && item.subitems.data.length > 0 && ( + + )} - - - {item.subitems && item.subitems.data.length > 0 && ( - - )} - - {item.contents && item.contents.data.length > 0 && ( - - )} -
+ {item.contents && item.contents.data.length > 0 && ( + + )} +
, + ]} +
); diff --git a/src/pages/news/index.tsx b/src/pages/news/index.tsx index ff245d4..443d1db 100644 --- a/src/pages/news/index.tsx +++ b/src/pages/news/index.tsx @@ -129,6 +129,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => { placeholder={format("search_title")} value={query} onChange={(name) => { + setPage(1); setQuery(name); if (isDefinedAndNotEmpty(name)) { sendAnalytics("News", "Change search term"); diff --git a/src/pages/wiki/chronology.tsx b/src/pages/wiki/chronology.tsx index 0bc1533..4c3c47c 100644 --- a/src/pages/wiki/chronology.tsx +++ b/src/pages/wiki/chronology.tsx @@ -1,5 +1,5 @@ import { GetStaticProps } from "next"; -import { Fragment, useCallback } from "react"; +import { Fragment, useCallback, useMemo } from "react"; import { useRouter } from "next/router"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { InsetBox } from "components/Containers/InsetBox"; @@ -27,6 +27,8 @@ import { useIntersectionList } from "hooks/useIntersectionList"; import { HorizontalLine } from "components/HorizontalLine"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; +import { useAtomSetter } from "helpers/atoms"; +import { atoms } from "contexts/atoms"; /* * ╭────────╮ @@ -40,8 +42,14 @@ interface Props extends AppLayoutRequired { const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props): JSX.Element => { const { format } = useFormat(); - const ids = filterHasAttributes(chronologyEras, ["attributes"] as const).map( - (era) => era.attributes.slug + const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened); + const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]); + const ids = useMemo( + () => + filterHasAttributes(chronologyEras, ["attributes"] as const).map( + (era) => era.attributes.slug + ), + [chronologyEras] ); const currentIntersection = useIntersectionList(ids); @@ -69,6 +77,7 @@ const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props): url={`#${era.attributes.slug}`} border active={currentIntersection === index} + onClick={closeSubPanel} /> ))} diff --git a/src/pages/wiki/index.tsx b/src/pages/wiki/index.tsx index 3a87483..d1e8e94 100644 --- a/src/pages/wiki/index.tsx +++ b/src/pages/wiki/index.tsx @@ -1,5 +1,5 @@ import { GetStaticProps } from "next"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useBoolean } from "usehooks-ts"; import { z } from "zod"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; @@ -24,6 +24,8 @@ import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/se import { Paginator } from "components/Containers/Paginator"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; +import { useAtomSetter } from "helpers/atoms"; +import { atoms } from "contexts/atoms"; /* * ╭─────────────╮ @@ -50,6 +52,8 @@ interface Props extends AppLayoutRequired {} const Wiki = (props: Props): JSX.Element => { const hoverable = useDeviceSupportsHover(); + const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened); + const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]); const { format } = useFormat(); const router = useTypedRouter(queryParamSchema); const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query); @@ -151,7 +155,18 @@ const Wiki = (props: Props): JSX.Element => {

{format("special_pages")}

- + + ); diff --git a/src/pages/wiki/weapons/[slug].tsx b/src/pages/wiki/weapons/[slug].tsx new file mode 100644 index 0000000..009ac97 --- /dev/null +++ b/src/pages/wiki/weapons/[slug].tsx @@ -0,0 +1,297 @@ +import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; +import { Fragment, useCallback, useMemo } from "react"; +import { AppLayout, AppLayoutRequired } from "components/AppLayout"; +import { getReadySdk } from "graphql/sdk"; +import { filterDefined, filterHasAttributes, isDefinedAndNotEmpty } from "helpers/asserts"; +import { getFormat } from "helpers/i18n"; +import { getOpenGraph } from "helpers/openGraph"; +import { ContentPanel } from "components/Containers/ContentPanel"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; +import { useFormat } from "hooks/useFormat"; +import { SubPanel } from "components/Containers/SubPanel"; +import { ThumbnailHeader } from "components/ThumbnailHeader"; +import { getDefaultPreferredLanguages } from "helpers/locales"; +import { prettySlug } from "helpers/formatters"; +import { useSmartLanguage } from "hooks/useSmartLanguage"; +import { Weapon, WeaponGroupPreview, WeaponStoryWithTranslations } from "types/types"; +import { InsetBox } from "components/Containers/InsetBox"; +import { Chip } from "components/Chip"; +import { Markdawn } from "components/Markdown/Markdawn"; +import { NavOption } from "components/PanelComponents/NavOption"; +import { HorizontalLine } from "components/HorizontalLine"; +import { useIntersectionList } from "hooks/useIntersectionList"; +import { ElementsSeparator } from "helpers/component"; +import { useAtomGetter } from "helpers/atoms"; +import { atoms } from "contexts/atoms"; +import { TranslatedPreviewCard } from "components/PreviewCard"; + +/* + * ╭────────╮ + * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── + */ + +interface Props extends AppLayoutRequired { + weapon: Weapon; + primaryName: string; + aliases: string[]; +} + +interface WeaponPreviewProps { + weapon: WeaponGroupPreview; +} + +const WeaponPreview = ({ weapon }: WeaponPreviewProps): JSX.Element => ( + ({ + language: language.data.attributes.code, + title: name, + }) + )} + fallback={{ title: prettySlug(weapon.slug) }} + thumbnail={weapon.thumbnail?.data?.attributes} + thumbnailAspectRatio="1/1" + thumbnailForceAspectRatio + thumbnailFitMethod="contain" + keepInfoVisible + topChips={ + weapon.type?.data?.attributes?.slug + ? [prettySlug(weapon.type.data.attributes.slug)] + : undefined + } + /> +); + +const WeaponPage = ({ weapon, primaryName, aliases, ...otherProps }: Props): JSX.Element => { + const { format } = useFormat(); + + const intersectionIds = useMemo( + () => filterDefined(weapon.stories).map(({ id }) => `story-${id}`), + [weapon.stories] + ); + const currentIntersection = useIntersectionList(intersectionIds); + const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout); + + const subPanel = ( + + + {[ + is3ColumnsLayout && ( + + ), + + + {intersectionIds.map((id, index) => ( + category.attributes?.name) + .join("・")} + active={currentIntersection === index} + border + /> + ))} + , + + weapon.weapon_group?.data?.attributes?.weapons?.data && ( + <> +

Weapon group

+

{`${ + weapon.weapon_group.data.attributes.weapons.data.length + } other weapons part of the ${prettySlug( + weapon.weapon_group.data.attributes.slug + )}'s group`}

+
+ {filterHasAttributes(weapon.weapon_group.data.attributes.weapons.data, [ + "attributes", + ] as const).map((groupWeapon) => ( + + ))} +
+ + ), + ]} +
+
+ ); + + const contentPanel = ( + + + + + + +
+ + {filterHasAttributes(weapon.stories, ["translations"] as const).map((story, index) => ( + + ))} + +
+
+ ); + + return ; +}; +export default WeaponPage; + +/* + * ╭──────────────────────╮ + * ───────────────────────────────────╯ NEXT DATA FETCHING ╰────────────────────────────────────── + */ + +export const getStaticProps: GetStaticProps = async (context) => { + const sdk = getReadySdk(); + const { format } = getFormat(context.locale); + const slug = context.params?.slug ? context.params.slug.toString() : ""; + const weaponResp = await sdk.getWeapon({ + slug: slug, + language_code: context.locale ?? "en", + }); + + const weapon = weaponResp.weaponStories?.data[0]?.attributes; + + if (!weapon?.stories || !context.locale || !context.locales) { + return { notFound: true }; + } + + const names = weapon.name; + const preferredLanguages = getDefaultPreferredLanguages(context.locale, context.locales); + + const [primaryName, ...aliases] = getFilteredNames(names, preferredLanguages); + + const props: Props = { + primaryName: primaryName ?? prettySlug(slug), + aliases, + // eslint-disable-next-line id-denylist + weapon, + openGraph: getOpenGraph(format, undefined, undefined, weapon.thumbnail?.data?.attributes), + }; + + return { + props: props, + }; +}; + +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +export const getStaticPaths: GetStaticPaths = async (context) => { + const sdk = getReadySdk(); + const weapons = await sdk.getWeaponsSlugs(); + const paths: GetStaticPathsResult["paths"] = []; + filterHasAttributes(weapons.weaponStories?.data, ["attributes"] as const).map((item) => { + context.locales?.map((local) => { + paths.push({ + params: { slug: item.attributes.slug }, + locale: local, + }); + }); + }); + return { + paths, + fallback: "blocking", + }; +}; + +/* + * ╭──────────────────────╮ + * ───────────────────────────────────╯ PRIVATE COMPONENTS ╰────────────────────────────────────── + */ + +interface WeaponStoryProps { + story: WeaponStoryWithTranslations; + storyNumber: number; + id?: string; +} + +const WeaponStory = ({ story, storyNumber, id }: WeaponStoryProps): JSX.Element => { + const { format } = useFormat(); + const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ + items: story.translations, + languageExtractor: useCallback( + (item: WeaponStoryProps["story"]["translations"][number]) => + item?.language?.data?.attributes?.code, + [] + ), + }); + + if (!selectedTranslation) return <>; + + return ( + +

{format("story_x", { x: storyNumber })}

+ + {languageSwitcherProps.locales.size > 1 && } + + {story.categories && story.categories.data.length > 0 && ( +
+ {filterHasAttributes(story.categories.data, ["attributes.name"] as const).map( + (category) => ( + + ) + )} +
+ )} + + {isDefinedAndNotEmpty(selectedTranslation.description) && ( +
+

{format("description")}

+ {selectedTranslation.description} +
+ )} +

{format("level_x", { x: 1 })}

+ + +

{format("level_x", { x: 2 })}

+ + +

{format("level_x", { x: 3 })}

+ + +

{format("level_x", { x: 4 })}

+ +
+ ); +}; + +/* + * ╭───────────────────╮ + * ─────────────────────────────────────╯ PRIVATE METHODS ╰─────────────────────────────────────── + */ + +export const getFilteredNames = ( + names: Props["weapon"]["name"], + preferredLanguages: string[] +): string[] => { + for (const language of preferredLanguages) { + const filteredNames = filterHasAttributes(names, ["name"] as const).filter( + (name) => name.language?.data?.attributes?.code === language + ); + if (filteredNames.length > 0) { + return filteredNames.map((name) => name.name); + } + } + return []; +}; diff --git a/src/pages/wiki/weapons/index.tsx b/src/pages/wiki/weapons/index.tsx new file mode 100644 index 0000000..b0b5ab3 --- /dev/null +++ b/src/pages/wiki/weapons/index.tsx @@ -0,0 +1,237 @@ +import { GetStaticProps } from "next"; +import { z } from "zod"; +import { useEffect, useState } from "react"; +import { useBoolean } from "usehooks-ts"; +import { AppLayout, AppLayoutRequired } from "components/AppLayout"; +import { getFormat } from "helpers/i18n"; +import { getOpenGraph } from "helpers/openGraph"; +import { SubPanel } from "components/Containers/SubPanel"; +import { PanelHeader } from "components/PanelComponents/PanelHeader"; +import { TextInput } from "components/Inputs/TextInput"; +import { useTypedRouter } from "hooks/useTypedRouter"; +import { useFormat } from "hooks/useFormat"; +import { + filterDefined, + filterHasAttributes, + isDefined, + isDefinedAndNotEmpty, +} from "helpers/asserts"; +import { sendAnalytics } from "helpers/analytics"; +import { Button } from "components/Inputs/Button"; +import { HorizontalLine } from "components/HorizontalLine"; +import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search"; +import { MeiliIndices, MeiliWeapon } from "shared/meilisearch-graphql-typings/meiliTypes"; +import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; +import { Paginator } from "components/Containers/Paginator"; +import { TranslatedPreviewCard } from "components/PreviewCard"; +import { prettySlug } from "helpers/formatters"; +import { useDeviceSupportsHover } from "hooks/useMediaQuery"; +import { WithLabel } from "components/Inputs/WithLabel"; +import { Switch } from "components/Inputs/Switch"; +import { ReturnButton } from "components/PanelComponents/ReturnButton"; + +/* + * ╭─────────────╮ + * ────────────────────────────────────────╯ CONSTANTS ╰────────────────────────────────────────── + */ + +const DEFAULT_FILTERS_STATE = { + query: "", + keepInfoVisible: true, + page: 1, +}; + +const queryParamSchema = z.object({ + query: z.coerce.string().optional(), + page: z.coerce.number().positive().optional(), +}); + +/* + * ╭────────╮ + * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── + */ + +interface Props extends AppLayoutRequired {} + +const Weapons = (props: Props): JSX.Element => { + const { format } = useFormat(); + const hoverable = useDeviceSupportsHover(); + const router = useTypedRouter(queryParamSchema); + + const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query); + const [page, setPage] = useState(router.query.page ?? DEFAULT_FILTERS_STATE.page); + + const { + value: keepInfoVisible, + toggle: toggleKeepInfoVisible, + setValue: setKeepInfoVisible, + } = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible); + + const [weapons, setWeapons] = useState>(); + + useEffect(() => { + const fetchPosts = async () => { + const searchResult = await meiliSearch(MeiliIndices.WEAPON, query, { + hitsPerPage: 25, + page, + attributesToRetrieve: ["*"], + attributesToHighlight: ["translations.description", "translations.names"], + attributesToCrop: ["translations.description"], + sort: ["slug:asc"], + }); + + searchResult.hits = searchResult.hits.map((item) => { + if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) { + item._formatted.translations = filterDefined(item._formatted.translations).filter( + (translation) => JSON.stringify(translation).includes("") + ); + } + return item; + }); + + setWeapons(searchResult); + }; + fetchPosts(); + }, [query, page]); + + useEffect(() => { + if (router.isReady) + router.updateQuery({ + page, + query, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [page, query, router.isReady]); + + useEffect(() => { + if (router.isReady) { + if (isDefined(router.query.page)) setPage(router.query.page); + if (isDefined(router.query.query)) setQuery(router.query.query); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router.isReady]); + + const subPanel = ( + + + + + + + { + setPage(1); + setQuery(name); + if (isDefinedAndNotEmpty(name)) { + sendAnalytics("Weapons", "Change search term"); + } else { + sendAnalytics("Weapons", "Clear search term"); + } + }} + /> + + {hoverable && ( + + { + toggleKeepInfoVisible(); + sendAnalytics("Weapons", `Always ${keepInfoVisible ? "hide" : "show"} info`); + }} + /> + + )} + +