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",
 | ||||
|   }, | ||||
| }; | ||||
|  | ||||
							
								
								
									
										149
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										149
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -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"; | ||||
| 
 | ||||
| /* | ||||
|  *                                         ╭─────────────╮ | ||||
| @ -406,7 +406,7 @@ export const AppLayout = ({ | ||||
|                     insertLabels={ | ||||
|                       new Map([ | ||||
|                         [0, langui.primary_language], | ||||
|                         [1, langui.secondary_language],  | ||||
|                         [1, langui.secondary_language], | ||||
|                       ]) | ||||
|                     } | ||||
|                     onChange={(items) => { | ||||
|  | ||||
| @ -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} | ||||
|     /> | ||||
|     <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> | ||||
|   <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, | ||||
|       }, | ||||
|     ]} | ||||
|   /> | ||||
| ); | ||||
|  | ||||
| @ -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"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
| @ -73,9 +73,10 @@ export const ScanSetCover = ({ | ||||
|           <div> | ||||
|             <div | ||||
|               className="flex flex-row flex-wrap place-items-center | ||||
|               gap-6 pt-10 text-base first-of-type:pt-0" | ||||
|           gap-6 pt-10 text-base first-of-type:pt-0" | ||||
|             > | ||||
|               <h2 id="cover" className="text-2xl"> | ||||
|               <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"; | ||||
| 
 | ||||
| /* | ||||
|  *                                        ╭─────────────╮ | ||||
|  | ||||
							
								
								
									
										183
									
								
								src/components/SmartList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/components/SmartList.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
|         {selectedTranslation?.status && ( | ||||
|           <ToolTip | ||||
|             content={getStatusDescription(selectedTranslation.status, langui)} | ||||
|             maxWidth={"20rem"} | ||||
|           > | ||||
|             <Chip>{selectedTranslation.status}</Chip> | ||||
|           </ToolTip> | ||||
|         )} | ||||
|       <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 && ( | ||||
|           <LanguageSwitcher {...languageSwitcherProps} /> | ||||
|           <> | ||||
|             <Separator /> | ||||
|             <LanguageSwitcher {...languageSwitcherProps} size={"small"} /> | ||||
|           </> | ||||
|         )} | ||||
| 
 | ||||
|         {selectedTranslation?.status && ( | ||||
|           <> | ||||
|             <Separator /> | ||||
|             <ToolTip | ||||
|               content={getStatusDescription(selectedTranslation.status, langui)} | ||||
|               maxWidth={"20rem"} | ||||
|             > | ||||
|               <Chip>{selectedTranslation.status}</Chip> | ||||
|             </ToolTip> | ||||
|           </> | ||||
|         )} | ||||
| 
 | ||||
|         {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<{ | ||||
|                 title: string; | ||||
|               } | null> | null; | ||||
|               titles?: | ||||
|                 | ({ | ||||
|                     title: string; | ||||
|                   } | null)[] | ||||
|                 | null; | ||||
|             } | null; | ||||
|           } | null; | ||||
|         } | null; | ||||
| @ -99,9 +103,11 @@ export const prettyItemSubType = ( | ||||
|           data?: { | ||||
|             attributes?: { | ||||
|               slug: string; | ||||
|               titles?: Array<{ | ||||
|                 title: string; | ||||
|               } | null> | null; | ||||
|               titles?: | ||||
|                 | ({ | ||||
|                     title: string; | ||||
|                   } | 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<{ | ||||
|                 title: string; | ||||
|               } | null> | null; | ||||
|               titles?: | ||||
|                 | ({ | ||||
|                     title: string; | ||||
|                   } | null)[] | ||||
|                 | null; | ||||
|             } | null; | ||||
|           } | null; | ||||
|         } | null; | ||||
| @ -133,27 +141,31 @@ export const prettyItemSubType = ( | ||||
|           data?: { | ||||
|             attributes?: { | ||||
|               slug: string; | ||||
|               titles?: Array<{ | ||||
|                 title: string; | ||||
|               } | null> | null; | ||||
|               titles?: | ||||
|                 | ({ | ||||
|                     title: string; | ||||
|                   } | null)[] | ||||
|                 | null; | ||||
|             } | null; | ||||
|           } | null; | ||||
|         } | null; | ||||
|       } | ||||
|     | { __typename: "ComponentMetadataOther" } | ||||
|     | { | ||||
|         __typename: "ComponentMetadataVideo"; | ||||
|         subtype?: { | ||||
|           data?: { | ||||
|             attributes?: { | ||||
|               slug: string; | ||||
|               titles?: Array<{ | ||||
|                 title: string; | ||||
|               } | null> | null; | ||||
|               titles?: | ||||
|                 | ({ | ||||
|                     title: string; | ||||
|                   } | 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, { | ||||
|         maximumSignificantDigits: 2, | ||||
|       }) + "K" | ||||
|     ); | ||||
|     return `${(number / 1000).toLocaleString(undefined, { | ||||
|       maximumSignificantDigits: 2, | ||||
|     })}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 ( | ||||
|     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; | ||||
|   } | ||||
| }; | ||||
| export const isUntangibleGroupItem = (metadata: any): boolean => | ||||
|   metadata && | ||||
|   metadata.__typename === "ComponentMetadataGroup" && | ||||
|   (metadata.subtype?.data?.attributes?.slug === "variant-set" || | ||||
|     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: [ | ||||
|       { | ||||
|         slug: string; | ||||
|       } | ||||
|     ]; | ||||
|     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) | ||||
|       ); | ||||
|     } | ||||
|     return memo; | ||||
|   }, [videos]); | ||||
|   const [searchName, setSearchName] = useState( | ||||
|     DEFAULT_FILTERS_STATE.searchName | ||||
|   ); | ||||
| 
 | ||||
|   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,107 +216,91 @@ 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}> | ||||
|                       {item.attributes.translations && ( | ||||
|                         <TranslatedPreviewCard | ||||
|                           href={`/contents/${item.attributes.slug}`} | ||||
|                           translations={item.attributes.translations.map( | ||||
|                             (translation) => ({ | ||||
|                               pre_title: translation?.pre_title, | ||||
|                               title: translation?.title, | ||||
|                               subtitle: translation?.subtitle, | ||||
|                               language: | ||||
|                                 translation?.language?.data?.attributes?.code, | ||||
|                             }) | ||||
|                           )} | ||||
|                           slug={item.attributes.slug} | ||||
|                           languages={languages} | ||||
|                           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 | ||||
|                               : 0 | ||||
|                           } | ||||
|                           topChips={ | ||||
|                             item.attributes.type?.data?.attributes | ||||
|                               ? [ | ||||
|                                   item.attributes.type.data.attributes | ||||
|                                     .titles?.[0] | ||||
|                                     ? item.attributes.type.data.attributes | ||||
|                                         .titles[0]?.title | ||||
|                                     : prettySlug( | ||||
|                                         item.attributes.type.data.attributes | ||||
|                                           .slug | ||||
|                                       ), | ||||
|                                 ] | ||||
|                               : undefined | ||||
|                           } | ||||
|                           bottomChips={item.attributes.categories?.data.map( | ||||
|                             (category) => category.attributes?.short ?? "" | ||||
|                           )} | ||||
|                           keepInfoVisible={keepInfoVisible} | ||||
|                         /> | ||||
|                       )} | ||||
|                     </Fragment> | ||||
|                   ))} | ||||
|                 </div> | ||||
|               </Fragment> | ||||
|             ) | ||||
|         )} | ||||
|         <SmartList | ||||
|           items={filterHasAttributes(contents)} | ||||
|           getItemId={(item) => item.id} | ||||
|           renderItem={({ item }) => ( | ||||
|             <> | ||||
|               {item.attributes.translations && ( | ||||
|                 <TranslatedPreviewCard | ||||
|                   href={`/contents/${item.attributes.slug}`} | ||||
|                   translations={item.attributes.translations.map( | ||||
|                     (translation) => ({ | ||||
|                       pre_title: translation?.pre_title, | ||||
|                       title: translation?.title, | ||||
|                       subtitle: translation?.subtitle, | ||||
|                       language: translation?.language?.data?.attributes?.code, | ||||
|                     }) | ||||
|                   )} | ||||
|                   slug={item.attributes.slug} | ||||
|                   languages={languages} | ||||
|                   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 | ||||
|                       : 0 | ||||
|                   } | ||||
|                   topChips={ | ||||
|                     item.attributes.type?.data?.attributes | ||||
|                       ? [ | ||||
|                           item.attributes.type.data.attributes.titles?.[0] | ||||
|                             ? item.attributes.type.data.attributes.titles[0] | ||||
|                                 ?.title | ||||
|                             : prettySlug( | ||||
|                                 item.attributes.type.data.attributes.slug | ||||
|                               ), | ||||
|                         ] | ||||
|                       : undefined | ||||
|                   } | ||||
|                   bottomChips={item.attributes.categories?.data.map( | ||||
|                     (category) => category.attributes?.short ?? "" | ||||
|                   )} | ||||
|                   keepInfoVisible={keepInfoVisible} | ||||
|                 /> | ||||
|               )} | ||||
|             </> | ||||
|           )} | ||||
|           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,72 +405,73 @@ 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}> | ||||
|                   <PreviewCard | ||||
|                     href={`/library/${item.attributes.slug}`} | ||||
|                     title={item.attributes.title} | ||||
|                     subtitle={item.attributes.subtitle} | ||||
|                     thumbnail={item.attributes.thumbnail?.data?.attributes} | ||||
|                     thumbnailAspectRatio="21/29.7" | ||||
|                     thumbnailRounded={false} | ||||
|                     keepInfoVisible={keepInfoVisible} | ||||
|                     topChips={ | ||||
|                       item.attributes.metadata && | ||||
|                       item.attributes.metadata.length > 0 && | ||||
|                       item.attributes.metadata[0] | ||||
|                         ? [prettyItemSubType(item.attributes.metadata[0])] | ||||
|                         : [] | ||||
|                     } | ||||
|                     bottomChips={item.attributes.categories?.data.map( | ||||
|                       (category) => category.attributes?.short ?? "" | ||||
|                     )} | ||||
|                     metadata={{ | ||||
|                       currencies: currencies, | ||||
|                       release_date: item.attributes.release_date, | ||||
|                       price: item.attributes.price, | ||||
|                       position: "Bottom", | ||||
|                     }} | ||||
|                     infoAppend={ | ||||
|                       !isUntangibleGroupItem(item.attributes.metadata?.[0]) && ( | ||||
|                         <PreviewCardCTAs id={item.id} langui={langui} /> | ||||
|                       ) | ||||
|                     } | ||||
|                   /> | ||||
|                 </Fragment> | ||||
|               ))} | ||||
|             </div> | ||||
|           </Fragment> | ||||
|         ))} | ||||
|         <SmartList | ||||
|           items={filterHasAttributes(items)} | ||||
|           getItemId={(item) => item.id} | ||||
|           renderItem={({ item }) => ( | ||||
|             <PreviewCard | ||||
|               href={`/library/${item.attributes.slug}`} | ||||
|               title={item.attributes.title} | ||||
|               subtitle={item.attributes.subtitle} | ||||
|               thumbnail={item.attributes.thumbnail?.data?.attributes} | ||||
|               thumbnailAspectRatio="21/29.7" | ||||
|               thumbnailRounded={false} | ||||
|               keepInfoVisible={keepInfoVisible} | ||||
|               topChips={ | ||||
|                 item.attributes.metadata && | ||||
|                 item.attributes.metadata.length > 0 && | ||||
|                 item.attributes.metadata[0] | ||||
|                   ? [prettyItemSubType(item.attributes.metadata[0])] | ||||
|                   : [] | ||||
|               } | ||||
|               bottomChips={item.attributes.categories?.data.map( | ||||
|                 (category) => category.attributes?.short ?? "" | ||||
|               )} | ||||
|               metadata={{ | ||||
|                 currencies: currencies, | ||||
|                 release_date: item.attributes.release_date, | ||||
|                 price: item.attributes.price, | ||||
|                 position: "Bottom", | ||||
|               }} | ||||
|               infoAppend={ | ||||
|                 !isUntangibleGroupItem(item.attributes.metadata?.[0]) && ( | ||||
|                   <PreviewCardCTAs id={item.id} langui={langui} /> | ||||
|                 ) | ||||
|               } | ||||
|             /> | ||||
|           )} | ||||
|           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,37 +96,46 @@ 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 | ||||
|                 href={`/news/${post.attributes.slug}`} | ||||
|                 title={ | ||||
|                   post.attributes.translations?.[0]?.title ?? | ||||
|                   prettySlug(post.attributes.slug) | ||||
|                 } | ||||
|                 description={post.attributes.translations?.[0]?.excerpt} | ||||
|                 thumbnail={post.attributes.thumbnail?.data?.attributes} | ||||
|                 thumbnailAspectRatio="3/2" | ||||
|                 thumbnailForceAspectRatio | ||||
|                 bottomChips={post.attributes.categories?.data.map( | ||||
|                   (category) => category.attributes?.short ?? "" | ||||
|                 )} | ||||
|                 keepInfoVisible={keepInfoVisible} | ||||
|                 metadata={{ | ||||
|                   release_date: post.attributes.date, | ||||
|                   position: "Top", | ||||
|                 }} | ||||
|               /> | ||||
|             </Fragment> | ||||
|           ))} | ||||
|         </div> | ||||
|         <SmartList | ||||
|           items={filterHasAttributes(posts)} | ||||
|           getItemId={(post) => post.id} | ||||
|           langui={langui} | ||||
|           renderItem={({ item: post }) => ( | ||||
|             <TranslatedPreviewCard | ||||
|               href={`/news/${post.attributes.slug}`} | ||||
|               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 | ||||
|               bottomChips={post.attributes.categories?.data.map( | ||||
|                 (category) => category.attributes?.short ?? "" | ||||
|               )} | ||||
|               keepInfoVisible={keepInfoVisible} | ||||
|               metadata={{ | ||||
|                 release_date: post.attributes.date, | ||||
|                 position: "Top", | ||||
|               }} | ||||
|             /> | ||||
|           )} | ||||
|           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,29 +106,38 @@ 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} | ||||
|                   translations={filterHasAttributes( | ||||
|                     definition.translations | ||||
|                   ).map((translation) => ({ | ||||
|                     language: translation.language.data?.attributes?.code, | ||||
|                     definition: translation.definition, | ||||
|                     status: translation.status, | ||||
|                   }))} | ||||
|                   index={index + 1} | ||||
|                   languages={languages} | ||||
|                   langui={langui} | ||||
|                 /> | ||||
|                 <> | ||||
|                   <DefinitionCard | ||||
|                     key={index} | ||||
|                     source={definition.source?.data?.attributes?.name} | ||||
|                     translations={filterHasAttributes( | ||||
|                       definition.translations | ||||
|                     ).map((translation) => ({ | ||||
|                       language: translation.language.data?.attributes?.code, | ||||
|                       definition: translation.definition, | ||||
|                       status: translation.status, | ||||
|                     }))} | ||||
|                     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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint