mirror of
https://github.com/zen-browser/www.git
synced 2025-07-08 09:20:00 +02:00
feat(lint): add biome formatter and linter, husky and lint-staged
This commit adds the Biome formatter and linter to replace Prettier, including: - Add biome.json config file - Add pre-commit hook with Husky - Configure GitHub Action to run Biome checks - Apply Biome formatting rules to codebase - Remove Prettier dependencies
This commit is contained in:
parent
b4e5fe2bea
commit
bcb1427a79
50 changed files with 904 additions and 793 deletions
3
.github/workflows/prbuildcheck.yml
vendored
3
.github/workflows/prbuildcheck.yml
vendored
|
@ -21,5 +21,8 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install --no-frozen-lockfile
|
run: npm install --no-frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run Biome check
|
||||||
|
run: npx biome check ./src
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged
|
25
biome.json
Normal file
25
biome.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 100
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignore": [
|
||||||
|
"node_modules",
|
||||||
|
".git",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
275
package-lock.json
generated
275
package-lock.json
generated
|
@ -28,14 +28,13 @@
|
||||||
"motion": "^11.13.5",
|
"motion": "^11.13.5",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"preact": "^10.26.2",
|
"preact": "^10.26.2",
|
||||||
"prettier": "^3.3.3",
|
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.15",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.9.4",
|
||||||
|
"husky": "^9.1.7",
|
||||||
"wrangler": "^3.94.0"
|
"wrangler": "^3.94.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -843,6 +842,170 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@biomejs/biome": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"biome": "bin/biome"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/biome"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@biomejs/cli-darwin-arm64": "1.9.4",
|
||||||
|
"@biomejs/cli-darwin-x64": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-arm64": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-x64": "1.9.4",
|
||||||
|
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
||||||
|
"@biomejs/cli-win32-arm64": "1.9.4",
|
||||||
|
"@biomejs/cli-win32-x64": "1.9.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-darwin-x64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-arm64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-x64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-win32-arm64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@biomejs/cli-win32-x64": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capsizecss/unpack": {
|
"node_modules/@capsizecss/unpack": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-2.4.0.tgz",
|
||||||
|
@ -4959,6 +5122,22 @@
|
||||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
|
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/husky": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"husky": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/typicode"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-meta-resolve": {
|
"node_modules/import-meta-resolve": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
||||||
|
@ -6830,6 +7009,8 @@
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz",
|
||||||
"integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==",
|
"integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
|
@ -6845,6 +7026,8 @@
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz",
|
||||||
"integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
|
"integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^2.9.1",
|
"@astrojs/compiler": "^2.9.1",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
@ -6854,84 +7037,6 @@
|
||||||
"node": "^14.15.0 || >=16.0.0"
|
"node": "^14.15.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-tailwindcss": {
|
|
||||||
"version": "0.6.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
|
|
||||||
"integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.21.3"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@ianvs/prettier-plugin-sort-imports": "*",
|
|
||||||
"@prettier/plugin-pug": "*",
|
|
||||||
"@shopify/prettier-plugin-liquid": "*",
|
|
||||||
"@trivago/prettier-plugin-sort-imports": "*",
|
|
||||||
"@zackad/prettier-plugin-twig": "*",
|
|
||||||
"prettier": "^3.0",
|
|
||||||
"prettier-plugin-astro": "*",
|
|
||||||
"prettier-plugin-css-order": "*",
|
|
||||||
"prettier-plugin-import-sort": "*",
|
|
||||||
"prettier-plugin-jsdoc": "*",
|
|
||||||
"prettier-plugin-marko": "*",
|
|
||||||
"prettier-plugin-multiline-arrays": "*",
|
|
||||||
"prettier-plugin-organize-attributes": "*",
|
|
||||||
"prettier-plugin-organize-imports": "*",
|
|
||||||
"prettier-plugin-sort-imports": "*",
|
|
||||||
"prettier-plugin-style-order": "*",
|
|
||||||
"prettier-plugin-svelte": "*"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@ianvs/prettier-plugin-sort-imports": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@prettier/plugin-pug": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@shopify/prettier-plugin-liquid": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@trivago/prettier-plugin-sort-imports": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@zackad/prettier-plugin-twig": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-astro": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-css-order": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-import-sort": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-jsdoc": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-marko": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-multiline-arrays": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-organize-attributes": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-organize-imports": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-sort-imports": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-style-order": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"prettier-plugin-svelte": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/printable-characters": {
|
"node_modules/printable-characters": {
|
||||||
"version": "1.0.42",
|
"version": "1.0.42",
|
||||||
"resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
|
"resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz",
|
||||||
|
@ -7448,13 +7553,17 @@
|
||||||
"version": "0.0.15",
|
"version": "0.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
|
||||||
"integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
|
"integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/sass-formatter": {
|
"node_modules/sass-formatter": {
|
||||||
"version": "0.7.9",
|
"version": "0.7.9",
|
||||||
"resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
|
||||||
"integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
|
"integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"suf-log": "^2.5.3"
|
"suf-log": "^2.5.3"
|
||||||
}
|
}
|
||||||
|
@ -7855,6 +7964,8 @@
|
||||||
"resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
|
||||||
"integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
|
"integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"s.color": "0.0.15"
|
"s.color": "0.0.15"
|
||||||
}
|
}
|
||||||
|
|
16
package.json
16
package.json
|
@ -8,7 +8,10 @@
|
||||||
"build": "astro check && astro build",
|
"build": "astro check && astro build",
|
||||||
"preview": "astro preview --port 3000",
|
"preview": "astro preview --port 3000",
|
||||||
"wrangler": "wrangler",
|
"wrangler": "wrangler",
|
||||||
"astro": "astro"
|
"astro": "astro",
|
||||||
|
"lint": "biome lint ./src",
|
||||||
|
"format": "biome format ./src --write",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
|
@ -31,14 +34,19 @@
|
||||||
"motion": "^11.13.5",
|
"motion": "^11.13.5",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"preact": "^10.26.2",
|
"preact": "^10.26.2",
|
||||||
"prettier": "^3.3.3",
|
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.15",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.9.4",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"lint-staged": "^15.2.7",
|
||||||
"wrangler": "^3.94.0"
|
"wrangler": "^3.94.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"src/**/*.{ts,tsx,astro,js,jsx}": [
|
||||||
|
"biome lint --apply"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
export function getTitleAnimation(delay = 0, duration = 0.3, once = true) {
|
export function getTitleAnimation(delay = 0, duration = 0.3, once = true) {
|
||||||
return {
|
return {
|
||||||
initial: { opacity: 0.001, translateY: 20, filter: 'blur(4px)' },
|
initial: { opacity: 0.001, translateY: 20, filter: "blur(4px)" },
|
||||||
whileInView: {
|
whileInView: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
translateY: 0,
|
translateY: 0,
|
||||||
filter: 'blur(0px)',
|
filter: "blur(0px)",
|
||||||
transition: { duration, delay },
|
transition: { duration, delay },
|
||||||
},
|
},
|
||||||
viewport: { once: once },
|
viewport: { once: once },
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getZoomInAnimation(delay = 0) {
|
export function getZoomInAnimation(delay = 0) {
|
||||||
return {
|
return {
|
||||||
initial: { scale: 0.8, opacity: 0.001 },
|
initial: { scale: 0.8, opacity: 0.001 },
|
||||||
whileInView: { scale: 1, opacity: 1, transition: { duration: 0.2, delay } },
|
whileInView: { scale: 1, opacity: 1, transition: { duration: 0.2, delay } },
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
---
|
---
|
||||||
import { ArrowLeft } from 'lucide-astro'
|
import { ArrowLeft } from "lucide-astro";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
mods: { slug },
|
mods: { slug },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
---
|
---
|
||||||
import { getLocale, getPath } from '~/utils/i18n'
|
import { getLocale, getPath } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale);
|
||||||
const {
|
const { class: className, isPrimary, isAlert, isBordered, href, id, extra } = Astro.props;
|
||||||
class: className,
|
|
||||||
isPrimary,
|
|
||||||
isAlert,
|
|
||||||
isBordered,
|
|
||||||
href,
|
|
||||||
id,
|
|
||||||
extra,
|
|
||||||
} = Astro.props
|
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
const { white, multiplier = 0.9, class: classList } = Astro.props
|
const { white, multiplier = 0.9, class: classList } = Astro.props;
|
||||||
const sizes = [216, 396, 576, 756]
|
const sizes = [216, 396, 576, 756];
|
||||||
const borderWidths = [20, 30, 40, 50]
|
const borderWidths = [20, 30, 40, 50];
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
---
|
---
|
||||||
import Image from 'astro/components/Image.astro'
|
import Image from "astro/components/Image.astro";
|
||||||
import { Check, Github } from 'lucide-astro'
|
import { Check, Github } from "lucide-astro";
|
||||||
import { motion } from 'motion/react'
|
import { motion } from "motion/react";
|
||||||
import { getTitleAnimation } from '~/animations'
|
import { getTitleAnimation } from "~/animations";
|
||||||
import ComImage from '~/assets/ComImage.png'
|
import ComImage from "~/assets/ComImage.png";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { community },
|
index: { community },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
|
|
@ -1,33 +1,31 @@
|
||||||
---
|
---
|
||||||
import { motion } from 'motion/react'
|
import { motion } from "motion/react";
|
||||||
import { getTitleAnimation } from '~/animations'
|
import { getTitleAnimation } from "~/animations";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
|
|
||||||
import CompactModeVideo from '~/assets/CompactMode.webm'
|
import CompactModeVideo from "~/assets/CompactMode.webm";
|
||||||
import GlanceVideo from '~/assets/Glance.webm'
|
import GlanceVideo from "~/assets/Glance.webm";
|
||||||
import SplitViewsVideo from '~/assets/SplitViews.webm'
|
import SplitViewsVideo from "~/assets/SplitViews.webm";
|
||||||
import WorkspacesVideo from '~/assets/Workspaces.webm'
|
import WorkspacesVideo from "~/assets/Workspaces.webm";
|
||||||
|
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
import Video from './Video.astro'
|
import Video from "./Video.astro";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { features },
|
index: { features },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title1 = features.title1,
|
title1 = features.title1,
|
||||||
title2 = features.title2,
|
title2 = features.title2,
|
||||||
title3 = features.title3,
|
title3 = features.title3,
|
||||||
} = Astro.props
|
} = Astro.props;
|
||||||
|
|
||||||
const descriptions = Object.values(features.featureTabs).map(
|
const descriptions = Object.values(features.featureTabs).map((tab) => tab.description);
|
||||||
(tab) => tab.description,
|
|
||||||
)
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from 'lucide-astro'
|
import { ArrowRight } from "lucide-astro";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Circles from '~/components/Circles.astro'
|
import Circles from "~/components/Circles.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
import SocialMediaStrip from "~/components/SocialMediaStrip.astro";
|
||||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale);
|
||||||
const {
|
const {
|
||||||
components: { footer },
|
components: { footer },
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer
|
<footer
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from 'lucide-astro'
|
import { ArrowRight } from "lucide-astro";
|
||||||
import { motion } from 'motion/react'
|
import { motion } from "motion/react";
|
||||||
import { getTitleAnimation } from '~/animations'
|
import { getTitleAnimation } from "~/animations";
|
||||||
import HomePageVideo from '~/assets/HomePageVideo.webm'
|
import HomePageVideo from "~/assets/HomePageVideo.webm";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import Title from '~/components/Title.astro'
|
import Title from "~/components/Title.astro";
|
||||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||||
import SocialMediaStrip from './SocialMediaStrip.astro'
|
import SocialMediaStrip from "./SocialMediaStrip.astro";
|
||||||
import Video from './Video.astro'
|
import Video from "./Video.astro";
|
||||||
|
|
||||||
let titleAnimationCounter = 0
|
let titleAnimationCounter = 0;
|
||||||
function getNewAnimationDelay() {
|
function getNewAnimationDelay() {
|
||||||
titleAnimationCounter++
|
titleAnimationCounter++;
|
||||||
return titleAnimationCounter * 0.15
|
return titleAnimationCounter * 0.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHeroTitleAnimation() {
|
function getHeroTitleAnimation() {
|
||||||
return getTitleAnimation(getNewAnimationDelay())
|
return getTitleAnimation(getNewAnimationDelay());
|
||||||
}
|
}
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { hero },
|
index: { hero },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<header
|
<header
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
const { class: className } = Astro.props
|
const { class: className } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale);
|
||||||
const {
|
const {
|
||||||
components: {
|
components: {
|
||||||
nav: { menu },
|
nav: { menu },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Hidden checkbox for menu toggle -->
|
<!-- Hidden checkbox for menu toggle -->
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
import { icon, library } from "@fortawesome/fontawesome-svg-core";
|
||||||
import { faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'
|
import { faSort, faSortDown, faSortUp } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useEffect, useState } from 'preact/hooks'
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { useModsSearch } from '~/hooks/useModsSearch'
|
import { useModsSearch } from "~/hooks/useModsSearch";
|
||||||
import type { ZenTheme } from '~/mods'
|
import type { ZenTheme } from "~/mods";
|
||||||
import { type Locale, getUI } from '~/utils/i18n'
|
import { type Locale, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
// Add icons to the library
|
// Add icons to the library
|
||||||
library.add(faSort, faSortUp, faSortDown)
|
library.add(faSort, faSortUp, faSortDown);
|
||||||
|
|
||||||
// Create icon objects
|
// Create icon objects
|
||||||
const defaultSortIcon = icon({ prefix: 'fas', iconName: 'sort' })
|
const defaultSortIcon = icon({ prefix: "fas", iconName: "sort" });
|
||||||
const ascSortIcon = icon({ prefix: 'fas', iconName: 'sort-up' })
|
const ascSortIcon = icon({ prefix: "fas", iconName: "sort-up" });
|
||||||
const descSortIcon = icon({ prefix: 'fas', iconName: 'sort-down' })
|
const descSortIcon = icon({ prefix: "fas", iconName: "sort-down" });
|
||||||
|
|
||||||
interface ModsListProps {
|
interface ModsListProps {
|
||||||
allMods: ZenTheme[]
|
allMods: ZenTheme[];
|
||||||
locale: Locale
|
locale: Locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ModsList({ allMods, locale }: ModsListProps) {
|
export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
|
@ -34,73 +34,71 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
setLimit,
|
setLimit,
|
||||||
mods: paginatedMods,
|
mods: paginatedMods,
|
||||||
// searchParams,
|
// searchParams,
|
||||||
} = useModsSearch(allMods)
|
} = useModsSearch(allMods);
|
||||||
|
|
||||||
const [pageInput, setPageInput] = useState(page.toString())
|
const [pageInput, setPageInput] = useState(page.toString());
|
||||||
|
|
||||||
// Keep page input in sync with actual page
|
// Keep page input in sync with actual page
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPageInput(page.toString())
|
setPageInput(page.toString());
|
||||||
}, [page])
|
}, [page]);
|
||||||
|
|
||||||
function getSortIcon(state: 'default' | 'asc' | 'desc') {
|
function getSortIcon(state: "default" | "asc" | "desc") {
|
||||||
if (state === 'asc') return ascSortIcon
|
if (state === "asc") return ascSortIcon;
|
||||||
if (state === 'desc') return descSortIcon
|
if (state === "desc") return descSortIcon;
|
||||||
return defaultSortIcon
|
return defaultSortIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSearch(e: Event) {
|
function handleSearch(e: Event) {
|
||||||
const target = e.target as HTMLInputElement
|
const target = e.target as HTMLInputElement;
|
||||||
setSearch(target.value)
|
setSearch(target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLimitChange(e: Event) {
|
function handleLimitChange(e: Event) {
|
||||||
const target = e.target as HTMLSelectElement
|
const target = e.target as HTMLSelectElement;
|
||||||
setLimit(Number.parseInt(target.value, 10))
|
setLimit(Number.parseInt(target.value, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePageSubmit(e: Event) {
|
function handlePageSubmit(e: Event) {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
const newPage = Number.parseInt(pageInput, 10)
|
const newPage = Number.parseInt(pageInput, 10);
|
||||||
if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) {
|
if (!Number.isNaN(newPage) && newPage >= 1 && newPage <= totalPages) {
|
||||||
setPage(newPage)
|
setPage(newPage);
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0);
|
||||||
} else {
|
} else {
|
||||||
setPageInput(page.toString())
|
setPageInput(page.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePageInputChange(e: Event) {
|
function handlePageInputChange(e: Event) {
|
||||||
const target = e.target as HTMLInputElement
|
const target = e.target as HTMLInputElement;
|
||||||
setPageInput(target.value)
|
setPageInput(target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigatePage(pageNum: number) {
|
function navigatePage(pageNum: number) {
|
||||||
setPage(pageNum)
|
setPage(pageNum);
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { mods },
|
routes: { mods },
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
|
|
||||||
function renderPagination() {
|
function renderPagination() {
|
||||||
if (totalPages <= 1) return null
|
if (totalPages <= 1) return null;
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
<div className="mx-auto mb-12 flex items-center justify-center gap-4 px-8">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigatePage(page - 1)}
|
onClick={() => navigatePage(page - 1)}
|
||||||
className={`px-3 py-2 ${
|
className={`px-3 py-2 ${
|
||||||
page === 1
|
page === 1 ? "pointer-events-none text-gray-400" : "text-dark hover:text-gray-600"
|
||||||
? 'pointer-events-none text-gray-400'
|
|
||||||
: 'text-dark hover:text-gray-600'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<
|
<
|
||||||
</button>
|
</button>
|
||||||
<form onSubmit={handlePageSubmit} className="flex items-center gap-2">
|
<form onSubmit={handlePageSubmit} className="flex items-center gap-2">
|
||||||
{mods.pagination.pagination.split('{input}').map((value, index) => {
|
{mods.pagination.pagination.split("{input}").map((value, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
|
@ -110,15 +108,15 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm"
|
className="w-16 rounded border border-dark bg-transparent px-2 py-1 text-center text-sm"
|
||||||
aria-label="Page number"
|
aria-label="Page number"
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span className="text-sm">
|
<span key={value} className="text-sm">
|
||||||
{value
|
{value
|
||||||
.replace('{totalPages}', totalPages.toString())
|
.replace("{totalPages}", totalPages.toString())
|
||||||
.replace('{totalItems}', totalItems.toString())}
|
.replace("{totalItems}", totalItems.toString())}
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</form>
|
</form>
|
||||||
<button
|
<button
|
||||||
|
@ -126,14 +124,14 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
onClick={() => navigatePage(page + 1)}
|
onClick={() => navigatePage(page + 1)}
|
||||||
className={`px-3 py-2 ${
|
className={`px-3 py-2 ${
|
||||||
page === totalPages
|
page === totalPages
|
||||||
? 'pointer-events-none text-gray-400'
|
? "pointer-events-none text-gray-400"
|
||||||
: 'text-dark hover:text-gray-600'
|
: "text-dark hover:text-gray-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -159,6 +157,7 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
>
|
>
|
||||||
{mods.sort.lastCreated}
|
{mods.sort.lastCreated}
|
||||||
<span
|
<span
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: Icons are safe
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: getSortIcon(createdSort).html[0],
|
__html: getSortIcon(createdSort).html[0],
|
||||||
}}
|
}}
|
||||||
|
@ -174,6 +173,7 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
>
|
>
|
||||||
{mods.sort.lastUpdated}
|
{mods.sort.lastUpdated}
|
||||||
<span
|
<span
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: Icons are safe
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: getSortIcon(updatedSort).html[0],
|
__html: getSortIcon(updatedSort).html[0],
|
||||||
}}
|
}}
|
||||||
|
@ -218,10 +218,7 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold">
|
<h2 className="text-lg font-bold">
|
||||||
{mod.name}{' '}
|
{mod.name} <span className="ml-1 text-sm font-normal">by @{mod.author}</span>
|
||||||
<span className="ml-1 text-sm font-normal">
|
|
||||||
by @{mod.author}
|
|
||||||
</span>
|
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm font-thin">{mod.description}</p>
|
<p className="text-sm font-thin">{mod.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -237,5 +234,5 @@ export default function ModsList({ allMods, locale }: ModsListProps) {
|
||||||
|
|
||||||
{renderPagination()}
|
{renderPagination()}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
---
|
---
|
||||||
import { Astronav, Dropdown, DropdownItems, MenuItems } from 'astro-navbar'
|
import { Astronav, Dropdown, DropdownItems, MenuItems } from "astro-navbar";
|
||||||
import { ArrowRight, ChevronDown, Download, Menu } from 'lucide-astro'
|
import { ArrowRight, ChevronDown, Download, Menu } from "lucide-astro";
|
||||||
import { motion } from 'motion/react'
|
import { motion } from "motion/react";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||||
import { getTitleAnimation } from '../animations.ts'
|
import { getTitleAnimation } from "../animations.ts";
|
||||||
import Logo from './Logo.astro'
|
import Logo from "./Logo.astro";
|
||||||
import MobileMenu from './MobileMenu.astro'
|
import MobileMenu from "./MobileMenu.astro";
|
||||||
import ThemeSwitch from './ThemeSwitch.astro'
|
import ThemeSwitch from "./ThemeSwitch.astro";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale);
|
||||||
const {
|
const {
|
||||||
components: {
|
components: {
|
||||||
nav: { brand, menu },
|
nav: { brand, menu },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
---
|
---
|
||||||
import { Accordion, AccordionItem } from 'free-astro-components'
|
import { Accordion, AccordionItem } from "free-astro-components";
|
||||||
import { Info } from 'lucide-astro'
|
import { Info } from "lucide-astro";
|
||||||
|
|
||||||
import { releaseNotes as releaseNotesData } from '~/release-notes'
|
import { releaseNotes as releaseNotesData } from "~/release-notes";
|
||||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||||
import {
|
import {
|
||||||
type BreakingChange,
|
type BreakingChange,
|
||||||
type ReleaseNote,
|
type ReleaseNote,
|
||||||
getReleaseNoteFirefoxVersion,
|
getReleaseNoteFirefoxVersion,
|
||||||
} from '../release-notes'
|
} from "../release-notes";
|
||||||
export type Props = ReleaseNote
|
export type Props = ReleaseNote;
|
||||||
const { isTwilight, ...props } = Astro.props
|
const { isTwilight, ...props } = Astro.props;
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale);
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
releaseNotes: {
|
releaseNotes: {
|
||||||
components: { releaseNoteItem },
|
components: { releaseNoteItem },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
|
|
||||||
let date
|
let date: Date | undefined;
|
||||||
if (props.date) {
|
if (props.date) {
|
||||||
const [day, month, year] = props.date.split('/')
|
const [day, month, year] = props.date.split("/");
|
||||||
date = new Date(Date.parse(`${year}-${month}-${day}`))
|
date = new Date(Date.parse(`${year}-${month}-${day}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const ffVersion = getReleaseNoteFirefoxVersion(props)
|
const ffVersion = getReleaseNoteFirefoxVersion(props);
|
||||||
const currentReleaseIndex = releaseNotesData.findIndex(
|
const currentReleaseIndex = releaseNotesData.findIndex(
|
||||||
(releaseNote: ReleaseNote) => releaseNote.version === props.version,
|
(releaseNote: ReleaseNote) => releaseNote.version === props.version,
|
||||||
)
|
);
|
||||||
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1]
|
const prevReleaseNote = releaseNotesData[currentReleaseIndex + 1];
|
||||||
let compareLink = ''
|
let compareLink = "";
|
||||||
if (prevReleaseNote && !isTwilight) {
|
if (prevReleaseNote && !isTwilight) {
|
||||||
compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`
|
compareLink = `https://github.com/zen-browser/desktop/compare/${prevReleaseNote.version}...${props.version}`;
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
---
|
---
|
||||||
const { gap = 4 } = Astro.props
|
const { gap = 4 } = Astro.props;
|
||||||
|
|
||||||
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
import { icon, library } from "@fortawesome/fontawesome-svg-core";
|
||||||
import {
|
import {
|
||||||
faBluesky,
|
faBluesky,
|
||||||
faGithub,
|
faGithub,
|
||||||
faMastodon,
|
faMastodon,
|
||||||
faReddit,
|
faReddit,
|
||||||
faXTwitter,
|
faXTwitter,
|
||||||
} from '@fortawesome/free-brands-svg-icons'
|
} from "@fortawesome/free-brands-svg-icons";
|
||||||
|
|
||||||
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
|
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit);
|
||||||
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
|
const Mastodon = icon({ prefix: "fab", iconName: "mastodon" });
|
||||||
const Bluesky = icon({ prefix: 'fab', iconName: 'bluesky' })
|
const Bluesky = icon({ prefix: "fab", iconName: "bluesky" });
|
||||||
const Github = icon({ prefix: 'fab', iconName: 'github' })
|
const Github = icon({ prefix: "fab", iconName: "github" });
|
||||||
const XTwitter = icon({ prefix: 'fab', iconName: 'x-twitter' })
|
const XTwitter = icon({ prefix: "fab", iconName: "x-twitter" });
|
||||||
const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
|
const Reddit = icon({ prefix: "fab", iconName: "reddit" });
|
||||||
---
|
---
|
||||||
|
|
||||||
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
---
|
---
|
||||||
import { motion } from 'motion/react'
|
import { motion } from "motion/react";
|
||||||
import { getTitleAnimation } from '~/animations'
|
import { getTitleAnimation } from "~/animations";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
import tutaLogo from '~/assets/tuta-logo.png'
|
import tutaLogo from "~/assets/tuta-logo.png";
|
||||||
|
|
||||||
import Image from 'astro/components/Image.astro'
|
import Image from "astro/components/Image.astro";
|
||||||
const { showSponsors = true } = Astro.props
|
const { showSponsors = true } = Astro.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
index: { sponsors },
|
index: { sponsors },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="sponsors" class:list={['mb-32 px-4', !showSponsors && 'hidden']}>
|
<section id="sponsors" class:list={['mb-32 px-4', !showSponsors && 'hidden']}>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
interface Props {
|
interface Props {
|
||||||
label?: string
|
label?: string;
|
||||||
className?: string
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { label, className = '' } = Astro.props
|
const { label, className = "" } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
const { class: className } = Astro.props
|
const { class: className } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1 class:list={['title text-dark', className]}>
|
<h1 class:list={['title text-dark', className]}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
const { src, class: className, ...rest } = Astro.props
|
const { src, class: className, ...rest } = Astro.props;
|
||||||
const type = src.split('.').pop() || 'webm'
|
const type = src.split(".").pop() || "webm";
|
||||||
---
|
---
|
||||||
|
|
||||||
<video
|
<video
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
---
|
---
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string;
|
||||||
href: string
|
href: string;
|
||||||
variant?: string
|
variant?: string;
|
||||||
checksum?: string
|
checksum?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { label, href, checksum } = Astro.props
|
const { label, href, checksum } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="relative flex flex-col">
|
<div class="relative flex flex-col">
|
||||||
|
|
|
@ -1,17 +1,32 @@
|
||||||
---
|
---
|
||||||
interface Props {
|
interface ReleaseInfo {
|
||||||
platform: 'mac' | 'windows' | 'linux'
|
label?: string;
|
||||||
icon: string[]
|
link: string;
|
||||||
title: string
|
checksum?: string;
|
||||||
description: string
|
|
||||||
releases: Record<string, any>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { platform, icon, title, description, releases } = Astro.props
|
interface PlatformReleases {
|
||||||
import { Image } from 'astro:assets'
|
universal?: ReleaseInfo;
|
||||||
import AppIconDark from '../../assets/app-icon-dark.png'
|
all?: ReleaseInfo;
|
||||||
import AppIconLight from '../../assets/app-icon-light.png'
|
tarball?: ReleaseInfo;
|
||||||
import DownloadCard from './ButtonCard.astro'
|
x86_64?: { tarball: ReleaseInfo } | ReleaseInfo;
|
||||||
|
arm64?: ReleaseInfo;
|
||||||
|
flathub?: { all: ReleaseInfo };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
platform: "mac" | "windows" | "linux";
|
||||||
|
icon: string[];
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
releases: PlatformReleases;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { platform, icon, title, description, releases } = Astro.props;
|
||||||
|
import { Image } from "astro:assets";
|
||||||
|
import AppIconDark from "../../assets/app-icon-dark.png";
|
||||||
|
import AppIconLight from "../../assets/app-icon-light.png";
|
||||||
|
import DownloadCard from "./ButtonCard.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -32,7 +47,7 @@ import DownloadCard from './ButtonCard.astro'
|
||||||
{
|
{
|
||||||
platform === 'linux' ? (
|
platform === 'linux' ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
{releases.flathub && releases.flathub.all.label && <div>
|
||||||
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
|
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
|
@ -41,8 +56,8 @@ import DownloadCard from './ButtonCard.astro'
|
||||||
variant="flathub"
|
variant="flathub"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>}
|
||||||
<div>
|
{releases.x86_64 && 'tarball' in releases.x86_64 && <div>
|
||||||
<h4 class="mb-3 text-lg font-medium">Tarball</h4>
|
<h4 class="mb-3 text-lg font-medium">Tarball</h4>
|
||||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
|
@ -53,23 +68,38 @@ import DownloadCard from './ButtonCard.astro'
|
||||||
/>
|
/>
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
label="ARM64"
|
label="ARM64"
|
||||||
href={releases.aarch64.tarball.link}
|
href={releases.x86_64.tarball.link}
|
||||||
variant="aarch64"
|
variant="aarch64"
|
||||||
checksum={releases.aarch64.tarball.checksum}
|
checksum={releases.x86_64.tarball.checksum}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{Object.entries(releases).map(([variant, releaseNote]) => (
|
<div class="space-y-3">
|
||||||
|
{releases.universal && releases.universal.label && (
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
label={releaseNote.label}
|
label={releases.universal.label}
|
||||||
href={releaseNote.link}
|
href={releases.universal.link}
|
||||||
variant={variant}
|
checksum={releases.universal.checksum}
|
||||||
checksum={releaseNote.checksum}
|
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
|
{releases.x86_64 && 'tarball' in releases.x86_64 && releases.x86_64.tarball && releases.x86_64.tarball.label && (
|
||||||
|
<DownloadCard
|
||||||
|
label={releases.x86_64.tarball.label}
|
||||||
|
href={releases.x86_64.tarball.link}
|
||||||
|
checksum={releases.x86_64.tarball.checksum}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{releases.arm64 && releases.arm64.label && (
|
||||||
|
<DownloadCard
|
||||||
|
label={releases.arm64.label}
|
||||||
|
href={releases.arm64.link}
|
||||||
|
checksum={releases.arm64.checksum}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,55 +7,55 @@ export function getReleasesWithChecksums(checksums: Record<string, string>) {
|
||||||
return {
|
return {
|
||||||
macos: {
|
macos: {
|
||||||
universal: {
|
universal: {
|
||||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg',
|
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.macos-universal.dmg",
|
||||||
label: 'Universal',
|
label: "Universal",
|
||||||
checksum: checksums['zen.macos-universal.dmg'],
|
checksum: checksums["zen.macos-universal.dmg"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
windows: {
|
windows: {
|
||||||
x86_64: {
|
x86_64: {
|
||||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe',
|
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer.exe",
|
||||||
label: '64-bit (Recommended)',
|
label: "64-bit (Recommended)",
|
||||||
checksum: checksums['zen.installer.exe'],
|
checksum: checksums["zen.installer.exe"],
|
||||||
},
|
},
|
||||||
arm64: {
|
arm64: {
|
||||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe',
|
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.installer-arm64.exe",
|
||||||
label: 'ARM64',
|
label: "ARM64",
|
||||||
checksum: checksums['zen.installer-arm64.exe'],
|
checksum: checksums["zen.installer-arm64.exe"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
linux: {
|
linux: {
|
||||||
x86_64: {
|
x86_64: {
|
||||||
tarball: {
|
tarball: {
|
||||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz',
|
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz",
|
||||||
label: 'Tarball x86_64',
|
label: "Tarball x86_64",
|
||||||
checksum: checksums['zen.linux-x86_64.tar.xz'],
|
checksum: checksums["zen.linux-x86_64.tar.xz"],
|
||||||
},
|
},
|
||||||
appImage: {
|
appImage: {
|
||||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-x86_64.AppImage',
|
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen-x86_64.AppImage",
|
||||||
label: 'AppImage x86_64',
|
label: "AppImage x86_64",
|
||||||
checksum: checksums['zen-x86_64.AppImage'],
|
checksum: checksums["zen-x86_64.AppImage"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aarch64: {
|
aarch64: {
|
||||||
tarball: {
|
tarball: {
|
||||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz',
|
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-aarch64.tar.xz",
|
||||||
label: 'Tarball aarch64',
|
label: "Tarball aarch64",
|
||||||
checksum: checksums['zen.linux-aarch64.tar.xz'],
|
checksum: checksums["zen.linux-aarch64.tar.xz"],
|
||||||
},
|
},
|
||||||
appImage: {
|
appImage: {
|
||||||
link: 'https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage',
|
link: "https://github.com/zen-browser/desktop/releases/latest/download/zen-aarch64.AppImage",
|
||||||
label: 'AppImage aarch64',
|
label: "AppImage aarch64",
|
||||||
checksum: checksums['zen-aarch64.AppImage'],
|
checksum: checksums["zen-aarch64.AppImage"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
flathub: {
|
flathub: {
|
||||||
all: {
|
all: {
|
||||||
link: 'https://flathub.org/apps/app.zen_browser.zen',
|
link: "https://flathub.org/apps/app.zen_browser.zen",
|
||||||
label: 'Flathub',
|
label: "Flathub",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const I18N = {
|
export const I18N = {
|
||||||
DEFAULT_LOCALE: 'en',
|
DEFAULT_LOCALE: "en",
|
||||||
LOCALES: [{ label: 'English', value: 'en' }],
|
LOCALES: [{ label: "English", value: "en" }],
|
||||||
} as const
|
} as const;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { I18N } from './i18n'
|
import { I18N } from "./i18n";
|
||||||
|
|
||||||
export const CONSTANT = {
|
export const CONSTANT = {
|
||||||
I18N,
|
I18N,
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,169 +1,158 @@
|
||||||
import { useEffect, useState } from 'preact/hooks'
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import type { ZenTheme } from '../mods'
|
import type { ZenTheme } from "../mods";
|
||||||
|
|
||||||
type SortOrder = 'default' | 'asc' | 'desc'
|
type SortOrder = "default" | "asc" | "desc";
|
||||||
|
|
||||||
interface ModsSearchState {
|
interface ModsSearchState {
|
||||||
search: string
|
search: string;
|
||||||
createdSort: SortOrder
|
createdSort: SortOrder;
|
||||||
updatedSort: SortOrder
|
updatedSort: SortOrder;
|
||||||
page: number
|
page: number;
|
||||||
limit: number
|
limit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 12
|
const DEFAULT_LIMIT = 12;
|
||||||
|
|
||||||
export function useModsSearch(mods: ZenTheme[]) {
|
export function useModsSearch(mods: ZenTheme[]) {
|
||||||
const [searchParams, setSearchParams] = useState<URLSearchParams>()
|
const [searchParams, setSearchParams] = useState<URLSearchParams>();
|
||||||
const [state, setState] = useState<ModsSearchState>({
|
const [state, setState] = useState<ModsSearchState>({
|
||||||
search: '',
|
search: "",
|
||||||
createdSort: 'desc',
|
createdSort: "desc",
|
||||||
updatedSort: 'default',
|
updatedSort: "default",
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: DEFAULT_LIMIT,
|
limit: DEFAULT_LIMIT,
|
||||||
})
|
});
|
||||||
|
|
||||||
// Initialize search params
|
// Initialize search params
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search);
|
||||||
setSearchParams(params)
|
setSearchParams(params);
|
||||||
setState({
|
setState({
|
||||||
search: params.get('q') || '',
|
search: params.get("q") || "",
|
||||||
createdSort: (params.get('created') as SortOrder) || 'desc',
|
createdSort: (params.get("created") as SortOrder) || "desc",
|
||||||
updatedSort: (params.get('updated') as SortOrder) || 'default',
|
updatedSort: (params.get("updated") as SortOrder) || "default",
|
||||||
page: Number.parseInt(params.get('page') || '1', 10),
|
page: Number.parseInt(params.get("page") || "1", 10),
|
||||||
limit: Number.parseInt(params.get('limit') || String(DEFAULT_LIMIT), 10),
|
limit: Number.parseInt(params.get("limit") || String(DEFAULT_LIMIT), 10),
|
||||||
})
|
});
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
// Update URL when state changes
|
// Update URL when state changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchParams) return
|
if (!searchParams) return;
|
||||||
|
|
||||||
if (state.search) {
|
if (state.search) {
|
||||||
searchParams.set('q', state.search)
|
searchParams.set("q", state.search);
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete('q')
|
searchParams.delete("q");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.createdSort !== 'default') {
|
if (state.createdSort !== "default") {
|
||||||
searchParams.set('created', state.createdSort)
|
searchParams.set("created", state.createdSort);
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete('created')
|
searchParams.delete("created");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.updatedSort !== 'default') {
|
if (state.updatedSort !== "default") {
|
||||||
searchParams.set('updated', state.updatedSort)
|
searchParams.set("updated", state.updatedSort);
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete('updated')
|
searchParams.delete("updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.page > 1) {
|
if (state.page > 1) {
|
||||||
searchParams.set('page', state.page.toString())
|
searchParams.set("page", state.page.toString());
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete('page')
|
searchParams.delete("page");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.limit !== DEFAULT_LIMIT) {
|
if (state.limit !== DEFAULT_LIMIT) {
|
||||||
searchParams.set('limit', state.limit.toString())
|
searchParams.set("limit", state.limit.toString());
|
||||||
} else {
|
} else {
|
||||||
searchParams.delete('limit')
|
searchParams.delete("limit");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUrl = `${window.location.pathname}${
|
const newUrl = `${window.location.pathname}${
|
||||||
searchParams.toString() ? `?${searchParams.toString()}` : ''
|
searchParams.toString() ? `?${searchParams.toString()}` : ""
|
||||||
}`
|
}`;
|
||||||
|
|
||||||
if (state.page > 1) {
|
if (state.page > 1) {
|
||||||
window.history.pushState({}, '', newUrl)
|
window.history.pushState({}, "", newUrl);
|
||||||
} else {
|
} else {
|
||||||
window.history.replaceState({}, '', newUrl)
|
window.history.replaceState({}, "", newUrl);
|
||||||
}
|
}
|
||||||
}, [state, searchParams])
|
}, [state, searchParams]);
|
||||||
|
|
||||||
const filteredMods = (() => {
|
const filteredMods = (() => {
|
||||||
let filtered = [...mods]
|
let filtered = [...mods];
|
||||||
|
|
||||||
// Filter by search
|
// Filter by search
|
||||||
const searchTerm = state.search.toLowerCase()
|
const searchTerm = state.search.toLowerCase();
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
filtered = filtered.filter(
|
filtered = filtered.filter(
|
||||||
(mod) =>
|
(mod) =>
|
||||||
mod.name.toLowerCase().includes(searchTerm) ||
|
mod.name.toLowerCase().includes(searchTerm) ||
|
||||||
mod.description.toLowerCase().includes(searchTerm) ||
|
mod.description.toLowerCase().includes(searchTerm) ||
|
||||||
mod.author.toLowerCase().includes(searchTerm) ||
|
mod.author.toLowerCase().includes(searchTerm) ||
|
||||||
(mod.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ??
|
(mod.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ?? false),
|
||||||
false),
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by createdAt if chosen
|
// Sort by createdAt if chosen
|
||||||
if (state.createdSort !== 'default') {
|
if (state.createdSort !== "default") {
|
||||||
filtered.sort((a, b) => {
|
filtered.sort((a, b) => {
|
||||||
const diff =
|
const diff = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||||
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
return state.createdSort === "asc" ? diff : -diff;
|
||||||
return state.createdSort === 'asc' ? diff : -diff
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by updatedAt if chosen
|
// Sort by updatedAt if chosen
|
||||||
if (state.updatedSort !== 'default') {
|
if (state.updatedSort !== "default") {
|
||||||
filtered.sort((a, b) => {
|
filtered.sort((a, b) => {
|
||||||
const diff =
|
const diff = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
|
||||||
new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
|
return state.updatedSort === "asc" ? diff : -diff;
|
||||||
return state.updatedSort === 'asc' ? diff : -diff
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered
|
return filtered;
|
||||||
})()
|
})();
|
||||||
|
|
||||||
// Calculate pagination
|
// Calculate pagination
|
||||||
const totalPages = Math.ceil(filteredMods.length / state.limit)
|
const totalPages = Math.ceil(filteredMods.length / state.limit);
|
||||||
const startIndex = (state.page - 1) * state.limit
|
const startIndex = (state.page - 1) * state.limit;
|
||||||
const endIndex = startIndex + state.limit
|
const endIndex = startIndex + state.limit;
|
||||||
const paginatedMods = filteredMods.slice(startIndex, endIndex)
|
const paginatedMods = filteredMods.slice(startIndex, endIndex);
|
||||||
|
|
||||||
const setSearch = (search: string) => {
|
const setSearch = (search: string) => {
|
||||||
setState((prev) => ({ ...prev, search, page: 1 })) // Reset page when search changes
|
setState((prev) => ({ ...prev, search, page: 1 })); // Reset page when search changes
|
||||||
}
|
};
|
||||||
|
|
||||||
const toggleCreatedSort = () => {
|
const toggleCreatedSort = () => {
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
createdSort:
|
createdSort:
|
||||||
prev.createdSort === 'default'
|
prev.createdSort === "default" ? "asc" : prev.createdSort === "asc" ? "desc" : "default",
|
||||||
? 'asc'
|
|
||||||
: prev.createdSort === 'asc'
|
|
||||||
? 'desc'
|
|
||||||
: 'default',
|
|
||||||
page: 1, // Reset page when sort changes
|
page: 1, // Reset page when sort changes
|
||||||
}))
|
}));
|
||||||
}
|
};
|
||||||
|
|
||||||
const toggleUpdatedSort = () => {
|
const toggleUpdatedSort = () => {
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
updatedSort:
|
updatedSort:
|
||||||
prev.updatedSort === 'default'
|
prev.updatedSort === "default" ? "asc" : prev.updatedSort === "asc" ? "desc" : "default",
|
||||||
? 'asc'
|
|
||||||
: prev.updatedSort === 'asc'
|
|
||||||
? 'desc'
|
|
||||||
: 'default',
|
|
||||||
page: 1, // Reset page when sort changes
|
page: 1, // Reset page when sort changes
|
||||||
}))
|
}));
|
||||||
}
|
};
|
||||||
|
|
||||||
const setPage = (page: number) => {
|
const setPage = (page: number) => {
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
page: Math.max(1, Math.min(page, totalPages)),
|
page: Math.max(1, Math.min(page, totalPages)),
|
||||||
}))
|
}));
|
||||||
}
|
};
|
||||||
|
|
||||||
const setLimit = (limit: number) => {
|
const setLimit = (limit: number) => {
|
||||||
setState((prev) => ({ ...prev, limit, page: 1 })) // Reset page when limit changes
|
setState((prev) => ({ ...prev, limit, page: 1 })); // Reset page when limit changes
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
search: state.search,
|
search: state.search,
|
||||||
|
@ -180,5 +169,5 @@ export function useModsSearch(mods: ZenTheme[]) {
|
||||||
setLimit,
|
setLimit,
|
||||||
mods: paginatedMods,
|
mods: paginatedMods,
|
||||||
searchParams,
|
searchParams,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
48
src/mods.ts
48
src/mods.ts
|
@ -1,41 +1,41 @@
|
||||||
import { format } from 'date-fns'
|
import { format } from "date-fns";
|
||||||
|
|
||||||
export interface ZenTheme {
|
export interface ZenTheme {
|
||||||
name: string
|
name: string;
|
||||||
description: string
|
description: string;
|
||||||
image: string
|
image: string;
|
||||||
downloadUrl: string
|
downloadUrl: string;
|
||||||
id: string
|
id: string;
|
||||||
homepage?: string
|
homepage?: string;
|
||||||
readme: string
|
readme: string;
|
||||||
preferences?: string
|
preferences?: string;
|
||||||
isColorTheme: boolean
|
isColorTheme: boolean;
|
||||||
author: string
|
author: string;
|
||||||
version: string
|
version: string;
|
||||||
tags: string[]
|
tags: string[];
|
||||||
createdAt: Date
|
createdAt: Date;
|
||||||
updatedAt: Date
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const THEME_API = 'https://zen-browser.github.io/theme-store/themes.json'
|
const THEME_API = "https://zen-browser.github.io/theme-store/themes.json";
|
||||||
|
|
||||||
export async function getAllMods(): Promise<ZenTheme[]> {
|
export async function getAllMods(): Promise<ZenTheme[]> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(THEME_API)
|
const res = await fetch(THEME_API);
|
||||||
const json = await res.json()
|
const json = await res.json();
|
||||||
// convert dict to array
|
// convert dict to array
|
||||||
const mods = Object.keys(json).map((key) => json[key])
|
const mods = Object.keys(json).map((key) => json[key]);
|
||||||
return mods
|
return mods;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAuthorLink(author: string): string {
|
export function getAuthorLink(author: string): string {
|
||||||
return `https://github.com/${author}`
|
return `https://github.com/${author}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLocalizedDate(date: Date): string {
|
export function getLocalizedDate(date: Date): string {
|
||||||
return format(date, 'PP')
|
return format(date, "PP");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import NotFound from './[...locale]/404.astro'
|
import NotFound from "./[...locale]/404.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<NotFound />
|
<NotFound />
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
---
|
---
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import Title from '~/components/Title.astro'
|
import Title from "~/components/Title.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
import { getLocale, getPath, getUI } from "~/utils/i18n";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale);
|
||||||
const {
|
const {
|
||||||
routes: { notFound },
|
routes: { notFound },
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={notFound.title}>
|
<Layout title={notFound.title}>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from 'lucide-astro'
|
import { ArrowRight } from "lucide-astro";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const {
|
const {
|
||||||
routes: { donate },
|
routes: { donate },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.donate.title} description={layout.donate.description}>
|
<Layout title={layout.donate.title} description={layout.donate.description}>
|
||||||
|
|
|
@ -1,40 +1,35 @@
|
||||||
---
|
---
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import DownloadScript from '~/components/download/DownloadScript.astro'
|
import DownloadScript from "~/components/download/DownloadScript.astro";
|
||||||
import PlatformDownload from '~/components/download/PlatformDownload.astro'
|
import PlatformDownload from "~/components/download/PlatformDownload.astro";
|
||||||
import { getReleasesWithChecksums } from '~/components/download/release-data.astro'
|
import { getReleasesWithChecksums } from "~/components/download/release-data.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getChecksums } from '~/utils/githubChecksums'
|
import { getChecksums } from "~/utils/githubChecksums";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
import { icon, library } from "@fortawesome/fontawesome-svg-core";
|
||||||
import {
|
import { faApple, faGithub, faLinux, faWindows } from "@fortawesome/free-brands-svg-icons";
|
||||||
faApple,
|
import { ExternalLink, Lock } from "lucide-astro";
|
||||||
faGithub,
|
|
||||||
faLinux,
|
|
||||||
faWindows,
|
|
||||||
} from '@fortawesome/free-brands-svg-icons'
|
|
||||||
import { ExternalLink, Lock } from 'lucide-astro'
|
|
||||||
|
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
const {
|
const {
|
||||||
routes: { download },
|
routes: { download },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
|
|
||||||
library.add(faWindows, faLinux, faApple, faGithub)
|
library.add(faWindows, faLinux, faApple, faGithub);
|
||||||
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
|
const windowsIcon = icon({ prefix: "fab", iconName: "windows" });
|
||||||
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
|
const linuxIcon = icon({ prefix: "fab", iconName: "linux" });
|
||||||
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
|
const appleIcon = icon({ prefix: "fab", iconName: "apple" });
|
||||||
const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
|
const githubIcon = icon({ prefix: "fab", iconName: "github" });
|
||||||
|
|
||||||
const checksums = await getChecksums()
|
const checksums = await getChecksums();
|
||||||
const releases = getReleasesWithChecksums(checksums)
|
const releases = getReleasesWithChecksums(checksums);
|
||||||
|
|
||||||
const platformNames = download.platformNames
|
const platformNames = download.platformNames;
|
||||||
const platformDescriptions = download.platformDescriptions
|
const platformDescriptions = download.platformDescriptions;
|
||||||
---
|
---
|
||||||
|
|
||||||
<DownloadScript />
|
<DownloadScript />
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
import rss, { type RSSOptions } from '@astrojs/rss'
|
import rss, { type RSSOptions } from "@astrojs/rss";
|
||||||
import { releaseNotes } from '~/release-notes'
|
import { releaseNotes } from "~/release-notes";
|
||||||
import type { ReleaseNote } from '~/release-notes'
|
import type { ReleaseNote } from "~/release-notes";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
/** The default number of entries to include in the RSS feed. */
|
/** The default number of entries to include in the RSS feed. */
|
||||||
const RSS_ENTRY_LIMIT = 20
|
const RSS_ENTRY_LIMIT = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the GET request for the `feed.xml` endpoint.
|
* Handles the GET request for the `feed.xml` endpoint.
|
||||||
* @returns The RSS feed for the Zen Browser release notes.
|
* @returns The RSS feed for the Zen Browser release notes.
|
||||||
*/
|
*/
|
||||||
export function GET(context: any) {
|
export function GET(context: { url: URL }) {
|
||||||
// Just in case the release notes array is empty for whatever reason.
|
// Just in case the release notes array is empty for whatever reason.
|
||||||
const latestDate =
|
const latestDate =
|
||||||
releaseNotes.length > 0
|
releaseNotes.length > 0 ? formatRssDate(releaseNotes[0].date as string) : new Date();
|
||||||
? formatRssDate(releaseNotes[0].date as string)
|
|
||||||
: new Date()
|
|
||||||
|
|
||||||
const rssData: RSSOptions = {
|
const rssData: RSSOptions = {
|
||||||
title: 'Zen Browser Release Notes',
|
title: "Zen Browser Release Notes",
|
||||||
description: 'Release Notes for the Zen Browser',
|
description: "Release Notes for the Zen Browser",
|
||||||
site: context.url,
|
site: context.url,
|
||||||
items: [],
|
items: [],
|
||||||
customData: `
|
customData: `
|
||||||
|
@ -33,7 +31,7 @@ export function GET(context: any) {
|
||||||
<link>https://www.zen-browser.app</link>
|
<link>https://www.zen-browser.app</link>
|
||||||
</image>
|
</image>
|
||||||
`,
|
`,
|
||||||
}
|
};
|
||||||
|
|
||||||
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
|
for (const releaseNote of releaseNotes.slice(0, RSS_ENTRY_LIMIT)) {
|
||||||
rssData.items.push({
|
rssData.items.push({
|
||||||
|
@ -42,10 +40,10 @@ export function GET(context: any) {
|
||||||
pubDate: formatRssDate(releaseNote.date as string),
|
pubDate: formatRssDate(releaseNote.date as string),
|
||||||
description: releaseNote.extra,
|
description: releaseNote.extra,
|
||||||
content: formatReleaseNote(releaseNote),
|
content: formatReleaseNote(releaseNote),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return rss(rssData)
|
return rss(rssData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,15 +54,15 @@ export function GET(context: any) {
|
||||||
* @returns The passed in date string as a Date object.
|
* @returns The passed in date string as a Date object.
|
||||||
*/
|
*/
|
||||||
function formatRssDate(dateStr: string) {
|
function formatRssDate(dateStr: string) {
|
||||||
const splitDate = dateStr.split('/')
|
const splitDate = dateStr.split("/");
|
||||||
if (splitDate.length !== 3) {
|
if (splitDate.length !== 3) {
|
||||||
throw new Error('Invalid date format')
|
throw new Error("Invalid date format");
|
||||||
}
|
}
|
||||||
|
|
||||||
const day = Number(splitDate[0])
|
const day = Number(splitDate[0]);
|
||||||
const month = Number(splitDate[1]) - 1
|
const month = Number(splitDate[1]) - 1;
|
||||||
const year = Number(splitDate[2])
|
const year = Number(splitDate[2]);
|
||||||
return new Date(year, month, day)
|
return new Date(year, month, day);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,99 +74,83 @@ function formatReleaseNote(releaseNote: ReleaseNote) {
|
||||||
let content = `<p>
|
let content = `<p>
|
||||||
If you encounter any issues, please report them on <a href="https://github.com/zen-browser/desktop/issues/">the issues page</a>.
|
If you encounter any issues, please report them on <a href="https://github.com/zen-browser/desktop/issues/">the issues page</a>.
|
||||||
Thanks everyone for your feedback! ❤️
|
Thanks everyone for your feedback! ❤️
|
||||||
</p>`
|
</p>`;
|
||||||
|
|
||||||
if (releaseNote.image) {
|
if (releaseNote.image) {
|
||||||
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
content += `<img src="https://cdn.jsdelivr.net/gh/zen-browser/www/public/releases/${releaseNote.version}.png"
|
||||||
alt="Release Image for version ${releaseNote.version}"
|
alt="Release Image for version ${releaseNote.version}"
|
||||||
style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
|
style="max-width: 30em; width: 100%; border-radius: 0.5rem;"
|
||||||
/>`
|
/>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (releaseNote.extra) {
|
if (releaseNote.extra) {
|
||||||
content += `<p>${releaseNote.extra.replace(/(\n)/g, '<br />')}</p>`
|
content += `<p>${releaseNote.extra.replace(/(\n)/g, "<br />")}</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
content += addReleaseNoteSection(
|
content += addReleaseNoteSection(
|
||||||
'⚠️ Breaking changes',
|
"⚠️ Breaking changes",
|
||||||
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote),
|
releaseNote.breakingChanges?.map(breakingChangeToReleaseNote),
|
||||||
)
|
);
|
||||||
content += addReleaseNoteSection(
|
content += addReleaseNoteSection("✓ Fixes", releaseNote.fixes?.map(fixToReleaseNote));
|
||||||
'✓ Fixes',
|
content += addReleaseNoteSection("🖌 Theme Changes", releaseNote.themeChanges);
|
||||||
releaseNote.fixes?.map(fixToReleaseNote),
|
content += addReleaseNoteSection("⭐ Features", releaseNote.features);
|
||||||
)
|
|
||||||
content += addReleaseNoteSection('🖌 Theme Changes', releaseNote.themeChanges)
|
|
||||||
content += addReleaseNoteSection('⭐ Features', releaseNote.features)
|
|
||||||
|
|
||||||
return content
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addReleaseNoteSection(title: string, items?: string[]): string {
|
function addReleaseNoteSection(title: string, items?: string[]): string {
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return ''
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = `<h2>${title}</h2>`
|
let content = `<h2>${title}</h2>`;
|
||||||
content += `<ul>`
|
content += "<ul>";
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item && item.length > 0) {
|
if (item && item.length > 0) {
|
||||||
content += `<li>${item}</li>`
|
content += `<li>${item}</li>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content += `</ul>`
|
content += "</ul>";
|
||||||
return content
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixToReleaseNote(
|
function fixToReleaseNote(fix?: Exclude<ReleaseNote["fixes"], undefined>[number]) {
|
||||||
fix?: Exclude<ReleaseNote['fixes'], undefined>[number],
|
if (typeof fix === "string") {
|
||||||
) {
|
return fix;
|
||||||
if (typeof fix === 'string') {
|
|
||||||
return fix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fix || !fix.description || fix.description.length === 0) {
|
if (!fix || !fix.description || fix.description.length === 0) {
|
||||||
return ''
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
let note = fix.description
|
let note = fix.description;
|
||||||
if (fix.issue) {
|
if (fix.issue) {
|
||||||
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`
|
note += ` (<a href="https://github.com/zen-browser/desktop/issues/${fix.issue}" target="_blank">#${fix.issue}</a>)`;
|
||||||
}
|
}
|
||||||
return note
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function breakingChangeToReleaseNote(
|
function breakingChangeToReleaseNote(
|
||||||
breakingChange?: Exclude<ReleaseNote['breakingChanges'], undefined>[number],
|
breakingChange?: Exclude<ReleaseNote["breakingChanges"], undefined>[number],
|
||||||
) {
|
) {
|
||||||
if (typeof breakingChange === 'string') {
|
if (typeof breakingChange === "string") {
|
||||||
return breakingChange
|
return breakingChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!breakingChange || !breakingChange.description || breakingChange.description.length === 0) {
|
||||||
!breakingChange ||
|
return "";
|
||||||
!breakingChange.description ||
|
|
||||||
breakingChange.description.length === 0
|
|
||||||
) {
|
|
||||||
return ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`
|
return `${breakingChange.description} (<a href="${breakingChange.link}" target="_blank">Learn more</a>)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pubDate(date?: Date) {
|
function pubDate(date?: Date) {
|
||||||
date ??= new Date()
|
const newDate = date ?? new Date();
|
||||||
|
|
||||||
const pieces = date.toString().split(' ')
|
const pieces = newDate.toString().split(" ");
|
||||||
const offsetTime = pieces[5].match(/[-+]\d{4}/)
|
const offsetTime = pieces[5].match(/[-+]\d{4}/);
|
||||||
const offset = offsetTime ? offsetTime : pieces[5]
|
const offset = offsetTime ? offsetTime : pieces[5];
|
||||||
const parts = [
|
const parts = [`${pieces[0]},`, pieces[2], pieces[1], pieces[3], pieces[4], offset];
|
||||||
pieces[0] + ',',
|
|
||||||
pieces[2],
|
|
||||||
pieces[1],
|
|
||||||
pieces[3],
|
|
||||||
pieces[4],
|
|
||||||
offset,
|
|
||||||
]
|
|
||||||
|
|
||||||
return parts.join(' ')
|
return parts.join(" ");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import Community from '~/components/Community.astro'
|
import Community from "~/components/Community.astro";
|
||||||
import Features from '~/components/Features.astro'
|
import Features from "~/components/Features.astro";
|
||||||
import Hero from '~/components/Hero.astro'
|
import Hero from "~/components/Hero.astro";
|
||||||
import Sponsors from '~/components/Sponsors.astro'
|
import Sponsors from "~/components/Sponsors.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const { layout } = getUI(locale)
|
const { layout } = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight, Info } from 'lucide-astro'
|
import { ArrowRight, Info } from "lucide-astro";
|
||||||
import BackButton from '~/components/BackButton.astro'
|
import BackButton from "~/components/BackButton.astro";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getAllMods, getAuthorLink, getLocalizedDate } from '~/mods'
|
import { getAllMods, getAuthorLink, getLocalizedDate } from "~/mods";
|
||||||
import { getUI } from '~/utils/i18n'
|
import { getUI } from "~/utils/i18n";
|
||||||
import { getLocale, getOtherLocales } from '~/utils/i18n'
|
import { getLocale, getOtherLocales } from "~/utils/i18n";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const mods = await getAllMods()
|
const mods = await getAllMods();
|
||||||
return mods.flatMap((mod) => [
|
return mods.flatMap((mod) => [
|
||||||
...getOtherLocales().map((locale) => ({
|
...getOtherLocales().map((locale) => ({
|
||||||
params: {
|
params: {
|
||||||
|
@ -31,25 +31,25 @@ export async function getStaticPaths() {
|
||||||
locale: undefined,
|
locale: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/TeaClientMC/Website/blob/7faacc9f8b2c79c74f711d413b155c84faafc00d/src/pages/news/%5B...slug%5D.astro
|
// https://github.com/TeaClientMC/Website/blob/7faacc9f8b2c79c74f711d413b155c84faafc00d/src/pages/news/%5B...slug%5D.astro
|
||||||
|
|
||||||
const mod = Astro.props
|
const mod = Astro.props;
|
||||||
|
|
||||||
const dates = {
|
const dates = {
|
||||||
createdAt: getLocalizedDate(mod.createdAt),
|
createdAt: getLocalizedDate(mod.createdAt),
|
||||||
updatedAt: getLocalizedDate(mod.updatedAt),
|
updatedAt: getLocalizedDate(mod.updatedAt),
|
||||||
}
|
};
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro as { params: { locale?: string } });
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
mods: { slug },
|
mods: { slug },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
---
|
---
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import ModsList from '~/components/ModsList'
|
import ModsList from "~/components/ModsList";
|
||||||
import { CONSTANT } from '~/constants'
|
import { CONSTANT } from "~/constants";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getAllMods } from '~/mods'
|
import { getAllMods } from "~/mods";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { mods },
|
routes: { mods },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
|
|
||||||
const allMods = (await getAllMods()) || []
|
const allMods = (await getAllMods()) || [];
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.mods.title}>
|
<Layout title={layout.mods.title}>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import Title from '~/components/Title.astro'
|
import Title from "~/components/Title.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { privacyPolicy },
|
routes: { privacyPolicy },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
---
|
---
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { releaseNotes } from '~/release-notes'
|
import { releaseNotes } from "~/release-notes";
|
||||||
import { getStaticPaths as getI18nPaths, getLocale, getUI } from '~/utils/i18n'
|
import { getStaticPaths as getI18nPaths, getLocale, getUI } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
releaseNotes: { slug },
|
releaseNotes: { slug },
|
||||||
},
|
},
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const i18nPaths = getI18nPaths()
|
const i18nPaths = getI18nPaths();
|
||||||
|
|
||||||
return i18nPaths.flatMap(({ params: { locale } }) => [
|
return i18nPaths.flatMap(({ params: { locale } }) => [
|
||||||
...releaseNotes.map((release: any) => ({
|
...releaseNotes.map((release) => ({
|
||||||
params: { slug: release.version, locale },
|
params: { slug: release.version, locale },
|
||||||
props: { ...release },
|
props: { ...release },
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
params: { slug: 'latest', locale },
|
params: { slug: "latest", locale },
|
||||||
props: { ...releaseNotes[0] },
|
props: { ...releaseNotes[0] },
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const release = Astro.props
|
const release = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
|
<Layout title={slug.title} redirect={`/release-notes#${release.version}`}>
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
---
|
---
|
||||||
import { Modal, ModalBody, ModalHeader } from 'free-astro-components'
|
import { Modal, ModalBody, ModalHeader } from "free-astro-components";
|
||||||
import { ArrowUp } from 'lucide-astro'
|
import { ArrowUp } from "lucide-astro";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import ReleaseNoteItem from '~/components/ReleaseNoteItem.astro'
|
import ReleaseNoteItem from "~/components/ReleaseNoteItem.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import {
|
import { releaseNotes as releaseNotesData, releaseNotesTwilight } from "~/release-notes";
|
||||||
releaseNotes as releaseNotesData,
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
releaseNotesTwilight,
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
} from '~/release-notes'
|
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { releaseNotes },
|
routes: { releaseNotes },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.releaseNotes.title}>
|
<Layout title={layout.releaseNotes.title}>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import Features from '~/components/Features.astro'
|
import Features from "~/components/Features.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { welcome },
|
routes: { welcome },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={layout.welcome.title} description={layout.welcome.description}>
|
<Layout title={layout.welcome.title} description={layout.welcome.description}>
|
||||||
|
|
|
@ -1,32 +1,29 @@
|
||||||
---
|
---
|
||||||
import { ArrowRight } from 'lucide-astro'
|
import { ArrowRight } from "lucide-astro";
|
||||||
import Button from '~/components/Button.astro'
|
import Button from "~/components/Button.astro";
|
||||||
import Description from '~/components/Description.astro'
|
import Description from "~/components/Description.astro";
|
||||||
import SocialMediaStrip from '~/components/SocialMediaStrip.astro'
|
import SocialMediaStrip from "~/components/SocialMediaStrip.astro";
|
||||||
import Layout from '~/layouts/Layout.astro'
|
import Layout from "~/layouts/Layout.astro";
|
||||||
|
|
||||||
import whatsNewVideo from '~/assets/whats-new.mp4'
|
import whatsNewVideo from "~/assets/whats-new.mp4";
|
||||||
import Video from '~/components/Video.astro'
|
import Video from "~/components/Video.astro";
|
||||||
import { releaseNotes } from '~/release-notes'
|
import { releaseNotes } from "~/release-notes";
|
||||||
import whatsNewText from '~/release-notes/whats-new.json'
|
import whatsNewText from "~/release-notes/whats-new.json";
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from "~/utils/i18n";
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from "~/utils/i18n";
|
||||||
|
|
||||||
const latestVersion = releaseNotes[0]
|
const latestVersion = releaseNotes[0];
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: { whatsNew },
|
routes: { whatsNew },
|
||||||
layout,
|
layout,
|
||||||
} = getUI(locale)
|
} = getUI(locale);
|
||||||
|
|
||||||
// Just redirect to the release notes if we are in a patch version
|
// Just redirect to the release notes if we are in a patch version
|
||||||
if (
|
if (latestVersion.version.split(".").length > 2 && whatsNewText[1] !== latestVersion.version) {
|
||||||
latestVersion.version.split('.').length > 2 &&
|
return Astro.redirect(`/release-notes#${latestVersion.version}`);
|
||||||
whatsNewText[1] !== latestVersion.version
|
|
||||||
) {
|
|
||||||
return Astro.redirect(`/release-notes#${latestVersion.version}`)
|
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,41 @@
|
||||||
import releaseNotesStable from './release-notes/stable.json'
|
import releaseNotesStable from "./release-notes/stable.json";
|
||||||
|
|
||||||
interface FixWithIssue {
|
interface FixWithIssue {
|
||||||
description: string
|
description: string;
|
||||||
issue?: number
|
issue?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fix = string | FixWithIssue
|
type Fix = string | FixWithIssue;
|
||||||
|
|
||||||
export type BreakingChange = string | { description: string; link: string }
|
export type BreakingChange = string | { description: string; link: string };
|
||||||
|
|
||||||
export interface ReleaseNote {
|
export interface ReleaseNote {
|
||||||
version: string
|
version: string;
|
||||||
date?: string // optional for twilight
|
date?: string; // optional for twilight
|
||||||
extra?: string
|
extra?: string;
|
||||||
image?: boolean
|
image?: boolean;
|
||||||
fixes?: Fix[]
|
fixes?: Fix[];
|
||||||
features?: string[]
|
features?: string[];
|
||||||
breakingChanges?: BreakingChange[]
|
breakingChanges?: BreakingChange[];
|
||||||
themeChanges?: string[]
|
themeChanges?: string[];
|
||||||
inProgress?: boolean
|
inProgress?: boolean;
|
||||||
workflowId?: number
|
workflowId?: number;
|
||||||
isTwilight?: boolean
|
isTwilight?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const releaseNotes: ReleaseNote[] = releaseNotesStable.reverse()
|
export const releaseNotes: ReleaseNote[] = releaseNotesStable.reverse();
|
||||||
export { default as releaseNotesTwilight } from './release-notes/twilight.json'
|
export { default as releaseNotesTwilight } from "./release-notes/twilight.json";
|
||||||
|
|
||||||
export function getReleaseNoteFirefoxVersion(
|
export function getReleaseNoteFirefoxVersion(releaseNote: ReleaseNote): string | null {
|
||||||
releaseNote: ReleaseNote,
|
|
||||||
): string | null {
|
|
||||||
// Check if "firefox" is on the feature list
|
// Check if "firefox" is on the feature list
|
||||||
for (const feature of releaseNote.features || []) {
|
for (const feature of releaseNote.features || []) {
|
||||||
if (feature.toLowerCase().includes('firefox')) {
|
if (feature.toLowerCase().includes("firefox")) {
|
||||||
// may be X or X.X or X.X.X
|
// may be X or X.X or X.X.X
|
||||||
const match = feature.match(/(\d+(\.\d+){0,2})/)
|
const match = feature.match(/(\d+(\.\d+){0,2})/);
|
||||||
if (match) {
|
if (match) {
|
||||||
return match[0]
|
return match[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,7 @@
|
||||||
"version": "1.0.0-a.2",
|
"version": "1.0.0-a.2",
|
||||||
"date": "12/07/2024",
|
"date": "12/07/2024",
|
||||||
"extra": "This release is the second alpha release of the 1.0.0-alpha series. It includes a lot of bug fixes and improvements given the feedback we received from the first alpha release. This release is still not considered stable, but it's a big step towards the first stable release.",
|
"extra": "This release is the second alpha release of the 1.0.0-alpha series. It includes a lot of bug fixes and improvements given the feedback we received from the first alpha release. This release is still not considered stable, but it's a big step towards the first stable release.",
|
||||||
"features": [
|
"features": ["Added support for macOS aaarch64!", "Some performance improvements"],
|
||||||
"Added support for macOS aaarch64!",
|
|
||||||
"Some performance improvements"
|
|
||||||
],
|
|
||||||
"fixes": [
|
"fixes": [
|
||||||
{
|
{
|
||||||
"description": "Fixed rounded corners of browser views for some websites",
|
"description": "Fixed rounded corners of browser views for some websites",
|
||||||
|
@ -49,18 +46,13 @@
|
||||||
"issue": 50
|
"issue": 50
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"breakingChanges": [
|
"breakingChanges": ["Removed support window's stub installer. It's under development."]
|
||||||
"Removed support window's stub installer. It's under development."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.0-a.3",
|
"version": "1.0.0-a.3",
|
||||||
"date": "14/07/2024",
|
"date": "14/07/2024",
|
||||||
"extra": "This release is the third alpha release of the 1.0.0-alpha series. One big feature of this release is the new workspaces feature. This feature allows you to create different workspaces with different tabs and configurations. This release also includes a lot of bug fixes and improvements.",
|
"extra": "This release is the third alpha release of the 1.0.0-alpha series. One big feature of this release is the new workspaces feature. This feature allows you to create different workspaces with different tabs and configurations. This release also includes a lot of bug fixes and improvements.",
|
||||||
"features": [
|
"features": ["Added support for workspaces (Experimental)", "Better support for macOS aarch64"],
|
||||||
"Added support for workspaces (Experimental)",
|
|
||||||
"Better support for macOS aarch64"
|
|
||||||
],
|
|
||||||
"fixes": [
|
"fixes": [
|
||||||
{
|
{
|
||||||
"description": "Fixed subwindows not being displayed correctly",
|
"description": "Fixed subwindows not being displayed correctly",
|
||||||
|
@ -262,9 +254,7 @@
|
||||||
"issue": 89
|
"issue": 89
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"breakingChanges": [
|
"breakingChanges": ["Changed the ID for flatpak to io.github.zen_browser.zen"]
|
||||||
"Changed the ID for flatpak to io.github.zen_browser.zen"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.0-a.11",
|
"version": "1.0.0-a.11",
|
||||||
|
@ -310,9 +300,7 @@
|
||||||
"issue": 124
|
"issue": 124
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"breakingChanges": [
|
"breakingChanges": ["Changed the ID for AppImage to io.github.zen_browser.zen"]
|
||||||
"Changed the ID for AppImage to io.github.zen_browser.zen"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.0-a.12",
|
"version": "1.0.0-a.12",
|
||||||
|
@ -363,9 +351,7 @@
|
||||||
"version": "1.0.0-a.13",
|
"version": "1.0.0-a.13",
|
||||||
"date": "05/08/2024",
|
"date": "05/08/2024",
|
||||||
"extra": "This is a smaller release to fix some bugs and improve some small details.\n\nIm going to try doing more frequent releases from now on, see how it goes.",
|
"extra": "This is a smaller release to fix some bugs and improve some small details.\n\nIm going to try doing more frequent releases from now on, see how it goes.",
|
||||||
"features": [
|
"features": ["Allow to remember sidebar width even after collapsing it."],
|
||||||
"Allow to remember sidebar width even after collapsing it."
|
|
||||||
],
|
|
||||||
"fixes": [
|
"fixes": [
|
||||||
{
|
{
|
||||||
"description": "Task Manager Icon Missing in Flatpak Version",
|
"description": "Task Manager Icon Missing in Flatpak Version",
|
||||||
|
@ -906,10 +892,7 @@
|
||||||
"description": "Fixed Mods not being applied to every single window opened"
|
"description": "Fixed Mods not being applied to every single window opened"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"breakingChanges": [
|
"breakingChanges": ["Removed Galaxy and Dream mods", "Removed the 'legacy-toolbar' preference"],
|
||||||
"Removed Galaxy and Dream mods",
|
|
||||||
"Removed the 'legacy-toolbar' preference"
|
|
||||||
],
|
|
||||||
"themeChanges": [
|
"themeChanges": [
|
||||||
"Themes will now be able to have string and number values",
|
"Themes will now be able to have string and number values",
|
||||||
"The configuration schema for mods has been updated. All current mods have been updated automatically."
|
"The configuration schema for mods has been updated. All current mods have been updated automatically."
|
||||||
|
@ -921,9 +904,7 @@
|
||||||
"image": true,
|
"image": true,
|
||||||
"workflowId": 11000317603,
|
"workflowId": 11000317603,
|
||||||
"extra": "This update addresses some significant issues with the previous release.\n\nWe appreciate your patience and support!",
|
"extra": "This update addresses some significant issues with the previous release.\n\nWe appreciate your patience and support!",
|
||||||
"features": [
|
"features": ["Added a new system for handling keyboard shortcuts"],
|
||||||
"Added a new system for handling keyboard shortcuts"
|
|
||||||
],
|
|
||||||
"fixes": [
|
"fixes": [
|
||||||
{
|
{
|
||||||
"description": "The New Tab button is not visible",
|
"description": "The New Tab button is not visible",
|
||||||
|
@ -980,9 +961,7 @@
|
||||||
"Enabled container tabs by default",
|
"Enabled container tabs by default",
|
||||||
"Improved Expand Tabs on Hover layout"
|
"Improved Expand Tabs on Hover layout"
|
||||||
],
|
],
|
||||||
"themeChanges": [
|
"themeChanges": ["Toggle inputs will not use the themed tertiary color"],
|
||||||
"Toggle inputs will not use the themed tertiary color"
|
|
||||||
],
|
|
||||||
"breakingChanges": [
|
"breakingChanges": [
|
||||||
"The keyboard shortcuts will be overriden by the defaults ones in this update"
|
"The keyboard shortcuts will be overriden by the defaults ones in this update"
|
||||||
],
|
],
|
||||||
|
@ -1279,9 +1258,7 @@
|
||||||
"issue": 2156
|
"issue": 2156
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"breakingChanges": [
|
"breakingChanges": ["Removed Show Expand Button option from settings"],
|
||||||
"Removed Show Expand Button option from settings"
|
|
||||||
],
|
|
||||||
"themeChanges": [
|
"themeChanges": [
|
||||||
"The variable '--zen-main-browser-background' will now contain the generated gradient",
|
"The variable '--zen-main-browser-background' will now contain the generated gradient",
|
||||||
"Added the 'unread' attribute for background tabs that haven't been accessed yet"
|
"Added the 'unread' attribute for background tabs that haven't been accessed yet"
|
||||||
|
@ -1355,9 +1332,7 @@
|
||||||
"issue": 2413
|
"issue": 2413
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"breakingChanges": [
|
"breakingChanges": ["Changed the default layout of the customizable UI buttons"],
|
||||||
"Changed the default layout of the customizable UI buttons"
|
|
||||||
],
|
|
||||||
"features": [
|
"features": [
|
||||||
"Added Zen Glance!",
|
"Added Zen Glance!",
|
||||||
"Updated to the latest stable version of Firefox (132.0)",
|
"Updated to the latest stable version of Firefox (132.0)",
|
||||||
|
@ -1384,9 +1359,7 @@
|
||||||
"description": "Fixed wrong aligment on glance action buttons"
|
"description": "Fixed wrong aligment on glance action buttons"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"features": [
|
"features": ["No new features, sorry"]
|
||||||
"No new features, sorry"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.1-a.17",
|
"version": "1.0.1-a.17",
|
||||||
|
@ -2325,9 +2298,7 @@
|
||||||
"Fixed pinning a tab adding them to the essentials container",
|
"Fixed pinning a tab adding them to the essentials container",
|
||||||
"Other small fixes for compact mode not animating properly"
|
"Other small fixes for compact mode not animating properly"
|
||||||
],
|
],
|
||||||
"features": [
|
"features": ["localhost and http URL will no longer be trimmed in single toolbar layout"],
|
||||||
"localhost and http URL will no longer be trimmed in single toolbar layout"
|
|
||||||
],
|
|
||||||
"workflowId": 13530880093,
|
"workflowId": 13530880093,
|
||||||
"date": "25/02/2025"
|
"date": "25/02/2025"
|
||||||
},
|
},
|
||||||
|
@ -2456,9 +2427,7 @@
|
||||||
"version": "1.10.3b",
|
"version": "1.10.3b",
|
||||||
"image": false,
|
"image": false,
|
||||||
"extra": "Terribly sorry for the frequent updates, this emergency release simply updates Firefox, fixing a critical vulnerability in chromium's sandboxing system. You can read more here:\n\n<a href='https://cyberinsider.com/firefox-says-its-vulnerable-to-chromes-zero-day-used-in-espionage-attacks/'>https://cyberinsider.com/firefox-says-its-vulnerable-to-chromes-zero-day-used-in-espionage-attacks/</a>",
|
"extra": "Terribly sorry for the frequent updates, this emergency release simply updates Firefox, fixing a critical vulnerability in chromium's sandboxing system. You can read more here:\n\n<a href='https://cyberinsider.com/firefox-says-its-vulnerable-to-chromes-zero-day-used-in-espionage-attacks/'>https://cyberinsider.com/firefox-says-its-vulnerable-to-chromes-zero-day-used-in-espionage-attacks/</a>",
|
||||||
"features": [
|
"features": ["Updated Firefox to 136.0.4"],
|
||||||
"Updated Firefox to 136.0.4"
|
|
||||||
],
|
|
||||||
"workflowId": 14109635630,
|
"workflowId": 14109635630,
|
||||||
"date": "27/03/2025"
|
"date": "27/03/2025"
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,29 +3,25 @@
|
||||||
* Returns a mapping from filename to checksum.
|
* Returns a mapping from filename to checksum.
|
||||||
*/
|
*/
|
||||||
export async function getChecksums() {
|
export async function getChecksums() {
|
||||||
const res = await fetch(
|
const res = await fetch("https://api.github.com/repos/zen-browser/desktop/releases/latest", {
|
||||||
'https://api.github.com/repos/zen-browser/desktop/releases/latest',
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/vnd.github+json',
|
Accept: "application/vnd.github+json",
|
||||||
'X-GitHub-Api-Version': '2022-11-28',
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
'User-Agent': 'zen-browser-checksum-fetcher',
|
"User-Agent": "zen-browser-checksum-fetcher",
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
)
|
if (!res.ok) throw new Error(`Failed to fetch GitHub release: ${res.statusText}`);
|
||||||
if (!res.ok)
|
const data = await res.json();
|
||||||
throw new Error('Failed to fetch GitHub release: ' + res.statusText)
|
const body = data.body as string;
|
||||||
const data = await res.json()
|
|
||||||
const body = data.body as string
|
|
||||||
|
|
||||||
// Extract the checksum block
|
// Extract the checksum block
|
||||||
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/)
|
const match = body.match(/File Checksums \(SHA-256\)[\s\S]*?```([\s\S]*?)```/);
|
||||||
const checksums: Record<string, string> = {}
|
const checksums: Record<string, string> = {};
|
||||||
if (match && match[1]) {
|
if (match?.[1]) {
|
||||||
match[1].split('\n').forEach((line) => {
|
for (const line of match[1].split("\n")) {
|
||||||
const [hash, filename] = line.trim().split(/\s+/, 2)
|
const [hash, filename] = line.trim().split(/\s+/, 2);
|
||||||
if (hash && filename) checksums[filename] = hash
|
if (hash && filename) checksums[filename] = hash;
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return checksums
|
}
|
||||||
|
return checksums;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,85 +1,105 @@
|
||||||
import type { GetStaticPaths } from 'astro'
|
import type { GetStaticPaths } from "astro";
|
||||||
import { CONSTANT } from '~/constants'
|
import { CONSTANT } from "~/constants";
|
||||||
import UI_EN from '~/i18n/en/translation.json'
|
import UI_EN from "~/i18n/en/translation.json";
|
||||||
|
|
||||||
export type Locale = (typeof locales)[number]
|
export type Locale = (typeof locales)[number];
|
||||||
|
|
||||||
export const getPath = (locale?: Locale) => (path: string) => {
|
export const getPath = (locale?: Locale) => (path: string) => {
|
||||||
if (locale && !path.startsWith(`/${locale}`)) {
|
if (locale && locale !== CONSTANT.I18N.DEFAULT_LOCALE && !path.startsWith(`/${locale}`)) {
|
||||||
return `/${locale}${path.startsWith('/') ? '' : '/'}${path}`
|
return `/${locale}${path.startsWith("/") ? "" : "/"}${path}`;
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
export const getLocale = (Astro: any) => {
|
export const getLocale = (Astro: { params?: { locale?: string } }) => {
|
||||||
if (Astro.params.locale) {
|
if (Astro.params?.locale) {
|
||||||
return Astro.params.locale as Locale
|
return Astro.params.locale as Locale;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return CONSTANT.I18N.DEFAULT_LOCALE as Locale;
|
||||||
|
};
|
||||||
|
|
||||||
export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value)
|
export const locales = CONSTANT.I18N.LOCALES.map(({ value }) => value);
|
||||||
|
|
||||||
const otherLocales = CONSTANT.I18N.LOCALES.filter(
|
const otherLocales = CONSTANT.I18N.LOCALES.filter(
|
||||||
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
||||||
)
|
);
|
||||||
|
|
||||||
export const getOtherLocales = () => otherLocales
|
export const getOtherLocales = () => otherLocales;
|
||||||
|
|
||||||
export type UI = typeof UI_EN
|
export type UI = typeof UI_EN;
|
||||||
|
|
||||||
export const ui = { en: UI_EN }
|
export const ui = { en: UI_EN };
|
||||||
|
|
||||||
export const getUI = (locale?: Locale | string): UI => {
|
export const getUI = (locale?: Locale | string): UI => {
|
||||||
const validLocale = locales.includes(locale as Locale)
|
const validLocale = locales.includes(locale as Locale) ? locale : CONSTANT.I18N.DEFAULT_LOCALE;
|
||||||
? locale
|
const defaultUI = ui[CONSTANT.I18N.DEFAULT_LOCALE];
|
||||||
: CONSTANT.I18N.DEFAULT_LOCALE
|
const localeUI = ui[validLocale as Locale];
|
||||||
const defaultUI = ui[CONSTANT.I18N.DEFAULT_LOCALE]
|
|
||||||
const localeUI = ui[validLocale as Locale]
|
|
||||||
|
|
||||||
function deepMerge<T>(defaultObj: T, overrideObj: Partial<T>): T {
|
function deepMerge<T extends object>(defaultObj: T, overrideObj: Partial<T>): T {
|
||||||
if (typeof defaultObj !== 'object' || defaultObj === null)
|
// Handle non-object cases
|
||||||
return (overrideObj ?? defaultObj) as T
|
if (typeof defaultObj !== "object" || defaultObj === null) {
|
||||||
if (typeof overrideObj !== 'object' || overrideObj === null)
|
return (overrideObj ?? defaultObj) as T;
|
||||||
return (overrideObj ?? defaultObj) as T
|
}
|
||||||
const result: any = Array.isArray(defaultObj)
|
if (typeof overrideObj !== "object" || overrideObj === null) {
|
||||||
? [...defaultObj]
|
return (overrideObj ?? defaultObj) as T;
|
||||||
: { ...defaultObj }
|
}
|
||||||
for (const key in defaultObj) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(defaultObj, key)) {
|
// Create a new object or array based on the default object's type
|
||||||
result[key] = deepMerge(
|
const result = Array.isArray(defaultObj) ? [...defaultObj] : { ...defaultObj };
|
||||||
(defaultObj as any)[key],
|
|
||||||
(overrideObj as any)?.[key],
|
// Merge properties from the default object
|
||||||
)
|
for (const key of Object.keys(defaultObj) as Array<keyof T>) {
|
||||||
|
const defaultValue = defaultObj[key];
|
||||||
|
const overrideValue = overrideObj[key];
|
||||||
|
|
||||||
|
// Recursively merge nested objects
|
||||||
|
if (
|
||||||
|
defaultValue !== null &&
|
||||||
|
overrideValue !== null &&
|
||||||
|
typeof defaultValue === "object" &&
|
||||||
|
typeof overrideValue === "object"
|
||||||
|
) {
|
||||||
|
// Type assertion to handle nested merging
|
||||||
|
(result as Record<keyof T, unknown>)[key] = deepMerge(
|
||||||
|
defaultValue as object,
|
||||||
|
overrideValue as Partial<object>,
|
||||||
|
);
|
||||||
|
} else if (overrideValue !== undefined) {
|
||||||
|
// Override with the new value if it exists
|
||||||
|
(result as Record<keyof T, unknown>)[key] = overrideValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const key in overrideObj) {
|
|
||||||
|
// Add any new properties from overrideObj
|
||||||
|
for (const key of Object.keys(overrideObj) as Array<keyof T>) {
|
||||||
if (!(key in defaultObj)) {
|
if (!(key in defaultObj)) {
|
||||||
result[key] = (overrideObj as any)[key]
|
(result as Record<keyof T, unknown>)[key] = overrideObj[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result as T
|
|
||||||
}
|
|
||||||
|
|
||||||
return deepMerge<UI>(defaultUI, localeUI)
|
return result as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return deepMerge(defaultUI, localeUI);
|
||||||
|
};
|
||||||
|
|
||||||
export const getStaticPaths = (() => {
|
export const getStaticPaths = (() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
params: { locale: undefined },
|
params: { locale: undefined },
|
||||||
props: { locale: CONSTANT.I18N.DEFAULT_LOCALE },
|
props: { locale: CONSTANT.I18N.DEFAULT_LOCALE },
|
||||||
},
|
},
|
||||||
...CONSTANT.I18N.LOCALES.filter(
|
...CONSTANT.I18N.LOCALES.filter(({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE).map(
|
||||||
({ value }) => value !== CONSTANT.I18N.DEFAULT_LOCALE,
|
({ value }) => ({
|
||||||
).map(({ value }) => ({
|
|
||||||
params: { locale: value },
|
params: { locale: value },
|
||||||
props: {
|
props: {
|
||||||
locale: value,
|
locale: value,
|
||||||
},
|
},
|
||||||
})),
|
}),
|
||||||
]
|
),
|
||||||
}) satisfies GetStaticPaths
|
];
|
||||||
|
}) satisfies GetStaticPaths;
|
||||||
|
|
||||||
export const getLocales = () => {
|
export const getLocales = () => {
|
||||||
return [...locales, ...otherLocales]
|
return [...locales, ...otherLocales];
|
||||||
}
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue