There's a lot to unpack here

This commit is contained in:
DrMint 2022-07-08 01:42:38 +02:00
parent ba13c736b0
commit ae25df8d72
76 changed files with 1313 additions and 1107 deletions

View File

@ -1,2 +1,9 @@
*.js src/graphql/generated.ts
*.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

View File

@ -7,6 +7,8 @@ module.exports = {
extends: [ extends: [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"next/core-web-vitals", "next/core-web-vitals",
], ],
rules: { rules: {
@ -122,7 +124,7 @@ module.exports = {
"prefer-exponentiation-operator": "warn", "prefer-exponentiation-operator": "warn",
"prefer-named-capture-group": "warn", "prefer-named-capture-group": "warn",
"prefer-numeric-literals": "warn", "prefer-numeric-literals": "warn",
// "prefer-object-has-own": "warn", "prefer-object-has-own": "warn",
"prefer-object-spread": "warn", "prefer-object-spread": "warn",
"prefer-promise-reject-errors": "warn", "prefer-promise-reject-errors": "warn",
"prefer-regex-literals": "warn", "prefer-regex-literals": "warn",
@ -214,5 +216,52 @@ module.exports = {
/* NEXTJS */ /* NEXTJS */
"@next/next/no-img-element": "off", "@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
View File

@ -22,6 +22,7 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hot-keys": "^2.7.2", "react-hot-keys": "^2.7.2",
"react-swipeable": "^7.0.0", "react-swipeable": "^7.0.0",
"tippy.js": "^6.3.7",
"turndown": "^7.1.1" "turndown": "^7.1.1"
}, },
"devDependencies": { "devDependencies": {
@ -38,10 +39,10 @@
"@typescript-eslint/parser": "^5.30.3", "@typescript-eslint/parser": "^5.30.3",
"eslint": "^8.19.0", "eslint": "^8.19.0",
"eslint-config-next": "12.2.0", "eslint-config-next": "12.2.0",
"eslint-plugin-import": "^2.26.0",
"graphql": "^16.5.0", "graphql": "^16.5.0",
"next-sitemap": "^3.1.7", "next-sitemap": "^3.1.7",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.1.11", "prettier-plugin-tailwindcss": "^0.1.11",
"tailwindcss": "^3.1.4", "tailwindcss": "^3.1.4",
"ts-unused-exports": "^8.0.0", "ts-unused-exports": "^8.0.0",
@ -1336,18 +1337,6 @@
"node": ">=10" "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": { "node_modules/@graphql-codegen/cli/node_modules/mute-stream": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@ -4414,6 +4403,29 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true "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": { "node_modules/end-of-stream": {
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -5589,9 +5601,9 @@
} }
}, },
"node_modules/https-proxy-agent": { "node_modules/https-proxy-agent": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"agent-base": "6", "agent-base": "6",
@ -6780,6 +6792,18 @@
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true "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": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -7611,16 +7635,6 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "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": { "node_modules/prettier-plugin-tailwindcss": {
"version": "0.1.11", "version": "0.1.11",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz",
@ -8067,7 +8081,7 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true "devOptional": true
}, },
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.23.0", "version": "0.23.0",
@ -8175,6 +8189,15 @@
"node": ">=0.10.0" "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": { "node_modules/source-map-js": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@ -8193,15 +8216,6 @@
"source-map": "^0.6.0" "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": { "node_modules/sponge-case": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz", "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-1.0.1.tgz",
@ -10052,12 +10066,6 @@
"brace-expansion": "^1.1.7" "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": { "mute-stream": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@ -12457,6 +12465,28 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true "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": { "end-of-stream": {
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -13362,9 +13392,9 @@
} }
}, },
"https-proxy-agent": { "https-proxy-agent": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dev": true, "dev": true,
"requires": { "requires": {
"agent-base": "6", "agent-base": "6",
@ -14271,6 +14301,12 @@
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true "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": { "ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -14806,13 +14842,6 @@
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true "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": { "prettier-plugin-tailwindcss": {
"version": "0.1.11", "version": "0.1.11",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.11.tgz",
@ -15125,7 +15154,7 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true "devOptional": true
}, },
"scheduler": { "scheduler": {
"version": "0.23.0", "version": "0.23.0",
@ -15212,6 +15241,12 @@
"integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
"dev": true "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": { "source-map-js": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@ -15225,14 +15260,6 @@
"requires": { "requires": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
"source-map": "^0.6.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": { "sponge-case": {

View File

@ -3,13 +3,14 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 12499", "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", "unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport=src/pages --ignoreFiles=generated",
"prebuild": "npm run generate", "prebuild": "npm run generate",
"build": "next build", "build": "next build",
"postbuild": "next-sitemap --config next-sitemap.config.js", "postbuild": "next-sitemap --config next-sitemap.config.js",
"start": "next start -p 12500", "start": "next start -p 12500",
"lint": "next lint", "lint": "next lint",
"eslint": "npx eslint .",
"generate": "graphql-codegen --config graphql-codegen.config.js", "generate": "graphql-codegen --config graphql-codegen.config.js",
"tsc": "tsc", "tsc": "tsc",
"prettier": "prettier --end-of-line auto --write ." "prettier": "prettier --end-of-line auto --write ."
@ -31,6 +32,7 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hot-keys": "^2.7.2", "react-hot-keys": "^2.7.2",
"react-swipeable": "^7.0.0", "react-swipeable": "^7.0.0",
"tippy.js": "^6.3.7",
"turndown": "^7.1.1" "turndown": "^7.1.1"
}, },
"devDependencies": { "devDependencies": {
@ -47,10 +49,10 @@
"@typescript-eslint/parser": "^5.30.3", "@typescript-eslint/parser": "^5.30.3",
"eslint": "^8.19.0", "eslint": "^8.19.0",
"eslint-config-next": "12.2.0", "eslint-config-next": "12.2.0",
"eslint-plugin-import": "^2.26.0",
"graphql": "^16.5.0", "graphql": "^16.5.0",
"next-sitemap": "^3.1.7", "next-sitemap": "^3.1.7",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.1.11", "prettier-plugin-tailwindcss": "^0.1.11",
"tailwindcss": "^3.1.4", "tailwindcss": "^3.1.4",
"ts-unused-exports": "^8.0.0", "ts-unused-exports": "^8.0.0",

View File

@ -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 Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useLayoutEffect, useMemo, useState } from "react"; import { useEffect, useLayoutEffect, useMemo, useState } from "react";
@ -26,6 +10,22 @@ import { TextInput } from "./Inputs/TextInput";
import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder"; import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder";
import { MainPanel } from "./Panels/MainPanel"; import { MainPanel } from "./Panels/MainPanel";
import { Popup } from "./Popup"; 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";
/* /*
* *

View File

@ -1,5 +1,5 @@
import { cJoin } from "helpers/className";
import { MouseEventHandler } from "react"; import { MouseEventHandler } from "react";
import { cJoin } from "helpers/className";
/* /*
* *
@ -31,6 +31,7 @@ export const Ico = ({ onClick, icon, className }: Props): JSX.Element => (
* OTHER * OTHER
*/ */
/* eslint-disable max-len */
export enum Icon { export enum Icon {
Onek = "1k", Onek = "1k",
OnekPlus = "1k_plus", OnekPlus = "1k_plus",

View File

@ -1,7 +1,7 @@
import { UploadImageFragment } from "graphql/generated";
import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img";
import { ImageProps } from "next/image"; import { ImageProps } from "next/image";
import { MouseEventHandler } from "react"; import { MouseEventHandler } from "react";
import { UploadImageFragment } from "graphql/generated";
import { getAssetURL, getImgSizesByQuality, ImageQuality } from "helpers/img";
/* /*
* *

View File

@ -1,9 +1,9 @@
import { useRouter } from "next/router";
import React, { MouseEventHandler } from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { ConditionalWrapper, Wrapper } from "helpers/component"; import { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; 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>; onClick?: MouseEventHandler<HTMLDivElement>;
draggable?: boolean; draggable?: boolean;
badgeNumber?: number; badgeNumber?: number;
size?: "normal" | "small";
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
@ -38,6 +39,7 @@ export const Button = ({
href, href,
locale, locale,
badgeNumber, badgeNumber,
size = "normal",
}: Props): JSX.Element => { }: Props): JSX.Element => {
const router = useRouter(); const router = useRouter();
@ -70,13 +72,17 @@ export const Button = ({
"!border-black bg-black !text-light drop-shadow-black-lg", "!border-black bg-black !text-light drop-shadow-black-lg",
"cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg" "cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
), ),
cIf(size === "small", "px-3 py-1 text-xs"),
className className
)} )}
> >
{isDefined(badgeNumber) && ( {isDefined(badgeNumber) && (
<div <div
className="absolute -top-3 -right-2 grid h-8 w-8 place-items-center rounded-full className={cJoin(
bg-dark font-bold text-light transition-opacity group-hover:opacity-0" `absolute -top-3 -right-2 grid h-8 w-8 place-items-center
rounded-full bg-dark font-bold text-light transition-opacity group-hover:opacity-0`,
cIf(size === "small", "-top-2 -right-2 h-5 w-5")
)}
> >
<p className="-translate-y-[0.05em]">{badgeNumber}</p> <p className="-translate-y-[0.05em]">{badgeNumber}</p>
</div> </div>

View File

@ -1,8 +1,8 @@
import { Button } from "./Button";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { ConditionalWrapper, Wrapper } from "helpers/component"; import { ConditionalWrapper, Wrapper } from "helpers/component";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
import { Button } from "./Button";
/* /*
* *

View File

@ -1,11 +1,11 @@
import { Fragment } from "react";
import { ToolTip } from "../ToolTip";
import { Button } from "./Button";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { prettyLanguage } from "helpers/formatters"; import { prettyLanguage } from "helpers/formatters";
import { iterateMap } from "helpers/others"; 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>; locales: Map<string, number>;
localesIndex: number | undefined; localesIndex: number | undefined;
onLanguageChanged: (index: number) => void; onLanguageChanged: (index: number) => void;
size?: Parameters<typeof Button>[0]["size"];
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
@ -27,6 +28,7 @@ export const LanguageSwitcher = ({
locales, locales,
localesIndex, localesIndex,
languages, languages,
size,
onLanguageChanged, onLanguageChanged,
}: Props): JSX.Element => ( }: Props): JSX.Element => (
<ToolTip <ToolTip
@ -47,6 +49,7 @@ export const LanguageSwitcher = ({
<Button <Button
badgeNumber={locales.size > 1 ? locales.size : undefined} badgeNumber={locales.size > 1 ? locales.size : undefined}
icon={Icon.Translate} icon={Icon.Translate}
size={size}
/> />
</ToolTip> </ToolTip>
); );

View File

@ -1,6 +1,6 @@
import { Fragment, useCallback, useState } from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others"; import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others";
import { Fragment, useCallback, useState } from "react";
/* /*
* *

View File

@ -1,7 +1,7 @@
import { Dispatch, SetStateAction } from "react";
import { ButtonGroup } from "./ButtonGroup";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { Dispatch, SetStateAction } from "react";
import { Button } from "./Button";
/* /*
* *
@ -23,19 +23,21 @@ export const PageSelector = ({
maxPage, maxPage,
className, className,
}: Props): JSX.Element => ( }: Props): JSX.Element => (
<div className={cJoin("flex flex-row place-content-center", className)}> <ButtonGroup
<Button className={cJoin("flex flex-row place-content-center", className)}
onClick={() => setPage((current) => (page > 0 ? current - 1 : current))} buttonsProps={[
className="rounded-r-none" {
icon={Icon.NavigateBefore} onClick: () => setPage((current) => (page > 0 ? current - 1 : current)),
/> icon: Icon.NavigateBefore,
<Button className="rounded-none border-x-0" text={(page + 1).toString()} /> },
<Button {
onClick={() => text: (page + 1).toString(),
setPage((current) => (page < maxPage ? page + 1 : current)) },
} {
className="rounded-l-none" onClick: () =>
icon={Icon.NavigateNext} setPage((current) => (page < maxPage ? page + 1 : current)),
/> icon: Icon.NavigateNext,
</div> },
]}
/>
); );

View File

@ -1,7 +1,7 @@
import { Dispatch, Fragment, SetStateAction, useState } from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { useToggle } from "hooks/useToggle"; import { useToggle } from "hooks/useToggle";
import { Dispatch, Fragment, SetStateAction, useState } from "react";
/* /*
* *

View File

@ -1,6 +1,6 @@
import { Dispatch, SetStateAction } from "react";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { useToggle } from "hooks/useToggle"; import { useToggle } from "hooks/useToggle";
import { Dispatch, SetStateAction } from "react";
/* /*
* *

View File

@ -1,7 +1,7 @@
import { Dispatch, SetStateAction } from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
import { Dispatch, SetStateAction } from "react";
/* /*
* *

View File

@ -1,3 +1,4 @@
import { Fragment, useCallback, useMemo } from "react";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { Img } from "components/Img"; import { Img } from "components/Img";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
@ -14,7 +15,6 @@ import {
isDefinedAndNotEmpty, isDefinedAndNotEmpty,
} from "helpers/others"; } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useCallback, useMemo } from "react";
/* /*
* *
@ -139,7 +139,7 @@ export const ScanSet = ({
<LanguageSwitcher {...languageSwitcherProps} /> <LanguageSwitcher {...languageSwitcherProps} />
<div className="grid place-content-center place-items-center"> <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 <ToolTip
content={getStatusDescription(selectedScan.status, langui)} content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"} maxWidth={"20rem"}
@ -150,7 +150,8 @@ export const ScanSet = ({
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && ( {selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data).map( {filterHasAttributes(selectedScan.scanners.data).map(
(scanner) => ( (scanner) => (
@ -168,7 +169,8 @@ export const ScanSet = ({
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && ( {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data).map( {filterHasAttributes(selectedScan.cleaners.data).map(
(cleaner) => ( (cleaner) => (
@ -187,7 +189,8 @@ export const ScanSet = ({
{selectedScan.typesetters && {selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && ( selectedScan.typesetters.data.length > 0 && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data).map( {filterHasAttributes(selectedScan.typesetters.data).map(
(typesetter) => ( (typesetter) => (
@ -205,6 +208,7 @@ export const ScanSet = ({
{isDefinedAndNotEmpty(selectedScan.notes) && ( {isDefinedAndNotEmpty(selectedScan.notes) && (
<ToolTip content={selectedScan.notes}> <ToolTip content={selectedScan.notes}>
{/* TODO: Add Notes to Typesetters */}
<Chip>{"Notes"}</Chip> <Chip>{"Notes"}</Chip>
</ToolTip> </ToolTip>
)} )}

View File

@ -1,3 +1,4 @@
import { Fragment, useCallback, useMemo } from "react";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { Img } from "components/Img"; import { Img } from "components/Img";
import { RecorderChip } from "components/RecorderChip"; import { RecorderChip } from "components/RecorderChip";
@ -10,7 +11,6 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
import { getAssetURL, ImageQuality } from "helpers/img"; import { getAssetURL, ImageQuality } from "helpers/img";
import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useCallback, useMemo } from "react";
/* /*
* *
@ -73,9 +73,10 @@ export const ScanSetCover = ({
<div> <div>
<div <div
className="flex flex-row flex-wrap place-items-center className="flex flex-row flex-wrap place-items-center
gap-6 pt-10 text-base first-of-type:pt-0" gap-6 pt-10 text-base first-of-type:pt-0"
> >
<h2 id="cover" className="text-2xl"> <h2 id={"cover"} className="text-2xl">
{/* TODO: Add Cover to langui */}
{"Cover"} {"Cover"}
</h2> </h2>
@ -91,7 +92,7 @@ export const ScanSetCover = ({
<LanguageSwitcher {...languageSwitcherProps} /> <LanguageSwitcher {...languageSwitcherProps} />
<div className="grid place-content-center place-items-center"> <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 <ToolTip
content={getStatusDescription(selectedScan.status, langui)} content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"} maxWidth={"20rem"}
@ -102,7 +103,8 @@ export const ScanSetCover = ({
{selectedScan.scanners && selectedScan.scanners.data.length > 0 && ( {selectedScan.scanners && selectedScan.scanners.data.length > 0 && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data).map( {filterHasAttributes(selectedScan.scanners.data).map(
(scanner) => ( (scanner) => (
@ -120,7 +122,8 @@ export const ScanSetCover = ({
{selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && ( {selectedScan.cleaners && selectedScan.cleaners.data.length > 0 && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data).map( {filterHasAttributes(selectedScan.cleaners.data).map(
(cleaner) => ( (cleaner) => (
@ -139,7 +142,8 @@ export const ScanSetCover = ({
{selectedScan.typesetters && {selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && ( selectedScan.typesetters.data.length > 0 && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data).map( {filterHasAttributes(selectedScan.typesetters.data).map(
(typesetter) => ( (typesetter) => (

View File

@ -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 { HorizontalLine } from "components/HorizontalLine";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { Img } from "components/Img"; import { Img } from "components/Img";
@ -10,10 +14,6 @@ import { slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img"; import { getAssetURL, ImageQuality } from "helpers/img";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import { useLightBox } from "hooks/useLightBox"; 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") .split("\n")
.map((line) => { .map((line) => {
if (line === "* * *" || line === "---") { if (line === "* * *" || line === "---") {
scenebreakIndex += 1; scenebreakIndex++;
return `<SceneBreak id="scene-break-${scenebreakIndex}">`; return `<SceneBreak id="scene-break-${scenebreakIndex}">`;
} }
@ -506,7 +506,7 @@ const markdawnHeadersParser = (
let index = 2; let index = 2;
while (visitedSlugs.includes(newSlug)) { while (visitedSlugs.includes(newSlug)) {
newSlug = `${slug}-${index}`; newSlug = `${slug}-${index}`;
index += 1; index++;
} }
visitedSlugs.push(newSlug); visitedSlugs.push(newSlug);
return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`; return `<h${headerLevel} id="${newSlug}">${lineText}</h${headerLevel}>`;
@ -543,7 +543,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
}); });
h2 += 1; h2++;
h3 = -1; h3 = -1;
h4 = -1; h4 = -1;
h5 = -1; h5 = -1;
@ -554,7 +554,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
}); });
h3 += 1; h3++;
h4 = -1; h4 = -1;
h5 = -1; h5 = -1;
scenebreak = 0; scenebreak = 0;
@ -564,7 +564,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
}); });
h4 += 1; h4++;
h5 = -1; h5 = -1;
scenebreak = 0; scenebreak = 0;
} else if (h4 >= 0 && line.startsWith("<h5 id=")) { } else if (h4 >= 0 && line.startsWith("<h5 id=")) {
@ -573,7 +573,7 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
slug: getSlug(line), slug: getSlug(line),
children: [], children: [],
}); });
h5 += 1; h5++;
scenebreak = 0; scenebreak = 0;
} else if (h5 >= 0 && line.startsWith("<h6 id=")) { } else if (h5 >= 0 && line.startsWith("<h6 id=")) {
toc.children[h2].children[h3].children[h4].children[h5].children.push({ toc.children[h2].children[h3].children[h4].children[h5].children.push({
@ -582,8 +582,8 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
children: [], children: [],
}); });
} else if (line.startsWith(`<SceneBreak`)) { } else if (line.startsWith(`<SceneBreak`)) {
scenebreak += 1; scenebreak++;
scenebreakIndex += 1; scenebreakIndex++;
const child = { const child = {
title: `Scene break ${scenebreak}`, title: `Scene break ${scenebreak}`,

View File

@ -1,9 +1,9 @@
import { useRouter } from "next/router";
import { MouseEventHandler, useMemo } from "react";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { cJoin, cIf } from "helpers/className"; import { cJoin, cIf } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";
import { useRouter } from "next/router";
import { MouseEventHandler, useMemo } from "react";
/* /*
* *

View File

@ -1,3 +1,5 @@
import Markdown from "markdown-to-jsx";
import Link from "next/link";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { NavOption } from "components/PanelComponents/NavOption"; import { NavOption } from "components/PanelComponents/NavOption";
@ -6,8 +8,6 @@ import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { useMediaDesktop } from "hooks/useMediaQuery"; import { useMediaDesktop } from "hooks/useMediaQuery";
import Markdown from "markdown-to-jsx";
import Link from "next/link";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { isDefinedAndNotEmpty } from "helpers/others"; import { isDefinedAndNotEmpty } from "helpers/others";

View File

@ -1,7 +1,7 @@
import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className";
import { Dispatch, SetStateAction, useEffect } from "react"; import { Dispatch, SetStateAction, useEffect } from "react";
import Hotkeys from "react-hot-keys"; import Hotkeys from "react-hot-keys";
import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className";
/* /*
* *

View File

@ -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 { Fragment, useCallback, useMemo } from "react";
import { AppLayout } from "./AppLayout"; import { AppLayout } from "./AppLayout";
import { Chip } from "./Chip"; import { Chip } from "./Chip";
@ -15,6 +9,12 @@ import { SubPanel } from "./Panels/SubPanel";
import { RecorderChip } from "./RecorderChip"; import { RecorderChip } from "./RecorderChip";
import { ThumbnailHeader } from "./ThumbnailHeader"; import { ThumbnailHeader } from "./ThumbnailHeader";
import { ToolTip } from "./ToolTip"; import { ToolTip } from "./ToolTip";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { 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 && ( {selectedTranslation && (
<div className="grid grid-flow-col place-content-center place-items-center gap-2"> <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 <ToolTip
content={getStatusDescription( content={getStatusDescription(
@ -111,7 +111,7 @@ export const PostPage = ({
{post.authors && post.authors.data.length > 0 && ( {post.authors && post.authors.data.length > 0 && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(post.authors.data).map((author) => ( {filterHasAttributes(post.authors.data).map((author) => (
<Fragment key={author.id}> <Fragment key={author.id}>

View File

@ -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 { useAppLayout } from "contexts/AppLayoutContext";
import { import {
DatePickerFragment, DatePickerFragment,
@ -15,11 +20,6 @@ import {
} from "helpers/formatters"; } from "helpers/formatters";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { useSmartLanguage } from "hooks/useSmartLanguage"; 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 <Ico
icon={Icon.PlayCircleOutline} icon={Icon.PlayCircleOutline}
className="text-6xl opacity-0 transition-opacity group-hover:opacity-100" className="!text-6xl opacity-0 transition-opacity group-hover:opacity-100"
/> />
</div> </div>
<div <div
@ -271,7 +271,7 @@ export const PreviewCard = ({
<p className="mb-1 break-words leading-none">{pre_title}</p> <p className="mb-1 break-words leading-none">{pre_title}</p>
)} )}
{title && ( {title && (
<p className="break-words font-headers font-bold text-lg leading-none"> <p className="break-words font-headers text-lg font-bold leading-none">
{title} {title}
</p> </p>
)} )}

View File

@ -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 { UploadImageFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { useSmartLanguage } from "hooks/useSmartLanguage"; 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"> <div className="my-1 flex flex-col">
{pre_title && <p className="mb-1 leading-none">{pre_title}</p>} {pre_title && <p className="mb-1 leading-none">{pre_title}</p>}
{title && ( {title && (
<p className="font-headers text-lg leading-none">{title}</p> <p className="font-headers text-lg font-bold leading-none">
{title}
</p>
)} )}
{subtitle && <p className="leading-none">{subtitle}</p>} {subtitle && <p className="leading-none">{subtitle}</p>}
</div> </div>

View File

@ -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 { Chip } from "components/Chip";
import { RecorderChipFragment } from "graphql/generated"; import { RecorderChipFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { ImageQuality } from "helpers/img"; import { ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/others";
import { Fragment } from "react";
import { Img } from "./Img";
import { Markdawn } from "./Markdown/Markdawn";
import { ToolTip } from "./ToolTip";
/* /*
* *

View 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"
/>
)}
</>
);
};

View File

@ -1,3 +1,4 @@
// eslint-disable-next-line import/named
import Tippy, { TippyProps } from "@tippyjs/react"; import Tippy, { TippyProps } from "@tippyjs/react";
import { cJoin } from "helpers/className"; import { cJoin } from "helpers/className";
import "tippy.js/animations/scale-subtle.css"; import "tippy.js/animations/scale-subtle.css";

View File

@ -1,3 +1,4 @@
import { Fragment } from "react";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
@ -12,7 +13,10 @@ import {
getStatusDescription, getStatusDescription,
} from "helpers/others"; } from "helpers/others";
import { Fragment } from "react"; /*
*
* COMPONENT
*/
interface Props { interface Props {
item: NonNullable<GetChronologyItemsQuery["chronologyItems"]>["data"][number]; item: NonNullable<GetChronologyItemsQuery["chronologyItems"]>["data"][number];
@ -20,6 +24,8 @@ interface Props {
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ChronologyItemComponent = ({ export const ChronologyItemComponent = ({
langui, langui,
item, item,
@ -124,6 +130,11 @@ export const ChronologyItemComponent = ({
return <></>; return <></>;
}; };
/*
*
* PRIVATE METHODS
*/
const generateAnchor = ( const generateAnchor = (
year: number | undefined, year: number | undefined,
month: number | null | undefined, month: number | null | undefined,

View File

@ -2,6 +2,11 @@ import { ChronologyItemComponent } from "components/Wiki/Chronology/ChronologyIt
import { GetChronologyItemsQuery } from "graphql/generated"; import { GetChronologyItemsQuery } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
/*
*
* COMPONENT
*/
interface Props { interface Props {
year: number; year: number;
items: NonNullable< items: NonNullable<
@ -10,6 +15,8 @@ interface Props {
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const ChronologyYearComponent = ({ export const ChronologyYearComponent = ({
langui, langui,
year, year,

View File

@ -1,9 +1,14 @@
import { useCallback } from "react";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { getStatusDescription } from "helpers/others"; import { getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { useCallback } from "react";
/*
*
* COMPONENT
*/
interface Props { interface Props {
source?: string; source?: string;
@ -15,14 +20,18 @@ interface Props {
languages: AppStaticProps["languages"]; languages: AppStaticProps["languages"];
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
index: number; index: number;
categories: string[];
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const DefinitionCard = ({ const DefinitionCard = ({
source, source,
translations = [], translations = [],
languages, languages,
langui, langui,
index, index,
categories,
}: Props): JSX.Element => { }: Props): JSX.Element => {
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
useSmartLanguage({ useSmartLanguage({
@ -36,24 +45,51 @@ const DefinitionCard = ({
return ( return (
<> <>
<div className="flex place-items-center gap-2"> <div className="flex flex-wrap place-items-center gap-2">
<p className="font-headers text-lg">{`${langui.definition} ${index}`}</p> <p className="font-headers text-lg font-bold">{`${langui.definition} ${index}`}</p>
{selectedTranslation?.status && (
<ToolTip
content={getStatusDescription(selectedTranslation.status, langui)}
maxWidth={"20rem"}
>
<Chip>{selectedTranslation.status}</Chip>
</ToolTip>
)}
{translations.length > 1 && ( {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> </div>
<p className="italic">{`${langui.source}: ${source}`}</p> <p className="italic">{`${langui.source}: ${source}`}</p>
<p>{selectedTranslation?.definition}</p> <p>{selectedTranslation?.definition}</p>
</> </>
); );
}; };
export default DefinitionCard; export default DefinitionCard;
/*
*
* PRIVATE COMPONENTS
*/
const Separator = () => <div className="mx-1 h-5 w-[1px] bg-dark" />;

View File

@ -1,10 +1,10 @@
import React, { ReactNode, useContext, useState } from "react";
import { isDefined } from "helpers/others"; import { isDefined } from "helpers/others";
import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types"; import { LibraryItemUserStatus, RequiredNonNullable } from "helpers/types";
import { useDarkMode } from "hooks/useDarkMode"; import { useDarkMode } from "hooks/useDarkMode";
import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage"; import { useStateWithLocalStorage } from "hooks/useStateWithLocalStorage";
import React, { ReactNode, useContext, useState } from "react";
export interface AppLayoutState { interface AppLayoutState {
subPanelOpen: boolean | undefined; subPanelOpen: boolean | undefined;
toggleSubPanelOpen: () => void; toggleSubPanelOpen: () => void;
setSubPanelOpen: React.Dispatch< setSubPanelOpen: React.Dispatch<

View File

@ -1,3 +1,4 @@
import { GetStaticPropsContext } from "next";
import { import {
GetCurrenciesQuery, GetCurrenciesQuery,
GetLanguagesQuery, GetLanguagesQuery,
@ -5,8 +6,6 @@ import {
} from "graphql/generated"; } from "graphql/generated";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetStaticPropsContext } from "next";
export type AppStaticProps = { export type AppStaticProps = {
langui: NonNullable< langui: NonNullable<
NonNullable< NonNullable<
@ -21,7 +20,7 @@ export const getAppStaticProps = async (
context: GetStaticPropsContext context: GetStaticPropsContext
): Promise<AppStaticProps> => { ): Promise<AppStaticProps> => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const languages = (await sdk.getLanguages()).languages; const { languages } = await sdk.getLanguages();
if (languages?.data) { if (languages?.data) {
languages.data.sort((a, b) => 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) { if (currencies?.data) {
currencies.data.sort((a, b) => currencies.data.sort((a, b) =>
a.attributes && b.attributes a.attributes && b.attributes

View File

@ -1,14 +1,15 @@
import { PostWithTranslations } from "helpers/types"; import { GetStaticProps } from "next";
import { GetStaticProps, GetStaticPropsContext } from "next";
import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "./getAppStaticProps";
import { getReadySdk } from "./sdk"; import { getReadySdk } from "./sdk";
import { PostWithTranslations } from "helpers/types";
export interface PostStaticProps extends AppStaticProps { export interface PostStaticProps extends AppStaticProps {
post: PostWithTranslations; post: PostWithTranslations;
} }
export const getPostStaticProps = (slug: string): GetStaticProps => { export const getPostStaticProps =
return async (context) => { (slug: string): GetStaticProps =>
async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const post = await sdk.getPost({ const post = await sdk.getPost({
slug: slug, slug: slug,
@ -25,4 +26,3 @@ export const getPostStaticProps = (slug: string): GetStaticProps => {
} }
return { notFound: true }; return { notFound: true };
}; };
};

View File

@ -1,4 +1,4 @@
query getPostsPreview($language_code: String) { query getPostsPreview {
posts(filters: { hidden: { eq: false } }) { posts(filters: { hidden: { eq: false } }) {
data { data {
id id
@ -22,7 +22,14 @@ query getPostsPreview($language_code: String) {
} }
} }
} }
translations(filters: { language: { code: { eq: $language_code } } }) { translations {
language {
data {
attributes {
code
}
}
}
title title
excerpt excerpt
thumbnail { thumbnail {

View File

@ -77,6 +77,15 @@ query getWikiPage($slug: String, $language_code: String) {
} }
} }
} }
categories {
data {
id
attributes {
name
short
}
}
}
translations { translations {
language { language {
data { data {
@ -85,7 +94,6 @@ query getWikiPage($slug: String, $language_code: String) {
} }
} }
} }
source_language { source_language {
data { data {
attributes { attributes {

View File

@ -1,7 +1,7 @@
import { GraphQLClient } from "graphql-request"; import { GraphQLClient } from "graphql-request";
import { getSdk } from "graphql/generated"; import { getSdk } from "graphql/generated";
export const getReadySdk = () => { export const getReadySdk = (): ReturnType<typeof getSdk> => {
const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", { const client = new GraphQLClient(process.env.URL_GRAPHQL ?? "", {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` }, headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
}); });

View File

@ -1,11 +1,8 @@
export const cIf = ( export const cIf = (
condition: boolean | null | undefined | string, condition: boolean | string | null | undefined,
ifTrueCss: string, ifTrueCss: string,
ifFalseCss?: string ifFalseCss?: string
) => { ): string => (condition ? ifTrueCss : ifFalseCss ?? "");
return condition ? ifTrueCss : ifFalseCss ?? "";
};
export const cJoin = (...args: (string | undefined)[]): string => { export const cJoin = (...args: (string | undefined)[]): string =>
return args.filter((elem) => elem).join(" "); args.filter((elem) => elem).join(" ");
};

View File

@ -1,7 +1,7 @@
import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettySlug } from "./formatters"; import { prettySlug } from "./formatters";
import { isDefined } from "./others"; import { isDefined } from "./others";
import { Content } from "./types"; import { Content } from "./types";
import { AppStaticProps } from "graphql/getAppStaticProps";
interface Description { interface Description {
langui: AppStaticProps["langui"]; langui: AppStaticProps["langui"];
@ -49,13 +49,11 @@ export const getDescription = ({
return description; return description;
}; };
const prettyMarkdown = (markdown: string): string => { const prettyMarkdown = (markdown: string): string =>
return markdown.replace(/[*]/gu, "").replace(/[_]/gu, ""); markdown.replace(/[*]/gu, "").replace(/[_]/gu, "");
};
const prettyChip = (items: (string | undefined)[]): string => { const prettyChip = (items: (string | undefined)[]): string =>
return items items
.filter((item) => isDefined(item)) .filter((item) => isDefined(item))
.map((item) => `(${item})`) .map((item) => `(${item})`)
.join(" "); .join(" ");
};

View File

@ -1,6 +1,7 @@
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
import { AppStaticProps } from "../graphql/getAppStaticProps"; import { AppStaticProps } from "../graphql/getAppStaticProps";
import { convertPrice } from "./numbers"; import { convertPrice } from "./numbers";
import { isDefinedAndNotEmpty } from "./others";
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
export const prettyDate = (datePicker: DatePickerFragment): string => { export const prettyDate = (datePicker: DatePickerFragment): string => {
let result = ""; let result = "";
@ -20,7 +21,7 @@ export const prettyPrice = (
if (!targetCurrencyCode) return ""; if (!targetCurrencyCode) return "";
let result = ""; let result = "";
currencies.map((currency) => { currencies.map((currency) => {
if (currency?.attributes?.code === targetCurrencyCode) { if (currency.attributes?.code === targetCurrencyCode) {
const amountInTargetCurrency = convertPrice(pricePicker, currency); const amountInTargetCurrency = convertPrice(pricePicker, currency);
result = result =
currency.attributes.symbol + currency.attributes.symbol +
@ -34,20 +35,20 @@ export const prettyPrice = (
}; };
export const prettySlug = (slug?: string, parentSlug?: string): string => { export const prettySlug = (slug?: string, parentSlug?: string): string => {
if (slug) { let newSlug = slug;
if (parentSlug && slug.startsWith(parentSlug)) if (newSlug) {
slug = slug.substring(parentSlug.length + 1); if (isDefinedAndNotEmpty(parentSlug) && newSlug.startsWith(parentSlug))
slug = slug.replace(new RegExp("-", "g"), " "); newSlug = newSlug.substring(parentSlug.length + 1);
slug = slug.replace(new RegExp("_", "g"), " "); newSlug = newSlug.replaceAll("-", " ");
return capitalizeString(slug); return capitalizeString(newSlug);
} }
return ""; return "";
}; };
export const prettyinlineTitle = ( export const prettyinlineTitle = (
pretitle: string | undefined | null, pretitle: string | null | undefined,
title: string | undefined | null, title: string | null | undefined,
subtitle: string | undefined | null subtitle: string | null | undefined
): string => { ): string => {
let result = ""; let result = "";
if (pretitle) result += `${pretitle}: `; if (pretitle) result += `${pretitle}: `;
@ -59,7 +60,7 @@ export const prettyinlineTitle = (
export const prettyItemType = ( export const prettyItemType = (
metadata: any, metadata: any,
langui: AppStaticProps["langui"] langui: AppStaticProps["langui"]
): string | undefined | null => { ): string | null | undefined => {
switch (metadata.__typename) { switch (metadata.__typename) {
case "ComponentMetadataAudio": case "ComponentMetadataAudio":
return langui.audio; return langui.audio;
@ -78,6 +79,7 @@ export const prettyItemType = (
} }
}; };
/* eslint-disable id-denylist */
export const prettyItemSubType = ( export const prettyItemSubType = (
metadata: metadata:
| { | {
@ -86,9 +88,11 @@ export const prettyItemSubType = (
data?: { data?: {
attributes?: { attributes?: {
slug: string; slug: string;
titles?: Array<{ titles?:
title: string; | ({
} | null> | null; title: string;
} | null)[]
| null;
} | null; } | null;
} | null; } | null;
} | null; } | null;
@ -99,9 +103,11 @@ export const prettyItemSubType = (
data?: { data?: {
attributes?: { attributes?: {
slug: string; slug: string;
titles?: Array<{ titles?:
title: string; | ({
} | null> | null; title: string;
} | null)[]
| null;
} | null; } | null;
} | null; } | null;
} | null; } | null;
@ -109,12 +115,12 @@ export const prettyItemSubType = (
| { | {
__typename: "ComponentMetadataGame"; __typename: "ComponentMetadataGame";
platforms?: { platforms?: {
data: Array<{ data: {
id?: string | null; id?: string | null;
attributes?: { attributes?: {
short: string; short: string;
} | null; } | null;
}>; }[];
} | null; } | null;
} }
| { | {
@ -123,9 +129,11 @@ export const prettyItemSubType = (
data?: { data?: {
attributes?: { attributes?: {
slug: string; slug: string;
titles?: Array<{ titles?:
title: string; | ({
} | null> | null; title: string;
} | null)[]
| null;
} | null; } | null;
} | null; } | null;
} | null; } | null;
@ -133,27 +141,31 @@ export const prettyItemSubType = (
data?: { data?: {
attributes?: { attributes?: {
slug: string; slug: string;
titles?: Array<{ titles?:
title: string; | ({
} | null> | null; title: string;
} | null)[]
| null;
} | null; } | null;
} | null; } | null;
} | null; } | null;
} }
| { __typename: "ComponentMetadataOther" }
| { | {
__typename: "ComponentMetadataVideo"; __typename: "ComponentMetadataVideo";
subtype?: { subtype?: {
data?: { data?: {
attributes?: { attributes?: {
slug: string; slug: string;
titles?: Array<{ titles?:
title: string; | ({
} | null> | null; title: string;
} | null)[]
| null;
} | null; } | null;
} | null; } | null;
} | null; } | null;
} }
| { __typename: "ComponentMetadataOther" }
| { __typename: "Error" } | { __typename: "Error" }
| null | null
): string => { ): string => {
@ -163,27 +175,27 @@ export const prettyItemSubType = (
case "ComponentMetadataBooks": case "ComponentMetadataBooks":
case "ComponentMetadataVideo": case "ComponentMetadataVideo":
return metadata.subtype?.data?.attributes?.titles && 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]
? metadata.subtype.data.attributes.titles[0].title ? metadata.subtype.data.attributes.titles[0].title
: prettySlug(metadata.subtype?.data?.attributes?.slug); : prettySlug(metadata.subtype?.data?.attributes?.slug);
case "ComponentMetadataGame": case "ComponentMetadataGame":
return metadata.platforms?.data && return metadata.platforms?.data &&
metadata.platforms?.data.length > 0 && metadata.platforms.data.length > 0 &&
metadata.platforms.data[0].attributes metadata.platforms.data[0].attributes
? metadata.platforms.data[0].attributes.short ? metadata.platforms.data[0].attributes.short
: ""; : "";
case "ComponentMetadataGroup": { case "ComponentMetadataGroup": {
const firstPart = const firstPart =
metadata.subtype?.data?.attributes?.titles && 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]
? metadata.subtype.data.attributes.titles[0].title ? metadata.subtype.data.attributes.titles[0].title
: prettySlug(metadata.subtype?.data?.attributes?.slug); : prettySlug(metadata.subtype?.data?.attributes?.slug);
const secondPart = const secondPart =
metadata.subitems_type?.data?.attributes?.titles && 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]
? metadata.subitems_type.data.attributes.titles[0].title ? metadata.subitems_type.data.attributes.titles[0].title
: prettySlug(metadata.subitems_type?.data?.attributes?.slug); : prettySlug(metadata.subitems_type?.data?.attributes?.slug);
@ -194,8 +206,8 @@ export const prettyItemSubType = (
} }
} }
return ""; return "";
/* eslint-enable @typescript-eslint/no-explicit-any */
}; };
/* eslint-enable id-denylist */
export const prettyShortenNumber = (number: number): string => { export const prettyShortenNumber = (number: number): string => {
if (number > 1000000) { if (number > 1000000) {
@ -203,11 +215,9 @@ export const prettyShortenNumber = (number: number): string => {
maximumSignificantDigits: 3, maximumSignificantDigits: 3,
}); });
} else if (number > 1000) { } else if (number > 1000) {
return ( return `${(number / 1000).toLocaleString(undefined, {
(number / 1000).toLocaleString(undefined, { maximumSignificantDigits: 2,
maximumSignificantDigits: 2, })}K`;
}) + "K"
);
} }
return number.toLocaleString(); return number.toLocaleString();
}; };
@ -215,18 +225,19 @@ export const prettyShortenNumber = (number: number): string => {
export const prettyDuration = (seconds: number): string => { export const prettyDuration = (seconds: number): string => {
let hours = 0; let hours = 0;
let minutes = 0; let minutes = 0;
while (seconds > 60) { let remainingSeconds = seconds;
minutes += 1; while (remainingSeconds > 60) {
seconds -= 60; minutes++;
remainingSeconds -= 60;
} }
while (minutes > 60) { while (minutes > 60) {
hours += 1; hours++;
minutes -= 60; minutes -= 60;
} }
let result = ""; let result = "";
if (hours) result += hours.toString().padStart(2, "0") + ":"; if (hours) result += `${hours.toString().padStart(2, "0")}:`;
result += minutes.toString().padStart(2, "0") + ":"; result += `${minutes.toString().padStart(2, "0")}:`;
result += seconds.toString().padStart(2, "0"); result += remainingSeconds.toString().padStart(2, "0");
return result; return result;
}; };
@ -236,21 +247,20 @@ export const prettyLanguage = (
): string => { ): string => {
let result = code; let result = code;
languages.forEach((language) => { languages.forEach((language) => {
if (language?.attributes?.code === code) if (language.attributes?.code === code)
result = language.attributes.localized_name; result = language.attributes.localized_name;
}); });
return result; return result;
}; };
export const prettyURL = (url: string): string => { export const prettyURL = (url: string): string => {
let domain = new URL(url); const domain = new URL(url);
return domain.hostname.replace("www.", ""); return domain.hostname.replace("www.", "");
}; };
const capitalizeString = (string: string): string => { const capitalizeString = (string: string): string => {
const capitalizeWord = (word: string): string => { const capitalizeWord = (word: string): string =>
return word.charAt(0).toUpperCase() + word.substring(1); word.charAt(0).toUpperCase() + word.substring(1);
};
let words = string.split(" "); let words = string.split(" ");
words = words.map((word) => capitalizeWord(word)); words = words.map((word) => capitalizeWord(word));
@ -262,7 +272,7 @@ export const slugify = (string: string | undefined): string => {
return ""; return "";
} }
return string return string
.replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/g, "a") .replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/gu, "a")
.replace(/[çÇ]/gu, "c") .replace(/[çÇ]/gu, "c")
.replace(/[ðÐ]/gu, "d") .replace(/[ðÐ]/gu, "d")
.replace(/[ÈÉÊËéèêë]/gu, "e") .replace(/[ÈÉÊËéèêë]/gu, "e")

View File

@ -73,12 +73,12 @@ export const getOgImage = (
const imgSize = getImgSizesByQuality( const imgSize = getImgSizesByQuality(
image.width ?? 0, image.width ?? 0,
image.height ?? 0, image.height ?? 0,
quality ? quality : ImageQuality.Small quality
); );
return { return {
image: getAssetURL(image.url, quality), image: getAssetURL(image.url, quality),
width: imgSize.width, width: imgSize.width,
height: imgSize.height, height: imgSize.height,
alt: image.alternativeText || "", alt: image.alternativeText ?? "",
}; };
}; };

View File

@ -1,252 +1,5 @@
import { AppLayoutState } from "contexts/AppLayoutContext"; export const isUntangibleGroupItem = (metadata: any): boolean =>
import { GetLibraryItemsPreviewQuery } from "graphql/generated"; metadata &&
import { AppStaticProps } from "graphql/getAppStaticProps"; metadata.__typename === "ComponentMetadataGroup" &&
import { prettyinlineTitle, prettyDate } from "./formatters"; (metadata.subtype?.data?.attributes?.slug === "variant-set" ||
import { convertPrice } from "./numbers"; metadata.subtype?.data?.attributes?.slug === "relation-set");
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;
}
};

View File

@ -16,15 +16,11 @@ export const convertPrice = (
return 0; return 0;
}; };
export const convertMmToInch = (mm: number | null | undefined): string => { export const convertMmToInch = (mm: number | null | undefined): string =>
return mm ? (mm * 0.03937008).toPrecision(3) : ""; mm ? (mm * 0.03937008).toPrecision(3) : "";
};
export const randomInt = (min: number, max: number) => { export const randomInt = (min: number, max: number): number =>
return Math.floor(Math.random() * (max - min)) + min; Math.floor(Math.random() * (max - min)) + min;
};
export const isInteger = (value: string): boolean => { export const isInteger = (value: string): boolean =>
// eslint-disable-next-line require-unicode-regexp /^[+-]?[0-9]+$/u.test(value);
return /^[+-]?[0-9]+$/.test(value);
};

View File

@ -1,12 +1,12 @@
import { AppStaticProps } from "../graphql/getAppStaticProps";
import { SelectiveRequiredNonNullable } from "./types";
import { import {
Enum_Componentsetstextset_Status, Enum_Componentsetstextset_Status,
GetLibraryItemQuery, GetLibraryItemQuery,
GetLibraryItemScansQuery, GetLibraryItemScansQuery,
} from "graphql/generated"; } from "graphql/generated";
import { AppStaticProps } from "../graphql/getAppStaticProps";
import { SelectiveRequiredNonNullable } from "./types";
type SortContentProps = type SortRangedContentProps =
| NonNullable< | NonNullable<
NonNullable< NonNullable<
GetLibraryItemQuery["libraryItems"] GetLibraryItemQuery["libraryItems"]
@ -18,7 +18,7 @@ type SortContentProps =
>["data"][number]["attributes"] >["data"][number]["attributes"]
>["contents"]; >["contents"];
export const sortContent = (contents: SortContentProps) => { export const sortRangedContent = (contents: SortRangedContentProps): void => {
contents?.data.sort((a, b) => { contents?.data.sort((a, b) => {
if ( if (
a.attributes?.range[0]?.__typename === "ComponentRangePageRange" && a.attributes?.range[0]?.__typename === "ComponentRangePageRange" &&
@ -59,20 +59,20 @@ export const isDefined = <T>(t: T): t is NonNullable<T> =>
t !== null && t !== undefined; t !== null && t !== undefined;
export const isUndefined = <T>( export const isUndefined = <T>(
t: T | undefined | null t: T | null | undefined
): t is undefined | null => t === null || t === undefined; ): t is null | undefined => t === null || t === undefined;
export const isDefinedAndNotEmpty = ( export const isDefinedAndNotEmpty = (
string: string | undefined | null string: string | null | undefined
): string is string => isDefined(string) && string.length > 0; ): 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) isUndefined(t)
? [] ? []
: (t.filter((item) => isDefined(item)) as NonNullable<T>[]); : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
export const filterHasAttributes = <T, P extends keyof NonNullable<T>>( export const filterHasAttributes = <T, P extends keyof NonNullable<T>>(
t: T[] | undefined | null, t: T[] | null | undefined,
attributes?: P[] attributes?: P[]
): SelectiveRequiredNonNullable<NonNullable<T>, P>[] => ): SelectiveRequiredNonNullable<NonNullable<T>, P>[] =>
isUndefined(t) isUndefined(t)
@ -89,27 +89,24 @@ export const filterHasAttributes = <T, P extends keyof NonNullable<T>>(
export const iterateMap = <K, V, U>( export const iterateMap = <K, V, U>(
map: Map<K, V>, 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[] => { ): U[] => {
const result: U[] = []; const toList = [...map];
let index = 0; if (isDefined(sortingFunction)) {
for (const [key, value] of map.entries()) { toList.sort(sortingFunction);
result.push(callbackfn(key, value, index)); console.log(toList.sort(sortingFunction));
index += 1;
} }
return result; return toList.map(([key, value], index) => callbackfn(key, value, index));
}; };
export const mapMoveEntry = <K, V>( export const mapMoveEntry = <K, V>(
map: Map<K, V>, map: Map<K, V>,
sourceIndex: number, sourceIndex: number,
targetIndex: 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) => { const arrayMove = <T>(arr: T[], sourceIndex: number, targetIndex: number) => {
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]); arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
return arr; return arr;
}; };
export const mapRemoveEmptyValues = <K, V>(groups: Map<K, V[]>): Map<K, V[]> =>
new Map([...groups].filter(([_, items]) => items.length > 0));

View File

@ -1,5 +1,5 @@
import { LightBox } from "components/LightBox";
import { useState } from "react"; import { useState } from "react";
import { LightBox } from "components/LightBox";
export const useLightBox = (): [ export const useLightBox = (): [
(images: string[], index?: number) => void, (images: string[], index?: number) => void,

View File

@ -1,20 +1,20 @@
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { breaks } from "../../design.config"; import { breaks } from "../../design.config";
const useMediaQuery = (query: string): boolean => { const useMediaQuery = (query: string): boolean => {
const getMatches = (query: string): boolean => { const getMatches = useCallback((): boolean => {
// Prevents SSR issues // Prevents SSR issues
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
return window.matchMedia(query).matches; return window.matchMedia(query).matches;
} }
return false; return false;
}; }, [query]);
const [matches, setMatches] = useState<boolean>(getMatches(query)); const [matches, setMatches] = useState<boolean>(getMatches());
useEffect(() => { useEffect(() => {
const handleChange = () => { const handleChange = () => {
setMatches(getMatches(query)); setMatches(getMatches());
}; };
const matchMedia = window.matchMedia(query); const matchMedia = window.matchMedia(query);
@ -28,19 +28,19 @@ const useMediaQuery = (query: string): boolean => {
return () => { return () => {
matchMedia.removeEventListener("change", handleChange); matchMedia.removeEventListener("change", handleChange);
}; };
}, [query]); }, [getMatches, query]);
return matches; return matches;
}; };
// ts-unused-exports:disable-next-line // 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)"); useMediaQuery("(prefers-color-scheme: dark)");

View File

@ -5,8 +5,15 @@ export enum AnchorIds {
} }
// Scroll to top of element "id" when "deps" update. // 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(() => { useEffect(() => {
document.querySelector(`#${id}`)?.scrollTo({ top: 0, behavior: "smooth" }); if (enabled)
}, deps); document
.querySelector(`#${id}`)
?.scrollTo({ top: 0, behavior: "smooth" });
}, [id, deps, enabled]);
}; };

View File

@ -1,11 +1,10 @@
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher"; import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { filterDefined, isDefined } from "helpers/others"; import { filterDefined, isDefined } from "helpers/others";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
interface Props<T> { interface Props<T> {
items: T[]; items: T[];
languages: AppStaticProps["languages"]; languages: AppStaticProps["languages"];

View File

@ -1,5 +1,5 @@
import { isDefined } from "helpers/others";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { isDefined } from "helpers/others";
export const useStateWithLocalStorage = <T>( export const useStateWithLocalStorage = <T>(
key: string, key: string,

View File

@ -1,6 +1,8 @@
import { Dispatch, SetStateAction, useCallback } from "react"; import { Dispatch, SetStateAction, useCallback } from "react";
export const useToggle = (setState: Dispatch<SetStateAction<boolean>>) => export const useToggle = (
setState: Dispatch<SetStateAction<boolean>>
): (() => void) =>
useCallback(() => { useCallback(() => {
setState((current) => !current); setState((current) => !current);
}, []); }, [setState]);

View File

@ -1,3 +1,4 @@
import { GetStaticProps } from "next";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { import {
ReturnButton, ReturnButton,
@ -5,7 +6,6 @@ import {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import { ContentPanel } from "components/Panels/ContentPanel"; import { ContentPanel } from "components/Panels/ContentPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
/* /*
* *

View File

@ -1,3 +1,4 @@
import { GetStaticProps } from "next";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { import {
ReturnButton, ReturnButton,
@ -5,7 +6,6 @@ import {
} from "components/PanelComponents/ReturnButton"; } from "components/PanelComponents/ReturnButton";
import { ContentPanel } from "components/Panels/ContentPanel"; import { ContentPanel } from "components/Panels/ContentPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
/* /*
* *

View File

@ -5,8 +5,8 @@ import "@fontsource/opendyslexic/700.css";
import "@fontsource/vollkorn/700.css"; import "@fontsource/vollkorn/700.css";
import "@fontsource/zen-maru-gothic/500.css"; import "@fontsource/zen-maru-gothic/500.css";
import "@fontsource/zen-maru-gothic/900.css"; import "@fontsource/zen-maru-gothic/900.css";
import { AppContextProvider } from "contexts/AppLayoutContext";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import { AppContextProvider } from "contexts/AppLayoutContext";
import "tailwind.css"; import "tailwind.css";
const AccordsLibraryApp = (props: AppProps): JSX.Element => ( const AccordsLibraryApp = (props: AppProps): JSX.Element => (

View File

@ -1,3 +1,5 @@
import { useRouter } from "next/router";
import { useState } from "react";
import { InsetBox } from "components/InsetBox"; import { InsetBox } from "components/InsetBox";
import { PostPage } from "components/PostPage"; import { PostPage } from "components/PostPage";
import { import {
@ -6,10 +8,7 @@ import {
} from "graphql/getPostStaticProps"; } from "graphql/getPostStaticProps";
import { cIf, cJoin } from "helpers/className"; import { cIf, cJoin } from "helpers/className";
import { randomInt } from "helpers/numbers"; import { randomInt } from "helpers/numbers";
import { useRouter } from "next/router";
import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; import { RequestMailProps, ResponseMailProps } from "pages/api/mail";
import { useState } from "react";
/* /*
* *

View File

@ -1,10 +1,10 @@
import { GetStaticProps } from "next";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { NavOption } from "components/PanelComponents/NavOption"; import { NavOption } from "components/PanelComponents/NavOption";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
/* /*
* *

View File

@ -17,7 +17,7 @@ export interface RequestMailProps {
const Mail = async ( const Mail = async (
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<ResponseMailProps> res: NextApiResponse<ResponseMailProps>
) => { ): Promise<void> => {
if (req.method === "POST") { if (req.method === "POST") {
const body = req.body as RequestMailProps; const body = req.body as RequestMailProps;

View File

@ -2,17 +2,17 @@ import type { NextApiRequest, NextApiResponse } from "next";
import getConfig from "next/config"; import getConfig from "next/config";
type RequestProps = type RequestProps =
| HookRangedContent
| HookPostContent
| HookLibraryItem
| HookChronology | HookChronology
| HookContent | HookContent
| HookContentGroup | HookContentGroup
| HookCustom | HookCustom
| HookLibraryItem
| HookPostContent
| HookRangedContent
| HookWiki; | HookWiki;
type HookRangedContent = { type HookRangedContent = {
event: "entry.update" | "entry.delete" | "entry.create"; event: "entry.create" | "entry.delete" | "entry.update";
model: "ranged-content"; model: "ranged-content";
entry: { entry: {
library_item?: { library_item?: {
@ -33,16 +33,14 @@ type HookContent = {
model: "content"; model: "content";
entry: { entry: {
slug: string; slug: string;
ranged_contents: [ ranged_contents: {
{ slug: string;
slug: string; }[];
}
];
}; };
}; };
type HookPostContent = { type HookPostContent = {
event: "entry.update" | "entry.delete" | "entry.create"; event: "entry.create" | "entry.delete" | "entry.update";
model: "post"; model: "post";
entry: { entry: {
slug: string; slug: string;
@ -50,7 +48,7 @@ type HookPostContent = {
}; };
type HookLibraryItem = { type HookLibraryItem = {
event: "entry.update" | "entry.delete" | "entry.create"; event: "entry.create" | "entry.delete" | "entry.update";
model: "library-item"; model: "library-item";
entry: { entry: {
slug: string; slug: string;
@ -63,7 +61,7 @@ type HookLibraryItem = {
}; };
type HookContentGroup = { type HookContentGroup = {
event: "entry.update" | "entry.delete" | "entry.create"; event: "entry.create" | "entry.delete" | "entry.update";
model: "contents-group"; model: "contents-group";
entry: { entry: {
contents: { contents: {
@ -73,12 +71,12 @@ type HookContentGroup = {
}; };
type HookChronology = { type HookChronology = {
event: "entry.update" | "entry.delete" | "entry.create"; event: "entry.create" | "entry.delete" | "entry.update";
model: "chronology-era" | "chronology-item"; model: "chronology-era" | "chronology-item";
}; };
type HookWiki = { type HookWiki = {
event: "entry.update" | "entry.delete" | "entry.create"; event: "entry.create" | "entry.delete" | "entry.update";
model: "wiki-page"; model: "wiki-page";
entry: { entry: {
slug: string; slug: string;
@ -90,10 +88,10 @@ type ResponseMailProps = {
revalidated: boolean; revalidated: boolean;
}; };
const Revalidate = async ( const Revalidate = (
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<ResponseMailProps> res: NextApiResponse<ResponseMailProps>
) => { ): void => {
const body = req.body as RequestProps; const body = req.body as RequestProps;
const { serverRuntimeConfig } = getConfig(); const { serverRuntimeConfig } = getConfig();
@ -101,9 +99,8 @@ const Revalidate = async (
if ( if (
req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}` req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`
) { ) {
return res res.status(401).json({ message: "Invalid token", revalidated: false });
.status(401) return;
.json({ message: "Invalid token", revalidated: false });
} }
const paths: string[] = []; const paths: string[] = [];
@ -229,13 +226,12 @@ const Revalidate = async (
await res.revalidate(path); await res.revalidate(path);
}) })
); );
return res.json({ message: "Success!", revalidated: true }); res.json({ message: "Success!", revalidated: true });
} catch (err) { return;
// If there was an error, Next.js will continue } catch (error) {
// to show the last successfully generated page res
return res
.status(500) .status(500)
.send({ message: "Error revalidating", revalidated: false }); .send({ message: `Error revalidating: ${error}`, revalidated: false });
} }
}; };
export default Revalidate; export default Revalidate;

View File

@ -1,12 +1,11 @@
import { GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { NavOption } from "components/PanelComponents/NavOption"; import { NavOption } from "components/PanelComponents/NavOption";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react";
/* /*
* *

View File

@ -1,3 +1,5 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useState, useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Switch } from "components/Inputs/Switch"; import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
@ -15,8 +17,6 @@ import { GetVideoChannelQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { getVideoThumbnailURL } from "helpers/videos"; import { getVideoThumbnailURL } from "helpers/videos";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useState, useMemo } from "react";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";

View File

@ -1,8 +1,12 @@
import { GetStaticProps } from "next";
import { useMemo, useState } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { SmartList } from "components/SmartList";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { PageSelector } from "components/Inputs/PageSelector";
import { Switch } from "components/Inputs/Switch"; import { Switch } from "components/Inputs/Switch";
import { TextInput } from "components/Inputs/TextInput";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { import {
ReturnButton, ReturnButton,
@ -21,15 +25,15 @@ import { prettyDate } from "helpers/formatters";
import { filterHasAttributes } from "helpers/others"; import { filterHasAttributes } from "helpers/others";
import { getVideoThumbnailURL } from "helpers/videos"; import { getVideoThumbnailURL } from "helpers/videos";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { GetStaticProps } from "next";
import { Fragment, useMemo, useState } from "react";
/* /*
* *
* CONSTANTS * 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 Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const [page, setPage] = useState(0);
const [keepInfoVisible, setKeepInfoVisible] = useState(true); const [keepInfoVisible, setKeepInfoVisible] = useState(true);
const [searchName, setSearchName] = useState(
const paginatedVideos = useMemo(() => { DEFAULT_FILTERS_STATE.searchName
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 subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
@ -72,6 +68,13 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
description={langui.archives_description} description={langui.archives_description}
/> />
<TextInput
className="mb-6 w-full"
placeholder={langui.search_title ?? undefined}
state={searchName}
setState={setSearchName}
/>
{hoverable && ( {hoverable && (
<WithLabel <WithLabel
label={langui.always_show_info} label={langui.always_show_info}
@ -82,56 +85,53 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
)} )}
</SubPanel> </SubPanel>
), ),
[hoverable, keepInfoVisible, langui] [hoverable, keepInfoVisible, langui, searchName]
); );
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<PageSelector <SmartList
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)} items={filterHasAttributes(videos)}
page={page} getItemId={(item) => item.id}
setPage={setPage} renderItem={({ item }) => (
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}>
<PreviewCard <PreviewCard
href={`/archives/videos/v/${video.attributes.uid}`} href={`/archives/videos/v/${item.attributes.uid}`}
title={video.attributes.title} title={item.attributes.title}
thumbnail={getVideoThumbnailURL(video.attributes.uid)} thumbnail={getVideoThumbnailURL(item.attributes.uid)}
thumbnailAspectRatio="16/9" thumbnailAspectRatio="16/9"
thumbnailForceAspectRatio
keepInfoVisible={keepInfoVisible} keepInfoVisible={keepInfoVisible}
metadata={{ metadata={{
release_date: video.attributes.published_date, release_date: item.attributes.published_date,
views: video.attributes.views, views: item.attributes.views,
author: video.attributes.channel?.data?.attributes?.title, author: item.attributes.channel?.data?.attributes?.title,
position: "Top", position: "Top",
}} }}
hoverlay={{ hoverlay={{
__typename: "Video", __typename: "Video",
duration: video.attributes.duration, duration: item.attributes.duration,
}} }}
/> />
</Fragment> </>
))} )}
</div> renderWhenEmpty={() => (
<ContentPlaceholder
<PageSelector message={langui.no_results_message ?? "No results"}
maxPage={Math.floor(videos.length / ITEM_PER_PAGE)} icon={Icon.ChevronLeft}
page={page} />
setPage={setPage} )}
className="mt-12" 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> </ContentPanel>
), ),
[keepInfoVisible, page, paginatedVideos, videos.length] [keepInfoVisible, langui, searchName, videos]
); );
return ( return (
<AppLayout <AppLayout

View File

@ -1,3 +1,5 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
import { Ico, Icon } from "components/Ico"; import { Ico, Icon } from "components/Ico";
@ -21,8 +23,6 @@ import { prettyDate, prettyShortenNumber } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/others";
import { getVideoFile } from "helpers/videos"; import { getVideoFile } from "helpers/videos";
import { useMediaMobile } from "hooks/useMediaQuery"; import { useMediaMobile } from "hooks/useMediaQuery";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useMemo } from "react";
/* /*
* *

View File

@ -1,11 +1,10 @@
import { GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { useMemo } from "react";
/* /*
* *

View File

@ -1,3 +1,5 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useCallback, useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
@ -34,8 +36,6 @@ import { ContentWithTranslations } from "helpers/types";
import { useMediaMobile } from "hooks/useMediaQuery"; import { useMediaMobile } from "hooks/useMediaQuery";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useCallback, useMemo } from "react";
/* /*
* *
@ -111,7 +111,9 @@ const Content = ({
.code !== .code !==
selectedTranslation.language?.data?.attributes?.code && ( selectedTranslation.language?.data?.attributes?.code && (
<div className="grid place-items-center gap-2"> <div className="grid place-items-center gap-2">
<p className="font-headers">{langui.source_language}:</p> <p className="font-headers font-bold">
{langui.source_language}:
</p>
<Chip> <Chip>
{prettyLanguage( {prettyLanguage(
selectedTranslation.text_set.source_language.data.attributes 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"> <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 <ToolTip
content={getStatusDescription( content={getStatusDescription(
@ -139,7 +141,9 @@ const Content = ({
{selectedTranslation.text_set.transcribers && {selectedTranslation.text_set.transcribers &&
selectedTranslation.text_set.transcribers.data.length > 0 && ( selectedTranslation.text_set.transcribers.data.length > 0 && (
<div> <div>
<p className="font-headers">{langui.transcribers}:</p> <p className="font-headers font-bold">
{langui.transcribers}:
</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes( {filterHasAttributes(
selectedTranslation.text_set.transcribers.data selectedTranslation.text_set.transcribers.data
@ -158,7 +162,9 @@ const Content = ({
{selectedTranslation.text_set.translators && {selectedTranslation.text_set.translators &&
selectedTranslation.text_set.translators.data.length > 0 && ( selectedTranslation.text_set.translators.data.length > 0 && (
<div> <div>
<p className="font-headers">{langui.translators}:</p> <p className="font-headers font-bold">
{langui.translators}:
</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes( {filterHasAttributes(
selectedTranslation.text_set.translators.data selectedTranslation.text_set.translators.data
@ -177,7 +183,9 @@ const Content = ({
{selectedTranslation.text_set.proofreaders && {selectedTranslation.text_set.proofreaders &&
selectedTranslation.text_set.proofreaders.data.length > 0 && ( selectedTranslation.text_set.proofreaders.data.length > 0 && (
<div> <div>
<p className="font-headers">{langui.proofreaders}:</p> <p className="font-headers font-bold">
{langui.proofreaders}:
</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes( {filterHasAttributes(
selectedTranslation.text_set.proofreaders.data selectedTranslation.text_set.proofreaders.data
@ -195,7 +203,7 @@ const Content = ({
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && ( {isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
<div> <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"> <div className="grid place-content-center place-items-center gap-2">
<Markdawn text={selectedTranslation.text_set.notes} /> <Markdawn text={selectedTranslation.text_set.notes} />
</div> </div>
@ -209,7 +217,9 @@ const Content = ({
<> <>
<HorizontalLine /> <HorizontalLine />
<div> <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"> <div className="mt-6 grid place-items-center gap-6 text-left">
{content.ranged_contents.data.map((rangedContent) => { {content.ranged_contents.data.map((rangedContent) => {
const libraryItem = const libraryItem =
@ -525,7 +535,7 @@ type Group = NonNullable<
>["data"]; >["data"];
const getPreviousContent = (group: Group, currentSlug: string) => { 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]; const content = group[index];
if (content.attributes?.slug === currentSlug && index > 0) { if (content.attributes?.slug === currentSlug && index > 0) {
return group[index - 1]; return group[index - 1];
@ -537,7 +547,7 @@ const getPreviousContent = (group: Group, currentSlug: string) => {
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
const getNextContent = (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]; const content = group[index];
if (content.attributes?.slug === currentSlug && index < group.length - 1) { if (content.attributes?.slug === currentSlug && index < group.length - 1) {
return group[index + 1]; return group[index + 1];

View File

@ -1,5 +1,6 @@
import { GetStaticProps } from "next";
import { useState, useMemo, useCallback } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip";
import { Select } from "components/Inputs/Select"; import { Select } from "components/Inputs/Select";
import { Switch } from "components/Inputs/Switch"; import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
@ -12,20 +13,16 @@ import { TranslatedPreviewCard } from "components/PreviewCard";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyinlineTitle, prettySlug } from "helpers/formatters"; import { prettyinlineTitle, prettySlug } from "helpers/formatters";
import { GetStaticProps } from "next"; import { TextInput } from "components/Inputs/TextInput";
import { Fragment, useState, useMemo } from "react";
import { Icon } from "components/Ico";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { TextInput } from "components/Inputs/TextInput";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { import { Icon } from "components/Ico";
filterHasAttributes, import { filterDefined, filterHasAttributes } from "helpers/others";
iterateMap,
mapRemoveEmptyValues,
} from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
import { GetContentsQuery } from "graphql/generated"; 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] [combineRelatedContent, searchName.length]
); );
const filteredItems = useMemo( const groupingFunction = useCallback(
() => filterContents(contents, effectiveCombineRelatedContent, searchName), (
[effectiveCombineRelatedContent, contents, searchName] 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( const filteringFunction = useCallback(
() => getGroups(langui, groupingMethod, filteredItems), (
[langui, groupingMethod, filteredItems] 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( const subPanel = useMemo(
@ -161,107 +216,91 @@ const Contents = ({
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{groups.size === 0 && ( <SmartList
<ContentPlaceholder items={filterHasAttributes(contents)}
message={langui.no_results_message ?? "No results"} getItemId={(item) => item.id}
icon={Icon.ChevronLeft} renderItem={({ item }) => (
/> <>
)} {item.attributes.translations && (
{iterateMap( <TranslatedPreviewCard
groups, href={`/contents/${item.attributes.slug}`}
(name, items, index) => translations={item.attributes.translations.map(
items.length > 0 && ( (translation) => ({
<Fragment key={index}> pre_title: translation?.pre_title,
{name && ( title: translation?.title,
<h2 subtitle: translation?.subtitle,
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl language: translation?.language?.data?.attributes?.code,
first-of-type:pt-0" })
> )}
{name} slug={item.attributes.slug}
<Chip>{`${items.reduce((currentSum, item) => { languages={languages}
if (effectiveCombineRelatedContent) { thumbnail={item.attributes.thumbnail?.data?.attributes}
if ( thumbnailAspectRatio="3/2"
item.attributes?.group?.data?.attributes?.combine === thumbnailForceAspectRatio
true stackNumber={
) { effectiveCombineRelatedContent &&
return ( item.attributes.group?.data?.attributes?.combine === true
currentSum + ? item.attributes.group.data.attributes.contents?.data
(item.attributes.group.data.attributes.contents .length
?.data.length ?? 1) : 0
); }
} topChips={
} item.attributes.type?.data?.attributes
return currentSum + 1; ? [
}, 0)} ${ item.attributes.type.data.attributes.titles?.[0]
items.length <= 1 ? item.attributes.type.data.attributes.titles[0]
? langui.result?.toLowerCase() ?? "" ?.title
: langui.results?.toLowerCase() ?? "" : prettySlug(
}`}</Chip> item.attributes.type.data.attributes.slug
</h2> ),
)} ]
: undefined
<div }
className="grid grid-cols-2 items-end gap-8 bottomChips={item.attributes.categories?.data.map(
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4" (category) => category.attributes?.short ?? ""
> )}
{filterHasAttributes(items).map((item) => ( keepInfoVisible={keepInfoVisible}
<Fragment key={item.id}> />
{item.attributes.translations && ( )}
<TranslatedPreviewCard </>
href={`/contents/${item.attributes.slug}`} )}
translations={item.attributes.translations.map( renderWhenEmpty={() => (
(translation) => ({ <ContentPlaceholder
pre_title: translation?.pre_title, message={langui.no_results_message ?? "No results"}
title: translation?.title, icon={Icon.ChevronLeft}
subtitle: translation?.subtitle, />
language: )}
translation?.language?.data?.attributes?.code, className="grid-cols-2 items-end desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))]"
}) groupingFunction={groupingFunction}
)} filteringFunction={filteringFunction}
slug={item.attributes.slug} searchingTerm={searchName}
languages={languages} searchingBy={(item) =>
thumbnail={ `
item.attributes.thumbnail?.data?.attributes ${item.attributes.slug}
} ${filterDefined(item.attributes.translations)
thumbnailAspectRatio="3/2" .map((translation) =>
thumbnailForceAspectRatio prettyinlineTitle(
stackNumber={ translation.pre_title,
effectiveCombineRelatedContent && translation.title,
item.attributes.group?.data?.attributes?.combine === translation.subtitle
true )
? item.attributes.group.data.attributes.contents )
?.data.length .join(" ")}`
: 0 }
} langui={langui}
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>
)
)}
</ContentPanel> </ContentPanel>
), ),
[effectiveCombineRelatedContent, groups, keepInfoVisible, languages, langui] [
contents,
effectiveCombineRelatedContent,
filteringFunction,
groupingFunction,
keepInfoVisible,
languages,
langui,
searchName,
]
); );
return ( return (
@ -303,107 +342,3 @@ export const getStaticProps: GetStaticProps = async (context) => {
props: props, 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;
});

View File

@ -1,3 +1,5 @@
import { GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
@ -10,8 +12,6 @@ import { DevGetContentsQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterDefined, filterHasAttributes } from "helpers/others"; import { filterDefined, filterHasAttributes } from "helpers/others";
import { GetStaticProps } from "next";
import { useMemo } from "react";
/* /*
* *

View File

@ -1,3 +1,5 @@
import { GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
@ -13,9 +15,6 @@ import {
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetStaticProps } from "next";
import { useMemo } from "react";
/* /*
* *
* PAGE * PAGE

View File

@ -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 { AppLayout } from "components/AppLayout";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { Markdawn, TableOfContents } from "components/Markdown/Markdawn"; import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
@ -8,9 +11,6 @@ import {
import { Popup } from "components/Popup"; import { Popup } from "components/Popup";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; 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"; import { Icon } from "components/Ico";
/* /*

View File

@ -1,3 +1,5 @@
import { GetStaticProps } from "next";
import { useCallback, useMemo, useRef, useState } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { ButtonGroup } from "components/Inputs/ButtonGroup";
@ -7,8 +9,6 @@ import {
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import { ToolTip } from "components/ToolTip"; import { ToolTip } from "components/ToolTip";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { useCallback, useMemo, useRef, useState } from "react";
/* /*
* *

View File

@ -1,3 +1,5 @@
import { Fragment, useCallback, useMemo, useState } from "react";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { Img } from "components/Img"; import { Img } from "components/Img";
@ -40,12 +42,10 @@ import {
filterHasAttributes, filterHasAttributes,
isDefined, isDefined,
isDefinedAndNotEmpty, isDefinedAndNotEmpty,
sortContent, sortRangedContent,
} from "helpers/others"; } from "helpers/others";
import { useLightBox } from "hooks/useLightBox"; import { useLightBox } from "hooks/useLightBox";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; 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 { isUntangibleGroupItem } from "helpers/libraryItem";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
@ -626,7 +626,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
language_code: context.locale ?? "en", language_code: context.locale ?? "en",
}); });
if (!item.libraryItems?.data[0]?.attributes) return { notFound: true }; if (!item.libraryItems?.data[0]?.attributes) return { notFound: true };
sortContent(item.libraryItems.data[0].attributes.contents); sortRangedContent(item.libraryItems.data[0].attributes.contents);
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
item: item.libraryItems.data[0].attributes, item: item.libraryItems.data[0].attributes,
@ -702,6 +702,8 @@ const ContentLine = ({
), ),
}); });
console.log(prettySlug(slug, parentSlug));
return ( return (
<div <div
className={cJoin( className={cJoin(
@ -722,7 +724,7 @@ const ContentLine = ({
selectedTranslation.subtitle selectedTranslation.subtitle
) )
: content : content
? prettySlug(content.slug) ? prettySlug(content.slug, parentSlug)
: prettySlug(slug, parentSlug)} : prettySlug(slug, parentSlug)}
</h3> </h3>
</a> </a>

View File

@ -1,3 +1,5 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { ScanSet } from "components/Library/ScanSet"; import { ScanSet } from "components/Library/ScanSet";
import { ScanSetCover } from "components/Library/ScanSetCover"; import { ScanSetCover } from "components/Library/ScanSetCover";
@ -15,11 +17,12 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyinlineTitle, prettySlug } from "helpers/formatters"; 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 { useLightBox } from "hooks/useLightBox";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { Fragment, useMemo } from "react";
/* /*
* *
@ -44,7 +47,6 @@ const LibrarySlug = ({
...otherProps ...otherProps
}: Props): JSX.Element => { }: Props): JSX.Element => {
const [openLightBox, LightBox] = useLightBox(); const [openLightBox, LightBox] = useLightBox();
sortContent(item.contents);
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
@ -158,6 +160,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
}); });
if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id) if (!item.libraryItems?.data[0]?.attributes || !item.libraryItems.data[0]?.id)
return { notFound: true }; return { notFound: true };
sortRangedContent(item.libraryItems.data[0].attributes.contents);
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
item: item.libraryItems.data[0].attributes, item: item.libraryItems.data[0].attributes,

View File

@ -1,5 +1,6 @@
import { GetStaticProps } from "next";
import { useState, useMemo, useCallback } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip";
import { Select } from "components/Inputs/Select"; import { Select } from "components/Inputs/Select";
import { Switch } from "components/Inputs/Switch"; import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
@ -11,32 +12,29 @@ import { SubPanel } from "components/Panels/SubPanel";
import { GetLibraryItemsPreviewQuery } from "graphql/generated"; import { GetLibraryItemsPreviewQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyItemSubType } from "helpers/formatters"; import {
import { LibraryItemUserStatus } from "helpers/types"; prettyDate,
import { GetStaticProps } from "next"; prettyinlineTitle,
import { Fragment, useState, useMemo } from "react"; prettyItemSubType,
} from "helpers/formatters";
import {
LibraryItemUserStatus,
SelectiveRequiredNonNullable,
} from "helpers/types";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { TextInput } from "components/Inputs/TextInput"; import { TextInput } from "components/Inputs/TextInput";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs"; import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
import { useAppLayout } from "contexts/AppLayoutContext"; import { isUntangibleGroupItem } from "helpers/libraryItem";
import {
filterItems,
getGroups,
sortBy,
isUntangibleGroupItem,
} from "helpers/libraryItem";
import { PreviewCard } from "components/PreviewCard"; import { PreviewCard } from "components/PreviewCard";
import { useMediaHoverable } from "hooks/useMediaQuery"; import { useMediaHoverable } from "hooks/useMediaQuery";
import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { ButtonGroup } from "components/Inputs/ButtonGroup";
import { import { filterHasAttributes, isDefined, isUndefined } from "helpers/others";
filterHasAttributes,
isDefinedAndNotEmpty,
isUndefined,
iterateMap,
} from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; 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 = ({ const Library = ({
langui, langui,
items: libraryItems, items,
currencies, currencies,
...otherProps ...otherProps
}: Props): JSX.Element => { }: Props): JSX.Element => {
const appLayout = useAppLayout();
const hoverable = useMediaHoverable(); const hoverable = useMediaHoverable();
const appLayout = useAppLayout();
const [searchName, setSearchName] = useState( const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName DEFAULT_FILTERS_STATE.searchName
@ -97,36 +95,170 @@ const Library = ({
LibraryItemUserStatus | undefined LibraryItemUserStatus | undefined
>(DEFAULT_FILTERS_STATE.filterUserStatus); >(DEFAULT_FILTERS_STATE.filterUserStatus);
const filteredItems = useMemo( const filteringFunction = useCallback(
() => (
filterItems( item: SelectiveRequiredNonNullable<
appLayout, Props["items"][number],
libraryItems, "attributes" | "id"
searchName, >
showSubitems, ) => {
showPrimaryItems, if (!showSubitems && !item.attributes.root_item) return false;
showSecondaryItems, if (
filterUserStatus 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, filterUserStatus,
libraryItems,
searchName,
showPrimaryItems, showPrimaryItems,
showSecondaryItems, showSecondaryItems,
showSubitems, showSubitems,
] ]
); );
const sortedItems = useMemo( const sortingFunction = useCallback(
() => sortBy(sortingMethod, filteredItems, currencies), (
[currencies, filteredItems, sortingMethod] 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( const groupingFunction = useCallback(
() => getGroups(langui, groupingMethod, sortedItems), (
[langui, groupingMethod, sortedItems] 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( const subPanel = useMemo(
@ -273,72 +405,73 @@ const Library = ({
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
{groups.size === 0 && ( <SmartList
<ContentPlaceholder items={filterHasAttributes(items)}
message={langui.no_results_message ?? "No results"} getItemId={(item) => item.id}
icon={Icon.ChevronLeft} renderItem={({ item }) => (
/> <PreviewCard
)} href={`/library/${item.attributes.slug}`}
{iterateMap(groups, (name, items) => ( title={item.attributes.title}
<Fragment key={name}> subtitle={item.attributes.subtitle}
{isDefinedAndNotEmpty(name) && ( thumbnail={item.attributes.thumbnail?.data?.attributes}
<h2 thumbnailAspectRatio="21/29.7"
className="flex flex-row place-items-center gap-2 thumbnailRounded={false}
pb-2 pt-10 text-2xl first-of-type:pt-0" keepInfoVisible={keepInfoVisible}
> topChips={
{name} item.attributes.metadata &&
<Chip>{`${items.length} ${ item.attributes.metadata.length > 0 &&
items.length <= 1 item.attributes.metadata[0]
? langui.result?.toLowerCase() ?? "result" ? [prettyItemSubType(item.attributes.metadata[0])]
: langui.results?.toLowerCase() ?? "results" : []
}`}</Chip> }
</h2> bottomChips={item.attributes.categories?.data.map(
)} (category) => category.attributes?.short ?? ""
<div )}
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 metadata={{
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))] currencies: currencies,
mobile:grid-cols-2 mobile:gap-4" release_date: item.attributes.release_date,
> price: item.attributes.price,
{filterHasAttributes(items).map((item) => ( position: "Bottom",
<Fragment key={item.id}> }}
<PreviewCard infoAppend={
href={`/library/${item.attributes.slug}`} !isUntangibleGroupItem(item.attributes.metadata?.[0]) && (
title={item.attributes.title} <PreviewCardCTAs id={item.id} langui={langui} />
subtitle={item.attributes.subtitle} )
thumbnail={item.attributes.thumbnail?.data?.attributes} }
thumbnailAspectRatio="21/29.7" />
thumbnailRounded={false} )}
keepInfoVisible={keepInfoVisible} renderWhenEmpty={() => (
topChips={ <ContentPlaceholder
item.attributes.metadata && message={langui.no_results_message ?? "No results"}
item.attributes.metadata.length > 0 && icon={Icon.ChevronLeft}
item.attributes.metadata[0] />
? [prettyItemSubType(item.attributes.metadata[0])] )}
: [] className="grid-cols-2 items-end desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]"
} searchingTerm={searchName}
bottomChips={item.attributes.categories?.data.map( sortingFunction={sortingFunction}
(category) => category.attributes?.short ?? "" groupingFunction={groupingFunction}
)} searchingBy={(item) =>
metadata={{ prettyinlineTitle(
currencies: currencies, "",
release_date: item.attributes.release_date, item.attributes.title,
price: item.attributes.price, item.attributes.subtitle
position: "Bottom", )
}} }
infoAppend={ filteringFunction={filteringFunction}
!isUntangibleGroupItem(item.attributes.metadata?.[0]) && ( langui={langui}
<PreviewCardCTAs id={item.id} langui={langui} /> />
)
}
/>
</Fragment>
))}
</div>
</Fragment>
))}
</ContentPanel> </ContentPanel>
), ),
[currencies, groups, keepInfoVisible, langui] [
currencies,
filteringFunction,
groupingFunction,
items,
keepInfoVisible,
langui,
searchName,
sortingFunction,
]
); );
return ( return (

View File

@ -1,9 +1,8 @@
import { GetStaticProps } from "next";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
/* /*

View File

@ -1,3 +1,4 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { PostPage } from "components/PostPage"; import { PostPage } from "components/PostPage";
import { AppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps } from "graphql/getAppStaticProps";
import { import {
@ -6,7 +7,6 @@ import {
} from "graphql/getPostStaticProps"; } from "graphql/getPostStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { filterHasAttributes, isDefined } from "helpers/others"; import { filterHasAttributes, isDefined } from "helpers/others";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
/* /*
* *

View File

@ -1,3 +1,5 @@
import { GetStaticProps } from "next";
import { useMemo, useState } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Switch } from "components/Inputs/Switch"; import { Switch } from "components/Inputs/Switch";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
@ -6,20 +8,18 @@ import {
ContentPanelWidthSizes, ContentPanelWidthSizes,
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { PreviewCard } from "components/PreviewCard"; import { TranslatedPreviewCard } from "components/PreviewCard";
import { GetPostsPreviewQuery } from "graphql/generated"; import { GetPostsPreviewQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettyDate, prettySlug } from "helpers/formatters"; import { prettyDate, prettySlug } from "helpers/formatters";
import { GetStaticProps } from "next";
import { Fragment, useMemo, useState } from "react";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { TextInput } from "components/Inputs/TextInput"; import { TextInput } from "components/Inputs/TextInput";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
import { useMediaHoverable } from "hooks/useMediaQuery"; 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"]; 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 hoverable = useMediaHoverable();
const [searchName, setSearchName] = useState( const [searchName, setSearchName] = useState(
DEFAULT_FILTERS_STATE.searchName DEFAULT_FILTERS_STATE.searchName
@ -49,11 +54,6 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
DEFAULT_FILTERS_STATE.keepInfoVisible DEFAULT_FILTERS_STATE.keepInfoVisible
); );
const filteredItems = useMemo(
() => filterItems(posts, searchName),
[posts, searchName]
);
const subPanel = useMemo( const subPanel = useMemo(
() => ( () => (
<SubPanel> <SubPanel>
@ -96,37 +96,46 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
const contentPanel = useMemo( const contentPanel = useMemo(
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<div <SmartList
className="grid grid-cols-1 items-end gap-8 items={filterHasAttributes(posts)}
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]" getItemId={(post) => post.id}
> langui={langui}
{filterHasAttributes(filteredItems).map((post) => ( renderItem={({ item: post }) => (
<Fragment key={post.id}> <TranslatedPreviewCard
<PreviewCard href={`/news/${post.attributes.slug}`}
href={`/news/${post.attributes.slug}`} translations={filterDefined(post.attributes.translations).map(
title={ (translation) => ({
post.attributes.translations?.[0]?.title ?? language: translation.language?.data?.attributes?.code,
prettySlug(post.attributes.slug) title: translation.title,
} description: translation.excerpt,
description={post.attributes.translations?.[0]?.excerpt} })
thumbnail={post.attributes.thumbnail?.data?.attributes} )}
thumbnailAspectRatio="3/2" languages={languages}
thumbnailForceAspectRatio slug={post.attributes.slug}
bottomChips={post.attributes.categories?.data.map( thumbnail={post.attributes.thumbnail?.data?.attributes}
(category) => category.attributes?.short ?? "" thumbnailAspectRatio="3/2"
)} thumbnailForceAspectRatio
keepInfoVisible={keepInfoVisible} bottomChips={post.attributes.categories?.data.map(
metadata={{ (category) => category.attributes?.short ?? ""
release_date: post.attributes.date, )}
position: "Top", keepInfoVisible={keepInfoVisible}
}} metadata={{
/> release_date: post.attributes.date,
</Fragment> position: "Top",
))} }}
</div> />
)}
className="grid-cols-1 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
searchingTerm={searchName}
searchingBy={(post) =>
`${prettySlug(post.attributes.slug)} ${post.attributes.translations
?.map((translation) => translation?.title)
.join(" ")}`
}
/>
</ContentPanel> </ContentPanel>
), ),
[filteredItems, keepInfoVisible] [keepInfoVisible, languages, langui, posts, searchName]
); );
return ( return (
@ -136,6 +145,7 @@ const News = ({ langui, posts, ...otherProps }: Props): JSX.Element => {
contentPanel={contentPanel} contentPanel={contentPanel}
subPanelIcon={Icon.Search} subPanelIcon={Icon.Search}
langui={langui} langui={langui}
languages={languages}
{...otherProps} {...otherProps}
/> />
); );
@ -149,9 +159,7 @@ export default News;
export const getStaticProps: GetStaticProps = async (context) => { export const getStaticProps: GetStaticProps = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const posts = await sdk.getPostsPreview({ const posts = await sdk.getPostsPreview();
language_code: context.locale ?? "en",
});
if (!posts.posts) return { notFound: true }; if (!posts.posts) return { notFound: true };
const props: Props = { const props: Props = {
...(await getAppStaticProps(context)), ...(await getAppStaticProps(context)),
@ -175,20 +183,3 @@ const sortPosts = (posts: Props["posts"]): Props["posts"] =>
return dateA.localeCompare(dateB); return dateA.localeCompare(dateB);
}) })
.reverse(); .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;
});

View File

@ -1,3 +1,5 @@
import { useCallback, useMemo } from "react";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { Chip } from "components/Chip"; import { Chip } from "components/Chip";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
@ -21,8 +23,6 @@ import {
} from "helpers/others"; } from "helpers/others";
import { WikiPageWithTranslations } from "helpers/types"; import { WikiPageWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage"; import { useSmartLanguage } from "hooks/useSmartLanguage";
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useCallback, useMemo } from "react";
interface Props extends AppStaticProps { interface Props extends AppStaticProps {
page: WikiPageWithTranslations; page: WikiPageWithTranslations;
@ -71,8 +71,16 @@ const WikiPage = ({
className="mb-10" 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> <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} /> <LanguageSwitcher {...languageSwitcherProps} />
</div> </div>
@ -88,7 +96,9 @@ const WikiPage = ({
<Img image={page.thumbnail.data.attributes} /> <Img image={page.thumbnail.data.attributes} />
)} )}
<div className="my-4 grid gap-4 p-4"> <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"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{page.categories?.data.map((category) => ( {page.categories?.data.map((category) => (
<Chip key={category.id}>{category.attributes?.name}</Chip> <Chip key={category.id}>{category.attributes?.name}</Chip>
@ -96,29 +106,38 @@ const WikiPage = ({
</div> </div>
</div> </div>
</div> </div>
{isDefinedAndNotEmpty(selectedTranslation.summary) && ( {isDefinedAndNotEmpty(selectedTranslation.summary) && (
<div className="mb-6"> <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> <p>{selectedTranslation.summary}</p>
</div> </div>
)} )}
{filterHasAttributes(page.definitions, ["translations"]).map( {filterHasAttributes(page.definitions, ["translations"]).map(
(definition, index) => ( (definition, index) => (
<DefinitionCard <>
key={index} <DefinitionCard
source={definition.source?.data?.attributes?.name} key={index}
translations={filterHasAttributes( source={definition.source?.data?.attributes?.name}
definition.translations translations={filterHasAttributes(
).map((translation) => ({ definition.translations
language: translation.language.data?.attributes?.code, ).map((translation) => ({
definition: translation.definition, language: translation.language.data?.attributes?.code,
status: translation.status, definition: translation.definition,
}))} status: translation.status,
index={index + 1} }))}
languages={languages} index={index + 1}
langui={langui} languages={languages}
/> langui={langui}
categories={filterHasAttributes(
definition.categories?.data
).map((category) => category.attributes.short)}
/>
<br />
</>
) )
)} )}
</div> </div>

View File

@ -1,3 +1,5 @@
import { GetStaticProps } from "next";
import { Fragment, useMemo } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { InsetBox } from "components/InsetBox"; import { InsetBox } from "components/InsetBox";
import { NavOption } from "components/PanelComponents/NavOption"; import { NavOption } from "components/PanelComponents/NavOption";
@ -13,8 +15,6 @@ import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { prettySlug } from "helpers/formatters"; import { prettySlug } from "helpers/formatters";
import { filterHasAttributes, isDefined } from "helpers/others"; 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 ?? (chronologyEras[currentChronologyEraIndex].attributes?.ending_year ??
999999) 999999)
) { ) {
currentChronologyEraIndex += 1; currentChronologyEraIndex++;
} }
if ( if (
Object.prototype.hasOwnProperty.call( Object.hasOwn(memo[currentChronologyEraIndex], item.attributes.year)
memo[currentChronologyEraIndex],
item.attributes.year
)
) { ) {
memo[currentChronologyEraIndex][item.attributes.year].push(item); memo[currentChronologyEraIndex][item.attributes.year].push(item);
} else { } else {

View File

@ -1,10 +1,10 @@
import { GetStaticProps } from "next";
import { Fragment, useMemo, useState } from "react";
import { AppLayout } from "components/AppLayout"; import { AppLayout } from "components/AppLayout";
import { NavOption } from "components/PanelComponents/NavOption"; import { NavOption } from "components/PanelComponents/NavOption";
import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Panels/SubPanel"; import { SubPanel } from "components/Panels/SubPanel";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { GetStaticProps } from "next";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { getReadySdk } from "graphql/sdk"; import { getReadySdk } from "graphql/sdk";
import { GetWikiPagesPreviewsQuery } from "graphql/generated"; import { GetWikiPagesPreviewsQuery } from "graphql/generated";
@ -12,7 +12,6 @@ import {
ContentPanel, ContentPanel,
ContentPanelWidthSizes, ContentPanelWidthSizes,
} from "components/Panels/ContentPanel"; } from "components/Panels/ContentPanel";
import { Fragment, useMemo, useState } from "react";
import { TranslatedPreviewCard } from "components/PreviewCard"; import { TranslatedPreviewCard } from "components/PreviewCard";
import { HorizontalLine } from "components/HorizontalLine"; import { HorizontalLine } from "components/HorizontalLine";
import { Button } from "components/Inputs/Button"; import { Button } from "components/Inputs/Button";
@ -98,7 +97,9 @@ const Wiki = ({
/> />
<HorizontalLine /> <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 /> <NavOption title={langui.chronology} url="/wiki/chronology" border />
</SubPanel> </SubPanel>
@ -127,6 +128,12 @@ const Wiki = ({
translations={page.attributes.translations.map( translations={page.attributes.translations.map(
(translation) => ({ (translation) => ({
title: translation?.title, title: translation?.title,
subtitle:
translation?.aliases && translation.aliases.length > 0
? translation.aliases
.map((alias) => alias?.alias)
.join(" | ")
: undefined,
description: translation?.summary, description: translation?.summary,
language: translation?.language?.data?.attributes?.code, language: translation?.language?.data?.attributes?.code,
}) })