Continue to improve code with hooks
This commit is contained in:
parent
d0b91f9db6
commit
2443dee83f
|
@ -18,8 +18,8 @@
|
||||||
"meilisearch": "^0.25.1",
|
"meilisearch": "^0.25.1",
|
||||||
"next": "^12.1.6",
|
"next": "^12.1.6",
|
||||||
"nodemailer": "^6.7.5",
|
"nodemailer": "^6.7.5",
|
||||||
"react": "18.2.0",
|
"react": "18.1.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.1.0",
|
||||||
"react-hot-keys": "^2.7.2",
|
"react-hot-keys": "^2.7.2",
|
||||||
"react-swipeable": "^7.0.0",
|
"react-swipeable": "^7.0.0",
|
||||||
"turndown": "^7.1.1"
|
"turndown": "^7.1.1"
|
||||||
|
@ -29,22 +29,23 @@
|
||||||
"@graphql-codegen/typescript": "2.5.1",
|
"@graphql-codegen/typescript": "2.5.1",
|
||||||
"@graphql-codegen/typescript-graphql-request": "^4.4.10",
|
"@graphql-codegen/typescript-graphql-request": "^4.4.10",
|
||||||
"@graphql-codegen/typescript-operations": "^2.4.2",
|
"@graphql-codegen/typescript-operations": "^2.4.2",
|
||||||
"@types/node": "18.0.0",
|
"@types/node": "17.0.41",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/react": "18.0.14",
|
"@types/react": "18.0.12",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@types/turndown": "^5.0.1",
|
"@types/turndown": "^5.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||||
"@typescript-eslint/parser": "^5.28.0",
|
"@typescript-eslint/parser": "^5.27.1",
|
||||||
"eslint": "^8.18.0",
|
"eslint": "^8.17.0",
|
||||||
"eslint-config-next": "12.1.6",
|
"eslint-config-next": "12.1.6",
|
||||||
"graphql": "^16.5.0",
|
"graphql": "^16.5.0",
|
||||||
"next-sitemap": "^3.0.5",
|
"next-sitemap": "^3.0.5",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.6.2",
|
||||||
"prettier-plugin-organize-imports": "^3.0.0",
|
"prettier-plugin-organize-imports": "^2.3.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.1.11",
|
"prettier-plugin-tailwindcss": "^0.1.11",
|
||||||
"tailwindcss": "^3.1.3",
|
"tailwindcss": "^3.1.2",
|
||||||
"typescript": "^4.7.4"
|
"ts-unused-exports": "^8.0.0",
|
||||||
|
"typescript": "^4.7.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
|
@ -2604,9 +2605,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.0.0",
|
"version": "17.0.41",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
|
||||||
"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==",
|
"integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/nodemailer": {
|
"node_modules/@types/nodemailer": {
|
||||||
|
@ -2631,9 +2632,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.0.14",
|
"version": "18.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz",
|
||||||
"integrity": "sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==",
|
"integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
|
@ -7587,9 +7588,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-organize-imports": {
|
"node_modules/prettier-plugin-organize-imports": {
|
||||||
"version": "3.0.0",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz",
|
||||||
"integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==",
|
"integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"prettier": ">=2.0",
|
"prettier": ">=2.0",
|
||||||
|
@ -7703,9 +7704,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.2.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
||||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
|
@ -7714,15 +7715,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
||||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.0"
|
"scheduler": "^0.22.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.2.0"
|
"react": "^18.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-hot-keys": {
|
"node_modules/react-hot-keys": {
|
||||||
|
@ -8045,9 +8046,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.23.0",
|
"version": "0.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
||||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
@ -8525,6 +8526,30 @@
|
||||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-unused-exports": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-gylHFyJqC80PSb4zy35KTckykEW1vmKjnOHjBeX9iKBo4b/SzqQIcXXbYSuif4YMgNm6ewFF62VM1C9z0bGZPw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"tsconfig-paths": "^3.9.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"ts-unused-exports": "bin/ts-unused-exports"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/pzavolinsky/ts-unused-exports?sponsor=1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=3.8.3"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tsconfig-paths": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||||
|
@ -11003,9 +11028,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "18.0.0",
|
"version": "17.0.41",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.41.tgz",
|
||||||
"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==",
|
"integrity": "sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/nodemailer": {
|
"@types/nodemailer": {
|
||||||
|
@ -11030,9 +11055,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
"version": "18.0.14",
|
"version": "18.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz",
|
||||||
"integrity": "sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q==",
|
"integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
|
@ -14738,9 +14763,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"prettier-plugin-organize-imports": {
|
"prettier-plugin-organize-imports": {
|
||||||
"version": "3.0.0",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz",
|
||||||
"integrity": "sha512-juSCJs5TMOqGGPaN/A/1xzWFzRPH2LG1LPLCr64dzKaVnPafJdtgDNmDVlU+8A4LbQzVJg0DTvgA8swBuIUhlg==",
|
"integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
@ -14819,20 +14844,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "18.2.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
||||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"version": "18.2.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
||||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.0"
|
"scheduler": "^0.22.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-hot-keys": {
|
"react-hot-keys": {
|
||||||
|
@ -15059,9 +15084,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"scheduler": {
|
"scheduler": {
|
||||||
"version": "0.23.0",
|
"version": "0.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
||||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
@ -15428,6 +15453,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ts-unused-exports": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-gylHFyJqC80PSb4zy35KTckykEW1vmKjnOHjBeX9iKBo4b/SzqQIcXXbYSuif4YMgNm6ewFF62VM1C9z0bGZPw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"tsconfig-paths": "^3.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tsconfig-paths": {
|
"tsconfig-paths": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
|
||||||
|
|
25
package.json
25
package.json
|
@ -3,6 +3,8 @@
|
||||||
"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!",
|
||||||
|
"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",
|
||||||
|
@ -25,8 +27,8 @@
|
||||||
"meilisearch": "^0.25.1",
|
"meilisearch": "^0.25.1",
|
||||||
"next": "^12.1.6",
|
"next": "^12.1.6",
|
||||||
"nodemailer": "^6.7.5",
|
"nodemailer": "^6.7.5",
|
||||||
"react": "18.2.0",
|
"react": "18.1.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.1.0",
|
||||||
"react-hot-keys": "^2.7.2",
|
"react-hot-keys": "^2.7.2",
|
||||||
"react-swipeable": "^7.0.0",
|
"react-swipeable": "^7.0.0",
|
||||||
"turndown": "^7.1.1"
|
"turndown": "^7.1.1"
|
||||||
|
@ -36,21 +38,22 @@
|
||||||
"@graphql-codegen/typescript": "2.5.1",
|
"@graphql-codegen/typescript": "2.5.1",
|
||||||
"@graphql-codegen/typescript-graphql-request": "^4.4.10",
|
"@graphql-codegen/typescript-graphql-request": "^4.4.10",
|
||||||
"@graphql-codegen/typescript-operations": "^2.4.2",
|
"@graphql-codegen/typescript-operations": "^2.4.2",
|
||||||
"@types/node": "18.0.0",
|
"@types/node": "17.0.41",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/react": "18.0.14",
|
"@types/react": "18.0.12",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@types/turndown": "^5.0.1",
|
"@types/turndown": "^5.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.28.0",
|
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||||
"@typescript-eslint/parser": "^5.28.0",
|
"@typescript-eslint/parser": "^5.27.1",
|
||||||
"eslint": "^8.18.0",
|
"eslint": "^8.17.0",
|
||||||
"eslint-config-next": "12.1.6",
|
"eslint-config-next": "12.1.6",
|
||||||
"graphql": "^16.5.0",
|
"graphql": "^16.5.0",
|
||||||
"next-sitemap": "^3.0.5",
|
"next-sitemap": "^3.0.5",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.6.2",
|
||||||
"prettier-plugin-organize-imports": "^3.0.0",
|
"prettier-plugin-organize-imports": "^2.3.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.1.11",
|
"prettier-plugin-tailwindcss": "^0.1.11",
|
||||||
"tailwindcss": "^3.1.3",
|
"tailwindcss": "^3.1.2",
|
||||||
"typescript": "^4.7.4"
|
"ts-unused-exports": "^8.0.0",
|
||||||
|
"typescript": "^4.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ import {
|
||||||
isUndefined,
|
isUndefined,
|
||||||
iterateMap,
|
iterateMap,
|
||||||
} from "helpers/others";
|
} from "helpers/others";
|
||||||
// import { getClient, Indexes, search, SearchResult } from "helpers/search";
|
|
||||||
|
|
||||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||||
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
import { AnchorIds } from "hooks/useScrollTopOnChange";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
@ -415,31 +413,33 @@ export function AppLayout(props: Props): JSX.Element {
|
||||||
<div className="grid place-items-center gap-8 text-center desktop:grid-cols-2">
|
<div className="grid place-items-center gap-8 text-center desktop:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl">{langui.theme}</h3>
|
<h3 className="text-xl">{langui.theme}</h3>
|
||||||
<ButtonGroup>
|
<ButtonGroup
|
||||||
<Button
|
buttonsProps={[
|
||||||
onClick={() => {
|
{
|
||||||
|
onClick: () => {
|
||||||
setDarkMode(false);
|
setDarkMode(false);
|
||||||
setSelectedThemeMode(true);
|
setSelectedThemeMode(true);
|
||||||
}}
|
},
|
||||||
active={selectedThemeMode === true && darkMode === false}
|
active: selectedThemeMode === true && darkMode === false,
|
||||||
text={langui.light}
|
text: langui.light,
|
||||||
/>
|
},
|
||||||
<Button
|
{
|
||||||
onClick={() => {
|
onClick: () => {
|
||||||
setSelectedThemeMode(false);
|
setSelectedThemeMode(false);
|
||||||
}}
|
},
|
||||||
active={selectedThemeMode === false}
|
active: selectedThemeMode === false,
|
||||||
text={langui.auto}
|
text: langui.auto,
|
||||||
/>
|
},
|
||||||
<Button
|
{
|
||||||
onClick={() => {
|
onClick: () => {
|
||||||
setDarkMode(true);
|
setDarkMode(true);
|
||||||
setSelectedThemeMode(true);
|
setSelectedThemeMode(true);
|
||||||
}}
|
},
|
||||||
active={selectedThemeMode === true && darkMode === true}
|
active: selectedThemeMode === true && darkMode === true,
|
||||||
text={langui.dark}
|
text: langui.dark,
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -456,22 +456,27 @@ export function AppLayout(props: Props): JSX.Element {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl">{langui.font_size}</h3>
|
<h3 className="text-xl">{langui.font_size}</h3>
|
||||||
<ButtonGroup>
|
<ButtonGroup
|
||||||
<Button
|
buttonsProps={[
|
||||||
onClick={() => setFontSize((fontSize ?? 1) / 1.05)}
|
{
|
||||||
icon={Icon.TextDecrease}
|
onClick: () => setFontSize((fontSize ?? 1) / 1.05),
|
||||||
/>
|
icon: Icon.TextDecrease,
|
||||||
<Button
|
},
|
||||||
onClick={() => setFontSize(1)}
|
{
|
||||||
text={`${((fontSize ?? 1) * 100).toLocaleString(undefined, {
|
onClick: () => setFontSize(1),
|
||||||
|
text: `${((fontSize ?? 1) * 100).toLocaleString(
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0,
|
||||||
})}%`}
|
}
|
||||||
|
)}%`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onClick: () => setFontSize((fontSize ?? 1) * 1.05),
|
||||||
|
icon: Icon.TextIncrease,
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
onClick={() => setFontSize((fontSize ?? 1) * 1.05)}
|
|
||||||
icon={Icon.TextIncrease}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -504,61 +509,7 @@ export function AppLayout(props: Props): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
{/* <Popup
|
|
||||||
state={searchPanelOpen}
|
|
||||||
setState={setSearchPanelOpen}
|
|
||||||
>
|
|
||||||
<div className="grid place-items-center gap-2">
|
|
||||||
TODO: add to langui
|
|
||||||
<h2 className="text-2xl">{"Search"}</h2>
|
|
||||||
<TextInput
|
|
||||||
className="mb-6 w-full"
|
|
||||||
placeholder={"Search query..."}
|
|
||||||
state={searchQuery}
|
|
||||||
setState={setSearchQuery}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
TODO: add to langui
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<p className="font-headers text-xl">In news:</p>
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-2 items-end gap-8
|
|
||||||
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
|
|
||||||
>
|
|
||||||
{searchResult?.hits.map((hit) => (
|
|
||||||
<PreviewCard
|
|
||||||
key={hit.id}
|
|
||||||
href={hit.href}
|
|
||||||
title={hit.title}
|
|
||||||
thumbnailAspectRatio={"3/2"}
|
|
||||||
thumbnail={hit.thumbnail}
|
|
||||||
keepInfoVisible
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Popup> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* const [searchQuery, setSearchQuery] = useState("");
|
|
||||||
* const [searchResult, setSearchResult] = useState<SearchResult>();
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* const client = getClient();
|
|
||||||
* useEffect(() => {
|
|
||||||
* if (searchQuery.length > 1) {
|
|
||||||
* search(client, Indexes.Post, searchQuery).then((result) => {
|
|
||||||
* setSearchResult(result);
|
|
||||||
* });
|
|
||||||
* } else {
|
|
||||||
* setSearchResult(undefined);
|
|
||||||
* }
|
|
||||||
* // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
* }, [searchQuery]);
|
|
||||||
*/
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ export function Button(props: Props): JSX.Element {
|
||||||
id={id}
|
id={id}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cJoin(
|
className={cJoin(
|
||||||
`component-button group grid select-none grid-flow-col place-content-center
|
`group grid select-none grid-flow-col place-content-center
|
||||||
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 leading-none
|
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 leading-none
|
||||||
text-dark transition-all`,
|
text-dark transition-all`,
|
||||||
cIf(
|
cIf(
|
||||||
|
|
|
@ -1,39 +1,53 @@
|
||||||
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { cJoin } from "helpers/className";
|
import { cJoin } from "helpers/className";
|
||||||
|
import { ConditionalWrapper, Wrapper } from "helpers/component";
|
||||||
import { useLayoutEffect, useRef } from "react";
|
import { isDefinedAndNotEmpty } from "helpers/others";
|
||||||
|
import { Button } from "./Button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
buttonsProps: (Parameters<typeof Button>[0] & {
|
||||||
|
tooltip?: string | null | undefined;
|
||||||
|
})[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ButtonGroup(props: Props): JSX.Element {
|
export function ButtonGroup(props: Props): JSX.Element {
|
||||||
const { children, className } = props;
|
const { buttonsProps, className } = props;
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
if (ref.current) {
|
|
||||||
const buttons = ref.current.querySelectorAll(".component-button");
|
|
||||||
buttons.forEach((button, index) => {
|
|
||||||
button.classList.remove("rounded-full");
|
|
||||||
button.classList.remove("border-[1px]");
|
|
||||||
if (index === 0) {
|
|
||||||
button.classList.add("rounded-l-full");
|
|
||||||
button.classList.add("border-l-[1px]");
|
|
||||||
} else if (index === buttons.length - 1) {
|
|
||||||
button.classList.add("rounded-r-full");
|
|
||||||
button.classList.add("border-r-[1px]");
|
|
||||||
} else {
|
|
||||||
button.classList.add("rounded-none");
|
|
||||||
}
|
|
||||||
button.classList.add("border-y-[1px]");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [children]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={cJoin("grid grid-flow-col", className)}>
|
<div className={cJoin("grid grid-flow-col", className)}>
|
||||||
{children}
|
{buttonsProps.map((buttonProps, index) => (
|
||||||
|
<ConditionalWrapper
|
||||||
|
key={index}
|
||||||
|
isWrapping={isDefinedAndNotEmpty(buttonProps.tooltip)}
|
||||||
|
wrapper={ToolTipWrapper}
|
||||||
|
wrapperProps={{ text: buttonProps.tooltip ?? "" }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
{...buttonProps}
|
||||||
|
className={
|
||||||
|
index === 0
|
||||||
|
? "rounded-r-none border-r-0"
|
||||||
|
: index === buttonsProps.length - 1
|
||||||
|
? "rounded-l-none"
|
||||||
|
: "rounded-none border-r-0"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ConditionalWrapper>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ToolTipWrapperProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToolTipWrapper(props: ToolTipWrapperProps & Wrapper) {
|
||||||
|
const { text, children } = props;
|
||||||
|
return (
|
||||||
|
<ToolTip content={text}>
|
||||||
|
<>{children}</>
|
||||||
|
</ToolTip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
import { Chip } from "components/Chip";
|
|
||||||
import { Ico, Icon } from "components/Ico";
|
|
||||||
import { Button } from "components/Inputs/Button";
|
|
||||||
import { GetLibraryItemQuery } from "graphql/generated";
|
|
||||||
import { AppStaticProps } from "graphql/getAppStaticProps";
|
|
||||||
import { cIf, cJoin } from "helpers/className";
|
|
||||||
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
|
|
||||||
import { filterHasAttributes } from "helpers/others";
|
|
||||||
|
|
||||||
import { useToggle } from "hooks/useToggle";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
content: NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
GetLibraryItemQuery["libraryItems"]
|
|
||||||
>["data"][number]["attributes"]
|
|
||||||
>["contents"]
|
|
||||||
>["data"][number];
|
|
||||||
parentSlug: string;
|
|
||||||
langui: AppStaticProps["langui"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ContentLine(props: Props): JSX.Element {
|
|
||||||
const { content, langui, parentSlug } = props;
|
|
||||||
|
|
||||||
const [opened, setOpened] = useState(false);
|
|
||||||
const toggleOpened = useToggle(setOpened);
|
|
||||||
|
|
||||||
if (content.attributes) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cJoin(
|
|
||||||
"grid gap-2 rounded-lg px-4",
|
|
||||||
cIf(opened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center
|
|
||||||
gap-4 thin:grid-cols-[auto_auto_1fr_auto]"
|
|
||||||
>
|
|
||||||
<a>
|
|
||||||
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
|
||||||
{content.attributes.content?.data?.attributes?.translations?.[0]
|
|
||||||
? prettyinlineTitle(
|
|
||||||
content.attributes.content.data.attributes.translations[0]
|
|
||||||
?.pre_title,
|
|
||||||
content.attributes.content.data.attributes.translations[0]
|
|
||||||
?.title,
|
|
||||||
content.attributes.content.data.attributes.translations[0]
|
|
||||||
?.subtitle
|
|
||||||
)
|
|
||||||
: prettySlug(content.attributes.slug, props.parentSlug)}
|
|
||||||
</h3>
|
|
||||||
</a>
|
|
||||||
<div className="flex flex-row flex-wrap gap-1">
|
|
||||||
{filterHasAttributes(
|
|
||||||
content.attributes.content?.data?.attributes?.categories?.data
|
|
||||||
).map((category) => (
|
|
||||||
<Chip key={category.id}>{category.attributes.short}</Chip>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
|
|
||||||
<p>
|
|
||||||
{content.attributes.range[0]?.__typename ===
|
|
||||||
"ComponentRangePageRange"
|
|
||||||
? content.attributes.range[0].starting_page
|
|
||||||
: ""}
|
|
||||||
</p>
|
|
||||||
{content.attributes.content?.data?.attributes?.type?.data
|
|
||||||
?.attributes && (
|
|
||||||
<Chip className="justify-self-end thin:hidden">
|
|
||||||
{content.attributes.content.data.attributes.type.data.attributes
|
|
||||||
.titles &&
|
|
||||||
content.attributes.content.data.attributes.type.data.attributes
|
|
||||||
.titles.length > 0
|
|
||||||
? content.attributes.content.data.attributes.type.data
|
|
||||||
.attributes.titles[0]?.title
|
|
||||||
: prettySlug(
|
|
||||||
content.attributes.content.data.attributes.type.data
|
|
||||||
.attributes.slug
|
|
||||||
)}
|
|
||||||
</Chip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
|
||||||
opened ? "grid" : "hidden"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
|
||||||
|
|
||||||
{content.attributes.scan_set &&
|
|
||||||
content.attributes.scan_set.length > 0 && (
|
|
||||||
<Button
|
|
||||||
href={`/library/${parentSlug}/scans#${content.attributes.slug}`}
|
|
||||||
text={langui.view_scans}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{content.attributes.content?.data && (
|
|
||||||
<Button
|
|
||||||
href={`/contents/${content.attributes.content.data.attributes?.slug}`}
|
|
||||||
text={langui.open_content}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{content.attributes.scan_set &&
|
|
||||||
content.attributes.scan_set.length === 0 &&
|
|
||||||
!content.attributes.content?.data
|
|
||||||
? "The content is not available"
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <></>;
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
} from "helpers/others";
|
} from "helpers/others";
|
||||||
|
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { Fragment, useMemo } from "react";
|
import { Fragment, useCallback, useMemo } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
openLightBox: (images: string[], index?: number) => void;
|
openLightBox: (images: string[], index?: number) => void;
|
||||||
|
@ -49,11 +49,16 @@ export function ScanSet(props: Props): JSX.Element {
|
||||||
const { openLightBox, scanSet, slug, title, languages, langui, content } =
|
const { openLightBox, scanSet, slug, title, languages, langui, content } =
|
||||||
props;
|
props;
|
||||||
|
|
||||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
items: scanSet,
|
items: scanSet,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: useCallback(
|
||||||
transform: (item) => {
|
(item: NonNullable<Props["scanSet"][number]>) =>
|
||||||
|
item.language?.data?.attributes?.code,
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
transform: useCallback((item: NonNullable<Props["scanSet"][number]>) => {
|
||||||
item.pages?.data.sort((a, b) => {
|
item.pages?.data.sort((a, b) => {
|
||||||
if (
|
if (
|
||||||
a.attributes &&
|
a.attributes &&
|
||||||
|
@ -83,7 +88,7 @@ export function ScanSet(props: Props): JSX.Element {
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
return item;
|
return item;
|
||||||
},
|
}, []),
|
||||||
});
|
});
|
||||||
|
|
||||||
const pages = useMemo(
|
const pages = useMemo(
|
||||||
|
@ -120,7 +125,7 @@ export function ScanSet(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LanguageSwitcher />
|
<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">{langui.status}:</p>
|
||||||
|
|
|
@ -11,7 +11,7 @@ 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, useMemo } from "react";
|
import { Fragment, useCallback, useMemo } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
openLightBox: (images: string[], index?: number) => void;
|
openLightBox: (images: string[], index?: number) => void;
|
||||||
|
@ -29,10 +29,15 @@ interface Props {
|
||||||
export function ScanSetCover(props: Props): JSX.Element {
|
export function ScanSetCover(props: Props): JSX.Element {
|
||||||
const { openLightBox, images, languages, langui } = props;
|
const { openLightBox, images, languages, langui } = props;
|
||||||
|
|
||||||
const [selectedScan, LanguageSwitcher] = useSmartLanguage({
|
const [selectedScan, LanguageSwitcher, languageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
items: images,
|
items: images,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: useCallback(
|
||||||
|
(item: NonNullable<Props["images"][number]>) =>
|
||||||
|
item.language?.data?.attributes?.code,
|
||||||
|
[]
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const coverImages = useMemo(() => {
|
const coverImages = useMemo(() => {
|
||||||
|
@ -74,7 +79,7 @@ export function ScanSetCover(props: Props): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
|
||||||
<LanguageSwitcher />
|
<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">{langui.status}:</p>
|
||||||
|
|
|
@ -69,7 +69,7 @@ interface TOCInterface {
|
||||||
children: TOCInterface[];
|
children: TOCInterface[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTocFromMarkdawn(text: string, title?: string): TOCInterface {
|
function getTocFromMarkdawn(text: string, title?: string): TOCInterface {
|
||||||
const toc: TOCInterface = {
|
const toc: TOCInterface = {
|
||||||
title: title ?? "Return to top",
|
title: title ?? "Return to top",
|
||||||
slug: slugify(title),
|
slug: slugify(title),
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { prettySlug } from "helpers/formatters";
|
||||||
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
import { filterHasAttributes, getStatusDescription } from "helpers/others";
|
||||||
import { PostWithTranslations } from "helpers/types";
|
import { PostWithTranslations } from "helpers/types";
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import { Fragment, 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";
|
||||||
import { HorizontalLine } from "./HorizontalLine";
|
import { HorizontalLine } from "./HorizontalLine";
|
||||||
|
@ -49,10 +49,15 @@ export function PostPage(props: Props): JSX.Element {
|
||||||
} = props;
|
} = props;
|
||||||
const displayTitle = props.displayTitle ?? true;
|
const displayTitle = props.displayTitle ?? true;
|
||||||
|
|
||||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
items: post.translations,
|
items: post.translations,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: useCallback(
|
||||||
|
(item: NonNullable<PostWithTranslations["translations"][number]>) =>
|
||||||
|
item.language?.data?.attributes?.code,
|
||||||
|
[]
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { thumbnail, body, title, excerpt } = useMemo(
|
const { thumbnail, body, title, excerpt } = useMemo(
|
||||||
|
@ -156,7 +161,7 @@ export function PostPage(props: Props): JSX.Element {
|
||||||
description={excerpt}
|
description={excerpt}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
categories={post.categories}
|
categories={post.categories}
|
||||||
languageSwitcher={<LanguageSwitcher />}
|
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
@ -165,7 +170,7 @@ export function PostPage(props: Props): JSX.Element {
|
||||||
<>
|
<>
|
||||||
{displayLanguageSwitcher && (
|
{displayLanguageSwitcher && (
|
||||||
<div className="grid place-content-end place-items-start">
|
<div className="grid place-content-end place-items-start">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{displayTitle && (
|
{displayTitle && (
|
||||||
|
@ -189,6 +194,7 @@ export function PostPage(props: Props): JSX.Element {
|
||||||
displayThumbnailHeader,
|
displayThumbnailHeader,
|
||||||
displayTitle,
|
displayTitle,
|
||||||
excerpt,
|
excerpt,
|
||||||
|
languageSwitcherProps,
|
||||||
langui,
|
langui,
|
||||||
post.categories,
|
post.categories,
|
||||||
prependBody,
|
prependBody,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { ImageQuality } from "helpers/img";
|
||||||
|
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useCallback } from "react";
|
||||||
import { Chip } from "./Chip";
|
import { Chip } from "./Chip";
|
||||||
import { Ico, Icon } from "./Ico";
|
import { Ico, Icon } from "./Ico";
|
||||||
import { Img } from "./Img";
|
import { Img } from "./Img";
|
||||||
|
@ -290,15 +291,13 @@ export function PreviewCard(props: Props): JSX.Element {
|
||||||
|
|
||||||
interface TranslatedProps
|
interface TranslatedProps
|
||||||
extends Omit<Props, "description" | "pre_title" | "subtitle" | "title"> {
|
extends Omit<Props, "description" | "pre_title" | "subtitle" | "title"> {
|
||||||
translations:
|
translations: {
|
||||||
| {
|
|
||||||
pre_title?: string | null | undefined;
|
pre_title?: string | null | undefined;
|
||||||
title: string | null | undefined;
|
title: string | null | undefined;
|
||||||
subtitle?: string | null | undefined;
|
subtitle?: string | null | undefined;
|
||||||
description?: string | null | undefined;
|
description?: string | null | undefined;
|
||||||
language: string | undefined;
|
language: string | undefined;
|
||||||
}[]
|
}[];
|
||||||
| undefined;
|
|
||||||
slug: string;
|
slug: string;
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
}
|
}
|
||||||
|
@ -313,7 +312,10 @@ export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language,
|
languageExtractor: useCallback(
|
||||||
|
(item: TranslatedProps["translations"][number]) => item.language,
|
||||||
|
[]
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ImageQuality } from "helpers/img";
|
||||||
|
|
||||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useCallback } from "react";
|
||||||
import { Chip } from "./Chip";
|
import { Chip } from "./Chip";
|
||||||
import { Img } from "./Img";
|
import { Img } from "./Img";
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ interface Props {
|
||||||
bottomChips?: string[];
|
bottomChips?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PreviewLine(props: Props): JSX.Element {
|
function PreviewLine(props: Props): JSX.Element {
|
||||||
const {
|
const {
|
||||||
href,
|
href,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
|
@ -76,14 +77,13 @@ export function PreviewLine(props: Props): JSX.Element {
|
||||||
|
|
||||||
interface TranslatedProps
|
interface TranslatedProps
|
||||||
extends Omit<Props, "pre_title" | "subtitle" | "title"> {
|
extends Omit<Props, "pre_title" | "subtitle" | "title"> {
|
||||||
translations:
|
translations: {
|
||||||
| {
|
|
||||||
pre_title?: string | null | undefined;
|
pre_title?: string | null | undefined;
|
||||||
title: string | null | undefined;
|
title: string | null | undefined;
|
||||||
subtitle?: string | null | undefined;
|
subtitle?: string | null | undefined;
|
||||||
language: string | undefined;
|
language: string | undefined;
|
||||||
}[]
|
}[];
|
||||||
| undefined;
|
|
||||||
slug: string;
|
slug: string;
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,10 @@ export function TranslatedPreviewLine(props: TranslatedProps): JSX.Element {
|
||||||
const [selectedTranslation] = useSmartLanguage({
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language,
|
languageExtractor: useCallback(
|
||||||
|
(item: TranslatedProps["translations"][number]) => item.language,
|
||||||
|
[]
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -3,16 +3,15 @@ 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";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
source?: string;
|
source?: string;
|
||||||
translations:
|
translations: {
|
||||||
| {
|
|
||||||
language: string | undefined;
|
language: string | undefined;
|
||||||
definition: string | null | undefined;
|
definition: string | null | undefined;
|
||||||
status: string | undefined;
|
status: string | undefined;
|
||||||
}[]
|
}[];
|
||||||
| undefined;
|
|
||||||
languages: AppStaticProps["languages"];
|
languages: AppStaticProps["languages"];
|
||||||
langui: AppStaticProps["langui"];
|
langui: AppStaticProps["langui"];
|
||||||
index: number;
|
index: number;
|
||||||
|
@ -21,10 +20,14 @@ interface Props {
|
||||||
export default function DefinitionCard(props: Props): JSX.Element {
|
export default function DefinitionCard(props: Props): JSX.Element {
|
||||||
const { source, translations = [], languages, langui, index } = props;
|
const { source, translations = [], languages, langui, index } = props;
|
||||||
|
|
||||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
items: translations,
|
items: translations,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language,
|
languageExtractor: useCallback(
|
||||||
|
(item: Props["translations"][number]) => item.language,
|
||||||
|
[]
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -40,7 +43,9 @@ export default function DefinitionCard(props: Props): JSX.Element {
|
||||||
<Chip>{selectedTranslation.status}</Chip>
|
<Chip>{selectedTranslation.status}</Chip>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
)}
|
)}
|
||||||
{translations.length > 1 && <LanguageSwitcher />}
|
{translations.length > 1 && (
|
||||||
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="italic">{`${langui.source}: ${source}`}</p>
|
<p className="italic">{`${langui.source}: ${source}`}</p>
|
||||||
|
|
|
@ -106,8 +106,6 @@ const initialState: RequiredNonNullable<AppLayoutState> = {
|
||||||
|
|
||||||
const AppContext = React.createContext<AppLayoutState>(initialState);
|
const AppContext = React.createContext<AppLayoutState>(initialState);
|
||||||
|
|
||||||
export default AppContext;
|
|
||||||
|
|
||||||
export function useAppLayout(): AppLayoutState {
|
export function useAppLayout(): AppLayoutState {
|
||||||
return useContext(AppContext);
|
return useContext(AppContext);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { ContentWithTranslations } from "./types";
|
|
||||||
|
|
||||||
type Group = NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<
|
|
||||||
NonNullable<ContentWithTranslations["group"]>["data"]
|
|
||||||
>["attributes"]
|
|
||||||
>["contents"]
|
|
||||||
>["data"];
|
|
||||||
|
|
||||||
export function getPreviousContent(group: Group, currentSlug: string) {
|
|
||||||
for (let index = 0; index < group.length; index += 1) {
|
|
||||||
const content = group[index];
|
|
||||||
if (content.attributes?.slug === currentSlug && index > 0) {
|
|
||||||
return group[index - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNextContent(group: Group, currentSlug: string) {
|
|
||||||
for (let index = 0; index < group.length; index += 1) {
|
|
||||||
const content = group[index];
|
|
||||||
if (content.attributes?.slug === currentSlug && index < group.length - 1) {
|
|
||||||
return group[index + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
|
@ -242,24 +242,12 @@ export function prettyLanguage(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prettyLanguageToCode(
|
|
||||||
prettyLanguage: string,
|
|
||||||
languages: AppStaticProps["languages"]
|
|
||||||
): string {
|
|
||||||
let result = prettyLanguage;
|
|
||||||
languages.forEach((language) => {
|
|
||||||
if (language?.attributes?.localized_name === prettyLanguage)
|
|
||||||
result = language.attributes.code;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prettyURL(url: string): string {
|
export function prettyURL(url: string): string {
|
||||||
let domain = new URL(url);
|
let domain = new URL(url);
|
||||||
return domain.hostname.replace("www.", "");
|
return domain.hostname.replace("www.", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function capitalizeString(string: string): string {
|
function capitalizeString(string: string): string {
|
||||||
function capitalizeWord(word: string): string {
|
function capitalizeWord(word: string): string {
|
||||||
return word.charAt(0).toUpperCase() + word.substring(1);
|
return word.charAt(0).toUpperCase() + word.substring(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ export enum ImageQuality {
|
||||||
Og = "og",
|
Og = "og",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OgImage {
|
interface OgImage {
|
||||||
image: string;
|
image: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -34,7 +34,7 @@ export function getAssetURL(url: string, quality: ImageQuality): string {
|
||||||
return process.env.NEXT_PUBLIC_URL_IMG + newUrl;
|
return process.env.NEXT_PUBLIC_URL_IMG + newUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getImgSizesByMaxSize(
|
function getImgSizesByMaxSize(
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
maxSize: number
|
maxSize: number
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { prettyinlineTitle, prettyDate } from "./formatters";
|
||||||
import { convertPrice } from "./numbers";
|
import { convertPrice } from "./numbers";
|
||||||
import { isDefined, mapRemoveEmptyValues } from "./others";
|
import { isDefined, mapRemoveEmptyValues } from "./others";
|
||||||
import { LibraryItemUserStatus } from "./types";
|
import { LibraryItemUserStatus } from "./types";
|
||||||
type Items = NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
|
import LibraryPage from "../pages/library/index";
|
||||||
|
|
||||||
|
type Items = Parameters<typeof LibraryPage>[0]["items"];
|
||||||
type GroupLibraryItems = Map<string, Items>;
|
type GroupLibraryItems = Map<string, Items>;
|
||||||
|
|
||||||
export function getGroups(
|
export function getGroups(
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { UploadImageFragment } from "graphql/generated";
|
|
||||||
import { MeiliSearch, SearchResponse } from "meilisearch";
|
|
||||||
import { prettySlug } from "./formatters";
|
|
||||||
|
|
||||||
export enum Indexes {
|
|
||||||
ChronologyEra = "chronology-era",
|
|
||||||
ChronologyItem = "chronology-item",
|
|
||||||
Content = "content",
|
|
||||||
GlossaryItem = "glossary-item",
|
|
||||||
LibraryItem = "library-item",
|
|
||||||
MerchItem = "merch-item",
|
|
||||||
Post = "post",
|
|
||||||
Video = "video",
|
|
||||||
VideoChannel = "video-channel",
|
|
||||||
WeaponStory = "weapon-story",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getClient() {
|
|
||||||
return new MeiliSearch({
|
|
||||||
host: process.env.NEXT_PUBLIC_URL_SEARCH ?? "",
|
|
||||||
apiKey: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getIndexes(client: MeiliSearch) {
|
|
||||||
return await client.getIndexes();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function search(
|
|
||||||
client: MeiliSearch,
|
|
||||||
indexName: Indexes,
|
|
||||||
query: string
|
|
||||||
) {
|
|
||||||
const index = await client.getIndex(indexName);
|
|
||||||
const results = await index.search(query);
|
|
||||||
return processSearchResults(results, indexName);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SearchResult = {
|
|
||||||
hits: {
|
|
||||||
id: string;
|
|
||||||
href: string;
|
|
||||||
title: string;
|
|
||||||
thumbnail?: UploadImageFragment;
|
|
||||||
}[];
|
|
||||||
indexName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function processSearchResults(
|
|
||||||
result: SearchResponse<Record<string, any>>,
|
|
||||||
indexName: Indexes
|
|
||||||
): SearchResult {
|
|
||||||
return {
|
|
||||||
hits: result.hits.map((hit) => {
|
|
||||||
switch (indexName) {
|
|
||||||
case Indexes.Post: {
|
|
||||||
return {
|
|
||||||
id: hit.id,
|
|
||||||
title:
|
|
||||||
hit.translations.length > 0
|
|
||||||
? hit.translations[0].title
|
|
||||||
: prettySlug(hit.slug),
|
|
||||||
href: `/news/${hit.slug}`,
|
|
||||||
thumbnail: hit.thumbnail,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return { id: hit.id, title: prettySlug(hit.slug), href: "error" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
indexName: indexName,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
GetPostQuery,
|
GetPostQuery,
|
||||||
GetWikiPageQuery,
|
GetWikiPageQuery,
|
||||||
} from "graphql/generated";
|
} from "graphql/generated";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
type Post = NonNullable<
|
type Post = NonNullable<
|
||||||
NonNullable<GetPostQuery["posts"]>["data"][number]["attributes"]
|
NonNullable<GetPostQuery["posts"]>["data"][number]["attributes"]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { breaks } from "../../design.config";
|
import { breaks } from "../../design.config";
|
||||||
|
|
||||||
export function useMediaQuery(query: string): boolean {
|
function useMediaQuery(query: string): boolean {
|
||||||
function getMatches(query: string): boolean {
|
function getMatches(query: string): boolean {
|
||||||
// Prevents SSR issues
|
// Prevents SSR issues
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
|
@ -33,6 +33,7 @@ export function useMediaQuery(query: string): boolean {
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ts-unused-exports:disable-next-line
|
||||||
export function useMediaThin() {
|
export function useMediaThin() {
|
||||||
return useMediaQuery(breaks.thin.raw);
|
return useMediaQuery(breaks.thin.raw);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
|
||||||
|
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||||
|
import { AppStaticProps } from "graphql/getAppStaticProps";
|
||||||
|
import { filterDefined, isDefined } from "helpers/others";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
interface Props<T> {
|
||||||
|
items: T[];
|
||||||
|
languages: AppStaticProps["languages"];
|
||||||
|
languageExtractor: (item: NonNullable<T>) => string | undefined;
|
||||||
|
transform?: (item: NonNullable<T>) => NonNullable<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreferredLanguage(
|
||||||
|
preferredLanguages: (string | undefined)[],
|
||||||
|
availableLanguages: Map<string, number>
|
||||||
|
): number | undefined {
|
||||||
|
for (const locale of preferredLanguages) {
|
||||||
|
if (isDefined(locale) && availableLanguages.has(locale)) {
|
||||||
|
return availableLanguages.get(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSmartLanguage<T>(
|
||||||
|
props: Props<T>
|
||||||
|
): [
|
||||||
|
T | undefined,
|
||||||
|
typeof LanguageSwitcher,
|
||||||
|
Parameters<typeof LanguageSwitcher>[0]
|
||||||
|
] {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
languageExtractor,
|
||||||
|
languages,
|
||||||
|
transform = (item) => item,
|
||||||
|
} = props;
|
||||||
|
const { preferredLanguages } = useAppLayout();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const availableLocales = useMemo(() => {
|
||||||
|
const memo = new Map<string, number>();
|
||||||
|
filterDefined(items).map((elem, index) => {
|
||||||
|
const result = languageExtractor(elem);
|
||||||
|
if (isDefined(result)) memo.set(result, index);
|
||||||
|
});
|
||||||
|
return memo;
|
||||||
|
}, [items, languageExtractor]);
|
||||||
|
|
||||||
|
const [selectedTranslationIndex, setSelectedTranslationIndex] = useState<
|
||||||
|
number | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedTranslationIndex(
|
||||||
|
getPreferredLanguage(
|
||||||
|
preferredLanguages ?? [router.locale],
|
||||||
|
availableLocales
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [preferredLanguages, availableLocales, router.locale]);
|
||||||
|
|
||||||
|
const selectedTranslation = useMemo(() => {
|
||||||
|
if (isDefined(selectedTranslationIndex)) {
|
||||||
|
const item = items[selectedTranslationIndex];
|
||||||
|
if (isDefined(item)) {
|
||||||
|
return transform(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [items, selectedTranslationIndex, transform]);
|
||||||
|
|
||||||
|
const languageSwitcherProps = {
|
||||||
|
languages: languages,
|
||||||
|
locales: availableLocales,
|
||||||
|
localesIndex: selectedTranslationIndex,
|
||||||
|
onLanguageChanged: setSelectedTranslationIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
return [selectedTranslation, LanguageSwitcher, languageSwitcherProps];
|
||||||
|
}
|
|
@ -27,7 +27,11 @@ function getPreferredLanguage(
|
||||||
|
|
||||||
export function useSmartLanguage<T>(
|
export function useSmartLanguage<T>(
|
||||||
props: Props<T>
|
props: Props<T>
|
||||||
): [T | undefined, () => JSX.Element] {
|
): [
|
||||||
|
T | undefined,
|
||||||
|
typeof LanguageSwitcher,
|
||||||
|
Parameters<typeof LanguageSwitcher>[0]
|
||||||
|
] {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
languageExtractor,
|
languageExtractor,
|
||||||
|
@ -52,8 +56,6 @@ export function useSmartLanguage<T>(
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedTranslationIndex(
|
setSelectedTranslationIndex(
|
||||||
(current) =>
|
|
||||||
current ??
|
|
||||||
getPreferredLanguage(
|
getPreferredLanguage(
|
||||||
preferredLanguages ?? [router.locale],
|
preferredLanguages ?? [router.locale],
|
||||||
availableLocales
|
availableLocales
|
||||||
|
@ -71,15 +73,12 @@ export function useSmartLanguage<T>(
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [items, selectedTranslationIndex, transform]);
|
}, [items, selectedTranslationIndex, transform]);
|
||||||
|
|
||||||
return [
|
const languageSwitcherProps = {
|
||||||
selectedTranslation,
|
languages: languages,
|
||||||
() => (
|
locales: availableLocales,
|
||||||
<LanguageSwitcher
|
localesIndex: selectedTranslationIndex,
|
||||||
languages={languages}
|
onLanguageChanged: setSelectedTranslationIndex,
|
||||||
locales={availableLocales}
|
};
|
||||||
localesIndex={selectedTranslationIndex}
|
|
||||||
onLanguageChanged={setSelectedTranslationIndex}
|
return [selectedTranslation, LanguageSwitcher, languageSwitcherProps];
|
||||||
/>
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import { ThumbnailHeader } from "components/ThumbnailHeader";
|
||||||
import { ToolTip } from "components/ToolTip";
|
import { ToolTip } from "components/ToolTip";
|
||||||
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
|
||||||
import { getReadySdk } from "graphql/sdk";
|
import { getReadySdk } from "graphql/sdk";
|
||||||
import { getNextContent, getPreviousContent } from "helpers/contents";
|
|
||||||
import { getDescription } from "helpers/description";
|
import { getDescription } from "helpers/description";
|
||||||
import {
|
import {
|
||||||
prettyinlineTitle,
|
prettyinlineTitle,
|
||||||
|
@ -27,6 +26,7 @@ import {
|
||||||
} from "helpers/formatters";
|
} from "helpers/formatters";
|
||||||
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
import { isUntangibleGroupItem } from "helpers/libraryItem";
|
||||||
import {
|
import {
|
||||||
|
filterDefined,
|
||||||
filterHasAttributes,
|
filterHasAttributes,
|
||||||
getStatusDescription,
|
getStatusDescription,
|
||||||
isDefinedAndNotEmpty,
|
isDefinedAndNotEmpty,
|
||||||
|
@ -40,20 +40,33 @@ import {
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { Fragment, useMemo } from "react";
|
import { Fragment, useCallback, useMemo } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
content: ContentWithTranslations;
|
content: ContentWithTranslations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Group = NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<
|
||||||
|
NonNullable<ContentWithTranslations["group"]>["data"]
|
||||||
|
>["attributes"]
|
||||||
|
>["contents"]
|
||||||
|
>["data"];
|
||||||
|
|
||||||
export default function Content(props: Props): JSX.Element {
|
export default function Content(props: Props): JSX.Element {
|
||||||
const { langui, content, languages, currencies } = props;
|
const { langui, content, languages, currencies } = props;
|
||||||
const isMobile = useMediaMobile();
|
const isMobile = useMediaMobile();
|
||||||
|
|
||||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
items: content.translations,
|
items: content.translations,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: useCallback(
|
||||||
|
(item: NonNullable<Props["content"]["translations"][number]>) =>
|
||||||
|
item.language?.data?.attributes?.code,
|
||||||
|
[]
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
useScrollTopOnChange(AnchorIds.ContentPanel, [selectedTranslation]);
|
useScrollTopOnChange(AnchorIds.ContentPanel, [selectedTranslation]);
|
||||||
|
@ -305,7 +318,7 @@ export default function Content(props: Props): JSX.Element {
|
||||||
type={content.type}
|
type={content.type}
|
||||||
categories={content.categories}
|
categories={content.categories}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
languageSwitcher={<LanguageSwitcher />}
|
languageSwitcher={<LanguageSwitcher {...languageSwitcherProps} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{previousContent?.attributes && (
|
{previousContent?.attributes && (
|
||||||
|
@ -315,14 +328,14 @@ export default function Content(props: Props): JSX.Element {
|
||||||
</h2>
|
</h2>
|
||||||
<TranslatedPreviewLine
|
<TranslatedPreviewLine
|
||||||
href={`/contents/${previousContent.attributes.slug}`}
|
href={`/contents/${previousContent.attributes.slug}`}
|
||||||
translations={previousContent.attributes.translations?.map(
|
translations={filterDefined(
|
||||||
(translation) => ({
|
previousContent.attributes.translations
|
||||||
pre_title: translation?.pre_title,
|
).map((translation) => ({
|
||||||
title: translation?.title,
|
pre_title: translation.pre_title,
|
||||||
subtitle: translation?.subtitle,
|
title: translation.title,
|
||||||
language: translation?.language?.data?.attributes?.code,
|
subtitle: translation.subtitle,
|
||||||
})
|
language: translation.language?.data?.attributes?.code,
|
||||||
)}
|
}))}
|
||||||
slug={previousContent.attributes.slug}
|
slug={previousContent.attributes.slug}
|
||||||
languages={languages}
|
languages={languages}
|
||||||
thumbnail={
|
thumbnail={
|
||||||
|
@ -368,14 +381,14 @@ export default function Content(props: Props): JSX.Element {
|
||||||
</h2>
|
</h2>
|
||||||
<TranslatedPreviewLine
|
<TranslatedPreviewLine
|
||||||
href={`/contents/${nextContent.attributes.slug}`}
|
href={`/contents/${nextContent.attributes.slug}`}
|
||||||
translations={nextContent.attributes.translations?.map(
|
translations={filterDefined(
|
||||||
(translation) => ({
|
nextContent.attributes.translations
|
||||||
pre_title: translation?.pre_title,
|
).map((translation) => ({
|
||||||
title: translation?.title,
|
pre_title: translation.pre_title,
|
||||||
subtitle: translation?.subtitle,
|
title: translation.title,
|
||||||
language: translation?.language?.data?.attributes?.code,
|
subtitle: translation.subtitle,
|
||||||
})
|
language: translation.language?.data?.attributes?.code,
|
||||||
)}
|
}))}
|
||||||
slug={nextContent.attributes.slug}
|
slug={nextContent.attributes.slug}
|
||||||
languages={languages}
|
languages={languages}
|
||||||
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
thumbnail={nextContent.attributes.thumbnail?.data?.attributes}
|
||||||
|
@ -413,11 +426,16 @@ export default function Content(props: Props): JSX.Element {
|
||||||
content.thumbnail?.data?.attributes,
|
content.thumbnail?.data?.attributes,
|
||||||
content.type,
|
content.type,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
languageSwitcherProps,
|
||||||
languages,
|
languages,
|
||||||
langui,
|
langui,
|
||||||
nextContent?.attributes,
|
nextContent?.attributes,
|
||||||
previousContent?.attributes,
|
previousContent?.attributes,
|
||||||
selectedTranslation,
|
selectedTranslation?.description,
|
||||||
|
selectedTranslation?.pre_title,
|
||||||
|
selectedTranslation?.subtitle,
|
||||||
|
selectedTranslation?.text_set?.text,
|
||||||
|
selectedTranslation?.title,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -487,3 +505,23 @@ export async function getStaticPaths(
|
||||||
fallback: "blocking",
|
fallback: "blocking",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPreviousContent(group: Group, currentSlug: string) {
|
||||||
|
for (let index = 0; index < group.length; index += 1) {
|
||||||
|
const content = group[index];
|
||||||
|
if (content.attributes?.slug === currentSlug && index > 0) {
|
||||||
|
return group[index - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextContent(group: Group, currentSlug: string) {
|
||||||
|
for (let index = 0; index < group.length; index += 1) {
|
||||||
|
const content = group[index];
|
||||||
|
if (content.attributes?.slug === currentSlug && index < group.length - 1) {
|
||||||
|
return group[index + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from "components/Panels/ContentPanel";
|
} from "components/Panels/ContentPanel";
|
||||||
import { SubPanel } from "components/Panels/SubPanel";
|
import { SubPanel } from "components/Panels/SubPanel";
|
||||||
import { TranslatedPreviewCard } from "components/PreviewCard";
|
import { TranslatedPreviewCard } from "components/PreviewCard";
|
||||||
import { GetContentsQuery } 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";
|
||||||
|
@ -27,6 +27,7 @@ import {
|
||||||
mapRemoveEmptyValues,
|
mapRemoveEmptyValues,
|
||||||
} from "helpers/others";
|
} from "helpers/others";
|
||||||
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
|
||||||
|
import { GetContentsQuery } from "graphql/generated";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
contents: NonNullable<GetContentsQuery["contents"]>["data"];
|
contents: NonNullable<GetContentsQuery["contents"]>["data"];
|
||||||
|
@ -194,9 +195,10 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
>
|
>
|
||||||
{filterHasAttributes(items).map((item) => (
|
{filterHasAttributes(items).map((item) => (
|
||||||
<Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
|
{item.attributes.translations && (
|
||||||
<TranslatedPreviewCard
|
<TranslatedPreviewCard
|
||||||
href={`/contents/${item.attributes.slug}`}
|
href={`/contents/${item.attributes.slug}`}
|
||||||
translations={item.attributes.translations?.map(
|
translations={item.attributes.translations.map(
|
||||||
(translation) => ({
|
(translation) => ({
|
||||||
pre_title: translation?.pre_title,
|
pre_title: translation?.pre_title,
|
||||||
title: translation?.title,
|
title: translation?.title,
|
||||||
|
@ -207,7 +209,9 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
slug={item.attributes.slug}
|
slug={item.attributes.slug}
|
||||||
languages={languages}
|
languages={languages}
|
||||||
thumbnail={item.attributes.thumbnail?.data?.attributes}
|
thumbnail={
|
||||||
|
item.attributes.thumbnail?.data?.attributes
|
||||||
|
}
|
||||||
thumbnailAspectRatio="3/2"
|
thumbnailAspectRatio="3/2"
|
||||||
thumbnailForceAspectRatio
|
thumbnailForceAspectRatio
|
||||||
stackNumber={
|
stackNumber={
|
||||||
|
@ -221,11 +225,13 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
topChips={
|
topChips={
|
||||||
item.attributes.type?.data?.attributes
|
item.attributes.type?.data?.attributes
|
||||||
? [
|
? [
|
||||||
item.attributes.type.data.attributes.titles?.[0]
|
item.attributes.type.data.attributes
|
||||||
|
.titles?.[0]
|
||||||
? item.attributes.type.data.attributes
|
? item.attributes.type.data.attributes
|
||||||
.titles[0]?.title
|
.titles[0]?.title
|
||||||
: prettySlug(
|
: prettySlug(
|
||||||
item.attributes.type.data.attributes.slug
|
item.attributes.type.data.attributes
|
||||||
|
.slug
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: undefined
|
: undefined
|
||||||
|
@ -235,6 +241,7 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
keepInfoVisible={keepInfoVisible}
|
keepInfoVisible={keepInfoVisible}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -243,14 +250,7 @@ export default function Contents(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
</ContentPanel>
|
</ContentPanel>
|
||||||
),
|
),
|
||||||
[
|
[effectiveCombineRelatedContent, groups, keepInfoVisible, languages, langui]
|
||||||
effectiveCombineRelatedContent,
|
|
||||||
groups,
|
|
||||||
keepInfoVisible,
|
|
||||||
languages,
|
|
||||||
langui.result,
|
|
||||||
langui.results,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -287,7 +287,7 @@ export async function getStaticProps(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGroups(
|
export function getGroups(
|
||||||
langui: AppStaticProps["langui"],
|
langui: AppStaticProps["langui"],
|
||||||
groupByType: number,
|
groupByType: number,
|
||||||
items: Props["contents"]
|
items: Props["contents"]
|
||||||
|
@ -349,7 +349,7 @@ function getGroups(
|
||||||
return mapRemoveEmptyValues(groups);
|
return mapRemoveEmptyValues(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterContents(
|
export function filterContents(
|
||||||
contents: Props["contents"],
|
contents: Props["contents"],
|
||||||
combineRelatedContent: boolean,
|
combineRelatedContent: boolean,
|
||||||
searchName: string
|
searchName: string
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { Img } from "components/Img";
|
||||||
import { Button } from "components/Inputs/Button";
|
import { Button } from "components/Inputs/Button";
|
||||||
import { Switch } from "components/Inputs/Switch";
|
import { Switch } from "components/Inputs/Switch";
|
||||||
import { InsetBox } from "components/InsetBox";
|
import { InsetBox } from "components/InsetBox";
|
||||||
import { ContentLine } from "components/Library/ContentLine";
|
|
||||||
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
|
||||||
import { NavOption } from "components/PanelComponents/NavOption";
|
import { NavOption } from "components/PanelComponents/NavOption";
|
||||||
import {
|
import {
|
||||||
|
@ -31,14 +30,17 @@ import {
|
||||||
prettyItemSubType,
|
prettyItemSubType,
|
||||||
prettyItemType,
|
prettyItemType,
|
||||||
prettyPrice,
|
prettyPrice,
|
||||||
|
prettySlug,
|
||||||
prettyURL,
|
prettyURL,
|
||||||
} from "helpers/formatters";
|
} from "helpers/formatters";
|
||||||
import { getAssetURL, ImageQuality } from "helpers/img";
|
import { getAssetURL, ImageQuality } from "helpers/img";
|
||||||
import { convertMmToInch } from "helpers/numbers";
|
import { convertMmToInch } from "helpers/numbers";
|
||||||
import {
|
import {
|
||||||
|
filterDefined,
|
||||||
filterHasAttributes,
|
filterHasAttributes,
|
||||||
isDefined,
|
isDefined,
|
||||||
isDefinedAndNotEmpty,
|
isDefinedAndNotEmpty,
|
||||||
|
isUndefined,
|
||||||
sortContent,
|
sortContent,
|
||||||
} from "helpers/others";
|
} from "helpers/others";
|
||||||
|
|
||||||
|
@ -49,10 +51,14 @@ import {
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { Fragment, useMemo, useState } from "react";
|
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";
|
||||||
|
import { useToggle } from "hooks/useToggle";
|
||||||
|
import { Ico, Icon } from "components/Ico";
|
||||||
|
import { cJoin, cIf } from "helpers/className";
|
||||||
|
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
item: NonNullable<
|
item: NonNullable<
|
||||||
|
@ -66,7 +72,7 @@ interface Props extends AppStaticProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LibrarySlug(props: Props): JSX.Element {
|
export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
const { item, itemId, langui, currencies } = props;
|
const { item, itemId, langui, currencies, languages } = props;
|
||||||
const appLayout = useAppLayout();
|
const appLayout = useAppLayout();
|
||||||
const hoverable = useMediaHoverable();
|
const hoverable = useMediaHoverable();
|
||||||
const [openLightBox, LightBox] = useLightBox();
|
const [openLightBox, LightBox] = useLightBox();
|
||||||
|
@ -501,14 +507,56 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="grid w-full gap-4">
|
<div className="grid w-full gap-4">
|
||||||
{item.contents.data.map((content) => (
|
{filterHasAttributes(item.contents.data).map(
|
||||||
|
(rangedContent) => (
|
||||||
<ContentLine
|
<ContentLine
|
||||||
|
content={
|
||||||
|
rangedContent.attributes.content?.data?.attributes
|
||||||
|
? {
|
||||||
|
translations: filterDefined(
|
||||||
|
rangedContent.attributes.content.data.attributes
|
||||||
|
.translations
|
||||||
|
).map((translation) => ({
|
||||||
|
pre_title: translation.pre_title,
|
||||||
|
title: translation.title,
|
||||||
|
subtitle: translation.subtitle,
|
||||||
|
language:
|
||||||
|
translation.language?.data?.attributes?.code,
|
||||||
|
})),
|
||||||
|
categories: filterHasAttributes(
|
||||||
|
rangedContent.attributes.content.data.attributes
|
||||||
|
.categories?.data
|
||||||
|
).map((category) => category.attributes.short),
|
||||||
|
type:
|
||||||
|
rangedContent.attributes.content.data.attributes
|
||||||
|
.type?.data?.attributes?.titles?.[0]?.title ??
|
||||||
|
prettySlug(
|
||||||
|
rangedContent.attributes.content.data
|
||||||
|
.attributes.type?.data?.attributes?.slug
|
||||||
|
),
|
||||||
|
slug: rangedContent.attributes.content.data
|
||||||
|
.attributes.slug,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
langui={langui}
|
langui={langui}
|
||||||
content={content}
|
rangeStart={
|
||||||
|
rangedContent.attributes.range[0]?.__typename ===
|
||||||
|
"ComponentRangePageRange"
|
||||||
|
? `${rangedContent.attributes.range[0].starting_page}`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
slug={rangedContent.attributes.slug}
|
||||||
parentSlug={item.slug}
|
parentSlug={item.slug}
|
||||||
key={content.id}
|
key={rangedContent.id}
|
||||||
|
languages={languages}
|
||||||
|
hasScanSet={
|
||||||
|
isDefined(rangedContent.attributes.scan_set) &&
|
||||||
|
rangedContent.attributes.scan_set.length > 0
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -517,16 +565,31 @@ export default function LibrarySlug(props: Props): JSX.Element {
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
LightBox,
|
LightBox,
|
||||||
openLightBox,
|
|
||||||
appLayout.currency,
|
|
||||||
currencies,
|
|
||||||
displayOpenScans,
|
|
||||||
hoverable,
|
|
||||||
isVariantSet,
|
|
||||||
item,
|
|
||||||
itemId,
|
|
||||||
keepInfoVisible,
|
|
||||||
langui,
|
langui,
|
||||||
|
item.thumbnail?.data?.attributes,
|
||||||
|
item.subitem_of?.data,
|
||||||
|
item.title,
|
||||||
|
item.subtitle,
|
||||||
|
item.metadata,
|
||||||
|
item.descriptions,
|
||||||
|
item.urls,
|
||||||
|
item.gallery,
|
||||||
|
item.release_date,
|
||||||
|
item.price,
|
||||||
|
item.categories,
|
||||||
|
item.size,
|
||||||
|
item.subitems,
|
||||||
|
item.contents,
|
||||||
|
item.slug,
|
||||||
|
itemId,
|
||||||
|
currencies,
|
||||||
|
appLayout.currency,
|
||||||
|
isVariantSet,
|
||||||
|
hoverable,
|
||||||
|
keepInfoVisible,
|
||||||
|
displayOpenScans,
|
||||||
|
openLightBox,
|
||||||
|
languages,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -582,3 +645,112 @@ export async function getStaticPaths(
|
||||||
fallback: "blocking",
|
fallback: "blocking",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ContentLineProps {
|
||||||
|
content?: {
|
||||||
|
translations: {
|
||||||
|
pre_title: string | null | undefined;
|
||||||
|
title: string;
|
||||||
|
subtitle: string | null | undefined;
|
||||||
|
language: string | undefined;
|
||||||
|
}[];
|
||||||
|
categories?: string[];
|
||||||
|
type?: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
rangeStart: string;
|
||||||
|
parentSlug: string;
|
||||||
|
slug: string;
|
||||||
|
langui: AppStaticProps["langui"];
|
||||||
|
languages: AppStaticProps["languages"];
|
||||||
|
hasScanSet: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContentLine(props: ContentLineProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
rangeStart,
|
||||||
|
content,
|
||||||
|
langui,
|
||||||
|
languages,
|
||||||
|
hasScanSet,
|
||||||
|
slug,
|
||||||
|
parentSlug,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
const toggleOpened = useToggle(setOpened);
|
||||||
|
|
||||||
|
const [selectedTranslation] = useSmartLanguage({
|
||||||
|
items: content?.translations ?? [],
|
||||||
|
languages: languages,
|
||||||
|
languageExtractor: useCallback(
|
||||||
|
(
|
||||||
|
item: NonNullable<ContentLineProps["content"]>["translations"][number]
|
||||||
|
) => item.language,
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cJoin(
|
||||||
|
"grid gap-2 rounded-lg px-4",
|
||||||
|
cIf(opened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center
|
||||||
|
gap-4 thin:grid-cols-[auto_auto_1fr_auto]"
|
||||||
|
>
|
||||||
|
<a>
|
||||||
|
<h3 className="cursor-pointer" onClick={toggleOpened}>
|
||||||
|
{selectedTranslation
|
||||||
|
? prettyinlineTitle(
|
||||||
|
selectedTranslation.pre_title,
|
||||||
|
selectedTranslation.title,
|
||||||
|
selectedTranslation.subtitle
|
||||||
|
)
|
||||||
|
: content
|
||||||
|
? prettySlug(content.slug)
|
||||||
|
: prettySlug(slug, parentSlug)}
|
||||||
|
</h3>
|
||||||
|
</a>
|
||||||
|
<div className="flex flex-row flex-wrap gap-1">
|
||||||
|
{content?.categories?.map((category, index) => (
|
||||||
|
<Chip key={index}>{category}</Chip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
|
||||||
|
<p>{rangeStart}</p>
|
||||||
|
{content?.type && (
|
||||||
|
<Chip className="justify-self-end thin:hidden">{content.type}</Chip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`grid-flow-col place-content-start place-items-center gap-2 ${
|
||||||
|
opened ? "grid" : "hidden"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Ico icon={Icon.SubdirectoryArrowRight} className="text-dark" />
|
||||||
|
|
||||||
|
{hasScanSet || isDefined(content) ? (
|
||||||
|
<>
|
||||||
|
{hasScanSet && (
|
||||||
|
<Button
|
||||||
|
href={`/library/${parentSlug}/scans#${slug}`}
|
||||||
|
text={langui.view_scans}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isDefined(content) && (
|
||||||
|
<Button href={`/contents/${slug}`} text={langui.open_content} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"The content is not available"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
|
@ -194,36 +194,36 @@ export default function Library(props: Props): JSX.Element {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ButtonGroup className="mt-4">
|
{/* TODO: Add "All" to langui */}
|
||||||
<ToolTip content={langui.only_display_items_i_want}>
|
<ButtonGroup
|
||||||
<Button
|
className="mt-4"
|
||||||
icon={Icon.Favorite}
|
buttonsProps={[
|
||||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Want)}
|
{
|
||||||
active={filterUserStatus === LibraryItemUserStatus.Want}
|
tooltip: langui.only_display_items_i_want,
|
||||||
|
icon: Icon.Favorite,
|
||||||
|
onClick: () => setFilterUserStatus(LibraryItemUserStatus.Want),
|
||||||
|
active: filterUserStatus === LibraryItemUserStatus.Want,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tooltip: langui.only_display_items_i_have,
|
||||||
|
icon: Icon.BackHand,
|
||||||
|
onClick: () => setFilterUserStatus(LibraryItemUserStatus.Have),
|
||||||
|
active: filterUserStatus === LibraryItemUserStatus.Have,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tooltip: langui.only_display_unmarked_items,
|
||||||
|
icon: Icon.RadioButtonUnchecked,
|
||||||
|
onClick: () => setFilterUserStatus(LibraryItemUserStatus.None),
|
||||||
|
active: filterUserStatus === LibraryItemUserStatus.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tooltip: langui.only_display_unmarked_items,
|
||||||
|
text: "All",
|
||||||
|
onClick: () => setFilterUserStatus(undefined),
|
||||||
|
active: isUndefined(filterUserStatus),
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</ToolTip>
|
|
||||||
<ToolTip content={langui.only_display_items_i_have}>
|
|
||||||
<Button
|
|
||||||
icon={Icon.BackHand}
|
|
||||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.Have)}
|
|
||||||
active={filterUserStatus === LibraryItemUserStatus.Have}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
<ToolTip content={langui.only_display_unmarked_items}>
|
|
||||||
<Button
|
|
||||||
icon={Icon.RadioButtonUnchecked}
|
|
||||||
onClick={() => setFilterUserStatus(LibraryItemUserStatus.None)}
|
|
||||||
active={filterUserStatus === LibraryItemUserStatus.None}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
<ToolTip content={langui.display_all_items}>
|
|
||||||
<Button
|
|
||||||
text={"All"}
|
|
||||||
onClick={() => setFilterUserStatus(undefined)}
|
|
||||||
active={isUndefined(filterUserStatus)}
|
|
||||||
/>
|
|
||||||
</ToolTip>
|
|
||||||
</ButtonGroup>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="mt-8"
|
className="mt-8"
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
GetStaticPathsResult,
|
GetStaticPathsResult,
|
||||||
GetStaticPropsContext,
|
GetStaticPropsContext,
|
||||||
} from "next";
|
} from "next";
|
||||||
import { useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
interface Props extends AppStaticProps {
|
interface Props extends AppStaticProps {
|
||||||
page: WikiPageWithTranslations;
|
page: WikiPageWithTranslations;
|
||||||
|
@ -35,10 +35,15 @@ interface Props extends AppStaticProps {
|
||||||
export default function WikiPage(props: Props): JSX.Element {
|
export default function WikiPage(props: Props): JSX.Element {
|
||||||
const { page, langui, languages } = props;
|
const { page, langui, languages } = props;
|
||||||
|
|
||||||
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
|
const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] =
|
||||||
|
useSmartLanguage({
|
||||||
items: page.translations,
|
items: page.translations,
|
||||||
languages: languages,
|
languages: languages,
|
||||||
languageExtractor: (item) => item.language?.data?.attributes?.code,
|
languageExtractor: useCallback(
|
||||||
|
(item: NonNullable<Props["page"]["translations"][number]>) =>
|
||||||
|
item.language?.data?.attributes?.code,
|
||||||
|
[]
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const subPanel = useMemo(
|
const subPanel = useMemo(
|
||||||
|
@ -69,7 +74,7 @@ export default function WikiPage(props: Props): JSX.Element {
|
||||||
|
|
||||||
<div className="flex place-content-center gap-4">
|
<div className="flex place-content-center gap-4">
|
||||||
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher {...languageSwitcherProps} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HorizontalLine />
|
<HorizontalLine />
|
||||||
|
@ -123,6 +128,7 @@ export default function WikiPage(props: Props): JSX.Element {
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
LanguageSwitcher,
|
LanguageSwitcher,
|
||||||
|
languageSwitcherProps,
|
||||||
languages,
|
languages,
|
||||||
langui,
|
langui,
|
||||||
page.categories?.data,
|
page.categories?.data,
|
||||||
|
|
|
@ -110,9 +110,10 @@ export default function Wiki(props: Props): JSX.Element {
|
||||||
)}
|
)}
|
||||||
{filterHasAttributes(filteredPages).map((page) => (
|
{filterHasAttributes(filteredPages).map((page) => (
|
||||||
<Fragment key={page.id}>
|
<Fragment key={page.id}>
|
||||||
|
{page.attributes.translations && (
|
||||||
<TranslatedPreviewCard
|
<TranslatedPreviewCard
|
||||||
href={`/wiki/${page.attributes.slug}`}
|
href={`/wiki/${page.attributes.slug}`}
|
||||||
translations={page.attributes.translations?.map(
|
translations={page.attributes.translations.map(
|
||||||
(translation) => ({
|
(translation) => ({
|
||||||
title: translation?.title,
|
title: translation?.title,
|
||||||
description: translation?.summary,
|
description: translation?.summary,
|
||||||
|
@ -130,6 +131,7 @@ export default function Wiki(props: Props): JSX.Element {
|
||||||
(category) => category.attributes?.short ?? ""
|
(category) => category.attributes?.short ?? ""
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue