Compare commits

..

28 Commits

Author SHA1 Message Date
Renovate d97fcec00d Update dependency shareon to v2.5.0 2024-04-17 10:01:02 +01:00
Renovate 3179adba31 Update dependency sass to v1.75.0 2024-04-17 09:19:14 +01:00
Renovate ee0649ec3c Update dependency esbuild to v0.20.2 2024-04-17 09:18:39 +01:00
Renovate f2e1f81900 Update dependency lxml to v5.2.1 2024-04-17 09:13:47 +01:00
Renovate 4e7e0f4b6d Update dependency @fortawesome/fontawesome-free to v6.5.2 2024-04-17 09:09:03 +01:00
Renovate b414c73804 Update dependency gunicorn to v22 2024-04-17 08:00:41 +01:00
Jake Howard a1808f4bb4
Add description to image lightbox 2024-04-07 20:05:31 +01:00
Renovate f1934ecdaa
Update dependency glightbox to v3.3.0 2024-04-07 16:12:33 +01:00
Jake Howard 17969086a4
Add alias for root shell 2024-04-07 15:52:40 +01:00
Jake Howard f83c38c2f1
Add logging for image rendition generation 2024-04-07 15:52:33 +01:00
Jake Howard 90b18c7d72
Fix comment for S6 updates 2024-04-07 15:38:01 +01:00
Jake Howard 506e554230
Upgrade to Psycopg3 2024-04-07 15:28:40 +01:00
Jake Howard 9b27baf1ba
Add more text to RSS feed 2024-04-01 19:43:26 +01:00
Jake Howard 3a8e6182ad
Simplify summary and word count implementations 2024-04-01 19:42:59 +01:00
Jake Howard fe43b9c683
install inflection for OpenAPI support
Required by DRF
2024-04-01 12:15:56 +01:00
Jake Howard 6cbac34f2d
Add continue reading button to feed items 2024-03-31 23:59:56 +01:00
Jake Howard e5de558958
Correct argument name when proxy view fails 2024-03-10 15:25:48 +00:00
Jake Howard 1a9d981c7d
Add location information 2024-03-01 17:52:14 +00:00
Jake Howard b0f1191d8f
Only show listing images if there are some
This saves a bit more space for page listings which don't have images
2024-03-01 17:34:26 +00:00
Jake Howard bd4c1a193a
Add page type for talks
Content coming soon, probably
2024-03-01 17:09:56 +00:00
Jake Howard 1934b36ec1
Use `urljoin` to safely join activitypub URL 2024-02-17 21:21:37 +00:00
Jake Howard 23ce49ca8f
Improve readability of recent posts 2024-02-17 20:44:43 +00:00
Jake Howard 926e62518c
Improve sizing of recent posts 2024-02-17 20:43:48 +00:00
Jake Howard ec609ae562
Add redirect view for old pages listing 2024-02-17 19:44:45 +00:00
Jake Howard a19964199f
Put similar content above comments 2024-02-10 15:06:01 +00:00
Jake Howard c69d8d8329
Add recent post cards to homepage 2024-02-10 14:56:26 +00:00
Jake Howard 0424c2dba2
Fix issues with public tags in templates 2024-02-06 18:35:40 +00:00
Jake Howard 4c600651b6
Actually allow customizing the page size 2024-02-05 19:40:39 +00:00
37 changed files with 1007 additions and 309 deletions

View File

@ -1,3 +1,6 @@
{
"extends": ["stylelint-config-standard-scss", "stylelint-config-prettier-scss"]
"extends": ["stylelint-config-standard-scss", "stylelint-config-prettier-scss"],
"rules": {
"scss/at-extend-no-missing-placeholder": null
}
}

View File

@ -15,7 +15,7 @@ FROM python:3.12-slim as production
ENV VIRTUAL_ENV=/venv
# renovate: datasource=github-tags depName=gchq/cyberchef
# renovate: datasource=github-tags depName=just-containers/s6-overlay
ENV S6_OVERLAY_VERSION=3.1.6.2
RUN useradd website --create-home -u 1000 && mkdir /app $VIRTUAL_ENV && chown -R website /app $VIRTUAL_ENV

View File

@ -52,5 +52,9 @@ lint_python:
docker-compose -f {{ DEV_COMPOSE }} up -d
docker-compose -f {{ DEV_COMPOSE }} exec web bash
@sh-root:
docker-compose -f {{ DEV_COMPOSE }} up -d
docker-compose -f {{ DEV_COMPOSE }} exec --user=root web bash
@down:
docker-compose -f {{ DEV_COMPOSE }} down

431
package-lock.json generated
View File

@ -9,19 +9,19 @@
"version": "0.0.0",
"dependencies": {
"@fontsource/fira-code": "5.0.2",
"@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-free": "6.5.2",
"@ledge/is-ie-11": "7.0.0",
"bulma": "0.9.4",
"elevator.js": "1.0.1",
"esbuild": "0.19.2",
"glightbox": "3.2.0",
"esbuild": "0.20.2",
"glightbox": "3.3.0",
"htmx.org": "1.9.2",
"lite-youtube-embed": "0.3.0",
"lodash.clamp": "4.0.3",
"lodash.debounce": "4.0.8",
"lodash.throttle": "4.1.1",
"npm-run-all": "4.1.5",
"sass": "1.70.0",
"sass": "1.75.0",
"shareon": "2.5.0"
},
"devDependencies": {
@ -94,10 +94,25 @@
"postcss-selector-parser": "^6.0.10"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz",
"integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"cpu": [
"arm"
],
@ -110,9 +125,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz",
"integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"cpu": [
"arm64"
],
@ -125,9 +140,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz",
"integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"cpu": [
"x64"
],
@ -140,9 +155,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz",
"integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"cpu": [
"arm64"
],
@ -155,9 +170,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz",
"integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"cpu": [
"x64"
],
@ -170,9 +185,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz",
"integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"cpu": [
"arm64"
],
@ -185,9 +200,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz",
"integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"cpu": [
"x64"
],
@ -200,9 +215,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz",
"integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"cpu": [
"arm"
],
@ -215,9 +230,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz",
"integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"cpu": [
"arm64"
],
@ -230,9 +245,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz",
"integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"cpu": [
"ia32"
],
@ -245,9 +260,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz",
"integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"cpu": [
"loong64"
],
@ -260,9 +275,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz",
"integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"cpu": [
"mips64el"
],
@ -275,9 +290,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz",
"integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"cpu": [
"ppc64"
],
@ -290,9 +305,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz",
"integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"cpu": [
"riscv64"
],
@ -305,9 +320,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz",
"integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"cpu": [
"s390x"
],
@ -320,9 +335,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz",
"integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"cpu": [
"x64"
],
@ -335,9 +350,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz",
"integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"cpu": [
"x64"
],
@ -350,9 +365,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz",
"integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"cpu": [
"x64"
],
@ -365,9 +380,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz",
"integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"cpu": [
"x64"
],
@ -380,9 +395,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz",
"integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"cpu": [
"arm64"
],
@ -395,9 +410,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz",
"integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"cpu": [
"ia32"
],
@ -410,9 +425,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz",
"integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"cpu": [
"x64"
],
@ -486,9 +501,9 @@
"integrity": "sha512-T3yzwKP/JFRYdBUHjDXQfRGp9EOI7+V0uCf9ky1rZXDzMUECxuUqTfBcj60CE3oRLzzSm9fgiEGYLSvzo/S/Fw=="
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
"integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ==",
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz",
"integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
@ -1133,9 +1148,9 @@
}
},
"node_modules/esbuild": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz",
"integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@ -1144,28 +1159,29 @@
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.19.2",
"@esbuild/android-arm64": "0.19.2",
"@esbuild/android-x64": "0.19.2",
"@esbuild/darwin-arm64": "0.19.2",
"@esbuild/darwin-x64": "0.19.2",
"@esbuild/freebsd-arm64": "0.19.2",
"@esbuild/freebsd-x64": "0.19.2",
"@esbuild/linux-arm": "0.19.2",
"@esbuild/linux-arm64": "0.19.2",
"@esbuild/linux-ia32": "0.19.2",
"@esbuild/linux-loong64": "0.19.2",
"@esbuild/linux-mips64el": "0.19.2",
"@esbuild/linux-ppc64": "0.19.2",
"@esbuild/linux-riscv64": "0.19.2",
"@esbuild/linux-s390x": "0.19.2",
"@esbuild/linux-x64": "0.19.2",
"@esbuild/netbsd-x64": "0.19.2",
"@esbuild/openbsd-x64": "0.19.2",
"@esbuild/sunos-x64": "0.19.2",
"@esbuild/win32-arm64": "0.19.2",
"@esbuild/win32-ia32": "0.19.2",
"@esbuild/win32-x64": "0.19.2"
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
}
},
"node_modules/escape-string-regexp": {
@ -1705,9 +1721,9 @@
}
},
"node_modules/glightbox": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.2.0.tgz",
"integrity": "sha512-iit1xYixqL4YVL+I2YJLfMeyJwvLi6FE6kY3qNKeZHEJgRIz80QU8Rm7YCyw1wOTgXvmNDnXGVhHOHRCwnDltQ=="
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.3.0.tgz",
"integrity": "sha512-SJukatHBZZ/POMOpLUQ6/dhXf/wJTDx1wZ/FwApjseXw2WrRj3Ze9DzNCFYzca0oU7RjXQhi9o02aIZ9SuCz1A=="
},
"node_modules/glob": {
"version": "7.2.3",
@ -3303,9 +3319,9 @@
}
},
"node_modules/sass": {
"version": "1.70.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz",
"integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==",
"version": "1.75.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
"integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -4078,136 +4094,142 @@
"dev": true,
"requires": {}
},
"@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"optional": true
},
"@esbuild/android-arm": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.2.tgz",
"integrity": "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz",
"integrity": "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"optional": true
},
"@esbuild/android-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.2.tgz",
"integrity": "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz",
"integrity": "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz",
"integrity": "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz",
"integrity": "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz",
"integrity": "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz",
"integrity": "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz",
"integrity": "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz",
"integrity": "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz",
"integrity": "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz",
"integrity": "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz",
"integrity": "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz",
"integrity": "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz",
"integrity": "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz",
"integrity": "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz",
"integrity": "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz",
"integrity": "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz",
"integrity": "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz",
"integrity": "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz",
"integrity": "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz",
"integrity": "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"optional": true
},
"@eslint-community/eslint-utils": {
@ -4254,9 +4276,9 @@
"integrity": "sha512-T3yzwKP/JFRYdBUHjDXQfRGp9EOI7+V0uCf9ky1rZXDzMUECxuUqTfBcj60CE3oRLzzSm9fgiEGYLSvzo/S/Fw=="
},
"@fortawesome/fontawesome-free": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
"integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ=="
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz",
"integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q=="
},
"@humanwhocodes/config-array": {
"version": "0.11.13",
@ -4733,32 +4755,33 @@
}
},
"esbuild": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.2.tgz",
"integrity": "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"requires": {
"@esbuild/android-arm": "0.19.2",
"@esbuild/android-arm64": "0.19.2",
"@esbuild/android-x64": "0.19.2",
"@esbuild/darwin-arm64": "0.19.2",
"@esbuild/darwin-x64": "0.19.2",
"@esbuild/freebsd-arm64": "0.19.2",
"@esbuild/freebsd-x64": "0.19.2",
"@esbuild/linux-arm": "0.19.2",
"@esbuild/linux-arm64": "0.19.2",
"@esbuild/linux-ia32": "0.19.2",
"@esbuild/linux-loong64": "0.19.2",
"@esbuild/linux-mips64el": "0.19.2",
"@esbuild/linux-ppc64": "0.19.2",
"@esbuild/linux-riscv64": "0.19.2",
"@esbuild/linux-s390x": "0.19.2",
"@esbuild/linux-x64": "0.19.2",
"@esbuild/netbsd-x64": "0.19.2",
"@esbuild/openbsd-x64": "0.19.2",
"@esbuild/sunos-x64": "0.19.2",
"@esbuild/win32-arm64": "0.19.2",
"@esbuild/win32-ia32": "0.19.2",
"@esbuild/win32-x64": "0.19.2"
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
}
},
"escape-string-regexp": {
@ -5151,9 +5174,9 @@
}
},
"glightbox": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.2.0.tgz",
"integrity": "sha512-iit1xYixqL4YVL+I2YJLfMeyJwvLi6FE6kY3qNKeZHEJgRIz80QU8Rm7YCyw1wOTgXvmNDnXGVhHOHRCwnDltQ=="
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/glightbox/-/glightbox-3.3.0.tgz",
"integrity": "sha512-SJukatHBZZ/POMOpLUQ6/dhXf/wJTDx1wZ/FwApjseXw2WrRj3Ze9DzNCFYzca0oU7RjXQhi9o02aIZ9SuCz1A=="
},
"glob": {
"version": "7.2.3",
@ -6262,9 +6285,9 @@
}
},
"sass": {
"version": "1.70.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz",
"integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==",
"version": "1.75.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
"integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",

View File

@ -29,19 +29,19 @@
},
"dependencies": {
"@fontsource/fira-code": "5.0.2",
"@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-free": "6.5.2",
"@ledge/is-ie-11": "7.0.0",
"bulma": "0.9.4",
"elevator.js": "1.0.1",
"esbuild": "0.19.2",
"glightbox": "3.2.0",
"esbuild": "0.20.2",
"glightbox": "3.3.0",
"htmx.org": "1.9.2",
"lite-youtube-embed": "0.3.0",
"lodash.clamp": "4.0.3",
"lodash.debounce": "4.0.8",
"lodash.throttle": "4.1.1",
"npm-run-all": "4.1.5",
"sass": "1.70.0",
"sass": "1.75.0",
"shareon": "2.5.0"
}
}

View File

@ -4,13 +4,13 @@ django-environ==0.11.2
whitenoise[brotli]==6.6.0
Pygments==2.17.2
beautifulsoup4
lxml==5.1.0
lxml==5.2.1
requests
wagtail-generic-chooser==0.6
django-rq==2.10.1
django-redis==5.4.0
gunicorn==21.2.0
psycopg2==2.9.9
gunicorn==22.0.0
psycopg==3.1.18
djangorestframework
django-htmx==1.17.2
wagtail-metadata==5.0.0
@ -32,6 +32,7 @@ wagtail-lite-youtube-embed==0.1.0
# DRF OpenAPI dependencies
uritemplate
PyYAML
inflection
# Use custom `wagtail-favicon` with performance improvements
git+https://github.com/RealOrangeOne/wagtail-favicon@b892165e047b35c46d7244109b9ad9226d32a213

View File

@ -56,6 +56,9 @@ section.content {
.gslide-image img {
object-fit: contain !important;
// Manually set sizes, as mermaid images are very small
width: 80vw !important;
&[src*="mermaid.ink"] {
@include dark-mode {
filter: invert(100%);

View File

@ -1,17 +1,27 @@
body.page-homepage {
height: 100vh;
min-height: 100vh;
main {
background-repeat: no-repeat;
background-size: cover;
background-position: center;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: $white;
margin-bottom: 0;
padding-bottom: 2rem;
display: flex;
flex-direction: column;
justify-content: space-evenly;
}
.top-section {
min-width: 90%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 2rem;
margin-bottom: 1rem;
}
h1 {
@ -23,7 +33,7 @@ body.page-homepage {
min-width: 45%;
}
.latest {
.box {
padding: 0.25rem 0.5rem;
margin-top: 2rem;
background-color: color.adjust($dark, $alpha: -0.2);
@ -59,4 +69,60 @@ body.page-homepage {
#to-top {
display: none;
}
.content-list {
width: 90%;
.card-image {
overflow: hidden;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
transition: filter 0.25s;
filter: brightness(0.85);
}
p {
position: absolute;
bottom: 0;
color: $white;
padding: 0.5rem;
transition: background-color 0.25s;
width: 100%;
background-color: rgb(0 0 0 / 55%);
}
figure {
margin: 0;
text-align: initial;
}
a:hover {
img {
filter: unset;
}
p {
background-color: rgb(0 0 0 / 75%);
}
}
}
.recent-posts {
display: flex;
align-items: center;
flex-direction: column;
width: 100%;
flex-grow: unset;
margin-top: 2rem;
.box {
margin: 0 auto;
display: inline-block;
}
}
}

View File

@ -48,7 +48,7 @@
}
}
.page-blogpostlistpage {
.container.listing {
.date-header {
font-size: $size-2;
font-weight: $weight-bold;

View File

@ -3,4 +3,5 @@ from rest_framework.pagination import PageNumberPagination
class CustomPageNumberPagination(PageNumberPagination):
page_size = 10
page_size_query_param = "page_size"
max_page_size = 25

View File

@ -7,6 +7,7 @@ from django.utils import timezone
from django.utils.functional import cached_property
from modelcluster.fields import ParentalManyToManyField
from wagtail.admin.panels import FieldPanel
from wagtail.models import PageQuerySet
from wagtail.search import index
from wagtailautocomplete.edit_handlers import AutocompletePanel
@ -61,6 +62,19 @@ class BlogPostPage(BaseContentPage):
def tag_list_page_url(self) -> Optional[str]:
return SingletonPageCache.get_url(BlogPostTagListPage)
@cached_property
def tags_list(self) -> models.QuerySet:
"""
Use this to get a page's tags.
"""
tags = self.tags.order_by("slug")
# In drafts, `django-modelcluster` doesn't support these filters
if isinstance(tags, PageQuerySet):
return tags.public().live()
return tags
@cached_property
def blog_post_list_page_url(self) -> Optional[str]:
return SingletonPageCache.get_url(BlogPostListPage)

View File

@ -8,7 +8,7 @@
{% endblock %}
{% block post_content %}
<section class="container">
<section class="container listing">
{% for page in listing_pages %}
{% ifchanged %}
<h2 id="date-{{ page.date|date:'Y-m' }}" class="date-header">

View File

@ -1,11 +1,11 @@
{% extends "common/content_page.html" %}
{% load wagtail_cache %}
{% load wagtail_cache navbar_tags %}
{% block post_content %}
{{ block.super }}
{% if not request.is_preview %}
{% include "common/shareon.html" %}
{% wagtailpagecache FRAGMENT_CACHE_TTL "similar-content" %}
<section class="container similar-content" id="similar-content">
<h2 class="subtitle is-size-2">Similar content</h2>
@ -22,5 +22,11 @@
</section>
{% endwagtailpagecache %}
{% include "common/comments.html" %}
{% if not request.user.is_authenticated %}
{% support_pill %}
{% endif %}
{% endif %}
{% endblock %}

View File

@ -11,7 +11,7 @@ from django.http.response import Http404, HttpResponse, HttpResponseBadRequest
from django.shortcuts import redirect
from django.template.defaultfilters import pluralize
from django.utils.functional import cached_property, classproperty
from django.utils.text import slugify
from django.utils.text import Truncator, slugify
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.contrib.settings.models import BaseGenericSetting, register_setting
@ -31,12 +31,10 @@ from .serializers import PaginationSerializer
from .streamfield import add_heading_anchors, get_blocks, get_content_html
from .utils import (
TocEntry,
count_words,
extract_text,
get_site_title,
get_table_of_contents,
get_url_mime_type,
truncate_string,
)
@ -141,16 +139,11 @@ class BaseContentPage(BasePage, MetadataMixin):
@cached_property
def word_count(self) -> int:
return count_words(self.plain_text)
return len(self.plain_text.split())
@cached_property
def summary(self) -> str:
summary = truncate_string(self.plain_text, 50)
if summary and summary != self.plain_text and not summary.endswith("."):
summary += ""
return summary
return Truncator(self.plain_text).words(50)
@cached_property
def body_html(self) -> str:
@ -263,7 +256,12 @@ class BaseListingPage(RoutablePageMixin, BaseContentPage):
def get_context(self, request: HttpRequest) -> dict:
context = super().get_context(request)
context["listing_pages"] = self.get_paginator_page()
listing_pages = self.get_paginator_page()
context["listing_pages"] = listing_pages
# Show listing images if at least 1 page has an image
context["show_listing_images"] = any(p.list_image_url for p in listing_pages)
return context
@cached_property

View File

@ -2,7 +2,7 @@
<figure>
<div class="image">
<a href="{% image_url value.image 'original' %}" class="glightbox" data-gallery="content" data-height="70vh" data-width="95vw" data-alt="{{ value.caption|richtext|extract_text }}">
<a href="{% image_url value.image 'original' %}" class="glightbox" data-gallery="content" data-height="70vh" data-alt="{{ value.caption|richtext|extract_text }}" data-title="{{ value.caption|richtext|extract_text }}">
<img src="{% image_url value.image 'width-1500' %}" alt="{{ value.caption|richtext|extract_text }}" loading="lazy" decoding="async" />
</a>
</div>

View File

@ -20,17 +20,50 @@
</div>
{% endif %}
{% if page.tags.public.live.all %}
{% if page.tags_list %}
<div class="icon-text is-family-code">
<span class="icon">
<a href="{{ page.tag_list_page_url }}" title="View all tags">
<i class="fas fa-lg fa-tags"></i>
</a>
</span>
{% for tag in page.tags.public.live.all|dictsort:"slug" %}
{% for tag in page.tags_list %}
<span><a title="{{ tag.name }}" href="{% pageurl tag %}">#{{ tag.slug }}</a></span>
{% endfor %}
</div>
{% endif %}
{% if page.slides_url %}
<span class="icon-text">
<a href="{{ page.slides_url }}">
<span class="icon">
<i class="fas fa-lg fa-images"></i>
</span>
<span>Slides</span>
</a>
</span>
{% endif %}
{% if page.video_url %}
<span class="icon-text">
<a href="{{ page.video_url }}">
<span class="icon">
<i class="fas fa-lg fa-film"></i>
</span>
<span>Video</span>
</a>
</span>
{% endif %}
{% if page.location_name and page.location_url %}
<span class="icon-text">
<a href="{{ page.location_url }}">
<span class="icon">
<i class="fas fa-lg fa-map-marker-alt"></i>
</span>
<span>{{ page.location_name }}</span>
</a>
</span>
{% endif %}
</div>
{% endwagtailpagecache %}

View File

@ -1,9 +1,9 @@
{% load wagtailcore_tags wagtail_cache util_tags %}
{% wagtailpagecache FRAGMENT_CACHE_TTL "listing-item" breadcrumbs %}
{% wagtailpagecache FRAGMENT_CACHE_TTL "listing-item" breadcrumbs show_listing_images %}
<article class="media listing-item">
<div class="columns">
<figure class="media-left column is-3 image-column">
<figure class="media-left column is-{{ show_listing_images|yesno:'3,1' }} image-column">
{% if page.list_image_url %}
<a href="{% pageurl page %}" class="image" title="{{ page.title }}">
<img src="{{ page.list_image_url }}" alt="{{ page.hero_image_alt }}" loading="lazy" decoding="async" referrerpolicy="no-referrer" />

View File

@ -0,0 +1,9 @@
{% load wagtailcore_tags %}
{% spaceless %}
{{ obj.content_html | truncatewords_html:100 | safe }}
<p>
<a href="{% fullpageurl obj %}">Continue Reading&hellip;</a>
</p>
{% endspaceless %}

View File

@ -3,7 +3,6 @@ from django.test import SimpleTestCase
from wagtail.rich_text import features as richtext_feature_registry
from website.common.utils import (
count_words,
extract_text,
get_table_of_contents,
heading_id,
@ -97,13 +96,6 @@ class ExtractTextTestCase(SimpleTestCase):
self.assertEqual(extract_text("Hello there!"), "Hello there!")
class CountWordsTestCase(SimpleTestCase):
def test_counts_words(self) -> None:
self.assertEqual(count_words("a b c"), 3)
self.assertEqual(count_words("Correct Horse Battery Staple"), 4)
self.assertEqual(count_words("Hello there! How are you?"), 5)
class RichTextFeaturesTestCase(SimpleTestCase):
def test_features_exist(self) -> None:
for editor, editor_config in settings.WAGTAILADMIN_RICH_TEXT_EDITORS.items():

View File

@ -1,13 +1,13 @@
from dataclasses import dataclass
from itertools import islice, pairwise
from typing import Iterable, Optional, Type
from itertools import pairwise
from typing import Optional, Type
import requests
from bs4 import BeautifulSoup, SoupStrainer
from django.conf import settings
from django.db import models
from django.http.request import HttpRequest
from django.utils.text import re_words, slugify
from django.utils.text import slugify
from django_cache_decorator import django_cache_decorator
from wagtail.models import Page, Site
from wagtail.models import get_page_models as get_wagtail_page_models
@ -69,19 +69,6 @@ def show_toolbar_callback(request: HttpRequest) -> bool:
return settings.DEBUG
def split_words(text: str) -> Iterable[str]:
for word in re_words.split(text):
if word and word.strip():
yield word.strip()
def count_words(text: str) -> int:
"""
Count the number of words in the text, without duplicating the item in memory
"""
return len(list(split_words(text)))
def extract_text(html: str) -> str:
"""
Get the plain text of some HTML.
@ -91,10 +78,6 @@ def extract_text(html: str) -> str:
)
def truncate_string(text: str, words: int) -> str:
return " ".join(islice(split_words(text), words))
def heading_id(heading: str) -> str:
"""
Convert a heading into an identifier which is valid for a HTML id attribute

View File

@ -15,6 +15,7 @@ from wagtail.query import PageQuerySet
from wagtail_favicon.models import FaviconSettings
from wagtail_favicon.utils import get_rendition_url
from website.blog.models import BlogPostPage
from website.common.utils import get_site_title
from website.contrib.singleton_page.utils import SingletonPageCache
from website.home.models import HomePage
@ -63,6 +64,7 @@ class KeybaseView(TemplateView):
class AllPagesFeed(Feed):
feed_type = CustomFeed
link = "/"
description_template = "feed-description.html"
def __init__(self) -> None:
self.style_tag = f'<?xml-stylesheet href="{static("contrib/pretty-feed-v3.xsl")}" type="text/xsl"?>'.encode()
@ -122,12 +124,9 @@ class AllPagesFeed(Feed):
def item_updateddate(self, item: BasePage) -> datetime:
return item.last_published_at
def item_description(self, item: BasePage) -> str:
return getattr(item, "summary", None) or item.title
def item_categories(self, item: BasePage) -> Optional[list[str]]:
if tags := getattr(item, "tags", None):
return tags.public().live().order_by("slug").values_list("slug", flat=True)
if isinstance(item, BlogPostPage):
return item.tags_list.values_list("slug", flat=True)
return None
def item_enclosure_url(self, item: BasePage) -> Optional[str]:

View File

@ -2,7 +2,7 @@
<figure>
<div class="image">
<a href="https://mermaid.ink/svg/{{ value.pako }}" data-gallery="content" class="glightbox" data-type="image" data-height="60vh" data-width="95vw" data-alt="{{ value.caption|richtext|extract_text }}">
<a href="https://mermaid.ink/svg/{{ value.pako }}" data-gallery="content" class="glightbox" data-type="image" data-height="70vh" data-alt="{{ value.caption|richtext|extract_text }}" data-title="{{ value.caption|richtext|extract_text }}">
<img src="https://mermaid.ink/svg/{{ value.pako }}" referrerpolicy="no-referrer" alt="{{ value.caption|richtext|extract_text }}" loading="lazy" decoding="async" />
</a>
</div>

View File

@ -1,8 +1,5 @@
from typing import Optional, Tuple
from django.db import models
from django.http.request import HttpRequest
from django_cache_decorator import django_cache_decorator
from wagtail.admin.panels import FieldPanel
from wagtail.images import get_image_model_string
from wagtail.images.models import Image
@ -12,20 +9,6 @@ from website.common.models import BasePage
from website.contrib.singleton_page.utils import SingletonPageCache
@django_cache_decorator(time=600)
def get_latest_blog_post() -> Optional[Tuple[str, str]]:
from website.blog.models import BlogPostPage
try:
latest_blog_post = (
BlogPostPage.objects.live().public().defer_streamfields().latest("date")
)
except BlogPostPage.DoesNotExist:
return None
return latest_blog_post.title, latest_blog_post.get_url()
class HomePage(BasePage, WagtailImageMetadataMixin):
max_count = 1
@ -55,9 +38,22 @@ class HomePage(BasePage, WagtailImageMetadataMixin):
return self.html_title
def get_context(self, request: HttpRequest) -> dict:
from website.blog.models import BlogPostListPage, BlogPostPage
from website.search.models import SearchPage
context = super().get_context(request)
context["latest_blog_post"] = get_latest_blog_post()
context["recent_posts"] = list(
BlogPostPage.objects.live()
.public()
.defer_streamfields()
.order_by("-date")[:7]
)
context["latest_blog_post"] = (
context["recent_posts"].pop(0) if context["recent_posts"] else None
)
context["search_page_url"] = SingletonPageCache.get_url(SearchPage, request)
context["blog_post_list_url"] = SingletonPageCache.get_url(
BlogPostListPage, request
)
return context

View File

@ -4,21 +4,38 @@
{% block main %}
<main {% if page.image %}style="background-image: url({% image_url page.image 'width-1200' %})"{% endif %}>
<div class="heading-wrapper">
<h1>{{ page.heading }}</h1>
{% if search_page_url %}
<form action="{{ search_page_url }}">
<input id="search-input" class="input" type="text" placeholder="Search" name="q" />
</form>
<div class="top-section">
<div class="heading-wrapper">
<h1>{{ page.heading }}</h1>
{% if search_page_url %}
<form action="{{ search_page_url }}">
<input id="search-input" class="input" type="text" placeholder="Search" name="q" />
</form>
{% endif %}
</div>
{% if latest_blog_post %}
<div class="box latest is-size-5">
<strong>Latest Post</strong>:
<a href="{% pageurl latest_blog_post %}">{{ latest_blog_post.title }}</a>
&rarr;
</div>
{% endif %}
</div>
{% if latest_blog_post %}
<div class="box latest">
<strong>Latest Post</strong>:
<a href="{{ latest_blog_post.1 }}">{{ latest_blog_post.0 }}</a>
&rarr;
<section class="container content recent-posts">
<h2 class="has-text-centered has-text-white is-size-3">Recent Posts</h2>
<div class="columns content-list is-multiline">
{% for page in recent_posts %}
{% include "home/home_page_card.html" %}
{% endfor %}
</div>
{% endif %}
{% if blog_post_list_url %}
<div class="box">
<a href="{{ blog_post_list_url }}">View more &rarr;</a>
</div>
{% endif %}
</section>
</main>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% load wagtail_cache wagtailcore_tags %}
{% wagtailpagecache FRAGMENT_CACHE_TTL "homepage-card" %}
<div class="column is-one-third-widescreen is-half">
<div class="card">
<div class="card-image">
<a href="{% pageurl page %}">
<figure class="image is-16by9">
<img src="{{ page.list_image_url }}" alt="{{ page.hero_image_alt }}" loading="lazy" decoding="async" referrerpolicy="no-referrer" />
<p>{{ page.title }}</p>
</figure>
</a>
</div>
</div>
</div>
{% endwagtailpagecache %}

View File

@ -10,4 +10,5 @@ urlpatterns = [
path("tags/<slug:slug>/", views.TagView.as_view()),
path("tags/", views.TagsView.as_view()),
path("categories/", views.TagsView.as_view()),
path("index.json", views.PageLinksView.as_view()),
]

View File

@ -12,6 +12,12 @@ class AllPagesFeedView(RedirectView):
permanent = True
@method_decorator(cache_control(max_age=60 * 60), name="dispatch")
class PageLinksView(RedirectView):
pattern_name = "api:page-links"
permanent = True
@method_decorator(cache_control(max_age=60 * 60), name="dispatch")
class TagView(RedirectView):
permanent = True

View File

@ -42,6 +42,7 @@ INSTALLED_APPS = [
"website.utils",
"website.well_known",
"website.legacy",
"website.talks",
"website.contrib.code_block",
"website.contrib.mermaid_block",
"website.contrib.unsplash",
@ -374,6 +375,11 @@ LOGGING = {
"level": "WARNING",
"propagate": False,
},
"wagtail.images": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": False,
},
"django.request": {
"handlers": ["console"],
"level": "ERROR",

View File

View File

@ -0,0 +1,17 @@
from datetime import timedelta
from website.common.factories import BaseContentFactory, BaseListingFactory
from . import models
class TalksListPageFactory(BaseListingFactory):
class Meta:
model = models.TalksListPage
class TalkPageFactory(BaseContentFactory):
duration = timedelta(minutes=30)
class Meta:
model = models.TalkPage

View File

@ -0,0 +1,354 @@
# Generated by Django 5.0.1 on 2024-03-01 17:44
import django.db.models.deletion
import django.utils.timezone
import wagtail.blocks
import wagtail.contrib.routable_page.models
import wagtail.contrib.typed_table_block.blocks
import wagtail.embeds.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtailmetadata.models
from django.db import migrations, models
import website.contrib.code_block.blocks
class Migration(migrations.Migration):
initial = True
dependencies = [
("images", "0002_alter_customimage_file_alter_customrendition_file"),
("unsplash", "0001_initial"),
("wagtailcore", "0089_log_entry_data_json_null_to_object"),
]
operations = [
migrations.CreateModel(
name="TalkPage",
fields=[
(
"page_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="wagtailcore.page",
),
),
("subtitle", wagtail.fields.RichTextField(blank=True)),
(
"body",
wagtail.fields.StreamField(
[
("embed", wagtail.embeds.blocks.EmbedBlock()),
("rich_text", wagtail.blocks.RichTextBlock()),
(
"lorem",
wagtail.blocks.StructBlock(
[
(
"paragraphs",
wagtail.blocks.IntegerBlock(min_value=1),
)
]
),
),
("html", wagtail.blocks.RawHTMLBlock()),
(
"image",
wagtail.blocks.StructBlock(
[
(
"image",
wagtail.images.blocks.ImageChooserBlock(),
),
(
"caption",
wagtail.blocks.RichTextBlock(
editor="plain", required=False
),
),
]
),
),
(
"code",
wagtail.blocks.StructBlock(
[
(
"filename",
wagtail.blocks.CharBlock(
max_length=128, required=False
),
),
(
"language",
wagtail.blocks.ChoiceBlock(
choices=website.contrib.code_block.blocks.get_language_choices
),
),
("source", wagtail.blocks.TextBlock()),
]
),
),
(
"tangent",
wagtail.blocks.StructBlock(
[
(
"name",
wagtail.blocks.CharBlock(max_length=64),
),
(
"content",
wagtail.blocks.RichTextBlock(
editor="simple"
),
),
]
),
),
(
"mermaid",
wagtail.blocks.StructBlock(
[
("source", wagtail.blocks.TextBlock()),
(
"caption",
wagtail.blocks.RichTextBlock(
editor="plain", required=False
),
),
]
),
),
(
"table",
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
[
(
"rich_text",
wagtail.blocks.RichTextBlock(
editor="plain"
),
),
("numeric", wagtail.blocks.FloatBlock()),
("text", wagtail.blocks.CharBlock()),
]
),
),
(
"iframe",
wagtail.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock()),
(
"caption",
wagtail.blocks.RichTextBlock(
editor="plain", required=False
),
),
]
),
),
],
blank=True,
use_json_field=True,
),
),
("date", models.DateField(default=django.utils.timezone.now)),
("duration", models.DurationField()),
("slides_url", models.URLField(blank=True)),
("video_url", models.URLField(blank=True)),
("location_name", models.CharField(blank=True, max_length=64)),
("location_url", models.URLField(blank=True)),
(
"hero_image",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="images.customimage",
),
),
(
"hero_unsplash_photo",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="unsplash.unsplashphoto",
),
),
],
options={
"abstract": False,
},
bases=("wagtailcore.page", wagtailmetadata.models.MetadataMixin),
),
migrations.CreateModel(
name="TalksListPage",
fields=[
(
"page_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="wagtailcore.page",
),
),
(
"body",
wagtail.fields.StreamField(
[
("embed", wagtail.embeds.blocks.EmbedBlock()),
("rich_text", wagtail.blocks.RichTextBlock()),
(
"lorem",
wagtail.blocks.StructBlock(
[
(
"paragraphs",
wagtail.blocks.IntegerBlock(min_value=1),
)
]
),
),
("html", wagtail.blocks.RawHTMLBlock()),
(
"image",
wagtail.blocks.StructBlock(
[
(
"image",
wagtail.images.blocks.ImageChooserBlock(),
),
(
"caption",
wagtail.blocks.RichTextBlock(
editor="plain", required=False
),
),
]
),
),
(
"code",
wagtail.blocks.StructBlock(
[
(
"filename",
wagtail.blocks.CharBlock(
max_length=128, required=False
),
),
(
"language",
wagtail.blocks.ChoiceBlock(
choices=website.contrib.code_block.blocks.get_language_choices
),
),
("source", wagtail.blocks.TextBlock()),
]
),
),
(
"tangent",
wagtail.blocks.StructBlock(
[
(
"name",
wagtail.blocks.CharBlock(max_length=64),
),
(
"content",
wagtail.blocks.RichTextBlock(
editor="simple"
),
),
]
),
),
(
"mermaid",
wagtail.blocks.StructBlock(
[
("source", wagtail.blocks.TextBlock()),
(
"caption",
wagtail.blocks.RichTextBlock(
editor="plain", required=False
),
),
]
),
),
(
"table",
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
[
(
"rich_text",
wagtail.blocks.RichTextBlock(
editor="plain"
),
),
("numeric", wagtail.blocks.FloatBlock()),
("text", wagtail.blocks.CharBlock()),
]
),
),
(
"iframe",
wagtail.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock()),
(
"caption",
wagtail.blocks.RichTextBlock(
editor="plain", required=False
),
),
]
),
),
],
blank=True,
use_json_field=True,
),
),
(
"hero_image",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="images.customimage",
),
),
(
"hero_unsplash_photo",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="unsplash.unsplashphoto",
),
),
],
options={
"abstract": False,
},
bases=(
wagtail.contrib.routable_page.models.RoutablePageMixin,
"wagtailcore.page",
wagtailmetadata.models.MetadataMixin,
),
),
]

View File

62
website/talks/models.py Normal file
View File

@ -0,0 +1,62 @@
from datetime import timedelta
from typing import Any
from django.db import models
from django.utils import timezone
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
from website.common.models import BaseContentPage, BaseListingPage
class TalksListPage(BaseListingPage):
max_count = 1
subpage_types = ["talks.TalkPage"]
class TalkPage(BaseContentPage):
subpage_types: list[Any] = []
parent_page_types = [TalksListPage]
date = models.DateField(default=timezone.now)
duration = models.DurationField()
slides_url = models.URLField(blank=True)
video_url = models.URLField(blank=True)
location_name = models.CharField(max_length=64, blank=True)
location_url = models.URLField(blank=True)
content_panels = BaseContentPage.content_panels + [
MultiFieldPanel(
[
FieldPanel("slides_url"),
FieldPanel("video_url"),
],
heading="Media",
),
MultiFieldPanel(
[
FieldPanel("location_name"),
FieldPanel("location_url"),
],
heading="Location",
),
FieldPanel("duration"),
]
promote_panels = BaseContentPage.promote_panels + [
FieldPanel("date"),
]
@property
def show_table_of_contents(self) -> bool:
return False
@property
def reading_time(self) -> timedelta:
return self.duration
@property
def word_count(self) -> int:
return 0

View File

@ -0,0 +1,11 @@
{% extends "common/content_page.html" %}
{% load wagtailembeds_tags %}
{% block pre_content %}
{% if page.video_url %}
<section class="container mb-5 content">
<div class="block-embed">{% embed page.video_url %}</div>
</section>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends "common/listing_page.html" %}
{% load wagtailroutablepage_tags %}
{% block post_content %}
<section class="container listing">
{% for page in listing_pages %}
{% ifchanged %}
<h2 id="date-{{ page.date.year }}" class="date-header">
<time datetime="{{ page.date.year }}" title="{{ page.date.year }}">
{{ page.date.year }}
</time>
</h2>
{% endifchanged %}
{% include "common/listing-item.html" %}
{% endfor %}
</section>
{% if listing_pages.has_other_pages %}
<section class="container">
<hr class="my-5" />
{% include "common/pagination.html" with page=listing_pages %}
</section>
{% endif %}
{% endblock %}

47
website/talks/tests.py Normal file
View File

@ -0,0 +1,47 @@
from django.test import TestCase
from django.urls import reverse
from website.home.models import HomePage
from .factories import TalkPageFactory, TalksListPageFactory
class TalkPageTestCase(TestCase):
@classmethod
def setUpTestData(cls) -> None:
cls.home_page = HomePage.objects.get()
cls.list_page = TalksListPageFactory(parent=cls.home_page)
cls.page = TalkPageFactory(parent=cls.list_page)
def test_accessible(self) -> None:
response = self.client.get(self.page.url)
self.assertEqual(response.status_code, 200)
def test_queries(self) -> None:
with self.assertNumQueries(34):
self.client.get(self.page.url)
class TalksListPageTestCase(TestCase):
@classmethod
def setUpTestData(cls) -> None:
cls.home_page = HomePage.objects.get()
cls.page = TalksListPageFactory(parent=cls.home_page)
TalkPageFactory(parent=cls.page)
TalkPageFactory(parent=cls.page)
def test_accessible(self) -> None:
response = self.client.get(self.page.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["listing_pages"]), 2)
def test_queries(self) -> None:
with self.assertNumQueries(35):
self.client.get(self.page.url)
def test_feed_accessible(self) -> None:
response = self.client.get(self.page.url + self.page.reverse_subpage("feed"))
self.assertRedirects(
response, reverse("feed"), status_code=301, fetch_redirect_response=True
)

View File

@ -1,4 +1,5 @@
from datetime import timedelta
from urllib.parse import urljoin
from django.conf import settings
from django.http.request import HttpRequest
@ -56,10 +57,13 @@ def activitypub_proxy(request: HttpRequest) -> HttpResponse:
if not settings.ACTIVITYPUB_HOST:
raise Http404
activitypub_url = urljoin(
"https://" + settings.ACTIVITYPUB_HOST,
request.path,
allow_fragments=True,
)
try:
return proxy_view(
request,
f"https://{settings.ACTIVITYPUB_HOST}{request.path}",
)
return proxy_view(request, activitypub_url)
except RequestException:
return HttpResponse(status_code=502)
return HttpResponse(status=502)