There's a lot to unpack here
This commit is contained in:
parent
ba13c736b0
commit
ae25df8d72
|
@ -1,2 +1,9 @@
|
|||
*.js
|
||||
*.ts
|
||||
src/graphql/generated.ts
|
||||
.eslintrc.js
|
||||
graphql-codegen.config.js
|
||||
next-env.d.ts
|
||||
next-sitemap.config.js
|
||||
next.config.js
|
||||
postcss.config.js
|
||||
tailwind.config.js
|
||||
design.config.js
|
51
.eslintrc.js
51
.eslintrc.js
|
@ -7,6 +7,8 @@ module.exports = {
|
|||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"next/core-web-vitals",
|
||||
],
|
||||
rules: {
|
||||
|
@ -122,7 +124,7 @@ module.exports = {
|
|||
"prefer-exponentiation-operator": "warn",
|
||||
"prefer-named-capture-group": "warn",
|
||||
"prefer-numeric-literals": "warn",
|
||||
// "prefer-object-has-own": "warn",
|
||||
"prefer-object-has-own": "warn",
|
||||
"prefer-object-spread": "warn",
|
||||
"prefer-promise-reject-errors": "warn",
|
||||
"prefer-regex-literals": "warn",
|
||||
|
@ -214,5 +216,52 @@ module.exports = {
|
|||
|
||||
/* NEXTJS */
|
||||
"@next/next/no-img-element": "off",
|
||||
|
||||
/* IMPORTS */
|
||||
"import/no-unresolved": "error",
|
||||
"import/named": "error",
|
||||
"import/default": "error",
|
||||
"import/namespace": "error",
|
||||
"import/no-restricted-paths": "error",
|
||||
"import/no-absolute-path": "error",
|
||||
"import/no-dynamic-require": "error",
|
||||
// "import/no-internal-modules": "error",
|
||||
"import/no-webpack-loader-syntax": "error",
|
||||
"import/no-self-import": "error",
|
||||
"import/no-cycle": "error",
|
||||
"import/no-useless-path-segments": "error",
|
||||
// "import/no-relative-parent-imports": "error",
|
||||
"import/no-relative-packages": "error",
|
||||
|
||||
"import/export": "error",
|
||||
"import/no-named-as-default": "error",
|
||||
"import/no-named-as-default-member": "error",
|
||||
"import/no-deprecated": "error",
|
||||
"import/no-extraneous-dependencies": "error",
|
||||
"import/no-mutable-exports": "error",
|
||||
"import/no-unused-modules": "error",
|
||||
|
||||
"import/unambiguous": "error",
|
||||
"import/no-commonjs": "error",
|
||||
"import/no-amd": "error",
|
||||
"import/no-nodejs-modules": "error",
|
||||
"import/no-import-module-exports": "error",
|
||||
|
||||
"import/first": "error",
|
||||
// "import/exports-last": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"import/no-namespace": "error",
|
||||
"import/extensions": "error",
|
||||
"import/order": "warn",
|
||||
"import/newline-after-import": "error",
|
||||
// "import/prefer-default-export": "error",
|
||||
// "import/max-dependencies": "error",
|
||||
// "import/no-unassigned-import": "error",
|
||||
"import/no-named-default": "error",
|
||||
// "import/no-default-export": "error",
|
||||
// "import/no-named-export": "error",
|
||||
"import/no-anonymous-default-export": "error",
|
||||
// "import/group-exports": "error",
|
||||
// "import/dynamic-import-chunkname)": "error",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"react-dom": "18.2.0",
|
||||
"react-hot-keys": "^2.7.2",
|
||||
"react-swipeable": "^7.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"turndown": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -38,10 +39,10 @@
|
|||
"@typescript-eslint/parser": "^5.30.3",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-next": "12.2.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"graphql": "^16.5.0",
|
||||
"next-sitemap": "^3.1.7",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.0.0",
|
||||
"prettier-plugin-tailwindcss": "^0.1.11",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"ts-unused-exports": "^8.0.0",
|
||||
|
@ -1336,18 +1337,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-codegen/cli/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-codegen/cli/node_modules/mute-stream": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
|
@ -4414,6 +4403,29 @@
|
|||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
|
@ -5589,9 +5601,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
|
@ -6780,6 +6792,18 @@
|
|||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -7611,16 +7635,6 @@
|
|||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-organize-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"prettier": ">=2.0",
|
||||
"typescript": ">=2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-tailwindcss": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz",
|
||||
|
@ -8067,7 +8081,7 @@
|
|||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.0",
|
||||
|
@ -8175,6 +8189,15 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
|
@ -8193,15 +8216,6 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sponge-case": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz",
|
||||
|
@ -10052,12 +10066,6 @@
|
|||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
},
|
||||
"mute-stream": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
|
@ -12457,6 +12465,28 @@
|
|||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
|
@ -13362,9 +13392,9 @@
|
|||
}
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "6",
|
||||
|
@ -14271,6 +14301,12 @@
|
|||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -14806,13 +14842,6 @@
|
|||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier-plugin-organize-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"prettier-plugin-tailwindcss": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz",
|
||||
|
@ -15125,7 +15154,7 @@
|
|||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.23.0",
|
||||
|
@ -15212,6 +15241,12 @@
|
|||
"integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
|
@ -15225,14 +15260,6 @@
|
|||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sponge-case": {
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 12499",
|
||||
"precommit": "npm run prettier && npm run unused-exports && npm run lint && npm run tsc && npm run generate && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
|
||||
"precommit": "npm run prettier && npm run unused-exports && npm run eslint && npm run tsc && npm run generate && echo ALL PRECOMMIT CHECKS PASSED SUCCESSFULLY, LET\\'S FUCKING GO!",
|
||||
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated",
|
||||
"prebuild": "npm run generate",
|
||||
"build": "next build",
|
||||
"postbuild": "next-sitemap --config next-sitemap.config.js",
|
||||
"start": "next start -p 12500",
|
||||
"lint": "next lint",
|
||||
"eslint": "npx eslint .",
|
||||
"generate": "graphql-codegen --config graphql-codegen.config.js",
|
||||
"tsc": "tsc",
|
||||
"prettier": "prettier --end-of-line auto --write ."
|
||||
|
@ -31,6 +32,7 @@
|
|||
"react-dom": "18.2.0",
|
||||
"react-hot-keys": "^2.7.2",
|
||||
"react-swipeable": "^7.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"turndown": "^7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -47,10 +49,10 @@
|
|||
"@typescript-eslint/parser": "^5.30.3",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-next": "12.2.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"graphql": "^16.5.0",
|
||||
"next-sitemap": "^3.1.7",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.0.0",
|
||||
"prettier-plugin-tailwindcss": "^0.1.11",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"ts-unused-exports": "^8.0.0",
|
||||
|
|
|
@ -1,19 +1,3 @@
|
|||
import { Button } from "components/Inputs/Button";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { prettyLanguage, prettySlug } from "helpers/formatters";
|
||||
import { getOgImage, ImageQuality } from "helpers/img";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
isUndefined,
|
||||
iterateMap,
|
||||
} from "helpers/others";
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
|
||||
|
@ -26,6 +10,22 @@ import { TextInput } from "./Inputs/TextInput";
|
|||
import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder";
|
||||
import { MainPanel } from "./Panels/MainPanel";
|
||||
import { Popup } from "./Popup";
|
||||
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
isUndefined,
|
||||
iterateMap,
|
||||
} from "helpers/others";
|
||||
import { getOgImage, ImageQuality } from "helpers/img";
|
||||
import { prettyLanguage, prettySlug } from "helpers/formatters";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { cJoin } from "helpers/className";
|
||||
import { MouseEventHandler } from "react";
|
||||
import { cJoin } from "helpers/className";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -31,6 +31,7 @@ export const Ico = ({ onClick, icon, className }: Props): JSX.Element => (
|
|||
* ─────────────────────────────────────────╯ OTHER ╰─────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
/* eslint-disable max-len */
|
||||
export enum Icon {
|
||||
Onek = "1k",
|
||||
OnekPlus = "1k_plus",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img";
|
||||
import { ImageProps } from "next/image";
|
||||
import { MouseEventHandler } from "react";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useRouter } from "next/router";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { ConditionalWrapper, Wrapper } from "helpers/component";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -22,6 +22,7 @@ interface Props {
|
|||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
draggable?: boolean;
|
||||
badgeNumber?: number;
|
||||
size?: "normal" | "small";
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
@ -38,6 +39,7 @@ export const Button = ({
|
|||
href,
|
||||
locale,
|
||||
badgeNumber,
|
||||
size = "normal",
|
||||
}: Props): JSX.Element => {
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -70,13 +72,17 @@ export const Button = ({
|
|||
"!border-black bg-black !text-light drop-shadow-black-lg",
|
||||
"cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
|
||||
),
|
||||
cIf(size === "small", "px-3 py-1 text-xs"),
|
||||
className
|
||||
)}
|
||||
>
|
||||
{isDefined(badgeNumber) && (
|
||||
<div
|
||||
className="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"
|
||||
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`,
|
||||
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
|
||||
)}
|
||||
>
|
||||
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
|
||||
</div>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Button } from "./Button";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { ConditionalWrapper, Wrapper } from "helpers/component";
|
||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { Button } from "./Button";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Fragment } from "react";
|
||||
import { ToolTip } from "../ToolTip";
|
||||
import { Button } from "./Button";
|
||||
import { Icon } from "components/Ico";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { prettyLanguage } from "helpers/formatters";
|
||||
import { iterateMap } from "helpers/others";
|
||||
import { Fragment } from "react";
|
||||
import { ToolTip } from "../ToolTip";
|
||||
import { Button } from "./Button";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -18,6 +18,7 @@ interface Props {
|
|||
locales: Map<string, number>;
|
||||
localesIndex: number | undefined;
|
||||
onLanguageChanged: (index: number) => void;
|
||||
size?: Parameters<typeof Button>[0]["size"];
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
@ -27,6 +28,7 @@ export const LanguageSwitcher = ({
|
|||
locales,
|
||||
localesIndex,
|
||||
languages,
|
||||
size,
|
||||
onLanguageChanged,
|
||||
}: Props): JSX.Element => (
|
||||
<ToolTip
|
||||
|
@ -47,6 +49,7 @@ export const LanguageSwitcher = ({
|
|||
<Button
|
||||
badgeNumber={locales.size > 1 ? locales.size : undefined}
|
||||
icon={Icon.Translate}
|
||||
size={size}
|
||||
/>
|
||||
</ToolTip>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Fragment, useCallback, useState } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others";
|
||||
import { Fragment, useCallback, useState } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Dispatch, SetStateAction } from "react";
|
||||
import { ButtonGroup } from "./ButtonGroup";
|
||||
import { Icon } from "components/Ico";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { Button } from "./Button";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -23,19 +23,21 @@ export const PageSelector = ({
|
|||
maxPage,
|
||||
className,
|
||||
}: Props): JSX.Element => (
|
||||
<div className={cJoin("flex flex-row place-content-center", className)}>
|
||||
<Button
|
||||
onClick={() => setPage((current) => (page > 0 ? current - 1 : current))}
|
||||
className="rounded-r-none"
|
||||
icon={Icon.NavigateBefore}
|
||||
<ButtonGroup
|
||||
className={cJoin("flex flex-row place-content-center", className)}
|
||||
buttonsProps={[
|
||||
{
|
||||
onClick: () => setPage((current) => (page > 0 ? current - 1 : current)),
|
||||
icon: Icon.NavigateBefore,
|
||||
},
|
||||
{
|
||||
text: (page + 1).toString(),
|
||||
},
|
||||
{
|
||||
onClick: () =>
|
||||
setPage((current) => (page < maxPage ? page + 1 : current)),
|
||||
icon: Icon.NavigateNext,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Button className="rounded-none border-x-0" text={(page + 1).toString()} />
|
||||
<Button
|
||||
onClick={() =>
|
||||
setPage((current) => (page < maxPage ? page + 1 : current))
|
||||
}
|
||||
className="rounded-l-none"
|
||||
icon={Icon.NavigateNext}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Dispatch, Fragment, SetStateAction, useState } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { Dispatch, Fragment, SetStateAction, useState } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Dispatch, SetStateAction } from "react";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Dispatch, SetStateAction } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
@ -14,7 +15,6 @@ import {
|
|||
isDefinedAndNotEmpty,
|
||||
} from "helpers/others";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -139,7 +139,7 @@ export const ScanSet = ({
|
|||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
||||
<div className="grid place-content-center place-items-center">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedScan.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
|
@ -150,7 +150,8 @@ export const ScanSet = ({
|
|||
|
||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Scanners"}:</p>
|
||||
{/* TODO: Add Scanner to langui */}
|
||||
<p className="font-headers font-bold">{"Scanners"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.scanners.data).map(
|
||||
(scanner) => (
|
||||
|
@ -168,7 +169,8 @@ export const ScanSet = ({
|
|||
|
||||
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Cleaners"}:</p>
|
||||
{/* TODO: Add Cleaners to langui */}
|
||||
<p className="font-headers font-bold">{"Cleaners"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.cleaners.data).map(
|
||||
(cleaner) => (
|
||||
|
@ -187,7 +189,8 @@ export const ScanSet = ({
|
|||
{selectedScan.typesetters &&
|
||||
selectedScan.typesetters.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Typesetters"}:</p>
|
||||
{/* TODO: Add Cleaners to Typesetters */}
|
||||
<p className="font-headers font-bold">{"Typesetters"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data).map(
|
||||
(typesetter) => (
|
||||
|
@ -205,6 +208,7 @@ export const ScanSet = ({
|
|||
|
||||
{isDefinedAndNotEmpty(selectedScan.notes) && (
|
||||
<ToolTip content={selectedScan.notes}>
|
||||
{/* TODO: Add Notes to Typesetters */}
|
||||
<Chip>{"Notes"}</Chip>
|
||||
</ToolTip>
|
||||
)}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
import { RecorderChip } from "components/RecorderChip";
|
||||
|
@ -10,7 +11,6 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
|
|||
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -75,7 +75,8 @@ export const ScanSetCover = ({
|
|||
className="flex flex-row flex-wrap place-items-center
|
||||
gap-6 pt-10 text-base first-of-type:pt-0"
|
||||
>
|
||||
<h2 id="cover" className="text-2xl">
|
||||
<h2 id={"cover"} className="text-2xl">
|
||||
{/* TODO: Add Cover to langui */}
|
||||
{"Cover"}
|
||||
</h2>
|
||||
|
||||
|
@ -91,7 +92,7 @@ export const ScanSetCover = ({
|
|||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
||||
<div className="grid place-content-center place-items-center">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedScan.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
|
@ -102,7 +103,8 @@ export const ScanSetCover = ({
|
|||
|
||||
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Scanners"}:</p>
|
||||
{/* TODO: Add Scanner to langui */}
|
||||
<p className="font-headers font-bold">{"Scanners"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.scanners.data).map(
|
||||
(scanner) => (
|
||||
|
@ -120,7 +122,8 @@ export const ScanSetCover = ({
|
|||
|
||||
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Cleaners"}:</p>
|
||||
{/* TODO: Add Cleaners to langui */}
|
||||
<p className="font-headers font-bold">{"Cleaners"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.cleaners.data).map(
|
||||
(cleaner) => (
|
||||
|
@ -139,7 +142,8 @@ export const ScanSetCover = ({
|
|||
{selectedScan.typesetters &&
|
||||
selectedScan.typesetters.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Typesetters"}:</p>
|
||||
{/* TODO: Add Cleaners to Typesetters */}
|
||||
<p className="font-headers font-bold">{"Typesetters"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(selectedScan.typesetters.data).map(
|
||||
(typesetter) => (
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import Markdown from "markdown-to-jsx";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { Fragment, useMemo } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { Img } from "components/Img";
|
||||
|
@ -10,10 +14,6 @@ import { slugify } from "helpers/formatters";
|
|||
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { useLightBox } from "hooks/useLightBox";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { Fragment, useMemo } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -458,7 +458,7 @@ const preprocessMarkDawn = (text: string): string => {
|
|||
.split("\n")
|
||||
.map((line) => {
|
||||
if (line === "* * *" || line === "---") {
|
||||
scenebreakIndex += 1;
|
||||
scenebreakIndex++;
|
||||
return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
|
||||
}
|
||||
|
||||
|
@ -506,7 +506,7 @@ const markdawnHeadersParser = (
|
|||
let index = 2;
|
||||
while (visitedSlugs.includes(newSlug)) {
|
||||
newSlug = `${slug}-${index}`;
|
||||
index += 1;
|
||||
index++;
|
||||
}
|
||||
visitedSlugs.push(newSlug);
|
||||
return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`;
|
||||
|
@ -543,7 +543,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
|||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h2 += 1;
|
||||
h2++;
|
||||
h3 = -1;
|
||||
h4 = -1;
|
||||
h5 = -1;
|
||||
|
@ -554,7 +554,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
|||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h3 += 1;
|
||||
h3++;
|
||||
h4 = -1;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
|
@ -564,7 +564,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
|||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h4 += 1;
|
||||
h4++;
|
||||
h5 = -1;
|
||||
scenebreak = 0;
|
||||
} else if (h4 >= 0 && line.startsWith("<h5 id=")) {
|
||||
|
@ -573,7 +573,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
|||
slug: getSlug(line),
|
||||
children: [],
|
||||
});
|
||||
h5 += 1;
|
||||
h5++;
|
||||
scenebreak = 0;
|
||||
} else if (h5 >= 0 && line.startsWith("<h6 id=")) {
|
||||
toc.children[h2].children[h3].children[h4].children[h5].children.push({
|
||||
|
@ -582,8 +582,8 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
|
|||
children: [],
|
||||
});
|
||||
} else if (line.startsWith(`<SceneBreak`)) {
|
||||
scenebreak += 1;
|
||||
scenebreakIndex += 1;
|
||||
scenebreak++;
|
||||
scenebreakIndex++;
|
||||
|
||||
const child = {
|
||||
title: `Scene break ${scenebreak}`,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { MouseEventHandler, useMemo } from "react";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { cJoin, cIf } from "helpers/className";
|
||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||
import { useRouter } from "next/router";
|
||||
import { MouseEventHandler, useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Markdown from "markdown-to-jsx";
|
||||
import Link from "next/link";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
|
@ -6,8 +8,6 @@ import { useAppLayout } from "contexts/AppLayoutContext";
|
|||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { useMediaDesktop } from "hooks/useMediaQuery";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import Link from "next/link";
|
||||
import { Icon } from "components/Ico";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { Dispatch, SetStateAction, useEffect } from "react";
|
||||
import Hotkeys from "react-hot-keys";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
import { PostWithTranslations } from "helpers/types";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { AppLayout } from "./AppLayout";
|
||||
import { Chip } from "./Chip";
|
||||
|
@ -15,6 +9,12 @@ import { SubPanel } from "./Panels/SubPanel";
|
|||
import { RecorderChip } from "./RecorderChip";
|
||||
import { ThumbnailHeader } from "./ThumbnailHeader";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { PostWithTranslations } from "helpers/types";
|
||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { getDescription } from "helpers/description";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -95,7 +95,7 @@ export const PostPage = ({
|
|||
<>
|
||||
{selectedTranslation && (
|
||||
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(
|
||||
|
@ -111,7 +111,7 @@ export const PostPage = ({
|
|||
|
||||
{post.authors && post.authors.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{"Authors"}:</p>
|
||||
<p className="font-headers font-bold">{"Authors"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
{filterHasAttributes(post.authors.data).map((author) => (
|
||||
<Fragment key={author.id}>
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import Link from "next/link";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { Ico, Icon } from "./Ico";
|
||||
import { Img } from "./Img";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import {
|
||||
DatePickerFragment,
|
||||
|
@ -15,11 +20,6 @@ import {
|
|||
} from "helpers/formatters";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { Ico, Icon } from "./Ico";
|
||||
import { Img } from "./Img";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -212,7 +212,7 @@ export const PreviewCard = ({
|
|||
>
|
||||
<Ico
|
||||
icon={Icon.PlayCircleOutline}
|
||||
className="text-6xl opacity-0 transition-opacity group-hover:opacity-100"
|
||||
className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -271,7 +271,7 @@ export const PreviewCard = ({
|
|||
<p className="mb-1 break-words leading-none">{pre_title}</p>
|
||||
)}
|
||||
{title && (
|
||||
<p className="break-words font-headers font-bold text-lg leading-none">
|
||||
<p className="break-words font-headers text-lg font-bold leading-none">
|
||||
{title}
|
||||
</p>
|
||||
)}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Link from "next/link";
|
||||
import { useCallback } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { Img } from "./Img";
|
||||
import { UploadImageFragment } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import Link from "next/link";
|
||||
import { useCallback } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { Img } from "./Img";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -59,7 +59,9 @@ 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 leading-none">{title}</p>
|
||||
<p className="font-headers text-lg font-bold leading-none">
|
||||
{title}
|
||||
</p>
|
||||
)}
|
||||
{subtitle && <p className="leading-none">{subtitle}</p>}
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Fragment } from "react";
|
||||
import { Img } from "./Img";
|
||||
import { Markdawn } from "./Markdown/Markdawn";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
import { Chip } from "components/Chip";
|
||||
import { RecorderChipFragment } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { filterHasAttributes } from "helpers/others";
|
||||
import { Fragment } from "react";
|
||||
import { Img } from "./Img";
|
||||
import { Markdawn } from "./Markdown/Markdawn";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
import { Fragment, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Chip } from "./Chip";
|
||||
import { PageSelector } from "./Inputs/PageSelector";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { isDefined, isDefinedAndNotEmpty, iterateMap } from "helpers/others";
|
||||
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
|
||||
interface Props<T> {
|
||||
// Items
|
||||
items: T[];
|
||||
getItemId: (item: T) => string;
|
||||
renderItem: (props: { item: T }) => JSX.Element;
|
||||
renderWhenEmpty?: () => JSX.Element;
|
||||
// Pagination
|
||||
paginationItemPerPage?: number;
|
||||
paginationSelectorTop?: boolean;
|
||||
paginationSelectorBottom?: boolean;
|
||||
paginationScroolTop?: boolean;
|
||||
// Searching
|
||||
searchingTerm?: string;
|
||||
searchingBy?: (item: T) => string;
|
||||
searchingCaseInsensitive?: boolean;
|
||||
// Grouping
|
||||
groupingFunction?: (item: T) => string[];
|
||||
groupSortingFunction?: (a: string, b: string) => number;
|
||||
// Filtering
|
||||
filteringFunction?: (item: T) => boolean;
|
||||
// Sorting
|
||||
sortingFunction?: (a: T, b: T) => number;
|
||||
// Other
|
||||
className?: string;
|
||||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
export const SmartList = <T,>({
|
||||
items,
|
||||
getItemId,
|
||||
renderItem: RenderItem,
|
||||
renderWhenEmpty: RenderWhenEmpty,
|
||||
paginationItemPerPage = 0,
|
||||
paginationSelectorTop = true,
|
||||
paginationSelectorBottom = true,
|
||||
paginationScroolTop = true,
|
||||
searchingTerm,
|
||||
searchingBy,
|
||||
searchingCaseInsensitive = true,
|
||||
groupingFunction = () => [""],
|
||||
groupSortingFunction = (a, b) => a.localeCompare(b),
|
||||
filteringFunction = () => true,
|
||||
sortingFunction = () => 0,
|
||||
className,
|
||||
langui,
|
||||
}: Props<T>): JSX.Element => {
|
||||
const [page, setPage] = useState(0);
|
||||
useScrollTopOnChange(AnchorIds.ContentPanel, [page], paginationScroolTop);
|
||||
|
||||
type Group = Map<string, T[]>;
|
||||
|
||||
useEffect(() => setPage(0), [searchingTerm]);
|
||||
|
||||
const searchFilter = useCallback(() => {
|
||||
if (isDefinedAndNotEmpty(searchingTerm) && isDefined(searchingBy)) {
|
||||
if (searchingCaseInsensitive) {
|
||||
return items.filter((item) =>
|
||||
searchingBy(item).toLowerCase().includes(searchingTerm.toLowerCase())
|
||||
);
|
||||
}
|
||||
return items.filter((item) => searchingBy(item).includes(searchingTerm));
|
||||
}
|
||||
return items;
|
||||
}, [items, searchingBy, searchingCaseInsensitive, searchingTerm]);
|
||||
|
||||
const filteredItems = useMemo(() => {
|
||||
const filteredBySearch = searchFilter();
|
||||
return filteredBySearch.filter(filteringFunction);
|
||||
}, [filteringFunction, searchFilter]);
|
||||
|
||||
const sortedItem = useMemo(
|
||||
() => filteredItems.sort(sortingFunction),
|
||||
[filteredItems, sortingFunction]
|
||||
);
|
||||
|
||||
const paginatedItems = useMemo(() => {
|
||||
if (paginationItemPerPage > 0) {
|
||||
const memo = [];
|
||||
for (
|
||||
let index = 0;
|
||||
paginationItemPerPage * index < sortedItem.length;
|
||||
index++
|
||||
) {
|
||||
memo.push(
|
||||
sortedItem.slice(
|
||||
index * paginationItemPerPage,
|
||||
(index + 1) * paginationItemPerPage
|
||||
)
|
||||
);
|
||||
}
|
||||
return memo;
|
||||
}
|
||||
return [sortedItem];
|
||||
}, [paginationItemPerPage, sortedItem]);
|
||||
|
||||
const groupedList = useMemo(() => {
|
||||
const groups: Group = new Map();
|
||||
paginatedItems[page]?.forEach((item) => {
|
||||
groupingFunction(item).forEach((category) => {
|
||||
if (groups.has(category)) {
|
||||
groups.get(category)?.push(item);
|
||||
} else {
|
||||
groups.set(category, [item]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return groups;
|
||||
}, [groupingFunction, page, paginatedItems]);
|
||||
|
||||
const pageCount = useMemo(
|
||||
() =>
|
||||
paginationItemPerPage > 0
|
||||
? Math.floor(filteredItems.length / paginationItemPerPage)
|
||||
: 1,
|
||||
[paginationItemPerPage, filteredItems.length]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{pageCount > 1 && paginationSelectorTop && (
|
||||
<PageSelector
|
||||
maxPage={pageCount}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
className="mb-12"
|
||||
/>
|
||||
)}
|
||||
|
||||
{groupedList.size > 0
|
||||
? iterateMap(
|
||||
groupedList,
|
||||
(name, groupItems) =>
|
||||
groupItems.length > 0 && (
|
||||
<Fragment key={name}>
|
||||
{name.length > 0 && (
|
||||
<h2
|
||||
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
||||
first-of-type:pt-0"
|
||||
>
|
||||
{name}
|
||||
<Chip>{`${groupItems.length} ${
|
||||
groupItems.length <= 1
|
||||
? langui.result?.toLowerCase() ?? ""
|
||||
: langui.results?.toLowerCase() ?? ""
|
||||
}`}</Chip>
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
className={cJoin(
|
||||
`grid items-start gap-8 border-b-[3px] border-dotted pb-12
|
||||
last-of-type:border-0 mobile:gap-4`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{groupItems.map((item) => (
|
||||
<RenderItem item={item} key={getItemId(item)} />
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
),
|
||||
([a], [b]) => groupSortingFunction(a, b)
|
||||
)
|
||||
: isDefined(RenderWhenEmpty) && <RenderWhenEmpty />}
|
||||
|
||||
{pageCount > 1 && paginationSelectorBottom && (
|
||||
<PageSelector
|
||||
maxPage={pageCount}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
className="mt-12"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
// eslint-disable-next-line import/named
|
||||
import Tippy, { TippyProps } from "@tippyjs/react";
|
||||
import { cJoin } from "helpers/className";
|
||||
import "tippy.js/animations/scale-subtle.css";
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Fragment } from "react";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
|
@ -12,7 +13,10 @@ import {
|
|||
getStatusDescription,
|
||||
} from "helpers/others";
|
||||
|
||||
import { Fragment } from "react";
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
item: NonNullable<GetChronologyItemsQuery["chronologyItems"]>["data"][number];
|
||||
|
@ -20,6 +24,8 @@ interface Props {
|
|||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ChronologyItemComponent = ({
|
||||
langui,
|
||||
item,
|
||||
|
@ -124,6 +130,11 @@ export const ChronologyItemComponent = ({
|
|||
return <></>;
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭───────────────────╮
|
||||
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
||||
*/
|
||||
|
||||
const generateAnchor = (
|
||||
year: number | undefined,
|
||||
month: number | null | undefined,
|
||||
|
|
|
@ -2,6 +2,11 @@ import { ChronologyItemComponent } from "components/Wiki/Chronology/ChronologyIt
|
|||
import { GetChronologyItemsQuery } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
year: number;
|
||||
items: NonNullable<
|
||||
|
@ -10,6 +15,8 @@ interface Props {
|
|||
langui: AppStaticProps["langui"];
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const ChronologyYearComponent = ({
|
||||
langui,
|
||||
year,
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { useCallback } from "react";
|
||||
import { Chip } from "components/Chip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getStatusDescription } from "helpers/others";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { useCallback } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ───────────────────────────────────────╯ COMPONENT ╰───────────────────────────────────────────
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
source?: string;
|
||||
|
@ -15,14 +20,18 @@ interface Props {
|
|||
languages: AppStaticProps["languages"];
|
||||
langui: AppStaticProps["langui"];
|
||||
index: number;
|
||||
categories: string[];
|
||||
}
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
const DefinitionCard = ({
|
||||
source,
|
||||
translations = [],
|
||||
languages,
|
||||
langui,
|
||||
index,
|
||||
categories,
|
||||
}: Props): JSX.Element => {
|
||||
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||
useSmartLanguage({
|
||||
|
@ -36,24 +45,51 @@ const DefinitionCard = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex place-items-center gap-2">
|
||||
<p className="font-headers text-lg">{`${langui.definition} ${index}`}</p>
|
||||
<div className="flex flex-wrap place-items-center gap-2">
|
||||
<p className="font-headers text-lg font-bold">{`${langui.definition} ${index}`}</p>
|
||||
|
||||
{translations.length > 1 && (
|
||||
<>
|
||||
<Separator />
|
||||
<LanguageSwitcher {...languageSwitcherProps} size={"small"} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedTranslation?.status && (
|
||||
<>
|
||||
<Separator />
|
||||
<ToolTip
|
||||
content={getStatusDescription(selectedTranslation.status, langui)}
|
||||
maxWidth={"20rem"}
|
||||
>
|
||||
<Chip>{selectedTranslation.status}</Chip>
|
||||
</ToolTip>
|
||||
</>
|
||||
)}
|
||||
{translations.length > 1 && (
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
|
||||
{categories.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex flex-row gap-1">
|
||||
{categories.map((category, categoryIndex) => (
|
||||
<Chip key={categoryIndex}>{category}</Chip>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="italic">{`${langui.source}: ${source}`}</p>
|
||||
|
||||
<p>{selectedTranslation?.definition}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DefinitionCard;
|
||||
|
||||
/*
|
||||
* ╭──────────────────────╮
|
||||
* ───────────────────────────────────╯ PRIVATE COMPONENTS ╰──────────────────────────────────────
|
||||
*/
|
||||
|
||||
const Separator = () => <div className="mx-1 h-5 w-[1px] bg-dark" />;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { ReactNode, useContext, useState } from "react";
|
||||
import { isDefined } from "helpers/others";
|
||||
import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types";
|
||||
import { useDarkMode } from "hooks/useDarkMode";
|
||||
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
|
||||
import React, { ReactNode, useContext, useState } from "react";
|
||||
|
||||
export interface AppLayoutState {
|
||||
interface AppLayoutState {
|
||||
subPanelOpen: boolean | undefined;
|
||||
toggleSubPanelOpen: () => void;
|
||||
setSubPanelOpen: React.Dispatch<
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GetStaticPropsContext } from "next";
|
||||
import {
|
||||
GetCurrenciesQuery,
|
||||
GetLanguagesQuery,
|
||||
|
@ -5,8 +6,6 @@ import {
|
|||
} from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
|
||||
import { GetStaticPropsContext } from "next";
|
||||
|
||||
export type AppStaticProps = {
|
||||
langui: NonNullable<
|
||||
NonNullable<
|
||||
|
@ -21,7 +20,7 @@ export const getAppStaticProps = async (
|
|||
context: GetStaticPropsContext
|
||||
): Promise<AppStaticProps> => {
|
||||
const sdk = getReadySdk();
|
||||
const languages = (await sdk.getLanguages()).languages;
|
||||
const { languages } = await sdk.getLanguages();
|
||||
|
||||
if (languages?.data) {
|
||||
languages.data.sort((a, b) =>
|
||||
|
@ -31,7 +30,7 @@ export const getAppStaticProps = async (
|
|||
);
|
||||
}
|
||||
|
||||
const currencies = (await sdk.getCurrencies()).currencies;
|
||||
const { currencies } = await sdk.getCurrencies();
|
||||
if (currencies?.data) {
|
||||
currencies.data.sort((a, b) =>
|
||||
a.attributes && b.attributes
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { PostWithTranslations } from "helpers/types";
|
||||
import { GetStaticProps, GetStaticPropsContext } from "next";
|
||||
import { GetStaticProps } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps";
|
||||
import { getReadySdk } from "./sdk";
|
||||
import { PostWithTranslations } from "helpers/types";
|
||||
|
||||
export interface PostStaticProps extends AppStaticProps {
|
||||
post: PostWithTranslations;
|
||||
}
|
||||
|
||||
export const getPostStaticProps = (slug: string): GetStaticProps => {
|
||||
return async (context) => {
|
||||
export const getPostStaticProps =
|
||||
(slug: string): GetStaticProps =>
|
||||
async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const post = await sdk.getPost({
|
||||
slug: slug,
|
||||
|
@ -25,4 +26,3 @@ export const getPostStaticProps = (slug: string): GetStaticProps => {
|
|||
}
|
||||
return { notFound: true };
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
query getPostsPreview($language_code: String) {
|
||||
query getPostsPreview {
|
||||
posts(filters: { hidden: { eq: false } }) {
|
||||
data {
|
||||
id
|
||||
|
@ -22,7 +22,14 @@ query getPostsPreview($language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
translations(filters: { language: { code: { eq: $language_code } } }) {
|
||||
translations {
|
||||
language {
|
||||
data {
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
title
|
||||
excerpt
|
||||
thumbnail {
|
||||
|
|
|
@ -77,6 +77,15 @@ query getWikiPage($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
translations {
|
||||
language {
|
||||
data {
|
||||
|
@ -85,7 +94,6 @@ query getWikiPage($slug: String, $language_code: String) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
source_language {
|
||||
data {
|
||||
attributes {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { GraphQLClient } from "graphql-request";
|
||||
import { getSdk } from "graphql/generated";
|
||||
|
||||
export const getReadySdk = () => {
|
||||
export const getReadySdk = (): ReturnType<typeof getSdk> => {
|
||||
const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", {
|
||||
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
|
||||
});
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
export const cIf = (
|
||||
condition: boolean | null | undefined | string,
|
||||
condition: boolean | string | null | undefined,
|
||||
ifTrueCss: string,
|
||||
ifFalseCss?: string
|
||||
) => {
|
||||
return condition ? ifTrueCss : ifFalseCss ?? "";
|
||||
};
|
||||
): string => (condition ? ifTrueCss : ifFalseCss ?? "");
|
||||
|
||||
export const cJoin = (...args: (string | undefined)[]): string => {
|
||||
return args.filter((elem) => elem).join(" ");
|
||||
};
|
||||
export const cJoin = (...args: (string | undefined)[]): string =>
|
||||
args.filter((elem) => elem).join(" ");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { prettySlug } from "./formatters";
|
||||
import { isDefined } from "./others";
|
||||
import { Content } from "./types";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
interface Description {
|
||||
langui: AppStaticProps["langui"];
|
||||
|
@ -49,13 +49,11 @@ export const getDescription = ({
|
|||
return description;
|
||||
};
|
||||
|
||||
const prettyMarkdown = (markdown: string): string => {
|
||||
return markdown.replace(/[*]/gu, "").replace(/[_]/gu, "");
|
||||
};
|
||||
const prettyMarkdown = (markdown: string): string =>
|
||||
markdown.replace(/[*]/gu, "").replace(/[_]/gu, "");
|
||||
|
||||
const prettyChip = (items: (string | undefined)[]): string => {
|
||||
return items
|
||||
const prettyChip = (items: (string | undefined)[]): string =>
|
||||
items
|
||||
.filter((item) => isDefined(item))
|
||||
.map((item) => `(${item})`)
|
||||
.join(" ");
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
||||
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
||||
import { convertPrice } from "./numbers";
|
||||
import { isDefinedAndNotEmpty } from "./others";
|
||||
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
||||
|
||||
export const prettyDate = (datePicker: DatePickerFragment): string => {
|
||||
let result = "";
|
||||
|
@ -20,7 +21,7 @@ export const prettyPrice = (
|
|||
if (!targetCurrencyCode) return "";
|
||||
let result = "";
|
||||
currencies.map((currency) => {
|
||||
if (currency?.attributes?.code === targetCurrencyCode) {
|
||||
if (currency.attributes?.code === targetCurrencyCode) {
|
||||
const amountInTargetCurrency = convertPrice(pricePicker, currency);
|
||||
result =
|
||||
currency.attributes.symbol +
|
||||
|
@ -34,20 +35,20 @@ export const prettyPrice = (
|
|||
};
|
||||
|
||||
export const prettySlug = (slug?: string, parentSlug?: string): string => {
|
||||
if (slug) {
|
||||
if (parentSlug && slug.startsWith(parentSlug))
|
||||
slug = slug.substring(parentSlug.length + 1);
|
||||
slug = slug.replace(new RegExp("-", "g"), " ");
|
||||
slug = slug.replace(new RegExp("_", "g"), " ");
|
||||
return capitalizeString(slug);
|
||||
let newSlug = slug;
|
||||
if (newSlug) {
|
||||
if (isDefinedAndNotEmpty(parentSlug) && newSlug.startsWith(parentSlug))
|
||||
newSlug = newSlug.substring(parentSlug.length + 1);
|
||||
newSlug = newSlug.replaceAll("-", " ");
|
||||
return capitalizeString(newSlug);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export const prettyinlineTitle = (
|
||||
pretitle: string | undefined | null,
|
||||
title: string | undefined | null,
|
||||
subtitle: string | undefined | null
|
||||
pretitle: string | null | undefined,
|
||||
title: string | null | undefined,
|
||||
subtitle: string | null | undefined
|
||||
): string => {
|
||||
let result = "";
|
||||
if (pretitle) result += `${pretitle}: `;
|
||||
|
@ -59,7 +60,7 @@ export const prettyinlineTitle = (
|
|||
export const prettyItemType = (
|
||||
metadata: any,
|
||||
langui: AppStaticProps["langui"]
|
||||
): string | undefined | null => {
|
||||
): string | null | undefined => {
|
||||
switch (metadata.__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
return langui.audio;
|
||||
|
@ -78,6 +79,7 @@ export const prettyItemType = (
|
|||
}
|
||||
};
|
||||
|
||||
/* eslint-disable id-denylist */
|
||||
export const prettyItemSubType = (
|
||||
metadata:
|
||||
| {
|
||||
|
@ -86,9 +88,11 @@ export const prettyItemSubType = (
|
|||
data?: {
|
||||
attributes?: {
|
||||
slug: string;
|
||||
titles?: Array<{
|
||||
titles?:
|
||||
| ({
|
||||
title: string;
|
||||
} | null> | null;
|
||||
} | null)[]
|
||||
| null;
|
||||
} | null;
|
||||
} | null;
|
||||
} | null;
|
||||
|
@ -99,9 +103,11 @@ export const prettyItemSubType = (
|
|||
data?: {
|
||||
attributes?: {
|
||||
slug: string;
|
||||
titles?: Array<{
|
||||
titles?:
|
||||
| ({
|
||||
title: string;
|
||||
} | null> | null;
|
||||
} | null)[]
|
||||
| null;
|
||||
} | null;
|
||||
} | null;
|
||||
} | null;
|
||||
|
@ -109,12 +115,12 @@ export const prettyItemSubType = (
|
|||
| {
|
||||
__typename: "ComponentMetadataGame";
|
||||
platforms?: {
|
||||
data: Array<{
|
||||
data: {
|
||||
id?: string | null;
|
||||
attributes?: {
|
||||
short: string;
|
||||
} | null;
|
||||
}>;
|
||||
}[];
|
||||
} | null;
|
||||
}
|
||||
| {
|
||||
|
@ -123,9 +129,11 @@ export const prettyItemSubType = (
|
|||
data?: {
|
||||
attributes?: {
|
||||
slug: string;
|
||||
titles?: Array<{
|
||||
titles?:
|
||||
| ({
|
||||
title: string;
|
||||
} | null> | null;
|
||||
} | null)[]
|
||||
| null;
|
||||
} | null;
|
||||
} | null;
|
||||
} | null;
|
||||
|
@ -133,27 +141,31 @@ export const prettyItemSubType = (
|
|||
data?: {
|
||||
attributes?: {
|
||||
slug: string;
|
||||
titles?: Array<{
|
||||
titles?:
|
||||
| ({
|
||||
title: string;
|
||||
} | null> | null;
|
||||
} | null)[]
|
||||
| null;
|
||||
} | null;
|
||||
} | null;
|
||||
} | null;
|
||||
}
|
||||
| { __typename: "ComponentMetadataOther" }
|
||||
| {
|
||||
__typename: "ComponentMetadataVideo";
|
||||
subtype?: {
|
||||
data?: {
|
||||
attributes?: {
|
||||
slug: string;
|
||||
titles?: Array<{
|
||||
titles?:
|
||||
| ({
|
||||
title: string;
|
||||
} | null> | null;
|
||||
} | null)[]
|
||||
| null;
|
||||
} | null;
|
||||
} | null;
|
||||
} | null;
|
||||
}
|
||||
| { __typename: "ComponentMetadataOther" }
|
||||
| { __typename: "Error" }
|
||||
| null
|
||||
): string => {
|
||||
|
@ -163,27 +175,27 @@ export const prettyItemSubType = (
|
|||
case "ComponentMetadataBooks":
|
||||
case "ComponentMetadataVideo":
|
||||
return metadata.subtype?.data?.attributes?.titles &&
|
||||
metadata.subtype?.data?.attributes?.titles.length > 0 &&
|
||||
metadata.subtype.data.attributes.titles.length > 0 &&
|
||||
metadata.subtype.data.attributes.titles[0]
|
||||
? metadata.subtype.data.attributes.titles[0].title
|
||||
: prettySlug(metadata.subtype?.data?.attributes?.slug);
|
||||
case "ComponentMetadataGame":
|
||||
return metadata.platforms?.data &&
|
||||
metadata.platforms?.data.length > 0 &&
|
||||
metadata.platforms.data.length > 0 &&
|
||||
metadata.platforms.data[0].attributes
|
||||
? metadata.platforms.data[0].attributes.short
|
||||
: "";
|
||||
case "ComponentMetadataGroup": {
|
||||
const firstPart =
|
||||
metadata.subtype?.data?.attributes?.titles &&
|
||||
metadata.subtype?.data?.attributes?.titles.length > 0 &&
|
||||
metadata.subtype.data.attributes.titles.length > 0 &&
|
||||
metadata.subtype.data.attributes.titles[0]
|
||||
? metadata.subtype.data.attributes.titles[0].title
|
||||
: prettySlug(metadata.subtype?.data?.attributes?.slug);
|
||||
|
||||
const secondPart =
|
||||
metadata.subitems_type?.data?.attributes?.titles &&
|
||||
metadata.subitems_type?.data?.attributes?.titles.length > 0 &&
|
||||
metadata.subitems_type.data.attributes.titles.length > 0 &&
|
||||
metadata.subitems_type.data.attributes.titles[0]
|
||||
? metadata.subitems_type.data.attributes.titles[0].title
|
||||
: prettySlug(metadata.subitems_type?.data?.attributes?.slug);
|
||||
|
@ -194,8 +206,8 @@ export const prettyItemSubType = (
|
|||
}
|
||||
}
|
||||
return "";
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
};
|
||||
/* eslint-enable id-denylist */
|
||||
|
||||
export const prettyShortenNumber = (number: number): string => {
|
||||
if (number > 1000000) {
|
||||
|
@ -203,11 +215,9 @@ export const prettyShortenNumber = (number: number): string => {
|
|||
maximumSignificantDigits: 3,
|
||||
});
|
||||
} else if (number > 1000) {
|
||||
return (
|
||||
(number / 1000).toLocaleString(undefined, {
|
||||
return `${(number / 1000).toLocaleString(undefined, {
|
||||
maximumSignificantDigits: 2,
|
||||
}) + "K"
|
||||
);
|
||||
})}K`;
|
||||
}
|
||||
return number.toLocaleString();
|
||||
};
|
||||
|
@ -215,18 +225,19 @@ export const prettyShortenNumber = (number: number): string => {
|
|||
export const prettyDuration = (seconds: number): string => {
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
while (seconds > 60) {
|
||||
minutes += 1;
|
||||
seconds -= 60;
|
||||
let remainingSeconds = seconds;
|
||||
while (remainingSeconds > 60) {
|
||||
minutes++;
|
||||
remainingSeconds -= 60;
|
||||
}
|
||||
while (minutes > 60) {
|
||||
hours += 1;
|
||||
hours++;
|
||||
minutes -= 60;
|
||||
}
|
||||
let result = "";
|
||||
if (hours) result += hours.toString().padStart(2, "0") + ":";
|
||||
result += minutes.toString().padStart(2, "0") + ":";
|
||||
result += seconds.toString().padStart(2, "0");
|
||||
if (hours) result += `${hours.toString().padStart(2, "0")}:`;
|
||||
result += `${minutes.toString().padStart(2, "0")}:`;
|
||||
result += remainingSeconds.toString().padStart(2, "0");
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -236,21 +247,20 @@ export const prettyLanguage = (
|
|||
): string => {
|
||||
let result = code;
|
||||
languages.forEach((language) => {
|
||||
if (language?.attributes?.code === code)
|
||||
if (language.attributes?.code === code)
|
||||
result = language.attributes.localized_name;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export const prettyURL = (url: string): string => {
|
||||
let domain = new URL(url);
|
||||
const domain = new URL(url);
|
||||
return domain.hostname.replace("www.", "");
|
||||
};
|
||||
|
||||
const capitalizeString = (string: string): string => {
|
||||
const capitalizeWord = (word: string): string => {
|
||||
return 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));
|
||||
|
@ -262,7 +272,7 @@ export const slugify = (string: string | undefined): string => {
|
|||
return "";
|
||||
}
|
||||
return string
|
||||
.replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/g, "a")
|
||||
.replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/gu, "a")
|
||||
.replace(/[çÇ]/gu, "c")
|
||||
.replace(/[ðÐ]/gu, "d")
|
||||
.replace(/[ÈÉÊËéèêë]/gu, "e")
|
||||
|
|
|
@ -73,12 +73,12 @@ export const getOgImage = (
|
|||
const imgSize = getImgSizesByQuality(
|
||||
image.width ?? 0,
|
||||
image.height ?? 0,
|
||||
quality ? quality : ImageQuality.Small
|
||||
quality
|
||||
);
|
||||
return {
|
||||
image: getAssetURL(image.url, quality),
|
||||
width: imgSize.width,
|
||||
height: imgSize.height,
|
||||
alt: image.alternativeText || "",
|
||||
alt: image.alternativeText ?? "",
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,252 +1,5 @@
|
|||
import { AppLayoutState } from "contexts/AppLayoutContext";
|
||||
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { prettyinlineTitle, prettyDate } from "./formatters";
|
||||
import { convertPrice } from "./numbers";
|
||||
import { isDefined, mapRemoveEmptyValues } from "./others";
|
||||
import { LibraryItemUserStatus } from "./types";
|
||||
import LibraryPage from "../pages/library/index";
|
||||
|
||||
type Items = Parameters<typeof LibraryPage>[0]["items"];
|
||||
type GroupLibraryItems = Map<string, Items>;
|
||||
|
||||
export const getGroups = (
|
||||
langui: AppStaticProps["langui"],
|
||||
groupByType: number,
|
||||
items: Items
|
||||
): GroupLibraryItems => {
|
||||
const groups: GroupLibraryItems = new Map();
|
||||
|
||||
switch (groupByType) {
|
||||
case 0: {
|
||||
const noCategory = langui.no_category ?? "No category";
|
||||
groups.set("Drakengard 1", []);
|
||||
groups.set("Drakengard 1.3", []);
|
||||
groups.set("Drakengard 2", []);
|
||||
groups.set("Drakengard 3", []);
|
||||
groups.set("Drakengard 4", []);
|
||||
groups.set("NieR Gestalt", []);
|
||||
groups.set("NieR Replicant", []);
|
||||
groups.set("NieR Replicant ver.1.22474487139...", []);
|
||||
groups.set("NieR:Automata", []);
|
||||
groups.set("NieR Re[in]carnation", []);
|
||||
groups.set("SINoALICE", []);
|
||||
groups.set("Voice of Cards", []);
|
||||
groups.set("Final Fantasy XIV", []);
|
||||
groups.set("Thou Shalt Not Die", []);
|
||||
groups.set("Bakuken", []);
|
||||
groups.set("YoRHa", []);
|
||||
groups.set("YoRHa Boys", []);
|
||||
groups.set(noCategory, []);
|
||||
|
||||
items.map((item) => {
|
||||
if (item.attributes?.categories?.data.length === 0) {
|
||||
groups.get(noCategory)?.push(item);
|
||||
} else {
|
||||
item.attributes?.categories?.data.map((category) => {
|
||||
groups.get(category.attributes?.name ?? noCategory)?.push(item);
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
groups.set(langui.audio ?? "Audio", []);
|
||||
groups.set(langui.game ?? "Game", []);
|
||||
groups.set(langui.textual ?? "Textual", []);
|
||||
groups.set(langui.video ?? "Video", []);
|
||||
groups.set(langui.other ?? "Other", []);
|
||||
groups.set(langui.group ?? "Group", []);
|
||||
groups.set(langui.no_type ?? "No type", []);
|
||||
items.map((item) => {
|
||||
if (item.attributes?.metadata && item.attributes.metadata.length > 0) {
|
||||
switch (item.attributes.metadata[0]?.__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
groups.get(langui.audio ?? "Audio")?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataGame":
|
||||
groups.get(langui.game ?? "Game")?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataBooks":
|
||||
groups.get(langui.textual ?? "Textual")?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataVideo":
|
||||
groups.get(langui.video ?? "Video")?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataOther":
|
||||
groups.get(langui.other ?? "Other")?.push(item);
|
||||
break;
|
||||
case "ComponentMetadataGroup":
|
||||
switch (
|
||||
item.attributes.metadata[0]?.subitems_type?.data?.attributes
|
||||
?.slug
|
||||
) {
|
||||
case "audio":
|
||||
groups.get(langui.audio ?? "Audio")?.push(item);
|
||||
break;
|
||||
case "video":
|
||||
groups.get(langui.video ?? "Video")?.push(item);
|
||||
break;
|
||||
case "game":
|
||||
groups.get(langui.game ?? "Game")?.push(item);
|
||||
break;
|
||||
case "textual":
|
||||
groups.get(langui.textual ?? "Textual")?.push(item);
|
||||
break;
|
||||
case "mixed":
|
||||
groups.get(langui.group ?? "Group")?.push(item);
|
||||
break;
|
||||
default: {
|
||||
throw new Error(
|
||||
"An unexpected subtype of group-metadata was given"
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
throw new Error("An unexpected type of metadata was given");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
groups.get(langui.no_type ?? "No type")?.push(item);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
const years: number[] = [];
|
||||
items.map((item) => {
|
||||
if (item.attributes?.release_date?.year) {
|
||||
if (!years.includes(item.attributes.release_date.year))
|
||||
years.push(item.attributes.release_date.year);
|
||||
}
|
||||
});
|
||||
|
||||
years.sort((a, b) => a - b);
|
||||
years.map((year) => {
|
||||
groups.set(year.toString(), []);
|
||||
});
|
||||
groups.set(langui.no_year ?? "No year", []);
|
||||
items.map((item) => {
|
||||
if (item.attributes?.release_date?.year) {
|
||||
groups.get(item.attributes.release_date.year.toString())?.push(item);
|
||||
} else {
|
||||
groups.get(langui.no_year ?? "No year")?.push(item);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
groups.set("", items);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mapRemoveEmptyValues(groups);
|
||||
};
|
||||
|
||||
export const filterItems = (
|
||||
appLayout: AppLayoutState,
|
||||
items: Items,
|
||||
searchName: string,
|
||||
showSubitems: boolean,
|
||||
showPrimaryItems: boolean,
|
||||
showSecondaryItems: boolean,
|
||||
filterUserStatus: LibraryItemUserStatus | undefined
|
||||
): Items => {
|
||||
return items.filter((item) => {
|
||||
if (!showSubitems && !item.attributes?.root_item) return false;
|
||||
if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) {
|
||||
return false;
|
||||
}
|
||||
if (item.attributes?.primary && !showPrimaryItems) return false;
|
||||
if (!item.attributes?.primary && !showSecondaryItems) return false;
|
||||
|
||||
if (
|
||||
searchName.length > 1 &&
|
||||
!prettyinlineTitle("", item.attributes?.title, item.attributes?.subtitle)
|
||||
.toLowerCase()
|
||||
.includes(searchName.toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(filterUserStatus) &&
|
||||
item.id &&
|
||||
appLayout.libraryItemUserStatus
|
||||
) {
|
||||
if (isUntangibleGroupItem(item.attributes?.metadata?.[0])) {
|
||||
return false;
|
||||
}
|
||||
if (filterUserStatus === LibraryItemUserStatus.None) {
|
||||
if (appLayout.libraryItemUserStatus[item.id]) {
|
||||
return false;
|
||||
}
|
||||
} else if (
|
||||
filterUserStatus !== appLayout.libraryItemUserStatus[item.id]
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Properly type this shit
|
||||
export const isUntangibleGroupItem = (metadata: any) => {
|
||||
return (
|
||||
export const isUntangibleGroupItem = (metadata: any): boolean =>
|
||||
metadata &&
|
||||
metadata.__typename === "ComponentMetadataGroup" &&
|
||||
(metadata.subtype?.data?.attributes?.slug === "variant-set" ||
|
||||
metadata.subtype?.data?.attributes?.slug === "relation-set")
|
||||
);
|
||||
};
|
||||
|
||||
export const sortBy = (
|
||||
orderByType: number,
|
||||
items: Items,
|
||||
currencies: AppStaticProps["currencies"]
|
||||
) => {
|
||||
switch (orderByType) {
|
||||
case 0:
|
||||
return items.sort((a, b) => {
|
||||
const titleA = prettyinlineTitle(
|
||||
"",
|
||||
a.attributes?.title,
|
||||
a.attributes?.subtitle
|
||||
);
|
||||
const titleB = prettyinlineTitle(
|
||||
"",
|
||||
b.attributes?.title,
|
||||
b.attributes?.subtitle
|
||||
);
|
||||
return titleA.localeCompare(titleB);
|
||||
});
|
||||
case 1:
|
||||
return items.sort((a, b) => {
|
||||
const priceA = a.attributes?.price
|
||||
? convertPrice(a.attributes.price, currencies[0])
|
||||
: 99999;
|
||||
const priceB = b.attributes?.price
|
||||
? convertPrice(b.attributes.price, currencies[0])
|
||||
: 99999;
|
||||
return priceA - priceB;
|
||||
});
|
||||
case 2:
|
||||
return items.sort((a, b) => {
|
||||
const dateA = a.attributes?.release_date
|
||||
? prettyDate(a.attributes.release_date)
|
||||
: "9999";
|
||||
const dateB = b.attributes?.release_date
|
||||
? prettyDate(b.attributes.release_date)
|
||||
: "9999";
|
||||
return dateA.localeCompare(dateB);
|
||||
});
|
||||
default:
|
||||
return items;
|
||||
}
|
||||
};
|
||||
metadata.subtype?.data?.attributes?.slug === "relation-set");
|
||||
|
|
|
@ -16,15 +16,11 @@ export const convertPrice = (
|
|||
return 0;
|
||||
};
|
||||
|
||||
export const convertMmToInch = (mm: number | null | undefined): string => {
|
||||
return mm ? (mm * 0.03937008).toPrecision(3) : "";
|
||||
};
|
||||
export const convertMmToInch = (mm: number | null | undefined): string =>
|
||||
mm ? (mm * 0.03937008).toPrecision(3) : "";
|
||||
|
||||
export const randomInt = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
};
|
||||
export const randomInt = (min: number, max: number): number =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
|
||||
export const isInteger = (value: string): boolean => {
|
||||
// eslint-disable-next-line require-unicode-regexp
|
||||
return /^[+-]?[0-9]+$/.test(value);
|
||||
};
|
||||
export const isInteger = (value: string): boolean =>
|
||||
/^[+-]?[0-9]+$/u.test(value);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
||||
import { SelectiveRequiredNonNullable } from "./types";
|
||||
import {
|
||||
Enum_Componentsetstextset_Status,
|
||||
GetLibraryItemQuery,
|
||||
GetLibraryItemScansQuery,
|
||||
} from "graphql/generated";
|
||||
import { AppStaticProps } from "../graphql/getAppStaticProps";
|
||||
import { SelectiveRequiredNonNullable } from "./types";
|
||||
|
||||
type SortContentProps =
|
||||
type SortRangedContentProps =
|
||||
| NonNullable<
|
||||
NonNullable<
|
||||
GetLibraryItemQuery["libraryItems"]
|
||||
|
@ -18,7 +18,7 @@ type SortContentProps =
|
|||
>["data"][number]["attributes"]
|
||||
>["contents"];
|
||||
|
||||
export const sortContent = (contents: SortContentProps) => {
|
||||
export const sortRangedContent = (contents: SortRangedContentProps): void => {
|
||||
contents?.data.sort((a, b) => {
|
||||
if (
|
||||
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
|
||||
|
@ -59,20 +59,20 @@ export const isDefined = <T>(t: T): t is NonNullable<T> =>
|
|||
t !== null && t !== undefined;
|
||||
|
||||
export const isUndefined = <T>(
|
||||
t: T | undefined | null
|
||||
): t is undefined | null => t === null || t === undefined;
|
||||
t: T | null | undefined
|
||||
): t is null | undefined => t === null || t === undefined;
|
||||
|
||||
export const isDefinedAndNotEmpty = (
|
||||
string: string | undefined | null
|
||||
string: string | null | undefined
|
||||
): string is string => isDefined(string) && string.length > 0;
|
||||
|
||||
export const filterDefined = <T>(t: T[] | undefined | null): NonNullable<T>[] =>
|
||||
export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
|
||||
isUndefined(t)
|
||||
? []
|
||||
: (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
|
||||
|
||||
export const filterHasAttributes = <T, P extends keyof NonNullable<T>>(
|
||||
t: T[] | undefined | null,
|
||||
t: T[] | null | undefined,
|
||||
attributes?: P[]
|
||||
): SelectiveRequiredNonNullable<NonNullable<T>, P>[] =>
|
||||
isUndefined(t)
|
||||
|
@ -89,27 +89,24 @@ export const filterHasAttributes = <T, P extends keyof NonNullable<T>>(
|
|||
|
||||
export const iterateMap = <K, V, U>(
|
||||
map: Map<K, V>,
|
||||
callbackfn: (key: K, value: V, index: number) => U
|
||||
callbackfn: (key: K, value: V, index: number) => U,
|
||||
sortingFunction?: (a: [K, V], b: [K, V]) => number
|
||||
): U[] => {
|
||||
const result: U[] = [];
|
||||
let index = 0;
|
||||
for (const [key, value] of map.entries()) {
|
||||
result.push(callbackfn(key, value, index));
|
||||
index += 1;
|
||||
const toList = [...map];
|
||||
if (isDefined(sortingFunction)) {
|
||||
toList.sort(sortingFunction);
|
||||
console.log(toList.sort(sortingFunction));
|
||||
}
|
||||
return result;
|
||||
return toList.map(([key, value], index) => callbackfn(key, value, index));
|
||||
};
|
||||
|
||||
export const mapMoveEntry = <K, V>(
|
||||
map: Map<K, V>,
|
||||
sourceIndex: number,
|
||||
targetIndex: number
|
||||
) => new Map(arrayMove([...map], sourceIndex, targetIndex));
|
||||
): Map<K, V> => new Map(arrayMove([...map], sourceIndex, targetIndex));
|
||||
|
||||
const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number) => {
|
||||
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
|
||||
return arr;
|
||||
};
|
||||
|
||||
export const mapRemoveEmptyValues = <K, V>(groups: Map<K, V[]>): Map<K, V[]> =>
|
||||
new Map([...groups].filter(([_, items]) => items.length > 0));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { LightBox } from "components/LightBox";
|
||||
import { useState } from "react";
|
||||
import { LightBox } from "components/LightBox";
|
||||
|
||||
export const useLightBox = (): [
|
||||
(images: string[], index?: number) => void,
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { breaks } from "../../design.config";
|
||||
|
||||
const useMediaQuery = (query: string): boolean => {
|
||||
const getMatches = (query: string): boolean => {
|
||||
const getMatches = useCallback((): boolean => {
|
||||
// Prevents SSR issues
|
||||
if (typeof window !== "undefined") {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}, [query]);
|
||||
|
||||
const [matches, setMatches] = useState<boolean>(getMatches(query));
|
||||
const [matches, setMatches] = useState<boolean>(getMatches());
|
||||
|
||||
useEffect(() => {
|
||||
const handleChange = () => {
|
||||
setMatches(getMatches(query));
|
||||
setMatches(getMatches());
|
||||
};
|
||||
|
||||
const matchMedia = window.matchMedia(query);
|
||||
|
@ -28,19 +28,19 @@ const useMediaQuery = (query: string): boolean => {
|
|||
return () => {
|
||||
matchMedia.removeEventListener("change", handleChange);
|
||||
};
|
||||
}, [query]);
|
||||
}, [getMatches, query]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
// ts-unused-exports:disable-next-line
|
||||
export const useMediaThin = () => useMediaQuery(breaks.thin.raw);
|
||||
export const useMediaThin = (): boolean => useMediaQuery(breaks.thin.raw);
|
||||
|
||||
export const useMediaMobile = () => useMediaQuery(breaks.mobile.raw);
|
||||
export const useMediaMobile = (): boolean => useMediaQuery(breaks.mobile.raw);
|
||||
|
||||
export const useMediaDesktop = () => useMediaQuery(breaks.desktop.raw);
|
||||
export const useMediaDesktop = (): boolean => useMediaQuery(breaks.desktop.raw);
|
||||
|
||||
export const useMediaHoverable = () => useMediaQuery("(hover: hover)");
|
||||
export const useMediaHoverable = (): boolean => useMediaQuery("(hover: hover)");
|
||||
|
||||
export const usePrefersDarkMode = () =>
|
||||
export const usePrefersDarkMode = (): boolean =>
|
||||
useMediaQuery("(prefers-color-scheme: dark)");
|
||||
|
|
|
@ -5,8 +5,15 @@ export enum AnchorIds {
|
|||
}
|
||||
|
||||
// Scroll to top of element "id" when "deps" update.
|
||||
export const useScrollTopOnChange = (id: AnchorIds, deps: DependencyList) => {
|
||||
export const useScrollTopOnChange = (
|
||||
id: AnchorIds,
|
||||
deps: DependencyList,
|
||||
enabled = true
|
||||
): void => {
|
||||
useEffect(() => {
|
||||
document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}, deps);
|
||||
if (enabled)
|
||||
document
|
||||
.querySelector(`#${id}`)
|
||||
?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}, [id, deps, enabled]);
|
||||
};
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { filterDefined, isDefined } from "helpers/others";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
interface Props<T> {
|
||||
items: T[];
|
||||
languages: AppStaticProps["languages"];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { isDefined } from "helpers/others";
|
||||
import { useEffect, useState } from "react";
|
||||
import { isDefined } from "helpers/others";
|
||||
|
||||
export const useStateWithLocalStorage = <T>(
|
||||
key: string,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { Dispatch, SetStateAction, useCallback } from "react";
|
||||
|
||||
export const useToggle = (setState: Dispatch<SetStateAction<boolean>>) =>
|
||||
export const useToggle = (
|
||||
setState: Dispatch<SetStateAction<boolean>>
|
||||
): (() => void) =>
|
||||
useCallback(() => {
|
||||
setState((current) => !current);
|
||||
}, []);
|
||||
}, [setState]);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import {
|
||||
ReturnButton,
|
||||
|
@ -5,7 +6,6 @@ import {
|
|||
} from "components/PanelComponents/ReturnButton";
|
||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { GetStaticProps } from "next";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import {
|
||||
ReturnButton,
|
||||
|
@ -5,7 +6,6 @@ import {
|
|||
} from "components/PanelComponents/ReturnButton";
|
||||
import { ContentPanel } from "components/Panels/ContentPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { GetStaticProps } from "next";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -5,8 +5,8 @@ import "@fontsource/opendyslexic/700.css";
|
|||
import "@fontsource/vollkorn/700.css";
|
||||
import "@fontsource/zen-maru-gothic/500.css";
|
||||
import "@fontsource/zen-maru-gothic/900.css";
|
||||
import { AppContextProvider } from "contexts/AppLayoutContext";
|
||||
import type { AppProps } from "next/app";
|
||||
import { AppContextProvider } from "contexts/AppLayoutContext";
|
||||
import "tailwind.css";
|
||||
|
||||
const AccordsLibraryApp = (props: AppProps): JSX.Element => (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import {
|
||||
|
@ -6,10 +8,7 @@ import {
|
|||
} from "graphql/getPostStaticProps";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { randomInt } from "helpers/numbers";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
|
||||
import { useState } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Icon } from "components/Ico";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { GetStaticProps } from "next";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -17,7 +17,7 @@ export interface RequestMailProps {
|
|||
const Mail = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<ResponseMailProps>
|
||||
) => {
|
||||
): Promise<void> => {
|
||||
if (req.method === "POST") {
|
||||
const body = req.body as RequestMailProps;
|
||||
|
||||
|
|
|
@ -2,17 +2,17 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||
import getConfig from "next/config";
|
||||
|
||||
type RequestProps =
|
||||
| HookRangedContent
|
||||
| HookPostContent
|
||||
| HookLibraryItem
|
||||
| HookChronology
|
||||
| HookContent
|
||||
| HookContentGroup
|
||||
| HookCustom
|
||||
| HookLibraryItem
|
||||
| HookPostContent
|
||||
| HookRangedContent
|
||||
| HookWiki;
|
||||
|
||||
type HookRangedContent = {
|
||||
event: "entry.update" | "entry.delete" | "entry.create";
|
||||
event: "entry.create" | "entry.delete" | "entry.update";
|
||||
model: "ranged-content";
|
||||
entry: {
|
||||
library_item?: {
|
||||
|
@ -33,16 +33,14 @@ type HookContent = {
|
|||
model: "content";
|
||||
entry: {
|
||||
slug: string;
|
||||
ranged_contents: [
|
||||
{
|
||||
ranged_contents: {
|
||||
slug: string;
|
||||
}
|
||||
];
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
type HookPostContent = {
|
||||
event: "entry.update" | "entry.delete" | "entry.create";
|
||||
event: "entry.create" | "entry.delete" | "entry.update";
|
||||
model: "post";
|
||||
entry: {
|
||||
slug: string;
|
||||
|
@ -50,7 +48,7 @@ type HookPostContent = {
|
|||
};
|
||||
|
||||
type HookLibraryItem = {
|
||||
event: "entry.update" | "entry.delete" | "entry.create";
|
||||
event: "entry.create" | "entry.delete" | "entry.update";
|
||||
model: "library-item";
|
||||
entry: {
|
||||
slug: string;
|
||||
|
@ -63,7 +61,7 @@ type HookLibraryItem = {
|
|||
};
|
||||
|
||||
type HookContentGroup = {
|
||||
event: "entry.update" | "entry.delete" | "entry.create";
|
||||
event: "entry.create" | "entry.delete" | "entry.update";
|
||||
model: "contents-group";
|
||||
entry: {
|
||||
contents: {
|
||||
|
@ -73,12 +71,12 @@ type HookContentGroup = {
|
|||
};
|
||||
|
||||
type HookChronology = {
|
||||
event: "entry.update" | "entry.delete" | "entry.create";
|
||||
event: "entry.create" | "entry.delete" | "entry.update";
|
||||
model: "chronology-era" | "chronology-item";
|
||||
};
|
||||
|
||||
type HookWiki = {
|
||||
event: "entry.update" | "entry.delete" | "entry.create";
|
||||
event: "entry.create" | "entry.delete" | "entry.update";
|
||||
model: "wiki-page";
|
||||
entry: {
|
||||
slug: string;
|
||||
|
@ -90,10 +88,10 @@ type ResponseMailProps = {
|
|||
revalidated: boolean;
|
||||
};
|
||||
|
||||
const Revalidate = async (
|
||||
const Revalidate = (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<ResponseMailProps>
|
||||
) => {
|
||||
): void => {
|
||||
const body = req.body as RequestProps;
|
||||
const { serverRuntimeConfig } = getConfig();
|
||||
|
||||
|
@ -101,9 +99,8 @@ const Revalidate = async (
|
|||
if (
|
||||
req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`
|
||||
) {
|
||||
return res
|
||||
.status(401)
|
||||
.json({ message: "Invalid token", revalidated: false });
|
||||
res.status(401).json({ message: "Invalid token", revalidated: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const paths: string[] = [];
|
||||
|
@ -229,13 +226,12 @@ const Revalidate = async (
|
|||
await res.revalidate(path);
|
||||
})
|
||||
);
|
||||
return res.json({ message: "Success!", revalidated: true });
|
||||
} catch (err) {
|
||||
// If there was an error, Next.js will continue
|
||||
// to show the last successfully generated page
|
||||
return res
|
||||
res.json({ message: "Success!", revalidated: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
res
|
||||
.status(500)
|
||||
.send({ message: "Error revalidating", revalidated: false });
|
||||
.send({ message: `Error revalidating: ${error}`, revalidated: false });
|
||||
}
|
||||
};
|
||||
export default Revalidate;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticProps } from "next";
|
||||
import { Icon } from "components/Ico";
|
||||
import { useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useState, useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
|
@ -15,8 +17,6 @@ import { GetVideoChannelQuery } from "graphql/generated";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { getVideoThumbnailURL } from "helpers/videos";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useState, useMemo } from "react";
|
||||
import { Icon } from "components/Ico";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo, useState } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { Icon } from "components/Ico";
|
||||
import { PageSelector } from "components/Inputs/PageSelector";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import {
|
||||
ReturnButton,
|
||||
|
@ -21,15 +25,15 @@ import { prettyDate } from "helpers/formatters";
|
|||
import { filterHasAttributes } from "helpers/others";
|
||||
import { getVideoThumbnailURL } from "helpers/videos";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { GetStaticProps } from "next";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
* ────────────────────────────────────────╯ CONSTANTS ╰──────────────────────────────────────────
|
||||
*/
|
||||
|
||||
const ITEM_PER_PAGE = 50;
|
||||
const DEFAULT_FILTERS_STATE = {
|
||||
searchName: "",
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -42,18 +46,10 @@ interface Props extends AppStaticProps {
|
|||
|
||||
const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
|
||||
const hoverable = useMediaHoverable();
|
||||
const [page, setPage] = useState(0);
|
||||
const [keepInfoVisible, setKeepInfoVisible] = useState(true);
|
||||
|
||||
const paginatedVideos = useMemo(() => {
|
||||
const memo = [];
|
||||
for (let index = 0; ITEM_PER_PAGE * index < videos.length; index += 1) {
|
||||
memo.push(
|
||||
videos.slice(index * ITEM_PER_PAGE, (index + 1) * ITEM_PER_PAGE)
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
);
|
||||
}
|
||||
return memo;
|
||||
}, [videos]);
|
||||
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
|
@ -72,6 +68,13 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
|
|||
description={langui.archives_description}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
className="mb-6 w-full"
|
||||
placeholder={langui.search_title ?? undefined}
|
||||
state={searchName}
|
||||
setState={setSearchName}
|
||||
/>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel
|
||||
label={langui.always_show_info}
|
||||
|
@ -82,56 +85,53 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
|
|||
)}
|
||||
</SubPanel>
|
||||
),
|
||||
[hoverable, keepInfoVisible, langui]
|
||||
[hoverable, keepInfoVisible, langui, searchName]
|
||||
);
|
||||
|
||||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<PageSelector
|
||||
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
className="mb-12"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
|
||||
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
|
||||
thin:grid-cols-1"
|
||||
>
|
||||
{filterHasAttributes(paginatedVideos[page]).map((video) => (
|
||||
<Fragment key={video.id}>
|
||||
<SmartList
|
||||
items={filterHasAttributes(videos)}
|
||||
getItemId={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<>
|
||||
<PreviewCard
|
||||
href={`/archives/videos/v/${video.attributes.uid}`}
|
||||
title={video.attributes.title}
|
||||
thumbnail={getVideoThumbnailURL(video.attributes.uid)}
|
||||
href={`/archives/videos/v/${item.attributes.uid}`}
|
||||
title={item.attributes.title}
|
||||
thumbnail={getVideoThumbnailURL(item.attributes.uid)}
|
||||
thumbnailAspectRatio="16/9"
|
||||
thumbnailForceAspectRatio
|
||||
keepInfoVisible={keepInfoVisible}
|
||||
metadata={{
|
||||
release_date: video.attributes.published_date,
|
||||
views: video.attributes.views,
|
||||
author: video.attributes.channel?.data?.attributes?.title,
|
||||
release_date: item.attributes.published_date,
|
||||
views: item.attributes.views,
|
||||
author: item.attributes.channel?.data?.attributes?.title,
|
||||
position: "Top",
|
||||
}}
|
||||
hoverlay={{
|
||||
__typename: "Video",
|
||||
duration: video.attributes.duration,
|
||||
duration: item.attributes.duration,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<PageSelector
|
||||
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
className="mt-12"
|
||||
</>
|
||||
)}
|
||||
renderWhenEmpty={() => (
|
||||
<ContentPlaceholder
|
||||
message={langui.no_results_message ?? "No results"}
|
||||
icon={Icon.ChevronLeft}
|
||||
/>
|
||||
)}
|
||||
className="desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
|
||||
thin:grid-cols-1"
|
||||
paginationItemPerPage={20}
|
||||
searchingTerm={searchName}
|
||||
searchingBy={(item) => item.attributes.title}
|
||||
langui={langui}
|
||||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[keepInfoVisible, page, paginatedVideos, videos.length]
|
||||
[keepInfoVisible, langui, searchName, videos]
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Ico, Icon } from "components/Ico";
|
||||
|
@ -21,8 +23,6 @@ import { prettyDate, prettyShortenNumber } from "helpers/formatters";
|
|||
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||
import { getVideoFile } from "helpers/videos";
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticProps } from "next";
|
||||
import { Icon } from "components/Ico";
|
||||
import { useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
|
@ -34,8 +36,6 @@ import { ContentWithTranslations } from "helpers/types";
|
|||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useCallback, useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -111,7 +111,9 @@ const Content = ({
|
|||
.code !==
|
||||
selectedTranslation.language?.data?.attributes?.code && (
|
||||
<div className="grid place-items-center gap-2">
|
||||
<p className="font-headers">{langui.source_language}:</p>
|
||||
<p className="font-headers font-bold">
|
||||
{langui.source_language}:
|
||||
</p>
|
||||
<Chip>
|
||||
{prettyLanguage(
|
||||
selectedTranslation.text_set.source_language.data.attributes
|
||||
|
@ -123,7 +125,7 @@ const Content = ({
|
|||
)}
|
||||
|
||||
<div className="grid grid-flow-col place-content-center place-items-center gap-2">
|
||||
<p className="font-headers">{langui.status}:</p>
|
||||
<p className="font-headers font-bold">{langui.status}:</p>
|
||||
|
||||
<ToolTip
|
||||
content={getStatusDescription(
|
||||
|
@ -139,7 +141,9 @@ const Content = ({
|
|||
{selectedTranslation.text_set.transcribers &&
|
||||
selectedTranslation.text_set.transcribers.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{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
|
||||
|
@ -158,7 +162,9 @@ const Content = ({
|
|||
{selectedTranslation.text_set.translators &&
|
||||
selectedTranslation.text_set.translators.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{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
|
||||
|
@ -177,7 +183,9 @@ const Content = ({
|
|||
{selectedTranslation.text_set.proofreaders &&
|
||||
selectedTranslation.text_set.proofreaders.data.length > 0 && (
|
||||
<div>
|
||||
<p className="font-headers">{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
|
||||
|
@ -195,7 +203,7 @@ const Content = ({
|
|||
|
||||
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
|
||||
<div>
|
||||
<p className="font-headers">{"Notes"}:</p>
|
||||
<p className="font-headers font-bold">{"Notes"}:</p>
|
||||
<div className="grid place-content-center place-items-center gap-2">
|
||||
<Markdawn text={selectedTranslation.text_set.notes} />
|
||||
</div>
|
||||
|
@ -209,7 +217,9 @@ const Content = ({
|
|||
<>
|
||||
<HorizontalLine />
|
||||
<div>
|
||||
<p className="font-headers text-2xl">{langui.source}</p>
|
||||
<p className="font-headers text-2xl font-bold">
|
||||
{langui.source}
|
||||
</p>
|
||||
<div className="mt-6 grid place-items-center gap-6 text-left">
|
||||
{content.ranged_contents.data.map((rangedContent) => {
|
||||
const libraryItem =
|
||||
|
@ -525,7 +535,7 @@ type Group = NonNullable<
|
|||
>["data"];
|
||||
|
||||
const getPreviousContent = (group: Group, currentSlug: string) => {
|
||||
for (let index = 0; index < group.length; index += 1) {
|
||||
for (let index = 0; index < group.length; index++) {
|
||||
const content = group[index];
|
||||
if (content.attributes?.slug === currentSlug && index > 0) {
|
||||
return group[index - 1];
|
||||
|
@ -537,7 +547,7 @@ const getPreviousContent = (group: Group, currentSlug: string) => {
|
|||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
const getNextContent = (group: Group, currentSlug: string) => {
|
||||
for (let index = 0; index < group.length; index += 1) {
|
||||
for (let index = 0; index < group.length; index++) {
|
||||
const content = group[index];
|
||||
if (content.attributes?.slug === currentSlug && index < group.length - 1) {
|
||||
return group[index + 1];
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
|
@ -12,20 +13,16 @@ import { TranslatedPreviewCard } from "components/PreviewCard";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||
import { GetStaticProps } from "next";
|
||||
import { Fragment, useState, useMemo } from "react";
|
||||
import { Icon } from "components/Ico";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
iterateMap,
|
||||
mapRemoveEmptyValues,
|
||||
} from "helpers/others";
|
||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
||||
import { Icon } from "components/Ico";
|
||||
import { filterDefined, filterHasAttributes } from "helpers/others";
|
||||
import { GetContentsQuery } from "graphql/generated";
|
||||
import { SmartList } from "components/SmartList";
|
||||
import { SelectiveRequiredNonNullable } from "helpers/types";
|
||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -74,14 +71,72 @@ const Contents = ({
|
|||
[combineRelatedContent, searchName.length]
|
||||
);
|
||||
|
||||
const filteredItems = useMemo(
|
||||
() => filterContents(contents, effectiveCombineRelatedContent, searchName),
|
||||
[effectiveCombineRelatedContent, contents, searchName]
|
||||
const groupingFunction = useCallback(
|
||||
(
|
||||
item: SelectiveRequiredNonNullable<
|
||||
NonNullable<GetContentsQuery["contents"]>["data"][number],
|
||||
"attributes" | "id"
|
||||
>
|
||||
): string[] => {
|
||||
switch (groupingMethod) {
|
||||
case 0: {
|
||||
const categories = filterHasAttributes(
|
||||
item.attributes.categories?.data
|
||||
);
|
||||
if (categories.length > 0) {
|
||||
return categories.map((category) => category.attributes.name);
|
||||
}
|
||||
return [langui.no_category ?? "No category"];
|
||||
}
|
||||
case 1: {
|
||||
return [
|
||||
item.attributes.type?.data?.attributes?.titles?.[0]?.title ??
|
||||
item.attributes.type?.data?.attributes?.slug
|
||||
? prettySlug(item.attributes.type.data.attributes.slug)
|
||||
: langui.no_type ?? "No type",
|
||||
];
|
||||
}
|
||||
default: {
|
||||
return [""];
|
||||
}
|
||||
}
|
||||
},
|
||||
[groupingMethod, langui]
|
||||
);
|
||||
|
||||
const groups = useMemo(
|
||||
() => getGroups(langui, groupingMethod, filteredItems),
|
||||
[langui, groupingMethod, filteredItems]
|
||||
const filteringFunction = useCallback(
|
||||
(
|
||||
item: SelectiveRequiredNonNullable<
|
||||
Props["contents"][number],
|
||||
"attributes" | "id"
|
||||
>
|
||||
) => {
|
||||
if (
|
||||
effectiveCombineRelatedContent &&
|
||||
item.attributes.group?.data?.attributes?.combine === true &&
|
||||
item.attributes.group.data.attributes.contents?.data[0].id !== item.id
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (searchName.length > 1) {
|
||||
if (
|
||||
filterDefined(item.attributes.translations).find((translation) =>
|
||||
prettyinlineTitle(
|
||||
translation.pre_title,
|
||||
translation.title,
|
||||
translation.subtitle
|
||||
)
|
||||
.toLowerCase()
|
||||
.includes(searchName.toLowerCase())
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[effectiveCombineRelatedContent, searchName]
|
||||
);
|
||||
|
||||
const subPanel = useMemo(
|
||||
|
@ -161,51 +216,11 @@ const Contents = ({
|
|||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
{groups.size === 0 && (
|
||||
<ContentPlaceholder
|
||||
message={langui.no_results_message ?? "No results"}
|
||||
icon={Icon.ChevronLeft}
|
||||
/>
|
||||
)}
|
||||
{iterateMap(
|
||||
groups,
|
||||
(name, items, index) =>
|
||||
items.length > 0 && (
|
||||
<Fragment key={index}>
|
||||
{name && (
|
||||
<h2
|
||||
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
|
||||
first-of-type:pt-0"
|
||||
>
|
||||
{name}
|
||||
<Chip>{`${items.reduce((currentSum, item) => {
|
||||
if (effectiveCombineRelatedContent) {
|
||||
if (
|
||||
item.attributes?.group?.data?.attributes?.combine ===
|
||||
true
|
||||
) {
|
||||
return (
|
||||
currentSum +
|
||||
(item.attributes.group.data.attributes.contents
|
||||
?.data.length ?? 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
return currentSum + 1;
|
||||
}, 0)} ${
|
||||
items.length <= 1
|
||||
? langui.result?.toLowerCase() ?? ""
|
||||
: langui.results?.toLowerCase() ?? ""
|
||||
}`}</Chip>
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="grid grid-cols-2 items-end gap-8
|
||||
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
|
||||
>
|
||||
{filterHasAttributes(items).map((item) => (
|
||||
<Fragment key={item.id}>
|
||||
<SmartList
|
||||
items={filterHasAttributes(contents)}
|
||||
getItemId={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<>
|
||||
{item.attributes.translations && (
|
||||
<TranslatedPreviewCard
|
||||
href={`/contents/${item.attributes.slug}`}
|
||||
|
@ -214,35 +229,29 @@ const Contents = ({
|
|||
pre_title: translation?.pre_title,
|
||||
title: translation?.title,
|
||||
subtitle: translation?.subtitle,
|
||||
language:
|
||||
translation?.language?.data?.attributes?.code,
|
||||
language: translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
)}
|
||||
slug={item.attributes.slug}
|
||||
languages={languages}
|
||||
thumbnail={
|
||||
item.attributes.thumbnail?.data?.attributes
|
||||
}
|
||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
thumbnailForceAspectRatio
|
||||
stackNumber={
|
||||
effectiveCombineRelatedContent &&
|
||||
item.attributes.group?.data?.attributes?.combine ===
|
||||
true
|
||||
? item.attributes.group.data.attributes.contents
|
||||
?.data.length
|
||||
item.attributes.group?.data?.attributes?.combine === true
|
||||
? item.attributes.group.data.attributes.contents?.data
|
||||
.length
|
||||
: 0
|
||||
}
|
||||
topChips={
|
||||
item.attributes.type?.data?.attributes
|
||||
? [
|
||||
item.attributes.type.data.attributes
|
||||
.titles?.[0]
|
||||
? item.attributes.type.data.attributes
|
||||
.titles[0]?.title
|
||||
item.attributes.type.data.attributes.titles?.[0]
|
||||
? item.attributes.type.data.attributes.titles[0]
|
||||
?.title
|
||||
: prettySlug(
|
||||
item.attributes.type.data.attributes
|
||||
.slug
|
||||
item.attributes.type.data.attributes.slug
|
||||
),
|
||||
]
|
||||
: undefined
|
||||
|
@ -253,15 +262,45 @@ const Contents = ({
|
|||
keepInfoVisible={keepInfoVisible}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
</>
|
||||
)}
|
||||
renderWhenEmpty={() => (
|
||||
<ContentPlaceholder
|
||||
message={langui.no_results_message ?? "No results"}
|
||||
icon={Icon.ChevronLeft}
|
||||
/>
|
||||
)}
|
||||
className="grid-cols-2 items-end desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
|
||||
groupingFunction={groupingFunction}
|
||||
filteringFunction={filteringFunction}
|
||||
searchingTerm={searchName}
|
||||
searchingBy={(item) =>
|
||||
`
|
||||
${item.attributes.slug}
|
||||
${filterDefined(item.attributes.translations)
|
||||
.map((translation) =>
|
||||
prettyinlineTitle(
|
||||
translation.pre_title,
|
||||
translation.title,
|
||||
translation.subtitle
|
||||
)
|
||||
)
|
||||
.join(" ")}`
|
||||
}
|
||||
langui={langui}
|
||||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[effectiveCombineRelatedContent, groups, keepInfoVisible, languages, langui]
|
||||
[
|
||||
contents,
|
||||
effectiveCombineRelatedContent,
|
||||
filteringFunction,
|
||||
groupingFunction,
|
||||
keepInfoVisible,
|
||||
languages,
|
||||
langui,
|
||||
searchName,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -303,107 +342,3 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
props: props,
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* ╭───────────────────╮
|
||||
* ─────────────────────────────────────╯ PRIVATE METHODS ╰───────────────────────────────────────
|
||||
*/
|
||||
|
||||
type GroupContentItems = Map<string, Props["contents"]>;
|
||||
|
||||
export const getGroups = (
|
||||
langui: AppStaticProps["langui"],
|
||||
groupByType: number,
|
||||
items: Props["contents"]
|
||||
): GroupContentItems => {
|
||||
const groups: GroupContentItems = new Map();
|
||||
|
||||
switch (groupByType) {
|
||||
case 0: {
|
||||
const noCategory = langui.no_category ?? "No category";
|
||||
groups.set("Drakengard 1", []);
|
||||
groups.set("Drakengard 1.3", []);
|
||||
groups.set("Drakengard 2", []);
|
||||
groups.set("Drakengard 3", []);
|
||||
groups.set("Drakengard 4", []);
|
||||
groups.set("NieR Gestalt", []);
|
||||
groups.set("NieR Replicant", []);
|
||||
groups.set("NieR Replicant ver.1.22474487139...", []);
|
||||
groups.set("NieR:Automata", []);
|
||||
groups.set("NieR Re[in]carnation", []);
|
||||
groups.set("SINoALICE", []);
|
||||
groups.set("Voice of Cards", []);
|
||||
groups.set("Final Fantasy XIV", []);
|
||||
groups.set("Thou Shalt Not Die", []);
|
||||
groups.set("Bakuken", []);
|
||||
groups.set("YoRHa", []);
|
||||
groups.set("YoRHa Boys", []);
|
||||
groups.set(noCategory, []);
|
||||
|
||||
items.map((item) => {
|
||||
if (item.attributes?.categories?.data.length === 0) {
|
||||
groups.get(noCategory)?.push(item);
|
||||
} else {
|
||||
item.attributes?.categories?.data.map((category) => {
|
||||
groups.get(category.attributes?.name ?? noCategory)?.push(item);
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
items.map((item) => {
|
||||
const noType = langui.no_type ?? "No type";
|
||||
const type =
|
||||
item.attributes?.type?.data?.attributes?.titles?.[0]?.title ??
|
||||
item.attributes?.type?.data?.attributes?.slug
|
||||
? prettySlug(item.attributes.type.data.attributes.slug)
|
||||
: langui.no_type;
|
||||
if (!groups.has(type ?? noType)) groups.set(type ?? noType, []);
|
||||
groups.get(type ?? noType)?.push(item);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
groups.set("", items);
|
||||
}
|
||||
}
|
||||
return mapRemoveEmptyValues(groups);
|
||||
};
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
export const filterContents = (
|
||||
contents: Props["contents"],
|
||||
combineRelatedContent: boolean,
|
||||
searchName: string
|
||||
): Props["contents"] =>
|
||||
contents.filter((content) => {
|
||||
if (
|
||||
combineRelatedContent &&
|
||||
content.attributes?.group?.data?.attributes?.combine === true &&
|
||||
content.attributes.group.data.attributes.contents?.data[0].id !==
|
||||
content.id
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (searchName.length > 1) {
|
||||
if (
|
||||
content.attributes?.translations?.find((translation) =>
|
||||
prettyinlineTitle(
|
||||
translation?.pre_title,
|
||||
translation?.title,
|
||||
translation?.subtitle
|
||||
)
|
||||
.toLowerCase()
|
||||
.includes(searchName.toLowerCase())
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
@ -10,8 +12,6 @@ import { DevGetContentsQuery } from "graphql/generated";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { filterDefined, filterHasAttributes } from "helpers/others";
|
||||
import { GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
@ -13,9 +15,6 @@ import {
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
|
||||
import { GetStaticProps } from "next";
|
||||
import { useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
* ──────────────────────────────────────────╯ PAGE ╰─────────────────────────────────────────────
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import TurndownService from "turndown";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
|
||||
|
@ -8,9 +11,6 @@ import {
|
|||
import { Popup } from "components/Popup";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { GetStaticProps } from "next";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import TurndownService from "turndown";
|
||||
import { Icon } from "components/Ico";
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||
|
@ -7,8 +9,6 @@ import {
|
|||
} from "components/Panels/ContentPanel";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { GetStaticProps } from "next";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Fragment, useCallback, useMemo, useState } from "react";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Img } from "components/Img";
|
||||
|
@ -40,12 +42,10 @@ import {
|
|||
filterHasAttributes,
|
||||
isDefined,
|
||||
isDefinedAndNotEmpty,
|
||||
sortContent,
|
||||
sortRangedContent,
|
||||
} from "helpers/others";
|
||||
import { useLightBox } from "hooks/useLightBox";
|
||||
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useCallback, useMemo, useState } from "react";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
|
@ -626,7 +626,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
language_code: context.locale ?? "en",
|
||||
});
|
||||
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
|
||||
sortContent(item.libraryItems.data[0].attributes.contents);
|
||||
sortRangedContent(item.libraryItems.data[0].attributes.contents);
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
item: item.libraryItems.data[0].attributes,
|
||||
|
@ -702,6 +702,8 @@ const ContentLine = ({
|
|||
),
|
||||
});
|
||||
|
||||
console.log(prettySlug(slug, parentSlug));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cJoin(
|
||||
|
@ -722,7 +724,7 @@ const ContentLine = ({
|
|||
selectedTranslation.subtitle
|
||||
)
|
||||
: content
|
||||
? prettySlug(content.slug)
|
||||
? prettySlug(content.slug, parentSlug)
|
||||
: prettySlug(slug, parentSlug)}
|
||||
</h3>
|
||||
</a>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { ScanSet } from "components/Library/ScanSet";
|
||||
import { ScanSetCover } from "components/Library/ScanSetCover";
|
||||
|
@ -15,11 +17,12 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
|
|||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
||||
import { filterHasAttributes, isDefined, sortContent } from "helpers/others";
|
||||
|
||||
import {
|
||||
filterHasAttributes,
|
||||
isDefined,
|
||||
sortRangedContent,
|
||||
} from "helpers/others";
|
||||
import { useLightBox } from "hooks/useLightBox";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { Fragment, useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -44,7 +47,6 @@ const LibrarySlug = ({
|
|||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const [openLightBox, LightBox] = useLightBox();
|
||||
sortContent(item.contents);
|
||||
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
|
@ -158,6 +160,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
});
|
||||
if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id)
|
||||
return { notFound: true };
|
||||
sortRangedContent(item.libraryItems.data[0].attributes.contents);
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
item: item.libraryItems.data[0].attributes,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
|
@ -11,32 +12,29 @@ import { SubPanel } from "components/Panels/SubPanel";
|
|||
import { GetLibraryItemsPreviewQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyItemSubType } from "helpers/formatters";
|
||||
import { LibraryItemUserStatus } from "helpers/types";
|
||||
import { GetStaticProps } from "next";
|
||||
import { Fragment, useState, useMemo } from "react";
|
||||
import {
|
||||
prettyDate,
|
||||
prettyinlineTitle,
|
||||
prettyItemSubType,
|
||||
} from "helpers/formatters";
|
||||
import {
|
||||
LibraryItemUserStatus,
|
||||
SelectiveRequiredNonNullable,
|
||||
} from "helpers/types";
|
||||
import { Icon } from "components/Ico";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import {
|
||||
filterItems,
|
||||
getGroups,
|
||||
sortBy,
|
||||
isUntangibleGroupItem,
|
||||
} from "helpers/libraryItem";
|
||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { ButtonGroup } from "components/Inputs/ButtonGroup";
|
||||
import {
|
||||
filterHasAttributes,
|
||||
isDefinedAndNotEmpty,
|
||||
isUndefined,
|
||||
iterateMap,
|
||||
} from "helpers/others";
|
||||
import { filterHasAttributes, isDefined, isUndefined } from "helpers/others";
|
||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { convertPrice } from "helpers/numbers";
|
||||
import { SmartList } from "components/SmartList";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -65,12 +63,12 @@ interface Props extends AppStaticProps {
|
|||
|
||||
const Library = ({
|
||||
langui,
|
||||
items: libraryItems,
|
||||
items,
|
||||
currencies,
|
||||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const appLayout = useAppLayout();
|
||||
const hoverable = useMediaHoverable();
|
||||
const appLayout = useAppLayout();
|
||||
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
|
@ -97,36 +95,170 @@ const Library = ({
|
|||
LibraryItemUserStatus | undefined
|
||||
>(DEFAULT_FILTERS_STATE.filterUserStatus);
|
||||
|
||||
const filteredItems = useMemo(
|
||||
() =>
|
||||
filterItems(
|
||||
appLayout,
|
||||
libraryItems,
|
||||
searchName,
|
||||
showSubitems,
|
||||
showPrimaryItems,
|
||||
showSecondaryItems,
|
||||
filterUserStatus
|
||||
),
|
||||
const filteringFunction = useCallback(
|
||||
(
|
||||
item: SelectiveRequiredNonNullable<
|
||||
Props["items"][number],
|
||||
"attributes" | "id"
|
||||
>
|
||||
) => {
|
||||
if (!showSubitems && !item.attributes.root_item) return false;
|
||||
if (
|
||||
showSubitems &&
|
||||
isUntangibleGroupItem(item.attributes.metadata?.[0])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (item.attributes.primary && !showPrimaryItems) return false;
|
||||
if (!item.attributes.primary && !showSecondaryItems) return false;
|
||||
|
||||
if (
|
||||
isDefined(filterUserStatus) &&
|
||||
item.id &&
|
||||
appLayout.libraryItemUserStatus
|
||||
) {
|
||||
if (isUntangibleGroupItem(item.attributes.metadata?.[0])) {
|
||||
return false;
|
||||
}
|
||||
if (filterUserStatus === LibraryItemUserStatus.None) {
|
||||
if (appLayout.libraryItemUserStatus[item.id]) {
|
||||
return false;
|
||||
}
|
||||
} else if (
|
||||
filterUserStatus !== appLayout.libraryItemUserStatus[item.id]
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[
|
||||
appLayout,
|
||||
appLayout.libraryItemUserStatus,
|
||||
filterUserStatus,
|
||||
libraryItems,
|
||||
searchName,
|
||||
showPrimaryItems,
|
||||
showSecondaryItems,
|
||||
showSubitems,
|
||||
]
|
||||
);
|
||||
|
||||
const sortedItems = useMemo(
|
||||
() => sortBy(sortingMethod, filteredItems, currencies),
|
||||
[currencies, filteredItems, sortingMethod]
|
||||
const sortingFunction = useCallback(
|
||||
(
|
||||
a: SelectiveRequiredNonNullable<
|
||||
Props["items"][number],
|
||||
"attributes" | "id"
|
||||
>,
|
||||
b: SelectiveRequiredNonNullable<
|
||||
Props["items"][number],
|
||||
"attributes" | "id"
|
||||
>
|
||||
) => {
|
||||
switch (sortingMethod) {
|
||||
case 0: {
|
||||
const titleA = prettyinlineTitle(
|
||||
"",
|
||||
a.attributes.title,
|
||||
a.attributes.subtitle
|
||||
);
|
||||
const titleB = prettyinlineTitle(
|
||||
"",
|
||||
b.attributes.title,
|
||||
b.attributes.subtitle
|
||||
);
|
||||
return titleA.localeCompare(titleB);
|
||||
}
|
||||
case 1: {
|
||||
const priceA = a.attributes.price
|
||||
? convertPrice(a.attributes.price, currencies[0])
|
||||
: 99999;
|
||||
const priceB = b.attributes.price
|
||||
? convertPrice(b.attributes.price, currencies[0])
|
||||
: 99999;
|
||||
return priceA - priceB;
|
||||
}
|
||||
case 2: {
|
||||
const dateA = a.attributes.release_date
|
||||
? prettyDate(a.attributes.release_date)
|
||||
: "9999";
|
||||
const dateB = b.attributes.release_date
|
||||
? prettyDate(b.attributes.release_date)
|
||||
: "9999";
|
||||
return dateA.localeCompare(dateB);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
[currencies, sortingMethod]
|
||||
);
|
||||
|
||||
const groups = useMemo(
|
||||
() => getGroups(langui, groupingMethod, sortedItems),
|
||||
[langui, groupingMethod, sortedItems]
|
||||
const groupingFunction = useCallback(
|
||||
(
|
||||
item: SelectiveRequiredNonNullable<
|
||||
Props["items"][number],
|
||||
"attributes" | "id"
|
||||
>
|
||||
): string[] => {
|
||||
switch (groupingMethod) {
|
||||
case 0: {
|
||||
const categories = filterHasAttributes(
|
||||
item.attributes.categories?.data
|
||||
);
|
||||
if (categories.length > 0) {
|
||||
return categories.map((category) => category.attributes.name);
|
||||
}
|
||||
return [langui.no_category ?? "No category"];
|
||||
}
|
||||
case 1: {
|
||||
if (item.attributes.metadata && item.attributes.metadata.length > 0) {
|
||||
switch (item.attributes.metadata[0]?.__typename) {
|
||||
case "ComponentMetadataAudio":
|
||||
return [langui.audio ?? "Audio"];
|
||||
case "ComponentMetadataGame":
|
||||
return [langui.game ?? "Game"];
|
||||
case "ComponentMetadataBooks":
|
||||
return [langui.textual ?? "Textual"];
|
||||
case "ComponentMetadataVideo":
|
||||
return [langui.video ?? "Video"];
|
||||
case "ComponentMetadataOther":
|
||||
return [langui.other ?? "Other"];
|
||||
case "ComponentMetadataGroup": {
|
||||
switch (
|
||||
item.attributes.metadata[0]?.subitems_type?.data?.attributes
|
||||
?.slug
|
||||
) {
|
||||
case "audio":
|
||||
return [langui.audio ?? "Audio"];
|
||||
case "video":
|
||||
return [langui.video ?? "Video"];
|
||||
case "game":
|
||||
return [langui.game ?? "Game"];
|
||||
case "textual":
|
||||
return [langui.textual ?? "Textual"];
|
||||
case "mixed":
|
||||
return [langui.group ?? "Group"];
|
||||
default: {
|
||||
return [langui.no_type ?? "No type"];
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return [langui.no_type ?? "No type"];
|
||||
}
|
||||
} else {
|
||||
return [langui.no_type ?? "No type"];
|
||||
}
|
||||
}
|
||||
case 2: {
|
||||
if (item.attributes.release_date?.year) {
|
||||
return [item.attributes.release_date.year.toString()];
|
||||
}
|
||||
return [langui.no_year ?? "No year"];
|
||||
}
|
||||
default:
|
||||
return [""];
|
||||
}
|
||||
},
|
||||
[groupingMethod, langui]
|
||||
);
|
||||
|
||||
const subPanel = useMemo(
|
||||
|
@ -273,34 +405,10 @@ const Library = ({
|
|||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
{groups.size === 0 && (
|
||||
<ContentPlaceholder
|
||||
message={langui.no_results_message ?? "No results"}
|
||||
icon={Icon.ChevronLeft}
|
||||
/>
|
||||
)}
|
||||
{iterateMap(groups, (name, items) => (
|
||||
<Fragment key={name}>
|
||||
{isDefinedAndNotEmpty(name) && (
|
||||
<h2
|
||||
className="flex flex-row place-items-center gap-2
|
||||
pb-2 pt-10 text-2xl first-of-type:pt-0"
|
||||
>
|
||||
{name}
|
||||
<Chip>{`${items.length} ${
|
||||
items.length <= 1
|
||||
? langui.result?.toLowerCase() ?? "result"
|
||||
: langui.results?.toLowerCase() ?? "results"
|
||||
}`}</Chip>
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12
|
||||
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]
|
||||
mobile:grid-cols-2 mobile:gap-4"
|
||||
>
|
||||
{filterHasAttributes(items).map((item) => (
|
||||
<Fragment key={item.id}>
|
||||
<SmartList
|
||||
items={filterHasAttributes(items)}
|
||||
getItemId={(item) => item.id}
|
||||
renderItem={({ item }) => (
|
||||
<PreviewCard
|
||||
href={`/library/${item.attributes.slug}`}
|
||||
title={item.attributes.title}
|
||||
|
@ -331,14 +439,39 @@ const Library = ({
|
|||
)
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
)}
|
||||
renderWhenEmpty={() => (
|
||||
<ContentPlaceholder
|
||||
message={langui.no_results_message ?? "No results"}
|
||||
icon={Icon.ChevronLeft}
|
||||
/>
|
||||
)}
|
||||
className="grid-cols-2 items-end desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]"
|
||||
searchingTerm={searchName}
|
||||
sortingFunction={sortingFunction}
|
||||
groupingFunction={groupingFunction}
|
||||
searchingBy={(item) =>
|
||||
prettyinlineTitle(
|
||||
"",
|
||||
item.attributes.title,
|
||||
item.attributes.subtitle
|
||||
)
|
||||
}
|
||||
filteringFunction={filteringFunction}
|
||||
langui={langui}
|
||||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[currencies, groups, keepInfoVisible, langui]
|
||||
[
|
||||
currencies,
|
||||
filteringFunction,
|
||||
groupingFunction,
|
||||
items,
|
||||
keepInfoVisible,
|
||||
langui,
|
||||
searchName,
|
||||
sortingFunction,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticProps } from "next";
|
||||
import { Icon } from "components/Ico";
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { PostPage } from "components/PostPage";
|
||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||
import {
|
||||
|
@ -6,7 +7,6 @@ import {
|
|||
} from "graphql/getPostStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useMemo, useState } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
|
@ -6,20 +8,18 @@ import {
|
|||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { PreviewCard } from "components/PreviewCard";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { GetPostsPreviewQuery } from "graphql/generated";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||
|
||||
import { GetStaticProps } from "next";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
import { Icon } from "components/Ico";
|
||||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { TextInput } from "components/Inputs/TextInput";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
import { useMediaHoverable } from "hooks/useMediaQuery";
|
||||
import { filterHasAttributes } from "helpers/others";
|
||||
import { filterDefined, filterHasAttributes } from "helpers/others";
|
||||
import { SmartList } from "components/SmartList";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -40,7 +40,12 @@ interface Props extends AppStaticProps {
|
|||
posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"];
|
||||
}
|
||||
|
||||
const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
|
||||
const News = ({
|
||||
langui,
|
||||
posts,
|
||||
languages,
|
||||
...otherProps
|
||||
}: Props): JSX.Element => {
|
||||
const hoverable = useMediaHoverable();
|
||||
const [searchName, setSearchName] = useState(
|
||||
DEFAULT_FILTERS_STATE.searchName
|
||||
|
@ -49,11 +54,6 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
|
|||
DEFAULT_FILTERS_STATE.keepInfoVisible
|
||||
);
|
||||
|
||||
const filteredItems = useMemo(
|
||||
() => filterItems(posts, searchName),
|
||||
[posts, searchName]
|
||||
);
|
||||
|
||||
const subPanel = useMemo(
|
||||
() => (
|
||||
<SubPanel>
|
||||
|
@ -96,19 +96,22 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
|
|||
const contentPanel = useMemo(
|
||||
() => (
|
||||
<ContentPanel width={ContentPanelWidthSizes.Full}>
|
||||
<div
|
||||
className="grid grid-cols-1 items-end gap-8
|
||||
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
|
||||
>
|
||||
{filterHasAttributes(filteredItems).map((post) => (
|
||||
<Fragment key={post.id}>
|
||||
<PreviewCard
|
||||
<SmartList
|
||||
items={filterHasAttributes(posts)}
|
||||
getItemId={(post) => post.id}
|
||||
langui={langui}
|
||||
renderItem={({ item: post }) => (
|
||||
<TranslatedPreviewCard
|
||||
href={`/news/${post.attributes.slug}`}
|
||||
title={
|
||||
post.attributes.translations?.[0]?.title ??
|
||||
prettySlug(post.attributes.slug)
|
||||
}
|
||||
description={post.attributes.translations?.[0]?.excerpt}
|
||||
translations={filterDefined(post.attributes.translations).map(
|
||||
(translation) => ({
|
||||
language: translation.language?.data?.attributes?.code,
|
||||
title: translation.title,
|
||||
description: translation.excerpt,
|
||||
})
|
||||
)}
|
||||
languages={languages}
|
||||
slug={post.attributes.slug}
|
||||
thumbnail={post.attributes.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
thumbnailForceAspectRatio
|
||||
|
@ -121,12 +124,18 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
|
|||
position: "Top",
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
className="grid-cols-1 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
|
||||
searchingTerm={searchName}
|
||||
searchingBy={(post) =>
|
||||
`${prettySlug(post.attributes.slug)} ${post.attributes.translations
|
||||
?.map((translation) => translation?.title)
|
||||
.join(" ")}`
|
||||
}
|
||||
/>
|
||||
</ContentPanel>
|
||||
),
|
||||
[filteredItems, keepInfoVisible]
|
||||
[keepInfoVisible, languages, langui, posts, searchName]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -136,6 +145,7 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
|
|||
contentPanel={contentPanel}
|
||||
subPanelIcon={Icon.Search}
|
||||
langui={langui}
|
||||
languages={languages}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
|
@ -149,9 +159,7 @@ export default News;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const posts = await sdk.getPostsPreview({
|
||||
language_code: context.locale ?? "en",
|
||||
});
|
||||
const posts = await sdk.getPostsPreview();
|
||||
if (!posts.posts) return { notFound: true };
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
|
@ -175,20 +183,3 @@ const sortPosts = (posts: Props["posts"]): Props["posts"] =>
|
|||
return dateA.localeCompare(dateB);
|
||||
})
|
||||
.reverse();
|
||||
|
||||
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
const filterItems = (posts: Props["posts"], searchName: string) =>
|
||||
posts.filter((post) => {
|
||||
if (searchName.length > 1) {
|
||||
if (
|
||||
post.attributes?.translations?.[0]?.title
|
||||
.toLowerCase()
|
||||
.includes(searchName.toLowerCase()) === true
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { useCallback, useMemo } from "react";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
|
@ -21,8 +23,6 @@ import {
|
|||
} from "helpers/others";
|
||||
import { WikiPageWithTranslations } from "helpers/types";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
page: WikiPageWithTranslations;
|
||||
|
@ -71,8 +71,16 @@ const WikiPage = ({
|
|||
className="mb-10"
|
||||
/>
|
||||
|
||||
<div className="flex place-content-center gap-4">
|
||||
<div className="flex 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>
|
||||
)}
|
||||
<LanguageSwitcher {...languageSwitcherProps} />
|
||||
</div>
|
||||
|
||||
|
@ -88,7 +96,9 @@ const WikiPage = ({
|
|||
<Img image={page.thumbnail.data.attributes} />
|
||||
)}
|
||||
<div className="my-4 grid gap-4 p-4">
|
||||
<p className="font-headers text-xl">{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">
|
||||
{page.categories?.data.map((category) => (
|
||||
<Chip key={category.id}>{category.attributes?.name}</Chip>
|
||||
|
@ -96,15 +106,19 @@ const WikiPage = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isDefinedAndNotEmpty(selectedTranslation.summary) && (
|
||||
<div className="mb-6">
|
||||
<p className="font-headers text-lg">{langui.summary}</p>
|
||||
<p className="font-headers text-lg font-bold">
|
||||
{langui.summary}
|
||||
</p>
|
||||
<p>{selectedTranslation.summary}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filterHasAttributes(page.definitions, ["translations"]).map(
|
||||
(definition, index) => (
|
||||
<>
|
||||
<DefinitionCard
|
||||
key={index}
|
||||
source={definition.source?.data?.attributes?.name}
|
||||
|
@ -118,7 +132,12 @@ const WikiPage = ({
|
|||
index={index + 1}
|
||||
languages={languages}
|
||||
langui={langui}
|
||||
categories={filterHasAttributes(
|
||||
definition.categories?.data
|
||||
).map((category) => category.attributes.short)}
|
||||
/>
|
||||
<br />
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { InsetBox } from "components/InsetBox";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
|
@ -13,8 +15,6 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
|||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { filterHasAttributes, isDefined } from "helpers/others";
|
||||
import { GetStaticProps } from "next";
|
||||
import { Fragment, useMemo } from "react";
|
||||
|
||||
/*
|
||||
* ╭────────╮
|
||||
|
@ -49,13 +49,10 @@ const Chronology = ({
|
|||
(chronologyEras[currentChronologyEraIndex].attributes?.ending_year ??
|
||||
999999)
|
||||
) {
|
||||
currentChronologyEraIndex += 1;
|
||||
currentChronologyEraIndex++;
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
memo[currentChronologyEraIndex],
|
||||
item.attributes.year
|
||||
)
|
||||
Object.hasOwn(memo[currentChronologyEraIndex], item.attributes.year)
|
||||
) {
|
||||
memo[currentChronologyEraIndex][item.attributes.year].push(item);
|
||||
} else {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
import { AppLayout } from "components/AppLayout";
|
||||
import { NavOption } from "components/PanelComponents/NavOption";
|
||||
import { PanelHeader } from "components/PanelComponents/PanelHeader";
|
||||
import { SubPanel } from "components/Panels/SubPanel";
|
||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||
|
||||
import { GetStaticProps } from "next";
|
||||
import { Icon } from "components/Ico";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetWikiPagesPreviewsQuery } from "graphql/generated";
|
||||
|
@ -12,7 +12,6 @@ import {
|
|||
ContentPanel,
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import { Fragment, useMemo, useState } from "react";
|
||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
import { Button } from "components/Inputs/Button";
|
||||
|
@ -98,7 +97,9 @@ const Wiki = ({
|
|||
/>
|
||||
<HorizontalLine />
|
||||
|
||||
<p className="mb-4 font-headers text-xl">{langui.special_pages}</p>
|
||||
<p className="mb-4 font-headers text-xl font-bold">
|
||||
{langui.special_pages}
|
||||
</p>
|
||||
|
||||
<NavOption title={langui.chronology} url="/wiki/chronology" border />
|
||||
</SubPanel>
|
||||
|
@ -127,6 +128,12 @@ const Wiki = ({
|
|||
translations={page.attributes.translations.map(
|
||||
(translation) => ({
|
||||
title: translation?.title,
|
||||
subtitle:
|
||||
translation?.aliases && translation.aliases.length > 0
|
||||
? translation.aliases
|
||||
.map((alias) => alias?.alias)
|
||||
.join(" | ")
|
||||
: undefined,
|
||||
description: translation?.summary,
|
||||
language: translation?.language?.data?.attributes?.code,
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue